taketomarket 0.1.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 (123) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +419 -0
  4. package/agents/ttm-producer.md +53 -0
  5. package/bin/lib/campaign.cjs +553 -0
  6. package/bin/lib/commit.cjs +105 -0
  7. package/bin/lib/core.cjs +172 -0
  8. package/bin/lib/deviation.cjs +149 -0
  9. package/bin/lib/drift-log.cjs +219 -0
  10. package/bin/lib/health.cjs +438 -0
  11. package/bin/lib/slug.cjs +59 -0
  12. package/bin/lib/state.cjs +96 -0
  13. package/bin/ttm-tools.cjs +157 -0
  14. package/gates/base-gates.md +266 -0
  15. package/gates/discipline/.gitkeep +0 -0
  16. package/gates/gate-evaluation.md +341 -0
  17. package/gates/meta-gates.md +19 -0
  18. package/install.js +307 -0
  19. package/package.json +53 -0
  20. package/playbooks/.gitkeep +0 -0
  21. package/playbooks/aeo.md +223 -0
  22. package/playbooks/affiliate.md +272 -0
  23. package/playbooks/base.md +110 -0
  24. package/playbooks/email.md +306 -0
  25. package/playbooks/events.md +320 -0
  26. package/playbooks/linkedin.md +263 -0
  27. package/playbooks/paid-ads.md +318 -0
  28. package/playbooks/pr-media.md +296 -0
  29. package/playbooks/seo.md +284 -0
  30. package/playbooks/social.md +305 -0
  31. package/playbooks/youtube.md +325 -0
  32. package/references/context-loading.md +107 -0
  33. package/references/learnings-extraction.md +94 -0
  34. package/references/measurement-template.md +48 -0
  35. package/references/meta-gate-evaluation.md +169 -0
  36. package/references/positioning-check-report.md +197 -0
  37. package/references/review-checklist.md +78 -0
  38. package/references/ship-checklist-items.md +94 -0
  39. package/settings.json +4 -0
  40. package/skills/ttm-aeo-check/SKILL.md +20 -0
  41. package/skills/ttm-affiliate-kit/SKILL.md +19 -0
  42. package/skills/ttm-archive/SKILL.md +13 -0
  43. package/skills/ttm-brand-refresh/SKILL.md +19 -0
  44. package/skills/ttm-brief/SKILL.md +14 -0
  45. package/skills/ttm-competitor-scan/SKILL.md +19 -0
  46. package/skills/ttm-email-preflight/SKILL.md +19 -0
  47. package/skills/ttm-fix/SKILL.md +13 -0
  48. package/skills/ttm-health/SKILL.md +12 -0
  49. package/skills/ttm-icp-refresh/SKILL.md +19 -0
  50. package/skills/ttm-init/SKILL.md +12 -0
  51. package/skills/ttm-keyword-map/SKILL.md +19 -0
  52. package/skills/ttm-learn/SKILL.md +14 -0
  53. package/skills/ttm-measure/SKILL.md +14 -0
  54. package/skills/ttm-new-campaign/SKILL.md +13 -0
  55. package/skills/ttm-next/SKILL.md +12 -0
  56. package/skills/ttm-positioning-check/SKILL.md +19 -0
  57. package/skills/ttm-positioning-shift/SKILL.md +19 -0
  58. package/skills/ttm-produce/SKILL.md +14 -0
  59. package/skills/ttm-repurpose/SKILL.md +20 -0
  60. package/skills/ttm-research/SKILL.md +13 -0
  61. package/skills/ttm-resume/SKILL.md +13 -0
  62. package/skills/ttm-review/SKILL.md +13 -0
  63. package/skills/ttm-seo-audit/SKILL.md +20 -0
  64. package/skills/ttm-ship/SKILL.md +13 -0
  65. package/skills/ttm-state/SKILL.md +13 -0
  66. package/skills/ttm-verify/SKILL.md +14 -0
  67. package/templates/agents-md.md +65 -0
  68. package/templates/campaign-brief.md +74 -0
  69. package/templates/campaign-research.md +39 -0
  70. package/templates/campaign-state.md +40 -0
  71. package/templates/claude-md.md +65 -0
  72. package/templates/deviation-log.md +12 -0
  73. package/templates/drift-log.md +17 -0
  74. package/templates/fix-brief.md +59 -0
  75. package/templates/fix-log.md +22 -0
  76. package/templates/measurement-report.md +75 -0
  77. package/templates/migration-plan.md +24 -0
  78. package/templates/production-manifest.json +20 -0
  79. package/templates/reference-files/brand.md +45 -0
  80. package/templates/reference-files/calendar.md +30 -0
  81. package/templates/reference-files/channels.md +40 -0
  82. package/templates/reference-files/competitors.md +40 -0
  83. package/templates/reference-files/icp.md +50 -0
  84. package/templates/reference-files/learnings.md +40 -0
  85. package/templates/reference-files/metrics.md +42 -0
  86. package/templates/reference-files/positioning.md +38 -0
  87. package/templates/reference-files/state.md +27 -0
  88. package/templates/verification-report.md +59 -0
  89. package/workflows/discipline/.gitkeep +0 -0
  90. package/workflows/discipline/aeo-check.md +180 -0
  91. package/workflows/discipline/affiliate-kit.md +147 -0
  92. package/workflows/discipline/email-preflight.md +150 -0
  93. package/workflows/discipline/keyword-map.md +125 -0
  94. package/workflows/discipline/repurpose.md +329 -0
  95. package/workflows/discipline/seo-audit.md +169 -0
  96. package/workflows/lifecycle/.gitkeep +0 -0
  97. package/workflows/lifecycle/brief-positioning-check.md +90 -0
  98. package/workflows/lifecycle/brief.md +355 -0
  99. package/workflows/lifecycle/fix.md +495 -0
  100. package/workflows/lifecycle/learn.md +405 -0
  101. package/workflows/lifecycle/measure.md +379 -0
  102. package/workflows/lifecycle/produce.md +383 -0
  103. package/workflows/lifecycle/research.md +264 -0
  104. package/workflows/lifecycle/review.md +432 -0
  105. package/workflows/lifecycle/ship.md +521 -0
  106. package/workflows/lifecycle/verify.md +507 -0
  107. package/workflows/reference-mgmt/.gitkeep +0 -0
  108. package/workflows/reference-mgmt/brand-refresh.md +193 -0
  109. package/workflows/reference-mgmt/competitor-scan.md +228 -0
  110. package/workflows/reference-mgmt/icp-refresh.md +200 -0
  111. package/workflows/reference-mgmt/positioning-check.md +339 -0
  112. package/workflows/reference-mgmt/positioning-shift.md +368 -0
  113. package/workflows/setup/.gitkeep +0 -0
  114. package/workflows/setup/init-questions.md +225 -0
  115. package/workflows/setup/init-validation.md +155 -0
  116. package/workflows/setup/init.md +449 -0
  117. package/workflows/setup/new-campaign.md +134 -0
  118. package/workflows/utility/.gitkeep +0 -0
  119. package/workflows/utility/archive.md +334 -0
  120. package/workflows/utility/health.md +166 -0
  121. package/workflows/utility/next.md +187 -0
  122. package/workflows/utility/resume.md +249 -0
  123. package/workflows/utility/state.md +207 -0
