@mcowger/oh-my-pi-plexus 1.9.0 → 1.10.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
@@ -176,21 +176,21 @@ To force a model refresh:
176
176
  /plexus-refresh
177
177
  ```
178
178
 
179
- `PLEXUS_API_URL` / `PLEXUS_BASE_URL` / `PLEXUS_API_KEY` env vars and plugin options (`plexusBaseURL`, `suppressModels`) are also honored. Until a URL is configured, the provider shows a single `plexus-unconfigured` placeholder. Plugin logs are written to `~/.local/share/opencode/plugins/plexus/plugin.log` (the OpenCode service discards plugin stdout).
179
+ `PLEXUS_API_URL` / `PLEXUS_BASE_URL` / `PLEXUS_API_KEY` env vars and plugin options (`plexusBaseURL`, `apiKey`, `apiKeyEnv`, `suppressModels`) are also honored. Until a URL is configured, the provider shows a single `plexus-unconfigured` placeholder. Plugin logs are written to `~/.local/share/opencode/plugins/plexus/plugin.log` (the OpenCode service discards plugin stdout).
180
180
 
181
181
  ## Configuration files
182
182
 
183
183
  ### Connection state
184
184
 
185
- Each adapter stores only non-secret Plexus settings in its own `config.json`:
185
+ Pi and OMP store only non-secret Plexus settings in their own `config.json`:
186
186
 
187
187
  ```text
188
- <agent-dir>/extensions/plexus/config.json # base URL and optional model suppression
188
+ <agent-dir>/extensions/plexus/config.json # base URL, apiKeyEnv, model suppression
189
189
  ```
190
190
 
191
191
  Credentials stay in the host credential store (`auth.json` for Pi, `agent.db` for OMP). Model catalogs stay in the host model registry/store. The extension no longer writes a second model cache, ETag file, raw API response, or default-model preference.
192
192
 
193
- At runtime, configuration resolves as follows:
193
+ With `apiKeyEnv` omitted, Pi resolves configuration as follows:
194
194
 
195
195
  ```text
196
196
  base URL: PLEXUS_API_URL → PLEXUS_BASE_URL → saved Plexus base URL
@@ -199,10 +199,73 @@ catalog: host model store → live Plexus refresh
199
199
  startup model: host model-picker preference
