armin-opencode 0.1.3 → 0.1.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/bin/armin.js CHANGED
@@ -95,25 +95,41 @@ function buildFromSource() {
95
95
  }
96
96
 
97
97
  let pendingTypesafeKey = null;
98
+ let pendingOpenrouterKey = null;
99
+ let pendingJevProvider = null; // "openrouter" | "skip" | null (typesafe needs no config entry)
100
+
101
+ /** Ask which relay serves Jev and for the matching key (TTY only; values
102
+ * go into the opencode config — the plugin passes them to the sidecar).
103
+ * A provider detected from the environment skips the prompt entirely. */
104
+ async function askForExtractionSetup() {
105
+ if (!process.stdin.isTTY) return;
106
+ let provider;
107
+ if (process.env.ARMIN_JEV_PROVIDER) provider = process.env.ARMIN_JEV_PROVIDER;
108
+ else if (process.env.TYPESAFE_AI_API_KEY) provider = "typesafe";
109
+ else if (process.env.OPENROUTER_API_KEY) provider = "openrouter";
110
+ if (provider) return;
98
111
 
99
- /** Ask for the Typesafe key when nothing is configured (TTY only; the key
100
- * goes into the opencode config — the plugin passes it to the sidecar). */
101
- function askForTypesafeKey() {
102
- if (process.env.TYPESAFE_AI_API_KEY) return Promise.resolve();
103
- if (!process.stdin.isTTY) return Promise.resolve();
104
112
  const readline = require("readline");
105
113
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
106
- return new Promise((resolve) => {
107
- rl.question(
108
- "TypeSafe System One API key (jev extraction is the default)paste, or Enter to skip: ",
109
- (answer) => {
110
- rl.close();
111
- const key = (answer || "").trim();
112
- if (key) pendingTypesafeKey = key;
113
- resolve();
114
- },
115
- );
116
- });
114
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
115
+ try {
116
+ console.log("\nExtraction backend (jev is the default — verbatim nodes, no generative LLM):");
117
+ console.log(" 1) TypeSafe System One (typesafe.ai) [default]");
118
+ console.log(" 2) Jev via OpenRouter (openrouter.ai — same API, OpenRouter billing)");
119
+ console.log(" 3) skip (LLM fallback / deterministic-only)");
120
+ const choice = ((await ask("Choose [1/2/3, Enter=1]: ")) || "1").trim();
121
+ if (choice === "3") { pendingJevProvider = "skip"; return; }
122
+ if (choice === "2") {
123
+ pendingJevProvider = "openrouter";
124
+ const key = ((await ask("OpenRouter API key (openrouter.ai/settings/keys) — paste, or Enter to skip: ")) || "").trim();
125
+ if (key) pendingOpenrouterKey = key;
126
+ } else {
127
+ const key = ((await ask("TypeSafe System One API key — paste, or Enter to skip: ")) || "").trim();
128
+ if (key) pendingTypesafeKey = key;
129
+ }
130
+ } finally {
131
+ rl.close();
132
+ }
117
133
  }
118
134
 
119
135
  function registerPlugin() {
@@ -148,6 +164,17 @@ function registerPlugin() {
148
164
  data.armin = { ...(data.armin || {}), typesafeKey: pendingTypesafeKey };
149
165
  changed = true;
150
166
  }
167
+ if (pendingOpenrouterKey && !process.env.OPENROUTER_API_KEY) {
168
+ data.armin = { ...(data.armin || {}), openrouterKey: pendingOpenrouterKey };
169
+ changed = true;
170
+ }
171
+ // Persist the relay choice only when the user explicitly picked
172
+ // OpenRouter in the prompt (env-detected providers drive the sidecar
173
+ // via their env vars; writing nothing avoids overriding that later).
174
+ if (pendingJevProvider === "openrouter" && data.armin.jevProvider !== "openrouter") {
175
+ data.armin = { ...(data.armin || {}), jevProvider: "openrouter" };
176
+ changed = true;
177
+ }
151
178
  if (changed) {
152
179
  fs.mkdirSync(cfgDir, { recursive: true });
153
180
  fs.writeFileSync(cfgPath, JSON.stringify(data, null, 2));
@@ -174,24 +201,29 @@ async function install() {
174
201
  }
175
202
  }
176
203
  }
177
- await askForTypesafeKey();
204
+ await askForExtractionSetup();
178
205
  registerPlugin();
179
206
  const tsKey = process.env.TYPESAFE_AI_API_KEY || pendingTypesafeKey;
207
+ const orKey = process.env.OPENROUTER_API_KEY || pendingOpenrouterKey;
180
208
  const llmKey = process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
181
209
  if (tsKey) {
182
210
  console.log("extraction: jev (TypeSafe System One) — ready");
211
+ } else if (orKey) {
212
+ console.log("extraction: jev via OpenRouter — ready");
183
213
  } else if (llmKey) {
184
214
  console.log(
185
- "extraction: LLM fallback — jev is the default but no Typesafe key is set.\n" +
186
- " Get a key at typesafe.ai and export TYPESAFE_AI_API_KEY (or put\n" +
187
- ' "armin": { "typesafeKey": "..." } in your opencode config).',
215
+ "extraction: LLM fallback — jev is the default but no key for it is set.\n" +
216
+ " Set TYPESAFE_AI_API_KEY (typesafe.ai) or OPENROUTER_API_KEY (openrouter.ai),\n" +
217
+ ' or put "armin": { "typesafeKey": "..." } / { "jevProvider": "openrouter",\n' +
218
+ ' "openrouterKey": "..." } in your opencode config.',
188
219
  );
189
220
  } else {
190
221
  console.log(
191
222
  "extraction: NONE (deterministic-only) — import, capture and unverified-edit\n" +
192
223
  " warnings work; prose extraction does not.\n" +
193
- " Set TYPESAFE_AI_API_KEY (typesafe.ai) for jev extraction — the default —\n" +
194
- " or ANTHROPIC_API_KEY / OPENAI_API_KEY for LLM fallback.",
224
+ " Set TYPESAFE_AI_API_KEY (typesafe.ai) or OPENROUTER_API_KEY (openrouter.ai)\n" +
225
+ " for jev extraction — the default — or ANTHROPIC_API_KEY / OPENAI_API_KEY\n" +
226
+ " for LLM fallback.",
195
227
  );
196
228
  }
197
229
  console.log(`
@@ -201,7 +233,12 @@ Enable ARMIN per environment:
201
233
  export ARMIN_ENABLED=1
202
234
 
203
235
  Optional:
204
- ARMIN_EXTRACTION_MODE=jev TypeSafe System One extraction
236
+ ARMIN_EXTRACTION_MODE=jev System One extraction (default; needs
237
+ TYPESAFE_AI_API_KEY or OPENROUTER_API_KEY)
238
+ ARMIN_JEV_PROVIDER=openrouter route jev via OpenRouter (auto-detected
239
+ when only that key is set)
240
+ ARMIN_JEV_MODEL=<model> jev model override (default jev-1.13.0,
241
+ or jev-latest via OpenRouter)
205
242
  ARMIN_MODEL=<model> extraction model override
206
243
  ARMIN_DEBUG=1 verbose logging + /ui URL
207
244
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "armin-opencode",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "ARMIN — queryable decision memory for AI coding agents. Installs the armin-engine sidecar and registers the opencode plugin.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Bastian Seifert",
package/plugins/armin.ts CHANGED
@@ -18,6 +18,11 @@
18
18
  * "apiKey": "sk-...", // sent to the sidecar only; env key wins if both set
19
19
  * "typesafeKey": "...", // Typesafe System One key (jev is the
20
20
  * // default extraction mode); env wins
21
+ * "jevProvider": "typesafe", // typesafe | openrouter — which relay serves
22
+ * // the System One API (openrouter bills
23
+ * // your OpenRouter account); env wins
24
+ * "openrouterKey": "sk-or-...", // OpenRouter key, used when jevProvider is
25
+ * // "openrouter"; env wins
21
26
  * "model": "session", // "session" = follow the live session model
22
27
  * // "small" = reuse opencode's small_model setting
23
28
  * // any string = fixed extraction model
@@ -496,6 +501,12 @@ const ArminPlugin: Plugin = async (ctx) => {
496
501
  // passed to the sidecar only; a key already in the environment wins.
497
502
  const typesafeKey = cfgStr("typesafeKey", "TYPESAFE_AI_API_KEY")
498
503
  if (typesafeKey) spawnEnv.TYPESAFE_AI_API_KEY = typesafeKey
504
+ // Jev relay: "typesafe" (api.typesafe.ai, default) or "openrouter"
505
+ // (openrouter.ai/api — same System One API, OpenRouter billing).
506
+ const jevProvider = cfgStr("jevProvider", "ARMIN_JEV_PROVIDER")
507
+ if (jevProvider) spawnEnv.ARMIN_JEV_PROVIDER = jevProvider
508
+ const openrouterKey = cfgStr("openrouterKey", "OPENROUTER_API_KEY")
509
+ if (openrouterKey) spawnEnv.OPENROUTER_API_KEY = openrouterKey
499
510
  // API key: config value is passed to the sidecar only; a key already in
500
511
  // the environment wins (no override).
501
512
  if (typeof armin.apiKey === "string" && armin.apiKey) {
@@ -529,21 +540,25 @@ const ArminPlugin: Plugin = async (ctx) => {
529
540
  // between "memory works" and a silent deterministic downgrade.
530
541
  void (async () => {
531
542
  try {
532
- const health = await api.get<{ extraction: string }>("/health")
543
+ const health = await api.get<{ extraction: string; jev_provider?: string }>("/health")
533
544
  const mode = health?.extraction ?? "unknown"
534
545
  if (mode === "jev") {
535
- log(`extraction: jev (TypeSafe System One) — ready`)
546
+ const via = health?.jev_provider ?? "typesafe"
547
+ log(`extraction: jev (System One via ${via}) — ready`)
536
548
  } else if (mode === "llm") {
537
549
  log(
538
- `extraction: llm (fallback) — jev is the default but no Typesafe key is ` +
539
- `configured. Set TYPESAFE_AI_API_KEY in your environment or ` +
540
- `"armin": { "typesafeKey": "..." } in your opencode config.`,
550
+ `extraction: llm (fallback) — jev is the default but no key is ` +
551
+ `configured. Set TYPESAFE_AI_API_KEY (typesafe.ai) or OPENROUTER_API_KEY ` +
552
+ `(openrouter.ai) in your environment, or \`"armin": { "typesafeKey": "..." }\`` +
553
+ ` / \`{ "jevProvider": "openrouter", "openrouterKey": "..." }\` in your ` +
554
+ `opencode config.`,
541
555
  )
542
556
  } else {
543
557
  log(
544
558
  `extraction: deterministic-only — no API keys found. Import and ` +
545
559
  `unverified-edit warnings work; prose extraction does not. Set ` +
546
- `TYPESAFE_AI_API_KEY (or ANTHROPIC/OPENAI_API_KEY) to enable it.`,
560
+ `TYPESAFE_AI_API_KEY or OPENROUTER_API_KEY (or ANTHROPIC/OPENAI_API_KEY) ` +
561
+ `to enable it.`,
547
562
  )
548
563
  }
549
564
  } catch {