@staff0rd/assist 0.158.2 → 0.159.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.
@@ -8,6 +8,10 @@
8
8
  {
9
9
  "matcher": "Bash",
10
10
  "hooks": [{ "type": "command", "command": "assist cli-hook" }]
11
+ },
12
+ {
13
+ "matcher": "PowerShell",
14
+ "hooks": [{ "type": "command", "command": "assist cli-hook" }]
11
15
  }
12
16
  ],
13
17
  "Notification": [
@@ -103,6 +107,8 @@
103
107
  "SlashCommand(/test-review)",
104
108
  "WebFetch(domain:staffordwilliams.com)"
105
109
  ],
106
- "deny": ["Bash(git commit:*)", "Bash(npm run:*)", "Bash(npx assist:*)"]
110
+ "deny": [
111
+ "Bash(git commit:*)", "Bash(npm run:*)", "Bash(npx assist:*)"
112
+ ]
107
113
  }
108
114
  }
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.158.2",
9
+ version: "0.159.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -3666,23 +3666,39 @@ function findCliRead(command) {
3666
3666
  return candidates.sort((a, b) => b.length - a.length).find((rc) => command === rc || command.startsWith(`${rc} `));
3667
3667
  }
3668
3668
 
3669
- // src/shared/matchesBashAllow.ts
3669
+ // src/shared/matchesAllow.ts
3670
3670
  import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
3671
3671
  import { homedir as homedir3 } from "os";
3672
3672
  import { join as join13 } from "path";
