@pushpalsdev/cli 1.0.19 → 1.0.21

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.
@@ -101,7 +101,6 @@ function normalizeLoopbackHttpUrl(value, fallbackPort) {
101
101
  // ../shared/src/config.ts
102
102
  var PROJECT_ROOT = resolve(import.meta.dir, "..", "..", "..");
103
103
  var DEFAULT_CONFIG_DIR = "configs";
104
- var LEGACY_CONFIG_DIR = "config";
105
104
  var TRUTHY = new Set(["1", "true", "yes", "on"]);
106
105
  var FALSY = new Set(["0", "false", "no", "off"]);
107
106
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE = 8;
@@ -114,6 +113,7 @@ var DEFAULT_WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS = 180000;
114
113
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS = 45000;
115
114
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS = 16000;
116
115
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS = 8000;
116
+ var DEFAULT_WORKERPALS_EXECUTOR = "openai_codex";
117
117
  var DEFAULT_WORKERPALS_EXECUTOR_RESULT_PREFIX = "__PUSHPALS_OH_RESULT__ ";
118
118
  var DEFAULT_REMOTEBUDDY_MEMORY_MAX_RECALL_ITEMS = 12;
119
119
  var DEFAULT_REMOTEBUDDY_MEMORY_MAX_RECALL_CHARS = 2400;
@@ -155,6 +155,12 @@ function parseTomlFile(path) {
155
155
  return {};
156
156
  return parsed;
157
157
  }
158
+ function parseRequiredTomlFile(path) {
159
+ if (!existsSync(path)) {
160
+ throw new Error(`Missing required runtime config file: ${path}`);
161
+ }
162
+ return parseTomlFile(path);
163
+ }
158
164
  function isObject(value) {
159
165
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
160
166
  }
@@ -260,20 +266,7 @@ function resolveRuntimeConfigDir(projectRoot, configuredDir) {
260
266
  if (configuredDir && configuredDir.trim()) {
261
267
  return resolvePathFromRoot(projectRoot, configuredDir);
262
268
  }
263
- const canonicalDir = resolvePathFromRoot(projectRoot, DEFAULT_CONFIG_DIR);
264
- const legacyDir = resolvePathFromRoot(projectRoot, LEGACY_CONFIG_DIR);
265
- if (existsSync(join(canonicalDir, "default.toml")))
266
- return canonicalDir;
267
- if (existsSync(join(legacyDir, "default.toml")))
268
- return legacyDir;
269
- return canonicalDir;
270
- }
271
- function parseTomlWithLegacyFallback(primaryPath, fallbackPath) {
272
- if (existsSync(primaryPath))
273
- return parseTomlFile(primaryPath);
274
- if (fallbackPath && existsSync(fallbackPath))
275
- return parseTomlFile(fallbackPath);
276
- return {};
269
+ return resolvePathFromRoot(projectRoot, DEFAULT_CONFIG_DIR);
277
270
  }
278
271
  function normalizeBackend(value) {
279
272
  const text = value.trim().toLowerCase();
@@ -345,17 +338,15 @@ function loadPushPalsConfig(options = {}) {
345
338
  const projectRoot = resolve(projectRootOverride);
346
339
  const configDirOverride = firstNonEmpty(options.configDir, process.env.PUSHPALS_CONFIG_DIR_OVERRIDE, "");
347
340
  const configDir = resolveRuntimeConfigDir(projectRoot, configDirOverride);
348
- const legacyConfigDir = resolvePathFromRoot(projectRoot, LEGACY_CONFIG_DIR);
349
- const fallbackConfigDir = !configDirOverride && configDir !== legacyConfigDir ? legacyConfigDir : "";
350
341
  const cacheKey = `${projectRoot}::${configDir}::${process.env.PUSHPALS_PROFILE ?? ""}`;
351
342
  if (!options.reload && cachedConfig && cachedConfigKey === cacheKey) {
352
343
  return cachedConfig;
353
344
  }
354
- const defaultToml = parseTomlWithLegacyFallback(join(configDir, "default.toml"), fallbackConfigDir ? join(fallbackConfigDir, "default.toml") : undefined);
345
+ const defaultToml = parseRequiredTomlFile(join(configDir, "default.toml"));
355
346
  const preferredProfile = firstNonEmpty(process.env.PUSHPALS_PROFILE, asString(defaultToml.profile, "dev"), "dev");
356
- const profileToml = parseTomlWithLegacyFallback(join(configDir, `${preferredProfile}.toml`), fallbackConfigDir ? join(fallbackConfigDir, `${preferredProfile}.toml`) : undefined);
357
- const localExampleToml = parseTomlWithLegacyFallback(join(configDir, "local.example.toml"), fallbackConfigDir ? join(fallbackConfigDir, "local.example.toml") : undefined);
358
- const localToml = parseTomlWithLegacyFallback(join(configDir, "local.toml"), fallbackConfigDir ? join(fallbackConfigDir, "local.toml") : undefined);
347
+ const profileToml = parseTomlFile(join(configDir, `${preferredProfile}.toml`));
348
+ const localExampleToml = parseTomlFile(join(configDir, "local.example.toml"));
349
+ const localToml = parseTomlFile(join(configDir, "local.toml"));
359
350
  const merged = mergeDeep(mergeDeep(mergeDeep(defaultToml, profileToml), localExampleToml), localToml);
360
351
  const profile = firstNonEmpty(process.env.PUSHPALS_PROFILE, asString(merged.profile, preferredProfile), preferredProfile);
361
352
  const sessionId = firstNonEmpty(process.env.PUSHPALS_SESSION_ID, asString(merged.session_id, "dev"), "dev");
@@ -462,7 +453,7 @@ function loadPushPalsConfig(options = {}) {
462
453
  const workerOpenHandsNode = getObject(workerNode, "openhands");
463
454
  const workerPollMs = Math.max(200, asInt(parseIntEnv("WORKERPALS_POLL_MS") ?? workerNode.poll_ms, 2000));
464
455
  const workerHeartbeatMs = Math.max(200, asInt(parseIntEnv("WORKERPALS_HEARTBEAT_MS") ?? workerNode.heartbeat_ms, 5000));
465
- const workerExecutor = firstNonEmpty(process.env.WORKERPALS_EXECUTOR, asString(workerNode.executor, "openhands"), "openhands").toLowerCase();
456
+ const workerExecutor = firstNonEmpty(process.env.WORKERPALS_EXECUTOR, asString(workerNode.executor, DEFAULT_WORKERPALS_EXECUTOR), DEFAULT_WORKERPALS_EXECUTOR).toLowerCase();
466
457
  const workerOpenHandsPython = firstNonEmpty(process.env.WORKERPALS_OPENHANDS_PYTHON, asString(workerNode.openhands_python, "python"), "python");
467
458
  const workerOpenHandsTimeoutMs = Math.max(1e4, asInt(parseIntEnv("WORKERPALS_OPENHANDS_TIMEOUT_MS") ?? workerNode.openhands_timeout_ms, 1800000));
468
459
  const workerMiniswePython = firstNonEmpty(process.env.WORKERPALS_MINISWE_PYTHON, asString(workerNode.miniswe_python, "python"), "python");
@@ -964,18 +955,10 @@ function resolveClientConfigDir(projectRoot, runtimeRoot, explicitConfigDir) {
964
955
  if (runtimeHasConfigDir(runtimeRoot, "configs")) {
965
956
  return runtimeCanonical;
966
957
  }
967
- const runtimeLegacy = resolve2(runtimeRoot, "config");
968
- if (runtimeHasConfigDir(runtimeRoot, "config")) {
969
- return runtimeLegacy;
970
- }
971
958
  const projectCanonical = resolve2(projectRoot, "configs");
972
959
  if (runtimeHasConfigDir(projectRoot, "configs")) {
973
960
  return projectCanonical;
974
961
  }
975
- const projectLegacy = resolve2(projectRoot, "config");
976
- if (runtimeHasConfigDir(projectRoot, "config")) {
977
- return projectLegacy;
978
- }
979
962
  return runtimeCanonical;
980
963
  }
981
964
  function toDisplayPath(currentRoot, pathValue) {
@@ -1025,8 +1008,7 @@ function evaluateClientRuntimePreflight(options) {
1025
1008
  });
1026
1009
  }
1027
1010
  const localTomlPath = resolve2(runtimeRoot, "configs", "local.toml");
1028
- const legacyLocalTomlPath = resolve2(runtimeRoot, "config", "local.toml");
1029
- if (!existsSync2(localTomlPath) && !existsSync2(legacyLocalTomlPath)) {
1011
+ if (!existsSync2(localTomlPath)) {
1030
1012
  const localExamplePath = resolve2(runtimeRoot, "configs", "local.example.toml");
1031
1013
  issues.push({
1032
1014
  code: "missing_local_toml",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,4 +1,4 @@
1
- default_backend = "miniswe"
1
+ default_backend = "openai_codex"
2
2
 
3
3
  [env]
4
4
  shared_passthrough = [
@@ -118,7 +118,7 @@ max_payload_bytes = 262144
118
118
  [workerpals]
119
119
  poll_ms = 2000
120
120
  heartbeat_ms = 5000
121
- executor = "miniswe"
121
+ executor = "openai_codex"
122
122
  openhands_python = "python"
123
123
  openhands_timeout_ms = 1800000
124
124
  miniswe_python = "python"
@@ -1,6 +1,6 @@
1
- import { readFileSync } from "fs";
2
- import { resolve } from "path";
3
- import { loadPushPalsConfig } from "shared";
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { DEFAULT_WORKERPALS_EXECUTOR, loadPushPalsConfig } from "shared";
4
4
  import type { ExecutorBackend } from "../common/types.js";
5
5
  import { MINISWE_BACKEND } from "./miniswe_backend.js";
6
6
  import { OPENAI_CODEX_BACKEND } from "./openai_codex_backend.js";
@@ -8,7 +8,7 @@ import { OPENHANDS_BACKEND } from "./openhands_backend.js";
8
8
  import type { BackendTaskExecutor, DockerBackendSpec } from "./types.js";
9
9
  import { registerBackendTaskExecutor } from "./task_execute_registry.js";
10
10
 
11
- const FALLBACK_DEFAULT_EXECUTOR: ExecutorBackend = "miniswe";
11
+ const FALLBACK_DEFAULT_EXECUTOR: ExecutorBackend = DEFAULT_WORKERPALS_EXECUTOR;
12
12
 
13
13
  interface BackendTomlShape {
14
14
  default_backend?: string;
@@ -32,16 +32,25 @@ function toStrings(value: unknown): string[] {
32
32
  .filter(Boolean);
33
33
  }
34
34
 
35
- function loadBackendToml(): BackendTomlShape {
36
- const projectRoot = loadPushPalsConfig().projectRoot;
37
- const path = resolve(projectRoot, "configs", "backend.toml");
38
- try {
39
- const parsed = Bun.TOML.parse(readFileSync(path, "utf-8")) as unknown;
40
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
41
- return parsed as BackendTomlShape;
42
- } catch {
43
- return {};
35
+ export function parseRequiredBackendToml(path: string): BackendTomlShape {
36
+ if (!existsSync(path)) {
37
+ throw new Error(`Missing required runtime backend config file: ${path}`);
38
+ }
39
+
40
+ const parsed = Bun.TOML.parse(readFileSync(path, "utf-8")) as unknown;
41
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
42
+ throw new Error(`Invalid runtime backend config file: ${path}`);
44
43
  }
44
+ return parsed as BackendTomlShape;
45
+ }
46
+
47
+ export function resolveBackendTomlPath(configDir: string): string {
48
+ return join(configDir, "backend.toml");
49
+ }
50
+
51
+ function loadBackendToml(): BackendTomlShape {
52
+ const path = resolveBackendTomlPath(loadPushPalsConfig().configDir);
53
+ return parseRequiredBackendToml(path);
45
54
  }
46
55
 
47
56
  const config = loadBackendToml();
@@ -58,7 +67,9 @@ export const DEFAULT_EXECUTOR = (
58
67
  typeof config.default_backend === "string" &&
59
68
  EXECUTOR_BACKENDS.includes(config.default_backend as ExecutorBackend)
60
69
  ? config.default_backend
61
- : (EXECUTOR_BACKENDS[0] ?? FALLBACK_DEFAULT_EXECUTOR)
70
+ : EXECUTOR_BACKENDS.includes(FALLBACK_DEFAULT_EXECUTOR)
71
+ ? FALLBACK_DEFAULT_EXECUTOR
72
+ : (EXECUTOR_BACKENDS[0] ?? FALLBACK_DEFAULT_EXECUTOR)
62
73
  ) as ExecutorBackend;
63
74
 
64
75
  export const SHARED_DOCKER_PASSTHROUGH_ENV = toStrings(config.env?.shared_passthrough);
@@ -1,4 +1,4 @@
1
- default_backend = "miniswe"
1
+ default_backend = "openai_codex"
2
2
 
3
3
  [env]
4
4
  shared_passthrough = [
@@ -118,7 +118,7 @@ max_payload_bytes = 262144
118
118
  [workerpals]
119
119
  poll_ms = 2000
120
120
  heartbeat_ms = 5000
121
- executor = "miniswe"
121
+ executor = "openai_codex"
122
122
  openhands_python = "python"
123
123
  openhands_timeout_ms = 1800000
124
124
  miniswe_python = "python"
@@ -67,21 +67,11 @@ function resolveClientConfigDir(
67
67
  return runtimeCanonical;
68
68
  }
69
69
 
70
- const runtimeLegacy = resolve(runtimeRoot, "config");
71
- if (runtimeHasConfigDir(runtimeRoot, "config")) {
72
- return runtimeLegacy;
73
- }
74
-
75
70
  const projectCanonical = resolve(projectRoot, "configs");
76
71
  if (runtimeHasConfigDir(projectRoot, "configs")) {
77
72
  return projectCanonical;
78
73
  }
79
74
 
80
- const projectLegacy = resolve(projectRoot, "config");
81
- if (runtimeHasConfigDir(projectRoot, "config")) {
82
- return projectLegacy;
83
- }
84
-
85
75
  return runtimeCanonical;
86
76
  }
87
77
 
@@ -145,8 +135,7 @@ export function evaluateClientRuntimePreflight(
145
135
  }
146
136
 
147
137
  const localTomlPath = resolve(runtimeRoot, "configs", "local.toml");
148
- const legacyLocalTomlPath = resolve(runtimeRoot, "config", "local.toml");
149
- if (!existsSync(localTomlPath) && !existsSync(legacyLocalTomlPath)) {
138
+ if (!existsSync(localTomlPath)) {
150
139
  const localExamplePath = resolve(runtimeRoot, "configs", "local.example.toml");
151
140
  issues.push({
152
141
  code: "missing_local_toml",
@@ -10,7 +10,6 @@ interface TomlObject {
10
10
 
11
11
  const PROJECT_ROOT = resolve(import.meta.dir, "..", "..", "..");
12
12
  const DEFAULT_CONFIG_DIR = "configs";
13
- const LEGACY_CONFIG_DIR = "config";
14
13
 
15
14
  const TRUTHY = new Set(["1", "true", "yes", "on"]);
16
15
  const FALSY = new Set(["0", "false", "no", "off"]);
@@ -24,6 +23,7 @@ const DEFAULT_WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS = 180_000;
24
23
  const DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS = 45_000;
25
24
  const DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS = 16_000;
26
25
  const DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS = 8_000;
26
+ export const DEFAULT_WORKERPALS_EXECUTOR = "openai_codex";
27
27
  const DEFAULT_WORKERPALS_EXECUTOR_RESULT_PREFIX = "__PUSHPALS_OH_RESULT__ ";
28
28
  const DEFAULT_REMOTEBUDDY_MEMORY_MAX_RECALL_ITEMS = 12;
29
29
  const DEFAULT_REMOTEBUDDY_MEMORY_MAX_RECALL_CHARS = 2400;
@@ -320,6 +320,13 @@ function parseTomlFile(path: string): TomlObject {
320
320
  return parsed as TomlObject;
321
321
  }
322
322
 
323
+ function parseRequiredTomlFile(path: string): TomlObject {
324
+ if (!existsSync(path)) {
325
+ throw new Error(`Missing required runtime config file: ${path}`);
326
+ }
327
+ return parseTomlFile(path);
328
+ }
329
+
323
330
  function isObject(value: unknown): value is TomlObject {
324
331
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
325
332
  }
@@ -424,20 +431,7 @@ function resolveRuntimeConfigDir(projectRoot: string, configuredDir?: string): s
424
431
  return resolvePathFromRoot(projectRoot, configuredDir);
425
432
  }
426
433
 
427
- const canonicalDir = resolvePathFromRoot(projectRoot, DEFAULT_CONFIG_DIR);
428
- const legacyDir = resolvePathFromRoot(projectRoot, LEGACY_CONFIG_DIR);
429
- if (existsSync(join(canonicalDir, "default.toml"))) return canonicalDir;
430
- if (existsSync(join(legacyDir, "default.toml"))) return legacyDir;
431
- return canonicalDir;
432
- }
433
-
434
- function parseTomlWithLegacyFallback(
435
- primaryPath: string,
436
- fallbackPath?: string,
437
- ): TomlObject {
438
- if (existsSync(primaryPath)) return parseTomlFile(primaryPath);
439
- if (fallbackPath && existsSync(fallbackPath)) return parseTomlFile(fallbackPath);
440
- return {};
434
+ return resolvePathFromRoot(projectRoot, DEFAULT_CONFIG_DIR);
441
435
  }
442
436
 
443
437
  function normalizeBackend(value: string): string {
@@ -570,35 +564,20 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
570
564
  "",
571
565
  );
572
566
  const configDir = resolveRuntimeConfigDir(projectRoot, configDirOverride);
573
- const legacyConfigDir = resolvePathFromRoot(projectRoot, LEGACY_CONFIG_DIR);
574
- const fallbackConfigDir =
575
- !configDirOverride && configDir !== legacyConfigDir ? legacyConfigDir : "";
576
567
  const cacheKey = `${projectRoot}::${configDir}::${process.env.PUSHPALS_PROFILE ?? ""}`;
577
568
  if (!options.reload && cachedConfig && cachedConfigKey === cacheKey) {
578
569
  return cachedConfig;
579
570
  }
580
571
 
581
- const defaultToml = parseTomlWithLegacyFallback(
582
- join(configDir, "default.toml"),
583
- fallbackConfigDir ? join(fallbackConfigDir, "default.toml") : undefined,
584
- );
572
+ const defaultToml = parseRequiredTomlFile(join(configDir, "default.toml"));
585
573
  const preferredProfile = firstNonEmpty(
586
574
  process.env.PUSHPALS_PROFILE,
587
575
  asString(defaultToml.profile, "dev"),
588
576
  "dev",
589
577
  );
590
- const profileToml = parseTomlWithLegacyFallback(
591
- join(configDir, `${preferredProfile}.toml`),
592
- fallbackConfigDir ? join(fallbackConfigDir, `${preferredProfile}.toml`) : undefined,
593
- );
594
- const localExampleToml = parseTomlWithLegacyFallback(
595
- join(configDir, "local.example.toml"),
596
- fallbackConfigDir ? join(fallbackConfigDir, "local.example.toml") : undefined,
597
- );
598
- const localToml = parseTomlWithLegacyFallback(
599
- join(configDir, "local.toml"),
600
- fallbackConfigDir ? join(fallbackConfigDir, "local.toml") : undefined,
601
- );
578
+ const profileToml = parseTomlFile(join(configDir, `${preferredProfile}.toml`));
579
+ const localExampleToml = parseTomlFile(join(configDir, "local.example.toml"));
580
+ const localToml = parseTomlFile(join(configDir, "local.toml"));
602
581
  const merged = mergeDeep(
603
582
  mergeDeep(mergeDeep(defaultToml, profileToml), localExampleToml),
604
583
  localToml,
@@ -889,8 +868,8 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
889
868
  );
890
869
  const workerExecutor = firstNonEmpty(
891
870
  process.env.WORKERPALS_EXECUTOR,
892
- asString(workerNode.executor, "openhands"),
893
- "openhands",
871
+ asString(workerNode.executor, DEFAULT_WORKERPALS_EXECUTOR),
872
+ DEFAULT_WORKERPALS_EXECUTOR,
894
873
  ).toLowerCase();
895
874
  const workerOpenHandsPython = firstNonEmpty(
896
875
  process.env.WORKERPALS_OPENHANDS_PYTHON,
@@ -44,6 +44,7 @@ export {
44
44
  type ResolveGitTokenOptions,
45
45
  } from "./git_backend.js";
46
46
  export {
47
+ DEFAULT_WORKERPALS_EXECUTOR,
47
48
  invalidatePushPalsConfigCache,
48
49
  loadPushPalsConfig,
49
50
  sanitizePushPalsConfigForLogging,
@@ -14,7 +14,6 @@ export type LocalBuddyRuntimeSnapshot = {
14
14
  export const DEFAULT_LOCALBUDDY_PORT = 3003;
15
15
 
16
16
  const DEFAULT_CONFIG_DIR = "configs";
17
- const LEGACY_CONFIG_DIR = "config";
18
17
  const TRUTHY = new Set(["1", "true", "yes", "on"]);
19
18
  const FALSY = new Set(["0", "false", "no", "off"]);
20
19
 
@@ -71,27 +70,11 @@ export function loadLocalBuddyRuntimeSnapshotFromFiles(
71
70
 
72
71
  const configDirOverride = firstNonEmpty(mergedEnv.PUSHPALS_CONFIG_DIR_OVERRIDE);
73
72
  const configDir = resolveRuntimeConfigDir(workspaceRoot, configDirOverride);
74
- const legacyConfigDir = resolvePathFromRoot(workspaceRoot, LEGACY_CONFIG_DIR);
75
- const fallbackConfigDir =
76
- !configDirOverride && configDir !== legacyConfigDir ? legacyConfigDir : "";
77
-
78
- const defaultToml = readTomlSliceWithFallback(
79
- join(configDir, "default.toml"),
80
- fallbackConfigDir ? join(fallbackConfigDir, "default.toml") : undefined,
81
- );
73
+ const defaultToml = readRequiredTomlSlice(join(configDir, "default.toml"));
82
74
  const preferredProfile = firstNonEmpty(mergedEnv.PUSHPALS_PROFILE, defaultToml.profile, "dev");
83
- const profileToml = readTomlSliceWithFallback(
84
- join(configDir, `${preferredProfile}.toml`),
85
- fallbackConfigDir ? join(fallbackConfigDir, `${preferredProfile}.toml`) : undefined,
86
- );
87
- const localExampleToml = readTomlSliceWithFallback(
88
- join(configDir, "local.example.toml"),
89
- fallbackConfigDir ? join(fallbackConfigDir, "local.example.toml") : undefined,
90
- );
91
- const localToml = readTomlSliceWithFallback(
92
- join(configDir, "local.toml"),
93
- fallbackConfigDir ? join(fallbackConfigDir, "local.toml") : undefined,
94
- );
75
+ const profileToml = readTomlSlice(join(configDir, `${preferredProfile}.toml`));
76
+ const localExampleToml = readTomlSlice(join(configDir, "local.example.toml"));
77
+ const localToml = readTomlSlice(join(configDir, "local.toml"));
95
78
 
96
79
  const mergedLocalbuddy = {
97
80
  ...defaultToml.localbuddy,
@@ -168,11 +151,7 @@ function resolveRuntimeConfigDir(workspaceRoot: string, configuredDir?: string):
168
151
  return resolvePathFromRoot(workspaceRoot, configuredDir);
169
152
  }
170
153
 
171
- const canonicalDir = resolvePathFromRoot(workspaceRoot, DEFAULT_CONFIG_DIR);
172
- const legacyDir = resolvePathFromRoot(workspaceRoot, LEGACY_CONFIG_DIR);
173
- if (existsSync(join(canonicalDir, "default.toml"))) return canonicalDir;
174
- if (existsSync(join(legacyDir, "default.toml"))) return legacyDir;
175
- return canonicalDir;
154
+ return resolvePathFromRoot(workspaceRoot, DEFAULT_CONFIG_DIR);
176
155
  }
177
156
 
178
157
  function parseBoolEnv(value: string | undefined): boolean | undefined {
@@ -190,12 +169,18 @@ function parseIntEnv(value: string | undefined): number | undefined {
190
169
  return Number.isFinite(parsed) ? parsed : undefined;
191
170
  }
192
171
 
193
- function readTomlSliceWithFallback(primaryPath: string, fallbackPath?: string): LocalBuddyTomlSlice {
194
- if (existsSync(primaryPath)) return parseTomlSlice(primaryPath);
195
- if (fallbackPath && existsSync(fallbackPath)) return parseTomlSlice(fallbackPath);
172
+ function readTomlSlice(path: string): LocalBuddyTomlSlice {
173
+ if (existsSync(path)) return parseTomlSlice(path);
196
174
  return {};
197
175
  }
198
176
 
177
+ function readRequiredTomlSlice(path: string): LocalBuddyTomlSlice {
178
+ if (!existsSync(path)) {
179
+ throw new Error(`Missing required runtime config file: ${path}`);
180
+ }
181
+ return parseTomlSlice(path);
182
+ }
183
+
199
184
  function parseTomlSlice(path: string): LocalBuddyTomlSlice {
200
185
  const text = readFileSync(path, "utf8");
201
186
  const result: LocalBuddyTomlSlice = {};