shiva-code 0.7.18 → 0.8.2

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.
@@ -187,6 +187,27 @@ var log = {
187
187
  // Done message (for completing operations)
188
188
  done: (message) => {
189
189
  console.log(`${shivaSuccess("\u25CF")} ${message}`);
190
+ },
191
+ // Auth required shorthand - for consistent auth error messages
192
+ authRequired: () => {
193
+ console.log(shivaError("\u2717"), "Nicht angemeldet");
194
+ console.log(chalk.dim(" \u2192"), shivaSecondary("Anmelden mit: shiva login"));
195
+ },
196
+ // Section header with centered box (like in onboarding)
197
+ sectionBox: (title, width = 45) => {
198
+ const innerWidth = width - 2;
199
+ const visibleLen = title.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "").length;
200
+ const paddingLeft = Math.floor((innerWidth - visibleLen) / 2);
201
+ const paddingRight = innerWidth - visibleLen - paddingLeft;
202
+ console.log(shivaPrimary(" \u256D" + "\u2500".repeat(innerWidth) + "\u256E"));
203
+ console.log(shivaPrimary(" \u2502") + " ".repeat(paddingLeft) + title + " ".repeat(paddingRight) + shivaPrimary("\u2502"));
204
+ console.log(shivaPrimary(" \u2570" + "\u2500".repeat(innerWidth) + "\u256F"));
205
+ },
206
+ // Inline key-value pairs with consistent padding
207
+ inlineKeyValue: (pairs) => {
208
+ for (const { key, value } of pairs) {
209
+ console.log(` ${chalk.dim(key.padEnd(15))} ${shivaSecondary(value)}`);
210
+ }
190
211
  }
191
212
  };
