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