nemoris 0.1.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.
Files changed (223) hide show
  1. package/.env.example +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +209 -0
  4. package/SECURITY.md +119 -0
  5. package/bin/nemoris +46 -0
  6. package/config/agents/agent.toml.example +28 -0
  7. package/config/agents/default.toml +22 -0
  8. package/config/agents/orchestrator.toml +18 -0
  9. package/config/delivery.toml +73 -0
  10. package/config/embeddings.toml +5 -0
  11. package/config/identity/default-purpose.md +1 -0
  12. package/config/identity/default-soul.md +3 -0
  13. package/config/identity/orchestrator-purpose.md +1 -0
  14. package/config/identity/orchestrator-soul.md +1 -0
  15. package/config/improvement-targets.toml +15 -0
  16. package/config/jobs/heartbeat-check.toml +30 -0
  17. package/config/jobs/memory-rollup.toml +46 -0
  18. package/config/jobs/workspace-health.toml +63 -0
  19. package/config/mcp.toml +16 -0
  20. package/config/output-contracts.toml +17 -0
  21. package/config/peers.toml +32 -0
  22. package/config/peers.toml.example +32 -0
  23. package/config/policies/memory-default.toml +10 -0
  24. package/config/policies/memory-heartbeat.toml +5 -0
  25. package/config/policies/memory-ops.toml +10 -0
  26. package/config/policies/tools-heartbeat-minimal.toml +8 -0
  27. package/config/policies/tools-interactive-safe.toml +8 -0
  28. package/config/policies/tools-ops-bounded.toml +8 -0
  29. package/config/policies/tools-orchestrator.toml +7 -0
  30. package/config/providers/anthropic.toml +15 -0
  31. package/config/providers/ollama.toml +5 -0
  32. package/config/providers/openai-codex.toml +9 -0
  33. package/config/providers/openrouter.toml +5 -0
  34. package/config/router.toml +22 -0
  35. package/config/runtime.toml +114 -0
  36. package/config/skills/self-improvement.toml +15 -0
  37. package/config/skills/telegram-onboarding-spec.md +240 -0
  38. package/config/skills/workspace-monitor.toml +15 -0
  39. package/config/task-router.toml +42 -0
  40. package/install.sh +50 -0
  41. package/package.json +90 -0
  42. package/src/auth/auth-profiles.js +169 -0
  43. package/src/auth/openai-codex-oauth.js +285 -0
  44. package/src/battle.js +449 -0
  45. package/src/cli/help.js +265 -0
  46. package/src/cli/output-filter.js +49 -0
  47. package/src/cli/runtime-control.js +704 -0
  48. package/src/cli-main.js +2763 -0
  49. package/src/cli.js +78 -0
  50. package/src/config/loader.js +332 -0
  51. package/src/config/schema-validator.js +214 -0
  52. package/src/config/toml-lite.js +8 -0
  53. package/src/daemon/action-handlers.js +71 -0
  54. package/src/daemon/healing-tick.js +87 -0
  55. package/src/daemon/health-probes.js +90 -0
  56. package/src/daemon/notifier.js +57 -0
  57. package/src/daemon/nurse.js +218 -0
  58. package/src/daemon/repair-log.js +106 -0
  59. package/src/daemon/rule-staging.js +90 -0
  60. package/src/daemon/rules.js +29 -0
  61. package/src/daemon/telegram-commands.js +54 -0
  62. package/src/daemon/updater.js +85 -0
  63. package/src/jobs/job-runner.js +78 -0
  64. package/src/mcp/consumer.js +129 -0
  65. package/src/memory/active-recall.js +171 -0
  66. package/src/memory/backend-manager.js +97 -0
  67. package/src/memory/backends/file-backend.js +38 -0
  68. package/src/memory/backends/qmd-backend.js +219 -0
  69. package/src/memory/embedding-guards.js +24 -0
  70. package/src/memory/embedding-index.js +118 -0
  71. package/src/memory/embedding-service.js +179 -0
  72. package/src/memory/file-index.js +177 -0
  73. package/src/memory/memory-signature.js +5 -0
  74. package/src/memory/memory-store.js +648 -0
  75. package/src/memory/retrieval-planner.js +66 -0
  76. package/src/memory/scoring.js +145 -0
  77. package/src/memory/simhash.js +78 -0
  78. package/src/memory/sqlite-active-store.js +824 -0
  79. package/src/memory/write-policy.js +36 -0
  80. package/src/onboarding/aliases.js +33 -0
  81. package/src/onboarding/auth/api-key.js +224 -0
  82. package/src/onboarding/auth/ollama-detect.js +42 -0
  83. package/src/onboarding/clack-prompter.js +77 -0
  84. package/src/onboarding/doctor.js +530 -0
  85. package/src/onboarding/lock.js +42 -0
  86. package/src/onboarding/model-catalog.js +344 -0
  87. package/src/onboarding/phases/auth.js +589 -0
  88. package/src/onboarding/phases/build.js +130 -0
  89. package/src/onboarding/phases/choose.js +82 -0
  90. package/src/onboarding/phases/detect.js +98 -0
  91. package/src/onboarding/phases/hatch.js +216 -0
  92. package/src/onboarding/phases/identity.js +79 -0
  93. package/src/onboarding/phases/ollama.js +345 -0
  94. package/src/onboarding/phases/scaffold.js +99 -0
  95. package/src/onboarding/phases/telegram.js +377 -0
  96. package/src/onboarding/phases/validate.js +204 -0
  97. package/src/onboarding/phases/verify.js +206 -0
  98. package/src/onboarding/platform.js +482 -0
  99. package/src/onboarding/status-bar.js +95 -0
  100. package/src/onboarding/templates.js +794 -0
  101. package/src/onboarding/toml-writer.js +38 -0
  102. package/src/onboarding/tui.js +250 -0
  103. package/src/onboarding/uninstall.js +153 -0
  104. package/src/onboarding/wizard.js +499 -0
  105. package/src/providers/anthropic.js +168 -0
  106. package/src/providers/base.js +247 -0
  107. package/src/providers/circuit-breaker.js +136 -0
  108. package/src/providers/ollama.js +163 -0
  109. package/src/providers/openai-codex.js +149 -0
  110. package/src/providers/openrouter.js +136 -0
  111. package/src/providers/registry.js +36 -0
  112. package/src/providers/router.js +16 -0
  113. package/src/runtime/bootstrap-cache.js +47 -0
  114. package/src/runtime/capabilities-prompt.js +25 -0
  115. package/src/runtime/completion-ping.js +99 -0
  116. package/src/runtime/config-validator.js +121 -0
  117. package/src/runtime/context-ledger.js +360 -0
  118. package/src/runtime/cutover-readiness.js +42 -0
  119. package/src/runtime/daemon.js +729 -0
  120. package/src/runtime/delivery-ack.js +195 -0
  121. package/src/runtime/delivery-adapters/local-file.js +41 -0
  122. package/src/runtime/delivery-adapters/openclaw-cli.js +94 -0
  123. package/src/runtime/delivery-adapters/openclaw-peer.js +98 -0
  124. package/src/runtime/delivery-adapters/shadow.js +13 -0
  125. package/src/runtime/delivery-adapters/standalone-http.js +98 -0
  126. package/src/runtime/delivery-adapters/telegram.js +104 -0
  127. package/src/runtime/delivery-adapters/tui.js +128 -0
  128. package/src/runtime/delivery-manager.js +807 -0
  129. package/src/runtime/delivery-store.js +168 -0
  130. package/src/runtime/dependency-health.js +118 -0
  131. package/src/runtime/envelope.js +114 -0
  132. package/src/runtime/evaluation.js +1089 -0
  133. package/src/runtime/exec-approvals.js +216 -0
  134. package/src/runtime/executor.js +500 -0
  135. package/src/runtime/failure-ping.js +67 -0
  136. package/src/runtime/flows.js +83 -0
  137. package/src/runtime/guards.js +45 -0
  138. package/src/runtime/handoff.js +51 -0
  139. package/src/runtime/identity-cache.js +28 -0
  140. package/src/runtime/improvement-engine.js +109 -0
  141. package/src/runtime/improvement-harness.js +581 -0
  142. package/src/runtime/input-sanitiser.js +72 -0
  143. package/src/runtime/interaction-contract.js +347 -0
  144. package/src/runtime/lane-readiness.js +226 -0
  145. package/src/runtime/migration.js +323 -0
  146. package/src/runtime/model-resolution.js +78 -0
  147. package/src/runtime/network.js +64 -0
  148. package/src/runtime/notification-store.js +97 -0
  149. package/src/runtime/notifier.js +256 -0
  150. package/src/runtime/orchestrator.js +53 -0
  151. package/src/runtime/orphan-reaper.js +41 -0
  152. package/src/runtime/output-contract-schema.js +139 -0
  153. package/src/runtime/output-contract-validator.js +439 -0
  154. package/src/runtime/peer-readiness.js +69 -0
  155. package/src/runtime/peer-registry.js +133 -0
  156. package/src/runtime/pilot-status.js +108 -0
  157. package/src/runtime/prompt-builder.js +261 -0
  158. package/src/runtime/provider-attempt.js +582 -0
  159. package/src/runtime/report-fallback.js +71 -0
  160. package/src/runtime/result-normalizer.js +183 -0
  161. package/src/runtime/retention.js +74 -0
  162. package/src/runtime/review.js +244 -0
  163. package/src/runtime/route-job.js +15 -0
  164. package/src/runtime/run-store.js +38 -0
  165. package/src/runtime/schedule.js +88 -0
  166. package/src/runtime/scheduler-state.js +434 -0
  167. package/src/runtime/scheduler.js +656 -0
  168. package/src/runtime/session-compactor.js +182 -0
  169. package/src/runtime/session-search.js +155 -0
  170. package/src/runtime/slack-inbound.js +249 -0
  171. package/src/runtime/ssrf.js +102 -0
  172. package/src/runtime/status-aggregator.js +330 -0
  173. package/src/runtime/task-contract.js +140 -0
  174. package/src/runtime/task-packet.js +107 -0
  175. package/src/runtime/task-router.js +140 -0
  176. package/src/runtime/telegram-inbound.js +1565 -0
  177. package/src/runtime/token-counter.js +134 -0
  178. package/src/runtime/token-estimator.js +59 -0
  179. package/src/runtime/tool-loop.js +200 -0
  180. package/src/runtime/transport-server.js +311 -0
  181. package/src/runtime/tui-server.js +411 -0
  182. package/src/runtime/ulid.js +44 -0
  183. package/src/security/ssrf-check.js +197 -0
  184. package/src/setup.js +369 -0
  185. package/src/shadow/bridge.js +303 -0
  186. package/src/skills/loader.js +84 -0
  187. package/src/tools/catalog.json +49 -0
  188. package/src/tools/cli-delegate.js +44 -0
  189. package/src/tools/mcp-client.js +106 -0
  190. package/src/tools/micro/cancel-task.js +6 -0
  191. package/src/tools/micro/complete-task.js +6 -0
  192. package/src/tools/micro/fail-task.js +6 -0
  193. package/src/tools/micro/http-fetch.js +74 -0
  194. package/src/tools/micro/index.js +36 -0
  195. package/src/tools/micro/lcm-recall.js +60 -0
  196. package/src/tools/micro/list-dir.js +17 -0
  197. package/src/tools/micro/list-skills.js +46 -0
  198. package/src/tools/micro/load-skill.js +38 -0
  199. package/src/tools/micro/memory-search.js +45 -0
  200. package/src/tools/micro/read-file.js +11 -0
  201. package/src/tools/micro/session-search.js +54 -0
  202. package/src/tools/micro/shell-exec.js +43 -0
  203. package/src/tools/micro/trigger-job.js +79 -0
  204. package/src/tools/micro/web-search.js +58 -0
  205. package/src/tools/micro/workspace-paths.js +39 -0
  206. package/src/tools/micro/write-file.js +14 -0
  207. package/src/tools/micro/write-memory.js +41 -0
  208. package/src/tools/registry.js +348 -0
  209. package/src/tools/tool-result-contract.js +36 -0
  210. package/src/tui/chat.js +835 -0
  211. package/src/tui/renderer.js +175 -0
  212. package/src/tui/socket-client.js +217 -0
  213. package/src/utils/canonical-json.js +29 -0
  214. package/src/utils/compaction.js +30 -0
  215. package/src/utils/env-loader.js +5 -0
  216. package/src/utils/errors.js +80 -0
  217. package/src/utils/fs.js +101 -0
  218. package/src/utils/ids.js +5 -0
  219. package/src/utils/model-context-limits.js +30 -0
  220. package/src/utils/token-budget.js +74 -0
  221. package/src/utils/usage-cost.js +25 -0
  222. package/src/utils/usage-metrics.js +14 -0
  223. package/vendor/smol-toml-1.5.2.tgz +0 -0