192
213
  var colors = {
@@ -0,0 +1,417 @@
1
+ import {
2
+ api
3
+ } from "./chunk-KB6M24M3.js";
4
+ import {
5
+ getExtendedConfig,
6
+ setClaudeArgs,
7
+ setClaudeSkipPermissions,
8
+ setDefaultTerminal
9
+ } from "./chunk-OP4HYQZZ.js";
10
+
11
+ // src/services/data/settings-sync.ts
12
+ import * as crypto from "crypto";
13
+ import Conf from "conf";
14
+ var settingsStore = new Conf({
15
+ projectName: "shiva-code-settings",
16
+ defaults: {
17
+ syncableSettings: {
18
+ version: 1,
19
+ lastModified: (/* @__PURE__ */ new Date()).toISOString(),
20
+ defaultTerminal: "auto",
21
+ claudeArgs: [],
22
+ claudeSkipPermissions: true,
23
+ docker: {
24
+ enabled: false,
25
+ defaultImage: "shiva/claude-runner:latest",
26
+ autoStart: true,
27
+ volumeMounts: {},
28
+ environment: {}
29
+ },
30
+ language: "de",
31
+ theme: "auto",
32
+ features: {
33
+ autoSync: true,
34
+ githubContext: true,
35
+ secretsInjection: true
36
+ }
37
+ },
38
+ syncState: {
39
+ lastSyncedAt: null,
40
+ localHash: "",
41
+ cloudHash: null,
42
+ conflictStrategy: "manual",
43
+ pendingChanges: false
44
+ }
45
+ }
46
+ });
47
+ var SettingsSyncService = class {
48
+ // ============================================
49
+ // Local Settings Management
50
+ // ============================================
51
+ /**
52
+ * Get local syncable settings
53
+ * Merges config.ts values with syncable settings store
54
+ */
55
+ getLocalSettings() {
56
+ const stored = settingsStore.get("syncableSettings");
57
+ const extConfig = getExtendedConfig();
58
+ const settings = {
59
+ ...stored,
60
+ defaultTerminal: extConfig.defaultTerminal,
61
+ claudeArgs: extConfig.claudeArgs,
62
+ claudeSkipPermissions: extConfig.claudeSkipPermissions,
63
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
64
+ };
65
+ return settings;
66
+ }
67
+ /**
68
+ * Update local settings
69
+ */
70
+ setLocalSettings(settings) {
71
+ const current = this.getLocalSettings();
72
+ const updated = {
73
+ ...current,
74
+ ...settings,
75
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
76
+ };
77
+ settingsStore.set("syncableSettings", updated);
78
+ if (settings.defaultTerminal !== void 0) {
79
+ setDefaultTerminal(settings.defaultTerminal);
80
+ }
81
+ if (settings.claudeArgs !== void 0) {
82
+ setClaudeArgs(settings.claudeArgs);
83
+ }
84
+ if (settings.claudeSkipPermissions !== void 0) {
85
+ setClaudeSkipPermissions(settings.claudeSkipPermissions);
86
+ }
87
+ this.updateSyncState({ pendingChanges: true });
88
+ }
89
+ /**
90
+ * Calculate hash of settings for conflict detection
91
+ */
92
+ getLocalHash() {
93
+ const settings = this.getLocalSettings();
94
+ const { lastModified, ...hashable } = settings;
95
+ return crypto.createHash("md5").update(JSON.stringify(hashable)).digest("hex").substring(0, 8);
96
+ }
97
+ // ============================================
98
+ // Sync State Management
99
+ // ============================================
100
+ /**
101
+ * Get current sync state
102
+ */
103
+ getSyncState() {
104
+ const state = settingsStore.get("syncState");
105
+ return {
106
+ ...state,
107
+ localHash: this.getLocalHash()
108
+ };
109
+ }
110
+ /**
111
+ * Update sync state
112
+ */
113
+ updateSyncState(partial) {
114
+ const current = settingsStore.get("syncState");
115
+ settingsStore.set("syncState", { ...current, ...partial });
116
+ }
117
+ /**
118
+ * Set conflict resolution strategy
119
+ */
120
+ setConflictStrategy(strategy) {
121
+ this.updateSyncState({ conflictStrategy: strategy });
122
+ }
123
+ // ============================================
124
+ // Cloud Sync Operations
125
+ // ============================================
126
+ /**
127
+ * Get settings from cloud
128
+ */
129
+ async getCloudSettings() {
130
+ try {
131
+ const response = await api.getUserSettings();
132
+ return response.settings || null;
133
+ } catch (error) {
134
+ return null;
135
+ }
136
+ }
137
+ /**
138
+ * Push local settings to cloud
139
+ */
140
+ async pushToCloud(settings) {
141
+ const localSettings = settings || this.getLocalSettings();
142
+ const localHash = this.getLocalHash();
143
+ await api.updateUserSettings({
144
+ settings: localSettings,
145
+ localHash
146
+ });
147
+ this.updateSyncState({
148
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
149
+ cloudHash: localHash,
150
+ pendingChanges: false
151
+ });
152
+ }
153
+ /**
154
+ * Pull settings from cloud and apply locally
155
+ */
156
+ async pullFromCloud() {
157
+ const cloudSettings = await this.getCloudSettings();
158
+ if (!cloudSettings) {
159
+ throw new Error("No cloud settings found");
160
+ }
161
+ this.setLocalSettings(cloudSettings);
162
+ const cloudHash = this.calculateHash(cloudSettings);
163
+ this.updateSyncState({
164
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
165
+ cloudHash,
166
+ pendingChanges: false
167
+ });
168
+ return cloudSettings;
169
+ }
170
+ /**
171
+ * Calculate hash for any settings object
172
+ */
173
+ calculateHash(settings) {
174
+ const { lastModified, ...hashable } = settings;
175
+ return crypto.createHash("md5").update(JSON.stringify(hashable)).digest("hex").substring(0, 8);
176
+ }
177
+ // ============================================
178
+ // Sync Operations
179
+ // ============================================
180
+ /**
181
+ * Check if local and cloud are out of sync
182
+ */
183
+ async isOutOfSync() {
184
+ const cloudSettings = await this.getCloudSettings();
185
+ if (!cloudSettings) {
186
+ return true;
187
+ }
188
+ const localHash = this.getLocalHash();
189
+ const cloudHash = this.calculateHash(cloudSettings);
190
+ return localHash !== cloudHash;
191
+ }
192
+ /**
193
+ * Perform sync with optional strategy override
194
+ */
195
+ async sync(strategy) {
196
+ const resolveStrategy = strategy || this.getSyncState().conflictStrategy;
197
+ const localSettings = this.getLocalSettings();
198
+ const localHash = this.getLocalHash();
199
+ try {
200
+ const response = await api.syncUserSettings({
201
+ settings: localSettings,
202
+ localHash
203
+ });
204
+ if (response.conflict) {
205
+ if (resolveStrategy === "local-wins") {
206
+ await api.updateUserSettings({
207
+ settings: localSettings,
208
+ localHash,
209
+ forceOverwrite: true
210
+ });
211
+ this.updateSyncState({
212
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
213
+ cloudHash: localHash,
214
+ pendingChanges: false
215
+ });
216
+ return {
217
+ success: true,
218
+ action: "pushed",
219
+ settings: localSettings,
220
+ message: "Lokale Einstellungen hochgeladen (local-wins)"
221
+ };
222
+ }
223
+ if (resolveStrategy === "cloud-wins") {
224
+ const cloudSettings = response.conflict.cloudSettings;
225
+ this.setLocalSettings(cloudSettings);
226
+ const cloudHash = this.calculateHash(cloudSettings);
227
+ this.updateSyncState({
228
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
229
+ cloudHash,
230
+ pendingChanges: false
231
+ });
232
+ return {
233
+ success: true,
234
+ action: "pulled",
235
+ settings: cloudSettings,
236
+ message: "Cloud-Einstellungen \xFCbernommen (cloud-wins)"
237
+ };
238
+ }
239
+ return {
240
+ success: false,
241
+ action: "conflict",
242
+ conflict: {
243
+ type: response.conflict.type,
244
+ localSettings,
245
+ cloudSettings: response.conflict.cloudSettings
246
+ },
247
+ message: "Konflikt erkannt. Manuelle Aufl\xF6sung erforderlich."
248
+ };
249
+ }
250
+ this.updateSyncState({
251
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
252
+ cloudHash: response.cloudHash,
253
+ pendingChanges: false
254
+ });
255
+ return {
256
+ success: true,
257
+ action: "pushed",
258
+ settings: response.settings,
259
+ message: "Sync erfolgreich"
260
+ };
261
+ } catch (error) {
262
+ if (error instanceof Error && error.message.includes("404")) {
263
+ await this.pushToCloud(localSettings);
264
+ return {
265
+ success: true,
266
+ action: "pushed",
267
+ settings: localSettings,
268
+ message: "Erste Synchronisation erfolgreich"
269
+ };
270
+ }
271
+ throw error;
272
+ }
273
+ }
274
+ /**
275
+ * Merge settings from local and cloud
276
+ */
277
+ mergeSettings(local, cloud) {
278
+ const localTime = new Date(local.lastModified).getTime();
279
+ const cloudTime = new Date(cloud.lastModified).getTime();
280
+ return {
281
+ version: Math.max(local.version, cloud.version),
282
+ lastModified: (/* @__PURE__ */ new Date()).toISOString(),
283
+ // CLI-specific: prefer local
284
+ defaultTerminal: local.defaultTerminal,
285
+ claudeArgs: local.claudeArgs,
286
+ claudeSkipPermissions: local.claudeSkipPermissions,
287
+ // Docker: merge
288
+ docker: {
289
+ ...cloud.docker,
290
+ ...local.docker,
291
+ volumeMounts: { ...cloud.docker.volumeMounts, ...local.docker.volumeMounts },
292
+ environment: { ...cloud.docker.environment, ...local.docker.environment }
293
+ },
294
+ // Preferences: prefer newer
295
+ language: localTime > cloudTime ? local.language : cloud.language,
296
+ theme: localTime > cloudTime ? local.theme : cloud.theme,
297
+ // Features: prefer cloud (browser-configurable)
298
+ features: cloud.features
299
+ };
300
+ }
301
+ /**
302
+ * Resolve conflict with specific resolution
303
+ */
304
+ async resolveConflict(resolution) {
305
+ const localSettings = this.getLocalSettings();
306
+ const cloudSettings = await this.getCloudSettings();
307
+ if (!cloudSettings) {
308
+ await this.pushToCloud(localSettings);
309
+ return {
310
+ success: true,
311
+ action: "pushed",
312
+ settings: localSettings,
313
+ message: "Lokale Einstellungen hochgeladen"
314
+ };
315
+ }
316
+ let finalSettings;
317
+ let action;
318
+ switch (resolution) {
319
+ case "local":
320
+ finalSettings = localSettings;
321
+ action = "pushed";
322
+ break;
323
+ case "cloud":
324
+ finalSettings = cloudSettings;
325
+ action = "pulled";
326
+ break;
327
+ case "merge":
328
+ finalSettings = this.mergeSettings(localSettings, cloudSettings);
329
+ action = "merged";
330
+ break;
331
+ }
332
+ if (resolution === "cloud") {
333
+ this.setLocalSettings(finalSettings);
334
+ } else {
335
+ await api.updateUserSettings({
336
+ settings: finalSettings,
337
+ localHash: this.calculateHash(finalSettings),
338
+ forceOverwrite: true
339
+ });
340
+ }
341
+ const finalHash = this.calculateHash(finalSettings);
342
+ this.updateSyncState({
343
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
344
+ cloudHash: finalHash,
345
+ pendingChanges: false
346
+ });
347
+ return {
348
+ success: true,
349
+ action,
350
+ settings: finalSettings,
351
+ message: `Konflikt aufgel\xF6st: ${resolution}`
352
+ };
353
+ }
354
+ // ============================================
355
+ // Docker Settings Helpers
356
+ // ============================================
357
+ /**
358
+ * Get Docker settings
359
+ */
360
+ getDockerSettings() {
361
+ return this.getLocalSettings().docker;
362
+ }
363
+ /**
364
+ * Update Docker settings
365
+ */
366
+ setDockerSettings(docker) {
367
+ const current = this.getLocalSettings();
368
+ this.setLocalSettings({
369
+ docker: { ...current.docker, ...docker }
370
+ });
371
+ }
372
+ /**
373
+ * Enable/disable Docker mode
374
+ */
375
+ setDockerEnabled(enabled) {
376
+ this.setDockerSettings({ enabled });
377
+ }
378
+ /**
379
+ * Set default Docker image
380
+ */
381
+ setDockerImage(image) {
382
+ this.setDockerSettings({ defaultImage: image });
383
+ }
384
+ // ============================================
385
+ // Feature Flags
386
+ // ============================================
387
+ /**
388
+ * Get feature flags
389
+ */
390
+ getFeatures() {
391
+ return this.getLocalSettings().features;
392
+ }
393
+ /**
394
+ * Set feature flag
395
+ */
396
+ setFeature(feature, enabled) {
397
+ const current = this.getLocalSettings();
398
+ this.setLocalSettings({
399
+ features: { ...current.features, [feature]: enabled }
400
+ });
401
+ }
402
+ // ============================================
403
+ // Settings History (for debugging)
404
+ // ============================================
405
+ /**
406
+ * Get settings history from cloud
407
+ */
408
+ async getHistory() {
409
+ return api.getSettingsHistory();
410
+ }
411
+ };
412
+ var settingsSync = new SettingsSyncService();
413
+
414
+ export {
415
+ SettingsSyncService,
416
+ settingsSync
417
+ };
@@ -95,7 +95,53 @@ function encodeProjectPath(projectPath) {
95
95
  }
96
96
  function decodeProjectPath(encoded) {
97
97
  const parts = encoded.split("-").filter(Boolean);
98
- return "/" + parts.join("/");
98
+ let currentPath = "";
99
+ let i = 0;
100
+ while (i < parts.length) {
101
+ const part = parts[i];
102
+ const testPath = currentPath + "/" + part;
103
+ if (fs.existsSync(testPath)) {
104
+ currentPath = testPath;
105
+ i++;
106
+ } else {
107
+ let found = false;
108
+ for (let j = i + 1; j < parts.length && j <= i + 5 && !found; j++) {
109
+ const partsToJoin = parts.slice(i, j + 1);
110
+ const withHyphen = partsToJoin.join("-");
111
+ const hyphenPath = currentPath + "/" + withHyphen;
112
+ if (fs.existsSync(hyphenPath)) {
113
+ currentPath = hyphenPath;
114
+ i = j + 1;
115
+ found = true;
116
+ break;
117
+ }
118
+ const withDot = partsToJoin.join(".");
119
+ const dotPath = currentPath + "/" + withDot;
120
+ if (fs.existsSync(dotPath)) {
121
+ currentPath = dotPath;
122
+ i = j + 1;
123
+ found = true;
124
+ break;
125
+ }
126
+ const withUnderscore = partsToJoin.join("_");
127
+ const underscorePath = currentPath + "/" + withUnderscore;
128
+ if (fs.existsSync(underscorePath)) {
129
+ currentPath = underscorePath;
130
+ i = j + 1;
131
+ found = true;
132
+ break;
133
+ }
134
+ }
135
+ if (!found) {
136
+ currentPath = testPath;
137
+ i++;
138
+ }
139
+ }
140
+ }
141
+ if (!currentPath || currentPath === "/") {
142
+ return "/" + parts.join("/");
143
+ }
144
+ return currentPath;
99
145
  }
100
146
  function getProjectName(projectPath) {
101
147
  return path2.basename(projectPath);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  findProject,
3
3
  getProjectName
4
- } from "./chunk-W3DKHCXB.js";
4
+ } from "./chunk-GHGMDDLO.js";
5
5
 
6
6
  // src/services/data/package-manager.ts
7
7
  import Conf from "conf";