@ngockhoale/ukit 3.1.4 → 3.1.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.1.6 - 2026-09-26
6
+
7
+ - **Decision endpoint URL normalization**: a `UKIT_DECISION_BASE_URL` that already ends in `/v1` (OpenAI convention) no longer produces `…/v1/v1/chat/completions`; the client strips a trailing `/v1` before joining. Applied to `src/decision/client.js` and mirrored in `unic-decision.mjs`.
8
+
9
+ ## 3.1.5 - 2026-09-26
10
+
11
+ - **Install/update warns when the decision lane has no API key.** After a project refresh, `ukit install` (and `ukit update`'s refresh) prints a bold-red warning with setup instructions — `ukit decision --base-url <url> --key <key>`, `~/.ukit/storage/gatewayDecision.json`, or `UKIT_DECISION_API_KEY` — whenever no dedicated decision key and no shared UNIC token are resolvable. When a key (or the shared UNIC token fallback) is found, a one-line confirmation is printed instead.
12
+
5
13
  ## 3.1.4 - 2026-09-26
6
14
 
7
15
  - **`ukit decision` — user-facing decision-lane gateway setup.** Resolution order (per field): env `UKIT_DECISION_BASE_URL`/`UKIT_DECISION_API_KEY`/`UKIT_DECISION_SCHEME` → `~/.ukit/storage/gatewayDecision.json` (new per-lane file; `gateway<Lane>.json` pattern) → `~/.ukit/storage/config.json` `decisionGateway` → `openai.unicjsc.com` + shared UNIC token. File field names mirror the env vars; `scheme` selects `Authorization: Bearer` vs `x-api-key`. Both files are seeded by `ukit install` with the full key set (`mergeStrategy: skip` — `ukit update` never overwrites user values). Commands: `--status`, `--base-url/--key/--scheme`, `--set-unic`, `--clear`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.1.4",
3
+ "version": "3.1.6",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -2,6 +2,7 @@ import { buildPathConfig } from '../../core/paths.js';
2
2
  import { runInstallPipeline } from '../../core/runInstallPipeline.js';
3
3
  import { formatRepairReport } from '../../core/repairBrokenHooks.js';
4
4
  import { formatGatewayResilienceReport } from '../../core/gatewayResilienceEnv.js';
5
+ import { resolveDecisionApiKey, resolveDecisionBaseUrl } from '../../core/gatewayProbe.js';
5
6
  import { buildCodeIndex, buildDerivedIndexArtifacts } from '../../index/buildIndex.js';
6
7
  import { installIndexRefreshHooks } from '../../index/gitHooks.js';
7
8
  import fs from 'node:fs/promises';
@@ -339,6 +340,29 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
339
340
  console.log(`[UKit] ${line}`);
340
341
  }
341
342
 
