proof-of-commitment 1.8.1 → 1.10.0
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/README.md +22 -0
- package/index.js +435 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,28 @@ npx proof-of-commitment --file go.sum # full transitive set
|
|
|
51
51
|
|
|
52
52
|
**Web demo (no install):** [getcommit.dev/audit](https://getcommit.dev/audit) — paste your packages, see risk scores in seconds.
|
|
53
53
|
|
|
54
|
+
**Commit Pro — daily monitoring + alerts (v1.9.0):**
|
|
55
|
+
```bash
|
|
56
|
+
# Install once, then use the `poc` alias:
|
|
57
|
+
npm install -g proof-of-commitment
|
|
58
|
+
|
|
59
|
+
# Add packages to daily monitoring (requires Pro API key):
|
|
60
|
+
poc watch chalk
|
|
61
|
+
poc watch requests --ecosystem pypi
|
|
62
|
+
poc watch serde --ecosystem cargo
|
|
63
|
+
|
|
64
|
+
# View your watchlist with current scores:
|
|
65
|
+
poc watchlist
|
|
66
|
+
|
|
67
|
+
# Remove a package:
|
|
68
|
+
poc unwatch chalk
|
|
69
|
+
|
|
70
|
+
# API key: set COMMIT_API_KEY env or add api_key=<key> to ~/.commit/config
|
|
71
|
+
# Get a key at https://getcommit.dev/pricing
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Alerts fire on: score drop ≥10 points · package crosses CRITICAL threshold · recovery to HEALTHY.
|
|
75
|
+
|
|
54
76
|
**MCP server (zero install):**
|
|
55
77
|
|
|
56
78
|
```json
|
package/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* proof-of-commitment CLI v1.
|
|
3
|
+
* proof-of-commitment CLI v1.10.0
|
|
4
4
|
* Scores npm/PyPI/Cargo/Go packages on behavioral commitment signals.
|
|
5
5
|
* Usage: npx proof-of-commitment [packages...] [options]
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const API = 'https://poc-backend.amdal-dev.workers.dev/api/audit';
|
|
9
|
+
const KEYS_API = 'https://poc-backend.amdal-dev.workers.dev/api/keys';
|
|
10
|
+
const WATCHLIST_API = 'https://poc-backend.amdal-dev.workers.dev/api/watchlist';
|
|
9
11
|
const WEB = 'https://getcommit.dev/audit';
|
|
10
12
|
|
|
11
13
|
// ANSI color helpers
|
|
@@ -25,6 +27,16 @@ const c = {
|
|
|
25
27
|
|
|
26
28
|
const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
|
|
27
29
|
|
|
30
|
+
// Synchronous API key check for upsell messaging (avoids async in printTable)
|
|
31
|
+
let _cachedHasKey = false;
|
|
32
|
+
try {
|
|
33
|
+
const _os = await import('os');
|
|
34
|
+
const _fs = await import('fs');
|
|
35
|
+
const _path = await import('path');
|
|
36
|
+
const _cfg = _fs.readFileSync(_path.join(_os.homedir(), '.commit', 'config'), 'utf-8');
|
|
37
|
+
_cachedHasKey = /^api_key\s*=\s*.+$/m.test(_cfg);
|
|
38
|
+
} catch {}
|
|
39
|
+
|
|
28
40
|
function clr(code, text) {
|
|
29
41
|
if (NO_COLOR) return text;
|
|
30
42
|
return `${code}${text}${c.reset}`;
|
|
@@ -173,15 +185,23 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
173
185
|
|
|
174
186
|
// Contextual upsell — show when findings make monitoring relevant
|
|
175
187
|
if (effectiveCritical > 0) {
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
// Check for API key synchronously via env (fast path)
|
|
189
|
+
const hasKey = !!process.env.COMMIT_API_KEY || _cachedHasKey;
|
|
190
|
+
if (hasKey) {
|
|
191
|
+
console.log(clr(c.dim, `\n 📊 Monitor ${effectiveCritical === 1 ? 'this package' : 'these packages'}: `) +
|
|
192
|
+
clr(c.cyan, `poc watch ${results.find(r => hasCritical(r.riskFlags))?.name || results[0]?.name}`));
|
|
193
|
+
} else {
|
|
194
|
+
console.log(clr(c.dim, `\n 📊 Monitor ${effectiveCritical === 1 ? 'this' : 'these ' + effectiveCritical} CRITICAL ${effectiveCritical === 1 ? 'package' : 'packages'} — get alerted when scores change.`));
|
|
195
|
+
console.log(clr(c.dim, ' Get a free API key: ') + clr(c.cyan, 'https://getcommit.dev/get-started'));
|
|
196
|
+
console.log(clr(c.dim, ' Then run: ') + clr(c.cyan, 'poc login'));
|
|
197
|
+
}
|
|
178
198
|
}
|
|
179
199
|
console.log();
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
function printHelp() {
|
|
183
203
|
console.log(`
|
|
184
|
-
${clr(c.bold, 'proof-of-commitment')} v1.
|
|
204
|
+
${clr(c.bold, 'proof-of-commitment')} v1.10.0 — supply chain risk scorer
|
|
185
205
|
|
|
186
206
|
${clr(c.bold, 'Usage:')}
|
|
187
207
|
npx proof-of-commitment Auto-detect manifest in current dir
|
|
@@ -198,6 +218,20 @@ ${clr(c.bold, 'Usage:')}
|
|
|
198
218
|
npx proof-of-commitment --file go.mod Audit Go direct + indirect deps
|
|
199
219
|
npx proof-of-commitment --file go.sum Audit Go full transitive set
|
|
200
220
|
|
|
221
|
+
${clr(c.bold, 'Account:')}
|
|
222
|
+
poc login [key] Save and validate your API key (interactive or direct)
|
|
223
|
+
poc status Show current tier, usage, and limits
|
|
224
|
+
poc logout Remove saved API key
|
|
225
|
+
|
|
226
|
+
${clr(c.bold, 'Monitoring (Pro):')}
|
|
227
|
+
poc watch <package> [--ecosystem npm|pypi|cargo|golang]
|
|
228
|
+
Add a package to daily monitoring
|
|
229
|
+
poc watchlist List monitored packages with current scores + risk
|
|
230
|
+
poc unwatch <pkg> Remove a package from monitoring
|
|
231
|
+
|
|
232
|
+
Get a free key: https://getcommit.dev/get-started
|
|
233
|
+
Upgrade to Pro: https://getcommit.dev/pricing
|
|
234
|
+
|
|
201
235
|
${clr(c.bold, 'Options:')}
|
|
202
236
|
--json Output results as JSON
|
|
203
237
|
--fail-on=<level> Exit 1 when findings meet the threshold. Levels:
|
|
@@ -616,6 +650,351 @@ function shouldFail(results, failOn) {
|
|
|
616
650
|
return false;
|
|
617
651
|
}
|
|
618
652
|
|
|
653
|
+
/**
|
|
654
|
+
* Read API key from env or ~/.commit/config file.
|
|
655
|
+
* Returns the key string, or null if not found.
|
|
656
|
+
*/
|
|
657
|
+
async function readApiKey() {
|
|
658
|
+
if (process.env.COMMIT_API_KEY) return process.env.COMMIT_API_KEY.trim();
|
|
659
|
+
try {
|
|
660
|
+
const os = await import('os');
|
|
661
|
+
const fs = await import('fs');
|
|
662
|
+
const path = await import('path');
|
|
663
|
+
const configPath = path.join(os.homedir(), '.commit', 'config');
|
|
664
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
665
|
+
for (const line of content.split('\n')) {
|
|
666
|
+
const m = line.match(/^api_key\s*=\s*(.+)$/);
|
|
667
|
+
if (m) return m[1].trim();
|
|
668
|
+
}
|
|
669
|
+
} catch {}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Write API key to ~/.commit/config, creating dir if needed.
|
|
675
|
+
*/
|
|
676
|
+
async function writeApiKey(key) {
|
|
677
|
+
const os = await import('os');
|
|
678
|
+
const fs = await import('fs');
|
|
679
|
+
const path = await import('path');
|
|
680
|
+
const dir = path.join(os.homedir(), '.commit');
|
|
681
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
682
|
+
|
|
683
|
+
const configPath = path.join(dir, 'config');
|
|
684
|
+
let lines = [];
|
|
685
|
+
try {
|
|
686
|
+
lines = fs.readFileSync(configPath, 'utf-8').split('\n');
|
|
687
|
+
} catch {}
|
|
688
|
+
|
|
689
|
+
// Replace existing api_key line or append
|
|
690
|
+
let found = false;
|
|
691
|
+
lines = lines.map(l => {
|
|
692
|
+
if (/^api_key\s*=/.test(l)) { found = true; return `api_key = ${key}`; }
|
|
693
|
+
return l;
|
|
694
|
+
});
|
|
695
|
+
if (!found) lines.push(`api_key = ${key}`);
|
|
696
|
+
|
|
697
|
+
fs.writeFileSync(configPath, lines.filter(l => l !== '').join('\n') + '\n');
|
|
698
|
+
return configPath;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Remove API key from ~/.commit/config.
|
|
703
|
+
*/
|
|
704
|
+
async function removeApiKey() {
|
|
705
|
+
const os = await import('os');
|
|
706
|
+
const fs = await import('fs');
|
|
707
|
+
const path = await import('path');
|
|
708
|
+
const configPath = path.join(os.homedir(), '.commit', 'config');
|
|
709
|
+
try {
|
|
710
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
711
|
+
const filtered = content.split('\n').filter(l => !/^api_key\s*=/.test(l)).join('\n');
|
|
712
|
+
fs.writeFileSync(configPath, filtered.trim() ? filtered.trim() + '\n' : '');
|
|
713
|
+
return true;
|
|
714
|
+
} catch { return false; }
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Validate an API key against the usage endpoint. Returns tier info or null.
|
|
719
|
+
*/
|
|
720
|
+
async function validateApiKey(key) {
|
|
721
|
+
try {
|
|
722
|
+
const res = await fetch(`${KEYS_API}/usage`, {
|
|
723
|
+
headers: { 'Authorization': `Bearer ${key}` },
|
|
724
|
+
});
|
|
725
|
+
if (!res.ok) return null;
|
|
726
|
+
return await res.json();
|
|
727
|
+
} catch { return null; }
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* poc login [key] — save and validate API key
|
|
732
|
+
*/
|
|
733
|
+
async function cmdLogin(keyArg) {
|
|
734
|
+
let key = keyArg;
|
|
735
|
+
|
|
736
|
+
if (!key) {
|
|
737
|
+
// Check if stdin has data (piped input)
|
|
738
|
+
const { createInterface } = await import('readline');
|
|
739
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
740
|
+
key = await new Promise(resolve => {
|
|
741
|
+
rl.question(clr(c.dim, ' Enter your API key: '), answer => {
|
|
742
|
+
rl.close();
|
|
743
|
+
resolve(answer.trim());
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (!key || !key.startsWith('sk_commit_')) {
|
|
749
|
+
console.error(clr(c.red, '\n Invalid API key format. Keys start with sk_commit_'));
|
|
750
|
+
console.error(clr(c.dim, ' Get one at https://getcommit.dev/get-started\n'));
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
process.stdout.write(clr(c.dim, ' Validating...'));
|
|
755
|
+
const info = await validateApiKey(key);
|
|
756
|
+
|
|
757
|
+
if (!info || info.error) {
|
|
758
|
+
console.error(clr(c.red, ' ✗ Invalid or expired API key.'));
|
|
759
|
+
console.error(clr(c.dim, ` ${info?.message || 'Key not recognized.'}`));
|
|
760
|
+
process.exit(1);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const configPath = await writeApiKey(key);
|
|
764
|
+
console.log(clr(c.green, ' ✓ Authenticated'));
|
|
765
|
+
console.log();
|
|
766
|
+
console.log(clr(c.bold, ` Tier: `) + tierLabel(info.tier));
|
|
767
|
+
console.log(clr(c.bold, ` Usage: `) + `${info.requests_used ?? 0}/${info.requests_limit ?? '?'} requests (${info.period || 'daily'})`);
|
|
768
|
+
console.log(clr(c.bold, ` Resets: `) + (info.period_reset_at || '—'));
|
|
769
|
+
console.log(clr(c.dim, ` Saved to: ${configPath}`));
|
|
770
|
+
console.log();
|
|
771
|
+
|
|
772
|
+
if (info.tier === 'pro' || info.tier === 'enterprise') {
|
|
773
|
+
console.log(clr(c.cyan, ' Pro features unlocked:'));
|
|
774
|
+
console.log(clr(c.dim, ' poc watch <package> Add a package to daily monitoring'));
|
|
775
|
+
console.log(clr(c.dim, ' poc watchlist View monitored packages'));
|
|
776
|
+
console.log(clr(c.dim, ' poc unwatch <package> Remove from monitoring'));
|
|
777
|
+
} else {
|
|
778
|
+
console.log(clr(c.dim, ' Upgrade to Pro for monitoring + alerts: https://getcommit.dev/pricing'));
|
|
779
|
+
}
|
|
780
|
+
console.log();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* poc status — show current auth + usage
|
|
785
|
+
*/
|
|
786
|
+
async function cmdStatus() {
|
|
787
|
+
const key = await readApiKey();
|
|
788
|
+
|
|
789
|
+
if (!key) {
|
|
790
|
+
console.log(clr(c.dim, '\n Not logged in.'));
|
|
791
|
+
console.log(clr(c.dim, ' Run ') + clr(c.cyan, 'poc login') + clr(c.dim, ' to authenticate.'));
|
|
792
|
+
console.log(clr(c.dim, ' Get a free key at https://getcommit.dev/get-started\n'));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
process.stdout.write(clr(c.dim, ' Checking...'));
|
|
797
|
+
const info = await validateApiKey(key);
|
|
798
|
+
|
|
799
|
+
if (!info || info.error) {
|
|
800
|
+
console.error(clr(c.red, ' ✗ Key invalid or expired.'));
|
|
801
|
+
console.error(clr(c.dim, ' Run ') + clr(c.cyan, 'poc login') + clr(c.dim, ' to re-authenticate.\n'));
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
console.log(clr(c.green, ' ✓ Connected'));
|
|
806
|
+
console.log();
|
|
807
|
+
console.log(clr(c.bold, ` Tier: `) + tierLabel(info.tier));
|
|
808
|
+
console.log(clr(c.bold, ` Usage: `) + `${info.requests_used ?? 0}/${info.requests_limit ?? '?'} requests (${info.period || 'daily'})`);
|
|
809
|
+
console.log(clr(c.bold, ` Resets: `) + (info.period_reset_at || '—'));
|
|
810
|
+
console.log(clr(c.bold, ` Prefix: `) + (info.key_prefix || key.slice(0, 19) + '...'));
|
|
811
|
+
console.log();
|
|
812
|
+
|
|
813
|
+
if (info.tier === 'free') {
|
|
814
|
+
const pct = info.requests_limit > 0 ? Math.round((info.requests_used / info.requests_limit) * 100) : 0;
|
|
815
|
+
if (pct >= 80) {
|
|
816
|
+
console.log(clr(c.yellow, ` ⚠ ${pct}% of daily limit used. Upgrade for 10K/month: https://getcommit.dev/pricing`));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* poc logout — remove saved API key
|
|
823
|
+
*/
|
|
824
|
+
async function cmdLogout() {
|
|
825
|
+
const removed = await removeApiKey();
|
|
826
|
+
if (removed) {
|
|
827
|
+
console.log(clr(c.green, '\n ✓ Logged out. API key removed from ~/.commit/config.'));
|
|
828
|
+
} else {
|
|
829
|
+
console.log(clr(c.dim, '\n No saved API key found.'));
|
|
830
|
+
}
|
|
831
|
+
console.log();
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function tierLabel(tier) {
|
|
835
|
+
if (tier === 'pro') return clr(c.cyan + c.bold, 'Pro');
|
|
836
|
+
if (tier === 'enterprise') return clr(c.magenta + c.bold, 'Enterprise');
|
|
837
|
+
if (tier === 'developer') return clr(c.green + c.bold, 'Developer');
|
|
838
|
+
return clr(c.dim, 'Free');
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Handle 402 upgrade response from watchlist endpoints.
|
|
843
|
+
*/
|
|
844
|
+
function printUpgradeRequired() {
|
|
845
|
+
console.error(clr(c.yellow + c.bold, '\n ✦ Commit Pro required'));
|
|
846
|
+
console.error(clr(c.dim, ' Monitoring, daily scans, and alerts are Pro features.'));
|
|
847
|
+
console.error(clr(c.cyan, ' Upgrade at https://getcommit.dev/pricing\n'));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* poc watch <package> [--ecosystem npm|pypi|cargo|golang]
|
|
852
|
+
*/
|
|
853
|
+
async function cmdWatch(pkg, ecosystem) {
|
|
854
|
+
const key = await readApiKey();
|
|
855
|
+
if (!key) {
|
|
856
|
+
console.error(clr(c.red, 'No API key found. Set COMMIT_API_KEY or add api_key=<key> to ~/.commit/config'));
|
|
857
|
+
console.error(clr(c.dim, 'Get a key at https://getcommit.dev/pricing'));
|
|
858
|
+
process.exit(1);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
process.stdout.write(clr(c.dim, `Adding ${pkg} (${ecosystem}) to watchlist...`));
|
|
862
|
+
const res = await fetch(WATCHLIST_API, {
|
|
863
|
+
method: 'POST',
|
|
864
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` },
|
|
865
|
+
body: JSON.stringify({ package: pkg, ecosystem }),
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
if (res.status === 402) { printUpgradeRequired(); process.exit(1); }
|
|
869
|
+
|
|
870
|
+
const data = await res.json();
|
|
871
|
+
if (!res.ok) {
|
|
872
|
+
console.error(`\n${clr(c.red, 'Error:')} ${data.message || JSON.stringify(data)}`);
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const isNew = data.new_packages > 0;
|
|
877
|
+
process.stdout.write('\n');
|
|
878
|
+
if (isNew) {
|
|
879
|
+
console.log(clr(c.green, ` ✓ Now watching ${pkg}`));
|
|
880
|
+
console.log(clr(c.dim, ' Daily scan runs at 06:00 UTC. Alerts on score drop ≥10 or CRITICAL threshold.'));
|
|
881
|
+
} else {
|
|
882
|
+
console.log(clr(c.dim, ` ↩ ${pkg} is already in your watchlist`));
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* poc watchlist — show monitored packages table
|
|
888
|
+
*/
|
|
889
|
+
async function cmdWatchlist() {
|
|
890
|
+
const key = await readApiKey();
|
|
891
|
+
if (!key) {
|
|
892
|
+
console.error(clr(c.red, 'No API key found. Set COMMIT_API_KEY or add api_key=<key> to ~/.commit/config'));
|
|
893
|
+
process.exit(1);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const res = await fetch(WATCHLIST_API, {
|
|
897
|
+
headers: { 'Authorization': `Bearer ${key}` },
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
if (res.status === 402) { printUpgradeRequired(); process.exit(1); }
|
|
901
|
+
|
|
902
|
+
const data = await res.json();
|
|
903
|
+
if (!res.ok) {
|
|
904
|
+
console.error(clr(c.red, `Error: ${data.message || JSON.stringify(data)}`));
|
|
905
|
+
process.exit(1);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const pkgs = data.packages || [];
|
|
909
|
+
if (pkgs.length === 0) {
|
|
910
|
+
console.log(clr(c.dim, '\n No packages monitored yet.'));
|
|
911
|
+
console.log(clr(c.dim, ' Add one: poc watch <package>\n'));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const COL = { name: 24, eco: 8, score: 7, prev: 7, risk: 14, scanned: 22 };
|
|
916
|
+
|
|
917
|
+
function riskLabelFromLevel(level) {
|
|
918
|
+
if (!level) return clr(c.dim, '—');
|
|
919
|
+
if (level === 'CRITICAL') return clr(c.red + c.bold, '🔴 CRITICAL');
|
|
920
|
+
if (level === 'HIGH') return clr(c.yellow + c.bold, '🟠 HIGH');
|
|
921
|
+
if (level === 'MODERATE') return clr(c.yellow, '🟡 MODERATE');
|
|
922
|
+
if (level === 'GOOD') return clr(c.yellow, '🟡 GOOD');
|
|
923
|
+
return clr(c.green, '🟢 HEALTHY');
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const header = [
|
|
927
|
+
padEnd(clr(c.bold, 'Package'), COL.name),
|
|
928
|
+
padEnd(clr(c.bold, 'Eco'), COL.eco),
|
|
929
|
+
padEnd(clr(c.bold, 'Score'), COL.score),
|
|
930
|
+
padEnd(clr(c.bold, 'Prev'), COL.prev),
|
|
931
|
+
padEnd(clr(c.bold, 'Risk'), COL.risk),
|
|
932
|
+
padEnd(clr(c.bold, 'Last scanned'), COL.scanned),
|
|
933
|
+
].join(' ');
|
|
934
|
+
|
|
935
|
+
const divWidth = COL.name + COL.eco + COL.score + COL.prev + COL.risk + COL.scanned + 10;
|
|
936
|
+
const divider = '─'.repeat(divWidth);
|
|
937
|
+
|
|
938
|
+
console.log('\n' + divider);
|
|
939
|
+
console.log(clr(c.dim, ` Commit Pro watchlist · ${pkgs.length}/${data.limit} packages · tier: ${data.tier}`));
|
|
940
|
+
console.log(divider);
|
|
941
|
+
console.log(header);
|
|
942
|
+
console.log(divider);
|
|
943
|
+
|
|
944
|
+
for (const pkg of pkgs) {
|
|
945
|
+
const scoreStr = pkg.current_score !== null ? String(pkg.current_score) : clr(c.dim, '—');
|
|
946
|
+
const prevStr = pkg.previous_score !== null ? String(pkg.previous_score) : clr(c.dim, '—');
|
|
947
|
+
const scanned = pkg.last_scanned_at ? pkg.last_scanned_at.replace('T', ' ').slice(0, 19) + ' UTC' : clr(c.dim, 'not yet');
|
|
948
|
+
|
|
949
|
+
const row = [
|
|
950
|
+
padEnd(pkg.name, COL.name),
|
|
951
|
+
padEnd(pkg.ecosystem, COL.eco),
|
|
952
|
+
padEnd(scoreStr, COL.score),
|
|
953
|
+
padEnd(prevStr, COL.prev),
|
|
954
|
+
padEnd(riskLabelFromLevel(pkg.risk_level), COL.risk),
|
|
955
|
+
padEnd(scanned, COL.scanned),
|
|
956
|
+
].join(' ');
|
|
957
|
+
console.log(row);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
console.log(divider);
|
|
961
|
+
console.log(clr(c.dim, '\n Alerts sent on: score drop ≥10 · CRITICAL threshold · recovery to HEALTHY'));
|
|
962
|
+
console.log(clr(c.cyan, ' Remove a package: poc unwatch <package>\n'));
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* poc unwatch <package> [--ecosystem npm|pypi|cargo|golang]
|
|
967
|
+
*/
|
|
968
|
+
async function cmdUnwatch(pkg, ecosystem) {
|
|
969
|
+
const key = await readApiKey();
|
|
970
|
+
if (!key) {
|
|
971
|
+
console.error(clr(c.red, 'No API key found. Set COMMIT_API_KEY or add api_key=<key> to ~/.commit/config'));
|
|
972
|
+
process.exit(1);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
process.stdout.write(clr(c.dim, `Removing ${pkg} (${ecosystem}) from watchlist...`));
|
|
976
|
+
const res = await fetch(WATCHLIST_API, {
|
|
977
|
+
method: 'DELETE',
|
|
978
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` },
|
|
979
|
+
body: JSON.stringify({ package: pkg, ecosystem }),
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
if (res.status === 402) { printUpgradeRequired(); process.exit(1); }
|
|
983
|
+
|
|
984
|
+
const data = await res.json();
|
|
985
|
+
if (!res.ok) {
|
|
986
|
+
console.error(`\n${clr(c.red, 'Error:')} ${data.message || JSON.stringify(data)}`);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
process.stdout.write('\n');
|
|
991
|
+
if ((data.removed ?? 0) > 0) {
|
|
992
|
+
console.log(clr(c.green, ` ✓ Removed ${pkg} from watchlist`));
|
|
993
|
+
} else {
|
|
994
|
+
console.log(clr(c.dim, ` ${pkg} was not in your watchlist`));
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
619
998
|
async function main() {
|
|
620
999
|
const args = process.argv.slice(2);
|
|
621
1000
|
|
|
@@ -624,6 +1003,58 @@ async function main() {
|
|
|
624
1003
|
process.exit(0);
|
|
625
1004
|
}
|
|
626
1005
|
|
|
1006
|
+
// Subcommands
|
|
1007
|
+
const subcmd = args[0];
|
|
1008
|
+
|
|
1009
|
+
if (subcmd === 'login') {
|
|
1010
|
+
const keyArg = args[1] || null;
|
|
1011
|
+
await cmdLogin(keyArg);
|
|
1012
|
+
process.exit(0);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if (subcmd === 'status') {
|
|
1016
|
+
await cmdStatus();
|
|
1017
|
+
process.exit(0);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (subcmd === 'logout') {
|
|
1021
|
+
await cmdLogout();
|
|
1022
|
+
process.exit(0);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (subcmd === 'watch') {
|
|
1026
|
+
const pkg = args[1];
|
|
1027
|
+
if (!pkg) { console.error('Usage: poc watch <package> [--ecosystem npm|pypi|cargo|golang]'); process.exit(1); }
|
|
1028
|
+
let ecosystem = 'npm';
|
|
1029
|
+
for (let i = 2; i < args.length; i++) {
|
|
1030
|
+
if (args[i] === '--ecosystem' || args[i] === '-e') ecosystem = args[++i] || 'npm';
|
|
1031
|
+
else if (args[i] === '--pypi') ecosystem = 'pypi';
|
|
1032
|
+
else if (args[i] === '--cargo') ecosystem = 'cargo';
|
|
1033
|
+
else if (args[i] === '--golang' || args[i] === '--go') ecosystem = 'golang';
|
|
1034
|
+
}
|
|
1035
|
+
await cmdWatch(pkg, ecosystem);
|
|
1036
|
+
process.exit(0);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (subcmd === 'watchlist') {
|
|
1040
|
+
await cmdWatchlist();
|
|
1041
|
+
process.exit(0);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (subcmd === 'unwatch') {
|
|
1045
|
+
const pkg = args[1];
|
|
1046
|
+
if (!pkg) { console.error('Usage: poc unwatch <package> [--ecosystem npm|pypi|cargo|golang]'); process.exit(1); }
|
|
1047
|
+
let ecosystem = 'npm';
|
|
1048
|
+
for (let i = 2; i < args.length; i++) {
|
|
1049
|
+
if (args[i] === '--ecosystem' || args[i] === '-e') ecosystem = args[++i] || 'npm';
|
|
1050
|
+
else if (args[i] === '--pypi') ecosystem = 'pypi';
|
|
1051
|
+
else if (args[i] === '--cargo') ecosystem = 'cargo';
|
|
1052
|
+
else if (args[i] === '--golang' || args[i] === '--go') ecosystem = 'golang';
|
|
1053
|
+
}
|
|
1054
|
+
await cmdUnwatch(pkg, ecosystem);
|
|
1055
|
+
process.exit(0);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
627
1058
|
let ecosystem = 'npm';
|
|
628
1059
|
let packages = [];
|
|
629
1060
|
let filePath = null;
|