@uagents/syncenv-cli 0.1.4 → 0.1.6
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/README.md +207 -49
- package/dist/{chunk-JBMZAAVP.js → chunk-LWTV6MO2.js} +1 -1
- package/dist/{chunk-OVEYHV4C.js → chunk-YXE467TO.js} +51 -11
- package/dist/{cookie-store-Z6DNTUGS.js → cookie-store-UGGEBXBV.js} +1 -1
- package/dist/index.js +1283 -242
- package/dist/{secure-storage-UEK3LD5L.js → secure-storage-AR7HZFTA.js} +2 -2
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
isKEKCached,
|
|
15
15
|
lockKEK,
|
|
16
16
|
unlockAndStoreKEK
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-LWTV6MO2.js";
|
|
18
18
|
import {
|
|
19
19
|
clearAuthState,
|
|
20
|
+
clearConfig,
|
|
20
21
|
client,
|
|
22
|
+
deleteConfig,
|
|
21
23
|
getConfig,
|
|
22
24
|
getConfigPath,
|
|
23
25
|
getOrUnlockUserKEK,
|
|
@@ -25,10 +27,11 @@ import {
|
|
|
25
27
|
isAuthenticated,
|
|
26
28
|
loadConfig,
|
|
27
29
|
loadProjectConfig,
|
|
30
|
+
resolveProjectIdentifier,
|
|
28
31
|
saveProjectConfig,
|
|
29
32
|
setConfig,
|
|
30
33
|
withAuthGuard
|
|
31
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-YXE467TO.js";
|
|
32
35
|
import {
|
|
33
36
|
applyMergeStrategy,
|
|
34
37
|
interactiveMerge,
|
|
@@ -36,8 +39,11 @@ import {
|
|
|
36
39
|
} from "./chunk-F7ZZUTRW.js";
|
|
37
40
|
|
|
38
41
|
// src/index.ts
|
|
39
|
-
import
|
|
40
|
-
import {
|
|
42
|
+
import { readFileSync } from "fs";
|
|
43
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
44
|
+
import { fileURLToPath } from "url";
|
|
45
|
+
import chalk10 from "chalk";
|
|
46
|
+
import { Command as Command9 } from "commander";
|
|
41
47
|
|
|
42
48
|
// src/commands/auth.ts
|
|
43
49
|
import chalk2 from "chalk";
|
|
@@ -131,8 +137,8 @@ authCommands.command("signup").description("Guide to create a new account via we
|
|
|
131
137
|
console.log(" \u2022 Secure password setup");
|
|
132
138
|
console.log(" \u2022 User-friendly interface");
|
|
133
139
|
console.log();
|
|
134
|
-
const apiUrl = getConfig("apiUrl") || "https://syncenv.uagents.app";
|
|
135
|
-
const signupUrl =
|
|
140
|
+
const apiUrl = getConfig("apiUrl") || "https://syncenv-api.uagents.app";
|
|
141
|
+
const signupUrl = apiUrl.replace("-api", "") + "/signup";
|
|
136
142
|
console.log(chalk2.cyan("\u{1F310} Please visit:"));
|
|
137
143
|
console.log(chalk2.bold(signupUrl));
|
|
138
144
|
console.log();
|
|
@@ -158,7 +164,8 @@ authCommands.command("signup").description("Guide to create a new account via we
|
|
|
158
164
|
success("Waiting for you to complete registration...");
|
|
159
165
|
info("Once done, run: syncenv auth login");
|
|
160
166
|
});
|
|
161
|
-
authCommands.command("login").description("Login to your account").option("-e, --email <email>", "email address").action(async (options) => {
|
|
167
|
+
authCommands.command("login").description("Login to your account").option("-e, --email <email>", "email address").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
168
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
162
169
|
try {
|
|
163
170
|
let email = options.email;
|
|
164
171
|
if (!email) {
|
|
@@ -183,29 +190,95 @@ authCommands.command("login").description("Login to your account").option("-e, -
|
|
|
183
190
|
mask: "*"
|
|
184
191
|
}
|
|
185
192
|
]);
|
|
193
|
+
if (isDebug) {
|
|
194
|
+
console.log(chalk2.dim("\n[DEBUG] Configuration:"));
|
|
195
|
+
console.log(chalk2.dim(` API URL: ${getConfig("apiUrl")}`));
|
|
196
|
+
console.log(chalk2.dim(` Email: ${email}`));
|
|
197
|
+
console.log(chalk2.dim(` Debug Mode: ${isDebug}
|
|
198
|
+
`));
|
|
199
|
+
}
|
|
186
200
|
const spinner = createSpinner("Authenticating...");
|
|
187
201
|
spinner.start();
|
|
188
202
|
try {
|
|
189
203
|
const apiUrl = getConfig("apiUrl");
|
|
190
|
-
const
|
|
204
|
+
const webOrigin = apiUrl.replace("-api", "");
|
|
205
|
+
const origin = apiUrl.startsWith("http://localhost") ? apiUrl : webOrigin;
|
|
206
|
+
const loginUrl = `${apiUrl}/api/auth/sign-in/email`;
|
|
207
|
+
const requestHeaders = {
|
|
208
|
+
"Content-Type": "application/json",
|
|
209
|
+
Origin: origin
|
|
210
|
+
};
|
|
211
|
+
if (isDebug) {
|
|
212
|
+
spinner.stop();
|
|
213
|
+
console.log(chalk2.dim("\n[DEBUG] Login Request:"));
|
|
214
|
+
console.log(chalk2.dim(` URL: ${loginUrl}`));
|
|
215
|
+
console.log(chalk2.dim(` Method: POST`));
|
|
216
|
+
console.log(chalk2.dim(` Headers:`));
|
|
217
|
+
console.log(chalk2.dim(` Content-Type: ${requestHeaders["Content-Type"]}`));
|
|
218
|
+
console.log(chalk2.dim(` Origin: ${requestHeaders.Origin}`));
|
|
219
|
+
console.log(chalk2.dim(` Body: { email: "${email}", password: "***" }
|
|
220
|
+
`));
|
|
221
|
+
spinner.start("Authenticating...");
|
|
222
|
+
}
|
|
223
|
+
const loginResponse = await fetch(loginUrl, {
|
|
191
224
|
method: "POST",
|
|
192
|
-
headers:
|
|
225
|
+
headers: requestHeaders,
|
|
193
226
|
body: JSON.stringify({ email, password })
|
|
194
227
|
});
|
|
228
|
+
if (isDebug) {
|
|
229
|
+
spinner.stop();
|
|
230
|
+
console.log(chalk2.dim("[DEBUG] Login Response:"));
|
|
231
|
+
console.log(chalk2.dim(` Status: ${loginResponse.status} ${loginResponse.statusText}`));
|
|
232
|
+
console.log(chalk2.dim(` Headers:`));
|
|
233
|
+
loginResponse.headers.forEach((value, key) => {
|
|
234
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
235
|
+
console.log(chalk2.dim(` ${key}: [redacted]`));
|
|
236
|
+
} else {
|
|
237
|
+
console.log(chalk2.dim(` ${key}: ${value}`));
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
console.log("");
|
|
241
|
+
spinner.start("Authenticating...");
|
|
242
|
+
}
|
|
195
243
|
if (!loginResponse.ok) {
|
|
196
|
-
const
|
|
244
|
+
const errorText = await loginResponse.text().catch(() => "Login failed");
|
|
245
|
+
let errorData = {};
|
|
246
|
+
try {
|
|
247
|
+
errorData = JSON.parse(errorText);
|
|
248
|
+
} catch {
|
|
249
|
+
errorData = { error: errorText };
|
|
250
|
+
}
|
|
251
|
+
if (isDebug) {
|
|
252
|
+
spinner.stop();
|
|
253
|
+
console.log(chalk2.dim("[DEBUG] Error Response Body:"));
|
|
254
|
+
console.log(chalk2.dim(` ${errorText}
|
|
255
|
+
`));
|
|
256
|
+
}
|
|
197
257
|
throw new Error(errorData.error || errorData.message || "Login failed");
|
|
198
258
|
}
|
|
199
259
|
const setCookie = loginResponse.headers.getSetCookie?.() || (loginResponse.headers.get("set-cookie") ? [loginResponse.headers.get("set-cookie")] : []);
|
|
200
260
|
if (setCookie.length > 0) {
|
|
201
|
-
const { storeCookies } = await import("./cookie-store-
|
|
261
|
+
const { storeCookies } = await import("./cookie-store-UGGEBXBV.js");
|
|
202
262
|
storeCookies(setCookie);
|
|
203
263
|
}
|
|
204
264
|
const result = await loginResponse.json();
|
|
265
|
+
if (isDebug) {
|
|
266
|
+
spinner.stop();
|
|
267
|
+
console.log(chalk2.dim("[DEBUG] Login Success:"));
|
|
268
|
+
console.log(chalk2.dim(` User ID: ${result.user.id}`));
|
|
269
|
+
console.log(chalk2.dim(` Email: ${result.user.email}`));
|
|
270
|
+
console.log(chalk2.dim(` Name: ${result.user.name}
|
|
271
|
+
`));
|
|
272
|
+
spinner.start("Finalizing...");
|
|
273
|
+
}
|
|
205
274
|
setConfig("userId", result.user.id);
|
|
206
275
|
setConfig("userEmail", result.user.email);
|
|
207
276
|
spinner.succeed("Authenticated");
|
|
208
277
|
success("Session active");
|
|
278
|
+
if (isDebug) {
|
|
279
|
+
info(`User: ${result.user.email}`);
|
|
280
|
+
info(`Cookies stored: ${setCookie.length > 0 ? "Yes" : "No"}`);
|
|
281
|
+
}
|
|
209
282
|
} catch (err) {
|
|
210
283
|
spinner.fail("Authentication failed");
|
|
211
284
|
throw err;
|
|
@@ -215,8 +288,14 @@ authCommands.command("login").description("Login to your account").option("-e, -
|
|
|
215
288
|
process.exit(1);
|
|
216
289
|
}
|
|
217
290
|
});
|
|
218
|
-
authCommands.command("logout").description("Logout and clear session").action(async () => {
|
|
291
|
+
authCommands.command("logout").description("Logout and clear session").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
292
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
219
293
|
try {
|
|
294
|
+
if (isDebug) {
|
|
295
|
+
console.log(chalk2.dim("\n[DEBUG] Logout Command"));
|
|
296
|
+
console.log(chalk2.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
297
|
+
`));
|
|
298
|
+
}
|
|
220
299
|
if (!isAuthenticated()) {
|
|
221
300
|
info("Not logged in.");
|
|
222
301
|
return;
|
|
@@ -225,18 +304,38 @@ authCommands.command("logout").description("Logout and clear session").action(as
|
|
|
225
304
|
spinner.start();
|
|
226
305
|
try {
|
|
227
306
|
await client.request("POST", "/api/auth/signout");
|
|
228
|
-
|
|
307
|
+
if (isDebug) {
|
|
308
|
+
console.log(chalk2.dim("[DEBUG] Server signout successful"));
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
if (isDebug) {
|
|
312
|
+
console.log(chalk2.dim(`[DEBUG] Server signout error (ignored): ${err.message}`));
|
|
313
|
+
}
|
|
229
314
|
}
|
|
230
315
|
await clearAuthState();
|
|
316
|
+
if (isDebug) {
|
|
317
|
+
console.log(chalk2.dim("[DEBUG] Local auth state cleared"));
|
|
318
|
+
}
|
|
231
319
|
spinner.succeed("Logged out");
|
|
232
320
|
success("Session terminated and local keys cleared");
|
|
233
321
|
} catch (err) {
|
|
322
|
+
if (isDebug) {
|
|
323
|
+
console.log(chalk2.dim(`
|
|
324
|
+
[DEBUG] Logout error: ${err.message}`));
|
|
325
|
+
console.log(chalk2.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
326
|
+
}
|
|
234
327
|
error("Logout failed:", err.message);
|
|
235
328
|
process.exit(1);
|
|
236
329
|
}
|
|
237
330
|
});
|
|
238
|
-
authCommands.command("status").description("Check authentication status").action(async () => {
|
|
331
|
+
authCommands.command("status").description("Check authentication status").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
332
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
239
333
|
try {
|
|
334
|
+
if (isDebug) {
|
|
335
|
+
console.log(chalk2.dim("\n[DEBUG] Status Command"));
|
|
336
|
+
console.log(chalk2.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
337
|
+
`));
|
|
338
|
+
}
|
|
240
339
|
if (!isAuthenticated()) {
|
|
241
340
|
info("Not authenticated. Run `syncenv auth login` to login.");
|
|
242
341
|
return;
|
|
@@ -244,6 +343,9 @@ authCommands.command("status").description("Check authentication status").action
|
|
|
244
343
|
const { data: session } = await withAuthGuard(
|
|
245
344
|
() => client.request("GET", "/api/auth/get-session")
|
|
246
345
|
);
|
|
346
|
+
if (isDebug) {
|
|
347
|
+
console.log(chalk2.dim("[DEBUG] Session fetched from server"));
|
|
348
|
+
}
|
|
247
349
|
if (session.session) {
|
|
248
350
|
console.log(
|
|
249
351
|
`${chalk2.green("\u2713")} Authenticated as ${chalk2.cyan(session.session.user.email)}`
|
|
@@ -253,12 +355,23 @@ authCommands.command("status").description("Check authentication status").action
|
|
|
253
355
|
info("Session expired. Please login again.");
|
|
254
356
|
}
|
|
255
357
|
} catch (err) {
|
|
358
|
+
if (isDebug) {
|
|
359
|
+
console.log(chalk2.dim(`
|
|
360
|
+
[DEBUG] Status error: ${err.message}`));
|
|
361
|
+
console.log(chalk2.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
362
|
+
}
|
|
256
363
|
error("Failed to check status:", err.message);
|
|
257
364
|
process.exit(1);
|
|
258
365
|
}
|
|
259
366
|
});
|
|
260
|
-
authCommands.command("change-password").description("Change your account password").action(async () => {
|
|
367
|
+
authCommands.command("change-password").description("Change your account password").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
368
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
261
369
|
try {
|
|
370
|
+
if (isDebug) {
|
|
371
|
+
console.log(chalk2.dim("\n[DEBUG] Change Password Command"));
|
|
372
|
+
console.log(chalk2.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
373
|
+
`));
|
|
374
|
+
}
|
|
262
375
|
if (!isAuthenticated()) {
|
|
263
376
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
264
377
|
process.exit(1);
|
|
@@ -309,8 +422,14 @@ authCommands.command("change-password").description("Change your account passwor
|
|
|
309
422
|
}
|
|
310
423
|
})
|
|
311
424
|
);
|
|
425
|
+
if (isDebug) {
|
|
426
|
+
console.log(chalk2.dim("[DEBUG] Account password changed successfully"));
|
|
427
|
+
}
|
|
312
428
|
try {
|
|
313
429
|
const userKeys = await withAuthGuard(() => client.userKeys.get());
|
|
430
|
+
if (isDebug) {
|
|
431
|
+
console.log(chalk2.dim("[DEBUG] User has encryption keys, re-encrypting..."));
|
|
432
|
+
}
|
|
314
433
|
spinner.text = "Re-encrypting encryption keys...";
|
|
315
434
|
const { unlockUserKEK: unlockUserKEK2, reencryptUserKEK: reencryptUserKEK2 } = await import("./crypto-X7MZU7DV.js");
|
|
316
435
|
const userKek = await unlockUserKEK2(
|
|
@@ -325,10 +444,16 @@ authCommands.command("change-password").description("Change your account passwor
|
|
|
325
444
|
info("Run `syncenv user-keys rotate` to fix this.");
|
|
326
445
|
return;
|
|
327
446
|
}
|
|
447
|
+
if (isDebug) {
|
|
448
|
+
console.log(chalk2.dim("[DEBUG] User KEK decrypted with old password"));
|
|
449
|
+
}
|
|
328
450
|
const { encryptedUserKek, kekIv, kekSalt } = await reencryptUserKEK2(
|
|
329
451
|
userKek,
|
|
330
452
|
answers.newPassword
|
|
331
453
|
);
|
|
454
|
+
if (isDebug) {
|
|
455
|
+
console.log(chalk2.dim("[DEBUG] User KEK re-encrypted with new password"));
|
|
456
|
+
}
|
|
332
457
|
await withAuthGuard(
|
|
333
458
|
() => client.userKeys.update({
|
|
334
459
|
encryptedUserKek,
|
|
@@ -336,60 +461,310 @@ authCommands.command("change-password").description("Change your account passwor
|
|
|
336
461
|
kekSalt
|
|
337
462
|
})
|
|
338
463
|
);
|
|
339
|
-
|
|
464
|
+
if (isDebug) {
|
|
465
|
+
console.log(chalk2.dim("[DEBUG] Updated encrypted keys on server"));
|
|
466
|
+
}
|
|
467
|
+
const { saveEncryptedKEK, saveKEKPassword, getKEKPassword } = await import("./secure-storage-AR7HZFTA.js");
|
|
340
468
|
saveEncryptedKEK({ encryptedUserKek, kekIv, kekSalt });
|
|
341
469
|
const existingPassword = await getKEKPassword();
|
|
342
470
|
if (existingPassword) {
|
|
343
471
|
await saveKEKPassword(answers.newPassword);
|
|
472
|
+
if (isDebug) {
|
|
473
|
+
console.log(chalk2.dim("[DEBUG] Updated stored password in keychain"));
|
|
474
|
+
}
|
|
344
475
|
}
|
|
345
476
|
} catch (err) {
|
|
346
477
|
if (!err.message?.includes("404")) {
|
|
347
478
|
throw err;
|
|
348
479
|
}
|
|
480
|
+
if (isDebug) {
|
|
481
|
+
console.log(
|
|
482
|
+
chalk2.dim("[DEBUG] User has no encryption keys (404), skipping key re-encryption")
|
|
483
|
+
);
|
|
484
|
+
}
|
|
349
485
|
}
|
|
350
486
|
spinner.succeed("Password changed successfully");
|
|
351
487
|
success("Your account password has been updated");
|
|
352
488
|
} catch (err) {
|
|
489
|
+
if (isDebug) {
|
|
490
|
+
console.log(chalk2.dim(`
|
|
491
|
+
[DEBUG] Change password error: ${err.message}`));
|
|
492
|
+
console.log(chalk2.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
493
|
+
}
|
|
353
494
|
spinner.fail("Failed to change password");
|
|
354
495
|
throw err;
|
|
355
496
|
}
|
|
356
497
|
} catch (err) {
|
|
498
|
+
if (isDebug) {
|
|
499
|
+
console.log(chalk2.dim(`
|
|
500
|
+
[DEBUG] Outer change password error: ${err.message}`));
|
|
501
|
+
console.log(chalk2.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
502
|
+
}
|
|
357
503
|
error("Change password failed:", err.message);
|
|
358
504
|
process.exit(1);
|
|
359
505
|
}
|
|
360
506
|
});
|
|
361
507
|
|
|
362
|
-
// src/commands/
|
|
363
|
-
import fs from "fs/promises";
|
|
508
|
+
// src/commands/config.ts
|
|
364
509
|
import chalk3 from "chalk";
|
|
365
510
|
import { Command as Command2 } from "commander";
|
|
366
|
-
|
|
511
|
+
import inquirer2 from "inquirer";
|
|
512
|
+
var CONFIG_KEYS = {
|
|
513
|
+
apiUrl: {
|
|
514
|
+
description: "API base URL",
|
|
515
|
+
defaultValue: "https://syncenv-api.uagents.app",
|
|
516
|
+
validate: (val) => {
|
|
517
|
+
try {
|
|
518
|
+
new URL(val);
|
|
519
|
+
return true;
|
|
520
|
+
} catch {
|
|
521
|
+
return "Please enter a valid URL";
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
autoLockMinutes: {
|
|
526
|
+
description: "Auto-lock timeout in minutes (0 to disable)",
|
|
527
|
+
defaultValue: "30",
|
|
528
|
+
validate: (val) => {
|
|
529
|
+
const num = parseInt(val);
|
|
530
|
+
if (isNaN(num) || num < 0) return "Please enter a positive number or 0";
|
|
531
|
+
return true;
|
|
532
|
+
},
|
|
533
|
+
transform: (val) => parseInt(val)
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
var ALL_KEYS = ["apiUrl", "autoLockMinutes"];
|
|
537
|
+
var configCommands = new Command2("config").description("Manage CLI configuration");
|
|
538
|
+
configCommands.command("list").alias("ls").description("List all configuration values").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
539
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
540
|
+
try {
|
|
541
|
+
if (isDebug) {
|
|
542
|
+
console.log(chalk3.dim("\n[DEBUG] Config List Command\n"));
|
|
543
|
+
}
|
|
544
|
+
console.log(chalk3.bold("\nConfiguration:"));
|
|
545
|
+
console.log(chalk3.dim("\u2500".repeat(60)));
|
|
546
|
+
for (const [key, meta] of Object.entries(CONFIG_KEYS)) {
|
|
547
|
+
const value = getConfig(key);
|
|
548
|
+
const displayValue = value !== void 0 ? chalk3.green(String(value)) : chalk3.gray("(not set)");
|
|
549
|
+
console.log(`${key.padEnd(20)} ${displayValue}`);
|
|
550
|
+
console.log(chalk3.dim(` ${meta.description}`));
|
|
551
|
+
if (value === void 0) {
|
|
552
|
+
console.log(chalk3.dim(` Default: ${meta.defaultValue}`));
|
|
553
|
+
}
|
|
554
|
+
console.log();
|
|
555
|
+
}
|
|
556
|
+
console.log(chalk3.dim(`Config file: ${getConfigPath()}`));
|
|
557
|
+
console.log();
|
|
558
|
+
} catch (err) {
|
|
559
|
+
if (isDebug) {
|
|
560
|
+
console.log(chalk3.dim(`
|
|
561
|
+
[DEBUG] List error: ${err.message}`));
|
|
562
|
+
console.log(chalk3.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
563
|
+
}
|
|
564
|
+
error("Failed to list config:", err.message);
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
configCommands.command("get <key>").description("Get a configuration value").option("--debug", "enable debug mode with verbose logging").action(async (key, options) => {
|
|
569
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
570
|
+
try {
|
|
571
|
+
if (isDebug) {
|
|
572
|
+
console.log(chalk3.dim("\n[DEBUG] Config Get Command"));
|
|
573
|
+
console.log(chalk3.dim(`[DEBUG] Key: ${key}
|
|
574
|
+
`));
|
|
575
|
+
}
|
|
576
|
+
if (!ALL_KEYS.includes(key)) {
|
|
577
|
+
error(`Unknown config key: ${key}`);
|
|
578
|
+
info(`Valid keys: ${ALL_KEYS.join(", ")}`);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
const value = getConfig(key);
|
|
582
|
+
const meta = CONFIG_KEYS[key];
|
|
583
|
+
if (value === void 0) {
|
|
584
|
+
console.log(chalk3.gray("(not set)"));
|
|
585
|
+
info(`Default: ${meta.defaultValue}`);
|
|
586
|
+
} else {
|
|
587
|
+
console.log(String(value));
|
|
588
|
+
}
|
|
589
|
+
} catch (err) {
|
|
590
|
+
if (isDebug) {
|
|
591
|
+
console.log(chalk3.dim(`
|
|
592
|
+
[DEBUG] Get error: ${err.message}`));
|
|
593
|
+
console.log(chalk3.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
594
|
+
}
|
|
595
|
+
error("Failed to get config:", err.message);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
configCommands.command("set <key> [value]").description("Set a configuration value").option("--debug", "enable debug mode with verbose logging").action(async (key, value, options) => {
|
|
600
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
601
|
+
try {
|
|
602
|
+
if (isDebug) {
|
|
603
|
+
console.log(chalk3.dim("\n[DEBUG] Config Set Command"));
|
|
604
|
+
console.log(chalk3.dim(`[DEBUG] Key: ${key}`));
|
|
605
|
+
console.log(chalk3.dim(`[DEBUG] Value: ${value}
|
|
606
|
+
`));
|
|
607
|
+
}
|
|
608
|
+
if (!ALL_KEYS.includes(key)) {
|
|
609
|
+
error(`Unknown config key: ${key}`);
|
|
610
|
+
info(`Valid keys: ${ALL_KEYS.join(", ")}`);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
const meta = CONFIG_KEYS[key];
|
|
614
|
+
let finalValue = value;
|
|
615
|
+
if (!value) {
|
|
616
|
+
const currentValue = getConfig(key);
|
|
617
|
+
const { inputValue } = await inquirer2.prompt([
|
|
618
|
+
{
|
|
619
|
+
type: "input",
|
|
620
|
+
name: "inputValue",
|
|
621
|
+
message: `Enter value for ${key}:`,
|
|
622
|
+
default: currentValue !== void 0 ? String(currentValue) : meta.defaultValue,
|
|
623
|
+
validate: meta.validate
|
|
624
|
+
}
|
|
625
|
+
]);
|
|
626
|
+
finalValue = inputValue;
|
|
627
|
+
}
|
|
628
|
+
const validation = meta.validate?.(String(finalValue));
|
|
629
|
+
if (validation !== true) {
|
|
630
|
+
error(`Invalid value: ${validation}`);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
const transformedValue = meta.transform ? meta.transform(String(finalValue)) : finalValue;
|
|
634
|
+
setConfig(key, transformedValue);
|
|
635
|
+
success(`${key} set to: ${transformedValue}`);
|
|
636
|
+
} catch (err) {
|
|
637
|
+
if (isDebug) {
|
|
638
|
+
console.log(chalk3.dim(`
|
|
639
|
+
[DEBUG] Set error: ${err.message}`));
|
|
640
|
+
console.log(chalk3.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
641
|
+
}
|
|
642
|
+
error("Failed to set config:", err.message);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
configCommands.command("delete <key>").alias("rm").description("Delete a configuration value (reset to default)").option("--debug", "enable debug mode with verbose logging").action(async (key, options) => {
|
|
647
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
648
|
+
try {
|
|
649
|
+
if (isDebug) {
|
|
650
|
+
console.log(chalk3.dim("\n[DEBUG] Config Delete Command"));
|
|
651
|
+
console.log(chalk3.dim(`[DEBUG] Key: ${key}
|
|
652
|
+
`));
|
|
653
|
+
}
|
|
654
|
+
if (!ALL_KEYS.includes(key)) {
|
|
655
|
+
error(`Unknown config key: ${key}`);
|
|
656
|
+
info(`Valid keys: ${ALL_KEYS.join(", ")}`);
|
|
657
|
+
process.exit(1);
|
|
658
|
+
}
|
|
659
|
+
const currentValue = getConfig(key);
|
|
660
|
+
if (currentValue === void 0) {
|
|
661
|
+
warning(`${key} is not set`);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const { confirm } = await inquirer2.prompt([
|
|
665
|
+
{
|
|
666
|
+
type: "confirm",
|
|
667
|
+
name: "confirm",
|
|
668
|
+
message: `Reset ${key} to default?`,
|
|
669
|
+
default: false
|
|
670
|
+
}
|
|
671
|
+
]);
|
|
672
|
+
if (!confirm) {
|
|
673
|
+
info("Cancelled.");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
deleteConfig(key);
|
|
677
|
+
success(`${key} reset to default`);
|
|
678
|
+
const meta = CONFIG_KEYS[key];
|
|
679
|
+
info(`Default value: ${meta.defaultValue}`);
|
|
680
|
+
} catch (err) {
|
|
681
|
+
if (isDebug) {
|
|
682
|
+
console.log(chalk3.dim(`
|
|
683
|
+
[DEBUG] Delete error: ${err.message}`));
|
|
684
|
+
console.log(chalk3.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
685
|
+
}
|
|
686
|
+
error("Failed to delete config:", err.message);
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
configCommands.command("reset").description("Reset all configuration to defaults").option("-f, --force", "skip confirmation").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
691
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
692
|
+
try {
|
|
693
|
+
if (isDebug) {
|
|
694
|
+
console.log(chalk3.dim("\n[DEBUG] Config Reset Command\n"));
|
|
695
|
+
}
|
|
696
|
+
if (!options.force) {
|
|
697
|
+
const { confirm } = await inquirer2.prompt([
|
|
698
|
+
{
|
|
699
|
+
type: "confirm",
|
|
700
|
+
name: "confirm",
|
|
701
|
+
message: "Reset ALL configuration to defaults? This cannot be undone.",
|
|
702
|
+
default: false
|
|
703
|
+
}
|
|
704
|
+
]);
|
|
705
|
+
if (!confirm) {
|
|
706
|
+
info("Cancelled.");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
clearConfig();
|
|
711
|
+
success("All configuration reset to defaults");
|
|
712
|
+
info(`Config file: ${getConfigPath()}`);
|
|
713
|
+
} catch (err) {
|
|
714
|
+
if (isDebug) {
|
|
715
|
+
console.log(chalk3.dim(`
|
|
716
|
+
[DEBUG] Reset error: ${err.message}`));
|
|
717
|
+
console.log(chalk3.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
718
|
+
}
|
|
719
|
+
error("Failed to reset config:", err.message);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
configCommands.command("path").description("Show configuration file path").action(() => {
|
|
724
|
+
console.log(getConfigPath());
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// src/commands/doctor.ts
|
|
728
|
+
import fs from "fs/promises";
|
|
729
|
+
import chalk4 from "chalk";
|
|
730
|
+
import { Command as Command3 } from "commander";
|
|
731
|
+
var doctorCommand = new Command3("doctor").description("Diagnose configuration and connectivity issues").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
732
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
367
733
|
let exitCode = 0;
|
|
368
|
-
|
|
734
|
+
if (isDebug) {
|
|
735
|
+
console.log(chalk4.dim("\n[DEBUG] Doctor Command\n"));
|
|
736
|
+
}
|
|
737
|
+
console.log(chalk4.bold("\n\u{1F50D} SyncEnv Doctor\n"));
|
|
369
738
|
const nodeVersion = process.version;
|
|
370
739
|
const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0]);
|
|
371
|
-
console.log(
|
|
740
|
+
console.log(chalk4.dim("Checking Node.js version..."));
|
|
741
|
+
if (isDebug) {
|
|
742
|
+
console.log(chalk4.dim(`[DEBUG] Node.js version: ${nodeVersion}`));
|
|
743
|
+
}
|
|
372
744
|
if (majorVersion >= 18) {
|
|
373
745
|
success(`Node.js ${nodeVersion}`);
|
|
374
746
|
} else {
|
|
375
747
|
error(`Node.js ${nodeVersion} (>= 18 required)`);
|
|
376
748
|
exitCode = 1;
|
|
377
749
|
}
|
|
378
|
-
console.log(
|
|
750
|
+
console.log(chalk4.dim("\nChecking configuration..."));
|
|
379
751
|
const configPath = getConfigPath();
|
|
752
|
+
if (isDebug) {
|
|
753
|
+
console.log(chalk4.dim(`[DEBUG] Config path: ${configPath}`));
|
|
754
|
+
}
|
|
380
755
|
try {
|
|
381
756
|
await fs.access(configPath);
|
|
382
757
|
success(`Config file exists: ${configPath}`);
|
|
383
758
|
} catch {
|
|
384
759
|
warning(`Config file not found: ${configPath}`);
|
|
385
760
|
}
|
|
386
|
-
console.log(
|
|
761
|
+
console.log(chalk4.dim("\nChecking project configuration..."));
|
|
387
762
|
const hasConfig = await hasProjectConfig();
|
|
388
763
|
if (hasConfig) {
|
|
389
764
|
try {
|
|
390
765
|
const config = await loadProjectConfig();
|
|
391
766
|
if (config) {
|
|
392
|
-
success(`.
|
|
767
|
+
success(`.syncenvrc found`);
|
|
393
768
|
info(` Project: ${config.project.name}`);
|
|
394
769
|
if (config.project.id) {
|
|
395
770
|
info(` Project ID: ${config.project.id}`);
|
|
@@ -399,13 +774,13 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
399
774
|
info(` Default env: ${config.defaults.environment}`);
|
|
400
775
|
}
|
|
401
776
|
} catch (err) {
|
|
402
|
-
error(`.
|
|
777
|
+
error(`.syncenvrc exists but is invalid: ${err.message}`);
|
|
403
778
|
exitCode = 1;
|
|
404
779
|
}
|
|
405
780
|
} else {
|
|
406
|
-
warning(`.
|
|
781
|
+
warning(`.syncenvrc not found - run \`syncenv init\``);
|
|
407
782
|
}
|
|
408
|
-
console.log(
|
|
783
|
+
console.log(chalk4.dim("\nChecking authentication..."));
|
|
409
784
|
if (isAuthenticated()) {
|
|
410
785
|
const userEmail = getConfig("userEmail");
|
|
411
786
|
success(`Authenticated as ${userEmail}`);
|
|
@@ -425,8 +800,11 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
425
800
|
} else {
|
|
426
801
|
warning(`Not authenticated - run \`syncenv auth login\``);
|
|
427
802
|
}
|
|
428
|
-
console.log(
|
|
429
|
-
const apiUrl = process.env.SYNCENV_API_URL || getConfig("apiUrl") || "
|
|
803
|
+
console.log(chalk4.dim("\nChecking API connectivity..."));
|
|
804
|
+
const apiUrl = process.env.SYNCENV_API_URL || getConfig("apiUrl") || "https://syncenv-api.uagents.app";
|
|
805
|
+
if (isDebug) {
|
|
806
|
+
console.log(chalk4.dim(`[DEBUG] API URL: ${apiUrl}`));
|
|
807
|
+
}
|
|
430
808
|
try {
|
|
431
809
|
const response = await fetch(apiUrl);
|
|
432
810
|
if (response.ok) {
|
|
@@ -440,6 +818,9 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
440
818
|
} catch (err) {
|
|
441
819
|
error(`Cannot connect to API at ${apiUrl}`);
|
|
442
820
|
info(` Error: ${err.message}`);
|
|
821
|
+
if (isDebug) {
|
|
822
|
+
console.log(chalk4.dim(`[DEBUG] Connection error details: ${err.stack}`));
|
|
823
|
+
}
|
|
443
824
|
exitCode = 1;
|
|
444
825
|
}
|
|
445
826
|
console.log("");
|
|
@@ -453,9 +834,9 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
453
834
|
|
|
454
835
|
// src/commands/env.ts
|
|
455
836
|
import fs2 from "fs/promises";
|
|
456
|
-
import
|
|
457
|
-
import { Command as
|
|
458
|
-
import
|
|
837
|
+
import chalk5 from "chalk";
|
|
838
|
+
import { Command as Command4 } from "commander";
|
|
839
|
+
import inquirer3 from "inquirer";
|
|
459
840
|
|
|
460
841
|
// src/state/index.ts
|
|
461
842
|
import { existsSync } from "fs";
|
|
@@ -463,7 +844,7 @@ import { readFile, writeFile, mkdir } from "fs/promises";
|
|
|
463
844
|
import { homedir } from "os";
|
|
464
845
|
import { join, dirname } from "path";
|
|
465
846
|
var STATE_VERSION = 1;
|
|
466
|
-
var STATE_DIR = ".
|
|
847
|
+
var STATE_DIR = ".syncenv";
|
|
467
848
|
var STATE_FILE = "state.json";
|
|
468
849
|
var StateManager = class {
|
|
469
850
|
statePath;
|
|
@@ -638,17 +1019,23 @@ function createStateManager(options) {
|
|
|
638
1019
|
var globalState = createStateManager();
|
|
639
1020
|
|
|
640
1021
|
// src/commands/env.ts
|
|
641
|
-
var envCommands = new
|
|
642
|
-
envCommands.command("push").description("Push .env file to server").option("-p, --project <
|
|
1022
|
+
var envCommands = new Command4("env").description("Environment variable commands");
|
|
1023
|
+
envCommands.command("push").description("Push .env file to server").option("-p, --project <identifier>", "project ID or name").option("-e, --env <name>", "environment name").option("-f, --file <path>", "file path").option("-m, --message <message>", "change description").option("--force", "force push without conflict check").option(
|
|
1024
|
+
"--strategy <strategy>",
|
|
1025
|
+
"merge strategy on conflict: local-wins, remote-wins, fail-on-conflict"
|
|
1026
|
+
).action(async (options) => {
|
|
643
1027
|
try {
|
|
644
1028
|
if (!isAuthenticated()) {
|
|
645
1029
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
646
1030
|
process.exit(1);
|
|
647
1031
|
}
|
|
648
1032
|
const config = await loadProjectConfig();
|
|
649
|
-
const projectId = options.project || config?.project.id
|
|
1033
|
+
const projectId = options.project || config?.project.id;
|
|
650
1034
|
if (!projectId) {
|
|
651
|
-
error("No project specified.
|
|
1035
|
+
error("No project specified.");
|
|
1036
|
+
info("Use one of the following:");
|
|
1037
|
+
info(" --project <id> Specify project ID for this command");
|
|
1038
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
652
1039
|
process.exit(1);
|
|
653
1040
|
}
|
|
654
1041
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -705,10 +1092,24 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
705
1092
|
warning(`Remote has been modified (version ${localVersion} \u2192 ${remoteVersion})`);
|
|
706
1093
|
info("Attempting to merge...\n");
|
|
707
1094
|
try {
|
|
708
|
-
const baseDownload = await client.environments.downloadContent(
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1095
|
+
const baseDownload = await client.environments.downloadContent(
|
|
1096
|
+
environment.id,
|
|
1097
|
+
localVersion
|
|
1098
|
+
);
|
|
1099
|
+
const baseContent = decryptContent(
|
|
1100
|
+
baseDownload.content,
|
|
1101
|
+
baseDownload.encryptionIv,
|
|
1102
|
+
dek
|
|
1103
|
+
);
|
|
1104
|
+
const remoteDownload = await client.environments.downloadContent(
|
|
1105
|
+
environment.id,
|
|
1106
|
+
remoteVersion
|
|
1107
|
+
);
|
|
1108
|
+
const remoteContent = decryptContent(
|
|
1109
|
+
remoteDownload.content,
|
|
1110
|
+
remoteDownload.encryptionIv,
|
|
1111
|
+
dek
|
|
1112
|
+
);
|
|
712
1113
|
const mergeInput = {
|
|
713
1114
|
base: parseEnvFile(baseContent),
|
|
714
1115
|
local: parseEnvFile(content),
|
|
@@ -716,10 +1117,10 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
716
1117
|
};
|
|
717
1118
|
const mergeResult = threeWayMerge(mergeInput);
|
|
718
1119
|
if (mergeResult.autoMerged) {
|
|
719
|
-
info(
|
|
720
|
-
info(
|
|
721
|
-
info(
|
|
722
|
-
info(
|
|
1120
|
+
info(chalk5.green("\u2713 Auto-merged successfully"));
|
|
1121
|
+
info(chalk5.dim(` Added: ${mergeResult.statistics.added}`));
|
|
1122
|
+
info(chalk5.dim(` Modified: ${mergeResult.statistics.modified}`));
|
|
1123
|
+
info(chalk5.dim(` Deleted: ${mergeResult.statistics.deleted}`));
|
|
723
1124
|
const mergedEncrypted = encryptContent(mergeResult.mergedContent, dek);
|
|
724
1125
|
spinner.start("Uploading merged content...");
|
|
725
1126
|
const envId = environment.id;
|
|
@@ -731,7 +1132,13 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
731
1132
|
changeSummary: options.message || `Merged with remote version ${remoteVersion}`
|
|
732
1133
|
})
|
|
733
1134
|
);
|
|
734
|
-
await stateManager.recordSync(
|
|
1135
|
+
await stateManager.recordSync(
|
|
1136
|
+
projectId,
|
|
1137
|
+
envId,
|
|
1138
|
+
updateResult.version,
|
|
1139
|
+
generateContentHash(mergeResult.mergedContent),
|
|
1140
|
+
"merge"
|
|
1141
|
+
);
|
|
735
1142
|
spinner.succeed("Merged and uploaded");
|
|
736
1143
|
success(`Environment "${envName}" updated to version ${updateResult.version}`);
|
|
737
1144
|
return;
|
|
@@ -757,7 +1164,13 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
757
1164
|
changeSummary: options.message || `Merged with remote version ${remoteVersion} (with conflict resolution)`
|
|
758
1165
|
})
|
|
759
1166
|
);
|
|
760
|
-
await stateManager.recordSync(
|
|
1167
|
+
await stateManager.recordSync(
|
|
1168
|
+
projectId,
|
|
1169
|
+
envId,
|
|
1170
|
+
updateResult.version,
|
|
1171
|
+
generateContentHash(interactiveResult.content),
|
|
1172
|
+
"merge"
|
|
1173
|
+
);
|
|
761
1174
|
spinner.succeed("Resolved and uploaded");
|
|
762
1175
|
success(`Environment "${envName}" updated to version ${updateResult.version}`);
|
|
763
1176
|
return;
|
|
@@ -819,16 +1232,19 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
819
1232
|
process.exit(1);
|
|
820
1233
|
}
|
|
821
1234
|
});
|
|
822
|
-
envCommands.command("pull").description("Pull .env file from server").option("-p, --project <
|
|
1235
|
+
envCommands.command("pull").description("Pull .env file from server").option("-p, --project <identifier>", "project ID or name").option("-e, --env <name>", "environment name").option("-f, --file <path>", "output file path").option("-v, --version <number>", "specific version to pull").option("-m, --merge", "merge with existing file (using three-way merge if possible)").option("-y, --yes", "skip confirmation").action(async (options) => {
|
|
823
1236
|
try {
|
|
824
1237
|
if (!isAuthenticated()) {
|
|
825
1238
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
826
1239
|
process.exit(1);
|
|
827
1240
|
}
|
|
828
1241
|
const config = await loadProjectConfig();
|
|
829
|
-
const projectId = options.project || config?.project.id
|
|
1242
|
+
const projectId = options.project || config?.project.id;
|
|
830
1243
|
if (!projectId) {
|
|
831
|
-
error("No project specified.
|
|
1244
|
+
error("No project specified.");
|
|
1245
|
+
info("Use one of the following:");
|
|
1246
|
+
info(" --project <id> Specify project ID for this command");
|
|
1247
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
832
1248
|
process.exit(1);
|
|
833
1249
|
}
|
|
834
1250
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -876,7 +1292,7 @@ envCommands.command("pull").description("Pull .env file from server").option("-p
|
|
|
876
1292
|
try {
|
|
877
1293
|
existingContent = await fs2.readFile(filePath, "utf-8");
|
|
878
1294
|
if (!options.yes && !options.merge) {
|
|
879
|
-
const { action } = await
|
|
1295
|
+
const { action } = await inquirer3.prompt([
|
|
880
1296
|
{
|
|
881
1297
|
type: "list",
|
|
882
1298
|
name: "action",
|
|
@@ -906,19 +1322,19 @@ envCommands.command("pull").description("Pull .env file from server").option("-p
|
|
|
906
1322
|
info("No changes to merge.");
|
|
907
1323
|
return;
|
|
908
1324
|
}
|
|
909
|
-
console.log(
|
|
1325
|
+
console.log(chalk5.bold("\nMerge changes:"));
|
|
910
1326
|
for (const key of diff.added) {
|
|
911
|
-
console.log(
|
|
1327
|
+
console.log(chalk5.green(` + ${key}=${localEnv[key]}`));
|
|
912
1328
|
}
|
|
913
1329
|
for (const key of diff.removed) {
|
|
914
|
-
console.log(
|
|
1330
|
+
console.log(chalk5.red(` - ${key}`));
|
|
915
1331
|
}
|
|
916
1332
|
for (const key of diff.modified) {
|
|
917
|
-
console.log(
|
|
918
|
-
console.log(
|
|
919
|
-
console.log(
|
|
1333
|
+
console.log(chalk5.yellow(` ~ ${key}`));
|
|
1334
|
+
console.log(chalk5.dim(` local: ${localEnv[key]}`));
|
|
1335
|
+
console.log(chalk5.dim(` remote: ${remoteEnv[key]}`));
|
|
920
1336
|
}
|
|
921
|
-
const { mergeAction } = await
|
|
1337
|
+
const { mergeAction } = await inquirer3.prompt([
|
|
922
1338
|
{
|
|
923
1339
|
type: "list",
|
|
924
1340
|
name: "mergeAction",
|
|
@@ -959,16 +1375,19 @@ envCommands.command("pull").description("Pull .env file from server").option("-p
|
|
|
959
1375
|
process.exit(1);
|
|
960
1376
|
}
|
|
961
1377
|
});
|
|
962
|
-
envCommands.command("history").description("Show version history").option("-p, --project <
|
|
1378
|
+
envCommands.command("history").description("Show version history").option("-p, --project <identifier>", "project ID or name").option("-e, --env <name>", "environment name").option("-l, --limit <number>", "number of versions to show", "10").action(async (options) => {
|
|
963
1379
|
try {
|
|
964
1380
|
if (!isAuthenticated()) {
|
|
965
1381
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
966
1382
|
process.exit(1);
|
|
967
1383
|
}
|
|
968
1384
|
const config = await loadProjectConfig();
|
|
969
|
-
const projectId = options.project || config?.project.id
|
|
1385
|
+
const projectId = options.project || config?.project.id;
|
|
970
1386
|
if (!projectId) {
|
|
971
1387
|
error("No project specified.");
|
|
1388
|
+
info("Use one of the following:");
|
|
1389
|
+
info(" --project <id> Specify project ID for this command");
|
|
1390
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
972
1391
|
process.exit(1);
|
|
973
1392
|
}
|
|
974
1393
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -983,14 +1402,16 @@ envCommands.command("history").description("Show version history").option("-p, -
|
|
|
983
1402
|
spinner.fail(`Environment "${envName}" not found`);
|
|
984
1403
|
process.exit(1);
|
|
985
1404
|
}
|
|
986
|
-
const versions = await withAuthGuard(
|
|
1405
|
+
const versions = await withAuthGuard(
|
|
1406
|
+
() => client.sync.getVersions(environment.id, parseInt(options.limit))
|
|
1407
|
+
);
|
|
987
1408
|
spinner.stop();
|
|
988
1409
|
if (versions.length === 0) {
|
|
989
1410
|
info("No versions found.");
|
|
990
1411
|
return;
|
|
991
1412
|
}
|
|
992
|
-
console.log(
|
|
993
|
-
console.log(
|
|
1413
|
+
console.log(chalk5.bold("\nVERSION DATE AUTHOR MESSAGE"));
|
|
1414
|
+
console.log(chalk5.dim("\u2500".repeat(70)));
|
|
994
1415
|
for (const version2 of versions.slice(0, limit)) {
|
|
995
1416
|
const ver = String(version2.versionNumber).padEnd(9);
|
|
996
1417
|
const date = formatRelativeTime(version2.createdAt).padEnd(17);
|
|
@@ -1004,16 +1425,19 @@ envCommands.command("history").description("Show version history").option("-p, -
|
|
|
1004
1425
|
process.exit(1);
|
|
1005
1426
|
}
|
|
1006
1427
|
});
|
|
1007
|
-
envCommands.command("diff <v1> <v2>").description("Compare two versions (not supported yet)").option("-p, --project <
|
|
1428
|
+
envCommands.command("diff <v1> <v2>").description("Compare two versions (not supported yet)").option("-p, --project <identifier>", "project ID or name").option("-e, --env <name>", "environment name").action(async (v1, v2, options) => {
|
|
1008
1429
|
try {
|
|
1009
1430
|
if (!isAuthenticated()) {
|
|
1010
1431
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1011
1432
|
process.exit(1);
|
|
1012
1433
|
}
|
|
1013
1434
|
const config = await loadProjectConfig();
|
|
1014
|
-
const projectId = options.project || config?.project.id
|
|
1435
|
+
const projectId = options.project || config?.project.id;
|
|
1015
1436
|
if (!projectId) {
|
|
1016
1437
|
error("No project specified.");
|
|
1438
|
+
info("Use one of the following:");
|
|
1439
|
+
info(" --project <id> Specify project ID for this command");
|
|
1440
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
1017
1441
|
process.exit(1);
|
|
1018
1442
|
}
|
|
1019
1443
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -1034,22 +1458,22 @@ envCommands.command("diff <v1> <v2>").description("Compare two versions (not sup
|
|
|
1034
1458
|
client.environments.downloadContent(environment.id, version2)
|
|
1035
1459
|
]);
|
|
1036
1460
|
spinner.stop();
|
|
1037
|
-
console.log(
|
|
1461
|
+
console.log(chalk5.bold(`
|
|
1038
1462
|
Comparing versions ${version1} and ${version2}`));
|
|
1039
|
-
console.log(
|
|
1040
|
-
console.log(
|
|
1463
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
1464
|
+
console.log(chalk5.dim(`
|
|
1041
1465
|
Version ${version1}:`));
|
|
1042
1466
|
console.log(` Content hash: ${generateContentHash(download1.content).slice(0, 16)}...`);
|
|
1043
1467
|
console.log(` Date: ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
1044
|
-
console.log(
|
|
1468
|
+
console.log(chalk5.dim(`
|
|
1045
1469
|
Version ${version2}:`));
|
|
1046
1470
|
console.log(` Content hash: ${generateContentHash(download2.content).slice(0, 16)}...`);
|
|
1047
1471
|
console.log(` Date: ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
1048
1472
|
if (download1.content !== download2.content) {
|
|
1049
|
-
console.log(
|
|
1473
|
+
console.log(chalk5.yellow("\n\u26A0 Content differs between versions"));
|
|
1050
1474
|
info("Use `syncenv env pull --version <number>` to view a specific version");
|
|
1051
1475
|
} else {
|
|
1052
|
-
console.log(
|
|
1476
|
+
console.log(chalk5.green("\n\u2713 Content is identical"));
|
|
1053
1477
|
}
|
|
1054
1478
|
console.log("");
|
|
1055
1479
|
} catch (err) {
|
|
@@ -1057,22 +1481,25 @@ Version ${version2}:`));
|
|
|
1057
1481
|
process.exit(1);
|
|
1058
1482
|
}
|
|
1059
1483
|
});
|
|
1060
|
-
envCommands.command("rollback <version>").description("Rollback to a specific version").option("-p, --project <
|
|
1484
|
+
envCommands.command("rollback <version>").description("Rollback to a specific version").option("-p, --project <identifier>", "project ID or name").option("-e, --env <name>", "environment name").option("-y, --yes", "skip confirmation").action(async (version2, options) => {
|
|
1061
1485
|
try {
|
|
1062
1486
|
if (!isAuthenticated()) {
|
|
1063
1487
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1064
1488
|
process.exit(1);
|
|
1065
1489
|
}
|
|
1066
1490
|
const config = await loadProjectConfig();
|
|
1067
|
-
const projectId = options.project || config?.project.id
|
|
1491
|
+
const projectId = options.project || config?.project.id;
|
|
1068
1492
|
if (!projectId) {
|
|
1069
1493
|
error("No project specified.");
|
|
1494
|
+
info("Use one of the following:");
|
|
1495
|
+
info(" --project <id> Specify project ID for this command");
|
|
1496
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
1070
1497
|
process.exit(1);
|
|
1071
1498
|
}
|
|
1072
1499
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
1073
1500
|
const targetVersion = parseInt(version2);
|
|
1074
1501
|
if (!options.yes) {
|
|
1075
|
-
const { confirm } = await
|
|
1502
|
+
const { confirm } = await inquirer3.prompt([
|
|
1076
1503
|
{
|
|
1077
1504
|
type: "confirm",
|
|
1078
1505
|
name: "confirm",
|
|
@@ -1128,16 +1555,19 @@ envCommands.command("rollback <version>").description("Rollback to a specific ve
|
|
|
1128
1555
|
process.exit(1);
|
|
1129
1556
|
}
|
|
1130
1557
|
});
|
|
1131
|
-
envCommands.command("list-envs").description("List all environments for a project").option("-p, --project <
|
|
1558
|
+
envCommands.command("list-envs").description("List all environments for a project").option("-p, --project <identifier>", "project ID or name").action(async (options) => {
|
|
1132
1559
|
try {
|
|
1133
1560
|
if (!isAuthenticated()) {
|
|
1134
1561
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1135
1562
|
process.exit(1);
|
|
1136
1563
|
}
|
|
1137
1564
|
const config = await loadProjectConfig();
|
|
1138
|
-
const projectId = options.project || config?.project.id
|
|
1565
|
+
const projectId = options.project || config?.project.id;
|
|
1139
1566
|
if (!projectId) {
|
|
1140
|
-
error("No project specified.
|
|
1567
|
+
error("No project specified.");
|
|
1568
|
+
info("Use one of the following:");
|
|
1569
|
+
info(" --project <id> Specify project ID for this command");
|
|
1570
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
1141
1571
|
process.exit(1);
|
|
1142
1572
|
}
|
|
1143
1573
|
const spinner = createSpinner("Fetching environments...");
|
|
@@ -1152,11 +1582,11 @@ envCommands.command("list-envs").description("List all environments for a projec
|
|
|
1152
1582
|
return;
|
|
1153
1583
|
}
|
|
1154
1584
|
const project = await withAuthGuard(() => client.projects.get(projectId));
|
|
1155
|
-
console.log(
|
|
1585
|
+
console.log(chalk5.bold(`
|
|
1156
1586
|
Environments for ${project.name}`));
|
|
1157
|
-
console.log(
|
|
1158
|
-
console.log(
|
|
1159
|
-
console.log(
|
|
1587
|
+
console.log(chalk5.dim("\u2500".repeat(70)));
|
|
1588
|
+
console.log(chalk5.bold("NAME VERSION SIZE LAST MODIFIED DESCRIPTION"));
|
|
1589
|
+
console.log(chalk5.dim("\u2500".repeat(70)));
|
|
1160
1590
|
for (const env of environments) {
|
|
1161
1591
|
const name = env.name.padEnd(11).slice(0, 11);
|
|
1162
1592
|
const version2 = `v${env.currentVersion || 1}`.padEnd(9);
|
|
@@ -1171,21 +1601,24 @@ Environments for ${project.name}`));
|
|
|
1171
1601
|
process.exit(1);
|
|
1172
1602
|
}
|
|
1173
1603
|
});
|
|
1174
|
-
envCommands.command("create-env").description("Create a new environment for a project").option("-p, --project <
|
|
1604
|
+
envCommands.command("create-env").description("Create a new environment for a project").option("-p, --project <identifier>", "project ID or name").option("-n, --name <name>", "environment name").option("-d, --description <description>", "environment description").action(async (options) => {
|
|
1175
1605
|
try {
|
|
1176
1606
|
if (!isAuthenticated()) {
|
|
1177
1607
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1178
1608
|
process.exit(1);
|
|
1179
1609
|
}
|
|
1180
1610
|
const config = await loadProjectConfig();
|
|
1181
|
-
const projectId = options.project || config?.project.id
|
|
1611
|
+
const projectId = options.project || config?.project.id;
|
|
1182
1612
|
if (!projectId) {
|
|
1183
|
-
error("No project specified.
|
|
1613
|
+
error("No project specified.");
|
|
1614
|
+
info("Use one of the following:");
|
|
1615
|
+
info(" --project <id> Specify project ID for this command");
|
|
1616
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
1184
1617
|
process.exit(1);
|
|
1185
1618
|
}
|
|
1186
1619
|
let envName = options.name;
|
|
1187
1620
|
if (!envName) {
|
|
1188
|
-
const answer = await
|
|
1621
|
+
const answer = await inquirer3.prompt([
|
|
1189
1622
|
{
|
|
1190
1623
|
type: "input",
|
|
1191
1624
|
name: "name",
|
|
@@ -1202,7 +1635,7 @@ envCommands.command("create-env").description("Create a new environment for a pr
|
|
|
1202
1635
|
}
|
|
1203
1636
|
let description = options.description;
|
|
1204
1637
|
if (!description) {
|
|
1205
|
-
const answer = await
|
|
1638
|
+
const answer = await inquirer3.prompt([
|
|
1206
1639
|
{
|
|
1207
1640
|
type: "input",
|
|
1208
1641
|
name: "description",
|
|
@@ -1239,7 +1672,7 @@ envCommands.command("create-env").description("Create a new environment for a pr
|
|
|
1239
1672
|
requireConfirmation: envName === "production"
|
|
1240
1673
|
};
|
|
1241
1674
|
await saveProjectConfig(config);
|
|
1242
|
-
info(`Updated .
|
|
1675
|
+
info(`Updated .syncenvrc with environment "${envName}"`);
|
|
1243
1676
|
}
|
|
1244
1677
|
} catch (err) {
|
|
1245
1678
|
spinner.fail("Failed to create environment");
|
|
@@ -1258,7 +1691,7 @@ envCommands.command("delete-env <id>").description("Delete an environment").opti
|
|
|
1258
1691
|
}
|
|
1259
1692
|
const env = await withAuthGuard(() => client.environments.get(id));
|
|
1260
1693
|
if (!options.force) {
|
|
1261
|
-
const { confirm } = await
|
|
1694
|
+
const { confirm } = await inquirer3.prompt([
|
|
1262
1695
|
{
|
|
1263
1696
|
type: "input",
|
|
1264
1697
|
name: "confirm",
|
|
@@ -1279,23 +1712,30 @@ envCommands.command("delete-env <id>").description("Delete an environment").opti
|
|
|
1279
1712
|
if (config && config.environments[env.name]) {
|
|
1280
1713
|
delete config.environments[env.name];
|
|
1281
1714
|
await saveProjectConfig(config);
|
|
1282
|
-
info("Updated .
|
|
1715
|
+
info("Updated .syncenvrc");
|
|
1283
1716
|
}
|
|
1284
1717
|
} catch (err) {
|
|
1285
1718
|
error("Failed to delete environment:", err.message);
|
|
1286
1719
|
process.exit(1);
|
|
1287
1720
|
}
|
|
1288
1721
|
});
|
|
1289
|
-
envCommands.command("sync").description("Sync with remote (pull + merge + push in one command)").option("-p, --project <
|
|
1722
|
+
envCommands.command("sync").description("Sync with remote (pull + merge + push in one command)").option("-p, --project <identifier>", "project ID or name").option("-e, --env <name>", "environment name").option("-f, --file <path>", "file path").option("--dry-run", "preview changes without applying").option(
|
|
1723
|
+
"--strategy <strategy>",
|
|
1724
|
+
"conflict resolution: local-wins, remote-wins, fail-on-conflict",
|
|
1725
|
+
"interactive"
|
|
1726
|
+
).option("-y, --yes", "skip confirmation prompts").action(async (options) => {
|
|
1290
1727
|
try {
|
|
1291
1728
|
if (!isAuthenticated()) {
|
|
1292
1729
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1293
1730
|
process.exit(1);
|
|
1294
1731
|
}
|
|
1295
1732
|
const config = await loadProjectConfig();
|
|
1296
|
-
const projectId = options.project || config?.project.id
|
|
1733
|
+
const projectId = options.project || config?.project.id;
|
|
1297
1734
|
if (!projectId) {
|
|
1298
|
-
error("No project specified.
|
|
1735
|
+
error("No project specified.");
|
|
1736
|
+
info("Use one of the following:");
|
|
1737
|
+
info(" --project <id> Specify project ID for this command");
|
|
1738
|
+
info(" syncenv project use <id> Set default project for current directory");
|
|
1299
1739
|
process.exit(1);
|
|
1300
1740
|
}
|
|
1301
1741
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -1333,7 +1773,13 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1333
1773
|
const decrypted = decryptContent(download.content, download.encryptionIv, dek2);
|
|
1334
1774
|
if (!options.dryRun) {
|
|
1335
1775
|
await fs2.writeFile(filePath, decrypted, "utf-8");
|
|
1336
|
-
await stateManager.recordSync(
|
|
1776
|
+
await stateManager.recordSync(
|
|
1777
|
+
projectId,
|
|
1778
|
+
environment.id,
|
|
1779
|
+
download.version || 1,
|
|
1780
|
+
download.contentHash || "",
|
|
1781
|
+
"pull"
|
|
1782
|
+
);
|
|
1337
1783
|
}
|
|
1338
1784
|
spinner.succeed("Pulled from remote");
|
|
1339
1785
|
success(`Written to ${filePath}`);
|
|
@@ -1351,12 +1797,19 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1351
1797
|
const dek = decryptDEK(project.encryptedDek, project.dekIv, userKek);
|
|
1352
1798
|
const remoteVersion = environment.currentVersion || 1;
|
|
1353
1799
|
const remoteDownload = await client.environments.downloadContent(environment.id);
|
|
1354
|
-
const remoteContent = decryptContent(
|
|
1800
|
+
const remoteContent = decryptContent(
|
|
1801
|
+
remoteDownload.content,
|
|
1802
|
+
remoteDownload.encryptionIv,
|
|
1803
|
+
dek
|
|
1804
|
+
);
|
|
1355
1805
|
let mergedContent;
|
|
1356
1806
|
let mergeSource = "push";
|
|
1357
1807
|
if (localState && localState.lastSync.version < remoteVersion) {
|
|
1358
1808
|
spinner.text = "Merging changes...";
|
|
1359
|
-
const baseDownload = await client.environments.downloadContent(
|
|
1809
|
+
const baseDownload = await client.environments.downloadContent(
|
|
1810
|
+
environment.id,
|
|
1811
|
+
localState.lastSync.version
|
|
1812
|
+
);
|
|
1360
1813
|
const baseContent = decryptContent(baseDownload.content, baseDownload.encryptionIv, dek);
|
|
1361
1814
|
const mergeInput = {
|
|
1362
1815
|
base: parseEnvFile(baseContent),
|
|
@@ -1411,12 +1864,14 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1411
1864
|
}
|
|
1412
1865
|
mergedContent = localContent;
|
|
1413
1866
|
if (!options.yes) {
|
|
1414
|
-
const { confirm } = await
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1867
|
+
const { confirm } = await inquirer3.prompt([
|
|
1868
|
+
{
|
|
1869
|
+
type: "confirm",
|
|
1870
|
+
name: "confirm",
|
|
1871
|
+
message: `Push local changes to "${envName}"?`,
|
|
1872
|
+
default: true
|
|
1873
|
+
}
|
|
1874
|
+
]);
|
|
1420
1875
|
if (!confirm) {
|
|
1421
1876
|
info("Cancelled.");
|
|
1422
1877
|
return;
|
|
@@ -1434,7 +1889,13 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1434
1889
|
changeSummary: mergeSource === "merge" ? "Synced with merge" : "Synced local changes"
|
|
1435
1890
|
})
|
|
1436
1891
|
);
|
|
1437
|
-
await stateManager.recordSync(
|
|
1892
|
+
await stateManager.recordSync(
|
|
1893
|
+
projectId,
|
|
1894
|
+
environment.id,
|
|
1895
|
+
result.version,
|
|
1896
|
+
newContentHash,
|
|
1897
|
+
mergeSource
|
|
1898
|
+
);
|
|
1438
1899
|
spinner.succeed("Sync complete");
|
|
1439
1900
|
success(`Environment "${envName}" updated to version ${result.version}`);
|
|
1440
1901
|
} catch (err) {
|
|
@@ -1445,13 +1906,20 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1445
1906
|
|
|
1446
1907
|
// src/commands/init.ts
|
|
1447
1908
|
import path from "path";
|
|
1448
|
-
import
|
|
1449
|
-
import { Command as
|
|
1450
|
-
import
|
|
1451
|
-
|
|
1909
|
+
import chalk6 from "chalk";
|
|
1910
|
+
import { Command as Command5 } from "commander";
|
|
1911
|
+
import inquirer4 from "inquirer";
|
|
1912
|
+
import { ApiError } from "@uagents/syncenv-client";
|
|
1913
|
+
var initCommand = new Command5("init").description("Initialize a new project configuration").option("-y, --yes", "skip prompts and use defaults").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
1914
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1452
1915
|
try {
|
|
1916
|
+
if (isDebug) {
|
|
1917
|
+
console.log(chalk6.dim("\n[DEBUG] Init Command"));
|
|
1918
|
+
console.log(chalk6.dim(`[DEBUG] Yes mode: ${options.yes || false}
|
|
1919
|
+
`));
|
|
1920
|
+
}
|
|
1453
1921
|
if (await hasProjectConfig()) {
|
|
1454
|
-
const { overwrite } = await
|
|
1922
|
+
const { overwrite } = await inquirer4.prompt([
|
|
1455
1923
|
{
|
|
1456
1924
|
type: "confirm",
|
|
1457
1925
|
name: "overwrite",
|
|
@@ -1464,24 +1932,38 @@ var initCommand = new Command4("init").description("Initialize a new project con
|
|
|
1464
1932
|
return;
|
|
1465
1933
|
}
|
|
1466
1934
|
}
|
|
1467
|
-
let
|
|
1935
|
+
let projectName2;
|
|
1468
1936
|
let environments;
|
|
1937
|
+
let envFileMappings = {};
|
|
1469
1938
|
if (options.yes) {
|
|
1470
|
-
|
|
1939
|
+
projectName2 = path.basename(process.cwd());
|
|
1471
1940
|
environments = ["dev", "staging", "production"];
|
|
1941
|
+
envFileMappings = {
|
|
1942
|
+
dev: ".env",
|
|
1943
|
+
staging: ".env.staging",
|
|
1944
|
+
production: ".env.production"
|
|
1945
|
+
};
|
|
1946
|
+
if (isDebug) {
|
|
1947
|
+
console.log(
|
|
1948
|
+
chalk6.dim(`[DEBUG] Using defaults: ${projectName2}, [${environments.join(", ")}]`)
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1472
1951
|
} else {
|
|
1473
|
-
const
|
|
1952
|
+
const { projectName: pname } = await inquirer4.prompt([
|
|
1474
1953
|
{
|
|
1475
1954
|
type: "input",
|
|
1476
1955
|
name: "projectName",
|
|
1477
1956
|
message: "Project name:",
|
|
1478
1957
|
default: path.basename(process.cwd()),
|
|
1479
1958
|
validate: (input) => input.length > 0 || "Project name is required"
|
|
1480
|
-
}
|
|
1959
|
+
}
|
|
1960
|
+
]);
|
|
1961
|
+
projectName2 = pname;
|
|
1962
|
+
const { environments: envs } = await inquirer4.prompt([
|
|
1481
1963
|
{
|
|
1482
1964
|
type: "checkbox",
|
|
1483
1965
|
name: "environments",
|
|
1484
|
-
message: "Select
|
|
1966
|
+
message: "Select environments to create:",
|
|
1485
1967
|
choices: [
|
|
1486
1968
|
{ name: "dev", checked: true },
|
|
1487
1969
|
{ name: "staging", checked: true },
|
|
@@ -1489,70 +1971,164 @@ var initCommand = new Command4("init").description("Initialize a new project con
|
|
|
1489
1971
|
{ name: "test", checked: false }
|
|
1490
1972
|
],
|
|
1491
1973
|
validate: (input) => input.length > 0 || "Select at least one environment"
|
|
1492
|
-
},
|
|
1493
|
-
{
|
|
1494
|
-
type: "confirm",
|
|
1495
|
-
name: "pushOnChange",
|
|
1496
|
-
message: "Auto-push on change?",
|
|
1497
|
-
default: false
|
|
1498
|
-
},
|
|
1499
|
-
{
|
|
1500
|
-
type: "confirm",
|
|
1501
|
-
name: "confirmOverwrite",
|
|
1502
|
-
message: "Confirm before overwriting files?",
|
|
1503
|
-
default: true
|
|
1504
1974
|
}
|
|
1505
1975
|
]);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
const envConfig = {};
|
|
1976
|
+
environments = envs;
|
|
1977
|
+
console.log(chalk6.dim("\nConfigure environment file mappings:"));
|
|
1509
1978
|
for (const env of environments) {
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1979
|
+
const defaultFile = env === "dev" ? ".env" : `.env.${env}`;
|
|
1980
|
+
const { fileName } = await inquirer4.prompt([
|
|
1981
|
+
{
|
|
1982
|
+
type: "input",
|
|
1983
|
+
name: "fileName",
|
|
1984
|
+
message: `File for "${env}" environment:`,
|
|
1985
|
+
default: defaultFile,
|
|
1986
|
+
validate: (input) => input.length > 0 || "File name is required"
|
|
1987
|
+
}
|
|
1988
|
+
]);
|
|
1989
|
+
envFileMappings[env] = fileName;
|
|
1515
1990
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
defaults: {
|
|
1523
|
-
environment: "dev",
|
|
1524
|
-
pushOnChange: answers.pushOnChange,
|
|
1525
|
-
confirmOverwrite: answers.confirmOverwrite
|
|
1526
|
-
},
|
|
1527
|
-
encryption: {
|
|
1528
|
-
algorithm: "AES-256-GCM",
|
|
1529
|
-
keyDerivation: "Argon2id"
|
|
1530
|
-
},
|
|
1531
|
-
environments: envConfig
|
|
1991
|
+
}
|
|
1992
|
+
const envConfig = {};
|
|
1993
|
+
for (const env of environments) {
|
|
1994
|
+
envConfig[env] = {
|
|
1995
|
+
file: envFileMappings[env],
|
|
1996
|
+
requireConfirmation: env === "production"
|
|
1532
1997
|
};
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1998
|
+
}
|
|
1999
|
+
const config = {
|
|
2000
|
+
project: {
|
|
2001
|
+
name: projectName2,
|
|
2002
|
+
id: ""
|
|
2003
|
+
// Will be set after creating project on server
|
|
2004
|
+
},
|
|
2005
|
+
defaults: {
|
|
2006
|
+
environment: "dev",
|
|
2007
|
+
pushOnChange: false,
|
|
2008
|
+
confirmOverwrite: true
|
|
2009
|
+
},
|
|
2010
|
+
encryption: {
|
|
2011
|
+
algorithm: "AES-256-GCM",
|
|
2012
|
+
keyDerivation: "Argon2id"
|
|
2013
|
+
},
|
|
2014
|
+
environments: envConfig
|
|
2015
|
+
};
|
|
2016
|
+
await saveProjectConfig(config);
|
|
2017
|
+
success(`Created ${chalk6.cyan(".syncenvrc")}`);
|
|
2018
|
+
if (isDebug) {
|
|
2019
|
+
console.log(chalk6.dim("[DEBUG] Config saved successfully"));
|
|
2020
|
+
}
|
|
2021
|
+
console.log("");
|
|
2022
|
+
console.log(chalk6.bold("Configuration Summary:"));
|
|
2023
|
+
console.log(` Project: ${projectName2}`);
|
|
2024
|
+
console.log(` Environments:`);
|
|
2025
|
+
for (const env of environments) {
|
|
2026
|
+
console.log(` - ${env}: ${envFileMappings[env]}`);
|
|
2027
|
+
}
|
|
2028
|
+
console.log("");
|
|
2029
|
+
if (!isAuthenticated()) {
|
|
2030
|
+
warning("Not authenticated. Skipping remote resource creation.");
|
|
2031
|
+
info("Run `syncenv auth login` to authenticate, then:");
|
|
2032
|
+
info(" 1. Run `syncenv project create` to create the project on the server");
|
|
2033
|
+
info(" 2. Run `syncenv env push` to upload your .env file");
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
const { createRemote } = await inquirer4.prompt([
|
|
2037
|
+
{
|
|
2038
|
+
type: "confirm",
|
|
2039
|
+
name: "createRemote",
|
|
2040
|
+
message: "Create project on remote server?",
|
|
2041
|
+
default: true
|
|
2042
|
+
}
|
|
2043
|
+
]);
|
|
2044
|
+
if (!createRemote) {
|
|
2045
|
+
info("Skipping remote project creation.");
|
|
2046
|
+
info("You can create it later with:");
|
|
2047
|
+
info(" syncenv project create # Create project on server");
|
|
2048
|
+
info(" syncenv env push # Push environment files");
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
console.log("");
|
|
2052
|
+
info("Creating remote project...");
|
|
2053
|
+
if (isDebug) {
|
|
2054
|
+
console.log(chalk6.dim("[DEBUG] Getting/unlocking User KEK..."));
|
|
2055
|
+
}
|
|
2056
|
+
const userKek = await getOrUnlockUserKEK();
|
|
2057
|
+
if (!userKek) {
|
|
2058
|
+
error("Your encryption keys are locked.");
|
|
2059
|
+
info("Run `syncenv user-keys unlock` to unlock your keys.");
|
|
2060
|
+
process.exit(1);
|
|
2061
|
+
}
|
|
2062
|
+
if (isDebug) {
|
|
2063
|
+
console.log(chalk6.dim("[DEBUG] User KEK acquired"));
|
|
2064
|
+
}
|
|
2065
|
+
const spinner = createSpinner("Creating project on server...");
|
|
2066
|
+
spinner.start();
|
|
2067
|
+
try {
|
|
2068
|
+
const { generateDEK, encryptDEK } = await import("./crypto-X7MZU7DV.js");
|
|
2069
|
+
const dek = generateDEK();
|
|
2070
|
+
const dekEncryption = encryptDEK(dek, userKek);
|
|
2071
|
+
const project = await withAuthGuard(
|
|
2072
|
+
() => client.projects.create({
|
|
2073
|
+
name: projectName2,
|
|
2074
|
+
slug: projectName2.toLowerCase().replace(/\s+/g, "-"),
|
|
2075
|
+
description: "",
|
|
2076
|
+
encryptedDek: dekEncryption.encrypted,
|
|
2077
|
+
dekIv: dekEncryption.iv
|
|
2078
|
+
})
|
|
1540
2079
|
);
|
|
1541
|
-
|
|
2080
|
+
spinner.succeed(`Project "${projectName2}" created on server`);
|
|
2081
|
+
const updatedConfig = await loadProjectConfig();
|
|
2082
|
+
if (updatedConfig) {
|
|
2083
|
+
updatedConfig.project.id = project.id;
|
|
2084
|
+
await saveProjectConfig(updatedConfig);
|
|
2085
|
+
if (isDebug) {
|
|
2086
|
+
console.log(chalk6.dim(`[DEBUG] Updated .syncenvrc with project ID: ${project.id}`));
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
console.log("");
|
|
2090
|
+
success("Project created on server successfully!");
|
|
2091
|
+
info("Environments will be created automatically when you push:");
|
|
2092
|
+
info(" syncenv env push -e dev");
|
|
2093
|
+
info(" syncenv env push -e staging");
|
|
2094
|
+
info(" syncenv env push -e production");
|
|
2095
|
+
} catch (err) {
|
|
2096
|
+
spinner.fail("Failed to create project on server");
|
|
2097
|
+
throw err;
|
|
1542
2098
|
}
|
|
1543
2099
|
} catch (err) {
|
|
2100
|
+
if (isDebug) {
|
|
2101
|
+
console.log(chalk6.dim(`
|
|
2102
|
+
[DEBUG] Init error: ${err.message}`));
|
|
2103
|
+
console.log(chalk6.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2104
|
+
}
|
|
2105
|
+
if (err instanceof ApiError && err.statusCode === 409) {
|
|
2106
|
+
error(`Project "${projectName}" already exists on the server`);
|
|
2107
|
+
info('Use `syncenv project use "' + projectName + '"` to use the existing project');
|
|
2108
|
+
info("Or choose a different project name");
|
|
2109
|
+
process.exit(1);
|
|
2110
|
+
}
|
|
1544
2111
|
error("Failed to initialize project:", err.message);
|
|
1545
2112
|
process.exit(1);
|
|
1546
2113
|
}
|
|
1547
2114
|
});
|
|
1548
2115
|
|
|
1549
2116
|
// src/commands/project.ts
|
|
1550
|
-
import
|
|
1551
|
-
import { Command as
|
|
1552
|
-
import
|
|
1553
|
-
|
|
1554
|
-
projectCommands
|
|
2117
|
+
import chalk7 from "chalk";
|
|
2118
|
+
import { Command as Command6 } from "commander";
|
|
2119
|
+
import inquirer5 from "inquirer";
|
|
2120
|
+
import { ApiError as ApiError2 } from "@uagents/syncenv-client";
|
|
2121
|
+
var projectCommands = new Command6("project").description("Project management commands");
|
|
2122
|
+
projectCommands.command("list").alias("ls").description("List all projects").option("-s, --search <query>", "search projects by name").option("-l, --limit <number>", "number of projects to show", "20").option("--cursor <cursor>", "cursor for pagination").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
2123
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1555
2124
|
try {
|
|
2125
|
+
if (isDebug) {
|
|
2126
|
+
console.log(chalk7.dim("\n[DEBUG] List Projects Command"));
|
|
2127
|
+
console.log(chalk7.dim(`[DEBUG] Authenticated: ${isAuthenticated()}`));
|
|
2128
|
+
console.log(chalk7.dim(`[DEBUG] Search: ${options.search || "none"}`));
|
|
2129
|
+
console.log(chalk7.dim(`[DEBUG] Limit: ${options.limit}
|
|
2130
|
+
`));
|
|
2131
|
+
}
|
|
1556
2132
|
if (!isAuthenticated()) {
|
|
1557
2133
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1558
2134
|
process.exit(1);
|
|
@@ -1570,6 +2146,11 @@ projectCommands.command("list").alias("ls").description("List all projects").opt
|
|
|
1570
2146
|
})
|
|
1571
2147
|
);
|
|
1572
2148
|
spinner.stop();
|
|
2149
|
+
if (isDebug) {
|
|
2150
|
+
console.log(chalk7.dim(`[DEBUG] Found ${result.data.length} projects`));
|
|
2151
|
+
console.log(chalk7.dim(`[DEBUG] Has more: ${result.hasMore}
|
|
2152
|
+
`));
|
|
2153
|
+
}
|
|
1573
2154
|
if (result.data.length === 0) {
|
|
1574
2155
|
if (options.search) {
|
|
1575
2156
|
info(`No projects found matching "${options.search}"`);
|
|
@@ -1578,8 +2159,8 @@ projectCommands.command("list").alias("ls").description("List all projects").opt
|
|
|
1578
2159
|
}
|
|
1579
2160
|
return;
|
|
1580
2161
|
}
|
|
1581
|
-
console.log(
|
|
1582
|
-
console.log(
|
|
2162
|
+
console.log(chalk7.bold("\nNAME DESCRIPTION UPDATED"));
|
|
2163
|
+
console.log(chalk7.dim("\u2500".repeat(70)));
|
|
1583
2164
|
for (const project of result.data) {
|
|
1584
2165
|
const name = project.name.padEnd(13).slice(0, 13);
|
|
1585
2166
|
const description = (project.description || "-").padEnd(24).slice(0, 24);
|
|
@@ -1587,31 +2168,43 @@ projectCommands.command("list").alias("ls").description("List all projects").opt
|
|
|
1587
2168
|
console.log(`${name} ${description} ${updated}`);
|
|
1588
2169
|
}
|
|
1589
2170
|
if (result.hasMore) {
|
|
1590
|
-
console.log(
|
|
2171
|
+
console.log(chalk7.dim(`
|
|
1591
2172
|
... and more projects available`));
|
|
1592
2173
|
info(`Use --cursor ${result.nextCursor} to see more`);
|
|
1593
2174
|
}
|
|
1594
2175
|
console.log("");
|
|
1595
2176
|
} catch (err) {
|
|
2177
|
+
if (isDebug) {
|
|
2178
|
+
console.log(chalk7.dim(`
|
|
2179
|
+
[DEBUG] List error: ${err.message}`));
|
|
2180
|
+
console.log(chalk7.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2181
|
+
}
|
|
1596
2182
|
error("Failed to list projects:", err.message);
|
|
1597
2183
|
process.exit(1);
|
|
1598
2184
|
}
|
|
1599
2185
|
});
|
|
1600
|
-
projectCommands.command("create [name]").description("Create a new project").option("-d, --description <description>", "project description").action(async (name, options) => {
|
|
2186
|
+
projectCommands.command("create [name]").description("Create a new project").option("-d, --description <description>", "project description").option("--debug", "enable debug mode with verbose logging").action(async (name, options) => {
|
|
2187
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1601
2188
|
try {
|
|
2189
|
+
if (isDebug) {
|
|
2190
|
+
console.log(chalk7.dim("\n[DEBUG] Create Project Command"));
|
|
2191
|
+
console.log(chalk7.dim(`[DEBUG] Authenticated: ${isAuthenticated()}`));
|
|
2192
|
+
console.log(chalk7.dim(`[DEBUG] Name: ${name || "not provided"}
|
|
2193
|
+
`));
|
|
2194
|
+
}
|
|
1602
2195
|
if (!isAuthenticated()) {
|
|
1603
2196
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1604
2197
|
process.exit(1);
|
|
1605
2198
|
}
|
|
1606
|
-
let
|
|
2199
|
+
let projectName2 = name;
|
|
1607
2200
|
let description = options.description;
|
|
1608
|
-
if (!
|
|
2201
|
+
if (!projectName2) {
|
|
1609
2202
|
const existingConfig = await loadProjectConfig();
|
|
1610
2203
|
if (existingConfig) {
|
|
1611
|
-
|
|
1612
|
-
info(`Using project name from .
|
|
2204
|
+
projectName2 = existingConfig.project.name;
|
|
2205
|
+
info(`Using project name from .syncenvrc: ${projectName2}`);
|
|
1613
2206
|
} else {
|
|
1614
|
-
const answer = await
|
|
2207
|
+
const answer = await inquirer5.prompt([
|
|
1615
2208
|
{
|
|
1616
2209
|
type: "input",
|
|
1617
2210
|
name: "name",
|
|
@@ -1619,11 +2212,11 @@ projectCommands.command("create [name]").description("Create a new project").opt
|
|
|
1619
2212
|
validate: (input) => input.length > 0 || "Name is required"
|
|
1620
2213
|
}
|
|
1621
2214
|
]);
|
|
1622
|
-
|
|
2215
|
+
projectName2 = answer.name;
|
|
1623
2216
|
}
|
|
1624
2217
|
}
|
|
1625
2218
|
if (!description) {
|
|
1626
|
-
const answer = await
|
|
2219
|
+
const answer = await inquirer5.prompt([
|
|
1627
2220
|
{
|
|
1628
2221
|
type: "input",
|
|
1629
2222
|
name: "description",
|
|
@@ -1632,36 +2225,46 @@ projectCommands.command("create [name]").description("Create a new project").opt
|
|
|
1632
2225
|
]);
|
|
1633
2226
|
description = answer.description;
|
|
1634
2227
|
}
|
|
2228
|
+
if (isDebug) {
|
|
2229
|
+
console.log(chalk7.dim("[DEBUG] Getting/unlocking User KEK..."));
|
|
2230
|
+
}
|
|
1635
2231
|
const userKek = await getOrUnlockUserKEK();
|
|
1636
2232
|
if (!userKek) {
|
|
1637
2233
|
error("Your encryption keys are locked.");
|
|
1638
2234
|
info("Run `syncenv user-keys unlock` to unlock your keys before creating a project.");
|
|
1639
2235
|
process.exit(1);
|
|
1640
2236
|
}
|
|
2237
|
+
if (isDebug) {
|
|
2238
|
+
console.log(chalk7.dim("[DEBUG] User KEK acquired\n"));
|
|
2239
|
+
}
|
|
1641
2240
|
const spinner = createSpinner("Creating project...");
|
|
1642
2241
|
spinner.start();
|
|
1643
2242
|
const { generateDEK, encryptDEK } = await import("./crypto-X7MZU7DV.js");
|
|
1644
2243
|
const dek = generateDEK();
|
|
1645
2244
|
const dekEncryption = encryptDEK(dek, userKek);
|
|
2245
|
+
if (isDebug) {
|
|
2246
|
+
console.log(chalk7.dim("[DEBUG] Generated DEK and encrypted with User KEK"));
|
|
2247
|
+
console.log(chalk7.dim(`[DEBUG] DEK IV: ${dekEncryption.iv.substring(0, 16)}...`));
|
|
2248
|
+
}
|
|
1646
2249
|
const project = await withAuthGuard(
|
|
1647
2250
|
() => client.projects.create({
|
|
1648
|
-
name:
|
|
1649
|
-
slug:
|
|
2251
|
+
name: projectName2,
|
|
2252
|
+
slug: projectName2.toLowerCase().replace(/\s+/g, "-"),
|
|
1650
2253
|
description,
|
|
1651
2254
|
encryptedDek: dekEncryption.encrypted,
|
|
1652
2255
|
dekIv: dekEncryption.iv
|
|
1653
2256
|
})
|
|
1654
2257
|
);
|
|
1655
|
-
spinner.succeed(`Project "${
|
|
2258
|
+
spinner.succeed(`Project "${projectName2}" created`);
|
|
1656
2259
|
const config = await loadProjectConfig();
|
|
1657
2260
|
if (config) {
|
|
1658
2261
|
config.project.id = project.id;
|
|
1659
2262
|
await saveProjectConfig(config);
|
|
1660
|
-
success("Updated .
|
|
2263
|
+
success("Updated .syncenvrc with project ID");
|
|
1661
2264
|
} else {
|
|
1662
2265
|
await saveProjectConfig({
|
|
1663
2266
|
project: {
|
|
1664
|
-
name:
|
|
2267
|
+
name: projectName2,
|
|
1665
2268
|
id: project.id
|
|
1666
2269
|
},
|
|
1667
2270
|
defaults: {
|
|
@@ -1677,27 +2280,48 @@ projectCommands.command("create [name]").description("Create a new project").opt
|
|
|
1677
2280
|
dev: { file: ".env" }
|
|
1678
2281
|
}
|
|
1679
2282
|
});
|
|
1680
|
-
success("Created .
|
|
2283
|
+
success("Created .syncenvrc");
|
|
1681
2284
|
}
|
|
1682
2285
|
info(`Project ID: ${project.id}`);
|
|
1683
2286
|
info("Default environments: dev, staging, prod");
|
|
1684
2287
|
} catch (err) {
|
|
2288
|
+
if (isDebug) {
|
|
2289
|
+
console.log(chalk7.dim(`
|
|
2290
|
+
[DEBUG] Create error: ${err.message}`));
|
|
2291
|
+
console.log(chalk7.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2292
|
+
}
|
|
2293
|
+
if (err instanceof ApiError2 && err.statusCode === 409) {
|
|
2294
|
+
error(`Project "${projectName}" already exists`);
|
|
2295
|
+
info("Use `syncenv project list` to see your projects");
|
|
2296
|
+
info('Or use `syncenv project use "' + projectName + '"` to use the existing project');
|
|
2297
|
+
process.exit(1);
|
|
2298
|
+
}
|
|
1685
2299
|
error("Failed to create project:", err.message);
|
|
1686
2300
|
process.exit(1);
|
|
1687
2301
|
}
|
|
1688
2302
|
});
|
|
1689
|
-
projectCommands.command("get <
|
|
2303
|
+
projectCommands.command("get <identifier>").description("Get project details (by ID or name)").option("--debug", "enable debug mode with verbose logging").action(async (identifier, options) => {
|
|
2304
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1690
2305
|
try {
|
|
2306
|
+
if (isDebug) {
|
|
2307
|
+
console.log(chalk7.dim("\n[DEBUG] Get Project Command"));
|
|
2308
|
+
console.log(chalk7.dim(`[DEBUG] Identifier: ${identifier}
|
|
2309
|
+
`));
|
|
2310
|
+
}
|
|
1691
2311
|
if (!isAuthenticated()) {
|
|
1692
2312
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1693
2313
|
process.exit(1);
|
|
1694
2314
|
}
|
|
1695
2315
|
const spinner = createSpinner("Fetching project...");
|
|
1696
2316
|
spinner.start();
|
|
1697
|
-
const project = await
|
|
2317
|
+
const project = await resolveProjectIdentifier(identifier);
|
|
1698
2318
|
spinner.stop();
|
|
1699
|
-
|
|
1700
|
-
|
|
2319
|
+
if (isDebug) {
|
|
2320
|
+
console.log(chalk7.dim(`[DEBUG] Project resolved: ${project.name} (${project.id})
|
|
2321
|
+
`));
|
|
2322
|
+
}
|
|
2323
|
+
console.log(chalk7.bold("\nProject Details"));
|
|
2324
|
+
console.log(chalk7.dim("\u2500".repeat(40)));
|
|
1701
2325
|
console.log(`Name: ${project.name}`);
|
|
1702
2326
|
console.log(`ID: ${project.id}`);
|
|
1703
2327
|
console.log(`Slug: ${project.slug}`);
|
|
@@ -1707,28 +2331,40 @@ projectCommands.command("get <id>").description("Get project details").action(as
|
|
|
1707
2331
|
console.log(`Created: ${new Date(project.createdAt).toLocaleString()}`);
|
|
1708
2332
|
console.log(`Updated: ${new Date(project.updatedAt).toLocaleString()}`);
|
|
1709
2333
|
const { data: environments } = await withAuthGuard(
|
|
1710
|
-
() => client.environments.listByProject(id)
|
|
2334
|
+
() => client.environments.listByProject(project.id)
|
|
1711
2335
|
);
|
|
1712
2336
|
if (environments?.length > 0) {
|
|
1713
|
-
console.log(
|
|
2337
|
+
console.log(chalk7.bold("\nEnvironments:"));
|
|
1714
2338
|
for (const env of environments) {
|
|
1715
2339
|
console.log(` \u2022 ${env.name} (v${env.currentVersion || 1})`);
|
|
1716
2340
|
}
|
|
1717
2341
|
}
|
|
1718
2342
|
} catch (err) {
|
|
2343
|
+
if (isDebug) {
|
|
2344
|
+
console.log(chalk7.dim(`
|
|
2345
|
+
[DEBUG] Get error: ${err.message}`));
|
|
2346
|
+
console.log(chalk7.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2347
|
+
}
|
|
1719
2348
|
error("Failed to get project:", err.message);
|
|
1720
2349
|
process.exit(1);
|
|
1721
2350
|
}
|
|
1722
2351
|
});
|
|
1723
|
-
projectCommands.command("delete <
|
|
2352
|
+
projectCommands.command("delete <identifier>").description("Delete a project (by ID or name)").option("-f, --force", "skip confirmation").option("--debug", "enable debug mode with verbose logging").action(async (identifier, options) => {
|
|
2353
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1724
2354
|
try {
|
|
2355
|
+
if (isDebug) {
|
|
2356
|
+
console.log(chalk7.dim("\n[DEBUG] Delete Project Command"));
|
|
2357
|
+
console.log(chalk7.dim(`[DEBUG] Identifier: ${identifier}`));
|
|
2358
|
+
console.log(chalk7.dim(`[DEBUG] Force: ${options.force}
|
|
2359
|
+
`));
|
|
2360
|
+
}
|
|
1725
2361
|
if (!isAuthenticated()) {
|
|
1726
2362
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1727
2363
|
process.exit(1);
|
|
1728
2364
|
}
|
|
1729
|
-
const project = await
|
|
2365
|
+
const project = await resolveProjectIdentifier(identifier);
|
|
1730
2366
|
if (!options.force) {
|
|
1731
|
-
const { confirm } = await
|
|
2367
|
+
const { confirm } = await inquirer5.prompt([
|
|
1732
2368
|
{
|
|
1733
2369
|
type: "input",
|
|
1734
2370
|
name: "confirm",
|
|
@@ -1743,62 +2379,329 @@ projectCommands.command("delete <id>").description("Delete a project").option("-
|
|
|
1743
2379
|
}
|
|
1744
2380
|
const spinner = createSpinner("Deleting project...");
|
|
1745
2381
|
spinner.start();
|
|
1746
|
-
await withAuthGuard(() => client.projects.delete(id));
|
|
2382
|
+
await withAuthGuard(() => client.projects.delete(project.id));
|
|
1747
2383
|
spinner.succeed(`Project "${project.name}" deleted`);
|
|
1748
2384
|
const config = await loadProjectConfig();
|
|
1749
|
-
if (config && config.project.id === id) {
|
|
2385
|
+
if (config && config.project.id === project.id) {
|
|
1750
2386
|
config.project.id = "";
|
|
1751
2387
|
await saveProjectConfig(config);
|
|
1752
|
-
info("Cleared project ID from .
|
|
2388
|
+
info("Cleared project ID from .syncenvrc");
|
|
1753
2389
|
}
|
|
1754
2390
|
} catch (err) {
|
|
2391
|
+
if (isDebug) {
|
|
2392
|
+
console.log(chalk7.dim(`
|
|
2393
|
+
[DEBUG] Delete error: ${err.message}`));
|
|
2394
|
+
console.log(chalk7.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2395
|
+
}
|
|
1755
2396
|
error("Failed to delete project:", err.message);
|
|
1756
2397
|
process.exit(1);
|
|
1757
2398
|
}
|
|
1758
2399
|
});
|
|
1759
|
-
projectCommands.command("use <
|
|
2400
|
+
projectCommands.command("use <identifier>").description("Set default project for current directory (updates .syncenvrc)").option("--debug", "enable debug mode with verbose logging").action(async (identifier, options) => {
|
|
2401
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1760
2402
|
try {
|
|
2403
|
+
if (isDebug) {
|
|
2404
|
+
console.log(chalk7.dim("\n[DEBUG] Use Project Command"));
|
|
2405
|
+
console.log(chalk7.dim(`[DEBUG] Identifier: ${identifier}
|
|
2406
|
+
`));
|
|
2407
|
+
}
|
|
1761
2408
|
if (!isAuthenticated()) {
|
|
1762
2409
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1763
2410
|
process.exit(1);
|
|
1764
2411
|
}
|
|
1765
|
-
const project = await
|
|
1766
|
-
|
|
1767
|
-
|
|
2412
|
+
const project = await resolveProjectIdentifier(identifier);
|
|
2413
|
+
const config = await loadProjectConfig();
|
|
2414
|
+
if (config) {
|
|
2415
|
+
config.project.id = project.id;
|
|
2416
|
+
config.project.name = project.name;
|
|
2417
|
+
await saveProjectConfig(config);
|
|
2418
|
+
if (isDebug) {
|
|
2419
|
+
console.log(chalk7.dim("[DEBUG] Updated .syncenvrc with project ID"));
|
|
2420
|
+
}
|
|
2421
|
+
success(`Set "${project.name}" as project for current directory`);
|
|
2422
|
+
info("This project will be used by default when running commands in this directory");
|
|
2423
|
+
} else {
|
|
2424
|
+
await saveProjectConfig({
|
|
2425
|
+
project: {
|
|
2426
|
+
name: project.name,
|
|
2427
|
+
id: project.id
|
|
2428
|
+
},
|
|
2429
|
+
defaults: {
|
|
2430
|
+
environment: "dev",
|
|
2431
|
+
pushOnChange: false,
|
|
2432
|
+
confirmOverwrite: true
|
|
2433
|
+
},
|
|
2434
|
+
encryption: {
|
|
2435
|
+
algorithm: "AES-256-GCM",
|
|
2436
|
+
keyDerivation: "Argon2id"
|
|
2437
|
+
},
|
|
2438
|
+
environments: {
|
|
2439
|
+
dev: { file: ".env" }
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
if (isDebug) {
|
|
2443
|
+
console.log(chalk7.dim("[DEBUG] Created new .syncenvrc with project ID"));
|
|
2444
|
+
}
|
|
2445
|
+
success(`Created .syncenvrc with "${project.name}" as project`);
|
|
2446
|
+
}
|
|
1768
2447
|
} catch (err) {
|
|
2448
|
+
if (isDebug) {
|
|
2449
|
+
console.log(chalk7.dim(`
|
|
2450
|
+
[DEBUG] Use error: ${err.message}`));
|
|
2451
|
+
console.log(chalk7.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2452
|
+
}
|
|
1769
2453
|
error("Failed to set default project:", err.message);
|
|
1770
2454
|
process.exit(1);
|
|
1771
2455
|
}
|
|
1772
2456
|
});
|
|
1773
2457
|
|
|
2458
|
+
// src/commands/setup.ts
|
|
2459
|
+
import chalk8 from "chalk";
|
|
2460
|
+
import { Command as Command7 } from "commander";
|
|
2461
|
+
import inquirer6 from "inquirer";
|
|
2462
|
+
var setupCommand = new Command7("setup").description("First-time setup wizard (login + encryption keys)").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
2463
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
2464
|
+
console.log(chalk8.bold("\n\u{1F527} SyncEnv Setup Wizard\n"));
|
|
2465
|
+
console.log(chalk8.dim("This wizard will help you set up SyncEnv for the first time.\n"));
|
|
2466
|
+
try {
|
|
2467
|
+
let userEmail = getConfig("userEmail");
|
|
2468
|
+
if (!isAuthenticated() || !userEmail) {
|
|
2469
|
+
info("Step 1: Authentication");
|
|
2470
|
+
console.log(chalk8.dim("You need to log in to use SyncEnv.\n"));
|
|
2471
|
+
const { hasAccount } = await inquirer6.prompt([
|
|
2472
|
+
{
|
|
2473
|
+
type: "confirm",
|
|
2474
|
+
name: "hasAccount",
|
|
2475
|
+
message: "Do you already have a SyncEnv account?",
|
|
2476
|
+
default: true
|
|
2477
|
+
}
|
|
2478
|
+
]);
|
|
2479
|
+
if (!hasAccount) {
|
|
2480
|
+
console.log("");
|
|
2481
|
+
info("Let's create your account first!");
|
|
2482
|
+
console.log(chalk8.dim("Opening browser for registration...\n"));
|
|
2483
|
+
const apiUrl = getConfig("apiUrl") || "https://syncenv-api.uagents.app";
|
|
2484
|
+
const signupUrl = apiUrl.replace("-api", "") + "/signup";
|
|
2485
|
+
console.log(chalk8.cyan("\u{1F310} Please visit:"));
|
|
2486
|
+
console.log(chalk8.bold(signupUrl));
|
|
2487
|
+
console.log("");
|
|
2488
|
+
const { openedBrowser } = await inquirer6.prompt([
|
|
2489
|
+
{
|
|
2490
|
+
type: "confirm",
|
|
2491
|
+
name: "openedBrowser",
|
|
2492
|
+
message: "Have you completed registration in the browser?",
|
|
2493
|
+
default: true
|
|
2494
|
+
}
|
|
2495
|
+
]);
|
|
2496
|
+
if (!openedBrowser) {
|
|
2497
|
+
warning("Please complete registration before continuing.");
|
|
2498
|
+
info(`Visit: ${signupUrl}`);
|
|
2499
|
+
process.exit(1);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
console.log("");
|
|
2503
|
+
info("Please log in with your credentials:\n");
|
|
2504
|
+
const { email, password: password2 } = await inquirer6.prompt([
|
|
2505
|
+
{
|
|
2506
|
+
type: "input",
|
|
2507
|
+
name: "email",
|
|
2508
|
+
message: "Email:",
|
|
2509
|
+
validate: (input) => input.includes("@") || "Please enter a valid email"
|
|
2510
|
+
},
|
|
2511
|
+
{
|
|
2512
|
+
type: "password",
|
|
2513
|
+
name: "password",
|
|
2514
|
+
message: "Password:",
|
|
2515
|
+
mask: "*",
|
|
2516
|
+
validate: (input) => input.length > 0 || "Password is required"
|
|
2517
|
+
}
|
|
2518
|
+
]);
|
|
2519
|
+
const spinner = createSpinner("Logging in...");
|
|
2520
|
+
spinner.start();
|
|
2521
|
+
try {
|
|
2522
|
+
const apiUrl = getConfig("apiUrl") || "https://syncenv-api.uagents.app";
|
|
2523
|
+
const webOrigin = apiUrl.replace("-api", "");
|
|
2524
|
+
const origin = apiUrl.startsWith("http://localhost") ? apiUrl : webOrigin;
|
|
2525
|
+
const loginResponse = await fetch(`${apiUrl}/api/auth/sign-in/email`, {
|
|
2526
|
+
method: "POST",
|
|
2527
|
+
headers: {
|
|
2528
|
+
"Content-Type": "application/json",
|
|
2529
|
+
Origin: origin
|
|
2530
|
+
},
|
|
2531
|
+
body: JSON.stringify({ email, password: password2 })
|
|
2532
|
+
});
|
|
2533
|
+
if (!loginResponse.ok) {
|
|
2534
|
+
const errorData = await loginResponse.json().catch(() => ({ error: "Login failed" }));
|
|
2535
|
+
throw new Error(errorData.error || errorData.message || "Login failed");
|
|
2536
|
+
}
|
|
2537
|
+
const setCookie = loginResponse.headers.getSetCookie?.() || (loginResponse.headers.get("set-cookie") ? [loginResponse.headers.get("set-cookie")] : []);
|
|
2538
|
+
if (setCookie.length > 0) {
|
|
2539
|
+
const { storeCookies } = await import("./cookie-store-UGGEBXBV.js");
|
|
2540
|
+
storeCookies(setCookie);
|
|
2541
|
+
}
|
|
2542
|
+
const result = await loginResponse.json();
|
|
2543
|
+
setConfig("userId", result.user.id);
|
|
2544
|
+
setConfig("userEmail", result.user.email);
|
|
2545
|
+
userEmail = result.user.email;
|
|
2546
|
+
spinner.succeed("Logged in successfully");
|
|
2547
|
+
} catch (err) {
|
|
2548
|
+
spinner.fail("Login failed");
|
|
2549
|
+
throw err;
|
|
2550
|
+
}
|
|
2551
|
+
} else {
|
|
2552
|
+
success(`Already authenticated as ${userEmail}`);
|
|
2553
|
+
}
|
|
2554
|
+
console.log("");
|
|
2555
|
+
info("Step 2: Encryption Keys");
|
|
2556
|
+
console.log(
|
|
2557
|
+
chalk8.dim("SyncEnv uses end-to-end encryption to protect your environment variables.\n")
|
|
2558
|
+
);
|
|
2559
|
+
const hasKeys = await checkUserKeys(isDebug);
|
|
2560
|
+
if (hasKeys) {
|
|
2561
|
+
warning("You already have encryption keys set up.");
|
|
2562
|
+
const { resetKeys } = await inquirer6.prompt([
|
|
2563
|
+
{
|
|
2564
|
+
type: "confirm",
|
|
2565
|
+
name: "resetKeys",
|
|
2566
|
+
message: "Do you want to reset your encryption keys?",
|
|
2567
|
+
default: false
|
|
2568
|
+
}
|
|
2569
|
+
]);
|
|
2570
|
+
if (!resetKeys) {
|
|
2571
|
+
info("Setup complete! Your existing keys will be used.");
|
|
2572
|
+
printNextSteps();
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
console.log("");
|
|
2577
|
+
info("Creating encryption keys...");
|
|
2578
|
+
console.log(chalk8.dim("Your login password will be used to encrypt your keys.\n"));
|
|
2579
|
+
const { password } = await inquirer6.prompt([
|
|
2580
|
+
{
|
|
2581
|
+
type: "password",
|
|
2582
|
+
name: "password",
|
|
2583
|
+
message: "Enter your login password:",
|
|
2584
|
+
mask: "*",
|
|
2585
|
+
validate: (input) => {
|
|
2586
|
+
if (input.length < 8) return "Password must be at least 8 characters";
|
|
2587
|
+
return true;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
]);
|
|
2591
|
+
const keySpinner = createSpinner("Generating encryption keys...");
|
|
2592
|
+
keySpinner.start();
|
|
2593
|
+
try {
|
|
2594
|
+
const { encryptedUserKek, kekIv, kekSalt } = await setupUserKEK(password);
|
|
2595
|
+
await withAuthGuard(
|
|
2596
|
+
() => client.userKeys.create({
|
|
2597
|
+
encryptedUserKek,
|
|
2598
|
+
kekIv,
|
|
2599
|
+
kekSalt
|
|
2600
|
+
})
|
|
2601
|
+
);
|
|
2602
|
+
await unlockAndStoreKEK(
|
|
2603
|
+
password,
|
|
2604
|
+
{ encryptedUserKek, kekIv, kekSalt },
|
|
2605
|
+
true
|
|
2606
|
+
// Remember in keychain
|
|
2607
|
+
);
|
|
2608
|
+
keySpinner.succeed("Encryption keys created successfully");
|
|
2609
|
+
} catch (err) {
|
|
2610
|
+
keySpinner.fail("Failed to create encryption keys");
|
|
2611
|
+
throw err;
|
|
2612
|
+
}
|
|
2613
|
+
console.log("");
|
|
2614
|
+
success("\u{1F389} Setup complete!");
|
|
2615
|
+
console.log("");
|
|
2616
|
+
console.log(chalk8.bold("Your encryption keys:"));
|
|
2617
|
+
console.log(" \u2713 Generated and encrypted with your password");
|
|
2618
|
+
console.log(" \u2713 Stored securely on server");
|
|
2619
|
+
console.log(" \u2713 Cached locally for convenience");
|
|
2620
|
+
console.log("");
|
|
2621
|
+
printNextSteps();
|
|
2622
|
+
} catch (err) {
|
|
2623
|
+
if (isDebug) {
|
|
2624
|
+
console.log(chalk8.dim(`
|
|
2625
|
+
[DEBUG] Setup error: ${err.message}`));
|
|
2626
|
+
console.log(chalk8.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2627
|
+
}
|
|
2628
|
+
error("Setup failed:", err.message);
|
|
2629
|
+
process.exit(1);
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
async function checkUserKeys(debug = false) {
|
|
2633
|
+
try {
|
|
2634
|
+
if (debug) {
|
|
2635
|
+
console.log(chalk8.dim("[DEBUG] Checking if user keys exist..."));
|
|
2636
|
+
}
|
|
2637
|
+
await withAuthGuard(() => client.userKeys.get());
|
|
2638
|
+
return true;
|
|
2639
|
+
} catch (err) {
|
|
2640
|
+
if (err.message?.includes("404") || err.status === 404) {
|
|
2641
|
+
return false;
|
|
2642
|
+
}
|
|
2643
|
+
throw err;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
function printNextSteps() {
|
|
2647
|
+
console.log(chalk8.bold("Next steps:"));
|
|
2648
|
+
console.log(" 1. Run `syncenv init` to initialize a project");
|
|
2649
|
+
console.log(" 2. Run `syncenv env push` to upload your .env file");
|
|
2650
|
+
console.log("");
|
|
2651
|
+
info("For help, run: syncenv --help");
|
|
2652
|
+
}
|
|
2653
|
+
|
|
1774
2654
|
// src/commands/user-keys.ts
|
|
1775
|
-
import
|
|
1776
|
-
import { Command as
|
|
1777
|
-
import
|
|
1778
|
-
var userKeysCommands = new
|
|
2655
|
+
import chalk9 from "chalk";
|
|
2656
|
+
import { Command as Command8 } from "commander";
|
|
2657
|
+
import inquirer7 from "inquirer";
|
|
2658
|
+
var userKeysCommands = new Command8("user-keys").description(
|
|
1779
2659
|
"User encryption key management commands"
|
|
1780
2660
|
);
|
|
1781
|
-
async function hasUserKeys() {
|
|
2661
|
+
async function hasUserKeys(debug = false) {
|
|
1782
2662
|
try {
|
|
2663
|
+
if (debug) {
|
|
2664
|
+
console.log(chalk9.dim("[DEBUG] Checking if user keys exist..."));
|
|
2665
|
+
console.log(chalk9.dim(`[DEBUG] API URL: ${client.getBaseUrl?.() || "N/A"}`));
|
|
2666
|
+
}
|
|
1783
2667
|
await withAuthGuard(() => client.userKeys.get());
|
|
2668
|
+
if (debug) {
|
|
2669
|
+
console.log(chalk9.dim("[DEBUG] User keys found on server"));
|
|
2670
|
+
}
|
|
1784
2671
|
return true;
|
|
1785
2672
|
} catch (err) {
|
|
1786
|
-
if (
|
|
2673
|
+
if (debug) {
|
|
2674
|
+
console.log(chalk9.dim(`[DEBUG] Error checking keys: ${err.message}`));
|
|
2675
|
+
console.log(chalk9.dim(`[DEBUG] Error status: ${err.status || err.statusCode || "unknown"}`));
|
|
2676
|
+
}
|
|
2677
|
+
if (err.message?.includes("404") || err.status === 404 || err.statusCode === 404) {
|
|
2678
|
+
if (debug) {
|
|
2679
|
+
console.log(chalk9.dim("[DEBUG] User keys not found (404)"));
|
|
2680
|
+
}
|
|
1787
2681
|
return false;
|
|
1788
2682
|
}
|
|
1789
2683
|
throw err;
|
|
1790
2684
|
}
|
|
1791
2685
|
}
|
|
1792
|
-
userKeysCommands.command("setup").description("Initialize your encryption keys (first time setup)").action(async () => {
|
|
2686
|
+
userKeysCommands.command("setup").description("Initialize your encryption keys (first time setup)").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
2687
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1793
2688
|
try {
|
|
2689
|
+
if (isDebug) {
|
|
2690
|
+
console.log(chalk9.dim("\n[DEBUG] Setup Command"));
|
|
2691
|
+
console.log(chalk9.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
2692
|
+
`));
|
|
2693
|
+
}
|
|
1794
2694
|
if (!isAuthenticated()) {
|
|
1795
2695
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1796
2696
|
process.exit(1);
|
|
1797
2697
|
}
|
|
1798
|
-
|
|
2698
|
+
if (isDebug) {
|
|
2699
|
+
console.log(chalk9.dim("[DEBUG] Checking if user keys already exist..."));
|
|
2700
|
+
}
|
|
2701
|
+
const hasKeys = await hasUserKeys(isDebug);
|
|
1799
2702
|
if (hasKeys) {
|
|
1800
2703
|
warning("You already have encryption keys set up.");
|
|
1801
|
-
const { overwrite } = await
|
|
2704
|
+
const { overwrite } = await inquirer7.prompt([
|
|
1802
2705
|
{
|
|
1803
2706
|
type: "confirm",
|
|
1804
2707
|
name: "overwrite",
|
|
@@ -1811,11 +2714,12 @@ userKeysCommands.command("setup").description("Initialize your encryption keys (
|
|
|
1811
2714
|
return;
|
|
1812
2715
|
}
|
|
1813
2716
|
}
|
|
1814
|
-
|
|
2717
|
+
info("Your login password will be used to encrypt your encryption keys.");
|
|
2718
|
+
const { password } = await inquirer7.prompt([
|
|
1815
2719
|
{
|
|
1816
2720
|
type: "password",
|
|
1817
2721
|
name: "password",
|
|
1818
|
-
message: "
|
|
2722
|
+
message: "Enter your login password:",
|
|
1819
2723
|
mask: "*",
|
|
1820
2724
|
validate: (input) => {
|
|
1821
2725
|
if (input.length < 8) return "Password must be at least 8 characters";
|
|
@@ -1823,11 +2727,11 @@ userKeysCommands.command("setup").description("Initialize your encryption keys (
|
|
|
1823
2727
|
}
|
|
1824
2728
|
}
|
|
1825
2729
|
]);
|
|
1826
|
-
await
|
|
2730
|
+
await inquirer7.prompt([
|
|
1827
2731
|
{
|
|
1828
2732
|
type: "password",
|
|
1829
2733
|
name: "confirmPassword",
|
|
1830
|
-
message: "Confirm
|
|
2734
|
+
message: "Confirm your login password:",
|
|
1831
2735
|
mask: "*",
|
|
1832
2736
|
validate: (input) => {
|
|
1833
2737
|
if (input !== password) return "Passwords do not match";
|
|
@@ -1854,68 +2758,138 @@ userKeysCommands.command("setup").description("Initialize your encryption keys (
|
|
|
1854
2758
|
);
|
|
1855
2759
|
spinner.succeed("Encryption keys set up successfully");
|
|
1856
2760
|
success("Your encryption keys are ready");
|
|
1857
|
-
info("Your keys are encrypted with your password and stored securely");
|
|
1858
|
-
|
|
2761
|
+
info("Your keys are encrypted with your login password and stored securely");
|
|
2762
|
+
info("Use your login password to unlock keys in the future");
|
|
1859
2763
|
} catch (err) {
|
|
2764
|
+
if (isDebug) {
|
|
2765
|
+
console.log(chalk9.dim(`
|
|
2766
|
+
[DEBUG] Setup error: ${err.message}`));
|
|
2767
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2768
|
+
}
|
|
1860
2769
|
spinner.fail("Failed to set up encryption keys");
|
|
1861
2770
|
throw err;
|
|
1862
2771
|
}
|
|
1863
2772
|
} catch (err) {
|
|
2773
|
+
if (isDebug) {
|
|
2774
|
+
console.log(chalk9.dim(`
|
|
2775
|
+
[DEBUG] Outer setup error: ${err.message}`));
|
|
2776
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2777
|
+
}
|
|
1864
2778
|
error("Setup failed:", err.message);
|
|
1865
2779
|
process.exit(1);
|
|
1866
2780
|
}
|
|
1867
2781
|
});
|
|
1868
|
-
userKeysCommands.command("unlock").description("Unlock your encryption keys for this session").option(
|
|
2782
|
+
userKeysCommands.command("unlock").description("Unlock your encryption keys for this session").option(
|
|
2783
|
+
"--no-remember",
|
|
2784
|
+
"Do not remember KEK in system keychain (will require password next time)"
|
|
2785
|
+
).option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
2786
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
2787
|
+
const remember = options.remember !== false;
|
|
1869
2788
|
try {
|
|
2789
|
+
if (isDebug) {
|
|
2790
|
+
console.log(chalk9.dim("\n[DEBUG] Unlock Command"));
|
|
2791
|
+
console.log(chalk9.dim(`[DEBUG] Authenticated: ${isAuthenticated()}`));
|
|
2792
|
+
console.log(chalk9.dim(`[DEBUG] Remember option: ${remember}
|
|
2793
|
+
`));
|
|
2794
|
+
}
|
|
1870
2795
|
if (!isAuthenticated()) {
|
|
1871
2796
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1872
2797
|
process.exit(1);
|
|
1873
2798
|
}
|
|
1874
|
-
|
|
2799
|
+
if (isDebug) {
|
|
2800
|
+
console.log(chalk9.dim("[DEBUG] Checking user keys..."));
|
|
2801
|
+
}
|
|
2802
|
+
const hasKeys = await hasUserKeys(isDebug);
|
|
1875
2803
|
if (!hasKeys) {
|
|
1876
2804
|
error("You do not have encryption keys set up yet.");
|
|
1877
2805
|
info("Run `syncenv user-keys setup` to create your keys.");
|
|
1878
2806
|
process.exit(1);
|
|
1879
2807
|
}
|
|
2808
|
+
if (isDebug) {
|
|
2809
|
+
console.log(chalk9.dim("[DEBUG] Checking if keys already cached..."));
|
|
2810
|
+
}
|
|
1880
2811
|
const cached = await getUserKEK();
|
|
1881
2812
|
if (cached) {
|
|
1882
2813
|
const remaining = Math.round(getKEKTimeRemaining() / 6e4);
|
|
1883
2814
|
info(`Your encryption keys are already unlocked (${remaining} minutes remaining).`);
|
|
1884
2815
|
return;
|
|
1885
2816
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
2817
|
+
if (isDebug) {
|
|
2818
|
+
console.log(chalk9.dim("[DEBUG] Keys not cached, need to unlock from server"));
|
|
2819
|
+
}
|
|
2820
|
+
if (isDebug) {
|
|
2821
|
+
console.log(chalk9.dim("[DEBUG] Fetching user keys from server..."));
|
|
2822
|
+
}
|
|
2823
|
+
let userKeys;
|
|
2824
|
+
try {
|
|
2825
|
+
userKeys = await withAuthGuard(() => client.userKeys.get());
|
|
2826
|
+
if (isDebug) {
|
|
2827
|
+
console.log(chalk9.dim("[DEBUG] User keys retrieved successfully"));
|
|
2828
|
+
console.log(chalk9.dim(`[DEBUG] Key version: ${userKeys.version || 1}`));
|
|
2829
|
+
console.log(chalk9.dim(`[DEBUG] Has encryptedUserKek: ${!!userKeys.encryptedUserKek}`));
|
|
2830
|
+
console.log(chalk9.dim(`[DEBUG] Has kekIv: ${!!userKeys.kekIv}`));
|
|
2831
|
+
console.log(chalk9.dim(`[DEBUG] Has kekSalt: ${!!userKeys.kekSalt}`));
|
|
2832
|
+
}
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
if (isDebug) {
|
|
2835
|
+
console.log(chalk9.dim(`[DEBUG] Failed to fetch user keys: ${err.message}`));
|
|
2836
|
+
console.log(
|
|
2837
|
+
chalk9.dim(`[DEBUG] Error status: ${err.status || err.statusCode || "unknown"}`)
|
|
2838
|
+
);
|
|
2839
|
+
console.log(chalk9.dim(`[DEBUG] Error body: ${JSON.stringify(err.body || {})}`));
|
|
2840
|
+
}
|
|
2841
|
+
throw err;
|
|
2842
|
+
}
|
|
2843
|
+
const { password } = await inquirer7.prompt([
|
|
1888
2844
|
{
|
|
1889
2845
|
type: "password",
|
|
1890
2846
|
name: "password",
|
|
1891
|
-
message: "Enter your
|
|
2847
|
+
message: "Enter your login password:",
|
|
1892
2848
|
mask: "*"
|
|
1893
2849
|
}
|
|
1894
2850
|
]);
|
|
1895
2851
|
const spinner = createSpinner("Unlocking encryption keys...");
|
|
1896
2852
|
spinner.start();
|
|
1897
2853
|
try {
|
|
1898
|
-
await unlockAndStoreKEK(password, userKeys,
|
|
2854
|
+
await unlockAndStoreKEK(password, userKeys, remember);
|
|
1899
2855
|
spinner.succeed("Encryption keys unlocked");
|
|
1900
2856
|
success("Your keys are now available for this session");
|
|
1901
|
-
if (
|
|
2857
|
+
if (remember) {
|
|
1902
2858
|
info("Keys are stored in system keychain for future sessions");
|
|
2859
|
+
} else {
|
|
2860
|
+
info("Keys are not stored (use --no-remember to skip storing in keychain)");
|
|
1903
2861
|
}
|
|
1904
2862
|
info("Keys will auto-lock after 30 minutes of inactivity");
|
|
1905
2863
|
info("Run `syncenv user-keys lock` to lock immediately");
|
|
1906
2864
|
} catch (err) {
|
|
2865
|
+
if (isDebug) {
|
|
2866
|
+
console.log(chalk9.dim(`
|
|
2867
|
+
[DEBUG] Unlock error: ${err.message}`));
|
|
2868
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2869
|
+
}
|
|
1907
2870
|
console.error(`Faild to unlock encryption keys: ${err}`);
|
|
1908
2871
|
spinner.fail("Failed to unlock encryption keys");
|
|
1909
|
-
error("Incorrect password. Please try again.");
|
|
2872
|
+
error("Incorrect login password. Please try again.");
|
|
1910
2873
|
process.exit(1);
|
|
1911
2874
|
}
|
|
1912
2875
|
} catch (err) {
|
|
2876
|
+
if (isDebug) {
|
|
2877
|
+
console.log(chalk9.dim(`
|
|
2878
|
+
[DEBUG] Outer error: ${err.message}`));
|
|
2879
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2880
|
+
}
|
|
1913
2881
|
error("Unlock failed:", err.message);
|
|
1914
2882
|
process.exit(1);
|
|
1915
2883
|
}
|
|
1916
2884
|
});
|
|
1917
|
-
userKeysCommands.command("lock").description("Lock (clear) your encryption keys from memory").option("--forget", "Also remove from system keychain").action(async (options) => {
|
|
2885
|
+
userKeysCommands.command("lock").description("Lock (clear) your encryption keys from memory").option("--forget", "Also remove from system keychain").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
2886
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1918
2887
|
try {
|
|
2888
|
+
if (isDebug) {
|
|
2889
|
+
console.log(chalk9.dim("\n[DEBUG] Lock Command"));
|
|
2890
|
+
console.log(chalk9.dim(`[DEBUG] Forget option: ${options.forget}
|
|
2891
|
+
`));
|
|
2892
|
+
}
|
|
1919
2893
|
await lockKEK(options.forget);
|
|
1920
2894
|
success("Encryption keys locked");
|
|
1921
2895
|
if (options.forget) {
|
|
@@ -1925,70 +2899,116 @@ userKeysCommands.command("lock").description("Lock (clear) your encryption keys
|
|
|
1925
2899
|
}
|
|
1926
2900
|
info("Run `syncenv user-keys unlock` to unlock again");
|
|
1927
2901
|
} catch (err) {
|
|
2902
|
+
if (isDebug) {
|
|
2903
|
+
console.log(chalk9.dim(`
|
|
2904
|
+
[DEBUG] Lock error: ${err.message}`));
|
|
2905
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2906
|
+
}
|
|
1928
2907
|
error("Lock failed:", err.message);
|
|
1929
2908
|
process.exit(1);
|
|
1930
2909
|
}
|
|
1931
2910
|
});
|
|
1932
|
-
userKeysCommands.command("status").description("Check your encryption key status").action(async () => {
|
|
2911
|
+
userKeysCommands.command("status").description("Check your encryption key status").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
2912
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1933
2913
|
try {
|
|
2914
|
+
if (isDebug) {
|
|
2915
|
+
console.log(chalk9.dim("\n[DEBUG] Status Command"));
|
|
2916
|
+
console.log(chalk9.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
2917
|
+
`));
|
|
2918
|
+
}
|
|
1934
2919
|
if (!isAuthenticated()) {
|
|
1935
2920
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1936
2921
|
process.exit(1);
|
|
1937
2922
|
}
|
|
1938
|
-
console.log(
|
|
1939
|
-
console.log(
|
|
1940
|
-
|
|
2923
|
+
console.log(chalk9.bold("\nEncryption Key Status"));
|
|
2924
|
+
console.log(chalk9.dim("\u2500".repeat(40)));
|
|
2925
|
+
if (isDebug) {
|
|
2926
|
+
console.log(chalk9.dim("[DEBUG] Checking user keys on server..."));
|
|
2927
|
+
}
|
|
2928
|
+
const hasKeys = await hasUserKeys(isDebug);
|
|
1941
2929
|
if (!hasKeys) {
|
|
1942
|
-
console.log(`Setup: ${
|
|
2930
|
+
console.log(`Setup: ${chalk9.yellow("Not set up")}`);
|
|
1943
2931
|
info("\nRun `syncenv user-keys setup` to create your encryption keys");
|
|
1944
2932
|
return;
|
|
1945
2933
|
}
|
|
1946
|
-
console.log(`Setup: ${
|
|
2934
|
+
console.log(`Setup: ${chalk9.green("\u2713 Complete")}`);
|
|
2935
|
+
if (isDebug) {
|
|
2936
|
+
console.log(chalk9.dim("[DEBUG] Checking local KEK cache..."));
|
|
2937
|
+
}
|
|
1947
2938
|
const cached = isKEKCached();
|
|
1948
2939
|
if (cached) {
|
|
1949
2940
|
const remaining = Math.round(getKEKTimeRemaining() / 6e4);
|
|
1950
|
-
console.log(`Session: ${
|
|
2941
|
+
console.log(`Session: ${chalk9.green("\u2713 Unlocked")} (${remaining} min remaining)`);
|
|
1951
2942
|
info("\nYour encryption keys are active for this session");
|
|
1952
2943
|
} else {
|
|
1953
|
-
console.log(`Session: ${
|
|
2944
|
+
console.log(`Session: ${chalk9.yellow("\u25CB Locked")}`);
|
|
1954
2945
|
info("\nRun `syncenv user-keys unlock` to unlock your keys");
|
|
1955
2946
|
}
|
|
2947
|
+
if (isDebug) {
|
|
2948
|
+
console.log(chalk9.dim("[DEBUG] Fetching user keys from server..."));
|
|
2949
|
+
}
|
|
1956
2950
|
const userKeys = await withAuthGuard(() => client.userKeys.get());
|
|
1957
2951
|
console.log(`
|
|
1958
2952
|
Key Version: ${userKeys.version || 1}`);
|
|
1959
2953
|
console.log(`Algorithm: AES-256-GCM with PBKDF2`);
|
|
2954
|
+
if (isDebug) {
|
|
2955
|
+
console.log(chalk9.dim("\n[DEBUG] Key details:"));
|
|
2956
|
+
console.log(chalk9.dim(`[DEBUG] Has encryptedUserKek: ${!!userKeys.encryptedUserKek}`));
|
|
2957
|
+
console.log(chalk9.dim(`[DEBUG] Has kekIv: ${!!userKeys.kekIv}`));
|
|
2958
|
+
console.log(chalk9.dim(`[DEBUG] Has kekSalt: ${!!userKeys.kekSalt}`));
|
|
2959
|
+
}
|
|
1960
2960
|
console.log("");
|
|
1961
2961
|
} catch (err) {
|
|
2962
|
+
if (isDebug) {
|
|
2963
|
+
console.log(chalk9.dim(`
|
|
2964
|
+
[DEBUG] Status error: ${err.message}`));
|
|
2965
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
2966
|
+
}
|
|
1962
2967
|
error("Status check failed:", err.message);
|
|
1963
2968
|
process.exit(1);
|
|
1964
2969
|
}
|
|
1965
2970
|
});
|
|
1966
|
-
userKeysCommands.command("rotate").description("Re-encrypt your keys with a new password").action(async () => {
|
|
2971
|
+
userKeysCommands.command("rotate").description("Re-encrypt your keys with a new password").option("--debug", "enable debug mode with verbose logging").action(async (options) => {
|
|
2972
|
+
const isDebug = options.debug || process.env.SYNCENV_DEBUG === "true";
|
|
1967
2973
|
try {
|
|
2974
|
+
if (isDebug) {
|
|
2975
|
+
console.log(chalk9.dim("\n[DEBUG] Rotate Command"));
|
|
2976
|
+
console.log(chalk9.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
2977
|
+
`));
|
|
2978
|
+
}
|
|
1968
2979
|
if (!isAuthenticated()) {
|
|
1969
2980
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1970
2981
|
process.exit(1);
|
|
1971
2982
|
}
|
|
1972
|
-
|
|
2983
|
+
if (isDebug) {
|
|
2984
|
+
console.log(chalk9.dim("[DEBUG] Checking user keys..."));
|
|
2985
|
+
}
|
|
2986
|
+
const hasKeys = await hasUserKeys(isDebug);
|
|
1973
2987
|
if (!hasKeys) {
|
|
1974
2988
|
error("You do not have encryption keys set up yet.");
|
|
1975
2989
|
info("Run `syncenv user-keys setup` to create your keys.");
|
|
1976
2990
|
process.exit(1);
|
|
1977
2991
|
}
|
|
2992
|
+
if (isDebug) {
|
|
2993
|
+
console.log(chalk9.dim("[DEBUG] Fetching user keys from server..."));
|
|
2994
|
+
}
|
|
1978
2995
|
const userKeys = await withAuthGuard(() => client.userKeys.get());
|
|
1979
|
-
|
|
2996
|
+
if (isDebug) {
|
|
2997
|
+
console.log(chalk9.dim("[DEBUG] User keys retrieved successfully"));
|
|
2998
|
+
}
|
|
2999
|
+
const { oldPassword } = await inquirer7.prompt([
|
|
1980
3000
|
{
|
|
1981
3001
|
type: "password",
|
|
1982
3002
|
name: "oldPassword",
|
|
1983
|
-
message: "Enter your current
|
|
3003
|
+
message: "Enter your current login password:",
|
|
1984
3004
|
mask: "*"
|
|
1985
3005
|
}
|
|
1986
3006
|
]);
|
|
1987
|
-
const { newPassword } = await
|
|
3007
|
+
const { newPassword } = await inquirer7.prompt([
|
|
1988
3008
|
{
|
|
1989
3009
|
type: "password",
|
|
1990
3010
|
name: "newPassword",
|
|
1991
|
-
message: "Enter your new
|
|
3011
|
+
message: "Enter your new login password:",
|
|
1992
3012
|
mask: "*",
|
|
1993
3013
|
validate: (input) => {
|
|
1994
3014
|
if (input.length < 8) return "Password must be at least 8 characters";
|
|
@@ -1996,7 +3016,7 @@ userKeysCommands.command("rotate").description("Re-encrypt your keys with a new
|
|
|
1996
3016
|
}
|
|
1997
3017
|
}
|
|
1998
3018
|
]);
|
|
1999
|
-
await
|
|
3019
|
+
await inquirer7.prompt([
|
|
2000
3020
|
{
|
|
2001
3021
|
type: "password",
|
|
2002
3022
|
name: "confirmPassword",
|
|
@@ -2030,33 +3050,52 @@ userKeysCommands.command("rotate").description("Re-encrypt your keys with a new
|
|
|
2030
3050
|
kekSalt
|
|
2031
3051
|
})
|
|
2032
3052
|
);
|
|
2033
|
-
const { saveEncryptedKEK, saveKEKPassword, getKEKPassword } = await import("./secure-storage-
|
|
3053
|
+
const { saveEncryptedKEK, saveKEKPassword, getKEKPassword } = await import("./secure-storage-AR7HZFTA.js");
|
|
2034
3054
|
saveEncryptedKEK({ encryptedUserKek, kekIv, kekSalt });
|
|
2035
3055
|
const existingPassword = await getKEKPassword();
|
|
2036
3056
|
if (existingPassword) {
|
|
2037
3057
|
await saveKEKPassword(newPassword);
|
|
2038
3058
|
}
|
|
2039
3059
|
spinner.succeed("Keys re-encrypted successfully");
|
|
2040
|
-
success("Your encryption
|
|
2041
|
-
|
|
3060
|
+
success("Your encryption keys have been updated for your new login password");
|
|
3061
|
+
info("Use your new login password to unlock keys in the future");
|
|
2042
3062
|
} catch (err) {
|
|
3063
|
+
if (isDebug) {
|
|
3064
|
+
console.log(chalk9.dim(`
|
|
3065
|
+
[DEBUG] Rotation error: ${err.message}`));
|
|
3066
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
3067
|
+
}
|
|
2043
3068
|
spinner.fail("Failed to re-encrypt keys");
|
|
2044
3069
|
throw err;
|
|
2045
3070
|
}
|
|
2046
3071
|
} catch (err) {
|
|
3072
|
+
if (isDebug) {
|
|
3073
|
+
console.log(chalk9.dim(`
|
|
3074
|
+
[DEBUG] Outer rotation error: ${err.message}`));
|
|
3075
|
+
console.log(chalk9.dim(`[DEBUG] Error stack: ${err.stack}`));
|
|
3076
|
+
}
|
|
2047
3077
|
error("Rotation failed:", err.message);
|
|
2048
3078
|
process.exit(1);
|
|
2049
3079
|
}
|
|
2050
3080
|
});
|
|
2051
3081
|
|
|
2052
3082
|
// src/index.ts
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
3083
|
+
var version = "0.0.0";
|
|
3084
|
+
try {
|
|
3085
|
+
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
3086
|
+
const pkg = JSON.parse(readFileSync(join2(__dirname, "../package.json"), "utf8"));
|
|
3087
|
+
version = pkg.version;
|
|
3088
|
+
} catch {
|
|
3089
|
+
version = process.env.SYNCENV_VERSION || "0.0.0";
|
|
3090
|
+
}
|
|
2056
3091
|
async function main() {
|
|
2057
|
-
const program = new
|
|
3092
|
+
const program = new Command9();
|
|
2058
3093
|
await loadConfig();
|
|
2059
|
-
program.name("syncenv").description("CLI for SyncEnv - Secure environment variable synchronization").version(version).option("-v, --verbose", "enable verbose logging").option(
|
|
3094
|
+
program.name("syncenv").description("CLI for SyncEnv - Secure environment variable synchronization").version(version).option("-v, --verbose", "enable verbose logging").option(
|
|
3095
|
+
"--api-url <url>",
|
|
3096
|
+
"API base URL",
|
|
3097
|
+
process.env.SYNCENV_API_URL || "https://syncenv-api.uagents.app"
|
|
3098
|
+
).hook("preAction", (thisCommand) => {
|
|
2060
3099
|
const opts = thisCommand.opts();
|
|
2061
3100
|
if (opts.verbose) {
|
|
2062
3101
|
process.env.SYNCENV_VERBOSE = "true";
|
|
@@ -2065,11 +3104,13 @@ async function main() {
|
|
|
2065
3104
|
process.env.SYNCENV_API_URL = opts.apiUrl;
|
|
2066
3105
|
}
|
|
2067
3106
|
});
|
|
3107
|
+
program.addCommand(setupCommand);
|
|
2068
3108
|
program.addCommand(authCommands);
|
|
2069
3109
|
program.addCommand(projectCommands);
|
|
2070
3110
|
program.addCommand(envCommands);
|
|
2071
3111
|
program.addCommand(userKeysCommands);
|
|
2072
3112
|
program.addCommand(initCommand);
|
|
3113
|
+
program.addCommand(configCommands);
|
|
2073
3114
|
program.addCommand(doctorCommand);
|
|
2074
3115
|
program.exitOverride();
|
|
2075
3116
|
try {
|
|
@@ -2084,7 +3125,7 @@ async function main() {
|
|
|
2084
3125
|
if (error2.code === "commander.helpDisplayed") {
|
|
2085
3126
|
process.exit(0);
|
|
2086
3127
|
}
|
|
2087
|
-
console.error(
|
|
3128
|
+
console.error(chalk10.red("\nError:"), error2.message || error2);
|
|
2088
3129
|
if (process.env.SYNCENV_VERBOSE) {
|
|
2089
3130
|
console.error(error2.stack);
|
|
2090
3131
|
}
|