@jeffreyjyz/opencode-command-code 0.3.2 → 0.3.4

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/dist/index.js CHANGED
@@ -3041,6 +3041,7 @@ async function loadModels(key) {
3041
3041
  }
3042
3042
  }
3043
3043
  let plan = { planId: "", purchasedCredits: 0, freeCredits: 0 };
3044
+ let gated = true;
3044
3045
  try {
3045
3046
  const [sub, cr] = await Promise.all([subscriptions(key), credits(key)]);
3046
3047
  plan = {
@@ -3048,10 +3049,12 @@ async function loadModels(key) {
3048
3049
  purchasedCredits: cr.credits.purchasedCredits ?? 0,
3049
3050
  freeCredits: cr.credits.freeCredits ?? 0
3050
3051
  };
3051
- } catch {}
3052
+ } catch {
3053
+ gated = false;
3054
+ }
3052
3055
  const allowed = models.filter((m) => evaluateModelAccess(m.id, plan).allowed);
3053
3056
  const { claude, open } = splitModels(allowed);
3054
- return { claude, open };
3057
+ return { claude, open, gated };
3055
3058
  }
3056
3059
 
3057
3060
  // src/provider/stream.ts
@@ -3702,21 +3705,53 @@ function createCommandCode(options = {}, images = () => false) {
3702
3705
  return { languageModel: model };
3703
3706
  }
3704
3707
 
3705
- // src/startupLog.ts
3706
- import { mkdir, appendFile } from "fs/promises";
3708
+ // src/modelsCache.ts
3709
+ import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
3707
3710
  import { homedir as homedir2 } from "os";
3708
3711
  import { join } from "path";
3712
+ function cachePath() {
3713
+ const base = process.env.XDG_CACHE_HOME ?? join(homedir2(), ".cache");
3714
+ return join(base, "command-code", "models.json");
3715
+ }
3716
+ async function readModelsCache() {
3717
+ try {
3718
+ const parsed = JSON.parse(await readFile2(cachePath(), "utf8"));
3719
+ if (!Array.isArray(parsed.claude) || !Array.isArray(parsed.open))
3720
+ return;
3721
+ return parsed;
3722
+ } catch {
3723
+ return;
3724
+ }
3725
+ }
3726
+ async function writeModelsCache(cache) {
3727
+ try {
3728
+ const path = cachePath();
3729
+ await mkdir(join(path, ".."), { recursive: true });
3730
+ await writeFile(path, JSON.stringify(cache));
3731
+ } catch {}
3732
+ }
3733
+ function idsDiffer(a, b) {
3734
+ if (a.length !== b.length)
3735
+ return true;
3736
+ const left = new Set(a.map((m) => m.id));
3737
+ return b.some((m) => !left.has(m.id));
3738
+ }
3739
+
3740
+ // src/startupLog.ts
3741
+ import { mkdir as mkdir2, appendFile } from "fs/promises";
3742
+ import { homedir as homedir3 } from "os";
3743
+ import { join as join2 } from "path";
3709
3744
  function setupTimingLine(t) {
3710
3745
  return `setup: key=${t.key}ms register=${t.register}ms connection=${t.connection}ms refresh=${t.refresh}ms models=${t.models}`;
3711
3746
  }
3712
3747
  function startupLogPath() {
3713
- const base = process.env.XDG_CACHE_HOME ?? join(homedir2(), ".cache");
3714
- return join(base, "command-code", "startup.log");
3748
+ const base = process.env.XDG_CACHE_HOME ?? join2(homedir3(), ".cache");
3749
+ return join2(base, "command-code", "startup.log");
3715
3750
  }
3716
3751
  async function writeStartupLine(line) {
3717
3752
  try {
3718
3753
  const path = startupLogPath();
3719
- await mkdir(join(path, ".."), { recursive: true });
3754
+ await mkdir2(join2(path, ".."), { recursive: true });
3720
3755
  await appendFile(path, `[${new Date().toISOString()}] ${line}
3721
3756
  `);
3722
3757
  } catch {}
@@ -3839,6 +3874,11 @@ var commandCodeV2 = {
3839
3874
  }, { providerID: lane.id });
3840
3875
  }
3841
3876
  const seed = staticSeedModels();
