auto-mode 0.20.0 → 0.20.1

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/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as readHostEnvironment, T as resolveXDGDir, a as classifyAction, b as loadConfig, i as recordScope, m as writeActionDiagnostic, n as parseScopeRecordRequest, p as loadPolicy, r as parseActionRequest, t as renderVerdict, v as findGitEntry } from "./render-verdict-0NxrIuIc.js";
2
+ import { C as readHostEnvironment, T as resolveXDGDir, a as classifyAction, b as loadConfig, i as recordScope, m as writeActionDiagnostic, n as parseScopeRecordRequest, p as loadPolicy, r as parseActionRequest, t as renderVerdict, v as findGitEntry } from "./render-verdict-BEZSxyzF.js";
3
3
  import { chmod, mkdir, open, realpath, stat } from "node:fs/promises";
4
4
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
5
  import { randomUUID } from "node:crypto";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { S as resolveConfigPath, _ as tryClassifyEdit, a as classifyAction, b as loadConfig, c as classifyWithModel, d as DecisionRequestError, f as buildDecisionRequest, g as loadClaudeSettings, h as checkContainment, i as recordScope, l as parseModelVerdict, n as parseScopeRecordRequest, o as buildTaskScope, p as loadPolicy, r as parseActionRequest, s as classifyLocally, t as renderVerdict, u as sendDecision, w as PRESETS, x as resolveApiKey, y as DEFAULT_CONFIG } from "./render-verdict-0NxrIuIc.js";
1
+ import { S as resolveConfigPath, _ as tryClassifyEdit, a as classifyAction, b as loadConfig, c as classifyWithModel, d as DecisionRequestError, f as buildDecisionRequest, g as loadClaudeSettings, h as checkContainment, i as recordScope, l as parseModelVerdict, n as parseScopeRecordRequest, o as buildTaskScope, p as loadPolicy, r as parseActionRequest, s as classifyLocally, t as renderVerdict, u as sendDecision, w as PRESETS, x as resolveApiKey, y as DEFAULT_CONFIG } from "./render-verdict-BEZSxyzF.js";
2
2
  //#region src/config/load-claude-rules.ts
3
3
  async function loadClaudeRules(path, host) {
4
4
  return (await loadClaudeSettings(path, host)).rules;
@@ -8681,13 +8681,10 @@ const READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
8681
8681
  "Grep",
8682
8682
  "LS",
8683
8683
  "NotebookRead",
8684
- "WebFetch",
8685
- "WebSearch",
8686
8684
  "TodoWrite"
8687
8685
  ]);
8688
8686
  const SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash"]);
8689
8687
  const READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
8690
- "awk",
8691
8688
  "basename",
8692
8689
  "cat",
8693
8690
  "cksum",
@@ -8700,7 +8697,6 @@ const READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
8700
8697
  "dirname",
8701
8698
  "du",
8702
8699
  "echo",
8703
- "env",
8704
8700
  "false",
8705
8701
  "fd",
8706
8702
  "file",
@@ -8710,7 +8706,6 @@ const READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
8710
8706
  "hostname",
8711
8707
  "id",
8712
8708
  "jq",
8713
- "less",
8714
8709
  "ls",
8715
8710
  "md5sum",
8716
8711
  "nl",
@@ -8735,9 +8730,138 @@ const READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
8735
8730
  "uptime",
8736
8731
  "wc",
8737
8732
  "which",
