aiblueprint-cli 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +18 -1
  2. package/dist/cli.js +250 -249
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -547,10 +547,27 @@ aiblueprint claude-code pro setup
547
547
  # Check premium status
548
548
  aiblueprint claude-code pro status
549
549
 
550
- # Update premium configs
550
+ # Update premium configs (overwrites all)
551
551
  aiblueprint claude-code pro update
552
+
553
+ # Sync premium configs (selective update)
554
+ aiblueprint claude-code pro sync
552
555
  ```
553
556
 
557
+ ### Sync Command
558
+
559
+ The `sync` command provides intelligent selective updates:
560
+
561
+ ```bash
562
+ aiblueprint claude-code pro sync
563
+ ```
564
+
565
+ **Features:**
566
+ - 🔍 **Diff Analysis** - Compares your local config with the latest premium version
567
+ - 📝 **Categorized Changes** - Shows NEW and MODIFIED items separately
568
+ - ✅ **Selective Update** - Choose exactly which files/folders to update
569
+ - 📁 **Smart Grouping** - Commands and agents shown individually, skills and scripts grouped as folders
570
+
554
571
  **Learn more:** https://mlv.sh/claude-cli
555
572
 
556
573
  ---
package/dist/cli.js CHANGED
@@ -33191,20 +33191,23 @@ async function checkAndInstallDependencies() {
33191
33191
  }
33192
33192
  }
33193
33193
  }
33194
- async function installStatuslineDependencies(claudeDir) {
33195
- const statuslineDir = path4.join(claudeDir, "scripts/statusline");
33194
+ async function installScriptsDependencies(claudeDir) {
33195
+ const scriptsDir = path4.join(claudeDir, "scripts");
33196
33196
  console.log(source_default.yellow(`
33197
- Installing statusline dependencies...`));
33197
+ Installing scripts dependencies...`));
33198
33198
  try {
33199
33199
  execSync("bun install", {
33200
- cwd: statuslineDir,
33200
+ cwd: scriptsDir,
33201
33201
  stdio: "inherit"
33202
33202
  });
33203
- console.log(source_default.green(" ✓ Statusline dependencies installed"));
33203
+ console.log(source_default.green(" ✓ Scripts dependencies installed"));
33204
33204
  } catch (error) {
33205
- console.log(source_default.red(" Failed to install statusline dependencies. Please run 'bun install' manually in ~/.claude/scripts/statusline"));
33205
+ console.log(source_default.red(" Failed to install scripts dependencies. Please run 'bun install' manually in ~/.claude/scripts"));
33206
33206
  }
33207
33207
  }
33208
+ async function installStatuslineDependencies(claudeDir) {
33209
+ await installScriptsDependencies(claudeDir);
33210
+ }
33208
33211
 
33209
33212
  // src/commands/setup/settings.ts
33210
33213
  var import_fs_extra3 = __toESM(require_lib4(), 1);
@@ -34917,6 +34920,10 @@ var de = () => {
34917
34920
  return process.on("uncaughtExceptionMonitor", () => $2(2)), process.on("unhandledRejection", () => $2(2)), process.on("SIGINT", () => $2(1)), process.on("SIGTERM", () => $2(1)), process.on("exit", $2), { start: l2, stop: u, message: m2 };
34918
34921
  };
34919
34922
 
34923
+ // src/commands/pro.ts
34924
+ import os10 from "os";
34925
+ import path16 from "path";
34926
+
34920
34927
  // src/lib/pro-installer.ts
34921
34928
  var import_fs_extra12 = __toESM(require_lib4(), 1);
34922
34929
  import os8 from "os";
@@ -34945,7 +34952,7 @@ async function downloadFromPrivateGitHub(repo, branch, relativePath, targetPath,
34945
34952
  return false;
34946
34953
  }
34947
34954
  }