200
200
  ```
201
201
 
202
- `PLEXUS_API_URL` and `PLEXUS_BASE_URL` are process-only URL overrides; `PLEXUS_API_KEY` is a process-only credential fallback. None are persisted. `/plexus status` reports the effective URL source, whether auth is available, and catalog state without exposing credentials.
202
+ OMP uses the same URL precedence, but its registered `PLEXUS_API_KEY` environment reference is a native config-key override, ahead of saved credentials. If that variable isn't set, OMP uses its host credential resolver. OpenCode uses `PLEXUS_API_KEY` before the saved `/connect` credential and plugin `apiKey` option.
203
+
204
+ `PLEXUS_API_URL` and `PLEXUS_BASE_URL` are process-only URL overrides; environment API keys aren't persisted. `/plexus status` reports the effective URL source, whether auth is available, and catalog state without exposing credentials.
203
205
 
204
206
  Plexus accepts a root URL or a `/v1` API URL. The adapter normalizes that only for the Plexus discovery request. Each model then receives its own API-specific base URL: OpenAI stays on `/v1`, Anthropic uses the root, and Google uses `/v1beta`.
205
207
 
208
+ ### Separate API-key environment variables
209
+
210
+ Set `apiKeyEnv` to the **name** of an environment variable, not its value or a `$VAR` template. The default is `PLEXUS_API_KEY`; no client-specific name is built in.
211
+
212
+ For Pi, edit `~/.pi/agent/extensions/plexus/config.json` (or `<agent-dir>/extensions/plexus/config.json` when using a custom agent directory):
213
+
214
+ ```json
215
+ {
216
+ "baseUrl": "https://plexus.example.com",
217
+ "apiKeyEnv": "AIHOME_PI_API_KEY"
218
+ }
219
+ ```
220
+
221
+ For OMP, edit `~/.omp/agent/extensions/plexus/config.json` (under the active profile's agent directory when using profiles):
222
+
223
+ ```json
224
+ {
225
+ "baseUrl": "https://plexus.example.com",
226
+ "apiKeyEnv": "AIHOME_OMP_API_KEY"
227
+ }
228
+ ```
229
+
230
+ For OpenCode V2, put `apiKeyEnv` in the plugin's `options` in `~/.config/opencode/opencode.jsonc` (or your project `opencode.jsonc`):
231
+
232
+ ```jsonc
233
+ {
234
+ "plugins": [
235
+ {
236
+ "package": "@mcowger/opencode-plexus",
237
+ "options": {
238
+ "plexusBaseURL": "https://plexus.example.com",
239
+ "apiKeyEnv": "AIHOME_OPENCODE_API_KEY"
240
+ }
241
+ }
242
+ ]
243
+ }
244
+ ```
245
+
246
+ Set the named variable in the process environment that launches the client. Restart Pi/OMP after editing their config; restart the OpenCode service after editing plugin options or changing its environment.
247
+
248
+ Credential precedence:
249
+
250
+ | Client | `apiKeyEnv` omitted | `apiKeyEnv` explicitly set |
251
+ |---|---|---|
252
+ | Pi | Saved host credential, then `PLEXUS_API_KEY` fallback | Only the named environment variable, ahead of saved credentials |
253
+ | OMP | Registered `PLEXUS_API_KEY` env override, then host credentials | Only the named environment variable, ahead of saved credentials |
254
+ | OpenCode V2 | `PLEXUS_API_KEY`, then saved `/connect` credential, then plugin `apiKey` option | Only the named environment variable, ahead of both saved credentials and `apiKey` |
255
+
256
+ An explicit setting is authoritative even when it names `PLEXUS_API_KEY`. If the selected variable is missing, empty, or whitespace-only, the plugin reports an error naming the variable without showing its value. It does not fall back to a saved key, OpenCode's `apiKey` option, or the shared `PLEXUS_API_KEY`. Saved credentials remain stored; remove `apiKeyEnv` to restore the default precedence.
257
+
258
+ ### Native login with `apiKeyEnv`
259
+
260
+ `apiKeyEnv` changes which key authenticates requests, not the native login UI:
261
+
262
+ - Pi and OMP: `/login plexus` still prompts for the base URL and API key, and saves both through the normal configuration and credential stores.
263
+ - OpenCode V2: `/connect` still stores the Plexus base URL and API key normally.
264
+
265
+ With an explicit `apiKeyEnv`, discovery and model requests use the named environment variable rather than the key entered during login. OMP's immediate post-login refresh also uses the environment key. Login remains useful for configuring the base URL, but entering a different key won't override the environment setting.
266
+
267
+ If the named variable is missing or empty at startup, the plugin fails to load. Native login cannot repair that setting: set the variable in the launching process environment or remove `apiKeyEnv`, then restart. Removing the setting restores the default precedence above without deleting saved credentials.
268
+
206
269
  ---
207
270
 
208
271
  ## Model suppression
package/dist/extension.js CHANGED
@@ -16414,6 +16414,29 @@ var toPlexusApiBase = (raw) => {
16414
16414
  const root = normalizeConfigBaseUrl(raw);
16415
16415
  return root ? `${root}/v1` : "";
16416
16416
  };
16417
+ function resolveApiKeyEnvSetting() {
16418
+ const raw = getConfigSync().apiKeyEnv;
16419
+ if (raw === undefined || raw === null)
16420
+ return { name: ENV_API_KEY, explicit: false };
16421
+ const name = typeof raw === "string" ? raw.trim() : "";
16422
+ if (!ENV_VAR_NAME_RE.test(name)) {
16423
+ throw new Error("Invalid apiKeyEnv: expected an environment variable name");
16424
+ }
16425
+ return { name, explicit: true };
16426
+ }
16427
+ function getApiKeyEnvName() {
16428
+ return resolveApiKeyEnvSetting().name;
16429
+ }
16430
+ function resolveExplicitApiKey() {
16431
+ const { name, explicit } = resolveApiKeyEnvSetting();
16432
+ if (!explicit)
16433
+ return;
16434
+ const value = process.env[name]?.trim();
16435
+ if (!value) {
16436
+ throw new Error(`apiKeyEnv environment variable "${name}" is missing or empty`);
16437
+ }
16438
+ return value;
16439
+ }
16417
16440
  var cachedConfig = null;
16418
16441
  function getConfigSync() {
16419
16442
  if (cachedConfig)
@@ -16452,6 +16475,12 @@ function getBaseUrlResolution() {
16452
16475
  function getEnvApiKey() {
16453
16476
  return resolveStringOption(process.env[ENV_API_KEY]) ?? null;
16454
16477
  }
16478
+ function resolveApiKey(storedKey) {
16479
+ const explicit = resolveExplicitApiKey();
16480
+ if (explicit)
16481
+ return explicit;
16482
+ return storedKey ?? getEnvApiKey();
16483
+ }
16455
16484
  function getModelsUrl() {
16456
16485
  const { baseUrl } = getBaseUrlResolution();
16457
16486
  return baseUrl ? `${toPlexusApiBase(baseUrl)}/models` : null;
@@ -23193,7 +23222,10 @@ function normalizeProviderConnectionClosed(message, providerName) {
23193
23222
  // src/extension.ts
23194
23223
  var PROVIDER_NAME = "plexus";
23195
23224
  function getProviderApiKeyConfig() {
23196
- return Bun.env[ENV_API_KEY]?.trim() ? { apiKey: ENV_API_KEY, authHeader: true } : {};
23225
+ const explicitApiKey = resolveExplicitApiKey();
23226
+ const envName = getApiKeyEnvName();
23227
+ const envValue = explicitApiKey ?? getEnvApiKey();
23228
+ return envValue ? { apiKey: envName, authHeader: true } : {};
23197
23229
  }
23198
23230
  var currentModels = [];
23199
23231
  var catalogSource = "none";
@@ -23211,7 +23243,15 @@ function plexusExtension(pi) {
23211
23243
  normalizeProviderConnectionClosed(event.message, PROVIDER_NAME);
23212
23244
  });
23213
23245
  pi.on("session_start", async (_event, ctx) => {
23214
- const apiKey = await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME) ?? getEnvApiKey();
23246
+ let apiKey;
23247
+ try {
23248
+ apiKey = resolveApiKey(await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME));
23249
+ } catch (error) {
23250
+ const message = error instanceof Error ? error.message : String(error);
23251
+ log("session_start: api key resolution failed", { error: message });
23252
+ ctx.ui.notify(message, "error");
23253
+ return;
23254
+ }
23215
23255
  const baseUrl = getBaseUrl();
23216
23256
  log("session_start", { hasApiKey: !!apiKey, baseUrl });
23217
23257
  if (!apiKey || !baseUrl) {
@@ -23260,7 +23300,13 @@ function createPlexusLoginProvider(pi) {
23260
23300
  };
23261
23301
  }
23262
23302
  async function handleRefresh(pi, ctx) {
23263
- const apiKey = await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME) ?? getEnvApiKey();
23303
+ let apiKey;
23304
+ try {
23305
+ apiKey = resolveApiKey(await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME));
23306
+ } catch (error) {
23307
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
23308
+ return;
23309
+ }
23264
23310
  if (!apiKey) {
23265
23311
  ctx.ui.notify("No Plexus API key configured. Run /login plexus first.", "error");
23266
23312
  return;
@@ -23273,14 +23319,26 @@ async function handleStatus(ctx) {
23273
23319
  const apiKey = await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME);
23274
23320
  ctx.ui.notify([
23275
23321
  `Plexus base URL: ${baseUrl.baseUrl ?? "not configured"} (${baseUrl.source})`,
23276
- `API key: ${apiKey ? getEnvApiKey() ? "host credential or PLEXUS_API_KEY fallback" : "host credential" : "not configured"}`,
23322
+ `API key: ${describeApiKey(apiKey)}`,
23277
23323
  `Catalog: ${currentModels.length} models (${catalogSource})`,
23278
23324
  "Default model: managed by OMP. Use /model or /models to save the selection there."
23279
23325
  ].join(`
