claudenv 1.0.2 → 1.2.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/README.md +163 -11
- package/bin/cli.js +167 -1
- package/package.json +1 -1
- package/scaffold/.claude/commands/improve.md +60 -0
- package/scaffold/.claude/commands/setup-mcp.md +115 -0
- package/scaffold/.claude/commands/update-docs.md +1 -0
- package/scaffold/.claude/skills/doc-generator/SKILL.md +3 -0
- package/scaffold/.claude/skills/doc-generator/templates/mcp-servers.md +168 -0
- package/scaffold/global/.claude/commands/claudenv.md +50 -4
- package/scaffold/global/.claude/commands/improve.md +60 -0
- package/scaffold/global/.claude/commands/setup-mcp.md +115 -0
- package/scaffold/global/.claude/skills/claudenv/SKILL.md +11 -0
- package/scaffold/global/.claude/skills/claudenv/scaffold/.claude/commands/improve.md +60 -0
- package/scaffold/global/.claude/skills/claudenv/scaffold/.claude/commands/setup-mcp.md +115 -0
- package/scaffold/global/.claude/skills/claudenv/scaffold/.claude/commands/update-docs.md +1 -0
- package/scaffold/global/.claude/skills/claudenv/scaffold/.claude/skills/doc-generator/SKILL.md +3 -0
- package/scaffold/global/.claude/skills/claudenv/scaffold/.claude/skills/doc-generator/templates/mcp-servers.md +168 -0
- package/scaffold/global/.claude/skills/claudenv/templates/mcp-servers.md +168 -0
- package/src/autonomy.js +117 -0
- package/src/hooks-gen.js +279 -0
- package/src/index.js +4 -0
- package/src/installer.js +2 -0
- package/src/loop.js +617 -0
- package/src/profiles.js +113 -0
package/src/hooks-gen.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// =============================================
|
|
2
|
+
// Hook & config file generation for autonomy profiles
|
|
3
|
+
// =============================================
|
|
4
|
+
|
|
5
|
+
import { CREDENTIAL_PATHS } from './profiles.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate .claude/settings.json content for a given profile.
|
|
9
|
+
* @param {object} profile - Autonomy profile
|
|
10
|
+
* @param {object} detected - Tech stack detection result
|
|
11
|
+
* @returns {string} JSON string
|
|
12
|
+
*/
|
|
13
|
+
export function generateSettingsJson(profile, detected = {}) {
|
|
14
|
+
const settings = {};
|
|
15
|
+
|
|
16
|
+
if (profile.allowedTools && profile.allowedTools.length > 0) {
|
|
17
|
+
settings.permissions = settings.permissions || {};
|
|
18
|
+
settings.permissions.allow = profile.allowedTools;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (profile.disallowedTools && profile.disallowedTools.length > 0) {
|
|
22
|
+
settings.permissions = settings.permissions || {};
|
|
23
|
+
settings.permissions.deny = profile.disallowedTools;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Add stack-aware package manager restrictions for moderate/safe
|
|
27
|
+
if (detected.packageManager && !profile.skipPermissions) {
|
|
28
|
+
settings.permissions = settings.permissions || {};
|
|
29
|
+
settings.permissions.deny = settings.permissions.deny || [];
|
|
30
|
+
const wrongManagers = getWrongPackageManagers(detected.packageManager);
|
|
31
|
+
for (const cmd of wrongManagers) {
|
|
32
|
+
if (!settings.permissions.deny.includes(cmd)) {
|
|
33
|
+
settings.permissions.deny.push(cmd);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Hooks config — PascalCase keys, command objects per Claude Code format
|
|
39
|
+
const preToolUseHook = {
|
|
40
|
+
type: 'command',
|
|
41
|
+
command: 'bash .claude/hooks/pre-tool-use.sh',
|
|
42
|
+
timeout: 10,
|
|
43
|
+
};
|
|
44
|
+
const auditLogHook = {
|
|
45
|
+
type: 'command',
|
|
46
|
+
command: 'bash .claude/hooks/audit-log.sh',
|
|
47
|
+
timeout: 10,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
settings.hooks = {
|
|
51
|
+
PreToolUse: [
|
|
52
|
+
{
|
|
53
|
+
matcher: 'Bash',
|
|
54
|
+
hooks: [preToolUseHook],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
matcher: 'Edit',
|
|
58
|
+
hooks: [preToolUseHook],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
matcher: 'Write',
|
|
62
|
+
hooks: [preToolUseHook],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
matcher: 'Read',
|
|
66
|
+
hooks: [preToolUseHook],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
PostToolUse: [
|
|
70
|
+
{
|
|
71
|
+
matcher: '',
|
|
72
|
+
hooks: [auditLogHook],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
settings.enableAllProjectMcpServers = false;
|
|
78
|
+
|
|
79
|
+
return JSON.stringify(settings, null, 2) + '\n';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generate pre-tool-use hook script.
|
|
84
|
+
* @param {object} profile - Autonomy profile
|
|
85
|
+
* @param {object} detected - Tech stack detection result
|
|
86
|
+
* @returns {string} Bash script
|
|
87
|
+
*/
|
|
88
|
+
export function generatePreToolUseHook(profile, detected = {}) {
|
|
89
|
+
const credentialPathsStr = CREDENTIAL_PATHS.map((p) => ` "${p}"`).join('\n');
|
|
90
|
+
const wrongManagerBlock = buildWrongManagerBlock(detected.packageManager);
|
|
91
|
+
|
|
92
|
+
const credentialAction =
|
|
93
|
+
profile.credentialPolicy === 'block'
|
|
94
|
+
? ` echo "BLOCKED: Access to credential path: $input_path" >&2
|
|
95
|
+
exit 2`
|
|
96
|
+
: ` echo "WARNING: Accessing credential path: $input_path" >&2
|
|
97
|
+
# Log warning to audit
|
|
98
|
+
echo "{\\"ts\\":\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\",\\"event\\":\\"credential_warning\\",\\"path\\":\\"$input_path\\"}" >> .claude/audit-log.jsonl
|
|
99
|
+
exit 0`;
|
|
100
|
+
|
|
101
|
+
return `#!/usr/bin/env bash
|
|
102
|
+
# Pre-tool-use hook — generated by claudenv autonomy (profile: ${profile.name})
|
|
103
|
+
# Exit 0 = allow, Exit 2 = block
|
|
104
|
+
|
|
105
|
+
set -euo pipefail
|
|
106
|
+
|
|
107
|
+
TOOL_NAME="\${CLAUDE_TOOL_NAME:-}"
|
|
108
|
+
TOOL_INPUT="\${CLAUDE_TOOL_INPUT:-}"
|
|
109
|
+
|
|
110
|
+
# === Hard blocks (all profiles) ===
|
|
111
|
+
|
|
112
|
+
# Block rm -rf / rm -fr
|
|
113
|
+
if echo "$TOOL_INPUT" | grep -qE 'rm\\s+.*-[rR].*-[fF]|rm\\s+.*-[fF].*-[rR]|rm\\s+-rf\\b|rm\\s+-fr\\b'; then
|
|
114
|
+
echo "BLOCKED: Destructive rm command detected" >&2
|
|
115
|
+
exit 2
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Block force push to main/master
|
|
119
|
+
if echo "$TOOL_INPUT" | grep -qE 'git\\s+push\\s+(--force|-f)\\s.*(main|master)|git\\s+push\\s+.*\\s(main|master)\\s+(--force|-f)'; then
|
|
120
|
+
echo "BLOCKED: Force push to main/master" >&2
|
|
121
|
+
exit 2
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Block sudo
|
|
125
|
+
if echo "$TOOL_INPUT" | grep -qE '^sudo\\s|\\bsudo\\s'; then
|
|
126
|
+
echo "BLOCKED: sudo command" >&2
|
|
127
|
+
exit 2
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# === Credential path checks ===
|
|
131
|
+
CREDENTIAL_PATHS=(
|
|
132
|
+
${credentialPathsStr}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Expand ~ to HOME
|
|
136
|
+
input_path=$(echo "$TOOL_INPUT" | grep -oE '~/[^ "]+|\\$HOME/[^ "]+' | head -1 || true)
|
|
137
|
+
if [ -n "$input_path" ]; then
|
|
138
|
+
for cred_path in "\${CREDENTIAL_PATHS[@]}"; do
|
|
139
|
+
expanded_cred="\${cred_path/#\\~/$HOME}"
|
|
140
|
+
expanded_input="\${input_path/#\\~/$HOME}"
|
|
141
|
+
if [[ "$expanded_input" == "$expanded_cred"* ]]; then
|
|
142
|
+
${credentialAction}
|
|
143
|
+
fi
|
|
144
|
+
done
|
|
145
|
+
fi
|
|
146
|
+
${wrongManagerBlock}
|
|
147
|
+
# All checks passed
|
|
148
|
+
exit 0
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Generate audit log hook script.
|
|
154
|
+
* @returns {string} Bash script
|
|
155
|
+
*/
|
|
156
|
+
export function generateAuditLogHook() {
|
|
157
|
+
return `#!/usr/bin/env bash
|
|
158
|
+
# Post-tool-use audit hook — generated by claudenv autonomy
|
|
159
|
+
# Logs every tool invocation to .claude/audit-log.jsonl
|
|
160
|
+
|
|
161
|
+
set -uo pipefail
|
|
162
|
+
|
|
163
|
+
TOOL_NAME="\${CLAUDE_TOOL_NAME:-unknown}"
|
|
164
|
+
TOOL_INPUT="\${CLAUDE_TOOL_INPUT:-}"
|
|
165
|
+
SESSION_ID="\${CLAUDE_SESSION_ID:-}"
|
|
166
|
+
|
|
167
|
+
# Truncate input for logging (max 500 chars)
|
|
168
|
+
TRUNCATED_INPUT=$(echo "$TOOL_INPUT" | head -c 500)
|
|
169
|
+
|
|
170
|
+
# Escape for JSON
|
|
171
|
+
ESCAPED_INPUT=$(echo "$TRUNCATED_INPUT" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g; s/\\t/\\\\t/g' | tr '\\n' ' ')
|
|
172
|
+
|
|
173
|
+
mkdir -p .claude
|
|
174
|
+
|
|
175
|
+
echo "{\\"ts\\":\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\",\\"tool\\":\\"$TOOL_NAME\\",\\"input\\":\\"$ESCAPED_INPUT\\",\\"session\\":\\"$SESSION_ID\\"}" >> .claude/audit-log.jsonl
|
|
176
|
+
|
|
177
|
+
exit 0
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generate shell aliases.
|
|
183
|
+
* @param {object} profile - Autonomy profile
|
|
184
|
+
* @returns {string} Shell script with aliases
|
|
185
|
+
*/
|
|
186
|
+
export function generateAliases(profile) {
|
|
187
|
+
return `#!/usr/bin/env bash
|
|
188
|
+
# Shell aliases — generated by claudenv autonomy (profile: ${profile.name})
|
|
189
|
+
# Source this file: source .claude/aliases.sh
|
|
190
|
+
|
|
191
|
+
# Safe mode — read-only, plan mode
|
|
192
|
+
alias claude-safe='claude --allowedTools "Read,Glob,Grep,Bash(ls *),Bash(cat *),Bash(git status),Bash(git log *),Bash(git diff *)"'
|
|
193
|
+
|
|
194
|
+
# Full autonomy — skip all permission prompts
|
|
195
|
+
alias claude-yolo='claude --dangerously-skip-permissions'
|
|
196
|
+
|
|
197
|
+
# CI mode — headless, JSON output, budget-limited
|
|
198
|
+
alias claude-ci='claude -p --output-format json --dangerously-skip-permissions --max-turns 50'
|
|
199
|
+
|
|
200
|
+
# Local dev — current profile (${profile.name})
|
|
201
|
+
alias claude-local='claude${profile.skipPermissions ? ' --dangerously-skip-permissions' : ''}${profile.disallowedTools && profile.disallowedTools.length > 0 ? ` --disallowedTools "${profile.disallowedTools.join(',')}"` : ''}'
|
|
202
|
+
`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Generate GitHub Actions workflow for CI profile.
|
|
207
|
+
* @returns {string} YAML content
|
|
208
|
+
*/
|
|
209
|
+
export function generateCIWorkflow() {
|
|
210
|
+
return `# Claude CI workflow — generated by claudenv autonomy (profile: ci)
|
|
211
|
+
name: Claude CI
|
|
212
|
+
|
|
213
|
+
on:
|
|
214
|
+
issues:
|
|
215
|
+
types: [opened, labeled]
|
|
216
|
+
pull_request_target:
|
|
217
|
+
types: [opened, synchronize]
|
|
218
|
+
|
|
219
|
+
jobs:
|
|
220
|
+
claude:
|
|
221
|
+
if: |
|
|
222
|
+
(github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'claude')) ||
|
|
223
|
+
github.event_name == 'pull_request_target'
|
|
224
|
+
runs-on: ubuntu-latest
|
|
225
|
+
permissions:
|
|
226
|
+
contents: read
|
|
227
|
+
pull-requests: write
|
|
228
|
+
issues: write
|
|
229
|
+
|
|
230
|
+
steps:
|
|
231
|
+
- name: Checkout
|
|
232
|
+
uses: actions/checkout@v4
|
|
233
|
+
with:
|
|
234
|
+
fetch-depth: 1
|
|
235
|
+
|
|
236
|
+
- name: Run Claude
|
|
237
|
+
uses: anthropics/claude-code-action@v1
|
|
238
|
+
with:
|
|
239
|
+
anthropic_api_key: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
240
|
+
max_turns: "50"
|
|
241
|
+
timeout_minutes: "30"
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// =============================================
|
|
246
|
+
// Internal helpers
|
|
247
|
+
// =============================================
|
|
248
|
+
|
|
249
|
+
function getWrongPackageManagers(detected) {
|
|
250
|
+
const managers = {
|
|
251
|
+
npm: ['Bash(yarn *)', 'Bash(pnpm *)', 'Bash(bun *)'],
|
|
252
|
+
yarn: ['Bash(npm install *)', 'Bash(npm ci *)', 'Bash(pnpm *)', 'Bash(bun *)'],
|
|
253
|
+
pnpm: ['Bash(npm install *)', 'Bash(npm ci *)', 'Bash(yarn *)', 'Bash(bun *)'],
|
|
254
|
+
bun: ['Bash(npm install *)', 'Bash(npm ci *)', 'Bash(yarn *)', 'Bash(pnpm *)'],
|
|
255
|
+
};
|
|
256
|
+
return managers[detected] || [];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function buildWrongManagerBlock(packageManager) {
|
|
260
|
+
if (!packageManager) return '';
|
|
261
|
+
const wrong = getWrongPackageManagers(packageManager);
|
|
262
|
+
if (wrong.length === 0) return '';
|
|
263
|
+
|
|
264
|
+
const patterns = wrong
|
|
265
|
+
.map((t) => t.replace('Bash(', '').replace(')', '').replace(' *', ''))
|
|
266
|
+
.map((p) => `"${p}"`)
|
|
267
|
+
.join(' ');
|
|
268
|
+
|
|
269
|
+
return `
|
|
270
|
+
# === Wrong package manager check ===
|
|
271
|
+
WRONG_MANAGERS=(${patterns})
|
|
272
|
+
for mgr in "\${WRONG_MANAGERS[@]}"; do
|
|
273
|
+
if echo "$TOOL_INPUT" | grep -qE "^$mgr\\b"; then
|
|
274
|
+
echo "BLOCKED: Use ${packageManager} instead of $mgr in this project" >&2
|
|
275
|
+
exit 2
|
|
276
|
+
fi
|
|
277
|
+
done
|
|
278
|
+
`;
|
|
279
|
+
}
|
package/src/index.js
CHANGED
|
@@ -3,3 +3,7 @@ export { generateDocs, writeDocs } from './generator.js';
|
|
|
3
3
|
export { validateClaudeMd, validateStructure, crossReferenceCheck } from './validator.js';
|
|
4
4
|
export { runExistingProjectFlow, runColdStartFlow, buildDefaultConfig } from './prompts.js';
|
|
5
5
|
export { installScaffold } from './generator.js';
|
|
6
|
+
export { runLoop, spawnClaude, checkClaudeCli } from './loop.js';
|
|
7
|
+
export { AUTONOMY_PROFILES, getProfile, listProfiles, CREDENTIAL_PATHS } from './profiles.js';
|
|
8
|
+
export { generateSettingsJson, generatePreToolUseHook, generateAuditLogHook, generateAliases, generateCIWorkflow } from './hooks-gen.js';
|
|
9
|
+
export { generateAutonomyConfig, printSecuritySummary, getFullModeWarning } from './autonomy.js';
|
package/src/installer.js
CHANGED
|
@@ -92,6 +92,8 @@ export async function uninstallGlobal(options = {}) {
|
|
|
92
92
|
|
|
93
93
|
const targets = [
|
|
94
94
|
join(targetBase, 'commands', 'claudenv.md'),
|
|
95
|
+
join(targetBase, 'commands', 'setup-mcp.md'),
|
|
96
|
+
join(targetBase, 'commands', 'improve.md'),
|
|
95
97
|
join(targetBase, 'skills', 'claudenv'),
|
|
96
98
|
];
|
|
97
99
|
|