botholomew 0.19.3 → 0.19.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botholomew",
3
- "version": "0.19.3",
3
+ "version": "0.19.4",
4
4
  "description": "An autonomous AI agent for knowledge work — works your task queue while you sleep.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,3 +1,4 @@
1
+ import { lstat, readlink, stat } from "node:fs/promises";
1
2
  import { getConfigPath } from "../constants.ts";
2
3
  import { setLogLevel } from "../utils/logger.ts";
3
4
  import {
@@ -44,6 +45,9 @@ export async function loadConfig(
44
45
  projectDir: string,
45
46
  ): Promise<BotholomewConfig> {
46
47
  const configPath = getConfigPath(projectDir);
48
+
49
+ await assertNotDanglingSymlink(configPath);
50
+
47
51
  const file = Bun.file(configPath);
48
52
 
49
53
  let userConfig: DeepPartial<BotholomewConfig> = {};
@@ -65,6 +69,31 @@ export async function loadConfig(
65
69
  return config;
66
70
  }
67
71
 
72
+ async function assertNotDanglingSymlink(configPath: string): Promise<void> {
73
+ let lst: Awaited<ReturnType<typeof lstat>>;
74
+ try {
75
+ lst = await lstat(configPath);
76
+ } catch (err) {
77
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return;
78
+ throw err;
79
+ }
80
+ if (!lst.isSymbolicLink()) return;
81
+ try {
82
+ await stat(configPath);
83
+ } catch (err) {
84
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
85
+ const target = await readlink(configPath).catch(() => "<unreadable>");
86
+ throw new Error(
87
+ `Config file is a symlink to a missing target: ${configPath} -> ${target}. ` +
88
+ `Symlink targets are resolved relative to the symlink's own directory, ` +
89
+ `not the current working directory — use an absolute path or a target ` +
90
+ `relative to ${configPath.replace(/\/[^/]+$/, "")}.`,
91
+ );
92
+ }
93
+ throw err;
94
+ }
95
+ }
96
+
68
97
  export async function saveConfig(
69
98
  projectDir: string,
70
99
  config: DeepPartial<BotholomewConfig>,