shiva-code 0.7.14 → 0.7.16

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.
@@ -1,2068 +0,0 @@
1
- import {
2
- colors,
3
- log
4
- } from "./chunk-UABU5VVI.js";
5
- import {
6
- formatContextAsMarkdown,
7
- generateGitHubContext,
8
- isGhAuthenticated,
9
- isGhInstalled
10
- } from "./chunk-IVFCZLBX.js";
11
- import {
12
- cacheGitHubContext,
13
- getProjectConfig,
14
- hasShivaDir
15
- } from "./chunk-PMA6MGQW.js";
16
-
17
- // src/commands/advanced/hook.ts
18
- import { Command } from "commander";
19
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
20
- import { homedir } from "os";
21
- import { join } from "path";
22
-
23
- // src/services/security/package-scanner.ts
24
- import Conf from "conf";
25
-
26
- // src/types/package.ts
27
- var DEFAULT_PACKAGE_SCAN_CONFIG = {
28
- enabled: true,
29
- autoBlock: false,
30
- minDownloads: 100,
31
- maxAgeDays: 7,
32
- checkTyposquatting: true,
33
- checkScripts: true,
34
- blocklist: [],
35
- allowlist: []
36
- };
37
- var POPULAR_NPM_PACKAGES = [
38
- // Core utilities
39
- "lodash",
40
- "underscore",
41
- "ramda",
42
- "moment",
43
- "dayjs",
44
- "date-fns",
45
- // Web frameworks
46
- "express",
47
- "koa",
48
- "fastify",
49
- "hapi",
50
- "restify",
51
- "nest",
52
- "nestjs",
53
- // Frontend frameworks
54
- "react",
55
- "vue",
56
- "angular",
57
- "svelte",
58
- "preact",
59
- "solid-js",
60
- "next",
61
- "nuxt",
62
- "gatsby",
63
- // HTTP & networking
64
- "axios",
65
- "node-fetch",
66
- "got",
67
- "superagent",
68
- "request",
69
- "undici",
70
- // CLI tools
71
- "commander",
72
- "yargs",
73
- "chalk",
74
- "inquirer",
75
- "ora",
76
- "boxen",
77
- "figlet",
78
- // Build tools
79
- "webpack",
80
- "rollup",
81
- "esbuild",
82
- "vite",
83
- "parcel",
84
- "tsup",
85
- "turbo",
86
- // Testing
87
- "jest",
88
- "mocha",
89
- "chai",
90
- "vitest",
91
- "ava",
92
- "tap",
93
- "cypress",
94
- "playwright",
95
- "puppeteer",
96
- // Linting & formatting
97
- "eslint",
98
- "prettier",
99
- "stylelint",
100
- "standardjs",
101
- "standard",
102
- // TypeScript
103
- "typescript",
104
- "ts-node",
105
- "tsx",
106
- // Database
107
- "mongoose",
108
- "sequelize",
109
- "prisma",
110
- "knex",
111
- "typeorm",
112
- "drizzle-orm",
113
- "pg",
114
- "mysql",
115
- "mysql2",
116
- "sqlite3",
117
- "better-sqlite3",
118
- "redis",
119
- "ioredis",
120
- // Security & auth
121
- "jsonwebtoken",
122
- "bcrypt",
123
- "bcryptjs",
124
- "passport",
125
- "helmet",
126
- "cors",
127
- "csurf",
128
- // Utilities
129
- "uuid",
130
- "nanoid",
131
- "dotenv",
132
- "debug",
133
- "winston",
134
- "pino",
135
- "bunyan",
136
- "morgan",
137
- // File system
138
- "fs-extra",
139
- "glob",
140
- "globby",
141
- "rimraf",
142
- "mkdirp",
143
- "chokidar",
144
- // Validation
145
- "joi",
146
- "yup",
147
- "zod",
148
- "ajv",
149
- "validator",
150
- // Crypto & encoding
151
- "crypto-js",
152
- "node-forge",
153
- "base64-js",
154
- // Process & system
155
- "cross-env",
156
- "execa",
157
- "shelljs",
158
- "concurrently",
159
- "npm-run-all",
160
- // Configuration
161
- "conf",
162
- "config",
163
- "dotenv",
164
- "cosmiconfig",
165
- "rc",
166
- // Misc popular
167
- "bluebird",
168
- "async",
169
- "rxjs",
170
- "socket.io",
171
- "ws",
172
- "graphql",
173
- "apollo-server",
174
- "body-parser",
175
- "cookie-parser",
176
- "multer",
177
- "sharp",
178
- "jimp",
179
- "pdf-lib",
180
- "puppeteer",
181
- "cheerio",
182
- "jsdom",
183
- "marked",
184
- "markdown-it",
185
- "highlight.js",
186
- "prismjs"
187
- ];
188
- var POPULAR_PIP_PACKAGES = [
189
- "requests",
190
- "numpy",
191
- "pandas",
192
- "scipy",
193
- "matplotlib",
194
- "flask",
195
- "django",
196
- "fastapi",
197
- "uvicorn",
198
- "pytest",
199
- "click",
200
- "pydantic",
201
- "sqlalchemy",
202
- "celery",
203
- "redis",
204
- "boto3",
205
- "pillow",
206
- "opencv-python",
207
- "tensorflow",
208
- "torch",
209
- "transformers",
210
- "scikit-learn",
211
- "beautifulsoup4",
212
- "lxml",
213
- "aiohttp",
214
- "httpx"
215
- ];
216
- var POPULAR_CARGO_PACKAGES = [
217
- "serde",
218
- "tokio",
219
- "async-std",
220
- "clap",
221
- "reqwest",
222
- "actix-web",
223
- "axum",
224
- "rocket",
225
- "diesel",
226
- "sqlx",
227
- "log",
228
- "env_logger",
229
- "tracing",
230
- "anyhow",
231
- "thiserror",
232
- "rand",
233
- "regex",
234
- "chrono",
235
- "uuid",
236
- "serde_json"
237
- ];
238
- var SUSPICIOUS_SCRIPT_PATTERNS = [
239
- /curl\s+.*\|\s*(ba)?sh/i,
240
- // curl | bash
241
- /wget\s+.*\|\s*(ba)?sh/i,
242
- // wget | bash
243
- /eval\s*\(/i,
244
- // eval()
245
- /new\s+Function\s*\(/i,
246
- // new Function()
247
- /child_process/i,
248
- // child_process
249
- /\.exe["']?\s*$/i,
250
- // .exe execution
251
- /powershell/i,
252
- // PowerShell
253
- /base64\s+-d/i,
254
- // Base64 decode
255
- /\$\(.*\)/,
256
- // Command substitution
257
- /nc\s+-e/i,
258
- // Netcat reverse shell
259
- /python\s+-c/i,
260
- // Python one-liner
261
- /node\s+-e/i,
262
- // Node one-liner
263
- /rm\s+-rf/i,
264
- // Dangerous removal
265
- /\/dev\/tcp/i,
266
- // Bash tcp
267
- /mkfifo/i
268
- // Named pipe (often used in reverse shells)
269
- ];
270
- var KNOWN_MALICIOUS_PACKAGES = [
271
- {
272
- package: "event-stream",
273
- manager: "npm",
274
- reason: "Known supply chain attack (flatmap-stream)",
275
- addedAt: "2018-11-26",
276
- source: "community"
277
- },
278
- {
279
- package: "flatmap-stream",
280
- manager: "npm",
281
- reason: "Malicious cryptocurrency stealer",
282
- addedAt: "2018-11-26",
283
- source: "community"
284
- },
285
- {
286
- package: "ua-parser-js",
287
- manager: "npm",
288
- reason: "Compromised versions (0.7.29, 0.8.0, 1.0.0)",
289
- addedAt: "2021-10-22",
290
- source: "community"
291
- },
292
- {
293
- package: "coa",
294
- manager: "npm",
295
- reason: "Compromised version 2.0.3",
296
- addedAt: "2021-11-04",
297
- source: "community"
298
- },
299
- {
300
- package: "rc",
301
- manager: "npm",
302
- reason: "Compromised versions 1.2.9, 1.3.9, 2.3.9",
303
- addedAt: "2021-11-04",
304
- source: "community"
305
- }
306
- ];
307
-
308
- // src/services/security/package-scanner.ts
309
- var CACHE_TTL = 60 * 60 * 1e3;
310
- var packageInfoCache = /* @__PURE__ */ new Map();
311
- var configStore = new Conf({
312
- projectName: "shiva-code",
313
- defaults: {
314
- packageSecurity: DEFAULT_PACKAGE_SCAN_CONFIG
315
- }
316
- });
317
- var PackageScannerService = class {
318
- // ─────────────────────────────────────────────────────────────────────────────
319
- // Command Parsing
320
- // ─────────────────────────────────────────────────────────────────────────────
321
- /**
322
- * Check if a command is an install command
323
- */
324
- isInstallCommand(command) {
325
- const installPatterns = [
326
- /^npm\s+(install|i|add)\b/i,
327
- /^yarn\s+(add|install)\b/i,
328
- /^pnpm\s+(add|install|i)\b/i,
329
- /^pip\s+install\b/i,
330
- /^pip3\s+install\b/i,
331
- /^cargo\s+(add|install)\b/i,
332
- /^go\s+(get|install)\b/i
333
- ];
334
- return installPatterns.some((pattern) => pattern.test(command.trim()));
335
- }
336
- /**
337
- * Parse an install command to extract package names
338
- */
339
- parseCommand(command) {
340
- const trimmed = command.trim();
341
- const npmMatch = trimmed.match(/^(npm|yarn|pnpm)\s+(install|i|add)\s+(.+)$/i);
342
- if (npmMatch) {
343
- const managerMap = {
344
- npm: "npm",
345
- yarn: "yarn",
346
- pnpm: "pnpm"
347
- };
348
- const manager = managerMap[npmMatch[1].toLowerCase()];
349
- const argsStr = npmMatch[3];
350
- const { packages, flags, isDev, isGlobal } = this.parseNpmArgs(argsStr);
351
- return { manager, packages, flags, isDev, isGlobal };
352
- }
353
- const pipMatch = trimmed.match(/^pip3?\s+install\s+(.+)$/i);
354
- if (pipMatch) {
355
- const { packages, flags } = this.parsePipArgs(pipMatch[1]);
356
- return { manager: "pip", packages, flags, isDev: false, isGlobal: false };
357
- }
358
- const cargoMatch = trimmed.match(/^cargo\s+(add|install)\s+(.+)$/i);
359
- if (cargoMatch) {
360
- const { packages, flags, isDev } = this.parseCargoArgs(cargoMatch[2]);
361
- return { manager: "cargo", packages, flags, isDev, isGlobal: false };
362
- }
363
- const goMatch = trimmed.match(/^go\s+(get|install)\s+(.+)$/i);
364
- if (goMatch) {
365
- const { packages, flags } = this.parseGoArgs(goMatch[2]);
366
- return { manager: "go", packages, flags, isDev: false, isGlobal: false };
367
- }
368
- return null;
369
- }
370
- parseNpmArgs(argsStr) {
371
- const parts = argsStr.split(/\s+/);
372
- const packages = [];
373
- const flags = [];
374
- let isDev = false;
375
- let isGlobal = false;
376
- for (const part of parts) {
377
- if (part.startsWith("-")) {
378
- flags.push(part);
379
- if (part === "-D" || part === "--save-dev" || part === "--dev") {
380
- isDev = true;
381
- }
382
- if (part === "-g" || part === "--global") {
383
- isGlobal = true;
384
- }
385
- } else if (part.length > 0) {
386
- const pkgName = part.split("@")[0] || part;
387
- if (pkgName.length > 0) {
388
- packages.push(pkgName);
389
- }
390
- }
391
- }
392
- return { packages, flags, isDev, isGlobal };
393
- }
394
- parsePipArgs(argsStr) {
395
- const parts = argsStr.split(/\s+/);
396
- const packages = [];
397
- const flags = [];
398
- for (let i = 0; i < parts.length; i++) {
399
- const part = parts[i];
400
- if (part.startsWith("-")) {
401
- flags.push(part);
402
- if ((part === "-r" || part === "--requirement") && i + 1 < parts.length) {
403
- i++;
404
- }
405
- } else if (part.length > 0) {
406
- const pkgName = part.split(/[=<>!~\[]/)[0];
407
- if (pkgName.length > 0) {
408
- packages.push(pkgName);
409
- }
410
- }
411
- }
412
- return { packages, flags };
413
- }
414
- parseCargoArgs(argsStr) {
415
- const parts = argsStr.split(/\s+/);
416
- const packages = [];
417
- const flags = [];
418
- let isDev = false;
419
- for (const part of parts) {
420
- if (part.startsWith("-")) {
421
- flags.push(part);
422
- if (part === "--dev" || part === "-D") {
423
- isDev = true;
424
- }
425
- } else if (part.length > 0) {
426
- packages.push(part);
427
- }
428
- }
429
- return { packages, flags, isDev };
430
- }
431
- parseGoArgs(argsStr) {
432
- const parts = argsStr.split(/\s+/);
433
- const packages = [];
434
- const flags = [];
435
- for (const part of parts) {
436
- if (part.startsWith("-")) {
437
- flags.push(part);
438
- } else if (part.length > 0) {
439
- packages.push(part);
440
- }
441
- }
442
- return { packages, flags };
443
- }
444
- // ─────────────────────────────────────────────────────────────────────────────
445
- // Package Info Fetching
446
- // ─────────────────────────────────────────────────────────────────────────────
447
- /**
448
- * Get npm package info from registry
449
- */
450
- async getNpmPackageInfo(name) {
451
- const cacheKey = `npm:${name}`;
452
- const cached = packageInfoCache.get(cacheKey);
453
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
454
- return cached.data;
455
- }
456
- try {
457
- const metaResponse = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}`);
458
- if (!metaResponse.ok) {
459
- if (metaResponse.status === 404) {
460
- return null;
461
- }
462
- throw new Error(`npm registry returned ${metaResponse.status}`);
463
- }
464
- const meta = await metaResponse.json();
465
- const latestVersion = meta["dist-tags"]?.latest;
466
- const versionData = latestVersion ? meta.versions?.[latestVersion] : null;
467
- let weeklyDownloads = 0;
468
- try {
469
- const downloadsResponse = await fetch(
470
- `https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(name)}`
471
- );
472
- if (downloadsResponse.ok) {
473
- const downloads = await downloadsResponse.json();
474
- weeklyDownloads = downloads.downloads || 0;
475
- }
476
- } catch {
477
- }
478
- const info = {
479
- name: meta.name || name,
480
- version: latestVersion || "0.0.0",
481
- description: meta.description,
482
- weeklyDownloads,
483
- publishedAt: (latestVersion ? meta.time?.[latestVersion] : void 0) || meta.time?.created || (/* @__PURE__ */ new Date()).toISOString(),
484
- maintainers: meta.maintainers?.map((m) => m.name) || [],
485
- repository: versionData?.repository?.url || meta.repository?.url,
486
- homepage: versionData?.homepage || meta.homepage,
487
- scripts: versionData?.scripts,
488
- dependencies: versionData?.dependencies,
489
- devDependencies: versionData?.devDependencies
490
- };
491
- packageInfoCache.set(cacheKey, { data: info, timestamp: Date.now() });
492
- return info;
493
- } catch {
494
- return null;
495
- }
496
- }
497
- /**
498
- * Get PyPI package info from registry
499
- */
500
- async getPyPIPackageInfo(name) {
501
- const cacheKey = `pip:${name}`;
502
- const cached = packageInfoCache.get(cacheKey);
503
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
504
- return cached.data;
505
- }
506
- try {
507
- const response = await fetch(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`);
508
- if (!response.ok) {
509
- if (response.status === 404) {
510
- return null;
511
- }
512
- throw new Error(`PyPI returned ${response.status}`);
513
- }
514
- const data = await response.json();
515
- const releaseInfo = data.info;
516
- const info = {
517
- name: releaseInfo.name,
518
- version: releaseInfo.version,
519
- summary: releaseInfo.summary,
520
- downloads: 0,
521
- // PyPI doesn't expose download counts directly
522
- releaseDate: data.urls?.[0]?.upload_time || (/* @__PURE__ */ new Date()).toISOString(),
523
- author: releaseInfo.author,
524
- homePage: releaseInfo.home_page || releaseInfo.project_url,
525
- license: releaseInfo.license
526
- };
527
- packageInfoCache.set(cacheKey, { data: info, timestamp: Date.now() });
528
- return info;
529
- } catch {
530
- return null;
531
- }
532
- }
533
- /**
534
- * Get Cargo package info from crates.io
535
- */
536
- async getCargoPackageInfo(name) {
537
- const cacheKey = `cargo:${name}`;
538
- const cached = packageInfoCache.get(cacheKey);
539
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
540
- return cached.data;
541
- }
542
- try {
543
- const response = await fetch(`https://crates.io/api/v1/crates/${encodeURIComponent(name)}`, {
544
- headers: {
545
- "User-Agent": "shiva-cli (https://shiva.li)"
546
- }
547
- });
548
- if (!response.ok) {
549
- if (response.status === 404) {
550
- return null;
551
- }
552
- throw new Error(`crates.io returned ${response.status}`);
553
- }
554
- const data = await response.json();
555
- const crateInfo = data.crate;
556
- const latestVersion = data.versions?.[0];
557
- const info = {
558
- name: crateInfo.name,
559
- version: latestVersion?.num || "0.0.0",
560
- description: crateInfo.description,
561
- downloads: crateInfo.downloads || 0,
562
- repository: crateInfo.repository,
563
- homepage: crateInfo.homepage,
564
- createdAt: latestVersion?.created_at || crateInfo.created_at || (/* @__PURE__ */ new Date()).toISOString()
565
- };
566
- packageInfoCache.set(cacheKey, { data: info, timestamp: Date.now() });
567
- return info;
568
- } catch {
569
- return null;
570
- }
571
- }
572
- // ─────────────────────────────────────────────────────────────────────────────
573
- // Individual Checks
574
- // ─────────────────────────────────────────────────────────────────────────────
575
- /**
576
- * Check download count
577
- */
578
- checkDownloadCount(weeklyDownloads, manager) {
579
- const config = this.getConfig();
580
- const minDownloads = config.minDownloads;
581
- if (weeklyDownloads < 10) {
582
- return {
583
- name: "Downloads",
584
- status: "fail",
585
- message: `${weeklyDownloads}/week (critical - nearly no usage)`,
586
- severity: "critical"
587
- };
588
- }
589
- if (weeklyDownloads < minDownloads) {
590
- return {
591
- name: "Downloads",
592
- status: "fail",
593
- message: `${weeklyDownloads.toLocaleString()}/week (< ${minDownloads})`,
594
- severity: "high"
595
- };
596
- }
597
- if (weeklyDownloads < minDownloads * 10) {
598
- return {
599
- name: "Downloads",
600
- status: "warn",
601
- message: `${weeklyDownloads.toLocaleString()}/week (low)`,
602
- severity: "medium"
603
- };
604
- }
605
- return {
606
- name: "Downloads",
607
- status: "pass",
608
- message: `${weeklyDownloads.toLocaleString()}/week`,
609
- severity: "info"
610
- };
611
- }
612
- /**
613
- * Check package age
614
- */
615
- checkPublishAge(publishedAt) {
616
- const config = this.getConfig();
617
- const publishDate = new Date(publishedAt);
618
- const now = /* @__PURE__ */ new Date();
619
- const ageInDays = Math.floor((now.getTime() - publishDate.getTime()) / (1e3 * 60 * 60 * 24));
620
- if (ageInDays < 1) {
621
- return {
622
- name: "Age",
623
- status: "fail",
624
- message: `Published today (critical)`,
625
- severity: "critical"
626
- };
627
- }
628
- if (ageInDays < config.maxAgeDays) {
629
- return {
630
- name: "Age",
631
- status: "fail",
632
- message: `${ageInDays} days old (< ${config.maxAgeDays} days)`,
633
- severity: "high"
634
- };
635
- }
636
- if (ageInDays < 30) {
637
- return {
638
- name: "Age",
639
- status: "warn",
640
- message: `${ageInDays} days old (relatively new)`,
641
- severity: "medium"
642
- };
643
- }
644
- const years = Math.floor(ageInDays / 365);
645
- const months = Math.floor(ageInDays % 365 / 30);
646
- let ageStr;
647
- if (years > 0) {
648
- ageStr = `${years} year${years > 1 ? "s" : ""}`;
649
- } else if (months > 0) {
650
- ageStr = `${months} month${months > 1 ? "s" : ""}`;
651
- } else {
652
- ageStr = `${ageInDays} days`;
653
- }
654
- return {
655
- name: "Age",
656
- status: "pass",
657
- message: ageStr,
658
- severity: "info"
659
- };
660
- }
661
- /**
662
- * Check for typosquatting
663
- */
664
- checkTyposquatting(name, manager) {
665
- const config = this.getConfig();
666
- if (!config.checkTyposquatting) {
667
- return {
668
- name: "Typosquatting",
669
- status: "pass",
670
- message: "Check disabled",
671
- severity: "info"
672
- };
673
- }
674
- const similarPackages = this.findSimilarPopularPackages(name, manager);
675
- if (similarPackages.length > 0) {
676
- return {
677
- name: "Typosquatting",
678
- status: "fail",
679
- message: `Similar to "${similarPackages[0]}"`,
680
- severity: "critical"
681
- };
682
- }
683
- return {
684
- name: "Typosquatting",
685
- status: "pass",
686
- message: "No similar popular packages",
687
- severity: "info"
688
- };
689
- }
690
- /**
691
- * Check install scripts for suspicious patterns
692
- */
693
- checkScripts(scripts) {
694
- const config = this.getConfig();
695
- if (!config.checkScripts || !scripts) {
696
- return {
697
- name: "Scripts",
698
- status: "pass",
699
- message: scripts ? "Check disabled" : "No install scripts",
700
- severity: "info"
701
- };
702
- }
703
- const suspiciousScripts = [];
704
- for (const [scriptName, scriptContent] of Object.entries(scripts)) {
705
- if (!scriptContent) continue;
706
- for (const pattern of SUSPICIOUS_SCRIPT_PATTERNS) {
707
- if (pattern.test(scriptContent)) {
708
- suspiciousScripts.push(scriptName);
709
- break;
710
- }
711
- }
712
- }
713
- if (suspiciousScripts.length > 0) {
714
- return {
715
- name: "Scripts",
716
- status: "fail",
717
- message: `Suspicious ${suspiciousScripts.join(", ")} script`,
718
- severity: "critical"
719
- };
720
- }
721
- const hasInstallScripts = scripts.preinstall || scripts.install || scripts.postinstall;
722
- if (hasInstallScripts) {
723
- return {
724
- name: "Scripts",
725
- status: "warn",
726
- message: "Has install scripts (review recommended)",
727
- severity: "low"
728
- };
729
- }
730
- return {
731
- name: "Scripts",
732
- status: "pass",
733
- message: "No suspicious scripts",
734
- severity: "info"
735
- };
736
- }
737
- /**
738
- * Check repository presence
739
- */
740
- checkRepository(repository) {
741
- if (!repository) {
742
- return {
743
- name: "Repository",
744
- status: "warn",
745
- message: "No repository linked",
746
- severity: "medium"
747
- };
748
- }
749
- const isKnownHost = repository.includes("github.com") || repository.includes("gitlab.com") || repository.includes("bitbucket.org");
750
- if (isKnownHost) {
751
- return {
752
- name: "Repository",
753
- status: "pass",
754
- message: repository.replace(/^git\+/, "").replace(/\.git$/, ""),
755
- severity: "info"
756
- };
757
- }
758
- return {
759
- name: "Repository",
760
- status: "warn",
761
- message: "Unknown repository host",
762
- severity: "low"
763
- };
764
- }
765
- /**
766
- * Check maintainer count
767
- */
768
- checkMaintainers(maintainers) {
769
- if (maintainers.length === 0) {
770
- return {
771
- name: "Maintainers",
772
- status: "warn",
773
- message: "No maintainers listed",
774
- severity: "medium"
775
- };
776
- }
777
- if (maintainers.length === 1) {
778
- return {
779
- name: "Maintainers",
780
- status: "pass",
781
- message: `1 (${maintainers[0]})`,
782
- severity: "info"
783
- };
784
- }
785
- return {
786
- name: "Maintainers",
787
- status: "pass",
788
- message: `${maintainers.length}`,
789
- severity: "info"
790
- };
791
- }
792
- /**
793
- * Check blocklist
794
- */
795
- checkBlocklist(name, manager) {
796
- const config = this.getConfig();
797
- const blocked = config.blocklist.find((entry) => entry.package === name && entry.manager === manager);
798
- if (blocked) {
799
- return {
800
- name: "Blocklist",
801
- status: "fail",
802
- message: blocked.reason,
803
- severity: "critical"
804
- };
805
- }
806
- const malicious = KNOWN_MALICIOUS_PACKAGES.find(
807
- (entry) => entry.package === name && entry.manager === manager
808
- );
809
- if (malicious) {
810
- return {
811
- name: "Blocklist",
812
- status: "fail",
813
- message: malicious.reason,
814
- severity: "critical"
815
- };
816
- }
817
- return {
818
- name: "Blocklist",
819
- status: "pass",
820
- message: "Not blocklisted",
821
- severity: "info"
822
- };
823
- }
824
- /**
825
- * Check allowlist
826
- */
827
- isAllowlisted(name) {
828
- const config = this.getConfig();
829
- return config.allowlist.includes(name);
830
- }
831
- // ─────────────────────────────────────────────────────────────────────────────
832
- // Typosquatting Detection
833
- // ─────────────────────────────────────────────────────────────────────────────
834
- /**
835
- * Calculate Levenshtein distance between two strings
836
- */
837
- getLevenshteinDistance(a, b) {
838
- const matrix = [];
839
- for (let i = 0; i <= b.length; i++) {
840
- matrix[i] = [i];
841
- }
842
- for (let j = 0; j <= a.length; j++) {
843
- matrix[0][j] = j;
844
- }
845
- for (let i = 1; i <= b.length; i++) {
846
- for (let j = 1; j <= a.length; j++) {
847
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
848
- matrix[i][j] = matrix[i - 1][j - 1];
849
- } else {
850
- matrix[i][j] = Math.min(
851
- matrix[i - 1][j - 1] + 1,
852
- // substitution
853
- matrix[i][j - 1] + 1,
854
- // insertion
855
- matrix[i - 1][j] + 1
856
- // deletion
857
- );
858
- }
859
- }
860
- }
861
- return matrix[b.length][a.length];
862
- }
863
- /**
864
- * Find similar popular packages (potential typosquatting)
865
- */
866
- findSimilarPopularPackages(name, manager) {
867
- let popularPackages;
868
- switch (manager) {
869
- case "npm":
870
- case "yarn":
871
- case "pnpm":
872
- popularPackages = POPULAR_NPM_PACKAGES;
873
- break;
874
- case "pip":
875
- popularPackages = POPULAR_PIP_PACKAGES;
876
- break;
877
- case "cargo":
878
- popularPackages = POPULAR_CARGO_PACKAGES;
879
- break;
880
- default:
881
- return [];
882
- }
883
- const normalizedName = name.toLowerCase();
884
- const similar = [];
885
- for (const pkg of popularPackages) {
886
- if (pkg === normalizedName) {
887
- continue;
888
- }
889
- const distance = this.getLevenshteinDistance(normalizedName, pkg);
890
- const maxAllowedDistance = Math.min(2, Math.floor(pkg.length / 4));
891
- const isCommonTypo = normalizedName.replace(/-/g, "") === pkg.replace(/-/g, "") || normalizedName.replace(/_/g, "") === pkg.replace(/_/g, "") || normalizedName.replace(/js$/, "") === pkg || normalizedName.replace(/-js$/, "") === pkg || `${normalizedName}js` === pkg || `${normalizedName}-js` === pkg;
892
- if (distance <= maxAllowedDistance || isCommonTypo) {
893
- similar.push(pkg);
894
- }
895
- }
896
- return similar;
897
- }
898
- // ─────────────────────────────────────────────────────────────────────────────
899
- // Risk Calculation
900
- // ─────────────────────────────────────────────────────────────────────────────
901
- /**
902
- * Calculate overall risk score from checks
903
- */
904
- calculateRiskScore(checks) {
905
- let penalty = 0;
906
- for (const check of checks) {
907
- if (check.status === "fail") {
908
- switch (check.severity) {
909
- case "critical":
910
- penalty += 40;
911
- break;
912
- case "high":
913
- penalty += 25;
914
- break;
915
- case "medium":
916
- penalty += 15;
917
- break;
918
- case "low":
919
- penalty += 5;
920
- break;
921
- }
922
- } else if (check.status === "warn") {
923
- switch (check.severity) {
924
- case "critical":
925
- penalty += 20;
926
- break;
927
- case "high":
928
- penalty += 12;
929
- break;
930
- case "medium":
931
- penalty += 8;
932
- break;
933
- case "low":
934
- penalty += 3;
935
- break;
936
- }
937
- }
938
- }
939
- return Math.max(0, 100 - penalty);
940
- }
941
- /**
942
- * Determine recommendation based on score and checks
943
- */
944
- determineRecommendation(score, checks) {
945
- const hasCriticalFail = checks.some((c) => c.status === "fail" && c.severity === "critical");
946
- if (hasCriticalFail || score < 30) {
947
- return "block";
948
- }
949
- if (score < 60) {
950
- return "warn";
951
- }
952
- return "allow";
953
- }
954
- /**
955
- * Determine risk level from score
956
- */
957
- getRiskLevel(score) {
958
- if (score < 30) return "critical";
959
- if (score < 50) return "high";
960
- if (score < 70) return "medium";
961
- return "low";
962
- }
963
- // ─────────────────────────────────────────────────────────────────────────────
964
- // Core Scanning
965
- // ─────────────────────────────────────────────────────────────────────────────
966
- /**
967
- * Scan a single package
968
- */
969
- async scanPackage(name, manager) {
970
- if (this.isAllowlisted(name)) {
971
- return {
972
- package: name,
973
- manager,
974
- risk: "low",
975
- score: 100,
976
- checks: [
977
- {
978
- name: "Allowlist",
979
- status: "pass",
980
- message: "Package is allowlisted",
981
- severity: "info"
982
- }
983
- ],
984
- recommendation: "allow"
985
- };
986
- }
987
- const checks = [];
988
- let suggestion;
989
- const blocklistCheck = this.checkBlocklist(name, manager);
990
- checks.push(blocklistCheck);
991
- if (blocklistCheck.status === "fail") {
992
- return {
993
- package: name,
994
- manager,
995
- risk: "critical",
996
- score: 0,
997
- checks,
998
- recommendation: "block"
999
- };
1000
- }
1001
- if (manager === "npm" || manager === "yarn" || manager === "pnpm") {
1002
- const info = await this.getNpmPackageInfo(name);
1003
- if (!info) {
1004
- checks.push({
1005
- name: "Registry",
1006
- status: "fail",
1007
- message: "Package not found in npm registry",
1008
- severity: "critical"
1009
- });
1010
- const typosquatCheck2 = this.checkTyposquatting(name, manager);
1011
- checks.push(typosquatCheck2);
1012
- if (typosquatCheck2.status === "fail") {
1013
- const similar = this.findSimilarPopularPackages(name, manager);
1014
- if (similar.length > 0) {
1015
- suggestion = similar[0];
1016
- }
1017
- }
1018
- return {
1019
- package: name,
1020
- manager,
1021
- risk: "critical",
1022
- score: 0,
1023
- checks,
1024
- recommendation: "block",
1025
- suggestion
1026
- };
1027
- }
1028
- checks.push(this.checkDownloadCount(info.weeklyDownloads, manager));
1029
- checks.push(this.checkPublishAge(info.publishedAt));
1030
- checks.push(this.checkTyposquatting(name, manager));
1031
- checks.push(this.checkScripts(info.scripts));
1032
- checks.push(this.checkRepository(info.repository));
1033
- checks.push(this.checkMaintainers(info.maintainers));
1034
- const typosquatCheck = checks.find((c) => c.name === "Typosquatting");
1035
- if (typosquatCheck?.status === "fail") {
1036
- const similar = this.findSimilarPopularPackages(name, manager);
1037
- if (similar.length > 0) {
1038
- suggestion = similar[0];
1039
- }
1040
- }
1041
- } else if (manager === "pip") {
1042
- const info = await this.getPyPIPackageInfo(name);
1043
- if (!info) {
1044
- checks.push({
1045
- name: "Registry",
1046
- status: "fail",
1047
- message: "Package not found in PyPI",
1048
- severity: "critical"
1049
- });
1050
- const typosquatCheck = this.checkTyposquatting(name, manager);
1051
- checks.push(typosquatCheck);
1052
- if (typosquatCheck.status === "fail") {
1053
- const similar = this.findSimilarPopularPackages(name, manager);
1054
- if (similar.length > 0) {
1055
- suggestion = similar[0];
1056
- }
1057
- }
1058
- return {
1059
- package: name,
1060
- manager,
1061
- risk: "critical",
1062
- score: 0,
1063
- checks,
1064
- recommendation: "block",
1065
- suggestion
1066
- };
1067
- }
1068
- checks.push(this.checkPublishAge(info.releaseDate));
1069
- checks.push(this.checkTyposquatting(name, manager));
1070
- checks.push({
1071
- name: "Repository",
1072
- status: info.homePage ? "pass" : "warn",
1073
- message: info.homePage || "No homepage linked",
1074
- severity: info.homePage ? "info" : "medium"
1075
- });
1076
- } else if (manager === "cargo") {
1077
- const info = await this.getCargoPackageInfo(name);
1078
- if (!info) {
1079
- checks.push({
1080
- name: "Registry",
1081
- status: "fail",
1082
- message: "Package not found in crates.io",
1083
- severity: "critical"
1084
- });
1085
- const typosquatCheck = this.checkTyposquatting(name, manager);
1086
- checks.push(typosquatCheck);
1087
- return {
1088
- package: name,
1089
- manager,
1090
- risk: "critical",
1091
- score: 0,
1092
- checks,
1093
- recommendation: "block"
1094
- };
1095
- }
1096
- checks.push(this.checkDownloadCount(info.downloads, manager));
1097
- checks.push(this.checkPublishAge(info.createdAt));
1098
- checks.push(this.checkTyposquatting(name, manager));
1099
- checks.push(this.checkRepository(info.repository));
1100
- }
1101
- const score = this.calculateRiskScore(checks);
1102
- const recommendation = this.determineRecommendation(score, checks);
1103
- const risk = this.getRiskLevel(score);
1104
- return {
1105
- package: name,
1106
- manager,
1107
- risk,
1108
- score,
1109
- checks,
1110
- recommendation,
1111
- suggestion
1112
- };
1113
- }
1114
- /**
1115
- * Scan multiple packages
1116
- */
1117
- async scanPackages(packages, manager) {
1118
- const results = await Promise.all(packages.map((pkg) => this.scanPackage(pkg, manager)));
1119
- return results;
1120
- }
1121
- /**
1122
- * Scan a command (for hook integration)
1123
- */
1124
- async scanCommand(command) {
1125
- if (!this.isInstallCommand(command)) {
1126
- return null;
1127
- }
1128
- const parsed = this.parseCommand(command);
1129
- if (!parsed || parsed.packages.length === 0) {
1130
- return null;
1131
- }
1132
- return this.scanPackages(parsed.packages, parsed.manager);
1133
- }
1134
- // ─────────────────────────────────────────────────────────────────────────────
1135
- // Hook Integration
1136
- // ─────────────────────────────────────────────────────────────────────────────
1137
- /**
1138
- * Generate hook output for Claude Code
1139
- */
1140
- generateHookOutput(results) {
1141
- const config = this.getConfig();
1142
- const blockedPackages = results.filter((r) => r.recommendation === "block");
1143
- const warnPackages = results.filter((r) => r.recommendation === "warn");
1144
- if (blockedPackages.length > 0) {
1145
- const pkg = blockedPackages[0];
1146
- let reason = `Package '${pkg.package}' blocked:`;
1147
- const criticalChecks = pkg.checks.filter((c) => c.status === "fail" && c.severity === "critical");
1148
- if (criticalChecks.length > 0) {
1149
- reason += ` ${criticalChecks.map((c) => c.message).join(", ")}`;
1150
- } else {
1151
- reason += ` Risk score ${pkg.score}/100`;
1152
- }
1153
- if (pkg.suggestion) {
1154
- reason += `
1155
-
1156
- Did you mean: ${pkg.suggestion}?`;
1157
- }
1158
- if (config.autoBlock) {
1159
- return {
1160
- decision: "block",
1161
- reason
1162
- };
1163
- }
1164
- return {
1165
- hookSpecificOutput: {
1166
- additionalContext: `\u{1F6E1}\uFE0F SHIVA Security Warning:
1167
-
1168
- ${reason}
1169
-
1170
- Review the package before proceeding.`
1171
- }
1172
- };
1173
- }
1174
- if (warnPackages.length > 0) {
1175
- const warnings = warnPackages.map((pkg) => {
1176
- const issues = pkg.checks.filter((c) => c.status === "warn" || c.status === "fail").map((c) => c.message);
1177
- return `${pkg.package}: ${issues.join(", ")}`;
1178
- }).join("\n");
1179
- return {
1180
- hookSpecificOutput: {
1181
- additionalContext: `\u26A0\uFE0F SHIVA Security Notice:
1182
-
1183
- ${warnings}
1184
-
1185
- Continue with caution.`
1186
- }
1187
- };
1188
- }
1189
- return { hookSpecificOutput: null };
1190
- }
1191
- // ─────────────────────────────────────────────────────────────────────────────
1192
- // Blocklist/Allowlist Management
1193
- // ─────────────────────────────────────────────────────────────────────────────
1194
- /**
1195
- * Add a package to the blocklist
1196
- */
1197
- addToBlocklist(entry) {
1198
- const config = this.getConfig();
1199
- const existing = config.blocklist.find(
1200
- (e) => e.package === entry.package && e.manager === entry.manager
1201
- );
1202
- if (existing) {
1203
- existing.reason = entry.reason;
1204
- existing.source = entry.source;
1205
- } else {
1206
- config.blocklist.push({
1207
- ...entry,
1208
- addedAt: (/* @__PURE__ */ new Date()).toISOString()
1209
- });
1210
- }
1211
- this.updateConfig(config);
1212
- }
1213
- /**
1214
- * Remove a package from the blocklist
1215
- */
1216
- removeFromBlocklist(packageName, manager) {
1217
- const config = this.getConfig();
1218
- const index = config.blocklist.findIndex(
1219
- (e) => e.package === packageName && e.manager === manager
1220
- );
1221
- if (index === -1) {
1222
- return false;
1223
- }
1224
- config.blocklist.splice(index, 1);
1225
- this.updateConfig(config);
1226
- return true;
1227
- }
1228
- /**
1229
- * Check if a package is blocked
1230
- */
1231
- isBlocked(name, manager) {
1232
- const config = this.getConfig();
1233
- return config.blocklist.some((e) => e.package === name && e.manager === manager) || KNOWN_MALICIOUS_PACKAGES.some((e) => e.package === name && e.manager === manager);
1234
- }
1235
- /**
1236
- * Add a package to the allowlist
1237
- */
1238
- addToAllowlist(packageName) {
1239
- const config = this.getConfig();
1240
- if (!config.allowlist.includes(packageName)) {
1241
- config.allowlist.push(packageName);
1242
- this.updateConfig(config);
1243
- }
1244
- }
1245
- /**
1246
- * Remove a package from the allowlist
1247
- */
1248
- removeFromAllowlist(packageName) {
1249
- const config = this.getConfig();
1250
- const index = config.allowlist.indexOf(packageName);
1251
- if (index === -1) {
1252
- return false;
1253
- }
1254
- config.allowlist.splice(index, 1);
1255
- this.updateConfig(config);
1256
- return true;
1257
- }
1258
- // ─────────────────────────────────────────────────────────────────────────────
1259
- // Configuration
1260
- // ─────────────────────────────────────────────────────────────────────────────
1261
- /**
1262
- * Get current configuration
1263
- */
1264
- getConfig() {
1265
- return configStore.get("packageSecurity");
1266
- }
1267
- /**
1268
- * Update configuration
1269
- */
1270
- updateConfig(config) {
1271
- const current = this.getConfig();
1272
- configStore.set("packageSecurity", { ...current, ...config });
1273
- }
1274
- /**
1275
- * Reset configuration to defaults
1276
- */
1277
- resetConfig() {
1278
- configStore.set("packageSecurity", DEFAULT_PACKAGE_SCAN_CONFIG);
1279
- }
1280
- /**
1281
- * Clear package info cache
1282
- */
1283
- clearCache() {
1284
- packageInfoCache.clear();
1285
- }
1286
- };
1287
- var packageScanner = new PackageScannerService();
1288
-
1289
- // src/commands/advanced/hook.ts
1290
- var CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
1291
- function getClaudeSettings() {
1292
- if (!existsSync(CLAUDE_SETTINGS_PATH)) {
1293
- return {};
1294
- }
1295
- try {
1296
- const content = readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
1297
- return JSON.parse(content);
1298
- } catch {
1299
- return {};
1300
- }
1301
- }
1302
- function saveClaudeSettings(settings) {
1303
- const dir = join(homedir(), ".claude");
1304
- if (!existsSync(dir)) {
1305
- mkdirSync(dir, { recursive: true });
1306
- }
1307
- writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
1308
- }
1309
- function hasShivaHook(hooks, event) {
1310
- if (!hooks || !hooks[event]) return false;
1311
- const eventHooks = hooks[event];
1312
- return eventHooks.some(
1313
- (entry) => entry.hooks?.some((h) => h.command?.includes("shiva"))
1314
- );
1315
- }
1316
- function removeShivaHooks(eventHooks) {
1317
- return eventHooks.filter(
1318
- (entry) => !entry.hooks?.some((h) => h.command?.includes("shiva"))
1319
- );
1320
- }
1321
- var hookCommand = new Command("hook").description("Claude Code Hook Integration verwalten");
1322
- hookCommand.command("install").description("SHIVA Hooks in Claude Code installieren").option("--github", "GitHub Context Injection aktivieren").option("--sync", "Cloud Sync Hooks aktivieren (Standard)").option("--scan", "Package Security Scanning aktivieren").option("--token-protection", "Token-Schutz aktivieren (erkennt & sch\xFCtzt Secrets)").option("--all", "Alle Hooks aktivieren").action((options) => {
1323
- log.brand();
1324
- const claudePath = join(homedir(), ".claude");
1325
- if (!existsSync(claudePath)) {
1326
- log.error("Claude Code nicht gefunden");
1327
- log.newline();
1328
- log.info("Installiere Claude Code:");
1329
- log.plain(" npm install -g @anthropic-ai/claude-code");
1330
- log.newline();
1331
- log.dim("Oder: https://claude.ai/code");
1332
- return;
1333
- }
1334
- const settings = getClaudeSettings();
1335
- if (!settings.hooks) {
1336
- settings.hooks = {};
1337
- }
1338
- const installSync = options.sync || options.all || !options.github && !options.scan && !options.tokenProtection;
1339
- const installGithub = options.github || options.all;
1340
- const installScan = options.scan || options.all;
1341
- const installTokenProtection = options.tokenProtection || options.all;
1342
- const hasSyncHooks = hasShivaHook(settings.hooks, "SessionStart") || hasShivaHook(settings.hooks, "Stop");
1343
- const hasGithubHook = hasShivaContextHook(settings.hooks);
1344
- const hasScanHook = hasShivaScanHook(settings.hooks);
1345
- const hasTokenHook = hasShivaTokenHook(settings.hooks);
1346
- if (hasSyncHooks && installSync && !installGithub && !installScan) {
1347
- log.warn("SHIVA Sync Hooks sind bereits installiert");
1348
- log.newline();
1349
- log.info("GitHub Context Injection hinzuf\xFCgen:");
1350
- log.plain(" shiva hook install --github");
1351
- log.newline();
1352
- log.info("Package Scanning hinzuf\xFCgen:");
1353
- log.plain(" shiva hook install --scan");
1354
- log.newline();
1355
- log.info("Oder neu installieren:");
1356
- log.plain(" shiva hook uninstall && shiva hook install --all");
1357
- return;
1358
- }
1359
- let installedSomething = false;
1360
- if (installSync && !hasSyncHooks) {
1361
- if (!settings.hooks.SessionStart) {
1362
- settings.hooks.SessionStart = [];
1363
- }
1364
- settings.hooks.SessionStart.push({
1365
- hooks: [
1366
- {
1367
- type: "command",
1368
- command: "shiva sync --pull --quiet",
1369
- timeout: 30
1370
- }
1371
- ]
1372
- });
1373
- if (!settings.hooks.Stop) {
1374
- settings.hooks.Stop = [];
1375
- }
1376
- settings.hooks.Stop.push({
1377
- hooks: [
1378
- {
1379
- type: "command",
1380
- command: "shiva sync --push --quiet",
1381
- timeout: 30
1382
- }
1383
- ]
1384
- });
1385
- installedSomething = true;
1386
- }
1387
- if (installGithub && !hasGithubHook) {
1388
- if (!settings.hooks.SessionStart) {
1389
- settings.hooks.SessionStart = [];
1390
- }
1391
- settings.hooks.SessionStart.push({
1392
- hooks: [
1393
- {
1394
- type: "command",
1395
- command: "shiva hook inject-context",
1396
- timeout: 15
1397
- }
1398
- ]
1399
- });
1400
- installedSomething = true;
1401
- }
1402
- if (installScan && !hasScanHook) {
1403
- if (!settings.hooks.PreToolUse) {
1404
- settings.hooks.PreToolUse = [];
1405
- }
1406
- settings.hooks.PreToolUse.push({
1407
- matcher: "Bash",
1408
- hooks: [
1409
- {
1410
- type: "command",
1411
- command: 'shiva hook scan-command "$TOOL_INPUT"',
1412
- timeout: 30
1413
- }
1414
- ]
1415
- });
1416
- installedSomething = true;
1417
- }
1418
- if (installTokenProtection && !hasTokenHook) {
1419
- if (!settings.hooks.Stop) {
1420
- settings.hooks.Stop = [];
1421
- }
1422
- settings.hooks.Stop.push({
1423
- hooks: [
1424
- {
1425
- type: "command",
1426
- command: "shiva hook scan-session --quiet",
1427
- timeout: 30
1428
- }
1429
- ]
1430
- });
1431
- installedSomething = true;
1432
- }
1433
- if (!installedSomething) {
1434
- log.warn("Alle ausgew\xE4hlten Hooks sind bereits installiert");
1435
- log.newline();
1436
- log.info("Status anzeigen: shiva hook status");
1437
- return;
1438
- }
1439
- saveClaudeSettings(settings);
1440
- log.success("SHIVA Hooks installiert!");
1441
- log.newline();
1442
- log.header("Aktive Hooks");
1443
- if (installSync || hasSyncHooks) {
1444
- log.tree.item("SessionStart: Memories laden (Cloud Sync)");
1445
- log.tree.item("Stop: Memories speichern (Cloud Sync)");
1446
- }
1447
- if (installGithub || hasGithubHook) {
1448
- log.tree.item("SessionStart: GitHub Context injizieren");
1449
- }
1450
- if (installScan || hasScanHook) {
1451
- log.tree.item("PreToolUse: Package Security Scanning");
1452
- }
1453
- if (installTokenProtection || hasTokenHook) {
1454
- log.tree.item("Stop: Token-Schutz (Secrets erkennen & sch\xFCtzen)");
1455
- }
1456
- log.newline();
1457
- log.dim("Claude Code wird nun automatisch mit SHIVA integriert.");
1458
- log.newline();
1459
- log.info("Starte Claude Code wie gewohnt:");
1460
- log.plain(" claude");
1461
- });
1462
- function hasShivaContextHook(hooks) {
1463
- if (!hooks || !hooks.SessionStart) return false;
1464
- const eventHooks = hooks.SessionStart;
1465
- return eventHooks.some(
1466
- (entry) => entry.hooks?.some((h) => h.command?.includes("inject-context"))
1467
- );
1468
- }
1469
- function hasShivaScanHook(hooks) {
1470
- if (!hooks || !hooks.PreToolUse) return false;
1471
- const eventHooks = hooks.PreToolUse;
1472
- return eventHooks.some(
1473
- (entry) => entry.matcher === "Bash" && entry.hooks?.some((h) => h.command?.includes("shiva hook scan-command"))
1474
- );
1475
- }
1476
- function hasShivaTokenHook(hooks) {
1477
- if (!hooks || !hooks.Stop) return false;
1478
- const eventHooks = hooks.Stop;
1479
- return eventHooks.some(
1480
- (entry) => entry.hooks?.some((h) => h.command?.includes("shiva hook scan-session"))
1481
- );
1482
- }
1483
- hookCommand.command("uninstall").description("SHIVA Hooks aus Claude Code entfernen").action(() => {
1484
- log.brand();
1485
- const settings = getClaudeSettings();
1486
- if (!settings.hooks) {
1487
- log.info("Keine Hooks installiert");
1488
- return;
1489
- }
1490
- let removed = false;
1491
- if (settings.hooks.SessionStart) {
1492
- const before = settings.hooks.SessionStart.length;
1493
- settings.hooks.SessionStart = removeShivaHooks(settings.hooks.SessionStart);
1494
- if (settings.hooks.SessionStart.length < before) removed = true;
1495
- if (settings.hooks.SessionStart.length === 0) delete settings.hooks.SessionStart;
1496
- }
1497
- if (settings.hooks.Stop) {
1498
- const before = settings.hooks.Stop.length;
1499
- settings.hooks.Stop = removeShivaHooks(settings.hooks.Stop);
1500
- if (settings.hooks.Stop.length < before) removed = true;
1501
- if (settings.hooks.Stop.length === 0) delete settings.hooks.Stop;
1502
- }
1503
- if (settings.hooks.PreToolUse) {
1504
- const before = settings.hooks.PreToolUse.length;
1505
- settings.hooks.PreToolUse = removeShivaHooks(settings.hooks.PreToolUse);
1506
- if (settings.hooks.PreToolUse.length < before) removed = true;
1507
- if (settings.hooks.PreToolUse.length === 0) delete settings.hooks.PreToolUse;
1508
- }
1509
- if (Object.keys(settings.hooks).length === 0) {
1510
- delete settings.hooks;
1511
- }
1512
- saveClaudeSettings(settings);
1513
- if (removed) {
1514
- log.success("SHIVA Hooks entfernt");
1515
- } else {
1516
- log.info("Keine SHIVA Hooks gefunden");
1517
- }
1518
- });
1519
- hookCommand.command("status").description("Hook-Status anzeigen").action(() => {
1520
- log.brand();
1521
- log.header("Hook Status");
1522
- log.newline();
1523
- const settings = getClaudeSettings();
1524
- if (!settings.hooks) {
1525
- log.listItem("Keine Hooks installiert", "pending");
1526
- log.newline();
1527
- log.info("Installieren mit: shiva hook install");
1528
- return;
1529
- }
1530
- const hasSessionStart = hasShivaHook(settings.hooks, "SessionStart");
1531
- const hasStop = hasShivaHook(settings.hooks, "Stop");
1532
- const hasGithub = hasShivaContextHook(settings.hooks);
1533
- const hasScan = hasShivaScanHook(settings.hooks);
1534
- const hasToken = hasShivaTokenHook(settings.hooks);
1535
- log.plain("Cloud Sync:");
1536
- if (hasSessionStart) {
1537
- log.listItem("SessionStart: Memories laden", "synced");
1538
- } else {
1539
- log.listItem("SessionStart: nicht aktiv", "pending");
1540
- }
1541
- if (hasStop) {
1542
- log.listItem("Stop: Memories speichern", "synced");
1543
- } else {
1544
- log.listItem("Stop: nicht aktiv", "pending");
1545
- }
1546
- log.newline();
1547
- log.plain("GitHub Integration:");
1548
- if (hasGithub) {
1549
- log.listItem("SessionStart: GitHub Context injizieren", "synced");
1550
- } else {
1551
- log.listItem("GitHub Context: nicht aktiv", "pending");
1552
- }
1553
- log.newline();
1554
- log.plain("Security:");
1555
- if (hasScan) {
1556
- log.listItem("PreToolUse: Package Security Scanning", "synced");
1557
- } else {
1558
- log.listItem("Package Scanning: nicht aktiv", "pending");
1559
- }
1560
- if (hasToken) {
1561
- log.listItem("Stop: Token-Schutz (Secrets erkennen)", "synced");
1562
- } else {
1563
- log.listItem("Token-Schutz: nicht aktiv", "pending");
1564
- }
1565
- log.newline();
1566
- const allInstalled = hasSessionStart && hasStop && hasGithub && hasScan && hasToken;
1567
- const syncInstalled = hasSessionStart && hasStop;
1568
- const anyInstalled = hasSessionStart || hasStop || hasGithub || hasScan;
1569
- if (allInstalled) {
1570
- log.success("SHIVA ist vollst\xE4ndig integriert (Sync + GitHub + Security)");
1571
- } else if (syncInstalled && hasGithub && !hasScan) {
1572
- log.success("Cloud Sync + GitHub aktiv");
1573
- log.info("Package Scanning hinzuf\xFCgen: shiva hook install --scan");
1574
- } else if (syncInstalled && !hasGithub) {
1575
- log.success("Cloud Sync aktiv");
1576
- log.info("GitHub Context hinzuf\xFCgen: shiva hook install --github");
1577
- log.info("Package Scanning hinzuf\xFCgen: shiva hook install --scan");
1578
- } else if (hasGithub && !syncInstalled) {
1579
- log.success("GitHub Context aktiv");
1580
- log.info("Cloud Sync hinzuf\xFCgen: shiva hook install --sync");
1581
- } else if (anyInstalled) {
1582
- log.warn("SHIVA ist teilweise integriert");
1583
- log.info("Alle Hooks: shiva hook install --all");
1584
- } else {
1585
- log.info("Installieren mit: shiva hook install");
1586
- }
1587
- });
1588
- hookCommand.command("inject-context").description("GitHub Context in Session injizieren (f\xFCr Hook)").option("--quiet", "Keine Logs ausgeben").action(async (options) => {
1589
- const projectPath = process.cwd();
1590
- if (!isGhInstalled() || !isGhAuthenticated()) {
1591
- console.log(JSON.stringify({}));
1592
- return;
1593
- }
1594
- if (hasShivaDir(projectPath)) {
1595
- const config = getProjectConfig(projectPath);
1596
- if (!config.autoInjectContext) {
1597
- console.log(JSON.stringify({}));
1598
- return;
1599
- }
1600
- }
1601
- try {
1602
- const context = await generateGitHubContext(projectPath);
1603
- if (!context || !context.repo) {
1604
- console.log(JSON.stringify({}));
1605
- return;
1606
- }
1607
- const markdown = formatContextAsMarkdown(context);
1608
- if (hasShivaDir(projectPath)) {
1609
- cacheGitHubContext(projectPath, markdown);
1610
- }
1611
- console.log(JSON.stringify({
1612
- hookSpecificOutput: {
1613
- additionalContext: markdown
1614
- }
1615
- }));
1616
- } catch {
1617
- console.log(JSON.stringify({}));
1618
- }
1619
- });
1620
- hookCommand.command("branch-switch").description("Branch-Wechsel behandeln (f\xFCr git hook)").argument("<old-ref>", "Alter Branch/Ref").argument("<new-ref>", "Neuer Branch/Ref").option("--quiet", "Keine Ausgabe").action(async (oldRef, newRef, options) => {
1621
- if (options.quiet) {
1622
- return;
1623
- }
1624
- const { getCurrentBranch } = await import("./api-OEHQTBH7.js");
1625
- const { getSessionForBranch, hasShivaDir: hasShivaDir2 } = await import("./config-D6M6LI6U.js");
1626
- const { findProject, findSessionByBranch, formatRelativeTime } = await import("./manager-CCRAV6A5.js");
1627
- const projectPath = process.cwd();
1628
- const newBranch = getCurrentBranch(projectPath);
1629
- let sessionInfo = null;
1630
- if (hasShivaDir2(projectPath)) {
1631
- const mappedSession = getSessionForBranch(projectPath, newBranch);
1632
- if (mappedSession) {
1633
- sessionInfo = {
1634
- source: "mapping",
1635
- lastAccessed: mappedSession.lastAccessed
1636
- };
1637
- }
1638
- }
1639
- if (!sessionInfo) {
1640
- const project = await findProject(projectPath);
1641
- if (project) {
1642
- const indexSession = findSessionByBranch(project, newBranch);
1643
- if (indexSession) {
1644
- sessionInfo = {
1645
- source: "index",
1646
- lastAccessed: indexSession.modified,
1647
- messageCount: indexSession.messageCount
1648
- };
1649
- }
1650
- }
1651
- }
1652
- if (sessionInfo) {
1653
- console.log(`
1654
- \u{1F4A1} SHIVA: Session f\xFCr Branch "${newBranch}" gefunden`);
1655
- if (sessionInfo.lastAccessed) {
1656
- const relTime = formatRelativeTime(sessionInfo.lastAccessed);
1657
- console.log(` Letzte Aktivit\xE4t: ${relTime}`);
1658
- }
1659
- if (sessionInfo.messageCount) {
1660
- console.log(` Messages: ${sessionInfo.messageCount}`);
1661
- }
1662
- console.log(` Fortsetzen mit: shiva resume
1663
- `);
1664
- }
1665
- });
1666
- hookCommand.command("scan-command").description("Scanne Bash-Befehle auf Package-Installationen (f\xFCr Hook)").argument("<tool-input>", "JSON Tool Input von Claude Code").action(async (toolInput) => {
1667
- const config = packageScanner.getConfig();
1668
- if (!config.enabled) {
1669
- console.log(JSON.stringify({ hookSpecificOutput: null }));
1670
- return;
1671
- }
1672
- try {
1673
- let command;
1674
- try {
1675
- const parsed = JSON.parse(toolInput);
1676
- command = parsed.command || toolInput;
1677
- } catch {
1678
- command = toolInput;
1679
- }
1680
- if (!packageScanner.isInstallCommand(command)) {
1681
- console.log(JSON.stringify({ hookSpecificOutput: null }));
1682
- return;
1683
- }
1684
- const results = await packageScanner.scanCommand(command);
1685
- if (!results || results.length === 0) {
1686
- console.log(JSON.stringify({ hookSpecificOutput: null }));
1687
- return;
1688
- }
1689
- const hookOutput = packageScanner.generateHookOutput(results);
1690
- console.log(JSON.stringify(hookOutput));
1691
- } catch (error) {
1692
- console.log(JSON.stringify({ hookSpecificOutput: null }));
1693
- }
1694
- });
1695
- hookCommand.command("push").description("Hooks in Cloud sichern").action(async () => {
1696
- const { api } = await import("./client-H3JXPT5B.js");
1697
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1698
- if (!isAuthenticated()) {
1699
- log.error("Nicht angemeldet");
1700
- log.info("Anmelden mit: shiva login");
1701
- return;
1702
- }
1703
- const settings = getClaudeSettings();
1704
- if (!settings.hooks) {
1705
- log.warn("Keine lokalen Hooks konfiguriert");
1706
- return;
1707
- }
1708
- try {
1709
- await api.updateHooks(settings.hooks);
1710
- log.success("Hooks in Cloud gesichert");
1711
- } catch (error) {
1712
- log.error(error instanceof Error ? error.message : "Fehler beim Sichern");
1713
- }
1714
- });
1715
- hookCommand.command("pull").description("Hooks aus Cloud laden").option("-f, --force", "Lokale Hooks \xFCberschreiben").action(async (options) => {
1716
- const { api } = await import("./client-H3JXPT5B.js");
1717
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1718
- if (!isAuthenticated()) {
1719
- log.error("Nicht angemeldet");
1720
- log.info("Anmelden mit: shiva login");
1721
- return;
1722
- }
1723
- const settings = getClaudeSettings();
1724
- if (settings.hooks && !options.force) {
1725
- log.warn("Lokale Hooks existieren bereits");
1726
- log.info("Mit --force \xFCberschreiben");
1727
- return;
1728
- }
1729
- try {
1730
- const result = await api.getHooks();
1731
- if (!result.hooks || Object.keys(result.hooks).length === 0) {
1732
- log.info("Keine Hooks in Cloud gefunden");
1733
- return;
1734
- }
1735
- settings.hooks = result.hooks;
1736
- saveClaudeSettings(settings);
1737
- log.success("Hooks aus Cloud geladen");
1738
- log.newline();
1739
- log.info("Aktive Hooks:");
1740
- for (const [event, hooks] of Object.entries(result.hooks)) {
1741
- log.tree.item(`${event}: ${Array.isArray(hooks) ? hooks.length : 1} Hook(s)`);
1742
- }
1743
- } catch (error) {
1744
- log.error(error instanceof Error ? error.message : "Fehler beim Laden");
1745
- }
1746
- });
1747
- hookCommand.command("sync").description("Hooks mit Cloud synchronisieren").action(async () => {
1748
- const { api } = await import("./client-H3JXPT5B.js");
1749
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1750
- if (!isAuthenticated()) {
1751
- log.error("Nicht angemeldet");
1752
- log.info("Anmelden mit: shiva login");
1753
- return;
1754
- }
1755
- const settings = getClaudeSettings();
1756
- try {
1757
- if (settings.hooks) {
1758
- await api.updateHooks(settings.hooks);
1759
- log.success("Lokale Hooks \u2192 Cloud");
1760
- }
1761
- const result = await api.getHooks();
1762
- if (result.hooks && Object.keys(result.hooks).length > 0) {
1763
- const merged = { ...result.hooks, ...settings.hooks };
1764
- settings.hooks = merged;
1765
- saveClaudeSettings(settings);
1766
- log.success("Cloud Hooks \u2192 Lokal (merged)");
1767
- }
1768
- log.newline();
1769
- log.success("Hooks synchronisiert");
1770
- } catch (error) {
1771
- log.error(error instanceof Error ? error.message : "Fehler beim Synchronisieren");
1772
- }
1773
- });
1774
- hookCommand.command("cloud-list").description("Cloud-Hooks auflisten").option("--event <type>", "Nach Event-Typ filtern").option("--json", "JSON Output").action(async (options) => {
1775
- const { api } = await import("./client-H3JXPT5B.js");
1776
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1777
- const { colors: colors2 } = await import("./logger-VVWOD6AA.js");
1778
- if (!isAuthenticated()) {
1779
- log.error("Nicht angemeldet");
1780
- log.info("Anmelden mit: shiva login");
1781
- return;
1782
- }
1783
- const ora = (await import("ora")).default;
1784
- const spinner = ora("Lade Cloud-Hooks...").start();
1785
- try {
1786
- let hooks;
1787
- if (options.event) {
1788
- hooks = await api.getHooksForEvent(options.event);
1789
- } else {
1790
- const result = await api.getHooks();
1791
- hooks = result.hooks || [];
1792
- }
1793
- spinner.stop();
1794
- if (options.json) {
1795
- console.log(JSON.stringify(hooks, null, 2));
1796
- return;
1797
- }
1798
- log.newline();
1799
- console.log(colors2.orange.bold("Cloud Hooks"));
1800
- console.log(colors2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1801
- log.newline();
1802
- if (!hooks || Array.isArray(hooks) && hooks.length === 0) {
1803
- log.dim("Keine Cloud-Hooks konfiguriert");
1804
- return;
1805
- }
1806
- const hooksArray = Array.isArray(hooks) ? hooks : Object.values(hooks).flat();
1807
- for (const hook of hooksArray) {
1808
- const enabledIcon = hook.enabled !== false ? colors2.green("\u25CF") : colors2.dim("\u25CB");
1809
- console.log(` ${enabledIcon} ${colors2.bold(hook.id || hook.event)}`);
1810
- console.log(` ${colors2.dim("Event:")} ${hook.event}`);
1811
- if (hook.matcher) {
1812
- console.log(` ${colors2.dim("Matcher:")} ${hook.matcher}`);
1813
- }
1814
- console.log(` ${colors2.dim("Type:")} ${hook.type}`);
1815
- console.log(` ${colors2.dim("Command:")} ${hook.command}`);
1816
- log.newline();
1817
- }
1818
- } catch (error) {
1819
- spinner.fail("Fehler beim Laden");
1820
- log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
1821
- }
1822
- });
1823
- hookCommand.command("cloud-create").description("Neuen Cloud-Hook erstellen").requiredOption("--event <event>", "Event-Typ (PreToolUse, PostToolUse, etc.)").requiredOption("--command <cmd>", "Auszuf\xFChrender Befehl").option("--matcher <pattern>", "Tool-Matcher Pattern").option("--type <type>", "Hook-Typ (command, script)", "command").option("--timeout <ms>", "Timeout in Millisekunden").action(async (options) => {
1824
- const { api } = await import("./client-H3JXPT5B.js");
1825
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1826
- if (!isAuthenticated()) {
1827
- log.error("Nicht angemeldet");
1828
- log.info("Anmelden mit: shiva login");
1829
- return;
1830
- }
1831
- const ora = (await import("ora")).default;
1832
- const spinner = ora("Erstelle Cloud-Hook...").start();
1833
- try {
1834
- const result = await api.createHook({
1835
- event: options.event,
1836
- command: options.command,
1837
- matcher: options.matcher,
1838
- type: options.type,
1839
- timeout: options.timeout ? parseInt(options.timeout) : void 0
1840
- });
1841
- if (result.success) {
1842
- spinner.succeed(`Hook erstellt: ${result.hookId}`);
1843
- } else {
1844
- spinner.fail(result.message || "Fehler beim Erstellen");
1845
- }
1846
- } catch (error) {
1847
- spinner.fail("Fehler beim Erstellen");
1848
- log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
1849
- }
1850
- });
1851
- hookCommand.command("cloud-test").description("Cloud-Hook testen").argument("<hook-id>", "Hook ID").action(async (hookId) => {
1852
- const { api } = await import("./client-H3JXPT5B.js");
1853
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1854
- const { colors: colors2 } = await import("./logger-VVWOD6AA.js");
1855
- if (!isAuthenticated()) {
1856
- log.error("Nicht angemeldet");
1857
- log.info("Anmelden mit: shiva login");
1858
- return;
1859
- }
1860
- const ora = (await import("ora")).default;
1861
- const spinner = ora("Teste Hook...").start();
1862
- try {
1863
- const result = await api.testHook(hookId);
1864
- spinner.stop();
1865
- log.newline();
1866
- if (result.success) {
1867
- log.success(`Hook erfolgreich (${result.duration}ms)`);
1868
- if (result.output) {
1869
- log.newline();
1870
- console.log(colors2.dim("Output:"));
1871
- console.log(result.output);
1872
- }
1873
- } else {
1874
- log.error(`Hook fehlgeschlagen (${result.duration}ms)`);
1875
- if (result.error) {
1876
- log.newline();
1877
- console.log(colors2.dim("Error:"));
1878
- console.log(colors2.red(result.error));
1879
- }
1880
- }
1881
- } catch (error) {
1882
- spinner.fail("Fehler beim Testen");
1883
- log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
1884
- }
1885
- });
1886
- hookCommand.command("cloud-delete").description("Cloud-Hook l\xF6schen").argument("<hook-id>", "Hook ID").option("-y, --yes", "Ohne Best\xE4tigung").action(async (hookId, options) => {
1887
- const { api } = await import("./client-H3JXPT5B.js");
1888
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1889
- if (!isAuthenticated()) {
1890
- log.error("Nicht angemeldet");
1891
- log.info("Anmelden mit: shiva login");
1892
- return;
1893
- }
1894
- if (!options.yes) {
1895
- const inquirer = (await import("inquirer")).default;
1896
- const { confirm } = await inquirer.prompt([{
1897
- type: "confirm",
1898
- name: "confirm",
1899
- message: `Hook ${hookId} l\xF6schen?`,
1900
- default: false
1901
- }]);
1902
- if (!confirm) {
1903
- log.dim("Abgebrochen");
1904
- return;
1905
- }
1906
- }
1907
- const ora = (await import("ora")).default;
1908
- const spinner = ora("L\xF6sche Hook...").start();
1909
- try {
1910
- const result = await api.deleteHook(hookId);
1911
- if (result.success) {
1912
- spinner.succeed("Hook gel\xF6scht");
1913
- } else {
1914
- spinner.fail(result.message || "Fehler beim L\xF6schen");
1915
- }
1916
- } catch (error) {
1917
- spinner.fail("Fehler beim L\xF6schen");
1918
- log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
1919
- }
1920
- });
1921
- hookCommand.command("cloud-toggle").description("Cloud-Hook aktivieren/deaktivieren").argument("<hook-id>", "Hook ID").option("--enable", "Hook aktivieren").option("--disable", "Hook deaktivieren").action(async (hookId, options) => {
1922
- const { api } = await import("./client-H3JXPT5B.js");
1923
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1924
- if (!isAuthenticated()) {
1925
- log.error("Nicht angemeldet");
1926
- log.info("Anmelden mit: shiva login");
1927
- return;
1928
- }
1929
- const enabled = options.enable ? true : options.disable ? false : void 0;
1930
- if (enabled === void 0) {
1931
- log.error("Bitte --enable oder --disable angeben");
1932
- return;
1933
- }
1934
- const ora = (await import("ora")).default;
1935
- const spinner = ora("Aktualisiere Hook...").start();
1936
- try {
1937
- const result = await api.updateHook(hookId, { enabled });
1938
- if (result.success) {
1939
- spinner.succeed(`Hook ${enabled ? "aktiviert" : "deaktiviert"}`);
1940
- } else {
1941
- spinner.fail(result.message || "Fehler beim Aktualisieren");
1942
- }
1943
- } catch (error) {
1944
- spinner.fail("Fehler beim Aktualisieren");
1945
- log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
1946
- }
1947
- });
1948
- hookCommand.command("scan-session").description("Session nach sensiblen Tokens scannen (f\xFCr Stop Hook)").option("--quiet", "Keine Ausgabe (f\xFCr Hook)").option("--redact", "Tokens in Session-History maskieren").option("--path <path>", "Session-Datei Pfad").action(async (options) => {
1949
- const { detectTokens, redactTokens, maskToken } = await import("./token-detection-K6KCIWAU.js");
1950
- const { api } = await import("./client-H3JXPT5B.js");
1951
- const { isAuthenticated } = await import("./config-FGMZONWV.js");
1952
- const { existsSync: existsSync2, readFileSync: readFileSync2, writeFileSync: writeFileSync2, readdirSync, statSync } = await import("fs");
1953
- const { join: join2 } = await import("path");
1954
- const { homedir: homedir2 } = await import("os");
1955
- const quiet = options.quiet;
1956
- try {
1957
- let autoRedact = options.redact || false;
1958
- let autoStore = false;
1959
- if (isAuthenticated()) {
1960
- try {
1961
- const settings = await api.getUserSettings();
1962
- if (settings.settings?.tokenProtection) {
1963
- autoRedact = settings.settings.tokenProtection.autoRedact ?? autoRedact;
1964
- autoStore = settings.settings.tokenProtection.autoStore ?? false;
1965
- }
1966
- } catch {
1967
- }
1968
- }
1969
- const claudeProjectsPath = join2(homedir2(), ".claude", "projects");
1970
- let sessionFiles = [];
1971
- if (options.path) {
1972
- sessionFiles = [options.path];
1973
- } else if (existsSync2(claudeProjectsPath)) {
1974
- const oneHourAgo = Date.now() - 60 * 60 * 1e3;
1975
- try {
1976
- const projects = readdirSync(claudeProjectsPath);
1977
- for (const project of projects) {
1978
- const projectPath = join2(claudeProjectsPath, project);
1979
- const stat = statSync(projectPath);
1980
- if (!stat.isDirectory()) continue;
1981
- const files = readdirSync(projectPath);
1982
- for (const file of files) {
1983
- if (!file.endsWith(".jsonl")) continue;
1984
- const filePath = join2(projectPath, file);
1985
- const fileStat = statSync(filePath);
1986
- if (fileStat.mtime.getTime() > oneHourAgo) {
1987
- sessionFiles.push(filePath);
1988
- }
1989
- }
1990
- }
1991
- } catch {
1992
- }
1993
- }
1994
- let totalTokensFound = 0;
1995
- let totalFilesScanned = 0;
1996
- let totalTokensRedacted = 0;
1997
- const detectedTokens = [];
1998
- for (const sessionFile of sessionFiles) {
1999
- if (!existsSync2(sessionFile)) continue;
2000
- try {
2001
- const content = readFileSync2(sessionFile, "utf-8");
2002
- const tokens = detectTokens(content);
2003
- if (tokens.length > 0) {
2004
- totalTokensFound += tokens.length;
2005
- for (const t of tokens) {
2006
- detectedTokens.push({
2007
- file: sessionFile,
2008
- token: t.token,
2009
- service: t.pattern.service
2010
- });
2011
- if (autoStore && isAuthenticated()) {
2012
- try {
2013
- const secretKey = `${t.pattern.name.toUpperCase()}_TOKEN`;
2014
- await api.addSecret({
2015
- key: secretKey,
2016
- value: t.token,
2017
- description: `Auto-detected ${t.pattern.description}`
2018
- });
2019
- } catch {
2020
- }
2021
- }
2022
- }
2023
- if (autoRedact) {
2024
- const redactedContent = redactTokens(content);
2025
- writeFileSync2(sessionFile, redactedContent);
2026
- totalTokensRedacted += tokens.length;
2027
- }
2028
- }
2029
- totalFilesScanned++;
2030
- } catch {
2031
- }
2032
- }
2033
- if (quiet) {
2034
- process.exit(0);
2035
- } else {
2036
- log.newline();
2037
- if (totalTokensFound === 0) {
2038
- log.success(`Keine sensiblen Tokens gefunden (${totalFilesScanned} Dateien gescannt)`);
2039
- } else {
2040
- console.log(colors.red.bold(`\u26A0\uFE0F ${totalTokensFound} sensible Tokens gefunden!`));
2041
- log.newline();
2042
- for (const t of detectedTokens) {
2043
- console.log(` ${colors.red("!")} ${t.service}: ${colors.dim(maskToken(t.token))}`);
2044
- }
2045
- log.newline();
2046
- if (totalTokensRedacted > 0) {
2047
- log.success(`${totalTokensRedacted} Tokens in Session-History maskiert`);
2048
- } else {
2049
- log.warn("Tokens NICHT maskiert. Verwende --redact oder aktiviere auto-redact in Einstellungen.");
2050
- }
2051
- log.newline();
2052
- log.info("Sichere Tokens mit: shiva secure-token");
2053
- }
2054
- log.newline();
2055
- }
2056
- } catch (error) {
2057
- if (quiet) {
2058
- process.exit(0);
2059
- } else {
2060
- log.error(error instanceof Error ? error.message : "Fehler beim Scannen");
2061
- }
2062
- }
2063
- });
2064
-
2065
- export {
2066
- packageScanner,
2067
- hookCommand
2068
- };