@deerdesign/deertools 1.2.5 → 1.2.7
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 +1 -0
- package/src/login.js +39 -8
- 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
|
@@ -543,6 +543,7 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
543
543
|
deertools api <produkt> <METODA> </ścieżka> Wywołuje bezpośrednio API produktu.
|
|
544
544
|
|
|
545
545
|
Narzędzia systemowe:
|
|
546
|
+
deertools update Sprawdza i podpowiada aktualizację CLI.
|
|
546
547
|
deertools doctor [--json] Pełna diagnostyka połączeń, portów i kluczy.
|
|
547
548
|
deertools audit [--limit 25] [--clear] [--json] Historia operacji i wywołań API.
|
|
548
549
|
deertools completion [install|zsh|bash] Autouzupełnianie w powłoce po wciśnięciu Tab.
|
package/src/login.js
CHANGED
|
@@ -33,9 +33,9 @@ function normalizeScope(input) {
|
|
|
33
33
|
return VALID_SCOPES.filter((s) => set.has(s)).join(' ');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function buildAuthorizeUrl(authorizationEndpoint, { redirectUri, state, scope, clientName, challenge }) {
|
|
36
|
+
function buildAuthorizeUrl(authorizationEndpoint, { redirectUri, state, scope, clientName, challenge, clientId = CLIENT_ID }) {
|
|
37
37
|
const u = new URL(authorizationEndpoint);
|
|
38
|
-
u.searchParams.set('client_id',
|
|
38
|
+
u.searchParams.set('client_id', clientId);
|
|
39
39
|
u.searchParams.set('redirect_uri', redirectUri);
|
|
40
40
|
u.searchParams.set('response_type', 'code');
|
|
41
41
|
u.searchParams.set('code_challenge', challenge);
|
|
@@ -67,13 +67,13 @@ function evaluateCallback(query, { expectedState }) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/** Wymiana kodu na klucz: bezpośrednie zapytanie CLI -> serwer (klucz nigdy nie przechodzi przez przeglądarkę). */
|
|
70
|
-
async function exchangeCode(product, tokenEndpoint, { code, verifier, redirectUri, fetchImpl = fetch }) {
|
|
70
|
+
async function exchangeCode(product, tokenEndpoint, { code, verifier, redirectUri, clientId = CLIENT_ID, fetchImpl = fetch }) {
|
|
71
71
|
let res;
|
|
72
72
|
try {
|
|
73
73
|
res = await fetchImpl(tokenEndpoint, {
|
|
74
74
|
method: 'POST',
|
|
75
75
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
76
|
-
body: JSON.stringify({ grant_type: 'authorization_code', code, code_verifier: verifier, redirect_uri: redirectUri, client_id:
|
|
76
|
+
body: JSON.stringify({ grant_type: 'authorization_code', code, code_verifier: verifier, redirect_uri: redirectUri, client_id: clientId }),
|
|
77
77
|
});
|
|
78
78
|
} catch (e) {
|
|
79
79
|
throw new Error(`Nie udało się połączyć z serwerem produktu ${product.name}: ${e.message}`);
|
|
@@ -162,6 +162,28 @@ function openBrowser(url) {
|
|
|
162
162
|
exec(cmd, () => { /* gdy się nie uda, użytkownik ma adres wypisany w terminalu */ });
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
async function registerDynamicClient(registrationEndpoint, { redirectUri, clientName, fetchImpl = fetch }) {
|
|
166
|
+
if (!registrationEndpoint) return null;
|
|
167
|
+
try {
|
|
168
|
+
const res = await fetchImpl(registrationEndpoint, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
client_name: clientName || 'DeerTools CLI',
|
|
173
|
+
redirect_uris: [redirectUri],
|
|
174
|
+
token_endpoint_auth_method: 'none',
|
|
175
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
176
|
+
response_types: ['code'],
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
if (!res.ok) return null;
|
|
180
|
+
const json = await res.json();
|
|
181
|
+
return json.client_id || null;
|
|
182
|
+
} catch (_) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
165
187
|
/**
|
|
166
188
|
* Logowanie OAuth 2.1 dla aplikacji natywnej (RFC 8252): kod autoryzacyjny + PKCE, powrót na 127.0.0.1 (losowy port).
|
|
167
189
|
* opts.metadata pozwala podać adresy bez pobierania (testy). Zwraca wpis poświadczeń zapisany na dysku.
|
|
@@ -179,6 +201,7 @@ async function login(product, opts = {}) {
|
|
|
179
201
|
return new Promise((resolve, reject) => {
|
|
180
202
|
let finished = false;
|
|
181
203
|
let redirectUri = null;
|
|
204
|
+
let activeClientId = CLIENT_ID;
|
|
182
205
|
|
|
183
206
|
const server = http.createServer(async (req, res) => {
|
|
184
207
|
const url = new URL(req.url, 'http://127.0.0.1');
|
|
@@ -192,7 +215,7 @@ async function login(product, opts = {}) {
|
|
|
192
215
|
return;
|
|
193
216
|
}
|
|
194
217
|
try {
|
|
195
|
-
const entry = await exchangeCode(product, meta.token_endpoint, { code: result.code, verifier: pkce.verifier, redirectUri, fetchImpl: opts.fetchImpl });
|
|
218
|
+
const entry = await exchangeCode(product, meta.token_endpoint, { code: result.code, verifier: pkce.verifier, redirectUri, clientId: activeClientId, fetchImpl: opts.fetchImpl });
|
|
196
219
|
credentials.saveProduct(product.id, entry);
|
|
197
220
|
res.writeHead(200, headers);
|
|
198
221
|
res.end(page('Zalogowano', `Klucz do produktu ${product.name} został zapisany w terminalu. Możesz zamknąć to okno.`, true));
|
|
@@ -215,9 +238,17 @@ async function login(product, opts = {}) {
|
|
|
215
238
|
}
|
|
216
239
|
|
|
217
240
|
server.on('error', (e) => done(e));
|
|
218
|
-
server.listen(opts.port || 0, '127.0.0.1', () => {
|
|
241
|
+
server.listen(opts.port || 0, '127.0.0.1', async () => {
|
|
219
242
|
redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
|
|
220
|
-
|
|
243
|
+
let authScope = scope;
|
|
244
|
+
if (meta.registration_endpoint) {
|
|
245
|
+
const dynId = await registerDynamicClient(meta.registration_endpoint, { redirectUri, clientName, fetchImpl: opts.fetchImpl });
|
|
246
|
+
if (dynId) {
|
|
247
|
+
activeClientId = dynId;
|
|
248
|
+
authScope = 'openid email profile offline_access';
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const url = buildAuthorizeUrl(meta.authorization_endpoint, { redirectUri, state, scope: authScope, clientName, challenge: pkce.challenge, clientId: activeClientId });
|
|
221
252
|
log(`Zakres: ${scope}. Otwieram przeglądarkę — zaloguj się w produkcie ${product.name} i kliknij „Zezwól na dostęp”.`);
|
|
222
253
|
log(`Jeśli przeglądarka się nie otworzy, wejdź na:\n${url}\n`);
|
|
223
254
|
if (open) open(url);
|
|
@@ -225,4 +256,4 @@ async function login(product, opts = {}) {
|
|
|
225
256
|
});
|
|
226
257
|
}
|
|
227
258
|
|
|
228
|
-
module.exports = { CLIENT_ID, createPkce, normalizeScope, buildAuthorizeUrl, evaluateCallback, exchangeCode, discoverOAuth, login };
|
|
259
|
+
module.exports = { CLIENT_ID, createPkce, normalizeScope, buildAuthorizeUrl, evaluateCallback, exchangeCode, discoverOAuth, login, registerDynamicClient };
|
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
|
+
};
|