opencode-resolve 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -24,7 +24,15 @@ Install this where OpenCode resolves plugins from. For most users, a global inst
24
24
  npm install -g opencode-resolve
25
25
  ```
26
26
 
27
- Then add it to your OpenCode config:
27
+ The package automatically registers itself in `~/.config/opencode/opencode.json` during `postinstall` and creates `~/.config/opencode/resolve.json` when missing.
28
+
29
+ To skip automatic registration:
30
+
31
+ ```sh
32
+ OPENCODE_RESOLVE_SKIP_POSTINSTALL=1 npm install -g opencode-resolve
33
+ ```
34
+
35
+ Manual config fallback:
28
36
 
29
37
  ```json
30
38
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-resolve",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenCode plugin that adds a small coder/reviewer agent set while preserving native plan/build behavior.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/jshsakura/opencode-resolve#readme",
@@ -29,6 +29,7 @@
29
29
  "scripts": {
30
30
  "build": "tsc -p tsconfig.json",
31
31
  "install:local": "npm run build && node scripts/install-local.mjs",
32
+ "postinstall": "node scripts/postinstall.mjs",
32
33
  "prepack": "npm test",
33
34
  "test": "npm run build && node --test test/*.mjs",
34
35
  "typecheck": "tsc -p tsconfig.json --noEmit"
@@ -0,0 +1,105 @@
1
+ import { constants } from "node:fs"
2
+ import { access, copyFile, mkdir, readFile, writeFile } from "node:fs/promises"
3
+ import { homedir } from "node:os"
4
+ import { dirname, join, resolve } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ const packageName = "opencode-resolve"
8
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..")
9
+ const configDir = process.env.OPENCODE_CONFIG_HOME || join(homedir(), ".config", "opencode")
10
+ const opencodeConfigPath = join(configDir, "opencode.json")
11
+ const resolveConfigPath = join(configDir, "resolve.json")
12
+ const exampleConfigPath = join(root, "opencode-resolve.example.json")
13
+
14
+ if (process.env.OPENCODE_RESOLVE_SKIP_POSTINSTALL === "1") {
15
+ process.exit(0)
16
+ }
17
+
18
+ try {
19
+ await registerPlugin()
20
+ } catch (error) {
21
+ console.warn(`[${packageName}] automatic OpenCode registration skipped: ${formatError(error)}`)
22
+ console.warn(`[${packageName}] add "${packageName}" to your OpenCode plugin list manually if needed.`)
23
+ }
24
+
25
+ async function registerPlugin() {
26
+ await mkdir(configDir, { recursive: true })
27
+
28
+ const config = await readOpenCodeConfig()
29
+ const changed = addPlugin(config)
30
+
31
+ if (changed) {
32
+ await writeFile(opencodeConfigPath, `${JSON.stringify(config, null, 2)}\n`)
33
+ console.log(`[${packageName}] registered in ${opencodeConfigPath}`)
34
+ } else {
35
+ console.log(`[${packageName}] already registered in ${opencodeConfigPath}`)
36
+ }
37
+
38
+ if (!(await exists(resolveConfigPath))) {
39
+ await assertReadable(exampleConfigPath)
40
+ await copyFile(exampleConfigPath, resolveConfigPath)
41
+ console.log(`[${packageName}] created ${resolveConfigPath}`)
42
+ }
43
+ }
44
+
45
+ async function readOpenCodeConfig() {
46
+ if (!(await exists(opencodeConfigPath))) {
47
+ return {
48
+ $schema: "https://opencode.ai/config.json",
49
+ plugin: [],
50
+ }
51
+ }
52
+
53
+ const raw = await readFile(opencodeConfigPath, "utf8")
54
+ const parsed = JSON.parse(raw)
55
+ if (!isObject(parsed)) throw new Error(`${opencodeConfigPath} must contain a JSON object`)
56
+ return parsed
57
+ }
58
+
59
+ function addPlugin(config) {
60
+ config.plugin ??= []
61
+ if (!Array.isArray(config.plugin)) {
62
+ throw new Error(`${opencodeConfigPath}.plugin must be an array`)
63
+ }
64
+
65
+ if (config.plugin.some(isRegisteredPluginEntry)) return false
66
+ config.plugin.push(packageName)
67
+ return true
68
+ }
69
+
70
+ function isRegisteredPluginEntry(entry) {
71
+ if (typeof entry === "string") return isResolvePluginName(entry)
72
+ if (Array.isArray(entry) && typeof entry[0] === "string") return isResolvePluginName(entry[0])
73
+ return false
74
+ }
75
+
76
+ function isResolvePluginName(value) {
77
+ const name = value.split("/").pop() || value
78
+ return name === packageName || name.startsWith(`${packageName}@`)
79
+ }
80
+
81
+ async function assertReadable(path) {
82
+ await access(path, constants.R_OK)
83
+ }
84
+
85
+ async function exists(path) {
86
+ try {
87
+ await access(path)
88
+ return true
89
+ } catch (error) {
90
+ if (isMissingFileError(error)) return false
91
+ throw error
92
+ }
93
+ }
94
+
95
+ function isMissingFileError(error) {
96
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"
97
+ }
98
+
99
+ function isObject(value) {
100
+ return typeof value === "object" && value !== null && !Array.isArray(value)
101
+ }
102
+
103
+ function formatError(error) {
104
+ return error instanceof Error ? error.message : String(error)
105
+ }