shiva-code 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{api-MT23KCO3.js → api-OEHQTBH7.js} +2 -2
- package/dist/{chunk-TI6Y3VT4.js → chunk-H5OFO4VS.js} +155 -27
- package/dist/{chunk-6RAACMKF.js → chunk-HIQO2DBA.js} +2 -2
- package/dist/{chunk-ZDLLPNCK.js → chunk-IVFCZLBX.js} +1 -1
- package/dist/{chunk-TQ6O4QB6.js → chunk-LBTCSQAX.js} +1 -1
- package/dist/index.js +987 -708
- package/dist/{manager-TQIASXKY.js → manager-ZPQWG7E6.js} +8 -2
- package/dist/{package-manager-NARG55B5.js → package-manager-NHNQATBH.js} +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
isGhAuthenticated,
|
|
40
40
|
isGhInstalled,
|
|
41
41
|
isGitRepo
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-IVFCZLBX.js";
|
|
43
43
|
import {
|
|
44
44
|
addProjectToPackage,
|
|
45
45
|
createPackage,
|
|
@@ -49,11 +49,12 @@ import {
|
|
|
49
49
|
getPackageLaunchConfig,
|
|
50
50
|
getPackageStats,
|
|
51
51
|
removeProjectFromPackage
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-LBTCSQAX.js";
|
|
53
53
|
import {
|
|
54
54
|
encodeProjectPath,
|
|
55
55
|
findProject,
|
|
56
56
|
findProjectForCurrentDir,
|
|
57
|
+
findProjectFromArray,
|
|
57
58
|
findSessionByBranch,
|
|
58
59
|
formatDate,
|
|
59
60
|
formatRelativeTime,
|
|
@@ -65,11 +66,16 @@ import {
|
|
|
65
66
|
invalidateSessionsCache,
|
|
66
67
|
isSessionActive,
|
|
67
68
|
isSessionCorrupted,
|
|
69
|
+
isSessionCorruptedQuick,
|
|
70
|
+
isValidProjectPath,
|
|
71
|
+
isValidSessionId,
|
|
72
|
+
maskSecret,
|
|
73
|
+
sanitizeForLog,
|
|
68
74
|
saveRecoveredContext
|
|
69
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-H5OFO4VS.js";
|
|
70
76
|
import {
|
|
71
77
|
cache
|
|
72
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-HIQO2DBA.js";
|
|
73
79
|
import {
|
|
74
80
|
__require
|
|
75
81
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -308,9 +314,9 @@ var ApiClient = class {
|
|
|
308
314
|
return this.request("/projects/stats");
|
|
309
315
|
}
|
|
310
316
|
// Find project by path
|
|
311
|
-
async findProjectByPath(
|
|
317
|
+
async findProjectByPath(path16) {
|
|
312
318
|
const projects = await this.getProjects();
|
|
313
|
-
return projects.find((p) => p.path ===
|
|
319
|
+
return projects.find((p) => p.path === path16) || null;
|
|
314
320
|
}
|
|
315
321
|
// ============================================
|
|
316
322
|
// Secrets Vault Endpoints
|
|
@@ -887,21 +893,21 @@ function generateBackupCodes(count = 10) {
|
|
|
887
893
|
return codes;
|
|
888
894
|
}
|
|
889
895
|
function generateDeviceFingerprint() {
|
|
890
|
-
const
|
|
896
|
+
const os8 = __require("os");
|
|
891
897
|
const components = [
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
898
|
+
os8.hostname(),
|
|
899
|
+
os8.platform(),
|
|
900
|
+
os8.arch(),
|
|
901
|
+
os8.cpus()[0]?.model || "unknown",
|
|
902
|
+
os8.userInfo().username
|
|
897
903
|
];
|
|
898
904
|
const fingerprint = crypto.createHash("sha256").update(components.join("|")).digest("hex").slice(0, 32);
|
|
899
905
|
return fingerprint;
|
|
900
906
|
}
|
|
901
907
|
function getDeviceName() {
|
|
902
|
-
const
|
|
903
|
-
const hostname =
|
|
904
|
-
const platform =
|
|
908
|
+
const os8 = __require("os");
|
|
909
|
+
const hostname = os8.hostname();
|
|
910
|
+
const platform = os8.platform();
|
|
905
911
|
const platformNames = {
|
|
906
912
|
darwin: "macOS",
|
|
907
913
|
linux: "Linux",
|
|
@@ -2399,9 +2405,9 @@ var Errors = {
|
|
|
2399
2405
|
"Bitte sp\xE4ter erneut versuchen"
|
|
2400
2406
|
),
|
|
2401
2407
|
// File/Path errors
|
|
2402
|
-
PATH_NOT_FOUND: (
|
|
2408
|
+
PATH_NOT_FOUND: (path16) => new ShivaError(
|
|
2403
2409
|
"PATH_404",
|
|
2404
|
-
|
|
2410
|
+
path16 ? `Pfad nicht gefunden: ${path16}` : "Pfad nicht gefunden",
|
|
2405
2411
|
"Pfad \xFCberpr\xFCfen und erneut versuchen"
|
|
2406
2412
|
),
|
|
2407
2413
|
FILE_NOT_FOUND: (file) => new ShivaError(
|
|
@@ -2409,9 +2415,9 @@ var Errors = {
|
|
|
2409
2415
|
file ? `Datei nicht gefunden: ${file}` : "Datei nicht gefunden",
|
|
2410
2416
|
void 0
|
|
2411
2417
|
),
|
|
2412
|
-
PERMISSION_DENIED: (
|
|
2418
|
+
PERMISSION_DENIED: (path16) => new ShivaError(
|
|
2413
2419
|
"PERMISSION_DENIED",
|
|
2414
|
-
|
|
2420
|
+
path16 ? `Keine Berechtigung: ${path16}` : "Keine Berechtigung",
|
|
2415
2421
|
"Berechtigungen pr\xFCfen oder mit sudo ausf\xFChren"
|
|
2416
2422
|
),
|
|
2417
2423
|
// Package errors
|
|
@@ -4399,15 +4405,17 @@ projectCommand.command("status").description("Projekt-Status anzeigen").action(a
|
|
|
4399
4405
|
// src/commands/session/start.ts
|
|
4400
4406
|
import { Command as Command10 } from "commander";
|
|
4401
4407
|
import * as fs from "fs";
|
|
4402
|
-
import * as
|
|
4408
|
+
import * as path4 from "path";
|
|
4403
4409
|
import ora6 from "ora";
|
|
4404
4410
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4405
4411
|
|
|
4406
4412
|
// src/services/infrastructure/terminal.ts
|
|
4407
|
-
import { spawn as spawn2,
|
|
4413
|
+
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
4414
|
+
import * as path3 from "path";
|
|
4415
|
+
import * as os3 from "os";
|
|
4408
4416
|
|
|
4409
4417
|
// src/services/infrastructure/docker.ts
|
|
4410
|
-
import {
|
|
4418
|
+
import { spawn, spawnSync } from "child_process";
|
|
4411
4419
|
import * as path2 from "path";
|
|
4412
4420
|
import * as os2 from "os";
|
|
4413
4421
|
var DockerService = class {
|
|
@@ -4420,33 +4428,46 @@ var DockerService = class {
|
|
|
4420
4428
|
}
|
|
4421
4429
|
/**
|
|
4422
4430
|
* Get Docker runtime information
|
|
4431
|
+
* SECURITY: Uses spawnSync with array args
|
|
4423
4432
|
*/
|
|
4424
4433
|
getDockerInfo() {
|
|
4425
4434
|
if (this.cachedInfo) {
|
|
4426
4435
|
return this.cachedInfo;
|
|
4427
4436
|
}
|
|
4428
4437
|
try {
|
|
4429
|
-
const
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
socket
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
+
const result = spawnSync("docker", ["--version"], {
|
|
4439
|
+
encoding: "utf8",
|
|
4440
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4441
|
+
});
|
|
4442
|
+
if (result.status === 0 && result.stdout) {
|
|
4443
|
+
const version = result.stdout.trim();
|
|
4444
|
+
const socket = this.findDockerSocket("docker");
|
|
4445
|
+
this.cachedInfo = {
|
|
4446
|
+
available: true,
|
|
4447
|
+
version: version.replace("Docker version ", "").split(",")[0],
|
|
4448
|
+
runtime: "docker",
|
|
4449
|
+
socket
|
|
4450
|
+
};
|
|
4451
|
+
return this.cachedInfo;
|
|
4452
|
+
}
|
|
4438
4453
|
} catch {
|
|
4439
4454
|
}
|
|
4440
4455
|
try {
|
|
4441
|
-
const
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
socket
|
|
4448
|
-
|
|
4449
|
-
|
|
4456
|
+
const result = spawnSync("podman", ["--version"], {
|
|
4457
|
+
encoding: "utf8",
|
|
4458
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4459
|
+
});
|
|
4460
|
+
if (result.status === 0 && result.stdout) {
|
|
4461
|
+
const version = result.stdout.trim();
|
|
4462
|
+
const socket = this.findDockerSocket("podman");
|
|
4463
|
+
this.cachedInfo = {
|
|
4464
|
+
available: true,
|
|
4465
|
+
version: version.replace("podman version ", ""),
|
|
4466
|
+
runtime: "podman",
|
|
4467
|
+
socket
|
|
4468
|
+
};
|
|
4469
|
+
return this.cachedInfo;
|
|
4470
|
+
}
|
|
4450
4471
|
} catch {
|
|
4451
4472
|
}
|
|
4452
4473
|
this.cachedInfo = {
|
|
@@ -4490,8 +4511,12 @@ var DockerService = class {
|
|
|
4490
4511
|
// ============================================
|
|
4491
4512
|
/**
|
|
4492
4513
|
* Pull a Docker image
|
|
4514
|
+
* SECURITY: Validates image name before use
|
|
4493
4515
|
*/
|
|
4494
4516
|
async pullImage(image) {
|
|
4517
|
+
if (!this.isValidImage(image)) {
|
|
4518
|
+
throw new Error(`Invalid image name: ${sanitizeForLog(image)}`);
|
|
4519
|
+
}
|
|
4495
4520
|
const cmd = this.getCommand();
|
|
4496
4521
|
return new Promise((resolve13, reject) => {
|
|
4497
4522
|
const pull = spawn(cmd, ["pull", image], { stdio: "inherit" });
|
|
@@ -4499,7 +4524,7 @@ var DockerService = class {
|
|
|
4499
4524
|
if (code === 0) {
|
|
4500
4525
|
resolve13();
|
|
4501
4526
|
} else {
|
|
4502
|
-
reject(new Error(`Failed to pull image: ${image}`));
|
|
4527
|
+
reject(new Error(`Failed to pull image: ${sanitizeForLog(image)}`));
|
|
4503
4528
|
}
|
|
4504
4529
|
});
|
|
4505
4530
|
pull.on("error", (err) => {
|
|
@@ -4507,14 +4532,32 @@ var DockerService = class {
|
|
|
4507
4532
|
});
|
|
4508
4533
|
});
|
|
4509
4534
|
}
|
|
4535
|
+
/**
|
|
4536
|
+
* Validate an image name
|
|
4537
|
+
*/
|
|
4538
|
+
isValidImage(image) {
|
|
4539
|
+
if (!image || typeof image !== "string") {
|
|
4540
|
+
return false;
|
|
4541
|
+
}
|
|
4542
|
+
if (/[;&|`$(){}[\]<>\\!#*?"'\n\r\t]/.test(image)) {
|
|
4543
|
+
return false;
|
|
4544
|
+
}
|
|
4545
|
+
return image.length > 0 && image.length <= 256;
|
|
4546
|
+
}
|
|
4510
4547
|
/**
|
|
4511
4548
|
* Check if an image exists locally
|
|
4549
|
+
* SECURITY: Uses spawnSync with array args
|
|
4512
4550
|
*/
|
|
4513
4551
|
imageExists(image) {
|
|
4552
|
+
if (!this.isValidImage(image)) {
|
|
4553
|
+
return false;
|
|
4554
|
+
}
|
|
4514
4555
|
const cmd = this.getCommand();
|
|
4515
4556
|
try {
|
|
4516
|
-
|
|
4517
|
-
|
|
4557
|
+
const result = spawnSync(cmd, ["image", "inspect", image], {
|
|
4558
|
+
stdio: "pipe"
|
|
4559
|
+
});
|
|
4560
|
+
return result.status === 0;
|
|
4518
4561
|
} catch {
|
|
4519
4562
|
return false;
|
|
4520
4563
|
}
|
|
@@ -4528,14 +4571,23 @@ var DockerService = class {
|
|
|
4528
4571
|
}
|
|
4529
4572
|
/**
|
|
4530
4573
|
* List local images
|
|
4574
|
+
* SECURITY: Uses spawnSync with array args
|
|
4531
4575
|
*/
|
|
4532
4576
|
listImages() {
|
|
4533
4577
|
const cmd = this.getCommand();
|
|
4534
4578
|
try {
|
|
4535
|
-
const
|
|
4536
|
-
|
|
4579
|
+
const result = spawnSync(cmd, [
|
|
4580
|
+
"images",
|
|
4581
|
+
"--format",
|
|
4582
|
+
"{{.Repository}} {{.Tag}} {{.ID}} {{.Size}}"
|
|
4583
|
+
], {
|
|
4584
|
+
encoding: "utf8",
|
|
4585
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4537
4586
|
});
|
|
4538
|
-
|
|
4587
|
+
if (result.status !== 0 || !result.stdout) {
|
|
4588
|
+
return [];
|
|
4589
|
+
}
|
|
4590
|
+
return result.stdout.trim().split("\n").filter((line) => line.includes("shiva") || line.includes("claude")).map((line) => {
|
|
4539
4591
|
const [repository, tag, id, size] = line.split(" ");
|
|
4540
4592
|
return { repository, tag, id, size };
|
|
4541
4593
|
});
|
|
@@ -4546,24 +4598,55 @@ var DockerService = class {
|
|
|
4546
4598
|
// ============================================
|
|
4547
4599
|
// Container Lifecycle
|
|
4548
4600
|
// ============================================
|
|
4601
|
+
/**
|
|
4602
|
+
* Validate container ID format
|
|
4603
|
+
*/
|
|
4604
|
+
isValidContainerId(id) {
|
|
4605
|
+
return /^[a-f0-9]{12,64}$/i.test(id);
|
|
4606
|
+
}
|
|
4607
|
+
/**
|
|
4608
|
+
* Validate container name
|
|
4609
|
+
*/
|
|
4610
|
+
isValidContainerNameInternal(name) {
|
|
4611
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name) && name.length <= 128;
|
|
4612
|
+
}
|
|
4549
4613
|
/**
|
|
4550
4614
|
* Create a new container
|
|
4615
|
+
* SECURITY: Uses spawnSync with array args, validates all inputs
|
|
4551
4616
|
*/
|
|
4552
4617
|
async createContainer(config2) {
|
|
4553
4618
|
const cmd = this.getCommand();
|
|
4554
4619
|
const args = ["create"];
|
|
4555
4620
|
if (config2.name) {
|
|
4621
|
+
if (!this.isValidContainerNameInternal(config2.name)) {
|
|
4622
|
+
throw new Error(`Invalid container name: ${sanitizeForLog(config2.name)}`);
|
|
4623
|
+
}
|
|
4556
4624
|
args.push("--name", config2.name);
|
|
4557
4625
|
}
|
|
4626
|
+
if (!isValidProjectPath(config2.workdir)) {
|
|
4627
|
+
throw new Error(`Invalid workdir: ${sanitizeForLog(config2.workdir)}`);
|
|
4628
|
+
}
|
|
4558
4629
|
args.push("--workdir", config2.workdir);
|
|
4559
4630
|
for (const mount of config2.mounts) {
|
|
4631
|
+
if (!isValidProjectPath(mount.host)) {
|
|
4632
|
+
log.warn(`Skipping invalid mount path: ${sanitizeForLog(mount.host)}`);
|
|
4633
|
+
continue;
|
|
4634
|
+
}
|
|
4560
4635
|
args.push("-v", `${mount.host}:${mount.container}:${mount.mode}`);
|
|
4561
4636
|
}
|
|
4562
4637
|
for (const [key, value] of Object.entries(config2.env)) {
|
|
4638
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
|
|
4639
|
+
log.warn(`Skipping invalid env var name: ${sanitizeForLog(key)}`);
|
|
4640
|
+
continue;
|
|
4641
|
+
}
|
|
4563
4642
|
args.push("-e", `${key}=${value}`);
|
|
4564
4643
|
}
|
|
4565
4644
|
if (config2.ports) {
|
|
4566
4645
|
for (const [host, container] of Object.entries(config2.ports)) {
|
|
4646
|
+
if (!/^\d+$/.test(String(host)) || !/^\d+$/.test(String(container))) {
|
|
4647
|
+
log.warn(`Skipping invalid port mapping: ${host}:${container}`);
|
|
4648
|
+
continue;
|
|
4649
|
+
}
|
|
4567
4650
|
args.push("-p", `${host}:${container}`);
|
|
4568
4651
|
}
|
|
4569
4652
|
}
|
|
@@ -4573,55 +4656,103 @@ var DockerService = class {
|
|
|
4573
4656
|
if (config2.autoRemove) {
|
|
4574
4657
|
args.push("--rm");
|
|
4575
4658
|
}
|
|
4659
|
+
if (!this.isValidImage(config2.image)) {
|
|
4660
|
+
throw new Error(`Invalid image: ${sanitizeForLog(config2.image)}`);
|
|
4661
|
+
}
|
|
4576
4662
|
args.push(config2.image);
|
|
4577
4663
|
try {
|
|
4578
|
-
const
|
|
4579
|
-
|
|
4664
|
+
const result = spawnSync(cmd, args, {
|
|
4665
|
+
encoding: "utf8",
|
|
4666
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4667
|
+
});
|
|
4668
|
+
if (result.status !== 0) {
|
|
4669
|
+
throw new Error(`Failed to create container: ${result.stderr || "Unknown error"}`);
|
|
4670
|
+
}
|
|
4671
|
+
return result.stdout.trim();
|
|
4580
4672
|
} catch (error) {
|
|
4581
4673
|
throw new Error(`Failed to create container: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4582
4674
|
}
|
|
4583
4675
|
}
|
|
4584
4676
|
/**
|
|
4585
4677
|
* Start a container
|
|
4678
|
+
* SECURITY: Validates container ID format
|
|
4586
4679
|
*/
|
|
4587
4680
|
async startContainer(containerId) {
|
|
4681
|
+
if (!this.isValidContainerId(containerId) && !this.isValidContainerNameInternal(containerId)) {
|
|
4682
|
+
throw new Error(`Invalid container ID: ${sanitizeForLog(containerId)}`);
|
|
4683
|
+
}
|
|
4588
4684
|
const cmd = this.getCommand();
|
|
4589
4685
|
try {
|
|
4590
|
-
|
|
4686
|
+
const result = spawnSync(cmd, ["start", containerId], {
|
|
4687
|
+
stdio: "pipe"
|
|
4688
|
+
});
|
|
4689
|
+
if (result.status !== 0) {
|
|
4690
|
+
throw new Error(`Failed to start container`);
|
|
4691
|
+
}
|
|
4591
4692
|
} catch (error) {
|
|
4592
4693
|
throw new Error(`Failed to start container: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4593
4694
|
}
|
|
4594
4695
|
}
|
|
4595
4696
|
/**
|
|
4596
4697
|
* Stop a container
|
|
4698
|
+
* SECURITY: Validates container ID format
|
|
4597
4699
|
*/
|
|
4598
4700
|
async stopContainer(containerId) {
|
|
4701
|
+
if (!this.isValidContainerId(containerId) && !this.isValidContainerNameInternal(containerId)) {
|
|
4702
|
+
throw new Error(`Invalid container ID: ${sanitizeForLog(containerId)}`);
|
|
4703
|
+
}
|
|
4599
4704
|
const cmd = this.getCommand();
|
|
4600
4705
|
try {
|
|
4601
|
-
|
|
4706
|
+
const result = spawnSync(cmd, ["stop", containerId], {
|
|
4707
|
+
stdio: "pipe"
|
|
4708
|
+
});
|
|
4709
|
+
if (result.status !== 0) {
|
|
4710
|
+
throw new Error(`Failed to stop container`);
|
|
4711
|
+
}
|
|
4602
4712
|
} catch (error) {
|
|
4603
4713
|
throw new Error(`Failed to stop container: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4604
4714
|
}
|
|
4605
4715
|
}
|
|
4606
4716
|
/**
|
|
4607
4717
|
* Remove a container
|
|
4718
|
+
* SECURITY: Validates container ID format
|
|
4608
4719
|
*/
|
|
4609
4720
|
async removeContainer(containerId) {
|
|
4721
|
+
if (!this.isValidContainerId(containerId) && !this.isValidContainerNameInternal(containerId)) {
|
|
4722
|
+
throw new Error(`Invalid container ID: ${sanitizeForLog(containerId)}`);
|
|
4723
|
+
}
|
|
4610
4724
|
const cmd = this.getCommand();
|
|
4611
4725
|
try {
|
|
4612
|
-
|
|
4726
|
+
const result = spawnSync(cmd, ["rm", "-f", containerId], {
|
|
4727
|
+
stdio: "pipe"
|
|
4728
|
+
});
|
|
4729
|
+
if (result.status !== 0) {
|
|
4730
|
+
throw new Error(`Failed to remove container`);
|
|
4731
|
+
}
|
|
4613
4732
|
} catch (error) {
|
|
4614
4733
|
throw new Error(`Failed to remove container: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4615
4734
|
}
|
|
4616
4735
|
}
|
|
4617
4736
|
/**
|
|
4618
4737
|
* Get container logs
|
|
4738
|
+
* SECURITY: Validates container ID, uses array args
|
|
4619
4739
|
*/
|
|
4620
4740
|
getContainerLogs(containerId, tail) {
|
|
4741
|
+
if (!this.isValidContainerId(containerId) && !this.isValidContainerNameInternal(containerId)) {
|
|
4742
|
+
return "";
|
|
4743
|
+
}
|
|
4621
4744
|
const cmd = this.getCommand();
|
|
4622
|
-
const
|
|
4745
|
+
const args = ["logs"];
|
|
4746
|
+
if (tail && tail > 0) {
|
|
4747
|
+
args.push("--tail", String(tail));
|
|
4748
|
+
}
|
|
4749
|
+
args.push(containerId);
|
|
4623
4750
|
try {
|
|
4624
|
-
|
|
4751
|
+
const result = spawnSync(cmd, args, {
|
|
4752
|
+
encoding: "utf8",
|
|
4753
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4754
|
+
});
|
|
4755
|
+
return result.stdout || "";
|
|
4625
4756
|
} catch {
|
|
4626
4757
|
return "";
|
|
4627
4758
|
}
|
|
@@ -4631,16 +4762,27 @@ var DockerService = class {
|
|
|
4631
4762
|
// ============================================
|
|
4632
4763
|
/**
|
|
4633
4764
|
* Run Claude Code in a container
|
|
4765
|
+
* SECURITY: Validates all inputs, uses spawn with array args
|
|
4634
4766
|
*/
|
|
4635
4767
|
async runClaudeInContainer(launch, dockerSettings) {
|
|
4768
|
+
if (!isValidProjectPath(launch.projectPath)) {
|
|
4769
|
+
throw new Error(`Invalid project path: ${sanitizeForLog(launch.projectPath)}`);
|
|
4770
|
+
}
|
|
4771
|
+
if (launch.sessionId && !isValidSessionId(launch.sessionId)) {
|
|
4772
|
+
throw new Error(`Invalid session ID: ${sanitizeForLog(launch.sessionId)}`);
|
|
4773
|
+
}
|
|
4636
4774
|
const cmd = this.getCommand();
|
|
4637
4775
|
const settings = dockerSettings || settingsSync.getDockerSettings();
|
|
4638
4776
|
const image = settings.defaultImage || this.getDefaultImage();
|
|
4777
|
+
if (!this.isValidImage(image)) {
|
|
4778
|
+
throw new Error(`Invalid image: ${sanitizeForLog(image)}`);
|
|
4779
|
+
}
|
|
4639
4780
|
if (!this.imageExists(image)) {
|
|
4640
|
-
log.info(`Pulling Docker image: ${image}...`);
|
|
4781
|
+
log.info(`Pulling Docker image: ${sanitizeForLog(image)}...`);
|
|
4641
4782
|
await this.pullImage(image);
|
|
4642
4783
|
}
|
|
4643
|
-
const
|
|
4784
|
+
const safeName = launch.projectName.replace(/[^a-zA-Z0-9-]/g, "-").substring(0, 50);
|
|
4785
|
+
const containerName = `shiva-${safeName}-${Date.now()}`;
|
|
4644
4786
|
const claudeArgs = [];
|
|
4645
4787
|
if (launch.sessionId && !launch.newSession) {
|
|
4646
4788
|
claudeArgs.push("--resume", launch.sessionId);
|
|
@@ -4669,13 +4811,19 @@ var DockerService = class {
|
|
|
4669
4811
|
`${path2.join(os2.homedir(), ".config", "shiva-code")}:/root/.config/shiva-code:rw`
|
|
4670
4812
|
];
|
|
4671
4813
|
for (const [host, container] of Object.entries(settings.volumeMounts || {})) {
|
|
4672
|
-
|
|
4814
|
+
if (isValidProjectPath(host)) {
|
|
4815
|
+
args.push("-v", `${host}:${container}:rw`);
|
|
4816
|
+
}
|
|
4673
4817
|
}
|
|
4674
4818
|
for (const [key, value] of Object.entries(settings.environment || {})) {
|
|
4675
|
-
|
|
4819
|
+
if (/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
|
|
4820
|
+
args.push("-e", `${key}=${value}`);
|
|
4821
|
+
}
|
|
4676
4822
|
}
|
|
4677
4823
|
for (const [key, value] of Object.entries(secrets)) {
|
|
4678
|
-
|
|
4824
|
+
if (/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
|
|
4825
|
+
args.push("-e", `${key}=${value}`);
|
|
4826
|
+
}
|
|
4679
4827
|
}
|
|
4680
4828
|
args.push(image);
|
|
4681
4829
|
args.push("claude", ...claudeArgs);
|
|
@@ -4693,15 +4841,26 @@ var DockerService = class {
|
|
|
4693
4841
|
}
|
|
4694
4842
|
/**
|
|
4695
4843
|
* List all SHIVA containers
|
|
4844
|
+
* SECURITY: Uses spawnSync with array args
|
|
4696
4845
|
*/
|
|
4697
4846
|
listShivaContainers() {
|
|
4698
4847
|
const cmd = this.getCommand();
|
|
4699
4848
|
try {
|
|
4700
|
-
const
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4849
|
+
const result = spawnSync(cmd, [
|
|
4850
|
+
"ps",
|
|
4851
|
+
"-a",
|
|
4852
|
+
"--filter",
|
|
4853
|
+
"name=shiva-",
|
|
4854
|
+
"--format",
|
|
4855
|
+
"{{.ID}} {{.Names}} {{.Image}} {{.Status}} {{.CreatedAt}} {{.Ports}}"
|
|
4856
|
+
], {
|
|
4857
|
+
encoding: "utf8",
|
|
4858
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4859
|
+
});
|
|
4860
|
+
if (result.status !== 0 || !result.stdout) {
|
|
4861
|
+
return [];
|
|
4862
|
+
}
|
|
4863
|
+
return result.stdout.trim().split("\n").filter((line) => line.length > 0).map((line) => {
|
|
4705
4864
|
const [id, name, image, statusRaw, createdAt, ports] = line.split(" ");
|
|
4706
4865
|
let status = "stopped";
|
|
4707
4866
|
if (statusRaw.toLowerCase().includes("up")) {
|
|
@@ -4797,12 +4956,18 @@ var DockerService = class {
|
|
|
4797
4956
|
* Set default Docker image
|
|
4798
4957
|
*/
|
|
4799
4958
|
setDefaultImage(image) {
|
|
4959
|
+
if (!this.isValidImage(image)) {
|
|
4960
|
+
throw new Error(`Invalid image name: ${sanitizeForLog(image)}`);
|
|
4961
|
+
}
|
|
4800
4962
|
settingsSync.setDockerImage(image);
|
|
4801
4963
|
}
|
|
4802
4964
|
/**
|
|
4803
4965
|
* Add a volume mount
|
|
4804
4966
|
*/
|
|
4805
4967
|
addVolumeMount(hostPath, containerPath) {
|
|
4968
|
+
if (!isValidProjectPath(hostPath)) {
|
|
4969
|
+
throw new Error(`Invalid host path: ${sanitizeForLog(hostPath)}`);
|
|
4970
|
+
}
|
|
4806
4971
|
const settings = settingsSync.getDockerSettings();
|
|
4807
4972
|
settingsSync.setDockerSettings({
|
|
4808
4973
|
volumeMounts: {
|
|
@@ -4825,6 +4990,9 @@ var DockerService = class {
|
|
|
4825
4990
|
* Add an environment variable
|
|
4826
4991
|
*/
|
|
4827
4992
|
addEnvVar(key, value) {
|
|
4993
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
|
|
4994
|
+
throw new Error(`Invalid environment variable name: ${sanitizeForLog(key)}`);
|
|
4995
|
+
}
|
|
4828
4996
|
const settings = settingsSync.getDockerSettings();
|
|
4829
4997
|
settingsSync.setDockerSettings({
|
|
4830
4998
|
environment: {
|
|
@@ -4909,15 +5077,26 @@ var DockerService = class {
|
|
|
4909
5077
|
}
|
|
4910
5078
|
/**
|
|
4911
5079
|
* Run Claude in container with project-specific settings
|
|
5080
|
+
* SECURITY: Validates all inputs, uses spawn with array args
|
|
4912
5081
|
*/
|
|
4913
5082
|
async runClaudeInContainerWithProjectConfig(launch, projectPath) {
|
|
5083
|
+
if (!isValidProjectPath(launch.projectPath)) {
|
|
5084
|
+
throw new Error(`Invalid project path: ${sanitizeForLog(launch.projectPath)}`);
|
|
5085
|
+
}
|
|
5086
|
+
if (launch.sessionId && !isValidSessionId(launch.sessionId)) {
|
|
5087
|
+
throw new Error(`Invalid session ID: ${sanitizeForLog(launch.sessionId)}`);
|
|
5088
|
+
}
|
|
4914
5089
|
const effectiveConfig = this.getEffectiveDockerConfig(projectPath);
|
|
4915
5090
|
const cmd = this.getCommand();
|
|
5091
|
+
if (!this.isValidImage(effectiveConfig.image)) {
|
|
5092
|
+
throw new Error(`Invalid image: ${sanitizeForLog(effectiveConfig.image)}`);
|
|
5093
|
+
}
|
|
4916
5094
|
if (!this.imageExists(effectiveConfig.image)) {
|
|
4917
|
-
log.info(`Pulling Docker image: ${effectiveConfig.image}...`);
|
|
5095
|
+
log.info(`Pulling Docker image: ${sanitizeForLog(effectiveConfig.image)}...`);
|
|
4918
5096
|
await this.pullImage(effectiveConfig.image);
|
|
4919
5097
|
}
|
|
4920
|
-
const
|
|
5098
|
+
const safeName = launch.projectName.replace(/[^a-zA-Z0-9-]/g, "-").substring(0, 50);
|
|
5099
|
+
const containerName = `shiva-${safeName}-${Date.now()}`;
|
|
4921
5100
|
const claudeArgs = [];
|
|
4922
5101
|
if (launch.sessionId && !launch.newSession) {
|
|
4923
5102
|
claudeArgs.push("--resume", launch.sessionId);
|
|
@@ -4941,26 +5120,34 @@ var DockerService = class {
|
|
|
4941
5120
|
} else if (effectiveConfig.network === "host") {
|
|
4942
5121
|
args.push("--network", "host");
|
|
4943
5122
|
}
|
|
4944
|
-
if (effectiveConfig.resources.cpuLimit) {
|
|
5123
|
+
if (effectiveConfig.resources.cpuLimit && /^[\d.]+$/.test(effectiveConfig.resources.cpuLimit)) {
|
|
4945
5124
|
args.push("--cpus", effectiveConfig.resources.cpuLimit);
|
|
4946
5125
|
}
|
|
4947
|
-
if (effectiveConfig.resources.memoryLimit) {
|
|
5126
|
+
if (effectiveConfig.resources.memoryLimit && /^[\d]+[kmgKMG]?$/.test(effectiveConfig.resources.memoryLimit)) {
|
|
4948
5127
|
args.push("--memory", effectiveConfig.resources.memoryLimit);
|
|
4949
5128
|
}
|
|
4950
5129
|
for (const opt of effectiveConfig.securityOpts) {
|
|
4951
|
-
|
|
5130
|
+
if (/^[a-z:=_-]+$/i.test(opt)) {
|
|
5131
|
+
args.push("--security-opt", opt);
|
|
5132
|
+
}
|
|
4952
5133
|
}
|
|
4953
5134
|
args.push("-v", `${launch.projectPath}:/workspace:rw`);
|
|
4954
5135
|
args.push("-v", `${path2.join(os2.homedir(), ".claude")}:/root/.claude:rw`);
|
|
4955
5136
|
args.push("-v", `${path2.join(os2.homedir(), ".config", "shiva-code")}:/root/.config/shiva-code:rw`);
|
|
4956
5137
|
for (const mount of effectiveConfig.mounts) {
|
|
4957
|
-
|
|
5138
|
+
if (isValidProjectPath(mount.host)) {
|
|
5139
|
+
args.push("-v", `${mount.host}:${mount.container}:${mount.mode}`);
|
|
5140
|
+
}
|
|
4958
5141
|
}
|
|
4959
5142
|
for (const [key, value] of Object.entries(effectiveConfig.environment)) {
|
|
4960
|
-
|
|
5143
|
+
if (/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
|
|
5144
|
+
args.push("-e", `${key}=${value}`);
|
|
5145
|
+
}
|
|
4961
5146
|
}
|
|
4962
5147
|
for (const [key, value] of Object.entries(secrets)) {
|
|
4963
|
-
|
|
5148
|
+
if (/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
|
|
5149
|
+
args.push("-e", `${key}=${value}`);
|
|
5150
|
+
}
|
|
4964
5151
|
}
|
|
4965
5152
|
args.push(effectiveConfig.image);
|
|
4966
5153
|
args.push("claude", ...claudeArgs);
|
|
@@ -4975,8 +5162,11 @@ var dockerService = new DockerService();
|
|
|
4975
5162
|
// src/services/infrastructure/terminal.ts
|
|
4976
5163
|
function commandExists(command) {
|
|
4977
5164
|
try {
|
|
4978
|
-
|
|
4979
|
-
|
|
5165
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(command)) {
|
|
5166
|
+
return false;
|
|
5167
|
+
}
|
|
5168
|
+
const result = spawnSync2("which", [command], { stdio: "pipe" });
|
|
5169
|
+
return result.status === 0;
|
|
4980
5170
|
} catch {
|
|
4981
5171
|
return false;
|
|
4982
5172
|
}
|
|
@@ -5014,90 +5204,93 @@ function isValidTerminalType(value) {
|
|
|
5014
5204
|
function isInsideTmux() {
|
|
5015
5205
|
return !!process.env.TMUX;
|
|
5016
5206
|
}
|
|
5017
|
-
function
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5207
|
+
function validateProject(project) {
|
|
5208
|
+
if (!isValidProjectPath(project.projectPath)) {
|
|
5209
|
+
return {
|
|
5210
|
+
valid: false,
|
|
5211
|
+
error: `Invalid project path: ${sanitizeForLog(project.projectPath)}`
|
|
5212
|
+
};
|
|
5023
5213
|
}
|
|
5024
|
-
|
|
5214
|
+
if (project.sessionId && !isValidSessionId(project.sessionId)) {
|
|
5215
|
+
return {
|
|
5216
|
+
valid: false,
|
|
5217
|
+
error: `Invalid session ID format: ${sanitizeForLog(project.sessionId)}`
|
|
5218
|
+
};
|
|
5219
|
+
}
|
|
5220
|
+
return { valid: true };
|
|
5221
|
+
}
|
|
5222
|
+
function buildClaudeArgs(project) {
|
|
5223
|
+
const args = [];
|
|
5224
|
+
if (!project.newSession && project.sessionId) {
|
|
5225
|
+
args.push("--resume", project.sessionId);
|
|
5226
|
+
}
|
|
5227
|
+
const launchArgs = getClaudeLaunchArgs();
|
|
5228
|
+
args.push(...launchArgs);
|
|
5229
|
+
return args;
|
|
5230
|
+
}
|
|
5231
|
+
function sanitizeProjectName(name) {
|
|
5232
|
+
return name.replace(/[^a-zA-Z0-9_.-]/g, "-").substring(0, 50);
|
|
5025
5233
|
}
|
|
5026
5234
|
async function spawnInTmux(projects, sessionName) {
|
|
5027
5235
|
const tmuxSession = sessionName || `shiva-${Date.now()}`;
|
|
5028
5236
|
const insideTmux = isInsideTmux();
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5237
|
+
for (let i = 0; i < projects.length; i++) {
|
|
5238
|
+
const project = projects[i];
|
|
5239
|
+
const validation = validateProject(project);
|
|
5240
|
+
if (!validation.valid) {
|
|
5241
|
+
console.error(`Skipping invalid project: ${validation.error}`);
|
|
5242
|
+
continue;
|
|
5243
|
+
}
|
|
5244
|
+
const claudeArgs = buildClaudeArgs(project);
|
|
5245
|
+
const safeName = sanitizeProjectName(project.projectName);
|
|
5246
|
+
if (insideTmux) {
|
|
5247
|
+
spawnSync2("tmux", [
|
|
5248
|
+
"new-window",
|
|
5249
|
+
"-n",
|
|
5250
|
+
safeName,
|
|
5251
|
+
"-c",
|
|
5252
|
+
project.projectPath
|
|
5253
|
+
], { stdio: "inherit" });
|
|
5254
|
+
const claudeCmd = ["claude", ...claudeArgs].join(" ");
|
|
5255
|
+
spawnSync2("tmux", [
|
|
5256
|
+
"send-keys",
|
|
5257
|
+
claudeCmd,
|
|
5258
|
+
"Enter"
|
|
5259
|
+
], { stdio: "inherit" });
|
|
5260
|
+
} else {
|
|
5033
5261
|
if (i === 0) {
|
|
5034
5262
|
spawnSync2("tmux", [
|
|
5035
|
-
"new-
|
|
5263
|
+
"new-session",
|
|
5264
|
+
"-d",
|
|
5265
|
+
"-s",
|
|
5266
|
+
tmuxSession,
|
|
5036
5267
|
"-n",
|
|
5037
|
-
|
|
5268
|
+
safeName,
|
|
5038
5269
|
"-c",
|
|
5039
5270
|
project.projectPath
|
|
5040
5271
|
], { stdio: "inherit" });
|
|
5041
|
-
spawnSync2("tmux", [
|
|
5042
|
-
"send-keys",
|
|
5043
|
-
command,
|
|
5044
|
-
"Enter"
|
|
5045
|
-
], { stdio: "inherit" });
|
|
5046
5272
|
} else {
|
|
5047
5273
|
spawnSync2("tmux", [
|
|
5048
5274
|
"new-window",
|
|
5275
|
+
"-t",
|
|
5276
|
+
tmuxSession,
|
|
5049
5277
|
"-n",
|
|
5050
|
-
|
|
5278
|
+
safeName,
|
|
5051
5279
|
"-c",
|
|
5052
5280
|
project.projectPath
|
|
5053
5281
|
], { stdio: "inherit" });
|
|
5054
|
-
spawnSync2("tmux", [
|
|
5055
|
-
"send-keys",
|
|
5056
|
-
command,
|
|
5057
|
-
"Enter"
|
|
5058
|
-
], { stdio: "inherit" });
|
|
5059
5282
|
}
|
|
5060
|
-
|
|
5061
|
-
} else {
|
|
5062
|
-
const firstProject = projects[0];
|
|
5063
|
-
const firstCommand = buildClaudeCommand(firstProject);
|
|
5064
|
-
spawnSync2("tmux", [
|
|
5065
|
-
"new-session",
|
|
5066
|
-
"-d",
|
|
5067
|
-
"-s",
|
|
5068
|
-
tmuxSession,
|
|
5069
|
-
"-n",
|
|
5070
|
-
firstProject.projectName,
|
|
5071
|
-
"-c",
|
|
5072
|
-
firstProject.projectPath
|
|
5073
|
-
], { stdio: "inherit" });
|
|
5074
|
-
spawnSync2("tmux", [
|
|
5075
|
-
"send-keys",
|
|
5076
|
-
"-t",
|
|
5077
|
-
`${tmuxSession}:${firstProject.projectName}`,
|
|
5078
|
-
firstCommand,
|
|
5079
|
-
"Enter"
|
|
5080
|
-
], { stdio: "inherit" });
|
|
5081
|
-
for (let i = 1; i < projects.length; i++) {
|
|
5082
|
-
const project = projects[i];
|
|
5083
|
-
const command = buildClaudeCommand(project);
|
|
5084
|
-
spawnSync2("tmux", [
|
|
5085
|
-
"new-window",
|
|
5086
|
-
"-t",
|
|
5087
|
-
tmuxSession,
|
|
5088
|
-
"-n",
|
|
5089
|
-
project.projectName,
|
|
5090
|
-
"-c",
|
|
5091
|
-
project.projectPath
|
|
5092
|
-
], { stdio: "inherit" });
|
|
5283
|
+
const claudeCmd = ["claude", ...claudeArgs].join(" ");
|
|
5093
5284
|
spawnSync2("tmux", [
|
|
5094
5285
|
"send-keys",
|
|
5095
5286
|
"-t",
|
|
5096
|
-
`${tmuxSession}:${
|
|
5097
|
-
|
|
5287
|
+
`${tmuxSession}:${safeName}`,
|
|
5288
|
+
claudeCmd,
|
|
5098
5289
|
"Enter"
|
|
5099
5290
|
], { stdio: "inherit" });
|
|
5100
5291
|
}
|
|
5292
|
+
}
|
|
5293
|
+
if (!insideTmux) {
|
|
5101
5294
|
spawn2("tmux", ["attach-session", "-t", tmuxSession], {
|
|
5102
5295
|
stdio: "inherit"
|
|
5103
5296
|
});
|
|
@@ -5105,81 +5298,102 @@ async function spawnInTmux(projects, sessionName) {
|
|
|
5105
5298
|
}
|
|
5106
5299
|
async function spawnInGnomeTerminal(projects) {
|
|
5107
5300
|
const args = [];
|
|
5108
|
-
for (
|
|
5109
|
-
const
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
} else {
|
|
5114
|
-
args.push("--tab", "--title", project.projectName, "--", "bash", "-c", `${command}; exec bash`);
|
|
5301
|
+
for (const project of projects) {
|
|
5302
|
+
const validation = validateProject(project);
|
|
5303
|
+
if (!validation.valid) {
|
|
5304
|
+
console.error(`Skipping invalid project: ${validation.error}`);
|
|
5305
|
+
continue;
|
|
5115
5306
|
}
|
|
5307
|
+
const claudeArgs = buildClaudeArgs(project);
|
|
5308
|
+
const safeName = sanitizeProjectName(project.projectName);
|
|
5309
|
+
const wrapperScript = `cd ${escapeShellArg(project.projectPath)} && exec claude ${claudeArgs.map(escapeShellArg).join(" ")}`;
|
|
5310
|
+
args.push(
|
|
5311
|
+
"--tab",
|
|
5312
|
+
"--title",
|
|
5313
|
+
safeName,
|
|
5314
|
+
"--",
|
|
5315
|
+
"bash",
|
|
5316
|
+
"-c",
|
|
5317
|
+
wrapperScript
|
|
5318
|
+
);
|
|
5319
|
+
}
|
|
5320
|
+
if (args.length > 0) {
|
|
5321
|
+
spawn2("gnome-terminal", args, {
|
|
5322
|
+
stdio: "ignore",
|
|
5323
|
+
detached: true
|
|
5324
|
+
}).unref();
|
|
5116
5325
|
}
|
|
5117
|
-
spawn2("gnome-terminal", args, {
|
|
5118
|
-
stdio: "ignore",
|
|
5119
|
-
detached: true
|
|
5120
|
-
}).unref();
|
|
5121
5326
|
}
|
|
5122
5327
|
async function spawnInKitty(projects) {
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5328
|
+
const isInsideKitty = !!process.env.KITTY_WINDOW_ID;
|
|
5329
|
+
for (let i = 0; i < projects.length; i++) {
|
|
5330
|
+
const project = projects[i];
|
|
5331
|
+
const validation = validateProject(project);
|
|
5332
|
+
if (!validation.valid) {
|
|
5333
|
+
console.error(`Skipping invalid project: ${validation.error}`);
|
|
5334
|
+
continue;
|
|
5335
|
+
}
|
|
5336
|
+
const claudeArgs = buildClaudeArgs(project);
|
|
5337
|
+
const safeName = sanitizeProjectName(project.projectName);
|
|
5338
|
+
if (isInsideKitty) {
|
|
5126
5339
|
spawn2("kitten", [
|
|
5127
5340
|
"@",
|
|
5128
5341
|
"launch",
|
|
5129
5342
|
"--type=tab",
|
|
5130
5343
|
"--tab-title",
|
|
5131
|
-
|
|
5344
|
+
safeName,
|
|
5132
5345
|
"--cwd",
|
|
5133
5346
|
project.projectPath,
|
|
5134
|
-
"
|
|
5135
|
-
|
|
5136
|
-
command
|
|
5347
|
+
"claude",
|
|
5348
|
+
...claudeArgs
|
|
5137
5349
|
], { stdio: "inherit" });
|
|
5350
|
+
} else {
|
|
5351
|
+
if (i === 0) {
|
|
5352
|
+
const kittyArgs = [
|
|
5353
|
+
"--title",
|
|
5354
|
+
safeName,
|
|
5355
|
+
"--directory",
|
|
5356
|
+
project.projectPath,
|
|
5357
|
+
"claude",
|
|
5358
|
+
...claudeArgs
|
|
5359
|
+
];
|
|
5360
|
+
spawn2("kitty", kittyArgs, {
|
|
5361
|
+
stdio: "ignore",
|
|
5362
|
+
detached: true
|
|
5363
|
+
}).unref();
|
|
5364
|
+
} else {
|
|
5365
|
+
spawn2("kitty", [
|
|
5366
|
+
"--title",
|
|
5367
|
+
safeName,
|
|
5368
|
+
"--directory",
|
|
5369
|
+
project.projectPath,
|
|
5370
|
+
"claude",
|
|
5371
|
+
...claudeArgs
|
|
5372
|
+
], {
|
|
5373
|
+
stdio: "ignore",
|
|
5374
|
+
detached: true
|
|
5375
|
+
}).unref();
|
|
5376
|
+
}
|
|
5138
5377
|
}
|
|
5139
|
-
} else {
|
|
5140
|
-
const firstProject = projects[0];
|
|
5141
|
-
const firstCommand = buildClaudeCommand(firstProject);
|
|
5142
|
-
const args = [
|
|
5143
|
-
"--title",
|
|
5144
|
-
firstProject.projectName,
|
|
5145
|
-
"--directory",
|
|
5146
|
-
firstProject.projectPath,
|
|
5147
|
-
"bash",
|
|
5148
|
-
"-c",
|
|
5149
|
-
firstCommand
|
|
5150
|
-
];
|
|
5151
|
-
for (let i = 1; i < projects.length; i++) {
|
|
5152
|
-
const project = projects[i];
|
|
5153
|
-
const command = buildClaudeCommand(project);
|
|
5154
|
-
args.push(
|
|
5155
|
-
"--new-tab",
|
|
5156
|
-
"--title",
|
|
5157
|
-
project.projectName,
|
|
5158
|
-
"--directory",
|
|
5159
|
-
project.projectPath,
|
|
5160
|
-
"bash",
|
|
5161
|
-
"-c",
|
|
5162
|
-
command
|
|
5163
|
-
);
|
|
5164
|
-
}
|
|
5165
|
-
spawn2("kitty", args, {
|
|
5166
|
-
stdio: "ignore",
|
|
5167
|
-
detached: true
|
|
5168
|
-
}).unref();
|
|
5169
5378
|
}
|
|
5170
5379
|
}
|
|
5171
5380
|
async function spawnInAlacritty(projects) {
|
|
5172
5381
|
for (const project of projects) {
|
|
5173
|
-
const
|
|
5382
|
+
const validation = validateProject(project);
|
|
5383
|
+
if (!validation.valid) {
|
|
5384
|
+
console.error(`Skipping invalid project: ${validation.error}`);
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
const claudeArgs = buildClaudeArgs(project);
|
|
5388
|
+
const safeName = sanitizeProjectName(project.projectName);
|
|
5174
5389
|
spawn2("alacritty", [
|
|
5175
5390
|
"--title",
|
|
5176
|
-
|
|
5391
|
+
safeName,
|
|
5177
5392
|
"--working-directory",
|
|
5178
5393
|
project.projectPath,
|
|
5179
5394
|
"-e",
|
|
5180
|
-
"
|
|
5181
|
-
|
|
5182
|
-
command
|
|
5395
|
+
"claude",
|
|
5396
|
+
...claudeArgs
|
|
5183
5397
|
], {
|
|
5184
5398
|
stdio: "ignore",
|
|
5185
5399
|
detached: true
|
|
@@ -5188,15 +5402,20 @@ async function spawnInAlacritty(projects) {
|
|
|
5188
5402
|
}
|
|
5189
5403
|
async function spawnSequential(projects) {
|
|
5190
5404
|
const project = projects[0];
|
|
5191
|
-
const
|
|
5192
|
-
|
|
5193
|
-
|
|
5405
|
+
const validation = validateProject(project);
|
|
5406
|
+
if (!validation.valid) {
|
|
5407
|
+
console.error(`Cannot start: ${validation.error}`);
|
|
5408
|
+
return;
|
|
5409
|
+
}
|
|
5410
|
+
console.log(`Starting: ${sanitizeForLog(project.projectName)}`);
|
|
5411
|
+
console.log(`Path: ${sanitizeForLog(project.projectPath)}`);
|
|
5194
5412
|
if (projects.length > 1) {
|
|
5195
5413
|
console.log(`
|
|
5196
5414
|
Note: ${projects.length - 1} additional projects cannot be started in parallel.`);
|
|
5197
5415
|
console.log("Install tmux for multi-project support: sudo apt install tmux");
|
|
5198
5416
|
}
|
|
5199
|
-
|
|
5417
|
+
const claudeArgs = buildClaudeArgs(project);
|
|
5418
|
+
spawn2("claude", claudeArgs, {
|
|
5200
5419
|
stdio: "inherit",
|
|
5201
5420
|
cwd: project.projectPath
|
|
5202
5421
|
});
|
|
@@ -5209,27 +5428,38 @@ async function spawnInDocker(projects) {
|
|
|
5209
5428
|
}
|
|
5210
5429
|
if (projects.length === 1) {
|
|
5211
5430
|
const project = projects[0];
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5431
|
+
const validation = validateProject(project);
|
|
5432
|
+
if (!validation.valid) {
|
|
5433
|
+
console.error(`Cannot start: ${validation.error}`);
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
console.log(`Starting in Docker: ${sanitizeForLog(project.projectName)}`);
|
|
5437
|
+
console.log(`Path: ${sanitizeForLog(project.projectPath)}`);
|
|
5438
|
+
console.log(`Image: ${sanitizeForLog(dockerSettings.defaultImage)}`);
|
|
5215
5439
|
await dockerService.runClaudeInContainer(project, dockerSettings);
|
|
5216
5440
|
return;
|
|
5217
5441
|
}
|
|
5218
5442
|
if (isInsideTmux()) {
|
|
5219
5443
|
for (let i = 0; i < projects.length; i++) {
|
|
5220
5444
|
const project = projects[i];
|
|
5445
|
+
const validation = validateProject(project);
|
|
5446
|
+
if (!validation.valid) {
|
|
5447
|
+
console.error(`Skipping invalid project: ${validation.error}`);
|
|
5448
|
+
continue;
|
|
5449
|
+
}
|
|
5221
5450
|
if (i === 0) {
|
|
5222
|
-
console.log(`Starting in Docker: ${project.projectName}`);
|
|
5451
|
+
console.log(`Starting in Docker: ${sanitizeForLog(project.projectName)}`);
|
|
5223
5452
|
await dockerService.runClaudeInContainer(project, dockerSettings);
|
|
5224
5453
|
} else {
|
|
5225
|
-
const
|
|
5226
|
-
const
|
|
5454
|
+
const safeName = sanitizeProjectName(project.projectName);
|
|
5455
|
+
const containerName = `shiva-${safeName}-${Date.now()}`;
|
|
5227
5456
|
spawnSync2("tmux", [
|
|
5228
5457
|
"new-window",
|
|
5229
5458
|
"-n",
|
|
5230
|
-
|
|
5459
|
+
safeName
|
|
5231
5460
|
], { stdio: "inherit" });
|
|
5232
|
-
const
|
|
5461
|
+
const dockerArgs = buildSecureDockerArgs(project, dockerSettings, containerName);
|
|
5462
|
+
const dockerCmd = dockerArgs.join(" ");
|
|
5233
5463
|
spawnSync2("tmux", [
|
|
5234
5464
|
"send-keys",
|
|
5235
5465
|
dockerCmd,
|
|
@@ -5240,37 +5470,59 @@ async function spawnInDocker(projects) {
|
|
|
5240
5470
|
} else {
|
|
5241
5471
|
console.log(`Starting ${projects.length} projects in Docker containers...`);
|
|
5242
5472
|
for (const project of projects) {
|
|
5243
|
-
|
|
5473
|
+
const validation2 = validateProject(project);
|
|
5474
|
+
if (validation2.valid) {
|
|
5475
|
+
console.log(` - ${sanitizeForLog(project.projectName)}`);
|
|
5476
|
+
}
|
|
5244
5477
|
}
|
|
5245
5478
|
const firstProject = projects[0];
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
console.log(`Background container started for: ${project.projectName}`);
|
|
5479
|
+
const validation = validateProject(firstProject);
|
|
5480
|
+
if (!validation.valid) {
|
|
5481
|
+
console.error(`Cannot start: ${validation.error}`);
|
|
5482
|
+
return;
|
|
5251
5483
|
}
|
|
5484
|
+
console.log(`
|
|
5485
|
+
Attaching to: ${sanitizeForLog(firstProject.projectName)}`);
|
|
5252
5486
|
await dockerService.runClaudeInContainer(firstProject, dockerSettings);
|
|
5253
5487
|
}
|
|
5254
5488
|
}
|
|
5255
|
-
function
|
|
5489
|
+
function buildSecureDockerArgs(project, settings, containerName) {
|
|
5256
5490
|
const cmd = dockerService.getDockerInfo().runtime === "podman" ? "podman" : "docker";
|
|
5257
|
-
const
|
|
5258
|
-
const
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5491
|
+
const homeDir = os3.homedir();
|
|
5492
|
+
const args = [
|
|
5493
|
+
cmd,
|
|
5494
|
+
"run",
|
|
5495
|
+
"-it",
|
|
5496
|
+
"--rm",
|
|
5497
|
+
"--name",
|
|
5498
|
+
containerName,
|
|
5499
|
+
"--workdir",
|
|
5500
|
+
"/workspace",
|
|
5501
|
+
"-v",
|
|
5502
|
+
`${project.projectPath}:/workspace:rw`,
|
|
5503
|
+
"-v",
|
|
5504
|
+
`${path3.join(homeDir, ".claude")}:/root/.claude:rw`,
|
|
5505
|
+
"-v",
|
|
5506
|
+
`${path3.join(homeDir, ".config", "shiva-code")}:/root/.config/shiva-code:rw`
|
|
5507
|
+
];
|
|
5508
|
+
for (const [hostPath, containerPath] of Object.entries(settings.volumeMounts || {})) {
|
|
5509
|
+
if (isValidProjectPath(hostPath)) {
|
|
5510
|
+
args.push("-v", `${hostPath}:${containerPath}:rw`);
|
|
5511
|
+
}
|
|
5265
5512
|
}
|
|
5266
5513
|
for (const [key, value] of Object.entries(settings.environment || {})) {
|
|
5267
|
-
|
|
5514
|
+
if (/^[A-Z_][A-Z0-9_]*$/.test(key)) {
|
|
5515
|
+
args.push("-e", `${key}=${value}`);
|
|
5516
|
+
}
|
|
5268
5517
|
}
|
|
5269
|
-
|
|
5518
|
+
args.push(settings.defaultImage, "claude");
|
|
5270
5519
|
if (project.sessionId && !project.newSession) {
|
|
5271
|
-
|
|
5520
|
+
args.push("--resume", project.sessionId);
|
|
5272
5521
|
}
|
|
5273
|
-
return
|
|
5522
|
+
return args;
|
|
5523
|
+
}
|
|
5524
|
+
function escapeShellArg(arg) {
|
|
5525
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
5274
5526
|
}
|
|
5275
5527
|
async function spawnProjects(projects, terminal) {
|
|
5276
5528
|
if (projects.length === 0) {
|
|
@@ -5318,9 +5570,9 @@ function getTerminalName(terminal) {
|
|
|
5318
5570
|
}
|
|
5319
5571
|
|
|
5320
5572
|
// src/services/sandbox/sandbox.ts
|
|
5321
|
-
import { execSync
|
|
5573
|
+
import { execSync } from "child_process";
|
|
5322
5574
|
import { existsSync as existsSync6, mkdirSync, rmSync, readdirSync as readdirSync2, copyFileSync, readFileSync as readFileSync2 } from "fs";
|
|
5323
|
-
import { join as
|
|
5575
|
+
import { join as join4, dirname } from "path";
|
|
5324
5576
|
import { randomUUID } from "crypto";
|
|
5325
5577
|
import Conf3 from "conf";
|
|
5326
5578
|
var DEFAULT_SANDBOX_CONFIG = {
|
|
@@ -5359,10 +5611,10 @@ var SandboxService = class {
|
|
|
5359
5611
|
/**
|
|
5360
5612
|
* Check if a path is a git repository
|
|
5361
5613
|
*/
|
|
5362
|
-
isGitRepo(
|
|
5614
|
+
isGitRepo(path16) {
|
|
5363
5615
|
try {
|
|
5364
|
-
|
|
5365
|
-
cwd:
|
|
5616
|
+
execSync("git rev-parse --git-dir", {
|
|
5617
|
+
cwd: path16,
|
|
5366
5618
|
stdio: "pipe"
|
|
5367
5619
|
});
|
|
5368
5620
|
return true;
|
|
@@ -5375,11 +5627,11 @@ var SandboxService = class {
|
|
|
5375
5627
|
*/
|
|
5376
5628
|
isDockerAvailable() {
|
|
5377
5629
|
try {
|
|
5378
|
-
|
|
5630
|
+
execSync("docker info", { stdio: "pipe" });
|
|
5379
5631
|
return true;
|
|
5380
5632
|
} catch {
|
|
5381
5633
|
try {
|
|
5382
|
-
|
|
5634
|
+
execSync("podman info", { stdio: "pipe" });
|
|
5383
5635
|
return true;
|
|
5384
5636
|
} catch {
|
|
5385
5637
|
return false;
|
|
@@ -5402,7 +5654,7 @@ var SandboxService = class {
|
|
|
5402
5654
|
* Get the .shiva/sandbox directory path
|
|
5403
5655
|
*/
|
|
5404
5656
|
getSandboxDir(projectPath) {
|
|
5405
|
-
return
|
|
5657
|
+
return join4(projectPath, ".shiva", "sandbox");
|
|
5406
5658
|
}
|
|
5407
5659
|
/**
|
|
5408
5660
|
* Generate a unique session ID
|
|
@@ -5464,13 +5716,13 @@ var SandboxService = class {
|
|
|
5464
5716
|
*/
|
|
5465
5717
|
async createWorktreeSandbox(projectPath, sessionId) {
|
|
5466
5718
|
const sandboxDir = this.getSandboxDir(projectPath);
|
|
5467
|
-
const sandboxPath =
|
|
5719
|
+
const sandboxPath = join4(sandboxDir, `session-${sessionId}`);
|
|
5468
5720
|
mkdirSync(dirname(sandboxPath), { recursive: true });
|
|
5469
|
-
const currentRef =
|
|
5721
|
+
const currentRef = execSync("git rev-parse HEAD", {
|
|
5470
5722
|
cwd: projectPath,
|
|
5471
5723
|
encoding: "utf-8"
|
|
5472
5724
|
}).trim();
|
|
5473
|
-
|
|
5725
|
+
execSync(`git worktree add --detach "${sandboxPath}" ${currentRef}`, {
|
|
5474
5726
|
cwd: projectPath,
|
|
5475
5727
|
stdio: "pipe"
|
|
5476
5728
|
});
|
|
@@ -5482,14 +5734,14 @@ var SandboxService = class {
|
|
|
5482
5734
|
*/
|
|
5483
5735
|
async copyUntrackedFiles(projectPath, sandboxPath) {
|
|
5484
5736
|
try {
|
|
5485
|
-
const untrackedOutput =
|
|
5737
|
+
const untrackedOutput = execSync("git ls-files --others --exclude-standard", {
|
|
5486
5738
|
cwd: projectPath,
|
|
5487
5739
|
encoding: "utf-8"
|
|
5488
5740
|
});
|
|
5489
5741
|
const untrackedFiles = untrackedOutput.split("\n").filter((f) => f.trim().length > 0);
|
|
5490
5742
|
for (const file of untrackedFiles) {
|
|
5491
|
-
const srcPath =
|
|
5492
|
-
const destPath =
|
|
5743
|
+
const srcPath = join4(projectPath, file);
|
|
5744
|
+
const destPath = join4(sandboxPath, file);
|
|
5493
5745
|
if (!existsSync6(srcPath)) continue;
|
|
5494
5746
|
const config2 = this.getConfig();
|
|
5495
5747
|
if (config2.excludePaths.some((excluded) => file.startsWith(excluded))) {
|
|
@@ -5509,7 +5761,7 @@ var SandboxService = class {
|
|
|
5509
5761
|
*/
|
|
5510
5762
|
async createCopySandbox(projectPath, sessionId, excludePaths) {
|
|
5511
5763
|
const sandboxDir = this.getSandboxDir(projectPath);
|
|
5512
|
-
const sandboxPath =
|
|
5764
|
+
const sandboxPath = join4(sandboxDir, `session-${sessionId}`);
|
|
5513
5765
|
mkdirSync(sandboxPath, { recursive: true });
|
|
5514
5766
|
this.copyDirectory(projectPath, sandboxPath, excludePaths);
|
|
5515
5767
|
return sandboxPath;
|
|
@@ -5520,8 +5772,8 @@ var SandboxService = class {
|
|
|
5520
5772
|
copyDirectory(src, dest, excludePaths) {
|
|
5521
5773
|
const entries = readdirSync2(src, { withFileTypes: true });
|
|
5522
5774
|
for (const entry of entries) {
|
|
5523
|
-
const srcPath =
|
|
5524
|
-
const destPath =
|
|
5775
|
+
const srcPath = join4(src, entry.name);
|
|
5776
|
+
const destPath = join4(dest, entry.name);
|
|
5525
5777
|
if (excludePaths.includes(entry.name)) {
|
|
5526
5778
|
continue;
|
|
5527
5779
|
}
|
|
@@ -5541,9 +5793,9 @@ var SandboxService = class {
|
|
|
5541
5793
|
*/
|
|
5542
5794
|
async createDockerOverlaySandbox(projectPath, sessionId) {
|
|
5543
5795
|
const sandboxDir = this.getSandboxDir(projectPath);
|
|
5544
|
-
const sandboxPath =
|
|
5545
|
-
const upperDir =
|
|
5546
|
-
const workDir =
|
|
5796
|
+
const sandboxPath = join4(sandboxDir, `session-${sessionId}`);
|
|
5797
|
+
const upperDir = join4(sandboxDir, `upper-${sessionId}`);
|
|
5798
|
+
const workDir = join4(sandboxDir, `work-${sessionId}`);
|
|
5547
5799
|
mkdirSync(sandboxPath, { recursive: true });
|
|
5548
5800
|
mkdirSync(upperDir, { recursive: true });
|
|
5549
5801
|
mkdirSync(workDir, { recursive: true });
|
|
@@ -5610,7 +5862,7 @@ var SandboxService = class {
|
|
|
5610
5862
|
}
|
|
5611
5863
|
if (session.mode === "worktree") {
|
|
5612
5864
|
try {
|
|
5613
|
-
|
|
5865
|
+
execSync(`git worktree remove "${session.sandboxPath}" --force`, {
|
|
5614
5866
|
cwd: session.projectPath,
|
|
5615
5867
|
stdio: "pipe"
|
|
5616
5868
|
});
|
|
@@ -5619,7 +5871,7 @@ var SandboxService = class {
|
|
|
5619
5871
|
rmSync(session.sandboxPath, { recursive: true, force: true });
|
|
5620
5872
|
}
|
|
5621
5873
|
try {
|
|
5622
|
-
|
|
5874
|
+
execSync("git worktree prune", {
|
|
5623
5875
|
cwd: session.projectPath,
|
|
5624
5876
|
stdio: "pipe"
|
|
5625
5877
|
});
|
|
@@ -5648,11 +5900,11 @@ var SandboxService = class {
|
|
|
5648
5900
|
}
|
|
5649
5901
|
const changes = [];
|
|
5650
5902
|
if (session.mode === "worktree") {
|
|
5651
|
-
const diffOutput =
|
|
5903
|
+
const diffOutput = execSync("git diff --stat --numstat HEAD", {
|
|
5652
5904
|
cwd: session.sandboxPath,
|
|
5653
5905
|
encoding: "utf-8"
|
|
5654
5906
|
});
|
|
5655
|
-
const untrackedOutput =
|
|
5907
|
+
const untrackedOutput = execSync("git ls-files --others --exclude-standard", {
|
|
5656
5908
|
cwd: session.sandboxPath,
|
|
5657
5909
|
encoding: "utf-8"
|
|
5658
5910
|
});
|
|
@@ -5671,7 +5923,7 @@ var SandboxService = class {
|
|
|
5671
5923
|
}
|
|
5672
5924
|
const untrackedFiles = untrackedOutput.split("\n").filter((f) => f.trim().length > 0);
|
|
5673
5925
|
for (const file of untrackedFiles) {
|
|
5674
|
-
const filePath =
|
|
5926
|
+
const filePath = join4(session.sandboxPath, file);
|
|
5675
5927
|
if (existsSync6(filePath)) {
|
|
5676
5928
|
const content = readFileSync2(filePath, "utf-8");
|
|
5677
5929
|
const lineCount = content.split("\n").length;
|
|
@@ -5706,7 +5958,7 @@ var SandboxService = class {
|
|
|
5706
5958
|
async compareDirectories(originalPath, sandboxPath, relativePath, changes) {
|
|
5707
5959
|
const config2 = this.getConfig();
|
|
5708
5960
|
const sandboxEntries = /* @__PURE__ */ new Set();
|
|
5709
|
-
const sandboxFullPath =
|
|
5961
|
+
const sandboxFullPath = join4(sandboxPath, relativePath);
|
|
5710
5962
|
if (existsSync6(sandboxFullPath)) {
|
|
5711
5963
|
const entries = readdirSync2(sandboxFullPath, { withFileTypes: true });
|
|
5712
5964
|
for (const entry of entries) {
|
|
@@ -5714,9 +5966,9 @@ var SandboxService = class {
|
|
|
5714
5966
|
continue;
|
|
5715
5967
|
}
|
|
5716
5968
|
sandboxEntries.add(entry.name);
|
|
5717
|
-
const entryRelPath = relativePath ?
|
|
5718
|
-
const originalEntryPath =
|
|
5719
|
-
const sandboxEntryPath =
|
|
5969
|
+
const entryRelPath = relativePath ? join4(relativePath, entry.name) : entry.name;
|
|
5970
|
+
const originalEntryPath = join4(originalPath, entryRelPath);
|
|
5971
|
+
const sandboxEntryPath = join4(sandboxPath, entryRelPath);
|
|
5720
5972
|
if (entry.isDirectory()) {
|
|
5721
5973
|
if (!existsSync6(originalEntryPath)) {
|
|
5722
5974
|
await this.countNewDirectoryChanges(sandboxEntryPath, entryRelPath, changes);
|
|
@@ -5750,7 +6002,7 @@ var SandboxService = class {
|
|
|
5750
6002
|
}
|
|
5751
6003
|
}
|
|
5752
6004
|
}
|
|
5753
|
-
const originalFullPath =
|
|
6005
|
+
const originalFullPath = join4(originalPath, relativePath);
|
|
5754
6006
|
if (existsSync6(originalFullPath)) {
|
|
5755
6007
|
const entries = readdirSync2(originalFullPath, { withFileTypes: true });
|
|
5756
6008
|
for (const entry of entries) {
|
|
@@ -5758,8 +6010,8 @@ var SandboxService = class {
|
|
|
5758
6010
|
continue;
|
|
5759
6011
|
}
|
|
5760
6012
|
if (!sandboxEntries.has(entry.name)) {
|
|
5761
|
-
const entryRelPath = relativePath ?
|
|
5762
|
-
const originalEntryPath =
|
|
6013
|
+
const entryRelPath = relativePath ? join4(relativePath, entry.name) : entry.name;
|
|
6014
|
+
const originalEntryPath = join4(originalPath, entryRelPath);
|
|
5763
6015
|
if (entry.isDirectory()) {
|
|
5764
6016
|
await this.countDeletedDirectoryChanges(originalEntryPath, entryRelPath, changes);
|
|
5765
6017
|
} else {
|
|
@@ -5802,8 +6054,8 @@ var SandboxService = class {
|
|
|
5802
6054
|
const config2 = this.getConfig();
|
|
5803
6055
|
for (const entry of entries) {
|
|
5804
6056
|
if (config2.excludePaths.includes(entry.name)) continue;
|
|
5805
|
-
const entryRelPath =
|
|
5806
|
-
const entryPath =
|
|
6057
|
+
const entryRelPath = join4(relativePath, entry.name);
|
|
6058
|
+
const entryPath = join4(dirPath, entry.name);
|
|
5807
6059
|
if (entry.isDirectory()) {
|
|
5808
6060
|
await this.countNewDirectoryChanges(entryPath, entryRelPath, changes);
|
|
5809
6061
|
} else {
|
|
@@ -5826,8 +6078,8 @@ var SandboxService = class {
|
|
|
5826
6078
|
const config2 = this.getConfig();
|
|
5827
6079
|
for (const entry of entries) {
|
|
5828
6080
|
if (config2.excludePaths.includes(entry.name)) continue;
|
|
5829
|
-
const entryRelPath =
|
|
5830
|
-
const entryPath =
|
|
6081
|
+
const entryRelPath = join4(relativePath, entry.name);
|
|
6082
|
+
const entryPath = join4(dirPath, entry.name);
|
|
5831
6083
|
if (entry.isDirectory()) {
|
|
5832
6084
|
await this.countDeletedDirectoryChanges(entryPath, entryRelPath, changes);
|
|
5833
6085
|
} else {
|
|
@@ -5852,13 +6104,13 @@ var SandboxService = class {
|
|
|
5852
6104
|
}
|
|
5853
6105
|
if (session.mode === "worktree") {
|
|
5854
6106
|
try {
|
|
5855
|
-
const diff =
|
|
6107
|
+
const diff = execSync(`git diff HEAD -- "${filePath}"`, {
|
|
5856
6108
|
cwd: session.sandboxPath,
|
|
5857
6109
|
encoding: "utf-8"
|
|
5858
6110
|
});
|
|
5859
6111
|
return diff || "(no changes)";
|
|
5860
6112
|
} catch {
|
|
5861
|
-
const sandboxFilePath2 =
|
|
6113
|
+
const sandboxFilePath2 = join4(session.sandboxPath, filePath);
|
|
5862
6114
|
if (existsSync6(sandboxFilePath2)) {
|
|
5863
6115
|
const content = readFileSync2(sandboxFilePath2, "utf-8");
|
|
5864
6116
|
return `+++ ${filePath} (new file)
|
|
@@ -5867,8 +6119,8 @@ ${content.split("\n").map((l) => `+ ${l}`).join("\n")}`;
|
|
|
5867
6119
|
return "(file not found)";
|
|
5868
6120
|
}
|
|
5869
6121
|
}
|
|
5870
|
-
const originalPath =
|
|
5871
|
-
const sandboxFilePath =
|
|
6122
|
+
const originalPath = join4(session.projectPath, filePath);
|
|
6123
|
+
const sandboxFilePath = join4(session.sandboxPath, filePath);
|
|
5872
6124
|
const originalExists = existsSync6(originalPath);
|
|
5873
6125
|
const sandboxExists = existsSync6(sandboxFilePath);
|
|
5874
6126
|
if (!originalExists && sandboxExists) {
|
|
@@ -5926,8 +6178,8 @@ ${content.split("\n").map((l) => `- ${l}`).join("\n")}`;
|
|
|
5926
6178
|
for (const filePath of pathsToApply) {
|
|
5927
6179
|
const change = diff.changes.find((c) => c.path === filePath);
|
|
5928
6180
|
if (!change) continue;
|
|
5929
|
-
const srcPath =
|
|
5930
|
-
const destPath =
|
|
6181
|
+
const srcPath = join4(session.sandboxPath, filePath);
|
|
6182
|
+
const destPath = join4(session.projectPath, filePath);
|
|
5931
6183
|
if (change.type === "deleted") {
|
|
5932
6184
|
if (existsSync6(destPath)) {
|
|
5933
6185
|
rmSync(destPath, { force: true });
|
|
@@ -5964,8 +6216,8 @@ ${content.split("\n").map((l) => `- ${l}`).join("\n")}`;
|
|
|
5964
6216
|
throw new Error(`Sandbox ${sessionId} not found`);
|
|
5965
6217
|
}
|
|
5966
6218
|
for (const filePath of filePaths) {
|
|
5967
|
-
const sandboxFilePath =
|
|
5968
|
-
const originalPath =
|
|
6219
|
+
const sandboxFilePath = join4(session.sandboxPath, filePath);
|
|
6220
|
+
const originalPath = join4(session.projectPath, filePath);
|
|
5969
6221
|
if (existsSync6(originalPath)) {
|
|
5970
6222
|
copyFileSync(originalPath, sandboxFilePath);
|
|
5971
6223
|
} else {
|
|
@@ -6059,11 +6311,12 @@ var startCommand = new Command10("start").description("Projekte starten (mit Git
|
|
|
6059
6311
|
return;
|
|
6060
6312
|
}
|
|
6061
6313
|
const spinner = ora6("Bereite Projekte vor...").start();
|
|
6314
|
+
const allProjects = await getAllClaudeProjects();
|
|
6062
6315
|
const launches = [];
|
|
6063
6316
|
for (const projektArg of projekte) {
|
|
6064
|
-
const absolutePath =
|
|
6317
|
+
const absolutePath = path4.resolve(projektArg);
|
|
6065
6318
|
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory()) {
|
|
6066
|
-
const project =
|
|
6319
|
+
const project = findProjectFromArray(allProjects, absolutePath);
|
|
6067
6320
|
const latestSession = project?.latestSession;
|
|
6068
6321
|
launches.push({
|
|
6069
6322
|
projectPath: absolutePath,
|
|
@@ -6072,7 +6325,7 @@ var startCommand = new Command10("start").description("Projekte starten (mit Git
|
|
|
6072
6325
|
newSession: options.new || !latestSession
|
|
6073
6326
|
});
|
|
6074
6327
|
} else {
|
|
6075
|
-
const project =
|
|
6328
|
+
const project = findProjectFromArray(allProjects, projektArg);
|
|
6076
6329
|
if (project) {
|
|
6077
6330
|
if (!fs.existsSync(project.absolutePath)) {
|
|
6078
6331
|
spinner.warn(`Projektpfad existiert nicht: ${project.absolutePath}`);
|
|
@@ -6811,10 +7064,10 @@ import ora9 from "ora";
|
|
|
6811
7064
|
|
|
6812
7065
|
// src/services/data/tags.ts
|
|
6813
7066
|
import * as fs4 from "fs";
|
|
6814
|
-
import * as
|
|
6815
|
-
import * as
|
|
7067
|
+
import * as path5 from "path";
|
|
7068
|
+
import * as os4 from "os";
|
|
6816
7069
|
function getTagsPath() {
|
|
6817
|
-
return
|
|
7070
|
+
return path5.join(os4.homedir(), ".shiva", "tags.json");
|
|
6818
7071
|
}
|
|
6819
7072
|
function loadTagsData() {
|
|
6820
7073
|
const filepath = getTagsPath();
|
|
@@ -6830,7 +7083,7 @@ function loadTagsData() {
|
|
|
6830
7083
|
}
|
|
6831
7084
|
function saveTagsData(data) {
|
|
6832
7085
|
const filepath = getTagsPath();
|
|
6833
|
-
const dir =
|
|
7086
|
+
const dir = path5.dirname(filepath);
|
|
6834
7087
|
if (!fs4.existsSync(dir)) {
|
|
6835
7088
|
fs4.mkdirSync(dir, { recursive: true });
|
|
6836
7089
|
}
|
|
@@ -6964,110 +7217,128 @@ function sessionHasAnyTag(sessionId, tags) {
|
|
|
6964
7217
|
return sessionTags.some((t) => normalizedTags.includes(t));
|
|
6965
7218
|
}
|
|
6966
7219
|
|
|
7220
|
+
// src/utils/command-wrapper.ts
|
|
7221
|
+
function withCommandErrorHandling(commandName, fn) {
|
|
7222
|
+
return async (...args) => {
|
|
7223
|
+
try {
|
|
7224
|
+
await fn(...args);
|
|
7225
|
+
} catch (error) {
|
|
7226
|
+
const shivaError = fromError(error);
|
|
7227
|
+
logErrorWithSuggestion(shivaError, commandName);
|
|
7228
|
+
process.exit(1);
|
|
7229
|
+
}
|
|
7230
|
+
};
|
|
7231
|
+
}
|
|
7232
|
+
function logErrorWithSuggestion(error, commandName) {
|
|
7233
|
+
if (commandName) {
|
|
7234
|
+
log.error(`[${commandName}] ${error.message}`);
|
|
7235
|
+
} else {
|
|
7236
|
+
log.error(error.message);
|
|
7237
|
+
}
|
|
7238
|
+
if (error.suggestion) {
|
|
7239
|
+
log.info(`\u2192 ${error.suggestion}`);
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
|
|
6967
7243
|
// src/commands/session/sessions.ts
|
|
6968
|
-
var sessionsCommand = new Command13("sessions").description("Alle Claude Code Sessions auflisten").option("-a, --all", "Auch leere Projekte zeigen").option("-p, --project <pfad>", "Nur ein bestimmtes Projekt").option("-t, --tag <tag>", "Nach Tag filtern (Komma-getrennt f\xFCr mehrere)").option("-r, --refresh", "Cache invalidieren und neu laden").option("--json", "JSON Output").action(async (options) => {
|
|
7244
|
+
var sessionsCommand = new Command13("sessions").description("Alle Claude Code Sessions auflisten").option("-a, --all", "Auch leere Projekte zeigen").option("-p, --project <pfad>", "Nur ein bestimmtes Projekt").option("-t, --tag <tag>", "Nach Tag filtern (Komma-getrennt f\xFCr mehrere)").option("-r, --refresh", "Cache invalidieren und neu laden").option("--json", "JSON Output").action(withCommandErrorHandling("sessions", async (options) => {
|
|
6969
7245
|
if (options.refresh) {
|
|
6970
7246
|
invalidateSessionsCache();
|
|
6971
7247
|
}
|
|
6972
7248
|
const spinner = ora9("Lade Sessions...").start();
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
spinner.fail(`Projekt nicht gefunden: ${options.project}`);
|
|
6981
|
-
return;
|
|
6982
|
-
}
|
|
6983
|
-
}
|
|
6984
|
-
if (options.tag) {
|
|
6985
|
-
const filterTags = options.tag.split(",").map((t) => t.trim());
|
|
6986
|
-
projects = projects.map((project) => ({
|
|
6987
|
-
...project,
|
|
6988
|
-
sessions: project.sessions.filter(
|
|
6989
|
-
(session) => sessionHasAnyTag(session.sessionId, filterTags)
|
|
6990
|
-
)
|
|
6991
|
-
})).filter((p) => p.sessions.length > 0);
|
|
6992
|
-
}
|
|
6993
|
-
if (!options.all) {
|
|
6994
|
-
projects = projects.filter((p) => p.sessions.length > 0);
|
|
6995
|
-
}
|
|
6996
|
-
spinner.stop();
|
|
6997
|
-
if (options.json) {
|
|
6998
|
-
console.log(JSON.stringify(projects, null, 2));
|
|
7249
|
+
let projects = await getAllClaudeProjects(options.refresh);
|
|
7250
|
+
if (options.project) {
|
|
7251
|
+
const found = await findProject(options.project);
|
|
7252
|
+
if (found) {
|
|
7253
|
+
projects = [found];
|
|
7254
|
+
} else {
|
|
7255
|
+
spinner.fail(`Projekt nicht gefunden: ${options.project}`);
|
|
6999
7256
|
return;
|
|
7000
7257
|
}
|
|
7258
|
+
}
|
|
7259
|
+
if (options.tag) {
|
|
7260
|
+
const filterTags = options.tag.split(",").map((t) => t.trim());
|
|
7261
|
+
projects = projects.map((project) => ({
|
|
7262
|
+
...project,
|
|
7263
|
+
sessions: project.sessions.filter(
|
|
7264
|
+
(session) => sessionHasAnyTag(session.sessionId, filterTags)
|
|
7265
|
+
)
|
|
7266
|
+
})).filter((p) => p.sessions.length > 0);
|
|
7267
|
+
}
|
|
7268
|
+
if (!options.all) {
|
|
7269
|
+
projects = projects.filter((p) => p.sessions.length > 0);
|
|
7270
|
+
}
|
|
7271
|
+
spinner.stop();
|
|
7272
|
+
if (options.json) {
|
|
7273
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
7274
|
+
return;
|
|
7275
|
+
}
|
|
7276
|
+
log.newline();
|
|
7277
|
+
console.log(colors.orange.bold("SHIVA Code - Sessions"));
|
|
7278
|
+
console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7279
|
+
log.newline();
|
|
7280
|
+
if (projects.length === 0) {
|
|
7281
|
+
log.dim("Keine Sessions gefunden.");
|
|
7001
7282
|
log.newline();
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7283
|
+
log.info("Claude Code Sessions werden automatisch erkannt.");
|
|
7284
|
+
return;
|
|
7285
|
+
}
|
|
7286
|
+
for (const project of projects) {
|
|
7287
|
+
const projectExists = await checkProjectExists(project.absolutePath);
|
|
7288
|
+
const projectIcon = projectExists ? "" : colors.yellow(" \u26A0");
|
|
7289
|
+
console.log(colors.bold(`Projekt: ${project.projectName}`) + projectIcon);
|
|
7290
|
+
if (!projectExists) {
|
|
7291
|
+
console.log(colors.dim(` Pfad existiert nicht mehr: ${project.absolutePath}`));
|
|
7010
7292
|
}
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
const projectIcon = projectExists ? "" : colors.yellow(" \u26A0");
|
|
7014
|
-
console.log(colors.bold(`Projekt: ${project.projectName}`) + projectIcon);
|
|
7015
|
-
if (!projectExists) {
|
|
7016
|
-
console.log(colors.dim(` Pfad existiert nicht mehr: ${project.absolutePath}`));
|
|
7017
|
-
}
|
|
7018
|
-
if (project.sessions.length === 0) {
|
|
7019
|
-
log.dim(" Keine Sessions");
|
|
7020
|
-
log.newline();
|
|
7021
|
-
continue;
|
|
7022
|
-
}
|
|
7023
|
-
const displaySessions = project.sessions.slice(0, 5);
|
|
7024
|
-
for (let i = 0; i < displaySessions.length; i++) {
|
|
7025
|
-
const session = displaySessions[i];
|
|
7026
|
-
const num = String(i + 1).padStart(2, " ");
|
|
7027
|
-
let statusIcon = "";
|
|
7028
|
-
if (isSessionActive(session)) {
|
|
7029
|
-
statusIcon = colors.green(" [aktiv]");
|
|
7030
|
-
} else if (isSessionCorrupted(session)) {
|
|
7031
|
-
statusIcon = colors.red(" [corrupted]");
|
|
7032
|
-
}
|
|
7033
|
-
const branch = session.gitBranch || "main";
|
|
7034
|
-
const msgs = `${session.messageCount} msgs`.padEnd(10);
|
|
7035
|
-
const time = formatRelativeTime(session.modified);
|
|
7036
|
-
let prompt = session.firstPrompt || "";
|
|
7037
|
-
if (prompt.length > 40) {
|
|
7038
|
-
prompt = prompt.substring(0, 37) + "...";
|
|
7039
|
-
}
|
|
7040
|
-
prompt = `"${prompt}"`;
|
|
7041
|
-
const tags = getSessionTags(session.sessionId);
|
|
7042
|
-
const tagsDisplay = tags.length > 0 ? " " + tags.map((t) => colors.magenta(`[${t}]`)).join(" ") : "";
|
|
7043
|
-
const line = [
|
|
7044
|
-
colors.dim(`${num}.`),
|
|
7045
|
-
branch.padEnd(15),
|
|
7046
|
-
colors.dim(`(${msgs})`),
|
|
7047
|
-
formatDate(session.modified).padEnd(14),
|
|
7048
|
-
colors.cyan(prompt),
|
|
7049
|
-
statusIcon,
|
|
7050
|
-
tagsDisplay
|
|
7051
|
-
].join(" ");
|
|
7052
|
-
console.log(` ${line}`);
|
|
7053
|
-
}
|
|
7054
|
-
if (project.sessions.length > 5) {
|
|
7055
|
-
log.dim(` ... und ${project.sessions.length - 5} weitere Sessions`);
|
|
7056
|
-
}
|
|
7293
|
+
if (project.sessions.length === 0) {
|
|
7294
|
+
log.dim(" Keine Sessions");
|
|
7057
7295
|
log.newline();
|
|
7296
|
+
continue;
|
|
7058
7297
|
}
|
|
7059
|
-
const
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
})
|
|
7067
|
-
|
|
7298
|
+
const displaySessions = project.sessions.slice(0, 5);
|
|
7299
|
+
for (let i = 0; i < displaySessions.length; i++) {
|
|
7300
|
+
const session = displaySessions[i];
|
|
7301
|
+
const num = String(i + 1).padStart(2, " ");
|
|
7302
|
+
let statusIcon = "";
|
|
7303
|
+
if (isSessionActive(session)) {
|
|
7304
|
+
statusIcon = colors.green(" [aktiv]");
|
|
7305
|
+
} else if (isSessionCorruptedQuick(session)) {
|
|
7306
|
+
statusIcon = colors.red(" [corrupted]");
|
|
7307
|
+
}
|
|
7308
|
+
const branch = session.gitBranch || "main";
|
|
7309
|
+
const msgs = `${session.messageCount} msgs`.padEnd(10);
|
|
7310
|
+
const time = formatRelativeTime(session.modified);
|
|
7311
|
+
let prompt = session.firstPrompt || "";
|
|
7312
|
+
if (prompt.length > 40) {
|
|
7313
|
+
prompt = prompt.substring(0, 37) + "...";
|
|
7314
|
+
}
|
|
7315
|
+
prompt = `"${prompt}"`;
|
|
7316
|
+
const tags = getSessionTags(session.sessionId);
|
|
7317
|
+
const tagsDisplay = tags.length > 0 ? " " + tags.map((t) => colors.magenta(`[${t}]`)).join(" ") : "";
|
|
7318
|
+
const line = [
|
|
7319
|
+
colors.dim(`${num}.`),
|
|
7320
|
+
branch.padEnd(15),
|
|
7321
|
+
colors.dim(`(${msgs})`),
|
|
7322
|
+
formatDate(session.modified).padEnd(14),
|
|
7323
|
+
colors.cyan(prompt),
|
|
7324
|
+
statusIcon,
|
|
7325
|
+
tagsDisplay
|
|
7326
|
+
].join(" ");
|
|
7327
|
+
console.log(` ${line}`);
|
|
7328
|
+
}
|
|
7329
|
+
if (project.sessions.length > 5) {
|
|
7330
|
+
log.dim(` ... und ${project.sessions.length - 5} weitere Sessions`);
|
|
7331
|
+
}
|
|
7332
|
+
log.newline();
|
|
7333
|
+
}
|
|
7334
|
+
const stats = await getSessionStats();
|
|
7335
|
+
log.dim(`Total: ${stats.activeProjects} Projekte, ${stats.totalSessions} Sessions`);
|
|
7336
|
+
log.newline();
|
|
7337
|
+
}));
|
|
7338
|
+
async function checkProjectExists(path16) {
|
|
7068
7339
|
try {
|
|
7069
7340
|
const fs15 = await import("fs");
|
|
7070
|
-
return fs15.existsSync(
|
|
7341
|
+
return fs15.existsSync(path16);
|
|
7071
7342
|
} catch {
|
|
7072
7343
|
return false;
|
|
7073
7344
|
}
|
|
@@ -7328,10 +7599,10 @@ sessionCommand.command("apply").description("Sandbox-\xC4nderungen \xFCbernehmen
|
|
|
7328
7599
|
log.header("Folgende \xC4nderungen werden \xFCbernommen:");
|
|
7329
7600
|
log.newline();
|
|
7330
7601
|
const pathsToApply = selectedPaths || diff.changes.map((c) => c.path);
|
|
7331
|
-
for (const
|
|
7332
|
-
const change = diff.changes.find((c) => c.path ===
|
|
7602
|
+
for (const path16 of pathsToApply) {
|
|
7603
|
+
const change = diff.changes.find((c) => c.path === path16);
|
|
7333
7604
|
if (change) {
|
|
7334
|
-
console.log(` ${formatChangeType(change.type)} ${
|
|
7605
|
+
console.log(` ${formatChangeType(change.type)} ${path16}`);
|
|
7335
7606
|
}
|
|
7336
7607
|
}
|
|
7337
7608
|
log.newline();
|
|
@@ -7526,7 +7797,7 @@ sessionCommand.command("config").description("Sandbox-Konfiguration verwalten").
|
|
|
7526
7797
|
import { Command as Command15 } from "commander";
|
|
7527
7798
|
import { spawn as spawn5, spawnSync as spawnSync5 } from "child_process";
|
|
7528
7799
|
import * as fs5 from "fs";
|
|
7529
|
-
import * as
|
|
7800
|
+
import * as path6 from "path";
|
|
7530
7801
|
import inquirer6 from "inquirer";
|
|
7531
7802
|
var githubCommand = new Command15("github").description("GitHub Integration verwalten").action(() => {
|
|
7532
7803
|
showStatus();
|
|
@@ -7948,9 +8219,9 @@ githubCommand.command("git-hook").description("Git Hooks f\xFCr Branch-Session I
|
|
|
7948
8219
|
log.error("Kein Git Repository");
|
|
7949
8220
|
return;
|
|
7950
8221
|
}
|
|
7951
|
-
const gitDir =
|
|
7952
|
-
const hooksDir =
|
|
7953
|
-
const hookFile =
|
|
8222
|
+
const gitDir = path6.join(cwd, ".git");
|
|
8223
|
+
const hooksDir = path6.join(gitDir, "hooks");
|
|
8224
|
+
const hookFile = path6.join(hooksDir, "post-checkout");
|
|
7954
8225
|
if (options.uninstall) {
|
|
7955
8226
|
if (fs5.existsSync(hookFile)) {
|
|
7956
8227
|
const content = fs5.readFileSync(hookFile, "utf-8");
|
|
@@ -8024,13 +8295,13 @@ githubCommand.command("map").description("Aktuellen Branch mit einer Session ver
|
|
|
8024
8295
|
initShivaDir(cwd);
|
|
8025
8296
|
}
|
|
8026
8297
|
if (!sessionId) {
|
|
8027
|
-
const { findProject:
|
|
8028
|
-
const project = await
|
|
8298
|
+
const { findProject: findProject3 } = await import("./manager-ZPQWG7E6.js");
|
|
8299
|
+
const project = await findProject3(cwd);
|
|
8029
8300
|
if (!project || project.sessions.length === 0) {
|
|
8030
8301
|
log.error("Keine Sessions f\xFCr dieses Projekt gefunden");
|
|
8031
8302
|
return;
|
|
8032
8303
|
}
|
|
8033
|
-
const { formatDate: formatDate2 } = await import("./manager-
|
|
8304
|
+
const { formatDate: formatDate2 } = await import("./manager-ZPQWG7E6.js");
|
|
8034
8305
|
const choices = project.sessions.slice(0, 10).map((s) => {
|
|
8035
8306
|
const time = formatDate2(s.modified);
|
|
8036
8307
|
const msgs = `${s.messageCount} msgs`;
|
|
@@ -8120,240 +8391,226 @@ githubCommand.command("mappings").description("Alle Branch-Session Mappings anze
|
|
|
8120
8391
|
// src/commands/github/issues.ts
|
|
8121
8392
|
import { Command as Command16 } from "commander";
|
|
8122
8393
|
import ora11 from "ora";
|
|
8123
|
-
var issuesCommand = new Command16("issues").description("GitHub Issues \xFCber alle Projekte anzeigen").option("-m, --mine", "Nur mir zugewiesene Issues").option("-p, --project <name>", "Nur Issues eines Projekts").option("-a, --all", "Auch geschlossene Issues").option("-l, --limit <n>", "Maximale Anzahl pro Projekt", "10").option("--json", "JSON Output").action(async (options) => {
|
|
8394
|
+
var issuesCommand = new Command16("issues").description("GitHub Issues \xFCber alle Projekte anzeigen").option("-m, --mine", "Nur mir zugewiesene Issues").option("-p, --project <name>", "Nur Issues eines Projekts").option("-a, --all", "Auch geschlossene Issues").option("-l, --limit <n>", "Maximale Anzahl pro Projekt", "10").option("--json", "JSON Output").action(withCommandErrorHandling("issues", async (options) => {
|
|
8124
8395
|
if (!isGhInstalled()) {
|
|
8125
|
-
|
|
8126
|
-
log.info("Installiere: https://cli.github.com/");
|
|
8127
|
-
return;
|
|
8396
|
+
throw Errors.GITHUB_NOT_INSTALLED();
|
|
8128
8397
|
}
|
|
8129
8398
|
if (!isGhAuthenticated()) {
|
|
8130
|
-
|
|
8131
|
-
log.info("Anmelden mit: shiva github login");
|
|
8132
|
-
return;
|
|
8399
|
+
throw Errors.GITHUB_NOT_AUTHENTICATED();
|
|
8133
8400
|
}
|
|
8134
8401
|
const currentUser = getGhUser();
|
|
8135
8402
|
const spinner = ora11("Lade Issues...").start();
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
spinner.fail(`Projekt nicht gefunden: ${options.project}`);
|
|
8145
|
-
return;
|
|
8146
|
-
}
|
|
8147
|
-
}
|
|
8148
|
-
const projectsWithIssues = [];
|
|
8149
|
-
const limit = parseInt(options.limit, 10) || 10;
|
|
8150
|
-
for (const project of projects) {
|
|
8151
|
-
const repo = getRepoInfo(project.absolutePath);
|
|
8152
|
-
if (!repo) continue;
|
|
8153
|
-
const issueOptions = { limit };
|
|
8154
|
-
if (options.mine && currentUser) {
|
|
8155
|
-
issueOptions.assignee = currentUser;
|
|
8156
|
-
}
|
|
8157
|
-
const issues = getOpenIssues(repo.fullName, issueOptions);
|
|
8158
|
-
if (issues.length > 0 || options.all) {
|
|
8159
|
-
const assignedCount = currentUser ? issues.filter((i) => i.assignees.includes(currentUser)).length : 0;
|
|
8160
|
-
projectsWithIssues.push({
|
|
8161
|
-
project,
|
|
8162
|
-
repo: repo.fullName,
|
|
8163
|
-
issues,
|
|
8164
|
-
assignedCount
|
|
8165
|
-
});
|
|
8166
|
-
}
|
|
8167
|
-
}
|
|
8168
|
-
spinner.stop();
|
|
8169
|
-
if (options.json) {
|
|
8170
|
-
const output = projectsWithIssues.map((p) => ({
|
|
8171
|
-
project: p.project.projectName,
|
|
8172
|
-
repo: p.repo,
|
|
8173
|
-
issues: p.issues
|
|
8174
|
-
}));
|
|
8175
|
-
console.log(JSON.stringify(output, null, 2));
|
|
8403
|
+
const allProjects = await getAllClaudeProjects();
|
|
8404
|
+
let projects = allProjects;
|
|
8405
|
+
if (options.project) {
|
|
8406
|
+
const found = await findProject(options.project);
|
|
8407
|
+
if (found) {
|
|
8408
|
+
projects = [found];
|
|
8409
|
+
} else {
|
|
8410
|
+
spinner.fail(`Projekt nicht gefunden: ${options.project}`);
|
|
8176
8411
|
return;
|
|
8177
8412
|
}
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8413
|
+
}
|
|
8414
|
+
const projectsWithIssues = [];
|
|
8415
|
+
const limit = parseInt(options.limit, 10) || 10;
|
|
8416
|
+
for (const project of projects) {
|
|
8417
|
+
const repo = getRepoInfo(project.absolutePath);
|
|
8418
|
+
if (!repo) continue;
|
|
8419
|
+
const issueOptions = { limit };
|
|
8420
|
+
if (options.mine && currentUser) {
|
|
8421
|
+
issueOptions.assignee = currentUser;
|
|
8422
|
+
}
|
|
8423
|
+
const issues = getOpenIssues(repo.fullName, issueOptions);
|
|
8424
|
+
if (issues.length > 0 || options.all) {
|
|
8425
|
+
const assignedCount = currentUser ? issues.filter((i) => i.assignees.includes(currentUser)).length : 0;
|
|
8426
|
+
projectsWithIssues.push({
|
|
8427
|
+
project,
|
|
8428
|
+
repo: repo.fullName,
|
|
8429
|
+
issues,
|
|
8430
|
+
assignedCount
|
|
8431
|
+
});
|
|
8432
|
+
}
|
|
8433
|
+
}
|
|
8434
|
+
spinner.stop();
|
|
8435
|
+
if (options.json) {
|
|
8436
|
+
const output = projectsWithIssues.map((p) => ({
|
|
8437
|
+
project: p.project.projectName,
|
|
8438
|
+
repo: p.repo,
|
|
8439
|
+
issues: p.issues
|
|
8440
|
+
}));
|
|
8441
|
+
console.log(JSON.stringify(output, null, 2));
|
|
8442
|
+
return;
|
|
8443
|
+
}
|
|
8444
|
+
log.newline();
|
|
8445
|
+
console.log(colors.orange.bold("SHIVA Code - GitHub Issues"));
|
|
8446
|
+
console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
8447
|
+
log.newline();
|
|
8448
|
+
if (projectsWithIssues.length === 0) {
|
|
8449
|
+
log.dim("Keine Issues gefunden.");
|
|
8450
|
+
if (!options.mine) {
|
|
8451
|
+
log.info("Versuche: shiva issues --mine");
|
|
8188
8452
|
}
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8453
|
+
return;
|
|
8454
|
+
}
|
|
8455
|
+
let totalIssues = 0;
|
|
8456
|
+
let totalAssigned = 0;
|
|
8457
|
+
for (const { project, repo, issues, assignedCount } of projectsWithIssues) {
|
|
8458
|
+
totalIssues += issues.length;
|
|
8459
|
+
totalAssigned += assignedCount;
|
|
8460
|
+
const assignedText = assignedCount > 0 ? colors.green(`, ${assignedCount} assigned`) : "";
|
|
8461
|
+
console.log(
|
|
8462
|
+
colors.bold(`\u{1F4E6} ${project.projectName}`) + colors.dim(` (${issues.length} open${assignedText})`)
|
|
8463
|
+
);
|
|
8464
|
+
for (const issue of issues) {
|
|
8465
|
+
const isAssigned = currentUser && issue.assignees.includes(currentUser);
|
|
8466
|
+
const assigneeText = isAssigned ? colors.green(" [you]") : issue.assignees.length > 0 ? colors.dim(` [@${issue.assignees[0]}]`) : "";
|
|
8467
|
+
let icon = "\u{1F7E1}";
|
|
8468
|
+
if (issue.labels.some((l) => l.toLowerCase().includes("bug"))) {
|
|
8469
|
+
icon = "\u{1F534}";
|
|
8470
|
+
} else if (issue.labels.some((l) => l.toLowerCase().includes("doc"))) {
|
|
8471
|
+
icon = "\u{1F7E2}";
|
|
8472
|
+
} else if (issue.labels.some((l) => l.toLowerCase().includes("feature"))) {
|
|
8473
|
+
icon = "\u{1F7E1}";
|
|
8474
|
+
}
|
|
8475
|
+
const labelsText = issue.labels.length > 0 ? colors.dim(` [${issue.labels.slice(0, 3).join(", ")}]`) : "";
|
|
8195
8476
|
console.log(
|
|
8196
|
-
|
|
8477
|
+
` ${icon} #${issue.number} ${issue.title.substring(0, 50)}${issue.title.length > 50 ? "..." : ""}${assigneeText}${labelsText}`
|
|
8197
8478
|
);
|
|
8198
|
-
for (const issue of issues) {
|
|
8199
|
-
const isAssigned = currentUser && issue.assignees.includes(currentUser);
|
|
8200
|
-
const assigneeText = isAssigned ? colors.green(" [you]") : issue.assignees.length > 0 ? colors.dim(` [@${issue.assignees[0]}]`) : "";
|
|
8201
|
-
let icon = "\u{1F7E1}";
|
|
8202
|
-
if (issue.labels.some((l) => l.toLowerCase().includes("bug"))) {
|
|
8203
|
-
icon = "\u{1F534}";
|
|
8204
|
-
} else if (issue.labels.some((l) => l.toLowerCase().includes("doc"))) {
|
|
8205
|
-
icon = "\u{1F7E2}";
|
|
8206
|
-
} else if (issue.labels.some((l) => l.toLowerCase().includes("feature"))) {
|
|
8207
|
-
icon = "\u{1F7E1}";
|
|
8208
|
-
}
|
|
8209
|
-
const labelsText = issue.labels.length > 0 ? colors.dim(` [${issue.labels.slice(0, 3).join(", ")}]`) : "";
|
|
8210
|
-
console.log(
|
|
8211
|
-
` ${icon} #${issue.number} ${issue.title.substring(0, 50)}${issue.title.length > 50 ? "..." : ""}${assigneeText}${labelsText}`
|
|
8212
|
-
);
|
|
8213
|
-
}
|
|
8214
|
-
log.newline();
|
|
8215
8479
|
}
|
|
8216
|
-
const summaryText = options.mine ? `Total: ${totalIssues} Issues (dir zugewiesen)` : `Total: ${totalIssues} Issues offen (${totalAssigned} dir zugewiesen)`;
|
|
8217
|
-
log.dim(summaryText);
|
|
8218
8480
|
log.newline();
|
|
8219
|
-
log.dim("Session f\xFCr Issue starten: shiva start --issue <nummer>");
|
|
8220
|
-
} catch (error) {
|
|
8221
|
-
spinner.fail("Fehler beim Laden der Issues");
|
|
8222
|
-
log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
|
|
8223
8481
|
}
|
|
8224
|
-
})
|
|
8482
|
+
const summaryText = options.mine ? `Total: ${totalIssues} Issues (dir zugewiesen)` : `Total: ${totalIssues} Issues offen (${totalAssigned} dir zugewiesen)`;
|
|
8483
|
+
log.dim(summaryText);
|
|
8484
|
+
log.newline();
|
|
8485
|
+
log.dim("Session f\xFCr Issue starten: shiva start --issue <nummer>");
|
|
8486
|
+
}));
|
|
8225
8487
|
|
|
8226
8488
|
// src/commands/github/prs.ts
|
|
8227
8489
|
import { Command as Command17 } from "commander";
|
|
8228
8490
|
import ora12 from "ora";
|
|
8229
|
-
var prsCommand = new Command17("prs").description("GitHub Pull Requests \xFCber alle Projekte anzeigen").option("-m, --mine", "Nur meine PRs").option("-r, --review", "PRs wo ich Reviewer bin").option("-p, --project <name>", "Nur PRs eines Projekts").option("-l, --limit <n>", "Maximale Anzahl pro Projekt", "10").option("--json", "JSON Output").action(async (options) => {
|
|
8491
|
+
var prsCommand = new Command17("prs").description("GitHub Pull Requests \xFCber alle Projekte anzeigen").option("-m, --mine", "Nur meine PRs").option("-r, --review", "PRs wo ich Reviewer bin").option("-p, --project <name>", "Nur PRs eines Projekts").option("-l, --limit <n>", "Maximale Anzahl pro Projekt", "10").option("--json", "JSON Output").action(withCommandErrorHandling("prs", async (options) => {
|
|
8230
8492
|
if (!isGhInstalled()) {
|
|
8231
|
-
|
|
8232
|
-
log.info("Installiere: https://cli.github.com/");
|
|
8233
|
-
return;
|
|
8493
|
+
throw Errors.GITHUB_NOT_INSTALLED();
|
|
8234
8494
|
}
|
|
8235
8495
|
if (!isGhAuthenticated()) {
|
|
8236
|
-
|
|
8237
|
-
log.info("Anmelden mit: shiva github login");
|
|
8238
|
-
return;
|
|
8496
|
+
throw Errors.GITHUB_NOT_AUTHENTICATED();
|
|
8239
8497
|
}
|
|
8240
8498
|
const currentUser = getGhUser();
|
|
8241
8499
|
const spinner = ora12("Lade Pull Requests...").start();
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
spinner.fail(`Projekt nicht gefunden: ${options.project}`);
|
|
8251
|
-
return;
|
|
8252
|
-
}
|
|
8253
|
-
}
|
|
8254
|
-
const projectsWithPRs = [];
|
|
8255
|
-
const limit = parseInt(options.limit, 10) || 10;
|
|
8256
|
-
for (const project of projects) {
|
|
8257
|
-
const repo = getRepoInfo(project.absolutePath);
|
|
8258
|
-
if (!repo) continue;
|
|
8259
|
-
const prOptions = { limit };
|
|
8260
|
-
if (options.mine && currentUser) {
|
|
8261
|
-
prOptions.author = currentUser;
|
|
8262
|
-
}
|
|
8263
|
-
let prs = getOpenPRs(repo.fullName, prOptions);
|
|
8264
|
-
if (options.review && currentUser) {
|
|
8265
|
-
prs = prs.filter((pr) => pr.author !== currentUser);
|
|
8266
|
-
}
|
|
8267
|
-
if (prs.length > 0) {
|
|
8268
|
-
const myPRsCount = currentUser ? prs.filter((pr) => pr.author === currentUser).length : 0;
|
|
8269
|
-
projectsWithPRs.push({
|
|
8270
|
-
project,
|
|
8271
|
-
repo: repo.fullName,
|
|
8272
|
-
prs,
|
|
8273
|
-
myPRsCount
|
|
8274
|
-
});
|
|
8275
|
-
}
|
|
8276
|
-
}
|
|
8277
|
-
spinner.stop();
|
|
8278
|
-
if (options.json) {
|
|
8279
|
-
const output = projectsWithPRs.map((p) => ({
|
|
8280
|
-
project: p.project.projectName,
|
|
8281
|
-
repo: p.repo,
|
|
8282
|
-
prs: p.prs
|
|
8283
|
-
}));
|
|
8284
|
-
console.log(JSON.stringify(output, null, 2));
|
|
8500
|
+
const allProjects = await getAllClaudeProjects();
|
|
8501
|
+
let projects = allProjects;
|
|
8502
|
+
if (options.project) {
|
|
8503
|
+
const found = await findProject(options.project);
|
|
8504
|
+
if (found) {
|
|
8505
|
+
projects = [found];
|
|
8506
|
+
} else {
|
|
8507
|
+
spinner.fail(`Projekt nicht gefunden: ${options.project}`);
|
|
8285
8508
|
return;
|
|
8286
8509
|
}
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8510
|
+
}
|
|
8511
|
+
const limit = parseInt(options.limit, 10) || 10;
|
|
8512
|
+
const projectsWithRepos = projects.map((project) => ({
|
|
8513
|
+
project,
|
|
8514
|
+
repo: getRepoInfo(project.absolutePath)
|
|
8515
|
+
})).filter((p) => p.repo !== null);
|
|
8516
|
+
const prPromises = projectsWithRepos.map(async ({ project, repo }) => {
|
|
8517
|
+
const prOptions = { limit };
|
|
8518
|
+
if (options.mine && currentUser) {
|
|
8519
|
+
prOptions.author = currentUser;
|
|
8520
|
+
}
|
|
8521
|
+
let prs = getOpenPRs(repo.fullName, prOptions);
|
|
8522
|
+
if (options.review && currentUser) {
|
|
8523
|
+
prs = prs.filter((pr) => pr.author !== currentUser);
|
|
8524
|
+
}
|
|
8525
|
+
if (prs.length > 0) {
|
|
8526
|
+
const myPRsCount = currentUser ? prs.filter((pr) => pr.author === currentUser).length : 0;
|
|
8527
|
+
return {
|
|
8528
|
+
project,
|
|
8529
|
+
repo: repo.fullName,
|
|
8530
|
+
prs,
|
|
8531
|
+
myPRsCount
|
|
8532
|
+
};
|
|
8297
8533
|
}
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8534
|
+
return null;
|
|
8535
|
+
});
|
|
8536
|
+
const results = await Promise.all(prPromises);
|
|
8537
|
+
const projectsWithPRs = results.filter((p) => p !== null);
|
|
8538
|
+
spinner.stop();
|
|
8539
|
+
if (options.json) {
|
|
8540
|
+
const output = projectsWithPRs.map((p) => ({
|
|
8541
|
+
project: p.project.projectName,
|
|
8542
|
+
repo: p.repo,
|
|
8543
|
+
prs: p.prs
|
|
8544
|
+
}));
|
|
8545
|
+
console.log(JSON.stringify(output, null, 2));
|
|
8546
|
+
return;
|
|
8547
|
+
}
|
|
8548
|
+
log.newline();
|
|
8549
|
+
console.log(colors.orange.bold("SHIVA Code - Pull Requests"));
|
|
8550
|
+
console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
8551
|
+
log.newline();
|
|
8552
|
+
if (projectsWithPRs.length === 0) {
|
|
8553
|
+
log.dim("Keine Pull Requests gefunden.");
|
|
8554
|
+
if (!options.mine) {
|
|
8555
|
+
log.info("Versuche: shiva prs --mine");
|
|
8556
|
+
}
|
|
8557
|
+
return;
|
|
8558
|
+
}
|
|
8559
|
+
let totalPRs = 0;
|
|
8560
|
+
let totalMyPRs = 0;
|
|
8561
|
+
for (const { project, repo, prs, myPRsCount } of projectsWithPRs) {
|
|
8562
|
+
totalPRs += prs.length;
|
|
8563
|
+
totalMyPRs += myPRsCount;
|
|
8564
|
+
console.log(
|
|
8565
|
+
colors.bold(`\u{1F4E6} ${project.projectName}`) + colors.dim(` (${prs.length} open)`)
|
|
8566
|
+
);
|
|
8567
|
+
for (const pr of prs) {
|
|
8568
|
+
const isMine = currentUser && pr.author === currentUser;
|
|
8569
|
+
let statusIcon = "\u{1F7E1}";
|
|
8570
|
+
if (pr.checksStatus === "success" && pr.reviewDecision === "approved") {
|
|
8571
|
+
statusIcon = "\u{1F7E2}";
|
|
8572
|
+
} else if (pr.checksStatus === "failure") {
|
|
8573
|
+
statusIcon = "\u{1F534}";
|
|
8574
|
+
}
|
|
8575
|
+
const ciIcons = {
|
|
8576
|
+
success: colors.green("\u2713"),
|
|
8577
|
+
failure: colors.red("\u2717"),
|
|
8578
|
+
pending: colors.yellow("\u23F3")
|
|
8579
|
+
};
|
|
8580
|
+
const ciText = pr.checksStatus ? ciIcons[pr.checksStatus] || "\u25CB" : "\u25CB";
|
|
8581
|
+
const reviewIcons = {
|
|
8582
|
+
approved: colors.green("\u2713 Approved"),
|
|
8583
|
+
changes_requested: colors.red("\u274C Changes"),
|
|
8584
|
+
review_required: colors.yellow("\u{1F440} Review pending")
|
|
8585
|
+
};
|
|
8586
|
+
const reviewText = pr.reviewDecision ? reviewIcons[pr.reviewDecision] : "";
|
|
8587
|
+
const authorText = isMine ? colors.green("@you") : colors.dim(`@${pr.author}`);
|
|
8588
|
+
const draftText = pr.isDraft ? colors.dim(" [draft]") : "";
|
|
8589
|
+
const title = pr.title.length > 35 ? pr.title.substring(0, 32) + "..." : pr.title;
|
|
8590
|
+
const branchInfo = colors.dim(`${pr.headBranch} \u2192 ${pr.baseBranch}`);
|
|
8303
8591
|
console.log(
|
|
8304
|
-
|
|
8592
|
+
` ${statusIcon} #${pr.number} ${title}${draftText}`
|
|
8593
|
+
);
|
|
8594
|
+
console.log(
|
|
8595
|
+
` ${branchInfo}`
|
|
8596
|
+
);
|
|
8597
|
+
console.log(
|
|
8598
|
+
` ${ciText} CI ${reviewText} ${authorText}`
|
|
8305
8599
|
);
|
|
8306
|
-
for (const pr of prs) {
|
|
8307
|
-
const isMine = currentUser && pr.author === currentUser;
|
|
8308
|
-
let statusIcon = "\u{1F7E1}";
|
|
8309
|
-
if (pr.checksStatus === "success" && pr.reviewDecision === "approved") {
|
|
8310
|
-
statusIcon = "\u{1F7E2}";
|
|
8311
|
-
} else if (pr.checksStatus === "failure") {
|
|
8312
|
-
statusIcon = "\u{1F534}";
|
|
8313
|
-
}
|
|
8314
|
-
const ciIcons = {
|
|
8315
|
-
success: colors.green("\u2713"),
|
|
8316
|
-
failure: colors.red("\u2717"),
|
|
8317
|
-
pending: colors.yellow("\u23F3")
|
|
8318
|
-
};
|
|
8319
|
-
const ciText = pr.checksStatus ? ciIcons[pr.checksStatus] || "\u25CB" : "\u25CB";
|
|
8320
|
-
const reviewIcons = {
|
|
8321
|
-
approved: colors.green("\u2713 Approved"),
|
|
8322
|
-
changes_requested: colors.red("\u274C Changes"),
|
|
8323
|
-
review_required: colors.yellow("\u{1F440} Review pending")
|
|
8324
|
-
};
|
|
8325
|
-
const reviewText = pr.reviewDecision ? reviewIcons[pr.reviewDecision] : "";
|
|
8326
|
-
const authorText = isMine ? colors.green("@you") : colors.dim(`@${pr.author}`);
|
|
8327
|
-
const draftText = pr.isDraft ? colors.dim(" [draft]") : "";
|
|
8328
|
-
const title = pr.title.length > 35 ? pr.title.substring(0, 32) + "..." : pr.title;
|
|
8329
|
-
const branchInfo = colors.dim(`${pr.headBranch} \u2192 ${pr.baseBranch}`);
|
|
8330
|
-
console.log(
|
|
8331
|
-
` ${statusIcon} #${pr.number} ${title}${draftText}`
|
|
8332
|
-
);
|
|
8333
|
-
console.log(
|
|
8334
|
-
` ${branchInfo}`
|
|
8335
|
-
);
|
|
8336
|
-
console.log(
|
|
8337
|
-
` ${ciText} CI ${reviewText} ${authorText}`
|
|
8338
|
-
);
|
|
8339
|
-
}
|
|
8340
|
-
log.newline();
|
|
8341
8600
|
}
|
|
8342
|
-
const summaryText = options.mine ? `Total: ${totalPRs} PRs (deine)` : `Total: ${totalPRs} PRs offen (${totalMyPRs} deine)`;
|
|
8343
|
-
log.dim(summaryText);
|
|
8344
8601
|
log.newline();
|
|
8345
|
-
log.dim("PR Session starten: shiva start --pr <nummer>");
|
|
8346
|
-
} catch (error) {
|
|
8347
|
-
spinner.fail("Fehler beim Laden der PRs");
|
|
8348
|
-
log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
|
|
8349
8602
|
}
|
|
8350
|
-
})
|
|
8603
|
+
const summaryText = options.mine ? `Total: ${totalPRs} PRs (deine)` : `Total: ${totalPRs} PRs offen (${totalMyPRs} deine)`;
|
|
8604
|
+
log.dim(summaryText);
|
|
8605
|
+
log.newline();
|
|
8606
|
+
log.dim("PR Session starten: shiva start --pr <nummer>");
|
|
8607
|
+
}));
|
|
8351
8608
|
|
|
8352
8609
|
// src/commands/security/scan.ts
|
|
8353
8610
|
import { Command as Command18 } from "commander";
|
|
8354
8611
|
import { readFile } from "fs/promises";
|
|
8355
8612
|
import { existsSync as existsSync12 } from "fs";
|
|
8356
|
-
import { join as
|
|
8613
|
+
import { join as join7, resolve as resolve7 } from "path";
|
|
8357
8614
|
|
|
8358
8615
|
// src/services/security/package-scanner.ts
|
|
8359
8616
|
import Conf4 from "conf";
|
|
@@ -9706,9 +9963,9 @@ async function parsePackageJson(filePath) {
|
|
|
9706
9963
|
}
|
|
9707
9964
|
const dir = resolve7(filePath, "..");
|
|
9708
9965
|
let manager = "npm";
|
|
9709
|
-
if (existsSync12(
|
|
9966
|
+
if (existsSync12(join7(dir, "pnpm-lock.yaml"))) {
|
|
9710
9967
|
manager = "pnpm";
|
|
9711
|
-
} else if (existsSync12(
|
|
9968
|
+
} else if (existsSync12(join7(dir, "yarn.lock"))) {
|
|
9712
9969
|
manager = "yarn";
|
|
9713
9970
|
}
|
|
9714
9971
|
return { packages, manager };
|
|
@@ -9809,15 +10066,15 @@ var scanCommand = new Command18("scan").description("Scanne Packages auf Sicherh
|
|
|
9809
10066
|
}
|
|
9810
10067
|
if (options.project) {
|
|
9811
10068
|
const cwd = process.cwd();
|
|
9812
|
-
if (existsSync12(
|
|
9813
|
-
const parsed = await parsePackageJson(
|
|
10069
|
+
if (existsSync12(join7(cwd, "package.json"))) {
|
|
10070
|
+
const parsed = await parsePackageJson(join7(cwd, "package.json"));
|
|
9814
10071
|
packagesToScan = parsed.packages;
|
|
9815
10072
|
manager = parsed.manager;
|
|
9816
|
-
} else if (existsSync12(
|
|
9817
|
-
packagesToScan = await parseRequirementsTxt(
|
|
10073
|
+
} else if (existsSync12(join7(cwd, "requirements.txt"))) {
|
|
10074
|
+
packagesToScan = await parseRequirementsTxt(join7(cwd, "requirements.txt"));
|
|
9818
10075
|
manager = "pip";
|
|
9819
|
-
} else if (existsSync12(
|
|
9820
|
-
packagesToScan = await parseCargoToml(
|
|
10076
|
+
} else if (existsSync12(join7(cwd, "Cargo.toml"))) {
|
|
10077
|
+
packagesToScan = await parseCargoToml(join7(cwd, "Cargo.toml"));
|
|
9821
10078
|
manager = "cargo";
|
|
9822
10079
|
} else {
|
|
9823
10080
|
log.error("No dependency file found in current directory");
|
|
@@ -10521,10 +10778,10 @@ import { Command as Command20 } from "commander";
|
|
|
10521
10778
|
import inquirer8 from "inquirer";
|
|
10522
10779
|
import ora13 from "ora";
|
|
10523
10780
|
import * as fs6 from "fs";
|
|
10524
|
-
import * as
|
|
10781
|
+
import * as path7 from "path";
|
|
10525
10782
|
|
|
10526
10783
|
// src/utils/clipboard.ts
|
|
10527
|
-
import { execSync as
|
|
10784
|
+
import { execSync as execSync2, spawnSync as spawnSync6 } from "child_process";
|
|
10528
10785
|
var CLIPBOARD_COMMANDS = [
|
|
10529
10786
|
// Wayland (modern Linux)
|
|
10530
10787
|
{ command: "wl-copy", args: [], check: "wl-copy" },
|
|
@@ -10537,7 +10794,7 @@ var CLIPBOARD_COMMANDS = [
|
|
|
10537
10794
|
];
|
|
10538
10795
|
function commandExists2(command) {
|
|
10539
10796
|
try {
|
|
10540
|
-
|
|
10797
|
+
execSync2(`which ${command}`, { stdio: "ignore" });
|
|
10541
10798
|
return true;
|
|
10542
10799
|
} catch {
|
|
10543
10800
|
return false;
|
|
@@ -10583,6 +10840,7 @@ function getClipboardInstallInstructions() {
|
|
|
10583
10840
|
}
|
|
10584
10841
|
|
|
10585
10842
|
// src/commands/security/secrets.ts
|
|
10843
|
+
var SECURE_FILE_MODE = 384;
|
|
10586
10844
|
var secretsCommand = new Command20("secrets").description("API Keys und Secrets verwalten (Cloud Vault)").action(async () => {
|
|
10587
10845
|
await listSecrets();
|
|
10588
10846
|
});
|
|
@@ -10737,7 +10995,7 @@ secretsCommand.command("remove").alias("rm").alias("delete").description("Secret
|
|
|
10737
10995
|
log.error(error instanceof Error ? error.message : "Fehler beim L\xF6schen");
|
|
10738
10996
|
}
|
|
10739
10997
|
});
|
|
10740
|
-
secretsCommand.command("get").description("Secret-Wert anzeigen (
|
|
10998
|
+
secretsCommand.command("get").description("Secret-Wert anzeigen (maskiert)").argument("<key>", "Secret-Name").option("-p, --project <id>", "Projekt-spezifisches Secret").option("-c, --copy", "In Zwischenablage kopieren").option("-r, --reveal", "Vollst\xE4ndigen Wert anzeigen (Sicherheitsrisiko!)").option("-q, --quiet", "Nur Wert ausgeben (f\xFCr Skripte)").action(async (key, options) => {
|
|
10741
10999
|
if (!isAuthenticated()) {
|
|
10742
11000
|
log.errorWithSuggestion(Errors.NOT_AUTHENTICATED());
|
|
10743
11001
|
return;
|
|
@@ -10789,7 +11047,12 @@ secretsCommand.command("get").description("Secret-Wert anzeigen (vorsicht!)").ar
|
|
|
10789
11047
|
}
|
|
10790
11048
|
log.newline();
|
|
10791
11049
|
log.keyValue("Key", result.key);
|
|
10792
|
-
|
|
11050
|
+
if (options.reveal) {
|
|
11051
|
+
log.keyValue("Value", result.value);
|
|
11052
|
+
} else {
|
|
11053
|
+
log.keyValue("Value", maskSecret(result.value));
|
|
11054
|
+
log.dim("Vollst\xE4ndiger Wert mit --reveal oder -r anzeigen");
|
|
11055
|
+
}
|
|
10793
11056
|
log.keyValue("Scope", result.scope);
|
|
10794
11057
|
log.newline();
|
|
10795
11058
|
} catch (error) {
|
|
@@ -10821,10 +11084,10 @@ secretsCommand.command("env").description("Secrets als .env Format ausgeben").op
|
|
|
10821
11084
|
spinner.stop();
|
|
10822
11085
|
const envContent = Object.entries(secrets).map(([key, value]) => `${key}="${value}"`).join("\n");
|
|
10823
11086
|
if (options.output) {
|
|
10824
|
-
|
|
10825
|
-
writeFileSync11(options.output, envContent + "\n", "utf-8");
|
|
11087
|
+
fs6.writeFileSync(options.output, envContent + "\n", { mode: SECURE_FILE_MODE });
|
|
10826
11088
|
log.success(`Secrets in ${options.output} geschrieben`);
|
|
10827
11089
|
log.warn("Diese Datei enth\xE4lt sensible Daten!");
|
|
11090
|
+
log.dim(`Datei-Berechtigungen auf ${SECURE_FILE_MODE.toString(8)} gesetzt`);
|
|
10828
11091
|
} else {
|
|
10829
11092
|
log.newline();
|
|
10830
11093
|
console.log(envContent);
|
|
@@ -10840,7 +11103,7 @@ secretsCommand.command("import").description(".env Datei in Vault importieren").
|
|
|
10840
11103
|
log.errorWithSuggestion(Errors.NOT_AUTHENTICATED());
|
|
10841
11104
|
return;
|
|
10842
11105
|
}
|
|
10843
|
-
const filePath =
|
|
11106
|
+
const filePath = path7.resolve(file);
|
|
10844
11107
|
if (!fs6.existsSync(filePath)) {
|
|
10845
11108
|
log.errorWithSuggestion(Errors.FILE_NOT_FOUND(file));
|
|
10846
11109
|
return;
|
|
@@ -10873,7 +11136,7 @@ secretsCommand.command("import").description(".env Datei in Vault importieren").
|
|
|
10873
11136
|
log.listItem(`${secret.key} = ${preview}`);
|
|
10874
11137
|
}
|
|
10875
11138
|
log.newline();
|
|
10876
|
-
log.dim(`${secrets.length} Secret(s) aus ${
|
|
11139
|
+
log.dim(`${secrets.length} Secret(s) aus ${path7.basename(file)}`);
|
|
10877
11140
|
log.newline();
|
|
10878
11141
|
if (options.dryRun) {
|
|
10879
11142
|
log.info("Dry-run: Keine \xC4nderungen vorgenommen");
|
|
@@ -10947,9 +11210,10 @@ secretsCommand.command("export").description("Secrets exportieren").option("-p,
|
|
|
10947
11210
|
}).join("\n");
|
|
10948
11211
|
}
|
|
10949
11212
|
if (options.output) {
|
|
10950
|
-
fs6.writeFileSync(options.output, output + "\n",
|
|
11213
|
+
fs6.writeFileSync(options.output, output + "\n", { mode: SECURE_FILE_MODE });
|
|
10951
11214
|
log.success(`Secrets in ${options.output} geschrieben`);
|
|
10952
11215
|
log.warn("Diese Datei enth\xE4lt sensible Daten!");
|
|
11216
|
+
log.dim(`Datei-Berechtigungen auf ${SECURE_FILE_MODE.toString(8)} gesetzt`);
|
|
10953
11217
|
log.dim("Zur .gitignore hinzuf\xFCgen!");
|
|
10954
11218
|
} else {
|
|
10955
11219
|
log.newline();
|
|
@@ -10969,22 +11233,22 @@ import ora14 from "ora";
|
|
|
10969
11233
|
|
|
10970
11234
|
// src/services/data/memory.ts
|
|
10971
11235
|
import * as fs7 from "fs";
|
|
10972
|
-
import * as
|
|
10973
|
-
import * as
|
|
11236
|
+
import * as path8 from "path";
|
|
11237
|
+
import * as os5 from "os";
|
|
10974
11238
|
function findAllClaudeMdFiles() {
|
|
10975
11239
|
const results = [];
|
|
10976
|
-
const sessionsPath =
|
|
11240
|
+
const sessionsPath = path8.join(os5.homedir(), ".claude", "projects");
|
|
10977
11241
|
if (fs7.existsSync(sessionsPath)) {
|
|
10978
11242
|
const dirs = fs7.readdirSync(sessionsPath);
|
|
10979
11243
|
for (const dir of dirs) {
|
|
10980
11244
|
const projectPath = decodeProjectPath(dir);
|
|
10981
11245
|
if (projectPath && fs7.existsSync(projectPath)) {
|
|
10982
|
-
const claudeMdPath =
|
|
11246
|
+
const claudeMdPath = path8.join(projectPath, "CLAUDE.md");
|
|
10983
11247
|
if (fs7.existsSync(claudeMdPath)) {
|
|
10984
11248
|
results.push({
|
|
10985
11249
|
path: claudeMdPath,
|
|
10986
11250
|
projectPath,
|
|
10987
|
-
projectName:
|
|
11251
|
+
projectName: path8.basename(projectPath)
|
|
10988
11252
|
});
|
|
10989
11253
|
}
|
|
10990
11254
|
}
|
|
@@ -11096,7 +11360,7 @@ async function searchMemories(query, options = {}) {
|
|
|
11096
11360
|
};
|
|
11097
11361
|
}
|
|
11098
11362
|
function deleteLocalMemory(projectPath, key) {
|
|
11099
|
-
const claudeMdPath =
|
|
11363
|
+
const claudeMdPath = path8.join(projectPath, "CLAUDE.md");
|
|
11100
11364
|
if (!fs7.existsSync(claudeMdPath)) {
|
|
11101
11365
|
return false;
|
|
11102
11366
|
}
|
|
@@ -11146,7 +11410,7 @@ async function deleteMemory(projectPath, key, options = {}) {
|
|
|
11146
11410
|
}
|
|
11147
11411
|
async function deleteAllProjectMemories(projectPath) {
|
|
11148
11412
|
const result = { local: 0, cloud: 0 };
|
|
11149
|
-
const claudeMdPath =
|
|
11413
|
+
const claudeMdPath = path8.join(projectPath, "CLAUDE.md");
|
|
11150
11414
|
if (fs7.existsSync(claudeMdPath)) {
|
|
11151
11415
|
try {
|
|
11152
11416
|
let content = fs7.readFileSync(claudeMdPath, "utf-8");
|
|
@@ -11175,8 +11439,8 @@ async function deleteAllProjectMemories(projectPath) {
|
|
|
11175
11439
|
return result;
|
|
11176
11440
|
}
|
|
11177
11441
|
async function getContextPreview(projectPath) {
|
|
11178
|
-
const projectName =
|
|
11179
|
-
const claudeMdPath =
|
|
11442
|
+
const projectName = path8.basename(projectPath);
|
|
11443
|
+
const claudeMdPath = path8.join(projectPath, "CLAUDE.md");
|
|
11180
11444
|
const preview = {
|
|
11181
11445
|
projectName,
|
|
11182
11446
|
projectPath,
|
|
@@ -11198,8 +11462,8 @@ async function getContextPreview(projectPath) {
|
|
|
11198
11462
|
projectPath
|
|
11199
11463
|
}));
|
|
11200
11464
|
}
|
|
11201
|
-
const shivaDir =
|
|
11202
|
-
const contextPath =
|
|
11465
|
+
const shivaDir = path8.join(projectPath, ".shiva");
|
|
11466
|
+
const contextPath = path8.join(shivaDir, "github-context.md");
|
|
11203
11467
|
if (fs7.existsSync(contextPath)) {
|
|
11204
11468
|
const content = fs7.readFileSync(contextPath, "utf-8");
|
|
11205
11469
|
preview.githubContext = content;
|
|
@@ -11293,7 +11557,7 @@ function escapeRegex2(str) {
|
|
|
11293
11557
|
|
|
11294
11558
|
// src/commands/memory/forget.ts
|
|
11295
11559
|
import { Command as Command22 } from "commander";
|
|
11296
|
-
import * as
|
|
11560
|
+
import * as path9 from "path";
|
|
11297
11561
|
import inquirer9 from "inquirer";
|
|
11298
11562
|
import ora15 from "ora";
|
|
11299
11563
|
var forgetCommand = new Command22("forget").description("Memories l\xF6schen (GDPR)").argument("[key]", "Memory-Key zum L\xF6schen").option("-p, --project <path>", "Projekt-Pfad").option("--all", "Alle Memories eines Projekts l\xF6schen").option("--search <query>", "Memories nach Suchbegriff l\xF6schen").option("--local-only", "Nur lokal l\xF6schen").option("--cloud-only", "Nur aus Cloud l\xF6schen").option("-f, --force", "Ohne Best\xE4tigung l\xF6schen").option("--dry-run", "Nur anzeigen, was gel\xF6scht w\xFCrde").action(async (key, options) => {
|
|
@@ -11301,7 +11565,7 @@ var forgetCommand = new Command22("forget").description("Memories l\xF6schen (GD
|
|
|
11301
11565
|
console.log(colors.orange.bold("SHIVA Code - Forget"));
|
|
11302
11566
|
console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11303
11567
|
log.newline();
|
|
11304
|
-
const projectPath = options.project ?
|
|
11568
|
+
const projectPath = options.project ? path9.resolve(options.project) : process.cwd();
|
|
11305
11569
|
if (options.all) {
|
|
11306
11570
|
await handleDeleteAll(projectPath, options);
|
|
11307
11571
|
return;
|
|
@@ -11317,7 +11581,7 @@ var forgetCommand = new Command22("forget").description("Memories l\xF6schen (GD
|
|
|
11317
11581
|
await handleInteractiveDelete(projectPath, options);
|
|
11318
11582
|
});
|
|
11319
11583
|
async function handleDeleteAll(projectPath, options) {
|
|
11320
|
-
const projectName =
|
|
11584
|
+
const projectName = path9.basename(projectPath);
|
|
11321
11585
|
console.log(colors.red.bold("\u26A0\uFE0F WARNUNG: Alle Memories l\xF6schen"));
|
|
11322
11586
|
console.log(colors.dim(`Projekt: ${projectName}`));
|
|
11323
11587
|
console.log(colors.dim(`Pfad: ${projectPath}`));
|
|
@@ -11423,7 +11687,7 @@ async function handleDeleteBySearch(query, projectPath, options) {
|
|
|
11423
11687
|
}
|
|
11424
11688
|
async function handleDeleteKey(key, projectPath, options) {
|
|
11425
11689
|
console.log(colors.bold(`Memory l\xF6schen: ${key}`));
|
|
11426
|
-
console.log(colors.dim(`Projekt: ${
|
|
11690
|
+
console.log(colors.dim(`Projekt: ${path9.basename(projectPath)}`));
|
|
11427
11691
|
log.newline();
|
|
11428
11692
|
if (options.dryRun) {
|
|
11429
11693
|
log.info("Dry-run: Keine \xC4nderungen werden vorgenommen");
|
|
@@ -11538,12 +11802,12 @@ function truncate(str, maxLen) {
|
|
|
11538
11802
|
|
|
11539
11803
|
// src/commands/memory/context.ts
|
|
11540
11804
|
import { Command as Command23 } from "commander";
|
|
11541
|
-
import * as
|
|
11805
|
+
import * as path10 from "path";
|
|
11542
11806
|
import * as fs8 from "fs";
|
|
11543
11807
|
import ora16 from "ora";
|
|
11544
11808
|
var contextCommand = new Command23("context").description("Zeigt was in Claude injected w\xFCrde").option("-d, --dir <path>", "Projektverzeichnis").option("--github", "GitHub Context einschlie\xDFen").option("--secrets", "Secret-Keys anzeigen").option("--raw", "Rohe CLAUDE.md Ausgabe").option("--json", "JSON Ausgabe").option("-s, --size", "Nur Gr\xF6\xDFe anzeigen").action(async (options) => {
|
|
11545
|
-
const projectPath = options.dir ?
|
|
11546
|
-
const projectName =
|
|
11809
|
+
const projectPath = options.dir ? path10.resolve(options.dir) : process.cwd();
|
|
11810
|
+
const projectName = path10.basename(projectPath);
|
|
11547
11811
|
if (!fs8.existsSync(projectPath)) {
|
|
11548
11812
|
log.error(`Verzeichnis nicht gefunden: ${projectPath}`);
|
|
11549
11813
|
return;
|
|
@@ -11927,15 +12191,15 @@ async function resolveSessionId(input) {
|
|
|
11927
12191
|
|
|
11928
12192
|
// src/commands/memory/export.ts
|
|
11929
12193
|
import { Command as Command25 } from "commander";
|
|
11930
|
-
import * as
|
|
12194
|
+
import * as path12 from "path";
|
|
11931
12195
|
import * as fs10 from "fs";
|
|
11932
12196
|
import ora17 from "ora";
|
|
11933
12197
|
import inquirer11 from "inquirer";
|
|
11934
12198
|
|
|
11935
12199
|
// src/services/session/export.ts
|
|
11936
12200
|
import * as fs9 from "fs";
|
|
11937
|
-
import * as
|
|
11938
|
-
import { execSync as
|
|
12201
|
+
import * as path11 from "path";
|
|
12202
|
+
import { execSync as execSync3 } from "child_process";
|
|
11939
12203
|
async function exportSession(sessionId, options = {}) {
|
|
11940
12204
|
const projects = await getAllClaudeProjects();
|
|
11941
12205
|
for (const project of projects) {
|
|
@@ -11950,7 +12214,7 @@ async function exportSession(sessionId, options = {}) {
|
|
|
11950
12214
|
tags: getSessionTags(sessionId)
|
|
11951
12215
|
};
|
|
11952
12216
|
if (options.includeTranscript) {
|
|
11953
|
-
const transcriptPath =
|
|
12217
|
+
const transcriptPath = path11.join(
|
|
11954
12218
|
getClaudeProjectsPath(),
|
|
11955
12219
|
encodeProjectPath(project.absolutePath),
|
|
11956
12220
|
session.sessionId + ".jsonl"
|
|
@@ -11960,7 +12224,7 @@ async function exportSession(sessionId, options = {}) {
|
|
|
11960
12224
|
}
|
|
11961
12225
|
}
|
|
11962
12226
|
if (options.includeConversation && options.includeTranscript) {
|
|
11963
|
-
const transcriptPath =
|
|
12227
|
+
const transcriptPath = path11.join(
|
|
11964
12228
|
getClaudeProjectsPath(),
|
|
11965
12229
|
encodeProjectPath(project.absolutePath),
|
|
11966
12230
|
session.sessionId + ".jsonl"
|
|
@@ -12001,7 +12265,7 @@ async function exportSessions(sessionIds, outputDir, options = {}) {
|
|
|
12001
12265
|
const exported = await exportSession(sessionId, options);
|
|
12002
12266
|
if (exported) {
|
|
12003
12267
|
const filename = `${exported.projectName}-${sessionId.slice(0, 8)}.json`;
|
|
12004
|
-
const filepath =
|
|
12268
|
+
const filepath = path11.join(outputDir, filename);
|
|
12005
12269
|
fs9.writeFileSync(filepath, JSON.stringify(exported, null, 2));
|
|
12006
12270
|
success++;
|
|
12007
12271
|
} else {
|
|
@@ -12031,7 +12295,7 @@ function createBackupArchive(outputPath, projectPaths) {
|
|
|
12031
12295
|
if (projectPaths && projectPaths.length > 0) {
|
|
12032
12296
|
for (const p of projectPaths) {
|
|
12033
12297
|
const encoded = encodeProjectPath(p);
|
|
12034
|
-
const fullPath =
|
|
12298
|
+
const fullPath = path11.join(claudeDir, encoded);
|
|
12035
12299
|
if (fs9.existsSync(fullPath)) {
|
|
12036
12300
|
includePaths.push(encoded);
|
|
12037
12301
|
}
|
|
@@ -12039,7 +12303,7 @@ function createBackupArchive(outputPath, projectPaths) {
|
|
|
12039
12303
|
} else {
|
|
12040
12304
|
const entries = fs9.readdirSync(claudeDir);
|
|
12041
12305
|
includePaths = entries.filter((e) => {
|
|
12042
|
-
const stat = fs9.statSync(
|
|
12306
|
+
const stat = fs9.statSync(path11.join(claudeDir, e));
|
|
12043
12307
|
return stat.isDirectory();
|
|
12044
12308
|
});
|
|
12045
12309
|
}
|
|
@@ -12047,7 +12311,7 @@ function createBackupArchive(outputPath, projectPaths) {
|
|
|
12047
12311
|
return false;
|
|
12048
12312
|
}
|
|
12049
12313
|
const tarArgs = includePaths.join(" ");
|
|
12050
|
-
|
|
12314
|
+
execSync3(`tar -czf "${outputPath}" -C "${claudeDir}" ${tarArgs}`, {
|
|
12051
12315
|
stdio: "ignore"
|
|
12052
12316
|
});
|
|
12053
12317
|
return true;
|
|
@@ -12059,11 +12323,11 @@ async function importSession(data, targetProjectPath) {
|
|
|
12059
12323
|
try {
|
|
12060
12324
|
const projectPath = targetProjectPath || data.projectPath;
|
|
12061
12325
|
const encodedPath = encodeProjectPath(projectPath);
|
|
12062
|
-
const sessionDir =
|
|
12326
|
+
const sessionDir = path11.join(getClaudeProjectsPath(), encodedPath);
|
|
12063
12327
|
if (!fs9.existsSync(sessionDir)) {
|
|
12064
12328
|
fs9.mkdirSync(sessionDir, { recursive: true });
|
|
12065
12329
|
}
|
|
12066
|
-
const sessionFile =
|
|
12330
|
+
const sessionFile = path11.join(sessionDir, data.session.sessionId + ".jsonl");
|
|
12067
12331
|
if (fs9.existsSync(sessionFile)) {
|
|
12068
12332
|
return {
|
|
12069
12333
|
success: false,
|
|
@@ -12135,7 +12399,7 @@ async function importSessionsFromDirectory(dirPath, targetProjectPath) {
|
|
|
12135
12399
|
let success = 0;
|
|
12136
12400
|
let failed = 0;
|
|
12137
12401
|
for (const file of files) {
|
|
12138
|
-
const filepath =
|
|
12402
|
+
const filepath = path11.join(dirPath, file);
|
|
12139
12403
|
const result = await importSessionFromFile(filepath, targetProjectPath);
|
|
12140
12404
|
results.push(result);
|
|
12141
12405
|
if (result.success) {
|
|
@@ -12155,7 +12419,7 @@ function restoreFromArchive(archivePath, targetDir) {
|
|
|
12155
12419
|
if (!fs9.existsSync(extractTo)) {
|
|
12156
12420
|
fs9.mkdirSync(extractTo, { recursive: true });
|
|
12157
12421
|
}
|
|
12158
|
-
|
|
12422
|
+
execSync3(`tar -xzf "${archivePath}" -C "${extractTo}"`, {
|
|
12159
12423
|
stdio: "ignore"
|
|
12160
12424
|
});
|
|
12161
12425
|
return true;
|
|
@@ -12213,7 +12477,7 @@ var importCommand = new Command25("import").description("Sessions importieren").
|
|
|
12213
12477
|
console.log(colors.orange.bold("SHIVA Code - Import"));
|
|
12214
12478
|
console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
12215
12479
|
log.newline();
|
|
12216
|
-
const absolutePath =
|
|
12480
|
+
const absolutePath = path12.resolve(inputPath);
|
|
12217
12481
|
if (!fs10.existsSync(absolutePath)) {
|
|
12218
12482
|
log.error(`Datei nicht gefunden: ${absolutePath}`);
|
|
12219
12483
|
return;
|
|
@@ -12236,7 +12500,7 @@ var importCommand = new Command25("import").description("Sessions importieren").
|
|
|
12236
12500
|
async function handleBackup(outputPath) {
|
|
12237
12501
|
const spinner = ora17("Erstelle Backup...").start();
|
|
12238
12502
|
const filename = `shiva-backup-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.tar.gz`;
|
|
12239
|
-
const output = outputPath ||
|
|
12503
|
+
const output = outputPath || path12.join(process.cwd(), filename);
|
|
12240
12504
|
const success = createBackupArchive(output);
|
|
12241
12505
|
if (success) {
|
|
12242
12506
|
spinner.succeed(`Backup erstellt: ${output}`);
|
|
@@ -12255,7 +12519,7 @@ async function handleExportAll(outputDir, options = {}) {
|
|
|
12255
12519
|
return;
|
|
12256
12520
|
}
|
|
12257
12521
|
spinner.text = `Exportiere ${allSessionIds.length} Sessions...`;
|
|
12258
|
-
const output = outputDir ||
|
|
12522
|
+
const output = outputDir || path12.join(process.cwd(), "shiva-export");
|
|
12259
12523
|
const result = await exportSessions(allSessionIds, output, options);
|
|
12260
12524
|
spinner.succeed(`Export abgeschlossen`);
|
|
12261
12525
|
log.info(`${result.success} Sessions exportiert`);
|
|
@@ -12279,7 +12543,7 @@ async function handleExportProject(projectName, outputDir, options = {}) {
|
|
|
12279
12543
|
return;
|
|
12280
12544
|
}
|
|
12281
12545
|
spinner.text = `Exportiere ${project.sessions.length} Sessions aus ${project.projectName}...`;
|
|
12282
|
-
const output = outputDir ||
|
|
12546
|
+
const output = outputDir || path12.join(process.cwd(), `${project.projectName}-export`);
|
|
12283
12547
|
const result = await exportProjectSessions(project.absolutePath, output, options);
|
|
12284
12548
|
spinner.succeed(`Export abgeschlossen`);
|
|
12285
12549
|
log.info(`${result.success} Sessions exportiert`);
|
|
@@ -12401,20 +12665,20 @@ async function handlePreview(filepath) {
|
|
|
12401
12665
|
|
|
12402
12666
|
// src/commands/system/doctor.ts
|
|
12403
12667
|
import { Command as Command26 } from "commander";
|
|
12404
|
-
import { execSync as
|
|
12668
|
+
import { execSync as execSync4 } from "child_process";
|
|
12405
12669
|
import * as fs11 from "fs";
|
|
12406
|
-
import * as
|
|
12407
|
-
import * as
|
|
12670
|
+
import * as path13 from "path";
|
|
12671
|
+
import * as os6 from "os";
|
|
12408
12672
|
function tryExec(command) {
|
|
12409
12673
|
try {
|
|
12410
|
-
return
|
|
12674
|
+
return execSync4(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12411
12675
|
} catch {
|
|
12412
12676
|
return null;
|
|
12413
12677
|
}
|
|
12414
12678
|
}
|
|
12415
12679
|
function commandExists3(cmd) {
|
|
12416
12680
|
try {
|
|
12417
|
-
|
|
12681
|
+
execSync4(`which ${cmd}`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
12418
12682
|
return true;
|
|
12419
12683
|
} catch {
|
|
12420
12684
|
return false;
|
|
@@ -12565,8 +12829,8 @@ function checkNpm() {
|
|
|
12565
12829
|
};
|
|
12566
12830
|
}
|
|
12567
12831
|
function checkShivaConfig() {
|
|
12568
|
-
const configDir =
|
|
12569
|
-
const configFile =
|
|
12832
|
+
const configDir = path13.join(os6.homedir(), ".config", "shiva-code");
|
|
12833
|
+
const configFile = path13.join(configDir, "config.json");
|
|
12570
12834
|
if (!fs11.existsSync(configDir)) {
|
|
12571
12835
|
return {
|
|
12572
12836
|
name: "SHIVA Config",
|
|
@@ -12590,7 +12854,7 @@ function checkShivaConfig() {
|
|
|
12590
12854
|
};
|
|
12591
12855
|
}
|
|
12592
12856
|
function checkClaudeProjects() {
|
|
12593
|
-
const claudeDir =
|
|
12857
|
+
const claudeDir = path13.join(os6.homedir(), ".claude", "projects");
|
|
12594
12858
|
if (!fs11.existsSync(claudeDir)) {
|
|
12595
12859
|
return {
|
|
12596
12860
|
name: "Claude Projects",
|
|
@@ -12601,7 +12865,7 @@ function checkClaudeProjects() {
|
|
|
12601
12865
|
}
|
|
12602
12866
|
try {
|
|
12603
12867
|
const projects = fs11.readdirSync(claudeDir).filter(
|
|
12604
|
-
(f) => fs11.statSync(
|
|
12868
|
+
(f) => fs11.statSync(path13.join(claudeDir, f)).isDirectory()
|
|
12605
12869
|
);
|
|
12606
12870
|
return {
|
|
12607
12871
|
name: "Claude Projects",
|
|
@@ -12634,11 +12898,11 @@ function checkTmux() {
|
|
|
12634
12898
|
};
|
|
12635
12899
|
}
|
|
12636
12900
|
function getSystemInfo() {
|
|
12637
|
-
const totalMem =
|
|
12901
|
+
const totalMem = os6.totalmem();
|
|
12638
12902
|
const memGB = (totalMem / (1024 * 1024 * 1024)).toFixed(1);
|
|
12639
12903
|
return {
|
|
12640
|
-
os: `${
|
|
12641
|
-
arch:
|
|
12904
|
+
os: `${os6.type()} ${os6.release()}`,
|
|
12905
|
+
arch: os6.arch(),
|
|
12642
12906
|
memory: `${memGB} GB`
|
|
12643
12907
|
};
|
|
12644
12908
|
}
|
|
@@ -12728,16 +12992,16 @@ var doctorCommand = new Command26("doctor").description("System-Check f\xFCr SHI
|
|
|
12728
12992
|
|
|
12729
12993
|
// src/commands/system/upgrade.ts
|
|
12730
12994
|
import { Command as Command27 } from "commander";
|
|
12731
|
-
import { execSync as
|
|
12995
|
+
import { execSync as execSync5, spawn as spawn6 } from "child_process";
|
|
12732
12996
|
import * as fs12 from "fs";
|
|
12733
|
-
import * as
|
|
12997
|
+
import * as path14 from "path";
|
|
12734
12998
|
import { fileURLToPath } from "url";
|
|
12735
12999
|
var __filename = fileURLToPath(import.meta.url);
|
|
12736
|
-
var __dirname =
|
|
13000
|
+
var __dirname = path14.dirname(__filename);
|
|
12737
13001
|
var PACKAGE_NAME = "shiva-code";
|
|
12738
13002
|
function getCurrentVersion() {
|
|
12739
13003
|
try {
|
|
12740
|
-
const packageJsonPath =
|
|
13004
|
+
const packageJsonPath = path14.resolve(__dirname, "../../package.json");
|
|
12741
13005
|
if (fs12.existsSync(packageJsonPath)) {
|
|
12742
13006
|
const packageJson = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
|
|
12743
13007
|
return packageJson.version || "0.0.0";
|
|
@@ -12754,7 +13018,7 @@ async function getLatestVersion() {
|
|
|
12754
13018
|
return data["dist-tags"]?.latest || null;
|
|
12755
13019
|
} catch {
|
|
12756
13020
|
try {
|
|
12757
|
-
const output =
|
|
13021
|
+
const output = execSync5(`npm view ${PACKAGE_NAME} version 2>/dev/null`, {
|
|
12758
13022
|
encoding: "utf-8"
|
|
12759
13023
|
}).trim();
|
|
12760
13024
|
return output || null;
|
|
@@ -12785,17 +13049,17 @@ async function checkForUpdates() {
|
|
|
12785
13049
|
}
|
|
12786
13050
|
function detectPackageManager2() {
|
|
12787
13051
|
try {
|
|
12788
|
-
const npmGlobal =
|
|
13052
|
+
const npmGlobal = execSync5("npm list -g --depth=0 2>/dev/null", { encoding: "utf-8" });
|
|
12789
13053
|
if (npmGlobal.includes(PACKAGE_NAME)) return "npm";
|
|
12790
13054
|
} catch {
|
|
12791
13055
|
}
|
|
12792
13056
|
try {
|
|
12793
|
-
const yarnGlobal =
|
|
13057
|
+
const yarnGlobal = execSync5("yarn global list 2>/dev/null", { encoding: "utf-8" });
|
|
12794
13058
|
if (yarnGlobal.includes(PACKAGE_NAME)) return "yarn";
|
|
12795
13059
|
} catch {
|
|
12796
13060
|
}
|
|
12797
13061
|
try {
|
|
12798
|
-
const pnpmGlobal =
|
|
13062
|
+
const pnpmGlobal = execSync5("pnpm list -g 2>/dev/null", { encoding: "utf-8" });
|
|
12799
13063
|
if (pnpmGlobal.includes(PACKAGE_NAME)) return "pnpm";
|
|
12800
13064
|
} catch {
|
|
12801
13065
|
}
|
|
@@ -13137,9 +13401,9 @@ complete -F _shiva_completions shiva
|
|
|
13137
13401
|
}
|
|
13138
13402
|
async function installBashCompletion() {
|
|
13139
13403
|
const fs15 = await import("fs");
|
|
13140
|
-
const
|
|
13141
|
-
const
|
|
13142
|
-
const bashrcPath =
|
|
13404
|
+
const os8 = await import("os");
|
|
13405
|
+
const path16 = await import("path");
|
|
13406
|
+
const bashrcPath = path16.join(os8.homedir(), ".bashrc");
|
|
13143
13407
|
const completionScript = generateBashCompletion();
|
|
13144
13408
|
const marker = "# SHIVA Code Bash Completion";
|
|
13145
13409
|
try {
|
|
@@ -13280,9 +13544,9 @@ compdef _shiva shiva
|
|
|
13280
13544
|
}
|
|
13281
13545
|
async function installZshCompletion() {
|
|
13282
13546
|
const fs15 = await import("fs");
|
|
13283
|
-
const
|
|
13284
|
-
const
|
|
13285
|
-
const zshrcPath =
|
|
13547
|
+
const os8 = await import("os");
|
|
13548
|
+
const path16 = await import("path");
|
|
13549
|
+
const zshrcPath = path16.join(os8.homedir(), ".zshrc");
|
|
13286
13550
|
const completionScript = generateZshCompletion();
|
|
13287
13551
|
const marker = "# SHIVA Code Zsh Completion";
|
|
13288
13552
|
try {
|
|
@@ -13385,10 +13649,10 @@ complete -c shiva -n "__fish_seen_subcommand_from stats" -l json -d "JSON Output
|
|
|
13385
13649
|
}
|
|
13386
13650
|
async function installFishCompletion() {
|
|
13387
13651
|
const fs15 = await import("fs");
|
|
13388
|
-
const
|
|
13389
|
-
const
|
|
13390
|
-
const fishCompletionsDir =
|
|
13391
|
-
const fishCompletionPath =
|
|
13652
|
+
const os8 = await import("os");
|
|
13653
|
+
const path16 = await import("path");
|
|
13654
|
+
const fishCompletionsDir = path16.join(os8.homedir(), ".config", "fish", "completions");
|
|
13655
|
+
const fishCompletionPath = path16.join(fishCompletionsDir, "shiva.fish");
|
|
13392
13656
|
const completionScript = generateFishCompletion();
|
|
13393
13657
|
try {
|
|
13394
13658
|
if (!fs15.existsSync(fishCompletionsDir)) {
|
|
@@ -13796,8 +14060,8 @@ dockerCommand.action(() => {
|
|
|
13796
14060
|
// src/commands/advanced/workflow.ts
|
|
13797
14061
|
import { Command as Command32 } from "commander";
|
|
13798
14062
|
import * as fs13 from "fs";
|
|
13799
|
-
import * as
|
|
13800
|
-
import * as
|
|
14063
|
+
import * as path15 from "path";
|
|
14064
|
+
import * as os7 from "os";
|
|
13801
14065
|
import ora20 from "ora";
|
|
13802
14066
|
import inquirer12 from "inquirer";
|
|
13803
14067
|
var builtInWorkflows = {
|
|
@@ -13956,7 +14220,7 @@ workflowCommand.command("delete").alias("rm").description("Workflow l\xF6schen")
|
|
|
13956
14220
|
log.success(`Workflow "${name}" gel\xF6scht`);
|
|
13957
14221
|
});
|
|
13958
14222
|
function getWorkflowsPath() {
|
|
13959
|
-
return
|
|
14223
|
+
return path15.join(os7.homedir(), ".shiva", "workflows.json");
|
|
13960
14224
|
}
|
|
13961
14225
|
function loadCustomWorkflows() {
|
|
13962
14226
|
const filepath = getWorkflowsPath();
|
|
@@ -13972,7 +14236,7 @@ function loadCustomWorkflows() {
|
|
|
13972
14236
|
}
|
|
13973
14237
|
function saveCustomWorkflow(name, workflow) {
|
|
13974
14238
|
const filepath = getWorkflowsPath();
|
|
13975
|
-
const dir =
|
|
14239
|
+
const dir = path15.dirname(filepath);
|
|
13976
14240
|
if (!fs13.existsSync(dir)) {
|
|
13977
14241
|
fs13.mkdirSync(dir, { recursive: true });
|
|
13978
14242
|
}
|
|
@@ -14075,9 +14339,9 @@ async function executeStep(step) {
|
|
|
14075
14339
|
// src/commands/advanced/hook.ts
|
|
14076
14340
|
import { Command as Command33 } from "commander";
|
|
14077
14341
|
import { existsSync as existsSync21, readFileSync as readFileSync10, writeFileSync as writeFileSync10, mkdirSync as mkdirSync6 } from "fs";
|
|
14078
|
-
import { homedir as
|
|
14079
|
-
import { join as
|
|
14080
|
-
var CLAUDE_SETTINGS_PATH =
|
|
14342
|
+
import { homedir as homedir8 } from "os";
|
|
14343
|
+
import { join as join13 } from "path";
|
|
14344
|
+
var CLAUDE_SETTINGS_PATH = join13(homedir8(), ".claude", "settings.json");
|
|
14081
14345
|
function getClaudeSettings() {
|
|
14082
14346
|
if (!existsSync21(CLAUDE_SETTINGS_PATH)) {
|
|
14083
14347
|
return {};
|
|
@@ -14090,7 +14354,7 @@ function getClaudeSettings() {
|
|
|
14090
14354
|
}
|
|
14091
14355
|
}
|
|
14092
14356
|
function saveClaudeSettings(settings) {
|
|
14093
|
-
const dir =
|
|
14357
|
+
const dir = join13(homedir8(), ".claude");
|
|
14094
14358
|
if (!existsSync21(dir)) {
|
|
14095
14359
|
mkdirSync6(dir, { recursive: true });
|
|
14096
14360
|
}
|
|
@@ -14111,7 +14375,7 @@ function removeShivaHooks(eventHooks) {
|
|
|
14111
14375
|
var hookCommand = new Command33("hook").description("Claude Code Hook Integration verwalten");
|
|
14112
14376
|
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) => {
|
|
14113
14377
|
log.brand();
|
|
14114
|
-
const claudePath =
|
|
14378
|
+
const claudePath = join13(homedir8(), ".claude");
|
|
14115
14379
|
if (!existsSync21(claudePath)) {
|
|
14116
14380
|
log.error("Claude Code nicht gefunden");
|
|
14117
14381
|
log.newline();
|
|
@@ -14378,9 +14642,9 @@ hookCommand.command("branch-switch").description("Branch-Wechsel behandeln (f\xF
|
|
|
14378
14642
|
if (options.quiet) {
|
|
14379
14643
|
return;
|
|
14380
14644
|
}
|
|
14381
|
-
const { getCurrentBranch: getCurrentBranch2 } = await import("./api-
|
|
14645
|
+
const { getCurrentBranch: getCurrentBranch2 } = await import("./api-OEHQTBH7.js");
|
|
14382
14646
|
const { getSessionForBranch: getSessionForBranch2, hasShivaDir: hasShivaDir2 } = await import("./config-D6M6LI6U.js");
|
|
14383
|
-
const { findProject:
|
|
14647
|
+
const { findProject: findProject3, findSessionByBranch: findSessionByBranch2, formatRelativeTime: formatRelativeTime3 } = await import("./manager-ZPQWG7E6.js");
|
|
14384
14648
|
const projectPath = process.cwd();
|
|
14385
14649
|
const newBranch = getCurrentBranch2(projectPath);
|
|
14386
14650
|
let sessionInfo = null;
|
|
@@ -14394,7 +14658,7 @@ hookCommand.command("branch-switch").description("Branch-Wechsel behandeln (f\xF
|
|
|
14394
14658
|
}
|
|
14395
14659
|
}
|
|
14396
14660
|
if (!sessionInfo) {
|
|
14397
|
-
const project = await
|
|
14661
|
+
const project = await findProject3(projectPath);
|
|
14398
14662
|
if (project) {
|
|
14399
14663
|
const indexSession = findSessionByBranch2(project, newBranch);
|
|
14400
14664
|
if (indexSession) {
|
|
@@ -14632,9 +14896,14 @@ function listPackages() {
|
|
|
14632
14896
|
|
|
14633
14897
|
// src/index.ts
|
|
14634
14898
|
var program = new Command35();
|
|
14635
|
-
program.name("shiva").description("SHIVA Code - Control Station for Claude Code").version("0.
|
|
14899
|
+
program.name("shiva").description("SHIVA Code - Control Station for Claude Code").version("0.5.1");
|
|
14636
14900
|
program.addCommand(loginCommand);
|
|
14637
14901
|
program.addCommand(logoutCommand);
|
|
14902
|
+
program.addCommand(sessionsCommand);
|
|
14903
|
+
program.addCommand(resumeCommand);
|
|
14904
|
+
program.addCommand(restoreCommand);
|
|
14905
|
+
program.addCommand(startCommand);
|
|
14906
|
+
program.addCommand(sessionCommand);
|
|
14638
14907
|
program.addCommand(initCommand);
|
|
14639
14908
|
program.addCommand(syncCommand);
|
|
14640
14909
|
program.addCommand(statusCommand);
|
|
@@ -14642,60 +14911,59 @@ program.addCommand(projectsCommand);
|
|
|
14642
14911
|
program.addCommand(connectCommand);
|
|
14643
14912
|
program.addCommand(disconnectCommand);
|
|
14644
14913
|
program.addCommand(configCommand);
|
|
14645
|
-
program.addCommand(
|
|
14646
|
-
program.addCommand(sessionsCommand);
|
|
14647
|
-
program.addCommand(resumeCommand);
|
|
14648
|
-
program.addCommand(restoreCommand);
|
|
14649
|
-
program.addCommand(startCommand);
|
|
14650
|
-
program.addCommand(packageCommand);
|
|
14914
|
+
program.addCommand(projectCommand);
|
|
14651
14915
|
program.addCommand(githubCommand);
|
|
14652
14916
|
program.addCommand(issuesCommand);
|
|
14653
14917
|
program.addCommand(prsCommand);
|
|
14654
14918
|
program.addCommand(secretsCommand);
|
|
14655
|
-
program.addCommand(
|
|
14656
|
-
program.addCommand(
|
|
14657
|
-
program.addCommand(completionsCommand);
|
|
14658
|
-
program.addCommand(tagsCommand);
|
|
14659
|
-
program.addCommand(exportCommand);
|
|
14660
|
-
program.addCommand(importCommand);
|
|
14919
|
+
program.addCommand(scanCommand);
|
|
14920
|
+
program.addCommand(securityCommand);
|
|
14661
14921
|
program.addCommand(searchCommand);
|
|
14662
14922
|
program.addCommand(forgetCommand);
|
|
14663
14923
|
program.addCommand(contextCommand);
|
|
14664
|
-
program.addCommand(
|
|
14665
|
-
program.addCommand(
|
|
14666
|
-
program.addCommand(
|
|
14667
|
-
program.addCommand(
|
|
14668
|
-
program.addCommand(sessionCommand);
|
|
14924
|
+
program.addCommand(tagsCommand);
|
|
14925
|
+
program.addCommand(exportCommand);
|
|
14926
|
+
program.addCommand(importCommand);
|
|
14927
|
+
program.addCommand(statsCommand);
|
|
14669
14928
|
program.addCommand(doctorCommand);
|
|
14670
14929
|
program.addCommand(upgradeCommand);
|
|
14671
14930
|
program.addCommand(selfUpdateCommand);
|
|
14672
14931
|
program.addCommand(telemetryCommand);
|
|
14932
|
+
program.addCommand(completionsCommand);
|
|
14933
|
+
program.addCommand(dockerCommand);
|
|
14934
|
+
program.addCommand(workflowCommand);
|
|
14935
|
+
program.addCommand(hookCommand);
|
|
14936
|
+
program.addCommand(packageCommand);
|
|
14673
14937
|
program.action(async () => {
|
|
14674
14938
|
await showDashboard();
|
|
14675
14939
|
});
|
|
14676
14940
|
async function interactiveMenu(choices, shortcuts) {
|
|
14941
|
+
const write = (str) => process.stdout.write(str);
|
|
14942
|
+
const totalLines = 1 + choices.length + 1;
|
|
14677
14943
|
return new Promise((resolve13) => {
|
|
14678
14944
|
let selectedIndex = 0;
|
|
14679
14945
|
let resolved = false;
|
|
14946
|
+
let firstRender = true;
|
|
14680
14947
|
const renderMenu = () => {
|
|
14681
|
-
if (
|
|
14682
|
-
|
|
14948
|
+
if (!firstRender) {
|
|
14949
|
+
write(`\x1B[${totalLines}A`);
|
|
14683
14950
|
}
|
|
14684
|
-
|
|
14951
|
+
firstRender = false;
|
|
14952
|
+
write("\x1B[?25l");
|
|
14953
|
+
write(`\x1B[K${colors.green("?")} ${colors.bold("Auswahl:")}
|
|
14954
|
+
`);
|
|
14685
14955
|
for (let i = 0; i < choices.length; i++) {
|
|
14686
|
-
const
|
|
14687
|
-
const
|
|
14688
|
-
|
|
14956
|
+
const isSelected = i === selectedIndex;
|
|
14957
|
+
const prefix = isSelected ? colors.cyan("\u276F ") : " ";
|
|
14958
|
+
const name = isSelected ? colors.cyan(choices[i].name) : choices[i].name;
|
|
14959
|
+
write(`\x1B[K${prefix}${name}
|
|
14960
|
+
`);
|
|
14689
14961
|
}
|
|
14690
|
-
|
|
14962
|
+
write(`\x1B[K${colors.dim("\u2191\u2193 navigate \u2022 \u21B5 select")}
|
|
14963
|
+
`);
|
|
14964
|
+
write("\x1B[?25h");
|
|
14691
14965
|
};
|
|
14692
|
-
|
|
14693
|
-
for (let i = 0; i < choices.length; i++) {
|
|
14694
|
-
const prefix = i === selectedIndex ? colors.cyan("\u276F ") : " ";
|
|
14695
|
-
const name = i === selectedIndex ? colors.cyan(choices[i].name) : choices[i].name;
|
|
14696
|
-
console.log(prefix + name);
|
|
14697
|
-
}
|
|
14698
|
-
console.log(colors.dim("\u2191\u2193 navigate \u2022 \u21B5 select"));
|
|
14966
|
+
renderMenu();
|
|
14699
14967
|
if (process.stdin.isTTY) {
|
|
14700
14968
|
readline.emitKeypressEvents(process.stdin);
|
|
14701
14969
|
process.stdin.setRawMode(true);
|
|
@@ -14706,6 +14974,12 @@ async function interactiveMenu(choices, shortcuts) {
|
|
|
14706
14974
|
if (process.stdin.isTTY) {
|
|
14707
14975
|
process.stdin.setRawMode(false);
|
|
14708
14976
|
}
|
|
14977
|
+
write("\x1B[?25h");
|
|
14978
|
+
};
|
|
14979
|
+
const showSelection = (label) => {
|
|
14980
|
+
write(`\x1B[${totalLines}A`);
|
|
14981
|
+
write("\x1B[J");
|
|
14982
|
+
console.log(`${colors.green("?")} ${colors.bold("Auswahl:")} ${colors.cyan(label)}`);
|
|
14709
14983
|
};
|
|
14710
14984
|
const handler = (_str, key) => {
|
|
14711
14985
|
if (resolved) return;
|
|
@@ -14713,14 +14987,14 @@ async function interactiveMenu(choices, shortcuts) {
|
|
|
14713
14987
|
const keyChar = key?.sequence?.toLowerCase() || "";
|
|
14714
14988
|
if (key?.ctrl && keyName === "c") {
|
|
14715
14989
|
cleanup();
|
|
14990
|
+
console.log("");
|
|
14716
14991
|
process.exit(0);
|
|
14717
14992
|
}
|
|
14718
|
-
|
|
14719
|
-
|
|
14993
|
+
const shortcutType = shortcuts[keyChar] || shortcuts[keyName];
|
|
14994
|
+
if (shortcutType) {
|
|
14720
14995
|
resolved = true;
|
|
14721
14996
|
cleanup();
|
|
14722
|
-
|
|
14723
|
-
console.log(colors.green("?") + " " + colors.bold("Auswahl:") + " " + colors.cyan(shortcutType));
|
|
14997
|
+
showSelection(shortcutType);
|
|
14724
14998
|
resolve13({ type: shortcutType });
|
|
14725
14999
|
return;
|
|
14726
15000
|
}
|
|
@@ -14733,13 +15007,12 @@ async function interactiveMenu(choices, shortcuts) {
|
|
|
14733
15007
|
} else if (keyName === "return") {
|
|
14734
15008
|
resolved = true;
|
|
14735
15009
|
cleanup();
|
|
14736
|
-
|
|
14737
|
-
console.log(colors.green("?") + " " + colors.bold("Auswahl:") + " " + colors.cyan(choices[selectedIndex].value.type));
|
|
15010
|
+
showSelection(choices[selectedIndex].name);
|
|
14738
15011
|
resolve13(choices[selectedIndex].value);
|
|
14739
15012
|
} else if (keyName === "escape") {
|
|
14740
15013
|
resolved = true;
|
|
14741
15014
|
cleanup();
|
|
14742
|
-
|
|
15015
|
+
showSelection("Beenden");
|
|
14743
15016
|
resolve13({ type: "quit" });
|
|
14744
15017
|
}
|
|
14745
15018
|
};
|
|
@@ -14836,7 +15109,7 @@ async function showDashboard() {
|
|
|
14836
15109
|
const packageName = selected.data;
|
|
14837
15110
|
log.newline();
|
|
14838
15111
|
log.success(`Starte Package ${packageName}...`);
|
|
14839
|
-
const { getPackageLaunchConfig: getPackageLaunchConfig2 } = await import("./package-manager-
|
|
15112
|
+
const { getPackageLaunchConfig: getPackageLaunchConfig2 } = await import("./package-manager-NHNQATBH.js");
|
|
14840
15113
|
const launches = await getPackageLaunchConfig2(packageName);
|
|
14841
15114
|
if (launches.length > 0) {
|
|
14842
15115
|
await spawnProjects(launches, detectTerminal());
|
|
@@ -14872,7 +15145,7 @@ async function showDashboard() {
|
|
|
14872
15145
|
function showHelpMenu() {
|
|
14873
15146
|
log.plain("Verf\xFCgbare Befehle:");
|
|
14874
15147
|
log.newline();
|
|
14875
|
-
console.log(colors.dim("
|
|
15148
|
+
console.log(colors.dim(" Session-Verwaltung:"));
|
|
14876
15149
|
log.plain(" shiva sessions Alle Claude Sessions auflisten");
|
|
14877
15150
|
log.plain(" shiva resume Session fortsetzen");
|
|
14878
15151
|
log.plain(" shiva restore Crashed Session wiederherstellen");
|
|
@@ -14884,27 +15157,33 @@ function showHelpMenu() {
|
|
|
14884
15157
|
log.plain(" shiva issues GitHub Issues anzeigen");
|
|
14885
15158
|
log.plain(" shiva prs Pull Requests anzeigen");
|
|
14886
15159
|
log.newline();
|
|
15160
|
+
console.log(colors.dim(" Memory & Context:"));
|
|
15161
|
+
log.plain(" shiva search In Memories suchen");
|
|
15162
|
+
log.plain(" shiva context Context anzeigen");
|
|
15163
|
+
log.plain(" shiva tags Session-Tags verwalten");
|
|
15164
|
+
log.plain(" shiva export Sessions exportieren");
|
|
15165
|
+
log.plain(" shiva import Sessions importieren");
|
|
15166
|
+
log.newline();
|
|
14887
15167
|
console.log(colors.dim(" Docker Integration:"));
|
|
14888
15168
|
log.plain(" shiva docker Docker-Modus verwalten");
|
|
14889
15169
|
log.plain(" shiva start -d In Docker starten");
|
|
14890
15170
|
log.newline();
|
|
14891
|
-
console.log(colors.dim(" Security
|
|
15171
|
+
console.log(colors.dim(" Security:"));
|
|
14892
15172
|
log.plain(" shiva scan Package auf Sicherheit pr\xFCfen");
|
|
15173
|
+
log.plain(" shiva secrets Secrets verwalten");
|
|
14893
15174
|
log.plain(" shiva start -s In Sandbox starten");
|
|
14894
|
-
log.plain(" shiva session Sandbox-Session verwalten");
|
|
14895
15175
|
log.newline();
|
|
14896
|
-
console.log(colors.dim("
|
|
14897
|
-
log.plain(" shiva login Anmelden");
|
|
15176
|
+
console.log(colors.dim(" Projekt-Verwaltung:"));
|
|
14898
15177
|
log.plain(" shiva init Projekt initialisieren");
|
|
14899
15178
|
log.plain(" shiva sync Mit Cloud synchronisieren");
|
|
14900
15179
|
log.plain(" shiva status Status anzeigen");
|
|
14901
15180
|
log.plain(" shiva projects Alle Projekte auflisten");
|
|
14902
|
-
log.plain(" shiva config
|
|
15181
|
+
log.plain(" shiva config Settings verwalten");
|
|
14903
15182
|
log.newline();
|
|
14904
|
-
console.log(colors.dim("
|
|
15183
|
+
console.log(colors.dim(" System:"));
|
|
14905
15184
|
log.plain(" shiva doctor System-Check");
|
|
14906
15185
|
log.plain(" shiva upgrade SHIVA aktualisieren");
|
|
14907
|
-
log.plain(" shiva
|
|
15186
|
+
log.plain(" shiva stats Analytics anzeigen");
|
|
14908
15187
|
log.newline();
|
|
14909
15188
|
log.dim("Hilfe: shiva <befehl> --help");
|
|
14910
15189
|
}
|