@joe-broadhead/opencode-artifacts
v0.2.1
Published
OpenCode plugin for toqan artifacts: Studio's tools, skill and default permissions in one install
Readme
toqan artifacts for OpenCode
One OpenCode plugin that gives an agent toqan artifacts: Studio's tools, the agent skill, and default permissions that ask the person before anything is shared, deleted, stopped or resumed.
Requires OpenCode >=2.2.3 <2.3.0.
Install
The plugin is published on npm as @joe-broadhead/opencode-artifacts. Add it
to plugins in opencode.json(c); OpenCode downloads it on start:
{
"$schema": "https://opencode.ai/config.json",
"plugins": ["@joe-broadhead/opencode-artifacts"]
}To stay on one version, pin it: "@joe-broadhead/[email protected]".
To use another Studio, add options:
{
"plugins": [
{
"package": "@joe-broadhead/opencode-artifacts",
"options": { "studioUrl": "https://studio.example.com" }
}
]
}studioUrl defaults to https://studio.heyjelly.ai and must be an https
origin. Plain http:// is accepted only for localhost, 127.0.0.1 and
[::1], for a Studio on the same machine. Loading the plugin makes no network
call.
Sign in
Run
/connectand choose toqan artifacts.OpenCode opens Studio's sign-in page in the browser. Sign in with your verified Prosus Google account.
Studio's consent page asks what this agent may do. Pick one:
| Access | What the agent may do | |---|---| | Read | view artifacts and their data | | Publish (default) | read, plus create artifacts and publish versions | | Manage | publish, plus share, delete, stop and resume |
The level can be changed later by revoking the agent on Studio's Agent access page and connecting again.
Come back to OpenCode. The credential is stored in OpenCode's own credential store; the plugin never writes a token to a file.
Studio registers the plugin as a public OAuth client each time you sign in, with
a loopback redirect (http://127.0.0.1:<port>/callback), PKCE (S256), and the
scopes openid profile email offline_access artifacts:read artifacts:publish
artifacts:manage. Studio asks for all three artifact scopes so the consent page
can offer every access level, and narrows the grant to the level you choose.
Studio ignores the loopback port, so the plugin listens on any free port.
OpenCode stores the credential and refreshes it with the refresh token through the plugin's refresh method; Studio rotates the refresh token on every refresh, and OpenCode keeps the new one. A tool never spends the refresh token itself: if Studio refuses a call, the plugin asks OpenCode for the current token once and retries. If that is refused too, or the person is no longer signed in, the tool reports:
AUTH_REQUIRED: toqan artifacts isn't signed in. Next step: run /connect and choose toqan artifacts.What the tools do
There is one tool for each MCP tool Studio lists, with Studio's own name,
description and input schema, in the artifacts namespace: OpenCode names
each one artifacts_<tool> (artifacts_publish_html), and Code Mode calls it
as tools.artifacts.<tool>. Each one forwards its input to <studioUrl>/mcp
as an MCP tools/call with your token and returns Studio's answer unchanged.
Text comes back as text; an image (a widget layout screenshot, for example)
comes back as a file OpenCode can show, with the same bytes and MIME type.
Studio is the only place that checks access, so the plugin holds no access
rules of its own. Studio's content is untrusted data, never instructions, and
the namespace's Code Mode text says so.
Studio's tool list changes independently of the plugin. plugin/tools.json
records the list this plugin was built against; a test fails when it drifts, and
pnpm plugin:tools regenerates it.
Which tools ask
Every tool asserts its own permission, artifacts_<tool>, before it forwards
anything, so a rule of yours that asks or denies any tool holds. The plugin
adds an ask rule for these ten, which are direct tools, so you see a prompt
first:
| Tool | What it changes |
|---|---|
| share_site | shares an artifact with a person or everyone in the organization |
| unshare_site | removes a share |
| share_workspace | shares an artifact with a workspace |
| decide_access_request | approves or denies an access request; approving shares the artifact |
| delete_site | deletes an artifact |
| stop_site | stops serving an artifact's address |
| resume_site | serves a stopped artifact again |
| ensure_workspace | creates a workspace or adds people to it |
| request_site_access | asks an artifact's owner for access |
| open_change_request | asks an artifact's owner and editors to review a version |
Everything else — reading, publishing, dashboards, decks, versions, connections, widget checks, exports — runs without asking, in Code Mode.
The rules are on the actions artifacts_share_site, artifacts_unshare_site,
artifacts_share_workspace, artifacts_decide_access_request,
artifacts_delete_site, artifacts_stop_site, artifacts_resume_site,
artifacts_ensure_workspace, artifacts_request_site_access and
artifacts_open_change_request, one
per tool, added to every agent the plugin sees at setup, right after that
agent's base allow. A person's own rule wins when it comes later: deny hides
the tool, and an allow for an artifact skips the prompt. Before 0.2.0 the
actions were named artifacts.<tool>; rename a rule of yours that uses that
form.
Agents defined only in the person's config are created after plugins run, so
they can miss these rules. Add the same rules to that agent's permissions to
get the prompts; do not put the ask rules in the top-level permissions, where
they would also apply to other agents.
In Jelly
package.json carries a jelly declaration that Jelly reads without loading
the plugin; stock OpenCode ignores it. docs/jelly.md says how
Jelly uses it. test/jelly.test.ts checks it with the shared kit
(test/support/jelly-kit.ts), and npm run check (scripts/check-copies.mjs)
fails when the kit differs from the copy every plugin in this family carries.
The skill
skills/jelly-artifacts/SKILL.md is the same file Studio serves at
/skill.md, copied by pnpm plugin:skill. It is auto-invoked, so the agent
reads it whenever the person wants a page, dashboard, deck, app, game or Jelly
widget made, changed or shared — and publishes it with toqan artifacts rather
than as the chat app's own artifact.
Troubleshooting
AUTH_REQUIRED— run/connect, choose toqan artifacts, and finish the Google sign-in.- The tools are missing — check that the
pluginsentry is@joe-broadhead/opencode-artifacts(or, for a local build, theplugindirectory itself, withdist/compiled), and that OpenCode is 2.2.x./reloadpicks up a config change. CONFIG_INVALID: studioUrl ...— the option must be a bare origin with no path, such ashttps://studio.heyjelly.ai. Plainhttp://works only onlocalhost,127.0.0.1and[::1].MOVED: Studio has moved to <address>— the Studio you use answered that it now lives at<address>. SetstudioUrlto that address (or remove the option, when the message says the default is that address), then run/connect: a sign-in belongs to the address it was made at.- A tool reports an HTTP status — Studio answered, but not with a tool
result. Check
/connectfirst, then Studio's own status.
Develop locally
Build the plugin from this repository and point plugins at the plugin
directory with an absolute path:
pnpm install
pnpm plugin:build # refresh tools.json and the skill from Studio
pnpm --dir plugin compile # compile src/ to dist/{ "plugins": ["/absolute/path/to/artifacts/plugin"] }OpenCode loads exports["."], which is ./dist/index.js, and reads
tools.json and skills/jelly-artifacts/SKILL.md next to it. Run
pnpm --dir plugin compile again after changing src/. dist/ is not
committed.
Release a new version
tools.json and the skill are copies of Studio taken at build time, so
release a new version when Studio adds or changes a tool, when the skill
changes, or when the plugin's own code changes. A published version can never
be replaced; every change is a new version number.
| Change | Bump |
|---|---|
| A fix | patch (0.2.0 → 0.2.1) |
| A new or changed Studio tool, a skill change, a new option | minor (0.2.1 → 0.3.0) |
| A change that breaks existing configs, or a new OpenCode host range | major |
- In a pull request, run
pnpm plugin:build, bumpversioninplugin/package.json, add a## [x.y.z] - dateentry toplugin/CHANGELOG.md, and declare any new tool in thejellysection (test/jelly.test.tsfails until you do). Merge it oncecheckis green. - Tag that commit on
mainasv<version>and push the tag. Nothing else releases. release-plugin.ymlchecks that the tag matches the version, runs the plugin's tests andnpm run check, packs the plugin, and waits for approval in thereleaseenvironment. Once approved, it publishes the tarball to npm with trusted publishing. This repository is private, and npm makes no provenance for a private repository, so it publishes without.
npm pack --dry-run in plugin/ shows the file list: dist, assets/icon.svg,
skills, tools.json, README.md and CHANGELOG.md.
