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