@sunasteriskrnd/takumi 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1830 -1400
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -11225,12 +11225,12 @@ function isSafeRelativeLayoutPath(value) {
11225
11225
  return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
11226
11226
  }
11227
11227
  function isValidKitType(value) {
11228
- return value === "engineer";
11228
+ return value === "engineer" || value === "extras";
11229
11229
  }
11230
- var KitType, KitConfigSchema, KitLayoutSchema, TakumiPackageMetadataSchema, DEFAULT_KIT_LAYOUT, AVAILABLE_KITS, NEVER_COPY_PATTERNS, USER_CONFIG_PATTERNS, PROTECTED_PATTERNS;
11230
+ var KitType, KitConfigSchema, KitLayoutSchema, TakumiPackageMetadataSchema, DEFAULT_KIT_LAYOUT, AVAILABLE_KITS, BASE_KIT = "engineer", NEVER_COPY_PATTERNS, USER_CONFIG_PATTERNS, PROTECTED_PATTERNS;
11231
11231
  var init_kit = __esm(() => {
11232
11232
  init_zod();
11233
- KitType = exports_external.enum(["engineer"]);
11233
+ KitType = exports_external.enum(["engineer", "extras"]);
11234
11234
  KitConfigSchema = exports_external.object({
11235
11235
  id: KitType,
11236
11236
  name: exports_external.string(),
@@ -11256,6 +11256,13 @@ var init_kit = __esm(() => {
11256
11256
  repo: "takumi",
11257
11257
  owner: "sun-asterisk-internal",
11258
11258
  description: "Engineering toolkit for building with Claude"
11259
+ },
11260
+ extras: {
11261
+ id: "extras",
11262
+ name: "Takumi Extras",
11263
+ repo: "takumi-extras",
11264
+ owner: "sun-asterisk-internal",
11265
+ description: "Extras agent kit"
11259
11266
  }
11260
11267
  };
11261
11268
  NEVER_COPY_PATTERNS = [
@@ -11444,13 +11451,14 @@ var init_metadata = __esm(() => {
11444
11451
  KitMetadataSchema = exports_external.object({
11445
11452
  version: exports_external.string(),
11446
11453
  installedAt: exports_external.string(),
11454
+ installState: exports_external.enum(["pending", "complete"]).optional(),
11447
11455
  files: exports_external.array(TrackedFileSchema).optional(),
11448
11456
  lastUpdateCheck: exports_external.string().optional(),
11449
11457
  dismissedVersion: exports_external.string().optional(),
11450
11458
  installedSettings: InstalledSettingsSchema.optional()
11451
11459
  });
11452
11460
  MultiKitMetadataSchema = exports_external.object({
11453
- kits: exports_external.record(KitType, KitMetadataSchema).optional(),
11461
+ kits: exports_external.record(exports_external.string(), KitMetadataSchema).optional(),
11454
11462
  scope: exports_external.enum(["local", "global"]).optional(),
11455
11463
  name: exports_external.string().optional(),
11456
11464
  version: exports_external.string().optional(),
@@ -12116,6 +12124,7 @@ __export(exports_types, {
12116
12124
  DEFAULT_FOLDERS: () => DEFAULT_FOLDERS,
12117
12125
  ConfigSchema: () => ConfigSchema,
12118
12126
  CodingLevelSchema: () => CodingLevelSchema,
12127
+ BASE_KIT: () => BASE_KIT,
12119
12128
  AuthenticationError: () => AuthenticationError,
12120
12129
  ApiStatusOptionsSchema: () => ApiStatusOptionsSchema,
12121
12130
  ApiSetupOptionsSchema: () => ApiSetupOptionsSchema,
@@ -34351,7 +34360,7 @@ __export(exports_monorepo_resolver, {
34351
34360
  resolveMonorepoRoot: () => resolveMonorepoRoot
34352
34361
  });
34353
34362
  import { existsSync as existsSync44, readFileSync as readFileSync11 } from "node:fs";
34354
- import { dirname as dirname25, join as join95, resolve as resolve20 } from "node:path";
34363
+ import { dirname as dirname25, join as join93, resolve as resolve20 } from "node:path";
34355
34364
  import { fileURLToPath as fileURLToPath3 } from "node:url";
34356
34365
  function parseMetadataAt(metadataPath) {
34357
34366
  if (!existsSync44(metadataPath))
@@ -34368,7 +34377,7 @@ function parseMetadataAt(metadataPath) {
34368
34377
  }
34369
34378
  }
34370
34379
  function readSourceDirFromPackageJson(candidateRoot) {
34371
- const packageJsonPath = join95(candidateRoot, "package.json");
34380
+ const packageJsonPath = join93(candidateRoot, "package.json");
34372
34381
  if (!existsSync44(packageJsonPath))
34373
34382
  return null;
34374
34383
  try {
@@ -34381,7 +34390,7 @@ function readSourceDirFromPackageJson(candidateRoot) {
34381
34390
  return null;
34382
34391
  }
34383
34392
  function tryReadAtCandidate(candidateRoot) {
34384
- const flatMetadata = parseMetadataAt(join95(candidateRoot, "metadata.json"));
34393
+ const flatMetadata = parseMetadataAt(join93(candidateRoot, "metadata.json"));
34385
34394
  if (flatMetadata) {
34386
34395
  return {
34387
34396
  root: candidateRoot,
@@ -34391,10 +34400,10 @@ function tryReadAtCandidate(candidateRoot) {
34391
34400
  };
34392
34401
  }
34393
34402
  const sourceDir = readSourceDirFromPackageJson(candidateRoot) ?? "claude";
34394
- const nestedMetadata = parseMetadataAt(join95(candidateRoot, sourceDir, "metadata.json"));
34403
+ const nestedMetadata = parseMetadataAt(join93(candidateRoot, sourceDir, "metadata.json"));
34395
34404
  if (nestedMetadata) {
34396
34405
  return {
34397
- root: join95(candidateRoot, sourceDir),
34406
+ root: join93(candidateRoot, sourceDir),
34398
34407
  repoRoot: candidateRoot,
34399
34408
  version: nestedMetadata.version,
34400
34409
  name: nestedMetadata.name
@@ -34554,9 +34563,9 @@ __export(exports_worktree_manager, {
34554
34563
  });
34555
34564
  import { existsSync as existsSync60 } from "node:fs";
34556
34565
  import { readFile as readFile49, writeFile as writeFile36 } from "node:fs/promises";
34557
- import { join as join119 } from "node:path";
34566
+ import { join as join120 } from "node:path";
34558
34567
  async function createWorktree(projectDir, issueNumber, baseBranch) {
34559
- const worktreePath = join119(projectDir, WORKTREE_DIR, `issue-${issueNumber}`);
34568
+ const worktreePath = join120(projectDir, WORKTREE_DIR, `issue-${issueNumber}`);
34560
34569
  const branchName = `sk-watch/issue-${issueNumber}`;
34561
34570
  await spawnAndCollect("git", ["fetch", "origin", baseBranch], projectDir).catch(() => {
34562
34571
  logger.warning(`[worktree] Could not fetch origin/${baseBranch}, using local`);
@@ -34574,7 +34583,7 @@ async function createWorktree(projectDir, issueNumber, baseBranch) {
34574
34583
  return worktreePath;
34575
34584
  }
34576
34585
  async function removeWorktree(projectDir, issueNumber) {
34577
- const worktreePath = join119(projectDir, WORKTREE_DIR, `issue-${issueNumber}`);
34586
+ const worktreePath = join120(projectDir, WORKTREE_DIR, `issue-${issueNumber}`);
34578
34587
  const branchName = `sk-watch/issue-${issueNumber}`;
34579
34588
  try {
34580
34589
  await spawnAndCollect("git", ["worktree", "remove", worktreePath, "--force"], projectDir);
@@ -34588,7 +34597,7 @@ async function listActiveWorktrees(projectDir) {
34588
34597
  try {
34589
34598
  const output2 = await spawnAndCollect("git", ["worktree", "list", "--porcelain"], projectDir);
34590
34599
  const issueNumbers = [];
34591
- const worktreePrefix = join119(projectDir, WORKTREE_DIR, "issue-").replace(/\\/g, "/");
34600
+ const worktreePrefix = join120(projectDir, WORKTREE_DIR, "issue-").replace(/\\/g, "/");
34592
34601
  for (const line of output2.split(`
34593
34602
  `)) {
34594
34603
  if (line.startsWith("worktree ")) {
@@ -34616,7 +34625,7 @@ async function cleanupAllWorktrees(projectDir) {
34616
34625
  await spawnAndCollect("git", ["worktree", "prune"], projectDir).catch(() => {});
34617
34626
  }
34618
34627
  async function ensureGitignore(projectDir) {
34619
- const gitignorePath = join119(projectDir, ".gitignore");
34628
+ const gitignorePath = join120(projectDir, ".gitignore");
34620
34629
  try {
34621
34630
  const content = existsSync60(gitignorePath) ? await readFile49(gitignorePath, "utf-8") : "";
34622
34631
  if (!content.includes(".worktrees")) {
@@ -34723,7 +34732,7 @@ import { createHash as createHash8 } from "node:crypto";
34723
34732
  import { existsSync as existsSync66, mkdirSync as mkdirSync4, readFileSync as readFileSync16, readdirSync as readdirSync10, statSync as statSync7 } from "node:fs";
34724
34733
  import { rename as rename10, writeFile as writeFile38 } from "node:fs/promises";
34725
34734
  import { homedir as homedir32 } from "node:os";
34726
- import { basename as basename17, join as join126 } from "node:path";
34735
+ import { basename as basename17, join as join127 } from "node:path";
34727
34736
  function getCachedContext(repoPath) {
34728
34737
  const cachePath = getCacheFilePath(repoPath);
34729
34738
  if (!existsSync66(cachePath))
@@ -34766,25 +34775,25 @@ function computeSourceHash(repoPath) {
34766
34775
  }
34767
34776
  function getDocSourcePaths(repoPath) {
34768
34777
  const paths = [];
34769
- const docsDir = join126(repoPath, "docs");
34778
+ const docsDir = join127(repoPath, "docs");
34770
34779
  if (existsSync66(docsDir)) {
34771
34780
  try {
34772
34781
  const files = readdirSync10(docsDir);
34773
34782
  for (const f4 of files) {
34774
34783
  if (f4.endsWith(".md"))
34775
- paths.push(join126(docsDir, f4));
34784
+ paths.push(join127(docsDir, f4));
34776
34785
  }
34777
34786
  } catch {}
34778
34787
  }
34779
- const readme = join126(repoPath, "README.md");
34788
+ const readme = join127(repoPath, "README.md");
34780
34789
  if (existsSync66(readme))
34781
34790
  paths.push(readme);
34782
- const stylesDir = join126(repoPath, "assets", "writing-styles");
34791
+ const stylesDir = join127(repoPath, "assets", "writing-styles");
34783
34792
  if (existsSync66(stylesDir)) {
34784
34793
  try {
34785
34794
  const files = readdirSync10(stylesDir);
34786
34795
  for (const f4 of files) {
34787
- paths.push(join126(stylesDir, f4));
34796
+ paths.push(join127(stylesDir, f4));
34788
34797
  }
34789
34798
  } catch {}
34790
34799
  }
@@ -34793,11 +34802,11 @@ function getDocSourcePaths(repoPath) {
34793
34802
  function getCacheFilePath(repoPath) {
34794
34803
  const repoName = basename17(repoPath).replace(/[^a-zA-Z0-9_-]/g, "_");
34795
34804
  const pathHash = createHash8("sha256").update(repoPath).digest("hex").slice(0, 8);
34796
- return join126(CACHE_DIR, `${repoName}-${pathHash}-context-cache.json`);
34805
+ return join127(CACHE_DIR, `${repoName}-${pathHash}-context-cache.json`);
34797
34806
  }
34798
34807
  var CACHE_DIR, CACHE_TTL_MS3;
34799
34808
  var init_context_cache_manager = __esm(() => {
34800
- CACHE_DIR = join126(homedir32(), ".sunagentkit", "cache");
34809
+ CACHE_DIR = join127(homedir32(), ".sunagentkit", "cache");
34801
34810
  CACHE_TTL_MS3 = 24 * 60 * 60 * 1000;
34802
34811
  });
34803
34812
 
@@ -34978,7 +34987,7 @@ function extractContentFromResponse(response) {
34978
34987
  // src/commands/content/phases/docs-summarizer.ts
34979
34988
  import { execSync as execSync5 } from "node:child_process";
34980
34989
  import { existsSync as existsSync67, readFileSync as readFileSync17, readdirSync as readdirSync11 } from "node:fs";
34981
- import { join as join127 } from "node:path";
34990
+ import { join as join128 } from "node:path";
34982
34991
  async function summarizeProjectDocs(repoPath, contentLogger) {
34983
34992
  const rawContent = collectRawDocs(repoPath);
34984
34993
  if (rawContent.total.length < 200) {
@@ -35032,12 +35041,12 @@ function collectRawDocs(repoPath) {
35032
35041
  return capped;
35033
35042
  };
35034
35043
  const docsContent = [];
35035
- const docsDir = join127(repoPath, "docs");
35044
+ const docsDir = join128(repoPath, "docs");
35036
35045
  if (existsSync67(docsDir)) {
35037
35046
  try {
35038
35047
  const files = readdirSync11(docsDir).filter((f4) => f4.endsWith(".md")).sort();
35039
35048
  for (const f4 of files) {
35040
- const content = readCapped(join127(docsDir, f4), 5000);
35049
+ const content = readCapped(join128(docsDir, f4), 5000);
35041
35050
  if (content) {
35042
35051
  docsContent.push(`### ${f4}
35043
35052
  ${content}`);
@@ -35051,21 +35060,21 @@ ${content}`);
35051
35060
  let brand = "";
35052
35061
  const brandCandidates = ["docs/brand-guidelines.md", "docs/design-guidelines.md"];
35053
35062
  for (const p2 of brandCandidates) {
35054
- brand = readCapped(join127(repoPath, p2), 3000);
35063
+ brand = readCapped(join128(repoPath, p2), 3000);
35055
35064
  if (brand)
35056
35065
  break;
35057
35066
  }
35058
35067
  let styles3 = "";
35059
- const stylesDir = join127(repoPath, "assets", "writing-styles");
35068
+ const stylesDir = join128(repoPath, "assets", "writing-styles");
35060
35069
  if (existsSync67(stylesDir)) {
35061
35070
  try {
35062
35071
  const files = readdirSync11(stylesDir).slice(0, 3);
35063
- styles3 = files.map((f4) => readCapped(join127(stylesDir, f4), 1000)).filter(Boolean).join(`
35072
+ styles3 = files.map((f4) => readCapped(join128(stylesDir, f4), 1000)).filter(Boolean).join(`
35064
35073
 
35065
35074
  `);
35066
35075
  } catch {}
35067
35076
  }
35068
- const readme = readCapped(join127(repoPath, "README.md"), 3000);
35077
+ const readme = readCapped(join128(repoPath, "README.md"), 3000);
35069
35078
  const total = [docs, brand, styles3, readme].join(`
35070
35079
  `);
35071
35080
  return { docs, brand, styles: styles3, readme, total };
@@ -35252,9 +35261,9 @@ IMPORTANT: Generate the image and output the path as JSON: {"imagePath": "/path/
35252
35261
  import { execSync as execSync6 } from "node:child_process";
35253
35262
  import { existsSync as existsSync68, mkdirSync as mkdirSync5, readdirSync as readdirSync12 } from "node:fs";
35254
35263
  import { homedir as homedir33 } from "node:os";
35255
- import { join as join128 } from "node:path";
35264
+ import { join as join129 } from "node:path";
35256
35265
  async function generatePhoto(_content, context, config, platform10, contentId, contentLogger) {
35257
- const mediaDir = join128(config.contentDir.replace(/^~/, homedir33()), "media", String(contentId));
35266
+ const mediaDir = join129(config.contentDir.replace(/^~/, homedir33()), "media", String(contentId));
35258
35267
  if (!existsSync68(mediaDir)) {
35259
35268
  mkdirSync5(mediaDir, { recursive: true });
35260
35269
  }
@@ -35279,7 +35288,7 @@ async function generatePhoto(_content, context, config, platform10, contentId, c
35279
35288
  const imageFile = files.find((f4) => /\.(png|jpg|jpeg|webp)$/i.test(f4));
35280
35289
  if (imageFile) {
35281
35290
  const ext2 = imageFile.split(".").pop() ?? "png";
35282
- return { path: join128(mediaDir, imageFile), ...dimensions, format: ext2 };
35291
+ return { path: join129(mediaDir, imageFile), ...dimensions, format: ext2 };
35283
35292
  }
35284
35293
  contentLogger.warn(`Photo generation produced no image for content ${contentId}`);
35285
35294
  return null;
@@ -35369,7 +35378,7 @@ var init_content_creator = __esm(() => {
35369
35378
  // src/commands/content/phases/content-logger.ts
35370
35379
  import { createWriteStream as createWriteStream5, existsSync as existsSync69, mkdirSync as mkdirSync6, statSync as statSync8 } from "node:fs";
35371
35380
  import { homedir as homedir34 } from "node:os";
35372
- import { join as join129 } from "node:path";
35381
+ import { join as join130 } from "node:path";
35373
35382
 
35374
35383
  class ContentLogger {
35375
35384
  stream = null;
@@ -35377,7 +35386,7 @@ class ContentLogger {
35377
35386
  logDir;
35378
35387
  maxBytes;
35379
35388
  constructor(maxBytes = 0) {
35380
- this.logDir = join129(homedir34(), ".sunagentkit", "logs");
35389
+ this.logDir = join130(homedir34(), ".sunagentkit", "logs");
35381
35390
  this.maxBytes = maxBytes;
35382
35391
  }
35383
35392
  init() {
@@ -35409,7 +35418,7 @@ class ContentLogger {
35409
35418
  }
35410
35419
  }
35411
35420
  getLogPath() {
35412
- return join129(this.logDir, `content-${this.getDateStr()}.log`);
35421
+ return join130(this.logDir, `content-${this.getDateStr()}.log`);
35413
35422
  }
35414
35423
  write(level, message) {
35415
35424
  this.rotateIfNeeded();
@@ -35426,18 +35435,18 @@ class ContentLogger {
35426
35435
  if (dateStr !== this.currentDate) {
35427
35436
  this.close();
35428
35437
  this.currentDate = dateStr;
35429
- const logPath = join129(this.logDir, `content-${dateStr}.log`);
35438
+ const logPath = join130(this.logDir, `content-${dateStr}.log`);
35430
35439
  this.stream = createWriteStream5(logPath, { flags: "a", mode: 384 });
35431
35440
  return;
35432
35441
  }
35433
35442
  if (this.maxBytes > 0 && this.stream) {
35434
- const logPath = join129(this.logDir, `content-${this.currentDate}.log`);
35443
+ const logPath = join130(this.logDir, `content-${this.currentDate}.log`);
35435
35444
  try {
35436
35445
  const stat16 = statSync8(logPath);
35437
35446
  if (stat16.size >= this.maxBytes) {
35438
35447
  this.close();
35439
35448
  const suffix = Date.now();
35440
- const rotatedPath = join129(this.logDir, `content-${this.currentDate}-${suffix}.log`);
35449
+ const rotatedPath = join130(this.logDir, `content-${this.currentDate}-${suffix}.log`);
35441
35450
  import("node:fs/promises").then(({ rename: rename11 }) => rename11(logPath, rotatedPath).catch(() => {}));
35442
35451
  this.stream = createWriteStream5(logPath, { flags: "w", mode: 384 });
35443
35452
  }
@@ -35663,7 +35672,7 @@ function isNoiseCommit(title, author) {
35663
35672
  // src/commands/content/phases/change-detector.ts
35664
35673
  import { execSync as execSync8 } from "node:child_process";
35665
35674
  import { existsSync as existsSync71, readFileSync as readFileSync18, readdirSync as readdirSync13, statSync as statSync9 } from "node:fs";
35666
- import { join as join130 } from "node:path";
35675
+ import { join as join131 } from "node:path";
35667
35676
  function detectCommits(repo, since) {
35668
35677
  try {
35669
35678
  const fetchUrl = sshToHttps(repo.remoteUrl);
@@ -35761,7 +35770,7 @@ function detectTags(repo, since) {
35761
35770
  }
35762
35771
  }
35763
35772
  function detectCompletedPlans(repo, since) {
35764
- const plansDir = join130(repo.path, "plans");
35773
+ const plansDir = join131(repo.path, "plans");
35765
35774
  if (!existsSync71(plansDir))
35766
35775
  return [];
35767
35776
  const sinceMs = new Date(since).getTime();
@@ -35771,7 +35780,7 @@ function detectCompletedPlans(repo, since) {
35771
35780
  for (const entry of entries) {
35772
35781
  if (!entry.isDirectory())
35773
35782
  continue;
35774
- const planFile = join130(plansDir, entry.name, "plan.md");
35783
+ const planFile = join131(plansDir, entry.name, "plan.md");
35775
35784
  if (!existsSync71(planFile))
35776
35785
  continue;
35777
35786
  try {
@@ -35849,7 +35858,7 @@ function classifyCommit(event) {
35849
35858
  // src/commands/content/phases/repo-discoverer.ts
35850
35859
  import { execSync as execSync9 } from "node:child_process";
35851
35860
  import { readdirSync as readdirSync14 } from "node:fs";
35852
- import { join as join131 } from "node:path";
35861
+ import { join as join132 } from "node:path";
35853
35862
  function discoverRepos2(cwd2) {
35854
35863
  const repos = [];
35855
35864
  if (isGitRepoRoot(cwd2)) {
@@ -35862,7 +35871,7 @@ function discoverRepos2(cwd2) {
35862
35871
  for (const entry of entries) {
35863
35872
  if (!entry.isDirectory() || entry.name.startsWith("."))
35864
35873
  continue;
35865
- const dirPath = join131(cwd2, entry.name);
35874
+ const dirPath = join132(cwd2, entry.name);
35866
35875
  if (isGitRepoRoot(dirPath)) {
35867
35876
  const info = getRepoInfo(dirPath);
35868
35877
  if (info)
@@ -36529,9 +36538,9 @@ var init_types3 = __esm(() => {
36529
36538
 
36530
36539
  // src/commands/content/phases/state-manager.ts
36531
36540
  import { readFile as readFile51, rename as rename11, writeFile as writeFile39 } from "node:fs/promises";
36532
- import { join as join132 } from "node:path";
36541
+ import { join as join133 } from "node:path";
36533
36542
  async function loadContentConfig(projectDir) {
36534
- const configPath = join132(projectDir, TAKUMI_CONFIG_FILE2);
36543
+ const configPath = join133(projectDir, TAKUMI_CONFIG_FILE2);
36535
36544
  try {
36536
36545
  const raw = await readFile51(configPath, "utf-8");
36537
36546
  const json = JSON.parse(raw);
@@ -36541,13 +36550,13 @@ async function loadContentConfig(projectDir) {
36541
36550
  }
36542
36551
  }
36543
36552
  async function saveContentConfig(projectDir, config) {
36544
- const configPath = join132(projectDir, TAKUMI_CONFIG_FILE2);
36553
+ const configPath = join133(projectDir, TAKUMI_CONFIG_FILE2);
36545
36554
  const json = await readJsonSafe(configPath);
36546
36555
  json.content = { ...json.content, ...config };
36547
36556
  await atomicWrite3(configPath, json);
36548
36557
  }
36549
36558
  async function loadContentState(projectDir) {
36550
- const configPath = join132(projectDir, TAKUMI_CONFIG_FILE2);
36559
+ const configPath = join133(projectDir, TAKUMI_CONFIG_FILE2);
36551
36560
  try {
36552
36561
  const raw = await readFile51(configPath, "utf-8");
36553
36562
  const json = JSON.parse(raw);
@@ -36558,7 +36567,7 @@ async function loadContentState(projectDir) {
36558
36567
  }
36559
36568
  }
36560
36569
  async function saveContentState(projectDir, state) {
36561
- const configPath = join132(projectDir, TAKUMI_CONFIG_FILE2);
36570
+ const configPath = join133(projectDir, TAKUMI_CONFIG_FILE2);
36562
36571
  const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
36563
36572
  for (const key of Object.keys(state.dailyPostCounts)) {
36564
36573
  const dateStr = key.slice(-10);
@@ -36840,7 +36849,7 @@ var init_platform_setup_x = __esm(() => {
36840
36849
 
36841
36850
  // src/commands/content/phases/setup-wizard.ts
36842
36851
  import { existsSync as existsSync72 } from "node:fs";
36843
- import { join as join133 } from "node:path";
36852
+ import { join as join134 } from "node:path";
36844
36853
  async function runSetupWizard2(cwd2, contentLogger) {
36845
36854
  console.log();
36846
36855
  oe(import_picocolors40.default.bgCyan(import_picocolors40.default.white(" SK Content — Multi-Channel Content Engine ")));
@@ -36908,8 +36917,8 @@ async function showRepoSummary(cwd2) {
36908
36917
  function detectBrandAssets(cwd2, contentLogger) {
36909
36918
  const repos = discoverRepos2(cwd2);
36910
36919
  for (const repo of repos) {
36911
- const hasGuidelines = existsSync72(join133(repo.path, "docs", "brand-guidelines.md"));
36912
- const hasStyles = existsSync72(join133(repo.path, "assets", "writing-styles"));
36920
+ const hasGuidelines = existsSync72(join134(repo.path, "docs", "brand-guidelines.md"));
36921
+ const hasStyles = existsSync72(join134(repo.path, "assets", "writing-styles"));
36913
36922
  if (!hasGuidelines) {
36914
36923
  f2.warning(`${repo.name}: No docs/brand-guidelines.md — content will use generic tone.`);
36915
36924
  contentLogger.warn(`${repo.name}: missing docs/brand-guidelines.md`);
@@ -37051,9 +37060,9 @@ __export(exports_content_subcommands, {
37051
37060
  });
37052
37061
  import { existsSync as existsSync74, readFileSync as readFileSync19, unlinkSync as unlinkSync6 } from "node:fs";
37053
37062
  import { homedir as homedir36 } from "node:os";
37054
- import { join as join134 } from "node:path";
37063
+ import { join as join135 } from "node:path";
37055
37064
  function isDaemonRunning() {
37056
- const lockFile = join134(LOCK_DIR, `${LOCK_NAME2}.lock`);
37065
+ const lockFile = join135(LOCK_DIR, `${LOCK_NAME2}.lock`);
37057
37066
  if (!existsSync74(lockFile))
37058
37067
  return { running: false, pid: null };
37059
37068
  try {
@@ -37085,7 +37094,7 @@ async function startContent(options2) {
37085
37094
  await contentCommand(options2);
37086
37095
  }
37087
37096
  async function stopContent() {
37088
- const lockFile = join134(LOCK_DIR, `${LOCK_NAME2}.lock`);
37097
+ const lockFile = join135(LOCK_DIR, `${LOCK_NAME2}.lock`);
37089
37098
  if (!existsSync74(lockFile)) {
37090
37099
  logger.info("Content daemon is not running.");
37091
37100
  return;
@@ -37124,9 +37133,9 @@ async function statusContent() {
37124
37133
  } catch {}
37125
37134
  }
37126
37135
  async function logsContent(options2) {
37127
- const logDir = join134(homedir36(), ".sunagentkit", "logs");
37136
+ const logDir = join135(homedir36(), ".sunagentkit", "logs");
37128
37137
  const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, "");
37129
- const logPath = join134(logDir, `content-${dateStr}.log`);
37138
+ const logPath = join135(logDir, `content-${dateStr}.log`);
37130
37139
  if (!existsSync74(logPath)) {
37131
37140
  logger.info("No content logs found for today.");
37132
37141
  return;
@@ -37158,13 +37167,13 @@ var init_content_subcommands = __esm(() => {
37158
37167
  init_setup_wizard();
37159
37168
  init_state_manager();
37160
37169
  init_content_review_commands();
37161
- LOCK_DIR = join134(homedir36(), ".sunagentkit", "locks");
37170
+ LOCK_DIR = join135(homedir36(), ".sunagentkit", "locks");
37162
37171
  });
37163
37172
 
37164
37173
  // src/commands/content/content-command.ts
37165
37174
  import { existsSync as existsSync75, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync6 } from "node:fs";
37166
37175
  import { homedir as homedir37 } from "node:os";
37167
- import { join as join135 } from "node:path";
37176
+ import { join as join136 } from "node:path";
37168
37177
  async function contentCommand(options2) {
37169
37178
  const cwd2 = process.cwd();
37170
37179
  const contentLogger = new ContentLogger;
@@ -37342,8 +37351,8 @@ var init_content_command = __esm(() => {
37342
37351
  init_publisher();
37343
37352
  init_review_manager();
37344
37353
  init_state_manager();
37345
- LOCK_DIR2 = join135(homedir37(), ".sunagentkit", "locks");
37346
- LOCK_FILE = join135(LOCK_DIR2, "sk-content.lock");
37354
+ LOCK_DIR2 = join136(homedir37(), ".sunagentkit", "locks");
37355
+ LOCK_FILE = join136(LOCK_DIR2, "sk-content.lock");
37347
37356
  });
37348
37357
 
37349
37358
  // src/commands/content/index.ts
@@ -48980,11 +48989,11 @@ async function detectMetadataFormat(claudeDir) {
48980
48989
  };
48981
48990
  }
48982
48991
  if (parsed.name || parsed.version || parsed.files) {
48983
- let detectedKit = null;
48992
+ let detectedKit = "engineer";
48984
48993
  const nameToCheck = parsed.name || "";
48985
- if (/\bengineer\b/i.test(nameToCheck)) {
48986
- detectedKit = "engineer";
48987
- } else {
48994
+ if (/\bextras\b/i.test(nameToCheck)) {
48995
+ detectedKit = "extras";
48996
+ } else if (/\bengineer\b/i.test(nameToCheck)) {
48988
48997
  detectedKit = "engineer";
48989
48998
  }
48990
48999
  return { format: "legacy", metadata: parsed, detectedKit };
@@ -49278,28 +49287,32 @@ var import_fs_extra6 = __toESM(require_lib(), 1);
49278
49287
  var import_picomatch = __toESM(require_picomatch2(), 1);
49279
49288
  function findFileInMetadata(metadata, path3) {
49280
49289
  if (!metadata)
49281
- return null;
49290
+ return { tracked: null, ownerKit: null };
49282
49291
  if (metadata.kits) {
49283
- for (const kitMeta of Object.values(metadata.kits)) {
49284
- if (kitMeta?.files) {
49285
- const found = kitMeta.files.find((f3) => f3.path === path3);
49286
- if (found)
49287
- return found;
49288
- }
49292
+ for (const [kitName, kitMeta] of Object.entries(metadata.kits)) {
49293
+ if (!kitMeta?.files)
49294
+ continue;
49295
+ const found = kitMeta.files.find((f3) => f3.path === path3);
49296
+ if (found)
49297
+ return { tracked: found, ownerKit: kitName };
49289
49298
  }
49290
49299
  }
49291
49300
  if (metadata.files) {
49292
49301
  const found = metadata.files.find((f3) => f3.path === path3);
49293
49302
  if (found)
49294
- return found;
49303
+ return { tracked: found, ownerKit: null };
49295
49304
  }
49296
- return null;
49305
+ return { tracked: null, ownerKit: null };
49297
49306
  }
49298
- function shouldDeletePath(path3, metadata) {
49299
- const tracked = findFileInMetadata(metadata, path3);
49307
+ function shouldDeletePath(path3, metadata, installingKit) {
49308
+ const { tracked, ownerKit } = findFileInMetadata(metadata, path3);
49300
49309
  if (!tracked)
49301
49310
  return true;
49302
- return tracked.ownership !== "user";
49311
+ if (tracked.ownership === "user")
49312
+ return false;
49313
+ if (installingKit && ownerKit && ownerKit !== installingKit)
49314
+ return false;
49315
+ return true;
49303
49316
  }
49304
49317
  function collectFilesRecursively(dir, baseDir) {
49305
49318
  const results = [];
@@ -49422,7 +49435,7 @@ async function updateMetadataAfterDeletion(claudeDir, deletedPaths) {
49422
49435
  logger.debug("Failed to write updated metadata.json");
49423
49436
  }
49424
49437
  }
49425
- async function handleDeletions(sourceMetadata, claudeDir) {
49438
+ async function handleDeletions(sourceMetadata, claudeDir, installingKit) {
49426
49439
  const deletionPatterns = sourceMetadata.deletions || [];
49427
49440
  if (deletionPatterns.length === 0) {
49428
49441
  return { deletedPaths: [], preservedPaths: [], errors: [] };
@@ -49439,9 +49452,9 @@ async function handleDeletions(sourceMetadata, claudeDir) {
49439
49452
  result.errors.push(path3);
49440
49453
  continue;
49441
49454
  }
49442
- if (!shouldDeletePath(path3, userMetadata)) {
49455
+ if (!shouldDeletePath(path3, userMetadata, installingKit)) {
49443
49456
  result.preservedPaths.push(path3);
49444
- logger.verbose(`Preserved user file: ${path3}`);
49457
+ logger.verbose(`Preserved file (cross-kit or user-owned): ${path3}`);
49445
49458
  continue;
49446
49459
  }
49447
49460
  if (existsSync13(fullPath)) {
@@ -52510,14 +52523,17 @@ async function writeManifest(providerRoot, kitName, version3, scope, kitType, tr
52510
52523
  logger.debug(`Could not read existing metadata: ${error}`);
52511
52524
  }
52512
52525
  }
52526
+ const existingKits = existingMetadata.kits || {};
52527
+ const otherKitsExist = Object.keys(existingKits).some((k2) => k2 !== kit);
52513
52528
  const installedAt = new Date().toISOString();
52529
+ const existingKitMeta = existingKits[kit];
52514
52530
  const kitMetadata = {
52531
+ ...existingKitMeta ?? {},
52515
52532
  version: version3,
52516
52533
  installedAt,
52534
+ installState: "complete",
52517
52535
  files: trackedFiles.length > 0 ? trackedFiles : undefined
52518
52536
  };
52519
- const existingKits = existingMetadata.kits || {};
52520
- const otherKitsExist = Object.keys(existingKits).some((k2) => k2 !== kit);
52521
52537
  const metadata = {
52522
52538
  kits: {
52523
52539
  ...existingKits,
@@ -52548,6 +52564,69 @@ async function writeManifest(providerRoot, kitName, version3, scope, kitType, tr
52548
52564
  }
52549
52565
  }
52550
52566
  }
52567
+ async function writeKitPending(providerRoot, kit, version3, scope) {
52568
+ const metadataPath = join28(providerRoot, "metadata.json");
52569
+ await import_fs_extra10.ensureFile(metadataPath);
52570
+ let release = null;
52571
+ try {
52572
+ release = await import_proper_lockfile4.lock(metadataPath, {
52573
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 1000 },
52574
+ stale: 60000
52575
+ });
52576
+ logger.debug(`Acquired lock on ${metadataPath} for pending-state write`);
52577
+ const migrationResult = await migrateToMultiKit(providerRoot);
52578
+ if (!migrationResult.success) {
52579
+ logger.warning(`Metadata migration warning: ${migrationResult.error}`);
52580
+ }
52581
+ let existingMetadata = { kits: {} };
52582
+ if (await import_fs_extra10.pathExists(metadataPath)) {
52583
+ try {
52584
+ const content = await import_fs_extra10.readFile(metadataPath, "utf-8");
52585
+ const parsed = JSON.parse(content);
52586
+ if (parsed && typeof parsed === "object" && Object.keys(parsed).length > 0) {
52587
+ existingMetadata = parsed;
52588
+ }
52589
+ } catch (error) {
52590
+ logger.debug(`Could not read existing metadata: ${error}`);
52591
+ }
52592
+ }
52593
+ const existingKits = existingMetadata.kits || {};
52594
+ const pendingMetadata = {
52595
+ version: version3,
52596
+ installedAt: new Date().toISOString(),
52597
+ installState: "pending",
52598
+ files: []
52599
+ };
52600
+ const metadata = {
52601
+ ...existingMetadata,
52602
+ kits: {
52603
+ ...existingKits,
52604
+ [kit]: pendingMetadata
52605
+ },
52606
+ scope
52607
+ };
52608
+ const validated = MetadataSchema.parse(metadata);
52609
+ await import_fs_extra10.writeFile(metadataPath, JSON.stringify(validated, null, 2), "utf-8");
52610
+ logger.debug(`Wrote pending-state entry for kit "${kit}"`);
52611
+ } finally {
52612
+ if (release) {
52613
+ await release();
52614
+ logger.debug(`Released lock on ${metadataPath}`);
52615
+ }
52616
+ }
52617
+ }
52618
+ async function findPendingKits(providerRoot) {
52619
+ const metadata = await readManifest(providerRoot);
52620
+ if (!metadata?.kits)
52621
+ return [];
52622
+ const pending = [];
52623
+ for (const [kitName, kitMeta] of Object.entries(metadata.kits)) {
52624
+ if (kitMeta?.installState === "pending") {
52625
+ pending.push(kitName);
52626
+ }
52627
+ }
52628
+ return pending;
52629
+ }
52551
52630
  async function removeKitFromManifest(providerRoot, kit) {
52552
52631
  const metadataPath = join28(providerRoot, "metadata.json");
52553
52632
  if (!await import_fs_extra10.pathExists(metadataPath))
@@ -52749,6 +52828,12 @@ class ManifestWriter {
52749
52828
  async writeManifest(providerRoot, kitName, version3, scope, kitType) {
52750
52829
  return writeManifest(providerRoot, kitName, version3, scope, kitType, this.getTrackedFiles(), this.getUserConfigFiles());
52751
52830
  }
52831
+ static async writeKitPending(providerRoot, kit, version3, scope) {
52832
+ return writeKitPending(providerRoot, kit, version3, scope);
52833
+ }
52834
+ static async findPendingKits(providerRoot) {
52835
+ return findPendingKits(providerRoot);
52836
+ }
52752
52837
  static async readManifest(providerRoot) {
52753
52838
  return readManifest(providerRoot);
52754
52839
  }
@@ -53716,7 +53801,7 @@ async function handleMerge(ctx) {
53716
53801
  }
53717
53802
  try {
53718
53803
  if (sourceMetadata?.deletions && sourceMetadata.deletions.length > 0) {
53719
- const deletionResult = await handleDeletions(sourceMetadata, ctx.claudeDir);
53804
+ const deletionResult = await handleDeletions(sourceMetadata, ctx.claudeDir, ctx.kitType);
53720
53805
  if (deletionResult.deletedPaths.length > 0) {
53721
53806
  logger.info(`Removed ${deletionResult.deletedPaths.length} deprecated file(s)`);
53722
53807
  for (const path4 of deletionResult.deletedPaths) {
@@ -55543,14 +55628,17 @@ async function handlePostInstall(ctx) {
55543
55628
  }
55544
55629
  }
55545
55630
  const skillsDir = PathResolver.buildSkillsPath(ctx.resolvedDir, ctx.options.global);
55546
- const depsResult = await promptAndInstallSkillsDeps({
55547
- skillsDir,
55548
- isNonInteractive: ctx.isNonInteractive,
55549
- installSkills: ctx.installSkills,
55550
- withSudo: ctx.options.withSudo,
55551
- prompts: ctx.prompts
55552
- });
55553
- const installSkills = depsResult.installSkills;
55631
+ let installSkills = ctx.installSkills;
55632
+ if (ctx.isLastKit === undefined || ctx.isLastKit) {
55633
+ const depsResult = await promptAndInstallSkillsDeps({
55634
+ skillsDir,
55635
+ isNonInteractive: ctx.isNonInteractive,
55636
+ installSkills: ctx.installSkills,
55637
+ withSudo: ctx.options.withSudo,
55638
+ prompts: ctx.prompts
55639
+ });
55640
+ installSkills = depsResult.installSkills;
55641
+ }
55554
55642
  if (!ctx.isNonInteractive) {
55555
55643
  const { isGeminiInstalled: isGeminiInstalled2 } = await Promise.resolve().then(() => (init_package_installer(), exports_package_installer));
55556
55644
  const { checkExistingGeminiConfig: checkExistingGeminiConfig2, findMcpConfigPath: findMcpConfigPath2, processGeminiMcpLinking: processGeminiMcpLinking2 } = await Promise.resolve().then(() => (init_gemini_mcp_linker(), exports_gemini_mcp_linker));
@@ -60490,6 +60578,8 @@ var codexInstaller = {
60490
60578
  await copySkillsAuxFiles(sources.skills, codexSkillsDir);
60491
60579
  await copySkillsScriptsDir(ctx.extractDir, codexSkillsDir);
60492
60580
  auxCopySucceeded = true;
60581
+ ctx.codexSkillsDirSeen = codexSkillsDir;
60582
+ ctx.codexAuxCopiedAny = true;
60493
60583
  } catch (error) {
60494
60584
  logger.error(`[init/codex] skills aux copy failed: ${error instanceof Error ? error.message : String(error)}`);
60495
60585
  logger.warning("[init/codex] skipping skills-deps install (aux files unavailable)");
@@ -60502,9 +60592,12 @@ var codexInstaller = {
60502
60592
  logger.debug(`[init/codex] project auto-registration skipped: ${error instanceof Error ? error.message : String(error)}`);
60503
60593
  }
60504
60594
  }
60505
- if (hasSkills && codexSkillsDir && auxCopySucceeded) {
60595
+ const isLastIteration = ctx.isLastKit === undefined || ctx.isLastKit;
60596
+ const depsTargetDir = codexSkillsDir ?? ctx.codexSkillsDirSeen;
60597
+ const auxAvailable = auxCopySucceeded || ctx.codexAuxCopiedAny === true;
60598
+ if (isLastIteration && depsTargetDir && auxAvailable) {
60506
60599
  const depsResult = await promptAndInstallSkillsDeps({
60507
- skillsDir: codexSkillsDir,
60600
+ skillsDir: depsTargetDir,
60508
60601
  isNonInteractive: ctx.isNonInteractive,
60509
60602
  installSkills: ctx.installSkills,
60510
60603
  withSudo: ctx.options.withSudo,
@@ -63269,10 +63362,170 @@ async function doctorCommand(options2 = {}) {
63269
63362
  }
63270
63363
  }
63271
63364
 
63272
- // src/domains/sync/config-version-checker.ts
63365
+ // src/commands/init/init-command.ts
63366
+ import { join as join102 } from "node:path";
63367
+
63368
+ // src/domains/ui/prompts.ts
63369
+ init_package_installer();
63370
+ init_logger();
63371
+
63372
+ // src/domains/ui/prompts/agent-selection-prompts.ts
63373
+ init_provider_registry();
63374
+
63375
+ // src/services/package-installer/agent-cli-detector.ts
63376
+ init_process_executor();
63377
+ var DETECT_TIMEOUT_MS = 5000;
63378
+ var BINARY_CANDIDATES = {
63379
+ "claude-code": process.platform === "win32" ? ["claude.exe", "claude.cmd", "claude"] : ["claude"],
63380
+ codex: process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex"] : ["codex"]
63381
+ };
63382
+ async function probeBinary(candidates) {
63383
+ for (const bin of candidates) {
63384
+ const needsShell = process.platform === "win32" && /\.(cmd|bat)$/i.test(bin);
63385
+ try {
63386
+ const { stdout } = await execFileAsync5(bin, ["--version"], {
63387
+ timeout: DETECT_TIMEOUT_MS,
63388
+ encoding: "utf8",
63389
+ shell: needsShell
63390
+ });
63391
+ return typeof stdout === "string" ? stdout : String(stdout);
63392
+ } catch {}
63393
+ }
63394
+ return null;
63395
+ }
63396
+ function extractVersion(stdout) {
63397
+ const match2 = stdout.match(/(\d+\.\d+\.\d+(?:[-+][\w.]+)?)/);
63398
+ return match2 ? match2[1] : undefined;
63399
+ }
63400
+ async function detectAgentInstallations(providers2) {
63401
+ const probes = providers2.filter((p) => (p in BINARY_CANDIDATES)).map(async (provider) => {
63402
+ const candidates = BINARY_CANDIDATES[provider];
63403
+ if (!candidates) {
63404
+ return [provider, { installed: false }];
63405
+ }
63406
+ const stdout = await probeBinary(candidates);
63407
+ const detection = stdout ? { installed: true, version: extractVersion(stdout) } : { installed: false };
63408
+ return [provider, detection];
63409
+ });
63410
+ const entries = await Promise.all(probes);
63411
+ const result = {};
63412
+ for (const [provider, detection] of entries) {
63413
+ result[provider] = detection;
63414
+ }
63415
+ return result;
63416
+ }
63417
+
63418
+ // src/domains/ui/prompts/agent-selection-prompts.ts
63419
+ function buildDetectionNote(supported, detections) {
63420
+ const lines = supported.map((provider) => {
63421
+ const detection = detections[provider];
63422
+ const label = providers[provider]?.displayName ?? provider;
63423
+ if (detection?.installed) {
63424
+ const version3 = detection.version ? ` v${detection.version}` : "";
63425
+ return ` ✓ ${label}${version3} detected on PATH`;
63426
+ }
63427
+ return ` ✗ ${label} not detected on PATH`;
63428
+ });
63429
+ lines.push("");
63430
+ lines.push("Init writes config files regardless — detection only informs defaults.");
63431
+ return lines.join(`
63432
+ `);
63433
+ }
63434
+ function computeInitialSelection(supported, detections) {
63435
+ if (detections["claude-code"]?.installed)
63436
+ return ["claude-code"];
63437
+ const firstInstalled = supported.find((p) => detections[p]?.installed);
63438
+ return firstInstalled ? [firstInstalled] : [];
63439
+ }
63440
+ async function promptAgentSelection(supported) {
63441
+ const detections = await detectAgentInstallations(supported);
63442
+ note(buildDetectionNote(supported, detections), "Detected agent CLIs");
63443
+ const options2 = supported.map((provider) => {
63444
+ const detection = detections[provider];
63445
+ const label = providers[provider]?.displayName ?? provider;
63446
+ const hint = detection?.installed ? detection.version ? `installed v${detection.version}` : "installed" : "not detected — config will still be written";
63447
+ return { value: provider, label, hint };
63448
+ });
63449
+ const initialValues = computeInitialSelection(supported, detections);
63450
+ const selected = await ae({
63451
+ message: "Select agent(s) to initialize",
63452
+ options: options2,
63453
+ required: true,
63454
+ initialValues
63455
+ });
63456
+ if (lD(selected))
63457
+ return null;
63458
+ return selected;
63459
+ }
63460
+ // src/domains/ui/prompts/kit-prompts.ts
63461
+ init_logger();
63462
+ init_types2();
63463
+ async function selectKit(defaultKit, accessibleKits) {
63464
+ const kits = accessibleKits ?? Object.keys(AVAILABLE_KITS);
63465
+ const kit = await ie({
63466
+ message: "Select a Takumi:",
63467
+ options: kits.map((key) => ({
63468
+ value: key,
63469
+ label: AVAILABLE_KITS[key].name,
63470
+ hint: AVAILABLE_KITS[key].description
63471
+ })),
63472
+ initialValue: defaultKit
63473
+ });
63474
+ if (lD(kit)) {
63475
+ throw new Error("Kit selection cancelled");
63476
+ }
63477
+ return kit;
63478
+ }
63479
+ async function selectKits(accessibleKits, defaults2) {
63480
+ const kits = accessibleKits ?? Object.keys(AVAILABLE_KITS);
63481
+ const options2 = kits.map((key) => {
63482
+ const isBase = key === BASE_KIT;
63483
+ const cfg = AVAILABLE_KITS[key];
63484
+ return {
63485
+ value: key,
63486
+ label: isBase ? `${cfg.name} (base, required)` : cfg.name,
63487
+ hint: cfg.description
63488
+ };
63489
+ });
63490
+ const initialValues = defaults2 && defaults2.length > 0 ? Array.from(new Set([BASE_KIT, ...defaults2])).filter((k2) => kits.includes(k2)) : kits;
63491
+ const selected = await ae({
63492
+ message: "Select kit(s) to install:",
63493
+ options: options2,
63494
+ initialValues,
63495
+ required: true
63496
+ });
63497
+ if (lD(selected)) {
63498
+ throw new Error("Kit selection cancelled");
63499
+ }
63500
+ const result = selected;
63501
+ if (!result.includes(BASE_KIT) && kits.includes(BASE_KIT)) {
63502
+ logger.info(`"${AVAILABLE_KITS[BASE_KIT].name}" is the base kit and was added back automatically.`);
63503
+ result.unshift(BASE_KIT);
63504
+ }
63505
+ const idx = result.indexOf(BASE_KIT);
63506
+ if (idx > 0) {
63507
+ result.splice(idx, 1);
63508
+ result.unshift(BASE_KIT);
63509
+ }
63510
+ return result;
63511
+ }
63512
+ async function getDirectory(defaultDir = ".") {
63513
+ const dir = await te({
63514
+ message: "Enter target directory:",
63515
+ placeholder: `Press Enter for "${defaultDir}"`,
63516
+ validate: () => {
63517
+ return;
63518
+ }
63519
+ });
63520
+ if (lD(dir)) {
63521
+ throw new Error("Directory input cancelled");
63522
+ }
63523
+ const trimmed = (dir ?? "").trim();
63524
+ return trimmed.length > 0 ? trimmed : defaultDir;
63525
+ }
63526
+ // src/domains/versioning/version-selector.ts
63273
63527
  init_auth_client();
63274
- import { mkdir as mkdir22, readFile as readFile38, unlink as unlink12, writeFile as writeFile29 } from "node:fs/promises";
63275
- import { join as join86 } from "node:path";
63528
+ init_github_client();
63276
63529
 
63277
63530
  // src/domains/installation/release-source.ts
63278
63531
  init_auth_client();
@@ -63383,900 +63636,198 @@ class WorkerSource {
63383
63636
  }
63384
63637
  }
63385
63638
 
63386
- // src/domains/versioning/checking/version-utils.ts
63387
- var import_compare_versions2 = __toESM(require_umd(), 1);
63388
- function isUpdateCheckDisabled() {
63389
- return process.env.NO_UPDATE_NOTIFIER === "1" || process.env.NO_UPDATE_NOTIFIER === "true" || !process.stdout.isTTY;
63390
- }
63391
- function normalizeVersion(version3) {
63392
- return version3.replace(/^v/i, "");
63393
- }
63394
- function isPrereleaseVersion(version3) {
63395
- if (!version3)
63396
- return false;
63397
- return /-(beta|alpha|rc|dev)[.\d]/i.test(normalizeVersion(version3));
63639
+ // src/domains/installation/release-entry-adapter.ts
63640
+ function githubReleaseToEntry(release, kit = "engineer") {
63641
+ const expected = assetNameFor(kit);
63642
+ const expectedAsset = release.assets.find((a3) => a3.name === expected);
63643
+ const fallback2 = release.assets[0];
63644
+ const archive = expectedAsset?.name ?? fallback2?.name ?? expected;
63645
+ return {
63646
+ tag: release.tag_name,
63647
+ version: release.tag_name.replace(/^v/, ""),
63648
+ prerelease: release.prerelease,
63649
+ publishedAt: release.published_at ?? "",
63650
+ archive
63651
+ };
63398
63652
  }
63399
- function parseVersionParts(version3) {
63400
- const normalized = normalizeVersion(version3);
63401
- const [base, ...prereleaseParts] = normalized.split("-");
63653
+ function releaseEntryToGitHubRelease(entry, kit) {
63402
63654
  return {
63403
- base,
63404
- prerelease: prereleaseParts.length > 0 ? prereleaseParts.join("-") : null
63655
+ id: 0,
63656
+ tag_name: entry.tag,
63657
+ name: entry.tag,
63658
+ draft: false,
63659
+ prerelease: entry.prerelease,
63660
+ published_at: entry.publishedAt || undefined,
63661
+ tarball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${entry.tag}.tar.gz`,
63662
+ zipball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${entry.tag}.zip`,
63663
+ assets: []
63405
63664
  };
63406
63665
  }
63407
- function isPrereleaseOfSameBase(currentVersion, latestVersion) {
63408
- const current = parseVersionParts(currentVersion);
63409
- const latest = parseVersionParts(latestVersion);
63410
- if (!current.prerelease)
63411
- return false;
63412
- if (latest.prerelease !== null)
63666
+
63667
+ // src/domains/versioning/version-selector.ts
63668
+ init_logger();
63669
+ init_dist2();
63670
+ init_release_filter();
63671
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
63672
+
63673
+ // src/domains/versioning/selection/version-filter.ts
63674
+ var VERSION_PATTERN = /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
63675
+ function isValidVersionFormat(version3) {
63676
+ const trimmed = version3.trim();
63677
+ if (!trimmed)
63413
63678
  return false;
63414
- return current.base === latest.base;
63415
- }
63416
- function versionsMatch(installed, latest) {
63417
- return normalizeVersion(installed) === normalizeVersion(latest);
63679
+ return VERSION_PATTERN.test(trimmed);
63418
63680
  }
63419
- function isNewerVersion(currentVersion, latestVersion) {
63420
- try {
63421
- const current = normalizeVersion(currentVersion);
63422
- const latest = normalizeVersion(latestVersion);
63423
- if (isPrereleaseOfSameBase(current, latest)) {
63424
- return false;
63425
- }
63426
- return import_compare_versions2.compareVersions(latest, current) > 0;
63427
- } catch {
63428
- return false;
63429
- }
63681
+ function normalizeVersionTag(version3) {
63682
+ const trimmed = version3.trim();
63683
+ return trimmed.startsWith("v") ? trimmed : `v${trimmed}`;
63430
63684
  }
63431
-
63432
- // src/domains/sync/config-version-checker.ts
63685
+ // src/domains/versioning/selection/selection-ui.ts
63686
+ init_environment();
63433
63687
  init_logger();
63434
- init_path_resolver();
63435
- var CACHE_TTL_HOURS = 24;
63436
- var DEFAULT_CACHE_TTL_MS = CACHE_TTL_HOURS * 60 * 60 * 1000;
63437
- var MIN_CACHE_TTL_MS = 60 * 1000;
63438
- var MAX_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
63439
- function parseCacheTtl() {
63440
- const envValue = process.env.TAKUMI_SYNC_CACHE_TTL;
63441
- if (!envValue) {
63442
- return DEFAULT_CACHE_TTL_MS;
63443
- }
63444
- const parsed = Number.parseInt(envValue, 10);
63445
- if (Number.isNaN(parsed) || parsed < 0) {
63446
- logger.warning(`Invalid TAKUMI_SYNC_CACHE_TTL value "${envValue}", using default (${CACHE_TTL_HOURS}h)`);
63447
- return DEFAULT_CACHE_TTL_MS;
63448
- }
63449
- const ttlMs = parsed * 1000;
63450
- if (ttlMs < MIN_CACHE_TTL_MS) {
63451
- logger.warning(`TAKUMI_SYNC_CACHE_TTL too low (${parsed}s), using minimum (60s)`);
63452
- return MIN_CACHE_TTL_MS;
63453
- }
63454
- if (ttlMs > MAX_CACHE_TTL_MS) {
63455
- logger.warning(`TAKUMI_SYNC_CACHE_TTL too high (${parsed}s), using maximum (7 days)`);
63456
- return MAX_CACHE_TTL_MS;
63457
- }
63458
- return ttlMs;
63459
- }
63460
- var CACHE_TTL_MS = parseCacheTtl();
63461
- var CACHE_FILENAME = "config-update-cache.json";
63688
+ init_dist2();
63689
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
63462
63690
 
63463
- class ConfigVersionChecker {
63464
- static getCacheFilePath(kitType, global3) {
63465
- const cacheDir = PathResolver.getCacheDir(global3);
63466
- return join86(cacheDir, `${kitType}-${CACHE_FILENAME}`);
63467
- }
63468
- static async loadCache(kitType, global3) {
63469
- try {
63470
- const cachePath = ConfigVersionChecker.getCacheFilePath(kitType, global3);
63471
- const data = await readFile38(cachePath, "utf8");
63472
- const parsed = JSON.parse(data);
63473
- if (typeof parsed !== "object" || parsed === null || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string" || !parsed.latestVersion || parsed.lastCheck < 0 || parsed.lastCheck > Date.now() + 7 * 24 * 60 * 60 * 1000) {
63474
- logger.debug("Invalid cache structure, ignoring");
63475
- return null;
63691
+ // src/domains/versioning/version-display.ts
63692
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
63693
+
63694
+ class VersionDisplayFormatter {
63695
+ static createBadges(release) {
63696
+ const badges = [];
63697
+ if (release.isLatestStable) {
63698
+ badges.push(import_picocolors18.default.bold(import_picocolors18.default.yellow("[latest]")));
63699
+ }
63700
+ if (release.prerelease || release.isLatestBeta) {
63701
+ if (release.isLatestBeta) {
63702
+ badges.push(import_picocolors18.default.bold(import_picocolors18.default.magenta("[beta]")));
63703
+ } else {
63704
+ badges.push(import_picocolors18.default.magenta("[prerelease]"));
63476
63705
  }
63477
- return parsed;
63478
- } catch {
63479
- return null;
63706
+ } else if (!release.draft) {
63707
+ badges.push(import_picocolors18.default.blue("[stable]"));
63480
63708
  }
63481
- }
63482
- static async saveCache(kitType, global3, cache) {
63483
- try {
63484
- const cachePath = ConfigVersionChecker.getCacheFilePath(kitType, global3);
63485
- const cacheDir = PathResolver.getCacheDir(global3);
63486
- await mkdir22(cacheDir, { recursive: true });
63487
- await writeFile29(cachePath, JSON.stringify(cache, null, 2));
63488
- } catch (error) {
63489
- logger.debug(`Cache write failed: ${error instanceof Error ? error.message : "Unknown error"}`);
63709
+ if (release.draft) {
63710
+ badges.push(import_picocolors18.default.gray("[draft]"));
63490
63711
  }
63712
+ return badges.length > 0 ? ` ${badges.join(" ")}` : "";
63491
63713
  }
63492
- static async fetchLatestVersion(kitType) {
63493
- const maxRetries = 3;
63494
- const baseBackoff = 1000;
63495
- for (let attempt = 0;attempt < maxRetries; attempt++) {
63496
- try {
63497
- const worker = new WorkerSource(getServerUrl(), kitType);
63498
- const entry = await worker.fetchLatest();
63499
- const version3 = entry.version.replace(/^v/, "");
63500
- if (!version3 || version3.length > 256) {
63501
- logger.debug(`Invalid version format from Worker: ${entry.version}`);
63502
- return null;
63503
- }
63504
- const semverParts = version3.split(/[-+]/);
63505
- const coreParts = semverParts[0].split(".");
63506
- if (coreParts.length !== 3 || !coreParts.every((p) => /^\d+$/.test(p))) {
63507
- logger.debug(`Invalid version format from Worker: ${entry.version}`);
63508
- return null;
63509
- }
63510
- return { version: version3 };
63511
- } catch (error) {
63512
- if (error instanceof NotLoggedInError) {
63513
- logger.debug("Skipping update check (not logged in)");
63514
- return null;
63515
- }
63516
- const isLastAttempt = attempt === maxRetries - 1;
63517
- if (isLastAttempt) {
63518
- logger.debug(`Version check failed after ${maxRetries} attempts: ${error}`);
63519
- return null;
63520
- }
63521
- const delay2 = baseBackoff * 2 ** attempt;
63522
- await new Promise((resolve17) => setTimeout(resolve17, delay2));
63523
- }
63524
- }
63525
- return null;
63714
+ static formatChoiceLabel(release) {
63715
+ const version3 = import_picocolors18.default.green(release.displayVersion);
63716
+ const badges = VersionDisplayFormatter.createBadges(release);
63717
+ const name = release.name || "Release";
63718
+ return `${version3}${badges} ${import_picocolors18.default.dim(name)}`;
63526
63719
  }
63527
- static async checkForUpdates(kitType, currentVersion, global3 = false) {
63528
- const normalizedCurrent = currentVersion.replace(/^v/, "");
63529
- const cache = await ConfigVersionChecker.loadCache(kitType, global3);
63530
- const now = Date.now();
63531
- if (cache && now - cache.lastCheck < CACHE_TTL_MS) {
63532
- const hasUpdates = isNewerVersion(normalizedCurrent, cache.latestVersion);
63533
- return {
63534
- hasUpdates,
63535
- currentVersion: normalizedCurrent,
63536
- latestVersion: cache.latestVersion,
63537
- fromCache: true
63538
- };
63720
+ static formatChoiceHint(release) {
63721
+ const parts = [];
63722
+ if (release.relativeTime && release.relativeTime !== "Unknown") {
63723
+ parts.push(release.relativeTime);
63539
63724
  }
63540
- const result = await ConfigVersionChecker.fetchLatestVersion(kitType);
63541
- if (result) {
63542
- await ConfigVersionChecker.saveCache(kitType, global3, {
63543
- lastCheck: now,
63544
- latestVersion: result.version
63725
+ if (release.assetCount > 0) {
63726
+ const assetText = release.assetCount === 1 ? "asset" : "assets";
63727
+ parts.push(`${release.assetCount} ${assetText}`);
63728
+ }
63729
+ if (release.normalizedVersion !== release.displayVersion) {
63730
+ parts.push(`(${release.normalizedVersion})`);
63731
+ }
63732
+ return parts.length > 0 ? parts.join(", ") : "";
63733
+ }
63734
+ static createSpecialOptions(releases) {
63735
+ const options2 = [];
63736
+ const latestStable = releases.find((r2) => r2.isLatestStable && !r2.prerelease);
63737
+ if (latestStable) {
63738
+ options2.push({
63739
+ value: latestStable.tag_name,
63740
+ label: `${import_picocolors18.default.bold(import_picocolors18.default.green("Latest Stable"))} (${latestStable.displayVersion})`,
63741
+ hint: "recommended version",
63742
+ isLatest: true,
63743
+ isPrerelease: false
63545
63744
  });
63546
- const hasUpdates = isNewerVersion(normalizedCurrent, result.version);
63547
- return {
63548
- hasUpdates,
63549
- currentVersion: normalizedCurrent,
63550
- latestVersion: result.version,
63551
- fromCache: false
63552
- };
63553
63745
  }
63554
- if (cache) {
63555
- const hasUpdates = isNewerVersion(normalizedCurrent, cache.latestVersion);
63556
- return {
63557
- hasUpdates,
63558
- currentVersion: normalizedCurrent,
63559
- latestVersion: cache.latestVersion,
63560
- fromCache: true
63561
- };
63746
+ const latestBeta = releases.find((r2) => r2.isLatestBeta || r2.prerelease && !r2.draft);
63747
+ if (latestBeta) {
63748
+ options2.push({
63749
+ value: latestBeta.tag_name,
63750
+ label: `${import_picocolors18.default.bold(import_picocolors18.default.magenta("Latest Beta"))} (${latestBeta.displayVersion})`,
63751
+ hint: "latest features, may be unstable",
63752
+ isLatest: false,
63753
+ isPrerelease: true
63754
+ });
63562
63755
  }
63756
+ return options2;
63757
+ }
63758
+ static createSeparator() {
63563
63759
  return {
63564
- hasUpdates: false,
63565
- currentVersion: normalizedCurrent,
63566
- latestVersion: normalizedCurrent,
63567
- fromCache: false
63760
+ value: "separator",
63761
+ label: import_picocolors18.default.dim("─".repeat(50)),
63762
+ hint: undefined,
63763
+ isLatest: false,
63764
+ isPrerelease: false
63568
63765
  };
63569
63766
  }
63570
- static async clearCache(kitType, global3 = false) {
63571
- const cachePath = ConfigVersionChecker.getCacheFilePath(kitType, global3);
63572
- try {
63573
- await unlink12(cachePath);
63574
- logger.debug(`Cleared sync cache for ${kitType}`);
63575
- } catch (error) {
63576
- if (error.code !== "ENOENT") {
63577
- throw error;
63767
+ static createCancelOption() {
63768
+ return {
63769
+ value: "cancel",
63770
+ label: import_picocolors18.default.red("Cancel"),
63771
+ hint: "exit version selection",
63772
+ isLatest: false,
63773
+ isPrerelease: false
63774
+ };
63775
+ }
63776
+ static formatVersionChoice(release) {
63777
+ return {
63778
+ value: release.tag_name,
63779
+ label: VersionDisplayFormatter.formatChoiceLabel(release),
63780
+ hint: VersionDisplayFormatter.formatChoiceHint(release),
63781
+ isLatest: release.isLatestStable,
63782
+ isPrerelease: release.prerelease
63783
+ };
63784
+ }
63785
+ static formatReleasesToChoices(releases, includeSpecialOptions = true, limit = 30) {
63786
+ const choices = [];
63787
+ if (includeSpecialOptions) {
63788
+ const specialOptions = VersionDisplayFormatter.createSpecialOptions(releases);
63789
+ choices.push(...specialOptions);
63790
+ if (specialOptions.length > 0 && releases.length > 0) {
63791
+ choices.push(VersionDisplayFormatter.createSeparator());
63578
63792
  }
63579
63793
  }
63794
+ const limitedReleases = releases.slice(0, limit);
63795
+ for (const release of limitedReleases) {
63796
+ choices.push(VersionDisplayFormatter.formatVersionChoice(release));
63797
+ }
63798
+ if (includeSpecialOptions) {
63799
+ choices.push(VersionDisplayFormatter.createSeparator());
63800
+ choices.push(VersionDisplayFormatter.createCancelOption());
63801
+ }
63802
+ return choices;
63580
63803
  }
63581
- }
63582
- // src/domains/sync/sync-engine.ts
63583
- import { lstat as lstat6, readFile as readFile39, readlink, realpath as realpath3, stat as stat8 } from "node:fs/promises";
63584
- import { isAbsolute as isAbsolute3, join as join87, normalize as normalize8, relative as relative14 } from "node:path";
63585
- init_logger();
63586
- var MAX_SYNC_FILE_SIZE = 10 * 1024 * 1024;
63587
- var MAX_SYMLINK_DEPTH = 20;
63588
- async function validateSymlinkChain(path8, basePath, maxDepth = MAX_SYMLINK_DEPTH) {
63589
- let current = path8;
63590
- let depth = 0;
63591
- while (depth < maxDepth) {
63592
- try {
63593
- const stats = await lstat6(current);
63594
- if (!stats.isSymbolicLink())
63595
- break;
63596
- const target = await readlink(current);
63597
- const resolvedTarget = isAbsolute3(target) ? target : join87(current, "..", target);
63598
- const normalizedTarget = normalize8(resolvedTarget);
63599
- const rel = relative14(basePath, normalizedTarget);
63600
- if (rel.startsWith("..") || isAbsolute3(rel)) {
63601
- throw new Error(`Symlink chain escapes base directory at depth ${depth}: ${path8}`);
63804
+ static getDefaultChoiceIndex(choices) {
63805
+ for (let i = 0;i < choices.length; i++) {
63806
+ const choice = choices[i];
63807
+ if (choice.isLatest && !choice.isPrerelease) {
63808
+ return i;
63602
63809
  }
63603
- current = normalizedTarget;
63604
- depth++;
63605
- } catch (error) {
63606
- if (error instanceof Error && error.message.includes("Symlink chain")) {
63607
- throw error;
63810
+ }
63811
+ for (let i = 0;i < choices.length; i++) {
63812
+ if (choices[i].value !== "separator") {
63813
+ return i;
63608
63814
  }
63609
- break;
63610
63815
  }
63816
+ return 0;
63611
63817
  }
63612
- if (depth >= maxDepth) {
63613
- throw new Error(`Symlink chain too deep (>${maxDepth}): ${path8}`);
63614
- }
63615
- }
63616
- async function validateSyncPath(basePath, filePath) {
63617
- if (!filePath || filePath.trim() === "") {
63618
- throw new Error("Empty file path not allowed");
63619
- }
63620
- if (filePath.includes("\x00")) {
63621
- throw new Error(`Invalid file path (null byte): ${filePath}`);
63622
- }
63623
- if (filePath.length > 1024) {
63624
- throw new Error(`Path too long: ${filePath.slice(0, 50)}...`);
63625
- }
63626
- const normalized = normalize8(filePath);
63627
- if (isAbsolute3(normalized)) {
63628
- throw new Error(`Absolute paths not allowed: ${filePath}`);
63629
- }
63630
- if (normalized.startsWith("..") || normalized.includes("/../")) {
63631
- throw new Error(`Path traversal not allowed: ${filePath}`);
63632
- }
63633
- const fullPath = join87(basePath, normalized);
63634
- const rel = relative14(basePath, fullPath);
63635
- if (rel.startsWith("..") || isAbsolute3(rel)) {
63636
- throw new Error(`Path escapes base directory: ${filePath}`);
63637
- }
63638
- await validateSymlinkChain(fullPath, basePath);
63639
- try {
63640
- const resolvedBase = await realpath3(basePath);
63641
- const resolvedFull = await realpath3(fullPath);
63642
- const resolvedRel = relative14(resolvedBase, resolvedFull);
63643
- if (resolvedRel.startsWith("..") || isAbsolute3(resolvedRel)) {
63644
- throw new Error(`Symlink escapes base directory: ${filePath}`);
63645
- }
63646
- } catch (error) {
63647
- if (error.code === "ENOENT") {
63648
- const parentPath = join87(fullPath, "..");
63649
- try {
63650
- const resolvedBase = await realpath3(basePath);
63651
- const resolvedParent = await realpath3(parentPath);
63652
- const resolvedRel = relative14(resolvedBase, resolvedParent);
63653
- if (resolvedRel.startsWith("..") || isAbsolute3(resolvedRel)) {
63654
- throw new Error(`Parent symlink escapes base directory: ${filePath}`);
63655
- }
63656
- } catch (parentError) {
63657
- if (parentError.code !== "ENOENT") {
63658
- throw parentError;
63659
- }
63660
- }
63661
- } else {
63662
- throw error;
63663
- }
63664
- }
63665
- return fullPath;
63666
- }
63667
-
63668
- class SyncEngine {
63669
- static async createSyncPlan(trackedFiles, claudeDir, upstreamDir) {
63670
- const plan = { autoUpdate: [], needsReview: [], skipped: [] };
63671
- for (const file of trackedFiles) {
63672
- if (file.ownership === "user") {
63673
- plan.skipped.push(file);
63674
- continue;
63675
- }
63676
- let upstreamPath;
63677
- try {
63678
- upstreamPath = await validateSyncPath(upstreamDir, file.path);
63679
- } catch (error) {
63680
- logger.warning(`Skipping invalid path: ${file.path}`);
63681
- plan.skipped.push(file);
63682
- continue;
63683
- }
63684
- try {
63685
- await stat8(upstreamPath);
63686
- } catch {
63687
- plan.skipped.push(file);
63688
- continue;
63689
- }
63690
- let localPath;
63691
- try {
63692
- localPath = await validateSyncPath(claudeDir, file.path);
63693
- } catch (error) {
63694
- logger.warning(`Skipping invalid local path: ${file.path}`);
63695
- plan.skipped.push(file);
63696
- continue;
63697
- }
63698
- try {
63699
- await stat8(localPath);
63700
- } catch {
63701
- plan.autoUpdate.push(file);
63702
- continue;
63703
- }
63704
- if (file.ownership === "takumi") {
63705
- plan.autoUpdate.push(file);
63706
- continue;
63707
- }
63708
- const currentChecksum = await OwnershipChecker.calculateChecksum(localPath);
63709
- const baseChecksum = file.baseChecksum || file.checksum;
63710
- if (currentChecksum === baseChecksum) {
63711
- plan.autoUpdate.push(file);
63712
- } else {
63713
- plan.needsReview.push(file);
63714
- }
63715
- }
63716
- return plan;
63717
- }
63718
- static generateHunks(currentContent, newContent, filename, contextLines = 3) {
63719
- const patch = structuredPatch(filename, filename, currentContent, newContent, "", "", {
63720
- context: contextLines
63721
- });
63722
- return patch.hunks.map((hunk) => ({
63723
- oldStart: hunk.oldStart,
63724
- oldLines: hunk.oldLines,
63725
- newStart: hunk.newStart,
63726
- newLines: hunk.newLines,
63727
- lines: hunk.lines
63728
- }));
63729
- }
63730
- static applyHunks(content, hunks, accepted) {
63731
- const acceptedHunks = hunks.filter((_3, i) => accepted[i]);
63732
- if (acceptedHunks.length === 0) {
63733
- return content;
63734
- }
63735
- const patchStr = SyncEngine.buildUnifiedDiff(content, acceptedHunks);
63736
- const result = applyPatch(content, patchStr);
63737
- if (result === false) {
63738
- try {
63739
- return SyncEngine.applyHunksManually(content, acceptedHunks);
63740
- } catch (fallbackError) {
63741
- throw new Error(`Failed to apply ${acceptedHunks.length} hunk(s): patch and manual methods both failed. ` + `Manual error: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
63742
- }
63743
- }
63744
- return result;
63745
- }
63746
- static buildUnifiedDiff(_content, hunks) {
63747
- let diff = `--- a
63748
- +++ b
63749
- `;
63750
- for (const hunk of hunks) {
63751
- diff += `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@
63752
- `;
63753
- for (const line of hunk.lines) {
63754
- diff += `${line}
63755
- `;
63756
- }
63757
- }
63758
- return diff;
63759
- }
63760
- static applyHunksManually(content, hunks) {
63761
- const lines = content.split(`
63762
- `);
63763
- const sortedHunks = [...hunks].sort((a3, b3) => b3.oldStart - a3.oldStart);
63764
- for (const hunk of sortedHunks) {
63765
- const startIndex = hunk.oldStart - 1;
63766
- if (startIndex < 0 || startIndex > lines.length) {
63767
- logger.warning(`Hunk start ${hunk.oldStart} out of bounds (file has ${lines.length} lines)`);
63768
- continue;
63769
- }
63770
- const newLines = [];
63771
- let deleteCount = 0;
63772
- for (const line of hunk.lines) {
63773
- if (!line || line.length === 0) {
63774
- continue;
63775
- }
63776
- const prefix = line[0];
63777
- const lineContent = line.slice(1);
63778
- if (prefix === "-") {
63779
- deleteCount++;
63780
- } else if (prefix === "+" || prefix === " ") {
63781
- newLines.push(lineContent);
63782
- }
63783
- }
63784
- if (startIndex + deleteCount > lines.length) {
63785
- logger.warning(`Hunk would delete past EOF (start: ${startIndex}, delete: ${deleteCount}, lines: ${lines.length})`);
63786
- continue;
63787
- }
63788
- lines.splice(startIndex, deleteCount, ...newLines);
63789
- }
63790
- return lines.join(`
63791
- `);
63792
- }
63793
- static isBinaryFile(content) {
63794
- if (content.length === 0) {
63795
- return false;
63796
- }
63797
- const sampleSize = Math.min(content.length, 8192);
63798
- let nonPrintableCount = 0;
63799
- for (let i = 0;i < sampleSize; i++) {
63800
- const code = content.charCodeAt(i);
63801
- if (code === 0) {
63802
- return true;
63803
- }
63804
- if (code < 32 && code !== 9 && code !== 10 && code !== 13) {
63805
- nonPrintableCount++;
63806
- }
63807
- }
63808
- return nonPrintableCount / sampleSize > 0.1;
63809
- }
63810
- static async loadFileContent(filePath) {
63811
- try {
63812
- const lstats = await lstat6(filePath);
63813
- if (lstats.isSymbolicLink()) {
63814
- throw new Error(`Symlink not allowed for sync: ${filePath}`);
63815
- }
63816
- if (lstats.size > MAX_SYNC_FILE_SIZE) {
63817
- throw new Error(`File too large for sync (${Math.round(lstats.size / 1024 / 1024)}MB > ${MAX_SYNC_FILE_SIZE / 1024 / 1024}MB limit)`);
63818
- }
63819
- const buffer = await readFile39(filePath);
63820
- if (buffer.includes(0)) {
63821
- return { content: "", isBinary: true };
63822
- }
63823
- const content = buffer.toString("utf8");
63824
- if (content.includes("�")) {
63825
- return { content: "", isBinary: true };
63826
- }
63827
- if (SyncEngine.isBinaryFile(content)) {
63828
- return { content: "", isBinary: true };
63829
- }
63830
- return { content, isBinary: false };
63831
- } catch (error) {
63832
- const errMsg = error instanceof Error ? error.message : "Unknown error";
63833
- throw new Error(`Cannot read file for sync: ${filePath} - ${errMsg}`);
63834
- }
63835
- }
63836
- }
63837
- // src/domains/sync/deletion-path-filter.ts
63838
- init_path_resolver();
63839
- var import_picomatch2 = __toESM(require_picomatch2(), 1);
63840
- function filterDeletionPaths(trackedFiles, deletions) {
63841
- if (!deletions || deletions.length === 0) {
63842
- return trackedFiles;
63843
- }
63844
- const exactPaths = new Set;
63845
- const globMatchers = [];
63846
- for (const pattern of deletions) {
63847
- if (PathResolver.isGlobPattern(pattern)) {
63848
- globMatchers.push(import_picomatch2.default(pattern));
63849
- } else {
63850
- exactPaths.add(pattern);
63851
- }
63852
- }
63853
- return trackedFiles.filter((file) => {
63854
- if (exactPaths.has(file.path))
63855
- return false;
63856
- for (const matcher of globMatchers) {
63857
- if (matcher(file.path))
63858
- return false;
63859
- }
63860
- return true;
63861
- });
63862
- }
63863
- // src/domains/sync/merge-ui.ts
63864
- init_dist2();
63865
- var import_picocolors18 = __toESM(require_picocolors(), 1);
63866
- var HUNK_SEPARATOR_WIDTH = 50;
63867
- var EXTENDED_CONTEXT_LINES = 10;
63868
- var MAX_LINE_DISPLAY_LENGTH = 120;
63869
- function requireTTY() {
63870
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
63871
- throw new Error("Interactive merge requires a TTY terminal. " + "Use --yes flag for non-interactive mode, or run in a terminal.");
63872
- }
63873
- }
63874
- function truncateLine(line) {
63875
- if (line.length <= MAX_LINE_DISPLAY_LENGTH)
63876
- return line;
63877
- return `${line.slice(0, MAX_LINE_DISPLAY_LENGTH - 3)}...`;
63878
- }
63879
-
63880
- class MergeUI {
63881
- static async promptHunk(hunk, hunkIndex, totalHunks, _filename) {
63882
- requireTTY();
63883
- const lineRange = `${hunk.oldStart}-${hunk.oldStart + hunk.oldLines - 1}`;
63884
- console.log(import_picocolors18.default.cyan(`
63885
- Hunk ${hunkIndex + 1}/${totalHunks}: Lines ${lineRange}`));
63886
- console.log(import_picocolors18.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
63887
- for (const line of hunk.lines) {
63888
- const displayLine = truncateLine(line);
63889
- const prefix = line[0];
63890
- if (prefix === "+") {
63891
- console.log(import_picocolors18.default.green(displayLine));
63892
- } else if (prefix === "-") {
63893
- console.log(import_picocolors18.default.red(displayLine));
63894
- } else {
63895
- console.log(import_picocolors18.default.dim(displayLine));
63896
- }
63897
- }
63898
- console.log(import_picocolors18.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
63899
- const action = await ie({
63900
- message: "Action?",
63901
- options: [
63902
- { value: "accept", label: "[a]ccept - Apply this change" },
63903
- { value: "reject", label: "[r]eject - Keep current" },
63904
- { value: "view", label: "[v]iew - More context" },
63905
- { value: "skip", label: "[s]kip - Skip entire file" }
63906
- ]
63907
- });
63908
- if (lD(action)) {
63909
- return "skip";
63910
- }
63911
- return action;
63912
- }
63913
- static showExtendedContext(currentContent, hunk, contextLines = EXTENDED_CONTEXT_LINES) {
63914
- const lines = currentContent.split(`
63915
- `);
63916
- const startLine = Math.max(0, hunk.oldStart - 1 - contextLines);
63917
- const endLine = Math.min(lines.length, hunk.oldStart + hunk.oldLines - 1 + contextLines);
63918
- console.log(import_picocolors18.default.cyan(`
63919
- Extended context (lines ${startLine + 1}-${endLine}):`));
63920
- console.log(import_picocolors18.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
63921
- for (let i = startLine;i < endLine; i++) {
63922
- const lineNum = String(i + 1).padStart(4, " ");
63923
- const isInHunk = i >= hunk.oldStart - 1 && i < hunk.oldStart - 1 + hunk.oldLines;
63924
- const prefix = isInHunk ? import_picocolors18.default.yellow("*") : " ";
63925
- console.log(`${import_picocolors18.default.dim(lineNum)} ${prefix} ${lines[i]}`);
63926
- }
63927
- console.log(import_picocolors18.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
63928
- }
63929
- static async mergeFile(filename, currentContent, _newContent, hunks) {
63930
- console.log(import_picocolors18.default.bold(`
63931
- ━━━ ${filename} ━━━`));
63932
- console.log(import_picocolors18.default.dim(`${hunks.length} change${hunks.length === 1 ? "" : "s"} to review
63933
- `));
63934
- const decisions = [];
63935
- for (let i = 0;i < hunks.length; i++) {
63936
- let action;
63937
- do {
63938
- action = await MergeUI.promptHunk(hunks[i], i, hunks.length, filename);
63939
- if (action === "view") {
63940
- MergeUI.showExtendedContext(currentContent, hunks[i]);
63941
- }
63942
- } while (action === "view");
63943
- if (action === "skip") {
63944
- return "skipped";
63945
- }
63946
- decisions.push(action === "accept");
63947
- }
63948
- const result = SyncEngine.applyHunks(currentContent, hunks, decisions);
63949
- const applied = decisions.filter(Boolean).length;
63950
- const rejected = decisions.length - applied;
63951
- return { result, applied, rejected };
63952
- }
63953
- static displayMergeSummary(filename, applied, rejected) {
63954
- if (applied > 0 && rejected > 0) {
63955
- console.log(import_picocolors18.default.dim(` ${filename}: `) + import_picocolors18.default.green(`${applied} applied`) + import_picocolors18.default.dim(", ") + import_picocolors18.default.yellow(`${rejected} rejected`));
63956
- } else if (applied > 0) {
63957
- console.log(import_picocolors18.default.dim(` ${filename}: `) + import_picocolors18.default.green(`${applied} applied`));
63958
- } else {
63959
- console.log(import_picocolors18.default.dim(` ${filename}: `) + import_picocolors18.default.yellow(`${rejected} rejected`));
63960
- }
63961
- }
63962
- static displaySkipped(filename) {
63963
- console.log(import_picocolors18.default.dim(` ${filename}: `) + import_picocolors18.default.yellow("skipped"));
63964
- }
63965
- }
63966
- // src/domains/sync/notification-display.ts
63967
- var import_picocolors19 = __toESM(require_picocolors(), 1);
63968
- import { stdout } from "node:process";
63969
- function createNotificationBox(borderColor, boxWidth) {
63970
- const contentWidth = boxWidth - 2;
63971
- const topBorder = borderColor(`╭${"─".repeat(contentWidth)}╮`);
63972
- const bottomBorder = borderColor(`╰${"─".repeat(contentWidth)}╯`);
63973
- const emptyLine = borderColor("│") + " ".repeat(contentWidth) + borderColor("│");
63974
- const padLine = (text, visibleLen) => {
63975
- const len = visibleLen ?? text.length;
63976
- const displayText = len > contentWidth ? `${text.slice(0, contentWidth - 3)}...` : text;
63977
- const actualLen = visibleLen ?? displayText.length;
63978
- const totalPadding = contentWidth - actualLen;
63979
- const leftPadding = Math.max(0, Math.floor(totalPadding / 2));
63980
- const rightPadding = Math.max(0, totalPadding - leftPadding);
63981
- return borderColor("│") + " ".repeat(leftPadding) + displayText + " ".repeat(rightPadding) + borderColor("│");
63982
- };
63983
- return { topBorder, bottomBorder, emptyLine, padLine };
63984
- }
63985
- function displayConfigUpdateNotification(currentVersion, latestVersion, isGlobal = false) {
63986
- const displayCurrent = currentVersion.replace(/^v/, "");
63987
- const displayLatest = latestVersion.replace(/^v/, "");
63988
- const terminalWidth = stdout.columns || 80;
63989
- const boxWidth = Math.min(52, terminalWidth - 4);
63990
- const { topBorder, bottomBorder, emptyLine, padLine } = createNotificationBox(import_picocolors19.default.cyan, boxWidth);
63991
- const headerText = import_picocolors19.default.bold(import_picocolors19.default.yellow("\uD83D\uDCE6 Config Updates Available"));
63992
- const headerLen = "\uD83D\uDCE6 Config Updates Available".length;
63993
- const versionText = `${import_picocolors19.default.dim(displayCurrent)} ${import_picocolors19.default.white("→")} ${import_picocolors19.default.green(import_picocolors19.default.bold(displayLatest))}`;
63994
- const versionLen = displayCurrent.length + 3 + displayLatest.length;
63995
- const updateCmd = isGlobal ? "tkm init -g --sync" : "tkm init --sync";
63996
- const commandText = `Run: ${import_picocolors19.default.cyan(import_picocolors19.default.bold(updateCmd))}`;
63997
- const commandLen = `Run: ${updateCmd}`.length;
63998
- console.log("");
63999
- console.log(topBorder);
64000
- console.log(emptyLine);
64001
- console.log(padLine(headerText, headerLen));
64002
- console.log(padLine(versionText, versionLen));
64003
- console.log(emptyLine);
64004
- console.log(padLine(commandText, commandLen));
64005
- console.log(emptyLine);
64006
- console.log(bottomBorder);
64007
- console.log("");
64008
- }
64009
- // src/domains/sync/passive-update-check.ts
64010
- init_logger();
64011
- async function maybeShowConfigUpdateNotification(claudeDir, global3) {
64012
- try {
64013
- const detection = await detectMetadataFormat(claudeDir);
64014
- if (detection.format === "none" || !detection.metadata) {
64015
- logger.debug("No metadata found, skipping update check");
64016
- return false;
64017
- }
64018
- const kitsToCheck = [];
64019
- if (detection.format === "multi-kit" && detection.metadata.kits) {
64020
- for (const [kitType, kitInfo] of Object.entries(detection.metadata.kits)) {
64021
- if (kitInfo?.version) {
64022
- kitsToCheck.push({ kit: kitType, version: kitInfo.version });
64023
- }
64024
- }
64025
- } else if (detection.format === "legacy" && detection.detectedKit && detection.metadata.version) {
64026
- kitsToCheck.push({
64027
- kit: detection.detectedKit,
64028
- version: detection.metadata.version
64029
- });
64030
- }
64031
- if (kitsToCheck.length === 0) {
64032
- logger.debug("No kits with versions found, skipping update check");
64033
- return false;
64034
- }
64035
- for (const { kit, version: version3 } of kitsToCheck) {
64036
- const result = await ConfigVersionChecker.checkForUpdates(kit, version3, global3);
64037
- if (result.hasUpdates) {
64038
- displayConfigUpdateNotification(result.currentVersion, result.latestVersion, global3);
64039
- return true;
64040
- }
64041
- }
64042
- return false;
64043
- } catch (error) {
64044
- logger.debug(`Passive update check failed: ${error instanceof Error ? error.message : error}`);
64045
- return false;
64046
- }
64047
- }
64048
- // src/domains/ui/prompts.ts
64049
- init_package_installer();
64050
- init_logger();
64051
-
64052
- // src/domains/ui/prompts/kit-prompts.ts
64053
- init_types2();
64054
- async function selectKit(defaultKit, accessibleKits) {
64055
- const kits = accessibleKits ?? Object.keys(AVAILABLE_KITS);
64056
- const kit = await ie({
64057
- message: "Select a Takumi:",
64058
- options: kits.map((key) => ({
64059
- value: key,
64060
- label: AVAILABLE_KITS[key].name,
64061
- hint: AVAILABLE_KITS[key].description
64062
- })),
64063
- initialValue: defaultKit
64064
- });
64065
- if (lD(kit)) {
64066
- throw new Error("Kit selection cancelled");
64067
- }
64068
- return kit;
64069
- }
64070
- async function getDirectory(defaultDir = ".") {
64071
- const dir = await te({
64072
- message: "Enter target directory:",
64073
- placeholder: `Press Enter for "${defaultDir}"`,
64074
- validate: () => {
64075
- return;
64076
- }
64077
- });
64078
- if (lD(dir)) {
64079
- throw new Error("Directory input cancelled");
64080
- }
64081
- const trimmed = (dir ?? "").trim();
64082
- return trimmed.length > 0 ? trimmed : defaultDir;
64083
- }
64084
- // src/domains/versioning/version-selector.ts
64085
- init_auth_client();
64086
- init_github_client();
64087
-
64088
- // src/domains/installation/release-entry-adapter.ts
64089
- function githubReleaseToEntry(release, kit = "engineer") {
64090
- const expected = assetNameFor(kit);
64091
- const expectedAsset = release.assets.find((a3) => a3.name === expected);
64092
- const fallback2 = release.assets[0];
64093
- const archive = expectedAsset?.name ?? fallback2?.name ?? expected;
64094
- return {
64095
- tag: release.tag_name,
64096
- version: release.tag_name.replace(/^v/, ""),
64097
- prerelease: release.prerelease,
64098
- publishedAt: release.published_at ?? "",
64099
- archive
64100
- };
64101
- }
64102
- function releaseEntryToGitHubRelease(entry, kit) {
64103
- return {
64104
- id: 0,
64105
- tag_name: entry.tag,
64106
- name: entry.tag,
64107
- draft: false,
64108
- prerelease: entry.prerelease,
64109
- published_at: entry.publishedAt || undefined,
64110
- tarball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${entry.tag}.tar.gz`,
64111
- zipball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${entry.tag}.zip`,
64112
- assets: []
64113
- };
64114
- }
64115
-
64116
- // src/domains/versioning/version-selector.ts
64117
- init_logger();
64118
- init_dist2();
64119
- init_release_filter();
64120
- var import_picocolors22 = __toESM(require_picocolors(), 1);
64121
-
64122
- // src/domains/versioning/selection/version-filter.ts
64123
- var VERSION_PATTERN = /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
64124
- function isValidVersionFormat(version3) {
64125
- const trimmed = version3.trim();
64126
- if (!trimmed)
64127
- return false;
64128
- return VERSION_PATTERN.test(trimmed);
64129
- }
64130
- function normalizeVersionTag(version3) {
64131
- const trimmed = version3.trim();
64132
- return trimmed.startsWith("v") ? trimmed : `v${trimmed}`;
64133
- }
64134
- // src/domains/versioning/selection/selection-ui.ts
64135
- init_environment();
64136
- init_logger();
64137
- init_dist2();
64138
- var import_picocolors21 = __toESM(require_picocolors(), 1);
64139
-
64140
- // src/domains/versioning/version-display.ts
64141
- var import_picocolors20 = __toESM(require_picocolors(), 1);
64142
-
64143
- class VersionDisplayFormatter {
64144
- static createBadges(release) {
64145
- const badges = [];
64146
- if (release.isLatestStable) {
64147
- badges.push(import_picocolors20.default.bold(import_picocolors20.default.yellow("[latest]")));
64148
- }
64149
- if (release.prerelease || release.isLatestBeta) {
64150
- if (release.isLatestBeta) {
64151
- badges.push(import_picocolors20.default.bold(import_picocolors20.default.magenta("[beta]")));
64152
- } else {
64153
- badges.push(import_picocolors20.default.magenta("[prerelease]"));
64154
- }
64155
- } else if (!release.draft) {
64156
- badges.push(import_picocolors20.default.blue("[stable]"));
64157
- }
64158
- if (release.draft) {
64159
- badges.push(import_picocolors20.default.gray("[draft]"));
64160
- }
64161
- return badges.length > 0 ? ` ${badges.join(" ")}` : "";
64162
- }
64163
- static formatChoiceLabel(release) {
64164
- const version3 = import_picocolors20.default.green(release.displayVersion);
64165
- const badges = VersionDisplayFormatter.createBadges(release);
64166
- const name = release.name || "Release";
64167
- return `${version3}${badges} ${import_picocolors20.default.dim(name)}`;
64168
- }
64169
- static formatChoiceHint(release) {
64170
- const parts = [];
64171
- if (release.relativeTime && release.relativeTime !== "Unknown") {
64172
- parts.push(release.relativeTime);
64173
- }
64174
- if (release.assetCount > 0) {
64175
- const assetText = release.assetCount === 1 ? "asset" : "assets";
64176
- parts.push(`${release.assetCount} ${assetText}`);
64177
- }
64178
- if (release.normalizedVersion !== release.displayVersion) {
64179
- parts.push(`(${release.normalizedVersion})`);
64180
- }
64181
- return parts.length > 0 ? parts.join(", ") : "";
64182
- }
64183
- static createSpecialOptions(releases) {
64184
- const options2 = [];
64185
- const latestStable = releases.find((r2) => r2.isLatestStable && !r2.prerelease);
64186
- if (latestStable) {
64187
- options2.push({
64188
- value: latestStable.tag_name,
64189
- label: `${import_picocolors20.default.bold(import_picocolors20.default.green("Latest Stable"))} (${latestStable.displayVersion})`,
64190
- hint: "recommended version",
64191
- isLatest: true,
64192
- isPrerelease: false
64193
- });
64194
- }
64195
- const latestBeta = releases.find((r2) => r2.isLatestBeta || r2.prerelease && !r2.draft);
64196
- if (latestBeta) {
64197
- options2.push({
64198
- value: latestBeta.tag_name,
64199
- label: `${import_picocolors20.default.bold(import_picocolors20.default.magenta("Latest Beta"))} (${latestBeta.displayVersion})`,
64200
- hint: "latest features, may be unstable",
64201
- isLatest: false,
64202
- isPrerelease: true
64203
- });
64204
- }
64205
- return options2;
64206
- }
64207
- static createSeparator() {
64208
- return {
64209
- value: "separator",
64210
- label: import_picocolors20.default.dim("─".repeat(50)),
64211
- hint: undefined,
64212
- isLatest: false,
64213
- isPrerelease: false
64214
- };
64215
- }
64216
- static createCancelOption() {
64217
- return {
64218
- value: "cancel",
64219
- label: import_picocolors20.default.red("Cancel"),
64220
- hint: "exit version selection",
64221
- isLatest: false,
64222
- isPrerelease: false
64223
- };
64224
- }
64225
- static formatVersionChoice(release) {
64226
- return {
64227
- value: release.tag_name,
64228
- label: VersionDisplayFormatter.formatChoiceLabel(release),
64229
- hint: VersionDisplayFormatter.formatChoiceHint(release),
64230
- isLatest: release.isLatestStable,
64231
- isPrerelease: release.prerelease
64232
- };
64233
- }
64234
- static formatReleasesToChoices(releases, includeSpecialOptions = true, limit = 30) {
64235
- const choices = [];
64236
- if (includeSpecialOptions) {
64237
- const specialOptions = VersionDisplayFormatter.createSpecialOptions(releases);
64238
- choices.push(...specialOptions);
64239
- if (specialOptions.length > 0 && releases.length > 0) {
64240
- choices.push(VersionDisplayFormatter.createSeparator());
64241
- }
64242
- }
64243
- const limitedReleases = releases.slice(0, limit);
64244
- for (const release of limitedReleases) {
64245
- choices.push(VersionDisplayFormatter.formatVersionChoice(release));
64246
- }
64247
- if (includeSpecialOptions) {
64248
- choices.push(VersionDisplayFormatter.createSeparator());
64249
- choices.push(VersionDisplayFormatter.createCancelOption());
64250
- }
64251
- return choices;
64252
- }
64253
- static getDefaultChoiceIndex(choices) {
64254
- for (let i = 0;i < choices.length; i++) {
64255
- const choice = choices[i];
64256
- if (choice.isLatest && !choice.isPrerelease) {
64257
- return i;
64258
- }
64259
- }
64260
- for (let i = 0;i < choices.length; i++) {
64261
- if (choices[i].value !== "separator") {
64262
- return i;
64263
- }
64264
- }
64265
- return 0;
64266
- }
64267
- static isValidVersionChoice(value) {
64268
- return value !== "separator" && value !== "cancel" && value.trim().length > 0;
63818
+ static isValidVersionChoice(value) {
63819
+ return value !== "separator" && value !== "cancel" && value.trim().length > 0;
64269
63820
  }
64270
63821
  static formatError(message, suggestion) {
64271
- let output2 = import_picocolors20.default.red(`Error: ${message}`);
63822
+ let output2 = import_picocolors18.default.red(`Error: ${message}`);
64272
63823
  if (suggestion) {
64273
63824
  output2 += `
64274
- ${import_picocolors20.default.dim(suggestion)}`;
63825
+ ${import_picocolors18.default.dim(suggestion)}`;
64275
63826
  }
64276
63827
  return output2;
64277
63828
  }
64278
63829
  static formatSuccess(version3, kitName) {
64279
- return `${import_picocolors20.default.green("✓")} Selected ${import_picocolors20.default.bold(version3)} for ${import_picocolors20.default.bold(kitName)}`;
63830
+ return `${import_picocolors18.default.green("✓")} Selected ${import_picocolors18.default.bold(version3)} for ${import_picocolors18.default.bold(kitName)}`;
64280
63831
  }
64281
63832
  }
64282
63833
 
@@ -64286,7 +63837,7 @@ async function handleNoReleases(kit, allowManualEntry) {
64286
63837
  This could be due to:
64287
63838
  • No releases published yet
64288
63839
  • Network connectivity issues
64289
- • Repository access permissions`, import_picocolors21.default.yellow("No Releases Available"));
63840
+ • Repository access permissions`, import_picocolors19.default.yellow("No Releases Available"));
64290
63841
  if (!allowManualEntry) {
64291
63842
  throw new Error(`No releases available for ${kit.name}`);
64292
63843
  }
@@ -64346,34 +63897,34 @@ async function createVersionPrompt(kit, choices, _defaultIndex, allowManualEntry
64346
63897
  if (latestStable) {
64347
63898
  clackChoices.push({
64348
63899
  value: latestStable.tag_name,
64349
- label: `${import_picocolors21.default.bold(import_picocolors21.default.green("Latest Stable"))} (${latestStable.displayVersion})`,
63900
+ label: `${import_picocolors19.default.bold(import_picocolors19.default.green("Latest Stable"))} (${latestStable.displayVersion})`,
64350
63901
  hint: "recommended"
64351
63902
  });
64352
63903
  }
64353
63904
  if (allowManualEntry) {
64354
63905
  clackChoices.push({
64355
63906
  value: "manual-entry",
64356
- label: import_picocolors21.default.cyan("↳ Enter Version Manually"),
63907
+ label: import_picocolors19.default.cyan("↳ Enter Version Manually"),
64357
63908
  hint: "for older versions"
64358
63909
  });
64359
63910
  }
64360
63911
  clackChoices.push({
64361
63912
  value: "cancel",
64362
- label: import_picocolors21.default.red("✕ Cancel")
63913
+ label: import_picocolors19.default.red("✕ Cancel")
64363
63914
  });
64364
63915
  const versionChoices = choices.filter((choice) => choice.value !== "separator" && choice.value !== "cancel");
64365
63916
  for (const choice of versionChoices) {
64366
63917
  const isCurrentlyInstalled = currentVersion && (choice.value === currentVersion || choice.value === `v${currentVersion}`);
64367
- const installedMarker = isCurrentlyInstalled ? import_picocolors21.default.cyan(" (installed)") : "";
63918
+ const installedMarker = isCurrentlyInstalled ? import_picocolors19.default.cyan(" (installed)") : "";
64368
63919
  clackChoices.push({
64369
63920
  value: choice.value,
64370
63921
  label: `${choice.label}${installedMarker}`,
64371
63922
  hint: choice.hint
64372
63923
  });
64373
63924
  }
64374
- const currentVersionHint = currentVersion ? import_picocolors21.default.dim(` (current: ${currentVersion})`) : "";
63925
+ const currentVersionHint = currentVersion ? import_picocolors19.default.dim(` (current: ${currentVersion})`) : "";
64375
63926
  const selected = await ie({
64376
- message: `Select version for ${import_picocolors21.default.bold(kit.name)}${currentVersionHint}:`,
63927
+ message: `Select version for ${import_picocolors19.default.bold(kit.name)}${currentVersionHint}:`,
64377
63928
  options: clackChoices,
64378
63929
  initialValue: latestStable?.tag_name
64379
63930
  });
@@ -64398,15 +63949,15 @@ async function createVersionPrompt(kit, choices, _defaultIndex, allowManualEntry
64398
63949
  async function handleSelectionError(error, kit, allowManualEntry, retryCallback) {
64399
63950
  logger.error(`Version selection error: ${error.message}`);
64400
63951
  if (error.message.includes("401") || error.message.includes("403")) {
64401
- le(VersionDisplayFormatter.formatError("Authentication failed", "Please check your GitHub token with: sk auth"), import_picocolors21.default.red("Authentication Error"));
63952
+ le(VersionDisplayFormatter.formatError("Authentication failed", "Please check your GitHub token with: sk auth"), import_picocolors19.default.red("Authentication Error"));
64402
63953
  } else if (error.message.includes("404")) {
64403
- le(VersionDisplayFormatter.formatError("Repository access denied", "Make sure you have access to the repository"), import_picocolors21.default.red("Access Error"));
63954
+ le(VersionDisplayFormatter.formatError("Repository access denied", "Make sure you have access to the repository"), import_picocolors19.default.red("Access Error"));
64404
63955
  } else if (error.message.includes("rate limit") || error.message.includes("403")) {
64405
- le(VersionDisplayFormatter.formatError("GitHub API rate limit exceeded", "Please wait a moment and try again"), import_picocolors21.default.yellow("Rate Limited"));
63956
+ le(VersionDisplayFormatter.formatError("GitHub API rate limit exceeded", "Please wait a moment and try again"), import_picocolors19.default.yellow("Rate Limited"));
64406
63957
  } else if (error.message.includes("network") || error.message.includes("ENOTFOUND")) {
64407
- le(VersionDisplayFormatter.formatError("Network connection failed", "Please check your internet connection"), import_picocolors21.default.yellow("Network Error"));
63958
+ le(VersionDisplayFormatter.formatError("Network connection failed", "Please check your internet connection"), import_picocolors19.default.yellow("Network Error"));
64408
63959
  } else {
64409
- le(VersionDisplayFormatter.formatError(error.message || "Unknown error occurred", "Please try again or contact support"), import_picocolors21.default.red("Error"));
63960
+ le(VersionDisplayFormatter.formatError(error.message || "Unknown error occurred", "Please try again or contact support"), import_picocolors19.default.red("Error"));
64410
63961
  }
64411
63962
  if (isNonInteractive()) {
64412
63963
  logger.warning("Non-interactive mode: version selection failed, cannot retry");
@@ -64468,7 +64019,7 @@ class VersionSelector {
64468
64019
  } = options2;
64469
64020
  try {
64470
64021
  const loadingSpinner = de();
64471
- loadingSpinner.start(`Fetching versions for ${import_picocolors22.default.bold(kit.name)}...`);
64022
+ loadingSpinner.start(`Fetching versions for ${import_picocolors20.default.bold(kit.name)}...`);
64472
64023
  let releases;
64473
64024
  if (useGh) {
64474
64025
  releases = await this.githubClient.listReleasesWithCache(kit, {
@@ -64730,6 +64281,9 @@ class PromptsManager {
64730
64281
  async selectKit(defaultKit, accessibleKits) {
64731
64282
  return selectKit(defaultKit, accessibleKits);
64732
64283
  }
64284
+ async selectKits(accessibleKits, defaults2) {
64285
+ return selectKits(accessibleKits, defaults2);
64286
+ }
64733
64287
  async selectVersion(versions, defaultVersion) {
64734
64288
  return selectVersion(versions, defaultVersion);
64735
64289
  }
@@ -64805,6 +64359,9 @@ class PromptsManager {
64805
64359
  async promptSkillsInstallation() {
64806
64360
  return promptSkillsInstallation();
64807
64361
  }
64362
+ async promptAgentSelection(supported) {
64363
+ return promptAgentSelection(supported);
64364
+ }
64808
64365
  showPackageInstallationResults(results) {
64809
64366
  const successfulInstalls = [];
64810
64367
  const failedInstalls = [];
@@ -64868,6 +64425,8 @@ class PromptsManager {
64868
64425
 
64869
64426
  // src/commands/init/init-command.ts
64870
64427
  init_logger();
64428
+ init_path_resolver();
64429
+ init_types2();
64871
64430
 
64872
64431
  // src/commands/init/context-factory.ts
64873
64432
  function createInitContext(rawOptions, prompts) {
@@ -64959,12 +64518,12 @@ import * as path8 from "node:path";
64959
64518
  // src/domains/github/auth-prompt.ts
64960
64519
  init_dist2();
64961
64520
  init_github_auth();
64962
- var import_picocolors23 = __toESM(require_picocolors(), 1);
64521
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
64963
64522
  async function promptForAuth() {
64964
64523
  const hasGit = GitCloneManager.isGitInstalled();
64965
64524
  const hasSshKeys = hasGit && GitCloneManager.hasSshKeys();
64966
64525
  const hasGhCli = AuthManager.isGhCliInstalled();
64967
- oe(import_picocolors23.default.yellow("No GitHub authentication found"));
64526
+ oe(import_picocolors21.default.yellow("No GitHub authentication found"));
64968
64527
  const options2 = [];
64969
64528
  if (hasGit) {
64970
64529
  const hint = hasSshKeys ? "SSH keys detected" : "Will use HTTPS";
@@ -65017,7 +64576,7 @@ async function promptForAuth() {
65017
64576
  return { method: "cancel" };
65018
64577
  }
65019
64578
  if (typeof token === "string" && token.startsWith("github_pat_")) {
65020
- le(import_picocolors23.default.yellow(`⚠️ Fine-grained PATs cannot access repos where you're a collaborator.
64579
+ le(import_picocolors21.default.yellow(`⚠️ Fine-grained PATs cannot access repos where you're a collaborator.
65021
64580
  ` + " If you encounter access issues, use a Classic PAT instead."));
65022
64581
  }
65023
64582
  return { method: "token", token };
@@ -65032,9 +64591,9 @@ init_github_client();
65032
64591
  // src/domains/installation/download-manager.ts
65033
64592
  init_environment();
65034
64593
  init_logger();
65035
- import { mkdir as mkdir27, stat as stat11 } from "node:fs/promises";
64594
+ import { mkdir as mkdir26, stat as stat10 } from "node:fs/promises";
65036
64595
  import { tmpdir as tmpdir3 } from "node:os";
65037
- import { join as join93 } from "node:path";
64596
+ import { join as join91 } from "node:path";
65038
64597
 
65039
64598
  // src/shared/temp-cleanup.ts
65040
64599
  init_logger();
@@ -65052,13 +64611,13 @@ var import_ignore3 = __toESM(require_ignore(), 1);
65052
64611
  init_logger();
65053
64612
  init_output_manager();
65054
64613
  import { createWriteStream as createWriteStream2, rmSync as rmSync3 } from "node:fs";
65055
- import { mkdir as mkdir23 } from "node:fs/promises";
65056
- import { join as join88 } from "node:path";
64614
+ import { mkdir as mkdir22 } from "node:fs/promises";
64615
+ import { join as join86 } from "node:path";
65057
64616
 
65058
64617
  // src/shared/progress-bar.ts
65059
64618
  init_output_manager();
65060
64619
  init_terminal_utils();
65061
- var import_picocolors24 = __toESM(require_picocolors(), 1);
64620
+ var import_picocolors22 = __toESM(require_picocolors(), 1);
65062
64621
  var BAR_CHARS = {
65063
64622
  unicode: { filled: "█", empty: "░" },
65064
64623
  ascii: { filled: "=", empty: "-" }
@@ -65116,7 +64675,7 @@ class ProgressBar {
65116
64675
  this.clearLine();
65117
64676
  if (message) {
65118
64677
  const symbols = output.getSymbols();
65119
- console.log(`${import_picocolors24.default.green(symbols.success)} ${message}`);
64678
+ console.log(`${import_picocolors22.default.green(symbols.success)} ${message}`);
65120
64679
  }
65121
64680
  }
65122
64681
  clearLine() {
@@ -65218,13 +64777,13 @@ init_types2();
65218
64777
  // src/domains/installation/utils/path-security.ts
65219
64778
  init_types2();
65220
64779
  import { lstatSync as lstatSync3, realpathSync as realpathSync2 } from "node:fs";
65221
- import { relative as relative15, resolve as resolve17 } from "node:path";
64780
+ import { relative as relative14, resolve as resolve17 } from "node:path";
65222
64781
  var MAX_EXTRACTION_SIZE = 500 * 1024 * 1024;
65223
64782
  function isPathSafe(basePath, targetPath) {
65224
64783
  const resolvedBase = resolve17(basePath);
65225
64784
  try {
65226
- const stat9 = lstatSync3(targetPath);
65227
- if (stat9.isSymbolicLink()) {
64785
+ const stat8 = lstatSync3(targetPath);
64786
+ if (stat8.isSymbolicLink()) {
65228
64787
  const realTarget = realpathSync2(targetPath);
65229
64788
  if (!realTarget.startsWith(resolvedBase)) {
65230
64789
  return false;
@@ -65232,7 +64791,7 @@ function isPathSafe(basePath, targetPath) {
65232
64791
  }
65233
64792
  } catch {}
65234
64793
  const resolvedTarget = resolve17(targetPath);
65235
- const relativePath = relative15(resolvedBase, resolvedTarget);
64794
+ const relativePath = relative14(resolvedBase, resolvedTarget);
65236
64795
  return !relativePath.startsWith("..") && !relativePath.startsWith("/") && resolvedTarget.startsWith(resolvedBase);
65237
64796
  }
65238
64797
 
@@ -65263,8 +64822,8 @@ var MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
65263
64822
  class FileDownloader {
65264
64823
  async downloadAsset(asset, destDir) {
65265
64824
  try {
65266
- const destPath = join88(destDir, asset.name);
65267
- await mkdir23(destDir, { recursive: true });
64825
+ const destPath = join86(destDir, asset.name);
64826
+ await mkdir22(destDir, { recursive: true });
65268
64827
  output.info(`Downloading ${asset.name} (${formatBytes(asset.size)})...`);
65269
64828
  logger.verbose("Download details", {
65270
64829
  url: asset.browser_download_url,
@@ -65348,8 +64907,8 @@ class FileDownloader {
65348
64907
  }
65349
64908
  async downloadFile(params) {
65350
64909
  const { url, name, size, destDir, token } = params;
65351
- const destPath = join88(destDir, name);
65352
- await mkdir23(destDir, { recursive: true });
64910
+ const destPath = join86(destDir, name);
64911
+ await mkdir22(destDir, { recursive: true });
65353
64912
  output.info(`Downloading ${name}${size ? ` (${formatBytes(size)})` : ""}...`);
65354
64913
  const headers = {};
65355
64914
  const isTarballUrl = /\/(tarball|zipball)\//.test(url);
@@ -65451,7 +65010,7 @@ init_logger();
65451
65010
  init_types2();
65452
65011
  import { constants as constants3 } from "node:fs";
65453
65012
  import { access as access3, readdir as readdir25 } from "node:fs/promises";
65454
- import { join as join89 } from "node:path";
65013
+ import { join as join87 } from "node:path";
65455
65014
  async function validateExtraction(extractDir) {
65456
65015
  try {
65457
65016
  const entries = await readdir25(extractDir, { encoding: "utf8" });
@@ -65463,7 +65022,7 @@ async function validateExtraction(extractDir) {
65463
65022
  const missingPaths = [];
65464
65023
  for (const path8 of criticalPaths) {
65465
65024
  try {
65466
- await access3(join89(extractDir, path8), constants3.F_OK);
65025
+ await access3(join87(extractDir, path8), constants3.F_OK);
65467
65026
  logger.debug(`Found: ${path8}`);
65468
65027
  } catch {
65469
65028
  logger.warning(`Expected path not found: ${path8}`);
@@ -65484,8 +65043,8 @@ async function validateExtraction(extractDir) {
65484
65043
 
65485
65044
  // src/domains/installation/extraction/tar-extractor.ts
65486
65045
  init_logger();
65487
- import { copyFile as copyFile5, mkdir as mkdir25, readdir as readdir27, rm as rm9, stat as stat9 } from "node:fs/promises";
65488
- import { join as join91 } from "node:path";
65046
+ import { copyFile as copyFile5, mkdir as mkdir24, readdir as readdir27, rm as rm9, stat as stat8 } from "node:fs/promises";
65047
+ import { join as join89 } from "node:path";
65489
65048
 
65490
65049
  // node_modules/tar/dist/esm/index.min.js
65491
65050
  import Kr from "events";
@@ -68697,8 +68256,8 @@ function decodeFilePath(path8) {
68697
68256
  // src/domains/installation/utils/file-utils.ts
68698
68257
  init_logger();
68699
68258
  init_types2();
68700
- import { copyFile as copyFile4, lstat as lstat7, mkdir as mkdir24, readdir as readdir26 } from "node:fs/promises";
68701
- import { join as join90, relative as relative16 } from "node:path";
68259
+ import { copyFile as copyFile4, lstat as lstat6, mkdir as mkdir23, readdir as readdir26 } from "node:fs/promises";
68260
+ import { join as join88, relative as relative15 } from "node:path";
68702
68261
  async function withRetry2(fn2, retries = 3) {
68703
68262
  for (let i = 0;i < retries; i++) {
68704
68263
  try {
@@ -68717,12 +68276,12 @@ var isRetryable2 = (e2) => {
68717
68276
  };
68718
68277
  var delay2 = (ms2) => new Promise((r2) => setTimeout(r2, ms2));
68719
68278
  async function moveDirectoryContents(sourceDir, destDir, shouldExclude, sizeTracker) {
68720
- await mkdir24(destDir, { recursive: true });
68279
+ await mkdir23(destDir, { recursive: true });
68721
68280
  const entries = await readdir26(sourceDir, { encoding: "utf8" });
68722
68281
  for (const entry of entries) {
68723
- const sourcePath = join90(sourceDir, entry);
68724
- const destPath = join90(destDir, entry);
68725
- const relativePath = relative16(sourceDir, sourcePath);
68282
+ const sourcePath = join88(sourceDir, entry);
68283
+ const destPath = join88(destDir, entry);
68284
+ const relativePath = relative15(sourceDir, sourcePath);
68726
68285
  if (!isPathSafe(destDir, destPath)) {
68727
68286
  logger.warning(`Skipping unsafe path: ${relativePath}`);
68728
68287
  throw new ExtractionError(`Path traversal attempt detected: ${relativePath}`);
@@ -68731,7 +68290,7 @@ async function moveDirectoryContents(sourceDir, destDir, shouldExclude, sizeTrac
68731
68290
  logger.debug(`Excluding: ${relativePath}`);
68732
68291
  continue;
68733
68292
  }
68734
- const entryStat = await lstat7(sourcePath);
68293
+ const entryStat = await lstat6(sourcePath);
68735
68294
  if (entryStat.isDirectory()) {
68736
68295
  await copyDirectory(sourcePath, destPath, shouldExclude, sizeTracker);
68737
68296
  } else if (entryStat.isFile()) {
@@ -68745,12 +68304,12 @@ async function moveDirectoryContents(sourceDir, destDir, shouldExclude, sizeTrac
68745
68304
  }
68746
68305
  }
68747
68306
  async function copyDirectory(sourceDir, destDir, shouldExclude, sizeTracker) {
68748
- await mkdir24(destDir, { recursive: true });
68307
+ await mkdir23(destDir, { recursive: true });
68749
68308
  const entries = await readdir26(sourceDir, { encoding: "utf8" });
68750
68309
  for (const entry of entries) {
68751
- const sourcePath = join90(sourceDir, entry);
68752
- const destPath = join90(destDir, entry);
68753
- const relativePath = relative16(sourceDir, sourcePath);
68310
+ const sourcePath = join88(sourceDir, entry);
68311
+ const destPath = join88(destDir, entry);
68312
+ const relativePath = relative15(sourceDir, sourcePath);
68754
68313
  if (!isPathSafe(destDir, destPath)) {
68755
68314
  logger.warning(`Skipping unsafe path: ${relativePath}`);
68756
68315
  throw new ExtractionError(`Path traversal attempt detected: ${relativePath}`);
@@ -68759,7 +68318,7 @@ async function copyDirectory(sourceDir, destDir, shouldExclude, sizeTracker) {
68759
68318
  logger.debug(`Excluding: ${relativePath}`);
68760
68319
  continue;
68761
68320
  }
68762
- const entryStat = await lstat7(sourcePath);
68321
+ const entryStat = await lstat6(sourcePath);
68763
68322
  if (entryStat.isDirectory()) {
68764
68323
  await copyDirectory(sourcePath, destPath, shouldExclude, sizeTracker);
68765
68324
  } else if (entryStat.isFile()) {
@@ -68777,7 +68336,7 @@ async function copyDirectory(sourceDir, destDir, shouldExclude, sizeTracker) {
68777
68336
  class TarExtractor {
68778
68337
  async extract(archivePath, destDir, shouldExclude, sizeTracker) {
68779
68338
  const tempExtractDir = `${destDir}-temp`;
68780
- await mkdir25(tempExtractDir, { recursive: true });
68339
+ await mkdir24(tempExtractDir, { recursive: true });
68781
68340
  try {
68782
68341
  await co({
68783
68342
  file: archivePath,
@@ -68804,8 +68363,8 @@ class TarExtractor {
68804
68363
  logger.debug(`Root entries: ${entries.join(", ")}`);
68805
68364
  if (entries.length === 1) {
68806
68365
  const rootEntry = entries[0];
68807
- const rootPath = join91(tempExtractDir, rootEntry);
68808
- const rootStat = await stat9(rootPath);
68366
+ const rootPath = join89(tempExtractDir, rootEntry);
68367
+ const rootStat = await stat8(rootPath);
68809
68368
  if (rootStat.isDirectory()) {
68810
68369
  const rootContents = await readdir27(rootPath, { encoding: "utf8" });
68811
68370
  logger.debug(`Root directory '${rootEntry}' contains: ${rootContents.join(", ")}`);
@@ -68819,8 +68378,8 @@ class TarExtractor {
68819
68378
  await moveDirectoryContents(tempExtractDir, destDir, shouldExclude, sizeTracker);
68820
68379
  }
68821
68380
  } else {
68822
- await mkdir25(destDir, { recursive: true });
68823
- await copyFile5(rootPath, join91(destDir, rootEntry));
68381
+ await mkdir24(destDir, { recursive: true });
68382
+ await copyFile5(rootPath, join89(destDir, rootEntry));
68824
68383
  }
68825
68384
  } else {
68826
68385
  logger.debug("Multiple root entries - moving all");
@@ -68840,14 +68399,14 @@ class TarExtractor {
68840
68399
  // src/domains/installation/extraction/zip-extractor.ts
68841
68400
  init_logger();
68842
68401
  import { createWriteStream as createWriteStream3 } from "node:fs";
68843
- import { chmod as chmod3, copyFile as copyFile6, mkdir as mkdir26, readdir as readdir28, rm as rm10, stat as stat10 } from "node:fs/promises";
68844
- import { dirname as dirname23, join as join92, resolve as resolve18 } from "node:path";
68402
+ import { chmod as chmod3, copyFile as copyFile6, mkdir as mkdir25, readdir as readdir28, rm as rm10, stat as stat9 } from "node:fs/promises";
68403
+ import { dirname as dirname23, join as join90, resolve as resolve18 } from "node:path";
68845
68404
  import { pipeline } from "node:stream/promises";
68846
68405
  import yauzl from "yauzl-promise";
68847
68406
  class ZipExtractor {
68848
68407
  async extract(archivePath, destDir, shouldExclude, sizeTracker) {
68849
68408
  const tempExtractDir = `${destDir}-temp`;
68850
- await mkdir26(tempExtractDir, { recursive: true });
68409
+ await mkdir25(tempExtractDir, { recursive: true });
68851
68410
  try {
68852
68411
  await this.extractToDir(archivePath, tempExtractDir);
68853
68412
  logger.debug(`Extracted ZIP to temp: ${tempExtractDir}`);
@@ -68855,8 +68414,8 @@ class ZipExtractor {
68855
68414
  logger.debug(`Root entries: ${entries.join(", ")}`);
68856
68415
  if (entries.length === 1) {
68857
68416
  const rootEntry = entries[0];
68858
- const rootPath = join92(tempExtractDir, rootEntry);
68859
- const rootStat = await stat10(rootPath);
68417
+ const rootPath = join90(tempExtractDir, rootEntry);
68418
+ const rootStat = await stat9(rootPath);
68860
68419
  if (rootStat.isDirectory()) {
68861
68420
  const rootContents = await readdir28(rootPath, { encoding: "utf8" });
68862
68421
  logger.debug(`Root directory '${rootEntry}' contains: ${rootContents.join(", ")}`);
@@ -68870,8 +68429,8 @@ class ZipExtractor {
68870
68429
  await moveDirectoryContents(tempExtractDir, destDir, shouldExclude, sizeTracker);
68871
68430
  }
68872
68431
  } else {
68873
- await mkdir26(destDir, { recursive: true });
68874
- await copyFile6(rootPath, join92(destDir, rootEntry));
68432
+ await mkdir25(destDir, { recursive: true });
68433
+ await copyFile6(rootPath, join90(destDir, rootEntry));
68875
68434
  }
68876
68435
  } else {
68877
68436
  logger.debug("Multiple root entries - moving all");
@@ -68899,10 +68458,10 @@ class ZipExtractor {
68899
68458
  throw new Error(`Unsafe zip entry path (zip-slip): ${name}`);
68900
68459
  }
68901
68460
  if (name.endsWith("/")) {
68902
- await mkdir26(outPath, { recursive: true });
68461
+ await mkdir25(outPath, { recursive: true });
68903
68462
  continue;
68904
68463
  }
68905
- await mkdir26(dirname23(outPath), { recursive: true });
68464
+ await mkdir25(dirname23(outPath), { recursive: true });
68906
68465
  const readStream = await entry.openReadStream();
68907
68466
  await pipeline(readStream, createWriteStream3(outPath));
68908
68467
  const unixMode = entry.externalFileAttributes >>> 16 & 511;
@@ -68961,7 +68520,7 @@ class DownloadManager {
68961
68520
  return this.fileDownloader.downloadFile(params);
68962
68521
  }
68963
68522
  async extractArchive(archivePath, destDir, archiveType) {
68964
- const archiveStats = await stat11(archivePath);
68523
+ const archiveStats = await stat10(archivePath);
68965
68524
  if (archiveStats.size > MAX_ARCHIVE_SIZE) {
68966
68525
  throw new ExtractionError(`Archive exceeds ${formatBytes(MAX_ARCHIVE_SIZE)} limit: ${formatBytes(archiveStats.size)}`);
68967
68526
  }
@@ -68975,7 +68534,7 @@ class DownloadManager {
68975
68534
  try {
68976
68535
  this.sizeTracker.reset();
68977
68536
  const detectedType = archiveType || detectArchiveType(archivePath);
68978
- await mkdir27(destDir, { recursive: true });
68537
+ await mkdir26(destDir, { recursive: true });
68979
68538
  if (detectedType === "tar.gz") {
68980
68539
  await this.tarExtractor.extract(archivePath, destDir, this.shouldExclude, this.sizeTracker);
68981
68540
  } else if (detectedType === "zip") {
@@ -69000,9 +68559,9 @@ class DownloadManager {
69000
68559
  async createTempDir() {
69001
68560
  const timestamp = Date.now();
69002
68561
  const counter = DownloadManager.tempDirCounter++;
69003
- const primaryTempDir = join93(tmpdir3(), `takumi-${timestamp}-${counter}`);
68562
+ const primaryTempDir = join91(tmpdir3(), `takumi-${timestamp}-${counter}`);
69004
68563
  try {
69005
- await mkdir27(primaryTempDir, { recursive: true });
68564
+ await mkdir26(primaryTempDir, { recursive: true });
69006
68565
  logger.debug(`Created temp directory: ${primaryTempDir}`);
69007
68566
  registerTempDir(primaryTempDir);
69008
68567
  return primaryTempDir;
@@ -69017,9 +68576,9 @@ Solutions:
69017
68576
  2. Set HOME environment variable
69018
68577
  3. Try running from a different directory`);
69019
68578
  }
69020
- const fallbackTempDir = join93(homeDir, ".sunagentkit", "tmp", `takumi-${timestamp}-${counter}`);
68579
+ const fallbackTempDir = join91(homeDir, ".sunagentkit", "tmp", `takumi-${timestamp}-${counter}`);
69021
68580
  try {
69022
- await mkdir27(fallbackTempDir, { recursive: true });
68581
+ await mkdir26(fallbackTempDir, { recursive: true });
69023
68582
  logger.debug(`Created temp directory (fallback): ${fallbackTempDir}`);
69024
68583
  logger.warning(`Using fallback temp directory: ${fallbackTempDir}
69025
68584
  (OS temp directory was not accessible)`);
@@ -69060,8 +68619,8 @@ function buildReleaseAllowlist(layout) {
69060
68619
  async function ensureLayoutSourceDir(projectRoot, layout, strict) {
69061
68620
  const sourceDir = path8.join(projectRoot, layout.sourceDir);
69062
68621
  try {
69063
- const stat12 = await fs11.promises.stat(sourceDir);
69064
- if (!stat12.isDirectory()) {
68622
+ const stat11 = await fs11.promises.stat(sourceDir);
68623
+ if (!stat11.isDirectory()) {
69065
68624
  throw new Error(`Expected source directory "${layout.sourceDir}" exists but is not a directory.`);
69066
68625
  }
69067
68626
  return sourceDir;
@@ -69302,8 +68861,8 @@ async function useLocalKitPath(kitPath, isMonorepo) {
69302
68861
  output.section("Using local kit");
69303
68862
  const absolutePath = path8.resolve(kitPath);
69304
68863
  try {
69305
- const stat12 = await fs11.promises.stat(absolutePath);
69306
- if (!stat12.isDirectory()) {
68864
+ const stat11 = await fs11.promises.stat(absolutePath);
68865
+ if (!stat11.isDirectory()) {
69307
68866
  throw new Error(`--kit-path must point to a directory, not a file.
69308
68867
 
69309
68868
  Provided path: ${absolutePath}
@@ -69341,8 +68900,8 @@ Please verify the path exists and is accessible.`);
69341
68900
  }
69342
68901
  const claudeDir = path8.join(absolutePath, DEFAULT_KIT_LAYOUT.runtimeDir);
69343
68902
  try {
69344
- const stat12 = await fs11.promises.stat(claudeDir);
69345
- if (!stat12.isDirectory()) {
68903
+ const stat11 = await fs11.promises.stat(claudeDir);
68904
+ if (!stat11.isDirectory()) {
69346
68905
  logger.warning(`Warning: ${claudeDir} exists but is not a directory.
69347
68906
  This may not be a valid Takumi installation.`);
69348
68907
  }
@@ -69376,15 +68935,15 @@ async function extractLocalArchive(archivePath, exclude) {
69376
68935
  const absolutePath = path8.resolve(archivePath);
69377
68936
  validateArchiveFormat(absolutePath);
69378
68937
  try {
69379
- const stat12 = await fs11.promises.stat(absolutePath);
69380
- if (!stat12.isFile()) {
68938
+ const stat11 = await fs11.promises.stat(absolutePath);
68939
+ if (!stat11.isFile()) {
69381
68940
  throw new Error(`--archive must point to a file, not a directory.
69382
68941
 
69383
68942
  Provided path: ${absolutePath}
69384
68943
 
69385
68944
  If you meant to use an extracted kit directory, use --kit-path instead.`);
69386
68945
  }
69387
- if (stat12.size === 0) {
68946
+ if (stat11.size === 0) {
69388
68947
  throw new Error(`Archive file is empty: ${absolutePath}
69389
68948
 
69390
68949
  The file exists but contains no data. Please verify the archive is not corrupted.`);
@@ -69545,8 +69104,28 @@ async function handleDownload(ctx) {
69545
69104
  // src/commands/init/phases/options-resolver.ts
69546
69105
  init_logger();
69547
69106
  init_types2();
69107
+ function parseKitFlag(raw) {
69108
+ const tokens = raw.split(",").map((s3) => s3.trim()).filter((s3) => s3.length > 0);
69109
+ if (tokens.length === 0)
69110
+ return null;
69111
+ if (tokens.length === 1 && tokens[0] === "all") {
69112
+ return Object.keys(AVAILABLE_KITS);
69113
+ }
69114
+ const seen = new Set;
69115
+ const out = [];
69116
+ for (const tok of tokens) {
69117
+ if (!isValidKitType(tok))
69118
+ return null;
69119
+ if (!seen.has(tok)) {
69120
+ seen.add(tok);
69121
+ out.push(tok);
69122
+ }
69123
+ }
69124
+ return out;
69125
+ }
69548
69126
  async function resolveOptions(ctx) {
69549
69127
  const explicitDir = ctx.rawOptions.dir !== undefined;
69128
+ const explicitAgents = Array.isArray(ctx.rawOptions.agents) && ctx.rawOptions.agents.length > 0;
69550
69129
  const parsed = UpdateCommandOptionsSchema.parse(ctx.rawOptions);
69551
69130
  const validOptions = {
69552
69131
  kit: parsed.kit,
@@ -69589,8 +69168,6 @@ async function resolveOptions(ctx) {
69589
69168
  ` + "For other agents, run init without --sync to perform a fresh install.");
69590
69169
  }
69591
69170
  validOptions.agents = Array.from(new Set(validOptions.agents));
69592
- } else {
69593
- validOptions.agents = ["claude-code"];
69594
69171
  }
69595
69172
  ConfigManager.setGlobalFlag(validOptions.global);
69596
69173
  if (validOptions.global) {
@@ -69635,10 +69212,52 @@ Example: takumi init --use-git --release v2.1.0`);
69635
69212
  Choose one approach.`);
69636
69213
  }
69637
69214
  const isNonInteractive2 = validOptions.yes || !process.stdin.isTTY || process.env.CI === "true" || process.env.NON_INTERACTIVE === "true";
69215
+ if (validOptions.kit) {
69216
+ const parsed2 = parseKitFlag(validOptions.kit);
69217
+ if (!parsed2 || parsed2.length === 0) {
69218
+ throw new Error(`Invalid --kit value: "${validOptions.kit}".
69219
+ Valid kits: ${Object.keys(AVAILABLE_KITS).join(", ")}`);
69220
+ }
69221
+ if (!parsed2.includes(BASE_KIT)) {
69222
+ if (isNonInteractive2) {
69223
+ throw new Error(`The "${BASE_KIT}" kit is required when installing additional kits.
69224
+ Re-run with explicit base kit, e.g. --kit ${BASE_KIT} --kit ${parsed2.join(" --kit ")}`);
69225
+ }
69226
+ logger.info(`"${BASE_KIT}" is the required base kit — adding it automatically.`);
69227
+ parsed2.unshift(BASE_KIT);
69228
+ } else {
69229
+ const idx = parsed2.indexOf(BASE_KIT);
69230
+ if (idx > 0) {
69231
+ parsed2.splice(idx, 1);
69232
+ parsed2.unshift(BASE_KIT);
69233
+ }
69234
+ }
69235
+ validOptions.selectedKits = parsed2;
69236
+ }
69638
69237
  if (validOptions.yes) {
69639
69238
  logger.info("Running in non-interactive mode (--yes flag)");
69640
69239
  }
69641
69240
  const initialInstallSkills = validOptions.installSkills ? true : undefined;
69241
+ if (!explicitAgents) {
69242
+ if (isNonInteractive2) {
69243
+ validOptions.agents = ["claude-code"];
69244
+ } else {
69245
+ const supported = listSupportedProviders();
69246
+ const selected = await ctx.prompts.promptAgentSelection(supported);
69247
+ if (selected === null) {
69248
+ logger.warning("Agent selection cancelled");
69249
+ return {
69250
+ ...ctx,
69251
+ options: validOptions,
69252
+ explicitDir,
69253
+ isNonInteractive: isNonInteractive2,
69254
+ installSkills: initialInstallSkills,
69255
+ cancelled: true
69256
+ };
69257
+ }
69258
+ validOptions.agents = selected;
69259
+ }
69260
+ }
69642
69261
  return {
69643
69262
  ...ctx,
69644
69263
  options: validOptions,
@@ -69648,8 +69267,8 @@ Choose one approach.`);
69648
69267
  };
69649
69268
  }
69650
69269
  // src/commands/init/phases/selection-handler.ts
69651
- import { mkdir as mkdir28 } from "node:fs/promises";
69652
- import { join as join97, resolve as resolve22 } from "node:path";
69270
+ import { mkdir as mkdir27 } from "node:fs/promises";
69271
+ import { join as join95, resolve as resolve22 } from "node:path";
69653
69272
 
69654
69273
  // src/commands/shared/agent-selector.ts
69655
69274
  function selectAgents(opts = {}) {
@@ -69730,13 +69349,13 @@ async function runPreflightChecks() {
69730
69349
  errorLines: []
69731
69350
  };
69732
69351
  try {
69733
- const { stdout: stdout2 } = await execAsync7("gh --version", { timeout: GH_COMMAND_TIMEOUT_MS });
69734
- const match2 = stdout2.match(/(\d+\.\d+\.\d+)/);
69352
+ const { stdout } = await execAsync7("gh --version", { timeout: GH_COMMAND_TIMEOUT_MS });
69353
+ const match2 = stdout.match(/(\d+\.\d+\.\d+)/);
69735
69354
  if (!match2) {
69736
- logger.debug(`GitHub CLI version not detected from output: ${stdout2.trim()}`);
69355
+ logger.debug(`GitHub CLI version not detected from output: ${stdout.trim()}`);
69737
69356
  result.ghInstalled = true;
69738
69357
  result.errorLines.push("✗ GitHub CLI installed but version could not be detected");
69739
- result.errorLines.push(` Output: ${stdout2.trim().slice(0, 100)}`);
69358
+ result.errorLines.push(` Output: ${stdout.trim().slice(0, 100)}`);
69740
69359
  result.errorLines.push(" Try running: gh --version");
69741
69360
  return result;
69742
69361
  }
@@ -69794,7 +69413,7 @@ async function runPreflightChecks() {
69794
69413
 
69795
69414
  // src/domains/installation/fresh-installer.ts
69796
69415
  import { existsSync as existsSync45, readdirSync as readdirSync4, rmSync as rmSync4, rmdirSync as rmdirSync2, unlinkSync as unlinkSync4 } from "node:fs";
69797
- import { dirname as dirname26, join as join96, resolve as resolve21 } from "node:path";
69416
+ import { dirname as dirname26, join as join94, resolve as resolve21 } from "node:path";
69798
69417
  init_logger();
69799
69418
  var import_fs_extra31 = __toESM(require_lib(), 1);
69800
69419
  var TAKUMI_SUBDIRECTORIES = ["commands", "agents", "skills", "rules", "hooks"];
@@ -69866,7 +69485,7 @@ async function removeFilesByOwnership(claudeDir, analysis, includeModified) {
69866
69485
  const filesToRemove = includeModified ? [...analysis.ckFiles, ...analysis.ckModifiedFiles] : analysis.ckFiles;
69867
69486
  const filesToPreserve = includeModified ? analysis.userFiles : [...analysis.ckModifiedFiles, ...analysis.userFiles];
69868
69487
  for (const file of filesToRemove) {
69869
- const fullPath = join96(claudeDir, file.path);
69488
+ const fullPath = join94(claudeDir, file.path);
69870
69489
  try {
69871
69490
  if (existsSync45(fullPath)) {
69872
69491
  unlinkSync4(fullPath);
@@ -69891,7 +69510,7 @@ async function removeFilesByOwnership(claudeDir, analysis, includeModified) {
69891
69510
  };
69892
69511
  }
69893
69512
  async function updateMetadataAfterFresh(claudeDir, removedFiles) {
69894
- const metadataPath = join96(claudeDir, "metadata.json");
69513
+ const metadataPath = join94(claudeDir, "metadata.json");
69895
69514
  if (!await import_fs_extra31.pathExists(metadataPath)) {
69896
69515
  return;
69897
69516
  }
@@ -69934,7 +69553,7 @@ async function removeSubdirectoriesFallback(claudeDir) {
69934
69553
  const removedFiles = [];
69935
69554
  let removedDirCount = 0;
69936
69555
  for (const subdir of TAKUMI_SUBDIRECTORIES) {
69937
- const subdirPath = join96(claudeDir, subdir);
69556
+ const subdirPath = join94(claudeDir, subdir);
69938
69557
  if (await import_fs_extra31.pathExists(subdirPath)) {
69939
69558
  rmSync4(subdirPath, { recursive: true, force: true });
69940
69559
  removedDirCount++;
@@ -69942,7 +69561,7 @@ async function removeSubdirectoriesFallback(claudeDir) {
69942
69561
  logger.debug(`Removed subdirectory: ${subdir}/`);
69943
69562
  }
69944
69563
  }
69945
- const metadataPath = join96(claudeDir, "metadata.json");
69564
+ const metadataPath = join94(claudeDir, "metadata.json");
69946
69565
  if (await import_fs_extra31.pathExists(metadataPath)) {
69947
69566
  unlinkSync4(metadataPath);
69948
69567
  removedFiles.push("metadata.json");
@@ -69979,10 +69598,56 @@ async function handleFreshInstallation(claudeDir, prompts) {
69979
69598
  if (result.preservedCount > 0) {
69980
69599
  logger.verbose(`Preserved user files: ${result.preservedFiles.slice(0, 5).join(", ")}${result.preservedFiles.length > 5 ? ` and ${result.preservedFiles.length - 5} more` : ""}`);
69981
69600
  }
69982
- return true;
69983
- } catch (error) {
69984
- spinner.fail("Failed to remove Takumi files");
69985
- throw new Error(`Failed to remove files: ${error instanceof Error ? error.message : "Unknown error"}`);
69601
+ return true;
69602
+ } catch (error) {
69603
+ spinner.fail("Failed to remove Takumi files");
69604
+ throw new Error(`Failed to remove files: ${error instanceof Error ? error.message : "Unknown error"}`);
69605
+ }
69606
+ }
69607
+
69608
+ // src/domains/versioning/checking/version-utils.ts
69609
+ var import_compare_versions2 = __toESM(require_umd(), 1);
69610
+ function isUpdateCheckDisabled() {
69611
+ return process.env.NO_UPDATE_NOTIFIER === "1" || process.env.NO_UPDATE_NOTIFIER === "true" || !process.stdout.isTTY;
69612
+ }
69613
+ function normalizeVersion(version3) {
69614
+ return version3.replace(/^v/i, "");
69615
+ }
69616
+ function isPrereleaseVersion(version3) {
69617
+ if (!version3)
69618
+ return false;
69619
+ return /-(beta|alpha|rc|dev)[.\d]/i.test(normalizeVersion(version3));
69620
+ }
69621
+ function parseVersionParts(version3) {
69622
+ const normalized = normalizeVersion(version3);
69623
+ const [base, ...prereleaseParts] = normalized.split("-");
69624
+ return {
69625
+ base,
69626
+ prerelease: prereleaseParts.length > 0 ? prereleaseParts.join("-") : null
69627
+ };
69628
+ }
69629
+ function isPrereleaseOfSameBase(currentVersion, latestVersion) {
69630
+ const current = parseVersionParts(currentVersion);
69631
+ const latest = parseVersionParts(latestVersion);
69632
+ if (!current.prerelease)
69633
+ return false;
69634
+ if (latest.prerelease !== null)
69635
+ return false;
69636
+ return current.base === latest.base;
69637
+ }
69638
+ function versionsMatch(installed, latest) {
69639
+ return normalizeVersion(installed) === normalizeVersion(latest);
69640
+ }
69641
+ function isNewerVersion(currentVersion, latestVersion) {
69642
+ try {
69643
+ const current = normalizeVersion(currentVersion);
69644
+ const latest = normalizeVersion(latestVersion);
69645
+ if (isPrereleaseOfSameBase(current, latest)) {
69646
+ return false;
69647
+ }
69648
+ return import_compare_versions2.compareVersions(latest, current) > 0;
69649
+ } catch {
69650
+ return false;
69986
69651
  }
69987
69652
  }
69988
69653
 
@@ -70052,275 +69717,950 @@ async function handleSelection(ctx) {
70052
69717
  return { ...ctx, cancelled: true };
70053
69718
  }
70054
69719
  }
70055
- let kitType;
70056
- const kitOption = ctx.options.kit || config.defaults?.kit;
70057
- if (kitOption) {
69720
+ let selectedKits = ctx.options.selectedKits;
69721
+ if (!selectedKits || selectedKits.length === 0) {
70058
69722
  const allKitTypes = Object.keys(AVAILABLE_KITS);
70059
- if (kitOption === "all" || kitOption.includes(",")) {
70060
- const kitsToInstall = accessibleKits ?? allKitTypes;
70061
- if (kitsToInstall.length === 0) {
70062
- logger.error("No kits accessible for installation");
69723
+ const kitOption = ctx.options.kit || config.defaults?.kit;
69724
+ if (kitOption) {
69725
+ if (kitOption === "all") {
69726
+ selectedKits = accessibleKits ?? allKitTypes;
69727
+ } else if (!isValidKitType(kitOption)) {
69728
+ logger.error(`Invalid kit: ${kitOption}`);
69729
+ logger.info(`Valid kits: ${allKitTypes.join(", ")}`);
69730
+ return { ...ctx, cancelled: true };
69731
+ } else {
69732
+ selectedKits = [kitOption];
69733
+ }
69734
+ }
69735
+ if (!selectedKits || selectedKits.length === 0) {
69736
+ if (ctx.isNonInteractive) {
69737
+ if (!accessibleKits || accessibleKits.length === 0) {
69738
+ throw new Error("Kit must be specified via --kit flag in non-interactive mode (no accessible kits detected)");
69739
+ }
69740
+ selectedKits = [accessibleKits[0]];
69741
+ logger.info(`Auto-selected: ${AVAILABLE_KITS[selectedKits[0]].name}`);
69742
+ } else if (accessibleKits?.length === 1) {
69743
+ selectedKits = [accessibleKits[0]];
69744
+ logger.info(`Using ${AVAILABLE_KITS[selectedKits[0]].name} (only accessible kit)`);
69745
+ } else {
69746
+ selectedKits = await ctx.prompts.selectKits(accessibleKits);
69747
+ }
69748
+ }
69749
+ }
69750
+ if (accessibleKits) {
69751
+ const inaccessible = selectedKits.filter((k3) => !accessibleKits.includes(k3));
69752
+ if (inaccessible.length > 0) {
69753
+ logger.error(`No access to: ${inaccessible.map((k3) => AVAILABLE_KITS[k3].name).join(", ")}`);
69754
+ logger.info("Request access from your team lead or check your GitHub invitation");
69755
+ return { ...ctx, cancelled: true };
69756
+ }
69757
+ }
69758
+ if (selectedKits.includes(BASE_KIT)) {
69759
+ const idx = selectedKits.indexOf(BASE_KIT);
69760
+ if (idx > 0) {
69761
+ selectedKits.splice(idx, 1);
69762
+ selectedKits.unshift(BASE_KIT);
69763
+ }
69764
+ }
69765
+ const kitType = selectedKits[0];
69766
+ const kit = AVAILABLE_KITS[kitType];
69767
+ if (selectedKits.length > 1) {
69768
+ logger.info(`Selected kits: ${selectedKits.map((k3) => AVAILABLE_KITS[k3].name).join(", ")} (installed in order)`);
69769
+ } else {
69770
+ logger.info(`Selected kit: ${kit.name}`);
69771
+ }
69772
+ let targetDir;
69773
+ if (ctx.explicitDir) {
69774
+ targetDir = ctx.options.dir;
69775
+ logger.info(`Using explicit directory: ${targetDir}`);
69776
+ } else if (ctx.options.global) {
69777
+ const targetAgents = selectAgents({ explicit: ctx.options.agents });
69778
+ targetDir = resolveGlobalTargetDir(targetAgents);
69779
+ logger.info(`Using global kit directory: ${targetDir}`);
69780
+ } else {
69781
+ targetDir = config.defaults?.dir || ".";
69782
+ if (!config.defaults?.dir) {
69783
+ if (ctx.isNonInteractive) {
69784
+ logger.info("Using current directory as target");
69785
+ } else {
69786
+ targetDir = await ctx.prompts.getDirectory(targetDir);
69787
+ }
69788
+ }
69789
+ }
69790
+ const resolvedDir = resolve22(targetDir);
69791
+ logger.info(`Target directory: ${resolvedDir}`);
69792
+ if (!ctx.options.global && isLocalSameAsGlobal(resolvedDir)) {
69793
+ logger.warning("You're at HOME directory. Installing here modifies your GLOBAL Takumi.");
69794
+ if (!ctx.isNonInteractive) {
69795
+ const choice = await ctx.prompts.selectScope();
69796
+ if (choice === "cancel") {
69797
+ return { ...ctx, cancelled: true };
69798
+ }
69799
+ if (choice === "global") {
69800
+ logger.info("Proceeding with global installation");
69801
+ }
69802
+ if (choice === "different") {
69803
+ logger.info("Please run 'takumi init' from a project directory instead.");
69804
+ return { ...ctx, cancelled: true };
69805
+ }
69806
+ } else {
69807
+ logger.error("Cannot use local installation at HOME directory.");
69808
+ logger.info("Use -g/--global flag or run from a project directory.");
69809
+ return { ...ctx, cancelled: true };
69810
+ }
69811
+ }
69812
+ if (!await import_fs_extra32.pathExists(resolvedDir)) {
69813
+ if (ctx.options.global) {
69814
+ await mkdir27(resolvedDir, { recursive: true });
69815
+ logger.info(`Created global directory: ${resolvedDir}`);
69816
+ } else {
69817
+ logger.error(`Directory does not exist: ${resolvedDir}`);
69818
+ logger.info('Use "tkm new" to create a new project');
69819
+ return { ...ctx, cancelled: true };
69820
+ }
69821
+ }
69822
+ if (!ctx.options.fresh) {
69823
+ const prefix = PathResolver.getPathPrefix(ctx.options.global);
69824
+ const claudeDir = prefix ? join95(resolvedDir, prefix) : resolvedDir;
69825
+ try {
69826
+ const existingMetadata = await readManifest(claudeDir);
69827
+ if (existingMetadata?.kits) {
69828
+ const existingKitTypes = Object.keys(existingMetadata.kits);
69829
+ const otherKits = existingKitTypes.filter((k3) => k3 !== kitType);
69830
+ if (otherKits.length > 0) {
69831
+ const kitsRecord = existingMetadata.kits;
69832
+ const existingKitsDisplay = otherKits.map((k3) => `${k3}@${kitsRecord[k3]?.version || "unknown"}`).join(", ");
69833
+ if (!ctx.options.yes && !ctx.isNonInteractive) {
69834
+ try {
69835
+ const confirmAdd = await ctx.prompts.confirm(`${existingKitsDisplay} already installed. Add ${kit.name} alongside?`);
69836
+ if (!confirmAdd) {
69837
+ logger.warning("Multi-kit installation cancelled by user");
69838
+ return { ...ctx, cancelled: true };
69839
+ }
69840
+ logger.info(`Adding ${kit.name} alongside existing kit(s)`);
69841
+ } catch {
69842
+ logger.warning("Prompt cancelled or interrupted");
69843
+ return { ...ctx, cancelled: true };
69844
+ }
69845
+ } else {
69846
+ const reason = ctx.options.yes ? "(--yes flag)" : "(non-interactive mode)";
69847
+ logger.info(`Adding ${kit.name} alongside ${existingKitsDisplay} ${reason}`);
69848
+ }
69849
+ }
69850
+ }
69851
+ } catch (error) {
69852
+ logger.debug(`Metadata read skipped: ${error instanceof Error ? error.message : "unknown error"}`);
69853
+ }
69854
+ }
69855
+ if (ctx.options.fresh) {
69856
+ const prefix = PathResolver.getPathPrefix(ctx.options.global);
69857
+ const claudeDir = prefix ? join95(resolvedDir, prefix) : resolvedDir;
69858
+ const canProceed = await handleFreshInstallation(claudeDir, ctx.prompts);
69859
+ if (!canProceed) {
69860
+ return { ...ctx, cancelled: true };
69861
+ }
69862
+ }
69863
+ const isOfflineMode = !!(ctx.options.kitPath || ctx.options.archive);
69864
+ const github = !isOfflineMode && useLegacyGh ? new GitHubClient : null;
69865
+ let selectedVersion = ctx.options.release;
69866
+ if (!selectedVersion && ctx.isNonInteractive && !ctx.options.yes && !isOfflineMode) {
69867
+ throw new Error("Non-interactive mode requires either: --release <tag> OR --yes (uses latest)");
69868
+ }
69869
+ if (!selectedVersion && ctx.options.yes && !isOfflineMode) {
69870
+ logger.info("Using latest stable version (--yes flag)");
69871
+ }
69872
+ if (!selectedVersion && !ctx.isNonInteractive && !isOfflineMode) {
69873
+ logger.info("Fetching available versions...");
69874
+ let currentVersion = null;
69875
+ try {
69876
+ const prefix = PathResolver.getPathPrefix(ctx.options.global);
69877
+ const claudeDir = prefix ? join95(resolvedDir, prefix) : resolvedDir;
69878
+ const existingMetadata = await readManifest(claudeDir);
69879
+ currentVersion = existingMetadata?.kits?.[kitType]?.version || null;
69880
+ if (currentVersion) {
69881
+ logger.debug(`Current installed version: ${currentVersion}`);
69882
+ }
69883
+ } catch {}
69884
+ try {
69885
+ const versionResult = await ctx.prompts.selectVersionEnhanced({
69886
+ kit,
69887
+ includePrereleases: ctx.options.beta,
69888
+ limit: 10,
69889
+ allowManualEntry: true,
69890
+ forceRefresh: ctx.options.refresh,
69891
+ currentVersion,
69892
+ useGh: useLegacyGh
69893
+ });
69894
+ if (!versionResult) {
69895
+ logger.warning("Version selection cancelled by user");
69896
+ return { ...ctx, cancelled: true };
69897
+ }
69898
+ selectedVersion = versionResult;
69899
+ logger.success(`Selected version: ${selectedVersion}`);
69900
+ } catch (error) {
69901
+ logger.error("Failed to fetch versions, using latest release");
69902
+ logger.debug(`Version selection error: ${error.message}`);
69903
+ selectedVersion = undefined;
69904
+ }
69905
+ }
69906
+ let release;
69907
+ if (isOfflineMode) {
69908
+ release = undefined;
69909
+ logger.verbose("Offline mode - skipping release fetch", {
69910
+ kitPath: ctx.options.kitPath,
69911
+ archive: ctx.options.archive
69912
+ });
69913
+ } else if (ctx.options.useGit && selectedVersion) {
69914
+ release = {
69915
+ id: 0,
69916
+ tag_name: selectedVersion,
69917
+ name: selectedVersion,
69918
+ draft: false,
69919
+ prerelease: selectedVersion.includes("-"),
69920
+ tarball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${selectedVersion}.tar.gz`,
69921
+ zipball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${selectedVersion}.zip`,
69922
+ assets: []
69923
+ };
69924
+ logger.verbose("Using git clone mode with tag", { tag: selectedVersion });
69925
+ } else if (selectedVersion && github) {
69926
+ release = await github.getReleaseByTag(kit, selectedVersion);
69927
+ } else if (github) {
69928
+ if (ctx.options.beta) {
69929
+ logger.info("Fetching latest beta release...");
69930
+ } else {
69931
+ logger.info("Fetching latest release...");
69932
+ }
69933
+ release = await github.getLatestRelease(kit, ctx.options.beta);
69934
+ if (release.prerelease) {
69935
+ logger.success(`Found beta: ${release.tag_name}`);
69936
+ } else {
69937
+ logger.success(`Found: ${release.tag_name}`);
69938
+ }
69939
+ } else if (selectedVersion) {
69940
+ const worker = new WorkerSource(getServerUrl(), kitType);
69941
+ const entry = await worker.fetchByTag(selectedVersion);
69942
+ release = releaseEntryToGitHubRelease(entry, kit);
69943
+ } else {
69944
+ if (ctx.options.beta) {
69945
+ logger.info("Fetching latest beta release...");
69946
+ } else {
69947
+ logger.info("Fetching latest release...");
69948
+ }
69949
+ const worker = new WorkerSource(getServerUrl(), kitType);
69950
+ const entry = await worker.fetchLatest(ctx.options.beta);
69951
+ release = releaseEntryToGitHubRelease(entry, kit);
69952
+ if (release.prerelease) {
69953
+ logger.success(`Found beta: ${release.tag_name}`);
69954
+ } else {
69955
+ logger.success(`Found: ${release.tag_name}`);
69956
+ }
69957
+ }
69958
+ if (ctx.options.force && !ctx.options.yes) {
69959
+ logger.info("--force has no effect without --yes (the version-match skip only applies in non-interactive mode)");
69960
+ }
69961
+ const releaseTag = release?.tag_name;
69962
+ if (ctx.options.yes && !ctx.options.fresh && !ctx.options.force && releaseTag && !isOfflineMode) {
69963
+ try {
69964
+ const prefix = PathResolver.getPathPrefix(ctx.options.global);
69965
+ const claudeDir = prefix ? join95(resolvedDir, prefix) : resolvedDir;
69966
+ const existingMetadata = await readManifest(claudeDir);
69967
+ const installedKitVersion = existingMetadata?.kits?.[kitType]?.version;
69968
+ if (installedKitVersion && versionsMatch(installedKitVersion, releaseTag)) {
69969
+ logger.success(`Already at latest version (${kitType}@${installedKitVersion}), skipping reinstall`);
70063
69970
  return { ...ctx, cancelled: true };
70064
69971
  }
70065
- kitType = kitsToInstall[0];
70066
- logger.info(`Using kit: ${AVAILABLE_KITS[kitType].name}`);
70067
- } else {
70068
- if (!isValidKitType(kitOption)) {
70069
- logger.error(`Invalid kit: ${kitOption}`);
70070
- logger.info(`Valid kits: ${allKitTypes.join(", ")}`);
70071
- return { ...ctx, cancelled: true };
69972
+ } catch (error) {
69973
+ logger.verbose(`Metadata read failed, proceeding with installation: ${error instanceof Error ? error.message : "unknown"}`);
69974
+ }
69975
+ }
69976
+ const releasesByKit = {};
69977
+ if (release)
69978
+ releasesByKit[kitType] = release;
69979
+ const shouldPromptSecondaryVersions = selectedKits.length > 1 && !ctx.isNonInteractive && !ctx.options.yes && !isOfflineMode;
69980
+ if (shouldPromptSecondaryVersions) {
69981
+ for (const secondaryKit of selectedKits) {
69982
+ if (secondaryKit === kitType)
69983
+ continue;
69984
+ const kitConfig = AVAILABLE_KITS[secondaryKit];
69985
+ let currentSecondaryVersion = null;
69986
+ try {
69987
+ const prefix = PathResolver.getPathPrefix(ctx.options.global);
69988
+ const claudeDir = prefix ? join95(resolvedDir, prefix) : resolvedDir;
69989
+ const existingMetadata = await readManifest(claudeDir);
69990
+ currentSecondaryVersion = existingMetadata?.kits?.[secondaryKit]?.version || null;
69991
+ } catch {}
69992
+ let secondaryVersion;
69993
+ try {
69994
+ const versionResult = await ctx.prompts.selectVersionEnhanced({
69995
+ kit: kitConfig,
69996
+ includePrereleases: ctx.options.beta,
69997
+ limit: 10,
69998
+ allowManualEntry: true,
69999
+ forceRefresh: ctx.options.refresh,
70000
+ currentVersion: currentSecondaryVersion,
70001
+ useGh: useLegacyGh
70002
+ });
70003
+ if (!versionResult) {
70004
+ logger.warning(`Version selection cancelled for ${kitConfig.name}`);
70005
+ return { ...ctx, cancelled: true };
70006
+ }
70007
+ secondaryVersion = versionResult;
70008
+ logger.success(`Selected ${kitConfig.name}: ${secondaryVersion}`);
70009
+ } catch (error) {
70010
+ logger.error(`Failed to fetch versions for ${kitConfig.name}, using latest`);
70011
+ logger.debug(`Version selection error: ${error.message}`);
70012
+ secondaryVersion = undefined;
70013
+ }
70014
+ try {
70015
+ let secondaryRelease;
70016
+ if (useLegacyGh) {
70017
+ const ghClient = github ?? new GitHubClient;
70018
+ secondaryRelease = secondaryVersion ? await ghClient.getReleaseByTag(kitConfig, secondaryVersion) : await ghClient.getLatestRelease(kitConfig, ctx.options.beta);
70019
+ } else {
70020
+ const worker = new WorkerSource(getServerUrl(), secondaryKit);
70021
+ const entry = secondaryVersion ? await worker.fetchByTag(secondaryVersion) : await worker.fetchLatest(ctx.options.beta);
70022
+ secondaryRelease = releaseEntryToGitHubRelease(entry, kitConfig);
70023
+ }
70024
+ releasesByKit[secondaryKit] = secondaryRelease;
70025
+ } catch (error) {
70026
+ logger.warning(`Could not pre-fetch release for ${kitConfig.name}: ${error instanceof Error ? error.message : String(error)}`);
70027
+ }
70028
+ }
70029
+ }
70030
+ return {
70031
+ ...ctx,
70032
+ kit,
70033
+ kitType,
70034
+ selectedKits,
70035
+ resolvedDir,
70036
+ release,
70037
+ selectedVersion,
70038
+ releasesByKit,
70039
+ accessibleKits
70040
+ };
70041
+ }
70042
+ async function resolveReleaseForKit(ctx, kitType) {
70043
+ const isOfflineMode = !!(ctx.options.kitPath || ctx.options.archive);
70044
+ if (isOfflineMode)
70045
+ return;
70046
+ const kit = AVAILABLE_KITS[kitType];
70047
+ if (ctx.options.useGit) {
70048
+ throw new Error(`--use-git is not supported for multi-kit installs (secondary kit: ${kitType}). Run kits individually.`);
70049
+ }
70050
+ if (ctx.options.useGh) {
70051
+ const github = new GitHubClient;
70052
+ const release = await github.getLatestRelease(kit, ctx.options.beta);
70053
+ return release;
70054
+ }
70055
+ const worker = new WorkerSource(getServerUrl(), kitType);
70056
+ const entry = await worker.fetchLatest(ctx.options.beta);
70057
+ return releaseEntryToGitHubRelease(entry, kit);
70058
+ }
70059
+ function resolveGlobalTargetDir(targetAgents) {
70060
+ const primary = targetAgents[0];
70061
+ if (primary) {
70062
+ const installer = getInstaller(primary);
70063
+ if (installer) {
70064
+ return installer.globalRoot();
70065
+ }
70066
+ }
70067
+ return getClaudeDir();
70068
+ }
70069
+ // src/commands/init/phases/sync-handler.ts
70070
+ import { copyFile as copyFile7, mkdir as mkdir29, open as open3, readFile as readFile41, rename as rename6, stat as stat12, unlink as unlink13, writeFile as writeFile31 } from "node:fs/promises";
70071
+ import { dirname as dirname27, join as join98, resolve as resolve23 } from "node:path";
70072
+
70073
+ // src/domains/sync/config-version-checker.ts
70074
+ init_auth_client();
70075
+ import { mkdir as mkdir28, readFile as readFile39, unlink as unlink12, writeFile as writeFile30 } from "node:fs/promises";
70076
+ import { join as join96 } from "node:path";
70077
+ init_logger();
70078
+ init_path_resolver();
70079
+ var CACHE_TTL_HOURS = 24;
70080
+ var DEFAULT_CACHE_TTL_MS = CACHE_TTL_HOURS * 60 * 60 * 1000;
70081
+ var MIN_CACHE_TTL_MS = 60 * 1000;
70082
+ var MAX_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
70083
+ function parseCacheTtl() {
70084
+ const envValue = process.env.TAKUMI_SYNC_CACHE_TTL;
70085
+ if (!envValue) {
70086
+ return DEFAULT_CACHE_TTL_MS;
70087
+ }
70088
+ const parsed = Number.parseInt(envValue, 10);
70089
+ if (Number.isNaN(parsed) || parsed < 0) {
70090
+ logger.warning(`Invalid TAKUMI_SYNC_CACHE_TTL value "${envValue}", using default (${CACHE_TTL_HOURS}h)`);
70091
+ return DEFAULT_CACHE_TTL_MS;
70092
+ }
70093
+ const ttlMs = parsed * 1000;
70094
+ if (ttlMs < MIN_CACHE_TTL_MS) {
70095
+ logger.warning(`TAKUMI_SYNC_CACHE_TTL too low (${parsed}s), using minimum (60s)`);
70096
+ return MIN_CACHE_TTL_MS;
70097
+ }
70098
+ if (ttlMs > MAX_CACHE_TTL_MS) {
70099
+ logger.warning(`TAKUMI_SYNC_CACHE_TTL too high (${parsed}s), using maximum (7 days)`);
70100
+ return MAX_CACHE_TTL_MS;
70101
+ }
70102
+ return ttlMs;
70103
+ }
70104
+ var CACHE_TTL_MS = parseCacheTtl();
70105
+ var CACHE_FILENAME = "config-update-cache.json";
70106
+
70107
+ class ConfigVersionChecker {
70108
+ static getCacheFilePath(kitType, global3) {
70109
+ const cacheDir = PathResolver.getCacheDir(global3);
70110
+ return join96(cacheDir, `${kitType}-${CACHE_FILENAME}`);
70111
+ }
70112
+ static async loadCache(kitType, global3) {
70113
+ try {
70114
+ const cachePath = ConfigVersionChecker.getCacheFilePath(kitType, global3);
70115
+ const data = await readFile39(cachePath, "utf8");
70116
+ const parsed = JSON.parse(data);
70117
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string" || !parsed.latestVersion || parsed.lastCheck < 0 || parsed.lastCheck > Date.now() + 7 * 24 * 60 * 60 * 1000) {
70118
+ logger.debug("Invalid cache structure, ignoring");
70119
+ return null;
70120
+ }
70121
+ return parsed;
70122
+ } catch {
70123
+ return null;
70124
+ }
70125
+ }
70126
+ static async saveCache(kitType, global3, cache) {
70127
+ try {
70128
+ const cachePath = ConfigVersionChecker.getCacheFilePath(kitType, global3);
70129
+ const cacheDir = PathResolver.getCacheDir(global3);
70130
+ await mkdir28(cacheDir, { recursive: true });
70131
+ await writeFile30(cachePath, JSON.stringify(cache, null, 2));
70132
+ } catch (error) {
70133
+ logger.debug(`Cache write failed: ${error instanceof Error ? error.message : "Unknown error"}`);
70134
+ }
70135
+ }
70136
+ static async fetchLatestVersion(kitType) {
70137
+ const maxRetries = 3;
70138
+ const baseBackoff = 1000;
70139
+ for (let attempt = 0;attempt < maxRetries; attempt++) {
70140
+ try {
70141
+ const worker = new WorkerSource(getServerUrl(), kitType);
70142
+ const entry = await worker.fetchLatest();
70143
+ const version3 = entry.version.replace(/^v/, "");
70144
+ if (!version3 || version3.length > 256) {
70145
+ logger.debug(`Invalid version format from Worker: ${entry.version}`);
70146
+ return null;
70147
+ }
70148
+ const semverParts = version3.split(/[-+]/);
70149
+ const coreParts = semverParts[0].split(".");
70150
+ if (coreParts.length !== 3 || !coreParts.every((p2) => /^\d+$/.test(p2))) {
70151
+ logger.debug(`Invalid version format from Worker: ${entry.version}`);
70152
+ return null;
70153
+ }
70154
+ return { version: version3 };
70155
+ } catch (error) {
70156
+ if (error instanceof NotLoggedInError) {
70157
+ logger.debug("Skipping update check (not logged in)");
70158
+ return null;
70159
+ }
70160
+ const isLastAttempt = attempt === maxRetries - 1;
70161
+ if (isLastAttempt) {
70162
+ logger.debug(`Version check failed after ${maxRetries} attempts: ${error}`);
70163
+ return null;
70164
+ }
70165
+ const delay3 = baseBackoff * 2 ** attempt;
70166
+ await new Promise((resolve23) => setTimeout(resolve23, delay3));
70167
+ }
70168
+ }
70169
+ return null;
70170
+ }
70171
+ static async checkForUpdates(kitType, currentVersion, global3 = false) {
70172
+ const normalizedCurrent = currentVersion.replace(/^v/, "");
70173
+ const cache = await ConfigVersionChecker.loadCache(kitType, global3);
70174
+ const now = Date.now();
70175
+ if (cache && now - cache.lastCheck < CACHE_TTL_MS) {
70176
+ const hasUpdates = isNewerVersion(normalizedCurrent, cache.latestVersion);
70177
+ return {
70178
+ hasUpdates,
70179
+ currentVersion: normalizedCurrent,
70180
+ latestVersion: cache.latestVersion,
70181
+ fromCache: true
70182
+ };
70183
+ }
70184
+ const result = await ConfigVersionChecker.fetchLatestVersion(kitType);
70185
+ if (result) {
70186
+ await ConfigVersionChecker.saveCache(kitType, global3, {
70187
+ lastCheck: now,
70188
+ latestVersion: result.version
70189
+ });
70190
+ const hasUpdates = isNewerVersion(normalizedCurrent, result.version);
70191
+ return {
70192
+ hasUpdates,
70193
+ currentVersion: normalizedCurrent,
70194
+ latestVersion: result.version,
70195
+ fromCache: false
70196
+ };
70197
+ }
70198
+ if (cache) {
70199
+ const hasUpdates = isNewerVersion(normalizedCurrent, cache.latestVersion);
70200
+ return {
70201
+ hasUpdates,
70202
+ currentVersion: normalizedCurrent,
70203
+ latestVersion: cache.latestVersion,
70204
+ fromCache: true
70205
+ };
70206
+ }
70207
+ return {
70208
+ hasUpdates: false,
70209
+ currentVersion: normalizedCurrent,
70210
+ latestVersion: normalizedCurrent,
70211
+ fromCache: false
70212
+ };
70213
+ }
70214
+ static async clearCache(kitType, global3 = false) {
70215
+ const cachePath = ConfigVersionChecker.getCacheFilePath(kitType, global3);
70216
+ try {
70217
+ await unlink12(cachePath);
70218
+ logger.debug(`Cleared sync cache for ${kitType}`);
70219
+ } catch (error) {
70220
+ if (error.code !== "ENOENT") {
70221
+ throw error;
70222
+ }
70223
+ }
70224
+ }
70225
+ }
70226
+ // src/domains/sync/sync-engine.ts
70227
+ import { lstat as lstat7, readFile as readFile40, readlink, realpath as realpath3, stat as stat11 } from "node:fs/promises";
70228
+ import { isAbsolute as isAbsolute3, join as join97, normalize as normalize8, relative as relative16 } from "node:path";
70229
+ init_logger();
70230
+ var MAX_SYNC_FILE_SIZE = 10 * 1024 * 1024;
70231
+ var MAX_SYMLINK_DEPTH = 20;
70232
+ async function validateSymlinkChain(path9, basePath, maxDepth = MAX_SYMLINK_DEPTH) {
70233
+ let current = path9;
70234
+ let depth = 0;
70235
+ while (depth < maxDepth) {
70236
+ try {
70237
+ const stats = await lstat7(current);
70238
+ if (!stats.isSymbolicLink())
70239
+ break;
70240
+ const target = await readlink(current);
70241
+ const resolvedTarget = isAbsolute3(target) ? target : join97(current, "..", target);
70242
+ const normalizedTarget = normalize8(resolvedTarget);
70243
+ const rel = relative16(basePath, normalizedTarget);
70244
+ if (rel.startsWith("..") || isAbsolute3(rel)) {
70245
+ throw new Error(`Symlink chain escapes base directory at depth ${depth}: ${path9}`);
70246
+ }
70247
+ current = normalizedTarget;
70248
+ depth++;
70249
+ } catch (error) {
70250
+ if (error instanceof Error && error.message.includes("Symlink chain")) {
70251
+ throw error;
70252
+ }
70253
+ break;
70254
+ }
70255
+ }
70256
+ if (depth >= maxDepth) {
70257
+ throw new Error(`Symlink chain too deep (>${maxDepth}): ${path9}`);
70258
+ }
70259
+ }
70260
+ async function validateSyncPath(basePath, filePath) {
70261
+ if (!filePath || filePath.trim() === "") {
70262
+ throw new Error("Empty file path not allowed");
70263
+ }
70264
+ if (filePath.includes("\x00")) {
70265
+ throw new Error(`Invalid file path (null byte): ${filePath}`);
70266
+ }
70267
+ if (filePath.length > 1024) {
70268
+ throw new Error(`Path too long: ${filePath.slice(0, 50)}...`);
70269
+ }
70270
+ const normalized = normalize8(filePath);
70271
+ if (isAbsolute3(normalized)) {
70272
+ throw new Error(`Absolute paths not allowed: ${filePath}`);
70273
+ }
70274
+ if (normalized.startsWith("..") || normalized.includes("/../")) {
70275
+ throw new Error(`Path traversal not allowed: ${filePath}`);
70276
+ }
70277
+ const fullPath = join97(basePath, normalized);
70278
+ const rel = relative16(basePath, fullPath);
70279
+ if (rel.startsWith("..") || isAbsolute3(rel)) {
70280
+ throw new Error(`Path escapes base directory: ${filePath}`);
70281
+ }
70282
+ await validateSymlinkChain(fullPath, basePath);
70283
+ try {
70284
+ const resolvedBase = await realpath3(basePath);
70285
+ const resolvedFull = await realpath3(fullPath);
70286
+ const resolvedRel = relative16(resolvedBase, resolvedFull);
70287
+ if (resolvedRel.startsWith("..") || isAbsolute3(resolvedRel)) {
70288
+ throw new Error(`Symlink escapes base directory: ${filePath}`);
70289
+ }
70290
+ } catch (error) {
70291
+ if (error.code === "ENOENT") {
70292
+ const parentPath = join97(fullPath, "..");
70293
+ try {
70294
+ const resolvedBase = await realpath3(basePath);
70295
+ const resolvedParent = await realpath3(parentPath);
70296
+ const resolvedRel = relative16(resolvedBase, resolvedParent);
70297
+ if (resolvedRel.startsWith("..") || isAbsolute3(resolvedRel)) {
70298
+ throw new Error(`Parent symlink escapes base directory: ${filePath}`);
70299
+ }
70300
+ } catch (parentError) {
70301
+ if (parentError.code !== "ENOENT") {
70302
+ throw parentError;
70303
+ }
70304
+ }
70305
+ } else {
70306
+ throw error;
70307
+ }
70308
+ }
70309
+ return fullPath;
70310
+ }
70311
+
70312
+ class SyncEngine {
70313
+ static async createSyncPlan(trackedFiles, claudeDir, upstreamDir) {
70314
+ const plan = { autoUpdate: [], needsReview: [], skipped: [] };
70315
+ for (const file of trackedFiles) {
70316
+ if (file.ownership === "user") {
70317
+ plan.skipped.push(file);
70318
+ continue;
70319
+ }
70320
+ let upstreamPath;
70321
+ try {
70322
+ upstreamPath = await validateSyncPath(upstreamDir, file.path);
70323
+ } catch (error) {
70324
+ logger.warning(`Skipping invalid path: ${file.path}`);
70325
+ plan.skipped.push(file);
70326
+ continue;
70327
+ }
70328
+ try {
70329
+ await stat11(upstreamPath);
70330
+ } catch {
70331
+ plan.skipped.push(file);
70332
+ continue;
70333
+ }
70334
+ let localPath;
70335
+ try {
70336
+ localPath = await validateSyncPath(claudeDir, file.path);
70337
+ } catch (error) {
70338
+ logger.warning(`Skipping invalid local path: ${file.path}`);
70339
+ plan.skipped.push(file);
70340
+ continue;
70341
+ }
70342
+ try {
70343
+ await stat11(localPath);
70344
+ } catch {
70345
+ plan.autoUpdate.push(file);
70346
+ continue;
70072
70347
  }
70073
- kitType = kitOption;
70074
- if (accessibleKits && !accessibleKits.includes(kitType)) {
70075
- logger.error(`No access to ${AVAILABLE_KITS[kitType].name}`);
70076
- logger.info("Request access from your team lead or check your GitHub invitation");
70077
- return { ...ctx, cancelled: true };
70348
+ if (file.ownership === "takumi") {
70349
+ plan.autoUpdate.push(file);
70350
+ continue;
70351
+ }
70352
+ const currentChecksum = await OwnershipChecker.calculateChecksum(localPath);
70353
+ const baseChecksum = file.baseChecksum || file.checksum;
70354
+ if (currentChecksum === baseChecksum) {
70355
+ plan.autoUpdate.push(file);
70356
+ } else {
70357
+ plan.needsReview.push(file);
70078
70358
  }
70079
70359
  }
70360
+ return plan;
70080
70361
  }
70081
- if (!kitType) {
70082
- if (ctx.isNonInteractive) {
70083
- if (!accessibleKits || accessibleKits.length === 0) {
70084
- throw new Error("Kit must be specified via --kit flag in non-interactive mode (no accessible kits detected)");
70362
+ static generateHunks(currentContent, newContent, filename, contextLines = 3) {
70363
+ const patch = structuredPatch(filename, filename, currentContent, newContent, "", "", {
70364
+ context: contextLines
70365
+ });
70366
+ return patch.hunks.map((hunk) => ({
70367
+ oldStart: hunk.oldStart,
70368
+ oldLines: hunk.oldLines,
70369
+ newStart: hunk.newStart,
70370
+ newLines: hunk.newLines,
70371
+ lines: hunk.lines
70372
+ }));
70373
+ }
70374
+ static applyHunks(content, hunks, accepted) {
70375
+ const acceptedHunks = hunks.filter((_4, i) => accepted[i]);
70376
+ if (acceptedHunks.length === 0) {
70377
+ return content;
70378
+ }
70379
+ const patchStr = SyncEngine.buildUnifiedDiff(content, acceptedHunks);
70380
+ const result = applyPatch(content, patchStr);
70381
+ if (result === false) {
70382
+ try {
70383
+ return SyncEngine.applyHunksManually(content, acceptedHunks);
70384
+ } catch (fallbackError) {
70385
+ throw new Error(`Failed to apply ${acceptedHunks.length} hunk(s): patch and manual methods both failed. ` + `Manual error: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
70085
70386
  }
70086
- kitType = accessibleKits[0];
70087
- logger.info(`Auto-selected: ${AVAILABLE_KITS[kitType].name}`);
70088
- } else if (accessibleKits?.length === 1) {
70089
- kitType = accessibleKits[0];
70090
- logger.info(`Using ${AVAILABLE_KITS[kitType].name} (only accessible kit)`);
70091
- } else {
70092
- kitType = await ctx.prompts.selectKit(undefined, accessibleKits);
70093
70387
  }
70388
+ return result;
70094
70389
  }
70095
- const kit = AVAILABLE_KITS[kitType];
70096
- logger.info(`Selected kit: ${kit.name}`);
70097
- let targetDir;
70098
- if (ctx.explicitDir) {
70099
- targetDir = ctx.options.dir;
70100
- logger.info(`Using explicit directory: ${targetDir}`);
70101
- } else if (ctx.options.global) {
70102
- const targetAgents = selectAgents({ explicit: ctx.options.agents });
70103
- targetDir = resolveGlobalTargetDir(targetAgents);
70104
- logger.info(`Using global kit directory: ${targetDir}`);
70105
- } else {
70106
- targetDir = config.defaults?.dir || ".";
70107
- if (!config.defaults?.dir) {
70108
- if (ctx.isNonInteractive) {
70109
- logger.info("Using current directory as target");
70110
- } else {
70111
- targetDir = await ctx.prompts.getDirectory(targetDir);
70390
+ static buildUnifiedDiff(_content, hunks) {
70391
+ let diff = `--- a
70392
+ +++ b
70393
+ `;
70394
+ for (const hunk of hunks) {
70395
+ diff += `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@
70396
+ `;
70397
+ for (const line of hunk.lines) {
70398
+ diff += `${line}
70399
+ `;
70112
70400
  }
70113
70401
  }
70402
+ return diff;
70114
70403
  }
70115
- const resolvedDir = resolve22(targetDir);
70116
- logger.info(`Target directory: ${resolvedDir}`);
70117
- if (!ctx.options.global && isLocalSameAsGlobal(resolvedDir)) {
70118
- logger.warning("You're at HOME directory. Installing here modifies your GLOBAL Takumi.");
70119
- if (!ctx.isNonInteractive) {
70120
- const choice = await ctx.prompts.selectScope();
70121
- if (choice === "cancel") {
70122
- return { ...ctx, cancelled: true };
70404
+ static applyHunksManually(content, hunks) {
70405
+ const lines = content.split(`
70406
+ `);
70407
+ const sortedHunks = [...hunks].sort((a3, b4) => b4.oldStart - a3.oldStart);
70408
+ for (const hunk of sortedHunks) {
70409
+ const startIndex = hunk.oldStart - 1;
70410
+ if (startIndex < 0 || startIndex > lines.length) {
70411
+ logger.warning(`Hunk start ${hunk.oldStart} out of bounds (file has ${lines.length} lines)`);
70412
+ continue;
70123
70413
  }
70124
- if (choice === "global") {
70125
- logger.info("Proceeding with global installation");
70414
+ const newLines = [];
70415
+ let deleteCount = 0;
70416
+ for (const line of hunk.lines) {
70417
+ if (!line || line.length === 0) {
70418
+ continue;
70419
+ }
70420
+ const prefix = line[0];
70421
+ const lineContent = line.slice(1);
70422
+ if (prefix === "-") {
70423
+ deleteCount++;
70424
+ } else if (prefix === "+" || prefix === " ") {
70425
+ newLines.push(lineContent);
70426
+ }
70126
70427
  }
70127
- if (choice === "different") {
70128
- logger.info("Please run 'takumi init' from a project directory instead.");
70129
- return { ...ctx, cancelled: true };
70428
+ if (startIndex + deleteCount > lines.length) {
70429
+ logger.warning(`Hunk would delete past EOF (start: ${startIndex}, delete: ${deleteCount}, lines: ${lines.length})`);
70430
+ continue;
70130
70431
  }
70131
- } else {
70132
- logger.error("Cannot use local installation at HOME directory.");
70133
- logger.info("Use -g/--global flag or run from a project directory.");
70134
- return { ...ctx, cancelled: true };
70432
+ lines.splice(startIndex, deleteCount, ...newLines);
70135
70433
  }
70434
+ return lines.join(`
70435
+ `);
70136
70436
  }
70137
- if (!await import_fs_extra32.pathExists(resolvedDir)) {
70138
- if (ctx.options.global) {
70139
- await mkdir28(resolvedDir, { recursive: true });
70140
- logger.info(`Created global directory: ${resolvedDir}`);
70141
- } else {
70142
- logger.error(`Directory does not exist: ${resolvedDir}`);
70143
- logger.info('Use "tkm new" to create a new project');
70144
- return { ...ctx, cancelled: true };
70437
+ static isBinaryFile(content) {
70438
+ if (content.length === 0) {
70439
+ return false;
70145
70440
  }
70146
- }
70147
- if (!ctx.options.fresh) {
70148
- const prefix = PathResolver.getPathPrefix(ctx.options.global);
70149
- const claudeDir = prefix ? join97(resolvedDir, prefix) : resolvedDir;
70150
- try {
70151
- const existingMetadata = await readManifest(claudeDir);
70152
- if (existingMetadata?.kits) {
70153
- const existingKitTypes = Object.keys(existingMetadata.kits);
70154
- const otherKits = existingKitTypes.filter((k3) => k3 !== kitType);
70155
- if (otherKits.length > 0) {
70156
- const kitsRecord = existingMetadata.kits;
70157
- const existingKitsDisplay = otherKits.map((k3) => `${k3}@${kitsRecord[k3]?.version || "unknown"}`).join(", ");
70158
- if (!ctx.options.yes && !ctx.isNonInteractive) {
70159
- try {
70160
- const confirmAdd = await ctx.prompts.confirm(`${existingKitsDisplay} already installed. Add ${kit.name} alongside?`);
70161
- if (!confirmAdd) {
70162
- logger.warning("Multi-kit installation cancelled by user");
70163
- return { ...ctx, cancelled: true };
70164
- }
70165
- logger.info(`Adding ${kit.name} alongside existing kit(s)`);
70166
- } catch {
70167
- logger.warning("Prompt cancelled or interrupted");
70168
- return { ...ctx, cancelled: true };
70169
- }
70170
- } else {
70171
- const reason = ctx.options.yes ? "(--yes flag)" : "(non-interactive mode)";
70172
- logger.info(`Adding ${kit.name} alongside ${existingKitsDisplay} ${reason}`);
70173
- }
70174
- }
70441
+ const sampleSize = Math.min(content.length, 8192);
70442
+ let nonPrintableCount = 0;
70443
+ for (let i = 0;i < sampleSize; i++) {
70444
+ const code = content.charCodeAt(i);
70445
+ if (code === 0) {
70446
+ return true;
70447
+ }
70448
+ if (code < 32 && code !== 9 && code !== 10 && code !== 13) {
70449
+ nonPrintableCount++;
70175
70450
  }
70176
- } catch (error) {
70177
- logger.debug(`Metadata read skipped: ${error instanceof Error ? error.message : "unknown error"}`);
70178
- }
70179
- }
70180
- if (ctx.options.fresh) {
70181
- const prefix = PathResolver.getPathPrefix(ctx.options.global);
70182
- const claudeDir = prefix ? join97(resolvedDir, prefix) : resolvedDir;
70183
- const canProceed = await handleFreshInstallation(claudeDir, ctx.prompts);
70184
- if (!canProceed) {
70185
- return { ...ctx, cancelled: true };
70186
70451
  }
70452
+ return nonPrintableCount / sampleSize > 0.1;
70187
70453
  }
70188
- const isOfflineMode = !!(ctx.options.kitPath || ctx.options.archive);
70189
- const github = !isOfflineMode && useLegacyGh ? new GitHubClient : null;
70190
- let selectedVersion = ctx.options.release;
70191
- if (!selectedVersion && ctx.isNonInteractive && !ctx.options.yes && !isOfflineMode) {
70192
- throw new Error("Non-interactive mode requires either: --release <tag> OR --yes (uses latest)");
70193
- }
70194
- if (!selectedVersion && ctx.options.yes && !isOfflineMode) {
70195
- logger.info("Using latest stable version (--yes flag)");
70196
- }
70197
- if (!selectedVersion && !ctx.isNonInteractive && !isOfflineMode) {
70198
- logger.info("Fetching available versions...");
70199
- let currentVersion = null;
70454
+ static async loadFileContent(filePath) {
70200
70455
  try {
70201
- const prefix = PathResolver.getPathPrefix(ctx.options.global);
70202
- const claudeDir = prefix ? join97(resolvedDir, prefix) : resolvedDir;
70203
- const existingMetadata = await readManifest(claudeDir);
70204
- currentVersion = existingMetadata?.kits?.[kitType]?.version || null;
70205
- if (currentVersion) {
70206
- logger.debug(`Current installed version: ${currentVersion}`);
70456
+ const lstats = await lstat7(filePath);
70457
+ if (lstats.isSymbolicLink()) {
70458
+ throw new Error(`Symlink not allowed for sync: ${filePath}`);
70207
70459
  }
70208
- } catch {}
70209
- try {
70210
- const versionResult = await ctx.prompts.selectVersionEnhanced({
70211
- kit,
70212
- includePrereleases: ctx.options.beta,
70213
- limit: 10,
70214
- allowManualEntry: true,
70215
- forceRefresh: ctx.options.refresh,
70216
- currentVersion,
70217
- useGh: useLegacyGh
70218
- });
70219
- if (!versionResult) {
70220
- logger.warning("Version selection cancelled by user");
70221
- return { ...ctx, cancelled: true };
70460
+ if (lstats.size > MAX_SYNC_FILE_SIZE) {
70461
+ throw new Error(`File too large for sync (${Math.round(lstats.size / 1024 / 1024)}MB > ${MAX_SYNC_FILE_SIZE / 1024 / 1024}MB limit)`);
70222
70462
  }
70223
- selectedVersion = versionResult;
70224
- logger.success(`Selected version: ${selectedVersion}`);
70463
+ const buffer = await readFile40(filePath);
70464
+ if (buffer.includes(0)) {
70465
+ return { content: "", isBinary: true };
70466
+ }
70467
+ const content = buffer.toString("utf8");
70468
+ if (content.includes("�")) {
70469
+ return { content: "", isBinary: true };
70470
+ }
70471
+ if (SyncEngine.isBinaryFile(content)) {
70472
+ return { content: "", isBinary: true };
70473
+ }
70474
+ return { content, isBinary: false };
70225
70475
  } catch (error) {
70226
- logger.error("Failed to fetch versions, using latest release");
70227
- logger.debug(`Version selection error: ${error.message}`);
70228
- selectedVersion = undefined;
70476
+ const errMsg = error instanceof Error ? error.message : "Unknown error";
70477
+ throw new Error(`Cannot read file for sync: ${filePath} - ${errMsg}`);
70229
70478
  }
70230
70479
  }
70231
- let release;
70232
- if (isOfflineMode) {
70233
- release = undefined;
70234
- logger.verbose("Offline mode - skipping release fetch", {
70235
- kitPath: ctx.options.kitPath,
70236
- archive: ctx.options.archive
70237
- });
70238
- } else if (ctx.options.useGit && selectedVersion) {
70239
- release = {
70240
- id: 0,
70241
- tag_name: selectedVersion,
70242
- name: selectedVersion,
70243
- draft: false,
70244
- prerelease: selectedVersion.includes("-"),
70245
- tarball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${selectedVersion}.tar.gz`,
70246
- zipball_url: `https://github.com/${kit.owner}/${kit.repo}/archive/refs/tags/${selectedVersion}.zip`,
70247
- assets: []
70248
- };
70249
- logger.verbose("Using git clone mode with tag", { tag: selectedVersion });
70250
- } else if (selectedVersion && github) {
70251
- release = await github.getReleaseByTag(kit, selectedVersion);
70252
- } else if (github) {
70253
- if (ctx.options.beta) {
70254
- logger.info("Fetching latest beta release...");
70480
+ }
70481
+ // src/domains/sync/deletion-path-filter.ts
70482
+ init_path_resolver();
70483
+ var import_picomatch2 = __toESM(require_picomatch2(), 1);
70484
+ function filterDeletionPaths(trackedFiles, deletions) {
70485
+ if (!deletions || deletions.length === 0) {
70486
+ return trackedFiles;
70487
+ }
70488
+ const exactPaths = new Set;
70489
+ const globMatchers = [];
70490
+ for (const pattern of deletions) {
70491
+ if (PathResolver.isGlobPattern(pattern)) {
70492
+ globMatchers.push(import_picomatch2.default(pattern));
70255
70493
  } else {
70256
- logger.info("Fetching latest release...");
70494
+ exactPaths.add(pattern);
70257
70495
  }
70258
- release = await github.getLatestRelease(kit, ctx.options.beta);
70259
- if (release.prerelease) {
70260
- logger.success(`Found beta: ${release.tag_name}`);
70261
- } else {
70262
- logger.success(`Found: ${release.tag_name}`);
70496
+ }
70497
+ return trackedFiles.filter((file) => {
70498
+ if (exactPaths.has(file.path))
70499
+ return false;
70500
+ for (const matcher of globMatchers) {
70501
+ if (matcher(file.path))
70502
+ return false;
70263
70503
  }
70264
- } else if (selectedVersion) {
70265
- const worker = new WorkerSource(getServerUrl(), kitType);
70266
- const entry = await worker.fetchByTag(selectedVersion);
70267
- release = releaseEntryToGitHubRelease(entry, kit);
70268
- } else {
70269
- if (ctx.options.beta) {
70270
- logger.info("Fetching latest beta release...");
70271
- } else {
70272
- logger.info("Fetching latest release...");
70504
+ return true;
70505
+ });
70506
+ }
70507
+ // src/domains/sync/merge-ui.ts
70508
+ init_dist2();
70509
+ var import_picocolors23 = __toESM(require_picocolors(), 1);
70510
+ var HUNK_SEPARATOR_WIDTH = 50;
70511
+ var EXTENDED_CONTEXT_LINES = 10;
70512
+ var MAX_LINE_DISPLAY_LENGTH = 120;
70513
+ function requireTTY() {
70514
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
70515
+ throw new Error("Interactive merge requires a TTY terminal. " + "Use --yes flag for non-interactive mode, or run in a terminal.");
70516
+ }
70517
+ }
70518
+ function truncateLine(line) {
70519
+ if (line.length <= MAX_LINE_DISPLAY_LENGTH)
70520
+ return line;
70521
+ return `${line.slice(0, MAX_LINE_DISPLAY_LENGTH - 3)}...`;
70522
+ }
70523
+
70524
+ class MergeUI {
70525
+ static async promptHunk(hunk, hunkIndex, totalHunks, _filename) {
70526
+ requireTTY();
70527
+ const lineRange = `${hunk.oldStart}-${hunk.oldStart + hunk.oldLines - 1}`;
70528
+ console.log(import_picocolors23.default.cyan(`
70529
+ Hunk ${hunkIndex + 1}/${totalHunks}: Lines ${lineRange}`));
70530
+ console.log(import_picocolors23.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
70531
+ for (const line of hunk.lines) {
70532
+ const displayLine = truncateLine(line);
70533
+ const prefix = line[0];
70534
+ if (prefix === "+") {
70535
+ console.log(import_picocolors23.default.green(displayLine));
70536
+ } else if (prefix === "-") {
70537
+ console.log(import_picocolors23.default.red(displayLine));
70538
+ } else {
70539
+ console.log(import_picocolors23.default.dim(displayLine));
70540
+ }
70273
70541
  }
70274
- const worker = new WorkerSource(getServerUrl(), kitType);
70275
- const entry = await worker.fetchLatest(ctx.options.beta);
70276
- release = releaseEntryToGitHubRelease(entry, kit);
70277
- if (release.prerelease) {
70278
- logger.success(`Found beta: ${release.tag_name}`);
70279
- } else {
70280
- logger.success(`Found: ${release.tag_name}`);
70542
+ console.log(import_picocolors23.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
70543
+ const action = await ie({
70544
+ message: "Action?",
70545
+ options: [
70546
+ { value: "accept", label: "[a]ccept - Apply this change" },
70547
+ { value: "reject", label: "[r]eject - Keep current" },
70548
+ { value: "view", label: "[v]iew - More context" },
70549
+ { value: "skip", label: "[s]kip - Skip entire file" }
70550
+ ]
70551
+ });
70552
+ if (lD(action)) {
70553
+ return "skip";
70281
70554
  }
70555
+ return action;
70282
70556
  }
70283
- if (ctx.options.force && !ctx.options.yes) {
70284
- logger.info("--force has no effect without --yes (the version-match skip only applies in non-interactive mode)");
70557
+ static showExtendedContext(currentContent, hunk, contextLines = EXTENDED_CONTEXT_LINES) {
70558
+ const lines = currentContent.split(`
70559
+ `);
70560
+ const startLine = Math.max(0, hunk.oldStart - 1 - contextLines);
70561
+ const endLine = Math.min(lines.length, hunk.oldStart + hunk.oldLines - 1 + contextLines);
70562
+ console.log(import_picocolors23.default.cyan(`
70563
+ Extended context (lines ${startLine + 1}-${endLine}):`));
70564
+ console.log(import_picocolors23.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
70565
+ for (let i = startLine;i < endLine; i++) {
70566
+ const lineNum = String(i + 1).padStart(4, " ");
70567
+ const isInHunk = i >= hunk.oldStart - 1 && i < hunk.oldStart - 1 + hunk.oldLines;
70568
+ const prefix = isInHunk ? import_picocolors23.default.yellow("*") : " ";
70569
+ console.log(`${import_picocolors23.default.dim(lineNum)} ${prefix} ${lines[i]}`);
70570
+ }
70571
+ console.log(import_picocolors23.default.dim("─".repeat(HUNK_SEPARATOR_WIDTH)));
70285
70572
  }
70286
- const releaseTag = release?.tag_name;
70287
- if (ctx.options.yes && !ctx.options.fresh && !ctx.options.force && releaseTag && !isOfflineMode) {
70288
- try {
70289
- const prefix = PathResolver.getPathPrefix(ctx.options.global);
70290
- const claudeDir = prefix ? join97(resolvedDir, prefix) : resolvedDir;
70291
- const existingMetadata = await readManifest(claudeDir);
70292
- const installedKitVersion = existingMetadata?.kits?.[kitType]?.version;
70293
- if (installedKitVersion && versionsMatch(installedKitVersion, releaseTag)) {
70294
- logger.success(`Already at latest version (${kitType}@${installedKitVersion}), skipping reinstall`);
70295
- return { ...ctx, cancelled: true };
70573
+ static async mergeFile(filename, currentContent, _newContent, hunks) {
70574
+ console.log(import_picocolors23.default.bold(`
70575
+ ━━━ ${filename} ━━━`));
70576
+ console.log(import_picocolors23.default.dim(`${hunks.length} change${hunks.length === 1 ? "" : "s"} to review
70577
+ `));
70578
+ const decisions = [];
70579
+ for (let i = 0;i < hunks.length; i++) {
70580
+ let action;
70581
+ do {
70582
+ action = await MergeUI.promptHunk(hunks[i], i, hunks.length, filename);
70583
+ if (action === "view") {
70584
+ MergeUI.showExtendedContext(currentContent, hunks[i]);
70585
+ }
70586
+ } while (action === "view");
70587
+ if (action === "skip") {
70588
+ return "skipped";
70296
70589
  }
70297
- } catch (error) {
70298
- logger.verbose(`Metadata read failed, proceeding with installation: ${error instanceof Error ? error.message : "unknown"}`);
70590
+ decisions.push(action === "accept");
70299
70591
  }
70592
+ const result = SyncEngine.applyHunks(currentContent, hunks, decisions);
70593
+ const applied = decisions.filter(Boolean).length;
70594
+ const rejected = decisions.length - applied;
70595
+ return { result, applied, rejected };
70300
70596
  }
70301
- return {
70302
- ...ctx,
70303
- kit,
70304
- kitType,
70305
- resolvedDir,
70306
- release,
70307
- selectedVersion,
70308
- accessibleKits
70597
+ static displayMergeSummary(filename, applied, rejected) {
70598
+ if (applied > 0 && rejected > 0) {
70599
+ console.log(import_picocolors23.default.dim(` ${filename}: `) + import_picocolors23.default.green(`${applied} applied`) + import_picocolors23.default.dim(", ") + import_picocolors23.default.yellow(`${rejected} rejected`));
70600
+ } else if (applied > 0) {
70601
+ console.log(import_picocolors23.default.dim(` ${filename}: `) + import_picocolors23.default.green(`${applied} applied`));
70602
+ } else {
70603
+ console.log(import_picocolors23.default.dim(` ${filename}: `) + import_picocolors23.default.yellow(`${rejected} rejected`));
70604
+ }
70605
+ }
70606
+ static displaySkipped(filename) {
70607
+ console.log(import_picocolors23.default.dim(` ${filename}: `) + import_picocolors23.default.yellow("skipped"));
70608
+ }
70609
+ }
70610
+ // src/domains/sync/notification-display.ts
70611
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
70612
+ import { stdout } from "node:process";
70613
+ function createNotificationBox(borderColor, boxWidth) {
70614
+ const contentWidth = boxWidth - 2;
70615
+ const topBorder = borderColor(`╭${"─".repeat(contentWidth)}╮`);
70616
+ const bottomBorder = borderColor(`╰${"─".repeat(contentWidth)}╯`);
70617
+ const emptyLine = borderColor("│") + " ".repeat(contentWidth) + borderColor("│");
70618
+ const padLine = (text, visibleLen) => {
70619
+ const len = visibleLen ?? text.length;
70620
+ const displayText = len > contentWidth ? `${text.slice(0, contentWidth - 3)}...` : text;
70621
+ const actualLen = visibleLen ?? displayText.length;
70622
+ const totalPadding = contentWidth - actualLen;
70623
+ const leftPadding = Math.max(0, Math.floor(totalPadding / 2));
70624
+ const rightPadding = Math.max(0, totalPadding - leftPadding);
70625
+ return borderColor("│") + " ".repeat(leftPadding) + displayText + " ".repeat(rightPadding) + borderColor("│");
70309
70626
  };
70627
+ return { topBorder, bottomBorder, emptyLine, padLine };
70310
70628
  }
70311
- function resolveGlobalTargetDir(targetAgents) {
70312
- const primary = targetAgents[0];
70313
- if (primary) {
70314
- const installer = getInstaller(primary);
70315
- if (installer) {
70316
- return installer.globalRoot();
70317
- }
70629
+ function displayConfigUpdateNotification(entries, isGlobal = false) {
70630
+ if (entries.length === 0)
70631
+ return;
70632
+ const lines = entries.map(({ kit, currentVersion, latestVersion }) => {
70633
+ const cur = currentVersion.replace(/^v/, "");
70634
+ const latest = latestVersion.replace(/^v/, "");
70635
+ const text = `${import_picocolors24.default.bold(kit)}: ${import_picocolors24.default.dim(cur)} ${import_picocolors24.default.white("→")} ${import_picocolors24.default.green(import_picocolors24.default.bold(latest))}`;
70636
+ const visibleLen = kit.length + 2 + cur.length + 3 + latest.length;
70637
+ return { text, visibleLen };
70638
+ });
70639
+ const updateCmd = isGlobal ? "tkm init -g --sync" : "tkm init --sync";
70640
+ const commandText = `Run: ${import_picocolors24.default.cyan(import_picocolors24.default.bold(updateCmd))}`;
70641
+ const commandLen = `Run: ${updateCmd}`.length;
70642
+ const headerLabel = "\uD83D\uDCE6 Config Updates Available";
70643
+ const headerText = import_picocolors24.default.bold(import_picocolors24.default.yellow(headerLabel));
70644
+ const longestLine = Math.max(headerLabel.length, commandLen, ...lines.map((l2) => l2.visibleLen));
70645
+ const terminalWidth = stdout.columns || 80;
70646
+ const boxWidth = Math.min(Math.max(longestLine + 6, 52), terminalWidth - 4);
70647
+ const { topBorder, bottomBorder, emptyLine, padLine } = createNotificationBox(import_picocolors24.default.cyan, boxWidth);
70648
+ console.log("");
70649
+ console.log(topBorder);
70650
+ console.log(emptyLine);
70651
+ console.log(padLine(headerText, headerLabel.length));
70652
+ for (const { text, visibleLen } of lines) {
70653
+ console.log(padLine(text, visibleLen));
70318
70654
  }
70319
- return getClaudeDir();
70655
+ console.log(emptyLine);
70656
+ console.log(padLine(commandText, commandLen));
70657
+ console.log(emptyLine);
70658
+ console.log(bottomBorder);
70659
+ console.log("");
70320
70660
  }
70661
+ // src/domains/sync/passive-update-check.ts
70662
+ init_logger();
70321
70663
  // src/commands/init/phases/sync-handler.ts
70322
- import { copyFile as copyFile7, mkdir as mkdir29, open as open3, readFile as readFile41, rename as rename6, stat as stat12, unlink as unlink13, writeFile as writeFile31 } from "node:fs/promises";
70323
- import { dirname as dirname27, join as join98, resolve as resolve23 } from "node:path";
70324
70664
  init_logger();
70325
70665
  init_path_resolver();
70326
70666
  var import_fs_extra33 = __toESM(require_lib(), 1);
@@ -70348,7 +70688,7 @@ async function handleSync(ctx) {
70348
70688
  Run 'takumi init' to update.`, "Legacy Installation");
70349
70689
  return { ...ctx, cancelled: true };
70350
70690
  }
70351
- let kitType = ctx.options.kit;
70691
+ let kitType = ctx.options.selectedKits?.[0];
70352
70692
  if (!kitType) {
70353
70693
  const engineerMeta = await readKitManifest(claudeDir, "engineer");
70354
70694
  if (engineerMeta) {
@@ -70385,7 +70725,13 @@ Run 'takumi init' to update.`, "Legacy Installation");
70385
70725
  ctx.prompts.note(`You're on the latest version (${updateResult.currentVersion})`, "Already Up to Date");
70386
70726
  return { ...ctx, cancelled: true };
70387
70727
  }
70388
- displayConfigUpdateNotification(updateResult.currentVersion, updateResult.latestVersion, ctx.options.global);
70728
+ displayConfigUpdateNotification([
70729
+ {
70730
+ kit: kitType,
70731
+ currentVersion: updateResult.currentVersion,
70732
+ latestVersion: updateResult.latestVersion
70733
+ }
70734
+ ], ctx.options.global);
70389
70735
  if (!ctx.isNonInteractive) {
70390
70736
  const proceed = await ctx.prompts.confirm("Proceed with config sync?");
70391
70737
  if (!proceed) {
@@ -71181,23 +71527,102 @@ async function executeInit(options2, prompts) {
71181
71527
  ctx = await handleSelection(ctx);
71182
71528
  if (ctx.cancelled)
71183
71529
  return;
71184
- ctx = await handleDownload(ctx);
71185
- if (ctx.cancelled)
71530
+ if (ctx.resolvedDir && !isSyncMode) {
71531
+ try {
71532
+ const prefix = PathResolver.getPathPrefix(ctx.options.global);
71533
+ const claudeDir = prefix ? join102(ctx.resolvedDir, prefix) : ctx.resolvedDir;
71534
+ const pending = await ManifestWriter.findPendingKits(claudeDir);
71535
+ if (pending.length > 0) {
71536
+ logger.warning(`Previous install was interrupted for kit(s): ${pending.join(", ")}. Re-running install will overwrite the pending entry.`);
71537
+ }
71538
+ } catch (error) {
71539
+ logger.debug(`Pending-kit check skipped: ${error}`);
71540
+ }
71541
+ }
71542
+ const selectedKits = ctx.selectedKits && ctx.selectedKits.length > 0 ? ctx.selectedKits : ctx.kitType ? [ctx.kitType] : [];
71543
+ if (selectedKits.length === 0) {
71544
+ logger.error("No kits resolved for installation");
71186
71545
  return;
71187
- if (!isSyncMode) {
71188
- ctx = await handleTransforms(ctx);
71189
- if (ctx.cancelled)
71190
- return;
71191
71546
  }
71192
- if (isSyncMode) {
71193
- ctx = await executeSyncMerge(ctx);
71194
- if (ctx.cancelled)
71547
+ const aggregatedResults = [];
71548
+ let lastCtx = ctx;
71549
+ for (let i = 0;i < selectedKits.length; i++) {
71550
+ const currentKit = selectedKits[i];
71551
+ const isLastKit = i === selectedKits.length - 1;
71552
+ if (i > 0) {
71553
+ let nextRelease = lastCtx.releasesByKit?.[currentKit];
71554
+ if (!nextRelease) {
71555
+ try {
71556
+ nextRelease = await resolveReleaseForKit(lastCtx, currentKit);
71557
+ } catch (error) {
71558
+ const message = error instanceof Error ? error.message : String(error);
71559
+ logger.error(`Failed to resolve release for ${currentKit}: ${message}`);
71560
+ aggregatedResults.push({
71561
+ provider: targetAgents[0] ?? "claude-code",
71562
+ success: false,
71563
+ cancelled: false,
71564
+ installedFiles: [],
71565
+ error: `Release fetch failed for kit "${currentKit}": ${message}`
71566
+ });
71567
+ continue;
71568
+ }
71569
+ }
71570
+ logger.info(`Installing kit ${i + 1}/${selectedKits.length}: ${AVAILABLE_KITS[currentKit].name}${nextRelease?.tag_name ? ` (${nextRelease.tag_name})` : ""}`);
71571
+ if (lastCtx.options.release && !lastCtx.releasesByKit?.[currentKit] && !lastCtx.options.kitPath && !lastCtx.options.archive) {
71572
+ logger.info(`Secondary kit "${currentKit}" uses latest channel — --release applies only to the primary kit.`);
71573
+ }
71574
+ const nextReleasesByKit = { ...lastCtx.releasesByKit ?? {} };
71575
+ if (nextRelease)
71576
+ nextReleasesByKit[currentKit] = nextRelease;
71577
+ lastCtx = {
71578
+ ...lastCtx,
71579
+ kit: AVAILABLE_KITS[currentKit],
71580
+ kitType: currentKit,
71581
+ release: nextRelease,
71582
+ selectedVersion: nextRelease?.tag_name,
71583
+ releasesByKit: nextReleasesByKit,
71584
+ tempDir: undefined,
71585
+ extractDir: undefined,
71586
+ archivePath: undefined,
71587
+ isLastKit
71588
+ };
71589
+ } else {
71590
+ if (selectedKits.length > 1) {
71591
+ logger.info(`Installing kit ${i + 1}/${selectedKits.length}: ${AVAILABLE_KITS[currentKit].name}${lastCtx.release?.tag_name ? ` (${lastCtx.release.tag_name})` : ""}`);
71592
+ }
71593
+ lastCtx = { ...lastCtx, isLastKit };
71594
+ }
71595
+ if (lastCtx.resolvedDir && lastCtx.release && !isSyncMode) {
71596
+ try {
71597
+ const prefix = PathResolver.getPathPrefix(lastCtx.options.global);
71598
+ const claudeDir = prefix ? join102(lastCtx.resolvedDir, prefix) : lastCtx.resolvedDir;
71599
+ const scope = lastCtx.options.global ? "global" : "local";
71600
+ await ManifestWriter.writeKitPending(claudeDir, currentKit, lastCtx.release.tag_name, scope);
71601
+ } catch (error) {
71602
+ logger.debug(`writeKitPending skipped: ${error}`);
71603
+ }
71604
+ }
71605
+ lastCtx = await handleDownload(lastCtx);
71606
+ if (lastCtx.cancelled)
71607
+ return;
71608
+ if (!isSyncMode) {
71609
+ lastCtx = await handleTransforms(lastCtx);
71610
+ if (lastCtx.cancelled)
71611
+ return;
71612
+ }
71613
+ if (isSyncMode) {
71614
+ lastCtx = await executeSyncMerge(lastCtx);
71615
+ if (lastCtx.cancelled)
71616
+ return;
71617
+ }
71618
+ const kitResults = await dispatchInstallers(lastCtx, targetAgents);
71619
+ aggregatedResults.push(...kitResults);
71620
+ if (kitResults.some((r2) => r2.cancelled))
71195
71621
  return;
71196
71622
  }
71197
- const results = await dispatchInstallers(ctx, targetAgents);
71623
+ ctx = lastCtx;
71624
+ const results = aggregatedResults;
71198
71625
  ctx.installerResults = results;
71199
- if (results.some((r2) => r2.cancelled))
71200
- return;
71201
71626
  if (targetAgents.length > 1) {
71202
71627
  displayMultiInstallerSummary(results);
71203
71628
  }
@@ -71229,13 +71654,14 @@ ${errorLines.join(`
71229
71654
  return;
71230
71655
  }
71231
71656
  prompts.outro(`Project initialized successfully at ${ctx.resolvedDir}`);
71232
- const protectedNote = ctx.customClaudeFiles.length > 0 ? `Your project has been initialized with the latest version.
71233
- Protected files (.env, .claude custom files, etc.) were not modified.` : `Your project has been initialized with the latest version.
71234
- Protected files (.env, etc.) were not modified.`;
71235
- prompts.note(protectedNote, "Initialization complete");
71236
- if (ctx.resolvedDir) {
71237
- await maybeShowConfigUpdateNotification(ctx.resolvedDir, ctx.options.global);
71238
- }
71657
+ const installedSummary = selectedKits.map((k3) => {
71658
+ const tag = ctx.releasesByKit?.[k3]?.tag_name;
71659
+ return tag ? `${k3}@${tag}` : k3;
71660
+ }).join(", ");
71661
+ const installedLine = installedSummary ? `Installed: ${installedSummary}.` : "Project initialized.";
71662
+ const protectedLine = ctx.customClaudeFiles.length > 0 ? "Your local files were left untouched — including .env and any custom files you added under .claude/." : "Your local files were left untouched — Takumi only writes inside .claude/ (and CLAUDE.md), so .env and other project files are never modified.";
71663
+ prompts.note(`${installedLine}
71664
+ ${protectedLine}`, "Initialization complete");
71239
71665
  }
71240
71666
  async function initCommand(options2) {
71241
71667
  const prompts = new PromptsManager;
@@ -71258,7 +71684,7 @@ async function initCommand(options2) {
71258
71684
  import { existsSync as existsSync47 } from "node:fs";
71259
71685
  import { readFile as readFile45, rm as rm13, unlink as unlink15 } from "node:fs/promises";
71260
71686
  import { homedir as homedir25 } from "node:os";
71261
- import { basename as basename12, join as join102, resolve as resolve26 } from "node:path";
71687
+ import { basename as basename12, join as join103, resolve as resolve26 } from "node:path";
71262
71688
  init_dist2();
71263
71689
  var import_picocolors27 = __toESM(require_picocolors(), 1);
71264
71690
  init_logger();
@@ -71740,7 +72166,7 @@ async function executeDeleteAction(action, options2) {
71740
72166
  async function processMetadataDeletions(skillSourcePath, installGlobally) {
71741
72167
  if (!skillSourcePath)
71742
72168
  return;
71743
- const sourceMetadataPath = join102(resolve26(skillSourcePath, ".."), "metadata.json");
72169
+ const sourceMetadataPath = join103(resolve26(skillSourcePath, ".."), "metadata.json");
71744
72170
  if (!existsSync47(sourceMetadataPath))
71745
72171
  return;
71746
72172
  let sourceMetadata;
@@ -71753,11 +72179,11 @@ async function processMetadataDeletions(skillSourcePath, installGlobally) {
71753
72179
  }
71754
72180
  if (!sourceMetadata.deletions || sourceMetadata.deletions.length === 0)
71755
72181
  return;
71756
- const claudeDir = installGlobally ? join102(homedir25(), ".claude") : join102(process.cwd(), ".claude");
72182
+ const claudeDir = installGlobally ? join103(homedir25(), ".claude") : join103(process.cwd(), ".claude");
71757
72183
  if (!existsSync47(claudeDir))
71758
72184
  return;
71759
72185
  try {
71760
- const result = await handleDeletions(sourceMetadata, claudeDir);
72186
+ const result = await handleDeletions(sourceMetadata, claudeDir, undefined);
71761
72187
  if (result.deletedPaths.length > 0) {
71762
72188
  logger.verbose(`[migrate] Cleaned up ${result.deletedPaths.length} deprecated path(s): ${result.deletedPaths.join(", ")}`);
71763
72189
  }
@@ -71901,8 +72327,8 @@ async function migrateCommand(options2) {
71901
72327
  selectedProviders = Array.from(new Set(selectedProviders));
71902
72328
  let installGlobally = options2.global ?? false;
71903
72329
  if (options2.global === undefined && !options2.yes) {
71904
- const projectTarget = join102(process.cwd(), ".claude");
71905
- const globalTarget = join102(homedir25(), ".claude");
72330
+ const projectTarget = join103(process.cwd(), ".claude");
72331
+ const globalTarget = join103(homedir25(), ".claude");
71906
72332
  const scopeChoice = await ie({
71907
72333
  message: "Installation scope",
71908
72334
  options: [
@@ -71954,7 +72380,7 @@ async function migrateCommand(options2) {
71954
72380
  }
71955
72381
  const providerNames = selectedProviders.map((prov) => import_picocolors27.default.cyan(providers[prov].displayName)).join(", ");
71956
72382
  f2.message(` Providers: ${providerNames}`);
71957
- const targetDir = installGlobally ? join102(homedir25(), ".claude") : join102(process.cwd(), ".claude");
72383
+ const targetDir = installGlobally ? join103(homedir25(), ".claude") : join103(process.cwd(), ".claude");
71958
72384
  f2.message(` Scope: ${installGlobally ? "Global" : "Project"} ${import_picocolors27.default.dim(`-> ${targetDir}`)}`);
71959
72385
  const cmdProviders = getProvidersSupporting("commands");
71960
72386
  const unsupportedCmd = selectedProviders.filter((pv) => !cmdProviders.includes(pv));
@@ -72464,7 +72890,7 @@ async function handleDirectorySetup(ctx) {
72464
72890
  };
72465
72891
  }
72466
72892
  // src/commands/new/phases/project-creation.ts
72467
- import { join as join103 } from "node:path";
72893
+ import { join as join104 } from "node:path";
72468
72894
  init_github_client();
72469
72895
  init_logger();
72470
72896
  init_output_manager();
@@ -72618,7 +73044,7 @@ async function projectCreation(kit, resolvedDir, validOptions, isNonInteractive2
72618
73044
  output.section("Installing");
72619
73045
  logger.verbose("Installation target", { directory: resolvedDir });
72620
73046
  const merger = new FileMerger;
72621
- const claudeDir = join103(resolvedDir, ".claude");
73047
+ const claudeDir = join104(resolvedDir, ".claude");
72622
73048
  merger.setMultiKitContext(claudeDir, kit);
72623
73049
  if (validOptions.exclude && validOptions.exclude.length > 0) {
72624
73050
  merger.addIgnorePatterns(validOptions.exclude);
@@ -72671,10 +73097,10 @@ async function handleProjectCreation(ctx) {
72671
73097
  };
72672
73098
  }
72673
73099
  // src/commands/new/phases/post-setup.ts
72674
- import { join as join105 } from "node:path";
73100
+ import { join as join106 } from "node:path";
72675
73101
 
72676
73102
  // src/domains/installation/setup-wizard.ts
72677
- import { join as join104 } from "node:path";
73103
+ import { join as join105 } from "node:path";
72678
73104
  init_logger();
72679
73105
  init_dist2();
72680
73106
  var import_fs_extra36 = __toESM(require_lib(), 1);
@@ -72754,7 +73180,7 @@ async function parseEnvFile(path9) {
72754
73180
  }
72755
73181
  }
72756
73182
  async function checkGlobalConfig() {
72757
- const globalEnvPath = join104(getClaudeDir(), ".env");
73183
+ const globalEnvPath = join105(getClaudeDir(), ".env");
72758
73184
  if (!await import_fs_extra36.pathExists(globalEnvPath))
72759
73185
  return false;
72760
73186
  const env2 = await parseEnvFile(globalEnvPath);
@@ -72770,7 +73196,7 @@ async function runSetupWizard(options2) {
72770
73196
  let globalEnv = {};
72771
73197
  const hasGlobalConfig = !isGlobal && await checkGlobalConfig();
72772
73198
  if (!isGlobal) {
72773
- const globalEnvPath = join104(getClaudeDir(), ".env");
73199
+ const globalEnvPath = join105(getClaudeDir(), ".env");
72774
73200
  if (await import_fs_extra36.pathExists(globalEnvPath)) {
72775
73201
  globalEnv = await parseEnvFile(globalEnvPath);
72776
73202
  }
@@ -72833,7 +73259,7 @@ async function runSetupWizard(options2) {
72833
73259
  }
72834
73260
  }
72835
73261
  await generateEnvFile(targetDir, values);
72836
- f2.success(`Configuration saved to ${join104(targetDir, ".env")}`);
73262
+ f2.success(`Configuration saved to ${join105(targetDir, ".env")}`);
72837
73263
  return true;
72838
73264
  }
72839
73265
  async function promptForAdditionalGeminiKeys(primaryKey) {
@@ -72936,9 +73362,9 @@ async function postSetup(resolvedDir, validOptions, isNonInteractive2, prompts)
72936
73362
  withSudo: validOptions.withSudo
72937
73363
  });
72938
73364
  }
72939
- const claudeDir = join105(resolvedDir, ".claude");
73365
+ const claudeDir = join106(resolvedDir, ".claude");
72940
73366
  await promptSetupWizardIfNeeded({
72941
- envPath: join105(claudeDir, ".env"),
73367
+ envPath: join106(claudeDir, ".env"),
72942
73368
  claudeDir,
72943
73369
  isGlobal: false,
72944
73370
  isNonInteractive: isNonInteractive2,
@@ -73015,11 +73441,11 @@ Example: tkm new --use-git --release v2.1.0`);
73015
73441
  // src/commands/plan/plan-command.ts
73016
73442
  init_output_manager();
73017
73443
  import { existsSync as existsSync52, statSync as statSync5 } from "node:fs";
73018
- import { dirname as dirname33, join as join109, parse as parse2, resolve as resolve31 } from "node:path";
73444
+ import { dirname as dirname33, join as join110, parse as parse2, resolve as resolve31 } from "node:path";
73019
73445
 
73020
73446
  // src/commands/plan/plan-read-handlers.ts
73021
73447
  import { existsSync as existsSync51, statSync as statSync4 } from "node:fs";
73022
- import { basename as basename15, dirname as dirname32, join as join108, relative as relative20, resolve as resolve29 } from "node:path";
73448
+ import { basename as basename15, dirname as dirname32, join as join109, relative as relative20, resolve as resolve29 } from "node:path";
73023
73449
 
73024
73450
  // src/domains/plan-parser/index.ts
73025
73451
  import { dirname as dirname31 } from "node:path";
@@ -73400,12 +73826,12 @@ function parsePlanFile(planFilePath, options2) {
73400
73826
  }
73401
73827
  // src/domains/plan-parser/plan-scanner.ts
73402
73828
  import { existsSync as existsSync48, readdirSync as readdirSync5 } from "node:fs";
73403
- import { join as join106 } from "node:path";
73829
+ import { join as join107 } from "node:path";
73404
73830
  function scanPlanDir(dir) {
73405
73831
  if (!existsSync48(dir))
73406
73832
  return [];
73407
73833
  try {
73408
- return readdirSync5(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join106(dir, entry.name, "plan.md")).filter(existsSync48);
73834
+ return readdirSync5(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join107(dir, entry.name, "plan.md")).filter(existsSync48);
73409
73835
  } catch {
73410
73836
  return [];
73411
73837
  }
@@ -73474,7 +73900,7 @@ function validatePlanFile(filePath, strict = false) {
73474
73900
  var import_gray_matter7 = __toESM(require_gray_matter(), 1);
73475
73901
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "node:fs";
73476
73902
  import { existsSync as existsSync50 } from "node:fs";
73477
- import { basename as basename14, dirname as dirname30, join as join107 } from "node:path";
73903
+ import { basename as basename14, dirname as dirname30, join as join108 } from "node:path";
73478
73904
  function phaseNameToFilename(id, name) {
73479
73905
  const numMatch = /^(\d+)([a-z]*)$/i.exec(id);
73480
73906
  const num = numMatch ? numMatch[1] : id;
@@ -73582,12 +74008,12 @@ function scaffoldPlan(options2) {
73582
74008
  mkdirSync3(dir, { recursive: true });
73583
74009
  const resolvedPhases = resolvePhaseIds(options2.phases);
73584
74010
  const optionsWithResolved = { ...options2, phases: resolvedPhases };
73585
- const planFile = join107(dir, "plan.md");
74011
+ const planFile = join108(dir, "plan.md");
73586
74012
  writeFileSync5(planFile, generatePlanMd(optionsWithResolved), "utf8");
73587
74013
  const phaseFiles = [];
73588
74014
  for (const phase of resolvedPhases) {
73589
74015
  const filename = phaseNameToFilename(phase.id, phase.name);
73590
- const phaseFile = join107(dir, filename);
74016
+ const phaseFile = join108(dir, filename);
73591
74017
  writeFileSync5(phaseFile, generatePhaseTemplate(phase), "utf8");
73592
74018
  phaseFiles.push(phaseFile);
73593
74019
  }
@@ -73667,7 +74093,7 @@ function phaseNameFilenameFromTableRow(body, phaseId, planDir) {
73667
74093
  continue;
73668
74094
  const linkMatch = /\[([^\]]+)\]\(\.\/([^)]+)\)/.exec(row);
73669
74095
  if (linkMatch)
73670
- return join107(planDir, linkMatch[2]);
74096
+ return join108(planDir, linkMatch[2]);
73671
74097
  }
73672
74098
  return null;
73673
74099
  }
@@ -73748,7 +74174,7 @@ function addPhase(planFile, name, afterId) {
73748
74174
  `);
73749
74175
  }
73750
74176
  writeFileSync5(planFile, import_gray_matter7.default.stringify(updatedBody, frontmatter), "utf8");
73751
- const phaseFilePath = join107(planDir, filename);
74177
+ const phaseFilePath = join108(planDir, filename);
73752
74178
  writeFileSync5(phaseFilePath, generatePhaseTemplate({ id: phaseId, name }), "utf8");
73753
74179
  return { phaseId, phaseFile: phaseFilePath };
73754
74180
  }
@@ -73852,7 +74278,7 @@ async function handleValidate(target, options2) {
73852
74278
  }
73853
74279
  async function handleStatus(target, options2) {
73854
74280
  const t = target ? resolve29(target) : null;
73855
- const plansDir = t && existsSync51(t) && statSync4(t).isDirectory() && !existsSync51(join108(t, "plan.md")) ? t : null;
74281
+ const plansDir = t && existsSync51(t) && statSync4(t).isDirectory() && !existsSync51(join109(t, "plan.md")) ? t : null;
73856
74282
  if (plansDir) {
73857
74283
  const planFiles = scanPlanDir(plansDir);
73858
74284
  if (planFiles.length === 0) {
@@ -74087,7 +74513,7 @@ function resolvePlanFile(target) {
74087
74513
  const stat13 = statSync5(t);
74088
74514
  if (stat13.isFile())
74089
74515
  return t;
74090
- const candidate = join109(t, "plan.md");
74516
+ const candidate = join110(t, "plan.md");
74091
74517
  if (existsSync52(candidate))
74092
74518
  return candidate;
74093
74519
  }
@@ -74095,7 +74521,7 @@ function resolvePlanFile(target) {
74095
74521
  let dir = process.cwd();
74096
74522
  const root = parse2(dir).root;
74097
74523
  while (dir !== root) {
74098
- const candidate = join109(dir, "plan.md");
74524
+ const candidate = join110(dir, "plan.md");
74099
74525
  if (existsSync52(candidate))
74100
74526
  return candidate;
74101
74527
  dir = dirname33(dir);
@@ -74367,112 +74793,112 @@ var import_picocolors34 = __toESM(require_picocolors(), 1);
74367
74793
  // src/commands/skills/agents.ts
74368
74794
  import { existsSync as existsSync54 } from "node:fs";
74369
74795
  import { homedir as homedir26 } from "node:os";
74370
- import { join as join110 } from "node:path";
74796
+ import { join as join111 } from "node:path";
74371
74797
  var home6 = homedir26();
74372
74798
  var agents = {
74373
74799
  "claude-code": {
74374
74800
  name: "claude-code",
74375
74801
  displayName: "Claude Code",
74376
74802
  projectPath: ".claude/skills",
74377
- globalPath: join110(home6, ".claude/skills"),
74378
- detect: async () => existsSync54(join110(home6, ".claude"))
74803
+ globalPath: join111(home6, ".claude/skills"),
74804
+ detect: async () => existsSync54(join111(home6, ".claude"))
74379
74805
  },
74380
74806
  cursor: {
74381
74807
  name: "cursor",
74382
74808
  displayName: "Cursor",
74383
74809
  projectPath: ".cursor/skills",
74384
- globalPath: join110(home6, ".cursor/skills"),
74385
- detect: async () => existsSync54(join110(home6, ".cursor"))
74810
+ globalPath: join111(home6, ".cursor/skills"),
74811
+ detect: async () => existsSync54(join111(home6, ".cursor"))
74386
74812
  },
74387
74813
  codex: {
74388
74814
  name: "codex",
74389
74815
  displayName: "Codex",
74390
74816
  projectPath: ".codex/skills",
74391
- globalPath: join110(home6, ".codex/skills"),
74392
- detect: async () => existsSync54(join110(home6, ".codex"))
74817
+ globalPath: join111(home6, ".codex/skills"),
74818
+ detect: async () => existsSync54(join111(home6, ".codex"))
74393
74819
  },
74394
74820
  opencode: {
74395
74821
  name: "opencode",
74396
74822
  displayName: "OpenCode",
74397
74823
  projectPath: ".opencode/skills",
74398
- globalPath: join110(home6, ".config/opencode/skills"),
74399
- detect: async () => existsSync54(join110(home6, ".config/opencode"))
74824
+ globalPath: join111(home6, ".config/opencode/skills"),
74825
+ detect: async () => existsSync54(join111(home6, ".config/opencode"))
74400
74826
  },
74401
74827
  goose: {
74402
74828
  name: "goose",
74403
74829
  displayName: "Goose",
74404
74830
  projectPath: ".goose/skills",
74405
- globalPath: join110(home6, ".config/goose/skills"),
74406
- detect: async () => existsSync54(join110(home6, ".config/goose"))
74831
+ globalPath: join111(home6, ".config/goose/skills"),
74832
+ detect: async () => existsSync54(join111(home6, ".config/goose"))
74407
74833
  },
74408
74834
  "gemini-cli": {
74409
74835
  name: "gemini-cli",
74410
74836
  displayName: "Gemini CLI",
74411
74837
  projectPath: ".agents/skills",
74412
- globalPath: join110(home6, ".agents/skills"),
74413
- detect: async () => existsSync54(join110(home6, ".gemini"))
74838
+ globalPath: join111(home6, ".agents/skills"),
74839
+ detect: async () => existsSync54(join111(home6, ".gemini"))
74414
74840
  },
74415
74841
  antigravity: {
74416
74842
  name: "antigravity",
74417
74843
  displayName: "Antigravity",
74418
74844
  projectPath: ".agent/skills",
74419
- globalPath: join110(home6, ".gemini/antigravity/skills"),
74420
- detect: async () => existsSync54(join110(process.cwd(), ".agent")) || existsSync54(join110(home6, ".gemini/antigravity"))
74845
+ globalPath: join111(home6, ".gemini/antigravity/skills"),
74846
+ detect: async () => existsSync54(join111(process.cwd(), ".agent")) || existsSync54(join111(home6, ".gemini/antigravity"))
74421
74847
  },
74422
74848
  "github-copilot": {
74423
74849
  name: "github-copilot",
74424
74850
  displayName: "GitHub Copilot",
74425
74851
  projectPath: ".github/skills",
74426
- globalPath: join110(home6, ".copilot/skills"),
74427
- detect: async () => existsSync54(join110(home6, ".copilot"))
74852
+ globalPath: join111(home6, ".copilot/skills"),
74853
+ detect: async () => existsSync54(join111(home6, ".copilot"))
74428
74854
  },
74429
74855
  amp: {
74430
74856
  name: "amp",
74431
74857
  displayName: "Amp",
74432
74858
  projectPath: ".agents/skills",
74433
- globalPath: join110(home6, ".config/agents/skills"),
74434
- detect: async () => existsSync54(join110(home6, ".config/amp"))
74859
+ globalPath: join111(home6, ".config/agents/skills"),
74860
+ detect: async () => existsSync54(join111(home6, ".config/amp"))
74435
74861
  },
74436
74862
  kilo: {
74437
74863
  name: "kilo",
74438
74864
  displayName: "Kilo Code",
74439
74865
  projectPath: ".kilocode/skills",
74440
- globalPath: join110(home6, ".kilocode/skills"),
74441
- detect: async () => existsSync54(join110(home6, ".kilocode"))
74866
+ globalPath: join111(home6, ".kilocode/skills"),
74867
+ detect: async () => existsSync54(join111(home6, ".kilocode"))
74442
74868
  },
74443
74869
  roo: {
74444
74870
  name: "roo",
74445
74871
  displayName: "Roo Code",
74446
74872
  projectPath: ".roo/skills",
74447
- globalPath: join110(home6, ".roo/skills"),
74448
- detect: async () => existsSync54(join110(home6, ".roo"))
74873
+ globalPath: join111(home6, ".roo/skills"),
74874
+ detect: async () => existsSync54(join111(home6, ".roo"))
74449
74875
  },
74450
74876
  windsurf: {
74451
74877
  name: "windsurf",
74452
74878
  displayName: "Windsurf",
74453
74879
  projectPath: ".windsurf/skills",
74454
- globalPath: join110(home6, ".codeium/windsurf/skills"),
74455
- detect: async () => existsSync54(join110(home6, ".codeium/windsurf"))
74880
+ globalPath: join111(home6, ".codeium/windsurf/skills"),
74881
+ detect: async () => existsSync54(join111(home6, ".codeium/windsurf"))
74456
74882
  },
74457
74883
  cline: {
74458
74884
  name: "cline",
74459
74885
  displayName: "Cline",
74460
74886
  projectPath: ".cline/skills",
74461
- globalPath: join110(home6, ".cline/skills"),
74462
- detect: async () => existsSync54(join110(home6, ".cline"))
74887
+ globalPath: join111(home6, ".cline/skills"),
74888
+ detect: async () => existsSync54(join111(home6, ".cline"))
74463
74889
  },
74464
74890
  openhands: {
74465
74891
  name: "openhands",
74466
74892
  displayName: "OpenHands",
74467
74893
  projectPath: ".openhands/skills",
74468
- globalPath: join110(home6, ".openhands/skills"),
74469
- detect: async () => existsSync54(join110(home6, ".openhands"))
74894
+ globalPath: join111(home6, ".openhands/skills"),
74895
+ detect: async () => existsSync54(join111(home6, ".openhands"))
74470
74896
  }
74471
74897
  };
74472
74898
  function getInstallPath(skillName, agent, options2) {
74473
74899
  const config = agents[agent];
74474
74900
  const basePath = options2.global ? config.globalPath : config.projectPath;
74475
- return join110(basePath, skillName);
74901
+ return join111(basePath, skillName);
74476
74902
  }
74477
74903
  function isSkillInstalled(skillName, agent, options2) {
74478
74904
  const installPath = getInstallPath(skillName, agent, options2);
@@ -74483,16 +74909,16 @@ function isSkillInstalled(skillName, agent, options2) {
74483
74909
  import { existsSync as existsSync56 } from "node:fs";
74484
74910
  import { cp as cp4, mkdir as mkdir31, rm as rm14, stat as stat13 } from "node:fs/promises";
74485
74911
  import { homedir as homedir28 } from "node:os";
74486
- import { dirname as dirname35, join as join112, resolve as resolve33 } from "node:path";
74912
+ import { dirname as dirname35, join as join113, resolve as resolve33 } from "node:path";
74487
74913
 
74488
74914
  // src/commands/skills/skills-registry.ts
74489
74915
  init_zod();
74490
74916
  import { existsSync as existsSync55 } from "node:fs";
74491
74917
  import { mkdir as mkdir30, readFile as readFile47, writeFile as writeFile35 } from "node:fs/promises";
74492
74918
  import { homedir as homedir27 } from "node:os";
74493
- import { dirname as dirname34, join as join111, sep as sep9 } from "node:path";
74919
+ import { dirname as dirname34, join as join112, sep as sep9 } from "node:path";
74494
74920
  var home7 = homedir27();
74495
- var REGISTRY_PATH2 = join111(home7, ".sunagentkit", "skill-registry.json");
74921
+ var REGISTRY_PATH2 = join112(home7, ".sunagentkit", "skill-registry.json");
74496
74922
  var SkillInstallationSchema = exports_external.object({
74497
74923
  skill: exports_external.string(),
74498
74924
  agent: exports_external.string(),
@@ -74630,7 +75056,7 @@ async function syncRegistry() {
74630
75056
  var LEGACY_SKILL_PATHS = {
74631
75057
  "gemini-cli": {
74632
75058
  project: ".gemini/skills",
74633
- global: join112(homedir28(), ".gemini/skills")
75059
+ global: join113(homedir28(), ".gemini/skills")
74634
75060
  }
74635
75061
  };
74636
75062
  function isSamePath3(path1, path22) {
@@ -74664,7 +75090,7 @@ async function cleanupLegacySkillPath(skillName, agent, global3) {
74664
75090
  if (!legacy)
74665
75091
  return;
74666
75092
  const legacyBase = global3 ? legacy.global : legacy.project;
74667
- const legacyPath = join112(legacyBase, skillName);
75093
+ const legacyPath = join113(legacyBase, skillName);
74668
75094
  if (!existsSync56(legacyPath))
74669
75095
  return;
74670
75096
  await rm14(legacyPath, { recursive: true, force: true });
@@ -74674,7 +75100,7 @@ async function cleanupLegacySkillPath(skillName, agent, global3) {
74674
75100
  if (entry.skill === skillName && entry.agent === agent && entry.global === global3) {
74675
75101
  if (entry.path === legacyPath) {
74676
75102
  const newBase = global3 ? agents[agent].globalPath : agents[agent].projectPath;
74677
- entry.path = join112(newBase, skillName);
75103
+ entry.path = join113(newBase, skillName);
74678
75104
  changed = true;
74679
75105
  }
74680
75106
  }
@@ -74759,7 +75185,7 @@ function getInstallPreview(skill, targetAgents, options2) {
74759
75185
  // src/commands/skills/skills-uninstaller.ts
74760
75186
  import { existsSync as existsSync57 } from "node:fs";
74761
75187
  import { rm as rm15 } from "node:fs/promises";
74762
- import { join as join113 } from "node:path";
75188
+ import { join as join114 } from "node:path";
74763
75189
  async function uninstallSkillFromAgent(skill, agent, global3) {
74764
75190
  const agentConfig = agents[agent];
74765
75191
  const registry = await readRegistry();
@@ -74807,7 +75233,7 @@ async function uninstallSkillFromAgent(skill, agent, global3) {
74807
75233
  async function forceUninstallSkill(skill, agent, global3) {
74808
75234
  const agentConfig = agents[agent];
74809
75235
  const basePath = global3 ? agentConfig.globalPath : agentConfig.projectPath;
74810
- const path9 = join113(basePath, skill);
75236
+ const path9 = join114(basePath, skill);
74811
75237
  if (!existsSync57(path9)) {
74812
75238
  return {
74813
75239
  skill,
@@ -75312,12 +75738,12 @@ init_logger();
75312
75738
  // src/commands/telemetry/shared.ts
75313
75739
  import { existsSync as existsSync58, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "node:fs";
75314
75740
  import { homedir as homedir29 } from "node:os";
75315
- import { join as join114 } from "node:path";
75741
+ import { join as join115 } from "node:path";
75316
75742
  init_token_store();
75317
- var USER_CACHE_PATH = join114(homedir29(), ".claude", "sk-user.json");
75318
- var EVENT_BUFFER_DIR = join114(homedir29(), ".claude", "sk-events");
75319
- var RATE_STATE_PATH = join114(homedir29(), ".claude", "sk-rate-state.json");
75320
- var METADATA_PATH = join114(homedir29(), ".claude", "metadata.json");
75743
+ var USER_CACHE_PATH = join115(homedir29(), ".claude", "sk-user.json");
75744
+ var EVENT_BUFFER_DIR = join115(homedir29(), ".claude", "sk-events");
75745
+ var RATE_STATE_PATH = join115(homedir29(), ".claude", "sk-rate-state.json");
75746
+ var METADATA_PATH = join115(homedir29(), ".claude", "metadata.json");
75321
75747
  var TELEMETRY_HOOK_FIELD = "hooks.telemetry";
75322
75748
  var TOKEN_PLACEHOLDER = "__INJECT_AT_RELEASE__";
75323
75749
  function readUserCache() {
@@ -75404,7 +75830,7 @@ async function handleDisable() {
75404
75830
  // src/commands/telemetry/phases/purge-local-handler.ts
75405
75831
  init_logger();
75406
75832
  import { existsSync as existsSync59, readdirSync as readdirSync7, unlinkSync as unlinkSync5 } from "node:fs";
75407
- import { join as join115 } from "node:path";
75833
+ import { join as join116 } from "node:path";
75408
75834
  function removeIfExists(path9) {
75409
75835
  try {
75410
75836
  if (!existsSync59(path9))
@@ -75424,7 +75850,7 @@ function removeBufferFiles() {
75424
75850
  if (!file.endsWith(".jsonl"))
75425
75851
  continue;
75426
75852
  try {
75427
- unlinkSync5(join115(EVENT_BUFFER_DIR, file));
75853
+ unlinkSync5(join116(EVENT_BUFFER_DIR, file));
75428
75854
  count += 1;
75429
75855
  } catch {}
75430
75856
  }
@@ -75650,13 +76076,13 @@ async function detectInstallations() {
75650
76076
 
75651
76077
  // src/commands/uninstall/removal-handler.ts
75652
76078
  import { readdirSync as readdirSync9, rmSync as rmSync6 } from "node:fs";
75653
- import { join as join117, resolve as resolve34, sep as sep10 } from "node:path";
76079
+ import { join as join118, resolve as resolve34, sep as sep10 } from "node:path";
75654
76080
  init_logger();
75655
76081
  var import_fs_extra38 = __toESM(require_lib(), 1);
75656
76082
 
75657
76083
  // src/commands/uninstall/analysis-handler.ts
75658
76084
  import { readdirSync as readdirSync8, rmSync as rmSync5 } from "node:fs";
75659
- import { dirname as dirname36, join as join116 } from "node:path";
76085
+ import { dirname as dirname36, join as join117 } from "node:path";
75660
76086
  init_logger();
75661
76087
  var import_picocolors35 = __toESM(require_picocolors(), 1);
75662
76088
  function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
@@ -75703,7 +76129,7 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
75703
76129
  if (uninstallManifest.isMultiKit && kit && metadata?.kits?.[kit]) {
75704
76130
  const kitFiles = metadata.kits[kit].files || [];
75705
76131
  for (const trackedFile of kitFiles) {
75706
- const filePath = join116(installation.path, trackedFile.path);
76132
+ const filePath = join117(installation.path, trackedFile.path);
75707
76133
  if (uninstallManifest.filesToPreserve.includes(trackedFile.path)) {
75708
76134
  result.toPreserve.push({ path: trackedFile.path, reason: "shared with other kit" });
75709
76135
  continue;
@@ -75733,7 +76159,7 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
75733
76159
  return result;
75734
76160
  }
75735
76161
  for (const trackedFile of allTrackedFiles) {
75736
- const filePath = join116(installation.path, trackedFile.path);
76162
+ const filePath = join117(installation.path, trackedFile.path);
75737
76163
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
75738
76164
  if (!ownershipResult.exists)
75739
76165
  continue;
@@ -75830,7 +76256,7 @@ async function removeInstallations(installations, options2) {
75830
76256
  let removedCount = 0;
75831
76257
  let cleanedDirs = 0;
75832
76258
  for (const item of analysis.toDelete) {
75833
- const filePath = join117(installation.path, item.path);
76259
+ const filePath = join118(installation.path, item.path);
75834
76260
  if (!await import_fs_extra38.pathExists(filePath))
75835
76261
  continue;
75836
76262
  if (!await isPathSafeToRemove(filePath, installation.path)) {
@@ -76032,7 +76458,7 @@ ${import_picocolors36.default.yellow("User modifications will be permanently del
76032
76458
  // src/commands/update-cli.ts
76033
76459
  init_takumi_config_manager();
76034
76460
  import { exec as exec8, spawn as spawn2 } from "node:child_process";
76035
- import { join as join118 } from "node:path";
76461
+ import { join as join119 } from "node:path";
76036
76462
  import { promisify as promisify14 } from "node:util";
76037
76463
 
76038
76464
  // src/domains/github/npm-registry.ts
@@ -76199,7 +76625,7 @@ var import_fs_extra39 = __toESM(require_lib(), 1);
76199
76625
  // package.json
76200
76626
  var package_default = {
76201
76627
  name: "@sunasteriskrnd/takumi",
76202
- version: "0.5.0",
76628
+ version: "0.7.0",
76203
76629
  description: "CLI tool for bootstrapping and managing Takumi projects",
76204
76630
  type: "module",
76205
76631
  repository: {
@@ -76222,6 +76648,7 @@ var package_default = {
76222
76648
  build: `bun build src/index.ts --outdir dist --target node --external @octokit/rest --external better-sqlite3 --external yauzl-promise --external @node-rs/crc32 && node -e "const fs=require('fs'),f='dist/index.js',c=fs.readFileSync(f,'utf-8');fs.writeFileSync(f,c.replace(/^#!.*\\n\\/\\/ @bun\\n/,''))"`,
76223
76649
  "verify:package": "node scripts/prepublish-check.js",
76224
76650
  test: "bun test",
76651
+ "test:ci": "NON_INTERACTIVE=true CI_SAFE_MODE=true bun test",
76225
76652
  "test:integration": "CK_RUN_CLI_INTEGRATION=1 bun test tests/integration/cli.test.ts",
76226
76653
  "test:watch": "bun test --watch",
76227
76654
  "test:quick": "./scripts/dev-quick-start.sh test",
@@ -76383,7 +76810,7 @@ function selectKitForUpdate(params) {
76383
76810
  };
76384
76811
  }
76385
76812
  async function readMetadataFile(claudeDir) {
76386
- const metadataPath = join118(claudeDir, "metadata.json");
76813
+ const metadataPath = join119(claudeDir, "metadata.json");
76387
76814
  try {
76388
76815
  if (!await import_fs_extra39.pathExists(metadataPath)) {
76389
76816
  return null;
@@ -76884,7 +77311,7 @@ init_logger();
76884
77311
  import { existsSync as existsSync65 } from "node:fs";
76885
77312
  import { rm as rm16 } from "node:fs/promises";
76886
77313
  import { homedir as homedir31 } from "node:os";
76887
- import { join as join125 } from "node:path";
77314
+ import { join as join126 } from "node:path";
76888
77315
  var import_picocolors38 = __toESM(require_picocolors(), 1);
76889
77316
 
76890
77317
  // src/commands/watch/phases/implementation-runner.ts
@@ -77394,7 +77821,7 @@ function spawnAndCollect3(command, args) {
77394
77821
 
77395
77822
  // src/commands/watch/phases/issue-processor.ts
77396
77823
  import { mkdir as mkdir32, writeFile as writeFile37 } from "node:fs/promises";
77397
- import { join as join121 } from "node:path";
77824
+ import { join as join122 } from "node:path";
77398
77825
 
77399
77826
  // src/commands/watch/phases/approval-detector.ts
77400
77827
  init_logger();
@@ -77769,9 +78196,9 @@ async function checkAwaitingApproval(state, setup, options2, watchLog, projectDi
77769
78196
 
77770
78197
  // src/commands/watch/phases/plan-dir-finder.ts
77771
78198
  import { readdir as readdir32, stat as stat14 } from "node:fs/promises";
77772
- import { join as join120 } from "node:path";
78199
+ import { join as join121 } from "node:path";
77773
78200
  async function findRecentPlanDir(cwd2, issueNumber, watchLog) {
77774
- const plansRoot = join120(cwd2, "plans");
78201
+ const plansRoot = join121(cwd2, "plans");
77775
78202
  try {
77776
78203
  const entries = await readdir32(plansRoot);
77777
78204
  const tenMinAgo = Date.now() - 10 * 60 * 1000;
@@ -77780,14 +78207,14 @@ async function findRecentPlanDir(cwd2, issueNumber, watchLog) {
77780
78207
  for (const entry of entries) {
77781
78208
  if (entry === "watch" || entry === "reports" || entry === "visuals")
77782
78209
  continue;
77783
- const dirPath = join120(plansRoot, entry);
78210
+ const dirPath = join121(plansRoot, entry);
77784
78211
  const dirStat = await stat14(dirPath);
77785
78212
  if (!dirStat.isDirectory())
77786
78213
  continue;
77787
78214
  if (dirStat.mtimeMs < tenMinAgo)
77788
78215
  continue;
77789
78216
  try {
77790
- await stat14(join120(dirPath, "plan.md"));
78217
+ await stat14(join121(dirPath, "plan.md"));
77791
78218
  } catch {
77792
78219
  continue;
77793
78220
  }
@@ -78018,13 +78445,13 @@ async function handlePlanGeneration(issue, state, config, setup, options2, watch
78018
78445
  stats.plansCreated++;
78019
78446
  const detectedPlanDir = await findRecentPlanDir(projectDir, issue.number, watchLog);
78020
78447
  if (detectedPlanDir) {
78021
- state.activeIssues[numStr].planPath = join121(detectedPlanDir, "plan.md");
78448
+ state.activeIssues[numStr].planPath = join122(detectedPlanDir, "plan.md");
78022
78449
  watchLog.info(`Plan directory detected: ${detectedPlanDir}`);
78023
78450
  } else {
78024
78451
  try {
78025
- const planDir = join121(projectDir, "plans", "watch");
78452
+ const planDir = join122(projectDir, "plans", "watch");
78026
78453
  await mkdir32(planDir, { recursive: true });
78027
- const planFilePath = join121(planDir, `issue-${issue.number}-plan.md`);
78454
+ const planFilePath = join122(planDir, `issue-${issue.number}-plan.md`);
78028
78455
  await writeFile37(planFilePath, planResult.planText, "utf-8");
78029
78456
  state.activeIssues[numStr].planPath = planFilePath;
78030
78457
  watchLog.info(`Plan saved (fallback) to ${planFilePath}`);
@@ -78329,18 +78756,18 @@ init_logger();
78329
78756
  import { spawnSync as spawnSync5 } from "node:child_process";
78330
78757
  import { existsSync as existsSync62 } from "node:fs";
78331
78758
  import { readdir as readdir33, stat as stat15 } from "node:fs/promises";
78332
- import { join as join122 } from "node:path";
78759
+ import { join as join123 } from "node:path";
78333
78760
  async function scanForRepos(parentDir) {
78334
78761
  const repos = [];
78335
78762
  const entries = await readdir33(parentDir);
78336
78763
  for (const entry of entries) {
78337
78764
  if (entry.startsWith("."))
78338
78765
  continue;
78339
- const fullPath = join122(parentDir, entry);
78766
+ const fullPath = join123(parentDir, entry);
78340
78767
  const entryStat = await stat15(fullPath);
78341
78768
  if (!entryStat.isDirectory())
78342
78769
  continue;
78343
- const gitDir = join122(fullPath, ".git");
78770
+ const gitDir = join123(fullPath, ".git");
78344
78771
  if (!existsSync62(gitDir))
78345
78772
  continue;
78346
78773
  const result = spawnSync5("gh", ["repo", "view", "--json", "owner,name"], {
@@ -78367,7 +78794,7 @@ init_logger();
78367
78794
  import { spawnSync as spawnSync6 } from "node:child_process";
78368
78795
  import { existsSync as existsSync63 } from "node:fs";
78369
78796
  import { homedir as homedir30 } from "node:os";
78370
- import { join as join123 } from "node:path";
78797
+ import { join as join124 } from "node:path";
78371
78798
  async function validateSetup(cwd2) {
78372
78799
  const workDir = cwd2 ?? process.cwd();
78373
78800
  const ghVersion = spawnSync6("gh", ["--version"], { encoding: "utf-8", timeout: 1e4 });
@@ -78398,7 +78825,7 @@ Run this command from a directory with a GitHub remote.`);
78398
78825
  } catch {
78399
78826
  throw new Error(`Failed to parse repository info: ${ghRepo.stdout}`);
78400
78827
  }
78401
- const skillsPath = join123(homedir30(), ".claude", "skills");
78828
+ const skillsPath = join124(homedir30(), ".claude", "skills");
78402
78829
  const skillsAvailable = existsSync63(skillsPath);
78403
78830
  if (!skillsAvailable) {
78404
78831
  logger.warning(`Takumi Engineer skills not found at ${skillsPath}`);
@@ -78417,7 +78844,7 @@ init_path_resolver();
78417
78844
  import { createWriteStream as createWriteStream4, statSync as statSync6 } from "node:fs";
78418
78845
  import { existsSync as existsSync64 } from "node:fs";
78419
78846
  import { mkdir as mkdir34, rename as rename9 } from "node:fs/promises";
78420
- import { join as join124 } from "node:path";
78847
+ import { join as join125 } from "node:path";
78421
78848
 
78422
78849
  class WatchLogger {
78423
78850
  logStream = null;
@@ -78425,7 +78852,7 @@ class WatchLogger {
78425
78852
  logPath = null;
78426
78853
  maxBytes;
78427
78854
  constructor(logDir, maxBytes = 0) {
78428
- this.logDir = logDir ?? join124(PathResolver.getTakumiDir(), "logs");
78855
+ this.logDir = logDir ?? join125(PathResolver.getTakumiDir(), "logs");
78429
78856
  this.maxBytes = maxBytes;
78430
78857
  }
78431
78858
  async init() {
@@ -78434,7 +78861,7 @@ class WatchLogger {
78434
78861
  await mkdir34(this.logDir, { recursive: true });
78435
78862
  }
78436
78863
  const dateStr = formatDate(new Date);
78437
- this.logPath = join124(this.logDir, `watch-${dateStr}.log`);
78864
+ this.logPath = join125(this.logDir, `watch-${dateStr}.log`);
78438
78865
  this.logStream = createWriteStream4(this.logPath, { flags: "a", mode: 384 });
78439
78866
  } catch (error) {
78440
78867
  logger.warning(`Cannot create watch log file: ${error instanceof Error ? error.message : "Unknown"}`);
@@ -78614,7 +79041,7 @@ async function watchCommand(options2) {
78614
79041
  }
78615
79042
  async function discoverRepos(options2, watchLog) {
78616
79043
  const cwd2 = process.cwd();
78617
- const isGitRepo = existsSync65(join125(cwd2, ".git"));
79044
+ const isGitRepo = existsSync65(join126(cwd2, ".git"));
78618
79045
  if (options2.force) {
78619
79046
  await forceRemoveLock(watchLog);
78620
79047
  }
@@ -78684,7 +79111,7 @@ async function resetState(state, projectDir, watchLog) {
78684
79111
  watchLog.info(`Watch state reset (--force) for ${projectDir}`);
78685
79112
  }
78686
79113
  async function forceRemoveLock(watchLog) {
78687
- const lockPath = join125(homedir31(), ".sunagentkit", "locks", `${LOCK_NAME}.lock`);
79114
+ const lockPath = join126(homedir31(), ".sunagentkit", "locks", `${LOCK_NAME}.lock`);
78688
79115
  try {
78689
79116
  await rm16(lockPath, { recursive: true, force: true });
78690
79117
  watchLog.info("Removed existing lock file (--force)");
@@ -78728,13 +79155,13 @@ function sleep2(ms2) {
78728
79155
  // src/cli/command-registry.ts
78729
79156
  init_logger();
78730
79157
  function registerCommands(cli) {
78731
- cli.command("new", "Bootstrap a new Takumi project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer)").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--force", "Overwrite existing files without confirmation").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--opencode", "Install OpenCode CLI package (non-interactive mode)").option("--gemini", "Install Google Gemini CLI package (non-interactive mode)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /sk: prefix to all slash commands by moving them to commands/sk/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").option("--local", "Use local monorepo as kit source (auto-detects from CLI location)").option("--use-gh", "Force GitHub release source (bypass Worker R2 proxy; default uses Worker)").action(async (options2) => {
79158
+ cli.command("new", "Bootstrap a new Takumi project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit(s) to install (engineer, extras). Repeat the flag to install multiple kits, e.g. --kit engineer --kit extras.").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--force", "Overwrite existing files without confirmation").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--opencode", "Install OpenCode CLI package (non-interactive mode)").option("--gemini", "Install Google Gemini CLI package (non-interactive mode)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /sk: prefix to all slash commands by moving them to commands/sk/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").option("--local", "Use local monorepo as kit source (auto-detects from CLI location)").option("--use-gh", "Force GitHub release source (bypass Worker R2 proxy; default uses Worker)").action(async (options2) => {
78732
79159
  if (options2.exclude && !Array.isArray(options2.exclude)) {
78733
79160
  options2.exclude = [options2.exclude];
78734
79161
  }
78735
79162
  await newCommand(options2);
78736
79163
  });
78737
- cli.command("init", "Initialize or update Takumi project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer)").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--only <pattern>", "Include only files matching glob pattern (can be used multiple times)").option("-g, --global", "Use platform-specific user configuration directory").option("--fresh", "Full reset: remove SK files, replace settings.json and CLAUDE.md, reinstall from scratch").option("--force", "Force reinstall even if already at latest version (use with --yes; re-onboards missing files without full reset)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /sk: prefix to all slash commands by moving them to commands/sk/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--dry-run", "Preview changes without applying them (requires --prefix)").option("--force-overwrite", "Override ownership protections and delete user-modified files (requires --prefix)").option("--force-overwrite-settings", "Fully replace settings.json instead of selective merge (destroys user customizations)").option("--skip-setup", "Skip interactive configuration wizard").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--sync", "Sync config files from upstream with interactive hunk-by-hunk merge").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").option("--local", "Use local monorepo as kit source (auto-detects from CLI location)").option("--use-gh", "Force GitHub release source (bypass Worker R2 proxy; default uses Worker)").option("-a, --agent <agents...>", "Target agents (claude-code, codex). Default: claude-code").action(async (options2) => {
79164
+ cli.command("init", "Initialize or update Takumi project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit(s) to install (engineer, extras). Repeat the flag to install multiple kits, e.g. --kit engineer --kit extras.").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--only <pattern>", "Include only files matching glob pattern (can be used multiple times)").option("-g, --global", "Use platform-specific user configuration directory").option("--fresh", "Full reset: remove SK files, replace settings.json and CLAUDE.md, reinstall from scratch").option("--force", "Force reinstall even if already at latest version (use with --yes; re-onboards missing files without full reset)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /sk: prefix to all slash commands by moving them to commands/sk/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--dry-run", "Preview changes without applying them (requires --prefix)").option("--force-overwrite", "Override ownership protections and delete user-modified files (requires --prefix)").option("--force-overwrite-settings", "Fully replace settings.json instead of selective merge (destroys user customizations)").option("--skip-setup", "Skip interactive configuration wizard").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--sync", "Sync config files from upstream with interactive hunk-by-hunk merge").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").option("--local", "Use local monorepo as kit source (auto-detects from CLI location)").option("--use-gh", "Force GitHub release source (bypass Worker R2 proxy; default uses Worker)").option("-a, --agent <agents...>", "Target agents (claude-code, codex). Default: claude-code").action(async (options2) => {
78738
79165
  if (options2.exclude && !Array.isArray(options2.exclude)) {
78739
79166
  options2.exclude = [options2.exclude];
78740
79167
  }
@@ -78747,6 +79174,9 @@ function registerCommands(cli) {
78747
79174
  if (options2.agent) {
78748
79175
  options2.agents = options2.agent;
78749
79176
  }
79177
+ if (options2.kit && Array.isArray(options2.kit)) {
79178
+ options2.kit = options2.kit.join(",");
79179
+ }
78750
79180
  await initCommand(options2);
78751
79181
  });
78752
79182
  cli.command("update", "Update Takumi CLI to the latest version").option("-r, --release <version>", "Update to a specific version").option("--check", "Check for updates without installing").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("-d, --dev", "Update to the latest dev version").option("--beta", "Alias for --dev (deprecated)").option("--registry <url>", "Custom npm registry URL").option("--kit <kit>", "[DEPRECATED] Use 'takumi init --kit <kit>' instead").option("-g, --global", "[DEPRECATED] Use 'takumi init --global' instead").action(async (options2) => {
@@ -78886,7 +79316,7 @@ function registerCommands(cli) {
78886
79316
 
78887
79317
  // src/cli/version-display.ts
78888
79318
  import { existsSync as existsSync77, readFileSync as readFileSync20 } from "node:fs";
78889
- import { join as join137 } from "node:path";
79319
+ import { join as join138 } from "node:path";
78890
79320
  init_help_banner();
78891
79321
  // src/domains/versioning/checking/kit-version-checker.ts
78892
79322
  init_github_client();
@@ -78898,14 +79328,14 @@ init_logger();
78898
79328
  init_path_resolver();
78899
79329
  import { existsSync as existsSync76 } from "node:fs";
78900
79330
  import { mkdir as mkdir35, readFile as readFile52, writeFile as writeFile40 } from "node:fs/promises";
78901
- import { join as join136 } from "node:path";
79331
+ import { join as join137 } from "node:path";
78902
79332
 
78903
79333
  class VersionCacheManager {
78904
79334
  static CACHE_FILENAME = "version-check.json";
78905
79335
  static CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
78906
79336
  static getCacheFile() {
78907
79337
  const cacheDir = PathResolver.getCacheDir(false);
78908
- return join136(cacheDir, VersionCacheManager.CACHE_FILENAME);
79338
+ return join137(cacheDir, VersionCacheManager.CACHE_FILENAME);
78909
79339
  }
78910
79340
  static async load() {
78911
79341
  const cacheFile = VersionCacheManager.getCacheFile();
@@ -79198,8 +79628,8 @@ async function displayVersion() {
79198
79628
  const localSubdir = PROVIDER_LOCAL_SUBDIRS[provider];
79199
79629
  if (!localSubdir)
79200
79630
  continue;
79201
- const localMeta = join137(process.cwd(), localSubdir, "metadata.json");
79202
- if (localMeta === join137(inst.globalRoot(), "metadata.json"))
79631
+ const localMeta = join138(process.cwd(), localSubdir, "metadata.json");
79632
+ if (localMeta === join138(inst.globalRoot(), "metadata.json"))
79203
79633
  continue;
79204
79634
  if (!existsSync77(localMeta))
79205
79635
  continue;
@@ -79224,7 +79654,7 @@ async function displayVersion() {
79224
79654
  }
79225
79655
  const singleInstall = detectedGlobals.length === 1 && detectedGlobals[0]?.provider === "claude-code";
79226
79656
  for (const { provider, path: installPath } of detectedGlobals) {
79227
- const metadataPath = join137(installPath, "metadata.json");
79657
+ const metadataPath = join138(installPath, "metadata.json");
79228
79658
  if (existsSync77(metadataPath)) {
79229
79659
  try {
79230
79660
  const rawMetadata = JSON.parse(readFileSync20(metadataPath, "utf-8"));