@route_33/opencode 0.1.2 → 0.2.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/README.md +18 -4
- package/dist/index.d.ts +7 -0
- package/dist/index.js +86 -268
- package/dist/tui/index.js +31 -31
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -4,17 +4,31 @@ An opencode provider plugin for the [route33.ai](https://route33.ai) AI gateway:
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
opencode plugin @route_33/opencode
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The installer patches both configs: the server plugin into `opencode.json` and the key-setup wizard (TUI plugin) into `tui.json`. To install manually instead, declare both entries yourself:
|
|
8
12
|
|
|
9
|
-
```
|
|
13
|
+
```jsonc
|
|
14
|
+
// opencode.json — server plugin (model discovery, cost/limit enrichment, auth)
|
|
10
15
|
{
|
|
11
|
-
"plugin": ["@route_33/opencode@^0.
|
|
16
|
+
"plugin": ["@route_33/opencode@^0.2"]
|
|
12
17
|
}
|
|
13
18
|
```
|
|
14
19
|
|
|
20
|
+
```jsonc
|
|
21
|
+
// tui.json — TUI plugin (the key-setup wizard)
|
|
22
|
+
{
|
|
23
|
+
"plugin": ["@route_33/opencode@^0.2"]
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This plugin never writes your config: opencode has no TUI-plugin auto-discovery, so without a `tui.json` entry the wizard simply never appears — the server plugin works regardless.
|
|
28
|
+
|
|
15
29
|
## Auth
|
|
16
30
|
|
|
17
|
-
Run `opencode auth login` and choose `route_33`, or set the `ROUTE33_API_KEY` environment variable. Without a key, the TUI wizard offers to save one: per-project inside a git workspace, global otherwise. `ROUTE33_BASE_URL` overrides the default gateway URL; `ROUTE33_TUI_PROMPT=0` disables the wizard.
|
|
31
|
+
Run `opencode auth login` and choose `route_33`, or set the `ROUTE33_API_KEY` environment variable. Without a key, the TUI wizard offers to save one: per-project inside a git workspace, global otherwise. A globally stored key applies only outside git workspaces — inside a workspace, keys resolve from the environment, the workspace config, or `.opencode/route33.key` (per-project keys are the metering boundary). `ROUTE33_BASE_URL` overrides the default gateway URL; `ROUTE33_TUI_PROMPT=0` disables the wizard.
|
|
18
32
|
|
|
19
33
|
## License
|
|
20
34
|
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ import { Plugin } from '@opencode-ai/plugin';
|
|
|
10
10
|
* validation — plus the `route_33` auth method and options loader.
|
|
11
11
|
* Discovery runs once at startup; restart opencode after a key or config
|
|
12
12
|
* change.
|
|
13
|
+
*
|
|
14
|
+
* The companion TUI plugin (`exports["./tui"]`, the key-setup wizard) is
|
|
15
|
+
* installed by the USER declaring it in `tui.json` — opencode has no TUI
|
|
16
|
+
* plugin auto-discovery, and this plugin never writes user config
|
|
17
|
+
* (upstream calls third-party tui.json mutation undesirable). The
|
|
18
|
+
* sanctioned install is `opencode plugin @route_33/opencode`, which
|
|
19
|
+
* patches both configs.
|
|
13
20
|
*/
|
|
14
21
|
declare const Route33Plugin: Plugin;
|
|
15
22
|
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ function readAuthEntry(env = process.env) {
|
|
|
32
32
|
|
|
33
33
|
// src/auth/workspace-key-file.ts
|
|
34
34
|
import * as fs2 from "fs";
|
|
35
|
-
import * as
|
|
35
|
+
import * as path4 from "path";
|
|
36
36
|
|
|
37
37
|
// src/shared/default-base-url.ts
|
|
38
38
|
var DEFAULT_BASE_URL = "https://api.route33.ai/v1";
|
|
@@ -64,6 +64,23 @@ var ENV_ENRICH_CAPS = "ROUTE33_ENRICH_CAPS";
|
|
|
64
64
|
// src/shared/env-strict.ts
|
|
65
65
|
var ENV_STRICT = "ROUTE33_STRICT";
|
|
66
66
|
|
|
67
|
+
// src/shared/git-workspace.ts
|
|
68
|
+
import { existsSync } from "fs";
|
|
69
|
+
import * as path2 from "path";
|
|
70
|
+
function isInsideGitWorktree(startDir) {
|
|
71
|
+
try {
|
|
72
|
+
let dir = path2.resolve(startDir);
|
|
73
|
+
for (; ; ) {
|
|
74
|
+
if (existsSync(path2.join(dir, ".git"))) return true;
|
|
75
|
+
const parent = path2.dirname(dir);
|
|
76
|
+
if (parent === dir) return false;
|
|
77
|
+
dir = parent;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
67
84
|
// src/shared/logger.ts
|
|
68
85
|
var logger = {
|
|
69
86
|
info: (...args) => {
|
|
@@ -82,18 +99,18 @@ var MODELS_DEV_TTL_MS = 864e5;
|
|
|
82
99
|
|
|
83
100
|
// src/shared/resolve-cache-dir.ts
|
|
84
101
|
import * as os2 from "os";
|
|
85
|
-
import * as
|
|
102
|
+
import * as path3 from "path";
|
|
86
103
|
function resolveCacheDir(env = process.env) {
|
|
87
104
|
const override = env[ENV_CACHE_DIR];
|
|
88
105
|
if (override) return override;
|
|
89
106
|
const xdgCacheHome = env.XDG_CACHE_HOME;
|
|
90
|
-
const base = xdgCacheHome ??
|
|
91
|
-
return
|
|
107
|
+
const base = xdgCacheHome ?? path3.join(os2.homedir(), ".cache");
|
|
108
|
+
return path3.join(base, "opencode-route33");
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
// src/auth/workspace-key-file.ts
|
|
95
|
-
var WORKSPACE_KEY_RELATIVE_PATH =
|
|
96
|
-
var WORKSPACE_GITIGNORE_RELATIVE_PATH =
|
|
112
|
+
var WORKSPACE_KEY_RELATIVE_PATH = path4.join(".opencode", "route33.key");
|
|
113
|
+
var WORKSPACE_GITIGNORE_RELATIVE_PATH = path4.join(".opencode", ".gitignore");
|
|
97
114
|
var GITIGNORE_ENTRIES = ["route33.key", "tui.json"];
|
|
98
115
|
function usable2(value) {
|
|
99
116
|
if (typeof value !== "string") return void 0;
|
|
@@ -107,7 +124,7 @@ function readWorkspaceKey(worktree) {
|
|
|
107
124
|
if (worktree === void 0) return void 0;
|
|
108
125
|
try {
|
|
109
126
|
const raw = fs2.readFileSync(
|
|
110
|
-
|
|
127
|
+
path4.join(worktree, WORKSPACE_KEY_RELATIVE_PATH),
|
|
111
128
|
"utf8"
|
|
112
129
|
);
|
|
113
130
|
const parsed = JSON.parse(raw);
|
|
@@ -119,10 +136,10 @@ function readWorkspaceKey(worktree) {
|
|
|
119
136
|
}
|
|
120
137
|
function ensureWorkspaceKeyGitignore(worktree) {
|
|
121
138
|
if (worktree === void 0 || worktree.trim() === "") return;
|
|
122
|
-
if (
|
|
123
|
-
const gitignorePath =
|
|
139
|
+
if (path4.resolve(worktree) === "/") return;
|
|
140
|
+
const gitignorePath = path4.join(worktree, WORKSPACE_GITIGNORE_RELATIVE_PATH);
|
|
124
141
|
try {
|
|
125
|
-
fs2.mkdirSync(
|
|
142
|
+
fs2.mkdirSync(path4.dirname(gitignorePath), { recursive: true });
|
|
126
143
|
let content = "";
|
|
127
144
|
try {
|
|
128
145
|
content = fs2.readFileSync(gitignorePath, "utf8");
|
|
@@ -157,8 +174,8 @@ function urlFromConfig(cfg) {
|
|
|
157
174
|
return literal;
|
|
158
175
|
return void 0;
|
|
159
176
|
}
|
|
160
|
-
function resolveBaseUrl(cfg, env = process.env) {
|
|
161
|
-
return usable3(env[ENV_BASE_URL]) ?? urlFromConfig(cfg) ?? readAuthEntry(env).baseURL ?? DEFAULT_BASE_URL;
|
|
177
|
+
function resolveBaseUrl(cfg, env = process.env, worktree) {
|
|
178
|
+
return usable3(env[ENV_BASE_URL]) ?? urlFromConfig(cfg) ?? (worktree !== void 0 && isInsideGitWorktree(worktree) ? void 0 : readAuthEntry(env).baseURL) ?? DEFAULT_BASE_URL;
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
// src/auth/api-key-method.ts
|
|
@@ -208,7 +225,7 @@ function keyFromConfig(cfg) {
|
|
|
208
225
|
return void 0;
|
|
209
226
|
}
|
|
210
227
|
function resolveKey(cfg, env = process.env, worktree) {
|
|
211
|
-
return usable4(env[ENV_API_KEY]) ?? keyFromConfig(cfg) ?? readWorkspaceKey(worktree) ?? readAuthEntry(env).key;
|
|
228
|
+
return usable4(env[ENV_API_KEY]) ?? keyFromConfig(cfg) ?? readWorkspaceKey(worktree) ?? (worktree !== void 0 && isInsideGitWorktree(worktree) ? void 0 : readAuthEntry(env).key);
|
|
212
229
|
}
|
|
213
230
|
|
|
214
231
|
// src/auth/route33-auth.ts
|
|
@@ -228,200 +245,6 @@ function buildRoute33Auth(env = process.env, worktree) {
|
|
|
228
245
|
};
|
|
229
246
|
}
|
|
230
247
|
|
|
231
|
-
// src/bootstrap/ensure-tui-registration.ts
|
|
232
|
-
import * as path7 from "path";
|
|
233
|
-
|
|
234
|
-
// src/bootstrap/global-context.ts
|
|
235
|
-
import * as os3 from "os";
|
|
236
|
-
import * as path4 from "path";
|
|
237
|
-
import { fileURLToPath } from "url";
|
|
238
|
-
function isGlobalContext(worktree, moduleUrl) {
|
|
239
|
-
try {
|
|
240
|
-
if (moduleUrl === void 0) return false;
|
|
241
|
-
if (worktree === void 0) return true;
|
|
242
|
-
const root = path4.resolve(worktree);
|
|
243
|
-
if (root === "/") return true;
|
|
244
|
-
const modulePath = fileURLToPath(moduleUrl);
|
|
245
|
-
return !modulePath.startsWith(root + path4.sep);
|
|
246
|
-
} catch {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
function usable5(value) {
|
|
251
|
-
const trimmed = value?.trim();
|
|
252
|
-
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
253
|
-
}
|
|
254
|
-
function globalTuiJsonPath(env) {
|
|
255
|
-
const configHome = usable5(env.XDG_CONFIG_HOME) ?? path4.join(os3.homedir(), ".config");
|
|
256
|
-
return path4.join(configHome, "opencode", "tui.json");
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// src/bootstrap/tui-json-upsert.ts
|
|
260
|
-
import * as fs4 from "fs";
|
|
261
|
-
import * as path6 from "path";
|
|
262
|
-
import {
|
|
263
|
-
applyEdits,
|
|
264
|
-
modify,
|
|
265
|
-
parse as parseJsonc
|
|
266
|
-
} from "jsonc-parser";
|
|
267
|
-
|
|
268
|
-
// src/bootstrap/tui-spec-resolution.ts
|
|
269
|
-
import * as fs3 from "fs";
|
|
270
|
-
import * as os4 from "os";
|
|
271
|
-
import * as path5 from "path";
|
|
272
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
273
|
-
var NPM_TUI_SPEC = "@route_33/opencode/tui";
|
|
274
|
-
var OLD_NPM_TUI_SPEC = "opencode-route33/tui";
|
|
275
|
-
var TUI_BUNDLE_NAME = "route33-tui.js";
|
|
276
|
-
var TUI_SCHEMA = "https://opencode.ai/tui.json";
|
|
277
|
-
var SERVER_BUNDLE_NAME = "route33.js";
|
|
278
|
-
var DIST_TUI_ENTRY_SUFFIX = "/opencode-route33/dist/tui/index.js";
|
|
279
|
-
var SCOPED_DIST_TUI_ENTRY_SUFFIX = "/@route_33/opencode/dist/tui/index.js";
|
|
280
|
-
function fileExists(candidate) {
|
|
281
|
-
try {
|
|
282
|
-
return fs3.statSync(candidate).isFile();
|
|
283
|
-
} catch {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function specFromModuleUrl(moduleUrl) {
|
|
288
|
-
try {
|
|
289
|
-
if (moduleUrl === void 0) return void 0;
|
|
290
|
-
const selfPath = fileURLToPath2(moduleUrl);
|
|
291
|
-
const dir = path5.dirname(selfPath);
|
|
292
|
-
if (path5.basename(selfPath) === SERVER_BUNDLE_NAME) {
|
|
293
|
-
const sibling = path5.join(dir, TUI_BUNDLE_NAME);
|
|
294
|
-
return fileExists(sibling) ? sibling : void 0;
|
|
295
|
-
}
|
|
296
|
-
if (path5.basename(selfPath) === "index.js" && path5.basename(dir) === "dist") {
|
|
297
|
-
const tuiEntry = path5.join(dir, "tui", "index.js");
|
|
298
|
-
return fileExists(tuiEntry) ? tuiEntry : void 0;
|
|
299
|
-
}
|
|
300
|
-
return void 0;
|
|
301
|
-
} catch {
|
|
302
|
-
return void 0;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function resolveTuiSpec(worktree, env, moduleUrl) {
|
|
306
|
-
const fromModule = specFromModuleUrl(moduleUrl);
|
|
307
|
-
if (fromModule !== void 0) return fromModule;
|
|
308
|
-
if (worktree !== void 0 && worktree.trim() !== "" && path5.resolve(worktree) !== "/") {
|
|
309
|
-
const local = path5.join(worktree, ".opencode", "plugins", TUI_BUNDLE_NAME);
|
|
310
|
-
if (fileExists(local)) return local;
|
|
311
|
-
}
|
|
312
|
-
const configHome = env.XDG_CONFIG_HOME?.trim() || path5.join(os4.homedir(), ".config");
|
|
313
|
-
const global = path5.join(configHome, "opencode", "plugins", TUI_BUNDLE_NAME);
|
|
314
|
-
if (fileExists(global)) return global;
|
|
315
|
-
return NPM_TUI_SPEC;
|
|
316
|
-
}
|
|
317
|
-
function isOurEntry(entry) {
|
|
318
|
-
return typeof entry === "string" && (entry === NPM_TUI_SPEC || entry === OLD_NPM_TUI_SPEC || entry.endsWith(`/${TUI_BUNDLE_NAME}`) || entry.endsWith(DIST_TUI_ENTRY_SUFFIX) || entry.endsWith(SCOPED_DIST_TUI_ENTRY_SUFFIX));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// src/bootstrap/tui-json-upsert.ts
|
|
322
|
-
function isRecord3(value) {
|
|
323
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
324
|
-
}
|
|
325
|
-
function fileExists2(candidate) {
|
|
326
|
-
try {
|
|
327
|
-
return fs4.statSync(candidate).isFile();
|
|
328
|
-
} catch {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
function upsertTuiSpec(file, spec) {
|
|
333
|
-
try {
|
|
334
|
-
if (!fileExists2(file)) {
|
|
335
|
-
fs4.mkdirSync(path6.dirname(file), { recursive: true });
|
|
336
|
-
fs4.writeFileSync(
|
|
337
|
-
file,
|
|
338
|
-
JSON.stringify({ $schema: TUI_SCHEMA, plugin: [spec] }, null, 2) + "\n",
|
|
339
|
-
"utf8"
|
|
340
|
-
);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
const text = fs4.readFileSync(file, "utf8");
|
|
344
|
-
const errors = [];
|
|
345
|
-
const parsed = parseJsonc(text, errors, {
|
|
346
|
-
allowTrailingComma: true,
|
|
347
|
-
allowEmptyContent: true
|
|
348
|
-
});
|
|
349
|
-
if (!isRecord3(parsed)) {
|
|
350
|
-
logger.warn(
|
|
351
|
-
`tui.json at ${file} did not parse to an object \u2014 skipping TUI registration`
|
|
352
|
-
);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (parsed.plugin !== void 0 && !Array.isArray(parsed.plugin)) {
|
|
356
|
-
logger.warn(
|
|
357
|
-
`tui.json at ${file} has a non-array "plugin" key \u2014 skipping TUI registration`
|
|
358
|
-
);
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
const entries = Array.isArray(parsed.plugin) ? parsed.plugin : [];
|
|
362
|
-
const ourIndices = entries.flatMap(
|
|
363
|
-
(entry, index) => isOurEntry(entry) ? [index] : []
|
|
364
|
-
);
|
|
365
|
-
const firstOurs = ourIndices[0];
|
|
366
|
-
if (ourIndices.length === 1 && firstOurs !== void 0 && entries[firstOurs] === spec)
|
|
367
|
-
return;
|
|
368
|
-
const formattingOptions = { insertSpaces: true, tabSize: 2 };
|
|
369
|
-
let next = text;
|
|
370
|
-
if (!Array.isArray(parsed.plugin)) {
|
|
371
|
-
next = applyEdits(
|
|
372
|
-
next,
|
|
373
|
-
modify(next, ["plugin"], [spec], { formattingOptions })
|
|
374
|
-
);
|
|
375
|
-
} else if (firstOurs === void 0) {
|
|
376
|
-
next = applyEdits(
|
|
377
|
-
next,
|
|
378
|
-
modify(next, ["plugin", -1], spec, {
|
|
379
|
-
formattingOptions,
|
|
380
|
-
isArrayInsertion: true
|
|
381
|
-
})
|
|
382
|
-
);
|
|
383
|
-
} else {
|
|
384
|
-
if (entries[firstOurs] !== spec) {
|
|
385
|
-
next = applyEdits(
|
|
386
|
-
next,
|
|
387
|
-
modify(next, ["plugin", firstOurs], spec, { formattingOptions })
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
for (const index of ourIndices.slice(1).sort((a, b) => b - a)) {
|
|
391
|
-
next = applyEdits(
|
|
392
|
-
next,
|
|
393
|
-
modify(next, ["plugin", index], void 0, { formattingOptions })
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (next !== text) fs4.writeFileSync(file, next, "utf8");
|
|
398
|
-
} catch (error) {
|
|
399
|
-
logger.warn(
|
|
400
|
-
`could not register the route33 TUI plugin in tui.json: ${String(error)}`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// src/bootstrap/ensure-tui-registration.ts
|
|
406
|
-
function ensureTuiRegistration(deps) {
|
|
407
|
-
try {
|
|
408
|
-
const { worktree, env } = deps;
|
|
409
|
-
if (env.ROUTE33_TUI_PROMPT === "0") return;
|
|
410
|
-
const usableWorktree = worktree !== void 0 && worktree.trim() !== "" && path7.resolve(worktree) !== "/" ? worktree : void 0;
|
|
411
|
-
const spec = resolveTuiSpec(usableWorktree, env, deps.moduleUrl);
|
|
412
|
-
if (usableWorktree !== void 0) {
|
|
413
|
-
upsertTuiSpec(path7.join(usableWorktree, ".opencode", "tui.json"), spec);
|
|
414
|
-
}
|
|
415
|
-
if (isGlobalContext(worktree, deps.moduleUrl)) {
|
|
416
|
-
upsertTuiSpec(globalTuiJsonPath(env), spec);
|
|
417
|
-
}
|
|
418
|
-
} catch (error) {
|
|
419
|
-
logger.warn(
|
|
420
|
-
`could not register the route33 TUI plugin in tui.json: ${String(error)}`
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
248
|
// src/config/live-auth-fetch.ts
|
|
426
249
|
function buildLiveAuthFetch(resolveKeyNow, innerFetch) {
|
|
427
250
|
return (input, init) => {
|
|
@@ -563,24 +386,24 @@ function hashKey(key) {
|
|
|
563
386
|
}
|
|
564
387
|
|
|
565
388
|
// src/catalog/catalog-cache/read-disk.ts
|
|
566
|
-
import * as
|
|
389
|
+
import * as fs3 from "fs";
|
|
567
390
|
|
|
568
391
|
// src/catalog/catalog-cache/cache-file-path.ts
|
|
569
|
-
import * as
|
|
392
|
+
import * as path5 from "path";
|
|
570
393
|
function cacheFilePath(keyHash, env) {
|
|
571
|
-
return
|
|
394
|
+
return path5.join(resolveCacheDir(env), `catalog-${keyHash}.json`);
|
|
572
395
|
}
|
|
573
396
|
|
|
574
397
|
// src/catalog/catalog-cache/read-disk.ts
|
|
575
|
-
function
|
|
398
|
+
function isRecord3(value) {
|
|
576
399
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
577
400
|
}
|
|
578
401
|
function readDisk(keyHash, env) {
|
|
579
402
|
try {
|
|
580
403
|
const parsed = JSON.parse(
|
|
581
|
-
|
|
404
|
+
fs3.readFileSync(cacheFilePath(keyHash, env), "utf8")
|
|
582
405
|
);
|
|
583
|
-
if (!
|
|
406
|
+
if (!isRecord3(parsed) || typeof parsed.fetchedAt !== "number" || !isRecord3(parsed.models))
|
|
584
407
|
return void 0;
|
|
585
408
|
return {
|
|
586
409
|
fetchedAt: parsed.fetchedAt,
|
|
@@ -622,15 +445,15 @@ function readStaleCatalog(opts) {
|
|
|
622
445
|
}
|
|
623
446
|
|
|
624
447
|
// src/catalog/catalog-cache/write-catalog.ts
|
|
625
|
-
import * as
|
|
448
|
+
import * as fs4 from "fs";
|
|
626
449
|
function writeCatalog(opts) {
|
|
627
450
|
const { key, models, env = process.env, now = Date.now() } = opts;
|
|
628
451
|
const keyHash = hashKey(key);
|
|
629
452
|
const record = { fetchedAt: now, keyHash, models };
|
|
630
453
|
memory.set(keyHash, record);
|
|
631
454
|
try {
|
|
632
|
-
|
|
633
|
-
|
|
455
|
+
fs4.mkdirSync(resolveCacheDir(env), { recursive: true });
|
|
456
|
+
fs4.writeFileSync(cacheFilePath(keyHash, env), JSON.stringify(record));
|
|
634
457
|
} catch (error) {
|
|
635
458
|
logger.warn(
|
|
636
459
|
`could not persist catalog cache: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -654,7 +477,7 @@ var CatalogFetchError = class extends Error {
|
|
|
654
477
|
var DEFAULT_FETCH_TIMEOUT_MS = 5e3;
|
|
655
478
|
|
|
656
479
|
// src/catalog/extract-pricing.ts
|
|
657
|
-
function
|
|
480
|
+
function isRecord4(value) {
|
|
658
481
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
659
482
|
}
|
|
660
483
|
var OPENROUTER_KEYS = [
|
|
@@ -711,7 +534,7 @@ function extractModelsDevPricing(raw) {
|
|
|
711
534
|
return cost;
|
|
712
535
|
}
|
|
713
536
|
function extractPricing(raw) {
|
|
714
|
-
if (!
|
|
537
|
+
if (!isRecord4(raw)) return void 0;
|
|
715
538
|
const hasOpenRouterKeys = OPENROUTER_KEYS.some((key) => key in raw);
|
|
716
539
|
const hasModelsDevKeys = MODELS_DEV_KEYS.some((key) => key in raw);
|
|
717
540
|
if (hasOpenRouterKeys && hasModelsDevKeys) return void 0;
|
|
@@ -742,12 +565,12 @@ function normalizeId(raw, env = process.env) {
|
|
|
742
565
|
}
|
|
743
566
|
|
|
744
567
|
// src/catalog/fetch-catalog.ts
|
|
745
|
-
function
|
|
568
|
+
function isRecord5(value) {
|
|
746
569
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
747
570
|
}
|
|
748
571
|
function parseEntries(payload) {
|
|
749
572
|
if (Array.isArray(payload)) return payload;
|
|
750
|
-
if (
|
|
573
|
+
if (isRecord5(payload) && Array.isArray(payload.data)) return payload.data;
|
|
751
574
|
throw new CatalogFetchError(
|
|
752
575
|
"parse",
|
|
753
576
|
"response is neither {data:[\u2026]} nor a bare array"
|
|
@@ -756,13 +579,13 @@ function parseEntries(payload) {
|
|
|
756
579
|
function toCatalogModels(entries, env) {
|
|
757
580
|
const models = {};
|
|
758
581
|
for (const entry of entries) {
|
|
759
|
-
if (!
|
|
582
|
+
if (!isRecord5(entry) || typeof entry.id !== "string") continue;
|
|
760
583
|
const slug2 = normalizeId(entry.id, env);
|
|
761
584
|
if (!slug2 || models[slug2]) continue;
|
|
762
585
|
const model = { id: slug2 };
|
|
763
586
|
const displayName = typeof entry.display_name === "string" ? entry.display_name.trim() : "";
|
|
764
587
|
if (displayName !== "") model.displayName = displayName;
|
|
765
|
-
if (
|
|
588
|
+
if (isRecord5(entry.pricing) && extractPricing(entry.pricing) !== void 0)
|
|
766
589
|
model.pricing = entry.pricing;
|
|
767
590
|
models[slug2] = model;
|
|
768
591
|
}
|
|
@@ -1171,11 +994,11 @@ function enrichModel(slug2, sources) {
|
|
|
1171
994
|
}
|
|
1172
995
|
|
|
1173
996
|
// src/enrich/models-dev/load-models-dev.ts
|
|
1174
|
-
import * as
|
|
1175
|
-
import * as
|
|
997
|
+
import * as fs5 from "fs";
|
|
998
|
+
import * as path6 from "path";
|
|
1176
999
|
|
|
1177
1000
|
// src/enrich/models-dev/is-record.ts
|
|
1178
|
-
function
|
|
1001
|
+
function isRecord6(value) {
|
|
1179
1002
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1180
1003
|
}
|
|
1181
1004
|
|
|
@@ -1194,7 +1017,7 @@ async function fetchLive(fetchImpl) {
|
|
|
1194
1017
|
return void 0;
|
|
1195
1018
|
}
|
|
1196
1019
|
const body = await response.json();
|
|
1197
|
-
return
|
|
1020
|
+
return isRecord6(body) ? body : void 0;
|
|
1198
1021
|
} catch (error) {
|
|
1199
1022
|
logger.warn("models.dev fetch failed:", error);
|
|
1200
1023
|
return void 0;
|
|
@@ -1211,7 +1034,7 @@ function num(value) {
|
|
|
1211
1034
|
// src/enrich/models-dev/normalize-cost.ts
|
|
1212
1035
|
var EXPRESSIBLE_TIER_SIZE = 2e5;
|
|
1213
1036
|
function parseRateBlock(value) {
|
|
1214
|
-
if (!
|
|
1037
|
+
if (!isRecord6(value)) return void 0;
|
|
1215
1038
|
const input = num(value.input);
|
|
1216
1039
|
const output = num(value.output);
|
|
1217
1040
|
if (input === void 0 || output === void 0) return void 0;
|
|
@@ -1224,7 +1047,7 @@ function parseRateBlock(value) {
|
|
|
1224
1047
|
}
|
|
1225
1048
|
function isExpressibleContextTier(tier) {
|
|
1226
1049
|
const marker = tier.tier;
|
|
1227
|
-
return
|
|
1050
|
+
return isRecord6(marker) && marker.type === "context" && marker.size === EXPRESSIBLE_TIER_SIZE;
|
|
1228
1051
|
}
|
|
1229
1052
|
function maxRate(current, candidate) {
|
|
1230
1053
|
const rate = num(candidate);
|
|
@@ -1232,7 +1055,7 @@ function maxRate(current, candidate) {
|
|
|
1232
1055
|
return current === void 0 || rate > current ? rate : current;
|
|
1233
1056
|
}
|
|
1234
1057
|
function normalizeCost(raw) {
|
|
1235
|
-
if (!
|
|
1058
|
+
if (!isRecord6(raw)) return void 0;
|
|
1236
1059
|
const input = num(raw.input);
|
|
1237
1060
|
const output = num(raw.output);
|
|
1238
1061
|
if (input === void 0 || output === void 0) return void 0;
|
|
@@ -1243,7 +1066,7 @@ function normalizeCost(raw) {
|
|
|
1243
1066
|
if (cacheWrite !== void 0) cost.cache_write = cacheWrite;
|
|
1244
1067
|
const over200k = parseRateBlock(raw.context_over_200k);
|
|
1245
1068
|
if (over200k) cost.context_over_200k = over200k;
|
|
1246
|
-
const tiers = Array.isArray(raw.tiers) ? raw.tiers.filter(
|
|
1069
|
+
const tiers = Array.isArray(raw.tiers) ? raw.tiers.filter(isRecord6) : [];
|
|
1247
1070
|
for (const tier of tiers) {
|
|
1248
1071
|
if (isExpressibleContextTier(tier)) {
|
|
1249
1072
|
if (cost.context_over_200k === void 0) {
|
|
@@ -1273,10 +1096,10 @@ function swapLast(value, from, to) {
|
|
|
1273
1096
|
return index === -1 ? value : `${value.slice(0, index)}${to}${value.slice(index + 1)}`;
|
|
1274
1097
|
}
|
|
1275
1098
|
function probeCandidates(slug2) {
|
|
1276
|
-
const
|
|
1277
|
-
if (
|
|
1278
|
-
const vendor = slug2.slice(0,
|
|
1279
|
-
const model = slug2.slice(
|
|
1099
|
+
const sep = slug2.indexOf("/");
|
|
1100
|
+
if (sep <= 0 || sep >= slug2.length - 1) return [];
|
|
1101
|
+
const vendor = slug2.slice(0, sep);
|
|
1102
|
+
const model = slug2.slice(sep + 1);
|
|
1280
1103
|
const vendors = [vendor];
|
|
1281
1104
|
const alias = VENDOR_ALIASES[vendor];
|
|
1282
1105
|
if (alias !== void 0 && alias !== vendor) vendors.push(alias);
|
|
@@ -1301,7 +1124,7 @@ function probeCandidates(slug2) {
|
|
|
1301
1124
|
|
|
1302
1125
|
// src/enrich/models-dev/lookup.ts
|
|
1303
1126
|
function normalizeLimit(raw) {
|
|
1304
|
-
if (!
|
|
1127
|
+
if (!isRecord6(raw)) return void 0;
|
|
1305
1128
|
const context = num(raw.context);
|
|
1306
1129
|
const output = num(raw.output);
|
|
1307
1130
|
if (context === void 0 || output === void 0) return void 0;
|
|
@@ -1330,9 +1153,9 @@ function buildLookup(model) {
|
|
|
1330
1153
|
function lookupSlug(data, slug2) {
|
|
1331
1154
|
for (const [providerKey, modelKey] of probeCandidates(slug2)) {
|
|
1332
1155
|
const provider = data[providerKey];
|
|
1333
|
-
if (!
|
|
1156
|
+
if (!isRecord6(provider) || !isRecord6(provider.models)) continue;
|
|
1334
1157
|
const model = provider.models[modelKey];
|
|
1335
|
-
if (
|
|
1158
|
+
if (isRecord6(model)) return buildLookup(model);
|
|
1336
1159
|
}
|
|
1337
1160
|
return void 0;
|
|
1338
1161
|
}
|
|
@@ -1341,10 +1164,10 @@ function lookupSlug(data, slug2) {
|
|
|
1341
1164
|
var CACHE_FILE = "models-dev.json";
|
|
1342
1165
|
function readCache(cachePath) {
|
|
1343
1166
|
try {
|
|
1344
|
-
const parsed = JSON.parse(
|
|
1345
|
-
if (!
|
|
1167
|
+
const parsed = JSON.parse(fs5.readFileSync(cachePath, "utf8"));
|
|
1168
|
+
if (!isRecord6(parsed)) return void 0;
|
|
1346
1169
|
const fetchedAt = num(parsed.fetchedAt);
|
|
1347
|
-
if (fetchedAt === void 0 || !
|
|
1170
|
+
if (fetchedAt === void 0 || !isRecord6(parsed.data)) return void 0;
|
|
1348
1171
|
return { fetchedAt, data: parsed.data };
|
|
1349
1172
|
} catch {
|
|
1350
1173
|
return void 0;
|
|
@@ -1352,8 +1175,8 @@ function readCache(cachePath) {
|
|
|
1352
1175
|
}
|
|
1353
1176
|
function writeCache(cachePath, envelope) {
|
|
1354
1177
|
try {
|
|
1355
|
-
|
|
1356
|
-
|
|
1178
|
+
fs5.mkdirSync(path6.dirname(cachePath), { recursive: true });
|
|
1179
|
+
fs5.writeFileSync(cachePath, JSON.stringify(envelope), "utf8");
|
|
1357
1180
|
} catch (error) {
|
|
1358
1181
|
logger.warn("failed to write models.dev cache:", error);
|
|
1359
1182
|
}
|
|
@@ -1366,7 +1189,7 @@ async function loadModelsDev(options = {}) {
|
|
|
1366
1189
|
const cacheDir = options.cacheDir ?? resolveCacheDir();
|
|
1367
1190
|
const ttlMs = options.ttlMs ?? MODELS_DEV_TTL_MS;
|
|
1368
1191
|
const now = options.now ?? Date.now;
|
|
1369
|
-
const cachePath =
|
|
1192
|
+
const cachePath = path6.join(cacheDir, CACHE_FILE);
|
|
1370
1193
|
const cached = readCache(cachePath);
|
|
1371
1194
|
if (cached && now() - cached.fetchedAt < ttlMs)
|
|
1372
1195
|
return makeSource("cache", cached.data);
|
|
@@ -1516,10 +1339,10 @@ function mergeConfig(cfg, blocks) {
|
|
|
1516
1339
|
|
|
1517
1340
|
// src/config/collected-ref.ts
|
|
1518
1341
|
function parseRef(ref, source) {
|
|
1519
|
-
const
|
|
1520
|
-
if (
|
|
1521
|
-
const providerId = ref.slice(0,
|
|
1522
|
-
const suffix = ref.slice(
|
|
1342
|
+
const sep = ref.indexOf("/");
|
|
1343
|
+
if (sep <= 0 || sep >= ref.length - 1) return void 0;
|
|
1344
|
+
const providerId = ref.slice(0, sep);
|
|
1345
|
+
const suffix = ref.slice(sep + 1);
|
|
1523
1346
|
if (providerId === "route_33")
|
|
1524
1347
|
return { providerId, modelKey: suffix, ref, source };
|
|
1525
1348
|
if (providerId !== "route_33_anthropic") return void 0;
|
|
@@ -1549,34 +1372,34 @@ function collectCfgRefs(cfg) {
|
|
|
1549
1372
|
}
|
|
1550
1373
|
|
|
1551
1374
|
// src/config/collect-flavour-refs.ts
|
|
1552
|
-
import * as
|
|
1553
|
-
import * as
|
|
1554
|
-
import * as
|
|
1555
|
-
import { parse as
|
|
1556
|
-
function
|
|
1375
|
+
import * as fs6 from "fs";
|
|
1376
|
+
import * as os3 from "os";
|
|
1377
|
+
import * as path7 from "path";
|
|
1378
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
1379
|
+
function isRecord7(value) {
|
|
1557
1380
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1558
1381
|
}
|
|
1559
1382
|
function findFlavourPath(env, cwd) {
|
|
1560
1383
|
const candidates = [];
|
|
1561
1384
|
const configDir = env.OPENCODE_CONFIG_DIR?.trim();
|
|
1562
|
-
if (configDir) candidates.push(
|
|
1563
|
-
candidates.push(
|
|
1564
|
-
const configHome = env.XDG_CONFIG_HOME?.trim() ||
|
|
1565
|
-
candidates.push(
|
|
1385
|
+
if (configDir) candidates.push(path7.join(configDir, "oh-my-openagent.jsonc"));
|
|
1386
|
+
candidates.push(path7.join(cwd, ".opencode", "oh-my-openagent.jsonc"));
|
|
1387
|
+
const configHome = env.XDG_CONFIG_HOME?.trim() || path7.join(os3.homedir(), ".config");
|
|
1388
|
+
candidates.push(path7.join(configHome, "opencode", "oh-my-openagent.jsonc"));
|
|
1566
1389
|
for (const candidate of candidates) {
|
|
1567
1390
|
try {
|
|
1568
|
-
if (
|
|
1391
|
+
if (fs6.existsSync(candidate)) return candidate;
|
|
1569
1392
|
} catch {
|
|
1570
1393
|
}
|
|
1571
1394
|
}
|
|
1572
1395
|
return void 0;
|
|
1573
1396
|
}
|
|
1574
1397
|
function collectSectionRefs(refs, section, label) {
|
|
1575
|
-
if (!
|
|
1398
|
+
if (!isRecord7(section)) return;
|
|
1576
1399
|
for (const [name, entry] of Object.entries(section)) {
|
|
1577
|
-
if (!
|
|
1400
|
+
if (!isRecord7(entry)) continue;
|
|
1578
1401
|
pushRef(refs, entry.model, `flavour ${label} "${name}"`);
|
|
1579
|
-
if (label === "agent" &&
|
|
1402
|
+
if (label === "agent" && isRecord7(entry.ultrawork)) {
|
|
1580
1403
|
pushRef(
|
|
1581
1404
|
refs,
|
|
1582
1405
|
entry.ultrawork.model,
|
|
@@ -1590,8 +1413,8 @@ function collectFlavourRefs(env, cwd) {
|
|
|
1590
1413
|
if (flavourPath === void 0) return [];
|
|
1591
1414
|
const refs = [];
|
|
1592
1415
|
try {
|
|
1593
|
-
const parsed =
|
|
1594
|
-
if (!
|
|
1416
|
+
const parsed = parseJsonc(fs6.readFileSync(flavourPath, "utf8"));
|
|
1417
|
+
if (!isRecord7(parsed)) {
|
|
1595
1418
|
logger.warn(
|
|
1596
1419
|
`flavour file ${flavourPath} did not parse to an object \u2014 skipping flavour ref validation`
|
|
1597
1420
|
);
|
|
@@ -1725,7 +1548,7 @@ function createConfigHook(options = {}) {
|
|
|
1725
1548
|
let key;
|
|
1726
1549
|
try {
|
|
1727
1550
|
key = resolveKey(cfg, env, cwd);
|
|
1728
|
-
const baseURL = resolveBaseUrl(cfg, env);
|
|
1551
|
+
const baseURL = resolveBaseUrl(cfg, env, cwd);
|
|
1729
1552
|
const catalog = await getCatalog({
|
|
1730
1553
|
key,
|
|
1731
1554
|
baseURL,
|
|
@@ -1770,11 +1593,6 @@ function createConfigHook(options = {}) {
|
|
|
1770
1593
|
|
|
1771
1594
|
// src/index.ts
|
|
1772
1595
|
var Route33Plugin = async (input, _options) => {
|
|
1773
|
-
ensureTuiRegistration({
|
|
1774
|
-
worktree: input?.worktree ?? input?.directory,
|
|
1775
|
-
env: process.env,
|
|
1776
|
-
moduleUrl: import.meta.url
|
|
1777
|
-
});
|
|
1778
1596
|
ensureWorkspaceKeyGitignore(input?.worktree ?? input?.directory);
|
|
1779
1597
|
return {
|
|
1780
1598
|
config: createConfigHook(),
|
package/dist/tui/index.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
// src/shared/env-api-key.ts
|
|
2
2
|
var ENV_API_KEY = "ROUTE33_API_KEY";
|
|
3
3
|
|
|
4
|
+
// src/shared/git-workspace.ts
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
function isInsideGitWorktree(startDir) {
|
|
8
|
+
try {
|
|
9
|
+
let dir = path.resolve(startDir);
|
|
10
|
+
for (; ; ) {
|
|
11
|
+
if (existsSync(path.join(dir, ".git"))) return true;
|
|
12
|
+
const parent = path.dirname(dir);
|
|
13
|
+
if (parent === dir) return false;
|
|
14
|
+
dir = parent;
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
// src/shared/logger.ts
|
|
5
22
|
var logger = {
|
|
6
23
|
info: (...args) => {
|
|
@@ -16,12 +33,12 @@ var logger = {
|
|
|
16
33
|
|
|
17
34
|
// src/shared/resolve-cache-dir.ts
|
|
18
35
|
import * as os from "os";
|
|
19
|
-
import * as
|
|
36
|
+
import * as path2 from "path";
|
|
20
37
|
|
|
21
38
|
// src/auth/auth-json.ts
|
|
22
39
|
import * as fs from "fs";
|
|
23
40
|
import * as os2 from "os";
|
|
24
|
-
import * as
|
|
41
|
+
import * as path3 from "path";
|
|
25
42
|
var PROVIDER_ID = "route_33";
|
|
26
43
|
function usable(value) {
|
|
27
44
|
if (typeof value !== "string") return void 0;
|
|
@@ -33,8 +50,8 @@ function isRecord(value) {
|
|
|
33
50
|
}
|
|
34
51
|
function readAuthEntry(env = process.env) {
|
|
35
52
|
try {
|
|
36
|
-
const dataHome = usable(env.XDG_DATA_HOME) ??
|
|
37
|
-
const authPath =
|
|
53
|
+
const dataHome = usable(env.XDG_DATA_HOME) ?? path3.join(os2.homedir(), ".local", "share");
|
|
54
|
+
const authPath = path3.join(dataHome, "opencode", "auth.json");
|
|
38
55
|
const parsed = JSON.parse(fs.readFileSync(authPath, "utf8"));
|
|
39
56
|
if (!isRecord(parsed)) return {};
|
|
40
57
|
const entry = parsed[PROVIDER_ID];
|
|
@@ -52,9 +69,9 @@ function readAuthEntry(env = process.env) {
|
|
|
52
69
|
|
|
53
70
|
// src/auth/workspace-key-file.ts
|
|
54
71
|
import * as fs2 from "fs";
|
|
55
|
-
import * as
|
|
56
|
-
var WORKSPACE_KEY_RELATIVE_PATH =
|
|
57
|
-
var WORKSPACE_GITIGNORE_RELATIVE_PATH =
|
|
72
|
+
import * as path4 from "path";
|
|
73
|
+
var WORKSPACE_KEY_RELATIVE_PATH = path4.join(".opencode", "route33.key");
|
|
74
|
+
var WORKSPACE_GITIGNORE_RELATIVE_PATH = path4.join(".opencode", ".gitignore");
|
|
58
75
|
var GITIGNORE_ENTRIES = ["route33.key", "tui.json"];
|
|
59
76
|
function usable2(value) {
|
|
60
77
|
if (typeof value !== "string") return void 0;
|
|
@@ -68,7 +85,7 @@ function readWorkspaceKey(worktree) {
|
|
|
68
85
|
if (worktree === void 0) return void 0;
|
|
69
86
|
try {
|
|
70
87
|
const raw = fs2.readFileSync(
|
|
71
|
-
|
|
88
|
+
path4.join(worktree, WORKSPACE_KEY_RELATIVE_PATH),
|
|
72
89
|
"utf8"
|
|
73
90
|
);
|
|
74
91
|
const parsed = JSON.parse(raw);
|
|
@@ -80,10 +97,10 @@ function readWorkspaceKey(worktree) {
|
|
|
80
97
|
}
|
|
81
98
|
function ensureWorkspaceKeyGitignore(worktree) {
|
|
82
99
|
if (worktree === void 0 || worktree.trim() === "") return;
|
|
83
|
-
if (
|
|
84
|
-
const gitignorePath =
|
|
100
|
+
if (path4.resolve(worktree) === "/") return;
|
|
101
|
+
const gitignorePath = path4.join(worktree, WORKSPACE_GITIGNORE_RELATIVE_PATH);
|
|
85
102
|
try {
|
|
86
|
-
fs2.mkdirSync(
|
|
103
|
+
fs2.mkdirSync(path4.dirname(gitignorePath), { recursive: true });
|
|
87
104
|
let content = "";
|
|
88
105
|
try {
|
|
89
106
|
content = fs2.readFileSync(gitignorePath, "utf8");
|
|
@@ -107,8 +124,8 @@ function ensureWorkspaceKeyGitignore(worktree) {
|
|
|
107
124
|
}
|
|
108
125
|
function writeWorkspaceKey(worktree, key) {
|
|
109
126
|
ensureWorkspaceKeyGitignore(worktree);
|
|
110
|
-
const filePath =
|
|
111
|
-
fs2.mkdirSync(
|
|
127
|
+
const filePath = path4.join(worktree, WORKSPACE_KEY_RELATIVE_PATH);
|
|
128
|
+
fs2.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
112
129
|
fs2.writeFileSync(
|
|
113
130
|
filePath,
|
|
114
131
|
`${JSON.stringify({ type: "api", key }, null, 2)}
|
|
@@ -130,7 +147,7 @@ function keyFromConfig(cfg) {
|
|
|
130
147
|
return void 0;
|
|
131
148
|
}
|
|
132
149
|
function resolveKey(cfg, env = process.env, worktree) {
|
|
133
|
-
return usable3(env[ENV_API_KEY]) ?? keyFromConfig(cfg) ?? readWorkspaceKey(worktree) ?? readAuthEntry(env).key;
|
|
150
|
+
return usable3(env[ENV_API_KEY]) ?? keyFromConfig(cfg) ?? readWorkspaceKey(worktree) ?? (worktree !== void 0 && isInsideGitWorktree(worktree) ? void 0 : readAuthEntry(env).key);
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
// src/tui/key-prompt-ui.ts
|
|
@@ -274,23 +291,6 @@ function createDismissalStore(kv, key = PROJECT_KEY_PROMPT_DISMISSED_AT_KEY) {
|
|
|
274
291
|
};
|
|
275
292
|
}
|
|
276
293
|
|
|
277
|
-
// src/tui/git-workspace.ts
|
|
278
|
-
import { existsSync } from "fs";
|
|
279
|
-
import * as path4 from "path";
|
|
280
|
-
function isInsideGitWorktree(startDir) {
|
|
281
|
-
try {
|
|
282
|
-
let dir = path4.resolve(startDir);
|
|
283
|
-
for (; ; ) {
|
|
284
|
-
if (existsSync(path4.join(dir, ".git"))) return true;
|
|
285
|
-
const parent = path4.dirname(dir);
|
|
286
|
-
if (parent === dir) return false;
|
|
287
|
-
dir = parent;
|
|
288
|
-
}
|
|
289
|
-
} catch {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
294
|
// src/tui/key-prompt-copy.ts
|
|
295
295
|
var PROJECT_COPY = {
|
|
296
296
|
selectTitle: KEY_SELECT_TITLE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@route_33/opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"description": "opencode provider plugin for the route33.ai AI gateway — key-scoped model discovery, cost/limit enrichment, auth, and the key-setup wizard.",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"import": "./dist/index.js"
|
|
21
21
|
},
|
|
22
|
+
"./server": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
},
|
|
22
26
|
"./tui": {
|
|
23
27
|
"types": "./dist/tui/index.d.ts",
|
|
24
28
|
"import": "./dist/tui/index.js"
|