taketomarket 1.0.0 → 2.0.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/plugin.json +1 -1
- package/README.md +77 -377
- package/install.js +501 -68
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taketomarket",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Marketing operating system for Claude Code. Spec-driven campaigns with positioning-as-invariant enforcement, quality gate walls, and compound learnings.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "takeToMarket"
|
package/README.md
CHANGED
|
@@ -2,418 +2,118 @@
|
|
|
2
2
|
|
|
3
3
|
A marketing operating system for Claude Code and Codex. Spec-driven campaigns with positioning-as-invariant enforcement, quality gate walls, and compound learnings.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Core invariant:** Every marketing asset ships with a verifiable outcome metric and passes through a positioning-invariant quality gate wall — no asset ships without both, ever.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## What it is / What it isn't
|
|
8
|
+
|
|
9
|
+
**IS:** A marketing OS that treats every campaign, asset, and channel as a spec-driven unit with a verifiable outcome, a positioning invariant, and a quality gate wall. Persistent state. Compound learnings. Nine-phase lifecycle enforcement.
|
|
10
|
+
|
|
11
|
+
**IS NOT:** A content generator, one-click blog writer, or social media scheduler. Not a replacement for marketers — it is the operating system a marketer uses to ship more, drift less, and compound learnings. Not a reporting dashboard. Not a scheduler. takeToMarket enforces discipline; it does not generate random content.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Node.js 18+
|
|
16
|
+
- Claude Code v1.x+ (or Codex)
|
|
17
|
+
- git
|
|
8
18
|
|
|
9
19
|
## Installation
|
|
10
20
|
|
|
11
|
-
###
|
|
21
|
+
### Option 1 — npx (recommended)
|
|
12
22
|
|
|
13
23
|
```bash
|
|
14
24
|
npx taketomarket
|
|
15
25
|
```
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Override detection with an explicit flag:
|
|
27
|
+
Interactive: asks which tool(s) you use, installs to all selected runtimes in one pass.
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
Flags:
|
|
30
|
+
- `--yes` or `-y` — skip confirmation (for CI/scripts)
|
|
31
|
+
- `--check` — show install status without installing
|
|
32
|
+
- `--runtime <claude|codex>` — skip interactive prompt, install to one runtime
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
### Option 2 — Claude Code plugin marketplace
|
|
26
35
|
|
|
27
|
-
```
|
|
28
|
-
|
|
36
|
+
```
|
|
37
|
+
/plugin install taketomarket@claude-plugins-official
|
|
29
38
|
```
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
> Status: pending marketplace approval. Check https://github.com/ranjanrishikesh/takeToMarket for current status.
|
|
32
41
|
|
|
33
|
-
###
|
|
42
|
+
### Option 3 — Direct from GitHub
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
git clone https://github.com/taketomarket/taketomarket.git
|
|
37
|
-
cd taketomarket
|
|
38
|
-
```
|
|
44
|
+
> Status: pending verification. Use Option 1 or Option 4 in the meantime.
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
### Option 4 — Manual (advanced)
|
|
41
47
|
|
|
42
48
|
```bash
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
cp -r workflows ~/.claude/plugins/taketomarket/workflows
|
|
47
|
-
cp -r templates ~/.claude/plugins/taketomarket/templates
|
|
48
|
-
cp -r references ~/.claude/plugins/taketomarket/references
|
|
49
|
-
cp -r playbooks ~/.claude/plugins/taketomarket/playbooks
|
|
50
|
-
cp -r gates ~/.claude/plugins/taketomarket/gates
|
|
51
|
-
cp -r bin ~/.claude/plugins/taketomarket/bin
|
|
52
|
-
cp settings.json ~/.claude/plugins/taketomarket/
|
|
49
|
+
git clone https://github.com/ranjanrishikesh/takeToMarket
|
|
50
|
+
cd takeToMarket
|
|
51
|
+
node install.js
|
|
53
52
|
```
|
|
54
53
|
|
|
55
|
-
For Codex, replace `~/.claude/` with `~/.codex/` in the paths above.
|
|
56
|
-
|
|
57
|
-
### Verify Installation
|
|
58
|
-
|
|
59
|
-
Run `/ttm-health` inside Claude Code to confirm the setup. It validates directory integrity, reference file presence, and state consistency.
|
|
60
|
-
|
|
61
54
|
## Quick Start
|
|
62
55
|
|
|
63
|
-
Once installed, run these commands inside Claude Code (or Codex) to execute your first campaign:
|
|
64
|
-
|
|
65
|
-
1. **Initialize your workspace:** `/ttm-init`
|
|
66
|
-
Guided interview about your product, brand, audience, channels, competitors, and metrics. Generates all reference files in `.marketing/`.
|
|
67
|
-
|
|
68
|
-
2. **Create a campaign:** `/ttm-new-campaign spring-launch`
|
|
69
|
-
Creates `.marketing/CAMPAIGNS/spring-launch/` with initialized state and reference file links.
|
|
70
|
-
|
|
71
|
-
3. **Research the market:** `/ttm-research spring-launch`
|
|
72
|
-
Market/audience research with SERP analysis, competitor content mapping, and ambient narrative capture. Supports web search MCP tools or manual paste.
|
|
73
|
-
|
|
74
|
-
4. **Write the brief:** `/ttm-brief spring-launch`
|
|
75
|
-
Generates a campaign brief with mandatory outcome metrics, positioning anchor, hook, proof points, channel mix, and asset list. Refuses to proceed without both output and outcome metrics.
|
|
76
|
-
|
|
77
|
-
5. **Produce content:** `/ttm-produce spring-launch`
|
|
78
|
-
Generates each asset in a fresh isolated context loaded with brief + positioning + brand + ICP + playbook. Hero asset first, then derivatives in wave-parallel.
|
|
79
|
-
|
|
80
|
-
6. **Verify quality:** `/ttm-verify spring-launch`
|
|
81
|
-
Runs every asset through the 10-gate quality wall in a separate context from production (preventing self-evaluation bias). Outputs pass/fail per gate with line-level feedback.
|
|
82
|
-
|
|
83
|
-
7. **Human review:** `/ttm-review spring-launch`
|
|
84
|
-
Presents assets with a structured checklist covering positioning reinforcement, outcome realism, claim substantiation, and competitor differentiation.
|
|
85
|
-
|
|
86
|
-
8. **Fix failures:** `/ttm-fix spring-launch`
|
|
87
|
-
Root cause analysis, fix brief with preservation constraints, re-production in isolated context, re-verification. Capped at 3 attempts per asset before escalating to human review.
|
|
88
|
-
|
|
89
|
-
9. **Ship it:** `/ttm-ship spring-launch`
|
|
90
|
-
Launch checklist with tracking confirmed, UTMs verified, funnel tested, assets finalized. Items tagged [AI] auto-check; items tagged [HUMAN] require confirmation.
|
|
91
|
-
|
|
92
|
-
10. **Measure outcomes:** `/ttm-measure spring-launch`
|
|
93
|
-
Paste analytics data (or use MCP tools). Analysis against outcome metrics using last-touch, linear, and time-decay attribution models. Outcome reported first.
|
|
94
|
-
|
|
95
|
-
11. **Extract learnings:** `/ttm-learn spring-launch`
|
|
96
|
-
Lessons extracted with proposed reference file edits (each requiring human approval). Root-cause taxonomy entries and winning patterns logged to LEARNINGS.md for future campaigns.
|
|
97
|
-
|
|
98
|
-
## How It Works
|
|
99
|
-
|
|
100
|
-
takeToMarket implements a 9-phase campaign lifecycle. Each phase has a dedicated command, persistent state tracking, and explicit entry/exit criteria.
|
|
101
|
-
|
|
102
56
|
```
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/ttm-new-campaign <slug>
|
|
107
|
-
|
|
|
108
|
-
v
|
|
109
|
-
[1. DISCOVER] -----> /ttm-research <slug>
|
|
110
|
-
| Market research, SERP analysis,
|
|
111
|
-
| competitor content, ambient narrative
|
|
112
|
-
v
|
|
113
|
-
[2. BRIEF] --------> /ttm-brief <slug>
|
|
114
|
-
| Outcome metrics, positioning anchor,
|
|
115
|
-
| hook, proof points, channel mix
|
|
116
|
-
v
|
|
117
|
-
[3. PRODUCE] ------> /ttm-produce <slug>
|
|
118
|
-
| Fresh context per asset, hero-first,
|
|
119
|
-
| wave-parallel execution, playbook-loaded
|
|
120
|
-
v
|
|
121
|
-
[4. VERIFY] -------> /ttm-verify <slug>
|
|
122
|
-
| 10-gate quality wall, separate context,
|
|
123
|
-
| line-level feedback, deviation options
|
|
124
|
-
v
|
|
125
|
-
[5. REVIEW] -------> /ttm-review <slug>
|
|
126
|
-
| Structured human checklist, per-asset
|
|
127
|
-
| approve/reject/revise decisions
|
|
128
|
-
v
|
|
129
|
-
[6. FIX] ----------> /ttm-fix <slug> (if needed)
|
|
130
|
-
| Root-cause analysis, 3-attempt cap,
|
|
131
|
-
| preservation constraints, auto-escalate
|
|
132
|
-
v
|
|
133
|
-
[7. SHIP] ---------> /ttm-ship <slug>
|
|
134
|
-
| Launch checklist, tracking confirmed,
|
|
135
|
-
| UTMs verified, funnel tested
|
|
136
|
-
v
|
|
137
|
-
[8. MEASURE] ------> /ttm-measure <slug>
|
|
138
|
-
| Paste analytics, 3 attribution models,
|
|
139
|
-
| outcome-first reporting
|
|
140
|
-
v
|
|
141
|
-
[9. LEARN] --------> /ttm-learn <slug>
|
|
142
|
-
Lessons extracted, reference files
|
|
143
|
-
updated, patterns logged to LEARNINGS.md
|
|
57
|
+
/taketomarket:ttm-init # set up workspace (one time)
|
|
58
|
+
/taketomarket:ttm-new-campaign # create first campaign
|
|
59
|
+
/taketomarket:ttm-produce # run production wave
|
|
144
60
|
```
|
|
145
61
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
1. **Positioning is the invariant.** POSITIONING.md loads into every phase context. It is read-only during campaigns. Drift is detected and flagged. Changing positioning requires an explicit shift workflow with migration planning and human approval.
|
|
149
|
-
|
|
150
|
-
2. **Outcome over output.** Every campaign brief requires both an output metric (what gets published) and an outcome metric (what business result is expected). Measure phase reports outcome first.
|
|
62
|
+
## Campaign Lifecycle
|
|
151
63
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
5. **
|
|
64
|
+
1. **Init** — set up workspace and reference files
|
|
65
|
+
2. **New Campaign** — create campaign directory with initialized state
|
|
66
|
+
3. **Research** — discover market, audience, and ambient narrative
|
|
67
|
+
4. **Brief** — generate brief with mandatory outcome metrics
|
|
68
|
+
5. **Produce** — generate assets in isolated contexts with full reference loading
|
|
69
|
+
6. **Review** — human quality evaluation with structured checklist
|
|
70
|
+
7. **Fix** — root cause analysis, re-produce, re-verify (capped 3×)
|
|
71
|
+
8. **Verify** — quality gate wall check across all assets
|
|
72
|
+
9. **Ship** — launch checklist confirming tracking, UTMs, funnel testing
|
|
73
|
+
10. **Measure** — analytics vs outcome metrics with attribution models
|
|
74
|
+
11. **Learn** — extract lessons, propose reference file edits, log to LEARNINGS.md
|
|
157
75
|
|
|
158
76
|
## Command Reference
|
|
159
77
|
|
|
160
|
-
### Setup
|
|
161
|
-
|
|
162
|
-
| Command | Description |
|
|
163
|
-
|---------|-------------|
|
|
164
|
-
| `/ttm-init` | Interview-driven onboarding that generates all `.marketing/` reference files from structured questioning with specificity validation |
|
|
165
|
-
| `/ttm-new-campaign <slug>` | Create a campaign directory with initialized state and reference file links |
|
|
166
|
-
|
|
167
|
-
### Campaign Lifecycle
|
|
168
|
-
|
|
169
|
-
| Command | Description |
|
|
170
|
-
|---------|-------------|
|
|
171
|
-
| `/ttm-research <slug>` | Discover phase: market/audience research, SERP analysis, competitor content mapping, ambient narrative capture |
|
|
172
|
-
| `/ttm-brief <slug>` | Generate campaign brief with outcome metric enforcement, positioning anchor, channel mix, and asset list |
|
|
173
|
-
| `/ttm-produce <slug>` | Produce content assets in fresh isolated contexts loaded with brief, positioning, brand, ICP, and playbook |
|
|
174
|
-
| `/ttm-verify <slug>` | Run all applicable quality gates on every asset with pass/fail report and line-level feedback |
|
|
175
|
-
| `/ttm-review <slug>` | Present assets with structured review checklist for human evaluation and per-asset decisions |
|
|
176
|
-
| `/ttm-fix <slug>` | Root cause analysis, fix brief, re-produce in isolated context, re-verify (3-attempt cap per asset) |
|
|
177
|
-
| `/ttm-ship <slug>` | Launch checklist with tracking, UTM, and funnel verification; items tagged [AI]/[HUMAN] |
|
|
178
|
-
| `/ttm-measure <slug>` | Analyze pasted analytics against outcome metrics using last-touch, linear, and time-decay attribution |
|
|
179
|
-
| `/ttm-learn <slug>` | Extract lessons, propose reference file edits with human approval, log root-cause taxonomy entries |
|
|
180
|
-
|
|
181
|
-
### State Management
|
|
182
|
-
|
|
183
|
-
| Command | Description |
|
|
184
|
-
|---------|-------------|
|
|
185
|
-
| `/ttm-state` | Dashboard showing campaign states, decisions in flight, blockers, and experiments |
|
|
186
|
-
| `/ttm-resume <slug>` | Resume a paused campaign at its last completed phase |
|
|
187
|
-
| `/ttm-archive <slug>` | Finalize a completed campaign, move to archive, update LEARNINGS.md |
|
|
188
|
-
| `/ttm-health` | Validate `.marketing/` directory integrity, reference file completeness, and state consistency |
|
|
189
|
-
| `/ttm-next` | Guidance on the right next command based on current campaign state |
|
|
190
|
-
|
|
191
|
-
### Positioning
|
|
192
|
-
|
|
193
78
|
| Command | Description |
|
|
194
79
|
|---------|-------------|
|
|
195
|
-
| `/ttm-
|
|
196
|
-
| `/ttm-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
|
201
|
-
|
|
202
|
-
| `/ttm-
|
|
203
|
-
| `/ttm-
|
|
204
|
-
| `/ttm-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
|
209
|
-
|
|
210
|
-
| `/ttm-
|
|
211
|
-
| `/ttm-
|
|
212
|
-
| `/ttm-
|
|
213
|
-
| `/ttm-
|
|
214
|
-
| `/ttm-
|
|
215
|
-
| `/ttm-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|---|------|------|----------|
|
|
227
|
-
| 1 | **Positioning Drift** | Tier 1 | Blocking -- asset must align with POSITIONING.md invariant |
|
|
228
|
-
| 2 | **Claim Accuracy** | Tier 1 | Blocking -- every claim must have a source or proof point |
|
|
229
|
-
| 3 | **Voice Drift** | Tier 2 | Advisory -- flags voice/tone inconsistency with BRAND.md |
|
|
230
|
-
| 4 | **Outcome Alignment** | Tier 1 | Blocking -- asset must serve the campaign outcome metric |
|
|
231
|
-
| 5 | **Funnel Integrity** | Tier 2 | Advisory -- CTA and funnel step match the campaign stage |
|
|
232
|
-
| 6 | **UTM Hygiene** | Tier 2 | Advisory -- all links have valid, consistent UTM parameters |
|
|
233
|
-
| 7 | **Compliance** | Tier 2 | Advisory -- legal disclaimers, required disclosures present |
|
|
234
|
-
| 8 | **Competitor Collision** | Tier 2 | Advisory -- avoids using competitor claims or positioning |
|
|
235
|
-
| 9 | **ICP Fit** | Tier 2 | Advisory -- content addresses the right audience segment |
|
|
236
|
-
| 10 | **Format Correctness** | Tier 2 | Advisory -- meets channel-specific format requirements |
|
|
237
|
-
|
|
238
|
-
### Tier Behavior
|
|
239
|
-
|
|
240
|
-
- **Tier 1 (blocking):** Asset cannot ship until the gate passes. Failing a Tier 1 gate triggers the fix loop.
|
|
241
|
-
- **Tier 2 (advisory):** Flagged for review but does not block shipping. Each failure offers three options: Correct, Accept+log, or Escalate to positioning shift.
|
|
242
|
-
|
|
243
|
-
### Meta-Gates
|
|
244
|
-
|
|
245
|
-
Multi-campaign portfolio checks run after per-asset gates:
|
|
246
|
-
|
|
247
|
-
- **Portfolio Balance** -- channel mix across active campaigns
|
|
248
|
-
- **Calendar Collision** -- overlapping launch dates or audience saturation
|
|
249
|
-
- **Theme Consistency** -- messaging coherence across concurrent campaigns
|
|
250
|
-
- **Learning Plan** -- measurement infrastructure coverage
|
|
251
|
-
|
|
252
|
-
## Playbooks
|
|
253
|
-
|
|
254
|
-
10 discipline playbooks extend the base quality gate wall with channel-specific knowledge, format rules, and additional gates:
|
|
255
|
-
|
|
256
|
-
| Playbook | Focus Areas |
|
|
257
|
-
|----------|-------------|
|
|
258
|
-
| **SEO** | Title/H1 alignment, search-intent match, schema markup, internal-link density, thin-content detection |
|
|
259
|
-
| **AEO** | Quote-worthy sentences, FAQ/HowTo schema, cross-domain fact consistency, AI citation optimization |
|
|
260
|
-
| **Email** | Subject/preview spam-trigger scan, dark-mode rendering, unsubscribe presence, deliverability checks |
|
|
261
|
-
| **LinkedIn** | Professional tone, opener hooks, native vs link format, engagement optimization |
|
|
262
|
-
| **Social** | Platform-specific rules, visual ratios, hashtag strategy, engagement hooks |
|
|
263
|
-
| **YouTube** | Thumbnail optimization, retention hooks, chapter markers, description SEO |
|
|
264
|
-
| **Paid Ads** | Headline character limits, ad relevance scoring, landing page alignment |
|
|
265
|
-
| **Affiliate** | Partner-safe messaging, commission disclosure, creative kit completeness |
|
|
266
|
-
| **PR/Media** | News angle clarity, quote-readiness, fact-checking rigor, embargo handling |
|
|
267
|
-
| **Events** | Session flow, attendee journey mapping, follow-up sequence planning |
|
|
268
|
-
|
|
269
|
-
All playbooks inherit from `playbooks/base.md` and extend it with additive gates. The base contract ensures consistent evaluation across disciplines.
|
|
270
|
-
|
|
271
|
-
## Architecture
|
|
272
|
-
|
|
80
|
+
| `/taketomarket:ttm-aeo-check` | Check citation status across AI engines for a query |
|
|
81
|
+
| `/taketomarket:ttm-affiliate-kit` | Generate creative kit for affiliate partners |
|
|
82
|
+
| `/taketomarket:ttm-archive` | Archive a completed campaign, finalize state, and update LEARNINGS.md |
|
|
83
|
+
| `/taketomarket:ttm-brand-refresh` | Update BRAND.md with new proof points and deprecate expired ones |
|
|
84
|
+
| `/taketomarket:ttm-brief` | Generate a campaign brief with mandatory outcome metrics, positioning anchor, and channel mix |
|
|
85
|
+
| `/taketomarket:ttm-competitor-scan` | On-demand competitor analysis that updates COMPETITORS.md |
|
|
86
|
+
| `/taketomarket:ttm-email-preflight` | Deliverability, dark-mode, and spam-trigger scan for email assets |
|
|
87
|
+
| `/taketomarket:ttm-fix` | Fix phase: root cause analysis, fix brief, re-produce, re-verify (capped 3×) |
|
|
88
|
+
| `/taketomarket:ttm-health` | Validate .marketing/ directory integrity, reference file completeness, and state consistency |
|
|
89
|
+
| `/taketomarket:ttm-icp-refresh` | Update ICP.md from new customer data including calls, reviews, and feedback |
|
|
90
|
+
| `/taketomarket:ttm-init` | Interview-driven onboarding that generates all .marketing/ reference files |
|
|
91
|
+
| `/taketomarket:ttm-keyword-map` | Generate keyword cluster map with intent tags |
|
|
92
|
+
| `/taketomarket:ttm-learn` | Extract lessons from campaign data, propose reference file edits, log to LEARNINGS.md |
|
|
93
|
+
| `/taketomarket:ttm-measure` | Analyze campaign analytics against outcome metrics using attribution models |
|
|
94
|
+
| `/taketomarket:ttm-new-campaign` | Create a new campaign directory with initialized state and reference file links |
|
|
95
|
+
| `/taketomarket:ttm-next` | Guide user to the right next command based on current campaign state |
|
|
96
|
+
| `/taketomarket:ttm-positioning-check` | Sample recent assets and report positioning drift percentage and analysis |
|
|
97
|
+
| `/taketomarket:ttm-positioning-shift` | Controlled positioning change with reasoning, migration plan, and approval gate |
|
|
98
|
+
| `/taketomarket:ttm-produce` | Generate content assets in fresh contexts loaded with brief, positioning, brand, ICP, and playbook |
|
|
99
|
+
| `/taketomarket:ttm-repurpose` | Fan out a long-form asset into derivatives across channels with full brief-produce-verify per derivative |
|
|
100
|
+
| `/taketomarket:ttm-research` | Market and audience research including SERP, competitor content, and narrative mapping |
|
|
101
|
+
| `/taketomarket:ttm-resume` | Resume a paused campaign at its last completed phase |
|
|
102
|
+
| `/taketomarket:ttm-review` | Present assets with structured review checklist for human evaluation |
|
|
103
|
+
| `/taketomarket:ttm-seo-audit` | Technical and content SEO audit of a URL or sitemap |
|
|
104
|
+
| `/taketomarket:ttm-ship` | Generate launch checklist confirming tracking, UTMs, funnel testing, and asset finalization |
|
|
105
|
+
| `/taketomarket:ttm-state` | Display current campaign states, decisions in flight, blockers, and experiments |
|
|
106
|
+
| `/taketomarket:ttm-verify` | Run all applicable quality gates on every asset with pass/fail report and line-level feedback |
|
|
107
|
+
|
|
108
|
+
## Verify Installation
|
|
109
|
+
|
|
110
|
+
Inside Claude Code, run:
|
|
273
111
|
```
|
|
274
|
-
|
|
275
|
-
.claude-plugin/
|
|
276
|
-
plugin.json Plugin manifest (name, version, description, keywords)
|
|
277
|
-
|
|
278
|
-
skills/ 27 SKILL.md command stubs (routing only, <30 lines each)
|
|
279
|
-
ttm-init/SKILL.md
|
|
280
|
-
ttm-new-campaign/SKILL.md
|
|
281
|
-
ttm-research/SKILL.md
|
|
282
|
-
ttm-brief/SKILL.md
|
|
283
|
-
ttm-produce/SKILL.md
|
|
284
|
-
ttm-verify/SKILL.md
|
|
285
|
-
ttm-review/SKILL.md
|
|
286
|
-
ttm-fix/SKILL.md
|
|
287
|
-
ttm-ship/SKILL.md
|
|
288
|
-
ttm-measure/SKILL.md
|
|
289
|
-
ttm-learn/SKILL.md
|
|
290
|
-
ttm-state/SKILL.md
|
|
291
|
-
ttm-resume/SKILL.md
|
|
292
|
-
ttm-archive/SKILL.md
|
|
293
|
-
ttm-health/SKILL.md
|
|
294
|
-
ttm-next/SKILL.md
|
|
295
|
-
ttm-positioning-check/SKILL.md
|
|
296
|
-
ttm-positioning-shift/SKILL.md
|
|
297
|
-
ttm-brand-refresh/SKILL.md
|
|
298
|
-
ttm-icp-refresh/SKILL.md
|
|
299
|
-
ttm-competitor-scan/SKILL.md
|
|
300
|
-
ttm-seo-audit/SKILL.md
|
|
301
|
-
ttm-aeo-check/SKILL.md
|
|
302
|
-
ttm-keyword-map/SKILL.md
|
|
303
|
-
ttm-email-preflight/SKILL.md
|
|
304
|
-
ttm-affiliate-kit/SKILL.md
|
|
305
|
-
ttm-repurpose/SKILL.md
|
|
306
|
-
|
|
307
|
-
workflows/
|
|
308
|
-
setup/ /ttm-init interview workflow
|
|
309
|
-
lifecycle/ Campaign phase workflows (research, brief, produce,
|
|
310
|
-
verify, review, fix, ship, measure, learn)
|
|
311
|
-
reference-mgmt/ Reference file management (positioning-check,
|
|
312
|
-
positioning-shift, brand-refresh, icp-refresh,
|
|
313
|
-
competitor-scan)
|
|
314
|
-
discipline/ Discipline utility workflows (seo-audit, aeo-check,
|
|
315
|
-
keyword-map, email-preflight, affiliate-kit, repurpose)
|
|
316
|
-
utility/ State management workflows (state, resume, archive,
|
|
317
|
-
health, next)
|
|
318
|
-
|
|
319
|
-
templates/ Markdown templates for generated files
|
|
320
|
-
reference-files/ Reference file templates (brand, icp, competitors, etc.)
|
|
321
|
-
campaign-brief.md Brief template with outcome/output metrics
|
|
322
|
-
production-manifest.json Asset manifest schema
|
|
323
|
-
|
|
324
|
-
references/ Domain knowledge and context loading rules
|
|
325
|
-
context-loading.md Two-tier context loading strategy
|
|
326
|
-
|
|
327
|
-
playbooks/ Discipline playbook files
|
|
328
|
-
base.md Base inheritance contract
|
|
329
|
-
seo.md SEO discipline playbook
|
|
330
|
-
aeo.md AEO discipline playbook
|
|
331
|
-
email.md Email discipline playbook
|
|
332
|
-
linkedin.md LinkedIn discipline playbook
|
|
333
|
-
social.md Social discipline playbook
|
|
334
|
-
youtube.md YouTube discipline playbook
|
|
335
|
-
paid-ads.md Paid Ads discipline playbook
|
|
336
|
-
affiliate.md Affiliate discipline playbook
|
|
337
|
-
pr-media.md PR/Media discipline playbook
|
|
338
|
-
events.md Events discipline playbook
|
|
339
|
-
|
|
340
|
-
gates/ Gate evaluation logic and base gate definitions
|
|
341
|
-
|
|
342
|
-
agents/ Subagent prompt templates (producer, verifier)
|
|
343
|
-
|
|
344
|
-
bin/
|
|
345
|
-
ttm-tools.cjs CLI entry point (single file, subcommand pattern)
|
|
346
|
-
lib/
|
|
347
|
-
campaign.cjs Campaign state operations, MANIFEST.json management
|
|
348
|
-
health.cjs Health validation checks
|
|
349
|
-
core.cjs Core utility functions
|
|
350
|
-
slug.cjs Slug generation and validation
|
|
351
|
-
state.cjs State file operations
|
|
352
|
-
drift-log.cjs Positioning drift logging
|
|
353
|
-
deviation.cjs Deviation tracking
|
|
354
|
-
commit.cjs Commit message helpers
|
|
355
|
-
|
|
356
|
-
settings.json Default plugin settings
|
|
357
|
-
install.js npm installer entry point
|
|
358
|
-
package.json npm package config (bin: taketomarket -> install.js)
|
|
112
|
+
/taketomarket:ttm-health
|
|
359
113
|
```
|
|
360
114
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
**Thin SKILL.md routing.** Each skill file is under 30 lines. It contains YAML frontmatter (name, description, allowed-tools, context mode) and a single instruction to read the corresponding workflow file. This keeps skills lightweight and maintainable.
|
|
364
|
-
|
|
365
|
-
**500-line file limit.** No single file exceeds 500 lines. Large content is extracted to `@`-referenced files. For example, the review workflow references `review-checklist.md` instead of embedding 400 lines of checklist questions inline.
|
|
366
|
-
|
|
367
|
-
**Two-tier context loading.** Compact summaries of reference files load into every phase context (positioning summary, brand overview, ICP highlights). Full documents load only during produce and verify phases where deep knowledge matters. This preserves context window budget.
|
|
368
|
-
|
|
369
|
-
**Positioning-as-invariant.** POSITIONING.md is architecturally read-only during campaigns. The system detects and blocks direct edits. Changing positioning requires the explicit `/ttm-positioning-shift` workflow with migration planning, human approval, and a deprecation schedule.
|
|
370
|
-
|
|
371
|
-
**Fresh context isolation.** Produce and verify run in forked contexts (`context: fork` in SKILL.md frontmatter). This prevents the producer from evaluating its own output and keeps each asset's context window loaded with only the relevant brief, positioning, brand, ICP, and playbook.
|
|
372
|
-
|
|
373
|
-
**Zero runtime dependencies.** The skill itself has no npm dependencies. All code in `bin/` uses only Node.js built-in modules (`fs`, `path`, `crypto`, `os`). Node.js 18+ is the only requirement, and it is already required by Claude Code.
|
|
374
|
-
|
|
375
|
-
### How to Add a Playbook
|
|
376
|
-
|
|
377
|
-
1. Create a new file in `playbooks/` (e.g., `playbooks/webinar.md`)
|
|
378
|
-
2. Follow the structure in `playbooks/base.md` -- extend with additive gates
|
|
379
|
-
3. Add a SKILL.md stub in `skills/ttm-webinar-audit/` pointing to a new workflow
|
|
380
|
-
4. Add the workflow in `workflows/discipline/webinar-audit.md`
|
|
381
|
-
5. Register the channel in the channels template at `templates/reference-files/`
|
|
382
|
-
|
|
383
|
-
### User's Project Structure (created by /ttm-init)
|
|
384
|
-
|
|
385
|
-
When a user runs `/ttm-init` in their project, it creates:
|
|
386
|
-
|
|
387
|
-
```
|
|
388
|
-
.marketing/
|
|
389
|
-
POSITIONING.md Positioning invariant (read-only during campaigns)
|
|
390
|
-
BRAND.md Voice, tone, proof points
|
|
391
|
-
ICP.md Ideal customer profiles
|
|
392
|
-
CHANNELS.md Channel strategy and constraints
|
|
393
|
-
COMPETITORS.md Competitor landscape
|
|
394
|
-
METRICS.md KPI definitions and baselines
|
|
395
|
-
CALENDAR.md Marketing calendar
|
|
396
|
-
STATE.md Global state tracking
|
|
397
|
-
LEARNINGS.md Accumulated campaign learnings
|
|
398
|
-
DRIFT-LOG.md Positioning drift event log
|
|
399
|
-
CAMPAIGNS/
|
|
400
|
-
<slug>/ Per-campaign directory
|
|
401
|
-
STATE.md Campaign state
|
|
402
|
-
RESEARCH.md Market research findings
|
|
403
|
-
BRIEF.md Campaign brief
|
|
404
|
-
MANIFEST.json Asset manifest and tracking
|
|
405
|
-
assets/ Produced content assets
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
## Dual-Runtime Support
|
|
409
|
-
|
|
410
|
-
takeToMarket works with both Claude Code and Codex:
|
|
411
|
-
|
|
412
|
-
- **Claude Code:** Skills install as a plugin at `~/.claude/plugins/taketomarket/`. Commands appear as `/ttm-*` in the Claude Code interface. Uses `AskUserQuestion` for interactive prompts with text-mode fallback.
|
|
413
|
-
- **Codex:** Skills install at `~/.codex/plugins/taketomarket/`. An AGENTS.md file provides Codex-compatible instructions alongside CLAUDE.md. Interactive prompts fall back to numbered list format when `AskUserQuestion` is unavailable.
|
|
414
|
-
|
|
415
|
-
The installer auto-detects the runtime. Both runtimes read the same SKILL.md files and execute the same workflows.
|
|
115
|
+
This validates directory integrity, reference file presence, and state consistency.
|
|
416
116
|
|
|
417
117
|
## License
|
|
418
118
|
|
|
419
|
-
MIT
|
|
119
|
+
MIT — see [LICENSE](LICENSE).
|
package/install.js
CHANGED
|
@@ -27,6 +27,170 @@ const FILES_TO_COPY = [
|
|
|
27
27
|
'settings.json',
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
+
// ── Runtime Selection ─────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const RUNTIME_MENU = ['claude', 'codex', 'cursor', 'windsurf', 'gemini'];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse user input from the runtime selection prompt.
|
|
36
|
+
* @param {string} input - Raw user input (e.g., '1,3' or '6')
|
|
37
|
+
* @returns {string[]|null} Array of runtime names, or null if invalid
|
|
38
|
+
*/
|
|
39
|
+
function parseRuntimeChoices(input) {
|
|
40
|
+
const trimmed = input.trim();
|
|
41
|
+
if (!trimmed) return null;
|
|
42
|
+
if (trimmed === '6') return [...RUNTIME_MENU];
|
|
43
|
+
if (trimmed === '7') return ['custom'];
|
|
44
|
+
|
|
45
|
+
const parts = trimmed.split(',').map(s => s.trim());
|
|
46
|
+
const names = new Set();
|
|
47
|
+
for (const part of parts) {
|
|
48
|
+
const n = parseInt(part, 10);
|
|
49
|
+
if (isNaN(n) || n < 1 || n > 7) return null;
|
|
50
|
+
if (n === 7) return ['custom'];
|
|
51
|
+
names.add(RUNTIME_MENU[n - 1]);
|
|
52
|
+
}
|
|
53
|
+
return [...names];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the install target map for all known runtimes.
|
|
58
|
+
* @param {string} [homeDir] - Home directory (injectable for tests)
|
|
59
|
+
* @returns {Object.<string, {label, dir, parentDir, register, partial}>}
|
|
60
|
+
*/
|
|
61
|
+
function buildRuntimeTargets(homeDir = os.homedir()) {
|
|
62
|
+
return {
|
|
63
|
+
claude: {
|
|
64
|
+
label: 'Claude Code',
|
|
65
|
+
dir: path.join(homeDir, '.claude', 'plugins', 'taketomarket'),
|
|
66
|
+
parentDir: path.join(homeDir, '.claude'),
|
|
67
|
+
register: true,
|
|
68
|
+
partial: false,
|
|
69
|
+
},
|
|
70
|
+
codex: {
|
|
71
|
+
label: 'Codex (OpenAI)',
|
|
72
|
+
dir: path.join(homeDir, '.codex', 'plugins', 'taketomarket'),
|
|
73
|
+
parentDir: path.join(homeDir, '.codex'),
|
|
74
|
+
register: false,
|
|
75
|
+
partial: true,
|
|
76
|
+
},
|
|
77
|
+
cursor: {
|
|
78
|
+
label: 'Cursor',
|
|
79
|
+
dir: path.join(homeDir, '.cursor', 'rules'),
|
|
80
|
+
parentDir: path.join(homeDir, '.cursor'),
|
|
81
|
+
register: false,
|
|
82
|
+
partial: true,
|
|
83
|
+
},
|
|
84
|
+
windsurf: {
|
|
85
|
+
label: 'Windsurf',
|
|
86
|
+
dir: path.join(homeDir, '.codeium', 'windsurf'),
|
|
87
|
+
parentDir: path.join(homeDir, '.codeium'),
|
|
88
|
+
register: false,
|
|
89
|
+
partial: true,
|
|
90
|
+
},
|
|
91
|
+
gemini: {
|
|
92
|
+
label: 'Gemini CLI',
|
|
93
|
+
dir: path.join(homeDir, '.gemini'),
|
|
94
|
+
parentDir: path.join(homeDir, '.gemini'),
|
|
95
|
+
register: false,
|
|
96
|
+
partial: true,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Interactively ask user which runtimes to install to.
|
|
103
|
+
* Falls back to auto-detect when stdin is not a TTY or --runtime flag is set.
|
|
104
|
+
* @param {string[]} args - process.argv slice
|
|
105
|
+
* @param {string} [homeDir]
|
|
106
|
+
* @returns {Promise<Array<{label, dir, parentDir, register, partial}>>}
|
|
107
|
+
*/
|
|
108
|
+
async function promptRuntimeSelection(args, homeDir = os.homedir()) {
|
|
109
|
+
// Legacy --runtime flag: bypass interactive prompt
|
|
110
|
+
const runtimeIdx = args.indexOf('--runtime');
|
|
111
|
+
if (runtimeIdx !== -1 && runtimeIdx + 1 < args.length) {
|
|
112
|
+
const name = args[runtimeIdx + 1].toLowerCase();
|
|
113
|
+
const allTargets = buildRuntimeTargets(homeDir);
|
|
114
|
+
if (!allTargets[name] && name !== 'custom') {
|
|
115
|
+
console.warn(`Warning: Unknown runtime "${name}". Defaulting to claude.`);
|
|
116
|
+
return [allTargets.claude];
|
|
117
|
+
}
|
|
118
|
+
return name === 'custom' ? [] : [allTargets[name]];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Non-TTY fallback: auto-detect installed runtimes
|
|
122
|
+
if (!process.stdin.isTTY) {
|
|
123
|
+
const detected = getInstalledRuntimes(homeDir);
|
|
124
|
+
const allTargets = buildRuntimeTargets(homeDir);
|
|
125
|
+
if (detected.length === 0) {
|
|
126
|
+
console.log('Note: No known runtimes detected. Defaulting to Claude Code.');
|
|
127
|
+
return [allTargets.claude];
|
|
128
|
+
}
|
|
129
|
+
console.log(`Note: Non-interactive mode. Auto-detected: ${detected.join(', ')}`);
|
|
130
|
+
return detected.map(name => allTargets[name]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Interactive prompt
|
|
134
|
+
const { createInterface } = require('node:readline');
|
|
135
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
136
|
+
|
|
137
|
+
const ask = (question) => new Promise(resolve => rl.question(question, resolve));
|
|
138
|
+
|
|
139
|
+
console.log('');
|
|
140
|
+
console.log('Which AI coding tool(s) are you using? (select all that apply)');
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(' 1. Claude Code');
|
|
143
|
+
console.log(' 2. Codex (OpenAI)');
|
|
144
|
+
console.log(' 3. Cursor');
|
|
145
|
+
console.log(' 4. Windsurf');
|
|
146
|
+
console.log(' 5. Gemini CLI');
|
|
147
|
+
console.log(' 6. All of the above');
|
|
148
|
+
console.log(' 7. Let me type a custom path');
|
|
149
|
+
console.log('');
|
|
150
|
+
|
|
151
|
+
let choices = null;
|
|
152
|
+
let attempts = 0;
|
|
153
|
+
while (choices === null && attempts < 2) {
|
|
154
|
+
const input = await ask('Your choice (comma-separated, e.g. 1,3): ');
|
|
155
|
+
choices = parseRuntimeChoices(input);
|
|
156
|
+
if (choices === null) {
|
|
157
|
+
console.log('Invalid input. Please enter numbers 1-7 separated by commas.');
|
|
158
|
+
attempts++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (choices === null) {
|
|
163
|
+
rl.close();
|
|
164
|
+
console.error('Invalid input after 2 attempts. Exiting.');
|
|
165
|
+
console.log('Something went wrong? File an issue: https://github.com/ranjanrishikesh/takeToMarket/issues');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let customPath = null;
|
|
170
|
+
if (choices.includes('custom')) {
|
|
171
|
+
customPath = await ask('Enter install path: ');
|
|
172
|
+
customPath = customPath.trim();
|
|
173
|
+
if (!customPath) {
|
|
174
|
+
rl.close();
|
|
175
|
+
console.error('Custom path cannot be empty.');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
rl.close();
|
|
181
|
+
|
|
182
|
+
const allTargets = buildRuntimeTargets(homeDir);
|
|
183
|
+
const result = [];
|
|
184
|
+
for (const name of choices) {
|
|
185
|
+
if (name === 'custom') {
|
|
186
|
+
result.push({ label: 'Custom', dir: customPath, parentDir: null, register: false, partial: false });
|
|
187
|
+
} else {
|
|
188
|
+
result.push(allTargets[name]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
30
194
|
// ── Runtime detection ────────────────────────────────────────────────────────
|
|
31
195
|
|
|
32
196
|
/**
|
|
@@ -116,6 +280,235 @@ function copyDirSync(src, dest) {
|
|
|
116
280
|
}
|
|
117
281
|
}
|
|
118
282
|
|
|
283
|
+
// ── Plugin Registration ───────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Register taketomarket in Claude Code's installed_plugins.json.
|
|
287
|
+
* Preserves existing plugins. Atomic write (tmp → rename).
|
|
288
|
+
* @param {string} installPath - Absolute path to the installed plugin directory
|
|
289
|
+
* @param {string} version - Plugin version string (e.g., '2.0.0')
|
|
290
|
+
* @param {string} [homeDir] - Home directory (defaults to os.homedir(); injectable for tests)
|
|
291
|
+
*/
|
|
292
|
+
function registerPlugin(installPath, version, homeDir = os.homedir()) {
|
|
293
|
+
const registryPath = path.join(homeDir, '.claude', 'plugins', 'installed_plugins.json');
|
|
294
|
+
const pluginsDir = path.dirname(registryPath);
|
|
295
|
+
|
|
296
|
+
let registry = { version: 2, plugins: {} };
|
|
297
|
+
if (fileExists(registryPath)) {
|
|
298
|
+
try {
|
|
299
|
+
const parsed = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
300
|
+
registry = parsed;
|
|
301
|
+
if (!registry.plugins) registry.plugins = {};
|
|
302
|
+
if (!registry.version) registry.version = 2;
|
|
303
|
+
} catch {
|
|
304
|
+
fs.renameSync(registryPath, registryPath + '.bak');
|
|
305
|
+
console.warn(' Warning: installed_plugins.json was corrupted. Backed up to .bak and recreated.');
|
|
306
|
+
registry = { version: 2, plugins: {} };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const now = new Date().toISOString();
|
|
311
|
+
const existing = (registry.plugins['taketomarket@npm'] || [])[0];
|
|
312
|
+
|
|
313
|
+
registry.plugins['taketomarket@npm'] = [{
|
|
314
|
+
scope: 'user',
|
|
315
|
+
installPath,
|
|
316
|
+
version,
|
|
317
|
+
installedAt: existing?.installedAt ?? now,
|
|
318
|
+
lastUpdated: now,
|
|
319
|
+
gitCommitSha: null,
|
|
320
|
+
}];
|
|
321
|
+
|
|
322
|
+
const tmpPath = registryPath + '.tmp';
|
|
323
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
324
|
+
fs.writeFileSync(tmpPath, JSON.stringify(registry, null, 2) + '\n', 'utf8');
|
|
325
|
+
fs.renameSync(tmpPath, registryPath);
|
|
326
|
+
|
|
327
|
+
console.log(' Registered in installed_plugins.json');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── Skill Introspection ───────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Read skill names and descriptions from skills/ subdirectory of packageRoot.
|
|
334
|
+
* Parses the first content line of the 'description:' YAML frontmatter field.
|
|
335
|
+
* @param {string} packageRoot - Root of the npm package (use PACKAGE_ROOT in production)
|
|
336
|
+
* @returns {Array<{name: string, description: string}>} Sorted by name
|
|
337
|
+
*/
|
|
338
|
+
function readSkillDescriptions(packageRoot) {
|
|
339
|
+
const skillsDir = path.join(packageRoot, 'skills');
|
|
340
|
+
if (!dirExists(skillsDir)) return [];
|
|
341
|
+
|
|
342
|
+
const results = [];
|
|
343
|
+
try {
|
|
344
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
345
|
+
for (const entry of entries) {
|
|
346
|
+
if (!entry.isDirectory()) continue;
|
|
347
|
+
const skillFile = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
348
|
+
if (!fileExists(skillFile)) continue;
|
|
349
|
+
|
|
350
|
+
const content = fs.readFileSync(skillFile, 'utf8');
|
|
351
|
+
const descMatch = content.match(/^description:\s*>\s*\n((?:[ \t]+.+\n?)+)/m);
|
|
352
|
+
let description = '';
|
|
353
|
+
if (descMatch) {
|
|
354
|
+
description = descMatch[1].split('\n')[0].trim().replace(/\.$/, '') + '.';
|
|
355
|
+
} else {
|
|
356
|
+
const inlineMatch = content.match(/^description:\s*(.+)$/m);
|
|
357
|
+
if (inlineMatch) description = inlineMatch[1].trim();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
results.push({ name: entry.name, description });
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
// ignore — skills dir may be empty or unreadable
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Print slash commands available after install. Reads from npm package source.
|
|
371
|
+
* @param {string} [packageRoot] - Root of npm package (defaults to PACKAGE_ROOT = __dirname)
|
|
372
|
+
*/
|
|
373
|
+
function printInstallSummary(packageRoot = PACKAGE_ROOT) {
|
|
374
|
+
const skills = readSkillDescriptions(packageRoot);
|
|
375
|
+
console.log('');
|
|
376
|
+
console.log(`Installation complete! ${skills.length} skills installed.`);
|
|
377
|
+
console.log('');
|
|
378
|
+
console.log('Available commands:');
|
|
379
|
+
for (const { name, description } of skills) {
|
|
380
|
+
const cmd = `/taketomarket:${name}`.padEnd(42);
|
|
381
|
+
console.log(` ${cmd} ${description}`);
|
|
382
|
+
}
|
|
383
|
+
console.log('');
|
|
384
|
+
console.log('Quick start: open any project in Claude Code and run /taketomarket:ttm-init');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Detect which known runtimes are installed by checking their parent directories.
|
|
389
|
+
* @param {string} [homeDir]
|
|
390
|
+
* @returns {string[]} Names of detected runtimes (subset of RUNTIME_MENU)
|
|
391
|
+
*/
|
|
392
|
+
function getInstalledRuntimes(homeDir = os.homedir()) {
|
|
393
|
+
const targets = buildRuntimeTargets(homeDir);
|
|
394
|
+
return RUNTIME_MENU.filter(name => {
|
|
395
|
+
const t = targets[name];
|
|
396
|
+
return t.parentDir && dirExists(t.parentDir);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── Status & Confirmation ─────────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Used by confirmInstall — extracted for testability.
|
|
404
|
+
* @param {boolean} yesFlag
|
|
405
|
+
* @returns {boolean} true if yesFlag is set (no prompt needed)
|
|
406
|
+
*/
|
|
407
|
+
function shouldProceed(yesFlag) {
|
|
408
|
+
return yesFlag === true || !process.stdin.isTTY;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Read Claude Code install status for the checkStatus output.
|
|
413
|
+
* @param {string} [homeDir]
|
|
414
|
+
* @returns {{ installed: boolean, registered: boolean, skillCount: number, dir: string }}
|
|
415
|
+
*/
|
|
416
|
+
function getClaudeStatus(homeDir = os.homedir()) {
|
|
417
|
+
const pluginDir = path.join(homeDir, '.claude', 'plugins', 'taketomarket');
|
|
418
|
+
const installed = dirExists(pluginDir);
|
|
419
|
+
if (!installed) return { installed: false, registered: false, skillCount: 0, dir: pluginDir };
|
|
420
|
+
|
|
421
|
+
const skillsDir = path.join(pluginDir, 'skills');
|
|
422
|
+
let skillCount = 0;
|
|
423
|
+
if (dirExists(skillsDir)) {
|
|
424
|
+
try {
|
|
425
|
+
skillCount = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
426
|
+
.filter(e => e.isDirectory() && fileExists(path.join(skillsDir, e.name, 'SKILL.md')))
|
|
427
|
+
.length;
|
|
428
|
+
} catch { /* ignore */ }
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const registryPath = path.join(homeDir, '.claude', 'plugins', 'installed_plugins.json');
|
|
432
|
+
let registered = false;
|
|
433
|
+
if (fileExists(registryPath)) {
|
|
434
|
+
try {
|
|
435
|
+
const reg = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
436
|
+
registered = !!(reg.plugins && reg.plugins['taketomarket@npm']);
|
|
437
|
+
} catch { /* ignore */ }
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return { installed: true, registered, skillCount, dir: pluginDir };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Print install status for all known runtimes and exit.
|
|
445
|
+
* @param {string} version
|
|
446
|
+
* @param {string} [homeDir]
|
|
447
|
+
*/
|
|
448
|
+
function checkStatus(version, homeDir = os.homedir()) {
|
|
449
|
+
const targets = buildRuntimeTargets(homeDir);
|
|
450
|
+
console.log('');
|
|
451
|
+
console.log(`takeToMarket v${version}`);
|
|
452
|
+
console.log('');
|
|
453
|
+
|
|
454
|
+
const claude = getClaudeStatus(homeDir);
|
|
455
|
+
if (claude.installed) {
|
|
456
|
+
console.log(`Claude Code: INSTALLED (${claude.skillCount} skills, ${claude.dir.replace(homeDir, '~')})`);
|
|
457
|
+
console.log(` registered: ${claude.registered ? 'yes (installed_plugins.json)' : 'NO — slash commands will not appear'}`);
|
|
458
|
+
} else {
|
|
459
|
+
console.log('Claude Code: NOT INSTALLED');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for (const name of ['codex', 'cursor', 'windsurf', 'gemini']) {
|
|
463
|
+
const t = targets[name];
|
|
464
|
+
const label = t.label.padEnd(12);
|
|
465
|
+
if (t.dir && dirExists(t.dir)) {
|
|
466
|
+
console.log(`${label} INSTALLED (${t.dir.replace(homeDir, '~')})`);
|
|
467
|
+
} else {
|
|
468
|
+
console.log(`${label} NOT INSTALLED`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
console.log('');
|
|
473
|
+
console.log('Run `npx taketomarket` to install or reinstall.');
|
|
474
|
+
process.exit(0);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Interactively confirm install. Skipped when yesFlag is true.
|
|
479
|
+
* @param {Array<{label, dir}>} targets
|
|
480
|
+
* @param {string} version
|
|
481
|
+
* @param {boolean} yesFlag
|
|
482
|
+
* @returns {Promise<boolean>}
|
|
483
|
+
*/
|
|
484
|
+
async function confirmInstall(targets, version, yesFlag) {
|
|
485
|
+
if (shouldProceed(yesFlag)) return true;
|
|
486
|
+
|
|
487
|
+
const { createInterface } = require('node:readline');
|
|
488
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
489
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
490
|
+
|
|
491
|
+
console.log('');
|
|
492
|
+
console.log(`takeToMarket v${version} — Marketing OS for AI coding tools`);
|
|
493
|
+
console.log('');
|
|
494
|
+
console.log('This will install to:');
|
|
495
|
+
for (const t of targets) {
|
|
496
|
+
const shortDir = t.dir.replace(os.homedir(), '~');
|
|
497
|
+
console.log(` ${shortDir.padEnd(45)} (${t.label})`);
|
|
498
|
+
}
|
|
499
|
+
console.log('');
|
|
500
|
+
|
|
501
|
+
const answer = await ask('Proceed? [Y/n]: ');
|
|
502
|
+
rl.close();
|
|
503
|
+
|
|
504
|
+
const trimmed = answer.trim().toLowerCase();
|
|
505
|
+
if (trimmed === 'n') {
|
|
506
|
+
console.log('Installation cancelled.');
|
|
507
|
+
process.exit(0);
|
|
508
|
+
}
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
|
|
119
512
|
// ── Validation ───────────────────────────────────────────────────────────────
|
|
120
513
|
|
|
121
514
|
/**
|
|
@@ -168,16 +561,14 @@ function validateInstall(targetDir) {
|
|
|
168
561
|
|
|
169
562
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
170
563
|
|
|
171
|
-
function main() {
|
|
564
|
+
async function main() {
|
|
172
565
|
const args = process.argv.slice(2);
|
|
173
566
|
|
|
174
|
-
// Check for --version / -v (D-10, D-11) — short-circuit BEFORE detectRuntime/validation.
|
|
175
567
|
if (args.includes('--version') || args.includes('-v')) {
|
|
176
568
|
process.stdout.write(`${VERSION}\n`);
|
|
177
569
|
process.exit(0);
|
|
178
570
|
}
|
|
179
571
|
|
|
180
|
-
// Check for --help
|
|
181
572
|
if (args.includes('--help') || args.includes('-h')) {
|
|
182
573
|
console.log(`
|
|
183
574
|
takeToMarket installer
|
|
@@ -185,35 +576,24 @@ takeToMarket installer
|
|
|
185
576
|
Usage: npx taketomarket [options]
|
|
186
577
|
|
|
187
578
|
Options:
|
|
188
|
-
--runtime <claude|codex> Target runtime
|
|
189
|
-
--
|
|
579
|
+
--runtime <claude|codex> Target a specific runtime, skip interactive prompt
|
|
580
|
+
--check Show install status without installing
|
|
581
|
+
--yes, -y Skip confirmation prompt (for CI/scripted use)
|
|
582
|
+
--dry-run Validate source package without writing files
|
|
583
|
+
--version, -v Print version
|
|
190
584
|
--help, -h Show this help message
|
|
191
585
|
`);
|
|
192
586
|
process.exit(0);
|
|
193
587
|
}
|
|
194
588
|
|
|
195
|
-
|
|
196
|
-
|
|
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);
|
|
589
|
+
if (args.includes('--check')) {
|
|
590
|
+
checkStatus(VERSION);
|
|
591
|
+
// checkStatus calls process.exit(0)
|
|
207
592
|
}
|
|
208
593
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
console.log(`Target: ${targetDir}`);
|
|
213
|
-
console.log('');
|
|
214
|
-
|
|
215
|
-
if (DRY_RUN) {
|
|
216
|
-
// Validate source completeness without writing
|
|
594
|
+
if (args.includes('--dry-run')) {
|
|
595
|
+
console.log('');
|
|
596
|
+
console.log(`takeToMarket installer v${VERSION}`);
|
|
217
597
|
console.log('[DRY RUN] Validating source package...');
|
|
218
598
|
console.log('');
|
|
219
599
|
const results = validateInstall(PACKAGE_ROOT);
|
|
@@ -223,59 +603,98 @@ Options:
|
|
|
223
603
|
process.exit(0);
|
|
224
604
|
}
|
|
225
605
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
606
|
+
console.log('');
|
|
607
|
+
console.log(`takeToMarket installer v${VERSION}`);
|
|
608
|
+
|
|
609
|
+
const yesFlag = args.includes('--yes') || args.includes('-y');
|
|
610
|
+
|
|
611
|
+
// Runtime selection
|
|
612
|
+
const targets = await promptRuntimeSelection(args);
|
|
613
|
+
|
|
614
|
+
if (targets.length === 0) {
|
|
615
|
+
console.log('No runtimes selected. Exiting.');
|
|
616
|
+
process.exit(0);
|
|
231
617
|
}
|
|
232
618
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
619
|
+
// Confirmation
|
|
620
|
+
await confirmInstall(targets, VERSION, yesFlag);
|
|
621
|
+
|
|
622
|
+
// Install loop
|
|
623
|
+
const results = [];
|
|
624
|
+
for (const target of targets) {
|
|
625
|
+
console.log('');
|
|
626
|
+
console.log(`Installing to ${target.label}...`);
|
|
627
|
+
|
|
628
|
+
if (target.parentDir && !dirExists(target.parentDir)) {
|
|
629
|
+
console.warn(` Warning: ${target.label} doesn't appear to be installed (${target.parentDir} not found).`);
|
|
630
|
+
console.warn(' Installing anyway — files will be ready when you install the runtime.');
|
|
241
631
|
}
|
|
242
|
-
}
|
|
243
632
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
633
|
+
try {
|
|
634
|
+
if (dirExists(target.dir)) {
|
|
635
|
+
console.log(' Existing installation found. Removing before reinstall...');
|
|
636
|
+
fs.rmSync(target.dir, { recursive: true, force: true });
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
for (const dir of DIRS_TO_COPY) {
|
|
640
|
+
const srcDir = path.join(PACKAGE_ROOT, dir);
|
|
641
|
+
if (dirExists(srcDir)) {
|
|
642
|
+
console.log(` Copying ${dir}/`);
|
|
643
|
+
copyDirSync(srcDir, path.join(target.dir, dir));
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
for (const file of FILES_TO_COPY) {
|
|
648
|
+
const srcFile = path.join(PACKAGE_ROOT, file);
|
|
649
|
+
if (fileExists(srcFile)) {
|
|
650
|
+
console.log(` Copying ${file}`);
|
|
651
|
+
const destFile = path.join(target.dir, file);
|
|
652
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
653
|
+
fs.copyFileSync(srcFile, destFile);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (target.register) {
|
|
658
|
+
registerPlugin(target.dir, VERSION);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const validation = validateInstall(target.dir);
|
|
662
|
+
printResults(validation);
|
|
663
|
+
const failures = validation.filter(r => r.status === 'fail');
|
|
664
|
+
|
|
665
|
+
if (failures.length > 0) {
|
|
666
|
+
results.push({ target, success: false, reason: 'validation failed' });
|
|
667
|
+
} else {
|
|
668
|
+
if (target.partial) {
|
|
669
|
+
console.log(` [PARTIAL] ${target.label}: files copied — slash command registration coming in a future update`);
|
|
670
|
+
}
|
|
671
|
+
results.push({ target, success: true });
|
|
672
|
+
}
|
|
673
|
+
} catch (err) {
|
|
674
|
+
console.error(` Error: ${err.message}`);
|
|
675
|
+
results.push({ target, success: false, reason: err.message });
|
|
254
676
|
}
|
|
255
677
|
}
|
|
256
678
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const results = validateInstall(targetDir);
|
|
261
|
-
printResults(results);
|
|
679
|
+
// Summary
|
|
680
|
+
const successes = results.filter(r => r.success);
|
|
681
|
+
const failures = results.filter(r => !r.success);
|
|
262
682
|
|
|
263
|
-
|
|
264
|
-
|
|
683
|
+
if (successes.length > 0) {
|
|
684
|
+
printInstallSummary();
|
|
685
|
+
}
|
|
265
686
|
|
|
266
687
|
if (failures.length > 0) {
|
|
267
|
-
console.log('
|
|
268
|
-
|
|
688
|
+
console.log('');
|
|
689
|
+
console.log('Failed runtimes:');
|
|
690
|
+
for (const f of failures) {
|
|
691
|
+
console.log(` ${f.target.label}: ${f.reason}`);
|
|
692
|
+
}
|
|
693
|
+
console.log('');
|
|
694
|
+
console.log('Something went wrong? File an issue: https://github.com/ranjanrishikesh/takeToMarket/issues');
|
|
269
695
|
}
|
|
270
696
|
|
|
271
|
-
|
|
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');
|
|
697
|
+
process.exit(failures.length === results.length ? 1 : 0);
|
|
279
698
|
}
|
|
280
699
|
|
|
281
700
|
/**
|
|
@@ -291,12 +710,15 @@ function printResults(results) {
|
|
|
291
710
|
}
|
|
292
711
|
|
|
293
712
|
if (require.main === module) {
|
|
294
|
-
main()
|
|
713
|
+
main().catch(err => {
|
|
714
|
+
console.error(`Fatal: ${err.message}`);
|
|
715
|
+
console.log('Something went wrong? File an issue: https://github.com/ranjanrishikesh/takeToMarket/issues');
|
|
716
|
+
process.exit(1);
|
|
717
|
+
});
|
|
295
718
|
}
|
|
296
719
|
|
|
297
720
|
module.exports = {
|
|
298
721
|
main,
|
|
299
|
-
detectRuntime,
|
|
300
722
|
validateInstall,
|
|
301
723
|
copyDirSync,
|
|
302
724
|
dirExists,
|
|
@@ -304,4 +726,15 @@ module.exports = {
|
|
|
304
726
|
printResults,
|
|
305
727
|
DIRS_TO_COPY,
|
|
306
728
|
FILES_TO_COPY,
|
|
729
|
+
registerPlugin,
|
|
730
|
+
parseRuntimeChoices,
|
|
731
|
+
buildRuntimeTargets,
|
|
732
|
+
getInstalledRuntimes,
|
|
733
|
+
readSkillDescriptions,
|
|
734
|
+
shouldProceed,
|
|
735
|
+
getClaudeStatus,
|
|
736
|
+
checkStatus,
|
|
737
|
+
confirmInstall,
|
|
738
|
+
printInstallSummary,
|
|
739
|
+
PACKAGE_ROOT,
|
|
307
740
|
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taketomarket",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Marketing operating system for Claude Code. Spec-driven campaigns with positioning-as-invariant enforcement, quality gate walls, and compound learnings.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/
|
|
8
|
+
"url": "git+https://github.com/ranjanrishikesh/takeToMarket.git"
|
|
9
9
|
},
|
|
10
|
-
"homepage": "https://github.com/
|
|
10
|
+
"homepage": "https://github.com/ranjanrishikesh/takeToMarket#readme",
|
|
11
11
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/
|
|
12
|
+
"url": "https://github.com/ranjanrishikesh/takeToMarket/issues"
|
|
13
13
|
},
|
|
14
14
|
"author": "Rishikesh Ranjan <59333266+ranjanrishikesh@users.noreply.github.com>",
|
|
15
15
|
"bin": {
|