proof-of-commitment 1.9.0 → 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.
Files changed (2) hide show
  1. package/index.js +217 -9
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * proof-of-commitment CLI v1.9.0
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';
9
10
  const WATCHLIST_API = 'https://poc-backend.amdal-dev.workers.dev/api/watchlist';
10
11
  const WEB = 'https://getcommit.dev/audit';
11
12
 
@@ -26,6 +27,16 @@ const c = {
26
27
 
27
28
  const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
28
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
+
29
40
  function clr(code, text) {
30
41
  if (NO_COLOR) return text;
31
42
  return `${code}${text}${c.reset}`;
@@ -174,15 +185,23 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
174
185
 
175
186
  // Contextual upsell — show when findings make monitoring relevant
176
187
  if (effectiveCritical > 0) {
177
- console.log(clr(c.dim, `\n 📊 Track ${effectiveCritical === 1 ? 'this package' : 'these packages'} daily. Get alerted on score changes.`));
178
- console.log(clr(c.dim, ` Commit Pro batch API, monitoring, alerts → https://getcommit.dev/pricing`));
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
+ }
179
198
  }
180
199
  console.log();
181
200
  }
182
201
 
183
202
  function printHelp() {
184
203
  console.log(`
185
- ${clr(c.bold, 'proof-of-commitment')} v1.9.0 — supply chain risk scorer
204
+ ${clr(c.bold, 'proof-of-commitment')} v1.10.0 — supply chain risk scorer
186
205
 
187
206
  ${clr(c.bold, 'Usage:')}
188
207
  npx proof-of-commitment Auto-detect manifest in current dir
@@ -199,14 +218,19 @@ ${clr(c.bold, 'Usage:')}
199
218
  npx proof-of-commitment --file go.mod Audit Go direct + indirect deps
200
219
  npx proof-of-commitment --file go.sum Audit Go full transitive set
201
220
 
202
- ${clr(c.bold, 'Commit Pro monitoring (poc watch):')}
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):')}
203
227
  poc watch <package> [--ecosystem npm|pypi|cargo|golang]
204
- Add a package to daily monitoring (Pro tier)
228
+ Add a package to daily monitoring
205
229
  poc watchlist List monitored packages with current scores + risk
206
230
  poc unwatch <pkg> Remove a package from monitoring
207
231
 
208
- API key: set COMMIT_API_KEY env or add api_key=<key> to ~/.commit/config
209
- Get a key: https://getcommit.dev/pricing
232
+ Get a free key: https://getcommit.dev/get-started
233
+ Upgrade to Pro: https://getcommit.dev/pricing
210
234
 
211
235
  ${clr(c.bold, 'Options:')}
212
236
  --json Output results as JSON
@@ -646,6 +670,174 @@ async function readApiKey() {
646
670
  return null;
647
671
  }
648
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
+
649
841
  /**
650
842
  * Handle 402 upgrade response from watchlist endpoints.
651
843
  */
@@ -811,9 +1003,25 @@ async function main() {
811
1003
  process.exit(0);
812
1004
  }
813
1005
 
814
- // Watchlist subcommands
1006
+ // Subcommands
815
1007
  const subcmd = args[0];
816
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
+
817
1025
  if (subcmd === 'watch') {
818
1026
  const pkg = args[1];
819
1027
  if (!pkg) { console.error('Usage: poc watch <package> [--ecosystem npm|pypi|cargo|golang]'); process.exit(1); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proof-of-commitment",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Supply chain risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
5
5
  "type": "module",
6
6
  "bin": {