Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
All notable changes to this project are documented here. The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.2] - 2026-11-13

### Added

- The `jelly` settings declare their choices: `access` is drawn as Allowed, Ask before changes or Read only, with
Jelly's own line for each (needs a jelly that includes app#1301).

## [0.0.1] - 2026-11-12

### Added
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Only for development. Install the package into an OpenCode project from the fold
{ "plugins": ["./opencode-boxd"] }
```

A published install is `opencode plugin add github:joe-broadhead/opencode-boxd#v0.0.1`.
A published install is `opencode plugin add github:joe-broadhead/opencode-boxd#v0.0.2`.

## Rules

Expand Down Expand Up @@ -134,5 +134,5 @@ Each release has its own tag, and a Git install does not update by itself. To mo
Or add the plugin to `opencode.json` instead, with the same spec:

```json
{ "plugins": [{ "package": "github:joe-broadhead/opencode-boxd#v0.0.1" }] }
{ "plugins": [{ "package": "github:joe-broadhead/opencode-boxd#v0.0.2" }] }
```
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ Unofficial; not affiliated with boxd.
## Install

```sh
opencode plugin add github:joe-broadhead/opencode-boxd#v0.0.1
opencode plugin add github:joe-broadhead/opencode-boxd#v0.0.2
```

Run `opencode reload`, or restart OpenCode, then run `/boxd-status`.

Each release has its own tag, and a Git install does not update by itself. To move to a newer release, remove the old entry with `opencode plugin remove github:joe-broadhead/opencode-boxd#v0.0.0`, then add the new tag with `opencode plugin add github:joe-broadhead/opencode-boxd#v<version>`.
Each release has its own tag, and a Git install does not update by itself. To move to a newer release, remove the old entry with `opencode plugin remove github:joe-broadhead/opencode-boxd#v0.0.1`, then add the new tag with `opencode plugin add github:joe-broadhead/opencode-boxd#v<version>`.

Or add the plugin to `opencode.json` instead, with the same spec:

```json
{ "plugins": [{ "package": "github:joe-broadhead/opencode-boxd#v0.0.1" }] }
{ "plugins": [{ "package": "github:joe-broadhead/opencode-boxd#v0.0.2" }] }
```

The install takes the GitHub release tag. Its commit carries the built `dist/`, so the install runs no build.
Expand Down Expand Up @@ -162,7 +162,7 @@ The snapshot is the one policy worth setting centrally: every member's machines
// /Library/Application Support/opencode/opencode.json on macOS
{
"plugins": [{
"package": "github:joe-broadhead/opencode-boxd#v0.0.1",
"package": "github:joe-broadhead/opencode-boxd#v0.0.2",
"options": { "snapshot": "opencode-golden" }
}]
}
Expand Down Expand Up @@ -241,12 +241,14 @@ agents without the default rules, the OpenCode version and one next step.
- **Guest credentials are boxd's too.** A guest reads its model key and any push token from `boxd env` at boot; the
plugin never reads or renews them. A token that expires, such as a one-hour GitHub App token, stops every guest that
uses it unless something renews it, and a guest session cannot finish an interactive sign-in by itself.
- In Jelly, the `access` setting is drawn as a choice of three, which needs a Jelly that reads declared choices: an
older Jelly refuses the plugin's whole declaration.
- Updates are not automatic for a Git install, and `opencode plugin update` and `opencode plugin check` do not work on one: to move to a newer release, remove the old entry and add the new tag.

## Uninstall

```sh
opencode plugin remove github:joe-broadhead/opencode-boxd#v0.0.1
opencode plugin remove github:joe-broadhead/opencode-boxd#v0.0.2
```

The ledger is yours to keep or delete. Machines are boxd's: `boxd machine remove oc-<work id> -y` destroys one.
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ All options go in the plugin entry in `opencode.json`:
```jsonc
{
"plugins": [{
"package": "github:joe-broadhead/opencode-boxd#v0.0.1",
"package": "github:joe-broadhead/opencode-boxd#v0.0.2",
"options": { "snapshot": "opencode-golden" }
}]
}
Expand Down
2 changes: 1 addition & 1 deletion docs/golden-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ Point the plugin at it in `opencode.json` (or rely on the default name):
```jsonc
{
"plugins": [{
"package": "github:joe-broadhead/opencode-boxd#v0.0.1",
"package": "github:joe-broadhead/opencode-boxd#v0.0.2",
"options": { "snapshot": "opencode-golden" }
}]
}
Expand Down
5 changes: 4 additions & 1 deletion docs/jelly.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ what a Jelly may call.
- **`skills`** — `boxd`.
- **`signIns`** — empty. The plugin has no sign-in of its own: it runs your `boxd` CLI, which is already signed in.
- **`icon`** — `assets/icon.svg`, this family's own mark, not boxd's logo.
- **`contributes`** — empty: no home feed, widget, work kind or setting.
- **`contributes`** — one setting, `access`, the option of the same name, as a choice of three: **Allowed** (`all`, the
default), **Ask before changes** (`ask-changes`) and **Read only** (`read-only`), with Jelly's own line for each.
Jelly draws it as a choice and saves only one of the three; a Jelly that does not know declared choices yet refuses
the whole declaration. No home feed, widget or work kind.

