Skip to content

Commit 4211384

Browse files
committed
feat: opencode-commandcode 1.0.0
OpenCode plugin for the Command Code provider: dynamic model discovery, multi-source credential resolution, session stability hooks and quota telemetry. Features - Dynamic model discovery from GET /provider/v1/models through the OpenCode v2 model editor. Only models advertising /chat/completions are registered, since the Anthropic-shaped claude-* line answers 400 unsupported_model on the OpenAI-compatible endpoint. Free and promo tiers are tagged [FREE PROMO]. The catalog is cached on disk (12h TTL) so an upstream outage cannot leave the provider with an empty model list at startup. - Credential resolution cascade: COMMAND_CODE_API_KEY, COMMANDCODE_KEY_FILE, $XDG_CONFIG_HOME/commandcode/key.txt, /etc/environment, then the official CLI stores under ~/.commandcode/. The key is injected as a bearer credential only when the parsed URL hostname is exactly api.commandcode.ai, or a host explicitly allow-listed via COMMANDCODE_ALLOWED_HOSTS. - Session stability hooks, all fail-open: connection hygiene (Connection: close in place, to avoid Bun reusing a socket Cloudflare already closed), a payload sanitizer for the HTTP 400 invalid request error, a 60s SSE inactivity watchdog for silently dropped streams, and retry telemetry scoped to Command Code. - Quota tool `commandcode_quota` and command `/commandcode-quota`: plan, status, remaining credit, 5h and weekly rate-limit windows with reset timers and cycle totals. Security - Credential injection requires an exact hostname match. Substring or path based matching would leak the API key to lookalike hosts, to query strings, or to any server carrying the provider path. An existing authorization header is never overwritten. - Logs record session ids, model ids, status codes and error messages only: no bodies, no headers, no key. - Every hook and transform is wrapped so a plugin error can never fail a model request or block startup. Quality - Portable: XDG and home-relative resolution for the state directory, the key file and the kill-switch sentinel. No user-specific absolute paths. - Bounded network: AbortSignal.timeout(10s) on every API call, with an on-disk catalog fallback. - 58 offline tests plus an opt-in live suite (COMMANDCODE_LIVE_TESTS=1), and a strict typecheck (allowImportingTsExtensions, strict, noUncheckedIndexedAccess). - Documentation in English and Spanish, with architecture, configuration and troubleshooting guides; MIT licensed; GitHub Actions CI and Dependabot.
0 parents  commit 4211384

38 files changed

Lines changed: 4061 additions & 0 deletions

‎.editorconfig‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
indent_style = space
9+
indent_size = 2
10+
11+
[*.md]
12+
trim_trailing_whitespace = false
13+
14+
[*.{yml,yaml}]
15+
indent_size = 2

