@openviking/opencode-plugin 2026.9.10 → 2026.9.21-2

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.
@@ -0,0 +1,359 @@
1
+ // GENERATED FROM examples/memory-plugin-shared/lib. DO NOT EDIT.
2
+ /**
3
+ * Every knob a memory plugin reads, declared once.
4
+ *
5
+ * A setting stayed uniform across harnesses only when a shared module read it
6
+ * on the hot path: `recallLimit`, `scoreThreshold` and `captureMaxLength` agree
7
+ * everywhere because `recall-core` and `capture-utils` read them. Everything
8
+ * else drifted — the same switch is `autoRecall` in three loaders, an
9
+ * `autoRecall` *object* in opencode, `syncTurns` in dsh and pi, and absent from
10
+ * the thin hook runtime — because each loader hand-wrote its own list and the
11
+ * author picked a name. Three more lists then had to be kept in step by hand:
12
+ * the doctor's known-knob set, the workspace file's dotted-key map, and the
13
+ * implicit inventory inside `loadAgentHookConfig`.
14
+ *
15
+ * This module is the one list. The others are projections of it:
16
+ * `pluginConfigKeys()` for the doctor, `WORKSPACE_KNOB_MAP` for the workspace
17
+ * file, and `resolveKnobs()` for every loader. A knob added here reaches all of
18
+ * them; a knob spelled wrong reaches none.
19
+ *
20
+ * Declaration fields:
21
+ * name canonical key, and the property the loaders read
22
+ * type bool | int | number | enum | string | list
23
+ * default value when no layer supplies one
24
+ * harness per-harness default, where one genuinely differs
25
+ * min/max clamped, not rejected: a config file must not kill a hook
26
+ * values the enum's members; anything else falls back to the default
27
+ * env OPENVIKING_* variable, the highest-priority layer
28
+ * aliases older spellings, honoured forever; the doctor names them
29
+ * workspace dotted key in `.openviking/config.json`
30
+ * capability which capability owns it, for the switches in recall/capture
31
+ * sendOnlyWhenConfigured
32
+ * the request omits this field unless a layer actually set it,
33
+ * so the server's own default stands. Loaders project the flag
34
+ * as `<name>Configured` and `recall-core` reads it from there.
35
+ */
36
+
37
+ export const CAPABILITIES = ["connection", "peer", "recall", "capture", "session", "debug"];
38
+
39
+ /** @type {ReadonlyArray<object>} */
40
+ export const KNOBS = [
41
+ // ── connection and identity ────────────────────────────────────────────
42
+ // Credentials resolve through `credentials.mjs`; these entries exist so the
43
+ // doctor recognises them inside `plugin` and so ov.conf's harness section
44
+ // keeps working.
45
+ { name: "enabled", type: "bool", default: true, env: "OPENVIKING_MEMORY_ENABLED", capability: "connection" },
46
+ { name: "apiKey", type: "string", default: "", capability: "connection" },
47
+ { name: "accountId", type: "string", default: "", capability: "connection" },
48
+ { name: "userId", type: "string", default: "", capability: "connection" },
49
+ { name: "authMode", type: "enum", values: ["trusted", "api_key"], default: "", env: "OPENVIKING_AUTH_MODE", aliases: ["auth_mode"], capability: "connection" },
50
+ {
51
+ name: "timeoutMs",
52
+ type: "int",
53
+ default: 15000,
54
+ // opencode drives an editor session that tolerates a slower call; dsh runs
55
+ // inside a chat host that does not.
56
+ harness: { opencode: 30000, dsh: 10000 },
57
+ min: 1000,
58
+ max: 300000,
59
+ env: "OPENVIKING_TIMEOUT_MS",
60
+ aliases: ["requestTimeoutMs"],
61
+ capability: "connection",
62
+ },
63
+ { name: "mcpEnabled", type: "bool", default: true, capability: "connection" },
64
+ { name: "mcpToolCallTimeoutMs", type: "int", default: 60000, min: 1000, max: 600000, capability: "connection" },
65
+ { name: "dataDir", type: "string", default: "", capability: "connection" },
66
+
67
+ { name: "peerId", type: "string", default: "", env: "OPENVIKING_PEER_ID", aliases: ["peer_id"], workspace: "peer.id", capability: "peer" },
68
+ { name: "peerSource", type: "template", default: "", env: "OPENVIKING_PEER_SOURCE", workspace: "peer.source", capability: "peer" },
69
+ { name: "workspacePeer", type: "bool", default: true, env: "OPENVIKING_WORKSPACE_PEER", capability: "peer" },
70
+
71
+ // ── recall ────────────────────────────────────────────────────────────
72
+ {
73
+ name: "autoRecall",
74
+ type: "bool",
75
+ default: true,
76
+ env: "OPENVIKING_AUTO_RECALL",
77
+ workspace: "recall.enabled",
78
+ capability: "recall",
79
+ },
80
+ { name: "recallLimit", type: "int", default: 10, min: 1, max: 50, env: "OPENVIKING_RECALL_LIMIT", workspace: "recall.max_items", sendOnlyWhenConfigured: true, capability: "recall" },
81
+ { name: "scoreThreshold", type: "number", default: 0.35, min: 0, max: 1, env: "OPENVIKING_SCORE_THRESHOLD", aliases: ["recallScoreThreshold"], workspace: "recall.score_threshold", capability: "recall" },
82
+ { name: "minQueryLength", type: "int", default: 3, min: 1, max: 64, env: "OPENVIKING_MIN_QUERY_LENGTH", aliases: ["recallMinQueryLength"], capability: "recall" },
83
+ { name: "recallTokenBudget", type: "int", default: 2000, min: 200, max: 50000, env: "OPENVIKING_RECALL_TOKEN_BUDGET", aliases: ["recallBudget"], capability: "recall" },
84
+ { name: "recallMaxContentChars", type: "int", default: 500, min: 100, max: 5000, env: "OPENVIKING_RECALL_MAX_CONTENT_CHARS", capability: "recall" },
85
+ { name: "recallPreferAbstract", type: "bool", default: true, env: "OPENVIKING_RECALL_PREFER_ABSTRACT", capability: "recall" },
86
+ { name: "recallPeerScope", type: "enum", values: ["all", "actor"], default: "all", env: "OPENVIKING_RECALL_PEER_SCOPE", workspace: "recall.peer_scope", capability: "recall" },
87
+ { name: "recallDedupTurns", type: "int", default: 5, min: 0, max: 20, env: "OPENVIKING_RECALL_DEDUP_TURNS", workspace: "recall.dedup_turns", capability: "recall" },
88
+ { name: "recallMaxTokens", type: "int", default: 1600, min: 64, max: 200000, env: "OPENVIKING_RECALL_MAX_TOKENS", sendOnlyWhenConfigured: true, capability: "recall" },
89
+ { name: "recallQueryExpansion", type: "enum", values: ["auto", "off"], default: "auto", env: "OPENVIKING_RECALL_QUERY_EXPANSION", sendOnlyWhenConfigured: true, capability: "recall" },
90
+ { name: "recallTimeoutMs", type: "int", default: 120000, min: 1000, max: 600000, env: "OPENVIKING_RECALL_TIMEOUT_MS", capability: "recall" },
91
+ // 0 keeps the built-in default, which outlasts the server's rewrite fuse.
92
+ { name: "recallContextTimeoutMs", type: "int", default: 0, min: 0, max: 600000, env: "OPENVIKING_RECALL_CONTEXT_TIMEOUT_MS", capability: "recall" },
93
+ { name: "logRankingDetails", type: "bool", default: false, env: "OPENVIKING_LOG_RANKING_DETAILS", capability: "recall" },
94
+ { name: "recallLedger", type: "bool", default: true, env: "OPENVIKING_RECALL_LEDGER", capability: "recall" },
95
+ { name: "recallQueryFilters", type: "list", default: [], env: "OPENVIKING_RECALL_QUERY_FILTERS", capability: "recall" },
96
+
97
+ // Digest compression. Claude Code reads this as the tri-state
98
+ // off/client/server/auto through `normalizeRewriteMode`; Codex reads the same
99
+ // key as on/off. Both spellings stay declared so neither harness's users are
100
+ // told their config is a typo.
101
+ { name: "recallCompress", type: "string", default: "off", harness: { claude_code: "auto", codex: "auto" }, env: "OPENVIKING_RECALL_COMPRESS", aliases: ["recallRewrite"], capability: "recall" },
102
+ { name: "recallCompressModel", type: "string", default: "", env: "OPENVIKING_RECALL_COMPRESS_MODEL", capability: "recall" },
103
+ { name: "recallCompressBaseUrl", type: "string", default: "", env: "OPENVIKING_RECALL_COMPRESS_BASE_URL", capability: "recall" },
104
+ { name: "recallCompressThinking", type: "string", default: "", env: "OPENVIKING_RECALL_COMPRESS_THINKING", aliases: ["recallCompressReasoningEffort"], capability: "recall" },
105
+ // 0 means "derive from recallTimeoutMs" — the loader that owns the fallback
106
+ // is the one that knows the request budget it has to fit inside.
107
+ { name: "recallCompressTimeoutMs", type: "int", default: 0, min: 0, max: 600000, env: "OPENVIKING_RECALL_COMPRESS_TIMEOUT_MS", capability: "recall" },
108
+ { name: "recallCompressDetectOnStartup", type: "bool", default: true, env: "OPENVIKING_RECALL_COMPRESS_DETECT_ON_STARTUP", capability: "recall" },
109
+ { name: "recallCompressDetectTimeoutMs", type: "int", default: 15000, min: 1000, max: 600000, env: "OPENVIKING_RECALL_COMPRESS_DETECT_TIMEOUT_MS", capability: "recall" },
110
+ { name: "recallCompressDetectTtlMs", type: "int", default: 604800000, min: 0, capability: "recall", env: "OPENVIKING_RECALL_COMPRESS_DETECT_TTL_MS" },
111
+ { name: "recallCompressMinInputChars", type: "int", default: 1500, min: 0, max: 100000, env: "OPENVIKING_RECALL_COMPRESS_MIN_INPUT_CHARS", capability: "recall" },
112
+ { name: "recallCompressMaxInputChars", type: "int", default: 18000, min: 1000, max: 200000, env: "OPENVIKING_RECALL_COMPRESS_MAX_INPUT_CHARS", capability: "recall" },
113
+ { name: "recallCompressMaxBullets", type: "int", default: 6, min: 1, max: 50, env: "OPENVIKING_RECALL_COMPRESS_MAX_BULLETS", sendOnlyWhenConfigured: true, capability: "recall" },
114
+
115
+ // ── capture ───────────────────────────────────────────────────────────
116
+ // `syncTurns` is dsh and pi's spelling of the same switch. It stays an alias
117
+ // rather than a second knob so `ov config switch` moves both.
118
+ {
119
+ name: "autoCapture",
120
+ type: "bool",
121
+ default: true,
122
+ env: "OPENVIKING_AUTO_CAPTURE",
123
+ aliases: ["syncTurns"],
124
+ workspace: "capture.enabled",
125
+ capability: "capture",
126
+ },
127
+ { name: "captureMode", type: "enum", values: ["semantic", "keyword"], default: "semantic", env: "OPENVIKING_CAPTURE_MODE", capability: "capture" },
128
+ { name: "captureMaxLength", type: "int", default: 24000, min: 200, max: 100000, env: "OPENVIKING_CAPTURE_MAX_LENGTH", capability: "capture" },
129
+ // Tool output is reported verbatim; the server owns truncation via
130
+ // tool_output_externalization. This cap only guards pathological payloads.
131
+ { name: "captureToolMaxChars", type: "int", default: 1000000, min: 200, max: 1000000, env: "OPENVIKING_CAPTURE_TOOL_MAX_CHARS", capability: "capture" },
132
+ // Default true: a memory plugin that only sees the user's half of the
133
+ // conversation extracts noticeably worse.
134
+ { name: "captureAssistantTurns", type: "bool", default: true, env: "OPENVIKING_CAPTURE_ASSISTANT_TURNS", capability: "capture" },
135
+ { name: "captureLastAssistantOnStop", type: "bool", default: true, env: "OPENVIKING_CAPTURE_LAST_ASSISTANT_ON_STOP", capability: "capture" },
136
+ { name: "captureToolResults", type: "bool", default: false, env: "OPENVIKING_CAPTURE_TOOL_RESULTS", capability: "capture" },
137
+ { name: "captureFilters", type: "list", default: [], env: "OPENVIKING_CAPTURE_FILTERS", capability: "capture" },
138
+ // 0 means "derive from timeoutMs": a write gets a longer budget than a read.
139
+ { name: "captureTimeoutMs", type: "int", default: 0, min: 0, max: 600000, env: "OPENVIKING_CAPTURE_TIMEOUT_MS", capability: "capture" },
140
+ { name: "commitTokenThreshold", type: "int", default: 20000, min: 1000, max: 1000000, env: "OPENVIKING_COMMIT_TOKEN_THRESHOLD", workspace: "capture.commit_token_threshold", capability: "capture" },
141
+ { name: "commitKeepRecentCount", type: "int", default: 10, min: 0, max: 1000, env: "OPENVIKING_COMMIT_KEEP_RECENT_COUNT", capability: "capture" },
142
+ { name: "commitTurnThreshold", type: "int", default: 8, min: 1, max: 1000, env: "OPENVIKING_COMMIT_TURN_THRESHOLD", capability: "capture" },
143
+ { name: "autoCommitOnCompact", type: "bool", default: true, env: "OPENVIKING_AUTO_COMMIT_ON_COMPACT", capability: "capture" },
144
+ { name: "writePathAsync", type: "bool", default: true, env: "OPENVIKING_WRITE_PATH_ASYNC", capability: "capture" },
145
+
146
+ // ── session lifecycle ─────────────────────────────────────────────────
147
+ { name: "noAutoInject", type: "bool", default: false, env: "OPENVIKING_NO_AUTO_INJECT", capability: "session" },
148
+ { name: "profileTokenBudget", type: "int", default: 10000, min: 500, max: 50000, env: "OPENVIKING_PROFILE_TOKEN_BUDGET", aliases: ["profileBudget"], capability: "session" },
149
+ { name: "resumeContextBudget", type: "int", default: 32000, min: 1024, max: 128000, env: "OPENVIKING_RESUME_CONTEXT_BUDGET", capability: "session" },
150
+ { name: "resumeArchiveInject", type: "bool", default: true, env: "OPENVIKING_RESUME_ARCHIVE_INJECT", capability: "session" },
151
+ { name: "resumeArchiveTokenBudget", type: "int", default: 32000, min: 0, max: 128000, env: "OPENVIKING_RESUME_ARCHIVE_TOKEN_BUDGET", capability: "session" },
152
+ { name: "resumeArchiveMaxChars", type: "int", default: 6000, min: 1000, max: 200000, env: "OPENVIKING_RESUME_ARCHIVE_MAX_CHARS", capability: "session" },
153
+ { name: "skillExperience", type: "bool", default: false, env: "OPENVIKING_SKILL_EXPERIENCE", capability: "session" },
154
+ { name: "skillExperienceLimit", type: "int", default: 3, min: 1, max: 50, env: "OPENVIKING_SKILL_EXPERIENCE_LIMIT", capability: "session" },
155
+ { name: "skipSubagentSessions", type: "bool", default: false, env: "OPENVIKING_SKIP_SUBAGENT_SESSIONS", capability: "session" },
156
+ { name: "repoContext", type: "bool", default: true, capability: "session" },
157
+ { name: "repoContextCacheTtlMs", type: "int", default: 60000, min: 1000, max: 3600000, capability: "session" },
158
+
159
+ { name: "takeoverEnabled", type: "bool", default: true, env: "OPENVIKING_TAKEOVER", capability: "session" },
160
+ { name: "takeoverTokenThreshold", type: "int", default: 30000, min: 1, max: 1000000, capability: "session" },
161
+ { name: "takeoverKeepRecentTurns", type: "int", default: 3, min: 0, max: 100, capability: "session" },
162
+ { name: "takeoverOverviewBudget", type: "int", default: 3000, min: 100, max: 50000, capability: "session" },
163
+ { name: "takeoverOverviewPollMs", type: "int", default: 2000, min: 0, max: 60000, capability: "session" },
164
+ { name: "takeoverOverviewPollMax", type: "int", default: 15, min: 1, max: 120, capability: "session" },
165
+
166
+ { name: "bypassSession", type: "bool", default: false, env: "OPENVIKING_BYPASS_SESSION", capability: "session" },
167
+ {
168
+ name: "bypassSessionPatterns",
169
+ type: "list",
170
+ default: [],
171
+ env: "OPENVIKING_BYPASS_SESSION_PATTERNS",
172
+ // pi shipped this under its own name before the shared matcher existed.
173
+ aliases: ["bypassPatterns"],
174
+ workspace: "bypass.session_patterns",
175
+ capability: "session",
176
+ },
177
+
178
+ // ── debug ─────────────────────────────────────────────────────────────
179
+ { name: "debug", type: "bool", default: false, env: "OPENVIKING_DEBUG", capability: "debug" },
180
+ // The default path is per-harness and built from a log directory the loader
181
+ // owns, so the schema only carries what a user set.
182
+ { name: "debugLogPath", type: "string", default: "", env: "OPENVIKING_DEBUG_LOG", capability: "debug" },
183
+ { name: "logLevel", type: "enum", values: ["silent", "error", "info"], default: "error", env: "OPENVIKING_LOG_LEVEL", capability: "debug" },
184
+ ];
185
+
186
+ export const KNOB_BY_NAME = new Map(KNOBS.map((knob) => [knob.name, knob]));
187
+
188
+ /** Every accepted spelling → the canonical knob it means. */
189
+ export const KNOB_BY_KEY = new Map();
190
+ for (const knob of KNOBS) {
191
+ KNOB_BY_KEY.set(knob.name, knob);
192
+ for (const alias of knob.aliases || []) KNOB_BY_KEY.set(alias, knob);
193
+ }
194
+
195
+ /** Every key a user may legitimately write in `ovcli.conf`'s plugin section. */
196
+ export function pluginConfigKeys() {
197
+ return new Set(KNOB_BY_KEY.keys());
198
+ }
199
+
200
+ /** The workspace file's dotted keys → the knob each one sets. */
201
+ export const WORKSPACE_KNOB_MAP = Object.fromEntries(
202
+ KNOBS.filter((knob) => knob.workspace).map((knob) => [knob.workspace, knob.name]),
203
+ );
204
+
205
+ /** The enum members and clamps the workspace validator reports on. */
206
+ export const WORKSPACE_ENUMS = Object.fromEntries(
207
+ KNOBS.filter((knob) => knob.workspace).map((knob) => [knob.workspace, knob.values || null]),
208
+ );
209
+
210
+ export const WORKSPACE_RANGES = Object.fromEntries(
211
+ KNOBS.filter((knob) => knob.workspace && knob.min !== undefined && knob.max !== undefined)
212
+ .map((knob) => [knob.workspace, {
213
+ min: knob.min,
214
+ max: knob.max,
215
+ integer: knob.type === "int",
216
+ }]),
217
+ );
218
+
219
+ /**
220
+ * The harnesses that may appear as a per-harness object inside `plugin`.
221
+ *
222
+ * The key is snake_case because that is what ov.conf's own sections use; the
223
+ * hyphenated spelling a host calls itself (`claude-code`, `trae-cn`) is
224
+ * accepted too, so a user who copies the harness name out of the installer
225
+ * lands on the right override instead of on a silent no-op.
226
+ */
227
+ export const HARNESS_KEYS = {
228
+ claudeCode: "claude_code",
229
+ codex: "codex",
230
+ cursor: "cursor",
231
+ trae: "trae",
232
+ traeCn: "trae_cn",
233
+ zcode: "zcode",
234
+ opencode: "opencode",
235
+ dsh: "dsh",
236
+ pi: "pi",
237
+ openclaw: "openclaw",
238
+ };
239
+
240
+ export function harnessKey(harness) {
241
+ return String(harness || "").trim().toLowerCase().replace(/-/g, "_");
242
+ }
243
+
244
+ /** Both accepted spellings of every harness, for the `plugin` section. */
245
+ export const HARNESS_CONFIG_KEYS = new Set(
246
+ Object.values(HARNESS_KEYS).flatMap((key) => [key, key.replace(/_/g, "-")]),
247
+ );
248
+
249
+ export function knobDefault(knob, harness = "") {
250
+ if (harness && knob.harness && knob.harness[harness] !== undefined) return knob.harness[harness];
251
+ return Array.isArray(knob.default) ? [...knob.default] : knob.default;
252
+ }
253
+
254
+ const TRUE_WORDS = ["1", "true", "yes", "on"];
255
+ const FALSE_WORDS = ["0", "false", "no", "off"];
256
+
257
+ /**
258
+ * Coerce one raw value to the knob's type.
259
+ *
260
+ * A value out of range is clamped and a value of the wrong shape falls back to
261
+ * the default: a hook that dies because someone typed a letter into a number
262
+ * would take the host's session down with it.
263
+ */
264
+ export function coerceKnobValue(knob, raw, fallback) {
265
+ if (raw === undefined || raw === null) return fallback;
266
+ switch (knob.type) {
267
+ case "bool": {
268
+ if (typeof raw === "boolean") return raw;
269
+ const word = String(raw).trim().toLowerCase();
270
+ if (TRUE_WORDS.includes(word)) return true;
271
+ if (FALSE_WORDS.includes(word)) return false;
272
+ return fallback;
273
+ }
274
+ case "int":
275
+ case "number": {
276
+ if (typeof raw === "string" && !raw.trim()) return fallback;
277
+ const value = knob.type === "int" ? Math.round(Number(raw)) : Number(raw);
278
+ if (!Number.isFinite(value)) return fallback;
279
+ const floored = knob.min === undefined ? value : Math.max(knob.min, value);
280
+ return knob.max === undefined ? floored : Math.min(knob.max, floored);
281
+ }
282
+ case "enum": {
283
+ const word = String(raw ?? "").trim();
284
+ return (knob.values || []).includes(word) ? word : fallback;
285
+ }
286
+ // A peer template may be one string or an ordered list of fallbacks, and
287
+ // `String([a, b])` would quietly turn the list into one bogus template.
288
+ case "template": {
289
+ if (Array.isArray(raw)) {
290
+ const items = raw.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
291
+ return items.length ? items : fallback;
292
+ }
293
+ const text = String(raw ?? "").trim();
294
+ return text ? text : fallback;
295
+ }
296
+ case "list": {
297
+ const items = Array.isArray(raw)
298
+ ? raw
299
+ : typeof raw === "string" ? raw.split(",") : null;
300
+ if (!items) return fallback;
301
+ return items.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
302
+ }
303
+ default: {
304
+ const text = String(raw ?? "").trim();
305
+ return text ? text : fallback;
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Resolve every knob through the layers, lowest priority first.
312
+ *
313
+ * `layers` is `[{ name, data }]`; `env` is the top layer and needs no entry.
314
+ * Returns the settings plus the names a layer actually supplied, which is what
315
+ * the `*Configured` flags report — several call sites send a field to the
316
+ * server only when the user asked for it, and a default must not look asked-for.
317
+ */
318
+ export function resolveKnobs({ harness = "", layers = [], env = {} } = {}) {
319
+ const settings = {};
320
+ const configured = new Set();
321
+ const sources = {};
322
+ // A value that would not parse is not a configuration: without this sentinel
323
+ // `recallLimit: "abc"` would report as configured and send a default to the
324
+ // server dressed up as the user's choice.
325
+ const UNPARSEABLE = Symbol("unparseable");
326
+
327
+ const take = (knob, raw, source, current) => {
328
+ const value = coerceKnobValue(knob, raw, UNPARSEABLE);
329
+ if (value === UNPARSEABLE) return current;
330
+ configured.add(knob.name);
331
+ sources[knob.name] = source;
332
+ return value;
333
+ };
334
+
335
+ for (const knob of KNOBS) {
336
+ let value = knobDefault(knob, harness);
337
+ for (const layer of layers) {
338
+ const data = layer?.data;
339
+ if (!data || typeof data !== "object") continue;
340
+ // Aliases first so the canonical name wins when one layer carries both:
341
+ // a file with `recallCompressThinking` beside its older
342
+ // `recallCompressReasoningEffort` means the newer one.
343
+ for (const key of [...(knob.aliases || []), knob.name]) {
344
+ if (!Object.prototype.hasOwnProperty.call(data, key)) continue;
345
+ if (data[key] === undefined) continue;
346
+ value = take(knob, data[key], layer.name || "file", value);
347
+ }
348
+ }
349
+ if (knob.env) {
350
+ const raw = env[knob.env];
351
+ if (raw !== undefined && raw !== null && String(raw) !== "") {
352
+ value = take(knob, raw, "env", value);
353
+ }
354
+ }
355
+ settings[knob.name] = value;
356
+ }
357
+
358
+ return { settings, configured, sources };
359
+ }