@dzhechkov/skills-presentation-storyteller 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/LICENSE +21 -0
- package/README.md +53 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/sources.json +19 -0
- package/src/cli.js +107 -0
- package/src/commands/doctor.js +340 -0
- package/src/commands/init.js +168 -0
- package/src/commands/list.js +146 -0
- package/src/commands/remove.js +182 -0
- package/src/commands/update.js +170 -0
- package/src/utils.js +149 -0
- package/templates/.claude/commands/presentation-storyteller.md +23 -0
- package/templates/.claude/skills/explore/SKILL.md +218 -0
- package/templates/.claude/skills/explore/references/questioning-techniques.md +151 -0
- package/templates/.claude/skills/explore/references/task-brief-templates.md +355 -0
- package/templates/.claude/skills/goap-research-ed25519/SKILL.md +418 -0
- package/templates/.claude/skills/goap-research-ed25519/references/ed25519-verification.md +658 -0
- package/templates/.claude/skills/goap-research-ed25519/references/research-actions.md +544 -0
- package/templates/.claude/skills/goap-research-ed25519/references/source-evaluation.md +560 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +662 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +720 -0
- package/templates/.claude/skills/presentation-storyteller/SKILL.md +374 -0
- package/templates/.claude/skills/presentation-storyteller/references/example-presentation.md +273 -0
- package/templates/.claude/skills/presentation-storyteller/references/slide-types.md +426 -0
- package/templates/.claude/skills/presentation-storyteller/references/sources-index-template.md +213 -0
- package/templates/.claude/skills/presentation-storyteller/references/speaker-script-patterns.md +324 -0
- package/templates/.claude/skills/presentation-storyteller/references/storytelling-frameworks.md +270 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
green, yellow, cyan, bold, dim,
|
|
7
|
+
info, success, warn, error: logError, step,
|
|
8
|
+
copyDirRecursive, copyDirFiltered, fileExists, readJSON,
|
|
9
|
+
ensureDir, getRelativePaths, getRelativePathsFiltered,
|
|
10
|
+
createManifest, writeManifest, getTemplatesDir,
|
|
11
|
+
COMPONENTS, MANIFEST_FILE, getComponentFilter,
|
|
12
|
+
} = require('../utils');
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Keysarium integration detection
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const KEYSARIUM_MANIFEST = '.keysarium.json';
|
|
19
|
+
|
|
20
|
+
function detectKeysarium(targetDir) {
|
|
21
|
+
const manifestPath = path.join(targetDir, KEYSARIUM_MANIFEST);
|
|
22
|
+
if (!fileExists(manifestPath)) return null;
|
|
23
|
+
return readJSON(manifestPath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function showKeysariumIntegration(keysariumManifest) {
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log(cyan(' \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510'));
|
|
29
|
+
console.log(cyan(' \u2502') + bold(' @dzhechkov/keysarium detected!') + ' ' + cyan('\u2502'));
|
|
30
|
+
console.log(cyan(' \u2502') + ` Version: ${dim(keysariumManifest.version)}` + ' '.repeat(39 - keysariumManifest.version.length) + cyan('\u2502'));
|
|
31
|
+
console.log(cyan(' \u2502') + ' Presentation Storyteller integrates with existing Keysarium. ' + cyan('\u2502'));
|
|
32
|
+
console.log(cyan(' \u2502') + ' Shared: .claude/commands, skills ' + cyan('\u2502'));
|
|
33
|
+
console.log(cyan(' \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518'));
|
|
34
|
+
console.log('');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function installComponent(key, comp, templatesDir, targetDir) {
|
|
42
|
+
const src = path.join(templatesDir, comp.src);
|
|
43
|
+
const dest = path.join(targetDir, comp.src);
|
|
44
|
+
|
|
45
|
+
if (!fileExists(src)) {
|
|
46
|
+
warn(`Template source not found: ${comp.src} \u2014 skipping.`);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Single file copy (for isFile components)
|
|
51
|
+
if (comp.isFile) {
|
|
52
|
+
ensureDir(path.dirname(dest));
|
|
53
|
+
fs.copyFileSync(src, dest);
|
|
54
|
+
return [comp.src];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const filterFn = getComponentFilter(comp);
|
|
58
|
+
|
|
59
|
+
if (filterFn) {
|
|
60
|
+
copyDirFiltered(src, dest, filterFn);
|
|
61
|
+
return getRelativePathsFiltered(src, filterFn).map((rel) => path.join(comp.src, rel));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
copyDirRecursive(src, dest);
|
|
65
|
+
return getRelativePaths(src).map((rel) => path.join(comp.src, rel));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Main command
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
async function run(options) {
|
|
73
|
+
const { force, dryRun, targetDir } = options;
|
|
74
|
+
const manifestPath = path.join(targetDir, MANIFEST_FILE);
|
|
75
|
+
|
|
76
|
+
// ── a) Check for existing installation ─────────────────────────────────
|
|
77
|
+
if (fileExists(manifestPath)) {
|
|
78
|
+
if (!force) {
|
|
79
|
+
warn('Presentation Storyteller skill pack is already installed in this directory.');
|
|
80
|
+
info(`Run ${cyan('@dzhechkov/skills-presentation-storyteller update')} to update, or use ${yellow('--force')} to overwrite.`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
warn('Existing Presentation Storyteller installation found \u2014 overwriting (--force).');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── b) Detect keysarium integration ────────────────────────────────────
|
|
87
|
+
const keysariumManifest = detectKeysarium(targetDir);
|
|
88
|
+
|
|
89
|
+
if (keysariumManifest) {
|
|
90
|
+
showKeysariumIntegration(keysariumManifest);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── c) Determine components ────────────────────────────────────────────
|
|
94
|
+
const componentKeys = Object.keys(COMPONENTS);
|
|
95
|
+
const templatesDir = getTemplatesDir();
|
|
96
|
+
|
|
97
|
+
// ── d) Show plan ───────────────────────────────────────────────────────
|
|
98
|
+
console.log('');
|
|
99
|
+
info(bold('Installation plan:'));
|
|
100
|
+
console.log('');
|
|
101
|
+
|
|
102
|
+
for (const key of componentKeys) {
|
|
103
|
+
const comp = COMPONENTS[key];
|
|
104
|
+
const filterNote = comp.filter ? dim(` (filtered: ${comp.filter}*)`) : '';
|
|
105
|
+
console.log(` ${green('+')} ${comp.label}${filterNote}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
if (dryRun) {
|
|
111
|
+
warn('Dry run \u2014 no files were written.');
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── e) Install components ──────────────────────────────────────────────
|
|
116
|
+
const totalComponents = componentKeys.length;
|
|
117
|
+
const installedFiles = [];
|
|
118
|
+
let stepNum = 0;
|
|
119
|
+
|
|
120
|
+
for (const key of componentKeys) {
|
|
121
|
+
stepNum++;
|
|
122
|
+
const comp = COMPONENTS[key];
|
|
123
|
+
step(stepNum, totalComponents, `Installing ${comp.label}...`);
|
|
124
|
+
|
|
125
|
+
const files = installComponent(key, comp, templatesDir, targetDir);
|
|
126
|
+
installedFiles.push(...files);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── f) Write manifest ──────────────────────────────────────────────────
|
|
130
|
+
const pkgPath = path.resolve(__dirname, '../../package.json');
|
|
131
|
+
const pkg = readJSON(pkgPath);
|
|
132
|
+
const version = pkg ? pkg.version : '0.0.0';
|
|
133
|
+
|
|
134
|
+
const manifest = createManifest(version, componentKeys, installedFiles.sort());
|
|
135
|
+
|
|
136
|
+
writeManifest(targetDir, manifest);
|
|
137
|
+
info(`Created ${MANIFEST_FILE} manifest`);
|
|
138
|
+
|
|
139
|
+
// ── g) Success summary ─────────────────────────────────────────────────
|
|
140
|
+
console.log('');
|
|
141
|
+
success(bold('Presentation Storyteller skill pack installed!'));
|
|
142
|
+
console.log('');
|
|
143
|
+
|
|
144
|
+
console.log(bold('Installed components:'));
|
|
145
|
+
for (const key of componentKeys) {
|
|
146
|
+
const comp = COMPONENTS[key];
|
|
147
|
+
console.log(` ${green('\u2713')} ${comp.label}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log(bold('Next steps:'));
|
|
152
|
+
console.log(` 1. Open ${cyan('Claude Code')} in this directory`);
|
|
153
|
+
console.log(` 2. Run ${cyan('/presentation-storyteller [topic or question]')} to start analysis`);
|
|
154
|
+
console.log(` 3. The skill orchestrates explore, research, and problem-solving`);
|
|
155
|
+
console.log('');
|
|
156
|
+
|
|
157
|
+
if (keysariumManifest) {
|
|
158
|
+
console.log(bold('Integration:'));
|
|
159
|
+
console.log(` Presentation Storyteller is available alongside your Keysarium pipeline.`);
|
|
160
|
+
console.log(` Use ${cyan('/casarium')} for research, ${cyan('/presentation-storyteller')} for deep analysis.`);
|
|
161
|
+
console.log('');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = run;
|
|
168
|
+
module.exports.run = run;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
green, red, cyan, bold, dim,
|
|
7
|
+
info, warn, error: logError,
|
|
8
|
+
fileExists, readManifest, getRelativePaths, getRelativePathsFiltered,
|
|
9
|
+
COMPONENTS, MANIFEST_FILE, getComponentFilter,
|
|
10
|
+
} = require('../utils');
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function countComponentFiles(comp, targetDir) {
|
|
17
|
+
const destPath = path.join(targetDir, comp.src);
|
|
18
|
+
|
|
19
|
+
if (!fileExists(destPath)) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const stat = fs.statSync(destPath);
|
|
25
|
+
if (stat.isDirectory()) {
|
|
26
|
+
const filterFn = getComponentFilter(comp);
|
|
27
|
+
if (filterFn) {
|
|
28
|
+
return getRelativePathsFiltered(destPath, filterFn).length;
|
|
29
|
+
}
|
|
30
|
+
return getRelativePaths(destPath).length;
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function padRight(str, width) {
|
|
40
|
+
if (str.length >= width) return str.slice(0, width);
|
|
41
|
+
return str + ' '.repeat(width - str.length);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Main command
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
async function run(options) {
|
|
49
|
+
const { targetDir } = options;
|
|
50
|
+
const manifestPath = path.join(targetDir, MANIFEST_FILE);
|
|
51
|
+
|
|
52
|
+
// ── a) Read manifest ──────────────────────────────────────────────────
|
|
53
|
+
if (!fileExists(manifestPath)) {
|
|
54
|
+
warn('Presentation Storyteller skill pack is not installed in this directory.');
|
|
55
|
+
info(`Run ${cyan('@dzhechkov/skills-presentation-storyteller init')} to install.`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const manifest = readManifest(targetDir);
|
|
60
|
+
if (!manifest) {
|
|
61
|
+
logError(`Failed to read ${MANIFEST_FILE} \u2014 file may be corrupted.`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const installedKeys = new Set(manifest.components || []);
|
|
66
|
+
|
|
67
|
+
// ── Header ────────────────────────────────────────────────────────────
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log(bold(`@dzhechkov/skills-presentation-storyteller v${manifest.version}`));
|
|
70
|
+
console.log(`Installed: ${manifest.installedAt ? manifest.installedAt.split('T')[0] : 'unknown'}`);
|
|
71
|
+
if (manifest.updatedAt) {
|
|
72
|
+
console.log(`Updated: ${manifest.updatedAt.split('T')[0]}`);
|
|
73
|
+
}
|
|
74
|
+
console.log('');
|
|
75
|
+
|
|
76
|
+
// ── b) Component table ────────────────────────────────────────────────
|
|
77
|
+
const nameWidth = 40;
|
|
78
|
+
const statusWidth = 18;
|
|
79
|
+
|
|
80
|
+
console.log(
|
|
81
|
+
padRight(bold('Component'), nameWidth) +
|
|
82
|
+
padRight(bold('Status'), statusWidth) +
|
|
83
|
+
bold('Files')
|
|
84
|
+
);
|
|
85
|
+
console.log('\u2500'.repeat(68));
|
|
86
|
+
|
|
87
|
+
const allKeys = Object.keys(COMPONENTS);
|
|
88
|
+
let totalFiles = 0;
|
|
89
|
+
|
|
90
|
+
for (const key of allKeys) {
|
|
91
|
+
const comp = COMPONENTS[key];
|
|
92
|
+
const isInstalled = installedKeys.has(key);
|
|
93
|
+
|
|
94
|
+
let fileCount = 0;
|
|
95
|
+
let statusText;
|
|
96
|
+
|
|
97
|
+
if (isInstalled) {
|
|
98
|
+
fileCount = countComponentFiles(comp, targetDir);
|
|
99
|
+
totalFiles += fileCount;
|
|
100
|
+
|
|
101
|
+
if (fileCount > 0) {
|
|
102
|
+
statusText = green('\u2713 OK');
|
|
103
|
+
} else {
|
|
104
|
+
statusText = red('\u2717 Missing');
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
statusText = dim('\u2717 Not installed');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const shortLabel = comp.label.split('(')[0].trim();
|
|
111
|
+
const filesStr = isInstalled ? `${fileCount} file${fileCount !== 1 ? 's' : ''}` : '';
|
|
112
|
+
const filterTag = comp.filter ? dim(` [${comp.filter}*]`) : '';
|
|
113
|
+
|
|
114
|
+
console.log(
|
|
115
|
+
padRight(shortLabel, nameWidth) +
|
|
116
|
+
padRight(statusText, statusWidth + 9) +
|
|
117
|
+
dim(filesStr) +
|
|
118
|
+
filterTag
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log('\u2500'.repeat(68));
|
|
123
|
+
console.log(
|
|
124
|
+
padRight(bold('Total'), nameWidth) +
|
|
125
|
+
padRight('', statusWidth) +
|
|
126
|
+
bold(`${totalFiles} files`)
|
|
127
|
+
);
|
|
128
|
+
console.log('');
|
|
129
|
+
|
|
130
|
+
// ── c) Integration info ───────────────────────────────────────────────
|
|
131
|
+
const keysariumPath = path.join(targetDir, '.keysarium.json');
|
|
132
|
+
if (fileExists(keysariumPath)) {
|
|
133
|
+
info(`Keysarium integration: ${green('active')}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const btoPath = path.join(targetDir, '.skills-bto.json');
|
|
137
|
+
if (fileExists(btoPath)) {
|
|
138
|
+
info(`BTO integration: ${green('active')}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('');
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = run;
|
|
146
|
+
module.exports.run = run;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const {
|
|
7
|
+
green, red, yellow, cyan, bold, dim,
|
|
8
|
+
info, success, warn, error: logError, step,
|
|
9
|
+
fileExists, readManifest,
|
|
10
|
+
MANIFEST_FILE,
|
|
11
|
+
} = require('../utils');
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Helpers
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
function confirmPrompt(question) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const rl = readline.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
rl.question(question, (answer) => {
|
|
25
|
+
rl.close();
|
|
26
|
+
const normalized = answer.trim().toLowerCase();
|
|
27
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function removeFile(filePath) {
|
|
33
|
+
try {
|
|
34
|
+
if (fileExists(filePath)) {
|
|
35
|
+
fs.unlinkSync(filePath);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
warn(`Could not remove ${filePath}: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function removeEmptyDirs(dirPath, stopDir) {
|
|
45
|
+
try {
|
|
46
|
+
let current = dirPath;
|
|
47
|
+
while (current !== stopDir && current !== path.dirname(current)) {
|
|
48
|
+
if (!fileExists(current)) {
|
|
49
|
+
current = path.dirname(current);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const stat = fs.statSync(current);
|
|
54
|
+
if (!stat.isDirectory()) break;
|
|
55
|
+
|
|
56
|
+
const entries = fs.readdirSync(current);
|
|
57
|
+
if (entries.length === 0) {
|
|
58
|
+
fs.rmdirSync(current);
|
|
59
|
+
current = path.dirname(current);
|
|
60
|
+
} else {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Silently ignore — directory cleanup is best-effort
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Main command
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
async function run(options) {
|
|
74
|
+
const { force, dryRun, targetDir } = options;
|
|
75
|
+
const manifestPath = path.join(targetDir, MANIFEST_FILE);
|
|
76
|
+
|
|
77
|
+
// ── a) Read manifest ──────────────────────────────────────────────────
|
|
78
|
+
if (!fileExists(manifestPath)) {
|
|
79
|
+
warn('Presentation Storyteller skill pack is not installed in this directory \u2014 nothing to remove.');
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const manifest = readManifest(targetDir);
|
|
84
|
+
if (!manifest) {
|
|
85
|
+
logError(`Failed to read ${MANIFEST_FILE} \u2014 file may be corrupted.`);
|
|
86
|
+
info(`You can manually delete ${MANIFEST_FILE} and the installed files.`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const files = manifest.files || [];
|
|
91
|
+
const components = manifest.components || [];
|
|
92
|
+
|
|
93
|
+
// ── b) Show what will be removed ──────────────────────────────────────
|
|
94
|
+
console.log('');
|
|
95
|
+
info(bold('The following Presentation Storyteller components will be removed:'));
|
|
96
|
+
console.log('');
|
|
97
|
+
|
|
98
|
+
for (const key of components) {
|
|
99
|
+
console.log(` ${red('-')} ${key}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(` ${dim(`${files.length} file(s) total`)}`);
|
|
104
|
+
console.log(` ${dim(`+ ${MANIFEST_FILE} manifest`)}`);
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
const keysariumPath = path.join(targetDir, '.keysarium.json');
|
|
108
|
+
if (fileExists(keysariumPath)) {
|
|
109
|
+
info('@dzhechkov/keysarium detected \u2014 shared directories will be preserved.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (dryRun) {
|
|
113
|
+
console.log(bold('Files to be removed:'));
|
|
114
|
+
for (const relPath of files) {
|
|
115
|
+
const absPath = path.join(targetDir, relPath);
|
|
116
|
+
const exists = fileExists(absPath);
|
|
117
|
+
const marker = exists ? red('- DEL') : dim('- N/A');
|
|
118
|
+
console.log(` ${marker} ${relPath}`);
|
|
119
|
+
}
|
|
120
|
+
console.log(` ${red('- DEL')} ${MANIFEST_FILE}`);
|
|
121
|
+
console.log('');
|
|
122
|
+
warn('Dry run \u2014 no files were removed.');
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── c) Confirm unless --force ─────────────────────────────────────────
|
|
127
|
+
if (!force) {
|
|
128
|
+
const confirmed = await confirmPrompt(
|
|
129
|
+
yellow('This will remove all Presentation Storyteller skill pack files. Continue? (y/N) ')
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (!confirmed) {
|
|
133
|
+
info('Aborted \u2014 no files were removed.');
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── d) Remove files ───────────────────────────────────────────────────
|
|
139
|
+
let removedCount = 0;
|
|
140
|
+
let skippedCount = 0;
|
|
141
|
+
const dirsToCheck = new Set();
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < files.length; i++) {
|
|
144
|
+
const relPath = files[i];
|
|
145
|
+
const absPath = path.join(targetDir, relPath);
|
|
146
|
+
|
|
147
|
+
step(i + 1, files.length, `Removing ${relPath}`);
|
|
148
|
+
|
|
149
|
+
if (removeFile(absPath)) {
|
|
150
|
+
removedCount++;
|
|
151
|
+
dirsToCheck.add(path.dirname(absPath));
|
|
152
|
+
} else {
|
|
153
|
+
skippedCount++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Remove empty directories (bottom-up)
|
|
158
|
+
const sortedDirs = Array.from(dirsToCheck).sort((a, b) => b.length - a.length);
|
|
159
|
+
for (const dir of sortedDirs) {
|
|
160
|
+
removeEmptyDirs(dir, targetDir);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── e) Remove manifest ────────────────────────────────────────────────
|
|
164
|
+
removeFile(manifestPath);
|
|
165
|
+
info(`Removed ${MANIFEST_FILE} manifest`);
|
|
166
|
+
|
|
167
|
+
// ── f) Summary ────────────────────────────────────────────────────────
|
|
168
|
+
console.log('');
|
|
169
|
+
success(bold('Presentation Storyteller skill pack removal complete!'));
|
|
170
|
+
console.log(` ${green('\u2713')} ${removedCount} file(s) removed`);
|
|
171
|
+
if (skippedCount > 0) {
|
|
172
|
+
console.log(` ${dim('-')} ${skippedCount} file(s) already missing (skipped)`);
|
|
173
|
+
}
|
|
174
|
+
console.log('');
|
|
175
|
+
info(`To reinstall, run: ${cyan('@dzhechkov/skills-presentation-storyteller init')}`);
|
|
176
|
+
console.log('');
|
|
177
|
+
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = run;
|
|
182
|
+
module.exports.run = run;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
green, yellow, cyan, bold, dim,
|
|
7
|
+
info, success, warn, error: logError, step,
|
|
8
|
+
copyDirRecursive, copyDirFiltered, fileExists, readJSON,
|
|
9
|
+
ensureDir, getRelativePaths, getRelativePathsFiltered, diffFiles,
|
|
10
|
+
readManifest, writeManifest, getTemplatesDir,
|
|
11
|
+
COMPONENTS, MANIFEST_FILE, getComponentFilter,
|
|
12
|
+
} = require('../utils');
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Main command
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
async function run(options) {
|
|
19
|
+
const { dryRun, targetDir } = options;
|
|
20
|
+
const manifestPath = path.join(targetDir, MANIFEST_FILE);
|
|
21
|
+
|
|
22
|
+
// ── a) Read existing manifest ─────────────────────────────────────────
|
|
23
|
+
if (!fileExists(manifestPath)) {
|
|
24
|
+
logError('Presentation Storyteller skill pack is not installed in this directory.');
|
|
25
|
+
info(`Run ${cyan('@dzhechkov/skills-presentation-storyteller init')} to install.`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const manifest = readManifest(targetDir);
|
|
30
|
+
if (!manifest) {
|
|
31
|
+
logError(`Failed to read ${MANIFEST_FILE} \u2014 file may be corrupted.`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const templatesDir = getTemplatesDir();
|
|
36
|
+
|
|
37
|
+
// ── b) Get installed components ───────────────────────────────────────
|
|
38
|
+
const installedKeys = manifest.components || [];
|
|
39
|
+
if (installedKeys.length === 0) {
|
|
40
|
+
warn('Manifest lists no installed components. Consider running init instead.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
info(`Current version: ${dim(manifest.version)}`);
|
|
45
|
+
const pkgPath = path.resolve(__dirname, '../../package.json');
|
|
46
|
+
const pkg = readJSON(pkgPath);
|
|
47
|
+
const newVersion = pkg ? pkg.version : manifest.version;
|
|
48
|
+
info(`Available version: ${bold(newVersion)}`);
|
|
49
|
+
console.log('');
|
|
50
|
+
|
|
51
|
+
// ── c) Diff each component ────────────────────────────────────────────
|
|
52
|
+
let totalAdded = 0;
|
|
53
|
+
let totalModified = 0;
|
|
54
|
+
let totalUnchanged = 0;
|
|
55
|
+
const filesToCopy = [];
|
|
56
|
+
|
|
57
|
+
for (const key of installedKeys) {
|
|
58
|
+
const comp = COMPONENTS[key];
|
|
59
|
+
if (!comp) {
|
|
60
|
+
warn(`Unknown component "${key}" in manifest \u2014 skipping.`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const srcBase = path.join(templatesDir, comp.src);
|
|
65
|
+
const destBase = path.join(targetDir, comp.src);
|
|
66
|
+
|
|
67
|
+
if (!fileExists(srcBase)) {
|
|
68
|
+
warn(`Template source not found for "${key}" \u2014 skipping.`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const filterFn = getComponentFilter(comp);
|
|
73
|
+
const diff = diffFiles(srcBase, destBase, filterFn);
|
|
74
|
+
|
|
75
|
+
for (const rel of diff.added) {
|
|
76
|
+
filesToCopy.push({
|
|
77
|
+
src: path.join(srcBase, rel),
|
|
78
|
+
dest: path.join(destBase, rel),
|
|
79
|
+
status: 'added',
|
|
80
|
+
relPath: path.join(comp.src, rel),
|
|
81
|
+
});
|
|
82
|
+
totalAdded++;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const rel of diff.modified) {
|
|
86
|
+
filesToCopy.push({
|
|
87
|
+
src: path.join(srcBase, rel),
|
|
88
|
+
dest: path.join(destBase, rel),
|
|
89
|
+
status: 'modified',
|
|
90
|
+
relPath: path.join(comp.src, rel),
|
|
91
|
+
});
|
|
92
|
+
totalModified++;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
totalUnchanged += diff.unchanged.length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Show diff summary ─────────────────────────────────────────────────
|
|
99
|
+
info(bold('Update summary:'));
|
|
100
|
+
console.log(` ${green('+')} ${totalAdded} file(s) to add`);
|
|
101
|
+
console.log(` ${yellow('~')} ${totalModified} file(s) to update`);
|
|
102
|
+
console.log(` ${dim('=')} ${totalUnchanged} file(s) unchanged`);
|
|
103
|
+
console.log('');
|
|
104
|
+
|
|
105
|
+
if (totalAdded === 0 && totalModified === 0) {
|
|
106
|
+
success('Everything is up to date!');
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (dryRun) {
|
|
111
|
+
console.log(bold('Files to be changed:'));
|
|
112
|
+
for (const f of filesToCopy) {
|
|
113
|
+
const marker = f.status === 'added' ? green('+ ADD') : yellow('~ MOD');
|
|
114
|
+
console.log(` ${marker} ${f.relPath}`);
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
warn('Dry run \u2014 no files were written.');
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── d) Copy updated files ─────────────────────────────────────────────
|
|
122
|
+
for (let i = 0; i < filesToCopy.length; i++) {
|
|
123
|
+
const f = filesToCopy[i];
|
|
124
|
+
const label = f.status === 'added' ? 'Adding' : 'Updating';
|
|
125
|
+
step(i + 1, filesToCopy.length, `${label} ${f.relPath}`);
|
|
126
|
+
|
|
127
|
+
ensureDir(path.dirname(f.dest));
|
|
128
|
+
fs.copyFileSync(f.src, f.dest);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── e) Update manifest ────────────────────────────────────────────────
|
|
132
|
+
const allFiles = [];
|
|
133
|
+
for (const key of installedKeys) {
|
|
134
|
+
const comp = COMPONENTS[key];
|
|
135
|
+
if (!comp) continue;
|
|
136
|
+
|
|
137
|
+
const destPath = path.join(targetDir, comp.src);
|
|
138
|
+
const srcPath = path.join(templatesDir, comp.src);
|
|
139
|
+
const filterFn = getComponentFilter(comp);
|
|
140
|
+
const scanBase = fileExists(srcPath) ? srcPath : destPath;
|
|
141
|
+
|
|
142
|
+
if (fileExists(destPath)) {
|
|
143
|
+
// Scan the TEMPLATE source, not the destination, so user files aren't adopted.
|
|
144
|
+
const paths = filterFn
|
|
145
|
+
? getRelativePathsFiltered(scanBase, filterFn)
|
|
146
|
+
: getRelativePaths(scanBase);
|
|
147
|
+
allFiles.push(...paths.map((rel) => path.join(comp.src, rel)));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
manifest.version = newVersion;
|
|
152
|
+
manifest.updatedAt = new Date().toISOString();
|
|
153
|
+
manifest.files = allFiles.sort();
|
|
154
|
+
|
|
155
|
+
writeManifest(targetDir, manifest);
|
|
156
|
+
info(`Updated ${MANIFEST_FILE} manifest`);
|
|
157
|
+
|
|
158
|
+
// ── f) Summary ────────────────────────────────────────────────────────
|
|
159
|
+
console.log('');
|
|
160
|
+
success(bold('Update complete!'));
|
|
161
|
+
console.log(` ${green('+')} ${totalAdded} file(s) added`);
|
|
162
|
+
console.log(` ${yellow('~')} ${totalModified} file(s) updated`);
|
|
163
|
+
console.log(` ${dim('=')} ${totalUnchanged} file(s) unchanged`);
|
|
164
|
+
console.log('');
|
|
165
|
+
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = run;
|
|
170
|
+
module.exports.run = run;
|