latinfo 0.20.0 → 0.20.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/dist/index.js CHANGED
@@ -63,6 +63,10 @@ function jsonError(error, message) {
63
63
  process.exit(1);
64
64
  }
65
65
  function loadConfig() {
66
+ // Env var takes priority — zero friction for agents
67
+ if (process.env.LATINFO_API_KEY) {
68
+ return { api_key: process.env.LATINFO_API_KEY, github_username: '', is_team: true, team_role: 'member' };
69
+ }
66
70
  try {
67
71
  return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, 'utf-8'));
68
72
  }
@@ -169,8 +173,19 @@ async function login(token) {
169
173
  process.exit(1);
170
174
  }
171
175
  const authData = await authRes.json();
172
- saveConfig({ api_key: authData.api_key, github_username: authData.github_username });
173
- console.log(`Logged in as ${authData.github_username}`);
176
+ const config = { api_key: authData.api_key, github_username: authData.github_username };
177
+ // Check team membership
178
+ try {
179
+ const teamRes = await fetch(`${API_URL}/team/me`, { headers: { Authorization: `Bearer ${config.api_key}` } });
180
+ if (teamRes.ok) {
181
+ const team = await teamRes.json();
182
+ config.is_team = true;
183
+ config.team_role = team.role;
184
+ }
185
+ }
186
+ catch { }
187
+ saveConfig(config);
188
+ console.log(`Logged in as ${authData.github_username}${config.is_team ? ` (team: ${config.team_role})` : ''}`);
174
189
  return;
175
190
  }
176
191
  // OAuth login: opens browser
@@ -194,8 +209,19 @@ async function login(token) {
194
209
  process.exit(1);
195
210
  }
196
211
  const authData = await authRes.json();
197
- saveConfig({ api_key: authData.api_key, github_username: authData.github_username });
198
- console.log(`Logged in as ${authData.github_username}`);
212
+ const config = { api_key: authData.api_key, github_username: authData.github_username };
213
+ // Check team membership
214
+ try {
215
+ const teamRes = await fetch(`${API_URL}/team/me`, { headers: { Authorization: `Bearer ${config.api_key}` } });
216
+ if (teamRes.ok) {
217
+ const team = await teamRes.json();
218
+ config.is_team = true;
219
+ config.team_role = team.role;
220
+ }
221
+ }
222
+ catch { }
223
+ saveConfig(config);
224
+ console.log(`Logged in as ${authData.github_username}${config.is_team ? ` (team: ${config.team_role})` : ''}`);
199
225
  }
