@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.
- package/claude/settings.json +7 -1
- package/dist/index.js +79 -42
- package/package.json +1 -1
package/claude/settings.json
CHANGED
|
@@ -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": [
|
|
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.
|
|
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/
|
|
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
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
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
|
|
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(...
|
|
3709
|
+
entries.push(...readPermissionArray(p, key));
|
|
3694
3710
|
}
|
|
3695
3711
|
return entries;
|
|
3696
3712
|
}
|
|
3697
|
-
function
|
|
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
|
|
3702
|
-
return Array.isArray(
|
|
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
|
|
3709
|
-
const prefixes = [];
|
|
3724
|
+
const map = /* @__PURE__ */ new Map();
|
|
3710
3725
|
for (const entry of entries) {
|
|
3711
|
-
const m = entry.match(
|
|
3712
|
-
if (m)
|
|
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
|
|
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 =
|
|
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
|
|
3846
|
+
if (!SUPPORTED_TOOLS.has(data.tool_name) || !data.tool_input?.command) {
|
|
3804
3847
|
return;
|
|
3805
3848
|
}
|
|
3806
|
-
const
|
|
3807
|
-
const parts = splitCompound(command);
|
|
3849
|
+
const parts = splitCompound(data.tool_input.command.trim());
|
|
3808
3850
|
if (!parts) return;
|
|
3809
|
-
const
|
|
3810
|
-
|
|
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
|
-
|
|
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,
|
|
4163
|
+
function parseCached(cli, cached) {
|
|
4127
4164
|
const prefix2 = `${cli} `;
|
|
4128
4165
|
const commands = [];
|
|
4129
|
-
for (const line of
|
|
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
|
|
4183
|
-
if (
|
|
4184
|
-
console.log(colorize(
|
|
4185
|
-
updateSettings(binary, parseCached(binary,
|
|
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
|
|
7120
|
-
if (
|
|
7121
|
-
return
|
|
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",
|