@deerdesign/deertools 1.2.4 → 1.2.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deerdesign/deertools",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
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 { loginSkillsBrowser, loginSkillsDirect } = require('./skills-login');
47
+ const { loginSkillsDirect } = require('./skills-login');
48
48
  const token = pos[1] || flags.token;
49
- let entry;
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ę zalogować do DeerSkills Hub.', 1);
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 });
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
- if (!String(json.access_token).startsWith(product.keyPrefix)) {
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: json.email || null,
105
+ email: email,
92
106
  scope: json.scope || 'read',
93
- expires_at: json.expires_at || null,
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
- const url = `${product.site}/.well-known/oauth-authorization-server`;
114
+ let url = `${product.site}/.well-known/oauth-authorization-server`;
101
115
  let res;
102
- try { res = await fetchImpl(url, { headers: { Accept: 'application/json', 'User-Agent': 'deertools-cli' } }); } catch (e) {
103
- throw new Error(`Nie można pobrać metadanych OAuth produktu ${product.name} (${url}): ${e.message}`);
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
- if (!res.ok) throw new Error(`Produkt ${product.name} nie opublikował jeszcze metadanych OAuth (${url} → ${res.status}). Logowanie z CLI nie jest tam dostępne.`);
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>`;