ocx 0.1.1 → 1.0.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.
@@ -1,139 +0,0 @@
1
- /**
2
- * Diff Command
3
- *
4
- * Compare installed components with upstream registry versions.
5
- */
6
-
7
- import { Command } from "commander"
8
- import * as Diff from "diff"
9
- import kleur from "kleur"
10
- import { readOcxLock, readOcxConfig } from "../schemas/config.js"
11
- import { fetchComponent, fetchFileContent } from "../registry/fetcher.js"
12
- import { logger, handleError, outputJson } from "../utils/index.js"
13
-
14
- interface DiffOptions {
15
- cwd: string
16
- json: boolean
17
- quiet: boolean
18
- }
19
-
20
- export function registerDiffCommand(program: Command): void {
21
- program
22
- .command("diff")
23
- .description("Compare installed components with upstream")
24
- .argument("[component]", "Component to diff (optional, diffs all if omitted)")
25
- .option("--cwd <path>", "Working directory", process.cwd())
26
- .option("--json", "Output as JSON", false)
27
- .option("-q, --quiet", "Suppress output", false)
28
- .action(async (component: string | undefined, options: DiffOptions) => {
29
- try {
30
- const lock = await readOcxLock(options.cwd)
31
- if (!lock) {
32
- if (options.json) {
33
- outputJson({
34
- success: false,
35
- error: { code: "NOT_FOUND", message: "No ocx.lock found" },
36
- })
37
- } else {
38
- logger.warn("No ocx.lock found. Run 'ocx add' first.")
39
- }
40
- return
41
- }
42
-
43
- const config = await readOcxConfig(options.cwd)
44
- if (!config) {
45
- if (options.json) {
46
- outputJson({
47
- success: false,
48
- error: { code: "NOT_FOUND", message: "No ocx.jsonc found" },
49
- })
50
- } else {
51
- logger.warn("No ocx.jsonc found. Run 'ocx init' first.")
52
- }
53
- return
54
- }
55
-
56
- const componentNames = component ? [component] : Object.keys(lock.installed)
57
-
58
- if (componentNames.length === 0) {
59
- if (options.json) {
60
- outputJson({ success: true, data: { diffs: [] } })
61
- } else {
62
- logger.info("No components installed.")
63
- }
64
- return
65
- }
66
-
67
- const results: Array<{ name: string; hasChanges: boolean; diff?: string }> = []
68
-
69
- for (const name of componentNames) {
70
- const installed = lock.installed[name]
71
- if (!installed) {
72
- if (component) {
73
- logger.warn(`Component '${name}' not found in lockfile.`)
74
- }
75
- continue
76
- }
77
-
78
- // Read local file
79
- const localPath = `${options.cwd}/${installed.target}`
80
- const localFile = Bun.file(localPath)
81
- if (!(await localFile.exists())) {
82
- results.push({ name, hasChanges: true, diff: "Local file missing" })
83
- continue
84
- }
85
- const localContent = await localFile.text()
86
-
87
- // Fetch upstream
88
- const registryConfig = config.registries[installed.registry]
89
- if (!registryConfig) {
90
- logger.warn(`Registry '${installed.registry}' not configured for component '${name}'.`)
91
- continue
92
- }
93
-
94
- try {
95
- const upstream = await fetchComponent(registryConfig.url, name)
96
-
97
- // Assume first file for simplicity in this MVP
98
- // In a full implementation we'd diff all files in the component
99
- const upstreamFile = upstream.files[0]
100
- if (!upstreamFile) {
101
- results.push({ name, hasChanges: false })
102
- continue
103
- }
104
-
105
- // Fetch actual content from registry
106
- const upstreamContent = await fetchFileContent(
107
- registryConfig.url,
108
- name,
109
- upstreamFile.path,
110
- )
111
-
112
- if (localContent === upstreamContent) {
113
- results.push({ name, hasChanges: false })
114
- } else {
115
- const patch = Diff.createPatch(name, upstreamContent, localContent)
116
- results.push({ name, hasChanges: true, diff: patch })
117
- }
118
- } catch (err) {
119
- logger.warn(`Could not fetch upstream for ${name}: ${String(err)}`)
120
- }
121
- }
122
-
123
- if (options.json) {
124
- outputJson({ success: true, data: { diffs: results } })
125
- } else {
126
- for (const res of results) {
127
- if (res.hasChanges) {
128
- console.log(kleur.yellow(`\nDiff for ${res.name}:`))
129
- console.log(res.diff || "Changes detected (no diff available)")
130
- } else if (!options.quiet) {
131
- logger.success(`${res.name}: No changes`)
132
- }
133
- }
134
- }
135
- } catch (error) {
136
- handleError(error, { json: options.json })
137
- }
138
- })
139
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * OCX CLI - init command
3
- * Initialize OCX configuration in a project
4
- */
5
-
6
- import { existsSync } from "node:fs"
7
- import { writeFile } from "node:fs/promises"
8
- import { join } from "node:path"
9
- import { Command } from "commander"
10
- import { OCX_SCHEMA_URL } from "../constants.js"
11
- import { ocxConfigSchema } from "../schemas/config.js"
12
- import { logger, createSpinner, handleError } from "../utils/index.js"
13
-
14
- interface InitOptions {
15
- yes?: boolean
16
- cwd?: string
17
- quiet?: boolean
18
- verbose?: boolean
19
- json?: boolean
20
- }
21
-
22
- export function registerInitCommand(program: Command): void {
23
- program
24
- .command("init")
25
- .description("Initialize OCX configuration in your project")
26
- .option("-y, --yes", "Skip prompts and use defaults")
27
- .option("--cwd <path>", "Working directory", process.cwd())
28
- .option("-q, --quiet", "Suppress output")
29
- .option("-v, --verbose", "Verbose output")
30
- .option("--json", "Output as JSON")
31
- .action(async (options: InitOptions) => {
32
- try {
33
- await runInit(options)
34
- } catch (error) {
35
- handleError(error, { json: options.json })
36
- }
37
- })
38
- }
39
-
40
- async function runInit(options: InitOptions): Promise<void> {
41
- const cwd = options.cwd ?? process.cwd()
42
- const configPath = join(cwd, "ocx.jsonc")
43
-
44
- // Check for existing config
45
- if (existsSync(configPath)) {
46
- if (!options.yes) {
47
- logger.warn("ocx.jsonc already exists")
48
- logger.info("Use --yes to overwrite")
49
- return
50
- }
51
- logger.info("Overwriting existing ocx.jsonc")
52
- }
53
-
54
- const spin = options.quiet ? null : createSpinner({ text: "Initializing OCX..." })
55
- spin?.start()
56
-
57
- try {
58
- // Create minimal config - schema will apply defaults
59
- const rawConfig = {
60
- $schema: OCX_SCHEMA_URL,
61
- registries: {},
62
- }
63
-
64
- // Validate with schema (applies defaults)
65
- const config = ocxConfigSchema.parse(rawConfig)
66
-
67
- // Write config file
68
- const content = JSON.stringify(config, null, 2)
69
- await writeFile(configPath, content, "utf-8")
70
-
71
- if (!options.quiet && !options.json) {
72
- logger.success("Initialized OCX configuration")
73
- }
74
-
75
- spin?.succeed("Initialized OCX configuration")
76
-
77
- if (options.json) {
78
- console.log(JSON.stringify({ success: true, path: configPath }))
79
- } else if (!options.quiet) {
80
- logger.info(`Created ${configPath}`)
81
- logger.info("")
82
- logger.info("Next steps:")
83
- logger.info(" 1. Add a registry: ocx registry add <url>")
84
- logger.info(" 2. Install components: ocx add <component>")
85
- }
86
- } catch (error) {
87
- spin?.fail("Failed to initialize")
88
- throw error
89
- }
90
- }
@@ -1,153 +0,0 @@
1
- /**
2
- * Registry Command
3
- *
4
- * Manage configured registries.
5
- */
6
-
7
- import { Command } from "commander"
8
- import kleur from "kleur"
9
- import { readOcxConfig, writeOcxConfig, type OcxConfig } from "../schemas/config.js"
10
- import { logger, handleError, outputJson } from "../utils/index.js"
11
-
12
- interface RegistryOptions {
13
- cwd: string
14
- json: boolean
15
- quiet: boolean
16
- }
17
-
18
- export function registerRegistryCommand(program: Command): void {
19
- const registry = program
20
- .command("registry")
21
- .description("Manage registries")
22
-
23
- // registry add
24
- registry
25
- .command("add")
26
- .description("Add a registry")
27
- .argument("<url>", "Registry URL")
28
- .option("--name <name>", "Registry alias (defaults to hostname)")
29
- .option("--version <version>", "Pin to specific version")
30
- .option("--cwd <path>", "Working directory", process.cwd())
31
- .option("--json", "Output as JSON", false)
32
- .option("-q, --quiet", "Suppress output", false)
33
- .action(async (url: string, options: RegistryOptions & { name?: string; version?: string }) => {
34
- try {
35
- let config = await readOcxConfig(options.cwd)
36
- if (!config) {
37
- logger.error("No ocx.jsonc found. Run 'ocx init' first.")
38
- process.exit(1)
39
- }
40
-
41
- if (config.lockRegistries) {
42
- logger.error("Registries are locked. Cannot add.")
43
- process.exit(1)
44
- }
45
-
46
- // Derive name from URL if not provided
47
- const name = options.name || new URL(url).hostname.replace(/\./g, "-")
48
-
49
- if (config.registries[name]) {
50
- logger.warn(`Registry '${name}' already exists. Use a different name.`)
51
- return
52
- }
53
-
54
- config.registries[name] = {
55
- url,
56
- version: options.version,
57
- }
58
-
59
- await writeOcxConfig(options.cwd, config)
60
-
61
- if (options.json) {
62
- outputJson({ success: true, data: { name, url } })
63
- } else {
64
- logger.success(`Added registry: ${name} -> ${url}`)
65
- }
66
- } catch (error) {
67
- handleError(error)
68
- }
69
- })
70
-
71
- // registry remove
72
- registry
73
- .command("remove")
74
- .description("Remove a registry")
75
- .argument("<name>", "Registry name")
76
- .option("--cwd <path>", "Working directory", process.cwd())
77
- .option("--json", "Output as JSON", false)
78
- .option("-q, --quiet", "Suppress output", false)
79
- .action(async (name: string, options: RegistryOptions) => {
80
- try {
81
- let config = await readOcxConfig(options.cwd)
82
- if (!config) {
83
- logger.error("No ocx.jsonc found. Run 'ocx init' first.")
84
- process.exit(1)
85
- }
86
-
87
- if (config.lockRegistries) {
88
- logger.error("Registries are locked. Cannot remove.")
89
- process.exit(1)
90
- }
91
-
92
- if (!config.registries[name]) {
93
- logger.warn(`Registry '${name}' not found.`)
94
- return
95
- }
96
-
97
- delete config.registries[name]
98
- await writeOcxConfig(options.cwd, config)
99
-
100
- if (options.json) {
101
- outputJson({ success: true, data: { removed: name } })
102
- } else {
103
- logger.success(`Removed registry: ${name}`)
104
- }
105
- } catch (error) {
106
- handleError(error)
107
- }
108
- })
109
-
110
- // registry list
111
- registry
112
- .command("list")
113
- .description("List configured registries")
114
- .option("--cwd <path>", "Working directory", process.cwd())
115
- .option("--json", "Output as JSON", false)
116
- .option("-q, --quiet", "Suppress output", false)
117
- .action(async (options: RegistryOptions) => {
118
- try {
119
- const config = await readOcxConfig(options.cwd)
120
- if (!config) {
121
- logger.warn("No ocx.jsonc found. Run 'ocx init' first.")
122
- return
123
- }
124
-
125
- const registries = Object.entries(config.registries).map(([name, cfg]) => ({
126
- name,
127
- url: cfg.url,
128
- version: cfg.version || "latest",
129
- }))
130
-
131
- if (options.json) {
132
- outputJson({
133
- success: true,
134
- data: {
135
- registries,
136
- locked: config.lockRegistries,
137
- }
138
- })
139
- } else {
140
- if (registries.length === 0) {
141
- logger.info("No registries configured.")
142
- } else {
143
- logger.info(`Configured registries${config.lockRegistries ? kleur.yellow(" (locked)") : ""}:`)
144
- for (const reg of registries) {
145
- console.log(` ${kleur.cyan(reg.name)}: ${reg.url} ${kleur.dim(`(${reg.version})`)}`)
146
- }
147
- }
148
- }
149
- } catch (error) {
150
- handleError(error)
151
- }
152
- })
153
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * Search/List Command
3
- *
4
- * Search for components across registries or list installed.
5
- */
6
-
7
- import { Command } from "commander"
8
- import kleur from "kleur"
9
- import fuzzysort from "fuzzysort"
10
- import { readOcxConfig, readOcxLock } from "../schemas/config.js"
11
- import { fetchRegistryIndex } from "../registry/fetcher.js"
12
- import { logger, handleError, outputJson, createSpinner } from "../utils/index.js"
13
-
14
- interface SearchOptions {
15
- cwd: string
16
- json: boolean
17
- quiet: boolean
18
- verbose: boolean
19
- installed: boolean
20
- limit: number
21
- }
22
-
23
- export function registerSearchCommand(program: Command): void {
24
- program
25
- .command("search")
26
- .alias("list")
27
- .description("Search for components across registries or list installed")
28
- .argument("[query]", "Search query")
29
- .option("--cwd <path>", "Working directory", process.cwd())
30
- .option("--json", "Output as JSON", false)
31
- .option("-q, --quiet", "Suppress output", false)
32
- .option("-v, --verbose", "Verbose output", false)
33
- .option("-i, --installed", "List installed components only", false)
34
- .option("-l, --limit <n>", "Limit results", "20")
35
- .action(async (query: string | undefined, options: SearchOptions) => {
36
- try {
37
- const limit = parseInt(String(options.limit), 10)
38
-
39
- // List installed only
40
- if (options.installed) {
41
- const lock = await readOcxLock(options.cwd)
42
- if (!lock) {
43
- if (options.json) {
44
- outputJson({ success: true, data: { components: [] } })
45
- } else {
46
- logger.info("No components installed.")
47
- }
48
- return
49
- }
50
-
51
- const installed = Object.entries(lock.installed).map(([name, info]) => ({
52
- name,
53
- registry: info.registry,
54
- version: info.version,
55
- installedAt: info.installedAt,
56
- }))
57
-
58
- if (options.json) {
59
- outputJson({ success: true, data: { components: installed } })
60
- } else {
61
- logger.info(`Installed components (${installed.length}):`)
62
- for (const comp of installed) {
63
- console.log(
64
- ` ${kleur.cyan(comp.name)} ${kleur.dim(`v${comp.version}`)} from ${comp.registry}`,
65
- )
66
- }
67
- }
68
- return
69
- }
70
-
71
- // Search across registries
72
- const config = await readOcxConfig(options.cwd)
73
- if (!config) {
74
- logger.warn("No ocx.jsonc found. Run 'ocx init' first.")
75
- return
76
- }
77
-
78
- if (options.verbose) {
79
- logger.info(`Searching in ${Object.keys(config.registries).length} registries...`)
80
- }
81
-
82
- const allComponents: Array<{
83
- name: string
84
- description: string
85
- type: string
86
- registry: string
87
- }> = []
88
-
89
- const spinner = createSpinner({
90
- text: "Searching registries...",
91
- quiet: options.quiet || options.verbose,
92
- })
93
-
94
- if (!options.json && !options.verbose) {
95
- spinner.start()
96
- }
97
-
98
- for (const [registryName, registryConfig] of Object.entries(config.registries)) {
99
- try {
100
- if (options.verbose) {
101
- logger.info(`Fetching index from ${registryName} (${registryConfig.url})...`)
102
- }
103
- const index = await fetchRegistryIndex(registryConfig.url)
104
- if (options.verbose) {
105
- logger.info(`Found ${index.components.length} components in ${registryName}`)
106
- }
107
- for (const comp of index.components) {
108
- allComponents.push({
109
- name: comp.name,
110
- description: comp.description,
111
- type: comp.type,
112
- registry: registryName,
113
- })
114
- }
115
- } catch (error) {
116
- if (options.verbose) {
117
- logger.warn(
118
- `Failed to fetch registry ${registryName}: ${error instanceof Error ? error.message : String(error)}`,
119
- )
120
- }
121
- // Skip failed registries
122
- }
123
- }
124
-
125
- if (!options.json && !options.verbose) {
126
- spinner.stop()
127
- }
128
-
129
- // Filter by query if provided
130
- let results = allComponents
131
- if (query) {
132
- const fuzzyResults = fuzzysort.go(query, allComponents, {
133
- keys: ["name", "description"],
134
- limit,
135
- })
136
- results = fuzzyResults.map((r) => r.obj)
137
- } else {
138
- results = results.slice(0, limit)
139
- }
140
-
141
- if (options.json) {
142
- outputJson({ success: true, data: { components: results } })
143
- } else {
144
- if (results.length === 0) {
145
- logger.info("No components found.")
146
- } else {
147
- logger.info(`Found ${results.length} components:`)
148
- for (const comp of results) {
149
- console.log(
150
- ` ${kleur.cyan(comp.name)} ${kleur.dim(`(${comp.type})`)} - ${comp.description}`,
151
- )
152
- }
153
- }
154
- }
155
- } catch (error) {
156
- handleError(error, { json: options.json })
157
- }
158
- })
159
- }
package/src/constants.ts DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * OCX URL Constants
3
- *
4
- * Centralized URL definitions to avoid hardcoding throughout the codebase.
5
- */
6
-
7
- // Base domains
8
- export const OCX_DOMAIN = "ocx.kdco.dev"
9
- export const GITHUB_REPO = "kdcokenny/ocx"
10
-
11
- // OCX URLs
12
- export const OCX_SCHEMA_URL = `https://${OCX_DOMAIN}/schema.json`
13
- export const OCX_LOCK_SCHEMA_URL = `https://${OCX_DOMAIN}/lock.schema.json`
14
- export const OCX_INSTALL_URL = `https://${OCX_DOMAIN}/install.sh`
15
-
16
- // GitHub URLs
17
- export const GITHUB_RELEASES_URL = `https://github.com/${GITHUB_REPO}/releases`
18
- export const GITHUB_RAW_URL = `https://raw.githubusercontent.com/${GITHUB_REPO}/main`
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * OCX CLI - OpenCode Extensions
4
- *
5
- * A ShadCN-style CLI for installing agents, skills, plugins, and commands
6
- * into OpenCode projects.
7
- */
8
-
9
- import { Command } from "commander"
10
- import { registerInitCommand } from "./commands/init.js"
11
- import { registerAddCommand } from "./commands/add.js"
12
- import { registerDiffCommand } from "./commands/diff.js"
13
- import { registerSearchCommand } from "./commands/search.js"
14
- import { registerRegistryCommand } from "./commands/registry.js"
15
- import { registerBuildCommand } from "./commands/build.js"
16
- import { handleError } from "./utils/index.js"
17
-
18
- // Version injected at build time
19
- declare const __VERSION__: string
20
- const version = typeof __VERSION__ !== "undefined" ? __VERSION__ : "0.0.0-dev"
21
-
22
- async function main() {
23
- const program = new Command()
24
- .name("ocx")
25
- .description("OpenCode Extensions - Install agents, skills, plugins, and commands")
26
- .version(version)
27
-
28
- // Register all commands using the registration pattern
29
- registerInitCommand(program)
30
- registerAddCommand(program)
31
- registerDiffCommand(program)
32
- registerSearchCommand(program)
33
- registerRegistryCommand(program)
34
- registerBuildCommand(program)
35
-
36
- // Parse and handle errors
37
- await program.parseAsync(process.argv)
38
- }
39
-
40
- main().catch((err) => {
41
- handleError(err)
42
- })