@deerdesign/deertools 1.2.4 → 1.2.6
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/package.json +1 -1
- package/src/cli.js +19 -5
- package/src/commands.js +5 -24
- package/src/login.js +46 -7
- package/src/updater.js +128 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ async function main(argv) {
|
|
|
35
35
|
mcp: c.cmdMcp, doctor: c.cmdDoctor, audit: c.cmdAudit, profile: c.cmdProfile,
|
|
36
36
|
completion: c.cmdCompletion, catalog: c.cmdCatalog, deliveries: c.cmdDeliveries,
|
|
37
37
|
supplier: c.cmdSupplier, rendermind: c.cmdRendermind, baselinker: c.cmdBaseLinker,
|
|
38
|
+
update: require('./updater').cmdUpdate,
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
if (pos.length === 0 && !flags.help && !flags.version && process.stdout.isTTY) {
|
|
@@ -48,15 +49,28 @@ async function main(argv) {
|
|
|
48
49
|
|
|
49
50
|
const cmd = pos.shift() || 'help';
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const shouldCheck = !flags.json && cmd !== 'update' && process.env.NODE_ENV !== 'test';
|
|
53
|
+
const updatePromise = shouldCheck ? require('./updater').checkUpdate().catch(() => null) : null;
|
|
54
|
+
|
|
55
|
+
const finish = async (code = 0) => {
|
|
56
|
+
if (updatePromise) {
|
|
57
|
+
try {
|
|
58
|
+
const info = await updatePromise;
|
|
59
|
+
if (info && info.hasUpdate) require('./updater').printUpdateNotice(info);
|
|
60
|
+
} catch (_) {}
|
|
61
|
+
}
|
|
62
|
+
return code;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (cmd === 'help' || cmd === '--help' || flags.help) { process.stdout.write(c.HELP); return finish(0); }
|
|
66
|
+
if (cmd === '--version' || cmd === 'version' || flags.version) { process.stdout.write(`${require('../package.json').version}\n`); return finish(0); }
|
|
67
|
+
if (!table[cmd]) { process.stderr.write(`Nieznana komenda „${cmd}”.\n${c.HELP}`); return finish(2); }
|
|
54
68
|
try {
|
|
55
69
|
await table[cmd](pos, flags);
|
|
56
|
-
return 0;
|
|
70
|
+
return finish(0);
|
|
57
71
|
} catch (e) {
|
|
58
72
|
if (!e.silent) process.stderr.write(`Błąd: ${e.message}\n`);
|
|
59
|
-
return e.exitCode || 1;
|
|
73
|
+
return finish(e.exitCode || 1);
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
|
package/src/commands.js
CHANGED
|
@@ -41,27 +41,18 @@ async function cmdLogin(pos, flags) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if (pos[0] === 'skills' || pos[0] === 'hub' || pos[0] === 'skill') {
|
|
44
|
+
if ((pos[0] === 'skills' || pos[0] === 'hub' || pos[0] === 'skill') && (pos[1] || flags.token)) {
|
|
45
45
|
out(`\n=== DeerSkills Hub ===`);
|
|
46
46
|
try {
|
|
47
|
-
const {
|
|
47
|
+
const { loginSkillsDirect } = require('./skills-login');
|
|
48
48
|
const token = pos[1] || flags.token;
|
|
49
|
-
|
|
50
|
-
if (token) {
|
|
51
|
-
entry = await loginSkillsDirect(token);
|
|
52
|
-
} else {
|
|
53
|
-
entry = await loginSkillsBrowser({
|
|
54
|
-
port: flags.port ? Number(flags.port) : 0,
|
|
55
|
-
openBrowser: flags['no-open'] ? null : undefined,
|
|
56
|
-
log: out
|
|
57
|
-
});
|
|
58
|
-
}
|
|
49
|
+
await loginSkillsDirect(token);
|
|
59
50
|
out(`✓ Zalogowano do DeerSkills Hub. Klucz zapisany w credentials.json.`);
|
|
60
51
|
out(` Przegląd umiejętności: deertools skill list | Katalog: https://skill-garden-vault.lovable.app`);
|
|
61
52
|
return;
|
|
62
53
|
} catch (e) {
|
|
63
54
|
err(`✗ DeerSkills: ${e.message}`);
|
|
64
|
-
fail('Nie udało się
|
|
55
|
+
fail('Nie udało się zapisać klucza do DeerSkills Hub.', 1);
|
|
65
56
|
}
|
|
66
57
|
}
|
|
67
58
|
|
|
@@ -69,17 +60,6 @@ async function cmdLogin(pos, flags) {
|
|
|
69
60
|
const scope = flags.write ? 'read write' : normalizeScope(flags.scope || 'read');
|
|
70
61
|
let failures = 0;
|
|
71
62
|
for (const product of products) {
|
|
72
|
-
if (product.id === 'skills') {
|
|
73
|
-
try {
|
|
74
|
-
const { loginSkillsBrowser } = require('./skills-login');
|
|
75
|
-
await loginSkillsBrowser({ port: flags.port ? Number(flags.port) : 0, openBrowser: flags['no-open'] ? null : undefined, log: out });
|
|
76
|
-
out(`✓ Zalogowano do DeerSkills Hub.`);
|
|
77
|
-
} catch (e) {
|
|
78
|
-
failures += 1;
|
|
79
|
-
err(`✗ DeerSkills: ${e.message}`);
|
|
80
|
-
}
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
63
|
out(`\n=== ${product.name} ===`);
|
|
84
64
|
try {
|
|
85
65
|
const entry = await login(product, { scope, port: flags.port ? Number(flags.port) : 0, clientName: flags.name, openBrowser: flags['no-open'] ? null : undefined, log: out });
|
|
@@ -563,6 +543,7 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
563
543
|
deertools api <produkt> <METODA> </ścieżka> Wywołuje bezpośrednio API produktu.
|
|
564
544
|
|
|
565
545
|
Narzędzia systemowe:
|
|
546
|
+
deertools update Sprawdza i podpowiada aktualizację CLI.
|
|
566
547
|
deertools doctor [--json] Pełna diagnostyka połączeń, portów i kluczy.
|
|
567
548
|
deertools audit [--limit 25] [--clear] [--json] Historia operacji i wywołań API.
|
|
568
549
|
deertools completion [install|zsh|bash] Autouzupełnianie w powłoce po wciśnięciu Tab.
|
package/src/login.js
CHANGED
|
@@ -83,26 +83,61 @@ async function exchangeCode(product, tokenEndpoint, { code, verifier, redirectUr
|
|
|
83
83
|
if (!res.ok || !json || !json.access_token) {
|
|
84
84
|
throw new Error(`Wymiana kodu na klucz nie powiodła się (${res.status}${json && json.error ? `: ${json.error}` : ''}). Spróbuj zalogować się ponownie.`);
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
const isExpectedPrefix = String(json.access_token).startsWith(product.keyPrefix);
|
|
87
|
+
const isSkillsJwt = product.id === 'skills' && String(json.access_token).startsWith('ey') && json.access_token.includes('.');
|
|
88
|
+
if (!isExpectedPrefix && !isSkillsJwt) {
|
|
87
89
|
throw new Error(`Produkt zwrócił nieoczekiwany klucz (oczekiwano prefiksu ${product.keyPrefix}…).`);
|
|
88
90
|
}
|
|
91
|
+
let email = json.email || null;
|
|
92
|
+
let expires_at = json.expires_at || null;
|
|
93
|
+
if (isSkillsJwt) {
|
|
94
|
+
try {
|
|
95
|
+
const parts = json.access_token.split('.');
|
|
96
|
+
if (parts.length >= 2) {
|
|
97
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf8'));
|
|
98
|
+
if (!email && payload.email) email = payload.email;
|
|
99
|
+
if (!expires_at && payload.exp) expires_at = new Date(payload.exp * 1000).toISOString();
|
|
100
|
+
}
|
|
101
|
+
} catch (_) {}
|
|
102
|
+
}
|
|
89
103
|
return {
|
|
90
104
|
key: json.access_token,
|
|
91
|
-
email:
|
|
105
|
+
email: email,
|
|
92
106
|
scope: json.scope || 'read',
|
|
93
|
-
expires_at:
|
|
107
|
+
expires_at: expires_at,
|
|
94
108
|
logged_at: new Date().toISOString(),
|
|
95
109
|
};
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
/** Adresy OAuth z metadanych serwera (RFC 8414) opublikowanych przez produkt. */
|
|
99
113
|
async function discoverOAuth(product, fetchImpl = fetch) {
|
|
100
|
-
|
|
114
|
+
let url = `${product.site}/.well-known/oauth-authorization-server`;
|
|
101
115
|
let res;
|
|
102
|
-
try {
|
|
103
|
-
|
|
116
|
+
try {
|
|
117
|
+
res = await fetchImpl(url, { headers: { Accept: 'application/json', 'User-Agent': 'deertools-cli' } });
|
|
118
|
+
} catch (e) {
|
|
119
|
+
if (product.id === 'skills') {
|
|
120
|
+
const fallbackSite = process.env.DEERTOOLS_SKILLS_URL || 'https://skill-garden-vault.lovable.app';
|
|
121
|
+
url = `${fallbackSite}/.well-known/oauth-authorization-server`;
|
|
122
|
+
try {
|
|
123
|
+
res = await fetchImpl(url, { headers: { Accept: 'application/json', 'User-Agent': 'deertools-cli' } });
|
|
124
|
+
} catch (_) {}
|
|
125
|
+
}
|
|
104
126
|
}
|
|
105
|
-
|
|
127
|
+
|
|
128
|
+
if (!res || !res.ok) {
|
|
129
|
+
if (product.authorizeUrl && product.tokenUrl && !opts_is_mock(fetchImpl)) {
|
|
130
|
+
const authEndpoint = product.id === 'skills' ? 'https://skill-garden-vault.lovable.app/oauth/authorize' : product.authorizeUrl;
|
|
131
|
+
return {
|
|
132
|
+
authorization_endpoint: authEndpoint,
|
|
133
|
+
token_endpoint: product.tokenUrl,
|
|
134
|
+
code_challenge_methods_supported: ['S256'],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (!res) throw new Error(`Nie można pobrać metadanych OAuth produktu ${product.name} (${url}).`);
|
|
138
|
+
throw new Error(`Produkt ${product.name} nie opublikował jeszcze metadanych OAuth (${url} → ${res.status}). Logowanie z CLI nie jest tam dostępne.`);
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
let meta;
|
|
107
142
|
try { meta = JSON.parse(await res.text()); } catch (_) { throw new Error(`Metadane OAuth produktu ${product.name} nie są poprawnym JSON-em.`); }
|
|
108
143
|
if (!meta.authorization_endpoint || !meta.token_endpoint) throw new Error(`Metadane OAuth produktu ${product.name} nie zawierają authorization_endpoint / token_endpoint.`);
|
|
@@ -113,6 +148,10 @@ async function discoverOAuth(product, fetchImpl = fetch) {
|
|
|
113
148
|
return meta;
|
|
114
149
|
}
|
|
115
150
|
|
|
151
|
+
function opts_is_mock(f) {
|
|
152
|
+
return f !== fetch && typeof global !== 'undefined' && f !== global.fetch;
|
|
153
|
+
}
|
|
154
|
+
|
|
116
155
|
function page(title, body, ok) {
|
|
117
156
|
const color = ok ? '#10b981' : '#ef4444';
|
|
118
157
|
return `<!doctype html><html lang="pl"><meta charset="utf-8"><title>DeerTools CLI</title><body style="font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#09090b;color:#f4f4f5;text-align:center;padding:60px 20px"><div style="max-width:480px;margin:0 auto;background:#18181b;border:1px solid #27272a;border-radius:16px;padding:40px"><h1 style="color:${color};font-size:22px">${escapeHtml(title)}</h1><p style="color:#a1a1aa;line-height:1.5">${escapeHtml(body)}</p></div></body></html>`;
|
package/src/updater.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
const CACHE_TTL_MS = 12 * 60 * 60 * 1000; // 12 hours
|
|
7
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/@deerdesign/deertools/latest';
|
|
8
|
+
const PKG = require('../package.json');
|
|
9
|
+
|
|
10
|
+
function getCachePath() {
|
|
11
|
+
const dir = path.join(os.homedir(), '.deertools');
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
14
|
+
} catch (_) {}
|
|
15
|
+
return path.join(dir, 'update-check.json');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function compareSemver(a, b) {
|
|
19
|
+
const pa = String(a).replace(/^v/, '').split('.').map(Number);
|
|
20
|
+
const pb = String(b).replace(/^v/, '').split('.').map(Number);
|
|
21
|
+
for (let i = 0; i < 3; i++) {
|
|
22
|
+
const na = pa[i] || 0;
|
|
23
|
+
const nb = pb[i] || 0;
|
|
24
|
+
if (na > nb) return 1;
|
|
25
|
+
if (na < nb) return -1;
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function fetchLatestVersion() {
|
|
31
|
+
const ctrl = new AbortController();
|
|
32
|
+
const t = setTimeout(() => ctrl.abort(), 2500);
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(REGISTRY_URL, {
|
|
35
|
+
headers: { 'Accept': 'application/json', 'User-Agent': 'deertools-cli' },
|
|
36
|
+
signal: ctrl.signal,
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) return null;
|
|
39
|
+
const json = await res.json();
|
|
40
|
+
return json.version || null;
|
|
41
|
+
} catch (_) {
|
|
42
|
+
return null;
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(t);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function checkUpdate() {
|
|
49
|
+
const cacheFile = getCachePath();
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
let cached = null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
if (fs.existsSync(cacheFile)) {
|
|
55
|
+
cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
56
|
+
}
|
|
57
|
+
} catch (_) {}
|
|
58
|
+
|
|
59
|
+
let latestVersion = cached && cached.latestVersion ? cached.latestVersion : null;
|
|
60
|
+
|
|
61
|
+
if (!cached || !cached.lastChecked || (now - cached.lastChecked > CACHE_TTL_MS)) {
|
|
62
|
+
const fetched = await fetchLatestVersion();
|
|
63
|
+
if (fetched) {
|
|
64
|
+
latestVersion = fetched;
|
|
65
|
+
try {
|
|
66
|
+
fs.writeFileSync(cacheFile, JSON.stringify({ lastChecked: now, latestVersion }), 'utf8');
|
|
67
|
+
} catch (_) {}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (latestVersion && compareSemver(latestVersion, PKG.version) > 0) {
|
|
72
|
+
return {
|
|
73
|
+
hasUpdate: true,
|
|
74
|
+
current: PKG.version,
|
|
75
|
+
latest: latestVersion,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { hasUpdate: false, current: PKG.version, latest: latestVersion };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function printUpdateNotice(updateInfo) {
|
|
83
|
+
if (!updateInfo || !updateInfo.hasUpdate) return;
|
|
84
|
+
const { current, latest } = updateInfo;
|
|
85
|
+
const line1 = `Dostępna nowa wersja DeerTools CLI: ${current} → ${latest}`;
|
|
86
|
+
const line2 = `Zaktualizuj: npm install -g @deerdesign/deertools@latest`;
|
|
87
|
+
const line3 = `lub użyj: npx -y @deerdesign/deertools@latest <komenda>`;
|
|
88
|
+
const len = Math.max(line1.length, line2.length, line3.length) + 4;
|
|
89
|
+
const border = '─'.repeat(len);
|
|
90
|
+
|
|
91
|
+
process.stderr.write(`\n\x1b[33m┌${border}┐\x1b[0m\n`);
|
|
92
|
+
process.stderr.write(`\x1b[33m│ ${line1.padEnd(len - 2)}│\x1b[0m\n`);
|
|
93
|
+
process.stderr.write(`\x1b[33m│ ${line2.padEnd(len - 2)}│\x1b[0m\n`);
|
|
94
|
+
process.stderr.write(`\x1b[33m│ ${line3.padEnd(len - 2)}│\x1b[0m\n`);
|
|
95
|
+
process.stderr.write(`\x1b[33m└${border}┘\x1b[0m\n\n`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function cmdUpdate(pos, flags) {
|
|
99
|
+
process.stdout.write(`Sprawdzam aktualizacje w rejestrze npm...\n`);
|
|
100
|
+
const latest = await fetchLatestVersion();
|
|
101
|
+
if (!latest) {
|
|
102
|
+
process.stdout.write(`Nie udało się połączyć z rejestrem npm. Sprawdź połączenie z internetem.\n`);
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const cmp = compareSemver(latest, PKG.version);
|
|
107
|
+
if (cmp <= 0) {
|
|
108
|
+
process.stdout.write(`✓ DeerTools CLI jest w najnowszej wersji (${PKG.version}).\n`);
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
process.stdout.write(`\nZnaleziono nowszą wersję: ${PKG.version} → \x1b[32m${latest}\x1b[0m\n\n`);
|
|
113
|
+
process.stdout.write(`Aby zaktualizować instalację globalną, uruchom:\n`);
|
|
114
|
+
process.stdout.write(` \x1b[36mnpm install -g @deerdesign/deertools@latest\x1b[0m\n\n`);
|
|
115
|
+
process.stdout.write(`Jeśli instalacja wymaga uprawnień administratora:\n`);
|
|
116
|
+
process.stdout.write(` \x1b[36msudo npm install -g @deerdesign/deertools@latest\x1b[0m\n\n`);
|
|
117
|
+
process.stdout.write(`A jeśli korzystasz z npx, każda komenda zawsze pobiera najnowszą wersję z tagiem @latest:\n`);
|
|
118
|
+
process.stdout.write(` \x1b[36mnpx -y @deerdesign/deertools@latest <komenda>\x1b[0m\n\n`);
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
checkUpdate,
|
|
124
|
+
printUpdateNotice,
|
|
125
|
+
cmdUpdate,
|
|
126
|
+
compareSemver,
|
|
127
|
+
fetchLatestVersion,
|
|
128
|
+
};
|