@jacobbd/relay-ai 0.2.2 → 0.2.3

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/AGENTS.md CHANGED
@@ -150,9 +150,9 @@ In all cases `process.env['OPENCODE_API_KEY']` is set immediately so the key is
150
150
  - `--restore` globs `models-*.json` (CLI) and `app-models-*.json` (App); the new files are `models-favorites.json` and `app-models-favorites.json`.
151
151
  - Zen/Go favorites are skipped in Codex (use Claude or Desktop gateway).
152
152
 
153
- ## Release status (v0.2.0)
153
+ ## Release status (v0.2.3)
154
154
 
155
- Current version is **v0.2.0** — native provider registry (`relay-ai providers`), registry-first launch, import key validation, and Phase 1.3 dead-code cleanup. Prior work includes the SDK adapter from the pre-0.2 lineage (see CHANGELOG `[0.3.0]`).
155
+ Current version is **v0.2.3** — native provider registry, agent boot flags (`--provider` / `--model`), `relay-ai --ai`, Codex favorites catalog, and OpenCode cloud provider setup in `relay-ai providers add`.
156
156
 
157
157
  **Known limitations (by design):**
158
158
  - Cost display in Codex is always inaccurate for non-Anthropic models.
package/CHANGELOG.md CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.3] - 2026-06-15
6
+
5
7
  ### Added
6
8
  - **Cross-surface favorites catalog** — Codex CLI (`relay-ai codex`) and Codex App (`relay-ai codex-app`) use `relay-ai models` favorites for mid-session model switching.
7
9
  - Server wizard defaults to favorites-only catalog when favorites are configured.
8
10
  - Provider labels in gateway model picker when using favorites-only mode.
11
+ - **OpenCode Zen / Go in `relay-ai providers add`** — dedicated cloud API key path; template search for "opencode" offers the correct add flow.
9
12
 
10
13
  ## [0.2.0] - 2026-06-10
11
14
 
package/dist/cli.js CHANGED
@@ -76,7 +76,7 @@ function classifyModelFormat(modelId, providerNpm) {
76
76
  if (lower.startsWith("gemini-")) return "unsupported";
77
77
  return "openai";
78
78
  }
79
- var VERSION = "0.2.2";
79
+ var VERSION = "0.2.3";
80
80
 
81
81
  // src/provider-factory.ts
82
82
  var RESPONSES_ONLY_PREFIXES = [
@@ -3099,6 +3099,17 @@ function zenRegistryStub(subscriptionFilter) {
3099
3099
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
3100
3100
  };
3101
3101
  }
3102
+ function goRegistryStub() {
3103
+ return {
3104
+ id: "go",
3105
+ templateId: "go",
3106
+ name: "OpenCode Go",
3107
+ enabled: true,
3108
+ authRef: "keyring:global:opencode",
3109
+ api: {},
3110
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
3111
+ };
3112
+ }
3102
3113
 
3103
3114
  // src/registry/convert.ts
3104
3115
  function modelToCached(model) {
@@ -3836,6 +3847,14 @@ function filterTemplates(templates, query) {
3836
3847
  (t) => t.id.toLowerCase().includes(q) || t.name.toLowerCase().includes(q) || t.npm.toLowerCase().includes(q)
3837
3848
  );
3838
3849
  }
3850
+ function matchesOpenCodeCloudSearch(query) {
3851
+ const q = query.trim().toLowerCase();
3852
+ if (!q) return false;
3853
+ if (q.includes("opencode") || q.includes("open code")) return true;
3854
+ if (q === "zen" || q === "go") return true;
3855
+ if (q.includes("zen ") || q.startsWith("zen/") || q.endsWith(" zen")) return true;
3856
+ return false;
3857
+ }
3839
3858
 
3840
3859
  // src/registry/resolve-template.ts
3841
3860
  var TEMPLATE_ID_ALIASES = {
@@ -7071,6 +7090,24 @@ async function removeProviderFromRegistry(id, opts) {
7071
7090
  credentialDeleted
7072
7091
  };
7073
7092
  }
