ccsini 0.1.25 → 0.1.27

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 +169 -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.27";
28000
28000
 
28001
28001
  // src/commands/init.ts
28002
28002
  init_source();
@@ -29001,6 +29001,15 @@ class CcsiniClient {
29001
29001
  throw new Error("Failed to delete blobs");
29002
29002
  return res.json();
29003
29003
  }
29004
+ async resetAll() {
29005
+ const res = await fetch(`${this.apiUrl}/api/sync/reset`, {
29006
+ method: "DELETE",
29007
+ headers: this.getHeaders()
29008
+ });
29009
+ if (!res.ok)
29010
+ throw new Error("Failed to reset account data");
29011
+ return res.json();
29012
+ }
29004
29013
  async logSyncEvent(event) {
29005
29014
  await fetch(`${this.apiUrl}/api/sync/log`, {
29006
29015
  method: "POST",
@@ -29360,6 +29369,8 @@ function chunkArray(arr, size) {
29360
29369
  }
29361
29370
 
29362
29371
  // src/commands/init.ts
29372
+ import { writeFile as writeFile6 } from "fs/promises";
29373
+ import { join as join7 } from "path";
29363
29374
  function registerInitCommand(program2) {
29364
29375
  program2.command("init").description("Connect this device to your ccsini account").option("--token <token>", "Setup token from dashboard").action(async (opts) => {
29365
29376
  const configDir = getConfigDir();
@@ -29390,24 +29401,6 @@ function registerInitCommand(program2) {
29390
29401
  ]);
29391
29402
  token = answer.token;
29392
29403
  }
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
29404
  const { deviceName } = await dist_default12.prompt([
29412
29405
  {
29413
29406
  type: "input",
@@ -29417,30 +29410,105 @@ function registerInitCommand(program2) {
29417
29410
  }
29418
29411
  ]);
29419
29412
  const spinner = ora("Setting up...").start();
29413
+ let keypair;
29414
+ let publicKeyB64;
29415
+ let privateKeyB64;
29416
+ let deviceId;
29417
+ let existingSalt;
29420
29418
  try {
29421
29419
  spinner.text = "Generating device keypair...";
29422
- const keypair = await generateDeviceKeypair();
29423
- const publicKeyB64 = await exportPublicKey(keypair.publicKey);
29424
- const privateKeyB64 = await exportPrivateKey(keypair.privateKey);
29420
+ keypair = await generateDeviceKeypair();
29421
+ publicKeyB64 = await exportPublicKey(keypair.publicKey);
29422
+ privateKeyB64 = await exportPrivateKey(keypair.privateKey);
29425
29423
  spinner.text = "Registering device...";
29426
29424
  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));
29425
+ const result = await client.registerDevice(token, deviceName, publicKeyB64, detectPlatform());
29426
+ deviceId = result.deviceId;
29427
+ existingSalt = result.salt;
29428
+ spinner.stop();
29429
+ } catch (e) {
29430
+ spinner.fail(e.message);
29431
+ process.exit(1);
29432
+ }
29433
+ const isMultiDevice = !!existingSalt;
29434
+ let masterKey;
29435
+ let salt;
29436
+ if (isMultiDevice) {
29437
+ salt = new Uint8Array(Buffer.from(existingSalt, "hex"));
29438
+ const MAX_ATTEMPTS = 3;
29439
+ for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
29440
+ const { password } = await dist_default12.prompt([
29441
+ {
29442
+ type: "password",
29443
+ name: "password",
29444
+ message: "Enter your encryption password (same as your first device):",
29445
+ mask: "*",
29446
+ validate: (v) => v.length >= 8 || "Password must be at least 8 characters"
29447
+ }
29448
+ ]);
29449
+ const derivingSpinner = ora("Deriving encryption key...").start();
29450
+ masterKey = await deriveKeyFromPassphrase(password, salt);
29451
+ try {
29452
+ const { importPrivateKey: importPrivateKey2, createDeviceJWT: createDeviceJWT2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
29453
+ const privateKey = await importPrivateKey2(privateKeyB64);
29454
+ const jwt = await createDeviceJWT2(privateKey, deviceId);
29455
+ const authedClient = new CcsiniClient("https://ccsini-api.anis-maisara190.workers.dev", jwt);
29456
+ derivingSpinner.text = "Validating password...";
29457
+ const manifestEnc = await authedClient.getManifest();
29458
+ if (manifestEnc) {
29459
+ decryptFile(masterKey, "__manifest__", manifestEnc);
29460
+ }
29461
+ derivingSpinner.stop();
29462
+ break;
29463
+ } catch {
29464
+ derivingSpinner.stop();
29465
+ if (attempt < MAX_ATTEMPTS) {
29466
+ console.log(source_default.red(` Password doesn't match your first device. ${MAX_ATTEMPTS - attempt} attempt(s) remaining.`));
29467
+ } else {
29468
+ console.log(source_default.red(`
29469
+ Too many failed attempts. Please verify your password and try again.`));
29470
+ process.exit(1);
29471
+ }
29472
+ }
29434
29473
  }
29435
- await deriveKeyFromPassphrase(password, salt);
29436
- if (!existingSalt) {
29474
+ } else {
29475
+ const { password } = await dist_default12.prompt([
29476
+ {
29477
+ type: "password",
29478
+ name: "password",
29479
+ message: "Set your encryption password (encrypts data locally):",
29480
+ mask: "*",
29481
+ validate: (v) => v.length >= 8 || "Password must be at least 8 characters"
29482
+ }
29483
+ ]);
29484
+ await dist_default12.prompt([
29485
+ {
29486
+ type: "password",
29487
+ name: "confirmPassword",
29488
+ message: "Confirm encryption password:",
29489
+ mask: "*",
29490
+ validate: (v) => v === password || "Passwords do not match"
29491
+ }
29492
+ ]);
29493
+ salt = crypto.getRandomValues(new Uint8Array(32));
29494
+ const derivingSpinner = ora("Deriving encryption key...").start();
29495
+ masterKey = await deriveKeyFromPassphrase(password, salt);
29496
+ derivingSpinner.stop();
29497
+ const uploadSpinner = ora("Uploading encryption salt...").start();
29498
+ try {
29437
29499
  const { importPrivateKey: importPrivateKey2, createDeviceJWT: createDeviceJWT2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
29438
29500
  const privateKey = await importPrivateKey2(privateKeyB64);
29439
29501
  const jwt = await createDeviceJWT2(privateKey, deviceId);
29440
29502
  const authedClient = new CcsiniClient("https://ccsini-api.anis-maisara190.workers.dev", jwt);
29441
29503
  await authedClient.putSalt(Buffer.from(salt).toString("hex"));
29504
+ uploadSpinner.stop();
29505
+ } catch (e) {
29506
+ uploadSpinner.fail(e.message);
29507
+ process.exit(1);
29442
29508
  }
29443
- spinner.text = "Saving configuration...";
29509
+ }
29510
+ const saveSpinner = ora("Saving configuration...").start();
29511
+ try {
29444
29512
  await saveKeys(configDir, {
29445
29513
  salt,
29446
29514
  devicePrivateKey: privateKeyB64,
@@ -29449,10 +29517,13 @@ function registerInitCommand(program2) {
29449
29517
  deviceName,
29450
29518
  apiUrl: "https://ccsini-api.anis-maisara190.workers.dev"
29451
29519
  });
29520
+ await writeFile6(join7(configDir, ".cached-key"), masterKey, {
29521
+ mode: 384
29522
+ });
29452
29523
  await saveDefaultSchema(configDir);
29453
- spinner.text = "Installing Claude Code hooks...";
29524
+ saveSpinner.text = "Installing Claude Code hooks...";
29454
29525
  await installHooks(getClaudeDir());
29455
- spinner.succeed("Setup complete!");
29526
+ saveSpinner.succeed("Setup complete!");
29456
29527
  console.log(source_default.green(`
29457
29528
  \u2713 Encryption keys generated`));
29458
29529
  console.log(source_default.green(" \u2713 Device registered"));
@@ -29462,7 +29533,7 @@ function registerInitCommand(program2) {
29462
29533
  Just use 'claude' as normal. Sync happens automatically.
29463
29534
  `));
29464
29535
  } catch (e) {
29465
- spinner.fail(e.message);
29536
+ saveSpinner.fail(e.message);
29466
29537
  process.exit(1);
29467
29538
  }
29468
29539
  });
@@ -29479,10 +29550,10 @@ function detectPlatform() {
29479
29550
  // src/commands/auto.ts
29480
29551
  init_auth();
29481
29552
  init_auth();
29482
- import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
29483
- import { join as join7 } from "path";
29553
+ import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
29554
+ import { join as join8 } from "path";
29484
29555
  async function getMasterKey(configDir) {
29485
- const cachedKeyPath = join7(configDir, ".cached-key");
29556
+ const cachedKeyPath = join8(configDir, ".cached-key");
29486
29557
  try {
29487
29558
  const cached = await readFile7(cachedKeyPath);
29488
29559
  return new Uint8Array(cached);
@@ -29542,7 +29613,7 @@ function registerAutoCommands(program2) {
29542
29613
  ]);
29543
29614
  const config = await loadKeys(configDir);
29544
29615
  const masterKey = await deriveKeyFromPassphrase(password, config.salt);
29545
- await writeFile6(join7(configDir, ".cached-key"), masterKey, {
29616
+ await writeFile7(join8(configDir, ".cached-key"), masterKey, {
29546
29617
  mode: 384
29547
29618
  });
29548
29619
  console.log("Unlocked. Auto-sync is now active.");
@@ -29551,7 +29622,7 @@ function registerAutoCommands(program2) {
29551
29622
  const configDir = getConfigDir();
29552
29623
  const { rm } = await import("fs/promises");
29553
29624
  try {
29554
- await rm(join7(configDir, ".cached-key"));
29625
+ await rm(join8(configDir, ".cached-key"));
29555
29626
  console.log("Locked. Auto-sync paused until next unlock.");
29556
29627
  } catch {
29557
29628
  console.log("Already locked.");
@@ -29562,7 +29633,7 @@ function registerAutoCommands(program2) {
29562
29633
  // src/commands/doctor.ts
29563
29634
  init_source();
29564
29635
  import { access as access2 } from "fs/promises";
29565
- import { join as join8 } from "path";
29636
+ import { join as join9 } from "path";
29566
29637
  function registerDoctorCommand(program2) {
29567
29638
  program2.command("doctor").description("Diagnose ccsini setup and connectivity").action(async () => {
29568
29639
  const configDir = getConfigDir();
@@ -29604,7 +29675,7 @@ function registerDoctorCommand(program2) {
29604
29675
  allGood = false;
29605
29676
  }
29606
29677
  try {
29607
- await access2(join8(configDir, ".cached-key"));
29678
+ await access2(join9(configDir, ".cached-key"));
29608
29679
  console.log(source_default.green(" \u2713 Auto-sync unlocked"));
29609
29680
  } catch {
29610
29681
  console.log(source_default.yellow(" \u26A0 Auto-sync locked (run 'ccsini unlock')"));
@@ -29712,9 +29783,9 @@ Uninstall failed. Try manually:`);
29712
29783
  // src/commands/sync.ts
29713
29784
  init_auth();
29714
29785
  import { readFile as readFile8 } from "fs/promises";
29715
- import { join as join9 } from "path";
29786
+ import { join as join10 } from "path";
29716
29787
  async function getMasterKey2(configDir) {
29717
- const cachedKeyPath = join9(configDir, ".cached-key");
29788
+ const cachedKeyPath = join10(configDir, ".cached-key");
29718
29789
  try {
29719
29790
  const cached = await readFile8(cachedKeyPath);
29720
29791
  return new Uint8Array(cached);
@@ -29730,8 +29801,8 @@ async function getMasterKey2(configDir) {
29730
29801
  ]);
29731
29802
  const config = await loadKeys(configDir);
29732
29803
  const masterKey = await deriveKeyFromPassphrase(password, config.salt);
29733
- const { writeFile: writeFile7 } = await import("fs/promises");
29734
- await writeFile7(cachedKeyPath, masterKey, { mode: 384 });
29804
+ const { writeFile: writeFile8 } = await import("fs/promises");
29805
+ await writeFile8(cachedKeyPath, masterKey, { mode: 384 });
29735
29806
  return masterKey;
29736
29807
  }
29737
29808
  }
@@ -29967,6 +30038,57 @@ function registerHooksCommands(program2) {
29967
30038
  });
29968
30039
  }
29969
30040
 
30041
+ // src/commands/reset.ts
30042
+ init_auth();
30043
+ import { rm } from "fs/promises";
30044
+ function registerResetCommand(program2) {
30045
+ program2.command("reset").description("Wipe all server data and local config (full account reset)").action(async () => {
30046
+ const configDir = getConfigDir();
30047
+ if (!await configExists(configDir)) {
30048
+ console.error("Not initialized. Nothing to reset.");
30049
+ process.exit(1);
30050
+ }
30051
+ const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
30052
+ const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
30053
+ console.log(chalk2.red.bold(`
30054
+ WARNING: This will permanently delete:`));
30055
+ console.log(chalk2.red(" - All encrypted files on the server"));
30056
+ console.log(chalk2.red(" - Your encryption salt"));
30057
+ console.log(chalk2.red(` - Your local config (~/.ccsini/)
30058
+ `));
30059
+ const { confirm } = await inquirer2.default.prompt([
30060
+ {
30061
+ type: "confirm",
30062
+ name: "confirm",
30063
+ message: "Are you sure you want to wipe everything?",
30064
+ default: false
30065
+ }
30066
+ ]);
30067
+ if (!confirm) {
30068
+ console.log("Cancelled.");
30069
+ return;
30070
+ }
30071
+ const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
30072
+ try {
30073
+ const config = await loadKeys(configDir);
30074
+ const privateKey = await importPrivateKey(config.devicePrivateKey);
30075
+ const jwt = await createDeviceJWT(privateKey, config.deviceId);
30076
+ const client = new CcsiniClient(config.apiUrl, jwt);
30077
+ const spinner = ora2("Wiping server data...").start();
30078
+ const { blobsDeleted } = await client.resetAll();
30079
+ spinner.succeed(`Server data wiped (${blobsDeleted} blob${blobsDeleted !== 1 ? "s" : ""} deleted)`);
30080
+ const localSpinner = ora2("Removing local config...").start();
30081
+ await rm(configDir, { recursive: true, force: true });
30082
+ localSpinner.succeed("Local config removed");
30083
+ console.log(chalk2.green(`
30084
+ Account fully reset. Run 'ccsini init --token <token>' to start fresh.`));
30085
+ } catch (e) {
30086
+ console.error(`Reset failed: ${e.message}`);
30087
+ process.exit(1);
30088
+ }
30089
+ });
30090
+ }
30091
+
29970
30092
  // src/index.ts
29971
30093
  var program2 = new Command;
29972
30094
  program2.name("ccsini").description("Claude Code seamless sync across devices").version(VERSION);
@@ -29976,6 +30098,7 @@ registerDoctorCommand(program2);
29976
30098
  registerSelfCommands(program2);
29977
30099
  registerSyncCommands(program2);
29978
30100
  registerHooksCommands(program2);
30101
+ registerResetCommand(program2);
29979
30102
  program2.command("version").description("Show current version").action(() => {
29980
30103
  console.log(VERSION);
29981
30104
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccsini",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Claude Code seamless sync across devices",
5
5
  "type": "module",
6
6
  "bin": {