codymaster 4.6.0 → 5.2.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/CHANGELOG.md +74 -8
- package/README.md +192 -95
- package/dist/advisory-handoff.js +89 -0
- package/dist/advisory-report.js +105 -0
- package/dist/browse-server.js +251 -0
- package/dist/cli/command-registry.js +34 -0
- package/dist/cli/commands/agent.js +120 -0
- package/dist/cli/commands/bench.js +69 -0
- package/dist/cli/commands/brain.js +108 -0
- package/dist/cli/commands/dashboard.js +93 -0
- package/dist/cli/commands/design-studio.js +111 -0
- package/dist/cli/commands/distro.js +25 -0
- package/dist/cli/commands/engineering.js +596 -0
- package/dist/cli/commands/evolve.js +123 -0
- package/dist/cli/commands/mcp-serve.js +104 -0
- package/dist/cli/commands/project.js +324 -0
- package/dist/cli/commands/skill-chain.js +269 -0
- package/dist/cli/commands/system.js +89 -0
- package/dist/cli/commands/task.js +254 -0
- package/dist/cli/update-check.js +83 -0
- package/dist/cm-config.js +92 -0
- package/dist/cm-suggest.js +77 -0
- package/dist/codybench/judges/automated.js +31 -0
- package/dist/codybench/runners/claude-code.js +32 -0
- package/dist/codybench/suites/memory-retention.js +85 -0
- package/dist/codybench/suites/tdd-regression.js +35 -0
- package/dist/codybench/suites/token-efficiency.js +55 -0
- package/dist/codybench/types.js +2 -0
- package/dist/context-db.js +157 -0
- package/dist/continuity.js +2 -6
- package/dist/distro-validate.js +54 -0
- package/dist/execution-analyzer.js +138 -0
- package/dist/guardian-core.js +74 -0
- package/dist/index.js +36 -2759
- package/dist/indexer/skills-lib.js +533 -0
- package/dist/indexer/skills-map.js +1374 -0
- package/dist/indexer/skills.js +16 -0
- package/dist/learning-promoter.js +246 -0
- package/dist/mcp-context-server.js +289 -1
- package/dist/mcp-skills-tools.js +81 -0
- package/dist/retro-summary.js +70 -0
- package/dist/second-opinion-providers.js +79 -0
- package/dist/skill-chain.js +63 -1
- package/dist/skill-evolver.js +456 -0
- package/dist/skill-execution-cache.js +254 -0
- package/dist/smart-brain-router.js +184 -0
- package/dist/sprint-pipeline.js +228 -0
- package/dist/storage-backend.js +14 -67
- package/dist/token-budget.js +88 -0
- package/dist/utils/cli-utils.js +76 -0
- package/dist/utils/skill-utils.js +32 -0
- package/package.json +17 -7
- package/scripts/build-skills.mjs +51 -0
- package/scripts/gate-0-repo-hygiene.js +75 -0
- package/scripts/postinstall.js +34 -28
- package/scripts/security-scan.js +1 -1
- package/scripts/validate-skills.mjs +42 -0
- package/skills/CLAUDE.md +2 -7
- package/skills/_shared/helpers.md +2 -8
- package/skills/cm-ads-tracker/SKILL.md +3 -6
- package/skills/cm-browse/SKILL.md +34 -0
- package/skills/cm-conductor-worktrees/SKILL.md +28 -0
- package/skills/cm-content-factory/SKILL.md +1 -1
- package/skills/cm-content-factory/landing/docs/content/changelog.md +36 -0
- package/skills/cm-content-factory/landing/docs/content/deployment.md +46 -0
- package/skills/cm-content-factory/landing/docs/content/execution-flow.md +67 -0
- package/skills/cm-content-factory/landing/docs/content/memory-system.md +38 -0
- package/skills/cm-content-factory/landing/docs/content/openspace.md +27 -0
- package/skills/cm-content-factory/landing/docs/content/use-cases.md +26 -0
- package/skills/cm-content-factory/landing/docs/content/v5-intro.md +28 -0
- package/skills/cm-content-factory/landing/docs/index.html +240 -0
- package/skills/cm-content-factory/landing/index.html +100 -100
- package/skills/cm-content-factory/landing/script.js +42 -0
- package/skills/cm-content-factory/landing/translations.js +400 -400
- package/skills/cm-continuity/SKILL.md +32 -33
- package/skills/cm-design-studio/SKILL.md +34 -0
- package/skills/cm-ecosystem-roadmap/SKILL.md +15 -0
- package/skills/cm-engineering-meta/SKILL.md +73 -0
- package/skills/cm-growth-hacking/SKILL.md +1 -12
- package/skills/cm-guardian-runtime/SKILL.md +26 -0
- package/skills/cm-mcp-engineering/SKILL.md +22 -0
- package/skills/cm-notebooklm/SKILL.md +1 -17
- package/skills/cm-post-deploy-canary/SKILL.md +22 -0
- package/skills/cm-project-bootstrap/SKILL.md +11 -0
- package/skills/cm-qa-visual-cli/SKILL.md +22 -0
- package/skills/cm-retro-cli/SKILL.md +23 -0
- package/skills/cm-second-opinion-cli/SKILL.md +23 -0
- package/skills/cm-secret-shield/SKILL.md +2 -2
- package/skills/cm-security-gate/SKILL.md +1 -0
- package/skills/cm-skill-chain/SKILL.md +25 -4
- package/skills/cm-skill-evolution/SKILL.md +83 -0
- package/skills/cm-skill-health/SKILL.md +83 -0
- package/skills/cm-skill-index/SKILL.md +11 -3
- package/skills/cm-skill-search/SKILL.md +49 -0
- package/skills/cm-skill-share/SKILL.md +58 -0
- package/skills/cm-sprint-bus/SKILL.md +33 -0
- package/skills/cm-start/SKILL.md +0 -10
- package/skills/cm-tdd/SKILL.md +59 -72
- package/skills/profiles/README.md +21 -0
- package/skills/profiles/core.txt +23 -0
- package/skills/profiles/design.txt +6 -0
- package/skills/profiles/full.txt +62 -0
- package/skills/profiles/growth.txt +10 -0
- package/skills/profiles/knowledge.txt +7 -0
- package/install.sh +0 -901
- package/scripts/test-gemini.js +0 -13
- package/skills/cm-frappe-agent/SKILL.md +0 -134
- package/skills/cm-frappe-agent/agents/doctype-architect.md +0 -596
- package/skills/cm-frappe-agent/agents/erpnext-customizer.md +0 -643
- package/skills/cm-frappe-agent/agents/frappe-backend.md +0 -814
- package/skills/cm-frappe-agent/agents/frappe-custom-frontend.md +0 -557
- package/skills/cm-frappe-agent/agents/frappe-debugger.md +0 -625
- package/skills/cm-frappe-agent/agents/frappe-fixer.md +0 -275
- package/skills/cm-frappe-agent/agents/frappe-frontend.md +0 -660
- package/skills/cm-frappe-agent/agents/frappe-installer.md +0 -158
- package/skills/cm-frappe-agent/agents/frappe-performance.md +0 -307
- package/skills/cm-frappe-agent/agents/frappe-planner.md +0 -419
- package/skills/cm-frappe-agent/agents/frappe-remote-ops.md +0 -153
- package/skills/cm-frappe-agent/agents/github-workflow.md +0 -286
- package/skills/cm-frappe-agent/commands/frappe-app.md +0 -351
- package/skills/cm-frappe-agent/commands/frappe-backend.md +0 -162
- package/skills/cm-frappe-agent/commands/frappe-bench.md +0 -254
- package/skills/cm-frappe-agent/commands/frappe-debug.md +0 -263
- package/skills/cm-frappe-agent/commands/frappe-doctype-create.md +0 -272
- package/skills/cm-frappe-agent/commands/frappe-doctype-field.md +0 -310
- package/skills/cm-frappe-agent/commands/frappe-erpnext.md +0 -210
- package/skills/cm-frappe-agent/commands/frappe-fix.md +0 -59
- package/skills/cm-frappe-agent/commands/frappe-frontend.md +0 -210
- package/skills/cm-frappe-agent/commands/frappe-fullstack.md +0 -243
- package/skills/cm-frappe-agent/commands/frappe-github.md +0 -57
- package/skills/cm-frappe-agent/commands/frappe-install.md +0 -52
- package/skills/cm-frappe-agent/commands/frappe-plan.md +0 -442
- package/skills/cm-frappe-agent/commands/frappe-remote.md +0 -58
- package/skills/cm-frappe-agent/commands/frappe-test.md +0 -356
- package/skills/cm-frappe-agent/docs/README.md +0 -51
- package/skills/cm-frappe-agent/docs/agents-catalog.md +0 -113
- package/skills/cm-frappe-agent/docs/architecture.md +0 -149
- package/skills/cm-frappe-agent/docs/commands-catalog.md +0 -82
- package/skills/cm-frappe-agent/docs/resources-catalog.md +0 -66
- package/skills/cm-frappe-agent/docs/sitemap-urls.txt +0 -52
- package/skills/cm-frappe-agent/docs/sitemap.md +0 -81
- package/skills/cm-frappe-agent/docs/sop/user-guide.md +0 -178
- package/skills/cm-frappe-agent/docs/sop/vibe-coding-guide.md +0 -122
- package/skills/cm-frappe-agent/resources/7-layer-architecture.md +0 -985
- package/skills/cm-frappe-agent/resources/bench_commands.md +0 -73
- package/skills/cm-frappe-agent/resources/code-patterns-guide.md +0 -948
- package/skills/cm-frappe-agent/resources/common_pitfalls.md +0 -266
- package/skills/cm-frappe-agent/resources/doctype-registry.md +0 -158
- package/skills/cm-frappe-agent/resources/installation-guide.md +0 -289
- package/skills/cm-frappe-agent/resources/rest-api-patterns.md +0 -182
- package/skills/cm-frappe-agent/resources/scaffold_checklist.md +0 -82
- package/skills/cm-frappe-agent/resources/upgrade_patterns.md +0 -113
- package/skills/cm-frappe-agent/resources/web-form-patterns.md +0 -252
- package/skills/cm-frappe-agent/skills/bench-commands/SKILL.md +0 -621
- package/skills/cm-frappe-agent/skills/client-scripts/SKILL.md +0 -642
- package/skills/cm-frappe-agent/skills/doctype-patterns/SKILL.md +0 -576
- package/skills/cm-frappe-agent/skills/frappe-api/SKILL.md +0 -740
- package/skills/cm-frappe-agent/skills/remote-operations/SKILL.md +0 -47
- package/skills/cm-frappe-agent/skills/server-scripts/SKILL.md +0 -608
- package/skills/cm-frappe-agent/skills/web-forms/SKILL.md +0 -46
- package/skills/frappe-app-builder.zip +0 -0
package/dist/storage-backend.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
3
|
+
exports.SqliteBackend = void 0;
|
|
7
4
|
exports.getBackend = getBackend;
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
5
|
const context_db_1 = require("./context-db");
|
|
11
|
-
const
|
|
12
|
-
Object.defineProperty(exports, "VikingBackend", { enumerable: true, get: function () { return viking_backend_1.VikingBackend; } });
|
|
13
|
-
const viking_http_client_1 = require("./backends/viking-http-client");
|
|
6
|
+
const cm_config_1 = require("./cm-config");
|
|
14
7
|
// ─── SqliteBackend ────────────────────────────────────────────────────────────
|
|
15
8
|
/**
|
|
16
9
|
* Default backend — thin wrapper around context-db.ts (better-sqlite3 + FTS5).
|
|
@@ -37,65 +30,18 @@ class SqliteBackend {
|
|
|
37
30
|
}
|
|
38
31
|
writeSkillOutput(o) { (0, context_db_1.writeSkillOutput)(this.dbPath, o); }
|
|
39
32
|
getSkillOutputs(sessionId) { return (0, context_db_1.getSkillOutputs)(this.dbPath, sessionId); }
|
|
33
|
+
recordExecutionAnalysis(a) { (0, context_db_1.recordExecutionAnalysis)(this.dbPath, a); }
|
|
34
|
+
getExecutionAnalyses(limit = 20) { return (0, context_db_1.getExecutionAnalyses)(this.dbPath, limit); }
|
|
35
|
+
getSkillMetric(skill) { return (0, context_db_1.getSkillMetric)(this.dbPath, skill); }
|
|
36
|
+
listSkillMetrics(limit = 50) { return (0, context_db_1.listSkillMetrics)(this.dbPath, limit); }
|
|
40
37
|
}
|
|
41
38
|
exports.SqliteBackend = SqliteBackend;
|
|
42
|
-
/**
|
|
43
|
-
* Minimal YAML parser — reads `storage.backend` and `storage.viking.*` keys.
|
|
44
|
-
* Avoids adding a js-yaml dependency for a handful of config fields.
|
|
45
|
-
*
|
|
46
|
-
* Supported format:
|
|
47
|
-
* storage:
|
|
48
|
-
* backend: viking
|
|
49
|
-
* viking:
|
|
50
|
-
* host: localhost
|
|
51
|
-
* port: 1933
|
|
52
|
-
* workspace: codymaster
|
|
53
|
-
* timeout: 60000
|
|
54
|
-
*/
|
|
55
|
-
function loadStorageConfig(projectPath) {
|
|
56
|
-
var _a;
|
|
57
|
-
const configPath = path_1.default.join(projectPath, '.cm', 'config.yaml');
|
|
58
|
-
if (!fs_1.default.existsSync(configPath))
|
|
59
|
-
return {};
|
|
60
|
-
try {
|
|
61
|
-
const raw = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
62
|
-
// Extract storage.backend
|
|
63
|
-
const backendMatch = raw.match(/^storage:\s*\n(?:[ \t]+\S[^\n]*\n)*?[ \t]+backend:\s*(\S+)/m);
|
|
64
|
-
const backend = (_a = backendMatch === null || backendMatch === void 0 ? void 0 : backendMatch[1]) === null || _a === void 0 ? void 0 : _a.trim();
|
|
65
|
-
// Extract storage.viking.* keys
|
|
66
|
-
const vikingBlock = raw.match(/[ \t]+viking:\s*\n((?:[ \t]{4,}[^\n]+\n?)*)/m);
|
|
67
|
-
let viking;
|
|
68
|
-
if (vikingBlock === null || vikingBlock === void 0 ? void 0 : vikingBlock[1]) {
|
|
69
|
-
viking = {};
|
|
70
|
-
for (const line of vikingBlock[1].split('\n')) {
|
|
71
|
-
const kv = line.match(/[ \t]+(\w+):\s*(\S+)/);
|
|
72
|
-
if (!kv)
|
|
73
|
-
continue;
|
|
74
|
-
const [, key, val] = kv;
|
|
75
|
-
if (key === 'host')
|
|
76
|
-
viking.host = val;
|
|
77
|
-
if (key === 'workspace')
|
|
78
|
-
viking.workspace = val;
|
|
79
|
-
if (key === 'port')
|
|
80
|
-
viking.port = parseInt(val, 10);
|
|
81
|
-
if (key === 'timeout')
|
|
82
|
-
viking.timeout = parseInt(val, 10);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (!backend)
|
|
86
|
-
return {};
|
|
87
|
-
return { storage: Object.assign({ backend }, (viking ? { viking } : {})) };
|
|
88
|
-
}
|
|
89
|
-
catch (_b) {
|
|
90
|
-
return {};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
39
|
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
94
40
|
/**
|
|
95
41
|
* Returns the configured StorageBackend for the given project.
|
|
96
42
|
*
|
|
97
|
-
* Reads `.cm/config.yaml → storage.backend` (default: `sqlite`).
|
|
98
|
-
*
|
|
43
|
+
* Reads `.cm/config.yaml → storage.backend` via `loadCmConfig` (default: `sqlite`).
|
|
44
|
+
* Legacy `storage.backend: viking` configs are warned and routed back to sqlite.
|
|
99
45
|
*
|
|
100
46
|
* Usage:
|
|
101
47
|
* const backend = getBackend('/path/to/project');
|
|
@@ -103,13 +49,14 @@ function loadStorageConfig(projectPath) {
|
|
|
103
49
|
* const results = backend.queryLearnings('i18n locale');
|
|
104
50
|
*/
|
|
105
51
|
function getBackend(projectPath) {
|
|
106
|
-
var _a, _b
|
|
107
|
-
const
|
|
108
|
-
const engine = (_b = (_a =
|
|
52
|
+
var _a, _b;
|
|
53
|
+
const cfg = (0, cm_config_1.loadCmConfig)(projectPath);
|
|
54
|
+
const engine = ((_b = (_a = cfg.storage) === null || _a === void 0 ? void 0 : _a.backend) !== null && _b !== void 0 ? _b : 'sqlite').toLowerCase();
|
|
109
55
|
switch (engine) {
|
|
110
56
|
case 'viking': {
|
|
111
|
-
|
|
112
|
-
|
|
57
|
+
console.warn('[CodyMaster] storage.backend: viking has been removed. ' +
|
|
58
|
+
'Falling back to sqlite for the supported default path.');
|
|
59
|
+
return new SqliteBackend(projectPath);
|
|
113
60
|
}
|
|
114
61
|
case 'sqlite':
|
|
115
62
|
default:
|
package/dist/token-budget.js
CHANGED
|
@@ -8,6 +8,10 @@ exports.loadBudget = loadBudget;
|
|
|
8
8
|
exports.checkBudget = checkBudget;
|
|
9
9
|
exports.estimateTokens = estimateTokens;
|
|
10
10
|
exports.generateBudgetReport = generateBudgetReport;
|
|
11
|
+
exports.getDefaultTierBudgets = getDefaultTierBudgets;
|
|
12
|
+
exports.checkTierBudget = checkTierBudget;
|
|
13
|
+
exports.generateTierReport = generateTierReport;
|
|
14
|
+
exports.formatSavingsReport = formatSavingsReport;
|
|
11
15
|
const fs_1 = __importDefault(require("fs"));
|
|
12
16
|
const path_1 = __importDefault(require("path"));
|
|
13
17
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
@@ -106,3 +110,87 @@ function generateBudgetReport(budget) {
|
|
|
106
110
|
lines.push(`${'TOTAL'.padEnd(25)} ${sum.toLocaleString().padStart(10)} ${(((sum / total) * 100).toFixed(2) + '%').padStart(10)}`);
|
|
107
111
|
return lines.join('\n');
|
|
108
112
|
}
|
|
113
|
+
// ─── Per-Tier Budgets (Smart Brain Router integration) ───────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Default per-tier token budgets for the 5-Tier Brain architecture.
|
|
116
|
+
* Used by SmartBrainRouter to enforce tier-level spending limits.
|
|
117
|
+
*/
|
|
118
|
+
function getDefaultTierBudgets() {
|
|
119
|
+
return [
|
|
120
|
+
{ tier: 1, label: 'Sensory', soft: 0, hard: 0 }, // Always on, zero cost
|
|
121
|
+
{ tier: 2, label: 'Working', soft: 500, hard: 1000 }, // CONTINUITY.md
|
|
122
|
+
{ tier: 3, label: 'Long-term', soft: 500, hard: 3000 }, // Learnings/decisions
|
|
123
|
+
{ tier: 4, label: 'Semantic', soft: 0, hard: 2000 }, // qmd vector search
|
|
124
|
+
{ tier: 5, label: 'Structural', soft: 0, hard: 4000 }, // CodeGraph AST
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if a tier's token usage is within budget.
|
|
129
|
+
*/
|
|
130
|
+
function checkTierBudget(tierBudgets, tier, tokenCount, enforcement = 'soft') {
|
|
131
|
+
const budget = tierBudgets.find(tb => tb.tier === tier);
|
|
132
|
+
if (!budget) {
|
|
133
|
+
return { allowed: false, remaining: 0, suggestion: `Unknown tier ${tier}` };
|
|
134
|
+
}
|
|
135
|
+
const limit = enforcement === 'hard' ? budget.hard : budget.soft;
|
|
136
|
+
if (limit === 0) {
|
|
137
|
+
// Tier is disabled by default (soft=0), but allowed up to hard limit
|
|
138
|
+
if (tokenCount <= budget.hard) {
|
|
139
|
+
return { allowed: true, remaining: budget.hard - tokenCount };
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
allowed: false,
|
|
143
|
+
remaining: budget.hard - tokenCount,
|
|
144
|
+
suggestion: `Tier ${tier} (${budget.label}) exceeds hard limit of ${budget.hard} tokens.`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const remaining = limit - tokenCount;
|
|
148
|
+
if (remaining >= 0) {
|
|
149
|
+
return { allowed: true, remaining };
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
allowed: enforcement !== 'hard',
|
|
153
|
+
remaining,
|
|
154
|
+
suggestion: `Tier ${tier} (${budget.label}) over by ~${Math.abs(remaining)} tokens. Consider L0 loading.`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Generate a tier-level budget report.
|
|
159
|
+
*/
|
|
160
|
+
function generateTierReport(tierBudgets) {
|
|
161
|
+
const lines = [
|
|
162
|
+
`Brain Tier Budget Report`,
|
|
163
|
+
'─'.repeat(60),
|
|
164
|
+
`${'Tier'.padEnd(6)} ${'Label'.padEnd(12)} ${'Soft'.padStart(8)} ${'Hard'.padStart(8)} ${'Status'.padStart(10)}`,
|
|
165
|
+
'─'.repeat(60),
|
|
166
|
+
];
|
|
167
|
+
for (const tb of tierBudgets) {
|
|
168
|
+
const status = tb.soft === 0 ? 'off/demand' : 'active';
|
|
169
|
+
lines.push(`${String(tb.tier).padEnd(6)} ${tb.label.padEnd(12)} ${tb.soft.toLocaleString().padStart(8)} ${tb.hard.toLocaleString().padStart(8)} ${status.padStart(10)}`);
|
|
170
|
+
}
|
|
171
|
+
const totalSoft = tierBudgets.reduce((s, tb) => s + tb.soft, 0);
|
|
172
|
+
const totalHard = tierBudgets.reduce((s, tb) => s + tb.hard, 0);
|
|
173
|
+
lines.push('─'.repeat(60));
|
|
174
|
+
lines.push(`${'TOTAL'.padEnd(18)} ${totalSoft.toLocaleString().padStart(8)} ${totalHard.toLocaleString().padStart(8)}`);
|
|
175
|
+
return lines.join('\n');
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Format token savings report for CLI display.
|
|
179
|
+
*/
|
|
180
|
+
function formatSavingsReport(savings) {
|
|
181
|
+
const lines = [
|
|
182
|
+
`💰 Token Savings Report`,
|
|
183
|
+
'─'.repeat(50),
|
|
184
|
+
`Brain routing: ~${savings.brainRoutingSaved.toLocaleString()} tokens saved`,
|
|
185
|
+
`Cache hits: ~${savings.cacheHitsSaved.toLocaleString()} tokens saved`,
|
|
186
|
+
`Progressive loading: ~${savings.progressiveLoadSaved.toLocaleString()} tokens saved`,
|
|
187
|
+
'─'.repeat(50),
|
|
188
|
+
`Total saved: ~${savings.totalSaved.toLocaleString()} tokens`,
|
|
189
|
+
`Tasks this session: ${savings.sessionTasks}`,
|
|
190
|
+
];
|
|
191
|
+
if (savings.sessionTasks > 0) {
|
|
192
|
+
const avgSaved = Math.round(savings.totalSaved / savings.sessionTasks);
|
|
193
|
+
lines.push(`Avg saved per task: ~${avgSaved.toLocaleString()} tokens`);
|
|
194
|
+
}
|
|
195
|
+
return lines.join('\n');
|
|
196
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.padRight = padRight;
|
|
7
|
+
exports.openUrl = openUrl;
|
|
8
|
+
exports.formatTimeAgoCli = formatTimeAgoCli;
|
|
9
|
+
exports.progressBar = progressBar;
|
|
10
|
+
exports.isDashboardRunning = isDashboardRunning;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
15
|
+
/**
|
|
16
|
+
* Pads a string on the right with spaces.
|
|
17
|
+
*/
|
|
18
|
+
function padRight(str, len) {
|
|
19
|
+
if (str.length >= len)
|
|
20
|
+
return str;
|
|
21
|
+
return str + ' '.repeat(len - str.length);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Opens a URL in the default browser.
|
|
25
|
+
*/
|
|
26
|
+
function openUrl(url) {
|
|
27
|
+
const plat = os_1.default.platform();
|
|
28
|
+
const command = plat === 'darwin' ? 'open' : plat === 'win32' ? 'start' : 'xdg-open';
|
|
29
|
+
(0, child_process_1.exec)(`${command} "${url}"`);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Formats a date string relative to now (e.g. "2m ago", "1h ago").
|
|
33
|
+
*/
|
|
34
|
+
function formatTimeAgoCli(dateStr) {
|
|
35
|
+
const date = new Date(dateStr);
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
|
38
|
+
if (seconds < 60)
|
|
39
|
+
return `${seconds}s ago`;
|
|
40
|
+
const minutes = Math.floor(seconds / 60);
|
|
41
|
+
if (minutes < 60)
|
|
42
|
+
return `${minutes}m ago`;
|
|
43
|
+
const hours = Math.floor(minutes / 60);
|
|
44
|
+
if (hours < 24)
|
|
45
|
+
return `${hours}h ago`;
|
|
46
|
+
const days = Math.floor(hours / 24);
|
|
47
|
+
return `${days}d ago`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generates a green/gray progress bar string.
|
|
51
|
+
*/
|
|
52
|
+
function progressBar(pct) {
|
|
53
|
+
const total = 12;
|
|
54
|
+
const filled = Math.max(0, Math.min(total, Math.round((pct / 100) * total)));
|
|
55
|
+
return chalk_1.default.green('█'.repeat(filled)) + chalk_1.default.gray('░'.repeat(total - filled));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns true if the dashboard is running (via PID file).
|
|
59
|
+
*/
|
|
60
|
+
function isDashboardRunning(pidFile) {
|
|
61
|
+
try {
|
|
62
|
+
if (!fs_1.default.existsSync(pidFile))
|
|
63
|
+
return false;
|
|
64
|
+
const pidInput = fs_1.default.readFileSync(pidFile, 'utf-8').trim();
|
|
65
|
+
if (!pidInput)
|
|
66
|
+
return false;
|
|
67
|
+
const pid = parseInt(pidInput);
|
|
68
|
+
if (isNaN(pid))
|
|
69
|
+
return false;
|
|
70
|
+
process.kill(pid, 0);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch (_a) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getAvailableSkills = getAvailableSkills;
|
|
7
|
+
exports.getSkillCount = getSkillCount;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/**
|
|
11
|
+
* Discovers and returns a list of available 'cm-' skills.
|
|
12
|
+
*/
|
|
13
|
+
function getAvailableSkills() {
|
|
14
|
+
try {
|
|
15
|
+
const rootDir = path_1.default.resolve(__dirname, '..', '..');
|
|
16
|
+
const skillsDir = path_1.default.join(rootDir, 'skills');
|
|
17
|
+
if (!fs_1.default.existsSync(skillsDir)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
return fs_1.default.readdirSync(skillsDir)
|
|
21
|
+
.filter(f => f.startsWith('cm-') && fs_1.default.statSync(path_1.default.join(skillsDir, f)).isDirectory());
|
|
22
|
+
}
|
|
23
|
+
catch (_a) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns the count of available 'cm-' skills.
|
|
29
|
+
*/
|
|
30
|
+
function getSkillCount() {
|
|
31
|
+
return getAvailableSkills().length;
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codymaster",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.2.0",
|
|
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": {
|
|
@@ -19,15 +19,23 @@
|
|
|
19
19
|
"build": "tsc",
|
|
20
20
|
"start": "ts-node src/index.ts",
|
|
21
21
|
"test:gate": "vitest run --reporter=verbose",
|
|
22
|
+
"test:gate:kit": "npm run build && npm run validate:skills && npm run check:skills && vitest run --reporter=verbose",
|
|
22
23
|
"gate:secrets": "node scripts/gate-0-secrets.js",
|
|
24
|
+
"gate:hygiene": "node scripts/gate-0-repo-hygiene.js",
|
|
23
25
|
"gate:fix": "node scripts/security-fixer.js --fix",
|
|
24
26
|
"gate:check": "node scripts/security-fixer.js",
|
|
25
27
|
"gate:syntax": "node scripts/gate-1-syntax.js",
|
|
26
28
|
"gate:dist": "node scripts/gate-5-dist-verify.js",
|
|
27
29
|
"gate:smoke": "node scripts/gate-6-smoke-test.js",
|
|
28
|
-
"deploy": "npm run gate:secrets && npm run gate:syntax && npm run test:gate && npm run gate:dist && npm run gate:smoke",
|
|
29
|
-
"deploy:dry": "npm run gate:secrets && npm run gate:syntax && npm run test:gate && npm run gate:dist && echo '✅ All gates passed. Ready to deploy.'",
|
|
30
|
-
"postinstall": "node scripts/postinstall.js"
|
|
30
|
+
"deploy": "npm run gate:secrets && npm run gate:hygiene && npm run gate:syntax && npm run test:gate:kit && npm run gate:dist && npm run gate:smoke",
|
|
31
|
+
"deploy:dry": "npm run gate:secrets && npm run gate:hygiene && npm run gate:syntax && npm run test:gate:kit && npm run gate:dist && echo '✅ All gates passed. Ready to deploy.'",
|
|
32
|
+
"postinstall": "node scripts/postinstall.js",
|
|
33
|
+
"validate:skills": "node scripts/validate-skills.mjs",
|
|
34
|
+
"build:skills": "node scripts/build-skills.mjs",
|
|
35
|
+
"check:skills": "node scripts/build-skills.mjs --check",
|
|
36
|
+
"docs:dev": "vitepress dev docs",
|
|
37
|
+
"docs:build": "vitepress build docs",
|
|
38
|
+
"docs:preview": "vitepress preview docs"
|
|
31
39
|
},
|
|
32
40
|
"keywords": [
|
|
33
41
|
"ai",
|
|
@@ -52,8 +60,7 @@
|
|
|
52
60
|
"commands/",
|
|
53
61
|
"public/",
|
|
54
62
|
"README.md",
|
|
55
|
-
"CHANGELOG.md"
|
|
56
|
-
"install.sh"
|
|
63
|
+
"CHANGELOG.md"
|
|
57
64
|
],
|
|
58
65
|
"publishConfig": {
|
|
59
66
|
"access": "public"
|
|
@@ -65,7 +72,8 @@
|
|
|
65
72
|
"chokidar": "^5.0.0",
|
|
66
73
|
"commander": "^14.0.3",
|
|
67
74
|
"express": "^5.2.1",
|
|
68
|
-
"prompts": "^2.4.2"
|
|
75
|
+
"prompts": "^2.4.2",
|
|
76
|
+
"yaml": "^2.8.3"
|
|
69
77
|
},
|
|
70
78
|
"devDependencies": {
|
|
71
79
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -74,8 +82,10 @@
|
|
|
74
82
|
"@types/prompts": "^2.4.9",
|
|
75
83
|
"acorn": "^8.16.0",
|
|
76
84
|
"jsdom": "^29.0.1",
|
|
85
|
+
"playwright": "^1.50.0",
|
|
77
86
|
"ts-node": "^10.9.2",
|
|
78
87
|
"typescript": "^5.9.3",
|
|
88
|
+
"vitepress": "^1.6.4",
|
|
79
89
|
"vitest": "^4.1.0"
|
|
80
90
|
},
|
|
81
91
|
"overrides": {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate SKILL.md from SKILL.md.tmpl + meta.json when present.
|
|
4
|
+
* Usage: node scripts/build-skills.mjs [--check]
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const skillsRoot = path.join(__dirname, '..', 'skills');
|
|
12
|
+
const check = process.argv.includes('--check');
|
|
13
|
+
|
|
14
|
+
function render(tmpl, vars) {
|
|
15
|
+
return tmpl.replace(/\{\{(\w+)\}\}/g, (_, k) => (vars[k] != null ? String(vars[k]) : `{{${k}}}`));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let tmplCount = 0;
|
|
19
|
+
if (!fs.existsSync(skillsRoot)) process.exit(0);
|
|
20
|
+
|
|
21
|
+
for (const dir of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
|
|
22
|
+
if (!dir.isDirectory()) continue;
|
|
23
|
+
const folder = path.join(skillsRoot, dir.name);
|
|
24
|
+
const tmplPath = path.join(folder, 'SKILL.md.tmpl');
|
|
25
|
+
const metaPath = path.join(folder, 'meta.json');
|
|
26
|
+
const outPath = path.join(folder, 'SKILL.md');
|
|
27
|
+
if (!fs.existsSync(tmplPath)) continue;
|
|
28
|
+
|
|
29
|
+
tmplCount++;
|
|
30
|
+
const tmpl = fs.readFileSync(tmplPath, 'utf8');
|
|
31
|
+
let meta = {};
|
|
32
|
+
if (fs.existsSync(metaPath)) {
|
|
33
|
+
meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
34
|
+
}
|
|
35
|
+
const out = render(tmpl, meta);
|
|
36
|
+
if (check) {
|
|
37
|
+
const cur = fs.existsSync(outPath) ? fs.readFileSync(outPath, 'utf8') : '';
|
|
38
|
+
if (cur !== out) {
|
|
39
|
+
console.error(`check failed: ${outPath} out of date (run npm run build:skills)`);
|
|
40
|
+
process.exit(2);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
fs.writeFileSync(outPath, out, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (tmplCount === 0) {
|
|
48
|
+
console.log('build-skills: no SKILL.md.tmpl under skills/ (OK)');
|
|
49
|
+
} else {
|
|
50
|
+
console.log(check ? `build-skills: --check OK (${tmplCount})` : `build-skills: wrote ${tmplCount} skill(s)`);
|
|
51
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gate 0b: Repo Hygiene
|
|
4
|
+
* Ensures tracked files and git remote URLs are safe before push/deploy.
|
|
5
|
+
*/
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const FORBIDDEN_TRACKED_PATTERNS = [
|
|
9
|
+
/^\.DS_Store$/,
|
|
10
|
+
/^\.env(\..+)?$/,
|
|
11
|
+
/^\.dev\.vars(\..+)?$/,
|
|
12
|
+
/^.*\.(pem|key|p12|pfx)$/i,
|
|
13
|
+
/^.*\.(log|tmp|bak|swp)$/i,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function run(command, args) {
|
|
17
|
+
return execFileSync(command, args, { encoding: 'utf-8' }).trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function hasEmbeddedCredentials(remoteUrl) {
|
|
21
|
+
return /^https?:\/\/[^/\s]+@/i.test(remoteUrl);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getTrackedFiles() {
|
|
25
|
+
const out = run('git', ['ls-files']);
|
|
26
|
+
if (!out) return [];
|
|
27
|
+
return out.split('\n').filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function checkTrackedFiles() {
|
|
31
|
+
const tracked = getTrackedFiles();
|
|
32
|
+
return tracked.filter((file) =>
|
|
33
|
+
!file.endsWith('.example') &&
|
|
34
|
+
FORBIDDEN_TRACKED_PATTERNS.some((pattern) => pattern.test(file)),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function checkOriginRemote() {
|
|
39
|
+
try {
|
|
40
|
+
return run('git', ['remote', 'get-url', 'origin']);
|
|
41
|
+
} catch (_error) {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function main() {
|
|
47
|
+
const failures = [];
|
|
48
|
+
|
|
49
|
+
const originUrl = checkOriginRemote();
|
|
50
|
+
if (originUrl && hasEmbeddedCredentials(originUrl)) {
|
|
51
|
+
failures.push(
|
|
52
|
+
[
|
|
53
|
+
'origin remote URL has embedded credentials.',
|
|
54
|
+
'Fix: git remote set-url origin https://github.com/<owner>/<repo>.git',
|
|
55
|
+
].join(' '),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const badTrackedFiles = checkTrackedFiles();
|
|
60
|
+
if (badTrackedFiles.length > 0) {
|
|
61
|
+
failures.push(
|
|
62
|
+
`forbidden files are tracked by git: ${badTrackedFiles.join(', ')}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (failures.length > 0) {
|
|
67
|
+
console.error('❌ Repo hygiene check failed:');
|
|
68
|
+
failures.forEach((failure) => console.error(` - ${failure}`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('✅ Repo hygiene check passed');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main();
|
package/scripts/postinstall.js
CHANGED
|
@@ -11,7 +11,9 @@ const NC = '\x1b[0m';
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
|
|
14
|
+
const { execSync, execFileSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
let skillCount = 68;
|
|
15
17
|
try {
|
|
16
18
|
const skillsDir = path.join(__dirname, '..', 'skills');
|
|
17
19
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -212,7 +214,6 @@ const showSkillGuide = (choice) => {
|
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
console.log('');
|
|
215
|
-
console.log(`${W}${BOLD}${isVi ? 'Nhấn ENTER để quay lại...' : 'Press ENTER to go back...'}${NC}`);
|
|
216
217
|
};
|
|
217
218
|
|
|
218
219
|
const printMenu = () => {
|
|
@@ -242,20 +243,11 @@ const printMenu = () => {
|
|
|
242
243
|
console.log(` ${C}⚙️ Operations${NC} : Triển khai an toàn & Quản lý CI/CD`);
|
|
243
244
|
console.log(` ${C}📈 Growth${NC} : Tối ưu chuyển đổi (CRO) & Tracking`);
|
|
244
245
|
console.log('');
|
|
245
|
-
console.log(` ${W}${BOLD}
|
|
246
|
+
console.log(` ${W}${BOLD}🌟 Lệnh phổ biến:${NC}`);
|
|
246
247
|
console.log('');
|
|
247
|
-
console.log(`
|
|
248
|
-
console.log(`
|
|
249
|
-
console.log(`
|
|
250
|
-
console.log(` 4. ${Y}Code giao diện từ URL/Ảnh ${NC} → cm-ux-master`);
|
|
251
|
-
console.log(` 5. ${Y}Lập trình TDD & Pair code ${NC} → cm-tdd`);
|
|
252
|
-
console.log(` 6. ${Y}Dọn dẹp & Tái cấu trúc ${NC} → cm-clean-code`);
|
|
253
|
-
console.log(` 7. ${Y}Quét & Sửa lỗi bảo mật ${NC} → cm-security-gate`);
|
|
254
|
-
console.log(` 8. ${Y}Viết tài liệu Docs & API ${NC} → cm-dockit`);
|
|
255
|
-
console.log(` 9. ${Y}Tạo Landing Page "WOW" ${NC} → cm-cro-methodology`);
|
|
256
|
-
console.log(` 10. ${Y}Bảng theo dõi tiến độ ${NC} → cm dashboard`);
|
|
257
|
-
console.log(` 11. ${Y}Xem Demo tự động ${NC} → /cm:demo`);
|
|
258
|
-
console.log(` 12. ${Y}Trợ giúp & Cú pháp lệnh ${NC} → cm help`);
|
|
248
|
+
console.log(` 🔸 ${Y}Bảng tiến độ (Dashboard) ${NC} → cm dashboard`);
|
|
249
|
+
console.log(` 🔸 ${Y}Cần trợ giúp / Tìm kỹ năng ${NC} → cm help`);
|
|
250
|
+
console.log(` 🔸 ${Y}Cách CodyMaster hoạt động ${NC} → cm-how-it-work`);
|
|
259
251
|
} else {
|
|
260
252
|
console.log(` ${C}🎯 Orchestration${NC} : Task Planning & Agent Synergy`);
|
|
261
253
|
console.log(` ${C}🎨 Product${NC} : UX/UI Mastery & User Psychology`);
|
|
@@ -264,20 +256,11 @@ const printMenu = () => {
|
|
|
264
256
|
console.log(` ${C}⚙️ Operations${NC} : Safe Deployments & CI/CD Excellence`);
|
|
265
257
|
console.log(` ${C}📈 Growth${NC} : Conversion Tracking & Hacks`);
|
|
266
258
|
console.log('');
|
|
267
|
-
console.log(` ${W}${BOLD}
|
|
259
|
+
console.log(` ${W}${BOLD}🌟 Popular Commands:${NC}`);
|
|
268
260
|
console.log('');
|
|
269
|
-
console.log(`
|
|
270
|
-
console.log(`
|
|
271
|
-
console.log(`
|
|
272
|
-
console.log(` 4. ${Y}Generate UX/UI designs ${NC} → cm-ux-master`);
|
|
273
|
-
console.log(` 5. ${Y}Code TDD & Pair coding ${NC} → cm-tdd`);
|
|
274
|
-
console.log(` 6. ${Y}Clean & Refactor codebase ${NC} → cm-clean-code`);
|
|
275
|
-
console.log(` 7. ${Y}Scan for vulnerabilities ${NC} → cm-security-gate`);
|
|
276
|
-
console.log(` 8. ${Y}Write docs & generate APIs ${NC} → cm-dockit`);
|
|
277
|
-
console.log(` 9. ${Y}Release WOW landing page ${NC} → cm-cro-methodology`);
|
|
278
|
-
console.log(` 10. ${Y}Open progress dashboard ${NC} → cm dashboard`);
|
|
279
|
-
console.log(` 11. ${Y}See an interactive demo ${NC} → /cm:demo`);
|
|
280
|
-
console.log(` 12. ${Y}Help & Command list ${NC} → cm help`);
|
|
261
|
+
console.log(` 🔸 ${Y}Open progress dashboard ${NC} → cm dashboard`);
|
|
262
|
+
console.log(` 🔸 ${Y}Help & Command list ${NC} → cm help`);
|
|
263
|
+
console.log(` 🔸 ${Y}The ultimate guide ${NC} → cm-how-it-work`);
|
|
281
264
|
}
|
|
282
265
|
|
|
283
266
|
console.log('');
|
|
@@ -285,7 +268,30 @@ const printMenu = () => {
|
|
|
285
268
|
console.log('');
|
|
286
269
|
};
|
|
287
270
|
|
|
271
|
+
const isInstalledAsNpmDependency = () => {
|
|
272
|
+
const root = path.resolve(__dirname, '..').replace(/\\/g, '/');
|
|
273
|
+
return /[/\\]node_modules[/\\]codymaster$/i.test(root);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const npmCmd = () => (process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
277
|
+
|
|
278
|
+
const activateCli = () => {
|
|
279
|
+
const pkgRoot = path.join(__dirname, '..');
|
|
280
|
+
|
|
281
|
+
if (isInstalledAsNpmDependency()) {
|
|
282
|
+
console.log(` ${C}CodyMaster CLI (per-project install — official path):${NC}`);
|
|
283
|
+
console.log(` ${W}npx cm${NC} or ${W}npx codymaster${NC}`);
|
|
284
|
+
console.log(
|
|
285
|
+
` ${DIM}Optional — bare ${W}cm${DIM} in any terminal: ${W}npm install -g codymaster${NC}`,
|
|
286
|
+
);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
};
|
|
292
|
+
|
|
288
293
|
const main = () => {
|
|
294
|
+
activateCli();
|
|
289
295
|
printMenu();
|
|
290
296
|
};
|
|
291
297
|
|
package/scripts/security-scan.js
CHANGED
|
@@ -6,7 +6,7 @@ const DANGEROUS_PATTERNS = [
|
|
|
6
6
|
{ name: 'Anon Key Variable', regex: /ANON_KEY\s*[=:]\s*['\"][a-zA-Z0-9._\/-]{20,}/g },
|
|
7
7
|
{ name: 'Private Key Block', regex: /-----BEGIN\s+(RSA|EC|DSA|OPENSSH)?\s*PRIVATE KEY-----/g },
|
|
8
8
|
{ name: 'JWT Token', regex: /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
|
|
9
|
-
{ name: 'Generic API Key', regex: /(?:api[_-]?key|api[_-]?secret|access[_-]?token)\s*[=:]\s*['\"][a-zA-Z0-9\/+=]{20,}['\"
|
|
9
|
+
{ name: 'Generic API Key', regex: /(?:api[_-]?key|api[_-]?secret|access[_-]?token)\s*[=:]\s*['\"][a-zA-Z0-9\/+=]{20,}['\"]/gi },
|
|
10
10
|
{ name: 'AWS Key', regex: /AKIA[0-9A-Z]{16}/g },
|
|
11
11
|
{ name: 'Slack Token', regex: /xox[baprs]-[0-9a-zA-Z-]{10,}/g },
|
|
12
12
|
{ name: 'GitHub Token', regex: /gh[ps]_[a-zA-Z0-9]{36,}/g },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Ensures each skill folder under skills/ has SKILL.md with a title line.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const root = path.join(__dirname, '..', 'skills');
|
|
11
|
+
|
|
12
|
+
/** Directories under skills/ that are not standalone skills (assets, packs). */
|
|
13
|
+
const SKIP_DIRS = new Set(['profiles', 'extensions', 'scripts']);
|
|
14
|
+
|
|
15
|
+
let errors = 0;
|
|
16
|
+
if (!fs.existsSync(root)) {
|
|
17
|
+
console.error('No skills/ directory');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const name of fs.readdirSync(root, { withFileTypes: true })) {
|
|
22
|
+
if (!name.isDirectory()) continue;
|
|
23
|
+
if (name.name.startsWith('_') || name.name.startsWith('.')) continue;
|
|
24
|
+
if (SKIP_DIRS.has(name.name)) continue;
|
|
25
|
+
const md = path.join(root, name.name, 'SKILL.md');
|
|
26
|
+
if (!fs.existsSync(md)) {
|
|
27
|
+
console.error(`Missing SKILL.md: ${name.name}`);
|
|
28
|
+
errors++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const text = fs.readFileSync(md, 'utf8');
|
|
32
|
+
if (!/^#\s+\S/m.test(text)) {
|
|
33
|
+
console.error(`SKILL.md missing H1 title: ${name.name}`);
|
|
34
|
+
errors++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (errors) {
|
|
39
|
+
console.error(`validate-skills: ${errors} error(s)`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
console.log('validate-skills: OK');
|