7093
+ function addZenRegistryStub(opts) {
7094
+ const registry = loadRegistry();
7095
+ if (registry.providers.some((p19) => p19.id === "zen")) {
7096
+ return { added: false, reason: "OpenCode Zen is already configured." };
7097
+ }
7098
+ registry.providers.push(zenRegistryStub(opts?.subscriptionFilter));
7099
+ saveRegistry(registry);
7100
+ return { added: true };
7101
+ }
7102
+ function addGoRegistryStub() {
7103
+ const registry = loadRegistry();
7104
+ if (registry.providers.some((p19) => p19.id === "go")) {
7105
+ return { added: false, reason: "OpenCode Go is already configured." };
7106
+ }
7107
+ registry.providers.push(goRegistryStub());
7108
+ saveRegistry(registry);
7109
+ return { added: true };
7110
+ }
7074
7111
  function toggleProviderEnabled(id) {
7075
7112
  const registry = loadRegistry();
7076
7113
  const provider = registry.providers.find((p19) => p19.id === id);
@@ -7687,8 +7724,24 @@ async function pickTemplateFromCatalog() {
7687
7724
  placeholder: "e.g. groq, mistral, openrouter"
7688
7725
  });
7689
7726
  if (p10.isCancel(searchInput)) continue;
7690
- const matched = filterTemplates(templates, String(searchInput));
7727
+ const query = String(searchInput);
7728
+ const matched = filterTemplates(templates, query);
7691
7729
  if (matched.length === 0) {
7730
+ if (matchesOpenCodeCloudSearch(query)) {
7731
+ p10.log.info(
7732
+ "OpenCode Zen / Go are cloud backends \u2014 not in the Groq/Mistral template list."
7733
+ );
7734
+ const addCloud = await p10.confirm({
7735
+ message: "Add OpenCode Zen / Go with your OpenCode API key?",
7736
+ initialValue: true
7737
+ });
7738
+ if (p10.isCancel(addCloud)) continue;
7739
+ if (addCloud) {
7740
+ await runOpenCodeCloudAddFlow();
7741
+ return null;
7742
+ }
7743
+ continue;
7744
+ }
7692
7745
  p10.log.warn("No providers match \u2014 try a different search");
7693
7746
  continue;
7694
7747
  }
@@ -7758,6 +7811,66 @@ async function runTemplateAddFlow() {
7758
7811
  logConnected(template.name, result.modelCount ?? 0);
7759
7812
  return 0;
7760
7813
  }
