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 +164 -39
- package/dist/index.js.map +6 -6
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
14630
|
-
|
|
14631
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
14790
|
-
|
|
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 =
|
|
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.
|
|
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=
|
|
15991
|
+
//# debugId=DCBED805AED28C6C64756E2164756E21
|