latinfo 0.20.4 → 0.20.6

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 +111 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1587,6 +1587,7 @@ DATA SOURCES
1587
1587
  latinfo co rues registry --search <query>
1588
1588
 
1589
1589
  COMMANDS
1590
+ sources List all data sources with stats
1590
1591
  login [--token <github_pat>] GitHub OAuth or PAT login
1591
1592
  logout Remove credentials
1592
1593
  whoami Show authenticated user
@@ -4038,6 +4039,100 @@ function requireTeamAdmin() {
4038
4039
  process.exit(1);
4039
4040
  }
4040
4041
  }
4042
+ // --- Imports Status ---
4043
+ async function importsStatus() {
4044
+ const { execSync: exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
4045
+ const ghRepo = 'carrerahaus/latinfo-api';
4046
+ // Get recent workflow runs
4047
+ const runsJson = exec(`gh run list --repo ${ghRepo} --workflow=import.yml --limit=5 --json databaseId,status,conclusion,createdAt`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
4048
+ const runs = JSON.parse(runsJson);
4049
+ if (runs.length === 0) {
4050
+ console.log('No recent import runs.');
4051
+ return;
4052
+ }
4053
+ console.log('\n IMPORT STATUS\n');
4054
+ for (const run of runs) {
4055
+ const jobsJson = exec(`gh run view ${run.databaseId} --repo ${ghRepo} --json jobs --jq '.jobs[] | "\\(.name)|\\(.status)|\\(.conclusion // "")"'`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
4056
+ const age = Math.floor((Date.now() - new Date(run.createdAt).getTime()) / 60000);
4057
+ const ageStr = age < 60 ? `${age}m ago` : `${Math.floor(age / 60)}h ago`;
4058
+ const runIcon = run.status === 'completed' ? (run.conclusion === 'success' ? '✅' : '❌') : '🔄';
4059
+ console.log(` ${runIcon} Run #${run.databaseId} (${ageStr})`);
4060
+ for (const line of jobsJson.split('\n')) {
4061
+ if (!line || line.includes('setup'))
4062
+ continue;
4063
+ const [name, status, conclusion] = line.split('|');
4064
+ const icon = status === 'completed' ? (conclusion === 'success' ? ' ✅' : ' ❌') :
4065
+ status === 'in_progress' ? ' 🔄' :
4066
+ status === 'queued' ? ' ⏳' : ' ⬜';
4067
+ const label = status === 'in_progress' ? 'running' :
4068
+ status === 'queued' ? 'queued' :
4069
+ conclusion || status;
4070
+ console.log(` ${icon} ${name.padEnd(25)} ${label}`);
4071
+ }
4072
+ console.log();
4073
+ }
4074
+ }
4075
+ // --- Sources ---
4076
+ async function sourcesCmd() {
4077
+ // Get import metadata dynamically from admin endpoint
4078
+ let data = [];
4079
+ try {
4080
+ const adminSecret = requireAdmin();
4081
+ const res = await fetch(`${API_URL}/admin/imports`, { headers: { Authorization: `Bearer ${adminSecret}` } });
4082
+ if (res.ok)
4083
+ data = await res.json();
4084
+ }
4085
+ catch {
4086
+ // Not admin — try reading source YAMLs from repo
4087
+ }
4088
+ if (data.length === 0) {
4089
+ // Fallback: read sources/*.yaml from repo
4090
+ try {
4091
+ const repo = getRepoPath();
4092
+ const sourcesDir = path_1.default.join(repo, 'sources');
4093
+ const files = fs_1.default.readdirSync(sourcesDir).filter(f => f.endsWith('.yaml'));
4094
+ for (const f of files) {
4095
+ const content = fs_1.default.readFileSync(path_1.default.join(sourcesDir, f), 'utf-8');
4096
+ const name = f.replace('.yaml', '');
4097
+ const country = content.match(/country:\s*(\S+)/)?.[1] || '?';
4098
+ const institution = content.match(/institution:\s*(\S+)/)?.[1] || '?';
4099
+ const dataset = content.match(/dataset:\s*(\S+)/)?.[1] || '?';
4100
+ const idName = content.match(/name:\s*(\S+)/)?.[1] || 'id';
4101
+ data.push({ source: name, country, institution, dataset, idName, rows: null, ageHours: null });
4102
+ }
4103
+ }
4104
+ catch { }
4105
+ }
4106
+ if (data.length === 0) {
4107
+ console.log('No sources found.');
4108
+ return;
4109
+ }
4110
+ console.log('\n SOURCES\n');
4111
+ console.log(` ${'Source'.padEnd(35)} ${'Records'.padStart(12)} ${'Updated'.padEnd(18)} Command`);
4112
+ console.log(` ${'─'.repeat(35)} ${'─'.repeat(12)} ${'─'.repeat(18)} ${'─'.repeat(30)}`);
4113
+ for (const s of data) {
4114
+ // Parse source name: pe-sunat-padron → country=pe, institution=sunat, dataset=padron
4115
+ const parts = s.source.split('-');
4116
+ const country = s.country || (parts[0] === 'pe' ? 'Peru' : parts[0] === 'co' ? 'Colombia' : parts[0]);
4117
+ const institution = s.institution || parts.slice(1, -1).join('-');
4118
+ const dataset = s.dataset || parts[parts.length - 1];
4119
+ const label = `${country} — ${institution.toUpperCase()} ${dataset}`;
4120
+ const rows = s.rows ? s.rows.toLocaleString() : '—';
4121
+ const age = s.ageHours != null ? (s.ageHours < 24 ? `${s.ageHours}h ago` : `${Math.floor(s.ageHours / 24)}d ago`) : '—';
4122
+ // Build route: pe-sunat-padron → pe/sunat/padron, co-rues → co/rues/registry
4123
+ const segs = s.source.split('-');
4124
+ let route;
4125
+ if (segs.length >= 3)
4126
+ route = `${segs[0]}/${segs.slice(1, -1).join('-')}/${segs[segs.length - 1]}`;
4127
+ else if (segs.length === 2)
4128
+ route = `${segs[0]}/${segs[1]}/registry`;
4129
+ else
4130
+ route = s.source;
4131
+ const idName = s.idName || 'id';
4132
+ console.log(` ${label.padEnd(35)} ${rows.padStart(12)} ${age.padEnd(18)} latinfo ${route} <${idName}>`);
4133
+ }
4134
+ console.log();
4135
+ }
4041
4136
  // --- Team & Tasks ---
4042
4137
  async function teamCmd(args) {
4043
4138
  const sub = args[0];
@@ -4407,6 +4502,9 @@ else {
4407
4502
  case 'users':
4408
4503
  users().catch(e => { console.error(e); process.exit(1); });
4409
4504
  break;
4505
+ case 'sources':
4506
+ sourcesCmd().catch(e => { console.error(e); process.exit(1); });
4507
+ break;
4410
4508
  case 'plan':
4411
4509
  plan().catch(e => { console.error(e); process.exit(1); });
4412
4510
  break;
@@ -4415,13 +4513,19 @@ else {
4415
4513
  break;
4416
4514
  // Team-only commands
4417
4515
  case 'imports':
4418
- requireTeam();
4419
- if (args[0] === 'run')
4420
- importsRun(args[1] || 'all').catch(e => { console.error(e); process.exit(1); });
4421
- else if (args[0] === 'report')
4422
- importsReport(parseInt(args[1]) || 7).catch(e => { console.error(e); process.exit(1); });
4423
- else
4424
- imports().catch(e => { console.error(e); process.exit(1); });
4516
+ if (args[0] === 'status') {
4517
+ requireTeam();
4518
+ importsStatus().catch(e => { console.error(e); process.exit(1); });
4519
+ }
4520
+ else {
4521
+ requireTeamAdmin();
4522
+ if (args[0] === 'run')
4523
+ importsRun(args[1] || 'all').catch(e => { console.error(e); process.exit(1); });
4524
+ else if (args[0] === 'report')
4525
+ importsReport(parseInt(args[1]) || 7).catch(e => { console.error(e); process.exit(1); });
4526
+ else
4527
+ imports().catch(e => { console.error(e); process.exit(1); });
4528
+ }
4425
4529
  break;
4426
4530
  case 'costs':
4427
4531
  requireTeamAdmin();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latinfo",
3
- "version": "0.20.4",
3
+ "version": "0.20.6",
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": {