‎.gitattributes‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Normalize line endings on checkout/commit
2+
* text=auto eol=lf
3+
4+
# Explicit language hints
5+
*.ts text eol=lf
6+
*.json text eol=lf
7+
*.md text eol=lf
8+
*.yml text eol=lf
9+
10+
# Keep lockfiles out of language statistics and diff noise
11+
bun.lock linguist-generated=true -diff
12+
13+
# Windows-only scripts keep CRLF (none today, reserved)
14+
*.bat text eol=crlf
15+
*.cmd text eol=crlf
16+
17+
# Binary
18+
*.png binary
19+
*.jpg binary
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Bug report
2+
description: Report a defect in the plugin
3+
title: "bug: "
4+
labels: ["bug"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
Before filing: check `docs/TROUBLESHOOTING.md` (English) or
10+
`docs/es/TROUBLESHOOTING.md` (Spanish). Many failures are documented there
11+
with their exact error payloads.
12+
13+
Do not paste an API key, an `authorization` header or a full request body.
14+
- type: textarea
15+
id: what-happened
16+
attributes:
17+
label: What happened
18+
description: Expected behaviour versus observed behaviour.
19+
validations:
20+
required: true
21+
- type: textarea
22+
id: reproduce
23+
attributes:
24+
label: Steps to reproduce
25+
placeholder: |
26+
1. Select model ...
27+
2. Send a prompt that ...
28+
3. Observe ...
29+
validations:
30+
required: true
31+
- type: textarea
32+
id: logs
33+
attributes:
34+
label: Relevant log lines
35+
description: From `commandcode-sanitizer.log`, `commandcode-retries.log` or `commandcode-watchdog.log`. Redact anything private.
36+
render: json
37+
- type: input
38+
id: opencode-version
39+
attributes:
40+
label: OpenCode version
41+
placeholder: v2.0.32
42+
validations:
43+
required: true
44+
- type: input
45+
id: plugin-commit
46+
attributes:
47+
label: Plugin commit
48+
placeholder: output of `git rev-parse --short HEAD`
49+
validations:
50+
required: true
51+
- type: input
52+
id: bun-version
53+
attributes:
54+
label: Bun version
55+
placeholder: 1.4.0
56+
validations:
57+
required: true
58+
- type: input
59+
id: model
60+
attributes:
61+
label: Model id
62+
placeholder: deepseek/deepseek-v4.1-flash
63+
- type: input
64+
id: os
65+
attributes:
66+
label: Operating system
67+
placeholder: Linux, macOS, WSL
68+
validations:
69+
required: true
70+
- type: checkboxes
71+
id: checks
72+
attributes:
73+
label: Checklist
74+
options:
75+
- label: I removed all secrets and personal paths from this report
76+
required: true
77+
- label: Only one plugin registers `commandcode_quota` in my config
78+
required: true

‎.github/ISSUE_TEMPLATE/config.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Security vulnerability
4+
url: https://github.com/JoshRob297/opencode-commandcode/security/advisories/new
5+
about: Report privately. Do not open a public issue for credential-handling defects.
6+
- name: Troubleshooting guide
7+
url: https://github.com/JoshRob297/opencode-commandcode/blob/main/docs/TROUBLESHOOTING.md
8+
about: Documented failure modes with their exact error payloads.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Feature request
2+
description: Propose a capability or an improvement
3+
title: "feat: "
4+
labels: ["enhancement"]
5+
body:
6+
- type: textarea
7+
id: problem
8+
attributes:
9+
label: Problem
10+
description: What is painful or impossible today, and for whom.
11+
validations:
12+
required: true
13+
- type: textarea
14+
id: proposal
15+
attributes:
16+
label: Proposal
17+
description: What you would like the plugin to do.
18+
validations:
19+
required: true
20+
- type: textarea
21+
id: alternatives
22+
attributes:
23+
label: Alternatives considered
24+
- type: dropdown
25+
id: area
26+
attributes:
27+
label: Area
28+
options:
29+
- auth / credentials
30+
- model catalog
31+
- sanitizer
32+
- watchdog
33+
- quota telemetry
34+
- docs
35+
- other
36+
validations:
37+
required: true
38+
- type: checkboxes
39+
id: guarantees
40+
attributes:
41+
label: Guarantees
42+
options:
43+
- label: This proposal keeps credential injection limited to an exact host match
44+
required: true
45+
- label: This proposal keeps every hook fail-open
46+
required: true

‎.github/dependabot.yml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: "/"
5+
schedule:
6+
interval: monthly
7+
open-pull-requests-limit: 3
8+
commit-message:
9+
prefix: "chore(deps)"
10+
groups:
11+
dev-dependencies:
12+
dependency-type: development
13+
14+
- package-ecosystem: github-actions
15+
directory: "/"
16+
schedule:
17+
interval: monthly
18+
commit-message:
19+
prefix: "chore(ci)"

‎.github/pull_request_template.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## What problem does this solve?
2+
3+
<!-- The failure mode you observed (or the gap you are closing), not just the change. -->
4+
5+
## Changes
6+
7+
<!-- Bullet list of the behavioural changes. Mention anything that alters an existing guarantee. -->
8+
9+
## Failure mode reproduced before the fix
10+
11+
<!-- Required for bug fixes: how you reproduced it, and the evidence (log line, HTTP status, payload shape). -->
12+
13+
## Verification
14+
15+
- [ ] `bun test` passes (offline suite)
16+
- [ ] `bun run typecheck` passes
17+
- [ ] Regression test added that fails before this change (bug fixes)
18+
- [ ] `CHANGELOG.md` updated under `## [Unreleased]`
19+
- [ ] Docs updated in both languages (`README.md`/`docs/*` and `README.es.md`/`docs/es/*`)
20+
- [ ] No secrets, keys, personal paths or private hosts in code, tests, docs or logs
21+
22+
## Guarantees affected
23+
24+
<!-- Tick only if the change touches one of these, and explain how it stays intact. -->
25+
26+
- [ ] Credential injection path (exact host match)
27+
- [ ] Fail-open behaviour of a hook
28+
- [ ] Payload sanitizer rules
29+
- [ ] Model catalog filtering
30+
- [ ] Nothing above

‎.github/workflows/ci.yml‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
jobs:
10+
verify:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: oven-sh/setup-bun@v2
16+
with:
17+
bun-version: latest
18+
19+
- name: Install dependencies
20+
run: bun install --frozen-lockfile
21+
22+
- name: Typecheck
23+
run: bun run typecheck
24+
25+
- name: Tests (offline suite, no network, no quota)
26+
run: bun test

‎.gitignore‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build output
5+
dist/
6+
7+
# Logs and telemetry
8+
*.log
9+
logs/
10+
11+
# Runtime kill-switch sentinel (machine-local)
12+
.disable-connection-close
13+
14+
# Environment and secrets
15+
.env
16+
.env.*
17+
!.env.example
18+
*.pem
19+
*.key
20+
!src/**/*.key.ts
21+
22+
# Editor and OS noise
23+
.DS_Store
24+
Thumbs.db
25+
.idea/
26+
.vscode/
27+
*.swp
28+
*~
29+
30+
# Local backups (never commit)
31+
*.bak
32+
*.bak-*
33+
*.orig

‎CHANGELOG.md‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here.
4+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - SemVer.
5+
6+
## [Unreleased]
7+
8+
## [1.0.0] - 2026-10-18
9+
10+
First modular release. Migrates the single-file `commandcode-quota` plugin into a
11+
structured package with dynamic model discovery.
12+
13+
### Added
14+
15+
- Dynamic model discovery from `GET /provider/v1/models` through the OpenCode v2
16+
model editor, with transport-aware filtering and automatic `[FREE PROMO]`
17+
badges for free/promo tiers.
18+
- On-disk model catalog cache (12h TTL) so an upstream outage cannot leave the
19+
provider with an empty model list at startup.
20+
- Multi-source credential resolution: `COMMAND_CODE_API_KEY`, `COMMANDCODE_KEY_FILE`,
21+
`$XDG_CONFIG_HOME/commandcode/key.txt`, `/etc/environment`, then the official CLI
22+
stores under `~/.commandcode/`.
23+
- Strict origin guard (`isCommandCodeHost`) for credential injection, plus the
24+
`COMMANDCODE_ALLOWED_HOSTS` allow-list opt-in.
25+
- Bounded API calls via `AbortSignal.timeout` (10s default).
26+
- Vision capability metadata (`image` input) for multimodal models.
27+
- `applyModelCatalog` exported for testability.
28+
- Offline test suite (58 tests) and an opt-in live suite gated by
29+
`COMMANDCODE_LIVE_TESTS`.
30+
- Strict typecheck configuration (`allowImportingTsExtensions`, `strict`,
31+
`noUncheckedIndexedAccess`) with `bun-types`.
32+
- Documentation: `README.md`, `docs/ARCHITECTURE.md`, `docs/CONFIGURATION.md`,
33+
`docs/TROUBLESHOOTING.md`, `LICENSE`.
34+
35+
### Changed
36+
37+
- State directory, key paths and the connection-close sentinel are resolved from
38+
`XDG`/home instead of hardcoded `/root` paths.
39+
- Retry telemetry now ignores non-Command-Code providers, keeping
40+
`commandcode-retries.log` meaningful.
41+
- The `retry` log records the provider id and degrades gracefully when the hook
42+
is unsupported by the running build.
43+
44+
### Fixed
45+
46+
- **Security:** credentials were attached based on substring URL matching
47+
(`api.commandcode.ai` anywhere in the URL, or any URL containing
48+
`/provider/v1/chat/completions`). That leaked the API key to lookalike hosts
49+
such as `api.commandcode.ai.evil.com`, to any host carrying the provider path,
50+
and to values injected in query strings or fragments.
51+
- Anthropic-shaped models (`claude-*`, `/messages` only) were registered under the
52+
OpenAI-compatible transport and failed with `400 unsupported_model`. They are now
53+
filtered out.
54+
- Free-model detection missed `-free` / `_free` / `.free` id shapes, so e.g.
55+
`poolside/laguna-s-2.1-free` was not tagged.
56+
- The sanitizer had regressed relative to the proven v1 implementation: `null` and
57+
empty tool response content was no longer normalized, orphan detection was no
58+
longer order-sensitive, and malformed `tool_call` entries were not pruned.
59+
- The sanitizer mutated the caller's message array in place instead of working on
60+
a shallow clone.
61+
- The watchdog could fire an abort after a consumer cancellation, and a throwing
62+
logging callback could leave the stream open.
63+
- Quota report rendering crashed on a missing window; it now degrades per field.
64+
65+
### Removed
66+
67+
- Hardcoded user-specific absolute paths removed from the runtime code in favour of
68+
XDG/home resolution.
69+
- Network calls from the default test suite.

0 commit comments

Comments
 (0)