@claude-sync/cli 0.1.17 → 0.1.18
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/bin/claude-sync.js +670 -158
- package/dist/bin/claude-sync.js.map +1 -1
- package/dist/src/index.js +667 -155
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/claude-sync.js
CHANGED
|
@@ -55,12 +55,12 @@ var require_path_encoding = __commonJS({
|
|
|
55
55
|
init_esm_shims();
|
|
56
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
57
|
exports.ROOT_PATTERN = void 0;
|
|
58
|
-
exports.encodePath =
|
|
58
|
+
exports.encodePath = encodePath5;
|
|
59
59
|
exports.decodeSuffix = decodeSuffix;
|
|
60
60
|
exports.decodeRoot = decodeRoot;
|
|
61
61
|
exports.extractCanonicalPath = extractCanonicalPath;
|
|
62
62
|
exports.ROOT_PATTERN = /^-(home-[^-]+|Users-[^-]+)/;
|
|
63
|
-
function
|
|
63
|
+
function encodePath5(p) {
|
|
64
64
|
return p.replace(/\//g, "-").replace(/\./g, "-");
|
|
65
65
|
}
|
|
66
66
|
function decodeSuffix(suffix, projectPath) {
|
|
@@ -80,7 +80,7 @@ var require_path_encoding = __commonJS({
|
|
|
80
80
|
}
|
|
81
81
|
function extractCanonicalPath(fullPath, homeDir) {
|
|
82
82
|
const relative = fullPath.startsWith(homeDir) ? fullPath.slice(homeDir.length) : fullPath;
|
|
83
|
-
return
|
|
83
|
+
return encodePath5(relative.startsWith("/") ? relative : "/" + relative);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
});
|
|
@@ -4346,7 +4346,7 @@ var require_validation = __commonJS({
|
|
|
4346
4346
|
"use strict";
|
|
4347
4347
|
init_esm_shims();
|
|
4348
4348
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4349
|
-
exports.updateCollaboratorRoleSchema = exports.inviteCollaboratorSchema = exports.editableRoleSchema = exports.projectRoleSchema = exports.createFeatureRequestCommentSchema = exports.updateFeatureRequestStatusSchema = exports.updateFeatureRequestSchema = exports.createFeatureRequestSchema = exports.featureRequestStatusSchema = exports.featureRequestTypeSchema = exports.updateProfileSchema = exports.resetPasswordSchema = exports.forgotPasswordSchema = exports.oauthExchangeSchema = exports.refreshTokenSchema = exports.syncCompleteSchema = exports.downloadUrlSchema = exports.uploadUrlSchema = exports.pullRequestSchema = exports.pushManifestSchema = exports.updateDeviceSchema = exports.createDeviceSchema = exports.registerSchema = exports.loginSchema = void 0;
|
|
4349
|
+
exports.updateCollaboratorRoleSchema = exports.inviteCollaboratorSchema = exports.editableRoleSchema = exports.projectRoleSchema = exports.createFeatureRequestCommentSchema = exports.updateFeatureRequestStatusSchema = exports.updateFeatureRequestSchema = exports.createFeatureRequestSchema = exports.featureRequestStatusSchema = exports.featureRequestTypeSchema = exports.updateProfileSchema = exports.resetPasswordSchema = exports.forgotPasswordSchema = exports.oauthExchangeSchema = exports.refreshTokenSchema = exports.pushBundleCompleteSchema = exports.pushBundlePrepareSchema = exports.manifestEntrySchema = exports.syncCompleteSchema = exports.downloadUrlSchema = exports.uploadUrlSchema = exports.pullRequestSchema = exports.pushManifestSchema = exports.updateDeviceSchema = exports.createDeviceSchema = exports.registerSchema = exports.loginSchema = void 0;
|
|
4350
4350
|
var zod_1 = require_zod();
|
|
4351
4351
|
exports.loginSchema = zod_1.z.object({
|
|
4352
4352
|
email: zod_1.z.string().email(),
|
|
@@ -4405,6 +4405,27 @@ var require_validation = __commonJS({
|
|
|
4405
4405
|
isCompressed: zod_1.z.boolean()
|
|
4406
4406
|
}))
|
|
4407
4407
|
});
|
|
4408
|
+
exports.manifestEntrySchema = zod_1.z.object({
|
|
4409
|
+
path: zod_1.z.string(),
|
|
4410
|
+
hash: zod_1.z.string(),
|
|
4411
|
+
size: zod_1.z.number(),
|
|
4412
|
+
modifiedAt: zod_1.z.string(),
|
|
4413
|
+
isCompressed: zod_1.z.boolean()
|
|
4414
|
+
});
|
|
4415
|
+
exports.pushBundlePrepareSchema = zod_1.z.object({
|
|
4416
|
+
deviceId: zod_1.z.string().uuid(),
|
|
4417
|
+
projectPath: zod_1.z.string().min(1),
|
|
4418
|
+
projectId: zod_1.z.string().uuid().optional(),
|
|
4419
|
+
manifest: zod_1.z.array(exports.manifestEntrySchema),
|
|
4420
|
+
bundleSize: zod_1.z.number().positive(),
|
|
4421
|
+
localVersion: zod_1.z.number().int().min(0),
|
|
4422
|
+
message: zod_1.z.string().max(500).optional()
|
|
4423
|
+
});
|
|
4424
|
+
exports.pushBundleCompleteSchema = zod_1.z.object({
|
|
4425
|
+
syncEventId: zod_1.z.string().uuid(),
|
|
4426
|
+
manifest: zod_1.z.array(exports.manifestEntrySchema),
|
|
4427
|
+
message: zod_1.z.string().min(1).max(500)
|
|
4428
|
+
});
|
|
4408
4429
|
exports.refreshTokenSchema = zod_1.z.object({
|
|
4409
4430
|
refreshToken: zod_1.z.string()
|
|
4410
4431
|
});
|
|
@@ -4703,8 +4724,8 @@ var init_auth = __esm({
|
|
|
4703
4724
|
// src/lib/progress.ts
|
|
4704
4725
|
import ora from "ora";
|
|
4705
4726
|
import chalk from "chalk";
|
|
4706
|
-
function createSpinner(
|
|
4707
|
-
return ora({ text: brandColor(
|
|
4727
|
+
function createSpinner(text5) {
|
|
4728
|
+
return ora({ text: brandColor(text5), spinner: "dots", color: "magenta" });
|
|
4708
4729
|
}
|
|
4709
4730
|
function formatBytes(bytes) {
|
|
4710
4731
|
if (bytes === 0) return "0 B";
|
|
@@ -4782,6 +4803,9 @@ function printSuccess(message) {
|
|
|
4782
4803
|
function printError(message) {
|
|
4783
4804
|
console.log(` ${error("\u2716")} ${message}`);
|
|
4784
4805
|
}
|
|
4806
|
+
function printWarn(message) {
|
|
4807
|
+
console.log(` ${warn("\u25B2")} ${message}`);
|
|
4808
|
+
}
|
|
4785
4809
|
function printInfo(message) {
|
|
4786
4810
|
console.log(` ${brand("\u25CF")} ${message}`);
|
|
4787
4811
|
}
|
|
@@ -5280,19 +5304,23 @@ var init_bundle = __esm({
|
|
|
5280
5304
|
}
|
|
5281
5305
|
});
|
|
5282
5306
|
|
|
5283
|
-
// src/commands/
|
|
5284
|
-
var
|
|
5285
|
-
__export(
|
|
5286
|
-
|
|
5287
|
-
syncCommand: () => syncCommand
|
|
5307
|
+
// src/commands/push.ts
|
|
5308
|
+
var push_exports = {};
|
|
5309
|
+
__export(push_exports, {
|
|
5310
|
+
pushCommand: () => pushCommand
|
|
5288
5311
|
});
|
|
5289
5312
|
import { Command as Command4 } from "commander";
|
|
5290
|
-
import {
|
|
5291
|
-
import { mkdir as mkdir3, lstat as lstat2, symlink, unlink, rename, rm } from "fs/promises";
|
|
5313
|
+
import { text as text3, isCancel as isCancel3 } from "@clack/prompts";
|
|
5292
5314
|
import { join as join4 } from "path";
|
|
5293
5315
|
import { homedir as homedir4, tmpdir } from "os";
|
|
5294
|
-
|
|
5295
|
-
|
|
5316
|
+
import { rm } from "fs/promises";
|
|
5317
|
+
function pushCommand() {
|
|
5318
|
+
return new Command4("push").description("Push local changes to cloud").option("-m, --message <message>", "Commit message").option("--dry-run", "Show what would be pushed without pushing").option("--verbose", "Show detailed output").action(async (options) => {
|
|
5319
|
+
await runPush(options);
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
async function runPush(options) {
|
|
5323
|
+
printIntro("Push");
|
|
5296
5324
|
const config = await loadConfig();
|
|
5297
5325
|
if (!isAuthenticated(config)) {
|
|
5298
5326
|
printError("Not logged in. Run `claude-sync login` first.");
|
|
@@ -5312,6 +5340,582 @@ async function runSync(options) {
|
|
|
5312
5340
|
projectDir = join4(projectsDir, ctx.symlinkTarget);
|
|
5313
5341
|
}
|
|
5314
5342
|
printInfo(`Project: ${brand(cwd)}`);
|
|
5343
|
+
const manifest = await buildProjectManifest(cwd);
|
|
5344
|
+
if (manifest.length === 0) {
|
|
5345
|
+
printInfo("No files to push.");
|
|
5346
|
+
printOutro("Done");
|
|
5347
|
+
return;
|
|
5348
|
+
}
|
|
5349
|
+
const totalBytes = manifest.reduce((sum, f) => sum + f.size, 0);
|
|
5350
|
+
printInfo(`Files: ${brand(String(manifest.length))} ${dim(`(${formatBytes(totalBytes)})`)}`);
|
|
5351
|
+
if (options.dryRun) {
|
|
5352
|
+
console.log();
|
|
5353
|
+
printInfo("Changes to push:");
|
|
5354
|
+
if (options.verbose) {
|
|
5355
|
+
for (const entry of manifest) {
|
|
5356
|
+
console.log(` ${success("+")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
|
|
5357
|
+
}
|
|
5358
|
+
} else {
|
|
5359
|
+
console.log(` ${brand(String(manifest.length))} files would be uploaded`);
|
|
5360
|
+
}
|
|
5361
|
+
printOutro("Dry run complete");
|
|
5362
|
+
return;
|
|
5363
|
+
}
|
|
5364
|
+
if (projectLink?.projectId) {
|
|
5365
|
+
const permission = await checkPushPermission(client, projectLink.projectId);
|
|
5366
|
+
if (!permission.allowed) {
|
|
5367
|
+
printError(`Cannot push: You have ${brand(permission.role)} access to this project.`);
|
|
5368
|
+
printInfo("Only project editors and owners can push changes.");
|
|
5369
|
+
return;
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
const localVersion = projectLink?.localVersion ?? 0;
|
|
5373
|
+
let commitMessage = options.message;
|
|
5374
|
+
if (!commitMessage) {
|
|
5375
|
+
const result = await text3({
|
|
5376
|
+
message: brand("Commit message:"),
|
|
5377
|
+
placeholder: "Describe your changes...",
|
|
5378
|
+
validate: (value) => {
|
|
5379
|
+
if (!value || value.trim().length === 0) {
|
|
5380
|
+
return "Please enter a commit message";
|
|
5381
|
+
}
|
|
5382
|
+
if (value.length > 500) {
|
|
5383
|
+
return "Message too long (max 500 characters)";
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
});
|
|
5387
|
+
if (isCancel3(result)) {
|
|
5388
|
+
printInfo("Push cancelled.");
|
|
5389
|
+
return;
|
|
5390
|
+
}
|
|
5391
|
+
commitMessage = result;
|
|
5392
|
+
}
|
|
5393
|
+
const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5394
|
+
try {
|
|
5395
|
+
const spinner = createSpinner("Preparing...").start();
|
|
5396
|
+
const bundleResult = await createBundle(projectDir, manifest, tempBundle, (bytes, total, phase) => {
|
|
5397
|
+
if (phase === "compressing") {
|
|
5398
|
+
const percent = Math.round(bytes / total * 100);
|
|
5399
|
+
spinner.text = `Compressing files... ${dim(`${percent}%`)}`;
|
|
5400
|
+
}
|
|
5401
|
+
});
|
|
5402
|
+
spinner.text = "Checking remote...";
|
|
5403
|
+
let prepareResponse;
|
|
5404
|
+
try {
|
|
5405
|
+
prepareResponse = await client.post("/api/sync/push/bundle/prepare", {
|
|
5406
|
+
deviceId: config.deviceId,
|
|
5407
|
+
projectPath: cwd,
|
|
5408
|
+
projectId: projectLink?.projectId,
|
|
5409
|
+
manifest,
|
|
5410
|
+
bundleSize: bundleResult.size,
|
|
5411
|
+
localVersion,
|
|
5412
|
+
message: commitMessage
|
|
5413
|
+
});
|
|
5414
|
+
} catch (err) {
|
|
5415
|
+
spinner.stop();
|
|
5416
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5417
|
+
printError(`Push failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5418
|
+
return;
|
|
5419
|
+
}
|
|
5420
|
+
if (!prepareResponse.canProceed) {
|
|
5421
|
+
spinner.stop();
|
|
5422
|
+
console.log();
|
|
5423
|
+
printError(`Cannot push: You are ${brand(String(prepareResponse.behindBy))} version(s) behind.`);
|
|
5424
|
+
console.log();
|
|
5425
|
+
if (prepareResponse.recentCommits && prepareResponse.recentCommits.length > 0) {
|
|
5426
|
+
printInfo("Recent commits on remote:");
|
|
5427
|
+
for (const commit of prepareResponse.recentCommits) {
|
|
5428
|
+
const deviceName = commit.device?.name || "Unknown";
|
|
5429
|
+
const timeAgo = formatTimeAgo(commit.createdAt);
|
|
5430
|
+
console.log(` v${commit.version} "${commit.message}" ${dim(`(${deviceName}, ${timeAgo})`)}`);
|
|
5431
|
+
}
|
|
5432
|
+
console.log();
|
|
5433
|
+
}
|
|
5434
|
+
printInfo(`Run ${brand("claude-sync pull")} first to get these changes.`);
|
|
5435
|
+
return;
|
|
5436
|
+
}
|
|
5437
|
+
const progress = createTransferProgress("Uploading", bundleResult.size, spinner);
|
|
5438
|
+
await streamUpload(prepareResponse.uploadUrl, tempBundle, (bytes) => {
|
|
5439
|
+
progress.update(bytes);
|
|
5440
|
+
});
|
|
5441
|
+
let completeResponse;
|
|
5442
|
+
try {
|
|
5443
|
+
completeResponse = await client.post("/api/sync/push/bundle/complete", {
|
|
5444
|
+
syncEventId: prepareResponse.syncEventId,
|
|
5445
|
+
manifest,
|
|
5446
|
+
message: commitMessage
|
|
5447
|
+
});
|
|
5448
|
+
} catch (err) {
|
|
5449
|
+
spinner.stop();
|
|
5450
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5451
|
+
printError(`Push failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5452
|
+
return;
|
|
5453
|
+
}
|
|
5454
|
+
const newVersion = completeResponse.commit.version;
|
|
5455
|
+
const compressionRatio = Math.round((1 - bundleResult.size / bundleResult.originalSize) * 100);
|
|
5456
|
+
progress.finish(`Pushed ${manifest.length} files ${dim(`(${formatBytes(bundleResult.size)}, ${compressionRatio}% smaller)`)}`);
|
|
5457
|
+
await saveProjectLink(cwd, {
|
|
5458
|
+
projectId: prepareResponse.projectId,
|
|
5459
|
+
foreignEncodedDir: (0, import_utils3.encodePath)(cwd),
|
|
5460
|
+
linkedAt: projectLink?.linkedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
5461
|
+
localVersion: newVersion
|
|
5462
|
+
});
|
|
5463
|
+
console.log();
|
|
5464
|
+
printSuccess(`Commit: "${commitMessage}"`);
|
|
5465
|
+
printInfo(`Version: ${localVersion} \u2192 ${newVersion}`);
|
|
5466
|
+
printOutro("Done");
|
|
5467
|
+
} finally {
|
|
5468
|
+
try {
|
|
5469
|
+
await rm(tempBundle, { force: true });
|
|
5470
|
+
} catch {
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
async function checkPushPermission(client, projectId) {
|
|
5475
|
+
try {
|
|
5476
|
+
const roleInfo = await client.get(`/api/projects/${projectId}/my-role`);
|
|
5477
|
+
return { allowed: roleInfo.canPush, role: roleInfo.role };
|
|
5478
|
+
} catch {
|
|
5479
|
+
return { allowed: true, role: "owner" };
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
function formatTimeAgo(dateStr) {
|
|
5483
|
+
const date = new Date(dateStr);
|
|
5484
|
+
const now = /* @__PURE__ */ new Date();
|
|
5485
|
+
const diffMs = now.getTime() - date.getTime();
|
|
5486
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
5487
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
5488
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
5489
|
+
if (diffMins < 1) return "just now";
|
|
5490
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
5491
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
5492
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
5493
|
+
return date.toLocaleDateString();
|
|
5494
|
+
}
|
|
5495
|
+
var import_utils3;
|
|
5496
|
+
var init_push = __esm({
|
|
5497
|
+
"src/commands/push.ts"() {
|
|
5498
|
+
"use strict";
|
|
5499
|
+
init_esm_shims();
|
|
5500
|
+
init_config();
|
|
5501
|
+
init_api_client();
|
|
5502
|
+
init_sync_engine();
|
|
5503
|
+
init_progress();
|
|
5504
|
+
init_bundle();
|
|
5505
|
+
import_utils3 = __toESM(require_dist(), 1);
|
|
5506
|
+
init_theme();
|
|
5507
|
+
}
|
|
5508
|
+
});
|
|
5509
|
+
|
|
5510
|
+
// src/commands/pull.ts
|
|
5511
|
+
var pull_exports = {};
|
|
5512
|
+
__export(pull_exports, {
|
|
5513
|
+
pullCommand: () => pullCommand
|
|
5514
|
+
});
|
|
5515
|
+
import { Command as Command5 } from "commander";
|
|
5516
|
+
import { confirm, isCancel as isCancel4 } from "@clack/prompts";
|
|
5517
|
+
import { join as join5 } from "path";
|
|
5518
|
+
import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
|
|
5519
|
+
import { rm as rm2, mkdir as mkdir3 } from "fs/promises";
|
|
5520
|
+
function pullCommand() {
|
|
5521
|
+
return new Command5("pull").description("Pull latest changes from cloud").option("--dry-run", "Show what would be pulled without pulling").option("--verbose", "Show detailed output").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
5522
|
+
await runPull(options);
|
|
5523
|
+
});
|
|
5524
|
+
}
|
|
5525
|
+
async function runPull(options) {
|
|
5526
|
+
printIntro("Pull");
|
|
5527
|
+
const config = await loadConfig();
|
|
5528
|
+
if (!isAuthenticated(config)) {
|
|
5529
|
+
printError("Not logged in. Run `claude-sync login` first.");
|
|
5530
|
+
return;
|
|
5531
|
+
}
|
|
5532
|
+
if (!config.deviceId) {
|
|
5533
|
+
printError("No device registered. Run `claude-sync device add` first.");
|
|
5534
|
+
return;
|
|
5535
|
+
}
|
|
5536
|
+
const cwd = process.cwd();
|
|
5537
|
+
const ctx = await resolveProjectState(cwd);
|
|
5538
|
+
const client = new ApiClient(config.apiUrl);
|
|
5539
|
+
const projectsDir = join5(homedir5(), import_utils4.CLAUDE_DIR, import_utils4.PROJECTS_DIR);
|
|
5540
|
+
const projectLink = await loadProjectLink(cwd);
|
|
5541
|
+
let projectDir = ctx.projectDir;
|
|
5542
|
+
if (ctx.state === "symlink" && ctx.symlinkTarget) {
|
|
5543
|
+
projectDir = join5(projectsDir, ctx.symlinkTarget);
|
|
5544
|
+
}
|
|
5545
|
+
printInfo(`Project: ${brand(cwd)}`);
|
|
5546
|
+
if (!projectLink?.projectId) {
|
|
5547
|
+
printInfo("No project linked yet. Run `claude-sync push` first to create one.");
|
|
5548
|
+
printOutro("Done");
|
|
5549
|
+
return;
|
|
5550
|
+
}
|
|
5551
|
+
const localVersion = projectLink.localVersion ?? 0;
|
|
5552
|
+
const spinner = createSpinner("Checking for updates...").start();
|
|
5553
|
+
let syncState;
|
|
5554
|
+
try {
|
|
5555
|
+
syncState = await client.get(`/api/projects/${projectLink.projectId}/sync-state?localVersion=${localVersion}`);
|
|
5556
|
+
} catch (err) {
|
|
5557
|
+
spinner.stop();
|
|
5558
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5559
|
+
printError(`Failed to check sync state: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5560
|
+
return;
|
|
5561
|
+
}
|
|
5562
|
+
if (!syncState.isBehind) {
|
|
5563
|
+
spinner.stop();
|
|
5564
|
+
console.log();
|
|
5565
|
+
printSuccess("Already up to date.");
|
|
5566
|
+
printInfo(`Version: ${localVersion} (latest)`);
|
|
5567
|
+
printOutro("Done");
|
|
5568
|
+
return;
|
|
5569
|
+
}
|
|
5570
|
+
spinner.stop();
|
|
5571
|
+
console.log();
|
|
5572
|
+
printInfo(`You are ${brand(String(syncState.behindBy))} version(s) behind.`);
|
|
5573
|
+
printInfo(`Local: v${localVersion} Remote: v${syncState.currentVersion}`);
|
|
5574
|
+
console.log();
|
|
5575
|
+
if (syncState.recentCommits.length > 0) {
|
|
5576
|
+
printInfo("Commits to pull:");
|
|
5577
|
+
for (const commit of syncState.recentCommits) {
|
|
5578
|
+
const deviceName = commit.device?.name || "Unknown";
|
|
5579
|
+
const timeAgo = formatTimeAgo2(commit.createdAt);
|
|
5580
|
+
console.log(` v${commit.version} "${commit.message}" ${dim(`(${deviceName}, ${timeAgo})`)}`);
|
|
5581
|
+
}
|
|
5582
|
+
console.log();
|
|
5583
|
+
}
|
|
5584
|
+
if (options.dryRun) {
|
|
5585
|
+
printOutro("Dry run complete");
|
|
5586
|
+
return;
|
|
5587
|
+
}
|
|
5588
|
+
if (!options.yes) {
|
|
5589
|
+
const proceed = await confirm({
|
|
5590
|
+
message: brand(`Pull ${syncState.behindBy} version(s)?`),
|
|
5591
|
+
initialValue: true
|
|
5592
|
+
});
|
|
5593
|
+
if (isCancel4(proceed) || !proceed) {
|
|
5594
|
+
printInfo("Pull cancelled.");
|
|
5595
|
+
return;
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
const pullSpinner = createSpinner("Downloading...").start();
|
|
5599
|
+
let prepareResponse;
|
|
5600
|
+
try {
|
|
5601
|
+
prepareResponse = await client.post("/api/sync/pull/bundle/prepare", {
|
|
5602
|
+
deviceId: config.deviceId,
|
|
5603
|
+
projectPath: cwd,
|
|
5604
|
+
projectId: projectLink.projectId
|
|
5605
|
+
});
|
|
5606
|
+
} catch (err) {
|
|
5607
|
+
pullSpinner.stop();
|
|
5608
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5609
|
+
printError(`Pull failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5610
|
+
return;
|
|
5611
|
+
}
|
|
5612
|
+
if (!prepareResponse.downloadUrl || !prepareResponse.bundleSize) {
|
|
5613
|
+
pullSpinner.stop();
|
|
5614
|
+
printInfo("No files to download.");
|
|
5615
|
+
printOutro("Done");
|
|
5616
|
+
return;
|
|
5617
|
+
}
|
|
5618
|
+
const tempBundle = join5(tmpdir2(), `claude-sync-pull-${Date.now()}.tar.gz`);
|
|
5619
|
+
try {
|
|
5620
|
+
const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, pullSpinner);
|
|
5621
|
+
await streamDownload(prepareResponse.downloadUrl, tempBundle, (bytes) => {
|
|
5622
|
+
progress.update(bytes);
|
|
5623
|
+
});
|
|
5624
|
+
progress.finish("Downloaded");
|
|
5625
|
+
const extractSpinner = createSpinner("Extracting...").start();
|
|
5626
|
+
await mkdir3(projectDir, { recursive: true });
|
|
5627
|
+
const extractedCount = await extractBundle(tempBundle, projectDir);
|
|
5628
|
+
extractSpinner.succeed(`Extracted ${extractedCount} files`);
|
|
5629
|
+
try {
|
|
5630
|
+
await client.post("/api/sync/pull/bundle/complete", {
|
|
5631
|
+
syncEventId: prepareResponse.syncEventId,
|
|
5632
|
+
manifest: prepareResponse.manifest
|
|
5633
|
+
});
|
|
5634
|
+
} catch {
|
|
5635
|
+
}
|
|
5636
|
+
await saveProjectLink(cwd, {
|
|
5637
|
+
...projectLink,
|
|
5638
|
+
localVersion: syncState.currentVersion
|
|
5639
|
+
});
|
|
5640
|
+
console.log();
|
|
5641
|
+
printSuccess(`Pulled ${prepareResponse.manifest.length} files`);
|
|
5642
|
+
printInfo(`Version: ${localVersion} \u2192 ${syncState.currentVersion}`);
|
|
5643
|
+
printOutro("Done");
|
|
5644
|
+
} finally {
|
|
5645
|
+
try {
|
|
5646
|
+
await rm2(tempBundle, { force: true });
|
|
5647
|
+
} catch {
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
function formatTimeAgo2(dateStr) {
|
|
5652
|
+
const date = new Date(dateStr);
|
|
5653
|
+
const now = /* @__PURE__ */ new Date();
|
|
5654
|
+
const diffMs = now.getTime() - date.getTime();
|
|
5655
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
5656
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
5657
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
5658
|
+
if (diffMins < 1) return "just now";
|
|
5659
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
5660
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
5661
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
5662
|
+
return date.toLocaleDateString();
|
|
5663
|
+
}
|
|
5664
|
+
var import_utils4;
|
|
5665
|
+
var init_pull = __esm({
|
|
5666
|
+
"src/commands/pull.ts"() {
|
|
5667
|
+
"use strict";
|
|
5668
|
+
init_esm_shims();
|
|
5669
|
+
init_config();
|
|
5670
|
+
init_api_client();
|
|
5671
|
+
init_sync_engine();
|
|
5672
|
+
init_progress();
|
|
5673
|
+
init_bundle();
|
|
5674
|
+
import_utils4 = __toESM(require_dist(), 1);
|
|
5675
|
+
init_theme();
|
|
5676
|
+
}
|
|
5677
|
+
});
|
|
5678
|
+
|
|
5679
|
+
// src/commands/log.ts
|
|
5680
|
+
var log_exports = {};
|
|
5681
|
+
__export(log_exports, {
|
|
5682
|
+
logCommand: () => logCommand
|
|
5683
|
+
});
|
|
5684
|
+
import { Command as Command6 } from "commander";
|
|
5685
|
+
function logCommand() {
|
|
5686
|
+
return new Command6("log").description("Show commit history for current project").option("-n, --limit <number>", "Number of commits to show", "10").action(async (options) => {
|
|
5687
|
+
await runLog(options);
|
|
5688
|
+
});
|
|
5689
|
+
}
|
|
5690
|
+
async function runLog(options) {
|
|
5691
|
+
printIntro("Log");
|
|
5692
|
+
const config = await loadConfig();
|
|
5693
|
+
if (!isAuthenticated(config)) {
|
|
5694
|
+
printError("Not logged in. Run `claude-sync login` first.");
|
|
5695
|
+
return;
|
|
5696
|
+
}
|
|
5697
|
+
const cwd = process.cwd();
|
|
5698
|
+
const projectLink = await loadProjectLink(cwd);
|
|
5699
|
+
printInfo(`Project: ${brand(cwd)}`);
|
|
5700
|
+
if (!projectLink?.projectId) {
|
|
5701
|
+
printInfo("No project linked yet. Run `claude-sync push` first.");
|
|
5702
|
+
printOutro("Done");
|
|
5703
|
+
return;
|
|
5704
|
+
}
|
|
5705
|
+
const client = new ApiClient(config.apiUrl);
|
|
5706
|
+
const limit = parseInt(options.limit, 10) || 10;
|
|
5707
|
+
let commits;
|
|
5708
|
+
try {
|
|
5709
|
+
commits = await client.get(`/api/projects/${projectLink.projectId}/commits?limit=${limit}`);
|
|
5710
|
+
} catch (err) {
|
|
5711
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5712
|
+
printError(`Failed to fetch commits: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5713
|
+
return;
|
|
5714
|
+
}
|
|
5715
|
+
if (commits.length === 0) {
|
|
5716
|
+
printInfo("No commits yet.");
|
|
5717
|
+
printOutro("Done");
|
|
5718
|
+
return;
|
|
5719
|
+
}
|
|
5720
|
+
const localVersion = projectLink.localVersion ?? 0;
|
|
5721
|
+
console.log();
|
|
5722
|
+
for (const commit of commits) {
|
|
5723
|
+
const deviceName = commit.device?.name || "Unknown device";
|
|
5724
|
+
const timeAgo = formatTimeAgo3(commit.createdAt);
|
|
5725
|
+
const isHead = commit.version === localVersion;
|
|
5726
|
+
const versionStr = isHead ? `${brand(`v${commit.version}`)} ${dim("(HEAD)")}` : `v${commit.version}`;
|
|
5727
|
+
console.log(` ${versionStr} "${commit.message}"`);
|
|
5728
|
+
console.log(` ${dim(`${deviceName} \u2022 ${timeAgo}`)}`);
|
|
5729
|
+
console.log();
|
|
5730
|
+
}
|
|
5731
|
+
printOutro("");
|
|
5732
|
+
}
|
|
5733
|
+
function formatTimeAgo3(dateStr) {
|
|
5734
|
+
const date = new Date(dateStr);
|
|
5735
|
+
const now = /* @__PURE__ */ new Date();
|
|
5736
|
+
const diffMs = now.getTime() - date.getTime();
|
|
5737
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
5738
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
5739
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
5740
|
+
if (diffMins < 1) return "just now";
|
|
5741
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
5742
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
5743
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
5744
|
+
return date.toLocaleDateString();
|
|
5745
|
+
}
|
|
5746
|
+
var init_log = __esm({
|
|
5747
|
+
"src/commands/log.ts"() {
|
|
5748
|
+
"use strict";
|
|
5749
|
+
init_esm_shims();
|
|
5750
|
+
init_config();
|
|
5751
|
+
init_api_client();
|
|
5752
|
+
init_theme();
|
|
5753
|
+
}
|
|
5754
|
+
});
|
|
5755
|
+
|
|
5756
|
+
// src/commands/status.ts
|
|
5757
|
+
var status_exports = {};
|
|
5758
|
+
__export(status_exports, {
|
|
5759
|
+
statusCommand: () => statusCommand
|
|
5760
|
+
});
|
|
5761
|
+
import { Command as Command8 } from "commander";
|
|
5762
|
+
function statusCommand() {
|
|
5763
|
+
return new Command8("status").description("Show sync status for current project").action(async () => {
|
|
5764
|
+
printIntro("Status");
|
|
5765
|
+
const config = await loadConfig();
|
|
5766
|
+
const cwd = process.cwd();
|
|
5767
|
+
printLabel("API", config.apiUrl);
|
|
5768
|
+
printLabel("Authenticated", isAuthenticated(config) ? success("yes") : error("no"));
|
|
5769
|
+
printLabel("Device", config.deviceName || dim("none"));
|
|
5770
|
+
printLabel("Project", brand(cwd));
|
|
5771
|
+
const ctx = await resolveProjectState(cwd);
|
|
5772
|
+
printLabel("State", ctx.state === "none" ? dim("no session data") : ctx.state === "symlink" ? `symlink \u2192 ${dim(ctx.symlinkTarget || "unknown")}` : "local");
|
|
5773
|
+
if (!isAuthenticated(config) || ctx.state === "none") return;
|
|
5774
|
+
const spinner = createSpinner("Scanning files...").start();
|
|
5775
|
+
const savedManifest = await loadProjectManifest(cwd);
|
|
5776
|
+
const currentManifest = await buildProjectManifest(cwd);
|
|
5777
|
+
const diff = computeDiff(currentManifest, savedManifest);
|
|
5778
|
+
spinner.stop();
|
|
5779
|
+
const totalSize = currentManifest.reduce((sum, e) => sum + e.size, 0);
|
|
5780
|
+
printDivider();
|
|
5781
|
+
printLabel("Local files", `${brand(String(currentManifest.length))} ${dim(`(${formatBytes(totalSize)})`)}`);
|
|
5782
|
+
console.log();
|
|
5783
|
+
printInfo("Changes since last sync:");
|
|
5784
|
+
console.log(` ${success(`+${diff.added.length}`)} added ${warn(`~${diff.modified.length}`)} modified ${error(`-${diff.deleted.length}`)} deleted`);
|
|
5785
|
+
console.log();
|
|
5786
|
+
const projectLink = await loadProjectLink(cwd);
|
|
5787
|
+
if (projectLink?.projectId) {
|
|
5788
|
+
const client = new ApiClient(config.apiUrl);
|
|
5789
|
+
const localVersion = projectLink.localVersion ?? 0;
|
|
5790
|
+
try {
|
|
5791
|
+
const syncState = await client.get(`/api/projects/${projectLink.projectId}/sync-state?localVersion=${localVersion}`);
|
|
5792
|
+
if (syncState.isBehind) {
|
|
5793
|
+
printWarn(`You are ${brand(String(syncState.behindBy))} version(s) behind.`);
|
|
5794
|
+
printInfo(`Local: v${localVersion} Remote: v${syncState.currentVersion}`);
|
|
5795
|
+
console.log();
|
|
5796
|
+
if (syncState.recentCommits.length > 0) {
|
|
5797
|
+
printInfo("Recent commits on remote:");
|
|
5798
|
+
for (const commit of syncState.recentCommits) {
|
|
5799
|
+
const deviceName = commit.device?.name || "Unknown";
|
|
5800
|
+
console.log(` v${commit.version} "${commit.message}" ${dim(`(${deviceName})`)}`);
|
|
5801
|
+
}
|
|
5802
|
+
console.log();
|
|
5803
|
+
}
|
|
5804
|
+
printInfo(`Run ${brand("claude-sync pull")} to get latest changes.`);
|
|
5805
|
+
} else {
|
|
5806
|
+
printSuccess("Up to date");
|
|
5807
|
+
printInfo(`Version: ${localVersion}`);
|
|
5808
|
+
}
|
|
5809
|
+
} catch {
|
|
5810
|
+
printInfo(`Local version: ${localVersion}`);
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
});
|
|
5814
|
+
}
|
|
5815
|
+
var init_status = __esm({
|
|
5816
|
+
"src/commands/status.ts"() {
|
|
5817
|
+
"use strict";
|
|
5818
|
+
init_esm_shims();
|
|
5819
|
+
init_config();
|
|
5820
|
+
init_sync_engine();
|
|
5821
|
+
init_api_client();
|
|
5822
|
+
init_progress();
|
|
5823
|
+
init_theme();
|
|
5824
|
+
}
|
|
5825
|
+
});
|
|
5826
|
+
|
|
5827
|
+
// src/commands/whoami.ts
|
|
5828
|
+
var whoami_exports = {};
|
|
5829
|
+
__export(whoami_exports, {
|
|
5830
|
+
whoamiCommand: () => whoamiCommand
|
|
5831
|
+
});
|
|
5832
|
+
import { Command as Command9 } from "commander";
|
|
5833
|
+
function whoamiCommand() {
|
|
5834
|
+
return new Command9("whoami").description("Show current user and device info").action(async () => {
|
|
5835
|
+
printIntro("Who Am I");
|
|
5836
|
+
const config = await loadConfig();
|
|
5837
|
+
if (!isAuthenticated(config)) {
|
|
5838
|
+
printError("Not logged in. Run `claude-sync login` first.");
|
|
5839
|
+
return;
|
|
5840
|
+
}
|
|
5841
|
+
const client = new ApiClient(config.apiUrl);
|
|
5842
|
+
const spinner = createSpinner("Fetching user info...").start();
|
|
5843
|
+
try {
|
|
5844
|
+
const user = await client.get("/api/auth/me");
|
|
5845
|
+
spinner.stop();
|
|
5846
|
+
printLabel("User", `${user.name} ${dim(`(${user.email})`)}`);
|
|
5847
|
+
printLabel("Plan", brand(user.plan));
|
|
5848
|
+
printDivider();
|
|
5849
|
+
printLabel("Device", config.deviceName || dim("none"));
|
|
5850
|
+
printLabel("Device ID", config.deviceId || dim("not registered"));
|
|
5851
|
+
} catch (err) {
|
|
5852
|
+
spinner.stop();
|
|
5853
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5854
|
+
printLabel("Email", config.email || dim("unknown"));
|
|
5855
|
+
printLabel("Device", config.deviceName || dim("none"));
|
|
5856
|
+
}
|
|
5857
|
+
console.log();
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
var init_whoami = __esm({
|
|
5861
|
+
"src/commands/whoami.ts"() {
|
|
5862
|
+
"use strict";
|
|
5863
|
+
init_esm_shims();
|
|
5864
|
+
init_config();
|
|
5865
|
+
init_api_client();
|
|
5866
|
+
init_progress();
|
|
5867
|
+
init_theme();
|
|
5868
|
+
}
|
|
5869
|
+
});
|
|
5870
|
+
|
|
5871
|
+
// bin/claude-sync.ts
|
|
5872
|
+
init_esm_shims();
|
|
5873
|
+
|
|
5874
|
+
// src/index.ts
|
|
5875
|
+
init_esm_shims();
|
|
5876
|
+
init_login();
|
|
5877
|
+
init_logout();
|
|
5878
|
+
init_device();
|
|
5879
|
+
init_push();
|
|
5880
|
+
init_pull();
|
|
5881
|
+
init_log();
|
|
5882
|
+
import { Command as Command10 } from "commander";
|
|
5883
|
+
|
|
5884
|
+
// src/commands/sync.ts
|
|
5885
|
+
init_esm_shims();
|
|
5886
|
+
init_config();
|
|
5887
|
+
init_api_client();
|
|
5888
|
+
init_sync_engine();
|
|
5889
|
+
init_progress();
|
|
5890
|
+
init_bundle();
|
|
5891
|
+
var import_utils5 = __toESM(require_dist(), 1);
|
|
5892
|
+
init_theme();
|
|
5893
|
+
import { Command as Command7 } from "commander";
|
|
5894
|
+
import { select as select2, isCancel as isCancel5 } from "@clack/prompts";
|
|
5895
|
+
import { mkdir as mkdir4, lstat as lstat2, symlink, unlink, rename, rm as rm3 } from "fs/promises";
|
|
5896
|
+
import { join as join6 } from "path";
|
|
5897
|
+
import { homedir as homedir6, tmpdir as tmpdir3 } from "os";
|
|
5898
|
+
async function runSync(options) {
|
|
5899
|
+
printIntro("Sync");
|
|
5900
|
+
const config = await loadConfig();
|
|
5901
|
+
if (!isAuthenticated(config)) {
|
|
5902
|
+
printError("Not logged in. Run `claude-sync login` first.");
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
if (!config.deviceId) {
|
|
5906
|
+
printError("No device registered. Run `claude-sync device add` first.");
|
|
5907
|
+
return;
|
|
5908
|
+
}
|
|
5909
|
+
const cwd = process.cwd();
|
|
5910
|
+
const ctx = await resolveProjectState(cwd);
|
|
5911
|
+
const client = new ApiClient(config.apiUrl);
|
|
5912
|
+
const projectsDir = join6(homedir6(), import_utils5.CLAUDE_DIR, import_utils5.PROJECTS_DIR);
|
|
5913
|
+
const projectLink = await loadProjectLink(cwd);
|
|
5914
|
+
let projectDir = ctx.projectDir;
|
|
5915
|
+
if (ctx.state === "symlink" && ctx.symlinkTarget) {
|
|
5916
|
+
projectDir = join6(projectsDir, ctx.symlinkTarget);
|
|
5917
|
+
}
|
|
5918
|
+
printInfo(`Project: ${brand(cwd)}`);
|
|
5315
5919
|
if (ctx.state === "symlink") {
|
|
5316
5920
|
printInfo(`Linked to: ${dim(ctx.symlinkTarget || "unknown")}`);
|
|
5317
5921
|
}
|
|
@@ -5413,7 +6017,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5413
6017
|
message: brand("What would you like to do?"),
|
|
5414
6018
|
options: menuOptions
|
|
5415
6019
|
});
|
|
5416
|
-
if (
|
|
6020
|
+
if (isCancel5(choice)) return "cancelled";
|
|
5417
6021
|
if (choice === "push") {
|
|
5418
6022
|
const result2 = await bundlePushPhase(client, config.deviceId, cwd, manifest, projectDir, void 0, options);
|
|
5419
6023
|
if (result2.failed) {
|
|
@@ -5435,7 +6039,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5435
6039
|
hint: dim(`${p.owner.name} \xB7 ${p.role}`)
|
|
5436
6040
|
}))
|
|
5437
6041
|
});
|
|
5438
|
-
if (
|
|
6042
|
+
if (isCancel5(sharedChoice)) return "cancelled";
|
|
5439
6043
|
const selectedShared = sharedProjects.find((p) => p.id === sharedChoice);
|
|
5440
6044
|
const syncStatusSpinner = createSpinner("Checking sync status...").start();
|
|
5441
6045
|
let syncStatus = null;
|
|
@@ -5454,7 +6058,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5454
6058
|
{ value: "no", label: "Cancel", hint: dim("Wait for them to finish") }
|
|
5455
6059
|
]
|
|
5456
6060
|
});
|
|
5457
|
-
if (
|
|
6061
|
+
if (isCancel5(confirmChoice) || confirmChoice === "no") return "cancelled";
|
|
5458
6062
|
}
|
|
5459
6063
|
const existingCheck2 = await handleExistingSessionDir(projectsDir, ctx.encodedPath);
|
|
5460
6064
|
if (existingCheck2 === "cancelled") {
|
|
@@ -5493,7 +6097,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5493
6097
|
hint: dim(`${d.hostname} \xB7 ${d.platform}`)
|
|
5494
6098
|
}))
|
|
5495
6099
|
});
|
|
5496
|
-
if (
|
|
6100
|
+
if (isCancel5(deviceChoice)) return "cancelled";
|
|
5497
6101
|
const spinner = createSpinner("Loading projects...").start();
|
|
5498
6102
|
let projects;
|
|
5499
6103
|
try {
|
|
@@ -5517,7 +6121,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5517
6121
|
hint: dim(p.localPath)
|
|
5518
6122
|
}))
|
|
5519
6123
|
});
|
|
5520
|
-
if (
|
|
6124
|
+
if (isCancel5(projectChoice)) return "cancelled";
|
|
5521
6125
|
const selectedProject = projects.find((p) => p.id === projectChoice);
|
|
5522
6126
|
const existingCheck = await handleExistingSessionDir(projectsDir, ctx.encodedPath);
|
|
5523
6127
|
if (existingCheck === "cancelled") {
|
|
@@ -5549,7 +6153,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5549
6153
|
return "pulled";
|
|
5550
6154
|
}
|
|
5551
6155
|
async function handleExistingSessionDir(projectsDir, localEncoded) {
|
|
5552
|
-
const symlinkPath =
|
|
6156
|
+
const symlinkPath = join6(projectsDir, localEncoded);
|
|
5553
6157
|
try {
|
|
5554
6158
|
const stats = await lstat2(symlinkPath);
|
|
5555
6159
|
if (stats.isSymbolicLink()) {
|
|
@@ -5573,7 +6177,7 @@ async function handleExistingSessionDir(projectsDir, localEncoded) {
|
|
|
5573
6177
|
}
|
|
5574
6178
|
]
|
|
5575
6179
|
});
|
|
5576
|
-
if (
|
|
6180
|
+
if (isCancel5(choice) || choice === "cancel") {
|
|
5577
6181
|
return "cancelled";
|
|
5578
6182
|
}
|
|
5579
6183
|
const backupPath = `${symlinkPath}.bak.${Date.now()}`;
|
|
@@ -5586,8 +6190,8 @@ async function handleExistingSessionDir(projectsDir, localEncoded) {
|
|
|
5586
6190
|
return "continue";
|
|
5587
6191
|
}
|
|
5588
6192
|
async function createProjectSymlink(projectsDir, localEncoded, foreignEncoded) {
|
|
5589
|
-
const symlinkPath =
|
|
5590
|
-
await
|
|
6193
|
+
const symlinkPath = join6(projectsDir, localEncoded);
|
|
6194
|
+
await mkdir4(projectsDir, { recursive: true });
|
|
5591
6195
|
try {
|
|
5592
6196
|
const stats = await lstat2(symlinkPath);
|
|
5593
6197
|
if (stats.isSymbolicLink()) {
|
|
@@ -5602,7 +6206,7 @@ async function bundlePushPhase(client, deviceId, projectPath, manifest, projectD
|
|
|
5602
6206
|
return { fileCount: 0, projectId: projectId || "", failed: false };
|
|
5603
6207
|
}
|
|
5604
6208
|
if (projectId) {
|
|
5605
|
-
const permission = await
|
|
6209
|
+
const permission = await checkPushPermission2(client, projectId);
|
|
5606
6210
|
if (!permission.allowed) {
|
|
5607
6211
|
printError(`Cannot push: You have ${brand(permission.role)} access to this project.`);
|
|
5608
6212
|
printInfo("Only project editors and owners can push changes.");
|
|
@@ -5619,7 +6223,7 @@ async function bundlePushPhase(client, deviceId, projectPath, manifest, projectD
|
|
|
5619
6223
|
}
|
|
5620
6224
|
return { fileCount: manifest.length, projectId: projectId || "", failed: false };
|
|
5621
6225
|
}
|
|
5622
|
-
const tempBundle =
|
|
6226
|
+
const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5623
6227
|
try {
|
|
5624
6228
|
const spinner = createSpinner("Syncing...").start();
|
|
5625
6229
|
const bundleResult = await createBundle(projectDir, manifest, tempBundle, (bytes, total, phase) => {
|
|
@@ -5659,7 +6263,7 @@ async function bundlePushPhase(client, deviceId, projectPath, manifest, projectD
|
|
|
5659
6263
|
return { fileCount: manifest.length, projectId: prepareResponse.projectId, failed: false };
|
|
5660
6264
|
} finally {
|
|
5661
6265
|
try {
|
|
5662
|
-
await
|
|
6266
|
+
await rm3(tempBundle, { force: true });
|
|
5663
6267
|
} catch {
|
|
5664
6268
|
}
|
|
5665
6269
|
}
|
|
@@ -5694,7 +6298,7 @@ async function bundlePullPhase(client, deviceId, projectPath, projectDir, projec
|
|
|
5694
6298
|
}
|
|
5695
6299
|
return { fileCount, failed: false };
|
|
5696
6300
|
}
|
|
5697
|
-
const tempBundle =
|
|
6301
|
+
const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5698
6302
|
try {
|
|
5699
6303
|
const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
|
|
5700
6304
|
await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
|
|
@@ -5714,7 +6318,7 @@ async function bundlePullPhase(client, deviceId, projectPath, projectDir, projec
|
|
|
5714
6318
|
return { fileCount: extractedCount, failed: false };
|
|
5715
6319
|
} finally {
|
|
5716
6320
|
try {
|
|
5717
|
-
await
|
|
6321
|
+
await rm3(tempBundle, { force: true });
|
|
5718
6322
|
} catch {
|
|
5719
6323
|
}
|
|
5720
6324
|
}
|
|
@@ -5750,15 +6354,15 @@ async function crossMachineBundlePull(client, deviceId, projectPath, fromDeviceI
|
|
|
5750
6354
|
}
|
|
5751
6355
|
return { fileCount, failed: false };
|
|
5752
6356
|
}
|
|
5753
|
-
const targetDir =
|
|
5754
|
-
const tempBundle =
|
|
6357
|
+
const targetDir = join6(projectsDir, foreignEncodedDir);
|
|
6358
|
+
const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5755
6359
|
try {
|
|
5756
6360
|
const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
|
|
5757
6361
|
await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
|
|
5758
6362
|
progress.update(bytes);
|
|
5759
6363
|
});
|
|
5760
6364
|
spinner.text = "Extracting files...";
|
|
5761
|
-
await
|
|
6365
|
+
await mkdir4(targetDir, { recursive: true });
|
|
5762
6366
|
const extractedCount = await extractBundle(tempBundle, targetDir);
|
|
5763
6367
|
try {
|
|
5764
6368
|
const completeManifest = await walkDirectory(targetDir);
|
|
@@ -5772,7 +6376,7 @@ async function crossMachineBundlePull(client, deviceId, projectPath, fromDeviceI
|
|
|
5772
6376
|
return { fileCount: extractedCount, failed: false };
|
|
5773
6377
|
} finally {
|
|
5774
6378
|
try {
|
|
5775
|
-
await
|
|
6379
|
+
await rm3(tempBundle, { force: true });
|
|
5776
6380
|
} catch {
|
|
5777
6381
|
}
|
|
5778
6382
|
}
|
|
@@ -5808,15 +6412,15 @@ async function collaboratorBundlePull(client, deviceId, projectPath, projectId,
|
|
|
5808
6412
|
return { fileCount, failed: false };
|
|
5809
6413
|
}
|
|
5810
6414
|
const foreignEncoded = canonicalPath.replace(/\//g, "%2F");
|
|
5811
|
-
const targetDir =
|
|
5812
|
-
const tempBundle =
|
|
6415
|
+
const targetDir = join6(projectsDir, foreignEncoded);
|
|
6416
|
+
const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5813
6417
|
try {
|
|
5814
6418
|
const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
|
|
5815
6419
|
await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
|
|
5816
6420
|
progress.update(bytes);
|
|
5817
6421
|
});
|
|
5818
6422
|
spinner.text = "Extracting files...";
|
|
5819
|
-
await
|
|
6423
|
+
await mkdir4(targetDir, { recursive: true });
|
|
5820
6424
|
const extractedCount = await extractBundle(tempBundle, targetDir);
|
|
5821
6425
|
try {
|
|
5822
6426
|
const completeManifest = await walkDirectory(targetDir);
|
|
@@ -5830,12 +6434,12 @@ async function collaboratorBundlePull(client, deviceId, projectPath, projectId,
|
|
|
5830
6434
|
return { fileCount: extractedCount, failed: false };
|
|
5831
6435
|
} finally {
|
|
5832
6436
|
try {
|
|
5833
|
-
await
|
|
6437
|
+
await rm3(tempBundle, { force: true });
|
|
5834
6438
|
} catch {
|
|
5835
6439
|
}
|
|
5836
6440
|
}
|
|
5837
6441
|
}
|
|
5838
|
-
async function
|
|
6442
|
+
async function checkPushPermission2(client, projectId) {
|
|
5839
6443
|
try {
|
|
5840
6444
|
const myRole = await client.get(`/api/projects/${projectId}/my-role`);
|
|
5841
6445
|
return { allowed: myRole.canPush, role: myRole.role };
|
|
@@ -5844,131 +6448,21 @@ async function checkPushPermission(client, projectId) {
|
|
|
5844
6448
|
}
|
|
5845
6449
|
}
|
|
5846
6450
|
function syncCommand() {
|
|
5847
|
-
return new
|
|
6451
|
+
return new Command7("sync").description("Sync current project sessions with cloud").option("--dry-run", "Show what would be synced without syncing").option("--verbose", "Show detailed output").action(async (options) => {
|
|
5848
6452
|
await runSync(options);
|
|
5849
6453
|
});
|
|
5850
6454
|
}
|
|
5851
|
-
var import_utils3;
|
|
5852
|
-
var init_sync = __esm({
|
|
5853
|
-
"src/commands/sync.ts"() {
|
|
5854
|
-
"use strict";
|
|
5855
|
-
init_esm_shims();
|
|
5856
|
-
init_config();
|
|
5857
|
-
init_api_client();
|
|
5858
|
-
init_sync_engine();
|
|
5859
|
-
init_progress();
|
|
5860
|
-
init_bundle();
|
|
5861
|
-
import_utils3 = __toESM(require_dist(), 1);
|
|
5862
|
-
init_theme();
|
|
5863
|
-
}
|
|
5864
|
-
});
|
|
5865
|
-
|
|
5866
|
-
// src/commands/status.ts
|
|
5867
|
-
var status_exports = {};
|
|
5868
|
-
__export(status_exports, {
|
|
5869
|
-
statusCommand: () => statusCommand
|
|
5870
|
-
});
|
|
5871
|
-
import { Command as Command5 } from "commander";
|
|
5872
|
-
function statusCommand() {
|
|
5873
|
-
return new Command5("status").description("Show sync status for current project").action(async () => {
|
|
5874
|
-
printIntro("Status");
|
|
5875
|
-
const config = await loadConfig();
|
|
5876
|
-
const cwd = process.cwd();
|
|
5877
|
-
printLabel("API", config.apiUrl);
|
|
5878
|
-
printLabel("Authenticated", isAuthenticated(config) ? success("yes") : error("no"));
|
|
5879
|
-
printLabel("Device", config.deviceName || dim("none"));
|
|
5880
|
-
printLabel("Project", brand(cwd));
|
|
5881
|
-
const ctx = await resolveProjectState(cwd);
|
|
5882
|
-
printLabel("State", ctx.state === "none" ? dim("no session data") : ctx.state === "symlink" ? `symlink \u2192 ${dim(ctx.symlinkTarget || "unknown")}` : "local");
|
|
5883
|
-
if (!isAuthenticated(config) || ctx.state === "none") return;
|
|
5884
|
-
const spinner = createSpinner("Scanning files...").start();
|
|
5885
|
-
const savedManifest = await loadProjectManifest(cwd);
|
|
5886
|
-
const currentManifest = await buildProjectManifest(cwd);
|
|
5887
|
-
const diff = computeDiff(currentManifest, savedManifest);
|
|
5888
|
-
spinner.stop();
|
|
5889
|
-
const totalSize = currentManifest.reduce((sum, e) => sum + e.size, 0);
|
|
5890
|
-
printDivider();
|
|
5891
|
-
printLabel("Local files", `${brand(String(currentManifest.length))} ${dim(`(${formatBytes(totalSize)})`)}`);
|
|
5892
|
-
console.log();
|
|
5893
|
-
printInfo("Changes since last sync:");
|
|
5894
|
-
console.log(` ${success(`+${diff.added.length}`)} added ${warn(`~${diff.modified.length}`)} modified ${error(`-${diff.deleted.length}`)} deleted`);
|
|
5895
|
-
console.log();
|
|
5896
|
-
});
|
|
5897
|
-
}
|
|
5898
|
-
var init_status = __esm({
|
|
5899
|
-
"src/commands/status.ts"() {
|
|
5900
|
-
"use strict";
|
|
5901
|
-
init_esm_shims();
|
|
5902
|
-
init_config();
|
|
5903
|
-
init_sync_engine();
|
|
5904
|
-
init_progress();
|
|
5905
|
-
init_theme();
|
|
5906
|
-
}
|
|
5907
|
-
});
|
|
5908
|
-
|
|
5909
|
-
// src/commands/whoami.ts
|
|
5910
|
-
var whoami_exports = {};
|
|
5911
|
-
__export(whoami_exports, {
|
|
5912
|
-
whoamiCommand: () => whoamiCommand
|
|
5913
|
-
});
|
|
5914
|
-
import { Command as Command6 } from "commander";
|
|
5915
|
-
function whoamiCommand() {
|
|
5916
|
-
return new Command6("whoami").description("Show current user and device info").action(async () => {
|
|
5917
|
-
printIntro("Who Am I");
|
|
5918
|
-
const config = await loadConfig();
|
|
5919
|
-
if (!isAuthenticated(config)) {
|
|
5920
|
-
printError("Not logged in. Run `claude-sync login` first.");
|
|
5921
|
-
return;
|
|
5922
|
-
}
|
|
5923
|
-
const client = new ApiClient(config.apiUrl);
|
|
5924
|
-
const spinner = createSpinner("Fetching user info...").start();
|
|
5925
|
-
try {
|
|
5926
|
-
const user = await client.get("/api/auth/me");
|
|
5927
|
-
spinner.stop();
|
|
5928
|
-
printLabel("User", `${user.name} ${dim(`(${user.email})`)}`);
|
|
5929
|
-
printLabel("Plan", brand(user.plan));
|
|
5930
|
-
printDivider();
|
|
5931
|
-
printLabel("Device", config.deviceName || dim("none"));
|
|
5932
|
-
printLabel("Device ID", config.deviceId || dim("not registered"));
|
|
5933
|
-
} catch (err) {
|
|
5934
|
-
spinner.stop();
|
|
5935
|
-
if (err instanceof AuthExpiredError) throw err;
|
|
5936
|
-
printLabel("Email", config.email || dim("unknown"));
|
|
5937
|
-
printLabel("Device", config.deviceName || dim("none"));
|
|
5938
|
-
}
|
|
5939
|
-
console.log();
|
|
5940
|
-
});
|
|
5941
|
-
}
|
|
5942
|
-
var init_whoami = __esm({
|
|
5943
|
-
"src/commands/whoami.ts"() {
|
|
5944
|
-
"use strict";
|
|
5945
|
-
init_esm_shims();
|
|
5946
|
-
init_config();
|
|
5947
|
-
init_api_client();
|
|
5948
|
-
init_progress();
|
|
5949
|
-
init_theme();
|
|
5950
|
-
}
|
|
5951
|
-
});
|
|
5952
|
-
|
|
5953
|
-
// bin/claude-sync.ts
|
|
5954
|
-
init_esm_shims();
|
|
5955
6455
|
|
|
5956
6456
|
// src/index.ts
|
|
5957
|
-
init_esm_shims();
|
|
5958
|
-
init_login();
|
|
5959
|
-
init_logout();
|
|
5960
|
-
init_device();
|
|
5961
|
-
init_sync();
|
|
5962
6457
|
init_status();
|
|
5963
6458
|
init_whoami();
|
|
5964
|
-
import { Command as Command7 } from "commander";
|
|
5965
6459
|
|
|
5966
6460
|
// src/commands/tui.ts
|
|
5967
6461
|
init_esm_shims();
|
|
5968
6462
|
init_theme();
|
|
5969
6463
|
init_config();
|
|
5970
6464
|
init_api_client();
|
|
5971
|
-
import { select as select3, text as
|
|
6465
|
+
import { select as select3, text as text4, isCancel as isCancel6 } from "@clack/prompts";
|
|
5972
6466
|
import chalk3 from "chalk";
|
|
5973
6467
|
function buildMenu(loggedIn) {
|
|
5974
6468
|
if (!loggedIn) {
|
|
@@ -5977,7 +6471,10 @@ function buildMenu(loggedIn) {
|
|
|
5977
6471
|
];
|
|
5978
6472
|
}
|
|
5979
6473
|
return [
|
|
5980
|
-
{ value: "
|
|
6474
|
+
{ value: "quick-push", label: "Quick Push", hint: "Push without message" },
|
|
6475
|
+
{ value: "push-message", label: "Push with Message", hint: "Add a commit message" },
|
|
6476
|
+
{ value: "pull", label: "Pull", hint: "Get latest changes" },
|
|
6477
|
+
{ value: "log", label: "View History", hint: "Show commit log" },
|
|
5981
6478
|
{ value: "status", label: "Status", hint: "Show sync state" },
|
|
5982
6479
|
{ value: "device:add", label: "Add Device", hint: "Register this machine" },
|
|
5983
6480
|
{ value: "device:list", label: "List Devices", hint: "Show registered devices" },
|
|
@@ -6019,7 +6516,7 @@ async function runTui() {
|
|
|
6019
6516
|
message: brand("What would you like to do?"),
|
|
6020
6517
|
options
|
|
6021
6518
|
});
|
|
6022
|
-
if (
|
|
6519
|
+
if (isCancel6(choice) || choice === "quit") {
|
|
6023
6520
|
printOutro("Goodbye!");
|
|
6024
6521
|
return;
|
|
6025
6522
|
}
|
|
@@ -6063,21 +6560,33 @@ async function promptDeviceSelection(config) {
|
|
|
6063
6560
|
hint: d.id === config.deviceId ? dim("current") : void 0
|
|
6064
6561
|
}))
|
|
6065
6562
|
});
|
|
6066
|
-
if (
|
|
6563
|
+
if (isCancel6(choice)) return null;
|
|
6067
6564
|
return choice;
|
|
6068
6565
|
}
|
|
6069
6566
|
async function executeCommand(command) {
|
|
6070
6567
|
const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
6071
6568
|
const { logoutCommand: logoutCommand2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
|
|
6072
6569
|
const { deviceCommand: deviceCommand2 } = await Promise.resolve().then(() => (init_device(), device_exports));
|
|
6073
|
-
const {
|
|
6570
|
+
const { pushCommand: pushCommand2 } = await Promise.resolve().then(() => (init_push(), push_exports));
|
|
6571
|
+
const { pullCommand: pullCommand2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
|
|
6572
|
+
const { logCommand: logCommand2 } = await Promise.resolve().then(() => (init_log(), log_exports));
|
|
6074
6573
|
const { statusCommand: statusCommand2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
6075
6574
|
const { whoamiCommand: whoamiCommand2 } = await Promise.resolve().then(() => (init_whoami(), whoami_exports));
|
|
6076
6575
|
const config = await loadConfig();
|
|
6077
6576
|
const handlers = {
|
|
6078
6577
|
login: () => loginCommand2().parseAsync(["", ""]),
|
|
6079
6578
|
logout: () => logoutCommand2().parseAsync(["", ""]),
|
|
6080
|
-
|
|
6579
|
+
"quick-push": () => pushCommand2().parseAsync(["", "", "-m", "Quick sync"]),
|
|
6580
|
+
"push-message": async () => {
|
|
6581
|
+
const message = await text4({
|
|
6582
|
+
message: brand("Commit message:"),
|
|
6583
|
+
placeholder: "Describe your changes..."
|
|
6584
|
+
});
|
|
6585
|
+
if (isCancel6(message)) return;
|
|
6586
|
+
await pushCommand2().parseAsync(["", "", "-m", message]);
|
|
6587
|
+
},
|
|
6588
|
+
pull: () => pullCommand2().parseAsync(["", "", "-y"]),
|
|
6589
|
+
log: () => logCommand2().parseAsync(["", ""]),
|
|
6081
6590
|
status: () => statusCommand2().parseAsync(["", ""]),
|
|
6082
6591
|
whoami: () => whoamiCommand2().parseAsync(["", ""]),
|
|
6083
6592
|
"device:add": () => deviceCommand2().parseAsync(["", "", "add"]),
|
|
@@ -6088,8 +6597,8 @@ async function executeCommand(command) {
|
|
|
6088
6597
|
await deviceCommand2().parseAsync(["", "", "remove", deviceId]);
|
|
6089
6598
|
},
|
|
6090
6599
|
"device:rename": async () => {
|
|
6091
|
-
const name = await
|
|
6092
|
-
if (
|
|
6600
|
+
const name = await text4({ message: `${brand("New device name")}:` });
|
|
6601
|
+
if (isCancel6(name)) return;
|
|
6093
6602
|
await deviceCommand2().parseAsync(["", "", "rename", name]);
|
|
6094
6603
|
}
|
|
6095
6604
|
};
|
|
@@ -6102,7 +6611,7 @@ async function executeCommand(command) {
|
|
|
6102
6611
|
// src/index.ts
|
|
6103
6612
|
init_config();
|
|
6104
6613
|
function run() {
|
|
6105
|
-
const program = new
|
|
6614
|
+
const program = new Command10();
|
|
6106
6615
|
program.name("claude-sync").description("Sync Claude Code sessions across machines \u2014 claude-sync.com").version("0.1.0").option("--menu", "Open interactive menu").action(async (options) => {
|
|
6107
6616
|
if (options.menu) {
|
|
6108
6617
|
await runTui();
|
|
@@ -6118,6 +6627,9 @@ function run() {
|
|
|
6118
6627
|
program.addCommand(loginCommand());
|
|
6119
6628
|
program.addCommand(logoutCommand());
|
|
6120
6629
|
program.addCommand(deviceCommand());
|
|
6630
|
+
program.addCommand(pushCommand());
|
|
6631
|
+
program.addCommand(pullCommand());
|
|
6632
|
+
program.addCommand(logCommand());
|
|
6121
6633
|
program.addCommand(syncCommand());
|
|
6122
6634
|
program.addCommand(statusCommand());
|
|
6123
6635
|
program.addCommand(whoamiCommand());
|