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/index.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  isGhAuthenticated,
40
40
  isGhInstalled,
41
41
  isGitRepo
42
- } from "./chunk-ZDLLPNCK.js";
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-TQ6O4QB6.js";
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-TI6Y3VT4.js";
75
+ } from "./chunk-H5OFO4VS.js";
70
76
  import {
71
77
  cache
72
- } from "./chunk-6RAACMKF.js";
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(path15) {
317
+ async findProjectByPath(path16) {
312
318
  const projects = await this.getProjects();
313
- return projects.find((p) => p.path === path15) || null;
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 os7 = __require("os");
896
+ const os8 = __require("os");
891
897
  const components = [
892
- os7.hostname(),
893
- os7.platform(),
894
- os7.arch(),
895
- os7.cpus()[0]?.model || "unknown",
896
- os7.userInfo().username
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 os7 = __require("os");
903
- const hostname = os7.hostname();
904
- const platform = os7.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: (path15) => new ShivaError(
2408
+ PATH_NOT_FOUND: (path16) => new ShivaError(
2403
2409
  "PATH_404",
2404
- path15 ? `Pfad nicht gefunden: ${path15}` : "Pfad nicht gefunden",
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: (path15) => new ShivaError(
2418
+ PERMISSION_DENIED: (path16) => new ShivaError(
2413
2419
  "PERMISSION_DENIED",
2414
- path15 ? `Keine Berechtigung: ${path15}` : "Keine Berechtigung",
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 path3 from "path";
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, execSync as execSync2, spawnSync as spawnSync2 } from "child_process";
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 { execSync, spawn } from "child_process";
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 version = execSync("docker --version", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
4430
- const socket = this.findDockerSocket("docker");
4431
- this.cachedInfo = {
4432
- available: true,
4433
- version: version.replace("Docker version ", "").split(",")[0],
4434
- runtime: "docker",
4435
- socket
4436
- };
4437
- return this.cachedInfo;
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 version = execSync("podman --version", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
4442
- const socket = this.findDockerSocket("podman");
4443
- this.cachedInfo = {
4444
- available: true,
4445
- version: version.replace("podman version ", ""),
4446
- runtime: "podman",
4447
- socket
4448
- };
4449
- return this.cachedInfo;
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
- execSync(`${cmd} image inspect ${image}`, { stdio: "ignore" });
4517
- return true;
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 output = execSync(`${cmd} images --format "{{.Repository}} {{.Tag}} {{.ID}} {{.Size}}"`, {
4536
- encoding: "utf8"
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
- return output.trim().split("\n").filter((line) => line.includes("shiva") || line.includes("claude")).map((line) => {
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 containerId = execSync(`${cmd} ${args.join(" ")}`, { encoding: "utf8" }).trim();
4579
- return containerId;
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
- execSync(`${cmd} start ${containerId}`, { stdio: "ignore" });
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
- execSync(`${cmd} stop ${containerId}`, { stdio: "ignore" });
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
- execSync(`${cmd} rm -f ${containerId}`, { stdio: "ignore" });
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 tailArg = tail ? `--tail ${tail}` : "";
4745
+ const args = ["logs"];
4746
+ if (tail && tail > 0) {
4747
+ args.push("--tail", String(tail));
4748
+ }
4749
+ args.push(containerId);
4623
4750
  try {
4624
- return execSync(`${cmd} logs ${tailArg} ${containerId}`, { encoding: "utf8" });
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 containerName = `shiva-${launch.projectName.replace(/[^a-zA-Z0-9-]/g, "-")}-${Date.now()}`;
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
- args.push("-v", `${host}:${container}:rw`);
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
- args.push("-e", `${key}=${value}`);
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
- args.push("-e", `${key}=${value}`);
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 output = execSync(
4701
- `${cmd} ps -a --filter "name=shiva-" --format "{{.ID}} {{.Names}} {{.Image}} {{.Status}} {{.CreatedAt}} {{.Ports}}"`,
4702
- { encoding: "utf8" }
4703
- );
4704
- return output.trim().split("\n").filter((line) => line.length > 0).map((line) => {
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 containerName = `shiva-${launch.projectName.replace(/[^a-zA-Z0-9-]/g, "-")}-${Date.now()}`;
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
- args.push("--security-opt", opt);
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
- args.push("-v", `${mount.host}:${mount.container}:${mount.mode}`);
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
- args.push("-e", `${key}=${value}`);
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
- args.push("-e", `${key}=${value}`);
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
- execSync2(`which ${command}`, { stdio: "ignore" });
4979
- return true;
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 buildClaudeCommand(project) {
5018
- const cdCommand = `cd "${project.projectPath}"`;
5019
- const launchArgs = getClaudeLaunchArgs();
5020
- const argsStr = launchArgs.length > 0 ? ` ${launchArgs.join(" ")}` : "";
5021
- if (project.newSession || !project.sessionId) {
5022
- return `${cdCommand} && claude${argsStr}`;
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
- return `${cdCommand} && claude --resume ${project.sessionId}${argsStr}`;
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
- if (insideTmux) {
5030
- for (let i = 0; i < projects.length; i++) {
5031
- const project = projects[i];
5032
- const command = buildClaudeCommand(project);
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-window",
5263
+ "new-session",
5264
+ "-d",
5265
+ "-s",
5266
+ tmuxSession,
5036
5267
  "-n",
5037
- project.projectName,
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
- project.projectName,
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}:${project.projectName}`,
5097
- command,
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 (let i = 0; i < projects.length; i++) {
5109
- const project = projects[i];
5110
- const command = buildClaudeCommand(project);
5111
- if (i === 0) {
5112
- args.push("--tab", "--title", project.projectName, "--", "bash", "-c", `${command}; exec bash`);
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
- if (process.env.KITTY_WINDOW_ID) {
5124
- for (const project of projects) {
5125
- const command = buildClaudeCommand(project);
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
- project.projectName,
5344
+ safeName,
5132
5345
  "--cwd",
5133
5346
  project.projectPath,
5134
- "bash",
5135
- "-c",
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 command = buildClaudeCommand(project);
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
- project.projectName,
5391
+ safeName,
5177
5392
  "--working-directory",
5178
5393
  project.projectPath,
5179
5394
  "-e",
5180
- "bash",
5181
- "-c",
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 command = buildClaudeCommand(project);
5192
- console.log(`Starting: ${project.projectName}`);
5193
- console.log(`Path: ${project.projectPath}`);
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
- spawn2("bash", ["-c", command], {
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
- console.log(`Starting in Docker: ${project.projectName}`);
5213
- console.log(`Path: ${project.projectPath}`);
5214
- console.log(`Image: ${dockerSettings.defaultImage}`);
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 containerName = `shiva-${project.projectName.replace(/[^a-zA-Z0-9-]/g, "-")}-${Date.now()}`;
5226
- const cmd = dockerService.getDockerInfo().runtime === "podman" ? "podman" : "docker";
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
- project.projectName
5459
+ safeName
5231
5460
  ], { stdio: "inherit" });
5232
- const dockerCmd = buildDockerCommand(project, dockerSettings, containerName);
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
- console.log(` - ${project.projectName}`);
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
- console.log(`
5247
- Attaching to: ${firstProject.projectName}`);
5248
- for (let i = 1; i < projects.length; i++) {
5249
- const project = projects[i];
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 buildDockerCommand(project, settings, containerName) {
5489
+ function buildSecureDockerArgs(project, settings, containerName) {
5256
5490
  const cmd = dockerService.getDockerInfo().runtime === "podman" ? "podman" : "docker";
5257
- const os7 = __require("os");
5258
- const path15 = __require("path");
5259
- let dockerCmd = `${cmd} run -it --rm --name ${containerName} --workdir /workspace`;
5260
- dockerCmd += ` -v "${project.projectPath}:/workspace:rw"`;
5261
- dockerCmd += ` -v "${path15.join(os7.homedir(), ".claude")}:/root/.claude:rw"`;
5262
- dockerCmd += ` -v "${path15.join(os7.homedir(), ".config", "shiva-code")}:/root/.config/shiva-code:rw"`;
5263
- for (const [host, container] of Object.entries(settings.volumeMounts || {})) {
5264
- dockerCmd += ` -v "${host}:${container}:rw"`;
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
- dockerCmd += ` -e "${key}=${value}"`;
5514
+ if (/^[A-Z_][A-Z0-9_]*$/.test(key)) {
5515
+ args.push("-e", `${key}=${value}`);
5516
+ }
5268
5517
  }
5269
- dockerCmd += ` ${settings.defaultImage} claude`;
5518
+ args.push(settings.defaultImage, "claude");
5270
5519
  if (project.sessionId && !project.newSession) {
5271
- dockerCmd += ` --resume ${project.sessionId}`;
5520
+ args.push("--resume", project.sessionId);
5272
5521
  }
5273
- return dockerCmd;
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 as execSync3 } from "child_process";
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 join3, dirname } from "path";
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(path15) {
5614
+ isGitRepo(path16) {
5363
5615
  try {
5364
- execSync3("git rev-parse --git-dir", {
5365
- cwd: path15,
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
- execSync3("docker info", { stdio: "pipe" });
5630
+ execSync("docker info", { stdio: "pipe" });
5379
5631
  return true;
5380
5632
  } catch {
5381
5633
  try {
5382
- execSync3("podman info", { stdio: "pipe" });
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 join3(projectPath, ".shiva", "sandbox");
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 = join3(sandboxDir, `session-${sessionId}`);
5719
+ const sandboxPath = join4(sandboxDir, `session-${sessionId}`);
5468
5720
  mkdirSync(dirname(sandboxPath), { recursive: true });
5469
- const currentRef = execSync3("git rev-parse HEAD", {
5721
+ const currentRef = execSync("git rev-parse HEAD", {
5470
5722
  cwd: projectPath,
5471
5723
  encoding: "utf-8"
5472
5724
  }).trim();
5473
- execSync3(`git worktree add --detach "${sandboxPath}" ${currentRef}`, {
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 = execSync3("git ls-files --others --exclude-standard", {
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 = join3(projectPath, file);
5492
- const destPath = join3(sandboxPath, file);
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 = join3(sandboxDir, `session-${sessionId}`);
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 = join3(src, entry.name);
5524
- const destPath = join3(dest, entry.name);
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 = join3(sandboxDir, `session-${sessionId}`);
5545
- const upperDir = join3(sandboxDir, `upper-${sessionId}`);
5546
- const workDir = join3(sandboxDir, `work-${sessionId}`);
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
- execSync3(`git worktree remove "${session.sandboxPath}" --force`, {
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
- execSync3("git worktree prune", {
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 = execSync3("git diff --stat --numstat HEAD", {
5903
+ const diffOutput = execSync("git diff --stat --numstat HEAD", {
5652
5904
  cwd: session.sandboxPath,
5653
5905
  encoding: "utf-8"
5654
5906
  });
5655
- const untrackedOutput = execSync3("git ls-files --others --exclude-standard", {
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 = join3(session.sandboxPath, file);
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 = join3(sandboxPath, relativePath);
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 ? join3(relativePath, entry.name) : entry.name;
5718
- const originalEntryPath = join3(originalPath, entryRelPath);
5719
- const sandboxEntryPath = join3(sandboxPath, entryRelPath);
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 = join3(originalPath, relativePath);
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 ? join3(relativePath, entry.name) : entry.name;
5762
- const originalEntryPath = join3(originalPath, entryRelPath);
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 = join3(relativePath, entry.name);
5806
- const entryPath = join3(dirPath, entry.name);
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 = join3(relativePath, entry.name);
5830
- const entryPath = join3(dirPath, entry.name);
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 = execSync3(`git diff HEAD -- "${filePath}"`, {
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 = join3(session.sandboxPath, filePath);
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 = join3(session.projectPath, filePath);
5871
- const sandboxFilePath = join3(session.sandboxPath, filePath);
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 = join3(session.sandboxPath, filePath);
5930
- const destPath = join3(session.projectPath, filePath);
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 = join3(session.sandboxPath, filePath);
5968
- const originalPath = join3(session.projectPath, filePath);
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 = path3.resolve(projektArg);
6317
+ const absolutePath = path4.resolve(projektArg);
6065
6318
  if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory()) {
6066
- const project = await findProject(absolutePath);
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 = await findProject(projektArg);
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 path4 from "path";
6815
- import * as os3 from "os";
7067
+ import * as path5 from "path";
7068
+ import * as os4 from "os";
6816
7069
  function getTagsPath() {
6817
- return path4.join(os3.homedir(), ".shiva", "tags.json");
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 = path4.dirname(filepath);
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
- try {
6974
- let projects = await getAllClaudeProjects(options.refresh);
6975
- if (options.project) {
6976
- const found = await findProject(options.project);
6977
- if (found) {
6978
- projects = [found];
6979
- } else {
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
- console.log(colors.orange.bold("SHIVA Code - Sessions"));
7003
- 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"));
7004
- log.newline();
7005
- if (projects.length === 0) {
7006
- log.dim("Keine Sessions gefunden.");
7007
- log.newline();
7008
- log.info("Claude Code Sessions werden automatisch erkannt.");
7009
- return;
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
- for (const project of projects) {
7012
- const projectExists = await checkProjectExists(project.absolutePath);
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 stats = await getSessionStats();
7060
- log.dim(`Total: ${stats.activeProjects} Projekte, ${stats.totalSessions} Sessions`);
7061
- log.newline();
7062
- } catch (error) {
7063
- spinner.fail("Fehler beim Laden der Sessions");
7064
- log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
7065
- }
7066
- });
7067
- async function checkProjectExists(path15) {
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(path15);
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 path15 of pathsToApply) {
7332
- const change = diff.changes.find((c) => c.path === path15);
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)} ${path15}`);
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 path5 from "path";
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 = path5.join(cwd, ".git");
7952
- const hooksDir = path5.join(gitDir, "hooks");
7953
- const hookFile = path5.join(hooksDir, "post-checkout");
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: findProject2 } = await import("./manager-TQIASXKY.js");
8028
- const project = await findProject2(cwd);
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-TQIASXKY.js");
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
- log.error("gh CLI nicht installiert");
8126
- log.info("Installiere: https://cli.github.com/");
8127
- return;
8396
+ throw Errors.GITHUB_NOT_INSTALLED();
8128
8397
  }
8129
8398
  if (!isGhAuthenticated()) {
8130
- log.error("Nicht mit GitHub angemeldet");
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
- try {
8137
- const allProjects = await getAllClaudeProjects();
8138
- let projects = allProjects;
8139
- if (options.project) {
8140
- const found = await findProject(options.project);
8141
- if (found) {
8142
- projects = [found];
8143
- } else {
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
- log.newline();
8179
- console.log(colors.orange.bold("SHIVA Code - GitHub Issues"));
8180
- 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"));
8181
- log.newline();
8182
- if (projectsWithIssues.length === 0) {
8183
- log.dim("Keine Issues gefunden.");
8184
- if (!options.mine) {
8185
- log.info("Versuche: shiva issues --mine");
8186
- }
8187
- return;
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
- let totalIssues = 0;
8190
- let totalAssigned = 0;
8191
- for (const { project, repo, issues, assignedCount } of projectsWithIssues) {
8192
- totalIssues += issues.length;
8193
- totalAssigned += assignedCount;
8194
- const assignedText = assignedCount > 0 ? colors.green(`, ${assignedCount} assigned`) : "";
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
- colors.bold(`\u{1F4E6} ${project.projectName}`) + colors.dim(` (${issues.length} open${assignedText})`)
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
- log.error("gh CLI nicht installiert");
8232
- log.info("Installiere: https://cli.github.com/");
8233
- return;
8493
+ throw Errors.GITHUB_NOT_INSTALLED();
8234
8494
  }
8235
8495
  if (!isGhAuthenticated()) {
8236
- log.error("Nicht mit GitHub angemeldet");
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
- try {
8243
- const allProjects = await getAllClaudeProjects();
8244
- let projects = allProjects;
8245
- if (options.project) {
8246
- const found = await findProject(options.project);
8247
- if (found) {
8248
- projects = [found];
8249
- } else {
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
- log.newline();
8288
- console.log(colors.orange.bold("SHIVA Code - Pull Requests"));
8289
- 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"));
8290
- log.newline();
8291
- if (projectsWithPRs.length === 0) {
8292
- log.dim("Keine Pull Requests gefunden.");
8293
- if (!options.mine) {
8294
- log.info("Versuche: shiva prs --mine");
8295
- }
8296
- return;
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
- let totalPRs = 0;
8299
- let totalMyPRs = 0;
8300
- for (const { project, repo, prs, myPRsCount } of projectsWithPRs) {
8301
- totalPRs += prs.length;
8302
- totalMyPRs += myPRsCount;
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
- colors.bold(`\u{1F4E6} ${project.projectName}`) + colors.dim(` (${prs.length} open)`)
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 join6, resolve as resolve7 } from "path";
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(join6(dir, "pnpm-lock.yaml"))) {
9966
+ if (existsSync12(join7(dir, "pnpm-lock.yaml"))) {
9710
9967
  manager = "pnpm";
9711
- } else if (existsSync12(join6(dir, "yarn.lock"))) {
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(join6(cwd, "package.json"))) {
9813
- const parsed = await parsePackageJson(join6(cwd, "package.json"));
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(join6(cwd, "requirements.txt"))) {
9817
- packagesToScan = await parseRequirementsTxt(join6(cwd, "requirements.txt"));
10073
+ } else if (existsSync12(join7(cwd, "requirements.txt"))) {
10074
+ packagesToScan = await parseRequirementsTxt(join7(cwd, "requirements.txt"));
9818
10075
  manager = "pip";
9819
- } else if (existsSync12(join6(cwd, "Cargo.toml"))) {
9820
- packagesToScan = await parseCargoToml(join6(cwd, "Cargo.toml"));
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 path6 from "path";
10781
+ import * as path7 from "path";
10525
10782
 
10526
10783
  // src/utils/clipboard.ts
10527
- import { execSync as execSync4, spawnSync as spawnSync6 } from "child_process";
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
- execSync4(`which ${command}`, { stdio: "ignore" });
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 (vorsicht!)").argument("<key>", "Secret-Name").option("-p, --project <id>", "Projekt-spezifisches Secret").option("-c, --copy", "In Zwischenablage kopieren").option("-q, --quiet", "Nur Wert ausgeben (f\xFCr Skripte)").action(async (key, options) => {
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
- log.keyValue("Value", result.value);
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
- const { writeFileSync: writeFileSync11 } = await import("fs");
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 = path6.resolve(file);
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 ${path6.basename(file)}`);
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", "utf-8");
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 path7 from "path";
10973
- import * as os4 from "os";
11236
+ import * as path8 from "path";
11237
+ import * as os5 from "os";
10974
11238
  function findAllClaudeMdFiles() {
10975
11239
  const results = [];
10976
- const sessionsPath = path7.join(os4.homedir(), ".claude", "projects");
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 = path7.join(projectPath, "CLAUDE.md");
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: path7.basename(projectPath)
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 = path7.join(projectPath, "CLAUDE.md");
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 = path7.join(projectPath, "CLAUDE.md");
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 = path7.basename(projectPath);
11179
- const claudeMdPath = path7.join(projectPath, "CLAUDE.md");
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 = path7.join(projectPath, ".shiva");
11202
- const contextPath = path7.join(shivaDir, "github-context.md");
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 path8 from "path";
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 ? path8.resolve(options.project) : process.cwd();
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 = path8.basename(projectPath);
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: ${path8.basename(projectPath)}`));
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 path9 from "path";
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 ? path9.resolve(options.dir) : process.cwd();
11546
- const projectName = path9.basename(projectPath);
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 path11 from "path";
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 path10 from "path";
11938
- import { execSync as execSync5 } from "child_process";
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 = path10.join(
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 = path10.join(
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 = path10.join(outputDir, filename);
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 = path10.join(claudeDir, encoded);
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(path10.join(claudeDir, e));
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
- execSync5(`tar -czf "${outputPath}" -C "${claudeDir}" ${tarArgs}`, {
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 = path10.join(getClaudeProjectsPath(), encodedPath);
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 = path10.join(sessionDir, data.session.sessionId + ".jsonl");
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 = path10.join(dirPath, file);
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
- execSync5(`tar -xzf "${archivePath}" -C "${extractTo}"`, {
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 = path11.resolve(inputPath);
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 || path11.join(process.cwd(), filename);
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 || path11.join(process.cwd(), "shiva-export");
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 || path11.join(process.cwd(), `${project.projectName}-export`);
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 execSync6 } from "child_process";
12668
+ import { execSync as execSync4 } from "child_process";
12405
12669
  import * as fs11 from "fs";
12406
- import * as path12 from "path";
12407
- import * as os5 from "os";
12670
+ import * as path13 from "path";
12671
+ import * as os6 from "os";
12408
12672
  function tryExec(command) {
12409
12673
  try {
12410
- return execSync6(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
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
- execSync6(`which ${cmd}`, { stdio: ["pipe", "pipe", "pipe"] });
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 = path12.join(os5.homedir(), ".config", "shiva-code");
12569
- const configFile = path12.join(configDir, "config.json");
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 = path12.join(os5.homedir(), ".claude", "projects");
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(path12.join(claudeDir, f)).isDirectory()
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 = os5.totalmem();
12901
+ const totalMem = os6.totalmem();
12638
12902
  const memGB = (totalMem / (1024 * 1024 * 1024)).toFixed(1);
12639
12903
  return {
12640
- os: `${os5.type()} ${os5.release()}`,
12641
- arch: os5.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 execSync7, spawn as spawn6 } from "child_process";
12995
+ import { execSync as execSync5, spawn as spawn6 } from "child_process";
12732
12996
  import * as fs12 from "fs";
12733
- import * as path13 from "path";
12997
+ import * as path14 from "path";
12734
12998
  import { fileURLToPath } from "url";
12735
12999
  var __filename = fileURLToPath(import.meta.url);
12736
- var __dirname = path13.dirname(__filename);
13000
+ var __dirname = path14.dirname(__filename);
12737
13001
  var PACKAGE_NAME = "shiva-code";
12738
13002
  function getCurrentVersion() {
12739
13003
  try {
12740
- const packageJsonPath = path13.resolve(__dirname, "../../package.json");
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 = execSync7(`npm view ${PACKAGE_NAME} version 2>/dev/null`, {
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 = execSync7("npm list -g --depth=0 2>/dev/null", { encoding: "utf-8" });
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 = execSync7("yarn global list 2>/dev/null", { encoding: "utf-8" });
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 = execSync7("pnpm list -g 2>/dev/null", { encoding: "utf-8" });
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 os7 = await import("os");
13141
- const path15 = await import("path");
13142
- const bashrcPath = path15.join(os7.homedir(), ".bashrc");
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 os7 = await import("os");
13284
- const path15 = await import("path");
13285
- const zshrcPath = path15.join(os7.homedir(), ".zshrc");
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 os7 = await import("os");
13389
- const path15 = await import("path");
13390
- const fishCompletionsDir = path15.join(os7.homedir(), ".config", "fish", "completions");
13391
- const fishCompletionPath = path15.join(fishCompletionsDir, "shiva.fish");
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 path14 from "path";
13800
- import * as os6 from "os";
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 path14.join(os6.homedir(), ".shiva", "workflows.json");
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 = path14.dirname(filepath);
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 homedir7 } from "os";
14079
- import { join as join12 } from "path";
14080
- var CLAUDE_SETTINGS_PATH = join12(homedir7(), ".claude", "settings.json");
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 = join12(homedir7(), ".claude");
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 = join12(homedir7(), ".claude");
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-MT23KCO3.js");
14645
+ const { getCurrentBranch: getCurrentBranch2 } = await import("./api-OEHQTBH7.js");
14382
14646
  const { getSessionForBranch: getSessionForBranch2, hasShivaDir: hasShivaDir2 } = await import("./config-D6M6LI6U.js");
14383
- const { findProject: findProject2, findSessionByBranch: findSessionByBranch2, formatRelativeTime: formatRelativeTime3 } = await import("./manager-TQIASXKY.js");
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 findProject2(projectPath);
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.4.3");
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(hookCommand);
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(statsCommand);
14656
- program.addCommand(workflowCommand);
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(dockerCommand);
14665
- program.addCommand(projectCommand);
14666
- program.addCommand(securityCommand);
14667
- program.addCommand(scanCommand);
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 (selectedIndex > 0 || choices.length > 0) {
14682
- process.stdout.write(`\x1B[${choices.length + 1}A`);
14948
+ if (!firstRender) {
14949
+ write(`\x1B[${totalLines}A`);
14683
14950
  }
14684
- console.log(colors.green("?") + " " + colors.bold("Auswahl:"));
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 prefix = i === selectedIndex ? colors.cyan("\u276F ") : " ";
14687
- const name = i === selectedIndex ? colors.cyan(choices[i].name) : choices[i].name;
14688
- console.log(prefix + name);
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
- console.log(colors.dim("\u2191\u2193 navigate \u2022 \u21B5 select"));
14962
+ write(`\x1B[K${colors.dim("\u2191\u2193 navigate \u2022 \u21B5 select")}
14963
+ `);
14964
+ write("\x1B[?25h");
14691
14965
  };
14692
- console.log(colors.green("?") + " " + colors.bold("Auswahl:"));
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
- if (shortcuts[keyChar] || shortcuts[keyName]) {
14719
- const shortcutType = shortcuts[keyChar] || shortcuts[keyName];
14993
+ const shortcutType = shortcuts[keyChar] || shortcuts[keyName];
14994
+ if (shortcutType) {
14720
14995
  resolved = true;
14721
14996
  cleanup();
14722
- process.stdout.write(`\x1B[${choices.length + 2}A\x1B[J`);
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
- process.stdout.write(`\x1B[${choices.length + 2}A\x1B[J`);
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
- process.stdout.write(`\x1B[${choices.length + 2}A\x1B[J`);
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-NARG55B5.js");
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(" Control Station:"));
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 (Phase 16-17):"));
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(" Cloud Sync:"));
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 sync Settings synchronisieren");
15181
+ log.plain(" shiva config Settings verwalten");
14903
15182
  log.newline();
14904
- console.log(colors.dim(" Utilities:"));
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 telemetry Analytics opt-in/out");
15186
+ log.plain(" shiva stats Analytics anzeigen");
14908
15187
  log.newline();
14909
15188
  log.dim("Hilfe: shiva <befehl> --help");
14910
15189
  }