@@ -0,0 +1,341 @@
1
+ # Gate Evaluation Reference
2
+
3
+ ## Usage
4
+
5
+ Referenced by `workflows/lifecycle/verify.md` via @-syntax.
6
+ Applied to each asset during verification. Each gate is evaluated independently
7
+ with its specific reference data loaded.
8
+
9
+ **Key rules:**
10
+ - Evaluate gates one at a time, not all bundled in a single prompt (prevents shallow evaluation)
11
+ - Load the gate's reference file(s) for each evaluation
12
+ - Use the structured output format below for every finding
13
+ - Soft fail on all gates -- report results, never block (per D-04)
14
+
15
+ ---
16
+
17
+ ## Structured Output Format
18
+
19
+ Every gate evaluation MUST produce output in this exact format:
20
+
21
+ ### Gate Result
22
+
23
+ - **Gate:** [Gate Name] (GATE-XX)
24
+ - **Tier:** [1|2]
25
+ - **Result:** [PASS|WARN|FAIL]
26
+ - **Summary:** [One-sentence summary of the finding]
27
+
28
+ ### Findings
29
+
30
+ For each check within the gate:
31
+
32
+ **Check: [check name]**
33
+ - **Result:** PASS | WARN | FAIL
34
+ - **Evidence:** "[exact quote from the asset that triggered this finding]" (line/section reference)
35
+ - **Reference:** "[quote from the reference file being compared against]"
36
+ - **Recommendation:** [what to change if WARN or FAIL; "None" if PASS]
37
+
38
+ If a check is N/A (e.g., UTM hygiene for an asset without links), record:
39
+ - **Result:** N/A
40
+ - **Evidence:** "Not applicable -- [reason]"
41
+ - **Reference:** N/A
42
+ - **Recommendation:** None
43
+
44
+ ### Aggregation Rule
45
+
46
+ - If ALL checks PASS: gate result = PASS
47
+ - If ANY check is WARN and none FAIL: gate result = WARN
48
+ - If ANY check is FAIL: gate result = FAIL
49
+ - N/A checks are excluded from aggregation
50
+
51
+ ---
52
+
53
+ ## Per-Gate Evaluation Instructions
54
+
55
+ ### Evaluating GATE-01: Positioning Drift
56
+
57
+ **Load:** `.marketing/POSITIONING.md` (Tier 2 full)
58
+ **Asset content:** Full asset text
59
+
60
+ **Evaluate:**
61
+ 1. Does the asset restate or naturally extend the primary differentiator from POSITIONING.md? Or does it introduce a different claim?
62
+ 2. Are all factual claims in the asset backed by proof points in the POSITIONING.md proof point library?
63
+ 3. Does the asset contain any terms from the POSITIONING.md must-not-say list?
64
+
65
+ **Tier:** 1
66
+ **On failure:** Prompt user for Correct / Accept+log / Escalate
67
+
68
+ ---
69
+
70
+ ### Evaluating GATE-02: Claim Accuracy
71
+
72
+ **Load:** `.marketing/BRAND.md` (Tier 2 full -- proof points section)
73
+ **Asset content:** Full asset text
74
+
75
+ **Evaluate:**
76
+ 1. For every factual or numeric claim in the asset, find the matching proof point in BRAND.md. If none exists, record FAIL.
77
+ 2. Where proof points are used, is the source cited or at least implied? Record WARN if implied only, FAIL if no attribution.
78
+ 3. Check each proof point used against BRAND.md for deprecation or expiration markers.
79
+
80
+ **Tier:** 1
81
+ **On failure:** Prompt user for Correct / Accept+log / Escalate
82
+
83
+ ---
84
+
85
+ ### Evaluating GATE-03: Voice Drift
86
+
87
+ **Load:** `.marketing/BRAND.md` (Tier 2 full -- voice archetype and banned words sections)
88
+ **Asset content:** Full asset text
89
+
90
+ **Evaluate:**
91
+ 1. Read the voice archetype description in BRAND.md. Does the asset's overall tone match? Flag sections that diverge.
92
+ 2. Scan the asset for every word in the BRAND.md banned words list. Any match is a FAIL.
93
+ 3. Read the asset start to end. Does the language register stay consistent, or does it shift between formal and casual?
94
+
95
+ **Tier:** 2
96
+ **On failure:** Report as advisory
97
+
98
+ ---
99
+
100
+ ### Evaluating GATE-04: Outcome Alignment
101
+
102
+ **Load:** `.marketing/CAMPAIGNS/<slug>/BRIEF.md` (outcome metric section)
103
+ **Asset content:** Full asset text
104
+
105
+ **Evaluate:**
106
+ 1. What is the outcome metric in the brief? Does this asset drive that metric, or does it only produce output (impressions, clicks) without a path to the outcome?
107
+ 2. Does the CTA or desired reader action serve the outcome metric directly, or does it target a tangential goal?
108
+
109
+ **Tier:** 1
110
+ **On failure:** Prompt user for Correct / Accept+log / Escalate
111
+
112
+ ---
113
+
114
+ ### Evaluating GATE-05: Funnel Integrity
115
+
116
+ **Load:** `.marketing/CAMPAIGNS/<slug>/BRIEF.md` (funnel/CTA section)
117
+ **Asset content:** Full asset text
118
+
119
+ **Evaluate:**
120
+ 1. Is a CTA present? Is it specific (e.g., "Start your free trial") rather than generic ("Learn more")? If the asset type does not require a CTA, mark N/A.
121
+ 2. Is the CTA destination defined (URL, landing page, next step)? Is it a dead end?
122
+ 3. Trace the path from CTA to outcome. Is it logical and unbroken, or are there gaps or unnecessary friction?
123
+
124
+ **Tier:** 2
125
+ **On failure:** Report as advisory
126
+
127
+ ---
128
+
129
+ ### Evaluating GATE-06: UTM Hygiene
130
+
131
+ **Load:** `.marketing/CHANNELS.md` (UTM schema section)
132
+ **Asset content:** Full asset text
133
+
134
+ **Evaluate:**
135
+ 1. Find all trackable links in the asset. Do they have UTM parameters (source, medium, campaign)? If no links exist, mark all checks N/A.
136
+ 2. Do UTM source and medium values match the naming conventions in CHANNELS.md?
137
+ 3. Does the UTM campaign tag match the campaign slug?
138
+
139
+ **Tier:** 2
140
+ **On failure:** Report as advisory
141
+
142
+ ---
143
+
144
+ ### Evaluating GATE-07: Compliance
145
+
146
+ **Load:** No specific reference file -- apply industry-standard requirements
147
+ **Asset content:** Full asset text + channel context
148
+
149
+ **Evaluate:**
150
+ 1. Based on the asset content, are disclaimers required (financial advice, health claims, affiliate disclosure, sponsorship)? If required, are they present? If not required, mark N/A.
151
+ 2. Scan the asset for PII patterns (email addresses, phone numbers, personal names of non-public figures).
152
+ 3. For email, SMS, or push notification assets: is an unsubscribe/opt-out mechanism referenced? For other channels, mark N/A.
153
+
154
+ **Tier:** 2
155
+ **On failure:** Report as advisory
156
+
157
+ ---
158
+
159
+ ### Evaluating GATE-08: Competitor Collision
160
+
161
+ **Load:** `.marketing/COMPETITORS.md` (Tier 2 full)
162
+ **Asset content:** Full asset text
163
+
164
+ **Evaluate:**
165
+ 1. Does the asset mention any competitor brand names from COMPETITORS.md? If so, are the mentions accompanied by substantiated comparative claims?
166
+ 2. Compare the asset's framing and key phrases against competitor positioning and taglines listed in COMPETITORS.md. Flag overlaps.
167
+ 3. Does the asset differentiate from competitors, or does it inadvertently validate a competitor's approach or market framing?
168
+
169
+ **Tier:** 2
170
+ **On failure:** Report as advisory
171
+
172
+ ---
173
+
174
+ ### Evaluating GATE-09: ICP Fit
175
+
176
+ **Load:** `.marketing/ICP.md` (Tier 2 full)
177
+ **Asset content:** Full asset text
178
+
179
+ **Evaluate:**
180
+ 1. Does the hook or core message address a specific pain point from ICP.md pains list?
181
+ 2. Does the asset use vocabulary from ICP.md's language library, or does it use internal jargon the ICP would not recognize?
182
+ 3. Is the content targeted at the ICP segment, or could it primarily attract the anti-ICP defined in ICP.md?
183
+
184
+ **Tier:** 2
185
+ **On failure:** Report as advisory
186
+
187
+ ---
188
+
189
+ ### Evaluating GATE-10: Format Correctness
190
+
191
+ **Load:** Channel-specific playbook if available (`${CLAUDE_PLUGIN_ROOT}/playbooks/<type>.md`), otherwise general platform guidelines
192
+ **Asset content:** Full asset text + asset type metadata from MANIFEST.json
193
+
194
+ **Evaluate:**
195
+ 1. Check asset length against platform limits (tweet < 280 chars, LinkedIn < 3000, email subject < 60). If no specific limit applies, check general word count reasonableness.
196
+ 2. Check for required structural elements: subject line (email), H1/title (blog), hook (social), preview text (email). Flag missing elements.
197
+ 3. Does the asset structure follow channel conventions (e.g., email has preview text and CTA above fold, blog has meta description, social post uses hashtags where expected)?
198
+
199
+ **Tier:** 2
200
+ **On failure:** Report as advisory
201
+
202
+ ---
203
+
204
+ ### Evaluating Discipline Gates (DISC-*)
205
+
206
+ **Load:** Playbook file for this asset type (`${CLAUDE_PLUGIN_ROOT}/playbooks/<type>.md`)
207
+ **Asset content:** Full asset text
208
+
209
+ **Evaluate:**
210
+ For each `### DISC-*` subsection in the playbook's `## Discipline Gates` section:
211
+
212
+ 1. Read the gate's **Checks** and **Against** fields to identify what is being evaluated and the reference data
213
+ 2. Evaluate each numbered criterion under **Evaluation Criteria** against the asset content
214
+ 3. Apply the same PASS/WARN/FAIL assessment and structured output format as base gates (see Structured Output Format above)
215
+ 4. Use the tier specified in the discipline gate definition (from the `-- Tier {1|2}` in the heading)
216
+
217
+ **On failure:** Apply deviation handling based on the gate's tier:
218
+ - Tier 1 discipline gates: Prompt user for Correct / Accept+log / Escalate (same as base Tier 1)
219
+ - Tier 2 discipline gates: Report as advisory (same as base Tier 2)
220
+
221
+ **Aggregation:** Same rule as base gates:
222
+ - If ALL checks PASS: gate result = PASS
223
+ - If ANY check is WARN and none FAIL: gate result = WARN
224
+ - If ANY check is FAIL: gate result = FAIL
225
+ - N/A checks are excluded from aggregation
226
+
227
+ **Key rules:**
228
+ - Evaluate each discipline gate separately (same as base gates -- no bundling)
229
+ - Discipline gates must not duplicate base gate checks
230
+ - If the asset has no playbook, skip discipline gate evaluation entirely
231
+
232
+ ---
233
+
234
+ ## Deviation Handling (GATE-12)
235
+
236
+ When a gate results in WARN or FAIL:
237
+
238
+ ### Tier 1 Failures (GATE-01, GATE-02, GATE-04, base gates overridden to Tier 1, and Tier 1 DISC-* gates)
239
+
240
+ Present the finding and prompt for action:
241
+
242
+ ```
243
+ Gate [name] returned [WARN|FAIL]:
244
+ [finding summary with evidence]
245
+
246
+ Choose an action:
247
+ 1. Correct -- Record this for /ttm-fix to address
248
+ 2. Accept+log -- Document exception and proceed
249
+ 3. Escalate -- Launch /ttm-positioning-shift to update positioning
250
+
251
+ Type 1, 2, or 3:
252
+ ```
253
+
254
+ **Text-mode fallback:** When `TEXT_MODE=true`, present the same numbered list as plain text
255
+ and read the user's response from the next message.
256
+
257
+ ### Tier 2 Findings (GATE-03, GATE-05 through GATE-10 unless overridden, and Tier 2 DISC-* gates)
258
+
259
+ Report findings without requiring action:
260
+
261
+ ```
262
+ [Advisory] Gate [name]: [finding summary]
263
+ Recommendation: [recommendation from finding]
264
+ ```
265
+
266
+ User may optionally request action on any Tier 2 finding.
267
+
268
+ ### Accept+log Record Format (D-07)
269
+
270
+ When user selects Accept+log, collect justification via AskUserQuestion:
271
+
272
+ ```
273
+ Why are you accepting this deviation? (This will be logged to DEVIATIONS.md)
274
+ ```
275
+
276
+ Record the deviation using the CLI:
277
+
278
+ ```bash
279
+ node "${CLAUDE_PLUGIN_ROOT}/bin/ttm-tools.cjs" deviation append \
280
+ --slug "${SLUG}" \
281
+ --gate "[gate name]" \
282
+ --gate-id "[GATE-XX]" \
283
+ --tier [1|2] \
284
+ --result "[WARN|FAIL]" \
285
+ --asset "[asset file path]" \
286
+ --finding "[exact finding text]" \
287
+ --action "Accept+log" \
288
+ --justification "[user's response]" \
289
+ --run [current run number]
290
+ ```
291
+
292
+ This writes a consistent entry to DEVIATIONS.md (append-only) and updates STATE.md gate field.
293
+
294
+ ### Escalate Behavior (D-08)
295
+
296
+ When user selects Escalate:
297
+ - Pause verification
298
+ - Display: "Launching /ttm-positioning-shift. Verification paused."
299
+ - After positioning resolution: "Positioning updated. Re-run /ttm-verify to validate with new positioning."
300
+
301
+ ### Correct Behavior (D-09)
302
+
303
+ When user selects Correct:
304
+ - Record the finding as needing fix
305
+ - Update campaign STATE.md via CLI:
306
+
307
+ ```bash
308
+ node "${CLAUDE_PLUGIN_ROOT}/bin/ttm-tools.cjs" campaign update "${SLUG}" gate.[gate_field] "fix_needed"
309
+ ```
310
+
311
+ - Display: "Recorded for /ttm-fix. Continue verifying remaining gates."
312
+ - Verification continues -- does not pause for Correct. Fix happens after verify completes.
313
+
314
+ ---
315
+
316
+ ## Summary Table Format (D-05)
317
+
318
+ After all gates are evaluated for all assets, display a summary table:
319
+
320
+ ```
321
+ ## Verification Report: [campaign slug]
322
+
323
+ **Run:** [N] | **Date:** [ISO date] | **Assets:** [count]
324
+
325
+ | # | Gate | Tier | Asset 1 | Asset 2 | ... |
326
+ |---|------|------|---------|---------|-----|
327
+ | 1 | Positioning Drift | T1 | PASS | WARN | ... |
328
+ | 2 | Claim Accuracy | T1 | PASS | PASS | ... |
329
+ ...
330
+ | 10 | Format Correctness | T2 | PASS | PASS | ... |
331
+ | 11 | DISC-{DISC}-01: {Name} | T{n} | PASS | N/A | ... |
332
+ | .. | ... | ... | ... | ... | ... |
333
+
334
+ Discipline gate rows appear after the 10 base gates. For assets without a matching
335
+ playbook, show N/A in that asset's column.
336
+
337
+ **Result:** [count] FAIL, [count] WARN -- [action required | all clear]
338
+ ```
339
+
340
+ Below the summary table, include drill-down detail for every WARN and FAIL finding
341
+ using the structured output format defined above.
@@ -0,0 +1,19 @@
1
+ # Meta Quality Gates
2
+
3
+ **Status:** Meta-gate definitions will be implemented in Phase 9
4
+
5
+ Meta-gates operate at the portfolio level, not on individual assets. They evaluate campaigns and content programs as a whole.
6
+
7
+ ## Meta-Gates
8
+
9
+ 1. **Portfolio Balance** -- META-01
10
+ Evaluates whether the active campaign mix covers all funnel stages (awareness, consideration, conversion, retention) and does not over-index on any single stage.
11
+
12
+ 2. **Calendar Collision** -- META-02
13
+ Detects scheduling conflicts between campaigns: overlapping launch dates, competing for the same audience segment simultaneously, or conflicting messages in market at the same time.
14
+
15
+ 3. **Theme Consistency** -- META-03
16
+ Verifies active campaigns align with the quarterly theme defined in CALENDAR.md. Flags campaigns that drift from the strategic narrative.
17
+
18
+ 4. **Learning Plan** -- META-04
19
+ Ensures every campaign has a measurement plan and a learning hypothesis. Flags campaigns that ship without defining what success looks like or what the team will learn.
package/install.js ADDED
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ // ── Constants ────────────────────────────────────────────────────────────────
10
+
11
+ const PACKAGE_ROOT = __dirname;
12
+ const VERSION = require('./package.json').version;
13
+
14
+ const DIRS_TO_COPY = [
15
+ '.claude-plugin',
16
+ 'skills',
17
+ 'workflows',
18
+ 'templates',
19
+ 'references',
20
+ 'playbooks',
21
+ 'gates',
22
+ 'bin',
23
+ 'agents',
24
+ ];
25
+
26
+ const FILES_TO_COPY = [
27
+ 'settings.json',
28
+ ];
29
+
30
+ // ── Runtime detection ────────────────────────────────────────────────────────
31
+
32
+ /**
33
+ * Detect target runtime from CLI args or environment sniffing.
34
+ * Priority: --runtime flag > .claude/ dir > .codex/ dir > default claude
35
+ * @param {string[]} args - CLI arguments
36
+ * @returns {string} 'claude' or 'codex'
37
+ */
38
+ function detectRuntime(args) {
39
+ // Check --runtime flag
40
+ const runtimeIdx = args.indexOf('--runtime');
41
+ if (runtimeIdx !== -1 && runtimeIdx + 1 < args.length) {
42
+ const value = args[runtimeIdx + 1].toLowerCase();
43
+ if (value === 'claude' || value === 'codex') {
44
+ return value;
45
+ }
46
+ console.warn(`Warning: Unknown runtime "${args[runtimeIdx + 1]}". Defaulting to claude.`);
47
+ return 'claude';
48
+ }
49
+
50
+ // Check for .claude/ directory
51
+ if (dirExists(path.join(os.homedir(), '.claude'))) {
52
+ return 'claude';
53
+ }
54
+
55
+ // Check for .codex/ directory
56
+ if (dirExists(path.join(os.homedir(), '.codex'))) {
57
+ return 'codex';
58
+ }
59
+
60
+ // Default
61
+ console.log('Note: Defaulting to Claude Code. Use --runtime codex if using Codex.');
62
+ return 'claude';
63
+ }
64
+
65
+ // ── File helpers ─────────────────────────────────────────────────────────────
66
+
67
+ /**
68
+ * Check if a path exists and is a directory.
69
+ * @param {string} p - Path to check
70
+ * @returns {boolean}
71
+ */
72
+ function dirExists(p) {
73
+ try {
74
+ return fs.statSync(p).isDirectory();
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Check if a path exists and is a file.
82
+ * @param {string} p - Path to check
83
+ * @returns {boolean}
84
+ */
85
+ function fileExists(p) {
86
+ try {
87
+ return fs.statSync(p).isFile();
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Recursively copy a directory. Skips symlinks with a warning.
95
+ * @param {string} src - Source directory path
96
+ * @param {string} dest - Destination directory path
97
+ */
98
+ function copyDirSync(src, dest) {
99
+ fs.mkdirSync(dest, { recursive: true });
100
+ const entries = fs.readdirSync(src, { withFileTypes: true });
101
+
102
+ for (const entry of entries) {
103
+ const srcPath = path.join(src, entry.name);
104
+ const destPath = path.join(dest, entry.name);
105
+
106
+ if (entry.isSymbolicLink()) {
107
+ console.warn(` Warning: Skipping symlink ${entry.name}`);
108
+ continue;
109
+ }
110
+
111
+ if (entry.isDirectory()) {
112
+ copyDirSync(srcPath, destPath);
113
+ } else if (entry.isFile()) {
114
+ fs.copyFileSync(srcPath, destPath);
115
+ }
116
+ }
117
+ }
118
+
119
+ // ── Validation ───────────────────────────────────────────────────────────────
120
+
121
+ /**
122
+ * Validate an installation directory has all required components.
123
+ * @param {string} targetDir - Directory to validate
124
+ * @returns {Array<{name: string, status: string}>} Validation results
125
+ */
126
+ function validateInstall(targetDir) {
127
+ const results = [];
128
+
129
+ // Check each required directory
130
+ for (const dir of DIRS_TO_COPY) {
131
+ results.push({
132
+ name: dir,
133
+ status: dirExists(path.join(targetDir, dir)) ? 'pass' : 'fail',
134
+ });
135
+ }
136
+
137
+ // Check plugin.json exists
138
+ results.push({
139
+ name: 'plugin.json',
140
+ status: fileExists(path.join(targetDir, '.claude-plugin', 'plugin.json')) ? 'pass' : 'fail',
141
+ });
142
+
143
+ // Check at least 5 SKILL.md files exist under skills/
144
+ const skillsDir = path.join(targetDir, 'skills');
145
+ let skillCount = 0;
146
+ if (dirExists(skillsDir)) {
147
+ try {
148
+ const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true });
149
+ for (const entry of skillDirs) {
150
+ if (entry.isDirectory()) {
151
+ const skillFile = path.join(skillsDir, entry.name, 'SKILL.md');
152
+ if (fileExists(skillFile)) {
153
+ skillCount++;
154
+ }
155
+ }
156
+ }
157
+ } catch {
158
+ // ignore
159
+ }
160
+ }
161
+ results.push({
162
+ name: `skills (${skillCount} SKILL.md files)`,
163
+ status: skillCount >= 5 ? 'pass' : 'fail',
164
+ });
165
+
166
+ return results;
167
+ }
168
+
169
+ // ── Main ─────────────────────────────────────────────────────────────────────
170
+
171
+ function main() {
172
+ const args = process.argv.slice(2);
173
+
174
+ // Check for --version / -v (D-10, D-11) — short-circuit BEFORE detectRuntime/validation.
175
+ if (args.includes('--version') || args.includes('-v')) {
176
+ process.stdout.write(`${VERSION}\n`);
177
+ process.exit(0);
178
+ }
179
+
180
+ // Check for --help
181
+ if (args.includes('--help') || args.includes('-h')) {
182
+ console.log(`
183
+ takeToMarket installer
184
+
185
+ Usage: npx taketomarket [options]
186
+
187
+ Options:
188
+ --runtime <claude|codex> Target runtime (default: auto-detect)
189
+ --dry-run Validate source without writing files
190
+ --help, -h Show this help message
191
+ `);
192
+ process.exit(0);
193
+ }
194
+
195
+ const DRY_RUN = args.includes('--dry-run');
196
+ const runtime = detectRuntime(args);
197
+
198
+ // Compute target directory using path.resolve for safety (T-10-01)
199
+ const runtimeDir = runtime === 'codex' ? '.codex' : '.claude';
200
+ const targetDir = path.resolve(os.homedir(), runtimeDir, 'plugins', 'taketomarket');
201
+
202
+ // Verify targetDir is within home directory (T-10-01, T-10-03)
203
+ const homeDir = os.homedir();
204
+ if (!targetDir.startsWith(homeDir + path.sep)) {
205
+ console.error('Error: Target directory resolves outside home directory. Aborting.');
206
+ process.exit(1);
207
+ }
208
+
209
+ console.log('');
210
+ console.log(`takeToMarket installer v${VERSION}`);
211
+ console.log(`Runtime: ${runtime}`);
212
+ console.log(`Target: ${targetDir}`);
213
+ console.log('');
214
+
215
+ if (DRY_RUN) {
216
+ // Validate source completeness without writing
217
+ console.log('[DRY RUN] Validating source package...');
218
+ console.log('');
219
+ const results = validateInstall(PACKAGE_ROOT);
220
+ printResults(results);
221
+ console.log('');
222
+ console.log('[DRY RUN] No files written.');
223
+ process.exit(0);
224
+ }
225
+
226
+ // Check for existing installation — remove stale files before copying (CR-02)
227
+ if (dirExists(targetDir)) {
228
+ console.log('Existing installation found. Removing before reinstall...');
229
+ fs.rmSync(targetDir, { recursive: true, force: true });
230
+ console.log('');
231
+ }
232
+
233
+ // Copy directories
234
+ for (const dir of DIRS_TO_COPY) {
235
+ const srcDir = path.join(PACKAGE_ROOT, dir);
236
+ if (dirExists(srcDir)) {
237
+ console.log(` Copying ${dir}/`);
238
+ copyDirSync(srcDir, path.join(targetDir, dir));
239
+ } else {
240
+ console.log(` Skipping ${dir}/ (not found in package)`);
241
+ }
242
+ }
243
+
244
+ // Copy individual files
245
+ for (const file of FILES_TO_COPY) {
246
+ const srcFile = path.join(PACKAGE_ROOT, file);
247
+ if (fileExists(srcFile)) {
248
+ console.log(` Copying ${file}`);
249
+ const destFile = path.join(targetDir, file);
250
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
251
+ fs.copyFileSync(srcFile, destFile);
252
+ } else {
253
+ console.log(` Skipping ${file} (not found in package)`);
254
+ }
255
+ }
256
+
257
+ console.log('');
258
+
259
+ // Validate
260
+ const results = validateInstall(targetDir);
261
+ printResults(results);
262
+
263
+ const failures = results.filter(r => r.status === 'fail');
264
+ console.log('');
265
+
266
+ if (failures.length > 0) {
267
+ console.log('Installation incomplete. Some components missing.');
268
+ process.exit(1);
269
+ }
270
+
271
+ console.log('Installation complete!');
272
+ console.log('');
273
+ console.log('Quick start:');
274
+ console.log(' 1. Open a project directory');
275
+ console.log(' 2. Run /ttm-init to set up your marketing workspace');
276
+ console.log(' 3. Run /ttm-new-campaign <name> to start your first campaign');
277
+ console.log('');
278
+ console.log('Documentation: https://github.com/taketomarket/taketomarket/blob/main/README.md');
279
+ }
280
+
281
+ /**
282
+ * Print validation results as a table.
283
+ * @param {Array<{name: string, status: string}>} results
284
+ */
285
+ function printResults(results) {
286
+ console.log('Validation:');
287
+ for (const r of results) {
288
+ const label = r.status === 'pass' ? '[PASS]' : '[FAIL]';
289
+ console.log(` ${label} ${r.name}`);
290
+ }
291
+ }
292
+
293
+ if (require.main === module) {
294
+ main();
295
+ }
296
+
297
+ module.exports = {
298
+ main,
299
+ detectRuntime,
300
+ validateInstall,
301
+ copyDirSync,
302
+ dirExists,
303
+ fileExists,
304
+ printResults,
305
+ DIRS_TO_COPY,
306
+ FILES_TO_COPY,
307
+ };