claude-cup 0.3.0 → 0.3.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.
@@ -103,6 +103,7 @@ export function getDefaultJarDir(configDir) {
103
103
  function ensureSignalCacheColumns(db) {
104
104
  const cols = new Set(db.prepare('PRAGMA table_info(signal_cache)').all().map((r) => r.name));
105
105
  const additions = [
106
+ ['high_exposure', 'ALTER TABLE signal_cache ADD COLUMN high_exposure INTEGER DEFAULT 0'],
106
107
  ['status', "ALTER TABLE signal_cache ADD COLUMN status TEXT DEFAULT 'unknown'"],
107
108
  ['validation_reason', 'ALTER TABLE signal_cache ADD COLUMN validation_reason TEXT'],
108
109
  ['source_count', 'ALTER TABLE signal_cache ADD COLUMN source_count INTEGER DEFAULT 1'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cup",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Claude Jar v2 — native desktop visual companion (Tauri + Svelte) with MCP/hook integration for live Claude activity. Beautiful accumulating jar + live intensity meter. The jar is the usage meter.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -134,8 +134,6 @@ function isSkippablePath(filePath, rootPath = '') {
134
134
 
135
135
  const PLACEHOLDER_RE = /(example|changeme|change_me|your[_-]?(key|token|secret|password)|dummy|placeholder|localhost|127\.0\.0\.1|test[_-]?(key|token|secret)?)/i;
136
136
  const KNOWN_DETECTED_ONLY_TYPES = new Set([
137
- 'google_api_key',
138
- 'db_uri',
139
137
  'jwt',
140
138
  'private_key',
141
139
  'slack',
@@ -144,11 +142,10 @@ const KNOWN_DETECTED_ONLY_TYPES = new Set([
144
142
  'sendgrid',
145
143
  'digitalocean',
146
144
  'sentry',
147
- 'auth_uri',
148
145
  'aws_access_key_id',
149
146
  'aws_secret_access_key',
150
147
  ]);
151
- const VALIDATOR_TYPES = new Set(['github', 'npm', 'openai', 'anthropic', 'aws_pair', 'gitlab', 'huggingface', 'stripe']);
148
+ const VALIDATOR_TYPES = new Set(['github', 'npm', 'openai', 'anthropic', 'aws_pair', 'gitlab', 'huggingface', 'stripe', 'google_api_key', 'db_uri', 'auth_uri']);
152
149
  const PROMOTABLE_TYPES = new Set([...VALIDATOR_TYPES, ...KNOWN_DETECTED_ONLY_TYPES]);
153
150
 
154
151
  export function normalizeRawValue(raw) {
@@ -632,32 +629,54 @@ function sourceGroup(source) {
632
629
 
633
630
  function groupAwsPairs(candidates) {
634
631
  const bySource = new Map();
632
+ const allKeyIds = [];
633
+ const allSecrets = [];
634
+ const pairedRaws = new Set();
635
+
635
636
  for (const cand of candidates) {
636
637
  const group = sourceGroup(cand.source);
637
638
  if (!bySource.has(group)) bySource.set(group, { source: cand.source, high_exposure: false });
638
639
  const item = bySource.get(group);
639
640
  item.high_exposure = item.high_exposure || !!cand.high_exposure;
640
641
  const key = String(cand.key || '').toLowerCase();
641
- if (cand.type === 'aws_access_key_id' || key.includes('aws_access_key_id')) item.accessKeyId = cand.raw;
642
- if (cand.type === 'aws_secret_access_key' || key.includes('secret_access_key')) item.secretAccessKey = cand.raw;
642
+ if (cand.type === 'aws_access_key_id' || key.includes('aws_access_key_id')) {
643
+ item.accessKeyId = cand.raw;
644
+ allKeyIds.push(cand);
645
+ }
646
+ if (cand.type === 'aws_secret_access_key' || key.includes('secret_access_key')) {
647
+ item.secretAccessKey = cand.raw;
648
+ allSecrets.push(cand);
649
+ }
643
650
  if (key.includes('session_token')) item.sessionToken = cand.raw;
644
651
  }
652
+
645
653
  const pairs = [];
646
654
  for (const item of bySource.values()) {
647
655
  if (item.accessKeyId && item.secretAccessKey) {
656
+ pairedRaws.add(item.accessKeyId);
657
+ pairedRaws.add(item.secretAccessKey);
658
+ pairs.push({
659
+ raw: { accessKeyId: item.accessKeyId, secretAccessKey: item.secretAccessKey, sessionToken: item.sessionToken || null },
660
+ type: 'aws_pair', source: item.source, category: 'cloud', high_exposure: item.high_exposure,
661
+ });
662
+ }
663
+ }
664
+
665
+ const unpairedIds = allKeyIds.filter(c => !pairedRaws.has(c.raw)).slice(0, 5);
666
+ const unpairedSecrets = allSecrets.filter(c => !pairedRaws.has(c.raw)).slice(0, 5);
667
+ for (const kid of unpairedIds) {
668
+ for (const sec of unpairedSecrets) {
669
+ if (pairedRaws.has(kid.raw) || pairedRaws.has(sec.raw)) continue;
670
+ pairedRaws.add(kid.raw);
671
+ pairedRaws.add(sec.raw);
648
672
  pairs.push({
649
- raw: {
650
- accessKeyId: item.accessKeyId,
651
- secretAccessKey: item.secretAccessKey,
652
- sessionToken: item.sessionToken || null,
653
- },
654
- type: 'aws_pair',
655
- source: item.source,
656
- category: 'cloud',
657
- high_exposure: item.high_exposure,
673
+ raw: { accessKeyId: kid.raw, secretAccessKey: sec.raw, sessionToken: null },
674
+ type: 'aws_pair', source: kid.source, category: 'cloud',
675
+ high_exposure: kid.high_exposure || sec.high_exposure,
658
676
  });
659
677
  }
660
678
  }
679
+
661
680
  return pairs;
662
681
  }
663
682
 
@@ -863,6 +882,103 @@ async function validateStripe(raw, cfg, deps = {}) {
863
882
  return validateGenericApi(raw, { url: cfg.url || 'https://api.stripe.com/v1/account', auth_header: cfg.auth_header || 'Bearer', timeout_ms: cfg.timeout_ms }, deps, 'stripe_account_200');
864
883
  }
865
884
 
885
+ async function validateGoogleApiKey(raw, cfg, deps = {}) {
886
+ const fetchImpl = deps.fetchImpl || fetch;
887
+ const urls = [
888
+ cfg.gemini_url || 'https://generativelanguage.googleapis.com/v1/models',
889
+ cfg.maps_url || 'https://maps.googleapis.com/maps/api/geocode/json?address=test',
890
+ ];
891
+ for (const base of urls) {
892
+ try {
893
+ const url = base + (base.includes('?') ? '&' : '?') + 'key=' + raw;
894
+ const res = await fetchWithTimeout(fetchImpl, url, {}, cfg.timeout_ms || 5000);
895
+ if (res.status === 200) return baseValidationResult('validated', 'google_api_200');
896
+ if (res.status === 403 || res.status === 401) {
897
+ const body = await res.json().catch(() => ({}));
898
+ const msg = JSON.stringify(body).toLowerCase();
899
+ if (msg.includes('api key not valid') || msg.includes('invalid')) {
900
+ return baseValidationResult('invalid', 'google_api_key_invalid');
901
+ }
902
+ continue;
903
+ }
904
+ } catch { continue; }
905
+ }
906
+ return baseValidationResult('invalid', 'google_api_all_probes_failed');
907
+ }
908
+
909
+ async function validateDbUri(raw, cfg, deps = {}) {
910
+ let parsed;
911
+ try { parsed = new URL(raw); } catch { return baseValidationResult('invalid', 'db_uri_parse_error'); }
912
+ const host = parsed.hostname;
913
+ const password = decodeURIComponent(parsed.password || '');
914
+ const port = parseInt(parsed.port, 10) || defaultDbPort(parsed.protocol);
915
+ const proto = parsed.protocol.replace(/:$/, '');
916
+
917
+ if (proto === 'redis' && password) {
918
+ return redisAuthProbe(host, port, password, cfg.timeout_ms || 3000);
919
+ }
920
+
921
+ const { resolve: dnsResolve } = await import('node:dns/promises');
922
+ try {
923
+ await dnsResolve(host);
924
+ return baseValidationResult('validated', `${proto}_dns_resolved`);
925
+ } catch {
926
+ return baseValidationResult('invalid', `${proto}_dns_failed`);
927
+ }
928
+ }
929
+
930
+ function defaultDbPort(protocol) {
931
+ const p = protocol.replace(/:$/, '');
932
+ const ports = { postgresql: 5432, postgres: 5432, mysql: 3306, redis: 6379, mongodb: 27017, 'mongodb+srv': 27017, amqp: 5672, amqps: 5671 };
933
+ return ports[p] || 5432;
934
+ }
935
+
936
+ function redisAuthProbe(host, port, password, timeout) {
937
+ const { connect } = require('node:net');
938
+ return new Promise((resolve) => {
939
+ const timer = setTimeout(() => { sock.destroy(); resolve(baseValidationResult('error', 'redis_timeout')); }, timeout || 3000);
940
+ const sock = connect(port, host);
941
+ let buf = '';
942
+ sock.on('connect', () => { sock.write(`AUTH ${password}\r\nQUIT\r\n`); });
943
+ sock.on('data', (d) => { buf += d.toString(); });
944
+ sock.on('end', () => {
945
+ clearTimeout(timer);
946
+ if (buf.includes('+OK')) resolve(baseValidationResult('validated', 'redis_auth_ok'));
947
+ else resolve(baseValidationResult('invalid', 'redis_auth_rejected'));
948
+ });
949
+ sock.on('error', () => { clearTimeout(timer); resolve(baseValidationResult('error', 'redis_connect_failed')); });
950
+ });
951
+ }
952
+
953
+ async function validateAuthUri(raw, cfg, deps = {}) {
954
+ const fetchImpl = deps.fetchImpl || fetch;
955
+ let parsed;
956
+ try { parsed = new URL(raw); } catch { return baseValidationResult('invalid', 'auth_uri_parse_error'); }
957
+
958
+ if (parsed.protocol === 'https:' || parsed.protocol === 'http:') {
959
+ const user = decodeURIComponent(parsed.username || '');
960
+ const pass = decodeURIComponent(parsed.password || '');
961
+ if (!user && !pass) return baseValidationResult('detected_only', 'auth_uri_no_credentials');
962
+ const auth = Buffer.from(`${user}:${pass}`).toString('base64');
963
+ const target = parsed.origin + (parsed.pathname || '/');
964
+ try {
965
+ const res = await fetchWithTimeout(fetchImpl, target, {
966
+ headers: { Authorization: `Basic ${auth}` },
967
+ redirect: 'manual',
968
+ }, cfg.timeout_ms || 5000);
969
+ if (res.status >= 200 && res.status < 400) return baseValidationResult('validated', 'http_auth_ok');
970
+ if (res.status === 401 || res.status === 403) return baseValidationResult('invalid', `http_auth_${res.status}`);
971
+ return baseValidationResult('detected_only', `http_auth_${res.status}`);
972
+ } catch { return baseValidationResult('error', 'http_auth_network_error'); }
973
+ }
974
+
975
+ const { resolve: dnsResolve } = await import('node:dns/promises');
976
+ try {
977
+ await dnsResolve(parsed.hostname);
978
+ return baseValidationResult('validated', 'auth_uri_dns_resolved');
979
+ } catch { return baseValidationResult('invalid', 'auth_uri_dns_failed'); }
980
+ }
981
+
866
982
  const VALIDATORS = {
867
983
  github: validateGithub,
868
984
  npm: validateNpm,
@@ -872,6 +988,9 @@ const VALIDATORS = {
872
988
  gitlab: validateGitLab,
873
989
  huggingface: validateHuggingFace,
874
990
  stripe: validateStripe,
991
+ google_api_key: validateGoogleApiKey,
992
+ db_uri: validateDbUri,
993
+ auth_uri: validateAuthUri,
875
994
  };
876
995
 
877
996
  export async function validateToken(raw, type, deps = {}) {