codymaster 4.5.1 → 4.5.3
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/dist/index.js +37 -27
- package/dist/ui/hamster.js +2 -2
- package/dist/ui/onboarding.js +18 -5
- package/install.sh +32 -9
- package/package.json +2 -1
- package/scripts/gate-0-secrets.js +63 -0
- package/scripts/gate-1-syntax.js +53 -0
- package/scripts/gate-5-dist-verify.js +55 -0
- package/scripts/gate-6-smoke-test.js +30 -0
- package/scripts/index-codebase.sh +552 -0
- package/scripts/mcp-bridge.js +284 -0
- package/scripts/postinstall.js +317 -0
- package/scripts/security-fixer.js +143 -0
- package/scripts/security-scan.js +55 -0
- package/scripts/test-gemini.js +13 -0
- package/scripts/todo-bridge.js +112 -0
package/dist/index.js
CHANGED
|
@@ -71,6 +71,30 @@ const hamster_1 = require("./ui/hamster");
|
|
|
71
71
|
const hooks_1 = require("./ui/hooks");
|
|
72
72
|
const onboarding_1 = require("./ui/onboarding");
|
|
73
73
|
const VERSION = require('../package.json').version;
|
|
74
|
+
let ALL_SKILLS = [];
|
|
75
|
+
try {
|
|
76
|
+
const distSkillsDir = path_1.default.join(__dirname, '..', 'skills');
|
|
77
|
+
if (fs_1.default.existsSync(distSkillsDir)) {
|
|
78
|
+
ALL_SKILLS = fs_1.default.readdirSync(distSkillsDir).filter(f => {
|
|
79
|
+
const fullPath = path_1.default.join(distSkillsDir, f);
|
|
80
|
+
return fs_1.default.statSync(fullPath).isDirectory() && fs_1.default.existsSync(path_1.default.join(fullPath, 'SKILL.md'));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
// Silent fallback
|
|
86
|
+
}
|
|
87
|
+
if (ALL_SKILLS.length === 0) {
|
|
88
|
+
ALL_SKILLS = [
|
|
89
|
+
'cm-tdd', 'cm-debugging', 'cm-quality-gate', 'cm-test-gate', 'cm-code-review',
|
|
90
|
+
'cm-safe-deploy', 'cm-identity-guard', 'cm-git-worktrees', 'cm-terminal', 'cm-secret-shield', 'cm-security-gate', 'cm-safe-i18n',
|
|
91
|
+
'cm-planning', 'cm-ux-master', 'cm-ui-preview', 'cm-brainstorm-idea', 'cm-jtbd', 'cm-dockit', 'cm-project-bootstrap', 'cm-readit',
|
|
92
|
+
'cm-content-factory', 'cm-ads-tracker', 'cro-methodology', 'cm-deep-search',
|
|
93
|
+
'cm-execution', 'cm-continuity', 'cm-skill-index', 'cm-skill-mastery', 'cm-skill-chain',
|
|
94
|
+
'cm-start', 'cm-dashboard', 'cm-status', 'cm-how-it-work', 'cm-example',
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
const SKILL_COUNT = ALL_SKILLS.length;
|
|
74
98
|
// ─── Update Check ───────────────────────────────────────────────────────────
|
|
75
99
|
let _updateMessage = '';
|
|
76
100
|
function checkForUpdates() {
|
|
@@ -134,7 +158,7 @@ function printUpdateNotice() {
|
|
|
134
158
|
function showBanner() {
|
|
135
159
|
const cPath = process.cwd().replace(os_1.default.homedir(), '~');
|
|
136
160
|
const profile = (0, hooks_1.loadProfile)();
|
|
137
|
-
console.log((0, hamster_1.renderHamsterBanner)(profile.userName || undefined, VERSION, cPath));
|
|
161
|
+
console.log((0, hamster_1.renderHamsterBanner)(profile.userName || undefined, VERSION, cPath, SKILL_COUNT));
|
|
138
162
|
printUpdateNotice();
|
|
139
163
|
}
|
|
140
164
|
// ─── Utility ────────────────────────────────────────────────────────────────
|
|
@@ -177,7 +201,7 @@ function postInstallOnboarding(platform) {
|
|
|
177
201
|
message: 'What would you like to do?',
|
|
178
202
|
options: [
|
|
179
203
|
{ label: `${theme_1.ICONS.dashboard} Launch Dashboard`, value: 'dashboard', hint: `localhost:${data_1.DEFAULT_PORT}` },
|
|
180
|
-
{ label: `${theme_1.ICONS.skill} Browse all
|
|
204
|
+
{ label: `${theme_1.ICONS.skill} Browse all ${SKILL_COUNT} skills`, value: 'skills' },
|
|
181
205
|
{ label: `${theme_1.ICONS.deploy} Start with your AI`, value: 'invoke', hint: profile.platform || 'any agent' },
|
|
182
206
|
{ label: `${(0, theme_1.success)('✓')} Done`, value: 'done' },
|
|
183
207
|
],
|
|
@@ -258,7 +282,7 @@ function showInteractiveMenu() {
|
|
|
258
282
|
{ label: `${theme_1.ICONS.dashboard} Dashboard`, value: 'dashboard', hint: isDashboardRunning() ? 'Open' : 'Start & open' },
|
|
259
283
|
{ label: `${theme_1.ICONS.task} My Tasks`, value: 'tasks', hint: `${taskCounts.totalTasks} total` },
|
|
260
284
|
{ label: `📈 Status`, value: 'status', hint: 'Health snapshot' },
|
|
261
|
-
{ label: `${theme_1.ICONS.skill} Browse Skills`, value: 'skills', hint:
|
|
285
|
+
{ label: `${theme_1.ICONS.skill} Browse Skills`, value: 'skills', hint: `${SKILL_COUNT} skills` },
|
|
262
286
|
{ label: `➕ Add a Task`, value: 'addtask', hint: 'Quick add' },
|
|
263
287
|
{ label: `⚡ Install Skills`, value: 'install', hint: 'Update all' },
|
|
264
288
|
{ label: `${theme_1.ICONS.hamster} My Profile`, value: 'profile', hint: `${profile.level}` },
|
|
@@ -318,7 +342,7 @@ function showInteractiveMenu() {
|
|
|
318
342
|
`${(0, theme_1.brand)('cm task list')} ${(0, theme_1.dim)('View tasks')}`,
|
|
319
343
|
`${(0, theme_1.brand)('cm status')} ${(0, theme_1.dim)('Project health')}`,
|
|
320
344
|
`${(0, theme_1.brand)('cm dashboard')} ${(0, theme_1.dim)('Mission Control')}`,
|
|
321
|
-
`${(0, theme_1.brand)('cm list')} ${(0, theme_1.dim)(
|
|
345
|
+
`${(0, theme_1.brand)('cm list')} ${(0, theme_1.dim)(`Browse ${SKILL_COUNT} skills`)}`,
|
|
322
346
|
`${(0, theme_1.brand)('cm deploy')} ${(0, theme_1.dim)('<env>')} ${(0, theme_1.dim)('Record deploy')}`,
|
|
323
347
|
`${(0, theme_1.brand)('cm profile')} ${(0, theme_1.dim)('Your stats')}`,
|
|
324
348
|
];
|
|
@@ -332,7 +356,7 @@ function showInteractiveMenu() {
|
|
|
332
356
|
const program = new commander_1.Command();
|
|
333
357
|
program
|
|
334
358
|
.name('cm')
|
|
335
|
-
.description(
|
|
359
|
+
.description(`Cody — ${SKILL_COUNT} Skills. Ship 10x faster.`)
|
|
336
360
|
.version(VERSION, '-v, --version', 'Show version')
|
|
337
361
|
.argument('[cmd]', 'Command to run', '')
|
|
338
362
|
.action((cmd) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1093,20 +1117,6 @@ program
|
|
|
1093
1117
|
console.log((0, box_1.renderResult)('success', `Skill '${skill}' installed for ${opts.platform}!`));
|
|
1094
1118
|
}));
|
|
1095
1119
|
// ─── Add Command (npx codymaster add --skill cm-debugging) ───────────────────
|
|
1096
|
-
const ALL_SKILLS = [
|
|
1097
|
-
// Engineering
|
|
1098
|
-
'cm-tdd', 'cm-debugging', 'cm-quality-gate', 'cm-test-gate', 'cm-code-review',
|
|
1099
|
-
// Operations
|
|
1100
|
-
'cm-safe-deploy', 'cm-identity-guard', 'cm-git-worktrees', 'cm-terminal', 'cm-secret-shield', 'cm-security-gate', 'cm-safe-i18n',
|
|
1101
|
-
// Product
|
|
1102
|
-
'cm-planning', 'cm-ux-master', 'cm-ui-preview', 'cm-brainstorm-idea', 'cm-jtbd', 'cm-dockit', 'cm-project-bootstrap', 'cm-readit',
|
|
1103
|
-
// Growth
|
|
1104
|
-
'cm-content-factory', 'cm-ads-tracker', 'cro-methodology', 'cm-deep-search',
|
|
1105
|
-
// Orchestration
|
|
1106
|
-
'cm-execution', 'cm-continuity', 'cm-skill-index', 'cm-skill-mastery', 'cm-skill-chain',
|
|
1107
|
-
// Workflow
|
|
1108
|
-
'cm-start', 'cm-dashboard', 'cm-status', 'cm-how-it-work', 'cm-example',
|
|
1109
|
-
];
|
|
1110
1120
|
const PLATFORM_TARGETS = {
|
|
1111
1121
|
gemini: { dir: '.gemini/skills', invoke: '@[/<skill>]', note: 'or ~/.gemini/antigravity/skills/ for global' },
|
|
1112
1122
|
cursor: { dir: '.cursor/rules', invoke: '@<skill>', note: 'Cursor rules directory' },
|
|
@@ -1169,7 +1179,7 @@ function doAddSkills(skills, platform) {
|
|
|
1169
1179
|
const { execFileSync } = require('child_process');
|
|
1170
1180
|
if (platform === 'claude') {
|
|
1171
1181
|
console.log((0, theme_1.brand)('🟣 Claude Code — Installing via plugin system'));
|
|
1172
|
-
console.log((0, theme_1.dim)(' (Claude installs all
|
|
1182
|
+
console.log((0, theme_1.dim)(' (Claude installs all ${SKILL_COUNT} skills as one bundle)\n'));
|
|
1173
1183
|
// Step 1: Register marketplace
|
|
1174
1184
|
console.log((0, theme_1.dim)(' $ claude plugin marketplace add tody-agent/codymaster'));
|
|
1175
1185
|
try {
|
|
@@ -1193,7 +1203,7 @@ function doAddSkills(skills, platform) {
|
|
|
1193
1203
|
console.log((0, theme_1.dim)(' $ claude plugin install codymaster@codymaster'));
|
|
1194
1204
|
try {
|
|
1195
1205
|
execFileSync('claude', ['plugin', 'install', 'codymaster@codymaster'], { stdio: 'inherit' });
|
|
1196
|
-
console.log((0, box_1.renderResult)('success',
|
|
1206
|
+
console.log((0, box_1.renderResult)('success', `All ${SKILL_COUNT} skills installed!`));
|
|
1197
1207
|
yield postInstallOnboarding('claude');
|
|
1198
1208
|
}
|
|
1199
1209
|
catch (_b) {
|
|
@@ -1225,7 +1235,7 @@ function doAddSkills(skills, platform) {
|
|
|
1225
1235
|
}
|
|
1226
1236
|
const icons = { cursor: '🔵', windsurf: '🟠', cline: '⚫', opencode: '📦', kiro: '🔶' };
|
|
1227
1237
|
const icon = icons[platform] || '📦';
|
|
1228
|
-
const label = skills.length === ALL_SKILLS.length ?
|
|
1238
|
+
const label = skills.length === ALL_SKILLS.length ? `all ${SKILL_COUNT} skills` : skills.join(', ');
|
|
1229
1239
|
console.log(`${icon} ${(0, theme_1.brand)(`${platform} — Installing ${label}`)}`);
|
|
1230
1240
|
console.log((0, theme_1.dim)(` Target: ./${target.dir}/\n`));
|
|
1231
1241
|
let ok = 0, fail = 0;
|
|
@@ -1282,7 +1292,7 @@ program
|
|
|
1282
1292
|
.command('add')
|
|
1283
1293
|
.description('Add skills to your AI agent (npx codymaster add --skill cm-debugging)')
|
|
1284
1294
|
.option('--skill <name>', 'Specific skill to add (e.g. cm-debugging)')
|
|
1285
|
-
.option('--all',
|
|
1295
|
+
.option('--all', `Add all ${SKILL_COUNT} skills`)
|
|
1286
1296
|
.option('--platform <platform>', 'Target: claude|gemini|cursor|windsurf|cline|opencode|kiro|copilot')
|
|
1287
1297
|
.option('--list', 'Show available skills and exit')
|
|
1288
1298
|
.action((opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1339,7 +1349,7 @@ program
|
|
|
1339
1349
|
const mode = yield p.select({
|
|
1340
1350
|
message: 'What to install?',
|
|
1341
1351
|
options: [
|
|
1342
|
-
{ label:
|
|
1352
|
+
{ label: `All ${SKILL_COUNT} skills (full kit)`, value: 'all' },
|
|
1343
1353
|
{ label: 'Search & pick one skill', value: 'pick' },
|
|
1344
1354
|
],
|
|
1345
1355
|
});
|
|
@@ -1365,7 +1375,7 @@ program
|
|
|
1365
1375
|
program
|
|
1366
1376
|
.command('list')
|
|
1367
1377
|
.alias('ls')
|
|
1368
|
-
.description(
|
|
1378
|
+
.description(`List all ${SKILL_COUNT} available skills`)
|
|
1369
1379
|
.option('-d, --domain <domain>', 'Filter by domain')
|
|
1370
1380
|
.action((opts) => {
|
|
1371
1381
|
skillList(opts.domain);
|
|
@@ -1978,7 +1988,7 @@ const SKILL_CATALOG = {
|
|
|
1978
1988
|
{ name: 'cm-start', desc: 'Onboarding & session kick-off wizard' },
|
|
1979
1989
|
{ name: 'cm-dashboard', desc: 'Project status & task Kanban board' },
|
|
1980
1990
|
{ name: 'cm-status', desc: 'Quick project health snapshot' },
|
|
1981
|
-
{ name: 'cm-how-it-work', desc:
|
|
1991
|
+
{ name: 'cm-how-it-work', desc: `Interactive explainer for all ${SKILL_COUNT} skills` },
|
|
1982
1992
|
{ name: 'cm-example', desc: 'Minimal template for new skills' },
|
|
1983
1993
|
],
|
|
1984
1994
|
},
|
|
@@ -2024,7 +2034,7 @@ function skillList(filterDomain) {
|
|
|
2024
2034
|
console.log((0, box_1.renderResult)('error', `Domain not found: ${filterDomain}`, [(0, theme_1.dim)('Domains: engineering, operations, product, growth, orchestration, workflow')]));
|
|
2025
2035
|
return;
|
|
2026
2036
|
}
|
|
2027
|
-
console.log((0, box_1.renderCommandHeader)(
|
|
2037
|
+
console.log((0, box_1.renderCommandHeader)(`Cody Master — ${SKILL_COUNT} Skills`, '🧩'));
|
|
2028
2038
|
let total = 0;
|
|
2029
2039
|
for (const [domain, data] of entries) {
|
|
2030
2040
|
console.log((0, theme_1.brand)(` ${data.icon} ${domain.charAt(0).toUpperCase() + domain.slice(1)}`));
|
package/dist/ui/hamster.js
CHANGED
|
@@ -193,7 +193,7 @@ function getErrorGuidance() {
|
|
|
193
193
|
/**
|
|
194
194
|
* Render the full hamster banner with greeting
|
|
195
195
|
*/
|
|
196
|
-
function renderHamsterBanner(userName, version, cwd) {
|
|
196
|
+
function renderHamsterBanner(userName, version, cwd, skillCount = 34) {
|
|
197
197
|
const art = getHamsterArt(getTimeOfDay() === 'night' ? 'sleeping' : 'greeting');
|
|
198
198
|
const greeting = getGreeting(userName);
|
|
199
199
|
const lines = [
|
|
@@ -202,7 +202,7 @@ function renderHamsterBanner(userName, version, cwd) {
|
|
|
202
202
|
'',
|
|
203
203
|
` ${(0, theme_1.brandBold)(greeting)}`,
|
|
204
204
|
'',
|
|
205
|
-
` ${(0, theme_1.dim)('CodyMaster')} ${(0, theme_1.brand)(`v${version || '?'}`)} ${(0, theme_1.dim)('•')} ${(0, theme_1.dim)(
|
|
205
|
+
` ${(0, theme_1.dim)('CodyMaster')} ${(0, theme_1.brand)(`v${version || '?'}`)} ${(0, theme_1.dim)('•')} ${(0, theme_1.dim)(`${skillCount} Skills`)} ${(0, theme_1.dim)('•')} ${(0, theme_1.dim)(cwd || '~')}`,
|
|
206
206
|
(0, theme_1.dim)(' ' + '─'.repeat(50)),
|
|
207
207
|
];
|
|
208
208
|
return lines.join('\n');
|
package/dist/ui/onboarding.js
CHANGED
|
@@ -56,13 +56,26 @@ const theme_2 = require("./theme");
|
|
|
56
56
|
const box_1 = require("./box");
|
|
57
57
|
const hamster_1 = require("./hamster");
|
|
58
58
|
const hooks_1 = require("./hooks");
|
|
59
|
+
let SKILL_COUNT = 33;
|
|
60
|
+
try {
|
|
61
|
+
const fs = require('fs');
|
|
62
|
+
const path = require('path');
|
|
63
|
+
const distSkillsDir = path.join(__dirname, '..', '..', 'skills');
|
|
64
|
+
if (fs.existsSync(distSkillsDir)) {
|
|
65
|
+
SKILL_COUNT = fs.readdirSync(distSkillsDir).filter((f) => {
|
|
66
|
+
const fullPath = path.join(distSkillsDir, f);
|
|
67
|
+
return fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, 'SKILL.md'));
|
|
68
|
+
}).length;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (e) { }
|
|
59
72
|
// ─── Onboarding Steps ──────────────────────────────────────────────────────
|
|
60
73
|
const TOTAL_STEPS = 5;
|
|
61
74
|
const STEP_INFO = {
|
|
62
75
|
1: { title: 'Meet your assistant', desc: 'What should I call you?' },
|
|
63
76
|
2: { title: 'Pick your platform', desc: 'Where do you code?' },
|
|
64
77
|
3: { title: 'Your first task', desc: 'Add something to build' },
|
|
65
|
-
4: { title: 'See the magic', desc:
|
|
78
|
+
4: { title: 'See the magic', desc: `Discover your ${SKILL_COUNT} skills` },
|
|
66
79
|
5: { title: 'You\'re ready!', desc: 'Welcome to the team' },
|
|
67
80
|
};
|
|
68
81
|
/**
|
|
@@ -219,7 +232,7 @@ function runOnboarding(version) {
|
|
|
219
232
|
if (startStep < 4) {
|
|
220
233
|
console.log((0, box_1.renderStepProgress)(4, TOTAL_STEPS));
|
|
221
234
|
console.log('');
|
|
222
|
-
console.log(` ${(0, theme_1.brandBold)(
|
|
235
|
+
console.log(` ${(0, theme_1.brandBold)(`You have ${SKILL_COUNT} superpowers!`)} ${theme_2.ICONS.skill}`);
|
|
223
236
|
console.log(` ${(0, theme_1.dim)('Grouped by what they help you do:')}`);
|
|
224
237
|
console.log('');
|
|
225
238
|
const SKILL_DOMAINS = [
|
|
@@ -267,10 +280,10 @@ function runOnboarding(version) {
|
|
|
267
280
|
}
|
|
268
281
|
console.log('');
|
|
269
282
|
}
|
|
270
|
-
console.log(` ${(0, theme_1.dim)(`Total:
|
|
283
|
+
console.log(` ${(0, theme_1.dim)(`Total: ${SKILL_COUNT} skills across 6 domains`)}`);
|
|
271
284
|
console.log('');
|
|
272
285
|
const viewAll = yield p.confirm({
|
|
273
|
-
message:
|
|
286
|
+
message: `Want to browse all ${SKILL_COUNT} skills in detail?`,
|
|
274
287
|
initialValue: false,
|
|
275
288
|
});
|
|
276
289
|
profile.onboardingStep = 4;
|
|
@@ -317,5 +330,5 @@ function runOnboarding(version) {
|
|
|
317
330
|
* Displays hamster + trigger + quick action
|
|
318
331
|
*/
|
|
319
332
|
function showReturningWelcome(profile, version, cwd) {
|
|
320
|
-
console.log((0, hamster_1.renderHamsterBanner)(profile.userName, version, cwd));
|
|
333
|
+
console.log((0, hamster_1.renderHamsterBanner)(profile.userName, version, cwd, SKILL_COUNT));
|
|
321
334
|
}
|
package/install.sh
CHANGED
|
@@ -27,6 +27,15 @@ RAW_URL="https://raw.githubusercontent.com/tody-agent/codymaster/main"
|
|
|
27
27
|
VERSION="4.4.0"
|
|
28
28
|
SCOPE="user" # default scope for Claude Code
|
|
29
29
|
|
|
30
|
+
if [ -d "skills" ]; then
|
|
31
|
+
TOTAL_SKILLS=$(ls -1d skills/*/SKILL.md 2>/dev/null | wc -l | tr -d ' ')
|
|
32
|
+
elif [ -d "$HOME/.cody-master/skills" ]; then
|
|
33
|
+
TOTAL_SKILLS=$(ls -1d "$HOME/.cody-master/skills"/*/SKILL.md 2>/dev/null | wc -l | tr -d ' ')
|
|
34
|
+
else
|
|
35
|
+
TOTAL_SKILLS="60+"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
|
|
30
39
|
# ── i18n ────────────────────────────────────────────────────────
|
|
31
40
|
detect_lang() {
|
|
32
41
|
local lang="${LANG:-en}"
|
|
@@ -115,7 +124,7 @@ print_header() {
|
|
|
115
124
|
echo ""
|
|
116
125
|
echo -e " ${O}${BOLD}$(msg welcome)${NC} 🐹"
|
|
117
126
|
echo ""
|
|
118
|
-
echo -e " ${DIM}CodyMaster${NC} ${O}v${VERSION}${NC} ${DIM}•
|
|
127
|
+
echo -e " ${DIM}CodyMaster${NC} ${O}v${VERSION}${NC} ${DIM}• ${TOTAL_SKILLS} Skills • ~${NC}"
|
|
119
128
|
echo -e "${DIM} ──────────────────────────────────────────────────${NC}"
|
|
120
129
|
echo ""
|
|
121
130
|
}
|
|
@@ -206,7 +215,7 @@ hamster_sentiment() {
|
|
|
206
215
|
"start")
|
|
207
216
|
case $idx in
|
|
208
217
|
0) echo -e " ${C}🐹: Whiskers twitching... CodyMaster incoming!${NC}" ;;
|
|
209
|
-
1) echo -e " ${C}🐹: Let's fill these cheeks with
|
|
218
|
+
1) echo -e " ${C}🐹: Let's fill these cheeks with ${TOTAL_SKILLS} skills! ✨${NC}" ;;
|
|
210
219
|
2) echo -e " ${C}🐹: Waking up from a power nap! Let's build! 🐭${NC}" ;;
|
|
211
220
|
esac
|
|
212
221
|
;;
|
|
@@ -220,7 +229,7 @@ hamster_sentiment() {
|
|
|
220
229
|
"finish")
|
|
221
230
|
case $idx in
|
|
222
231
|
0) echo -e " ${C}🐹: Mission accomplished! Can I have a walnut now? 🥜${NC}" ;;
|
|
223
|
-
1) echo -e " ${C}🐹: My cheeks are stuffed with
|
|
232
|
+
1) echo -e " ${C}🐹: My cheeks are stuffed with ${TOTAL_SKILLS} skills for you! ✨${NC}" ;;
|
|
224
233
|
2) echo -e " ${C}🐹: Terminal is Hamster-approved! Better than a wheel! 🎡${NC}" ;;
|
|
225
234
|
esac
|
|
226
235
|
;;
|
|
@@ -403,7 +412,7 @@ print_onboarding() {
|
|
|
403
412
|
print_header
|
|
404
413
|
|
|
405
414
|
if [ "$LANG_CODE" = "vi" ]; then
|
|
406
|
-
echo -e " ${W}${BOLD}🎉 Thành công! Bạn đã mở khóa
|
|
415
|
+
echo -e " ${W}${BOLD}🎉 Thành công! Bạn đã mở khóa ${TOTAL_SKILLS} kỹ năng AI toàn năng:${NC}"
|
|
407
416
|
echo ""
|
|
408
417
|
hamster_sentiment "finish"
|
|
409
418
|
echo ""
|
|
@@ -429,7 +438,7 @@ print_onboarding() {
|
|
|
429
438
|
echo -e " 11. ${Y}Xem Demo tự động ${NC} → /cm:demo"
|
|
430
439
|
echo -e " 12. ${Y}Trợ giúp & Cú pháp lệnh ${NC} → cm help"
|
|
431
440
|
else
|
|
432
|
-
echo -e " ${W}${BOLD}🎉 Success! You just unlocked
|
|
441
|
+
echo -e " ${W}${BOLD}🎉 Success! You just unlocked ${TOTAL_SKILLS} omnipotent AI skills:${NC}"
|
|
433
442
|
echo ""
|
|
434
443
|
hamster_sentiment "finish"
|
|
435
444
|
echo ""
|
|
@@ -611,6 +620,7 @@ install_skills_to() {
|
|
|
611
620
|
echo ""
|
|
612
621
|
mkdir -p "$target"
|
|
613
622
|
local count=0
|
|
623
|
+
local installed=()
|
|
614
624
|
for skill_dir in "${CLONE_DIR}"/skills/*/; do
|
|
615
625
|
skill_name=$(basename "$skill_dir")
|
|
616
626
|
if [ -f "${skill_dir}SKILL.md" ]; then
|
|
@@ -621,19 +631,32 @@ install_skills_to() {
|
|
|
621
631
|
echo "globs: *" >> "${target}/${skill_name}.mdc"
|
|
622
632
|
echo "---" >> "${target}/${skill_name}.mdc"
|
|
623
633
|
cat "${skill_dir}SKILL.md" >> "${target}/${skill_name}.mdc"
|
|
624
|
-
|
|
634
|
+
installed+=("${skill_name}.mdc")
|
|
625
635
|
elif [[ "$format" == "md" ]]; then
|
|
626
636
|
cp "${skill_dir}SKILL.md" "${target}/${skill_name}.md"
|
|
627
|
-
|
|
637
|
+
installed+=("${skill_name}.md")
|
|
628
638
|
else
|
|
629
639
|
cp -r "$skill_dir" "${target}/${skill_name}"
|
|
630
|
-
|
|
640
|
+
installed+=("$skill_name")
|
|
631
641
|
fi
|
|
632
642
|
count=$((count + 1))
|
|
633
643
|
fi
|
|
634
644
|
done
|
|
645
|
+
|
|
646
|
+
local line=" ${DIM}"
|
|
647
|
+
for s in "${installed[@]}"; do
|
|
648
|
+
if [ ${#line} -gt 70 ]; then
|
|
649
|
+
echo -e "${line}${NC}"
|
|
650
|
+
line=" ${DIM}"
|
|
651
|
+
fi
|
|
652
|
+
line="${line}${s}, "
|
|
653
|
+
done
|
|
654
|
+
if [ "${line}" != " ${DIM}" ]; then
|
|
655
|
+
echo -e "${line%, }${NC}"
|
|
656
|
+
fi
|
|
657
|
+
|
|
635
658
|
echo ""
|
|
636
|
-
echo -e "${G}${count} skills installed to ${target}${NC}"
|
|
659
|
+
echo -e "${G}✅ ${count} skills installed to ${target}${NC}"
|
|
637
660
|
}
|
|
638
661
|
|
|
639
662
|
# ── Legacy alias ─────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codymaster",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.3",
|
|
4
4
|
"description": "68+ Skills. Ship 10x faster. AI-powered coding skill kit for Claude, Cursor, Gemini & more.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"type": "commonjs",
|
|
47
47
|
"files": [
|
|
48
48
|
"dist/",
|
|
49
|
+
"scripts/",
|
|
49
50
|
"skills/",
|
|
50
51
|
"adapters/",
|
|
51
52
|
"commands/",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gate 0: Secret Hygiene
|
|
4
|
+
* Fastest gate (< 0.5s). Checks for leaked secrets before anything else runs.
|
|
5
|
+
*/
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
let failed = false;
|
|
10
|
+
|
|
11
|
+
// 1. Check wrangler config files for hardcoded secret values
|
|
12
|
+
const wranglerFiles = ['wrangler.toml', 'wrangler.jsonc', 'wrangler.json'];
|
|
13
|
+
const dangerous = [
|
|
14
|
+
'SERVICE_KEY', 'ANON_KEY', 'DB_PASSWORD', 'SECRET_KEY', 'PRIVATE_KEY', 'API_SECRET',
|
|
15
|
+
'GCP_SERVICE_ACCOUNT', 'AZURE_CONNECTION_STRING', 'HEROKU_API_KEY', 'POSTMAN_API_KEY'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const wf of wranglerFiles) {
|
|
19
|
+
if (!fs.existsSync(wf)) continue;
|
|
20
|
+
const src = fs.readFileSync(wf, 'utf-8');
|
|
21
|
+
for (const key of dangerous) {
|
|
22
|
+
const valuePattern = new RegExp(key + '\\s*[=:]\\s*["\'][a-zA-Z0-9/+=]{20,}', 'g');
|
|
23
|
+
if (valuePattern.test(src)) {
|
|
24
|
+
console.error('❌ DANGEROUS: ' + wf + ' contains a ' + key + ' VALUE');
|
|
25
|
+
console.error(' Fix: wrangler secret put ' + key + ' (then remove from ' + wf + ')');
|
|
26
|
+
failed = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Check .gitignore has required patterns
|
|
32
|
+
if (fs.existsSync('.gitignore')) {
|
|
33
|
+
const gi = fs.readFileSync('.gitignore', 'utf-8');
|
|
34
|
+
const required = ['.env', '.dev.vars'];
|
|
35
|
+
const missing = required.filter(r => !gi.includes(r));
|
|
36
|
+
if (missing.length > 0) {
|
|
37
|
+
console.error('❌ .gitignore missing: ' + missing.join(', '));
|
|
38
|
+
failed = true;
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
console.error('❌ No .gitignore found!');
|
|
42
|
+
failed = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 3. Check .env / .dev.vars files aren't tracked by git
|
|
46
|
+
try {
|
|
47
|
+
const tracked = execSync('git ls-files', { encoding: 'utf-8' });
|
|
48
|
+
const badFiles = ['.env', '.dev.vars', '.env.local', '.env.production'];
|
|
49
|
+
const trackedBad = badFiles.filter(f => tracked.split('\n').includes(f));
|
|
50
|
+
if (trackedBad.length > 0) {
|
|
51
|
+
console.error('❌ CRITICAL: Secret files tracked by git: ' + trackedBad.join(', '));
|
|
52
|
+
console.error(' Fix: git rm --cached ' + trackedBad.join(' '));
|
|
53
|
+
failed = true;
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// Not a git repo — skip
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (failed) {
|
|
60
|
+
console.error('\n🛡️ Gate 0 FAILED. Fix issues above before deploying.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
console.log('✅ Gate 0 passed: secret hygiene verified');
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gate 1: Syntax Validation
|
|
4
|
+
* Fast-fail (< 2s). Catches syntax errors before the slower test suite.
|
|
5
|
+
* - Parses all JS files in public/js/ with acorn
|
|
6
|
+
* - Runs tsc --noEmit for TypeScript in src/
|
|
7
|
+
*/
|
|
8
|
+
const { parse } = require('acorn');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
let failed = false;
|
|
14
|
+
|
|
15
|
+
// 1. Parse all JS files in public/js/
|
|
16
|
+
const jsDir = 'public/js';
|
|
17
|
+
if (fs.existsSync(jsDir)) {
|
|
18
|
+
const jsFiles = fs.readdirSync(jsDir).filter(f => f.endsWith('.js'));
|
|
19
|
+
for (const file of jsFiles) {
|
|
20
|
+
const filePath = path.join(jsDir, file);
|
|
21
|
+
const code = fs.readFileSync(filePath, 'utf-8');
|
|
22
|
+
try {
|
|
23
|
+
parse(code, { ecmaVersion: 2022, sourceType: 'script' });
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`❌ Syntax error in ${filePath}:`);
|
|
26
|
+
console.error(` Line ${err.loc?.line}, Column ${err.loc?.column}: ${err.message}`);
|
|
27
|
+
failed = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!failed) {
|
|
31
|
+
console.log(`✅ ${jsFiles.length} JS files parsed successfully`);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
console.log('⚠ No public/js/ directory found, skipping JS syntax check');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. TypeScript compilation check
|
|
38
|
+
if (fs.existsSync('tsconfig.json')) {
|
|
39
|
+
try {
|
|
40
|
+
execSync('npx tsc --noEmit', { encoding: 'utf-8', stdio: 'pipe' });
|
|
41
|
+
console.log('✅ TypeScript compilation check passed');
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('❌ TypeScript compilation errors:');
|
|
44
|
+
console.error(err.stdout || err.stderr || err.message);
|
|
45
|
+
failed = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (failed) {
|
|
50
|
+
console.error('\n🔴 Gate 1 FAILED. Fix syntax errors before proceeding.');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
console.log('✅ Gate 1 passed: syntax validation complete');
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gate 5: Dist Asset Verification
|
|
4
|
+
* Verifies critical files exist in public/ after build:html + docs:build.
|
|
5
|
+
* Build can "succeed" but produce an incomplete output directory.
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const publicDir = 'public';
|
|
11
|
+
// Critical HTML pages
|
|
12
|
+
const requiredHtml = [
|
|
13
|
+
'dashboard/index.html',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// Critical JS files
|
|
17
|
+
const requiredJs = [
|
|
18
|
+
'dashboard/app.js',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Critical CSS files
|
|
22
|
+
const requiredCss = [
|
|
23
|
+
'dashboard/style.css',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Build required file list
|
|
27
|
+
const required = [];
|
|
28
|
+
|
|
29
|
+
// HTML pages
|
|
30
|
+
for (const html of requiredHtml) {
|
|
31
|
+
required.push(path.join(publicDir, html));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// JS files
|
|
35
|
+
for (const js of requiredJs) {
|
|
36
|
+
required.push(path.join(publicDir, js));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// CSS files
|
|
40
|
+
for (const css of requiredCss) {
|
|
41
|
+
required.push(path.join(publicDir, css));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// Check all required files
|
|
46
|
+
const missing = required.filter(f => !fs.existsSync(f));
|
|
47
|
+
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
console.error('❌ Missing critical files:');
|
|
50
|
+
missing.forEach(f => console.error(' ' + f));
|
|
51
|
+
console.error(`\n🔴 Gate 5 FAILED. ${missing.length} file(s) missing from build output.`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`✅ Gate 5 passed: all ${required.length} critical files present`);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gate 6: Post-Deploy Smoke Test
|
|
4
|
+
* Verifies the deployed site returns HTTP 200.
|
|
5
|
+
* Usage: node scripts/gate-6-smoke-test.js [optional-url]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const DEPLOY_URL = process.argv[2] || process.env.DEPLOY_URL || 'https://cody.todyle.com';
|
|
9
|
+
|
|
10
|
+
async function smokeTest() {
|
|
11
|
+
console.log(`🔍 Smoke testing: ${DEPLOY_URL}`);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(DEPLOY_URL);
|
|
15
|
+
if (res.status === 200) {
|
|
16
|
+
console.log(`✅ Gate 6 passed: HTTP ${res.status} from ${DEPLOY_URL}`);
|
|
17
|
+
} else {
|
|
18
|
+
console.error(`❌ Gate 6 FAILED: HTTP ${res.status} from ${DEPLOY_URL}`);
|
|
19
|
+
console.error(' ⚠ Consider immediate rollback.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(`❌ Gate 6 FAILED: Could not reach ${DEPLOY_URL}`);
|
|
24
|
+
console.error(` Error: ${err.message}`);
|
|
25
|
+
console.error(' ⚠ Consider immediate rollback.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
smokeTest();
|