latinfo 0.20.1 → 0.20.2

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.
Files changed (2) hide show
  1. package/dist/index.js +84 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ const odis_search_1 = require("./odis-search");
49
49
  const mphf_search_1 = require("./mphf-search");
50
50
  const VERSION = '0.18.1';
51
51
  const API_URL = process.env.LATINFO_API_URL || 'https://api.latinfo.dev';
52
- const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || 'Ov23li5fcQaiCsVtaMKK';
52
+ const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || 'Ov23liZAqpaGnYQ6Kp5I';
53
53
  const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.latinfo');
54
54
  const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
55
55
  // --- JSON mode ---
@@ -157,7 +157,35 @@ async function apiRequest(config, path) {
157
157
  return res;
158
158
  }
159
159
  // --- Commands ---
160
- async function login(token) {
160
+ async function login(token, username) {
161
+ // Zero-friction login: admin generates key for a username (no browser)
162
+ if (username) {
163
+ let adminSecret;
164
+ try {
165
+ adminSecret = requireAdmin();
166
+ }
167
+ catch {
168
+ console.error('Admin access required for username login. Need ADMIN_SECRET in .dev.vars or env.');
169
+ process.exit(1);
170
+ }
171
+ const res = await fetch(`${API_URL}/team/members`, {
172
+ method: 'POST',
173
+ headers: { Authorization: `Bearer ${adminSecret}`, 'Content-Type': 'application/json' },
174
+ body: JSON.stringify({ github_username: username }),
175
+ });
176
+ const data = await res.json();
177
+ if (!res.ok) {
178
+ console.error(data.message || data.error);
179
+ process.exit(1);
180
+ }
181
+ if (!data.api_key) {
182
+ console.error('Failed to generate API key');
183
+ process.exit(1);
184
+ }
185
+ saveConfig({ api_key: data.api_key, github_username: username, is_team: true, team_role: data.role });
186
+ console.log(`Logged in as ${username} (${data.role}).`);
187
+ return;
188
+ }
161
189
  // PAT login: no browser needed
162
190
  if (token) {
163
191
  const authRes = await fetch(`${API_URL}/auth/github`, {
@@ -188,21 +216,52 @@ async function login(token) {
188
216
  console.log(`Logged in as ${authData.github_username}${config.is_team ? ` (team: ${config.team_role})` : ''}`);
189
217
  return;
190
218
  }
191
- // OAuth login: opens browser
192
- const port = 8400;
193
- const redirectUri = `http://localhost:${port}/callback`;
194
- const scope = 'read:user,user:email';
195
- const state = crypto.randomUUID();
196
- const authUrl = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${scope}&state=${state}`;
197
- console.log('Opening GitHub...');
198
- console.log('If your browser does not open, visit this URL:\n');
199
- console.log(authUrl + '\n');
200
- openBrowser(authUrl);
201
- const code = await waitForCallback(port, state);
219
+ // Device Flow: no browser redirect needed, works in SSH/containers/agents
220
+ const deviceRes = await fetch('https://github.com/login/device/code', {
221
+ method: 'POST',
222
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
223
+ body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, scope: 'read:user,user:email' }),
224
+ });
225
+ const device = await deviceRes.json();
226
+ console.log(`\n Go to: ${device.verification_uri}`);
227
+ console.log(` Enter code: ${device.user_code}\n`);
228
+ // Try to open browser automatically
229
+ try {
230
+ openBrowser(device.verification_uri);
231
+ }
232
+ catch { }
233
+ // Poll until authorized
234
+ let ghAccessToken = null;
235
+ const deadline = Date.now() + device.expires_in * 1000;
236
+ while (Date.now() < deadline) {
237
+ await new Promise(r => setTimeout(r, (device.interval || 5) * 1000));
238
+ const pollRes = await fetch('https://github.com/login/oauth/access_token', {
239
+ method: 'POST',
240
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
241
+ body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, device_code: device.device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }),
242
+ });
243
+ const poll = await pollRes.json();
244
+ if (poll.access_token) {
245
+ ghAccessToken = poll.access_token;
246
+ break;
247
+ }
248
+ if (poll.error === 'expired_token') {
249
+ console.error('Code expired. Run latinfo login again.');
250
+ process.exit(1);
251
+ }
252
+ if (poll.error && poll.error !== 'authorization_pending' && poll.error !== 'slow_down') {
253
+ console.error(`GitHub error: ${poll.error}`);
254
+ process.exit(1);
255
+ }
256
+ }
257
+ if (!ghAccessToken) {
258
+ console.error('Login timed out.');
259
+ process.exit(1);
260
+ }
202
261
  const authRes = await fetch(`${API_URL}/auth/github`, {
203
262
  method: 'POST',
204
263
  headers: { 'Content-Type': 'application/json' },
205
- body: JSON.stringify({ code, redirect_uri: redirectUri }),
264
+ body: JSON.stringify({ access_token: ghAccessToken }),
206
265
  });
207
266
  if (!authRes.ok) {
208
267
  console.error('Error getting API key:', await authRes.text());
@@ -4000,9 +4059,16 @@ async function teamCmd(args) {
4000
4059
  }
4001
4060
  console.log(`Added ${username} to team (${data.role}).`);
4002
4061
  if (data.api_key) {
4003
- console.log(`\n API key: ${data.api_key}\n`);
4004
- console.log(` Share this with the member. They set:`);
4005
- console.log(` export LATINFO_API_KEY=${data.api_key}`);
4062
+ if (args.includes('--login')) {
4063
+ // Save key locally login as this user
4064
+ saveConfig({ api_key: data.api_key, github_username: username, is_team: true, team_role: data.role });
4065
+ console.log(`Logged in as ${username} (${data.role}).`);
4066
+ }
4067
+ else {
4068
+ console.log(`\n API key: ${data.api_key}\n`);
4069
+ console.log(` Share this with the member. They set:`);
4070
+ console.log(` export LATINFO_API_KEY=${data.api_key}`);
4071
+ }
4006
4072
  }
4007
4073
  try {
4008
4074
  const { execSync: exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
@@ -4260,7 +4326,7 @@ else {
4260
4326
  // Admin commands (flat)
4261
4327
  switch (command) {
4262
4328
  case 'login':
4263
- login(tokenFlag).catch(e => { console.error(e); process.exit(1); });
4329
+ login(tokenFlag, args[0]).catch(e => { console.error(e); process.exit(1); });
4264
4330
  break;
4265
4331
  case 'logout':
4266
4332
  logout();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latinfo",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
4
4
  "description": "Tax registry & procurement API for Latin America. Query RUC, DNI, NIT, licitaciones from Peru & Colombia. Offline MPHF search, full OCDS data, updated daily.",
5
5
  "homepage": "https://latinfo.dev",
6
6
  "repository": {