1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
import { readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { parse } from "smol-toml";
import type { PermissionRule, Ruleset } from "../permission/index.js";
import type { DispatchConfig } from "../types/index.js";
import { validateConfig } from "./schema.js";
const DEFAULT_CONFIG: DispatchConfig = { permissions: {} };
const VALID_ACTIONS = new Set(["allow", "deny", "ask"]);
function validateAction(raw: string): "allow" | "deny" | "ask" {
if (VALID_ACTIONS.has(raw)) return raw as "allow" | "deny" | "ask";
console.warn(`dispatch: unrecognized action "${raw}", defaulting to "ask"`);
return "ask";
}
// Load dispatch.toml from the given directory
export function loadConfig(dir: string): DispatchConfig {
const tomlPath = join(dir, "dispatch.toml");
let raw: unknown;
try {
const content = readFileSync(tomlPath, "utf-8");
raw = parse(content);
} catch (err: unknown) {
if (err instanceof Error && (err as NodeJS.ErrnoException).code === "ENOENT") {
// File doesn't exist — return empty default
return DEFAULT_CONFIG;
}
console.warn(
`dispatch: failed to parse dispatch.toml: ${err instanceof Error ? err.message : String(err)}`,
);
throw err;
}
const { config, errors } = validateConfig(raw);
for (const e of errors) {
console.warn(`dispatch: config warning at ${e.path}: ${e.message}`);
}
return config;
}
// Convert the config's permission block to a Ruleset
export function configToRuleset(config: DispatchConfig): Ruleset {
const home = homedir();
const rules: PermissionRule[] = [];
for (const [permission, value] of Object.entries(config.permissions)) {
if (typeof value === "string") {
const action = validateAction(value);
rules.push({ permission, pattern: "*", action });
} else {
for (const [rawPattern, rawAction] of Object.entries(value)) {
const pattern = rawPattern
.replace(/^\$HOME(?=[/\\]|$)/, home)
.replace(/^~(?=[/\\]|$)/, home);
const action = validateAction(rawAction);
rules.push({ permission, pattern, action });
}
}
}
return rules;
}
|