kimiflare 0.9.2 → 0.10.0

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/index.js CHANGED
@@ -489,21 +489,209 @@ function nextMode(m) {
489
489
  function isBlockedInPlanMode(toolName) {
490
490
  return MUTATING_TOOLS.has(toolName);
491
491
  }
492
+ function tokenizeCommand(command) {
493
+ const tokens = [];
494
+ let current = "";
495
+ let inQuote = null;
496
+ for (const ch of command) {
497
+ if (inQuote) {
498
+ if (ch === inQuote) {
499
+ inQuote = null;
500
+ } else {
501
+ current += ch;
502
+ }
503
+ } else if (ch === '"' || ch === "'") {
504
+ inQuote = ch;
505
+ } else if (/\s/.test(ch)) {
506
+ if (current) {
507
+ tokens.push(current);
508
+ current = "";
509
+ }
510
+ } else {
511
+ current += ch;
512
+ }
513
+ }
514
+ if (current) tokens.push(current);
515
+ return tokens;
516
+ }
517
+ function isReadOnlyBash(command) {
518
+ const trimmed = command.trim();
519
+ if (!trimmed) return false;
520
+ if (DANGEROUS_PATTERNS.test(trimmed)) return false;
521
+ const tokens = tokenizeCommand(trimmed);
522
+ if (tokens.length === 0) return false;
523
+ const first = tokens[0];
524
+ let cmdIndex = 0;
525
+ if (first === "cd" && tokens.length >= 3 && tokens[1] && tokens[2] === "&&") {
526
+ cmdIndex = 3;
527
+ }
528
+ const cmd = tokens[cmdIndex];
529
+ if (!cmd) return false;
530
+ const args = tokens.slice(cmdIndex + 1);
531
+ if (cmd === "git") {
532
+ const sub = args[0] ?? "";
533
+ const allowed = GIT_READONLY_SUBCOMMANDS[sub];
534
+ if (allowed === void 0) return false;
535
+ if (allowed === true) return true;
536
+ switch (sub) {
537
+ case "branch":
538
+ return !args.some((a) => /^-[dDmMcC]/.test(a));
539
+ case "stash":
540
+ return args[1] === "list";
541
+ case "remote":
542
+ return args[1] === "-v" || args[1] === "--verbose" || args.length === 1;
543
+ case "tag":
544
+ return args[1] === "-l" || args[1] === "--list" || args.length === 1;
545
+ case "config":
546
+ return args[1] === "--list" || args[1]?.startsWith("--get") === true || args.length === 1;
547
+ default:
548
+ return false;
549
+ }
550
+ }
551
+ const argCheck = COMMANDS_NEEDING_ARG_CHECK[cmd];
552
+ if (argCheck) {
553
+ return argCheck(args);
554
+ }
555
+ return READONLY_COMMANDS.has(cmd);
556
+ }
492
557
  function systemPromptForMode(m) {
493
558
  if (m === "plan") {
494
- return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or bash. Only use read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
559
+ return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or mutating bash commands. You may use read-only bash commands (e.g., git log, git diff, ls, cat) along with read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
495
560
  }
496
561
  if (m === "auto") {
497
562
  return "\n\nAUTO MODE is active. The user has opted into autonomous execution \u2014 every tool call will be auto-approved. Work efficiently, but do not take irreversible destructive actions (rm -rf, git push --force, dropping tables, etc.) without pausing to describe them in chat first. Prefer smaller reversible steps.";
498
563
  }
499
564
  return "";
500
565
  }
501
- var MODES, MUTATING_TOOLS;
566
+ var MODES, MUTATING_TOOLS, DANGEROUS_PATTERNS, GIT_READONLY_SUBCOMMANDS, READONLY_COMMANDS, COMMANDS_NEEDING_ARG_CHECK;
502
567
  var init_mode = __esm({
503
568
  "src/mode.ts"() {
504
569
  "use strict";
505
570
  MODES = ["edit", "plan", "auto"];
506
571
  MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "bash"]);
572
+ DANGEROUS_PATTERNS = /[<>|;&`$]|\$\(|\$\{|&&|\|\||\b&\s*$/;
573
+ GIT_READONLY_SUBCOMMANDS = {
574
+ log: true,
575
+ diff: true,
576
+ status: true,
577
+ show: true,
578
+ blame: true,
579
+ describe: true,
580
+ "rev-parse": true,
581
+ "ls-files": true,
582
+ reflog: true,
583
+ shortlog: true,
584
+ whatchanged: true,
585
+ grep: true,
586
+ branch: false,
587
+ // needs check: block -d/-D/-m/-M/-c/-C
588
+ stash: false,
589
+ // needs check: only allow "list"
590
+ remote: false,
591
+ // needs check: only allow -v
592
+ tag: false,
593
+ // needs check: only allow -l
594
+ config: false
595
+ // needs check: only allow --list/--get
596
+ };
597
+ READONLY_COMMANDS = /* @__PURE__ */ new Set([
598
+ // File system
599
+ "ls",
600
+ "cat",
601
+ "head",
602
+ "tail",
603
+ "pwd",
604
+ "echo",
605
+ "file",
606
+ "stat",
607
+ "readlink",
608
+ "realpath",
609
+ "dirname",
610
+ "basename",
611
+ "wc",
612
+ "sort",
613
+ "uniq",
614
+ "diff",
615
+ "cmp",
616
+ // Search
617
+ "grep",
618
+ "rg",
619
+ "ag",
620
+ "fd",
621
+ // System info
622
+ "ps",
623
+ "df",
624
+ "du",
625
+ "env",
626
+ "printenv",
627
+ "which",
628
+ "whereis",
629
+ "uname",
630
+ "hostname",
631
+ "uptime",
632
+ "free",
633
+ "date",
634
+ "id",
635
+ "whoami",
636
+ "groups",
637
+ // Dev tools (version/info only)
638
+ "node",
639
+ "npx",
640
+ "python3",
641
+ "ruby",
642
+ "perl",
643
+ // Utilities
644
+ "jq",
645
+ "yq",
646
+ "awk",
647
+ "cut",
648
+ "tr",
649
+ "base64",
650
+ "sha256sum",
651
+ "md5sum",
652
+ "shasum",
653
+ "hexdump",
654
+ "xxd",
655
+ "strings",
656
+ "less",
657
+ "more",
658
+ "man",
659
+ "clear",
660
+ "history",
661
+ // Archive inspection
662
+ "zipinfo",
663
+ // Network
664
+ "ping",
665
+ "netstat",
666
+ "ss",
667
+ "lsof"
668
+ ]);
669
+ COMMANDS_NEEDING_ARG_CHECK = {
670
+ find: (args) => !args.some((a) => a === "-delete" || a === "-exec"),
671
+ sed: (args) => !args.some((a) => a === "-i" || a.startsWith("-i")),
672
+ tar: (args) => args[0] === "-tf" || args[0] === "--list",
673
+ unzip: (args) => args[0] === "-l",
674
+ curl: (args) => !args.some((a) => a === "-o" || a === "-O" || a === "-d" || a === "--data" || a.startsWith("-X")),
675
+ wget: (args) => !args.some((a) => a === "-O" || a === "--output-document" || a.startsWith("--post")),
676
+ npm: (args) => ["list", "view", "config"].includes(args[0] ?? "") && !(args[0] === "config" && args[1] && !args[1].startsWith("get") && args[1] !== "list"),
677
+ tsc: (args) => args.every(
678
+ (a) => ["--noEmit", "--version", "--showConfig", "--help", "-h", "--init"].includes(a)
679
+ ),
680
+ eslint: (args) => args.every(
681
+ (a) => ["--version", "--print-config", "--help", "-h"].includes(a) || !a.startsWith("-")
682
+ ),
683
+ prettier: (args) => args.every(
684
+ (a) => ["--version", "--check", "--help", "-h"].includes(a) || !a.startsWith("-")
685
+ ),
686
+ jest: (args) => args.every(
687
+ (a) => ["--version", "--listTests", "--showConfig", "--help", "-h"].includes(a) || !a.startsWith("-")
688
+ ),
689
+ vitest: (args) => args.every(
690
+ (a) => ["--version", "--help", "-h"].includes(a) || !a.startsWith("-")
691
+ ),
692
+ go: (args) => ["version", "env", "list", "mod"].includes(args[0] ?? "") && !(args[0] === "mod" && args[1] && !["graph", "download", "why", "verify"].includes(args[1])),
693
+ cargo: (args) => ["--version", "-V", "check", "test", "metadata"].includes(args[0] ?? "") && !(args[0] === "test" && args.includes("--no-run") === false)
694
+ };
507
695
  }
508
696
  });
509
697
 
@@ -3711,7 +3899,26 @@ function App({ initialCfg, initialUpdateResult }) {
3711
3899
  setUsage(u);
3712
3900
  },
3713
3901
  askPermission: (req) => new Promise((resolve2) => {
3714
- if (modeRef.current === "auto") return resolve2("allow");
3902
+ if (modeRef.current === "auto") {
3903
+ resolve2("allow");
3904
+ return;
3905
+ }
3906
+ if (modeRef.current === "plan" && isBlockedInPlanMode(req.tool.name)) {
3907
+ if (req.tool.name === "bash" && typeof req.args.command === "string" && isReadOnlyBash(req.args.command)) {
3908
+ resolve2("allow");
3909
+ return;
3910
+ }
3911
+ setEvents((e) => [
3912
+ ...e,
3913
+ {
3914
+ kind: "info",
3915
+ key: mkKey(),
3916
+ text: `plan mode blocked ${req.tool.name}; exit plan mode to execute`
3917
+ }
3918
+ ]);
3919
+ resolve2("deny");
3920
+ return;
3921
+ }
3715
3922
  setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
3716
3923
  })
3717
3924
  }
@@ -4106,6 +4313,10 @@ use: /thinking low | medium | high`
4106
4313
  return;
4107
4314
  }
4108
4315
  if (modeRef.current === "plan" && isBlockedInPlanMode(req.tool.name)) {
4316
+ if (req.tool.name === "bash" && typeof req.args.command === "string" && isReadOnlyBash(req.args.command)) {
4317
+ resolve2("allow");
4318
+ return;
4319
+ }
4109
4320
  setEvents((e) => [
4110
4321
  ...e,
4111
4322
  {