@route_33/opencode 0.2.0 → 0.3.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 +1 -1
- package/dist/tui/index.js +80 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ This plugin never writes your config: opencode has no TUI-plugin auto-discovery,
|
|
|
28
28
|
|
|
29
29
|
## Auth
|
|
30
30
|
|
|
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.
|
|
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). Skipping the project-key wizard snoozes it for 7 days — "Skip" for that workspace only, "Skip for all workspaces" everywhere (both live in opencode's TUI kv store; remove `project-key-prompt.dismissed-at*` from `~/.local/state/opencode/kv.json` to re-arm sooner). `ROUTE33_BASE_URL` overrides the default gateway URL; `ROUTE33_TUI_PROMPT=0` disables the wizard.
|
|
32
32
|
|
|
33
33
|
## License
|
|
34
34
|
|
package/dist/tui/index.js
CHANGED
|
@@ -155,7 +155,9 @@ var KEY_SELECT_TITLE = "route33.ai project key";
|
|
|
155
155
|
var KEY_SELECT_ENTER_LABEL = "Enter a project key";
|
|
156
156
|
var KEY_SELECT_ENTER_DESCRIPTION = "Per workspace on purpose \u2014 one shared key would lose the per-project metrics";
|
|
157
157
|
var KEY_SELECT_SKIP_LABEL = "Skip";
|
|
158
|
-
var KEY_SELECT_SKIP_DESCRIPTION = "Ask again in 7 days";
|
|
158
|
+
var KEY_SELECT_SKIP_DESCRIPTION = "Ask again in 7 days \u2014 this workspace only";
|
|
159
|
+
var KEY_SELECT_SKIP_ALL_LABEL = "Skip for all workspaces";
|
|
160
|
+
var KEY_SELECT_SKIP_ALL_DESCRIPTION = "Ask again in 7 days \u2014 everywhere";
|
|
159
161
|
var KEY_SELECT_PLACEHOLDER = "No route33.ai key found for this workspace yet.";
|
|
160
162
|
var KEY_PROMPT_TITLE = "Paste your route33.ai project key";
|
|
161
163
|
var KEY_SAVED_MESSAGE = "route33.ai project key saved to .opencode/route33.key \u2014 live immediately, no restart needed.";
|
|
@@ -211,8 +213,10 @@ async function runProjectKeyPromptFlow({
|
|
|
211
213
|
dismissal,
|
|
212
214
|
now = Date.now
|
|
213
215
|
}) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
const choice = await ui.askChoice();
|
|
217
|
+
if (choice === "skip" || choice === "skip-all") {
|
|
218
|
+
if (choice === "skip-all") dismissal.dismissGlobally(now());
|
|
219
|
+
else dismissal.dismiss(now());
|
|
216
220
|
ui.close();
|
|
217
221
|
return "skipped";
|
|
218
222
|
}
|
|
@@ -262,6 +266,9 @@ async function runActivation(deps) {
|
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
268
|
|
|
269
|
+
// src/tui/dismissal-store.ts
|
|
270
|
+
import { resolve as resolve3 } from "path";
|
|
271
|
+
|
|
265
272
|
// src/tui/dismissal-cooldown.ts
|
|
266
273
|
var DISMISSAL_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
267
274
|
function isWithinCooldown(dismissedAt, now) {
|
|
@@ -279,15 +286,53 @@ function readDismissedAt(kv, key) {
|
|
|
279
286
|
return void 0;
|
|
280
287
|
}
|
|
281
288
|
}
|
|
282
|
-
function
|
|
289
|
+
function readByWorkspace(kv, key) {
|
|
290
|
+
try {
|
|
291
|
+
const value = kv.get(key);
|
|
292
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
293
|
+
return {};
|
|
294
|
+
}
|
|
295
|
+
const entries = {};
|
|
296
|
+
for (const [path5, at] of Object.entries(value)) {
|
|
297
|
+
if (typeof at === "number" && Number.isFinite(at)) entries[path5] = at;
|
|
298
|
+
}
|
|
299
|
+
return entries;
|
|
300
|
+
} catch {
|
|
301
|
+
return {};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function createDismissalStore(kv, key = PROJECT_KEY_PROMPT_DISMISSED_AT_KEY, worktree) {
|
|
305
|
+
const scoped = worktree !== void 0 ? resolve3(worktree) : void 0;
|
|
306
|
+
const byWorkspaceKey = `${key}.by-workspace`;
|
|
307
|
+
const dismissGlobally = (now) => {
|
|
308
|
+
try {
|
|
309
|
+
kv.set(key, now);
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
};
|
|
283
313
|
return {
|
|
284
|
-
isDismissed: (now) =>
|
|
314
|
+
isDismissed: (now) => {
|
|
315
|
+
if (isWithinCooldown(readDismissedAt(kv, key), now)) return true;
|
|
316
|
+
if (scoped === void 0) return false;
|
|
317
|
+
return isWithinCooldown(readByWorkspace(kv, byWorkspaceKey)[scoped], now);
|
|
318
|
+
},
|
|
285
319
|
dismiss: (now) => {
|
|
320
|
+
if (scoped === void 0) {
|
|
321
|
+
dismissGlobally(now);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
286
324
|
try {
|
|
287
|
-
kv
|
|
325
|
+
const entries = readByWorkspace(kv, byWorkspaceKey);
|
|
326
|
+
const pruned = {};
|
|
327
|
+
for (const [path5, at] of Object.entries(entries)) {
|
|
328
|
+
if (isWithinCooldown(at, now)) pruned[path5] = at;
|
|
329
|
+
}
|
|
330
|
+
pruned[scoped] = now;
|
|
331
|
+
kv.set(byWorkspaceKey, pruned);
|
|
288
332
|
} catch {
|
|
289
333
|
}
|
|
290
|
-
}
|
|
334
|
+
},
|
|
335
|
+
dismissGlobally
|
|
291
336
|
};
|
|
292
337
|
}
|
|
293
338
|
|
|
@@ -298,6 +343,8 @@ var PROJECT_COPY = {
|
|
|
298
343
|
enterDescription: KEY_SELECT_ENTER_DESCRIPTION,
|
|
299
344
|
skipLabel: KEY_SELECT_SKIP_LABEL,
|
|
300
345
|
skipDescription: KEY_SELECT_SKIP_DESCRIPTION,
|
|
346
|
+
skipAllLabel: KEY_SELECT_SKIP_ALL_LABEL,
|
|
347
|
+
skipAllDescription: KEY_SELECT_SKIP_ALL_DESCRIPTION,
|
|
301
348
|
selectPlaceholder: KEY_SELECT_PLACEHOLDER,
|
|
302
349
|
promptTitle: KEY_PROMPT_TITLE
|
|
303
350
|
};
|
|
@@ -314,12 +361,12 @@ var GLOBAL_COPY = {
|
|
|
314
361
|
// src/tui/tui-api-adapter.ts
|
|
315
362
|
var READY_POLL_INTERVAL_MS = 100;
|
|
316
363
|
function askChoice(api, copy) {
|
|
317
|
-
return new Promise((
|
|
364
|
+
return new Promise((resolve4) => {
|
|
318
365
|
let settled = false;
|
|
319
366
|
const settle = (choice) => {
|
|
320
367
|
if (settled) return;
|
|
321
368
|
settled = true;
|
|
322
|
-
|
|
369
|
+
resolve4(choice);
|
|
323
370
|
};
|
|
324
371
|
api.ui.dialog.replace(
|
|
325
372
|
() => api.ui.DialogSelect({
|
|
@@ -335,10 +382,21 @@ function askChoice(api, copy) {
|
|
|
335
382
|
title: copy.skipLabel,
|
|
336
383
|
value: "skip",
|
|
337
384
|
description: copy.skipDescription
|
|
338
|
-
}
|
|
385
|
+
},
|
|
386
|
+
// Only the project copy defines a skip-all scope; the global
|
|
387
|
+
// prompt's plain skip is global already.
|
|
388
|
+
...copy.skipAllLabel !== void 0 ? [
|
|
389
|
+
{
|
|
390
|
+
title: copy.skipAllLabel,
|
|
391
|
+
value: "skip-all",
|
|
392
|
+
description: copy.skipAllDescription ?? ""
|
|
393
|
+
}
|
|
394
|
+
] : []
|
|
339
395
|
],
|
|
340
396
|
onSelect: (option) => {
|
|
341
|
-
settle(
|
|
397
|
+
settle(
|
|
398
|
+
option.value === "enter" ? "enter" : option.value === "skip-all" ? "skip-all" : "skip"
|
|
399
|
+
);
|
|
342
400
|
}
|
|
343
401
|
}),
|
|
344
402
|
() => {
|
|
@@ -348,12 +406,12 @@ function askChoice(api, copy) {
|
|
|
348
406
|
});
|
|
349
407
|
}
|
|
350
408
|
function askKey(api, copy) {
|
|
351
|
-
return new Promise((
|
|
409
|
+
return new Promise((resolve4) => {
|
|
352
410
|
let settled = false;
|
|
353
411
|
const settle = (value) => {
|
|
354
412
|
if (settled) return;
|
|
355
413
|
settled = true;
|
|
356
|
-
|
|
414
|
+
resolve4(value);
|
|
357
415
|
};
|
|
358
416
|
api.ui.dialog.replace(
|
|
359
417
|
() => api.ui.DialogPrompt({
|
|
@@ -375,20 +433,20 @@ function askKey(api, copy) {
|
|
|
375
433
|
});
|
|
376
434
|
}
|
|
377
435
|
function waitUntilReady(api) {
|
|
378
|
-
return new Promise((
|
|
436
|
+
return new Promise((resolve4) => {
|
|
379
437
|
if (api.state.ready) {
|
|
380
|
-
|
|
438
|
+
resolve4(true);
|
|
381
439
|
return;
|
|
382
440
|
}
|
|
383
441
|
const timer = setInterval(() => {
|
|
384
442
|
if (api.lifecycle.signal.aborted) {
|
|
385
443
|
clearInterval(timer);
|
|
386
|
-
|
|
444
|
+
resolve4(false);
|
|
387
445
|
return;
|
|
388
446
|
}
|
|
389
447
|
if (api.state.ready) {
|
|
390
448
|
clearInterval(timer);
|
|
391
|
-
|
|
449
|
+
resolve4(true);
|
|
392
450
|
}
|
|
393
451
|
}, READY_POLL_INTERVAL_MS);
|
|
394
452
|
});
|
|
@@ -448,7 +506,11 @@ async function activate(api) {
|
|
|
448
506
|
await runActivation({
|
|
449
507
|
ui,
|
|
450
508
|
keyStore,
|
|
451
|
-
dismissal: createDismissalStore(
|
|
509
|
+
dismissal: createDismissalStore(
|
|
510
|
+
kv,
|
|
511
|
+
PROJECT_KEY_PROMPT_DISMISSED_AT_KEY,
|
|
512
|
+
process.cwd()
|
|
513
|
+
),
|
|
452
514
|
globalUi,
|
|
453
515
|
globalStore,
|
|
454
516
|
globalDismissal: createDismissalStore(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@route_33/opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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.",
|