feat: add /plan command and allow gh commands in plan mode (#544)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-14 15:29:44 -08:00
committed by GitHub
parent 38b86b756e
commit 565010f4f7
4 changed files with 131 additions and 2 deletions

View File

@@ -61,6 +61,19 @@ const SAFE_GIT_SUBCOMMANDS = new Set([
"remote",
]);
// gh CLI read-only commands: category -> allowed actions
// null means any action is allowed for that category
const SAFE_GH_COMMANDS: Record<string, Set<string> | null> = {
pr: new Set(["list", "status", "checks", "diff", "view"]),
issue: new Set(["list", "status", "view"]),
repo: new Set(["list", "view", "gitignore", "license"]),
run: new Set(["list", "view", "watch", "download"]),
release: new Set(["list", "view", "download"]),
search: null, // all search subcommands are read-only
api: null, // usually GET requests for exploration
status: null, // top-level command, no action needed
};
/**
* Read-only bundled skill scripts that are safe to execute without approval.
* Only scripts from the bundled searching-messages skill are allowed.
@@ -168,6 +181,29 @@ function isSafeSegment(segment: string): boolean {
}
return SAFE_GIT_SUBCOMMANDS.has(subcommand);
}
if (command === "gh") {
const category = tokens[1];
if (!category) {
return false;
}
if (!(category in SAFE_GH_COMMANDS)) {
return false;
}
const allowedActions = SAFE_GH_COMMANDS[category];
// null means any action is allowed (e.g., gh search, gh api, gh status)
if (allowedActions === null) {
return true;
}
// undefined means category not in map (shouldn't happen after 'in' check)
if (allowedActions === undefined) {
return false;
}
const action = tokens[2];
if (!action) {
return false;
}
return allowedActions.has(action);
}
if (command === "find") {
return !/-delete|\s-exec\b/.test(segment);
}