One rule stays for good: a tool boxd adds later is not asked about in Jelly, a write included. It is not in `jelly.tools` until it is classified in
`src/tools.ts`, and status names it as unknown until then.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 20 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@joe-broadhead/opencode-boxd",
"version": "0.0.1",
"version": "0.0.2",
"description": "Unofficial OpenCode plugin for boxd: run a session on a private boxd machine and keep the transcript on your laptop",
"keywords": [
"opencode",
Expand Down Expand Up @@ -141,8 +141,25 @@
"key": "access",
"label": "Access",
"type": "string",
"default": "all",
"description": "all, ask-changes or read-only. Generates default rules for agents with a base allow rule."
"description": "How much the plugin's tools may do. Generates default rules for agents with a base allow rule.",
"choices": [
{
"value": "all",
"label": "Allowed",
"description": "Every tool runs without asking, reads and changes alike."
},
{
"value": "ask-changes",
"label": "Ask before changes",
"description": "Reads run without asking. Each change asks you first."
},
{
"value": "read-only",
"label": "Read only",
"description": "Reads run. Changes never run."
}
],
"default": "all"
}
]
}
Expand Down
56 changes: 33 additions & 23 deletions scripts/check-copies.mjs
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
// The tools RPC module is shared by several plugins: identical outside its PLUGIN-SPECIFIC block. This fails when this
// repository's copy drifted from the pin every plugin carries, so a one-plugin edit cannot break the hand-over.
import { readFileSync } from "node:fs"
// Fails when a shared module's copy in this plugin differs from the one every plugin carries.
// node scripts/check-copies.mjs check the copies this plugin has
// node scripts/check-copies.mjs --print print each copy's hash, after a change made in every plugin at once
// Each plugin keeps its own copy, because each bundle and each test suite stands alone. The hashes below name the one
// version all plugins carry: the tools RPC outside its PLUGIN-SPECIFIC block, and Jelly's setting rules whole.
import { createHash } from "node:crypto"
import { fileURLToPath } from "node:url"
import { existsSync, readFileSync } from "node:fs"

/** The v2 shared-part hash, as hey-jelly computes it. */
const PIN = "15d82caa53aaae7ad9efb88dddb97056d3895c4dbf49b8efbb85b743871b2a6d"
const START = "// >>> PLUGIN-SPECIFIC >>>"
const BEGIN = "// >>> PLUGIN-SPECIFIC >>>"
const END = "// <<< PLUGIN-SPECIFIC <<<"

/** The module minus its plugin block: what every plugin shares, byte for byte. */
export function sharedPart(text) {
const start = text.indexOf(START)
const COPIES = [
{ path: "src/host/tools-rpc.ts", optional: false, sha256: "15d82caa53aaae7ad9efb88dddb97056d3895c4dbf49b8efbb85b743871b2a6d", part: sharedPart },
// Jelly's setting rules, restated without zod (the file's header says what it restates and how to update it).
{ path: "test/support/jelly-settings.ts", optional: false, sha256: "966aeac7eddce490da51e8e46780f1fbcfaf9b3e892bd62f2c3952c2a6111383", part: (text) => text },
]

function sharedPart(text, path) {
const begin = text.indexOf(BEGIN)
const end = text.indexOf(END)
if (start === -1 || end === -1) throw new Error("the tools RPC module has no PLUGIN-SPECIFIC block")
return `${text.slice(0, start)}${text.slice(end + END.length)}`
if (begin === -1 || end === -1 || end < begin) throw new Error(`${path}: the PLUGIN-SPECIFIC markers are missing or out of order`)
return text.slice(0, begin) + text.slice(end + END.length)
}

const files = process.argv.slice(2)
if (files.length === 0) files.push(fileURLToPath(new URL("../src/host/tools-rpc.ts", import.meta.url)))
let failed = false
for (const file of files) {
const hash = createHash("sha256").update(sharedPart(readFileSync(file, "utf8"))).digest("hex")
if (hash === PIN) {
console.log(`same: ${file}`)
} else {
console.error(`different: ${file} has shared-part hash ${hash}, the pin is ${PIN}`)
failed = true
const print = process.argv.includes("--print")
let failed = 0
for (const copy of COPIES) {
if (!existsSync(copy.path)) {
if (copy.optional) continue
console.error(`MISSING: ${copy.path}`)
failed++
continue
}
const hash = createHash("sha256").update(copy.part(readFileSync(copy.path, "utf8").replaceAll("\r\n", "\n"), copy.path)).digest("hex")
if (print) console.log(`${copy.path}: ${hash}`)
else if (hash === copy.sha256) console.log(`same: ${copy.path}`)
else {
console.error(`DRIFT: ${copy.path} differs from the copy every plugin carries. Change it in every plugin at once, then update its hash here.`)
failed++
}
}
process.exit(failed ? 1 : 0)
process.exit(failed === 0 ? 0 : 1)
2 changes: 1 addition & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface Options {
readonly allowedRoots: readonly string[]
}

const EXAMPLE = `"plugins": [{ "package": "github:joe-broadhead/opencode-boxd#v0.0.1", "options": { "snapshot": "opencode-golden" } }]`
const EXAMPLE = `"plugins": [{ "package": "github:joe-broadhead/opencode-boxd#v0.0.2", "options": { "snapshot": "opencode-golden" } }]`
const ACCEPTED = {
snapshot: "a lowercase boxd snapshot name (opencode-golden by default)",
allowUntestedHost: "true or false (the default)",
Expand Down
Loading
Loading