@modelworks/quack 0.0.0-stage → 0.2.8
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/LICENSE +21 -0
- package/README.md +242 -2
- package/dist/agents/opencode.d.ts +40 -0
- package/dist/agents/opencode.js +409 -0
- package/dist/agents/registry.d.ts +54 -0
- package/dist/agents/registry.js +134 -0
- package/dist/agents/types.d.ts +62 -0
- package/dist/agents/types.js +11 -0
- package/dist/auth.d.ts +91 -0
- package/dist/auth.js +388 -0
- package/dist/bin.d.ts +35 -0
- package/dist/bin.js +787 -0
- package/dist/build-info.json +8 -0
- package/dist/circuit.d.ts +115 -0
- package/dist/circuit.js +393 -0
- package/dist/history.d.ts +34 -0
- package/dist/history.js +154 -0
- package/dist/install.d.ts +23 -0
- package/dist/install.js +326 -0
- package/dist/project.d.ts +50 -0
- package/dist/project.js +147 -0
- package/dist/proxy.d.ts +18 -0
- package/dist/proxy.js +203 -0
- package/dist/types.d.ts +239 -0
- package/dist/types.js +6 -0
- package/package.json +50 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ModelWorks
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,243 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @modelworks/quack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Launch AI coding agents with [ModelWorks](https://modelworks.ai) circuits as model providers — with OAuth login, a local auth-injecting proxy, run history, and git-like project config.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
quack connects [ModelWorks](https://modelworks.ai) circuits to AI coding agents like [OpenCode](https://opencode.ai). It:
|
|
8
|
+
|
|
9
|
+
- **Authenticates** via OAuth (browser-based login to your ModelWorks account)
|
|
10
|
+
- **Discovers** circuits and their SSE-compatible adaptors automatically
|
|
11
|
+
- **Proxies** all requests through a local HTTP server that injects your auth token — so it never appears in config files or `--print` output
|
|
12
|
+
- **Injects** SSE circuits as OpenAI-compatible model providers + the global ModelWorks MCP server (13 tools)
|
|
13
|
+
- **Records** every circuit run to `.quack/runs/` — status, cost, tokens, timing — safe to commit
|
|
14
|
+
- **Shares** project config via `config.json` (committed) while keeping per-user state in `state.json` (gitignored)
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
**npm:**
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm i -g @modelworks/quack
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Node 18+.
|
|
25
|
+
|
|
26
|
+
**curl (no npm required):**
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
curl -fsSL https://raw.githubusercontent.com/modelworks-ai/quack/main/install/install.sh | bash
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Downloads quack to `~/.quack/`. If Node 18+ isn't installed, the script
|
|
33
|
+
automatically downloads a bundled Node binary — works on a clean machine
|
|
34
|
+
with only `curl` and `tar`.
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. Log in (opens browser for OAuth)
|
|
40
|
+
quack auth login
|
|
41
|
+
|
|
42
|
+
# 2. Initialize a quack project
|
|
43
|
+
mkdir my-project && cd my-project
|
|
44
|
+
quack init
|
|
45
|
+
|
|
46
|
+
# 3. Add a circuit
|
|
47
|
+
quack circuit add https://modelworks.ai/circuits/c-xxx
|
|
48
|
+
|
|
49
|
+
# 4. Launch OpenCode with the circuit as a model provider
|
|
50
|
+
quack
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In OpenCode, run `/models` and select your circuit (e.g. `alice/my-circuit` under the ModelWorks provider). Type a message and it streams through the circuit via the quack proxy.
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
quack Launch the default agent (opencode)
|
|
59
|
+
quack init Create .quack/ in current directory
|
|
60
|
+
quack circuit add <url> Add a circuit and discover adaptors
|
|
61
|
+
quack circuit remove <uid> Remove a circuit from the project
|
|
62
|
+
quack circuit list [--long] List circuits, adaptors, and SSE status
|
|
63
|
+
quack circuit refresh [uid] Re-resolve versions and rediscover adaptors
|
|
64
|
+
quack circuit describe <uid> Show full circuit details
|
|
65
|
+
quack circuit use <uid> Set the default circuit for agent launch
|
|
66
|
+
|
|
67
|
+
quack history [run-uid] Show run history (table, or details for one run)
|
|
68
|
+
quack history --user <name> Filter by caller username
|
|
69
|
+
quack history --circuit <uid> Filter by circuit UID
|
|
70
|
+
|
|
71
|
+
quack opencode [args] Start proxy + launch OpenCode
|
|
72
|
+
quack opencode --print Show injected config + proxy info, don't launch
|
|
73
|
+
|
|
74
|
+
quack agent list List available agents and install status
|
|
75
|
+
quack agent describe <name> Show agent details
|
|
76
|
+
|
|
77
|
+
quack auth login OAuth flow → store credentials
|
|
78
|
+
quack auth logout Revoke + delete credentials
|
|
79
|
+
quack auth status Show current user
|
|
80
|
+
|
|
81
|
+
quack --help | -h
|
|
82
|
+
quack --version | -v
|
|
83
|
+
quack --debug Verbose proxy logging to .quack/debug.log
|
|
84
|
+
quack --verbose Proxy request logging to stderr
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## How it works
|
|
88
|
+
|
|
89
|
+
### Project structure
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
.quack/
|
|
93
|
+
├── config.json # committable — circuits, default_agent, default_circuit
|
|
94
|
+
├── state.json # gitignored — per-user adaptor UIDs, last_agent
|
|
95
|
+
├── runs/ # committable — circuit run records (metadata only)
|
|
96
|
+
└── .gitignore # ignores state.json and debug.log
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`config.json` is safe to commit — it contains circuit UIDs, names, and defaults.
|
|
100
|
+
Share it with your team (like `package.json`). When a teammate clones the repo
|
|
101
|
+
and runs `quack`, it auto-syncs: creates the per-user state and adaptors
|
|
102
|
+
automatically (like `npm install` creating `node_modules`).
|
|
103
|
+
|
|
104
|
+
`runs/` contains one JSON file per circuit run — metadata only (run UID,
|
|
105
|
+
circuit, caller, status, cost, tokens, timing). No request or response
|
|
106
|
+
bodies. Safe to commit and share.
|
|
107
|
+
|
|
108
|
+
### The proxy
|
|
109
|
+
|
|
110
|
+
When you run `quack` (or `quack opencode`), quack:
|
|
111
|
+
|
|
112
|
+
1. Starts a local HTTP proxy on a random port (`127.0.0.1` only)
|
|
113
|
+
2. Builds an OpenCode config where all ModelWorks URLs point to the proxy
|
|
114
|
+
3. Writes the config to a temp file and launches OpenCode via `OPENCODE_CONFIG`
|
|
115
|
+
|
|
116
|
+
The proxy:
|
|
117
|
+
- **Injects auth** — adds `Authorization: Bearer <token>` to every forwarded request, overriding any client-sent auth header
|
|
118
|
+
- **Streams** — passes SSE/NDJSON/JSON responses through without buffering
|
|
119
|
+
- **Refreshes on 401** — automatically refreshes the access token and retries
|
|
120
|
+
- **Saves run history** — captures the `X-Circuit-Run-Uid` response header and fetches the run record after the stream completes
|
|
121
|
+
- **Shuts down** when OpenCode exits
|
|
122
|
+
|
|
123
|
+
The injected config contains **no secrets** — only `localhost` URLs with
|
|
124
|
+
ephemeral ports. `quack opencode --print` output is safe to share.
|
|
125
|
+
|
|
126
|
+
### What gets injected
|
|
127
|
+
|
|
128
|
+
Only circuits with `OPEN_AI_SSE` in their adapters are injected as
|
|
129
|
+
OpenAI-compatible model providers. All circuits (SSE or not) are accessible
|
|
130
|
+
as tools via the global **ModelWorks x.MCP** server (`/api/x.mcp`, 13 tools:
|
|
131
|
+
circuit search, query, pricing, adaptors, blobs, etc.).
|
|
132
|
+
|
|
133
|
+
The OpenCode config (V2 format):
|
|
134
|
+
|
|
135
|
+
```jsonc
|
|
136
|
+
{
|
|
137
|
+
"$schema": "https://opencode.ai/config.json",
|
|
138
|
+
"mcp": {
|
|
139
|
+
"servers": {
|
|
140
|
+
"ModelWorks x.MCP": {
|
|
141
|
+
"type": "remote",
|
|
142
|
+
"url": "http://localhost:<port>/api/x.mcp",
|
|
143
|
+
"oauth": false,
|
|
144
|
+
"protocol": "2026-07-28",
|
|
145
|
+
"headers": { "Authorization": "Bearer quack" }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"providers": {
|
|
150
|
+
"circuit-c-xxx": {
|
|
151
|
+
"name": "ModelWorks",
|
|
152
|
+
"package": "@opencode/ai/providers/openai-compatible",
|
|
153
|
+
"settings": {
|
|
154
|
+
"baseURL": "http://localhost:<port>/api/circuit-adaptors/<adaptor-uid>/query",
|
|
155
|
+
"apiKey": "quack"
|
|
156
|
+
},
|
|
157
|
+
"models": {
|
|
158
|
+
"c-xxx": { "name": "alice/my-circuit" }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The `Bearer quack` and `apiKey: "quack"` are dummies — the proxy overrides
|
|
166
|
+
them with the real token on every request. OpenCode's deep-merge preserves
|
|
167
|
+
your existing config at the per-key level.
|
|
168
|
+
|
|
169
|
+
### Run history
|
|
170
|
+
|
|
171
|
+
After each circuit request through the proxy, quack:
|
|
172
|
+
|
|
173
|
+
1. Captures the `X-Circuit-Run-Uid` response header
|
|
174
|
+
2. Fetches the full run record from `GET /api/circuit-runs/{uid}`
|
|
175
|
+
3. Saves it to `.quack/runs/cr-xxx.json`
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"run_uid": "cr-xxx",
|
|
180
|
+
"circuit_uid": "c-xxx",
|
|
181
|
+
"circuit_name": "my-circuit",
|
|
182
|
+
"owner_username": "alice",
|
|
183
|
+
"caller_username": "alice",
|
|
184
|
+
"version_uid": "cv-xxx",
|
|
185
|
+
"status": "COMPLETED",
|
|
186
|
+
"created_at": "2026-10-21T06:08:11Z",
|
|
187
|
+
"completed_at": "2026-10-21T06:08:14Z",
|
|
188
|
+
"duration_ms": 3100,
|
|
189
|
+
"cost_credits": 0.05,
|
|
190
|
+
"input_tokens": 42,
|
|
191
|
+
"output_tokens": 128
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
No request or response bodies — just metadata. View with `quack history`.
|
|
196
|
+
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
### API host
|
|
200
|
+
|
|
201
|
+
Default: `https://modelworks.ai`. Override with:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
QUACK_API_BASE=http://localhost:8080 quack auth login
|
|
205
|
+
# or
|
|
206
|
+
quack auth login --api-base http://localhost:8080
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Credentials
|
|
210
|
+
|
|
211
|
+
Stored globally at `~/.config/quack/` (not per-project):
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
~/.config/quack/
|
|
215
|
+
├── client.json # OAuth client (DCR) — created once
|
|
216
|
+
└── credentials.json # access_token, refresh_token, expires_at — chmod 0600
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Agents
|
|
220
|
+
|
|
221
|
+
quack supports multiple agents via a plugin system. Currently built-in:
|
|
222
|
+
|
|
223
|
+
- **opencode** — [OpenCode](https://opencode.ai) (V2 with MCP 2.0 support)
|
|
224
|
+
|
|
225
|
+
Bare `quack` launches the default agent (set in `config.json` or picked
|
|
226
|
+
interactively). Adding a new agent is a single file in `src/agents/`.
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
npm install
|
|
232
|
+
npm run build # tsc → dist/
|
|
233
|
+
npm run dev # tsc --watch
|
|
234
|
+
npm test # 83 tests (node:test + node:assert, zero deps)
|
|
235
|
+
node dist/bin.js --help
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Zero runtime dependencies (Node 18+ built-ins only, except `commander` for
|
|
239
|
+
CLI parsing). TypeScript strict mode. ESM.
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode agent — injects SSE circuits as OpenAI-compatible providers
|
|
3
|
+
* via `OPENCODE_CONFIG_CONTENT`, plus the global `modelworks` MCP server.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import type { QuackConfig, QuackState, OpenCodeConfig } from "../types.js";
|
|
8
|
+
import type { Agent } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Resolve which circuit to use as the default OpenCode model.
|
|
11
|
+
*
|
|
12
|
+
* Resolution order:
|
|
13
|
+
* 1. `config.default_circuit` if it's SSE-compatible and injected
|
|
14
|
+
* 2. First injectable SSE circuit in state (fallback, not persisted)
|
|
15
|
+
* 3. `null` — no default model injected
|
|
16
|
+
*
|
|
17
|
+
* Prints a warning to stderr if `default_circuit` is stale (not in the
|
|
18
|
+
* circuits array) or not SSE-compatible.
|
|
19
|
+
*
|
|
20
|
+
* @returns the OpenCode model ID (`circuit-<uid>/<uid>`) or `null`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveDefaultModel(config: QuackConfig, state: QuackState): string | null;
|
|
23
|
+
/** Build the OpenCode config from project state. */
|
|
24
|
+
export declare function buildOpenCodeConfig(state: QuackState, port: number, defaultModel?: string | null): OpenCodeConfig;
|
|
25
|
+
/** Describe the injection decision for `--print` output. */
|
|
26
|
+
export interface InjectionSummary {
|
|
27
|
+
injected: Array<{
|
|
28
|
+
circuit_uid: string;
|
|
29
|
+
name: string;
|
|
30
|
+
sse_adaptor_uid: string;
|
|
31
|
+
}>;
|
|
32
|
+
skipped: Array<{
|
|
33
|
+
circuit_uid: string;
|
|
34
|
+
name: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export declare function summarizeInjection(state: QuackState): InjectionSummary;
|
|
39
|
+
/** The OpenCode agent plugin. */
|
|
40
|
+
export declare const opencodeAgent: Agent;
|