ccsini 0.1.25 → 0.1.26

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.
Files changed (2) hide show
  1. package/dist/index.js +108 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -27996,7 +27996,7 @@ var {
27996
27996
  } = import__.default;
27997
27997
 
27998
27998
  // src/version.ts
27999
- var VERSION = "0.1.25";
27999
+ var VERSION = "0.1.26";
28000
28000
 
28001
28001
  // src/commands/init.ts
28002
28002
  init_source();
@@ -29360,6 +29360,8 @@ function chunkArray(arr, size) {
29360
29360
  }
29361
29361
 
29362
29362
  // src/commands/init.ts
29363
+ import { writeFile as writeFile6 } from "fs/promises";
29364
+ import { join as join7 } from "path";
29363
29365
  function registerInitCommand(program2) {
29364
29366
  program2.command("init").description("Connect this device to your ccsini account").option("--token <token>", "Setup token from dashboard").action(async (opts) => {
29365
29367
  const configDir = getConfigDir();
@@ -29390,24 +29392,6 @@ function registerInitCommand(program2) {
29390
29392
  ]);
29391
29393
  token = answer.token;
29392
29394
  }
29393
- const { password } = await dist_default12.prompt([
29394
- {
29395
- type: "password",
29396
- name: "password",
29397
- message: "Set your encryption password (encrypts data locally):",
29398
- mask: "*",
29399
- validate: (v) => v.length >= 8 || "Password must be at least 8 characters"
29400
- }
29401
- ]);
29402
- const { confirmPassword } = await dist_default12.prompt([
29403
- {
29404
- type: "password",
29405
- name: "confirmPassword",
29406
- message: "Confirm encryption password:",
29407
- mask: "*",
29408
- validate: (v) => v === password || "Passwords do not match"
29409
- }
29410
- ]);
29411
29395
  const { deviceName } = await dist_default12.prompt([
29412
29396
  {
29413
29397
  type: "input",
@@ -29417,30 +29401,105 @@ function registerInitCommand(program2) {
29417
29401
  }
29418
29402
  ]);
29419
29403
  const spinner = ora("Setting up...").start();
29404
+ let keypair;
29405
+ let publicKeyB64;
29406
+ let privateKeyB64;
29407
+ let deviceId;
29408
+ let existingSalt;
29420
29409
  try {
29421
29410
  spinner.text = "Generating device keypair...";
29422
- const keypair = await generateDeviceKeypair();
29423
- const publicKeyB64 = await exportPublicKey(keypair.publicKey);
29424
- const privateKeyB64 = await exportPrivateKey(keypair.privateKey);
29411
+ keypair = await generateDeviceKeypair();
29412
+ publicKeyB64 = await exportPublicKey(keypair.publicKey);
29413
+ privateKeyB64 = await exportPrivateKey(keypair.privateKey);
29425
29414
  spinner.text = "Registering device...";
29426
29415
  const client = new CcsiniClient("https://ccsini-api.anis-maisara190.workers.dev", "");
29427
- const { deviceId, salt: existingSalt } = await client.registerDevice(token, deviceName, publicKeyB64, detectPlatform());
29428
- spinner.text = "Setting up encryption...";
29429
- let salt;
29430
- if (existingSalt) {
29431
- salt = new Uint8Array(Buffer.from(existingSalt, "hex"));
29432
- } else {
29433
- salt = crypto.getRandomValues(new Uint8Array(32));
29416
+ const result = await client.registerDevice(token, deviceName, publicKeyB64, detectPlatform());
29417
+ deviceId = result.deviceId;
29418
+ existingSalt = result.salt;
29419
+ spinner.stop();
29420
+ } catch (e) {
29421
+ spinner.fail(e.message);
29422
+ process.exit(1);
29423
+ }
29424
+ const isMultiDevice = !!existingSalt;
29425
+ let masterKey;
29426
+ let salt;
29427
+ if (isMultiDevice) {
29428
+ salt = new Uint8Array(Buffer.from(existingSalt, "hex"));
29429
+ const MAX_ATTEMPTS = 3;
29430
+ for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
29431
+ const { password } = await dist_default12.prompt([
29432
+ {
29433
+ type: "password",
29434
+ name: "password",
29435
+ message: "Enter your encryption password (same as your first device):",
29436
+ mask: "*",
29437
+ validate: (v) => v.length >= 8 || "Password must be at least 8 characters"
29438
+ }
29439
+ ]);
29440
+ const derivingSpinner = ora("Deriving encryption key...").start();
29441
+ masterKey = await deriveKeyFromPassphrase(password, salt);
29442
+ try {
29443
+ const { importPrivateKey: importPrivateKey2, createDeviceJWT: createDeviceJWT2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
29444
+ const privateKey = await importPrivateKey2(privateKeyB64);
29445
+ const jwt = await createDeviceJWT2(privateKey, deviceId);
29446
+ const authedClient = new CcsiniClient("https://ccsini-api.anis-maisara190.workers.dev", jwt);
29447
+ derivingSpinner.text = "Validating password...";
29448
+ const manifestEnc = await authedClient.getManifest();
29449
+ if (manifestEnc) {
29450
+ decryptFile(masterKey, "__manifest__", manifestEnc);
29451
+ }
29452
+ derivingSpinner.stop();
29453
+ break;
29454
+ } catch {
29455
+ derivingSpinner.stop();
29456
+ if (attempt < MAX_ATTEMPTS) {
29457
+ console.log(source_default.red(` Password doesn't match your first device. ${MAX_ATTEMPTS - attempt} attempt(s) remaining.`));
29458
+ } else {
29459
+ console.log(source_default.red(`
29460
+ Too many failed attempts. Please verify your password and try again.`));
29461
+ process.exit(1);
29462
+ }
29463
+ }
29434
29464
  }
29435
- await deriveKeyFromPassphrase(password, salt);
29436
- if (!existingSalt) {
29465
+ } else {
29466
+ const { password } = await dist_default12.prompt([
29467
+ {
29468
+ type: "password",
29469
+ name: "password",
29470
+ message: "Set your encryption password (encrypts data locally):",
29471
+ mask: "*",
29472
+ validate: (v) => v.length >= 8 || "Password must be at least 8 characters"
29473
+ }
29474
+ ]);
29475
+ await dist_default12.prompt([
29476
+ {
29477
+ type: "password",
29478
+ name: "confirmPassword",
29479
+ message: "Confirm encryption password:",
29480
+ mask: "*",
29481
+ validate: (v) => v === password || "Passwords do not match"
29482
+ }
29483
+ ]);
29484
+ salt = crypto.getRandomValues(new Uint8Array(32));
29485
+ const derivingSpinner = ora("Deriving encryption key...").start();
29486
+ masterKey = await deriveKeyFromPassphrase(password, salt);
29487
+ derivingSpinner.stop();
29488
+ const uploadSpinner = ora("Uploading encryption salt...").start();
29489
+ try {
29437
29490
  const { importPrivateKey: importPrivateKey2, createDeviceJWT: createDeviceJWT2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
29438
29491
  const privateKey = await importPrivateKey2(privateKeyB64);
29439
29492
  const jwt = await createDeviceJWT2(privateKey, deviceId);
29440
29493
  const authedClient = new CcsiniClient("https://ccsini-api.anis-maisara190.workers.dev", jwt);
29441
29494
  await authedClient.putSalt(Buffer.from(salt).toString("hex"));
29495
+ uploadSpinner.stop();
29496
+ } catch (e) {
29497
+ uploadSpinner.fail(e.message);
29498
+ process.exit(1);
29442
29499
  }
29443
- spinner.text = "Saving configuration...";
29500
+ }
29501
+ const saveSpinner = ora("Saving configuration...").start();
29502
+ try {
29444
29503
  await saveKeys(configDir, {
29445
29504
  salt,
29446
29505
  devicePrivateKey: privateKeyB64,
@@ -29449,10 +29508,13 @@ function registerInitCommand(program2) {
29449
29508
  deviceName,
29450
29509
  apiUrl: "https://ccsini-api.anis-maisara190.workers.dev"
29451
29510
  });
29511
+ await writeFile6(join7(configDir, ".cached-key"), masterKey, {
29512
+ mode: 384
29513
+ });
29452
29514
  await saveDefaultSchema(configDir);
29453
- spinner.text = "Installing Claude Code hooks...";
29515
+ saveSpinner.text = "Installing Claude Code hooks...";
29454
29516
  await installHooks(getClaudeDir());
29455
- spinner.succeed("Setup complete!");
29517
+ saveSpinner.succeed("Setup complete!");
29456
29518
  console.log(source_default.green(`
29457
29519
  \u2713 Encryption keys generated`));
29458
29520
  console.log(source_default.green(" \u2713 Device registered"));
@@ -29462,7 +29524,7 @@ function registerInitCommand(program2) {
29462
29524
  Just use 'claude' as normal. Sync happens automatically.
29463
29525
  `));
29464
29526
  } catch (e) {
29465
- spinner.fail(e.message);
29527
+ saveSpinner.fail(e.message);
29466
29528
  process.exit(1);
29467
29529
  }
29468
29530
  });
@@ -29479,10 +29541,10 @@ function detectPlatform() {
29479
29541
  // src/commands/auto.ts
29480
29542
  init_auth();
29481
29543
  init_auth();
29482
- import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
29483
- import { join as join7 } from "path";
29544
+ import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
29545
+ import { join as join8 } from "path";
29484
29546
  async function getMasterKey(configDir) {
29485
- const cachedKeyPath = join7(configDir, ".cached-key");
29547
+ const cachedKeyPath = join8(configDir, ".cached-key");
29486
29548
  try {
29487
29549
  const cached = await readFile7(cachedKeyPath);
29488
29550
  return new Uint8Array(cached);
@@ -29542,7 +29604,7 @@ function registerAutoCommands(program2) {
29542
29604
  ]);
29543
29605
  const config = await loadKeys(configDir);
29544
29606
  const masterKey = await deriveKeyFromPassphrase(password, config.salt);
29545
- await writeFile6(join7(configDir, ".cached-key"), masterKey, {
29607
+ await writeFile7(join8(configDir, ".cached-key"), masterKey, {
29546
29608
  mode: 384
29547
29609
  });
29548
29610
  console.log("Unlocked. Auto-sync is now active.");
@@ -29551,7 +29613,7 @@ function registerAutoCommands(program2) {
29551
29613
  const configDir = getConfigDir();
29552
29614
  const { rm } = await import("fs/promises");
29553
29615
  try {
29554
- await rm(join7(configDir, ".cached-key"));
29616
+ await rm(join8(configDir, ".cached-key"));
29555
29617
  console.log("Locked. Auto-sync paused until next unlock.");
29556
29618
  } catch {
29557
29619
  console.log("Already locked.");
@@ -29562,7 +29624,7 @@ function registerAutoCommands(program2) {
29562
29624
  // src/commands/doctor.ts
29563
29625
  init_source();
29564
29626
  import { access as access2 } from "fs/promises";
29565
- import { join as join8 } from "path";
29627
+ import { join as join9 } from "path";
29566
29628
  function registerDoctorCommand(program2) {
29567
29629
  program2.command("doctor").description("Diagnose ccsini setup and connectivity").action(async () => {
29568
29630
  const configDir = getConfigDir();
@@ -29604,7 +29666,7 @@ function registerDoctorCommand(program2) {
29604
29666
  allGood = false;
29605
29667
  }
29606
29668
  try {
29607
- await access2(join8(configDir, ".cached-key"));
29669
+ await access2(join9(configDir, ".cached-key"));
29608
29670
  console.log(source_default.green(" \u2713 Auto-sync unlocked"));
29609
29671
  } catch {
29610
29672
  console.log(source_default.yellow(" \u26A0 Auto-sync locked (run 'ccsini unlock')"));
@@ -29712,9 +29774,9 @@ Uninstall failed. Try manually:`);
29712
29774
  // src/commands/sync.ts
29713
29775
  init_auth();
29714
29776
  import { readFile as readFile8 } from "fs/promises";
29715
- import { join as join9 } from "path";
29777
+ import { join as join10 } from "path";
29716
29778
  async function getMasterKey2(configDir) {
29717
- const cachedKeyPath = join9(configDir, ".cached-key");
29779
+ const cachedKeyPath = join10(configDir, ".cached-key");
29718
29780
  try {
29719
29781
  const cached = await readFile8(cachedKeyPath);
29720
29782
  return new Uint8Array(cached);
@@ -29730,8 +29792,8 @@ async function getMasterKey2(configDir) {
29730
29792
  ]);
29731
29793
  const config = await loadKeys(configDir);
29732
29794
  const masterKey = await deriveKeyFromPassphrase(password, config.salt);
29733
- const { writeFile: writeFile7 } = await import("fs/promises");
29734
- await writeFile7(cachedKeyPath, masterKey, { mode: 384 });
29795
+ const { writeFile: writeFile8 } = await import("fs/promises");
29796
+ await writeFile8(cachedKeyPath, masterKey, { mode: 384 });
29735
29797
  return masterKey;
29736
29798
  }
29737
29799
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccsini",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Claude Code seamless sync across devices",
5
5
  "type": "module",
6
6
  "bin": {