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 +490 -37
- package/dist/licitaciones.js +1 -1
- package/package.json +1 -1
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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-
|
|
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 (
|
|
1527
|
+
latinfo co rues registry <nit> Business registry (9M+ records)
|
|
1486
1528
|
latinfo co rues registry --search <query>
|
|
1487
1529
|
|
|
1488
|
-
|
|
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
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
|
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;
|
|
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':
|
package/dist/licitaciones.js
CHANGED
|
@@ -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-
|
|
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.
|
|
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": {
|