34948
- async function downloadDirectoryFromPrivateGitHub(repo, branch, dirPath, targetDir, githubToken) {
34955
+ async function downloadDirectoryFromPrivateGitHub(repo, branch, dirPath, targetDir, githubToken, onProgress) {
34949
34956
  try {
34950
34957
  const apiUrl = `https://api.github.com/repos/${repo}/contents/${dirPath}?ref=${branch}`;
34951
34958
  const response = await fetch(apiUrl, {
@@ -34967,10 +34974,12 @@ async function downloadDirectoryFromPrivateGitHub(repo, branch, dirPath, targetD
34967
34974
  for (const file of files) {
34968
34975
  const relativePath = dirPath ? `${dirPath}/${file.name}` : file.name;
34969
34976
  const targetPath = path14.join(targetDir, file.name);
34977
+ const displayPath = relativePath.replace("claude-code-config/", "");
34970
34978
  if (file.type === "file") {
34979
+ onProgress?.(displayPath, "file");
34971
34980
  await downloadFromPrivateGitHub(repo, branch, relativePath, targetPath, githubToken);
34972
34981
  } else if (file.type === "dir") {
34973
- await downloadDirectoryFromPrivateGitHub(repo, branch, relativePath, targetPath, githubToken);
34982
+ await downloadDirectoryFromPrivateGitHub(repo, branch, relativePath, targetPath, githubToken, onProgress);
34974
34983
  }
34975
34984
  }
34976
34985
  return true;
@@ -34980,11 +34989,11 @@ async function downloadDirectoryFromPrivateGitHub(repo, branch, dirPath, targetD
34980
34989
  }
34981
34990
  }
34982
34991
  async function installProConfigs(options) {
34983
- const { githubToken, claudeCodeFolder } = options;
34992
+ const { githubToken, claudeCodeFolder, onProgress } = options;
34984
34993
  const claudeFolder = claudeCodeFolder || path14.join(os8.homedir(), ".claude");
34985
34994
  const tempDir = path14.join(os8.tmpdir(), `aiblueprint-premium-${Date.now()}`);
34986
34995
  try {
34987
- const success = await downloadDirectoryFromPrivateGitHub(PREMIUM_REPO, PREMIUM_BRANCH, "claude-code-config", tempDir, githubToken);
34996
+ const success = await downloadDirectoryFromPrivateGitHub(PREMIUM_REPO, PREMIUM_BRANCH, "claude-code-config", tempDir, githubToken, onProgress);
34988
34997
  if (!success) {
34989
34998
  throw new Error("Failed to download premium configurations");
34990
34999
  }
@@ -34992,66 +35001,45 @@ async function installProConfigs(options) {
34992
35001
  overwrite: true,
34993
35002
  recursive: true
34994
35003
  });
34995
- console.log(`✓ Premium configurations installed to ${claudeFolder}`);
34996
35004
  } catch (error) {
34997
35005
  throw new Error(`Failed to install premium configs: ${error instanceof Error ? error.message : "Unknown error"}`);
34998
35006
  } finally {
34999
35007
  try {
35000
35008
  await import_fs_extra12.default.remove(tempDir);
35001
- } catch (error) {
35009
+ } catch {
35002
35010
  }
35003
35011
  }
35004
35012
  }
35005
35013
 
35006
- // src/lib/setup-helper.ts
35014
+ // src/lib/token-storage.ts
35007
35015
  var import_fs_extra13 = __toESM(require_lib4(), 1);
35008
- import path15 from "path";
35009
35016
  import os9 from "os";
35010
- async function installBasicConfigs(options = {}, skipStatusline = false) {
35011
- const claudeDir = options.claudeCodeFolder || path15.join(os9.homedir(), ".claude");
35012
- await import_fs_extra13.default.ensureDir(claudeDir);
35013
- console.log(source_default.gray("\uD83D\uDCE6 Installing free configurations..."));
35014
- console.log(source_default.gray(" • Commands..."));
35015
- await downloadDirectoryFromGitHub("commands", path15.join(claudeDir, "commands"));
35016
- console.log(source_default.gray(" • Agents..."));
35017
- await downloadDirectoryFromGitHub("agents", path15.join(claudeDir, "agents"));
35018
- if (!skipStatusline) {
35019
- console.log(source_default.gray(" • Statusline (basic)..."));
35020
- await downloadDirectoryFromGitHub("scripts/statusline", path15.join(claudeDir, "scripts", "statusline"));
35021
- }
35022
- console.log(source_default.green("✓ Free configurations installed"));
35023
- return claudeDir;
35024
- }
35025
-
35026
- // src/lib/token-storage.ts
35027
- var import_fs_extra14 = __toESM(require_lib4(), 1);
35028
- import os10 from "os";
35029
- import path16 from "path";
35017
+ import path15 from "path";
35030
35018
  function getConfigDir() {
35031
- const platform = os10.platform();
35019
+ const platform = os9.platform();
35032
35020
  if (platform === "win32") {
35033
- const appData = process.env.APPDATA || path16.join(os10.homedir(), "AppData", "Roaming");
35034
- return path16.join(appData, "aiblueprint");
35021
+ const appData = process.env.APPDATA || path15.join(os9.homedir(), "AppData", "Roaming");
35022
+ return path15.join(appData, "aiblueprint");
35035
35023
  } else {
35036
- const configHome = process.env.XDG_CONFIG_HOME || path16.join(os10.homedir(), ".config");
35037
- return path16.join(configHome, "aiblueprint");
35024
+ const configHome = process.env.XDG_CONFIG_HOME || path15.join(os9.homedir(), ".config");
35025
+ return path15.join(configHome, "aiblueprint");
35038
35026
  }
35039
35027
  }
35040
35028
  function getTokenFilePath() {
35041
- return path16.join(getConfigDir(), "token.txt");
35029
+ return path15.join(getConfigDir(), "token.txt");
35042
35030
  }
35043
35031
  async function saveToken(githubToken) {
35044
35032
  const tokenFile = getTokenFilePath();
35045
- await import_fs_extra14.default.ensureDir(path16.dirname(tokenFile));
35046
- await import_fs_extra14.default.writeFile(tokenFile, githubToken, { mode: 384 });
35033
+ await import_fs_extra13.default.ensureDir(path15.dirname(tokenFile));
35034
+ await import_fs_extra13.default.writeFile(tokenFile, githubToken, { mode: 384 });
35047
35035
  }
35048
35036
  async function getToken() {
35049
35037
  const tokenFile = getTokenFilePath();
35050
- if (!await import_fs_extra14.default.pathExists(tokenFile)) {
35038
+ if (!await import_fs_extra13.default.pathExists(tokenFile)) {
35051
35039
  return null;
35052
35040
  }
35053
35041
  try {
35054
- const token = await import_fs_extra14.default.readFile(tokenFile, "utf-8");
35042
+ const token = await import_fs_extra13.default.readFile(tokenFile, "utf-8");
35055
35043
  return token.trim();
35056
35044
  } catch (error) {
35057
35045
  return null;
@@ -35060,13 +35048,12 @@ async function getToken() {
35060
35048
  function getTokenInfo() {
35061
35049
  return {
35062
35050
  path: getTokenFilePath(),
35063
- platform: os10.platform()
35051
+ platform: os9.platform()
35064
35052
  };
35065
35053
  }
35066
35054
 
35067
35055
  // src/commands/pro.ts
35068
- var import_fs_extra15 = __toESM(require_lib4(), 1);
35069
- import path17 from "path";
35056
+ var import_fs_extra14 = __toESM(require_lib4(), 1);
35070
35057
  var API_URL = "https://codeline.app/api/products";
35071
35058
  var PRODUCT_ID = "prd_XJVgxVPbGG";
35072
35059
  async function countInstalledItems(claudeDir) {
@@ -35076,27 +35063,27 @@ async function countInstalledItems(claudeDir) {
35076
35063
  skills: 0
35077
35064
  };
35078
35065
  try {
35079
- const commandsDir = path17.join(claudeDir, "commands");
35080
- if (await import_fs_extra15.default.pathExists(commandsDir)) {
35081
- const files = await import_fs_extra15.default.readdir(commandsDir);
35066
+ const commandsDir = path16.join(claudeDir, "commands");
35067
+ if (await import_fs_extra14.default.pathExists(commandsDir)) {
35068
+ const files = await import_fs_extra14.default.readdir(commandsDir);
35082
35069
  counts.commands = files.filter((f3) => f3.endsWith(".md")).length;
35083
35070
  }
35084
35071
  } catch {
35085
35072
  }
35086
35073
  try {
35087
- const agentsDir = path17.join(claudeDir, "agents");
35088
- if (await import_fs_extra15.default.pathExists(agentsDir)) {
35089
- const files = await import_fs_extra15.default.readdir(agentsDir);
35074
+ const agentsDir = path16.join(claudeDir, "agents");
35075
+ if (await import_fs_extra14.default.pathExists(agentsDir)) {
35076
+ const files = await import_fs_extra14.default.readdir(agentsDir);
35090
35077
  counts.agents = files.filter((f3) => f3.endsWith(".md")).length;
35091
35078
  }
35092
35079
  } catch {
35093
35080
  }
35094
35081
  try {
35095
- const skillsDir = path17.join(claudeDir, "skills");
35096
- if (await import_fs_extra15.default.pathExists(skillsDir)) {
35097
- const items = await import_fs_extra15.default.readdir(skillsDir);
35082
+ const skillsDir = path16.join(claudeDir, "skills");
35083
+ if (await import_fs_extra14.default.pathExists(skillsDir)) {
35084
+ const items = await import_fs_extra14.default.readdir(skillsDir);
35098
35085
  const dirs = await Promise.all(items.map(async (item) => {
35099
- const stat = await import_fs_extra15.default.stat(path17.join(skillsDir, item));
35086
+ const stat = await import_fs_extra14.default.stat(path16.join(skillsDir, item));
35100
35087
  return stat.isDirectory();
35101
35088
  }));
35102
35089
  counts.skills = dirs.filter(Boolean).length;
@@ -35203,16 +35190,24 @@ async function proSetupCommand(options = {}) {
35203
35190
  $e(source_default.red("❌ Not activated"));
35204
35191
  process.exit(1);
35205
35192
  }
35193
+ const claudeDir = options.folder ? path16.resolve(options.folder) : path16.join(os10.homedir(), ".claude");
35206
35194
  const spinner = de();
35207
- spinner.start("Installing free configurations...");
35208
- const claudeDir = await installBasicConfigs({ claudeCodeFolder: options.folder }, true);
35209
- spinner.stop("Free configurations installed");
35195
+ const onProgress = (file, type) => {
35196
+ spinner.message(`Installing: ${source_default.cyan(file)} ${source_default.gray(`(${type})`)}`);
35197
+ };
35210
35198
  spinner.start("Installing premium configurations...");
35211
35199
  await installProConfigs({
35212
35200
  githubToken,
35213
- claudeCodeFolder: claudeDir
35201
+ claudeCodeFolder: claudeDir,
35202
+ onProgress
35214
35203
  });
35215
35204
  spinner.stop("Premium configurations installed");
35205
+ spinner.start("Checking global dependencies...");
35206
+ await checkAndInstallDependencies();
35207
+ spinner.stop("Global dependencies ready");
35208
+ spinner.start("Installing scripts dependencies...");
35209
+ await installScriptsDependencies(claudeDir);
35210
+ spinner.stop("Scripts dependencies installed");
35216
35211
  spinner.start("Setting up shell shortcuts...");
35217
35212
  await setupShellShortcuts();
35218
35213
  spinner.stop("Shell shortcuts configured");
@@ -35273,11 +35268,11 @@ async function proUpdateCommand(options = {}) {
35273
35268
 
35274
35269
  // src/commands/sync.ts
35275
35270
  import os11 from "os";
35276
- import path19 from "path";
35271
+ import path18 from "path";
35277
35272
 
35278
35273
  // src/lib/sync-utils.ts
35279
- var import_fs_extra16 = __toESM(require_lib4(), 1);
35280
- import path18 from "path";
35274
+ var import_fs_extra15 = __toESM(require_lib4(), 1);
35275
+ import path17 from "path";
35281
35276
  import crypto from "crypto";
35282
35277
  var PREMIUM_REPO2 = "Melvynx/aiblueprint-cli-premium";
35283
35278
  var PREMIUM_BRANCH2 = "main";
@@ -35307,147 +35302,140 @@ async function listRemoteDirectory(dirPath, githubToken) {
35307
35302
  }
35308
35303
  return files;
35309
35304
  }
35310
- async function computeLocalFileSha(filePath) {
35311
- try {
35312
- const content = await import_fs_extra16.default.readFile(filePath);
35313
- return computeFileSha(content);
35314
- } catch {
35315
- return null;
35305
+ async function listRemoteFilesRecursive(dirPath, githubToken, basePath = "") {
35306
+ const results = [];
35307
+ const files = await listRemoteDirectory(dirPath, githubToken);
35308
+ for (const file of files) {
35309
+ const relativePath = basePath ? `${basePath}/${file.name}` : file.name;
35310
+ if (file.type === "file") {
35311
+ results.push({ path: relativePath, sha: file.sha, isFolder: false });
35312
+ } else if (file.type === "dir") {
35313
+ results.push({ path: relativePath, sha: "", isFolder: true });
35314
+ const subFiles = await listRemoteFilesRecursive(`${dirPath}/${file.name}`, githubToken, relativePath);
35315
+ results.push(...subFiles);
35316
+ }
35316
35317
  }
35318
+ return results;
35317
35319
  }
35318
- async function computeFolderSha(folderPath) {
35320
+ async function computeLocalFileSha(filePath) {
35319
35321
  try {
35320
- if (!await import_fs_extra16.default.pathExists(folderPath)) {
35321
- return null;
35322
- }
35323
- const files = await getAllFilesRecursive(folderPath);
35324
- if (files.length === 0) {
35325
- return null;
35326
- }
35327
- const hashes = [];
35328
- for (const file of files.sort()) {
35329
- const content = await import_fs_extra16.default.readFile(file);
35330
- hashes.push(computeFileSha(content));
35331
- }
35332
- return crypto.createHash("sha1").update(hashes.join("")).digest("hex");
35322
+ const content = await import_fs_extra15.default.readFile(filePath);
35323
+ return computeFileSha(content);
35333
35324
  } catch {
35334
35325
  return null;
35335
35326
  }
35336
35327
  }
35337
- async function getAllFilesRecursive(dir) {
35328
+ async function listLocalFiles(dir) {
35338
35329
  const files = [];
35339
- const items = await import_fs_extra16.default.readdir(dir);
35330
+ if (!await import_fs_extra15.default.pathExists(dir)) {
35331
+ return files;
35332
+ }
35333
+ const items = await import_fs_extra15.default.readdir(dir);
35340
35334
  for (const item of items) {
35341
- const fullPath = path18.join(dir, item);
35342
- const stat = await import_fs_extra16.default.stat(fullPath);
35335
+ const fullPath = path17.join(dir, item);
35336
+ const stat = await import_fs_extra15.default.stat(fullPath);
35343
35337
  if (stat.isDirectory()) {
35344
- const subFiles = await getAllFilesRecursive(fullPath);
35338
+ files.push(item);
35339
+ const subFiles = await listLocalFilesRecursive(fullPath, item);
35345
35340
  files.push(...subFiles);
35346
35341
  } else {
35347
- files.push(fullPath);
35342
+ files.push(item);
35348
35343
  }
35349
35344
  }
35350
35345
  return files;
35351
35346
  }
35352
- async function computeRemoteFolderSha(dirPath, githubToken) {
35353
- const hashes = [];
35354
- await collectRemoteFolderHashes(dirPath, githubToken, hashes);
35355
- hashes.sort();
35356
- return crypto.createHash("sha1").update(hashes.join("")).digest("hex");
35357
- }
35358
- async function collectRemoteFolderHashes(dirPath, githubToken, hashes) {
35359
- const files = await listRemoteDirectory(dirPath, githubToken);
35360
- for (const file of files) {
35361
- if (file.type === "file") {
35362
- hashes.push(file.sha);
35363
- } else if (file.type === "dir") {
35364
- await collectRemoteFolderHashes(`${dirPath}/${file.name}`, githubToken, hashes);
35347
+ async function listLocalFilesRecursive(dir, basePath) {
35348
+ const files = [];
35349
+ const items = await import_fs_extra15.default.readdir(dir);
35350
+ for (const item of items) {
35351
+ const fullPath = path17.join(dir, item);
35352
+ const relativePath = `${basePath}/${item}`;
35353
+ const stat = await import_fs_extra15.default.stat(fullPath);
35354
+ if (stat.isDirectory()) {
35355
+ files.push(relativePath);
35356
+ const subFiles = await listLocalFilesRecursive(fullPath, relativePath);
35357
+ files.push(...subFiles);
35358
+ } else {
35359
+ files.push(relativePath);
35365
35360
  }
35366
35361
  }
35362
+ return files;
35367
35363
  }
35368
- async function analyzeSyncChanges(claudeDir, githubToken) {
35364
+ async function analyzeCategory(category, claudeDir, githubToken) {
35369
35365
  const items = [];
35370
- const commandsRemote = await listRemoteDirectory("commands", githubToken);
35371
- for (const file of commandsRemote) {
35372
- if (file.type === "file" && file.name.endsWith(".md")) {
35373
- const localPath = path18.join(claudeDir, "commands", file.name);
35374
- const localSha = await computeLocalFileSha(localPath);
35375
- let status = "new";
35376
- if (localSha) {
35377
- status = localSha === file.sha ? "unchanged" : "modified";
35378
- }
35366
+ const localDir = path17.join(claudeDir, category);
35367
+ const remoteFiles = await listRemoteFilesRecursive(category, githubToken);
35368
+ const localFiles = await listLocalFiles(localDir);
35369
+ const remoteSet = new Map;
35370
+ for (const rf of remoteFiles) {
35371
+ remoteSet.set(rf.path, { sha: rf.sha, isFolder: rf.isFolder });
35372
+ }
35373
+ const localSet = new Set(localFiles);
35374
+ for (const [remotePath, { sha, isFolder }] of remoteSet) {
35375
+ const localPath = path17.join(localDir, remotePath);
35376
+ if (isFolder) {
35377
+ continue;
35378
+ }
35379
+ if (!localSet.has(remotePath)) {
35379
35380
  items.push({
35380
- name: file.name.replace(".md", ""),
35381
- relativePath: `commands/${file.name}`,
35382
- type: "file",
35383
- status,
35384
- remoteSha: file.sha,
35385
- localSha: localSha || undefined,
35386
- category: "commands"
35381
+ name: remotePath,
35382
+ relativePath: `${category}/${remotePath}`,
35383
+ status: "new",
35384
+ category
35387
35385
  });
35388
- }
35389
- }
35390
- const agentsRemote = await listRemoteDirectory("agents", githubToken);
35391
- for (const file of agentsRemote) {
35392
- if (file.type === "file" && file.name.endsWith(".md")) {
35393
- const localPath = path18.join(claudeDir, "agents", file.name);
35386
+ } else {
35394
35387
  const localSha = await computeLocalFileSha(localPath);
35395
- let status = "new";
35396
- if (localSha) {
35397
- status = localSha === file.sha ? "unchanged" : "modified";
35388
+ if (localSha !== sha) {
35389
+ items.push({
35390
+ name: remotePath,
35391
+ relativePath: `${category}/${remotePath}`,
35392
+ status: "modified",
35393
+ category
35394
+ });
35395
+ } else {
35396
+ items.push({
35397
+ name: remotePath,
35398
+ relativePath: `${category}/${remotePath}`,
35399
+ status: "unchanged",
35400
+ category
35401
+ });
35398
35402
  }
35399
- items.push({
35400
- name: file.name.replace(".md", ""),
35401
- relativePath: `agents/${file.name}`,
35402
- type: "file",
35403
- status,
35404
- remoteSha: file.sha,
35405
- localSha: localSha || undefined,
35406
- category: "agents"
35407
- });
35408
35403
  }
35409
35404
  }
35410
- const skillsRemote = await listRemoteDirectory("skills", githubToken);
35411
- if (skillsRemote.length > 0) {
35412
- const remoteSha = await computeRemoteFolderSha("skills", githubToken);
35413
- const localSha = await computeFolderSha(path18.join(claudeDir, "skills"));
35414
- let status = "new";
35415
- if (localSha) {
35416
- status = localSha === remoteSha ? "unchanged" : "modified";
35417
- }
35418
- items.push({
35419
- name: "skills",
35420
- relativePath: "skills",
35421
- type: "folder",
35422
- status,
35423
- remoteSha,
35424
- localSha: localSha || undefined,
35425
- category: "skills"
35426
- });
35405
+ for (const localPath of localSet) {
35406
+ if (!remoteSet.has(localPath)) {
35407
+ const fullPath = path17.join(localDir, localPath);
35408
+ const stat = await import_fs_extra15.default.stat(fullPath).catch(() => null);
35409
+ if (stat && !stat.isDirectory()) {
35410
+ items.push({
35411
+ name: localPath,
35412
+ relativePath: `${category}/${localPath}`,
35413
+ status: "deleted",
35414
+ category
35415
+ });
35416
+ }
35417
+ }
35427
35418
  }
35428
- const scriptsRemote = await listRemoteDirectory("scripts", githubToken);
35429
- if (scriptsRemote.length > 0) {
35430
- const remoteSha = await computeRemoteFolderSha("scripts", githubToken);
35431
- const localSha = await computeFolderSha(path18.join(claudeDir, "scripts"));
35432
- let status = "new";
35433
- if (localSha) {
35434
- status = localSha === remoteSha ? "unchanged" : "modified";
35435
- }
35436
- items.push({
35437
- name: "scripts",
35438
- relativePath: "scripts",
35439
- type: "folder",
35440
- status,
35441
- remoteSha,
35442
- localSha: localSha || undefined,
35443
- category: "scripts"
35444
- });
35419
+ return items;
35420
+ }
35421
+ async function analyzeSyncChanges(claudeDir, githubToken) {
35422
+ const allItems = [];
35423
+ const categories = [
35424
+ "commands",
35425
+ "agents",
35426
+ "skills",
35427
+ "scripts"
35428
+ ];
35429
+ for (const category of categories) {
35430
+ const items = await analyzeCategory(category, claudeDir, githubToken);
35431
+ allItems.push(...items);
35445
35432
  }
35446
35433
  return {
35447
- items,
35448
- newCount: items.filter((i) => i.status === "new").length,
35449
- modifiedCount: items.filter((i) => i.status === "modified").length,
35450
- unchangedCount: items.filter((i) => i.status === "unchanged").length
35434
+ items: allItems,
35435
+ newCount: allItems.filter((i) => i.status === "new").length,
35436
+ modifiedCount: allItems.filter((i) => i.status === "modified").length,
35437
+ deletedCount: allItems.filter((i) => i.status === "deleted").length,
35438
+ unchangedCount: allItems.filter((i) => i.status === "unchanged").length
35451
35439
  };
35452
35440
  }
35453
35441
  async function downloadFromPrivateGitHub2(relativePath, targetPath, githubToken) {
@@ -35463,45 +35451,30 @@ async function downloadFromPrivateGitHub2(relativePath, targetPath, githubToken)
35463
35451
  return false;
35464
35452
  }
35465
35453
  const content = await response.arrayBuffer();
35466
- await import_fs_extra16.default.ensureDir(path18.dirname(targetPath));
35467
- await import_fs_extra16.default.writeFile(targetPath, Buffer.from(content));
35468
- return true;
35469
- } catch {
35470
- return false;
35471
- }
35472
- }
35473
- async function downloadDirectoryFromPrivateGitHub2(dirPath, targetDir, githubToken) {
35474
- try {
35475
- const files = await listRemoteDirectory(dirPath, githubToken);
35476
- await import_fs_extra16.default.ensureDir(targetDir);
35477
- for (const file of files) {
35478
- const relativePath = `${dirPath}/${file.name}`;
35479
- const targetPath = path18.join(targetDir, file.name);
35480
- if (file.type === "file") {
35481
- await downloadFromPrivateGitHub2(relativePath, targetPath, githubToken);
35482
- } else if (file.type === "dir") {
35483
- await downloadDirectoryFromPrivateGitHub2(relativePath, targetPath, githubToken);
35484
- }
35485
- }
35454
+ await import_fs_extra15.default.ensureDir(path17.dirname(targetPath));
35455
+ await import_fs_extra15.default.writeFile(targetPath, Buffer.from(content));
35486
35456
  return true;
35487
35457
  } catch {
35488
35458
  return false;
35489
35459
  }
35490
35460
  }
35491
- async function syncSelectedItems(claudeDir, items, githubToken) {
35461
+ async function syncSelectedItems(claudeDir, items, githubToken, onProgress) {
35492
35462
  let success = 0;
35493
35463
  let failed = 0;
35464
+ let deleted = 0;
35494
35465
  for (const item of items) {
35495
- const targetPath = path18.join(claudeDir, item.relativePath);
35496
- if (item.type === "file") {
35497
- const ok = await downloadFromPrivateGitHub2(item.relativePath, targetPath, githubToken);
35498
- if (ok) {
35499
- success++;
35500
- } else {
35466
+ const targetPath = path17.join(claudeDir, item.relativePath);
35467
+ if (item.status === "deleted") {
35468
+ onProgress?.(item.relativePath, "deleting");
35469
+ try {
35470
+ await import_fs_extra15.default.remove(targetPath);
35471
+ deleted++;
35472
+ } catch {
35501
35473
  failed++;
35502
35474
  }
35503
35475
  } else {
35504
- const ok = await downloadDirectoryFromPrivateGitHub2(item.relativePath, targetPath, githubToken);
35476
+ onProgress?.(item.relativePath, item.status === "new" ? "adding" : "updating");
35477
+ const ok = await downloadFromPrivateGitHub2(item.relativePath, targetPath, githubToken);
35505
35478
  if (ok) {
35506
35479
  success++;
35507
35480
  } else {
@@ -35509,10 +35482,34 @@ async function syncSelectedItems(claudeDir, items, githubToken) {
35509
35482
  }
35510
35483
  }
35511
35484
  }
35512
- return { success, failed };
35485
+ return { success, failed, deleted };
35513
35486
  }
35514
35487
 
35515
35488
  // src/commands/sync.ts
35489
+ function formatItem(item) {
35490
+ const icons = {
35491
+ new: "\uD83C\uDD95",
35492
+ modified: "\uD83D\uDCDD",
35493
+ deleted: "\uD83D\uDDD1️",
35494
+ unchanged: "✅"
35495
+ };
35496
+ const colors12 = {
35497
+ new: source_default.green,
35498
+ modified: source_default.yellow,
35499
+ deleted: source_default.red,
35500
+ unchanged: source_default.gray
35501
+ };
35502
+ return `${icons[item.status]} ${colors12[item.status](item.relativePath)}`;
35503
+ }
35504
+ function groupByCategory(items) {
35505
+ const grouped = new Map;
35506
+ for (const item of items) {
35507
+ const existing = grouped.get(item.category) || [];
35508
+ existing.push(item);
35509
+ grouped.set(item.category, existing);
35510
+ }
35511
+ return grouped;
35512
+ }
35516
35513
  async function proSyncCommand(options = {}) {
35517
35514
  oe(source_default.blue("\uD83D\uDD04 Sync Premium Configurations"));
35518
35515
  try {
@@ -35523,37 +35520,38 @@ async function proSyncCommand(options = {}) {
35523
35520
  $e(source_default.red("❌ Not activated"));
35524
35521
  process.exit(1);
35525
35522
  }
35526
- const claudeDir = options.folder ? path19.resolve(options.folder) : path19.join(os11.homedir(), ".claude");
35523
+ const claudeDir = options.folder ? path18.resolve(options.folder) : path18.join(os11.homedir(), ".claude");
35527
35524
  const spinner = de();
35528
35525
  spinner.start("Analyzing changes...");
35529
35526
  const result = await analyzeSyncChanges(claudeDir, githubToken);
35530
35527
  spinner.stop("Analysis complete");
35531
- if (result.newCount === 0 && result.modifiedCount === 0) {
35528
+ const changedItems = result.items.filter((i) => i.status !== "unchanged");
35529
+ if (changedItems.length === 0) {
35532
35530
  f2.success("Everything is up to date!");
35533
35531
  $e(source_default.green("✅ No changes needed"));
35534
35532
  return;
35535
35533
  }
35536
- f2.info(`Found ${result.newCount} new, ${result.modifiedCount} modified, ${result.unchangedCount} unchanged`);
35537
- const newItems = result.items.filter((i) => i.status === "new");
35538
- const modifiedItems = result.items.filter((i) => i.status === "modified");
35539
- const choices = [];
35540
- if (newItems.length > 0) {
35541
- for (const item of newItems) {
35542
- choices.push({
35543
- value: item,
35544
- label: `\uD83C\uDD95 ${item.name}`,
35545
- hint: `${item.category} (new ${item.type})`
35546
- });
35534
+ f2.info(`Found: ${source_default.green(`${result.newCount} new`)}, ${source_default.yellow(`${result.modifiedCount} modified`)}, ${source_default.red(`${result.deletedCount} to remove`)}, ${source_default.gray(`${result.unchangedCount} unchanged`)}`);
35535
+ f2.message("");
35536
+ f2.message(source_default.bold("Changes by category:"));
35537
+ const grouped = groupByCategory(changedItems);
35538
+ for (const [category, items] of grouped) {
35539
+ f2.message("");
35540
+ f2.message(source_default.cyan.bold(` ${category.toUpperCase()}`));
35541
+ for (const item of items) {
35542
+ f2.message(` ${formatItem(item)}`);
35547
35543
  }
35548
35544
  }
35549
- if (modifiedItems.length > 0) {
35550
- for (const item of modifiedItems) {
35551
- choices.push({
35552
- value: item,
35553
- label: `\uD83D\uDCDD ${item.name}`,
35554
- hint: `${item.category} (modified ${item.type})`
35555
- });
35556
- }
35545
+ f2.message("");
35546
+ const choices = [];
35547
+ for (const item of changedItems) {
35548
+ const icons = { new: "\uD83C\uDD95", modified: "\uD83D\uDCDD", deleted: "\uD83D\uDDD1️", unchanged: "" };
35549
+ const actions = { new: "add", modified: "update", deleted: "remove", unchanged: "" };
35550
+ choices.push({
35551
+ value: item,
35552
+ label: `${icons[item.status]} ${item.relativePath}`,
35553
+ hint: actions[item.status]
35554
+ });
35557
35555
  }
35558
35556
  const selected = await ae({
35559
35557
  message: "Select items to sync:",
@@ -35571,38 +35569,41 @@ async function proSyncCommand(options = {}) {
35571
35569
  $e(source_default.yellow("⚠️ Nothing to sync"));
35572
35570
  return;
35573
35571
  }
35572
+ const toAdd = selectedItems.filter((i) => i.status === "new").length;
35573
+ const toUpdate = selectedItems.filter((i) => i.status === "modified").length;
35574
+ const toRemove = selectedItems.filter((i) => i.status === "deleted").length;
35575
+ const summary = [
35576
+ toAdd > 0 ? `add ${toAdd}` : "",
35577
+ toUpdate > 0 ? `update ${toUpdate}` : "",
35578
+ toRemove > 0 ? `remove ${toRemove}` : ""
35579
+ ].filter(Boolean).join(", ");
35574
35580
  const confirmResult = await se({
35575
- message: `Sync ${selectedItems.length} item(s)?`,
35581
+ message: `Proceed? (${summary})`,
35576
35582
  initialValue: true
35577
35583
  });
35578
35584
  if (lD(confirmResult) || !confirmResult) {
35579
35585
  ue("Sync cancelled");
35580
35586
  process.exit(0);
35581
35587
  }
35582
- spinner.start(`Syncing ${selectedItems.length} item(s)...`);
35583
- const syncResult = await syncSelectedItems(claudeDir, selectedItems, githubToken);
35588
+ spinner.start("Syncing...");
35589
+ const syncResult = await syncSelectedItems(claudeDir, selectedItems, githubToken, (file, action) => {
35590
+ spinner.message(`${action}: ${source_default.cyan(file)}`);
35591
+ });
35584
35592
  spinner.stop("Sync complete");
35585
- if (syncResult.failed > 0) {
35586
- f2.warn(`${syncResult.success} succeeded, ${syncResult.failed} failed`);
35587
- } else {
35588
- f2.success(`${syncResult.success} item(s) synced successfully`);
35593
+ const results = [];
35594
+ if (syncResult.success > 0)
35595
+ results.push(source_default.green(`${syncResult.success} added/updated`));
35596
+ if (syncResult.deleted > 0)
35597
+ results.push(source_default.red(`${syncResult.deleted} removed`));
35598
+ if (syncResult.failed > 0)
35599
+ results.push(source_default.yellow(`${syncResult.failed} failed`));
35600
+ f2.success(results.join(", "));
35601
+ const scriptsWereSynced = selectedItems.some((i) => i.category === "scripts");
35602
+ if (scriptsWereSynced) {
35603
+ spinner.start("Installing scripts dependencies...");
35604
+ await installScriptsDependencies(claudeDir);
35605
+ spinner.stop("Scripts dependencies installed");
35589
35606
  }
35590
- const syncedByCategory = {
35591
- commands: selectedItems.filter((i) => i.category === "commands").length,
35592
- agents: selectedItems.filter((i) => i.category === "agents").length,
35593
- skills: selectedItems.filter((i) => i.category === "skills").length,
35594
- scripts: selectedItems.filter((i) => i.category === "scripts").length
35595
- };
35596
- const summary = [];
35597
- if (syncedByCategory.commands > 0)
35598
- summary.push(`${syncedByCategory.commands} command(s)`);
35599
- if (syncedByCategory.agents > 0)
35600
- summary.push(`${syncedByCategory.agents} agent(s)`);
35601
- if (syncedByCategory.skills > 0)
35602
- summary.push("skills folder");
35603
- if (syncedByCategory.scripts > 0)
35604
- summary.push("scripts folder");
35605
- f2.info(`Synced: ${summary.join(", ")}`);
35606
35607
  $e(source_default.green("✅ Sync completed"));
35607
35608
  } catch (error) {
35608
35609
  if (error instanceof Error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiblueprint-cli",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "AIBlueprint CLI for setting up Claude Code configurations",
5
5
  "author": "AIBlueprint",
6
6
  "license": "MIT",