ocx 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10528,7 +10528,7 @@ class GhostConfigProvider {
10528
10528
  // package.json
10529
10529
  var package_default = {
10530
10530
  name: "ocx",
10531
- version: "1.2.1",
10531
+ version: "1.2.2",
10532
10532
  description: "OCX CLI - ShadCN-style registry for OpenCode extensions. Install agents, plugins, skills, and MCP servers.",
10533
10533
  author: "kdcokenny",
10534
10534
  license: "MIT",
@@ -14522,7 +14522,7 @@ Profile location:`);
14522
14522
  // src/commands/ghost/opencode.ts
14523
14523
  import { renameSync, rmSync } from "fs";
14524
14524
  import { copyFile as copyFilePromise } from "fs/promises";
14525
- import path7 from "path";
14525
+ import path8 from "path";
14526
14526
 
14527
14527
  // src/utils/opencode-discovery.ts
14528
14528
  import { exists } from "fs/promises";
@@ -14585,40 +14585,73 @@ async function discoverProjectFiles(start, stop) {
14585
14585
  return excluded;
14586
14586
  }
14587
14587
 
14588
+ // src/utils/symlink-farm.ts
14589
+ import { randomBytes } from "crypto";
14590
+ import { mkdir as mkdir4, readdir as readdir3, rename as rename3, rm as rm2, stat as stat3, symlink as symlink2 } from "fs/promises";
14591
+ import { tmpdir } from "os";
14592
+ import { dirname as dirname4, isAbsolute, join as join5, relative as relative2 } from "path";
14593
+
14588
14594
  // src/utils/pattern-filter.ts
14595
+ import path6 from "path";
14589
14596
  var {Glob: Glob2 } = globalThis.Bun;
14590
- function matchesAnyGlob(filePath, globs) {
14591
- return globs.some((g) => g.match(filePath));
14592
- }
14593
- function filterExcludedPaths(excludedPaths, includePatterns, excludePatterns) {
14594
- if (!includePatterns || includePatterns.length === 0) {
14595
- return new Set(excludedPaths);
14596
- }
14597
- const includeGlobs = includePatterns.map((p) => new Glob2(p));
14598
- const excludeGlobs = excludePatterns?.map((p) => new Glob2(p)) ?? [];
14599
- const filteredExclusions = new Set;
14600
- for (const path6 of excludedPaths) {
14601
- const matchesInclude = matchesAnyGlob(path6, includeGlobs);
14602
- const matchesExclude = matchesAnyGlob(path6, excludeGlobs);
14603
- if (matchesInclude && !matchesExclude) {
14604
- continue;
14597
+ function normalizeForMatching(absolutePath, projectRoot) {
14598
+ const relativePath = path6.relative(projectRoot, absolutePath);
14599
+ return relativePath.split(path6.sep).join("/").replace(/^\.\//, "");
14600
+ }
14601
+ class PathMatcher {
14602
+ includeGlobs;
14603
+ excludeGlobs;
14604
+ includePatterns;
14605
+ constructor(includePatterns = [], excludePatterns = []) {
14606
+ this.includePatterns = includePatterns;
14607
+ this.includeGlobs = includePatterns.map((p) => ({ pattern: p, glob: new Glob2(p) }));
14608
+ this.excludeGlobs = excludePatterns.map((p) => ({ pattern: p, glob: new Glob2(p) }));
14609
+ }
14610
+ getDisposition(relativePath) {
14611
+ if (this.excludeGlobs.some((g) => g.glob.match(relativePath))) {
14612
+ return { type: "excluded" };
14613
+ }
14614
+ if (this.includeGlobs.some((g) => g.glob.match(relativePath))) {
14615
+ return { type: "included" };
14616
+ }
14617
+ const patternsInsideDir = this.includePatterns.filter((pattern) => {
14618
+ if (pattern.startsWith(`${relativePath}/`))
14619
+ return true;
14620
+ if (pattern.startsWith("**/"))
14621
+ return true;
14622
+ return false;
14623
+ });
14624
+ if (patternsInsideDir.length > 0) {
14625
+ return { type: "partial", patterns: patternsInsideDir };
14626
+ }
14627
+ if (this.includePatterns.length > 0) {
14628
+ return { type: "excluded" };
14605
14629
  }
14606
- filteredExclusions.add(path6);
14630
+ return { type: "included" };
14631
+ }
14632
+ targetsInside(dirPath) {
14633
+ const normalizedDir = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
14634
+ return this.includePatterns.some((p) => p.startsWith(normalizedDir));
14635
+ }
14636
+ getInnerPatterns(dirPath) {
14637
+ const normalizedDir = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
14638
+ return this.includePatterns.filter((p) => p.startsWith(normalizedDir));
14607
14639
  }
14608
- return filteredExclusions;
14640
+ hasIncludePatterns() {
14641
+ return this.includePatterns.length > 0;
14642
+ }
14643
+ }
14644
+ function createPathMatcher(includePatterns = [], excludePatterns = []) {
14645
+ return new PathMatcher(includePatterns, excludePatterns);
14609
14646
  }
14610
14647
 
14611
14648
  // src/utils/symlink-farm.ts
14612
- import { randomBytes } from "crypto";
14613
- import { mkdir as mkdir4, readdir as readdir3, rename as rename3, rm as rm2, stat as stat3, symlink as symlink2 } from "fs/promises";
14614
- import { tmpdir } from "os";
14615
- import { dirname as dirname4, isAbsolute, join as join5, relative as relative2 } from "path";
14616
14649
  var STALE_SESSION_THRESHOLD_MS = 24 * 60 * 60 * 1000;
14617
14650
  var REMOVING_THRESHOLD_MS = 60 * 60 * 1000;
14618
14651
  var GHOST_DIR_PREFIX = "ocx-ghost-";
14619
14652
  var REMOVING_SUFFIX = "-removing";
14620
14653
  var GHOST_MARKER_FILE = ".ocx-ghost-marker";
14621
- async function createSymlinkFarm(sourceDir, excludePaths) {
14654
+ async function createSymlinkFarm(sourceDir, excludePaths, options2) {
14622
14655
  if (!isAbsolute(sourceDir)) {
14623
14656
  throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
14624
14657
  }
@@ -14626,14 +14659,9 @@ async function createSymlinkFarm(sourceDir, excludePaths) {
14626
14659
  const tempDir = join5(tmpdir(), `${GHOST_DIR_PREFIX}${suffix}`);
14627
14660
  await Bun.write(join5(tempDir, GHOST_MARKER_FILE), "");
14628
14661
  try {
14629
- const entries = await readdir3(sourceDir, { withFileTypes: true });
14630
- for (const entry of entries) {
14631
- const sourcePath = join5(sourceDir, entry.name);
14632
- if (excludePaths.has(sourcePath))
14633
- continue;
14634
- const targetPath = join5(tempDir, entry.name);
14635
- await symlink2(sourcePath, targetPath);
14636
- }
14662
+ const matcher = createPathMatcher(options2?.includePatterns ?? [], options2?.excludePatterns ?? []);
14663
+ const plan = await computeSymlinkPlan(sourceDir, sourceDir, excludePaths, matcher);
14664
+ await executeSymlinkPlan(plan, sourceDir, tempDir);
14637
14665
  return tempDir;
14638
14666
  } catch (error) {
14639
14667
  await rm2(tempDir, { recursive: true, force: true }).catch(() => {});
@@ -14717,9 +14745,104 @@ async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
14717
14745
  }
14718
14746
  return cleanedCount;
14719
14747
  }
14748
+ async function handleExcludedEntry(isDirectory, disposition, computeNestedPlan) {
14749
+ if (disposition.type === "excluded") {
14750
+ return { action: "skip" };
14751
+ }
14752
+ if (disposition.type === "included") {
14753
+ return { action: "includeWhole" };
14754
+ }
14755
+ if (isDirectory) {
14756
+ const nestedPlan = await computeNestedPlan();
14757
+ return { action: "partial", nestedPlan };
14758
+ }
14759
+ return { action: "skip" };
14760
+ }
14761
+ async function computeSymlinkPlan(sourceDir, projectRoot, excludedPaths, matcher, insideExcludedTree = false) {
14762
+ if (!isAbsolute(sourceDir)) {
14763
+ throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
14764
+ }
14765
+ const plan = {
14766
+ wholeDirs: [],
14767
+ files: [],
14768
+ partialDirs: new Map
14769
+ };
14770
+ const entries = await readdir3(sourceDir, { withFileTypes: true });
14771
+ for (const entry of entries) {
14772
+ const sourcePath = join5(sourceDir, entry.name);
14773
+ const relativePath = normalizeForMatching(sourcePath, projectRoot);
14774
+ if (insideExcludedTree) {
14775
+ const disposition = matcher.getDisposition(relativePath);
14776
+ const result = await handleExcludedEntry(entry.isDirectory(), disposition, () => computeSymlinkPlan(sourcePath, projectRoot, excludedPaths, matcher, true));
14777
+ if (result.action === "skip") {
14778
+ continue;
14779
+ }
14780
+ if (result.action === "includeWhole") {
14781
+ if (entry.isDirectory()) {
14782
+ plan.wholeDirs.push(entry.name);
14783
+ } else {
14784
+ plan.files.push(entry.name);
14785
+ }
14786
+ continue;
14787
+ }
14788
+ plan.partialDirs.set(entry.name, result.nestedPlan);
14789
+ continue;
14790
+ }
14791
+ if (excludedPaths.has(sourcePath)) {
14792
+ if (!matcher.hasIncludePatterns()) {
14793
+ continue;
14794
+ }
14795
+ const disposition = matcher.getDisposition(relativePath);
14796
+ const result = await handleExcludedEntry(entry.isDirectory(), disposition, () => computeSymlinkPlan(sourcePath, projectRoot, excludedPaths, matcher, true));
14797
+ if (result.action === "skip") {
14798
+ continue;
14799
+ }
14800
+ if (result.action === "includeWhole") {
14801
+ if (entry.isDirectory()) {
14802
+ plan.wholeDirs.push(entry.name);
14803
+ } else {
14804
+ plan.files.push(entry.name);
14805
+ }
14806
+ continue;
14807
+ }
14808
+ plan.partialDirs.set(entry.name, result.nestedPlan);
14809
+ continue;
14810
+ }
14811
+ if (entry.isDirectory()) {
14812
+ plan.wholeDirs.push(entry.name);
14813
+ } else {
14814
+ plan.files.push(entry.name);
14815
+ }
14816
+ }
14817
+ return plan;
14818
+ }
14819
+ async function executeSymlinkPlan(plan, sourceRoot, targetRoot) {
14820
+ if (!isAbsolute(sourceRoot)) {
14821
+ throw new Error(`sourceRoot must be an absolute path, got: ${sourceRoot}`);
14822
+ }
14823
+ if (!isAbsolute(targetRoot)) {
14824
+ throw new Error(`targetRoot must be an absolute path, got: ${targetRoot}`);
14825
+ }
14826
+ for (const dirName of plan.wholeDirs) {
14827
+ const sourcePath = join5(sourceRoot, dirName);
14828
+ const targetPath = join5(targetRoot, dirName);
14829
+ await symlink2(sourcePath, targetPath);
14830
+ }
14831
+ for (const fileName of plan.files) {
14832
+ const sourcePath = join5(sourceRoot, fileName);
14833
+ const targetPath = join5(targetRoot, fileName);
14834
+ await symlink2(sourcePath, targetPath);
14835
+ }
14836
+ for (const [dirName, nestedPlan] of plan.partialDirs) {
14837
+ const sourcePath = join5(sourceRoot, dirName);
14838
+ const targetPath = join5(targetRoot, dirName);
14839
+ await mkdir4(targetPath, { recursive: true });
14840
+ await executeSymlinkPlan(nestedPlan, sourcePath, targetPath);
14841
+ }
14842
+ }
14720
14843
 
14721
14844
  // src/utils/terminal-title.ts
14722
- import path6 from "path";
14845
+ import path7 from "path";
14723
14846
  var MAX_BRANCH_LENGTH = 20;
14724
14847
  function isInsideTmux() {
14725
14848
  return Boolean(process.env.TMUX);
@@ -14742,7 +14865,7 @@ function setTerminalName(name) {
14742
14865
  setTerminalTitle(name);
14743
14866
  }
14744
14867
  function formatTerminalName(cwd, profileName, gitInfo) {
14745
- const repoName = gitInfo.repoName ?? path6.basename(cwd);
14868
+ const repoName = gitInfo.repoName ?? path7.basename(cwd);
14746
14869
  if (!gitInfo.branch) {
14747
14870
  return `ghost[${profileName}]:${repoName}`;
14748
14871
  }
@@ -14786,13 +14909,15 @@ async function runGhostOpenCode(args, options2) {
14786
14909
  const gitRoot = gitContext?.workTree ?? cwd;
14787
14910
  const discoveredPaths = await discoverProjectFiles(cwd, gitRoot);
14788
14911
  const ghostConfig = profile.ghost;
14789
- const excludePaths = filterExcludedPaths(discoveredPaths, ghostConfig.include, ghostConfig.exclude);
14790
- const tempDir = await createSymlinkFarm(cwd, excludePaths);
14912
+ const tempDir = await createSymlinkFarm(cwd, discoveredPaths, {
14913
+ includePatterns: ghostConfig.include,
14914
+ excludePatterns: ghostConfig.exclude
14915
+ });
14791
14916
  const ghostFiles = await discoverProjectFiles(profileDir, profileDir);
14792
14917
  await injectGhostFiles(tempDir, profileDir, ghostFiles);
14793
14918
  if (profile.hasAgents) {
14794
14919
  const agentsPath = getProfileAgents(profileName);
14795
- const destAgentsPath = path7.join(tempDir, "AGENTS.md");
14920
+ const destAgentsPath = path8.join(tempDir, "AGENTS.md");
14796
14921
  await copyFilePromise(agentsPath, destAgentsPath);
14797
14922
  }
14798
14923
  let cleanupDone = false;
@@ -15836,7 +15961,7 @@ async function hashBundle2(files) {
15836
15961
  `));
15837
15962
  }
15838
15963
  // src/index.ts
15839
- var version = "1.2.1";
15964
+ var version = "1.2.2";
15840
15965
  async function main2() {
15841
15966
  const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
15842
15967
  registerInitCommand(program2);
@@ -15863,4 +15988,4 @@ export {
15863
15988
  buildRegistry
15864
15989
  };
15865
15990
 
15866
- //# debugId=FF19E8C89476382D64756E2164756E21
15991
+ //# debugId=DCBED805AED28C6C64756E2164756E21