23280
23326
  `), "info");
23281
23327
  }
23328
+ function describeApiKey(savedApiKey) {
23329
+ try {
23330
+ const envName = getApiKeyEnvName();
23331
+ if (resolveExplicitApiKey() !== undefined)
23332
+ return `${envName} (apiKeyEnv)`;
23333
+ if (getEnvApiKey())
23334
+ return `${envName} (overrides host credential)`;
23335
+ return savedApiKey ? "host credential" : "not configured";
23336
+ } catch (error) {
23337
+ return `invalid apiKeyEnv configuration (${error instanceof Error ? error.message : String(error)})`;
23338
+ }
23339
+ }
23282
23340
  async function fetchPlexusModelConfigs(apiKey) {
23283
- const key = apiKey ?? getEnvApiKey();
23341
+ const key = resolveApiKey(apiKey);
23284
23342
  const modelsUrl = getModelsUrl();
23285
23343
  const baseUrl = getBaseUrl();
23286
23344
  if (!key || !modelsUrl || !baseUrl)
@@ -23302,7 +23360,14 @@ async function doRefresh(pi, apiKey, ctx) {
23302
23360
  return;
23303
23361
  }
23304
23362
  try {
23305
- const { models: apiModels } = await fetchPlexusModels(apiKey, modelsUrl);
23363
+ const resolvedApiKey = resolveApiKey(apiKey);
23364
+ if (!resolvedApiKey) {
23365
+ if (ctx)
23366
+ ctx.ui.notify("No Plexus API key configured. Run /login plexus first.", "warning");
23367
+ log("doRefresh: no API key configured");
23368
+ return;
23369
+ }
23370
+ const { models: apiModels } = await fetchPlexusModels(resolvedApiKey, modelsUrl);
23306
23371
  const suppressPatterns = getSuppressedModels();
23307
23372
  const descriptors = convertDescriptors(apiModels, baseUrl, suppressPatterns);
23308
23373
  const ohMyPiModels = descriptors.map(descriptorToOhMyPiModel);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcowger/oh-my-pi-plexus",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Plexus AI proxy plugin for the Oh My Pi coding agent",
5
5
  "type": "module",
6
6
  "files": [