3877
+ const cached = await readModelsCache();
3878
+ if (cached) {
3879
+ seed["command-code-anthropic"] = mergeModels(seed["command-code-anthropic"], cached.claude.map((m) => toV2Model(m, LANES[0])));
3880
+ seed["command-code-openai"] = mergeModels(seed["command-code-openai"], cached.open.map((m) => toV2Model(m, LANES[1])));
3881
+ }
3842
3882
  await ctx.provider.transform((editor) => {
3843
3883
  for (const lane of LANES) {
3844
3884
  editor.update(lane.id, (provider) => {
@@ -3901,6 +3941,16 @@ var commandCodeV2 = {
3901
3941
  }
3902
3942
  const controller = new AbortController;
3903
3943
  let refreshing = false;
3944
+ let appliedClaude = cached?.claude ?? [];
3945
+ let appliedOpen = cached?.open ?? [];
3946
+ let canTransform = false;
3947
+ const logTimings = (refreshMs, modelCount) => void writeStartupLine(setupTimingLine({
3948
+ key: keyMs,
3949
+ register: registerMs,
3950
+ connection: connectionMs,
3951
+ refresh: refreshMs,
3952
+ models: modelCount
3953
+ }));
3904
3954
  const refresh = async () => {
3905
3955
  if (refreshing || controller.signal.aborted)
3906
3956
  return;
@@ -3913,27 +3963,35 @@ var commandCodeV2 = {
3913
3963
  const split = await loadModels(key);
3914
3964
  if (controller.signal.aborted)
3915
3965
  return;
3966
+ const modelCount = split.claude.length + split.open.length;
3967
+ if (!split.gated) {
3968
+ logTimings(Date.now() - refreshStart, modelCount);
3969
+ return;
3970
+ }
3971
+ writeModelsCache({ fetchedAt: Date.now(), claude: split.claude, open: split.open });
3972
+ if (!canTransform || !idsDiffer(split.claude, appliedClaude) && !idsDiffer(split.open, appliedOpen)) {
3973
+ logTimings(Date.now() - refreshStart, modelCount);
3974
+ return;
3975
+ }
3916
3976
  await ctx.provider.transform((editor) => {
3917
3977
  editor.models.set("command-code-anthropic", mergeModels(seed["command-code-anthropic"], split.claude.map((m) => toV2Model(m, LANES[0]))));
3918
3978
  editor.models.set("command-code-openai", mergeModels(seed["command-code-openai"], split.open.map((m) => toV2Model(m, LANES[1]))));
3919
3979
  });
3920
- writeStartupLine(setupTimingLine({
3921
- key: keyMs,
3922
- register: registerMs,
3923
- connection: connectionMs,
3924
- refresh: Date.now() - refreshStart,
3925
- models: split.claude.length + split.open.length
3926
- }));
3980
+ appliedClaude = split.claude;
3981
+ appliedOpen = split.open;
3982
+ logTimings(Date.now() - refreshStart, modelCount);
3927
3983
  } catch (e) {
3928
3984
  console.warn("[command-code] live model list unavailable, keeping the snapshot:", e);
3929
3985
  } finally {
3930
3986
  refreshing = false;
3987
+ canTransform = true;
3931
3988
  }
3932
3989
  };
3933
- refresh();
3990
+ const firstRefresh = setTimeout(() => void refresh(), 3000);
3934
3991
  const timer = setInterval(() => void refresh(), 30 * 60 * 1000);
3935
3992
  return () => {
3936
3993
  controller.abort();
3994
+ clearTimeout(firstRefresh);
3937
3995
  clearInterval(timer);
3938
3996
  };
3939
3997
  }
@@ -3941,7 +3999,7 @@ var commandCodeV2 = {
3941
3999
  // package.json
3942
4000
  var package_default = {
3943
4001
  name: "@jeffreyjyz/opencode-command-code",
3944
- version: "0.3.2",
4002
+ version: "0.3.4",
3945
4003
  description: "Command Code provider + usage dashboard for opencode: two providers (Anthropic wire for Claude, OpenAI wire for everything else) against api.commandcode.ai, live model list, plan gating, /cmd-usage. Requires the cmduse CLI for usage rendering.",
3946
4004
  type: "module",
3947
4005
  main: "dist/index.js",
package/dist/models.d.ts CHANGED
@@ -6,6 +6,10 @@ export type CmdModel = {
6
6
  export type ModelSplit = {
7
7
  claude: CmdModel[];
8
8
  open: CmdModel[];
9
+ /** True when the billing lookup succeeded and the plan gate really ran. A
10
+ * false here means "show everything", which is the API-enforced fallback —
11
+ * callers must not treat that as a new model list. */
12
+ gated: boolean;
9
13
  };
10
14
  export declare function isClaude(id: string): boolean;
11
15
  export declare function splitModels(models: CmdModel[]): {
@@ -0,0 +1,18 @@
1
+ export interface CachedModel {
2
+ id: string;
3
+ name: string;
4
+ contextLength: number;
5
+ }
6
+ export interface ModelsCache {
7
+ fetchedAt: number;
8
+ claude: CachedModel[];
9
+ open: CachedModel[];
10
+ }
11
+ /** Never throws: a missing or corrupt cache is simply "no cache". */
12
+ export declare function readModelsCache(): Promise<ModelsCache | undefined>;
13
+ /** Best-effort write; diagnostics must never break setup. */
14
+ export declare function writeModelsCache(cache: ModelsCache): Promise<void>;
15
+ /** True when two lists differ by ids (the only thing a picker shows). */
16
+ export declare function idsDiffer(a: readonly CachedModel[], b: readonly CachedModel[]): boolean;
17
+ /** Path for tests and diagnostics. */
18
+ export declare function modelsCachePath(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffreyjyz/opencode-command-code",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Command Code provider + usage dashboard for opencode: two providers (Anthropic wire for Claude, OpenAI wire for everything else) against api.commandcode.ai, live model list, plan gating, /cmd-usage. Requires the cmduse CLI for usage rendering.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",