shiva-code 0.2.2 → 0.4.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.
@@ -0,0 +1,169 @@
1
+ // src/services/cache.ts
2
+ var CacheService = class _CacheService {
3
+ cache;
4
+ // Default TTLs in milliseconds
5
+ static TTL = {
6
+ SESSIONS: 30 * 1e3,
7
+ // 30 seconds for sessions
8
+ GITHUB_ISSUES: 5 * 60 * 1e3,
9
+ // 5 minutes for GitHub issues
10
+ GITHUB_PRS: 5 * 60 * 1e3,
11
+ // 5 minutes for GitHub PRs
12
+ GITHUB_CONTEXT: 5 * 60 * 1e3,
13
+ // 5 minutes for full context
14
+ TERMINAL: 24 * 60 * 60 * 1e3
15
+ // 24 hours for terminal detection
16
+ };
17
+ constructor() {
18
+ this.cache = /* @__PURE__ */ new Map();
19
+ }
20
+ /**
21
+ * Get a cached value
22
+ */
23
+ get(key) {
24
+ const entry = this.cache.get(key);
25
+ if (!entry) {
26
+ return null;
27
+ }
28
+ if (Date.now() - entry.timestamp > entry.ttl) {
29
+ this.cache.delete(key);
30
+ return null;
31
+ }
32
+ return entry.data;
33
+ }
34
+ /**
35
+ * Set a cached value with TTL
36
+ */
37
+ set(key, data, ttlMs) {
38
+ this.cache.set(key, {
39
+ data,
40
+ timestamp: Date.now(),
41
+ ttl: ttlMs
42
+ });
43
+ }
44
+ /**
45
+ * Invalidate a specific key
46
+ */
47
+ invalidate(key) {
48
+ this.cache.delete(key);
49
+ }
50
+ /**
51
+ * Invalidate all keys matching a pattern
52
+ */
53
+ invalidatePattern(pattern) {
54
+ const regex = new RegExp(pattern.replace(/\*/g, ".*"));
55
+ for (const key of this.cache.keys()) {
56
+ if (regex.test(key)) {
57
+ this.cache.delete(key);
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Clear the entire cache
63
+ */
64
+ clear() {
65
+ this.cache.clear();
66
+ }
67
+ /**
68
+ * Get cache statistics
69
+ */
70
+ stats() {
71
+ return {
72
+ size: this.cache.size,
73
+ keys: Array.from(this.cache.keys())
74
+ };
75
+ }
76
+ // ============================================
77
+ // Session-specific methods
78
+ // ============================================
79
+ /**
80
+ * Get cached Claude projects (sessions)
81
+ */
82
+ getSessions() {
83
+ return this.get("sessions:all");
84
+ }
85
+ /**
86
+ * Set cached Claude projects (sessions)
87
+ */
88
+ setSessions(projects) {
89
+ this.set("sessions:all", projects, _CacheService.TTL.SESSIONS);
90
+ }
91
+ /**
92
+ * Invalidate sessions cache
93
+ */
94
+ invalidateSessions() {
95
+ this.invalidatePattern("sessions:*");
96
+ }
97
+ // ============================================
98
+ // GitHub-specific methods
99
+ // ============================================
100
+ /**
101
+ * Get cached GitHub issues for a repo
102
+ */
103
+ getGitHubIssues(repo) {
104
+ return this.get(`github:issues:${repo}`);
105
+ }
106
+ /**
107
+ * Set cached GitHub issues for a repo
108
+ */
109
+ setGitHubIssues(repo, issues) {
110
+ this.set(`github:issues:${repo}`, issues, _CacheService.TTL.GITHUB_ISSUES);
111
+ }
112
+ /**
113
+ * Get cached GitHub PRs for a repo
114
+ */
115
+ getGitHubPRs(repo) {
116
+ return this.get(`github:prs:${repo}`);
117
+ }
118
+ /**
119
+ * Set cached GitHub PRs for a repo
120
+ */
121
+ setGitHubPRs(repo, prs) {
122
+ this.set(`github:prs:${repo}`, prs, _CacheService.TTL.GITHUB_PRS);
123
+ }
124
+ /**
125
+ * Get cached GitHub context for a project
126
+ */
127
+ getGitHubContext(projectPath) {
128
+ return this.get(`github:context:${projectPath}`);
129
+ }
130
+ /**
131
+ * Set cached GitHub context for a project
132
+ */
133
+ setGitHubContext(projectPath, context) {
134
+ this.set(`github:context:${projectPath}`, context, _CacheService.TTL.GITHUB_CONTEXT);
135
+ }
136
+ /**
137
+ * Invalidate all GitHub caches
138
+ */
139
+ invalidateGitHub() {
140
+ this.invalidatePattern("github:*");
141
+ }
142
+ /**
143
+ * Invalidate GitHub caches for a specific repo
144
+ */
145
+ invalidateGitHubRepo(repo) {
146
+ this.invalidate(`github:issues:${repo}`);
147
+ this.invalidate(`github:prs:${repo}`);
148
+ }
149
+ // ============================================
150
+ // Terminal detection cache
151
+ // ============================================
152
+ /**
153
+ * Get cached detected terminal
154
+ */
155
+ getDetectedTerminal() {
156
+ return this.get("terminal:detected");
157
+ }
158
+ /**
159
+ * Set cached detected terminal
160
+ */
161
+ setDetectedTerminal(terminal) {
162
+ this.set("terminal:detected", terminal, _CacheService.TTL.TERMINAL);
163
+ }
164
+ };
165
+ var cache = new CacheService();
166
+
167
+ export {
168
+ cache
169
+ };
@@ -0,0 +1,175 @@
1
+ import {
2
+ findProject,
3
+ getProjectName
4
+ } from "./chunk-MDMZWOX7.js";
5
+
6
+ // src/services/package-manager.ts
7
+ import Conf from "conf";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ var packageStore = new Conf({
11
+ projectName: "shiva-code",
12
+ configName: "packages",
13
+ defaults: {
14
+ packages: {}
15
+ }
16
+ });
17
+ function getAllPackages() {
18
+ const packages = packageStore.get("packages") || {};
19
+ return Object.values(packages).sort(
20
+ (a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
21
+ );
22
+ }
23
+ function getPackage(name) {
24
+ const packages = packageStore.get("packages") || {};
25
+ return packages[name.toLowerCase()] || null;
26
+ }
27
+ function createPackage(name, description, launchOrder = "parallel") {
28
+ const packages = packageStore.get("packages") || {};
29
+ const key = name.toLowerCase();
30
+ if (packages[key]) {
31
+ throw new Error(`Package "${name}" existiert bereits`);
32
+ }
33
+ const now = (/* @__PURE__ */ new Date()).toISOString();
34
+ const newPackage = {
35
+ name,
36
+ description,
37
+ projects: [],
38
+ created: now,
39
+ modified: now,
40
+ launchOrder
41
+ };
42
+ packages[key] = newPackage;
43
+ packageStore.set("packages", packages);
44
+ return newPackage;
45
+ }
46
+ function updatePackage(name, updates) {
47
+ const packages = packageStore.get("packages") || {};
48
+ const key = name.toLowerCase();
49
+ const existing = packages[key];
50
+ if (!existing) {
51
+ throw new Error(`Package "${name}" nicht gefunden`);
52
+ }
53
+ if (updates.name && updates.name.toLowerCase() !== key) {
54
+ const newKey = updates.name.toLowerCase();
55
+ if (packages[newKey]) {
56
+ throw new Error(`Package "${updates.name}" existiert bereits`);
57
+ }
58
+ delete packages[key];
59
+ packages[newKey] = {
60
+ ...existing,
61
+ ...updates,
62
+ modified: (/* @__PURE__ */ new Date()).toISOString()
63
+ };
64
+ packageStore.set("packages", packages);
65
+ return packages[newKey];
66
+ }
67
+ packages[key] = {
68
+ ...existing,
69
+ ...updates,
70
+ modified: (/* @__PURE__ */ new Date()).toISOString()
71
+ };
72
+ packageStore.set("packages", packages);
73
+ return packages[key];
74
+ }
75
+ function deletePackage(name) {
76
+ const packages = packageStore.get("packages") || {};
77
+ const key = name.toLowerCase();
78
+ if (!packages[key]) {
79
+ throw new Error(`Package "${name}" nicht gefunden`);
80
+ }
81
+ delete packages[key];
82
+ packageStore.set("packages", packages);
83
+ }
84
+ function addProjectToPackage(packageName, projectPath) {
85
+ const packages = packageStore.get("packages") || {};
86
+ const key = packageName.toLowerCase();
87
+ const pkg = packages[key];
88
+ if (!pkg) {
89
+ throw new Error(`Package "${packageName}" nicht gefunden`);
90
+ }
91
+ const absolutePath = path.resolve(projectPath);
92
+ if (!fs.existsSync(absolutePath)) {
93
+ throw new Error(`Pfad existiert nicht: ${absolutePath}`);
94
+ }
95
+ if (pkg.projects.includes(absolutePath)) {
96
+ throw new Error(`Projekt bereits in Package: ${getProjectName(absolutePath)}`);
97
+ }
98
+ pkg.projects.push(absolutePath);
99
+ pkg.modified = (/* @__PURE__ */ new Date()).toISOString();
100
+ packageStore.set("packages", packages);
101
+ return pkg;
102
+ }
103
+ function removeProjectFromPackage(packageName, projectPath) {
104
+ const packages = packageStore.get("packages") || {};
105
+ const key = packageName.toLowerCase();
106
+ const pkg = packages[key];
107
+ if (!pkg) {
108
+ throw new Error(`Package "${packageName}" nicht gefunden`);
109
+ }
110
+ const absolutePath = path.resolve(projectPath);
111
+ const projectName = getProjectName(projectPath);
112
+ const index = pkg.projects.findIndex(
113
+ (p) => p === absolutePath || getProjectName(p).toLowerCase() === projectName.toLowerCase()
114
+ );
115
+ if (index === -1) {
116
+ throw new Error(`Projekt nicht in Package: ${projectPath}`);
117
+ }
118
+ pkg.projects.splice(index, 1);
119
+ pkg.modified = (/* @__PURE__ */ new Date()).toISOString();
120
+ packageStore.set("packages", packages);
121
+ return pkg;
122
+ }
123
+ async function getPackageLaunchConfig(packageName, newSessions = false) {
124
+ const pkg = getPackage(packageName);
125
+ if (!pkg) {
126
+ throw new Error(`Package "${packageName}" nicht gefunden`);
127
+ }
128
+ const launches = [];
129
+ for (const projectPath of pkg.projects) {
130
+ if (!fs.existsSync(projectPath)) {
131
+ console.warn(`Skipping non-existent path: ${projectPath}`);
132
+ continue;
133
+ }
134
+ const project = await findProject(projectPath);
135
+ const latestSession = project?.latestSession;
136
+ launches.push({
137
+ projectPath,
138
+ projectName: getProjectName(projectPath),
139
+ sessionId: newSessions ? void 0 : latestSession?.sessionId,
140
+ newSession: newSessions || !latestSession
141
+ });
142
+ }
143
+ return launches;
144
+ }
145
+ function findPackagesWithProject(projectPath) {
146
+ const absolutePath = path.resolve(projectPath);
147
+ const packages = getAllPackages();
148
+ return packages.filter(
149
+ (pkg) => pkg.projects.some(
150
+ (p) => p === absolutePath || getProjectName(p).toLowerCase() === getProjectName(projectPath).toLowerCase()
151
+ )
152
+ );
153
+ }
154
+ function getPackageStats() {
155
+ const packages = getAllPackages();
156
+ const totalProjects = packages.reduce((sum, pkg) => sum + pkg.projects.length, 0);
157
+ return {
158
+ totalPackages: packages.length,
159
+ totalProjects,
160
+ averageProjectsPerPackage: packages.length > 0 ? Math.round(totalProjects / packages.length * 10) / 10 : 0
161
+ };
162
+ }
163
+
164
+ export {
165
+ getAllPackages,
166
+ getPackage,
167
+ createPackage,
168
+ updatePackage,
169
+ deletePackage,
170
+ addProjectToPackage,
171
+ removeProjectFromPackage,
172
+ getPackageLaunchConfig,
173
+ findPackagesWithProject,
174
+ getPackageStats
175
+ };
@@ -0,0 +1,304 @@
1
+ import {
2
+ cache
3
+ } from "./chunk-G2G6UUWM.js";
4
+
5
+ // src/services/session-manager.ts
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import * as os from "os";
9
+ import * as readline from "readline";
10
+ function getClaudeProjectsPath() {
11
+ return path.join(os.homedir(), ".claude", "projects");
12
+ }
13
+ function encodeProjectPath(projectPath) {
14
+ return projectPath.replace(/\//g, "-").replace(/^-/, "");
15
+ }
16
+ function decodeProjectPath(encoded) {
17
+ const parts = encoded.split("-").filter(Boolean);
18
+ return "/" + parts.join("/");
19
+ }
20
+ function getProjectName(projectPath) {
21
+ return path.basename(projectPath);
22
+ }
23
+ async function getAllClaudeProjects(skipCache = false) {
24
+ if (!skipCache) {
25
+ const cached = cache.getSessions();
26
+ if (cached) {
27
+ return cached;
28
+ }
29
+ }
30
+ const projectsPath = getClaudeProjectsPath();
31
+ if (!fs.existsSync(projectsPath)) {
32
+ return [];
33
+ }
34
+ const projects = [];
35
+ const entries = fs.readdirSync(projectsPath, { withFileTypes: true });
36
+ for (const entry of entries) {
37
+ if (!entry.isDirectory()) continue;
38
+ const encodedPath = entry.name;
39
+ const absolutePath = decodeProjectPath(encodedPath);
40
+ const projectName = getProjectName(absolutePath);
41
+ const sessionIndex = getSessionIndex(encodedPath);
42
+ const sessions = sessionIndex?.entries || [];
43
+ sessions.sort(
44
+ (a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
45
+ );
46
+ projects.push({
47
+ encodedPath,
48
+ absolutePath,
49
+ projectName,
50
+ sessions,
51
+ latestSession: sessions[0]
52
+ });
53
+ }
54
+ projects.sort((a, b) => {
55
+ const aTime = a.latestSession ? new Date(a.latestSession.modified).getTime() : 0;
56
+ const bTime = b.latestSession ? new Date(b.latestSession.modified).getTime() : 0;
57
+ return bTime - aTime;
58
+ });
59
+ cache.setSessions(projects);
60
+ return projects;
61
+ }
62
+ function invalidateSessionsCache() {
63
+ cache.invalidateSessions();
64
+ }
65
+ function getSessionIndex(encodedPath) {
66
+ const indexPath = path.join(
67
+ getClaudeProjectsPath(),
68
+ encodedPath,
69
+ "sessions-index.json"
70
+ );
71
+ if (!fs.existsSync(indexPath)) {
72
+ return null;
73
+ }
74
+ try {
75
+ const content = fs.readFileSync(indexPath, "utf-8");
76
+ return JSON.parse(content);
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ async function findProject(nameOrPath) {
82
+ const projects = await getAllClaudeProjects();
83
+ const absolutePath = path.resolve(nameOrPath);
84
+ const byPath = projects.find((p) => p.absolutePath === absolutePath);
85
+ if (byPath) return byPath;
86
+ const lowerName = nameOrPath.toLowerCase();
87
+ const byName = projects.find(
88
+ (p) => p.projectName.toLowerCase() === lowerName || p.projectName.toLowerCase().includes(lowerName)
89
+ );
90
+ return byName || null;
91
+ }
92
+ function findSessionByBranch(project, branch) {
93
+ const branchSessions = project.sessions.filter((s) => s.gitBranch === branch);
94
+ if (branchSessions.length === 0) {
95
+ return null;
96
+ }
97
+ return branchSessions[0];
98
+ }
99
+ async function findProjectForCurrentDir() {
100
+ const cwd = process.cwd();
101
+ return findProject(cwd);
102
+ }
103
+ function getSessionFilePath(session) {
104
+ return session.fullPath;
105
+ }
106
+ function isSessionCorrupted(session) {
107
+ const filePath = getSessionFilePath(session);
108
+ if (!fs.existsSync(filePath)) {
109
+ return true;
110
+ }
111
+ try {
112
+ const stats = fs.statSync(filePath);
113
+ if (stats.size > 100 * 1024 * 1024) {
114
+ return true;
115
+ }
116
+ const fd = fs.openSync(filePath, "r");
117
+ const buffer = Buffer.alloc(4096);
118
+ const bytesRead = fs.readSync(fd, buffer, 0, 4096, 0);
119
+ fs.closeSync(fd);
120
+ if (bytesRead === 0) {
121
+ return true;
122
+ }
123
+ const firstLine = buffer.toString("utf-8").split("\n")[0];
124
+ JSON.parse(firstLine);
125
+ return false;
126
+ } catch {
127
+ return true;
128
+ }
129
+ }
130
+ function isSessionActive(session) {
131
+ const filePath = getSessionFilePath(session);
132
+ if (!fs.existsSync(filePath)) {
133
+ return false;
134
+ }
135
+ try {
136
+ const stats = fs.statSync(filePath);
137
+ return Date.now() - stats.mtimeMs < 3e4;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+ async function parseSessionFile(jsonlPath, level = "standard") {
143
+ const summaries = [];
144
+ if (!fs.existsSync(jsonlPath)) {
145
+ return summaries;
146
+ }
147
+ return new Promise((resolve2) => {
148
+ const fileStream = fs.createReadStream(jsonlPath, { encoding: "utf-8" });
149
+ const rl = readline.createInterface({
150
+ input: fileStream,
151
+ crlfDelay: Infinity
152
+ });
153
+ let lineCount = 0;
154
+ const maxLines = level === "minimal" ? 1e3 : level === "standard" ? 1e4 : Infinity;
155
+ rl.on("line", (line) => {
156
+ lineCount++;
157
+ if (lineCount > maxLines) {
158
+ rl.close();
159
+ return;
160
+ }
161
+ try {
162
+ const entry = JSON.parse(line);
163
+ if (entry.type === "summary") {
164
+ summaries.push({
165
+ type: "summary",
166
+ content: entry.summary || entry.content || "",
167
+ timestamp: entry.timestamp
168
+ });
169
+ }
170
+ if (level === "full") {
171
+ if (entry.type === "user" && entry.message?.content) {
172
+ const content = typeof entry.message.content === "string" ? entry.message.content : JSON.stringify(entry.message.content);
173
+ summaries.push({
174
+ type: "user",
175
+ content: content.substring(0, 500),
176
+ timestamp: entry.timestamp
177
+ });
178
+ }
179
+ }
180
+ } catch {
181
+ }
182
+ });
183
+ rl.on("close", () => {
184
+ resolve2(summaries);
185
+ });
186
+ rl.on("error", () => {
187
+ resolve2(summaries);
188
+ });
189
+ });
190
+ }
191
+ async function getRecoveredContext(session, level = "standard") {
192
+ const summaries = await parseSessionFile(session.fullPath, level);
193
+ const summaryTexts = summaries.filter((s) => s.type === "summary").map((s) => s.content).filter(Boolean);
194
+ const filteredSummaries = level === "minimal" ? summaryTexts.slice(-5) : summaryTexts;
195
+ return {
196
+ projectPath: session.projectPath,
197
+ summaries: filteredSummaries,
198
+ firstPrompt: session.firstPrompt,
199
+ lastModified: session.modified,
200
+ messageCount: session.messageCount,
201
+ gitBranch: session.gitBranch
202
+ };
203
+ }
204
+ function formatRecoveredContextAsMarkdown(context) {
205
+ const lines = [];
206
+ lines.push("# Session Recovery Context");
207
+ lines.push("");
208
+ lines.push(`**Project:** ${context.projectPath}`);
209
+ lines.push(`**Branch:** ${context.gitBranch}`);
210
+ lines.push(`**Last Modified:** ${context.lastModified}`);
211
+ lines.push(`**Messages:** ~${context.messageCount}`);
212
+ lines.push("");
213
+ if (context.firstPrompt) {
214
+ lines.push("## Original Task");
215
+ lines.push("");
216
+ lines.push(context.firstPrompt);
217
+ lines.push("");
218
+ }
219
+ if (context.summaries.length > 0) {
220
+ lines.push("## Session Summaries");
221
+ lines.push("");
222
+ for (const summary of context.summaries) {
223
+ lines.push("---");
224
+ lines.push("");
225
+ lines.push(summary);
226
+ lines.push("");
227
+ }
228
+ }
229
+ return lines.join("\n");
230
+ }
231
+ function saveRecoveredContext(context, outputDir) {
232
+ const dir = outputDir || path.join(os.homedir(), ".shiva", "recovery");
233
+ if (!fs.existsSync(dir)) {
234
+ fs.mkdirSync(dir, { recursive: true });
235
+ }
236
+ const projectSlug = getProjectName(context.projectPath).toLowerCase().replace(/[^a-z0-9]+/g, "-");
237
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
238
+ const filename = `${projectSlug}-${date}.md`;
239
+ const filepath = path.join(dir, filename);
240
+ const content = formatRecoveredContextAsMarkdown(context);
241
+ fs.writeFileSync(filepath, content, "utf-8");
242
+ return filepath;
243
+ }
244
+ async function getSessionStats() {
245
+ const projects = await getAllClaudeProjects();
246
+ let totalSessions = 0;
247
+ let activeProjects = 0;
248
+ for (const project of projects) {
249
+ totalSessions += project.sessions.length;
250
+ if (project.sessions.length > 0) {
251
+ activeProjects++;
252
+ }
253
+ }
254
+ return {
255
+ totalProjects: projects.length,
256
+ totalSessions,
257
+ activeProjects
258
+ };
259
+ }
260
+ function formatRelativeTime(date) {
261
+ const now = /* @__PURE__ */ new Date();
262
+ const then = new Date(date);
263
+ const diffMs = now.getTime() - then.getTime();
264
+ const minutes = Math.floor(diffMs / (1e3 * 60));
265
+ const hours = Math.floor(diffMs / (1e3 * 60 * 60));
266
+ const days = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
267
+ if (minutes < 1) return "gerade eben";
268
+ if (minutes < 60) return `${minutes}m`;
269
+ if (hours < 24) return `${hours}h`;
270
+ if (days < 7) return `${days}d`;
271
+ return then.toLocaleDateString("de-DE", { month: "short", day: "numeric" });
272
+ }
273
+ function formatDate(date) {
274
+ const d = new Date(date);
275
+ return d.toLocaleDateString("de-DE", {
276
+ month: "short",
277
+ day: "numeric",
278
+ hour: "2-digit",
279
+ minute: "2-digit"
280
+ });
281
+ }
282
+
283
+ export {
284
+ getClaudeProjectsPath,
285
+ encodeProjectPath,
286
+ decodeProjectPath,
287
+ getProjectName,
288
+ getAllClaudeProjects,
289
+ invalidateSessionsCache,
290
+ getSessionIndex,
291
+ findProject,
292
+ findSessionByBranch,
293
+ findProjectForCurrentDir,
294
+ getSessionFilePath,
295
+ isSessionCorrupted,
296
+ isSessionActive,
297
+ parseSessionFile,
298
+ getRecoveredContext,
299
+ formatRecoveredContextAsMarkdown,
300
+ saveRecoveredContext,
301
+ getSessionStats,
302
+ formatRelativeTime,
303
+ formatDate
304
+ };