@uagents/syncenv-cli 0.1.5 → 0.1.7
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 +1272 -239
- package/dist/{secure-storage-UEK3LD5L.js → secure-storage-AR7HZFTA.js} +2 -2
- package/package.json +5 -4
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,11 +39,11 @@ import {
|
|
|
36
39
|
} from "./chunk-F7ZZUTRW.js";
|
|
37
40
|
|
|
38
41
|
// src/index.ts
|
|
39
|
-
import chalk8 from "chalk";
|
|
40
|
-
import { Command as Command7 } from "commander";
|
|
41
42
|
import { readFileSync } from "fs";
|
|
42
43
|
import { dirname as dirname2, join as join2 } from "path";
|
|
43
44
|
import { fileURLToPath } from "url";
|
|
45
|
+
import chalk10 from "chalk";
|
|
46
|
+
import { Command as Command9 } from "commander";
|
|
44
47
|
|
|
45
48
|
// src/commands/auth.ts
|
|
46
49
|
import chalk2 from "chalk";
|
|
@@ -134,8 +137,8 @@ authCommands.command("signup").description("Guide to create a new account via we
|
|
|
134
137
|
console.log(" \u2022 Secure password setup");
|
|
135
138
|
console.log(" \u2022 User-friendly interface");
|
|
136
139
|
console.log();
|
|
137
|
-
const apiUrl = getConfig("apiUrl") || "https://syncenv.uagents.app";
|
|
138
|
-
const signupUrl =
|
|
140
|
+
const apiUrl = getConfig("apiUrl") || "https://syncenv-api.uagents.app";
|
|
141
|
+
const signupUrl = apiUrl.replace("-api", "") + "/signup";
|
|
139
142
|
console.log(chalk2.cyan("\u{1F310} Please visit:"));
|
|
140
143
|
console.log(chalk2.bold(signupUrl));
|
|
141
144
|
console.log();
|
|
@@ -161,7 +164,8 @@ authCommands.command("signup").description("Guide to create a new account via we
|
|
|
161
164
|
success("Waiting for you to complete registration...");
|
|
162
165
|
info("Once done, run: syncenv auth login");
|
|
163
166
|
});
|
|
164
|
-
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";
|
|
165
169
|
try {
|
|
166
170
|
let email = options.email;
|
|
167
171
|
if (!email) {
|
|
@@ -186,29 +190,95 @@ authCommands.command("login").description("Login to your account").option("-e, -
|
|
|
186
190
|
mask: "*"
|
|
187
191
|
}
|
|
188
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
|
+
}
|
|
189
200
|
const spinner = createSpinner("Authenticating...");
|
|
190
201
|
spinner.start();
|
|
191
202
|
try {
|
|
192
203
|
const apiUrl = getConfig("apiUrl");
|
|
193
|
-
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, {
|
|
194
224
|
method: "POST",
|
|
195
|
-
headers:
|
|
225
|
+
headers: requestHeaders,
|
|
196
226
|
body: JSON.stringify({ email, password })
|
|
197
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
|
+
}
|
|
198
243
|
if (!loginResponse.ok) {
|
|
199
|
-
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
|
+
}
|
|
200
257
|
throw new Error(errorData.error || errorData.message || "Login failed");
|
|
201
258
|
}
|
|
202
259
|
const setCookie = loginResponse.headers.getSetCookie?.() || (loginResponse.headers.get("set-cookie") ? [loginResponse.headers.get("set-cookie")] : []);
|
|
203
260
|
if (setCookie.length > 0) {
|
|
204
|
-
const { storeCookies } = await import("./cookie-store-
|
|
261
|
+
const { storeCookies } = await import("./cookie-store-UGGEBXBV.js");
|
|
205
262
|
storeCookies(setCookie);
|
|
206
263
|
}
|
|
207
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
|
+
}
|
|
208
274
|
setConfig("userId", result.user.id);
|
|
209
275
|
setConfig("userEmail", result.user.email);
|
|
210
276
|
spinner.succeed("Authenticated");
|
|
211
277
|
success("Session active");
|
|
278
|
+
if (isDebug) {
|
|
279
|
+
info(`User: ${result.user.email}`);
|
|
280
|
+
info(`Cookies stored: ${setCookie.length > 0 ? "Yes" : "No"}`);
|
|
281
|
+
}
|
|
212
282
|
} catch (err) {
|
|
213
283
|
spinner.fail("Authentication failed");
|
|
214
284
|
throw err;
|
|
@@ -218,8 +288,14 @@ authCommands.command("login").description("Login to your account").option("-e, -
|
|
|
218
288
|
process.exit(1);
|
|
219
289
|
}
|
|
220
290
|
});
|
|
221
|
-
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";
|
|
222
293
|
try {
|
|
294
|
+
if (isDebug) {
|
|
295
|
+
console.log(chalk2.dim("\n[DEBUG] Logout Command"));
|
|
296
|
+
console.log(chalk2.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
297
|
+
`));
|
|
298
|
+
}
|
|
223
299
|
if (!isAuthenticated()) {
|
|
224
300
|
info("Not logged in.");
|
|
225
301
|
return;
|
|
@@ -228,18 +304,38 @@ authCommands.command("logout").description("Logout and clear session").action(as
|
|
|
228
304
|
spinner.start();
|
|
229
305
|
try {
|
|
230
306
|
await client.request("POST", "/api/auth/signout");
|
|
231
|
-
|
|
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
|
+
}
|
|
232
314
|
}
|
|
233
315
|
await clearAuthState();
|
|
316
|
+
if (isDebug) {
|
|
317
|
+
console.log(chalk2.dim("[DEBUG] Local auth state cleared"));
|
|
318
|
+
}
|
|
234
319
|
spinner.succeed("Logged out");
|
|
235
320
|
success("Session terminated and local keys cleared");
|
|
236
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
|
+
}
|
|
237
327
|
error("Logout failed:", err.message);
|
|
238
328
|
process.exit(1);
|
|
239
329
|
}
|
|
240
330
|
});
|
|
241
|
-
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";
|
|
242
333
|
try {
|
|
334
|
+
if (isDebug) {
|
|
335
|
+
console.log(chalk2.dim("\n[DEBUG] Status Command"));
|
|
336
|
+
console.log(chalk2.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
337
|
+
`));
|
|
338
|
+
}
|
|
243
339
|
if (!isAuthenticated()) {
|
|
244
340
|
info("Not authenticated. Run `syncenv auth login` to login.");
|
|
245
341
|
return;
|
|
@@ -247,6 +343,9 @@ authCommands.command("status").description("Check authentication status").action
|
|
|
247
343
|
const { data: session } = await withAuthGuard(
|
|
248
344
|
() => client.request("GET", "/api/auth/get-session")
|
|
249
345
|
);
|
|
346
|
+
if (isDebug) {
|
|
347
|
+
console.log(chalk2.dim("[DEBUG] Session fetched from server"));
|
|
348
|
+
}
|
|
250
349
|
if (session.session) {
|
|
251
350
|
console.log(
|
|
252
351
|
`${chalk2.green("\u2713")} Authenticated as ${chalk2.cyan(session.session.user.email)}`
|
|
@@ -256,12 +355,23 @@ authCommands.command("status").description("Check authentication status").action
|
|
|
256
355
|
info("Session expired. Please login again.");
|
|
257
356
|
}
|
|
258
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
|
+
}
|
|
259
363
|
error("Failed to check status:", err.message);
|
|
260
364
|
process.exit(1);
|
|
261
365
|
}
|
|
262
366
|
});
|
|
263
|
-
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";
|
|
264
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
|
+
}
|
|
265
375
|
if (!isAuthenticated()) {
|
|
266
376
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
267
377
|
process.exit(1);
|
|
@@ -312,8 +422,14 @@ authCommands.command("change-password").description("Change your account passwor
|
|
|
312
422
|
}
|
|
313
423
|
})
|
|
314
424
|
);
|
|
425
|
+
if (isDebug) {
|
|
426
|
+
console.log(chalk2.dim("[DEBUG] Account password changed successfully"));
|
|
427
|
+
}
|
|
315
428
|
try {
|
|
316
429
|
const userKeys = await withAuthGuard(() => client.userKeys.get());
|
|
430
|
+
if (isDebug) {
|
|
431
|
+
console.log(chalk2.dim("[DEBUG] User has encryption keys, re-encrypting..."));
|
|
432
|
+
}
|
|
317
433
|
spinner.text = "Re-encrypting encryption keys...";
|
|
318
434
|
const { unlockUserKEK: unlockUserKEK2, reencryptUserKEK: reencryptUserKEK2 } = await import("./crypto-X7MZU7DV.js");
|
|
319
435
|
const userKek = await unlockUserKEK2(
|
|
@@ -328,10 +444,16 @@ authCommands.command("change-password").description("Change your account passwor
|
|
|
328
444
|
info("Run `syncenv user-keys rotate` to fix this.");
|
|
329
445
|
return;
|
|
330
446
|
}
|
|
447
|
+
if (isDebug) {
|
|
448
|
+
console.log(chalk2.dim("[DEBUG] User KEK decrypted with old password"));
|
|
449
|
+
}
|
|
331
450
|
const { encryptedUserKek, kekIv, kekSalt } = await reencryptUserKEK2(
|
|
332
451
|
userKek,
|
|
333
452
|
answers.newPassword
|
|
334
453
|
);
|
|
454
|
+
if (isDebug) {
|
|
455
|
+
console.log(chalk2.dim("[DEBUG] User KEK re-encrypted with new password"));
|
|
456
|
+
}
|
|
335
457
|
await withAuthGuard(
|
|
336
458
|
() => client.userKeys.update({
|
|
337
459
|
encryptedUserKek,
|
|
@@ -339,60 +461,310 @@ authCommands.command("change-password").description("Change your account passwor
|
|
|
339
461
|
kekSalt
|
|
340
462
|
})
|
|
341
463
|
);
|
|
342
|
-
|
|
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");
|
|
343
468
|
saveEncryptedKEK({ encryptedUserKek, kekIv, kekSalt });
|
|
344
469
|
const existingPassword = await getKEKPassword();
|
|
345
470
|
if (existingPassword) {
|
|
346
471
|
await saveKEKPassword(answers.newPassword);
|
|
472
|
+
if (isDebug) {
|
|
473
|
+
console.log(chalk2.dim("[DEBUG] Updated stored password in keychain"));
|
|
474
|
+
}
|
|
347
475
|
}
|
|
348
476
|
} catch (err) {
|
|
349
477
|
if (!err.message?.includes("404")) {
|
|
350
478
|
throw err;
|
|
351
479
|
}
|
|
480
|
+
if (isDebug) {
|
|
481
|
+
console.log(
|
|
482
|
+
chalk2.dim("[DEBUG] User has no encryption keys (404), skipping key re-encryption")
|
|
483
|
+
);
|
|
484
|
+
}
|
|
352
485
|
}
|
|
353
486
|
spinner.succeed("Password changed successfully");
|
|
354
487
|
success("Your account password has been updated");
|
|
355
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
|
+
}
|
|
356
494
|
spinner.fail("Failed to change password");
|
|
357
495
|
throw err;
|
|
358
496
|
}
|
|
359
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
|
+
}
|
|
360
503
|
error("Change password failed:", err.message);
|
|
361
504
|
process.exit(1);
|
|
362
505
|
}
|
|
363
506
|
});
|
|
364
507
|
|
|
365
|
-
// src/commands/
|
|
366
|
-
import fs from "fs/promises";
|
|
508
|
+
// src/commands/config.ts
|
|
367
509
|
import chalk3 from "chalk";
|
|
368
510
|
import { Command as Command2 } from "commander";
|
|
369
|
-
|
|
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";
|
|
370
733
|
let exitCode = 0;
|
|
371
|
-
|
|
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"));
|
|
372
738
|
const nodeVersion = process.version;
|
|
373
739
|
const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0]);
|
|
374
|
-
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
|
+
}
|
|
375
744
|
if (majorVersion >= 18) {
|
|
376
745
|
success(`Node.js ${nodeVersion}`);
|
|
377
746
|
} else {
|
|
378
747
|
error(`Node.js ${nodeVersion} (>= 18 required)`);
|
|
379
748
|
exitCode = 1;
|
|
380
749
|
}
|
|
381
|
-
console.log(
|
|
750
|
+
console.log(chalk4.dim("\nChecking configuration..."));
|
|
382
751
|
const configPath = getConfigPath();
|
|
752
|
+
if (isDebug) {
|
|
753
|
+
console.log(chalk4.dim(`[DEBUG] Config path: ${configPath}`));
|
|
754
|
+
}
|
|
383
755
|
try {
|
|
384
756
|
await fs.access(configPath);
|
|
385
757
|
success(`Config file exists: ${configPath}`);
|
|
386
758
|
} catch {
|
|
387
759
|
warning(`Config file not found: ${configPath}`);
|
|
388
760
|
}
|
|
389
|
-
console.log(
|
|
761
|
+
console.log(chalk4.dim("\nChecking project configuration..."));
|
|
390
762
|
const hasConfig = await hasProjectConfig();
|
|
391
763
|
if (hasConfig) {
|
|
392
764
|
try {
|
|
393
765
|
const config = await loadProjectConfig();
|
|
394
766
|
if (config) {
|
|
395
|
-
success(`.
|
|
767
|
+
success(`.syncenvrc found`);
|
|
396
768
|
info(` Project: ${config.project.name}`);
|
|
397
769
|
if (config.project.id) {
|
|
398
770
|
info(` Project ID: ${config.project.id}`);
|
|
@@ -402,13 +774,13 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
402
774
|
info(` Default env: ${config.defaults.environment}`);
|
|
403
775
|
}
|
|
404
776
|
} catch (err) {
|
|
405
|
-
error(`.
|
|
777
|
+
error(`.syncenvrc exists but is invalid: ${err.message}`);
|
|
406
778
|
exitCode = 1;
|
|
407
779
|
}
|
|
408
780
|
} else {
|
|
409
|
-
warning(`.
|
|
781
|
+
warning(`.syncenvrc not found - run \`syncenv init\``);
|
|
410
782
|
}
|
|
411
|
-
console.log(
|
|
783
|
+
console.log(chalk4.dim("\nChecking authentication..."));
|
|
412
784
|
if (isAuthenticated()) {
|
|
413
785
|
const userEmail = getConfig("userEmail");
|
|
414
786
|
success(`Authenticated as ${userEmail}`);
|
|
@@ -428,8 +800,11 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
428
800
|
} else {
|
|
429
801
|
warning(`Not authenticated - run \`syncenv auth login\``);
|
|
430
802
|
}
|
|
431
|
-
console.log(
|
|
432
|
-
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
|
+
}
|
|
433
808
|
try {
|
|
434
809
|
const response = await fetch(apiUrl);
|
|
435
810
|
if (response.ok) {
|
|
@@ -443,6 +818,9 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
443
818
|
} catch (err) {
|
|
444
819
|
error(`Cannot connect to API at ${apiUrl}`);
|
|
445
820
|
info(` Error: ${err.message}`);
|
|
821
|
+
if (isDebug) {
|
|
822
|
+
console.log(chalk4.dim(`[DEBUG] Connection error details: ${err.stack}`));
|
|
823
|
+
}
|
|
446
824
|
exitCode = 1;
|
|
447
825
|
}
|
|
448
826
|
console.log("");
|
|
@@ -456,9 +834,9 @@ var doctorCommand = new Command2("doctor").description("Diagnose configuration a
|
|
|
456
834
|
|
|
457
835
|
// src/commands/env.ts
|
|
458
836
|
import fs2 from "fs/promises";
|
|
459
|
-
import
|
|
460
|
-
import { Command as
|
|
461
|
-
import
|
|
837
|
+
import chalk5 from "chalk";
|
|
838
|
+
import { Command as Command4 } from "commander";
|
|
839
|
+
import inquirer3 from "inquirer";
|
|
462
840
|
|
|
463
841
|
// src/state/index.ts
|
|
464
842
|
import { existsSync } from "fs";
|
|
@@ -466,7 +844,7 @@ import { readFile, writeFile, mkdir } from "fs/promises";
|
|
|
466
844
|
import { homedir } from "os";
|
|
467
845
|
import { join, dirname } from "path";
|
|
468
846
|
var STATE_VERSION = 1;
|
|
469
|
-
var STATE_DIR = ".
|
|
847
|
+
var STATE_DIR = ".syncenv";
|
|
470
848
|
var STATE_FILE = "state.json";
|
|
471
849
|
var StateManager = class {
|
|
472
850
|
statePath;
|
|
@@ -641,17 +1019,23 @@ function createStateManager(options) {
|
|
|
641
1019
|
var globalState = createStateManager();
|
|
642
1020
|
|
|
643
1021
|
// src/commands/env.ts
|
|
644
|
-
var envCommands = new
|
|
645
|
-
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) => {
|
|
646
1027
|
try {
|
|
647
1028
|
if (!isAuthenticated()) {
|
|
648
1029
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
649
1030
|
process.exit(1);
|
|
650
1031
|
}
|
|
651
1032
|
const config = await loadProjectConfig();
|
|
652
|
-
const projectId = options.project || config?.project.id
|
|
1033
|
+
const projectId = options.project || config?.project.id;
|
|
653
1034
|
if (!projectId) {
|
|
654
|
-
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");
|
|
655
1039
|
process.exit(1);
|
|
656
1040
|
}
|
|
657
1041
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -708,10 +1092,24 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
708
1092
|
warning(`Remote has been modified (version ${localVersion} \u2192 ${remoteVersion})`);
|
|
709
1093
|
info("Attempting to merge...\n");
|
|
710
1094
|
try {
|
|
711
|
-
const baseDownload = await client.environments.downloadContent(
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
+
);
|
|
715
1113
|
const mergeInput = {
|
|
716
1114
|
base: parseEnvFile(baseContent),
|
|
717
1115
|
local: parseEnvFile(content),
|
|
@@ -719,10 +1117,10 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
719
1117
|
};
|
|
720
1118
|
const mergeResult = threeWayMerge(mergeInput);
|
|
721
1119
|
if (mergeResult.autoMerged) {
|
|
722
|
-
info(
|
|
723
|
-
info(
|
|
724
|
-
info(
|
|
725
|
-
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}`));
|
|
726
1124
|
const mergedEncrypted = encryptContent(mergeResult.mergedContent, dek);
|
|
727
1125
|
spinner.start("Uploading merged content...");
|
|
728
1126
|
const envId = environment.id;
|
|
@@ -734,7 +1132,13 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
734
1132
|
changeSummary: options.message || `Merged with remote version ${remoteVersion}`
|
|
735
1133
|
})
|
|
736
1134
|
);
|
|
737
|
-
await stateManager.recordSync(
|
|
1135
|
+
await stateManager.recordSync(
|
|
1136
|
+
projectId,
|
|
1137
|
+
envId,
|
|
1138
|
+
updateResult.version,
|
|
1139
|
+
generateContentHash(mergeResult.mergedContent),
|
|
1140
|
+
"merge"
|
|
1141
|
+
);
|
|
738
1142
|
spinner.succeed("Merged and uploaded");
|
|
739
1143
|
success(`Environment "${envName}" updated to version ${updateResult.version}`);
|
|
740
1144
|
return;
|
|
@@ -760,7 +1164,13 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
760
1164
|
changeSummary: options.message || `Merged with remote version ${remoteVersion} (with conflict resolution)`
|
|
761
1165
|
})
|
|
762
1166
|
);
|
|
763
|
-
await stateManager.recordSync(
|
|
1167
|
+
await stateManager.recordSync(
|
|
1168
|
+
projectId,
|
|
1169
|
+
envId,
|
|
1170
|
+
updateResult.version,
|
|
1171
|
+
generateContentHash(interactiveResult.content),
|
|
1172
|
+
"merge"
|
|
1173
|
+
);
|
|
764
1174
|
spinner.succeed("Resolved and uploaded");
|
|
765
1175
|
success(`Environment "${envName}" updated to version ${updateResult.version}`);
|
|
766
1176
|
return;
|
|
@@ -822,16 +1232,19 @@ envCommands.command("push").description("Push .env file to server").option("-p,
|
|
|
822
1232
|
process.exit(1);
|
|
823
1233
|
}
|
|
824
1234
|
});
|
|
825
|
-
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) => {
|
|
826
1236
|
try {
|
|
827
1237
|
if (!isAuthenticated()) {
|
|
828
1238
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
829
1239
|
process.exit(1);
|
|
830
1240
|
}
|
|
831
1241
|
const config = await loadProjectConfig();
|
|
832
|
-
const projectId = options.project || config?.project.id
|
|
1242
|
+
const projectId = options.project || config?.project.id;
|
|
833
1243
|
if (!projectId) {
|
|
834
|
-
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");
|
|
835
1248
|
process.exit(1);
|
|
836
1249
|
}
|
|
837
1250
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -879,7 +1292,7 @@ envCommands.command("pull").description("Pull .env file from server").option("-p
|
|
|
879
1292
|
try {
|
|
880
1293
|
existingContent = await fs2.readFile(filePath, "utf-8");
|
|
881
1294
|
if (!options.yes && !options.merge) {
|
|
882
|
-
const { action } = await
|
|
1295
|
+
const { action } = await inquirer3.prompt([
|
|
883
1296
|
{
|
|
884
1297
|
type: "list",
|
|
885
1298
|
name: "action",
|
|
@@ -909,19 +1322,19 @@ envCommands.command("pull").description("Pull .env file from server").option("-p
|
|
|
909
1322
|
info("No changes to merge.");
|
|
910
1323
|
return;
|
|
911
1324
|
}
|
|
912
|
-
console.log(
|
|
1325
|
+
console.log(chalk5.bold("\nMerge changes:"));
|
|
913
1326
|
for (const key of diff.added) {
|
|
914
|
-
console.log(
|
|
1327
|
+
console.log(chalk5.green(` + ${key}=${localEnv[key]}`));
|
|
915
1328
|
}
|
|
916
1329
|
for (const key of diff.removed) {
|
|
917
|
-
console.log(
|
|
1330
|
+
console.log(chalk5.red(` - ${key}`));
|
|
918
1331
|
}
|
|
919
1332
|
for (const key of diff.modified) {
|
|
920
|
-
console.log(
|
|
921
|
-
console.log(
|
|
922
|
-
console.log(
|
|
1333
|
+
console.log(chalk5.yellow(` ~ ${key}`));
|
|
1334
|
+
console.log(chalk5.dim(` local: ${localEnv[key]}`));
|
|
1335
|
+
console.log(chalk5.dim(` remote: ${remoteEnv[key]}`));
|
|
923
1336
|
}
|
|
924
|
-
const { mergeAction } = await
|
|
1337
|
+
const { mergeAction } = await inquirer3.prompt([
|
|
925
1338
|
{
|
|
926
1339
|
type: "list",
|
|
927
1340
|
name: "mergeAction",
|
|
@@ -962,16 +1375,19 @@ envCommands.command("pull").description("Pull .env file from server").option("-p
|
|
|
962
1375
|
process.exit(1);
|
|
963
1376
|
}
|
|
964
1377
|
});
|
|
965
|
-
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) => {
|
|
966
1379
|
try {
|
|
967
1380
|
if (!isAuthenticated()) {
|
|
968
1381
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
969
1382
|
process.exit(1);
|
|
970
1383
|
}
|
|
971
1384
|
const config = await loadProjectConfig();
|
|
972
|
-
const projectId = options.project || config?.project.id
|
|
1385
|
+
const projectId = options.project || config?.project.id;
|
|
973
1386
|
if (!projectId) {
|
|
974
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");
|
|
975
1391
|
process.exit(1);
|
|
976
1392
|
}
|
|
977
1393
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -986,14 +1402,16 @@ envCommands.command("history").description("Show version history").option("-p, -
|
|
|
986
1402
|
spinner.fail(`Environment "${envName}" not found`);
|
|
987
1403
|
process.exit(1);
|
|
988
1404
|
}
|
|
989
|
-
const versions = await withAuthGuard(
|
|
1405
|
+
const versions = await withAuthGuard(
|
|
1406
|
+
() => client.sync.getVersions(environment.id, parseInt(options.limit))
|
|
1407
|
+
);
|
|
990
1408
|
spinner.stop();
|
|
991
1409
|
if (versions.length === 0) {
|
|
992
1410
|
info("No versions found.");
|
|
993
1411
|
return;
|
|
994
1412
|
}
|
|
995
|
-
console.log(
|
|
996
|
-
console.log(
|
|
1413
|
+
console.log(chalk5.bold("\nVERSION DATE AUTHOR MESSAGE"));
|
|
1414
|
+
console.log(chalk5.dim("\u2500".repeat(70)));
|
|
997
1415
|
for (const version2 of versions.slice(0, limit)) {
|
|
998
1416
|
const ver = String(version2.versionNumber).padEnd(9);
|
|
999
1417
|
const date = formatRelativeTime(version2.createdAt).padEnd(17);
|
|
@@ -1007,16 +1425,19 @@ envCommands.command("history").description("Show version history").option("-p, -
|
|
|
1007
1425
|
process.exit(1);
|
|
1008
1426
|
}
|
|
1009
1427
|
});
|
|
1010
|
-
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) => {
|
|
1011
1429
|
try {
|
|
1012
1430
|
if (!isAuthenticated()) {
|
|
1013
1431
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1014
1432
|
process.exit(1);
|
|
1015
1433
|
}
|
|
1016
1434
|
const config = await loadProjectConfig();
|
|
1017
|
-
const projectId = options.project || config?.project.id
|
|
1435
|
+
const projectId = options.project || config?.project.id;
|
|
1018
1436
|
if (!projectId) {
|
|
1019
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");
|
|
1020
1441
|
process.exit(1);
|
|
1021
1442
|
}
|
|
1022
1443
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -1037,22 +1458,22 @@ envCommands.command("diff <v1> <v2>").description("Compare two versions (not sup
|
|
|
1037
1458
|
client.environments.downloadContent(environment.id, version2)
|
|
1038
1459
|
]);
|
|
1039
1460
|
spinner.stop();
|
|
1040
|
-
console.log(
|
|
1461
|
+
console.log(chalk5.bold(`
|
|
1041
1462
|
Comparing versions ${version1} and ${version2}`));
|
|
1042
|
-
console.log(
|
|
1043
|
-
console.log(
|
|
1463
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
1464
|
+
console.log(chalk5.dim(`
|
|
1044
1465
|
Version ${version1}:`));
|
|
1045
1466
|
console.log(` Content hash: ${generateContentHash(download1.content).slice(0, 16)}...`);
|
|
1046
1467
|
console.log(` Date: ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
1047
|
-
console.log(
|
|
1468
|
+
console.log(chalk5.dim(`
|
|
1048
1469
|
Version ${version2}:`));
|
|
1049
1470
|
console.log(` Content hash: ${generateContentHash(download2.content).slice(0, 16)}...`);
|
|
1050
1471
|
console.log(` Date: ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
1051
1472
|
if (download1.content !== download2.content) {
|
|
1052
|
-
console.log(
|
|
1473
|
+
console.log(chalk5.yellow("\n\u26A0 Content differs between versions"));
|
|
1053
1474
|
info("Use `syncenv env pull --version <number>` to view a specific version");
|
|
1054
1475
|
} else {
|
|
1055
|
-
console.log(
|
|
1476
|
+
console.log(chalk5.green("\n\u2713 Content is identical"));
|
|
1056
1477
|
}
|
|
1057
1478
|
console.log("");
|
|
1058
1479
|
} catch (err) {
|
|
@@ -1060,22 +1481,25 @@ Version ${version2}:`));
|
|
|
1060
1481
|
process.exit(1);
|
|
1061
1482
|
}
|
|
1062
1483
|
});
|
|
1063
|
-
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) => {
|
|
1064
1485
|
try {
|
|
1065
1486
|
if (!isAuthenticated()) {
|
|
1066
1487
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1067
1488
|
process.exit(1);
|
|
1068
1489
|
}
|
|
1069
1490
|
const config = await loadProjectConfig();
|
|
1070
|
-
const projectId = options.project || config?.project.id
|
|
1491
|
+
const projectId = options.project || config?.project.id;
|
|
1071
1492
|
if (!projectId) {
|
|
1072
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");
|
|
1073
1497
|
process.exit(1);
|
|
1074
1498
|
}
|
|
1075
1499
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
1076
1500
|
const targetVersion = parseInt(version2);
|
|
1077
1501
|
if (!options.yes) {
|
|
1078
|
-
const { confirm } = await
|
|
1502
|
+
const { confirm } = await inquirer3.prompt([
|
|
1079
1503
|
{
|
|
1080
1504
|
type: "confirm",
|
|
1081
1505
|
name: "confirm",
|
|
@@ -1131,16 +1555,19 @@ envCommands.command("rollback <version>").description("Rollback to a specific ve
|
|
|
1131
1555
|
process.exit(1);
|
|
1132
1556
|
}
|
|
1133
1557
|
});
|
|
1134
|
-
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) => {
|
|
1135
1559
|
try {
|
|
1136
1560
|
if (!isAuthenticated()) {
|
|
1137
1561
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1138
1562
|
process.exit(1);
|
|
1139
1563
|
}
|
|
1140
1564
|
const config = await loadProjectConfig();
|
|
1141
|
-
const projectId = options.project || config?.project.id
|
|
1565
|
+
const projectId = options.project || config?.project.id;
|
|
1142
1566
|
if (!projectId) {
|
|
1143
|
-
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");
|
|
1144
1571
|
process.exit(1);
|
|
1145
1572
|
}
|
|
1146
1573
|
const spinner = createSpinner("Fetching environments...");
|
|
@@ -1155,11 +1582,11 @@ envCommands.command("list-envs").description("List all environments for a projec
|
|
|
1155
1582
|
return;
|
|
1156
1583
|
}
|
|
1157
1584
|
const project = await withAuthGuard(() => client.projects.get(projectId));
|
|
1158
|
-
console.log(
|
|
1585
|
+
console.log(chalk5.bold(`
|
|
1159
1586
|
Environments for ${project.name}`));
|
|
1160
|
-
console.log(
|
|
1161
|
-
console.log(
|
|
1162
|
-
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)));
|
|
1163
1590
|
for (const env of environments) {
|
|
1164
1591
|
const name = env.name.padEnd(11).slice(0, 11);
|
|
1165
1592
|
const version2 = `v${env.currentVersion || 1}`.padEnd(9);
|
|
@@ -1174,21 +1601,24 @@ Environments for ${project.name}`));
|
|
|
1174
1601
|
process.exit(1);
|
|
1175
1602
|
}
|
|
1176
1603
|
});
|
|
1177
|
-
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) => {
|
|
1178
1605
|
try {
|
|
1179
1606
|
if (!isAuthenticated()) {
|
|
1180
1607
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1181
1608
|
process.exit(1);
|
|
1182
1609
|
}
|
|
1183
1610
|
const config = await loadProjectConfig();
|
|
1184
|
-
const projectId = options.project || config?.project.id
|
|
1611
|
+
const projectId = options.project || config?.project.id;
|
|
1185
1612
|
if (!projectId) {
|
|
1186
|
-
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");
|
|
1187
1617
|
process.exit(1);
|
|
1188
1618
|
}
|
|
1189
1619
|
let envName = options.name;
|
|
1190
1620
|
if (!envName) {
|
|
1191
|
-
const answer = await
|
|
1621
|
+
const answer = await inquirer3.prompt([
|
|
1192
1622
|
{
|
|
1193
1623
|
type: "input",
|
|
1194
1624
|
name: "name",
|
|
@@ -1205,7 +1635,7 @@ envCommands.command("create-env").description("Create a new environment for a pr
|
|
|
1205
1635
|
}
|
|
1206
1636
|
let description = options.description;
|
|
1207
1637
|
if (!description) {
|
|
1208
|
-
const answer = await
|
|
1638
|
+
const answer = await inquirer3.prompt([
|
|
1209
1639
|
{
|
|
1210
1640
|
type: "input",
|
|
1211
1641
|
name: "description",
|
|
@@ -1242,7 +1672,7 @@ envCommands.command("create-env").description("Create a new environment for a pr
|
|
|
1242
1672
|
requireConfirmation: envName === "production"
|
|
1243
1673
|
};
|
|
1244
1674
|
await saveProjectConfig(config);
|
|
1245
|
-
info(`Updated .
|
|
1675
|
+
info(`Updated .syncenvrc with environment "${envName}"`);
|
|
1246
1676
|
}
|
|
1247
1677
|
} catch (err) {
|
|
1248
1678
|
spinner.fail("Failed to create environment");
|
|
@@ -1261,7 +1691,7 @@ envCommands.command("delete-env <id>").description("Delete an environment").opti
|
|
|
1261
1691
|
}
|
|
1262
1692
|
const env = await withAuthGuard(() => client.environments.get(id));
|
|
1263
1693
|
if (!options.force) {
|
|
1264
|
-
const { confirm } = await
|
|
1694
|
+
const { confirm } = await inquirer3.prompt([
|
|
1265
1695
|
{
|
|
1266
1696
|
type: "input",
|
|
1267
1697
|
name: "confirm",
|
|
@@ -1282,23 +1712,30 @@ envCommands.command("delete-env <id>").description("Delete an environment").opti
|
|
|
1282
1712
|
if (config && config.environments[env.name]) {
|
|
1283
1713
|
delete config.environments[env.name];
|
|
1284
1714
|
await saveProjectConfig(config);
|
|
1285
|
-
info("Updated .
|
|
1715
|
+
info("Updated .syncenvrc");
|
|
1286
1716
|
}
|
|
1287
1717
|
} catch (err) {
|
|
1288
1718
|
error("Failed to delete environment:", err.message);
|
|
1289
1719
|
process.exit(1);
|
|
1290
1720
|
}
|
|
1291
1721
|
});
|
|
1292
|
-
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) => {
|
|
1293
1727
|
try {
|
|
1294
1728
|
if (!isAuthenticated()) {
|
|
1295
1729
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1296
1730
|
process.exit(1);
|
|
1297
1731
|
}
|
|
1298
1732
|
const config = await loadProjectConfig();
|
|
1299
|
-
const projectId = options.project || config?.project.id
|
|
1733
|
+
const projectId = options.project || config?.project.id;
|
|
1300
1734
|
if (!projectId) {
|
|
1301
|
-
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");
|
|
1302
1739
|
process.exit(1);
|
|
1303
1740
|
}
|
|
1304
1741
|
const envName = options.env || config?.defaults.environment || "dev";
|
|
@@ -1336,7 +1773,13 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1336
1773
|
const decrypted = decryptContent(download.content, download.encryptionIv, dek2);
|
|
1337
1774
|
if (!options.dryRun) {
|
|
1338
1775
|
await fs2.writeFile(filePath, decrypted, "utf-8");
|
|
1339
|
-
await stateManager.recordSync(
|
|
1776
|
+
await stateManager.recordSync(
|
|
1777
|
+
projectId,
|
|
1778
|
+
environment.id,
|
|
1779
|
+
download.version || 1,
|
|
1780
|
+
download.contentHash || "",
|
|
1781
|
+
"pull"
|
|
1782
|
+
);
|
|
1340
1783
|
}
|
|
1341
1784
|
spinner.succeed("Pulled from remote");
|
|
1342
1785
|
success(`Written to ${filePath}`);
|
|
@@ -1354,12 +1797,19 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1354
1797
|
const dek = decryptDEK(project.encryptedDek, project.dekIv, userKek);
|
|
1355
1798
|
const remoteVersion = environment.currentVersion || 1;
|
|
1356
1799
|
const remoteDownload = await client.environments.downloadContent(environment.id);
|
|
1357
|
-
const remoteContent = decryptContent(
|
|
1800
|
+
const remoteContent = decryptContent(
|
|
1801
|
+
remoteDownload.content,
|
|
1802
|
+
remoteDownload.encryptionIv,
|
|
1803
|
+
dek
|
|
1804
|
+
);
|
|
1358
1805
|
let mergedContent;
|
|
1359
1806
|
let mergeSource = "push";
|
|
1360
1807
|
if (localState && localState.lastSync.version < remoteVersion) {
|
|
1361
1808
|
spinner.text = "Merging changes...";
|
|
1362
|
-
const baseDownload = await client.environments.downloadContent(
|
|
1809
|
+
const baseDownload = await client.environments.downloadContent(
|
|
1810
|
+
environment.id,
|
|
1811
|
+
localState.lastSync.version
|
|
1812
|
+
);
|
|
1363
1813
|
const baseContent = decryptContent(baseDownload.content, baseDownload.encryptionIv, dek);
|
|
1364
1814
|
const mergeInput = {
|
|
1365
1815
|
base: parseEnvFile(baseContent),
|
|
@@ -1414,12 +1864,14 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1414
1864
|
}
|
|
1415
1865
|
mergedContent = localContent;
|
|
1416
1866
|
if (!options.yes) {
|
|
1417
|
-
const { confirm } = await
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
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
|
+
]);
|
|
1423
1875
|
if (!confirm) {
|
|
1424
1876
|
info("Cancelled.");
|
|
1425
1877
|
return;
|
|
@@ -1437,7 +1889,13 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1437
1889
|
changeSummary: mergeSource === "merge" ? "Synced with merge" : "Synced local changes"
|
|
1438
1890
|
})
|
|
1439
1891
|
);
|
|
1440
|
-
await stateManager.recordSync(
|
|
1892
|
+
await stateManager.recordSync(
|
|
1893
|
+
projectId,
|
|
1894
|
+
environment.id,
|
|
1895
|
+
result.version,
|
|
1896
|
+
newContentHash,
|
|
1897
|
+
mergeSource
|
|
1898
|
+
);
|
|
1441
1899
|
spinner.succeed("Sync complete");
|
|
1442
1900
|
success(`Environment "${envName}" updated to version ${result.version}`);
|
|
1443
1901
|
} catch (err) {
|
|
@@ -1448,13 +1906,20 @@ envCommands.command("sync").description("Sync with remote (pull + merge + push i
|
|
|
1448
1906
|
|
|
1449
1907
|
// src/commands/init.ts
|
|
1450
1908
|
import path from "path";
|
|
1451
|
-
import
|
|
1452
|
-
import { Command as
|
|
1453
|
-
import
|
|
1454
|
-
|
|
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";
|
|
1455
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
|
+
}
|
|
1456
1921
|
if (await hasProjectConfig()) {
|
|
1457
|
-
const { overwrite } = await
|
|
1922
|
+
const { overwrite } = await inquirer4.prompt([
|
|
1458
1923
|
{
|
|
1459
1924
|
type: "confirm",
|
|
1460
1925
|
name: "overwrite",
|
|
@@ -1467,24 +1932,38 @@ var initCommand = new Command4("init").description("Initialize a new project con
|
|
|
1467
1932
|
return;
|
|
1468
1933
|
}
|
|
1469
1934
|
}
|
|
1470
|
-
let
|
|
1935
|
+
let projectName2;
|
|
1471
1936
|
let environments;
|
|
1937
|
+
let envFileMappings = {};
|
|
1472
1938
|
if (options.yes) {
|
|
1473
|
-
|
|
1939
|
+
projectName2 = path.basename(process.cwd());
|
|
1474
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
|
+
}
|
|
1475
1951
|
} else {
|
|
1476
|
-
const
|
|
1952
|
+
const { projectName: pname } = await inquirer4.prompt([
|
|
1477
1953
|
{
|
|
1478
1954
|
type: "input",
|
|
1479
1955
|
name: "projectName",
|
|
1480
1956
|
message: "Project name:",
|
|
1481
1957
|
default: path.basename(process.cwd()),
|
|
1482
1958
|
validate: (input) => input.length > 0 || "Project name is required"
|
|
1483
|
-
}
|
|
1959
|
+
}
|
|
1960
|
+
]);
|
|
1961
|
+
projectName2 = pname;
|
|
1962
|
+
const { environments: envs } = await inquirer4.prompt([
|
|
1484
1963
|
{
|
|
1485
1964
|
type: "checkbox",
|
|
1486
1965
|
name: "environments",
|
|
1487
|
-
message: "Select
|
|
1966
|
+
message: "Select environments to create:",
|
|
1488
1967
|
choices: [
|
|
1489
1968
|
{ name: "dev", checked: true },
|
|
1490
1969
|
{ name: "staging", checked: true },
|
|
@@ -1492,70 +1971,164 @@ var initCommand = new Command4("init").description("Initialize a new project con
|
|
|
1492
1971
|
{ name: "test", checked: false }
|
|
1493
1972
|
],
|
|
1494
1973
|
validate: (input) => input.length > 0 || "Select at least one environment"
|
|
1495
|
-
},
|
|
1496
|
-
{
|
|
1497
|
-
type: "confirm",
|
|
1498
|
-
name: "pushOnChange",
|
|
1499
|
-
message: "Auto-push on change?",
|
|
1500
|
-
default: false
|
|
1501
|
-
},
|
|
1502
|
-
{
|
|
1503
|
-
type: "confirm",
|
|
1504
|
-
name: "confirmOverwrite",
|
|
1505
|
-
message: "Confirm before overwriting files?",
|
|
1506
|
-
default: true
|
|
1507
1974
|
}
|
|
1508
1975
|
]);
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const envConfig = {};
|
|
1976
|
+
environments = envs;
|
|
1977
|
+
console.log(chalk6.dim("\nConfigure environment file mappings:"));
|
|
1512
1978
|
for (const env of environments) {
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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;
|
|
1518
1990
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
defaults: {
|
|
1526
|
-
environment: "dev",
|
|
1527
|
-
pushOnChange: answers.pushOnChange,
|
|
1528
|
-
confirmOverwrite: answers.confirmOverwrite
|
|
1529
|
-
},
|
|
1530
|
-
encryption: {
|
|
1531
|
-
algorithm: "AES-256-GCM",
|
|
1532
|
-
keyDerivation: "Argon2id"
|
|
1533
|
-
},
|
|
1534
|
-
environments: envConfig
|
|
1991
|
+
}
|
|
1992
|
+
const envConfig = {};
|
|
1993
|
+
for (const env of environments) {
|
|
1994
|
+
envConfig[env] = {
|
|
1995
|
+
file: envFileMappings[env],
|
|
1996
|
+
requireConfirmation: env === "production"
|
|
1535
1997
|
};
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
+
})
|
|
1543
2079
|
);
|
|
1544
|
-
|
|
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;
|
|
1545
2098
|
}
|
|
1546
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
|
+
}
|
|
1547
2111
|
error("Failed to initialize project:", err.message);
|
|
1548
2112
|
process.exit(1);
|
|
1549
2113
|
}
|
|
1550
2114
|
});
|
|
1551
2115
|
|
|
1552
2116
|
// src/commands/project.ts
|
|
1553
|
-
import
|
|
1554
|
-
import { Command as
|
|
1555
|
-
import
|
|
1556
|
-
|
|
1557
|
-
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";
|
|
1558
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
|
+
}
|
|
1559
2132
|
if (!isAuthenticated()) {
|
|
1560
2133
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1561
2134
|
process.exit(1);
|
|
@@ -1573,6 +2146,11 @@ projectCommands.command("list").alias("ls").description("List all projects").opt
|
|
|
1573
2146
|
})
|
|
1574
2147
|
);
|
|
1575
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
|
+
}
|
|
1576
2154
|
if (result.data.length === 0) {
|
|
1577
2155
|
if (options.search) {
|
|
1578
2156
|
info(`No projects found matching "${options.search}"`);
|
|
@@ -1581,8 +2159,8 @@ projectCommands.command("list").alias("ls").description("List all projects").opt
|
|
|
1581
2159
|
}
|
|
1582
2160
|
return;
|
|
1583
2161
|
}
|
|
1584
|
-
console.log(
|
|
1585
|
-
console.log(
|
|
2162
|
+
console.log(chalk7.bold("\nNAME DESCRIPTION UPDATED"));
|
|
2163
|
+
console.log(chalk7.dim("\u2500".repeat(70)));
|
|
1586
2164
|
for (const project of result.data) {
|
|
1587
2165
|
const name = project.name.padEnd(13).slice(0, 13);
|
|
1588
2166
|
const description = (project.description || "-").padEnd(24).slice(0, 24);
|
|
@@ -1590,31 +2168,43 @@ projectCommands.command("list").alias("ls").description("List all projects").opt
|
|
|
1590
2168
|
console.log(`${name} ${description} ${updated}`);
|
|
1591
2169
|
}
|
|
1592
2170
|
if (result.hasMore) {
|
|
1593
|
-
console.log(
|
|
2171
|
+
console.log(chalk7.dim(`
|
|
1594
2172
|
... and more projects available`));
|
|
1595
2173
|
info(`Use --cursor ${result.nextCursor} to see more`);
|
|
1596
2174
|
}
|
|
1597
2175
|
console.log("");
|
|
1598
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
|
+
}
|
|
1599
2182
|
error("Failed to list projects:", err.message);
|
|
1600
2183
|
process.exit(1);
|
|
1601
2184
|
}
|
|
1602
2185
|
});
|
|
1603
|
-
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";
|
|
1604
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
|
+
}
|
|
1605
2195
|
if (!isAuthenticated()) {
|
|
1606
2196
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1607
2197
|
process.exit(1);
|
|
1608
2198
|
}
|
|
1609
|
-
let
|
|
2199
|
+
let projectName2 = name;
|
|
1610
2200
|
let description = options.description;
|
|
1611
|
-
if (!
|
|
2201
|
+
if (!projectName2) {
|
|
1612
2202
|
const existingConfig = await loadProjectConfig();
|
|
1613
2203
|
if (existingConfig) {
|
|
1614
|
-
|
|
1615
|
-
info(`Using project name from .
|
|
2204
|
+
projectName2 = existingConfig.project.name;
|
|
2205
|
+
info(`Using project name from .syncenvrc: ${projectName2}`);
|
|
1616
2206
|
} else {
|
|
1617
|
-
const answer = await
|
|
2207
|
+
const answer = await inquirer5.prompt([
|
|
1618
2208
|
{
|
|
1619
2209
|
type: "input",
|
|
1620
2210
|
name: "name",
|
|
@@ -1622,11 +2212,11 @@ projectCommands.command("create [name]").description("Create a new project").opt
|
|
|
1622
2212
|
validate: (input) => input.length > 0 || "Name is required"
|
|
1623
2213
|
}
|
|
1624
2214
|
]);
|
|
1625
|
-
|
|
2215
|
+
projectName2 = answer.name;
|
|
1626
2216
|
}
|
|
1627
2217
|
}
|
|
1628
2218
|
if (!description) {
|
|
1629
|
-
const answer = await
|
|
2219
|
+
const answer = await inquirer5.prompt([
|
|
1630
2220
|
{
|
|
1631
2221
|
type: "input",
|
|
1632
2222
|
name: "description",
|
|
@@ -1635,36 +2225,46 @@ projectCommands.command("create [name]").description("Create a new project").opt
|
|
|
1635
2225
|
]);
|
|
1636
2226
|
description = answer.description;
|
|
1637
2227
|
}
|
|
2228
|
+
if (isDebug) {
|
|
2229
|
+
console.log(chalk7.dim("[DEBUG] Getting/unlocking User KEK..."));
|
|
2230
|
+
}
|
|
1638
2231
|
const userKek = await getOrUnlockUserKEK();
|
|
1639
2232
|
if (!userKek) {
|
|
1640
2233
|
error("Your encryption keys are locked.");
|
|
1641
2234
|
info("Run `syncenv user-keys unlock` to unlock your keys before creating a project.");
|
|
1642
2235
|
process.exit(1);
|
|
1643
2236
|
}
|
|
2237
|
+
if (isDebug) {
|
|
2238
|
+
console.log(chalk7.dim("[DEBUG] User KEK acquired\n"));
|
|
2239
|
+
}
|
|
1644
2240
|
const spinner = createSpinner("Creating project...");
|
|
1645
2241
|
spinner.start();
|
|
1646
2242
|
const { generateDEK, encryptDEK } = await import("./crypto-X7MZU7DV.js");
|
|
1647
2243
|
const dek = generateDEK();
|
|
1648
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
|
+
}
|
|
1649
2249
|
const project = await withAuthGuard(
|
|
1650
2250
|
() => client.projects.create({
|
|
1651
|
-
name:
|
|
1652
|
-
slug:
|
|
2251
|
+
name: projectName2,
|
|
2252
|
+
slug: projectName2.toLowerCase().replace(/\s+/g, "-"),
|
|
1653
2253
|
description,
|
|
1654
2254
|
encryptedDek: dekEncryption.encrypted,
|
|
1655
2255
|
dekIv: dekEncryption.iv
|
|
1656
2256
|
})
|
|
1657
2257
|
);
|
|
1658
|
-
spinner.succeed(`Project "${
|
|
2258
|
+
spinner.succeed(`Project "${projectName2}" created`);
|
|
1659
2259
|
const config = await loadProjectConfig();
|
|
1660
2260
|
if (config) {
|
|
1661
2261
|
config.project.id = project.id;
|
|
1662
2262
|
await saveProjectConfig(config);
|
|
1663
|
-
success("Updated .
|
|
2263
|
+
success("Updated .syncenvrc with project ID");
|
|
1664
2264
|
} else {
|
|
1665
2265
|
await saveProjectConfig({
|
|
1666
2266
|
project: {
|
|
1667
|
-
name:
|
|
2267
|
+
name: projectName2,
|
|
1668
2268
|
id: project.id
|
|
1669
2269
|
},
|
|
1670
2270
|
defaults: {
|
|
@@ -1680,27 +2280,48 @@ projectCommands.command("create [name]").description("Create a new project").opt
|
|
|
1680
2280
|
dev: { file: ".env" }
|
|
1681
2281
|
}
|
|
1682
2282
|
});
|
|
1683
|
-
success("Created .
|
|
2283
|
+
success("Created .syncenvrc");
|
|
1684
2284
|
}
|
|
1685
2285
|
info(`Project ID: ${project.id}`);
|
|
1686
2286
|
info("Default environments: dev, staging, prod");
|
|
1687
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
|
+
}
|
|
1688
2299
|
error("Failed to create project:", err.message);
|
|
1689
2300
|
process.exit(1);
|
|
1690
2301
|
}
|
|
1691
2302
|
});
|
|
1692
|
-
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";
|
|
1693
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
|
+
}
|
|
1694
2311
|
if (!isAuthenticated()) {
|
|
1695
2312
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1696
2313
|
process.exit(1);
|
|
1697
2314
|
}
|
|
1698
2315
|
const spinner = createSpinner("Fetching project...");
|
|
1699
2316
|
spinner.start();
|
|
1700
|
-
const project = await
|
|
2317
|
+
const project = await resolveProjectIdentifier(identifier);
|
|
1701
2318
|
spinner.stop();
|
|
1702
|
-
|
|
1703
|
-
|
|
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)));
|
|
1704
2325
|
console.log(`Name: ${project.name}`);
|
|
1705
2326
|
console.log(`ID: ${project.id}`);
|
|
1706
2327
|
console.log(`Slug: ${project.slug}`);
|
|
@@ -1710,28 +2331,40 @@ projectCommands.command("get <id>").description("Get project details").action(as
|
|
|
1710
2331
|
console.log(`Created: ${new Date(project.createdAt).toLocaleString()}`);
|
|
1711
2332
|
console.log(`Updated: ${new Date(project.updatedAt).toLocaleString()}`);
|
|
1712
2333
|
const { data: environments } = await withAuthGuard(
|
|
1713
|
-
() => client.environments.listByProject(id)
|
|
2334
|
+
() => client.environments.listByProject(project.id)
|
|
1714
2335
|
);
|
|
1715
2336
|
if (environments?.length > 0) {
|
|
1716
|
-
console.log(
|
|
2337
|
+
console.log(chalk7.bold("\nEnvironments:"));
|
|
1717
2338
|
for (const env of environments) {
|
|
1718
2339
|
console.log(` \u2022 ${env.name} (v${env.currentVersion || 1})`);
|
|
1719
2340
|
}
|
|
1720
2341
|
}
|
|
1721
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
|
+
}
|
|
1722
2348
|
error("Failed to get project:", err.message);
|
|
1723
2349
|
process.exit(1);
|
|
1724
2350
|
}
|
|
1725
2351
|
});
|
|
1726
|
-
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";
|
|
1727
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
|
+
}
|
|
1728
2361
|
if (!isAuthenticated()) {
|
|
1729
2362
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1730
2363
|
process.exit(1);
|
|
1731
2364
|
}
|
|
1732
|
-
const project = await
|
|
2365
|
+
const project = await resolveProjectIdentifier(identifier);
|
|
1733
2366
|
if (!options.force) {
|
|
1734
|
-
const { confirm } = await
|
|
2367
|
+
const { confirm } = await inquirer5.prompt([
|
|
1735
2368
|
{
|
|
1736
2369
|
type: "input",
|
|
1737
2370
|
name: "confirm",
|
|
@@ -1746,62 +2379,329 @@ projectCommands.command("delete <id>").description("Delete a project").option("-
|
|
|
1746
2379
|
}
|
|
1747
2380
|
const spinner = createSpinner("Deleting project...");
|
|
1748
2381
|
spinner.start();
|
|
1749
|
-
await withAuthGuard(() => client.projects.delete(id));
|
|
2382
|
+
await withAuthGuard(() => client.projects.delete(project.id));
|
|
1750
2383
|
spinner.succeed(`Project "${project.name}" deleted`);
|
|
1751
2384
|
const config = await loadProjectConfig();
|
|
1752
|
-
if (config && config.project.id === id) {
|
|
2385
|
+
if (config && config.project.id === project.id) {
|
|
1753
2386
|
config.project.id = "";
|
|
1754
2387
|
await saveProjectConfig(config);
|
|
1755
|
-
info("Cleared project ID from .
|
|
2388
|
+
info("Cleared project ID from .syncenvrc");
|
|
1756
2389
|
}
|
|
1757
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
|
+
}
|
|
1758
2396
|
error("Failed to delete project:", err.message);
|
|
1759
2397
|
process.exit(1);
|
|
1760
2398
|
}
|
|
1761
2399
|
});
|
|
1762
|
-
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";
|
|
1763
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
|
+
}
|
|
1764
2408
|
if (!isAuthenticated()) {
|
|
1765
2409
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1766
2410
|
process.exit(1);
|
|
1767
2411
|
}
|
|
1768
|
-
const project = await
|
|
1769
|
-
|
|
1770
|
-
|
|
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
|
+
}
|
|
1771
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
|
+
}
|
|
1772
2453
|
error("Failed to set default project:", err.message);
|
|
1773
2454
|
process.exit(1);
|
|
1774
2455
|
}
|
|
1775
2456
|
});
|
|
1776
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
|
+
|
|
1777
2654
|
// src/commands/user-keys.ts
|
|
1778
|
-
import
|
|
1779
|
-
import { Command as
|
|
1780
|
-
import
|
|
1781
|
-
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(
|
|
1782
2659
|
"User encryption key management commands"
|
|
1783
2660
|
);
|
|
1784
|
-
async function hasUserKeys() {
|
|
2661
|
+
async function hasUserKeys(debug = false) {
|
|
1785
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
|
+
}
|
|
1786
2667
|
await withAuthGuard(() => client.userKeys.get());
|
|
2668
|
+
if (debug) {
|
|
2669
|
+
console.log(chalk9.dim("[DEBUG] User keys found on server"));
|
|
2670
|
+
}
|
|
1787
2671
|
return true;
|
|
1788
2672
|
} catch (err) {
|
|
1789
|
-
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
|
+
}
|
|
1790
2681
|
return false;
|
|
1791
2682
|
}
|
|
1792
2683
|
throw err;
|
|
1793
2684
|
}
|
|
1794
2685
|
}
|
|
1795
|
-
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";
|
|
1796
2688
|
try {
|
|
2689
|
+
if (isDebug) {
|
|
2690
|
+
console.log(chalk9.dim("\n[DEBUG] Setup Command"));
|
|
2691
|
+
console.log(chalk9.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
2692
|
+
`));
|
|
2693
|
+
}
|
|
1797
2694
|
if (!isAuthenticated()) {
|
|
1798
2695
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1799
2696
|
process.exit(1);
|
|
1800
2697
|
}
|
|
1801
|
-
|
|
2698
|
+
if (isDebug) {
|
|
2699
|
+
console.log(chalk9.dim("[DEBUG] Checking if user keys already exist..."));
|
|
2700
|
+
}
|
|
2701
|
+
const hasKeys = await hasUserKeys(isDebug);
|
|
1802
2702
|
if (hasKeys) {
|
|
1803
2703
|
warning("You already have encryption keys set up.");
|
|
1804
|
-
const { overwrite } = await
|
|
2704
|
+
const { overwrite } = await inquirer7.prompt([
|
|
1805
2705
|
{
|
|
1806
2706
|
type: "confirm",
|
|
1807
2707
|
name: "overwrite",
|
|
@@ -1814,11 +2714,12 @@ userKeysCommands.command("setup").description("Initialize your encryption keys (
|
|
|
1814
2714
|
return;
|
|
1815
2715
|
}
|
|
1816
2716
|
}
|
|
1817
|
-
|
|
2717
|
+
info("Your login password will be used to encrypt your encryption keys.");
|
|
2718
|
+
const { password } = await inquirer7.prompt([
|
|
1818
2719
|
{
|
|
1819
2720
|
type: "password",
|
|
1820
2721
|
name: "password",
|
|
1821
|
-
message: "
|
|
2722
|
+
message: "Enter your login password:",
|
|
1822
2723
|
mask: "*",
|
|
1823
2724
|
validate: (input) => {
|
|
1824
2725
|
if (input.length < 8) return "Password must be at least 8 characters";
|
|
@@ -1826,11 +2727,11 @@ userKeysCommands.command("setup").description("Initialize your encryption keys (
|
|
|
1826
2727
|
}
|
|
1827
2728
|
}
|
|
1828
2729
|
]);
|
|
1829
|
-
await
|
|
2730
|
+
await inquirer7.prompt([
|
|
1830
2731
|
{
|
|
1831
2732
|
type: "password",
|
|
1832
2733
|
name: "confirmPassword",
|
|
1833
|
-
message: "Confirm
|
|
2734
|
+
message: "Confirm your login password:",
|
|
1834
2735
|
mask: "*",
|
|
1835
2736
|
validate: (input) => {
|
|
1836
2737
|
if (input !== password) return "Passwords do not match";
|
|
@@ -1857,68 +2758,138 @@ userKeysCommands.command("setup").description("Initialize your encryption keys (
|
|
|
1857
2758
|
);
|
|
1858
2759
|
spinner.succeed("Encryption keys set up successfully");
|
|
1859
2760
|
success("Your encryption keys are ready");
|
|
1860
|
-
info("Your keys are encrypted with your password and stored securely");
|
|
1861
|
-
|
|
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");
|
|
1862
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
|
+
}
|
|
1863
2769
|
spinner.fail("Failed to set up encryption keys");
|
|
1864
2770
|
throw err;
|
|
1865
2771
|
}
|
|
1866
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
|
+
}
|
|
1867
2778
|
error("Setup failed:", err.message);
|
|
1868
2779
|
process.exit(1);
|
|
1869
2780
|
}
|
|
1870
2781
|
});
|
|
1871
|
-
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;
|
|
1872
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
|
+
}
|
|
1873
2795
|
if (!isAuthenticated()) {
|
|
1874
2796
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1875
2797
|
process.exit(1);
|
|
1876
2798
|
}
|
|
1877
|
-
|
|
2799
|
+
if (isDebug) {
|
|
2800
|
+
console.log(chalk9.dim("[DEBUG] Checking user keys..."));
|
|
2801
|
+
}
|
|
2802
|
+
const hasKeys = await hasUserKeys(isDebug);
|
|
1878
2803
|
if (!hasKeys) {
|
|
1879
2804
|
error("You do not have encryption keys set up yet.");
|
|
1880
2805
|
info("Run `syncenv user-keys setup` to create your keys.");
|
|
1881
2806
|
process.exit(1);
|
|
1882
2807
|
}
|
|
2808
|
+
if (isDebug) {
|
|
2809
|
+
console.log(chalk9.dim("[DEBUG] Checking if keys already cached..."));
|
|
2810
|
+
}
|
|
1883
2811
|
const cached = await getUserKEK();
|
|
1884
2812
|
if (cached) {
|
|
1885
2813
|
const remaining = Math.round(getKEKTimeRemaining() / 6e4);
|
|
1886
2814
|
info(`Your encryption keys are already unlocked (${remaining} minutes remaining).`);
|
|
1887
2815
|
return;
|
|
1888
2816
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
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([
|
|
1891
2844
|
{
|
|
1892
2845
|
type: "password",
|
|
1893
2846
|
name: "password",
|
|
1894
|
-
message: "Enter your
|
|
2847
|
+
message: "Enter your login password:",
|
|
1895
2848
|
mask: "*"
|
|
1896
2849
|
}
|
|
1897
2850
|
]);
|
|
1898
2851
|
const spinner = createSpinner("Unlocking encryption keys...");
|
|
1899
2852
|
spinner.start();
|
|
1900
2853
|
try {
|
|
1901
|
-
await unlockAndStoreKEK(password, userKeys,
|
|
2854
|
+
await unlockAndStoreKEK(password, userKeys, remember);
|
|
1902
2855
|
spinner.succeed("Encryption keys unlocked");
|
|
1903
2856
|
success("Your keys are now available for this session");
|
|
1904
|
-
if (
|
|
2857
|
+
if (remember) {
|
|
1905
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)");
|
|
1906
2861
|
}
|
|
1907
2862
|
info("Keys will auto-lock after 30 minutes of inactivity");
|
|
1908
2863
|
info("Run `syncenv user-keys lock` to lock immediately");
|
|
1909
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
|
+
}
|
|
1910
2870
|
console.error(`Faild to unlock encryption keys: ${err}`);
|
|
1911
2871
|
spinner.fail("Failed to unlock encryption keys");
|
|
1912
|
-
error("Incorrect password. Please try again.");
|
|
2872
|
+
error("Incorrect login password. Please try again.");
|
|
1913
2873
|
process.exit(1);
|
|
1914
2874
|
}
|
|
1915
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
|
+
}
|
|
1916
2881
|
error("Unlock failed:", err.message);
|
|
1917
2882
|
process.exit(1);
|
|
1918
2883
|
}
|
|
1919
2884
|
});
|
|
1920
|
-
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";
|
|
1921
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
|
+
}
|
|
1922
2893
|
await lockKEK(options.forget);
|
|
1923
2894
|
success("Encryption keys locked");
|
|
1924
2895
|
if (options.forget) {
|
|
@@ -1928,70 +2899,116 @@ userKeysCommands.command("lock").description("Lock (clear) your encryption keys
|
|
|
1928
2899
|
}
|
|
1929
2900
|
info("Run `syncenv user-keys unlock` to unlock again");
|
|
1930
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
|
+
}
|
|
1931
2907
|
error("Lock failed:", err.message);
|
|
1932
2908
|
process.exit(1);
|
|
1933
2909
|
}
|
|
1934
2910
|
});
|
|
1935
|
-
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";
|
|
1936
2913
|
try {
|
|
2914
|
+
if (isDebug) {
|
|
2915
|
+
console.log(chalk9.dim("\n[DEBUG] Status Command"));
|
|
2916
|
+
console.log(chalk9.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
2917
|
+
`));
|
|
2918
|
+
}
|
|
1937
2919
|
if (!isAuthenticated()) {
|
|
1938
2920
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1939
2921
|
process.exit(1);
|
|
1940
2922
|
}
|
|
1941
|
-
console.log(
|
|
1942
|
-
console.log(
|
|
1943
|
-
|
|
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);
|
|
1944
2929
|
if (!hasKeys) {
|
|
1945
|
-
console.log(`Setup: ${
|
|
2930
|
+
console.log(`Setup: ${chalk9.yellow("Not set up")}`);
|
|
1946
2931
|
info("\nRun `syncenv user-keys setup` to create your encryption keys");
|
|
1947
2932
|
return;
|
|
1948
2933
|
}
|
|
1949
|
-
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
|
+
}
|
|
1950
2938
|
const cached = isKEKCached();
|
|
1951
2939
|
if (cached) {
|
|
1952
2940
|
const remaining = Math.round(getKEKTimeRemaining() / 6e4);
|
|
1953
|
-
console.log(`Session: ${
|
|
2941
|
+
console.log(`Session: ${chalk9.green("\u2713 Unlocked")} (${remaining} min remaining)`);
|
|
1954
2942
|
info("\nYour encryption keys are active for this session");
|
|
1955
2943
|
} else {
|
|
1956
|
-
console.log(`Session: ${
|
|
2944
|
+
console.log(`Session: ${chalk9.yellow("\u25CB Locked")}`);
|
|
1957
2945
|
info("\nRun `syncenv user-keys unlock` to unlock your keys");
|
|
1958
2946
|
}
|
|
2947
|
+
if (isDebug) {
|
|
2948
|
+
console.log(chalk9.dim("[DEBUG] Fetching user keys from server..."));
|
|
2949
|
+
}
|
|
1959
2950
|
const userKeys = await withAuthGuard(() => client.userKeys.get());
|
|
1960
2951
|
console.log(`
|
|
1961
2952
|
Key Version: ${userKeys.version || 1}`);
|
|
1962
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
|
+
}
|
|
1963
2960
|
console.log("");
|
|
1964
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
|
+
}
|
|
1965
2967
|
error("Status check failed:", err.message);
|
|
1966
2968
|
process.exit(1);
|
|
1967
2969
|
}
|
|
1968
2970
|
});
|
|
1969
|
-
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";
|
|
1970
2973
|
try {
|
|
2974
|
+
if (isDebug) {
|
|
2975
|
+
console.log(chalk9.dim("\n[DEBUG] Rotate Command"));
|
|
2976
|
+
console.log(chalk9.dim(`[DEBUG] Authenticated: ${isAuthenticated()}
|
|
2977
|
+
`));
|
|
2978
|
+
}
|
|
1971
2979
|
if (!isAuthenticated()) {
|
|
1972
2980
|
error("Not authenticated. Run `syncenv auth login` first.");
|
|
1973
2981
|
process.exit(1);
|
|
1974
2982
|
}
|
|
1975
|
-
|
|
2983
|
+
if (isDebug) {
|
|
2984
|
+
console.log(chalk9.dim("[DEBUG] Checking user keys..."));
|
|
2985
|
+
}
|
|
2986
|
+
const hasKeys = await hasUserKeys(isDebug);
|
|
1976
2987
|
if (!hasKeys) {
|
|
1977
2988
|
error("You do not have encryption keys set up yet.");
|
|
1978
2989
|
info("Run `syncenv user-keys setup` to create your keys.");
|
|
1979
2990
|
process.exit(1);
|
|
1980
2991
|
}
|
|
2992
|
+
if (isDebug) {
|
|
2993
|
+
console.log(chalk9.dim("[DEBUG] Fetching user keys from server..."));
|
|
2994
|
+
}
|
|
1981
2995
|
const userKeys = await withAuthGuard(() => client.userKeys.get());
|
|
1982
|
-
|
|
2996
|
+
if (isDebug) {
|
|
2997
|
+
console.log(chalk9.dim("[DEBUG] User keys retrieved successfully"));
|
|
2998
|
+
}
|
|
2999
|
+
const { oldPassword } = await inquirer7.prompt([
|
|
1983
3000
|
{
|
|
1984
3001
|
type: "password",
|
|
1985
3002
|
name: "oldPassword",
|
|
1986
|
-
message: "Enter your current
|
|
3003
|
+
message: "Enter your current login password:",
|
|
1987
3004
|
mask: "*"
|
|
1988
3005
|
}
|
|
1989
3006
|
]);
|
|
1990
|
-
const { newPassword } = await
|
|
3007
|
+
const { newPassword } = await inquirer7.prompt([
|
|
1991
3008
|
{
|
|
1992
3009
|
type: "password",
|
|
1993
3010
|
name: "newPassword",
|
|
1994
|
-
message: "Enter your new
|
|
3011
|
+
message: "Enter your new login password:",
|
|
1995
3012
|
mask: "*",
|
|
1996
3013
|
validate: (input) => {
|
|
1997
3014
|
if (input.length < 8) return "Password must be at least 8 characters";
|
|
@@ -1999,7 +3016,7 @@ userKeysCommands.command("rotate").description("Re-encrypt your keys with a new
|
|
|
1999
3016
|
}
|
|
2000
3017
|
}
|
|
2001
3018
|
]);
|
|
2002
|
-
await
|
|
3019
|
+
await inquirer7.prompt([
|
|
2003
3020
|
{
|
|
2004
3021
|
type: "password",
|
|
2005
3022
|
name: "confirmPassword",
|
|
@@ -2033,20 +3050,30 @@ userKeysCommands.command("rotate").description("Re-encrypt your keys with a new
|
|
|
2033
3050
|
kekSalt
|
|
2034
3051
|
})
|
|
2035
3052
|
);
|
|
2036
|
-
const { saveEncryptedKEK, saveKEKPassword, getKEKPassword } = await import("./secure-storage-
|
|
3053
|
+
const { saveEncryptedKEK, saveKEKPassword, getKEKPassword } = await import("./secure-storage-AR7HZFTA.js");
|
|
2037
3054
|
saveEncryptedKEK({ encryptedUserKek, kekIv, kekSalt });
|
|
2038
3055
|
const existingPassword = await getKEKPassword();
|
|
2039
3056
|
if (existingPassword) {
|
|
2040
3057
|
await saveKEKPassword(newPassword);
|
|
2041
3058
|
}
|
|
2042
3059
|
spinner.succeed("Keys re-encrypted successfully");
|
|
2043
|
-
success("Your encryption
|
|
2044
|
-
|
|
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");
|
|
2045
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
|
+
}
|
|
2046
3068
|
spinner.fail("Failed to re-encrypt keys");
|
|
2047
3069
|
throw err;
|
|
2048
3070
|
}
|
|
2049
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
|
+
}
|
|
2050
3077
|
error("Rotation failed:", err.message);
|
|
2051
3078
|
process.exit(1);
|
|
2052
3079
|
}
|
|
@@ -2062,9 +3089,13 @@ try {
|
|
|
2062
3089
|
version = process.env.SYNCENV_VERSION || "0.0.0";
|
|
2063
3090
|
}
|
|
2064
3091
|
async function main() {
|
|
2065
|
-
const program = new
|
|
3092
|
+
const program = new Command9();
|
|
2066
3093
|
await loadConfig();
|
|
2067
|
-
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) => {
|
|
2068
3099
|
const opts = thisCommand.opts();
|
|
2069
3100
|
if (opts.verbose) {
|
|
2070
3101
|
process.env.SYNCENV_VERBOSE = "true";
|
|
@@ -2073,11 +3104,13 @@ async function main() {
|
|
|
2073
3104
|
process.env.SYNCENV_API_URL = opts.apiUrl;
|
|
2074
3105
|
}
|
|
2075
3106
|
});
|
|
3107
|
+
program.addCommand(setupCommand);
|
|
2076
3108
|
program.addCommand(authCommands);
|
|
2077
3109
|
program.addCommand(projectCommands);
|
|
2078
3110
|
program.addCommand(envCommands);
|
|
2079
3111
|
program.addCommand(userKeysCommands);
|
|
2080
3112
|
program.addCommand(initCommand);
|
|
3113
|
+
program.addCommand(configCommands);
|
|
2081
3114
|
program.addCommand(doctorCommand);
|
|
2082
3115
|
program.exitOverride();
|
|
2083
3116
|
try {
|
|
@@ -2092,7 +3125,7 @@ async function main() {
|
|
|
2092
3125
|
if (error2.code === "commander.helpDisplayed") {
|
|
2093
3126
|
process.exit(0);
|
|
2094
3127
|
}
|
|
2095
|
-
console.error(
|
|
3128
|
+
console.error(chalk10.red("\nError:"), error2.message || error2);
|
|
2096
3129
|
if (process.env.SYNCENV_VERBOSE) {
|
|
2097
3130
|
console.error(error2.stack);
|
|
2098
3131
|
}
|