@deerdesign/deertools 1.0.4 → 1.2.1
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/README.md +133 -16
- package/docs/prezentacja-it-deertools.html +645 -0
- package/docs/prezentacja-it-deertools.md +182 -0
- package/package.json +2 -1
- package/src/audit.js +65 -0
- package/src/baselinker/browser-login.js +214 -0
- package/src/baselinker/catalog-tools.js +457 -0
- package/src/baselinker/client.js +136 -0
- package/src/baselinker/crawler.js +157 -0
- package/src/baselinker/index.js +24 -0
- package/src/baselinker/mcp-server.js +153 -0
- package/src/baselinker/methods-index.json +1226 -0
- package/src/baselinker/schema-cache.json +6342 -0
- package/src/baselinker/ui.js +386 -0
- package/src/baselinker-commands.js +180 -0
- package/src/cli.js +22 -2
- package/src/commands.js +302 -35
- package/src/completion.js +189 -0
- package/src/credentials.js +39 -2
- package/src/doctor.js +286 -0
- package/src/domain-commands.js +288 -0
- package/src/formatters.js +50 -0
- package/src/mcp-stdio.js +197 -0
- package/src/profiles.js +78 -0
- package/src/registry.js +22 -1
- package/src/skill-commands.js +223 -0
- package/src/skills-hub.js +201 -0
- package/src/tui.js +139 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const credentials = require('./credentials');
|
|
6
|
+
const { fetchText } = require('./http');
|
|
7
|
+
const { parseFrontmatterName } = require('./openapi');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_API_BASE = 'https://ezspmlsnmxuhtzvwrnoj.supabase.co/functions/v1/skills-api';
|
|
10
|
+
const SECRET_PATTERNS = [
|
|
11
|
+
{ name: 'DeerTools Live API Key', regex: /deer_[a-z]+_live_[A-Za-z0-9_]{20,}/g },
|
|
12
|
+
{ name: 'GitHub Token', regex: /gh[pousr]_[A-Za-z0-9_]{30,}/g },
|
|
13
|
+
{ name: 'OpenAI / Claude Secret Key', regex: /sk-[A-Za-z0-9_-]{30,}/g },
|
|
14
|
+
{ name: 'BaseLinker Token', regex: /[0-9]{7}-[0-9]{7}-[A-Z0-9]{32}/g },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function getApiBase() {
|
|
18
|
+
return process.env.DEERTOOLS_SKILLS_API || DEFAULT_API_BASE;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getApiKey() {
|
|
22
|
+
if (process.env.DEERTOOLS_SKILLS_KEY) return process.env.DEERTOOLS_SKILLS_KEY;
|
|
23
|
+
try {
|
|
24
|
+
const { getProduct } = require('./registry');
|
|
25
|
+
const p = getProduct('skills');
|
|
26
|
+
const found = credentials.getKey(p);
|
|
27
|
+
return found ? found.key : null;
|
|
28
|
+
} catch (_) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function scanForSecrets(text) {
|
|
34
|
+
const matches = [];
|
|
35
|
+
for (const { name, regex } of SECRET_PATTERNS) {
|
|
36
|
+
const found = text.match(regex);
|
|
37
|
+
if (found && found.length > 0) {
|
|
38
|
+
matches.push({ name, count: found.length });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
safe: matches.length === 0,
|
|
43
|
+
matches,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function hubRequest(endpoint, { method = 'GET', body = null } = {}) {
|
|
48
|
+
const base = getApiBase().replace(/\/+$/, '');
|
|
49
|
+
const url = `${base}${endpoint}`;
|
|
50
|
+
const key = getApiKey();
|
|
51
|
+
const headers = {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
'User-Agent': 'deertools-cli',
|
|
54
|
+
};
|
|
55
|
+
if (key) {
|
|
56
|
+
headers['x-api-key'] = key;
|
|
57
|
+
headers['Authorization'] = `Bearer ${key}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const opts = { method, headers };
|
|
61
|
+
if (body) opts.body = JSON.stringify(body);
|
|
62
|
+
|
|
63
|
+
const res = await fetch(url, opts);
|
|
64
|
+
let json = null;
|
|
65
|
+
try {
|
|
66
|
+
json = await res.json();
|
|
67
|
+
} catch (_) {
|
|
68
|
+
json = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
status: res.status,
|
|
73
|
+
ok: res.ok,
|
|
74
|
+
data: json,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function listSkills(params = {}) {
|
|
79
|
+
const query = new URLSearchParams();
|
|
80
|
+
if (params.query) query.set('query', params.query);
|
|
81
|
+
if (params.tag) query.set('tag', params.tag);
|
|
82
|
+
if (params.category) query.set('category', params.category);
|
|
83
|
+
if (params.scope) query.set('scope', params.scope);
|
|
84
|
+
if (params.limit) query.set('limit', String(params.limit));
|
|
85
|
+
const qs = query.toString();
|
|
86
|
+
return hubRequest(`/skills${qs ? `?${qs}` : ''}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function getSkill(slug, version = null) {
|
|
90
|
+
const qs = version ? `?version=${encodeURIComponent(version)}` : '';
|
|
91
|
+
return hubRequest(`/skills/${encodeURIComponent(slug)}${qs}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function pushSkill(skillDir, { version = '1.0.0', changelog = 'Publikacja przez DeerTools CLI', visibility = 'shared' } = {}) {
|
|
95
|
+
const mdPath = fs.existsSync(skillDir) && fs.statSync(skillDir).isFile()
|
|
96
|
+
? skillDir
|
|
97
|
+
: path.join(skillDir, 'SKILL.md');
|
|
98
|
+
|
|
99
|
+
if (!fs.existsSync(mdPath)) {
|
|
100
|
+
throw new Error(`Nie znaleziono pliku SKILL.md w ścieżce: ${skillDir}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const skillContent = fs.readFileSync(mdPath, 'utf8');
|
|
104
|
+
|
|
105
|
+
// 1. Skanowanie sekretów
|
|
106
|
+
const scan = scanForSecrets(skillContent);
|
|
107
|
+
if (!scan.safe) {
|
|
108
|
+
const list = scan.matches.map((m) => `${m.name} (${m.count})`).join(', ');
|
|
109
|
+
throw new Error(`BLOKADA BEZPIECZEŃSTWA: Wykryto potencjalne klucze API w treści SKILL.md [${list}]. Usuń sekrety przed publikacją!`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 2. Parsowanie frontmatter
|
|
113
|
+
let slug = path.basename(path.dirname(mdPath));
|
|
114
|
+
let title = slug;
|
|
115
|
+
let description = '';
|
|
116
|
+
if (skillContent.startsWith('---')) {
|
|
117
|
+
const parts = skillContent.split('---', 2);
|
|
118
|
+
if (parts.length >= 2) {
|
|
119
|
+
const nameMatch = parts[1].match(/^name:\s*(.+)$/m);
|
|
120
|
+
if (nameMatch) {
|
|
121
|
+
slug = nameMatch[1].trim();
|
|
122
|
+
title = slug;
|
|
123
|
+
}
|
|
124
|
+
const descMatch = parts[1].match(/^description:\s*(.+)$/m);
|
|
125
|
+
if (descMatch) description = descMatch[1].trim();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 3. Odczyt dodatkowych plików (references/, scripts/, assets/)
|
|
130
|
+
const dir = path.dirname(mdPath);
|
|
131
|
+
const filesManifest = [];
|
|
132
|
+
const allowedDirs = ['references', 'scripts', 'assets'];
|
|
133
|
+
|
|
134
|
+
for (const sub of allowedDirs) {
|
|
135
|
+
const subPath = path.join(dir, sub);
|
|
136
|
+
if (fs.existsSync(subPath) && fs.statSync(subPath).isDirectory()) {
|
|
137
|
+
const files = fs.readdirSync(subPath);
|
|
138
|
+
for (const f of files) {
|
|
139
|
+
const fullF = path.join(subPath, f);
|
|
140
|
+
if (fs.statSync(fullF).isFile() && !f.startsWith('.')) {
|
|
141
|
+
try {
|
|
142
|
+
const rel = path.join(sub, f);
|
|
143
|
+
const content = fs.readFileSync(fullF, 'utf8');
|
|
144
|
+
filesManifest.push({ path: rel, content });
|
|
145
|
+
} catch (_) {
|
|
146
|
+
// binarne pomijamy w manifeście tekstowym
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const body = {
|
|
154
|
+
slug,
|
|
155
|
+
title,
|
|
156
|
+
summary: description,
|
|
157
|
+
version,
|
|
158
|
+
changelog,
|
|
159
|
+
visibility,
|
|
160
|
+
skill_md_content: skillContent,
|
|
161
|
+
files_manifest: filesManifest,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return hubRequest('/skills/push', { method: 'POST', body });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function listMcpServers() {
|
|
168
|
+
return hubRequest('/mcp-servers');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function getProducts() {
|
|
172
|
+
return hubRequest('/products');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function getDocs(slug = null) {
|
|
176
|
+
return hubRequest(slug ? `/docs/${encodeURIComponent(slug)}` : '/docs');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function submitPromotion(skillSlug, proposedGlobalSlug, justification) {
|
|
180
|
+
return hubRequest('/promotions', {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
body: {
|
|
183
|
+
skill_slug: skillSlug,
|
|
184
|
+
proposed_global_slug: proposedGlobalSlug,
|
|
185
|
+
justification,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
getApiBase,
|
|
192
|
+
getApiKey,
|
|
193
|
+
scanForSecrets,
|
|
194
|
+
listSkills,
|
|
195
|
+
getSkill,
|
|
196
|
+
pushSkill,
|
|
197
|
+
listMcpServers,
|
|
198
|
+
getProducts,
|
|
199
|
+
getDocs,
|
|
200
|
+
submitPromotion,
|
|
201
|
+
};
|
package/src/tui.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
const { c } = require('./formatters');
|
|
4
|
+
|
|
5
|
+
function selectMenu(title, options) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
let selected = 0;
|
|
8
|
+
const stdout = process.stdout;
|
|
9
|
+
const stdin = process.stdin;
|
|
10
|
+
|
|
11
|
+
function render(first = false) {
|
|
12
|
+
if (!first) {
|
|
13
|
+
readline.cursorTo(stdout, 0);
|
|
14
|
+
readline.moveCursor(stdout, 0, -(options.length + 2));
|
|
15
|
+
}
|
|
16
|
+
stdout.write(`${c.bold}${title}${c.reset}\n`);
|
|
17
|
+
for (let i = 0; i < options.length; i++) {
|
|
18
|
+
const isCur = i === selected;
|
|
19
|
+
const prefix = isCur ? `${c.green}❯ ${c.bold}` : ' ';
|
|
20
|
+
const suffix = isCur ? c.reset : '';
|
|
21
|
+
readline.clearLine(stdout, 0);
|
|
22
|
+
stdout.write(`${prefix}${options[i].label}${suffix}\n`);
|
|
23
|
+
}
|
|
24
|
+
stdout.write(`${c.dim}(użyj ↑/↓ aby wybrać, Enter aby zatwierdzić, Ctrl+C aby wyjść)${c.reset}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
stdin.setRawMode(true);
|
|
28
|
+
stdin.resume();
|
|
29
|
+
stdin.setEncoding('utf8');
|
|
30
|
+
|
|
31
|
+
render(true);
|
|
32
|
+
|
|
33
|
+
function onKey(key) {
|
|
34
|
+
if (key === '\u0003') { // Ctrl+C
|
|
35
|
+
cleanup();
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
if (key === '\r' || key === '\n') { // Enter
|
|
39
|
+
cleanup();
|
|
40
|
+
resolve(options[selected].value);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (key === '\u001b[A' || key === 'k') { // Up
|
|
44
|
+
selected = (selected - 1 + options.length) % options.length;
|
|
45
|
+
render();
|
|
46
|
+
} else if (key === '\u001b[B' || key === 'j') { // Down
|
|
47
|
+
selected = (selected + 1) % options.length;
|
|
48
|
+
render();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cleanup() {
|
|
53
|
+
stdin.removeListener('data', onKey);
|
|
54
|
+
stdin.setRawMode(false);
|
|
55
|
+
stdin.pause();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
stdin.on('data', onKey);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function runInteractiveMenu(dispatch) {
|
|
63
|
+
process.stdout.write(`
|
|
64
|
+
${c.bold}${c.green} ____ _____ _
|
|
65
|
+
| _ \\ ___ ___ _ __|_ _|__ ___ | |___
|
|
66
|
+
| | | |/ _ \\/ _ \\ '__| | |/ _ \\ / _ \\ | / __|
|
|
67
|
+
| |_| | __/ __/ | | | (_) | (_) || \\__ \\
|
|
68
|
+
|____/ \\___|\\___|_| |_|\\___/ \\___(_)_|___/${c.reset}
|
|
69
|
+
${c.dim}Ekosystem narzędzi wykończeniowych Deer Design (v1.0.5)${c.reset}
|
|
70
|
+
\n`);
|
|
71
|
+
|
|
72
|
+
const choice = await selectMenu('Co chcesz zrobić?', [
|
|
73
|
+
{ label: '1. Szybka wycena z metrażu (Kosztorys)', value: 'estimate_calc' },
|
|
74
|
+
{ label: '2. Wyszukaj produkt w Katalogu Connect', value: 'catalog_search' },
|
|
75
|
+
{ label: '3. Sprawdź status dostaw i logistyki', value: 'deliveries_list' },
|
|
76
|
+
{ label: '4. Kalkulator marży i rabatów (Supplier Hub)', value: 'supplier_calc' },
|
|
77
|
+
{ label: '5. Diagnostyka środowiska i połączeń (Doctor)', value: 'doctor' },
|
|
78
|
+
{ label: '6. Sprawdź status logowania i kluczy (Whoami)', value: 'whoami' },
|
|
79
|
+
{ label: '7. Zaloguj do wszystkich produktów (OAuth)', value: 'login' },
|
|
80
|
+
{ label: '8. Zainstaluj integracje MCP dla Cursor / Antigravity', value: 'mcp_install' },
|
|
81
|
+
{ label: '9. Historia wykonanych operacji (Audit log)', value: 'audit' },
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
if (choice === 'estimate_calc') {
|
|
85
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
86
|
+
rl.question('\nPodaj metraż mieszkania w m² (np. 64): ', (ans) => {
|
|
87
|
+
rl.close();
|
|
88
|
+
const area = Number(ans) || 50;
|
|
89
|
+
dispatch(['estimate', 'calculate', String(area)], {});
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (choice === 'catalog_search') {
|
|
95
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
96
|
+
rl.question('\nSzukana fraza (np. bateria, płytka): ', (ans) => {
|
|
97
|
+
rl.close();
|
|
98
|
+
dispatch(['catalog', 'search', ans || 'bateria'], {});
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (choice === 'deliveries_list') {
|
|
104
|
+
await dispatch(['deliveries', 'list'], {});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (choice === 'supplier_calc') {
|
|
109
|
+
await dispatch(['supplier', 'calculate', '1000', '35', '20'], {});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (choice === 'doctor') {
|
|
114
|
+
await dispatch(['doctor'], {});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (choice === 'whoami') {
|
|
119
|
+
await dispatch(['whoami'], {});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (choice === 'login') {
|
|
124
|
+
await dispatch(['login', 'all'], {});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (choice === 'mcp_install') {
|
|
129
|
+
await dispatch(['mcp', 'install'], {});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (choice === 'audit') {
|
|
134
|
+
await dispatch(['audit'], {});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { runInteractiveMenu, selectMenu };
|