200
226
  async function ruc(rucNumber) {
201
227
  if (!rucNumber || !/^\d{11}$/.test(rucNumber)) {
@@ -477,10 +503,17 @@ async function search(query) {
477
503
  function whoami() {
478
504
  const config = requireAuth();
479
505
  if (jsonFlag) {
480
- console.log(JSON.stringify({ username: config.github_username, api_key: config.api_key }));
506
+ console.log(JSON.stringify({ username: config.github_username, api_key: config.api_key, is_team: config.is_team, team_role: config.team_role }));
481
507
  return;
482
508
  }
483
- console.log(config.github_username);
509
+ if (config.is_team) {
510
+ const badges = { admin: '★', member: '●' };
511
+ const badge = badges[config.team_role || 'member'] || '●';
512
+ console.log(`${badge} ${config.github_username} [TEAM ${(config.team_role || 'member').toUpperCase()}]`);
513
+ }
514
+ else {
515
+ console.log(config.github_username);
516
+ }
484
517
  }
485
518
  async function plan() {
486
519
  const config = requireAuth();
@@ -553,7 +586,7 @@ async function adminRequest(path) {
553
586
  return res;
554
587
  }
555
588
  async function importsRun(source) {
556
- const valid = ['pe-sunat-padron', 'pe-oece-licitaciones', 'co-rues', 'all'];
589
+ const valid = ['pe-sunat-padron', 'pe-oece-tenders', 'co-rues', 'all'];
557
590
  if (!valid.includes(source)) {
558
591
  console.error(`Unknown source. Valid: ${valid.join(', ')}`);
559
592
  process.exit(1);
@@ -1450,11 +1483,12 @@ function logout() {
1450
1483
  console.log('Logged out.');
1451
1484
  }
1452
1485
  function help() {
1486
+ const config = loadConfig();
1487
+ const isTeam = config?.is_team;
1453
1488
  console.log(`latinfo v${VERSION} — Tax registry API for Latin America
1454
1489
 
1455
1490
  USAGE
1456
1491
  latinfo <country> <institution> <dataset> <id|--search query|--dni id> [--json]
1457
- latinfo <admin-command> [args]
1458
1492
 
1459
1493
  QUICK START
1460
1494
  npm install -g latinfo
@@ -1477,25 +1511,26 @@ DATA SOURCES
1477
1511
  latinfo pe osce fines <ruc> Provider fines
1478
1512
  latinfo pe osce fines --search <query>
1479
1513
 
1514
+ Peru — SERVIR
1515
+ latinfo pe servir sanctions <dni> Public sector sanctions
1516
+ latinfo pe servir sanctions --search <query>
1517
+
1518
+ Peru — REDAM
1519
+ latinfo pe redam registry <dni> Food debt debtors
1520
+ latinfo pe redam registry --search <query>
1521
+
1480
1522
  Peru — OECE
1481
1523
  latinfo pe oece tenders <query> [flags] Government procurement
1482
1524
  Flags: --category, --min-amount, --max-amount, --buyer, --status, --limit
1483
1525
 
1484
1526
  Colombia — RUES
1485
- latinfo co rues registry <nit> Business registry (3.3M records)
1527
+ latinfo co rues registry <nit> Business registry (9M+ records)
1486
1528
  latinfo co rues registry --search <query>
1487
1529
 
1488
- ADMIN
1530
+ COMMANDS
1489
1531
  login [--token <github_pat>] GitHub OAuth or PAT login
1490
1532
  logout Remove credentials
1491
1533
  whoami Show authenticated user
1492
- imports Show import status
1493
- imports run <source> Trigger import
1494
- imports report [days] Import diagnostics
1495
- costs <users> [avg_req] [pro_%] Cost simulation
1496
- costs --live Production cost report
1497
- bench [flags] Stress test API
1498
- easypipe <command> Generic import pipeline
1499
1534
  completion [bash|zsh] Shell completions
1500
1535
  help This help text
1501
1536
 
@@ -1505,13 +1540,34 @@ FLAGS
1505
1540
  --dni Lookup by DNI (Peru only)
1506
1541
  --version Print version
1507
1542
 
1508
- PRICING
1509
- Free 100,000 requests/day
1510
- Pro 10M requests/month $1/month
1511
-
1512
- CONFIG
1513
- ~/.latinfo/config.json API key
1514
- LATINFO_API_URL Override API URL`);
1543
+ Free and unlimited. No credit card needed.`);
1544
+ if (isTeam) {
1545
+ const isAdmin = config?.team_role === 'admin';
1546
+ console.log(`
1547
+ TEAM
1548
+ tasks My tasks
1549
+ tasks complete <id> Mark done
1550
+ tasks rank Team ranking
1551
+ pipe local <source> Import data locally
1552
+ pipe publish <source> Publish to production
1553
+ docs <topic> Internal documentation`);
1554
+ if (isAdmin) {
1555
+ console.log(`
1556
+ ADMIN
1557
+ team add <username> [--admin] Add team member
1558
+ team remove <username> Remove member
1559
+ team list List all members
1560
+ tasks assign <user> "<title>" [--points N] Assign task
1561
+ tasks approve <id> Approve + award points
1562
+ tasks reject <id> "<reason>" Reject back
1563
+ tasks delete <id> Delete task
1564
+ tasks list --all All tasks
1565
+ imports Show import status
1566
+ imports run <source> Trigger import
1567
+ bench [flags] Stress test API
1568
+ costs --live Production cost report`);
1569
+ }
1570
+ }
1515
1571
  }
1516
1572
  function printLogo() {
1517
1573
  if (!process.stdout.isTTY)
@@ -2710,14 +2766,47 @@ async function pipePublish(args) {
2710
2766
  console.error(`[pipe] Git error: ${e.message}`);
2711
2767
  process.exit(1);
2712
2768
  }
2713
- // 2. Deploy Worker
2714
- console.log(`[pipe] Deploying Worker...`);
2769
+ // 2. Deploy Worker via GitHub Actions (no local Cloudflare token needed)
2770
+ console.log(`[pipe] Waiting for deploy workflow (GitHub Actions)...`);
2715
2771
  try {
2716
- run(`npx wrangler deploy`, { cwd: repo, stdio: 'inherit' });
2772
+ const { execSync: exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
2773
+ const maxWait = 300; // 5 min max
2774
+ const interval = 10;
2775
+ let elapsed = 0;
2776
+ let deployed = false;
2777
+ // Give GitHub a moment to register the push event
2778
+ exec('sleep 5');
2779
+ while (elapsed < maxWait) {
2780
+ const result = exec(`gh run list --workflow=deploy.yml --branch=main --limit=1 --json status,conclusion,headSha`, { cwd: repo, encoding: 'utf-8', stdio: 'pipe' }).toString().trim();
2781
+ const ghRuns = JSON.parse(result);
2782
+ if (ghRuns.length > 0) {
2783
+ const latest = ghRuns[0];
2784
+ if (latest.status === 'completed') {
2785
+ if (latest.conclusion === 'success') {
2786
+ console.log(`[pipe] Deploy successful.`);
2787
+ deployed = true;
2788
+ break;
2789
+ }
2790
+ else {
2791
+ console.error(`[pipe] Deploy failed (${latest.conclusion}) — rolling back`);
2792
+ exec(`git checkout HEAD^ -- src/sources.ts .github/workflows/import.yml && git commit -m "Rollback: remove ${sourceName}" && git push`, { cwd: repo, stdio: 'pipe' });
2793
+ process.exit(1);
2794
+ }
2795
+ }
2796
+ if (elapsed % 30 === 0 && elapsed > 0) {
2797
+ console.log(`[pipe] Still deploying... (${elapsed}s)`);
2798
+ }
2799
+ }
2800
+ exec(`sleep ${interval}`);
2801
+ elapsed += interval;
2802
+ }
2803
+ if (!deployed) {
2804
+ console.error(`[pipe] Deploy timed out after ${maxWait}s`);
2805
+ process.exit(1);
2806
+ }
2717
2807
  }
2718
- catch {
2719
- console.error(`[pipe] Deploy failed — rolling back`);
2720
- run(`git checkout HEAD^ -- src/sources.ts .github/workflows/import.yml && git commit -m "Rollback: remove ${sourceName}" && git push`, { cwd: repo, stdio: 'pipe' });
2808
+ catch (e) {
2809
+ console.error(`[pipe] Deploy error: ${e.message}`);
2721
2810
  process.exit(1);
2722
2811
  }
2723
2812
  // 3. Trigger import on runner
@@ -3408,6 +3497,7 @@ const DOCS = {
3408
3497
  index: `latinfo docs — complete documentation
3409
3498
 
3410
3499
  TOPICS
3500
+ latinfo docs team Team system, tasks, ranking, and how the AI PM works
3411
3501
  latinfo docs pipe How to create a data pipeline (full guide)
3412
3502
  latinfo docs fields searchFieldIndex, statusFieldIndex explained
3413
3503
  latinfo docs v2 V2 search index + MPHF (mandatory)
@@ -3416,6 +3506,75 @@ TOPICS
3416
3506
  latinfo docs troubleshooting Common errors and fixes
3417
3507
  latinfo docs architecture How latinfo works internally
3418
3508
  latinfo docs api API endpoints and response format`,
3509
+ team: `TEAM SYSTEM
3510
+
3511
+ The AI agent is the project manager (PM). It assigns tasks, tracks progress,
3512
+ and coordinates the team through the CLI. No human PM needed.
3513
+
3514
+ GETTING STARTED (new member)
3515
+
3516
+ 1. Install: npm i -g latinfo
3517
+ 2. Login: latinfo login (authenticates with your GitHub account)
3518
+ 3. See tasks: latinfo tasks (shows your assigned tasks)
3519
+ 4. Complete: latinfo tasks complete <id>
3520
+ 5. Ranking: latinfo tasks rank
3521
+
3522
+ ADMIN COMMANDS (requires admin access)
3523
+
3524
+ latinfo team add <username> Add team member
3525
+ latinfo team add <username> --admin Add as admin
3526
+ latinfo team remove <username> Remove member
3527
+ latinfo team list List all members + points
3528
+
3529
+ latinfo tasks assign <user> "<title>" [--points N] Assign task (default 10 pts)
3530
+ latinfo tasks approve <id> Approve completed task (awards points)
3531
+ latinfo tasks reject <id> "<reason>" Reject back to member
3532
+ latinfo tasks delete <id> Delete task
3533
+ latinfo tasks list --all See all tasks from all members
3534
+
3535
+ MEMBER COMMANDS (requires latinfo login)
3536
+
3537
+ latinfo tasks My tasks
3538
+ latinfo tasks complete <id> Mark task as done (waits for approval)
3539
+ latinfo tasks rank Team ranking
3540
+
3541
+ TASK FLOW
3542
+
3543
+ Admin assigns task → Member sees it (pending)
3544
+ Member completes → Status changes to "completed"
3545
+ Admin approves → Points awarded, status "approved"
3546
+ Admin rejects → Back to "pending" with reason
3547
+
3548
+ RANKING LEVELS
3549
+
3550
+ Bronze 0-49 pts
3551
+ Silver 50-149 pts
3552
+ Gold 150-299 pts
3553
+ Diamond 300+ pts
3554
+
3555
+ Points come from approved tasks. The ranking updates automatically.
3556
+ Admins cannot manually change points — only approve/reject tasks.
3557
+
3558
+ HOW THE AI PM WORKS
3559
+
3560
+ The AI agent (Claude, GPT, etc.) can run all these commands autonomously:
3561
+
3562
+ latinfo tasks list --all → See everything
3563
+ latinfo tasks assign ... → Assign work
3564
+ latinfo tasks approve ... → Approve completed work
3565
+ latinfo tasks rank → Report progress
3566
+
3567
+ The AI reads "latinfo docs team" to learn how to operate,
3568
+ then manages the team without human intervention.
3569
+
3570
+ FIRST TIME SETUP (admin)
3571
+
3572
+ 1. You (admin) run: latinfo team add <their-github-username>
3573
+ 2. They run: latinfo login
3574
+ 3. You assign: latinfo tasks assign <username> "Find Mexico SAT data sources"
3575
+ 4. They work and: latinfo tasks complete <id>
3576
+ 5. You approve: latinfo tasks approve <id>
3577
+ 6. Everyone sees: latinfo tasks rank`,
3419
3578
  pipe: `HOW TO CREATE A DATA PIPELINE
3420
3579
 
3421
3580
  latinfo pipe handles storage, indexing, search, and API serving automatically.
@@ -3799,6 +3958,281 @@ function docs(args) {
3799
3958
  }
3800
3959
  console.log(content);
3801
3960
  }
3961
+ function requireTeam() {
3962
+ const config = loadConfig();
3963
+ if (!config?.is_team) {
3964
+ console.error('This command is only available to team members.');
3965
+ process.exit(1);
3966
+ }
3967
+ }
3968
+ function requireTeamAdmin() {
3969
+ const config = loadConfig();
3970
+ if (!config?.is_team) {
3971
+ console.error('This command is only available to team members.');
3972
+ process.exit(1);
3973
+ }
3974
+ if (config.team_role !== 'admin') {
3975
+ console.error('This command requires admin access.');
3976
+ process.exit(1);
3977
+ }
3978
+ }
3979
+ // --- Team & Tasks ---
3980
+ async function teamCmd(args) {
3981
+ const sub = args[0];
3982
+ const adminSecret = requireAdmin();
3983
+ const headers = { Authorization: `Bearer ${adminSecret}`, 'Content-Type': 'application/json' };
3984
+ switch (sub) {
3985
+ case 'add': {
3986
+ const username = args[1];
3987
+ const isAdmin = args.includes('--admin');
3988
+ if (!username) {
3989
+ console.error('Usage: latinfo team add <github-username> [--admin]');
3990
+ process.exit(1);
3991
+ }
3992
+ const res = await fetch(`${API_URL}/team/members`, {
3993
+ method: 'POST', headers,
3994
+ body: JSON.stringify({ github_username: username, role: isAdmin ? 'admin' : 'member' }),
3995
+ });
3996
+ const data = await res.json();
3997
+ if (!res.ok) {
3998
+ console.error(data.message || data.error);
3999
+ process.exit(1);
4000
+ }
4001
+ console.log(`Added ${username} to team (${data.role}).`);
4002
+ 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}`);
4006
+ }
4007
+ try {
4008
+ const { execSync: exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
4009
+ exec(`gh api repos/carrerahaus/latinfo-api/collaborators/${username} -X PUT -f permission=push`, { stdio: 'pipe' });
4010
+ console.log(`Added ${username} as GitHub collaborator.`);
4011
+ }
4012
+ catch {
4013
+ console.log('Note: Could not add as GitHub collaborator (gh CLI required).');
4014
+ }
4015
+ break;
4016
+ }
4017
+ case 'remove': {
4018
+ const username = args[1];
4019
+ if (!username) {
4020
+ console.error('Usage: latinfo team remove <github-username>');
4021
+ process.exit(1);
4022
+ }
4023
+ const res = await fetch(`${API_URL}/team/members/${username}`, { method: 'DELETE', headers });
4024
+ const data = await res.json();
4025
+ if (!res.ok) {
4026
+ console.error(data.message || data.error);
4027
+ process.exit(1);
4028
+ }
4029
+ console.log(`Removed ${username} from team.`);
4030
+ try {
4031
+ const { execSync: exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
4032
+ exec(`gh api repos/carrerahaus/latinfo-api/collaborators/${username} -X DELETE`, { stdio: 'pipe' });
4033
+ console.log(`Removed ${username} from GitHub.`);
4034
+ }
4035
+ catch { }
4036
+ break;
4037
+ }
4038
+ case 'list': {
4039
+ const res = await fetch(`${API_URL}/team/members`, { headers });
4040
+ const data = await res.json();
4041
+ if (!res.ok) {
4042
+ console.error('Failed to list members');
4043
+ process.exit(1);
4044
+ }
4045
+ if (data.length === 0) {
4046
+ console.log('No team members.');
4047
+ return;
4048
+ }
4049
+ console.log('\n TEAM\n');
4050
+ for (const m of data) {
4051
+ const level = m.points >= 300 ? 'Diamond' : m.points >= 150 ? 'Gold' : m.points >= 50 ? 'Silver' : 'Bronze';
4052
+ console.log(` ${m.github_username.padEnd(22)} ${m.role.padEnd(8)} ${String(m.points).padStart(4)} pts ${level}`);
4053
+ }
4054
+ console.log();
4055
+ break;
4056
+ }
4057
+ default:
4058
+ console.log(`Usage:
4059
+ latinfo team add <username> [--admin] Add team member
4060
+ latinfo team remove <username> Remove member
4061
+ latinfo team list List all members`);
4062
+ }
4063
+ }
4064
+ async function tasksCmd(args) {
4065
+ const sub = args[0];
4066
+ // Admin commands use ADMIN_SECRET
4067
+ if (['assign', 'approve', 'reject', 'delete'].includes(sub)) {
4068
+ const adminSecret = requireAdmin();
4069
+ const headers = { Authorization: `Bearer ${adminSecret}`, 'Content-Type': 'application/json' };
4070
+ switch (sub) {
4071
+ case 'assign': {
4072
+ const username = args[1];
4073
+ const title = args[2];
4074
+ const pointsIdx = args.indexOf('--points');
4075
+ const points = pointsIdx !== -1 ? parseInt(args[pointsIdx + 1]) : 10;
4076
+ if (!username || !title) {
4077
+ console.error('Usage: latinfo tasks assign <username> "<title>" [--points N]');
4078
+ process.exit(1);
4079
+ }
4080
+ const res = await fetch(`${API_URL}/team/tasks`, {
4081
+ method: 'POST', headers,
4082
+ body: JSON.stringify({ assignee_username: username, title, points }),
4083
+ });
4084
+ const data = await res.json();
4085
+ if (!res.ok) {
4086
+ console.error(data.message || data.error);
4087
+ process.exit(1);
4088
+ }
4089
+ console.log(`Task #${data.id} assigned to ${username}: "${title}" (${data.points} pts)`);
4090
+ break;
4091
+ }
4092
+ case 'approve': {
4093
+ const taskId = args[1];
4094
+ if (!taskId) {
4095
+ console.error('Usage: latinfo tasks approve <task-id>');
4096
+ process.exit(1);
4097
+ }
4098
+ const res = await fetch(`${API_URL}/team/tasks/${taskId}`, {
4099
+ method: 'PATCH', headers,
4100
+ body: JSON.stringify({ action: 'approve' }),
4101
+ });
4102
+ const data = await res.json();
4103
+ if (!res.ok) {
4104
+ console.error(data.message || data.error);
4105
+ process.exit(1);
4106
+ }
4107
+ console.log(`Task #${taskId} approved. ${data.points_awarded} points awarded.`);
4108
+ break;
4109
+ }
4110
+ case 'reject': {
4111
+ const taskId = args[1];
4112
+ const reason = args[2] || '';
4113
+ if (!taskId) {
4114
+ console.error('Usage: latinfo tasks reject <task-id> "<reason>"');
4115
+ process.exit(1);
4116
+ }
4117
+ const res = await fetch(`${API_URL}/team/tasks/${taskId}`, {
4118
+ method: 'PATCH', headers,
4119
+ body: JSON.stringify({ action: 'reject', reason }),
4120
+ });
4121
+ const data = await res.json();
4122
+ if (!res.ok) {
4123
+ console.error(data.message || data.error);
4124
+ process.exit(1);
4125
+ }
4126
+ console.log(`Task #${taskId} rejected.`);
4127
+ break;
4128
+ }
4129
+ case 'delete': {
4130
+ const taskId = args[1];
4131
+ if (!taskId) {
4132
+ console.error('Usage: latinfo tasks delete <task-id>');
4133
+ process.exit(1);
4134
+ }
4135
+ const res = await fetch(`${API_URL}/team/tasks/${taskId}`, { method: 'DELETE', headers });
4136
+ const data = await res.json();
4137
+ if (!res.ok) {
4138
+ console.error(data.message || data.error);
4139
+ process.exit(1);
4140
+ }
4141
+ console.log(`Task #${taskId} deleted.`);
4142
+ break;
4143
+ }
4144
+ }
4145
+ return;
4146
+ }
4147
+ // Member commands use API key
4148
+ const config = loadConfig();
4149
+ if (!config?.api_key) {
4150
+ console.error('Not logged in. Run: latinfo login');
4151
+ process.exit(1);
4152
+ }
4153
+ const headers = { Authorization: `Bearer ${config.api_key}`, 'Content-Type': 'application/json' };
4154
+ switch (sub) {
4155
+ case 'complete': {
4156
+ const taskId = args[1];
4157
+ if (!taskId) {
4158
+ console.error('Usage: latinfo tasks complete <task-id>');
4159
+ process.exit(1);
4160
+ }
4161
+ const res = await fetch(`${API_URL}/team/tasks/${taskId}`, {
4162
+ method: 'PATCH', headers,
4163
+ body: JSON.stringify({ action: 'complete' }),
4164
+ });
4165
+ const data = await res.json();
4166
+ if (!res.ok) {
4167
+ console.error(data.message || data.error);
4168
+ process.exit(1);
4169
+ }
4170
+ console.log(`Task #${taskId} marked as completed. Waiting for approval.`);
4171
+ break;
4172
+ }
4173
+ case 'rank': {
4174
+ const res = await fetch(`${API_URL}/team/rank`, { headers });
4175
+ const ranking = await res.json();
4176
+ if (!res.ok) {
4177
+ console.error('Failed to get ranking');
4178
+ process.exit(1);
4179
+ }
4180
+ if (ranking.length === 0) {
4181
+ console.log('No team members yet.');
4182
+ return;
4183
+ }
4184
+ const badges = { Bronze: '●', Silver: '◆', Gold: '★', Diamond: '◈' };
4185
+ console.log('\n RANKING\n');
4186
+ for (const r of ranking) {
4187
+ console.log(` #${r.rank} ${r.username.padEnd(22)} ${String(r.points).padStart(4)} pts ${badges[r.level] || ''} ${r.level}`);
4188
+ }
4189
+ console.log();
4190
+ break;
4191
+ }
4192
+ case 'list': {
4193
+ const allFlag = args.includes('--all');
4194
+ const url = allFlag ? `${API_URL}/team/tasks?all=true` : `${API_URL}/team/tasks`;
4195
+ const reqHeaders = allFlag
4196
+ ? { ...headers, 'X-Admin-Secret': requireAdmin() }
4197
+ : headers;
4198
+ const res = await fetch(url, { headers: reqHeaders });
4199
+ const tasksList = await res.json();
4200
+ if (!res.ok) {
4201
+ console.error('Failed to list tasks');
4202
+ process.exit(1);
4203
+ }
4204
+ if (tasksList.length === 0) {
4205
+ console.log('No tasks.');
4206
+ return;
4207
+ }
4208
+ const icons = { pending: '○', completed: '◉', approved: '★' };
4209
+ for (const t of tasksList) {
4210
+ const assignee = allFlag ? ` @${t.assignee_username}` : '';
4211
+ const rejection = t.status === 'pending' && t.reject_reason ? ` (rejected: ${t.reject_reason})` : '';
4212
+ console.log(` [#${t.id}] ${icons[t.status] || t.status} ${t.title}${assignee} (${t.points} pts)${rejection}`);
4213
+ }
4214
+ break;
4215
+ }
4216
+ default: {
4217
+ // No subcommand = show my tasks
4218
+ const res = await fetch(`${API_URL}/team/tasks`, { headers });
4219
+ const tasksList = await res.json();
4220
+ if (!res.ok) {
4221
+ console.error('Failed to list tasks');
4222
+ process.exit(1);
4223
+ }
4224
+ if (tasksList.length === 0) {
4225
+ console.log('No tasks assigned to you.');
4226
+ return;
4227
+ }
4228
+ const icons = { pending: '○', completed: '◉', approved: '★' };
4229
+ for (const t of tasksList) {
4230
+ const rejection = t.status === 'pending' && t.reject_reason ? ` (rejected: ${t.reject_reason})` : '';
4231
+ console.log(` [#${t.id}] ${icons[t.status] || t.status} ${t.title} (${t.points} pts)${rejection}`);
4232
+ }
4233
+ }
4234
+ }
4235
+ }
3802
4236
  // --- Main ---
3803
4237
  const [command, ...args] = rawArgs;
3804
4238
  const COUNTRIES = ['pe', 'co', 'br', 'mx', 'ar', 'cl', 'ec'];
@@ -3837,7 +4271,15 @@ else {
3837
4271
  case 'users':
3838
4272
  users().catch(e => { console.error(e); process.exit(1); });
3839
4273
  break;
4274
+ case 'plan':
4275
+ plan().catch(e => { console.error(e); process.exit(1); });
4276
+ break;
4277
+ case 'completion':
4278
+ completion();
4279
+ break;
4280
+ // Team-only commands
3840
4281
  case 'imports':
4282
+ requireTeam();
3841
4283
  if (args[0] === 'run')
3842
4284
  importsRun(args[1] || 'all').catch(e => { console.error(e); process.exit(1); });
3843
4285
  else if (args[0] === 'report')
@@ -3845,38 +4287,49 @@ else {
3845
4287
  else
3846
4288
  imports().catch(e => { console.error(e); process.exit(1); });
3847
4289
  break;
3848
- case 'plan':
3849
- plan().catch(e => { console.error(e); process.exit(1); });
3850
- break;
3851
4290
  case 'costs':
4291
+ requireTeamAdmin();
3852
4292
  (liveFlag ? costsLive() : Promise.resolve(costsSimulate(args[0], args[1], args[2]))).catch(e => { console.error(e); process.exit(1); });
3853
4293
  break;
3854
4294
  case 'bench':
4295
+ requireTeamAdmin();
3855
4296
  bench(args).catch(e => { console.error(e); process.exit(1); });
3856
4297
  break;
3857
4298
  case 'search-server':
4299
+ requireTeamAdmin();
3858
4300
  searchServerStatus().catch(e => { console.error(e); process.exit(1); });
3859
4301
  break;
4302
+ case 'team':
4303
+ requireTeamAdmin();
4304
+ teamCmd(args).catch(e => { console.error(e); process.exit(1); });
4305
+ break;
4306
+ case 'tasks':
4307
+ requireTeam();
4308
+ tasksCmd(args).catch(e => { console.error(e); process.exit(1); });
4309
+ break;
3860
4310
  case 'pipe':
4311
+ requireTeam();
3861
4312
  pipe(args).catch(e => { console.error(e); process.exit(1); });
3862
4313
  break;
3863
4314
  case 'admin':
4315
+ requireTeamAdmin();
3864
4316
  pipe(args).catch(e => { console.error(e); process.exit(1); });
3865
- break; // backward compat
4317
+ break;
3866
4318
  case 'easypipe':
3867
4319
  case 'ep':
4320
+ requireTeam();
3868
4321
  easypipe(args).catch(e => { console.error(e); process.exit(1); });
3869
4322
  break;
3870
4323
  case 'report':
4324
+ requireTeamAdmin();
3871
4325
  report(args).catch(e => { console.error(e); process.exit(1); });
3872
4326
  break;
3873
4327
  case 'issues':
4328
+ requireTeamAdmin();
3874
4329
  issues().catch(e => { console.error(e); process.exit(1); });
3875
4330
  break;
3876
- case 'completion':
3877
- completion();
3878
- break;
3879
4331
  case 'docs':
4332
+ requireTeam();
3880
4333
  docs(args);
3881
4334
  break;
3882
4335
  case 'help':
@@ -45,7 +45,7 @@ exports.dataInfo = dataInfo;
45
45
  const fs = __importStar(require("fs"));
46
46
  const path = __importStar(require("path"));
47
47
  const os_1 = __importDefault(require("os"));
48
- const BASE_NAME = 'pe-oece-licitaciones';
48
+ const BASE_NAME = 'pe-oece-tenders';
49
49
  const FIELD_COUNT = 12;
50
50
  const DATA_DIR = path.join(os_1.default.homedir(), '.latinfo', 'data');
51
51
  const FIELD_NAMES = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latinfo",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
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": {