343
+ // Decision-lane credential check: warn (bold red) when no dedicated key is
344
+ // configured. Resolution: env UKIT_DECISION_API_KEY →
345
+ // ~/.ukit/storage/gatewayDecision.json → config.json decisionGateway →
346
+ // shared UNIC token. Tell the user exactly how to set it.
347
+ try {
348
+ const decisionKey = await resolveDecisionApiKey({ projectRoot });
349
+ const decisionBase = resolveDecisionBaseUrl({});
350
+ const R = '';
351
+ const X = '';
352
+ if (!decisionKey?.value) {
353
+ console.warn(`${R}[UKit] WARNING — decision lane (unic-decision) has NO API key configured.${X}`);
354
+ console.warn(`${R}[UKit] The typed-decision features will report 'unavailable' and fall back to deterministic rules.${X}`);
355
+ console.warn(`${R}[UKit] Set it with: ukit decision --base-url https://openai.unicjsc.com --key <YOUR_KEY>${X}`);
356
+ console.warn(`${R}[UKit] Or edit ~/.ukit/storage/gatewayDecision.json -> UKIT_DECISION_API_KEY, or export UKIT_DECISION_API_KEY=<key>.${X}`);
357
+ } else if (decisionKey.source === 'shared-unic-token') {
358
+ console.log(`[UKit] Decision lane: using shared UNIC token at ${decisionBase.baseUrl} (no dedicated key needed).`);
359
+ } else {
360
+ console.log(`[UKit] Decision lane: key configured (${decisionKey.source}) at ${decisionBase.baseUrl}.`);
361
+ }
362
+ } catch {
363
+ // advisory only — never fail install on the check
364
+ }
365
+
342
366
  const docsLabels = [
343
367
  'docs/PROJECT.md',
344
368
  'docs/MEMORY.md',
@@ -56,6 +56,15 @@ const DEFAULT_CHECKPOINTS = {
56
56
  const FALLBACK_CHECKPOINT = 'unic-decision';
57
57
 
58
58
  const DEFAULT_TIMEOUT_MS = 5000;
59
+
60
+ // Join `<baseUrl>/v1/chat/completions` tolerantly: users reasonably paste a
61
+ // base that already ends in /v1 (OpenAI convention). Strip a trailing /v1
62
+ // before appending so `.../v1` and `.../` yield the same endpoint.
63
+ function decisionCompletionsUrl(baseUrl) {
64
+ const trimmed = String(baseUrl).replace(/\/+$/, '').replace(/\/v1$/i, '');
65
+ return `${trimmed}/v1/chat/completions`;
66
+ }
67
+
59
68
  const DEFAULT_MAX_RETRIES = 1;
60
69
  const DEFAULT_BREAKER = { failureThreshold: 3, cooldownMs: 30_000 };
61
70
 
@@ -218,7 +227,7 @@ export function createDecisionClient({
218
227
  if (!base?.baseUrl) return null;
219
228
  const key = await resolveDecisionApiKey({ projectRoot, homeDir, env });
220
229
  return {
221
- url: `${String(base.baseUrl).replace(/\/+$/, '')}/v1/chat/completions`,
230
+ url: `${decisionCompletionsUrl(base.baseUrl)}`,
222
231
  headers: buildAuthHeaders(key),
223
232
  };
224
233
  }
@@ -486,6 +486,15 @@ const DEFAULT_CHECKPOINTS = {
486
486
 
487
487
  const DEFAULT_TIMEOUT_MS = 5000;
488
488
  const DEFAULT_MAX_RETRIES = 1;
489
+
490
+ // Join `<baseUrl>/v1/chat/completions` tolerantly: users reasonably paste a
491
+ // base that already ends in /v1 (OpenAI convention). Strip a trailing /v1
492
+ // before appending so `.../v1` and `.../` yield the same endpoint.
493
+ function decisionCompletionsUrl(baseUrl) {
494
+ const trimmed = String(baseUrl).replace(/\/+$/, '').replace(/\/v1$/i, '');
495
+ return `${trimmed}/v1/chat/completions`;
496
+ }
497
+
489
498
  const DEFAULT_STATE_LIMITS = { multilingual: 1024, english: 512 };
490
499
 
491
500
  function decisionPlaneConfig(config) {
@@ -671,7 +680,7 @@ export async function requestBatch(batch, {
671
680
  }
672
681
  const key = resolveDecisionApiKey({ projectRoot, homeDir, env });
673
682
  const endpoint = {
674
- url: `${String(base.baseUrl).replace(/\/+$/, '')}/v1/chat/completions`,
683
+ url: decisionCompletionsUrl(base.baseUrl),
675
684
  headers: buildAuthHeaders(key),
676
685
  };
677
686
 
@@ -801,7 +810,7 @@ export async function runHealthProbe({
801
810
  try {
802
811
  const res = await postWithDeadline(
803
812
  transport,
804
- `${String(base.baseUrl).replace(/\/+$/, '')}/v1/chat/completions`,
813
+ decisionCompletionsUrl(base.baseUrl),
805
814
  buildAuthHeaders(key),
806
815
  body,
807
816
  timeoutMs,