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.
- package/dist/index.js +169 -46
- 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.
|
|
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
|
-
|
|
29423
|
-
|
|
29424
|
-
|
|
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
|
|
29428
|
-
|
|
29429
|
-
|
|
29430
|
-
|
|
29431
|
-
|
|
29432
|
-
|
|
29433
|
-
|
|
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
|
-
|
|
29436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29524
|
+
saveSpinner.text = "Installing Claude Code hooks...";
|
|
29454
29525
|
await installHooks(getClaudeDir());
|
|
29455
|
-
|
|
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
|
-
|
|
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
|
|
29483
|
-
import { join as
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
29786
|
+
import { join as join10 } from "path";
|
|
29716
29787
|
async function getMasterKey2(configDir) {
|
|
29717
|
-
const cachedKeyPath =
|
|
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:
|
|
29734
|
-
await
|
|
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
|
});
|