@route_33/opencode 0.1.2
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 +21 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1786 -0
- package/dist/tui/index.d.ts +5 -0
- package/dist/tui/index.js +476 -0
- package/package.json +57 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
// src/shared/env-api-key.ts
|
|
2
|
+
var ENV_API_KEY = "ROUTE33_API_KEY";
|
|
3
|
+
|
|
4
|
+
// src/shared/logger.ts
|
|
5
|
+
var logger = {
|
|
6
|
+
info: (...args) => {
|
|
7
|
+
console.log("[route_33]", ...args);
|
|
8
|
+
},
|
|
9
|
+
warn: (...args) => {
|
|
10
|
+
console.warn("[route_33]", ...args);
|
|
11
|
+
},
|
|
12
|
+
error: (...args) => {
|
|
13
|
+
console.error("[route_33]", ...args);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/shared/resolve-cache-dir.ts
|
|
18
|
+
import * as os from "os";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
|
|
21
|
+
// src/auth/auth-json.ts
|
|
22
|
+
import * as fs from "fs";
|
|
23
|
+
import * as os2 from "os";
|
|
24
|
+
import * as path2 from "path";
|
|
25
|
+
var PROVIDER_ID = "route_33";
|
|
26
|
+
function usable(value) {
|
|
27
|
+
if (typeof value !== "string") return void 0;
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
return trimmed ? trimmed : void 0;
|
|
30
|
+
}
|
|
31
|
+
function isRecord(value) {
|
|
32
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
function readAuthEntry(env = process.env) {
|
|
35
|
+
try {
|
|
36
|
+
const dataHome = usable(env.XDG_DATA_HOME) ?? path2.join(os2.homedir(), ".local", "share");
|
|
37
|
+
const authPath = path2.join(dataHome, "opencode", "auth.json");
|
|
38
|
+
const parsed = JSON.parse(fs.readFileSync(authPath, "utf8"));
|
|
39
|
+
if (!isRecord(parsed)) return {};
|
|
40
|
+
const entry = parsed[PROVIDER_ID];
|
|
41
|
+
if (!isRecord(entry) || entry.type !== "api") return {};
|
|
42
|
+
const result = {};
|
|
43
|
+
const key = usable(entry.key);
|
|
44
|
+
if (key !== void 0) result.key = key;
|
|
45
|
+
const baseURL = isRecord(entry.metadata) ? usable(entry.metadata.baseURL) : void 0;
|
|
46
|
+
if (baseURL !== void 0) result.baseURL = baseURL;
|
|
47
|
+
return result;
|
|
48
|
+
} catch {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/auth/workspace-key-file.ts
|
|
54
|
+
import * as fs2 from "fs";
|
|
55
|
+
import * as path3 from "path";
|
|
56
|
+
var WORKSPACE_KEY_RELATIVE_PATH = path3.join(".opencode", "route33.key");
|
|
57
|
+
var WORKSPACE_GITIGNORE_RELATIVE_PATH = path3.join(".opencode", ".gitignore");
|
|
58
|
+
var GITIGNORE_ENTRIES = ["route33.key", "tui.json"];
|
|
59
|
+
function usable2(value) {
|
|
60
|
+
if (typeof value !== "string") return void 0;
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
return trimmed ? trimmed : void 0;
|
|
63
|
+
}
|
|
64
|
+
function isRecord2(value) {
|
|
65
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66
|
+
}
|
|
67
|
+
function readWorkspaceKey(worktree) {
|
|
68
|
+
if (worktree === void 0) return void 0;
|
|
69
|
+
try {
|
|
70
|
+
const raw = fs2.readFileSync(
|
|
71
|
+
path3.join(worktree, WORKSPACE_KEY_RELATIVE_PATH),
|
|
72
|
+
"utf8"
|
|
73
|
+
);
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
if (!isRecord2(parsed) || parsed.type !== "api") return void 0;
|
|
76
|
+
return usable2(parsed.key);
|
|
77
|
+
} catch {
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function ensureWorkspaceKeyGitignore(worktree) {
|
|
82
|
+
if (worktree === void 0 || worktree.trim() === "") return;
|
|
83
|
+
if (path3.resolve(worktree) === "/") return;
|
|
84
|
+
const gitignorePath = path3.join(worktree, WORKSPACE_GITIGNORE_RELATIVE_PATH);
|
|
85
|
+
try {
|
|
86
|
+
fs2.mkdirSync(path3.dirname(gitignorePath), { recursive: true });
|
|
87
|
+
let content = "";
|
|
88
|
+
try {
|
|
89
|
+
content = fs2.readFileSync(gitignorePath, "utf8");
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
const lines = content.split("\n");
|
|
93
|
+
const missing = GITIGNORE_ENTRIES.filter(
|
|
94
|
+
(entry) => !lines.some(
|
|
95
|
+
(line) => line.trim() === entry || line.trim() === `/${entry}`
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
if (missing.length === 0) return;
|
|
99
|
+
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
100
|
+
fs2.writeFileSync(gitignorePath, `${content}${prefix}${missing.join("\n")}
|
|
101
|
+
`);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
logger.warn(
|
|
104
|
+
`opencode-route33: could not update ${gitignorePath} (${String(error)}) \u2014 verify route33.key and tui.json are git-ignored before committing`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function writeWorkspaceKey(worktree, key) {
|
|
109
|
+
ensureWorkspaceKeyGitignore(worktree);
|
|
110
|
+
const filePath = path3.join(worktree, WORKSPACE_KEY_RELATIVE_PATH);
|
|
111
|
+
fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
112
|
+
fs2.writeFileSync(
|
|
113
|
+
filePath,
|
|
114
|
+
`${JSON.stringify({ type: "api", key }, null, 2)}
|
|
115
|
+
`,
|
|
116
|
+
{ mode: 384 }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/auth/resolve-key.ts
|
|
121
|
+
var UNRESOLVED_TEMPLATE_MARKER = "{env:";
|
|
122
|
+
function usable3(value) {
|
|
123
|
+
const trimmed = value?.trim();
|
|
124
|
+
return trimmed ? trimmed : void 0;
|
|
125
|
+
}
|
|
126
|
+
function keyFromConfig(cfg) {
|
|
127
|
+
const literal = usable3(cfg?.provider?.route_33?.options?.apiKey);
|
|
128
|
+
if (literal !== void 0 && !literal.includes(UNRESOLVED_TEMPLATE_MARKER))
|
|
129
|
+
return literal;
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
function resolveKey(cfg, env = process.env, worktree) {
|
|
133
|
+
return usable3(env[ENV_API_KEY]) ?? keyFromConfig(cfg) ?? readWorkspaceKey(worktree) ?? readAuthEntry(env).key;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/tui/key-prompt-ui.ts
|
|
137
|
+
var KEY_SELECT_TITLE = "route33.ai project key";
|
|
138
|
+
var KEY_SELECT_ENTER_LABEL = "Enter a project key";
|
|
139
|
+
var KEY_SELECT_ENTER_DESCRIPTION = "Per workspace on purpose \u2014 one shared key would lose the per-project metrics";
|
|
140
|
+
var KEY_SELECT_SKIP_LABEL = "Skip";
|
|
141
|
+
var KEY_SELECT_SKIP_DESCRIPTION = "Ask again in 7 days";
|
|
142
|
+
var KEY_SELECT_PLACEHOLDER = "No route33.ai key found for this workspace yet.";
|
|
143
|
+
var KEY_PROMPT_TITLE = "Paste your route33.ai project key";
|
|
144
|
+
var KEY_SAVED_MESSAGE = "route33.ai project key saved to .opencode/route33.key \u2014 live immediately, no restart needed.";
|
|
145
|
+
var KEY_SAVE_FAILED_MESSAGE = "Could not save the key \u2014 will ask again next start.";
|
|
146
|
+
var GLOBAL_SELECT_TITLE = "route33.ai global token";
|
|
147
|
+
var GLOBAL_SELECT_ENTER_LABEL = "Enter a global token";
|
|
148
|
+
var GLOBAL_SELECT_ENTER_DESCRIPTION = "One token for every workspace without its own project key";
|
|
149
|
+
var GLOBAL_SELECT_SKIP_LABEL = "Skip";
|
|
150
|
+
var GLOBAL_SELECT_SKIP_DESCRIPTION = "Ask again in 7 days";
|
|
151
|
+
var GLOBAL_SELECT_PLACEHOLDER = "No project folder detected \u2014 save a token for all workspaces?";
|
|
152
|
+
var GLOBAL_PROMPT_TITLE = "Paste your route33.ai global token";
|
|
153
|
+
var GLOBAL_TOKEN_SAVED_MESSAGE = "route33.ai global token saved \u2014 applies to all workspaces without a project key.";
|
|
154
|
+
var GLOBAL_TOKEN_SAVE_FAILED_MESSAGE = "Could not save the token \u2014 will ask again next start.";
|
|
155
|
+
|
|
156
|
+
// src/tui/global-token-flow.ts
|
|
157
|
+
async function runGlobalTokenPromptFlow({
|
|
158
|
+
ui,
|
|
159
|
+
globalStore,
|
|
160
|
+
dismissal,
|
|
161
|
+
now = Date.now
|
|
162
|
+
}) {
|
|
163
|
+
if (await ui.askChoice() === "skip") {
|
|
164
|
+
dismissal.dismiss(now());
|
|
165
|
+
ui.close();
|
|
166
|
+
return "skipped";
|
|
167
|
+
}
|
|
168
|
+
while (true) {
|
|
169
|
+
const value = await ui.askKey();
|
|
170
|
+
if (value === void 0) {
|
|
171
|
+
dismissal.dismiss(now());
|
|
172
|
+
ui.close();
|
|
173
|
+
return "skipped";
|
|
174
|
+
}
|
|
175
|
+
const key = value.trim();
|
|
176
|
+
if (key.length === 0) continue;
|
|
177
|
+
try {
|
|
178
|
+
await globalStore.save(key);
|
|
179
|
+
ui.toastSuccess(GLOBAL_TOKEN_SAVED_MESSAGE);
|
|
180
|
+
ui.close();
|
|
181
|
+
return "saved";
|
|
182
|
+
} catch {
|
|
183
|
+
ui.toastError(GLOBAL_TOKEN_SAVE_FAILED_MESSAGE);
|
|
184
|
+
ui.close();
|
|
185
|
+
return "failed";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/tui/prompt-flow.ts
|
|
191
|
+
async function runProjectKeyPromptFlow({
|
|
192
|
+
ui,
|
|
193
|
+
keyStore,
|
|
194
|
+
dismissal,
|
|
195
|
+
now = Date.now
|
|
196
|
+
}) {
|
|
197
|
+
if (await ui.askChoice() === "skip") {
|
|
198
|
+
dismissal.dismiss(now());
|
|
199
|
+
ui.close();
|
|
200
|
+
return "skipped";
|
|
201
|
+
}
|
|
202
|
+
while (true) {
|
|
203
|
+
const value = await ui.askKey();
|
|
204
|
+
if (value === void 0) {
|
|
205
|
+
dismissal.dismiss(now());
|
|
206
|
+
ui.close();
|
|
207
|
+
return "skipped";
|
|
208
|
+
}
|
|
209
|
+
const key = value.trim();
|
|
210
|
+
if (key.length === 0) continue;
|
|
211
|
+
try {
|
|
212
|
+
await keyStore.save(key);
|
|
213
|
+
ui.toastSuccess(KEY_SAVED_MESSAGE);
|
|
214
|
+
ui.close();
|
|
215
|
+
return "saved";
|
|
216
|
+
} catch {
|
|
217
|
+
ui.toastError(KEY_SAVE_FAILED_MESSAGE);
|
|
218
|
+
ui.close();
|
|
219
|
+
return "failed";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/tui/activate.ts
|
|
225
|
+
async function runActivation(deps) {
|
|
226
|
+
try {
|
|
227
|
+
if (!await deps.waitUntilReady()) return "state-unready";
|
|
228
|
+
if (deps.resolveKey() !== void 0) return "key-present";
|
|
229
|
+
if (deps.isGitWorkspace) {
|
|
230
|
+
if (deps.dismissal.isDismissed(Date.now())) return "dismissed";
|
|
231
|
+
return await runProjectKeyPromptFlow({
|
|
232
|
+
ui: deps.ui,
|
|
233
|
+
keyStore: deps.keyStore,
|
|
234
|
+
dismissal: deps.dismissal
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (deps.globalDismissal.isDismissed(Date.now())) return "dismissed";
|
|
238
|
+
return await runGlobalTokenPromptFlow({
|
|
239
|
+
ui: deps.globalUi,
|
|
240
|
+
globalStore: deps.globalStore,
|
|
241
|
+
dismissal: deps.globalDismissal
|
|
242
|
+
});
|
|
243
|
+
} catch {
|
|
244
|
+
return "state-unready";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/tui/dismissal-cooldown.ts
|
|
249
|
+
var DISMISSAL_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
250
|
+
function isWithinCooldown(dismissedAt, now) {
|
|
251
|
+
return dismissedAt !== void 0 && now - dismissedAt < DISMISSAL_COOLDOWN_MS;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/tui/dismissal-store.ts
|
|
255
|
+
var PROJECT_KEY_PROMPT_DISMISSED_AT_KEY = "project-key-prompt.dismissed-at";
|
|
256
|
+
var GLOBAL_TOKEN_PROMPT_DISMISSED_AT_KEY = "global-token-prompt.dismissed-at";
|
|
257
|
+
function readDismissedAt(kv, key) {
|
|
258
|
+
try {
|
|
259
|
+
const value = kv.get(key);
|
|
260
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
261
|
+
} catch {
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function createDismissalStore(kv, key = PROJECT_KEY_PROMPT_DISMISSED_AT_KEY) {
|
|
266
|
+
return {
|
|
267
|
+
isDismissed: (now) => isWithinCooldown(readDismissedAt(kv, key), now),
|
|
268
|
+
dismiss: (now) => {
|
|
269
|
+
try {
|
|
270
|
+
kv.set(key, now);
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
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
|
+
// src/tui/key-prompt-copy.ts
|
|
295
|
+
var PROJECT_COPY = {
|
|
296
|
+
selectTitle: KEY_SELECT_TITLE,
|
|
297
|
+
enterLabel: KEY_SELECT_ENTER_LABEL,
|
|
298
|
+
enterDescription: KEY_SELECT_ENTER_DESCRIPTION,
|
|
299
|
+
skipLabel: KEY_SELECT_SKIP_LABEL,
|
|
300
|
+
skipDescription: KEY_SELECT_SKIP_DESCRIPTION,
|
|
301
|
+
selectPlaceholder: KEY_SELECT_PLACEHOLDER,
|
|
302
|
+
promptTitle: KEY_PROMPT_TITLE
|
|
303
|
+
};
|
|
304
|
+
var GLOBAL_COPY = {
|
|
305
|
+
selectTitle: GLOBAL_SELECT_TITLE,
|
|
306
|
+
enterLabel: GLOBAL_SELECT_ENTER_LABEL,
|
|
307
|
+
enterDescription: GLOBAL_SELECT_ENTER_DESCRIPTION,
|
|
308
|
+
skipLabel: GLOBAL_SELECT_SKIP_LABEL,
|
|
309
|
+
skipDescription: GLOBAL_SELECT_SKIP_DESCRIPTION,
|
|
310
|
+
selectPlaceholder: GLOBAL_SELECT_PLACEHOLDER,
|
|
311
|
+
promptTitle: GLOBAL_PROMPT_TITLE
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/tui/tui-api-adapter.ts
|
|
315
|
+
var READY_POLL_INTERVAL_MS = 100;
|
|
316
|
+
function askChoice(api, copy) {
|
|
317
|
+
return new Promise((resolve3) => {
|
|
318
|
+
let settled = false;
|
|
319
|
+
const settle = (choice) => {
|
|
320
|
+
if (settled) return;
|
|
321
|
+
settled = true;
|
|
322
|
+
resolve3(choice);
|
|
323
|
+
};
|
|
324
|
+
api.ui.dialog.replace(
|
|
325
|
+
() => api.ui.DialogSelect({
|
|
326
|
+
title: copy.selectTitle,
|
|
327
|
+
placeholder: copy.selectPlaceholder,
|
|
328
|
+
options: [
|
|
329
|
+
{
|
|
330
|
+
title: copy.enterLabel,
|
|
331
|
+
value: "enter",
|
|
332
|
+
description: copy.enterDescription
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
title: copy.skipLabel,
|
|
336
|
+
value: "skip",
|
|
337
|
+
description: copy.skipDescription
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
onSelect: (option) => {
|
|
341
|
+
settle(option.value === "enter" ? "enter" : "skip");
|
|
342
|
+
}
|
|
343
|
+
}),
|
|
344
|
+
() => {
|
|
345
|
+
settle("skip");
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function askKey(api, copy) {
|
|
351
|
+
return new Promise((resolve3) => {
|
|
352
|
+
let settled = false;
|
|
353
|
+
const settle = (value) => {
|
|
354
|
+
if (settled) return;
|
|
355
|
+
settled = true;
|
|
356
|
+
resolve3(value);
|
|
357
|
+
};
|
|
358
|
+
api.ui.dialog.replace(
|
|
359
|
+
() => api.ui.DialogPrompt({
|
|
360
|
+
title: copy.promptTitle,
|
|
361
|
+
// No description prop: the opencode TUI renders it as a JSX element,
|
|
362
|
+
// and a bare string crashes the renderer ("orphan text"). The
|
|
363
|
+
// informational line lives on the DialogSelect placeholder instead.
|
|
364
|
+
onConfirm: (value) => {
|
|
365
|
+
settle(value);
|
|
366
|
+
},
|
|
367
|
+
onCancel: () => {
|
|
368
|
+
settle(void 0);
|
|
369
|
+
}
|
|
370
|
+
}),
|
|
371
|
+
() => {
|
|
372
|
+
settle(void 0);
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
function waitUntilReady(api) {
|
|
378
|
+
return new Promise((resolve3) => {
|
|
379
|
+
if (api.state.ready) {
|
|
380
|
+
resolve3(true);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const timer = setInterval(() => {
|
|
384
|
+
if (api.lifecycle.signal.aborted) {
|
|
385
|
+
clearInterval(timer);
|
|
386
|
+
resolve3(false);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (api.state.ready) {
|
|
390
|
+
clearInterval(timer);
|
|
391
|
+
resolve3(true);
|
|
392
|
+
}
|
|
393
|
+
}, READY_POLL_INTERVAL_MS);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function createUi(api, copy) {
|
|
397
|
+
return {
|
|
398
|
+
askChoice: () => askChoice(api, copy),
|
|
399
|
+
askKey: () => askKey(api, copy),
|
|
400
|
+
toastSuccess: (message) => {
|
|
401
|
+
api.ui.toast({ variant: "success", message });
|
|
402
|
+
},
|
|
403
|
+
toastError: (message) => {
|
|
404
|
+
api.ui.toast({ variant: "error", message });
|
|
405
|
+
},
|
|
406
|
+
close: () => {
|
|
407
|
+
api.ui.dialog.clear();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function createGlobalStore(api) {
|
|
412
|
+
return {
|
|
413
|
+
// The sdk client runs with ThrowOnError=false, so a failed auth.set
|
|
414
|
+
// RESOLVES `{ data, error }` instead of rejecting — inspect the result
|
|
415
|
+
// and throw on a carried error so the flow can toast + retry.
|
|
416
|
+
save: async (key) => {
|
|
417
|
+
const result = await api.client.auth.set({
|
|
418
|
+
providerID: "route_33",
|
|
419
|
+
auth: { type: "api", key }
|
|
420
|
+
});
|
|
421
|
+
if (result != null && typeof result === "object" && "error" in result && result.error != null) {
|
|
422
|
+
throw new Error("route_33 auth.set failed");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function createTuiAdapter(api) {
|
|
428
|
+
return {
|
|
429
|
+
ui: createUi(api, PROJECT_COPY),
|
|
430
|
+
globalUi: createUi(api, GLOBAL_COPY),
|
|
431
|
+
keyStore: {
|
|
432
|
+
// The workspace file is the only store needed: the live-auth fetch in
|
|
433
|
+
// the provider options re-resolves it per request, so the key applies
|
|
434
|
+
// immediately — even in the session that is open right now.
|
|
435
|
+
save: async (key) => {
|
|
436
|
+
writeWorkspaceKey(process.cwd(), key);
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
globalStore: createGlobalStore(api),
|
|
440
|
+
kv: api.kv,
|
|
441
|
+
waitUntilReady: () => waitUntilReady(api)
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/tui/index.ts
|
|
446
|
+
async function activate(api) {
|
|
447
|
+
const { ui, keyStore, globalUi, globalStore, kv, waitUntilReady: waitUntilReady2 } = createTuiAdapter(api);
|
|
448
|
+
await runActivation({
|
|
449
|
+
ui,
|
|
450
|
+
keyStore,
|
|
451
|
+
dismissal: createDismissalStore(kv),
|
|
452
|
+
globalUi,
|
|
453
|
+
globalStore,
|
|
454
|
+
globalDismissal: createDismissalStore(
|
|
455
|
+
kv,
|
|
456
|
+
GLOBAL_TOKEN_PROMPT_DISMISSED_AT_KEY
|
|
457
|
+
),
|
|
458
|
+
resolveKey: () => resolveKey(api.state.config, process.env, process.cwd()),
|
|
459
|
+
// api.state.vcs populates asynchronously and arrives too late in real
|
|
460
|
+
// workspaces; a direct .git walk-up from the launch cwd is deterministic.
|
|
461
|
+
isGitWorkspace: isInsideGitWorktree(process.cwd()),
|
|
462
|
+
waitUntilReady: waitUntilReady2
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
var Route33TuiPlugin = {
|
|
466
|
+
id: "route33",
|
|
467
|
+
tui: async (api) => {
|
|
468
|
+
if (process.env.ROUTE33_TUI_PROMPT === "0") return;
|
|
469
|
+
void activate(api).catch(() => {
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
var tui_default = Route33TuiPlugin;
|
|
474
|
+
export {
|
|
475
|
+
tui_default as default
|
|
476
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@route_33/opencode",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"description": "opencode provider plugin for the route33.ai AI gateway — key-scoped model discovery, cost/limit enrichment, auth, and the key-setup wizard.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Cronos-AI-Lab/harness-bench.git",
|
|
10
|
+
"directory": "packages/integrations/opencode-route33"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"opencode",
|
|
14
|
+
"opencode-plugin",
|
|
15
|
+
"route33"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./tui": {
|
|
23
|
+
"types": "./dist/tui/index.d.ts",
|
|
24
|
+
"import": "./dist/tui/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@opencode-ai/plugin": ">=1.17.0 <2.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"jsonc-parser": "^3.3.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@opencode-ai/plugin": "^1.17.12",
|
|
43
|
+
"@types/node": "^22.10.7",
|
|
44
|
+
"tsup": "^8.3.5",
|
|
45
|
+
"typescript": "^5.7.3",
|
|
46
|
+
"vitest": "^3.0.5"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"prepack": "bun run build",
|
|
51
|
+
"bundle": "tsup",
|
|
52
|
+
"bundle:watch": "tsup --watch",
|
|
53
|
+
"verify:bundle": "bun run scripts/verify-bundle.ts",
|
|
54
|
+
"test": "vitest run && bun run verify:bundle",
|
|
55
|
+
"typecheck": "tsc --noEmit"
|
|
56
|
+
}
|
|
57
|
+
}
|