@@ -0,0 +1,344 @@
1
+ import { inspectOpenAICodexProfile } from "../auth/openai-codex-oauth.js";
2
+ import { getAuthProfile } from "../auth/auth-profiles.js";
3
+
4
+ export const PROVIDER_ENV_KEYS = {
5
+ anthropic: ["NEMORIS_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY"],
6
+ openrouter: ["OPENROUTER_API_KEY"],
7
+ openai: ["NEMORIS_OPENAI_API_KEY", "OPENAI_API_KEY"],
8
+ };
9
+
10
+ export const CURATED_MODELS = {
11
+ anthropic: [
12
+ {
13
+ value: "anthropic/claude-haiku-4-5",
14
+ label: "claude-haiku-4-5",
15
+ hint: "ctx 200k · fast · cheapest",
16
+ },
17
+ {
18
+ value: "anthropic/claude-sonnet-4-6",
19
+ label: "claude-sonnet-4-6",
20
+ hint: "ctx 200k · balanced · recommended",
21
+ },
22
+ {
23
+ value: "anthropic/claude-opus-4-6",
24
+ label: "claude-opus-4-6",
25
+ hint: "ctx 200k · most capable · expensive",
26
+ },
27
+ ],
28
+ openai: [
29
+ {
30
+ value: "openai-codex/gpt-4.1",
31
+ label: "gpt-4.1",
32
+ hint: "ctx 1M · latest · recommended",
33
+ },
34
+ {
35
+ value: "openai-codex/gpt-4o",
36
+ label: "gpt-4o",
37
+ hint: "ctx 128k · fast · multimodal",
38
+ },
39
+ {
40
+ value: "openai-codex/o4-mini",
41
+ label: "o4-mini",
42
+ hint: "ctx 200k · reasoning · efficient",
43
+ },
44
+ {
45
+ value: "openai-codex/o3",
46
+ label: "o3",
47
+ hint: "ctx 200k · reasoning · most capable",
48
+ },
49
+ ],
50
+ openrouter: [
51
+ {
52
+ value: "openrouter/anthropic/claude-haiku-4-5",
53
+ label: "anthropic/claude-haiku-4-5",
54
+ hint: "ctx 200k · fast · recommended",
55
+ },
56
+ {
57
+ value: "openrouter/anthropic/claude-sonnet-4-6",
58
+ label: "anthropic/claude-sonnet-4-6",
59
+ hint: "ctx 200k · balanced · strong default",
60
+ },
61
+ {
62
+ value: "openrouter/openai/gpt-4o",
63
+ label: "openai/gpt-4o",
64
+ hint: "ctx 128k · vision · broad compatibility",
65
+ },
66
+ {
67
+ value: "openrouter/google/gemini-2.5-flash",
68
+ label: "google/gemini-2.5-flash",
69
+ hint: "ctx 1M · fast · long context",
70
+ },
71
+ {
72
+ value: "openrouter/anthropic/claude-opus-4-6",
73
+ label: "anthropic/claude-opus-4-6",
74
+ hint: "ctx 200k · most capable · expensive",
75
+ },
76
+ ],
77
+ };
78
+
79
+ const OPENAI_CHAT_MODEL_PREFIXES = ["gpt-4", "gpt-3.5", "o1", "o2", "o3", "o4"];
80
+ const OPENAI_EXCLUDED_MODEL_TOKENS = ["whisper", "tts", "dall-e", "text-embedding", "babbage", "davinci", "curie", "ada"];
81
+
82
+ function isOpenAIChatModelId(id) {
83
+ const normalized = String(id || "").trim().toLowerCase();
84
+ if (!normalized) return false;
85
+ if (!OPENAI_CHAT_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
86
+ return false;
87
+ }
88
+ return !OPENAI_EXCLUDED_MODEL_TOKENS.some((token) => normalized.includes(token));
89
+ }
90
+
91
+ export async function fetchOpenAIModels(apiKey, { fetchImpl = globalThis.fetch } = {}) {
92
+ const token = String(apiKey || "").trim();
93
+ if (!token || typeof fetchImpl !== "function") {
94
+ return [];
95
+ }
96
+
97
+ try {
98
+ const response = await fetchImpl("https://api.openai.com/v1/models", {
99
+ method: "GET",
100
+ headers: {
101
+ Authorization: `Bearer ${token}`,
102
+ },
103
+ signal: AbortSignal.timeout(5000),
104
+ });
105
+
106
+ if (!response?.ok) {
107
+ return [];
108
+ }
109
+
110
+ const payload = await response.json();
111
+ if (!Array.isArray(payload?.data)) {
112
+ return [];
113
+ }
114
+
115
+ const seen = new Set();
116
+ return payload.data
117
+ .filter((item) => isOpenAIChatModelId(item?.id))
118
+ .sort((left, right) => {
119
+ const rightCreated = Number(right?.created) || 0;
120
+ const leftCreated = Number(left?.created) || 0;
121
+ if (rightCreated !== leftCreated) {
122
+ return rightCreated - leftCreated;
123
+ }
124
+ return String(left?.id || "").localeCompare(String(right?.id || ""));
125
+ })
126
+ .map((item) => item.id)
127
+ .filter((id) => {
128
+ if (!id || seen.has(id)) return false;
129
+ seen.add(id);
130
+ return true;
131
+ });
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+
137
+ export function resolveEnvKey(provider, env = process.env) {
138
+ const candidates = PROVIDER_ENV_KEYS[provider] || [];
139
+ for (const key of candidates) {
140
+ if (env[key]) {
141
+ return { name: key, value: env[key] };
142
+ }
143
+ }
144
+ return { name: candidates[0] || null, value: null };
145
+ }
146
+
147
+ export function maskSecret(secret) {
148
+ const value = String(secret || "").trim();
149
+ if (!value) return "missing";
150
+ if (value.length <= 8) return "****";
151
+ return `${value.slice(0, 6)}...${value.slice(-4)}`;
152
+ }
153
+
154
+ export function formatExpiry(msUntilExpiry) {
155
+ if (!Number.isFinite(msUntilExpiry)) return "expires unknown";
156
+ if (msUntilExpiry <= 0) return "expired";
157
+ const days = Math.floor(msUntilExpiry / (24 * 60 * 60 * 1000));
158
+ if (days >= 1) return `expires in ${days}d`;
159
+ const hours = Math.max(1, Math.floor(msUntilExpiry / (60 * 60 * 1000)));
160
+ return `expires in ${hours}h`;
161
+ }
162
+
163
+ export function detectProviderOptions({ env = process.env, ollamaResult = { ok: false, models: [] } } = {}) {
164
+ const anthropic = resolveEnvKey("anthropic", env);
165
+ const openrouter = resolveEnvKey("openrouter", env);
166
+ const openai = resolveEnvKey("openai", env);
167
+
168
+ return [
169
+ {
170
+ value: "anthropic",
171
+ label: "Anthropic",
172
+ hint: anthropic.value ? "API key found in env" : "API key required",
173
+ detected: Boolean(anthropic.value),
174
+ },
175
+ {
176
+ value: "openai",
177
+ label: "OpenAI",
178
+ hint: openai.value ? "API key found in env" : "API key or ChatGPT OAuth",
179
+ detected: Boolean(openai.value),
180
+ },
181
+ {
182
+ value: "openrouter",
183
+ label: "OpenRouter",
184
+ hint: openrouter.value ? "API key found" : "One key, 100+ models — openrouter.ai",
185
+ detected: Boolean(openrouter.value),
186
+ },
187
+ {
188
+ value: "ollama",
189
+ label: "Ollama",
190
+ hint: ollamaResult.ok
191
+ ? `Local, free — ${ollamaResult.models?.length || 0} models installed`
192
+ : "Local, free — not detected",
193
+ detected: Boolean(ollamaResult.ok),
194
+ },
195
+ {
196
+ value: "skip",
197
+ label: "Skip for now",
198
+ hint: "Finish setup without provider auth.",
199
+ detected: false,
200
+ },
201
+ ];
202
+ }
203
+
204
+ export function detectPreferredProvider({ env = process.env, ollamaResult = { ok: false } } = {}) {
205
+ for (const provider of ["anthropic", "openai", "openrouter"]) {
206
+ if (resolveEnvKey(provider, env).value) {
207
+ return provider;
208
+ }
209
+ }
210
+ if (ollamaResult.ok) return "ollama";
211
+ return "skip";
212
+ }
213
+
214
+ function normalizeProviderModel(provider, value) {
215
+ const trimmed = String(value || "").trim();
216
+ if (!trimmed) return "";
217
+ if (provider === "openrouter") {
218
+ return trimmed.startsWith("openrouter/") ? trimmed : `openrouter/${trimmed}`;
219
+ }
220
+ if (provider === "openai") {
221
+ return trimmed.startsWith("openai-codex/") ? trimmed : `openai-codex/${trimmed}`;
222
+ }
223
+ if (provider === "anthropic") {
224
+ return trimmed.startsWith("anthropic/") ? trimmed : `anthropic/${trimmed}`;
225
+ }
226
+ if (provider === "ollama") {
227
+ return trimmed.startsWith("ollama/") ? trimmed.slice("ollama/".length) : trimmed;
228
+ }
229
+ return trimmed;
230
+ }
231
+
232
+ export function buildModelSelectionOptions({
233
+ provider,
234
+ currentModel = "",
235
+ installedModels = [],
236
+ discoveredModels = [],
237
+ includeKeep = true,
238
+ includeManual = true,
239
+ } = {}) {
240
+ const options = [];
241
+ const seen = new Set();
242
+ const add = (entry) => {
243
+ if (!entry || !entry.value || seen.has(entry.value)) return;
244
+ seen.add(entry.value);
245
+ options.push(entry);
246
+ };
247
+
248
+ if (includeKeep && currentModel) {
249
+ add({ value: "__keep__", label: `Keep current (${currentModel})` });
250
+ }
251
+ if (includeManual) {
252
+ add({ value: "__manual__", label: "Enter model name manually" });
253
+ }
254
+
255
+ for (const entry of CURATED_MODELS[provider] || []) {
256
+ add(entry);
257
+ }
258
+
259
+ const rawModels = provider === "ollama"
260
+ ? installedModels
261
+ : discoveredModels;
262
+ for (const model of rawModels) {
263
+ const normalized = normalizeProviderModel(provider, model);
264
+ if (!normalized) continue;
265
+ add({
266
+ value: normalized,
267
+ label: provider === "ollama" ? model : normalized.replace(/^openai-codex\//, "").replace(/^openrouter\//, ""),
268
+ hint: provider === "ollama" ? "installed locally" : "detected from provider",
269
+ });
270
+ }
271
+
272
+ return options;
273
+ }
274
+
275
+ export function summarizeSelectedModels(selectedModels = []) {
276
+ const unique = [...new Set(selectedModels.filter(Boolean))];
277
+ return {
278
+ defaultModel: unique[0] || "",
279
+ fallbackModel: unique[1] || "",
280
+ localModel: unique.find((model) => String(model).startsWith("ollama/")) || "",
281
+ };
282
+ }
283
+
284
+ export function buildModelsOverview({ router = {}, providers = {}, authProfilesPath } = {}) {
285
+ const defaultModel = router.interactive_primary?.primary || "";
286
+ const fallbackModel = router.interactive_primary?.fallback || router.report_fallback_lowcost?.primary || "";
287
+ const localModel = router.local_primary?.primary || router.local_report?.primary || "";
288
+
289
+ const authLines = [];
290
+ for (const provider of ["anthropic", "openrouter", "openai-codex", "ollama"]) {
291
+ const providerConfig = providers[provider];
292
+ if (!providerConfig) continue;
293
+
294
+ if (provider === "ollama") {
295
+ authLines.push({
296
+ provider,
297
+ ok: true,
298
+ method: "local",
299
+ detail: localModel.replace(/^ollama\//, "") || "configured",
300
+ });
301
+ continue;
302
+ }
303
+
304
+ const authRef = providerConfig.authRef || "";
305
+ if (authRef.startsWith("env:")) {
306
+ const envName = authRef.slice(4);
307
+ const secret = process.env[envName] || "";
308
+ authLines.push({
309
+ provider,
310
+ ok: Boolean(secret),
311
+ method: "token",
312
+ detail: secret ? maskSecret(secret) : `missing ${envName}`,
313
+ });
314
+ continue;
315
+ }
316
+
317
+ if (authRef.startsWith("profile:")) {
318
+ const profileId = authRef.slice("profile:".length);
319
+ const profile = getAuthProfile(profileId, authProfilesPath);
320
+ const inspection = inspectOpenAICodexProfile(profile, { now: Date.now() });
321
+ authLines.push({
322
+ provider,
323
+ ok: inspection.state === "usable_token" || inspection.state === "refresh_needed",
324
+ method: "oauth",
325
+ detail: inspection.expiresAt ? formatExpiry(inspection.expiresAt - Date.now()) : inspection.reason,
326
+ });
327
+ continue;
328
+ }
329
+
330
+ authLines.push({
331
+ provider,
332
+ ok: false,
333
+ method: "unknown",
334
+ detail: "not configured",
335
+ });
336
+ }
337
+
338
+ return {
339
+ defaultModel,
340
+ fallbackModel,
341
+ localModel,
342
+ authLines,
343
+ };
344
+ }