8738
- "whoami",
8739
- "xxd",
8740
- "yq"
8733
+ "whoami"
8734
+ ]);
8735
+ const UNSAFE_OPTIONS = /* @__PURE__ */ new Map([
8736
+ ["date", {
8737
+ short: ["s"],
8738
+ long: ["set"],
8739
+ valued: {
8740
+ short: [
8741
+ "d",
8742
+ "f",
8743
+ "r"
8744
+ ],
8745
+ attached: ["I"],
8746
+ long: [
8747
+ "date",
8748
+ "file",
8749
+ "reference",
8750
+ "rfc-3339"
8751
+ ]
8752
+ }
8753
+ }],
8754
+ ["fd", {
8755
+ short: ["x", "X"],
8756
+ long: ["exec", "exec-batch"],
8757
+ valued: {
8758
+ short: [
8759
+ "c",
8760
+ "d",
8761
+ "e",
8762
+ "E",
8763
+ "j",
8764
+ "o",
8765
+ "S",
8766
+ "t"
8767
+ ],
8768
+ long: [
8769
+ "color",
8770
+ "exclude",
8771
+ "extension",
8772
+ "max-depth",
8773
+ "owner",
8774
+ "size",
8775
+ "threads",
8776
+ "type"
8777
+ ]
8778
+ }
8779
+ }],
8780
+ ["file", {
8781
+ short: ["C"],
8782
+ long: ["compile"],
8783
+ valued: {
8784
+ short: [
8785
+ "e",
8786
+ "f",
8787
+ "F",
8788
+ "m",
8789
+ "P"
8790
+ ],
8791
+ long: [
8792
+ "exclude",
8793
+ "exclude-quiet",
8794
+ "files-from",
8795
+ "magic-file",
8796
+ "parameter",
8797
+ "separator"
8798
+ ]
8799
+ }
8800
+ }],
8801
+ ["find", { words: [
8802
+ "-delete",
8803
+ "-exec",
8804
+ "-execdir",
8805
+ "-fls",
8806
+ "-fprint",
8807
+ "-fprint0",
8808
+ "-fprintf",
8809
+ "-ok",
8810
+ "-okdir"
8811
+ ] }],
8812
+ ["hostname", {
8813
+ short: ["b", "F"],
8814
+ long: ["boot", "file"],
8815
+ operands: 0
8816
+ }],
8817
+ ["rg", {
8818
+ short: ["z"],
8819
+ long: [
8820
+ "hostname-bin",
8821
+ "pre",
8822
+ "search-zip"
8823
+ ]
8824
+ }],
8825
+ ["sort", {
8826
+ short: ["o", "T"],
8827
+ long: [
8828
+ "compress-program",
8829
+ "output",
8830
+ "temporary-directory"
8831
+ ],
8832
+ valued: {
8833
+ short: [
8834
+ "k",
8835
+ "S",
8836
+ "t"
8837
+ ],
8838
+ long: [
8839
+ "batch-size",
8840
+ "buffer-size",
8841
+ "field-separator",
8842
+ "files0-from",
8843
+ "key",
8844
+ "parallel",
8845
+ "random-source",
8846
+ "sort"
8847
+ ]
8848
+ }
8849
+ }],
8850
+ ["uniq", {
8851
+ operands: 1,
8852
+ valued: {
8853
+ short: [
8854
+ "f",
8855
+ "s",
8856
+ "w"
8857
+ ],
8858
+ long: [
8859
+ "check-chars",
8860
+ "skip-chars",
8861
+ "skip-fields"
8862
+ ]
8863
+ }
8864
+ }]
8741
8865
  ]);
