opencode-qoder-bridge 0.1.0
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 +185 -0
- package/RELEASING.md +64 -0
- package/SECURITY.md +24 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/bin/statusline.mjs +33 -0
- package/bin/usage.mjs +35 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +75 -0
- package/dist/auth.js.map +1 -0
- package/dist/cost.d.ts +53 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +173 -0
- package/dist/cost.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/language-model.d.ts +15 -0
- package/dist/language-model.d.ts.map +1 -0
- package/dist/language-model.js +408 -0
- package/dist/language-model.js.map +1 -0
- package/dist/mcp-bridge.d.ts +8 -0
- package/dist/mcp-bridge.d.ts.map +1 -0
- package/dist/mcp-bridge.js +81 -0
- package/dist/mcp-bridge.js.map +1 -0
- package/dist/models.d.ts +27 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +112 -0
- package/dist/models.js.map +1 -0
- package/dist/prompt-builder.d.ts +30 -0
- package/dist/prompt-builder.d.ts.map +1 -0
- package/dist/prompt-builder.js +308 -0
- package/dist/prompt-builder.js.map +1 -0
- package/dist/provider.d.ts +12 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +17 -0
- package/dist/provider.js.map +1 -0
- package/dist/sdk-session.d.ts +3 -0
- package/dist/sdk-session.d.ts.map +1 -0
- package/dist/sdk-session.js +8 -0
- package/dist/sdk-session.js.map +1 -0
- package/dist/tool-normalizer.d.ts +13 -0
- package/dist/tool-normalizer.d.ts.map +1 -0
- package/dist/tool-normalizer.js +99 -0
- package/dist/tool-normalizer.js.map +1 -0
- package/dist/tui-register.d.ts +2 -0
- package/dist/tui-register.d.ts.map +1 -0
- package/dist/tui-register.js +67 -0
- package/dist/tui-register.js.map +1 -0
- package/dist/tui.d.ts +9 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +190 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.d.ts +11 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +87 -0
- package/dist/usage.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 opencode-qoder-bridge contributors
|
|
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
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# opencode-qoder-bridge
|
|
2
|
+
|
|
3
|
+
An [opencode](https://opencode.ai) plugin that bridges **Qoder AI** models into your terminal via the official [`@qoder-ai/qoder-agent-sdk`](https://www.npmjs.com/package/@qoder-ai/qoder-agent-sdk).
|
|
4
|
+
|
|
5
|
+
A ground-up rewrite focused on reliability, performance, and first-class usage/cost visibility.
|
|
6
|
+
|
|
7
|
+
> [!IMPORTANT]
|
|
8
|
+
> This is an independent community project. It is not affiliated with,
|
|
9
|
+
> endorsed by, or sponsored by Qoder or OpenCode. Use of the Qoder SDK and
|
|
10
|
+
> services is subject to the [Qoder Product Service Terms](https://qoder.com/product-service).
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- **Official SDK, no vendoring** — depends on `@qoder-ai/qoder-agent-sdk` directly. No patched SDK copies, no CLI compat-wrapper scripts.
|
|
15
|
+
- **Correct streaming** — native AI SDK v3 stream-part translation (`content_block_start/delta/stop`, `message_delta`, assistant fallback), reasoning blocks, and tool-call handoff to opencode.
|
|
16
|
+
- **Usage & cost tracking** — every completed turn is recorded to a local ledger (`~/.config/opencode-qoder-bridge/usage.json`) with per-model cost and token totals. Query it via the `qoder_usage` tool or the `opencode-qoder-bridge` statusline binary.
|
|
17
|
+
- **Live quota** — `qoder_usage` also pulls live account quota via the SDK's `getUsageInfo()` (cached 60s).
|
|
18
|
+
- **Reliable lifecycle** — proper abort propagation, idempotent cleanup, and external-abort vs. internal-error distinction so cancellations don't surface as errors.
|
|
19
|
+
- **Image input** — multimodal prompts are passed through the SDK's async-iterable path (base64, data URLs, `file://`, `~/`, and absolute paths).
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
1. Install and log in to the Qoder CLI:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
qoder login
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Credentials are stored under `~/.qoder/.auth/user`.
|
|
30
|
+
|
|
31
|
+
2. Install opencode:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g opencode-ai
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
For a published npm installation, add this to
|
|
40
|
+
`~/.config/opencode/opencode.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"plugin": ["opencode-qoder-bridge"]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For a local checkout or an unpublished package, point OpenCode at the built
|
|
49
|
+
plugin entry directly:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"plugin": [
|
|
54
|
+
"file:///absolute/path/to/opencode-qoder-bridge/dist/index.js"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
opencode installs published npm plugins automatically on startup. A bare package
|
|
60
|
+
name must exist in the npm registry; installing an unpublished package only in
|
|
61
|
+
`~/.config/opencode/node_modules` is not enough for current opencode releases.
|
|
62
|
+
Once loaded, the plugin injects the `qoder` provider and all models — no manual
|
|
63
|
+
`provider` block required.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
opencode run -m qoder/lite "say hello" # free model
|
|
69
|
+
opencode run -m qoder/auto "explain async/await"
|
|
70
|
+
opencode -m qoder/performance # interactive
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Usage & cost
|
|
74
|
+
|
|
75
|
+
Run `/qoder-usage` in OpenCode (it uses the free `qoder/lite` model), run
|
|
76
|
+
`qoder-usage` in a terminal for the same live report, or add the statusline
|
|
77
|
+
binary to your OpenCode statusline config:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
opencode-qoder-bridge
|
|
81
|
+
# qoder: cost $0.0123 · turns 1 · tok 1500 · last performance
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The package also exports a TUI entry at `opencode-qoder-bridge/tui`. It shows
|
|
85
|
+
live Qoder credits only while the current session's selected provider is
|
|
86
|
+
`qoder`; it stays hidden and does not query quota for other providers. The
|
|
87
|
+
sidebar shows OpenCode's session spend with four-decimal precision and derives
|
|
88
|
+
fractional session Credits from Qoder's cent-denominated reference cost
|
|
89
|
+
(`session.cost * 100`). The value is marked with `~` because Qoder's personal
|
|
90
|
+
SDK exposes only a rounded whole-account quota, not its per-request Credits Log.
|
|
91
|
+
The authoritative account balance still comes from SDK `userQuota`, refreshing
|
|
92
|
+
after each completed Qoder turn and every 30 seconds while active.
|
|
93
|
+
|
|
94
|
+
OpenCode 1.18.5 loads server plugins and TUI plugins independently. On its
|
|
95
|
+
first load, the bridge safely adds its bundled TUI entry to the global
|
|
96
|
+
`tui.json`, preserving existing settings. Restart OpenCode once after initial
|
|
97
|
+
installation so the TUI loader can activate it. The resulting entry is
|
|
98
|
+
equivalent to:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
103
|
+
"plugin": ["file:///path/to/opencode-qoder-bridge/dist/tui.js"]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The ledger accumulates across sessions. Delete `~/.config/opencode-qoder-bridge/usage.json` to reset it.
|
|
108
|
+
|
|
109
|
+
## Models
|
|
110
|
+
|
|
111
|
+
The bridge discovers the available catalog at startup through the SDK's
|
|
112
|
+
`getAvailableModels()` API. The following is the complete enabled catalog
|
|
113
|
+
returned by a live query on **2026-07-25**:
|
|
114
|
+
|
|
115
|
+
| Model ID | Name | Credit multiplier | Vision | Reasoning | Max input | Max output |
|
|
116
|
+
|----------|------|-------------------|--------|-----------|-----------|------------|
|
|
117
|
+
| `auto` | Auto | 1.00x | ✓ | ✗ | 200K | 32K |
|
|
118
|
+
| `ultimate` | Ultimate | 0.80x | ✓ | ✓ | 1M | 32K |
|
|
119
|
+
| `performance` | Performance | 1.10x | ✓ | ✗ | 1M | 32K |
|
|
120
|
+
| `efficient` | Efficient | 0.30x | ✓ | ✗ | 200K | 32K |
|
|
121
|
+
| `lite` | Lite | 0.00x | ✗ | ✗ | 200K | 32K |
|
|
122
|
+
| `cmodel` | Cantus | 1.60x | ✓ | ✓ | 200K | 32K |
|
|
123
|
+
| `qmodel_preview` | Qwen3.8-Max-Preview | 0.01x promo | ✓ | ✓ | 200K | 32K |
|
|
124
|
+
| `qmodel_latest` | Qwen3.7-Max | 0.10x promo | ✓ | ✗ | 1M | 32K |
|
|
125
|
+
| `qmodel` | Qwen3.7-Plus | 0.04x promo | ✓ | ✗ | 1M | 32K |
|
|
126
|
+
| `kmodel_latest` | Kimi-K3 | 0.80x | ✓ | ✗ | 200K | 32K |
|
|
127
|
+
| `kmodel` | Kimi-K2.7-Code | 0.30x | ✓ | ✗ | 256K | 32K |
|
|
128
|
+
| `gm51model` | GLM-5.2 | 0.50x promo | ✓ | ✓ | 1M | 32K |
|
|
129
|
+
| `dmodel` | DeepSeek-V4-Pro | 0.50x | ✓ | ✓ | 1M | 32K |
|
|
130
|
+
| `dfmodel` | DeepSeek-V4-Flash | 0.10x | ✓ | ✓ | 1M | 32K |
|
|
131
|
+
| `mmodel` | MiniMax-M3 | 0.20x | ✓ | ✗ | 1M | 32K |
|
|
132
|
+
|
|
133
|
+
This table is a snapshot, not a hard-coded allowlist. Qoder can vary model
|
|
134
|
+
availability by account, plan, CLI version, or staged rollout. Promotional
|
|
135
|
+
multipliers are time-dependent; the SDK's current `priceFactor` is
|
|
136
|
+
authoritative. Restart OpenCode to refresh the bridge's in-process model cache.
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
Bridge opencode MCP servers into the SDK by passing provider options:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"provider": {
|
|
145
|
+
"qoder": {
|
|
146
|
+
"options": {
|
|
147
|
+
"extraArgs": { "--experimental-mcp-load": null }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`config.mcp` servers are bridged into the SDK's `mcpServers` automatically.
|
|
155
|
+
|
|
156
|
+
## Troubleshooting
|
|
157
|
+
|
|
158
|
+
| Problem | Solution |
|
|
159
|
+
|---------|----------|
|
|
160
|
+
| Auth prompt at startup | Run `qoder login`, then restart opencode |
|
|
161
|
+
| `qodercli not found` | Install the Qoder CLI; ensure `qodercli` is on PATH or under `~/.qoder/` |
|
|
162
|
+
| Model not found | Verify the model ID matches the table above |
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm install
|
|
168
|
+
npm run build # compile to dist/
|
|
169
|
+
npm run typecheck # type-check only
|
|
170
|
+
npm test # build and run the test suite
|
|
171
|
+
npm run check # full pre-publish verification
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Security
|
|
175
|
+
|
|
176
|
+
Report suspected vulnerabilities privately as described in
|
|
177
|
+
[SECURITY.md](./SECURITY.md). Do not include Qoder credentials, npm tokens, or
|
|
178
|
+
private prompt content in reports.
|
|
179
|
+
|
|
180
|
+
Maintainer release instructions are in [RELEASING.md](./RELEASING.md).
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
The bridge source is MIT licensed; see [LICENSE](./LICENSE). Dependencies retain
|
|
185
|
+
their own licenses and terms; see [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
|
package/RELEASING.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
## Before the first release
|
|
4
|
+
|
|
5
|
+
1. Confirm that publishing this integration and using the Qoder name and SDK
|
|
6
|
+
complies with the current Qoder Product Service Terms. Keep any written
|
|
7
|
+
permission with the project records.
|
|
8
|
+
2. Create the public GitHub repository:
|
|
9
|
+
`https://github.com/naoufalelbani/opencode-qoder-bridge`.
|
|
10
|
+
3. Create an npm account, enable two-factor authentication, and run `npm login`.
|
|
11
|
+
4. Review production dependency advisories with `npm audit --omit=dev`. Do not
|
|
12
|
+
apply forced or major-version fixes without running the full integration
|
|
13
|
+
suite.
|
|
14
|
+
|
|
15
|
+
## First publish
|
|
16
|
+
|
|
17
|
+
Run the complete release gate from a clean checkout:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm ci
|
|
21
|
+
npm run check
|
|
22
|
+
npm pack --dry-run
|
|
23
|
+
npm publish
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Verify the registry package and install it through OpenCode:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm view opencode-qoder-bridge
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"plugin": ["opencode-qoder-bridge"]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Trusted publishing
|
|
39
|
+
|
|
40
|
+
After the package exists on npm:
|
|
41
|
+
|
|
42
|
+
1. Open the package settings on npmjs.com.
|
|
43
|
+
2. Add a GitHub Actions trusted publisher for:
|
|
44
|
+
- Owner: `naoufalelbani`
|
|
45
|
+
- Repository: `opencode-qoder-bridge`
|
|
46
|
+
- Workflow: `publish.yml`
|
|
47
|
+
- Allowed action: `npm publish`
|
|
48
|
+
3. Keep the repository public so npm can generate provenance.
|
|
49
|
+
4. Consider configuring the npm package to disallow token-based publishing
|
|
50
|
+
after the trusted workflow succeeds.
|
|
51
|
+
|
|
52
|
+
## Subsequent releases
|
|
53
|
+
|
|
54
|
+
Update `version` using semantic versioning, run the release gate, commit the
|
|
55
|
+
change, and push a matching tag:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm version patch
|
|
59
|
+
git push origin main --follow-tags
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The tag triggers `.github/workflows/publish.yml`. Never reuse a version already
|
|
63
|
+
published to npm.
|
|
64
|
+
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Please do not open a public issue for a suspected vulnerability.
|
|
6
|
+
|
|
7
|
+
Use GitHub's private vulnerability reporting for this repository:
|
|
8
|
+
|
|
9
|
+
https://github.com/naoufalelbani/opencode-qoder-bridge/security/advisories/new
|
|
10
|
+
|
|
11
|
+
Include the affected version, reproduction steps, impact, and any suggested
|
|
12
|
+
mitigation. You should receive an acknowledgement within seven days.
|
|
13
|
+
|
|
14
|
+
## Supported versions
|
|
15
|
+
|
|
16
|
+
Security fixes are provided for the latest published version. Users should also
|
|
17
|
+
keep OpenCode, the Qoder CLI, and package dependencies current.
|
|
18
|
+
|
|
19
|
+
## Credential handling
|
|
20
|
+
|
|
21
|
+
This plugin does not distribute Qoder credentials. Authentication remains with
|
|
22
|
+
the locally installed Qoder CLI. Never include files from `~/.qoder/`, npm
|
|
23
|
+
tokens, prompts containing secrets, or local usage ledgers in a report.
|
|
24
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This community project is not affiliated with, endorsed by, or sponsored by
|
|
4
|
+
Qoder or OpenCode.
|
|
5
|
+
|
|
6
|
+
The MIT license in this repository applies only to this project's own source
|
|
7
|
+
code. Runtime dependencies remain subject to their respective licenses and
|
|
8
|
+
terms.
|
|
9
|
+
|
|
10
|
+
In particular, `@qoder-ai/qoder-agent-sdk` is provided by Qoder under the Qoder
|
|
11
|
+
Product Service Terms:
|
|
12
|
+
|
|
13
|
+
https://qoder.com/product-service
|
|
14
|
+
|
|
15
|
+
Installing or using this plugin installs and uses that SDK. Users are
|
|
16
|
+
responsible for reviewing and complying with the Qoder terms and the terms of
|
|
17
|
+
any models or third-party services they access.
|
|
18
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const STATE_FILE = join(homedir(), ".config", "opencode-qoder-bridge", "usage.json");
|
|
7
|
+
|
|
8
|
+
function fmt(usd) {
|
|
9
|
+
return `$${(usd ?? 0).toFixed(4)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function main() {
|
|
13
|
+
let state = null;
|
|
14
|
+
try {
|
|
15
|
+
state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
|
|
16
|
+
} catch {
|
|
17
|
+
process.stdout.write("qoder: no usage yet");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parts = [
|
|
22
|
+
`cost ${fmt(state.totalCostUsd)}`,
|
|
23
|
+
`turns ${state.turnCount ?? 0}`,
|
|
24
|
+
`tok ${(state.totalInputTokens ?? 0) + (state.totalOutputTokens ?? 0)}`,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const last = Array.isArray(state.recent) ? state.recent.at(-1) : null;
|
|
28
|
+
if (last?.model) parts.push(`last ${last.model}`);
|
|
29
|
+
|
|
30
|
+
process.stdout.write(`qoder: ${parts.join(" · ")}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
main();
|
package/bin/usage.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getLiveUsage, formatUsageReport } from "../dist/usage.js";
|
|
3
|
+
import { summarize, formatCost } from "../dist/cost.js";
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const lines = [];
|
|
7
|
+
const live = await getLiveUsage(true);
|
|
8
|
+
lines.push(
|
|
9
|
+
live
|
|
10
|
+
? formatUsageReport(live)
|
|
11
|
+
: "Live Qoder usage unavailable (not logged in or Qoder CLI missing).",
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const summary = summarize();
|
|
15
|
+
lines.push("");
|
|
16
|
+
lines.push("Local Usage Ledger");
|
|
17
|
+
lines.push(` Reference cost: ${formatCost(summary.totalCostUsd)}`);
|
|
18
|
+
lines.push(` Turns: ${summary.turnCount}`);
|
|
19
|
+
lines.push(` Tokens: ${summary.totalInputTokens} in / ${summary.totalOutputTokens} out`);
|
|
20
|
+
|
|
21
|
+
const models = Object.entries(summary.byModel);
|
|
22
|
+
if (models.length > 0) {
|
|
23
|
+
lines.push(" By model:");
|
|
24
|
+
for (const [name, bucket] of models) {
|
|
25
|
+
lines.push(` ${name}: ${formatCost(bucket.costUsd)} (${bucket.turns} turns)`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process.stdout.write(lines.join("\n"));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main().catch((error) => {
|
|
33
|
+
process.stderr.write(`Unable to read Qoder usage: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
});
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function isAuthenticated(): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the qodercli binary. Search order:
|
|
4
|
+
* 1. PATH
|
|
5
|
+
* 2. ~/.qoder/local/qodercli
|
|
6
|
+
* 3. ~/.qoder/bin/qodercli/qodercli-<version> (latest)
|
|
7
|
+
* Returns null when nothing is found.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findQoderCLI(): string | null;
|
|
10
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AASA,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAuBD;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAI5C"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { accessSync, constants, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, delimiter } from "node:path";
|
|
4
|
+
const AUTH_FILES = [
|
|
5
|
+
join(homedir(), ".qoderwork", ".auth", "user"),
|
|
6
|
+
join(homedir(), ".qoder", ".auth", "user"),
|
|
7
|
+
];
|
|
8
|
+
export function isAuthenticated() {
|
|
9
|
+
return AUTH_FILES.some((p) => isRegularFile(p));
|
|
10
|
+
}
|
|
11
|
+
function isRegularFile(p) {
|
|
12
|
+
try {
|
|
13
|
+
return statSync(p).isFile();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function isExecutableFile(p) {
|
|
20
|
+
if (!isRegularFile(p))
|
|
21
|
+
return false;
|
|
22
|
+
if (process.platform === "win32")
|
|
23
|
+
return true;
|
|
24
|
+
try {
|
|
25
|
+
accessSync(p, constants.X_OK);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let cachedCliPath;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the qodercli binary. Search order:
|
|
35
|
+
* 1. PATH
|
|
36
|
+
* 2. ~/.qoder/local/qodercli
|
|
37
|
+
* 3. ~/.qoder/bin/qodercli/qodercli-<version> (latest)
|
|
38
|
+
* Returns null when nothing is found.
|
|
39
|
+
*/
|
|
40
|
+
export function findQoderCLI() {
|
|
41
|
+
if (cachedCliPath !== undefined)
|
|
42
|
+
return cachedCliPath;
|
|
43
|
+
cachedCliPath = resolveCli();
|
|
44
|
+
return cachedCliPath;
|
|
45
|
+
}
|
|
46
|
+
function resolveCli() {
|
|
47
|
+
const exe = process.platform === "win32" ? "qodercli.exe" : "qodercli";
|
|
48
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
49
|
+
if (!dir)
|
|
50
|
+
continue;
|
|
51
|
+
const p = join(dir, exe);
|
|
52
|
+
if (isExecutableFile(p))
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
const local = join(homedir(), ".qoder", "local", exe);
|
|
56
|
+
if (isExecutableFile(local))
|
|
57
|
+
return local;
|
|
58
|
+
const binDir = join(homedir(), ".qoder", "bin", "qodercli");
|
|
59
|
+
try {
|
|
60
|
+
const latest = readdirSync(binDir)
|
|
61
|
+
.filter((f) => f.startsWith("qodercli-"))
|
|
62
|
+
.sort()
|
|
63
|
+
.at(-1);
|
|
64
|
+
if (latest) {
|
|
65
|
+
const p = join(binDir, latest);
|
|
66
|
+
if (isExecutableFile(p))
|
|
67
|
+
return p;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* not installed */
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/cost.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ModelUsage } from "@qoder-ai/qoder-agent-sdk";
|
|
2
|
+
export interface TurnCost {
|
|
3
|
+
model: string;
|
|
4
|
+
inputTokens: number;
|
|
5
|
+
outputTokens: number;
|
|
6
|
+
cacheReadTokens: number;
|
|
7
|
+
cacheWriteTokens: number;
|
|
8
|
+
costUsd: number;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
turns: number;
|
|
11
|
+
at: number;
|
|
12
|
+
}
|
|
13
|
+
interface PersistedState {
|
|
14
|
+
totalCostUsd: number;
|
|
15
|
+
totalInputTokens: number;
|
|
16
|
+
totalOutputTokens: number;
|
|
17
|
+
turnCount: number;
|
|
18
|
+
byModel: Record<string, {
|
|
19
|
+
costUsd: number;
|
|
20
|
+
inputTokens: number;
|
|
21
|
+
outputTokens: number;
|
|
22
|
+
turns: number;
|
|
23
|
+
}>;
|
|
24
|
+
recent: TurnCost[];
|
|
25
|
+
}
|
|
26
|
+
export interface RecordInput {
|
|
27
|
+
model: string;
|
|
28
|
+
usage: {
|
|
29
|
+
input_tokens?: number | null;
|
|
30
|
+
output_tokens?: number | null;
|
|
31
|
+
cache_read_input_tokens?: number | null;
|
|
32
|
+
cache_creation_input_tokens?: number | null;
|
|
33
|
+
};
|
|
34
|
+
costUsd: number;
|
|
35
|
+
durationMs: number;
|
|
36
|
+
turns: number;
|
|
37
|
+
modelUsage?: Record<string, ModelUsage>;
|
|
38
|
+
}
|
|
39
|
+
/** Record a completed turn into the persistent ledger. */
|
|
40
|
+
export declare function recordTurn(input: RecordInput): TurnCost;
|
|
41
|
+
export interface UsageSummary {
|
|
42
|
+
totalCostUsd: number;
|
|
43
|
+
totalInputTokens: number;
|
|
44
|
+
totalOutputTokens: number;
|
|
45
|
+
turnCount: number;
|
|
46
|
+
byModel: PersistedState["byModel"];
|
|
47
|
+
recent: TurnCost[];
|
|
48
|
+
}
|
|
49
|
+
export declare function summarize(): UsageSummary;
|
|
50
|
+
export declare function resetLedger(): void;
|
|
51
|
+
export declare function formatCost(usd: number): string;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=cost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAO5D,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,cAAc;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AA6GD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QACL,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxC,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7C,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACzC;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,CA+BvD;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,wBAAgB,SAAS,IAAI,YAAY,CAUxC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|