3673
- var cached;
3674
- function loadBashAllowPrefixes() {
3675
- if (cached) return cached;
3676
- cached = parsePrefixes(collectAllowEntries());
3677
- return cached;
3678
- }
3679
- function matchesBashAllow(command) {
3680
- const prefixes = loadBashAllowPrefixes();
3673
+ var allowCache;
3674
+ var denyCache;
3675
+ var TOOL_RE = /^(Bash|PowerShell)\((.+?)(?::.*)\)$/;
3676
+ function loadPrefixes(key) {
3677
+ const entries = collectEntries(key);
3678
+ return parsePrefixes(entries);
3679
+ }
3680
+ var SHELL_TOOLS = ["Bash", "PowerShell"];
3681
+ function shellPrefixes(map, toolName) {
3682
+ if (SHELL_TOOLS.includes(toolName)) {
3683
+ return SHELL_TOOLS.flatMap((t) => map.get(t) ?? []);
3684
+ }
3685
+ return map.get(toolName) ?? [];
3686
+ }
3687
+ function matchesAllow(toolName, command) {
3688
+ if (!allowCache) allowCache = loadPrefixes("allow");
3689
+ const prefixes = shellPrefixes(allowCache, toolName);
3681
3690
  return prefixes.find(
3682
3691
  (pfx) => command === pfx || command.startsWith(`${pfx} `)
3683
3692
  );
3684
3693
  }
3685
- function collectAllowEntries() {
3694
+ function matchesDeny(toolName, command) {
3695
+ if (!denyCache) denyCache = loadPrefixes("deny");
3696
+ const prefixes = shellPrefixes(denyCache, toolName);
3697
+ return prefixes.find(
3698
+ (pfx) => command === pfx || command.startsWith(`${pfx} `)
3699
+ );
3700
+ }
3701
+ function collectEntries(key) {
3686
3702
  const paths = [
3687
3703
  join13(homedir3(), ".claude", "settings.json"),
3688
3704
  join13(process.cwd(), ".claude", "settings.json"),
@@ -3690,37 +3706,42 @@ function collectAllowEntries() {
3690
3706
  ];
3691
3707
  const entries = [];
3692
3708
  for (const p of paths) {
3693
- entries.push(...readAllowArray(p));
3709
+ entries.push(...readPermissionArray(p, key));
3694
3710
  }
3695
3711
  return entries;
3696
3712
  }
3697
- function readAllowArray(filePath) {
3713
+ function readPermissionArray(filePath, key) {
3698
3714
  if (!existsSync21(filePath)) return [];
3699
3715
  try {
3700
3716
  const data = JSON.parse(readFileSync16(filePath, "utf-8"));
3701
- const allow = data?.permissions?.allow;
3702
- return Array.isArray(allow) ? allow.filter((e) => typeof e === "string") : [];
3717
+ const arr = data?.permissions?.[key];
3718
+ return Array.isArray(arr) ? arr.filter((e) => typeof e === "string") : [];
3703
3719
  } catch {
3704
3720
  return [];
3705
3721
  }
3706
3722
  }
3707
3723
  function parsePrefixes(entries) {
3708
- const re = /^Bash\((.+?)(?::.*)\)$/;
3709
- const prefixes = [];
3724
+ const map = /* @__PURE__ */ new Map();
3710
3725
  for (const entry of entries) {
3711
- const m = entry.match(re);
3712
- if (m) prefixes.push(m[1]);
3726
+ const m = entry.match(TOOL_RE);
3727
+ if (m) {
3728
+ const tool = m[1];
3729
+ const prefix2 = m[2];
3730
+ const list4 = map.get(tool) ?? [];
3731
+ list4.push(prefix2);
3732
+ map.set(tool, list4);
3733
+ }
3713
3734
  }
3714
- return prefixes;
3735
+ return map;
3715
3736
  }
3716
3737
 
3717
3738
  // src/shared/isApprovedRead.ts
3718
- function isApprovedRead(command) {
3739
+ function isApprovedRead(command, toolName = "Bash") {
3719
3740
  if (isCdToCwd(command)) return "cd to current directory";
3720
3741
  const matched = findCliRead(command);
3721
3742
  if (matched) return `Read-only CLI command: ${matched}`;
3722
3743
  if (isGhApiRead(command)) return "Read-only gh api command";
3723
- const allowMatch = matchesBashAllow(command);
3744
+ const allowMatch = matchesAllow(toolName, command);
3724
3745
  if (allowMatch) return `Allowed by settings: ${allowMatch}`;
3725
3746
  return void 0;
3726
3747
  }
@@ -3792,6 +3813,28 @@ function stripEnvPrefix(parts) {
3792
3813
  }
3793
3814
 
3794
3815
  // src/commands/cliHook/index.ts
3816
+ var SUPPORTED_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
3817
+ function resolvePermission(toolName, parts) {
3818
+ for (const part of parts) {
3819
+ const denied = matchesDeny(toolName, part);
3820
+ if (denied) {
3821
+ return {
3822
+ permissionDecision: "deny",
3823
+ permissionDecisionReason: `Denied by settings: ${denied}`
3824
+ };
3825
+ }
3826
+ }
3827
+ const reasons = [];
3828
+ for (const part of parts) {
3829
+ const reason = isApprovedRead(part, toolName);
3830
+ if (!reason) return void 0;
3831
+ reasons.push(reason);
3832
+ }
3833
+ return {
3834
+ permissionDecision: "allow",
3835
+ permissionDecisionReason: reasons.join("; ")
3836
+ };
3837
+ }
3795
3838
  async function cliHook() {
3796
3839
  const inputData = await readStdin2();
3797
3840
  let data;
@@ -3800,24 +3843,18 @@ async function cliHook() {
3800
3843
  } catch {
3801
3844
  return;
3802
3845
  }
3803
- if (data.tool_name !== "Bash" || !data.tool_input?.command) {
3846
+ if (!SUPPORTED_TOOLS.has(data.tool_name) || !data.tool_input?.command) {
3804
3847
  return;
3805
3848
  }
3806
- const command = data.tool_input.command.trim();
3807
- const parts = splitCompound(command);
3849
+ const parts = splitCompound(data.tool_input.command.trim());
3808
3850
  if (!parts) return;
3809
- const reasons = [];
3810
- for (const part of parts) {
3811
- const reason = isApprovedRead(part);
3812
- if (!reason) return;
3813
- reasons.push(reason);
3814
- }
3851
+ const decision = resolvePermission(data.tool_name, parts);
3852
+ if (!decision) return;
3815
3853
  console.log(
3816
3854
  JSON.stringify({
3817
3855
  hookSpecificOutput: {
3818
3856
  hookEventName: "PreToolUse",
3819
- permissionDecision: "allow",
3820
- permissionDecisionReason: reasons.join("; ")
3857
+ ...decision
3821
3858
  }
3822
3859
  })
3823
3860
  );
@@ -4123,10 +4160,10 @@ function formatHuman(cli, commands) {
4123
4160
  }
4124
4161
 
4125
4162
  // src/commands/permitCliReads/parseCached.ts
4126
- function parseCached(cli, cached2) {
4163
+ function parseCached(cli, cached) {
4127
4164
  const prefix2 = `${cli} `;
4128
4165
  const commands = [];
4129
- for (const line of cached2.split("\n")) {
4166
+ for (const line of cached.split("\n")) {
4130
4167
  const trimmed = line.replace(/^ [RW?] {2}/, "").trim();
4131
4168
  if (!trimmed.startsWith(prefix2)) continue;
4132
4169
  const rest = trimmed.slice(prefix2.length);
@@ -4179,10 +4216,10 @@ async function permitCliReads(cli, options2 = { noCache: false }) {
4179
4216
  const binary = parts[0];
4180
4217
  const prefixPath = parts.slice(1);
4181
4218
  if (!options2.noCache) {
4182
- const cached2 = readCache(cli);
4183
- if (cached2) {
4184
- console.log(colorize(cached2));
4185
- updateSettings(binary, parseCached(binary, cached2));
4219
+ const cached = readCache(cli);
4220
+ if (cached) {
4221
+ console.log(colorize(cached));
4222
+ updateSettings(binary, parseCached(binary, cached));
4186
4223
  return;
4187
4224
  }
4188
4225
  }
@@ -7116,9 +7153,9 @@ function clearCachedToken(apiKey) {
7116
7153
  }
7117
7154
  async function getAccessToken(apiKey) {
7118
7155
  const now = Date.now();
7119
- const cached2 = tokenCache.get(apiKey);
7120
- if (cached2 && now < cached2.expiry) {
7121
- return cached2.token;
7156
+ const cached = tokenCache.get(apiKey);
7157
+ if (cached && now < cached.expiry) {
7158
+ return cached.token;
7122
7159
  }
7123
7160
  const response = await fetch(OAUTH_URL, {
7124
7161
  method: "GET",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.158.2",
3
+ "version": "0.159.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {