@modelworks/quack 0.0.0-stage → 0.2.9

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 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
- # Temporary Holding Version
1
+ # @modelworks/quack
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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;