update config setup
This commit is contained in:
@@ -22,19 +22,19 @@ Installation defaults to `~/.config/opencode` and respects `XDG_CONFIG_HOME`. Ov
|
|||||||
OPENCODE_CONFIG_DIR=/path/to/opencode-config bash install.sh
|
OPENCODE_CONFIG_DIR=/path/to/opencode-config bash install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer atomically replaces `plugins/stanp-opencode.js` for [V2 automatic plugin discovery](https://opencode.ai/v2/docs/plugins). It does not create symlinks, install package dependencies, edit `opencode.json` or `opencode.jsonc`, install OpenCode itself, or configure provider credentials.
|
The installer atomically replaces the package directory at `plugins/stanp-opencode` and adds its default GPT configuration to `opencode.json`. Existing unrelated JSON settings and `opencode.jsonc` are preserved. It does not create symlinks, install package dependencies, install OpenCode itself, or configure provider credentials.
|
||||||
|
|
||||||
Restart `opencode2` after installation (or `opencode2 service restart` for an existing service). Connect your provider, then select `coordinate` in the agent picker. The default configuration uses OpenAI GPT models; agents are only enabled when the required models are available.
|
Restart `opencode2` after installation (or `opencode2 service restart` for an existing service). Connect your provider, then select `coordinate` in the agent picker. The default configuration uses OpenAI GPT models; agents are only enabled when the required models are available.
|
||||||
|
|
||||||
## Configure
|
## Configure
|
||||||
|
|
||||||
See [GPT configuration](examples/gpt.opencode.jsonc) and [custom model assignments](examples/custom.opencode.jsonc). When using the installer, point the example's plugin package entry at the installed package directory rather than the unpublished npm package name:
|
See [GPT configuration](examples/gpt.opencode.jsonc) and [custom model assignments](examples/custom.opencode.jsonc). When using the installer, point the example's plugin package entry at the installed package directory rather than the unpublished npm package name. Configured local plugin paths must be directories; pointing at `plugins/stanp-opencode.js` causes OpenCode to reject that configured entry and auto-load the file without its options:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"package": "./plugins/stanp-opencode.js",
|
"package": "./plugins/stanp-opencode",
|
||||||
"options": {
|
"options": {
|
||||||
"mode": "gpt",
|
"mode": "gpt",
|
||||||
"provider": "openai",
|
"provider": "openai",
|
||||||
@@ -51,4 +51,4 @@ The plugin supplies session-local `todoread` and `todowrite` tools when no other
|
|||||||
|
|
||||||
## Uninstall
|
## Uninstall
|
||||||
|
|
||||||
Remove `plugins/stanp-opencode.js` from the selected config directory and remove any explicit configuration entry for it, then restart OpenCode.
|
Remove `plugins/stanp-opencode` from the selected config directory and remove any explicit configuration entry for it, then restart OpenCode.
|
||||||
|
|||||||
+33
-5
@@ -45,11 +45,39 @@ bun -e '
|
|||||||
' "$install/package/dist/index.js"
|
' "$install/package/dist/index.js"
|
||||||
|
|
||||||
tag=$(jq -r '.tag_name' "$install/release.json")
|
tag=$(jq -r '.tag_name' "$install/release.json")
|
||||||
if ! mv -f "$install/package/dist/index.js" "$config/plugins/stanp-opencode.js"; then
|
config_file="$config/opencode.json"
|
||||||
|
config_source="$config_file"
|
||||||
|
if [[ ! -e "$config_source" ]]; then
|
||||||
|
config_source="$install/empty-config.json"
|
||||||
|
printf '{}\n' > "$config_source"
|
||||||
|
fi
|
||||||
|
jq -e '
|
||||||
|
if type != "object" then error("opencode.json must contain an object") else . end
|
||||||
|
| if (.plugins // []) | type != "array" then error("opencode.json plugins must be an array") else . end
|
||||||
|
| .["$schema"] = (.["$schema"] // "https://opencode.ai/config.json")
|
||||||
|
| .plugins = (.plugins // [])
|
||||||
|
| if any(.plugins[]?; .package == "./plugins/stanp-opencode") then . else
|
||||||
|
.plugins += [{
|
||||||
|
package: "./plugins/stanp-opencode",
|
||||||
|
options: { mode: "gpt", provider: "openai", lunaPriority: true }
|
||||||
|
}]
|
||||||
|
end
|
||||||
|
' "$config_source" > "$install/opencode.json"
|
||||||
|
target="$config/plugins/stanp-opencode"
|
||||||
|
staged="$config/plugins/.stanp-opencode.new.$$"
|
||||||
|
backup="$config/plugins/.stanp-opencode.old.$$"
|
||||||
|
mkdir "$staged"
|
||||||
|
mv "$install/package/dist/index.js" "$staged/index.js"
|
||||||
|
cat > "$staged/package.json" <<'EOF'
|
||||||
|
{"name":"stanp-opencode","type":"module","main":"./index.js","exports":"./index.js"}
|
||||||
|
EOF
|
||||||
|
if [[ -e "$target" || -L "$target" ]]; then mv "$target" "$backup"; fi
|
||||||
|
if ! mv "$staged" "$target"; then
|
||||||
|
if [[ -e "$backup" || -L "$backup" ]]; then mv "$backup" "$target"; fi
|
||||||
printf 'Could not activate plugin; installation unchanged\n' >&2
|
printf 'Could not activate plugin; installation unchanged\n' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ -L "$config/plugins/stanp-opencode" ]]; then
|
rm -rf "$backup"
|
||||||
rm -f "$config/plugins/stanp-opencode"
|
rm -f "$config/plugins/stanp-opencode.js"
|
||||||
fi
|
mv "$install/opencode.json" "$config_file"
|
||||||
printf 'Installed stanp-opencode %s into %s/plugins/stanp-opencode.js\nRestart opencode2 to load it.\n' "$tag" "$config"
|
printf 'Installed stanp-opencode %s into %s/plugins/stanp-opencode and updated %s\nRestart opencode2 to load it.\n' "$tag" "$config" "$config_file"
|
||||||
|
|||||||
+60
-4
@@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url";
|
|||||||
const installer = fileURLToPath(new URL("../install.sh", import.meta.url));
|
const installer = fileURLToPath(new URL("../install.sh", import.meta.url));
|
||||||
const directories: string[] = [];
|
const directories: string[] = [];
|
||||||
const configContents = '{\n // Keep user settings and comments.\n "theme": "dark"\n}\n';
|
const configContents = '{\n // Keep user settings and comments.\n "theme": "dark"\n}\n';
|
||||||
|
const jsonConfig = { permissions: { shell: "ask" }, plugins: [{ package: "existing-plugin" }] };
|
||||||
const previousPlugin = 'export default { id: "previous", setup() { return "preserved"; } };\n';
|
const previousPlugin = 'export default { id: "previous", setup() { return "preserved"; } };\n';
|
||||||
const previousTarget = "../stanp-opencode/install.previous";
|
const previousTarget = "../stanp-opencode/install.previous";
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ async function run(command: string[], cwd: string, env: NodeJS.ProcessEnv) {
|
|||||||
return { exitCode, stdout, stderr };
|
return { exitCode, stdout, stderr };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function install(mode: "native" | "checksum" | "legacy") {
|
async function install(mode: "native" | "checksum" | "legacy", configured = false) {
|
||||||
const directory = await mkdtemp(join(tmpdir(), "stanp-install-"));
|
const directory = await mkdtemp(join(tmpdir(), "stanp-install-"));
|
||||||
directories.push(directory);
|
directories.push(directory);
|
||||||
const config =
|
const config =
|
||||||
@@ -60,6 +61,27 @@ async function install(mode: "native" | "checksum" | "legacy") {
|
|||||||
]);
|
]);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
writeFile(join(config, "opencode.jsonc"), configContents),
|
writeFile(join(config, "opencode.jsonc"), configContents),
|
||||||
|
writeFile(
|
||||||
|
join(config, "opencode.json"),
|
||||||
|
JSON.stringify(
|
||||||
|
configured
|
||||||
|
? {
|
||||||
|
...jsonConfig,
|
||||||
|
plugins: [
|
||||||
|
...jsonConfig.plugins,
|
||||||
|
{
|
||||||
|
package: "./plugins/stanp-opencode",
|
||||||
|
options: {
|
||||||
|
mode: "gpt",
|
||||||
|
provider: "existing-provider",
|
||||||
|
lunaPriority: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: jsonConfig,
|
||||||
|
),
|
||||||
|
),
|
||||||
symlink(previousTarget, join(config, "plugins", "stanp-opencode")),
|
symlink(previousTarget, join(config, "plugins", "stanp-opencode")),
|
||||||
writeFile(join(config, "stanp-opencode", "install.previous", "index.js"), previousPlugin),
|
writeFile(join(config, "stanp-opencode", "install.previous", "index.js"), previousPlugin),
|
||||||
writeFile(
|
writeFile(
|
||||||
@@ -165,7 +187,7 @@ exec sha256sum "$@"
|
|||||||
return { directory, config, env, result };
|
return { directory, config, env, result };
|
||||||
}
|
}
|
||||||
|
|
||||||
test("installs an importable native plugin under XDG without changing user configuration or running scripts", async () => {
|
test("installs an importable native plugin and default config under XDG without running scripts", async () => {
|
||||||
const { directory, config, env, result } = await install("native");
|
const { directory, config, env, result } = await install("native");
|
||||||
expect(result, result.stderr).toMatchObject({ exitCode: 0 });
|
expect(result, result.stderr).toMatchObject({ exitCode: 0 });
|
||||||
// The generated package path is only known at runtime; exercise its module-loading boundary.
|
// The generated package path is only known at runtime; exercise its module-loading boundary.
|
||||||
@@ -174,7 +196,7 @@ test("installs an importable native plugin under XDG without changing user confi
|
|||||||
process.execPath,
|
process.execPath,
|
||||||
"-e",
|
"-e",
|
||||||
"const { default: plugin } = await import(process.argv[1]); console.log(JSON.stringify([plugin.id, plugin.setup()]));",
|
"const { default: plugin } = await import(process.argv[1]); console.log(JSON.stringify([plugin.id, plugin.setup()]));",
|
||||||
join(config, "plugins", "stanp-opencode.js"),
|
join(config, "plugins", "stanp-opencode"),
|
||||||
],
|
],
|
||||||
directory,
|
directory,
|
||||||
env,
|
env,
|
||||||
@@ -184,19 +206,53 @@ test("installs an importable native plugin under XDG without changing user confi
|
|||||||
stdout: '["stanp-opencode","native-ready"]\n',
|
stdout: '["stanp-opencode","native-ready"]\n',
|
||||||
});
|
});
|
||||||
expect(await readFile(join(config, "opencode.jsonc"), "utf8")).toBe(configContents);
|
expect(await readFile(join(config, "opencode.jsonc"), "utf8")).toBe(configContents);
|
||||||
expect(await Bun.file(join(config, "plugins", "stanp-opencode")).exists()).toBe(false);
|
expect(JSON.parse(await readFile(join(config, "opencode.json"), "utf8"))).toEqual({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
plugins: [
|
||||||
|
{ package: "existing-plugin" },
|
||||||
|
{
|
||||||
|
package: "./plugins/stanp-opencode",
|
||||||
|
options: { mode: "gpt", provider: "openai", lunaPriority: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
permissions: { shell: "ask" },
|
||||||
|
});
|
||||||
|
expect(await Bun.file(join(config, "plugins", "stanp-opencode", "package.json")).exists()).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
await readFile(join(config, "stanp-opencode", "install.previous", "index.js"), "utf8"),
|
await readFile(join(config, "stanp-opencode", "install.previous", "index.js"), "utf8"),
|
||||||
).toBe(previousPlugin);
|
).toBe(previousPlugin);
|
||||||
expect(await Bun.file(env.POSTINSTALL_MARKER).exists()).toBe(false);
|
expect(await Bun.file(env.POSTINSTALL_MARKER).exists()).toBe(false);
|
||||||
}, 60_000);
|
}, 60_000);
|
||||||
|
|
||||||
|
test("preserves an existing stanp-opencode configuration", async () => {
|
||||||
|
const { config, result } = await install("native", true);
|
||||||
|
expect(result, result.stderr).toMatchObject({ exitCode: 0 });
|
||||||
|
const installed = JSON.parse(await readFile(join(config, "opencode.json"), "utf8"));
|
||||||
|
expect(installed.plugins).toEqual([
|
||||||
|
{ package: "existing-plugin" },
|
||||||
|
{
|
||||||
|
package: "./plugins/stanp-opencode",
|
||||||
|
options: {
|
||||||
|
mode: "gpt",
|
||||||
|
provider: "existing-provider",
|
||||||
|
lunaPriority: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(installed.permissions).toEqual(jsonConfig.permissions);
|
||||||
|
}, 60_000);
|
||||||
|
|
||||||
for (const mode of ["checksum", "legacy"] as const) {
|
for (const mode of ["checksum", "legacy"] as const) {
|
||||||
test(`${mode} failure preserves the installed plugin in a custom path containing spaces`, async () => {
|
test(`${mode} failure preserves the installed plugin in a custom path containing spaces`, async () => {
|
||||||
const { directory, config, env, result } = await install(mode);
|
const { directory, config, env, result } = await install(mode);
|
||||||
expect(result.exitCode, result.stderr).not.toBe(0);
|
expect(result.exitCode, result.stderr).not.toBe(0);
|
||||||
expect(await readlink(join(config, "plugins", "stanp-opencode"))).toBe(previousTarget);
|
expect(await readlink(join(config, "plugins", "stanp-opencode"))).toBe(previousTarget);
|
||||||
expect(await readFile(join(config, "opencode.jsonc"), "utf8")).toBe(configContents);
|
expect(await readFile(join(config, "opencode.jsonc"), "utf8")).toBe(configContents);
|
||||||
|
expect(JSON.parse(await readFile(join(config, "opencode.json"), "utf8"))).toEqual(
|
||||||
|
jsonConfig,
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
await readFile(join(config, "stanp-opencode", "install.previous", "index.js"), "utf8"),
|
await readFile(join(config, "stanp-opencode", "install.previous", "index.js"), "utf8"),
|
||||||
).toBe(previousPlugin);
|
).toBe(previousPlugin);
|
||||||
|
|||||||
Reference in New Issue
Block a user