8742
8866
  const READ_ONLY_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set([
8743
8867
  "blame",
@@ -8745,7 +8869,6 @@ const READ_ONLY_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set([
8745
8869
  "diff",
8746
8870
  "log",
8747
8871
  "ls-files",
8748
- "ls-remote",
8749
8872
  "ls-tree",
8750
8873
  "rev-list",
8751
8874
  "rev-parse",
@@ -8753,9 +8876,14 @@ const READ_ONLY_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set([
8753
8876
  "show",
8754
8877
  "show-ref",
8755
8878
  "status",
8756
- "tag",
8757
8879
  "whatchanged"
8758
8880
  ]);
8881
+ const UNSAFE_GIT_OPTIONS = { long: [
8882
+ "ext-diff",
8883
+ "output",
8884
+ "textconv"
8885
+ ] };
8886
+ const SAFE_GIT_GLOBAL_FLAGS = /* @__PURE__ */ new Set(["--no-pager", "-P"]);
8759
8887
  const REGENERABLE_DIRS = /* @__PURE__ */ new Set([
8760
8888
  ".gradle",
8761
8889
  ".next",
@@ -8798,21 +8926,90 @@ function classifyLocally(payload) {
8798
8926
  };
8799
8927
  }
8800
8928
  function classifySegment(text, cwd) {
8929
+ if (splitShellTokens(text).tokens.some((token) => token.kind === "redirect")) return null;
8801
8930
  const words = splitWords(text);
8802
- const start = words.findIndex((word) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(word.raw));
8803
- if (start === -1) return "Read-only actions";
8804
- const head = words[start];
8805
- invariant(head !== void 0, "findIndex returned an index the array holds");
8806
- const rest = words.slice(start + 1);
8931
+ if (words.length === 0 || splitCommandWords(text).length !== words.length) return null;
8932
+ if (words.some((word) => hasShellExpansion(word.raw))) return null;
8933
+ const [head, ...rest] = words;
8934
+ invariant(head !== void 0, "the segment holds at least one word");
8807
8935
  const name = head.raw.slice(head.raw.lastIndexOf("/") + 1);
8808
- if (READ_ONLY_COMMANDS.has(name)) return "Read-only actions";
8809
- if (name === "git") {
8810
- const sub = rest.find((word) => !word.raw.startsWith("-"));
8811
- return sub !== void 0 && READ_ONLY_GIT_SUBCOMMANDS.has(sub.raw) ? "Read-only actions" : null;
8936
+ const args = rest.map((word) => word.value);
8937
+ if (READ_ONLY_COMMANDS.has(name)) {
8938
+ const unsafe = UNSAFE_OPTIONS.get(name);
8939
+ return unsafe === void 0 || !hasUnsafeOption(args, unsafe) ? "Read-only actions" : null;
8812
8940
  }
8941
+ if (name === "git") return classifyGit(args, cwd);
8813
8942
  if (name === "rm") return classifyRemove(rest, cwd);
8814
8943
  return null;
8815
8944
  }
8945
+ function hasUnsafeOption(args, unsafe) {
8946
+ if (args.some((arg) => unsafe.words?.includes(arg) === true)) return true;
8947
+ let operands = 0;
8948
+ let index = 0;
8949
+ while (index < args.length) {
8950
+ const arg = args[index] ?? "";
8951
+ if (arg === "--") {
8952
+ operands += args.length - index - 1;
8953
+ break;
8954
+ }
8955
+ if (isOptionWord(arg)) {
8956
+ const option = parseOption(arg, unsafe);
8957
+ if (option.isUnsafe) return true;
8958
+ if (option.takesNextWord && !isOptionWord(args[index + 1] ?? "-")) index += 1;
8959
+ } else operands += 1;
8960
+ index += 1;
8961
+ }
8962
+ return unsafe.operands !== void 0 && operands > unsafe.operands;
8963
+ }
8964
+ function parseOption(arg, unsafe) {
8965
+ const valued = unsafe.valued;
8966
+ if (arg.startsWith("--")) {
8967
+ const [name = "", ...value] = arg.slice(2).split("=");
8968
+ return {
8969
+ isUnsafe: name !== "" && unsafe.long?.some((long) => long.startsWith(name)) === true,
8970
+ takesNextWord: value.length === 0 && valued?.long.includes(name) === true
8971
+ };
8972
+ }
8973
+ for (let index = 1; index < arg.length; index += 1) {
8974
+ const letter = arg.charAt(index);
8975
+ if (unsafe.short?.includes(letter) === true) return {
8976
+ isUnsafe: true,
8977
+ takesNextWord: false
8978
+ };
8979
+ if (valued?.attached?.includes(letter) === true) break;
8980
+ if (valued?.short.includes(letter) === true) return {
8981
+ isUnsafe: false,
8982
+ takesNextWord: index === arg.length - 1
8983
+ };
8984
+ }
8985
+ return {
8986
+ isUnsafe: false,
8987
+ takesNextWord: false
8988
+ };
8989
+ }
8990
+ function isOptionWord(arg) {
8991
+ return arg.startsWith("-") && arg !== "-";
8992
+ }
8993
+ function classifyGit(args, cwd) {
8994
+ const git = splitGitCommand(args, cwd, "");
8995
+ const globals = args.slice(0, args.length - git.args.length);
8996
+ const [sub, ...subArgs] = git.args;
8997
+ if (sub === void 0 || !READ_ONLY_GIT_SUBCOMMANDS.has(sub)) return null;
8998
+ return hasOnlySafeGitGlobals(globals) && !hasUnsafeOption(subArgs, UNSAFE_GIT_OPTIONS) ? "Read-only actions" : null;
8999
+ }
9000
+ function hasOnlySafeGitGlobals(globals) {
9001
+ let index = 0;
9002
+ while (index < globals.length) {
9003
+ const option = globals[index] ?? "";
9004
+ if (option === "-C") {
9005
+ const directory = globals[index + 1];
9006
+ if (directory === void 0 || directory.startsWith("-")) return false;
9007
+ index += 2;
9008
+ } else if (SAFE_GIT_GLOBAL_FLAGS.has(option)) index += 1;
9009
+ else return false;
9010
+ }
9011
+ return true;
9012
+ }
8816
9013
  function classifyRemove(args, cwd) {
8817
9014
  const paths = args.filter((arg) => !arg.raw.startsWith("-"));
8818
9015
  if (paths.length === 0) return null;
@@ -8834,6 +9031,23 @@ function splitWords(text) {
8834
9031
  value: token.value
8835
9032
  }] : []);
8836
9033
  }
9034
+ function hasShellExpansion(raw) {
9035
+ let quote = null;
9036
+ let previous = "";
9037
+ for (const char of raw) {
9038
+ if (quote === "'") quote = char === "'" ? null : quote;
9039
+ else if (char === "\\" || char === "$" || char === "`") return true;
9040
+ else if (char === quote) quote = null;
9041
+ else if (quote === null && (char === "'" || char === "\"")) quote = char;
9042
+ else if (quote === null && isExpansionCharacter(char, previous)) return true;
9043
+ previous = char;
9044
+ }
9045
+ return false;
9046
+ }
9047
+ function isExpansionCharacter(char, previous) {
9048
+ if (char === "~") return previous === "" || previous === "=" || previous === ":";
9049
+ return "{}*?[]()!".includes(char) || /\s/u.test(char);
9050
+ }
8837
9051
  function isPlainOperand(word) {
8838
9052
  return [
8839
9053
  word.value,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-mode",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "A permission classifier library and Claude Code mod, so agents that are not Claude can run unattended",
5
5
  "keywords": [
6
6
  "agent",