mango-lollipop 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.
- package/CLAUDE.md +69 -0
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/bin/mango-lollipop.js +385 -0
- package/dist/excel.d.ts +4 -0
- package/dist/excel.js +342 -0
- package/dist/html.d.ts +4 -0
- package/dist/html.js +938 -0
- package/dist/schema.d.ts +120 -0
- package/dist/schema.js +211 -0
- package/lib/excel.ts +433 -0
- package/lib/html.ts +993 -0
- package/lib/schema.ts +394 -0
- package/package.json +44 -0
- package/skills/audit/SKILL.md +248 -0
- package/skills/dev-handoff/SKILL.md +295 -0
- package/skills/generate-dashboard/SKILL.md +195 -0
- package/skills/generate-matrix/SKILL.md +374 -0
- package/skills/generate-messages/SKILL.md +262 -0
- package/skills/iterate/SKILL.md +242 -0
- package/skills/start/SKILL.md +310 -0
- package/templates/copywriting-guide.md +155 -0
- package/templates/dashboard.html +522 -0
- package/templates/events/saas-collaboration.yaml +50 -0
- package/templates/events/saas-document.yaml +44 -0
- package/templates/events/saas-general.yaml +38 -0
- package/templates/events/saas-marketplace.yaml +48 -0
- package/templates/overview.html +598 -0
- package/templates/saas-matrix.json +172 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'fs';
|
|
5
|
+
import { resolve, join, dirname } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
const STAGES = ['TX', 'AQ', 'AC', 'RV', 'RT', 'RF'];
|
|
14
|
+
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Helper Functions
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Search for a project file by name, checking common locations:
|
|
21
|
+
* 1. Current directory
|
|
22
|
+
* 2. output/ subdirectories
|
|
23
|
+
*/
|
|
24
|
+
function findProjectFile(filename) {
|
|
25
|
+
// Check current directory
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
const direct = join(cwd, filename);
|
|
28
|
+
if (existsSync(direct)) return direct;
|
|
29
|
+
|
|
30
|
+
// Check output/ subdirectories
|
|
31
|
+
const outputDir = join(cwd, 'output');
|
|
32
|
+
if (existsSync(outputDir)) {
|
|
33
|
+
try {
|
|
34
|
+
const projects = readdirSync(outputDir, { withFileTypes: true });
|
|
35
|
+
for (const entry of projects) {
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
const candidate = join(outputDir, entry.name, filename);
|
|
38
|
+
if (existsSync(candidate)) return candidate;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// ignore read errors
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Find the project directory containing the given file.
|
|
51
|
+
*/
|
|
52
|
+
function findProjectDir(filename) {
|
|
53
|
+
const filePath = findProjectFile(filename);
|
|
54
|
+
if (!filePath) return null;
|
|
55
|
+
return dirname(filePath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load and parse a mango-lollipop.json config file.
|
|
60
|
+
* Searches current directory and output/ subdirectories.
|
|
61
|
+
*/
|
|
62
|
+
function loadConfig() {
|
|
63
|
+
const configPath = findProjectFile('mango-lollipop.json');
|
|
64
|
+
if (!configPath) {
|
|
65
|
+
console.log('No mango-lollipop.json found. Run `mango-lollipop init <name>` first.');
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
70
|
+
return JSON.parse(raw);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.log(`Error reading config: ${err.message}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Group an array of objects by a key.
|
|
79
|
+
*/
|
|
80
|
+
function groupBy(arr, key) {
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const item of arr) {
|
|
83
|
+
const val = item[key];
|
|
84
|
+
if (!result[val]) result[val] = [];
|
|
85
|
+
result[val].push(item);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Count occurrences of each value in an array.
|
|
92
|
+
*/
|
|
93
|
+
function countBy(arr) {
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const item of arr) {
|
|
96
|
+
result[item] = (result[item] || 0) + 1;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// -----------------------------------------------------------------------------
|
|
102
|
+
// CLI Program
|
|
103
|
+
// -----------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
program
|
|
106
|
+
.name('mango-lollipop')
|
|
107
|
+
.description('AI-powered lifecycle messaging generator')
|
|
108
|
+
.version('0.1.0');
|
|
109
|
+
|
|
110
|
+
// --- init -------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
program
|
|
113
|
+
.command('init [name]')
|
|
114
|
+
.description('Initialize a new Mango Lollipop project')
|
|
115
|
+
.action((name = 'my-project') => {
|
|
116
|
+
const dir = resolve(`output/${name}`);
|
|
117
|
+
mkdirSync(dir, { recursive: true });
|
|
118
|
+
|
|
119
|
+
// Create message folders per stage
|
|
120
|
+
for (const stage of STAGES) {
|
|
121
|
+
mkdirSync(`${dir}/messages/${stage}`, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Create initial config
|
|
125
|
+
const config = {
|
|
126
|
+
name,
|
|
127
|
+
version: '0.1.0',
|
|
128
|
+
created: new Date().toISOString(),
|
|
129
|
+
stage: 'initialized',
|
|
130
|
+
path: null,
|
|
131
|
+
channels: [],
|
|
132
|
+
analysis: null,
|
|
133
|
+
matrix: null,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
writeFileSync(`${dir}/mango-lollipop.json`, JSON.stringify(config, null, 2));
|
|
137
|
+
|
|
138
|
+
console.log(`Mango Lollipop project "${name}" initialized at ${dir}`);
|
|
139
|
+
console.log();
|
|
140
|
+
console.log('Next step: Run the start skill in Claude Code:');
|
|
141
|
+
console.log(` cd ${dir}`);
|
|
142
|
+
console.log(' claude "Read the start skill and help me set up lifecycle messaging"');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// --- generate ---------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
program
|
|
148
|
+
.command('generate')
|
|
149
|
+
.description('Generate the full lifecycle messaging system')
|
|
150
|
+
.action(() => {
|
|
151
|
+
const config = loadConfig();
|
|
152
|
+
if (!config) return;
|
|
153
|
+
|
|
154
|
+
if (!config.analysis) {
|
|
155
|
+
console.log('No analysis found. Run the start skill first:');
|
|
156
|
+
console.log(' claude "Read the start skill and help me set up lifecycle messaging"');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`Generating lifecycle messaging for "${config.name}"...`);
|
|
161
|
+
console.log();
|
|
162
|
+
console.log('Run these skills in Claude Code in order:');
|
|
163
|
+
console.log();
|
|
164
|
+
console.log(' 1. Generate matrix:');
|
|
165
|
+
console.log(' claude "Read the generate-matrix skill and build the lifecycle matrix"');
|
|
166
|
+
console.log();
|
|
167
|
+
console.log(' 2. Generate message copy:');
|
|
168
|
+
console.log(' claude "Read the generate-messages skill and write all message copy"');
|
|
169
|
+
console.log();
|
|
170
|
+
console.log(' 3. Generate visuals:');
|
|
171
|
+
console.log(' claude "Read the generate-dashboard skill and create the dashboard and journey map"');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// --- audit ------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.command('audit')
|
|
178
|
+
.description('Audit existing lifecycle messaging')
|
|
179
|
+
.action(() => {
|
|
180
|
+
console.log('Starting lifecycle messaging audit...');
|
|
181
|
+
console.log();
|
|
182
|
+
console.log('Run the audit skill in Claude Code:');
|
|
183
|
+
console.log(' claude "Read the audit skill and help me audit my existing lifecycle messaging"');
|
|
184
|
+
console.log();
|
|
185
|
+
console.log('Have your existing messages ready to paste or upload.');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// --- view -------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
program
|
|
191
|
+
.command('view')
|
|
192
|
+
.description('Open the visual dashboard in your browser')
|
|
193
|
+
.action(async () => {
|
|
194
|
+
const dashboard = findProjectFile('dashboard.html');
|
|
195
|
+
if (dashboard) {
|
|
196
|
+
console.log(`Opening dashboard: ${dashboard}`);
|
|
197
|
+
await open(dashboard);
|
|
198
|
+
} else {
|
|
199
|
+
console.log('No dashboard found. Run `mango-lollipop generate` first.');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// --- export -----------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
program
|
|
206
|
+
.command('export <type>')
|
|
207
|
+
.description('Generate outputs from project data (excel, html, messages)')
|
|
208
|
+
.option('-p, --project <dir>', 'Project directory (auto-detected if omitted)')
|
|
209
|
+
.action(async (type, opts) => {
|
|
210
|
+
const projectDir = opts.project || findProjectDir('matrix.json');
|
|
211
|
+
if (!projectDir) {
|
|
212
|
+
console.log('No matrix.json found. Run the generate-matrix skill first.');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
switch (type) {
|
|
217
|
+
case 'excel': {
|
|
218
|
+
const matrixPath = join(projectDir, 'matrix.json');
|
|
219
|
+
const analysisPath = join(projectDir, 'analysis.json');
|
|
220
|
+
|
|
221
|
+
if (!existsSync(matrixPath)) {
|
|
222
|
+
console.log(`No matrix.json in ${projectDir}. Run the generate-matrix skill first.`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!existsSync(analysisPath)) {
|
|
226
|
+
console.log(`No analysis.json in ${projectDir}. Run the start skill first.`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const matrix = JSON.parse(readFileSync(matrixPath, 'utf-8'));
|
|
231
|
+
const analysis = JSON.parse(readFileSync(analysisPath, 'utf-8'));
|
|
232
|
+
|
|
233
|
+
// Flatten tag definitions from analysis
|
|
234
|
+
const allTags = [
|
|
235
|
+
...(analysis.tags?.sources || []),
|
|
236
|
+
...(analysis.tags?.plans || []).map(p => `plan:${p}`),
|
|
237
|
+
...(analysis.tags?.segments || []).map(s => `segment:${s}`),
|
|
238
|
+
...(analysis.tags?.features || []).map(f => `feature:${f}`),
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
const { generateMatrixWorkbook, writeWorkbook } = await import(
|
|
242
|
+
resolve(__dirname, '..', 'dist', 'excel.js')
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const wb = generateMatrixWorkbook(matrix.messages, analysis.events, allTags, analysis);
|
|
246
|
+
const outPath = join(projectDir, 'matrix.xlsx');
|
|
247
|
+
writeWorkbook(wb, outPath);
|
|
248
|
+
|
|
249
|
+
console.log(`Excel written: ${outPath}`);
|
|
250
|
+
console.log(`${matrix.messages.length} messages across 6 sheets (Welcome + 5 data sheets)`);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case 'html':
|
|
254
|
+
case 'visuals': {
|
|
255
|
+
const vMatrixPath = join(projectDir, 'matrix.json');
|
|
256
|
+
const vAnalysisPath = join(projectDir, 'analysis.json');
|
|
257
|
+
|
|
258
|
+
if (!existsSync(vMatrixPath)) {
|
|
259
|
+
console.log(`No matrix.json in ${projectDir}. Run the generate-matrix skill first.`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (!existsSync(vAnalysisPath)) {
|
|
263
|
+
console.log(`No analysis.json in ${projectDir}. Run the start skill first.`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const vMatrix = JSON.parse(readFileSync(vMatrixPath, 'utf-8'));
|
|
268
|
+
const vAnalysis = JSON.parse(readFileSync(vAnalysisPath, 'utf-8'));
|
|
269
|
+
|
|
270
|
+
const { generateDashboard, generateOverview, generateMessageViewer } = await import(
|
|
271
|
+
resolve(__dirname, '..', 'dist', 'html.js')
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// 1. Dashboard
|
|
275
|
+
const dashboardHtml = generateDashboard(vMatrix.messages, vAnalysis);
|
|
276
|
+
const dashboardPath = join(projectDir, 'dashboard.html');
|
|
277
|
+
writeFileSync(dashboardPath, dashboardHtml);
|
|
278
|
+
console.log(`Dashboard written: ${dashboardPath}`);
|
|
279
|
+
|
|
280
|
+
// 2. Overview
|
|
281
|
+
const overviewHtml = generateOverview(vMatrix.messages, vAnalysis);
|
|
282
|
+
const overviewPath = join(projectDir, 'overview.html');
|
|
283
|
+
writeFileSync(overviewPath, overviewHtml);
|
|
284
|
+
console.log(`Overview written: ${overviewPath}`);
|
|
285
|
+
|
|
286
|
+
// 3. Message viewer — read message files if they exist
|
|
287
|
+
const msgContentMap = {};
|
|
288
|
+
const messagesDir = join(projectDir, 'messages');
|
|
289
|
+
if (existsSync(messagesDir)) {
|
|
290
|
+
for (const stage of ['TX', 'AQ', 'AC', 'RV', 'RT', 'RF']) {
|
|
291
|
+
const stageDir = join(messagesDir, stage);
|
|
292
|
+
if (existsSync(stageDir)) {
|
|
293
|
+
const files = readdirSync(stageDir).filter(f => f.endsWith('.md'));
|
|
294
|
+
for (const file of files) {
|
|
295
|
+
const raw = readFileSync(join(stageDir, file), 'utf-8');
|
|
296
|
+
const idMatch = file.match(/^([A-Z]+-\d+)/);
|
|
297
|
+
if (idMatch) {
|
|
298
|
+
// Strip YAML frontmatter, keep body only
|
|
299
|
+
const bodyMatch = raw.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
300
|
+
msgContentMap[idMatch[1]] = bodyMatch ? bodyMatch[1].trim() : raw;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const viewerHtml = generateMessageViewer(vMatrix.messages, vAnalysis, msgContentMap);
|
|
308
|
+
const viewerPath = join(projectDir, 'messages.html');
|
|
309
|
+
writeFileSync(viewerPath, viewerHtml);
|
|
310
|
+
console.log(`Message viewer: ${viewerPath}`);
|
|
311
|
+
|
|
312
|
+
console.log(`\n3 visual outputs generated from ${vMatrix.messages.length} messages.`);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'messages':
|
|
316
|
+
console.log('Run in Claude Code:');
|
|
317
|
+
console.log(' claude "Read the generate-messages skill and regenerate all message files"');
|
|
318
|
+
break;
|
|
319
|
+
default:
|
|
320
|
+
console.log(`Unknown export type: "${type}"`);
|
|
321
|
+
console.log('Valid types: excel, html, messages');
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// --- status -----------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
program
|
|
328
|
+
.command('status')
|
|
329
|
+
.description('Show project status')
|
|
330
|
+
.action(() => {
|
|
331
|
+
const config = loadConfig();
|
|
332
|
+
if (!config) return;
|
|
333
|
+
|
|
334
|
+
console.log(`Mango Lollipop Project: ${config.name}`);
|
|
335
|
+
console.log(` Path: ${config.path || 'not set'}`);
|
|
336
|
+
console.log(` Stage: ${config.stage}`);
|
|
337
|
+
console.log(` Channels: ${config.channels.length > 0 ? config.channels.join(', ') : 'not set'}`);
|
|
338
|
+
|
|
339
|
+
if (config.matrix && config.matrix.messages) {
|
|
340
|
+
const msgs = config.matrix.messages;
|
|
341
|
+
|
|
342
|
+
const tx = msgs.filter(m => m.classification === 'transactional');
|
|
343
|
+
const lc = msgs.filter(m => m.classification === 'lifecycle');
|
|
344
|
+
console.log();
|
|
345
|
+
console.log(` Transactional: ${tx.length} messages`);
|
|
346
|
+
console.log(` Lifecycle: ${lc.length} messages`);
|
|
347
|
+
|
|
348
|
+
const stages = groupBy(msgs, 'stage');
|
|
349
|
+
console.log();
|
|
350
|
+
console.log(' By stage:');
|
|
351
|
+
for (const stage of STAGES) {
|
|
352
|
+
if (stages[stage]) {
|
|
353
|
+
console.log(` ${stage}: ${stages[stage].length} messages`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const channels = msgs.flatMap(m => m.channels || []);
|
|
358
|
+
const channelCounts = countBy(channels);
|
|
359
|
+
console.log();
|
|
360
|
+
console.log(' By channel:');
|
|
361
|
+
for (const [channel, count] of Object.entries(channelCounts)) {
|
|
362
|
+
console.log(` ${channel}: ${count} uses`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const tags = msgs.flatMap(m => m.tags || []);
|
|
366
|
+
const tagCounts = countBy(tags);
|
|
367
|
+
const topTags = Object.entries(tagCounts)
|
|
368
|
+
.sort((a, b) => b[1] - a[1])
|
|
369
|
+
.slice(0, 10);
|
|
370
|
+
if (topTags.length) {
|
|
371
|
+
console.log();
|
|
372
|
+
console.log(' Top tags:');
|
|
373
|
+
for (const [tag, count] of topTags) {
|
|
374
|
+
console.log(` ${tag}: ${count}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
console.log();
|
|
379
|
+
console.log(' No matrix generated yet. Run `mango-lollipop generate` to create one.');
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// --- parse ------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
program.parse();
|
package/dist/excel.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import XLSX from "xlsx-js-style";
|
|
2
|
+
import type { Message, EventTaxonomy, Analysis } from "./schema.js";
|
|
3
|
+
export declare function generateMatrixWorkbook(messages: Message[], events: EventTaxonomy, tags: string[], analysis?: Analysis): XLSX.WorkBook;
|
|
4
|
+
export declare function writeWorkbook(workbook: XLSX.WorkBook, filePath: string): void;
|