@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 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
- if (await ui.askChoice() === "skip") {
215
- dismissal.dismiss(now());
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 createDismissalStore(kv, key = PROJECT_KEY_PROMPT_DISMISSED_AT_KEY) {
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) => isWithinCooldown(readDismissedAt(kv, key), 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.set(key, now);
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((resolve3) => {
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
- resolve3(choice);
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(option.value === "enter" ? "enter" : "skip");
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((resolve3) => {
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
- resolve3(value);
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((resolve3) => {
436
+ return new Promise((resolve4) => {
379
437
  if (api.state.ready) {
380
- resolve3(true);
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
- resolve3(false);
444
+ resolve4(false);
387
445
  return;
388
446
  }
389
447
  if (api.state.ready) {
390
448
  clearInterval(timer);
391
- resolve3(true);
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(kv),
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.2.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.",