armin-opencode 0.1.1 → 0.1.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/README.md +3 -0
- package/bin/armin.js +53 -1
- package/package.json +1 -1
- package/plugins/armin.ts +31 -0
package/README.md
CHANGED
|
@@ -8,6 +8,9 @@ npx armin-opencode install # or: npm i -g armin-opencode && armin install
|
|
|
8
8
|
export ARMIN_ENABLED=1
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Prefer a global install over one-off `npx` runs: the plugin registration
|
|
12
|
+
points at the package's install location, and `npx` caches are evicted.
|
|
13
|
+
|
|
11
14
|
`armin install` downloads the `armin-engine` sidecar binary for your platform
|
|
12
15
|
from the [GitHub releases](https://github.com/bastian-seifert/armin/releases)
|
|
13
16
|
and registers the plugin in your global opencode config. Enable it per
|
package/bin/armin.js
CHANGED
|
@@ -94,10 +94,35 @@ function buildFromSource() {
|
|
|
94
94
|
return true;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
let pendingTypesafeKey = null;
|
|
98
|
+
|
|
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
|
+
const readline = require("readline");
|
|
105
|
+
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
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
97
119
|
function registerPlugin() {
|
|
98
120
|
const plugin = path.join(PKG_ROOT, "plugins", "armin.ts");
|
|
99
121
|
if (!fs.existsSync(plugin)) throw new Error(`plugin not found at ${plugin}`);
|
|
100
|
-
const cfgDir =
|
|
122
|
+
const cfgDir = path.join(
|
|
123
|
+
process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"),
|
|
124
|
+
"opencode",
|
|
125
|
+
);
|
|
101
126
|
const cfgPath = fs.existsSync(path.join(cfgDir, "opencode.jsonc"))
|
|
102
127
|
? path.join(cfgDir, "opencode.jsonc")
|
|
103
128
|
: path.join(cfgDir, "opencode.json");
|
|
@@ -112,10 +137,18 @@ function registerPlugin() {
|
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
139
|
const entry = "file://" + plugin;
|
|
140
|
+
let changed = false;
|
|
115
141
|
const plugins = Array.isArray(data.plugin) ? data.plugin : [];
|
|
116
142
|
if (!plugins.includes(entry)) {
|
|
117
143
|
plugins.push(entry);
|
|
118
144
|
data.plugin = plugins;
|
|
145
|
+
changed = true;
|
|
146
|
+
}
|
|
147
|
+
if (pendingTypesafeKey && !process.env.TYPESAFE_AI_API_KEY) {
|
|
148
|
+
data.armin = { ...(data.armin || {}), typesafeKey: pendingTypesafeKey };
|
|
149
|
+
changed = true;
|
|
150
|
+
}
|
|
151
|
+
if (changed) {
|
|
119
152
|
fs.mkdirSync(cfgDir, { recursive: true });
|
|
120
153
|
fs.writeFileSync(cfgPath, JSON.stringify(data, null, 2));
|
|
121
154
|
}
|
|
@@ -141,7 +174,26 @@ async function install() {
|
|
|
141
174
|
}
|
|
142
175
|
}
|
|
143
176
|
}
|
|
177
|
+
await askForTypesafeKey();
|
|
144
178
|
registerPlugin();
|
|
179
|
+
const tsKey = process.env.TYPESAFE_AI_API_KEY || pendingTypesafeKey;
|
|
180
|
+
const llmKey = process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
|
|
181
|
+
if (tsKey) {
|
|
182
|
+
console.log("extraction: jev (TypeSafe System One) — ready");
|
|
183
|
+
} else if (llmKey) {
|
|
184
|
+
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).',
|
|
188
|
+
);
|
|
189
|
+
} else {
|
|
190
|
+
console.log(
|
|
191
|
+
"extraction: NONE (deterministic-only) — import, capture and unverified-edit\n" +
|
|
192
|
+
" 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.",
|
|
195
|
+
);
|
|
196
|
+
}
|
|
145
197
|
console.log(`
|
|
146
198
|
── done ────────────────────────────────────────────────────────
|
|
147
199
|
Enable ARMIN per environment:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "armin-opencode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* "enabled": true,
|
|
17
17
|
* "provider": "anthropic", // anthropic | openai (default: infer from keys)
|
|
18
18
|
* "apiKey": "sk-...", // sent to the sidecar only; env key wins if both set
|
|
19
|
+
* "typesafeKey": "...", // Typesafe System One key (jev is the
|
|
20
|
+
* // default extraction mode); env wins
|
|
19
21
|
* "model": "session", // "session" = follow the live session model
|
|
20
22
|
* // "small" = reuse opencode's small_model setting
|
|
21
23
|
* // any string = fixed extraction model
|
|
@@ -490,6 +492,10 @@ const ArminPlugin: Plugin = async (ctx) => {
|
|
|
490
492
|
if (provider === "anthropic" || provider === "openai") {
|
|
491
493
|
spawnEnv.LLM_PROVIDER = provider
|
|
492
494
|
}
|
|
495
|
+
// Typesafe key (jev is the default extraction mode): config value is
|
|
496
|
+
// passed to the sidecar only; a key already in the environment wins.
|
|
497
|
+
const typesafeKey = cfgStr("typesafeKey", "TYPESAFE_AI_API_KEY")
|
|
498
|
+
if (typesafeKey) spawnEnv.TYPESAFE_AI_API_KEY = typesafeKey
|
|
493
499
|
// API key: config value is passed to the sidecar only; a key already in
|
|
494
500
|
// the environment wins (no override).
|
|
495
501
|
if (typeof armin.apiKey === "string" && armin.apiKey) {
|
|
@@ -519,6 +525,31 @@ const ArminPlugin: Plugin = async (ctx) => {
|
|
|
519
525
|
if (sidecar.port) {
|
|
520
526
|
log(`reasoning-state UI: http://127.0.0.1:${sidecar.port}/ui`)
|
|
521
527
|
}
|
|
528
|
+
// Surface the effective extraction mode once — this is the difference
|
|
529
|
+
// between "memory works" and a silent deterministic downgrade.
|
|
530
|
+
void (async () => {
|
|
531
|
+
try {
|
|
532
|
+
const health = await api.get<{ extraction: string }>("/health")
|
|
533
|
+
const mode = health?.extraction ?? "unknown"
|
|
534
|
+
if (mode === "jev") {
|
|
535
|
+
log(`extraction: jev (TypeSafe System One) — ready`)
|
|
536
|
+
} else if (mode === "llm") {
|
|
537
|
+
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.`,
|
|
541
|
+
)
|
|
542
|
+
} else {
|
|
543
|
+
log(
|
|
544
|
+
`extraction: deterministic-only — no API keys found. Import and ` +
|
|
545
|
+
`unverified-edit warnings work; prose extraction does not. Set ` +
|
|
546
|
+
`TYPESAFE_AI_API_KEY (or ANTHROPIC/OPENAI_API_KEY) to enable it.`,
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
} catch {
|
|
550
|
+
// best-effort status only
|
|
551
|
+
}
|
|
552
|
+
})()
|
|
522
553
|
|
|
523
554
|
// ── Cold start: import AGENTS.md / CLAUDE.md into an empty graph ──────
|
|
524
555
|
// Deterministic parse on the engine side (content-hash IDs, idempotent).
|