7814
+ async function runOpenCodeCloudAddFlow() {
7815
+ const registry = loadRegistry();
7816
+ const hasZen = registry.providers.some((pr) => pr.id === "zen");
7817
+ const hasGo = registry.providers.some((pr) => pr.id === "go");
7818
+ if (hasZen && hasGo) {
7819
+ p10.log.info("OpenCode Zen and Go are already configured.");
7820
+ return 0;
7821
+ }
7822
+ printPanel(pc10.cyan("OpenCode cloud"), [
7823
+ `${pc10.white("Get an API key at:")} ${fmtUrl("https://opencode.ai/auth")}`,
7824
+ `${pc10.dim("Uses OpenCode Zen / Go cloud models \u2014 not the same as importing from the OpenCode CLI.")}`
7825
+ ]);
7826
+ const existingKey = await readGlobalOpencodeCredential();
7827
+ if (!existingKey) {
7828
+ const apiKey = await resolveOrCollectApiKey(false, false);
7829
+ if (!apiKey) {
7830
+ p10.cancel("Cancelled.");
7831
+ return 0;
7832
+ }
7833
+ }
7834
+ await migrateGlobalOpencodeCredential();
7835
+ let pick;
7836
+ if (!hasZen && !hasGo) {
7837
+ const choice = await p10.select({
7838
+ message: "Which OpenCode cloud backend?",
7839
+ options: [
7840
+ { value: "zen", label: "OpenCode Zen", hint: "Free + paid models" },
7841
+ { value: "go", label: "OpenCode Go", hint: "Paid models" },
7842
+ { value: "both", label: "Both Zen and Go", hint: "Same API key" }
7843
+ ]
7844
+ });
7845
+ if (p10.isCancel(choice)) {
7846
+ p10.cancel("Cancelled.");
7847
+ return 0;
7848
+ }
7849
+ pick = choice;
7850
+ } else if (!hasZen) {
7851
+ pick = "zen";
7852
+ } else {
7853
+ pick = "go";
7854
+ }
7855
+ const added = [];
7856
+ if ((pick === "zen" || pick === "both") && !hasZen) {
7857
+ const zen = addZenRegistryStub();
7858
+ if (zen.added) added.push("OpenCode Zen");
7859
+ else if (zen.reason) p10.log.warn(zen.reason);
7860
+ }
7861
+ if ((pick === "go" || pick === "both") && !hasGo) {
7862
+ const go = addGoRegistryStub();
7863
+ if (go.added) added.push("OpenCode Go");
7864
+ else if (go.reason) p10.log.warn(go.reason);
7865
+ }
7866
+ if (added.length === 0) {
7867
+ p10.log.info("Nothing new to add.");
7868
+ return 0;
7869
+ }
7870
+ p10.log.success(`Added ${added.join(" and ")}.`);
7871
+ p10.log.info("Run relay-ai providers refresh-models to cache model lists.");
7872
+ return 0;
7873
+ }
7761
7874
  async function runCustomEndpointAddFlow() {
7762
7875
  const kindChoice = await p10.select({
7763
7876
  message: "Custom server type",
@@ -7819,7 +7932,16 @@ async function runProvidersAdd() {
7819
7932
  const registry = loadRegistry();
7820
7933
  const hasOpencode = findOpencodeBinary() !== null;
7821
7934
  const options = [
7822
- { value: "import", label: "Bring settings from OpenCode", hint: hasOpencode ? "One-time import" : "Requires OpenCode CLI" }
7935
+ {
7936
+ value: "opencode-cloud",
7937
+ label: "OpenCode Zen / Go (cloud API key)",
7938
+ hint: "Key from opencode.ai/auth \u2014 not in the Groq/Mistral list"
7939
+ },
7940
+ {
7941
+ value: "import",
7942
+ label: "Bring settings from OpenCode CLI",
7943
+ hint: hasOpencode ? "Import Groq, OpenAI, etc. from your OpenCode config" : "Requires OpenCode CLI"
7944
+ }
7823
7945
  ];
7824
7946
  const addableTemplates = listAddableTemplates(registry.providers.map((p19) => p19.id));
7825
7947
  if (addableTemplates.length > 0) {
@@ -7839,6 +7961,9 @@ async function runProvidersAdd() {
7839
7961
  p10.cancel("Cancelled.");
7840
7962
  return 0;
7841
7963
  }
7964
+ if (choice === "opencode-cloud") {
7965
+ return runOpenCodeCloudAddFlow();
7966
+ }
7842
7967
  if (choice === "import") {
7843
7968
  if (!hasOpencode) {
7844
7969
  p10.log.error("OpenCode CLI not found. Install from https://opencode.ai");
@@ -7956,7 +8081,7 @@ async function runProvidersHub() {
7956
8081
  options.push({ value: "refresh-all", label: "\u21BA Refresh all models", hint: "Update model lists for all providers" });
7957
8082
  }
7958
8083
  if (hasOpencode) {
7959
- options.push({ value: "import", label: "\u2192 Bring settings from OpenCode", hint: "One-time import" });
8084
+ options.push({ value: "import", label: "\u2192 Bring settings from OpenCode CLI", hint: "One-time import" });
7960
8085
  }
7961
8086
  options.push({ value: "done", label: "Done", hint: "" });
7962
8087
  const choice = await p10.select({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacobbd/relay-ai",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },