cc-dev-template 0.1.45 → 0.1.48
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/bin/install.js +66 -1
- package/package.json +4 -2
- package/src/commands/done.md +5 -11
- package/src/scripts/env-config.json +5 -0
- package/src/scripts/statusline.js +157 -49
- package/src/hooks/bash-precheck-hook.json +0 -15
- package/src/hooks/bash-precheck.sh +0 -117
package/bin/install.js
CHANGED
|
@@ -233,7 +233,7 @@ if (fs.existsSync(mergeSettingsPath)) {
|
|
|
233
233
|
{ file: 'read-guard-hook.json', name: 'Context guard for large reads' },
|
|
234
234
|
{ file: 'statusline-config.json', name: 'Custom status line' },
|
|
235
235
|
{ file: 'bash-overflow-hook.json', name: 'Bash overflow guard hook' },
|
|
236
|
-
{ file: '
|
|
236
|
+
{ file: 'env-config.json', name: 'Environment variables' }
|
|
237
237
|
];
|
|
238
238
|
|
|
239
239
|
configs.forEach(({ file, name }) => {
|
|
@@ -251,6 +251,71 @@ if (fs.existsSync(mergeSettingsPath)) {
|
|
|
251
251
|
});
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// Remove deprecated files and settings
|
|
255
|
+
console.log('\nCleanup:');
|
|
256
|
+
let cleanupPerformed = false;
|
|
257
|
+
|
|
258
|
+
// Remove deprecated bash wrapper files
|
|
259
|
+
const deprecatedFiles = [
|
|
260
|
+
path.join(CLAUDE_DIR, 'hooks', 'bash-precheck.sh'),
|
|
261
|
+
path.join(CLAUDE_DIR, 'hooks', 'bash-wrapper-helper.sh'),
|
|
262
|
+
path.join(CLAUDE_DIR, 'scripts', 'bash-precheck-hook.json')
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
deprecatedFiles.forEach(file => {
|
|
266
|
+
if (fs.existsSync(file)) {
|
|
267
|
+
fs.unlinkSync(file);
|
|
268
|
+
console.log(`✓ Removed deprecated ${path.basename(file)}`);
|
|
269
|
+
cleanupPerformed = true;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Remove deprecated hooks from settings.json
|
|
274
|
+
if (fs.existsSync(settingsFile)) {
|
|
275
|
+
try {
|
|
276
|
+
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
277
|
+
let settingsModified = false;
|
|
278
|
+
|
|
279
|
+
// Remove deprecated code-simplifier plugin
|
|
280
|
+
if (settings.enabledPlugins && settings.enabledPlugins['code-simplifier@claude-plugins-official']) {
|
|
281
|
+
delete settings.enabledPlugins['code-simplifier@claude-plugins-official'];
|
|
282
|
+
console.log('✓ Removed deprecated code-simplifier plugin');
|
|
283
|
+
settingsModified = true;
|
|
284
|
+
cleanupPerformed = true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Remove bash-precheck hooks from settings
|
|
288
|
+
if (settings.hooks) {
|
|
289
|
+
const hookTypes = ['PreToolUse', 'PostToolUse'];
|
|
290
|
+
hookTypes.forEach(hookType => {
|
|
291
|
+
if (settings.hooks[hookType] && Array.isArray(settings.hooks[hookType])) {
|
|
292
|
+
const originalLength = settings.hooks[hookType].length;
|
|
293
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter(hook => {
|
|
294
|
+
const command = hook.hooks?.[0]?.command || '';
|
|
295
|
+
return !command.includes('bash-precheck');
|
|
296
|
+
});
|
|
297
|
+
if (settings.hooks[hookType].length < originalLength) {
|
|
298
|
+
console.log(`✓ Removed deprecated bash-precheck hook from ${hookType}`);
|
|
299
|
+
settingsModified = true;
|
|
300
|
+
cleanupPerformed = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if (settingsModified) {
|
|
308
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
309
|
+
}
|
|
310
|
+
} catch (e) {
|
|
311
|
+
// Ignore errors reading settings
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!cleanupPerformed) {
|
|
316
|
+
console.log(' No deprecated items to remove');
|
|
317
|
+
}
|
|
318
|
+
|
|
254
319
|
console.log('\n' + '='.repeat(50));
|
|
255
320
|
console.log('Installation complete!');
|
|
256
321
|
console.log('='.repeat(50));
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-dev-template",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"description": "Structured AI-assisted development framework for Claude Code",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cc-dev-template": "./bin/install.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
|
-
"src/"
|
|
10
|
+
"src/",
|
|
11
|
+
"!src/mcp-servers/*/node_modules/",
|
|
12
|
+
"!src/mcp-servers/*/dist/"
|
|
11
13
|
],
|
|
12
14
|
"keywords": [
|
|
13
15
|
"claude",
|
package/src/commands/done.md
CHANGED
|
@@ -19,17 +19,11 @@ This requires full conversation context. Handle it yourself rather than delegati
|
|
|
19
19
|
|
|
20
20
|
## Steps
|
|
21
21
|
|
|
22
|
-
**1.
|
|
23
|
-
|
|
24
|
-
Stage your changes, then use the code simplifier agent to refine them for clarity and consistency. Run tests afterward to verify nothing broke.
|
|
25
|
-
|
|
26
|
-
Skip this step if no code simplifier agent is available.
|
|
27
|
-
|
|
28
|
-
**2. Commit your work**
|
|
22
|
+
**1. Commit your work**
|
|
29
23
|
|
|
30
24
|
Write clear commit messages that explain what was accomplished. This IS your record of completed work.
|
|
31
25
|
|
|
32
|
-
**
|
|
26
|
+
**2. Update `docs/CURRENT_WORK.md`**
|
|
33
27
|
|
|
34
28
|
This file is forward-looking. Review it holistically and ensure it contains ONLY:
|
|
35
29
|
|
|
@@ -46,7 +40,7 @@ This file is forward-looking. Review it holistically and ensure it contains ONLY
|
|
|
46
40
|
|
|
47
41
|
The file should be scannable in 30 seconds. If it takes longer, it's too long.
|
|
48
42
|
|
|
49
|
-
**
|
|
43
|
+
**3. Capture workflow discoveries (rarely)**
|
|
50
44
|
|
|
51
45
|
Add to CLAUDE.md only high-value operational knowledge that can't be found by reading code:
|
|
52
46
|
- Dev commands, ports, local URLs
|
|
@@ -55,11 +49,11 @@ Add to CLAUDE.md only high-value operational knowledge that can't be found by re
|
|
|
55
49
|
|
|
56
50
|
Most sessions: add nothing.
|
|
57
51
|
|
|
58
|
-
**
|
|
52
|
+
**4. Push**
|
|
59
53
|
|
|
60
54
|
Push your commits to the remote.
|
|
61
55
|
|
|
62
|
-
**
|
|
56
|
+
**5. Report what was updated**
|
|
63
57
|
|
|
64
58
|
Summarize: commits made, CURRENT_WORK.md changes, any items removed/added.
|
|
65
59
|
|
|
@@ -161,6 +161,71 @@ function getGitBranch(projectDir) {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Get submodule paths from .gitmodules
|
|
166
|
+
*/
|
|
167
|
+
function getSubmodulePaths(projectDir) {
|
|
168
|
+
try {
|
|
169
|
+
const output = execSync('git config --file .gitmodules --get-regexp path', {
|
|
170
|
+
cwd: projectDir,
|
|
171
|
+
encoding: 'utf-8',
|
|
172
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
173
|
+
});
|
|
174
|
+
// Parse lines like "submodule.packages/core.path packages/core"
|
|
175
|
+
return output
|
|
176
|
+
.trim()
|
|
177
|
+
.split('\n')
|
|
178
|
+
.map((line) => line.split(' ')[1])
|
|
179
|
+
.filter(Boolean);
|
|
180
|
+
} catch {
|
|
181
|
+
return []; // No .gitmodules or no submodules
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get all modules (main + submodules) that have changes
|
|
187
|
+
* Returns array of { name, path, branch, status }
|
|
188
|
+
*/
|
|
189
|
+
function getModulesWithChanges(projectDir) {
|
|
190
|
+
const submodulePaths = getSubmodulePaths(projectDir);
|
|
191
|
+
|
|
192
|
+
// If no submodules, return null to indicate regular repo
|
|
193
|
+
if (submodulePaths.length === 0) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const modules = [];
|
|
198
|
+
|
|
199
|
+
// Check main repo
|
|
200
|
+
const mainStatus = getGitStatus(projectDir);
|
|
201
|
+
const mainStatusStr = mainStatus ? formatGitStatus(mainStatus) : '';
|
|
202
|
+
if (mainStatusStr) {
|
|
203
|
+
modules.push({
|
|
204
|
+
name: 'MAIN',
|
|
205
|
+
path: projectDir,
|
|
206
|
+
branch: getGitBranch(projectDir),
|
|
207
|
+
status: mainStatusStr,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check each submodule
|
|
212
|
+
for (const subPath of submodulePaths) {
|
|
213
|
+
const fullPath = join(projectDir, subPath);
|
|
214
|
+
const subStatus = getGitStatus(fullPath);
|
|
215
|
+
const subStatusStr = subStatus ? formatGitStatus(subStatus) : '';
|
|
216
|
+
if (subStatusStr) {
|
|
217
|
+
modules.push({
|
|
218
|
+
name: basename(subPath).toUpperCase(),
|
|
219
|
+
path: fullPath,
|
|
220
|
+
branch: getGitBranch(fullPath),
|
|
221
|
+
status: subStatusStr,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return modules;
|
|
227
|
+
}
|
|
228
|
+
|
|
164
229
|
/**
|
|
165
230
|
* Main function
|
|
166
231
|
*/
|
|
@@ -191,18 +256,35 @@ function main() {
|
|
|
191
256
|
|
|
192
257
|
// Get token usage from context_window (available in Claude Code v2.0.70+)
|
|
193
258
|
let tokenData = null;
|
|
194
|
-
if (data.context_window
|
|
195
|
-
const usage = data.context_window.current_usage;
|
|
196
|
-
const used =
|
|
197
|
-
(usage.input_tokens || 0) +
|
|
198
|
-
(usage.cache_read_input_tokens || 0) +
|
|
199
|
-
(usage.cache_creation_input_tokens || 0);
|
|
259
|
+
if (data.context_window) {
|
|
200
260
|
const limit = data.context_window.context_window_size || 200000;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
261
|
+
|
|
262
|
+
// Use new percentage fields if available (v2.1.6+)
|
|
263
|
+
if (typeof data.context_window.used_percentage === 'number') {
|
|
264
|
+
const usage = data.context_window.current_usage;
|
|
265
|
+
const used = usage
|
|
266
|
+
? (usage.input_tokens || 0) +
|
|
267
|
+
(usage.cache_read_input_tokens || 0) +
|
|
268
|
+
(usage.cache_creation_input_tokens || 0)
|
|
269
|
+
: 0;
|
|
270
|
+
tokenData = {
|
|
271
|
+
used,
|
|
272
|
+
limit,
|
|
273
|
+
percentage: Math.round(data.context_window.used_percentage),
|
|
274
|
+
};
|
|
275
|
+
} else if (data.context_window.current_usage) {
|
|
276
|
+
// Fallback to manual calculation for older versions
|
|
277
|
+
const usage = data.context_window.current_usage;
|
|
278
|
+
const used =
|
|
279
|
+
(usage.input_tokens || 0) +
|
|
280
|
+
(usage.cache_read_input_tokens || 0) +
|
|
281
|
+
(usage.cache_creation_input_tokens || 0);
|
|
282
|
+
tokenData = {
|
|
283
|
+
used,
|
|
284
|
+
limit,
|
|
285
|
+
percentage: Math.min(100, Math.round((used / limit) * 100)),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
206
288
|
}
|
|
207
289
|
|
|
208
290
|
// Generate context display
|
|
@@ -220,14 +302,17 @@ function main() {
|
|
|
220
302
|
ctxDisplay = `CTX: ${greyColor}[${bar}] ${pct}%${DIM_GREY} | ${usedTokens}/${limitTokens}`;
|
|
221
303
|
}
|
|
222
304
|
|
|
223
|
-
// Get git information
|
|
224
|
-
const gitBranch = getGitBranch(data.workspace.project_dir);
|
|
225
|
-
const gitStatus = getGitStatus(data.workspace.project_dir);
|
|
226
|
-
const gitStatusStr = gitStatus ? formatGitStatus(gitStatus) : '';
|
|
227
|
-
|
|
228
305
|
// Multi-line bordered box format
|
|
229
306
|
const width = 56;
|
|
230
307
|
|
|
308
|
+
// Helper to create a padded box line
|
|
309
|
+
const makeBoxLine = (content) => {
|
|
310
|
+
const plain = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
311
|
+
const padding = width - plain.length;
|
|
312
|
+
const padded = content + ' '.repeat(Math.max(0, padding));
|
|
313
|
+
return `${DIM_GREY}║ ${padded} ║${RESET}`;
|
|
314
|
+
};
|
|
315
|
+
|
|
231
316
|
// Top border with model name (add 2 to match content line width: ║ + space + 56 + space + ║ = 60)
|
|
232
317
|
const modelName = data.model.display_name.toUpperCase();
|
|
233
318
|
const labelWidth = modelName.length + 2; // +2 for brackets
|
|
@@ -238,46 +323,69 @@ function main() {
|
|
|
238
323
|
const maxDirLen = width - 5; // "DIR: " = 5 chars
|
|
239
324
|
const dirName =
|
|
240
325
|
rawDirName.length > maxDirLen ? rawDirName.substring(0, maxDirLen - 3) + '...' : rawDirName;
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
326
|
+
const line0 = makeBoxLine(`DIR: ${dirName}`);
|
|
327
|
+
|
|
328
|
+
// Get git information - check for submodules
|
|
329
|
+
const modulesWithChanges = getModulesWithChanges(data.workspace.project_dir);
|
|
330
|
+
|
|
331
|
+
// Build branch/module lines
|
|
332
|
+
const branchLines = [];
|
|
333
|
+
|
|
334
|
+
if (modulesWithChanges === null) {
|
|
335
|
+
// No submodules - use original single-line format
|
|
336
|
+
const gitBranch = getGitBranch(data.workspace.project_dir);
|
|
337
|
+
const gitStatus = getGitStatus(data.workspace.project_dir);
|
|
338
|
+
const gitStatusStr = gitStatus ? formatGitStatus(gitStatus) : '';
|
|
339
|
+
|
|
340
|
+
let line1Content = '';
|
|
341
|
+
if (gitBranch) {
|
|
342
|
+
const branchPrefix = 'BRANCH: ';
|
|
343
|
+
const branchSuffix = ' ⎇';
|
|
344
|
+
const gitStatusSpace = gitStatusStr ? ` ${gitStatusStr}` : '';
|
|
345
|
+
|
|
346
|
+
const availableSpace = width - branchPrefix.length - branchSuffix.length - gitStatusSpace.length;
|
|
347
|
+
const displayBranch =
|
|
348
|
+
gitBranch.length > availableSpace
|
|
349
|
+
? gitBranch.substring(0, availableSpace - 3) + '...'
|
|
350
|
+
: gitBranch;
|
|
351
|
+
|
|
352
|
+
line1Content = `${branchPrefix}${displayBranch.toUpperCase()}${branchSuffix}${gitStatusSpace}`;
|
|
353
|
+
} else {
|
|
354
|
+
line1Content = 'BRANCH: (none)';
|
|
355
|
+
}
|
|
356
|
+
branchLines.push(makeBoxLine(line1Content));
|
|
357
|
+
} else if (modulesWithChanges.length === 0) {
|
|
358
|
+
// Has submodules but all clean
|
|
359
|
+
const gitBranch = getGitBranch(data.workspace.project_dir);
|
|
360
|
+
const branchName = gitBranch ? gitBranch.toUpperCase() : '(none)';
|
|
361
|
+
branchLines.push(makeBoxLine(`BRANCH: ${branchName} ⎇ (all clean)`));
|
|
262
362
|
} else {
|
|
263
|
-
|
|
363
|
+
// Has submodules with changes - one line per module
|
|
364
|
+
for (const mod of modulesWithChanges) {
|
|
365
|
+
const branchName = mod.branch ? mod.branch.toUpperCase() : '(none)';
|
|
366
|
+
const prefix = `[${mod.name}] `;
|
|
367
|
+
const suffix = ' ⎇';
|
|
368
|
+
const statusSpace = ` ${mod.status}`;
|
|
369
|
+
|
|
370
|
+
const availableSpace = width - prefix.length - suffix.length - statusSpace.length;
|
|
371
|
+
const displayBranch =
|
|
372
|
+
branchName.length > availableSpace
|
|
373
|
+
? branchName.substring(0, availableSpace - 3) + '...'
|
|
374
|
+
: branchName;
|
|
375
|
+
|
|
376
|
+
branchLines.push(makeBoxLine(`${prefix}${displayBranch}${suffix}${statusSpace}`));
|
|
377
|
+
}
|
|
264
378
|
}
|
|
265
379
|
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
const paddedLine1 = line1Content + ' '.repeat(Math.max(0, padding1));
|
|
269
|
-
const line1 = `${DIM_GREY}║ ${paddedLine1} ║${RESET}`;
|
|
270
|
-
|
|
271
|
-
// Line 2: Context bar (padded to width)
|
|
272
|
-
const plainCtx = ctxDisplay.replace(/\x1b\[[0-9;]*m/g, '');
|
|
273
|
-
const padding2 = width - plainCtx.length;
|
|
274
|
-
const paddedCtx = ctxDisplay + ' '.repeat(Math.max(0, padding2));
|
|
275
|
-
const line2 = `${DIM_GREY}║ ${paddedCtx} ║${RESET}`;
|
|
380
|
+
// Context bar line
|
|
381
|
+
const ctxLine = makeBoxLine(ctxDisplay);
|
|
276
382
|
|
|
277
383
|
// Bottom border (add 2 to match content line width)
|
|
278
384
|
const bottomBorder = `${DIM_GREY}╚${'═'.repeat(width + 2)}╝${RESET}`;
|
|
279
385
|
|
|
280
|
-
|
|
386
|
+
// Combine all lines
|
|
387
|
+
const allLines = [topBorder, line0, ...branchLines, ctxLine, bottomBorder];
|
|
388
|
+
console.log(allLines.join('\n'));
|
|
281
389
|
} catch (error) {
|
|
282
390
|
// Log error for debugging (goes to stderr, not visible in status line)
|
|
283
391
|
console.error(`Status line error: ${error.message}`);
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# bash-precheck.sh - PreToolUse hook to wrap Bash commands with output limiting
|
|
3
|
-
#
|
|
4
|
-
# Intercepts Bash commands before execution and wraps them to:
|
|
5
|
-
# 1. Capture output to temp file
|
|
6
|
-
# 2. If output exceeds threshold, save and return truncated version
|
|
7
|
-
#
|
|
8
|
-
# This ACTUALLY prevents large outputs from consuming context.
|
|
9
|
-
# Compatible with bash and zsh on macOS and Linux.
|
|
10
|
-
|
|
11
|
-
set -e
|
|
12
|
-
|
|
13
|
-
# Configuration
|
|
14
|
-
MAX_CHARS=${BASH_OVERFLOW_MAX_CHARS:-20000}
|
|
15
|
-
OVERFLOW_DIR="${HOME}/.claude/bash-overflow"
|
|
16
|
-
|
|
17
|
-
# Read hook input from stdin
|
|
18
|
-
input=$(cat)
|
|
19
|
-
|
|
20
|
-
# Parse tool name and command
|
|
21
|
-
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
22
|
-
|
|
23
|
-
# Only process Bash tool
|
|
24
|
-
if [[ "$tool_name" != "Bash" ]]; then
|
|
25
|
-
exit 0
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
# Get the original command
|
|
29
|
-
original_cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
|
|
30
|
-
|
|
31
|
-
# Skip if empty
|
|
32
|
-
if [[ -z "$original_cmd" ]]; then
|
|
33
|
-
exit 0
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# Skip commands that are already piped to head/tail or are simple commands
|
|
37
|
-
if [[ "$original_cmd" =~ \|[[:space:]]*(head|tail|wc|grep.*-c) ]]; then
|
|
38
|
-
exit 0
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
# Skip very short commands (likely simple operations)
|
|
42
|
-
if [[ ${#original_cmd} -lt 10 ]]; then
|
|
43
|
-
exit 0
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
# Create overflow directory
|
|
47
|
-
mkdir -p "$OVERFLOW_DIR"
|
|
48
|
-
|
|
49
|
-
# Generate unique filename
|
|
50
|
-
timestamp=$(date +%Y%m%d_%H%M%S)
|
|
51
|
-
rand_suffix=$RANDOM
|
|
52
|
-
overflow_file="$OVERFLOW_DIR/bash_output_${timestamp}_${rand_suffix}.txt"
|
|
53
|
-
temp_file="/tmp/claude_bash_${timestamp}_${rand_suffix}.tmp"
|
|
54
|
-
|
|
55
|
-
# Escape the original command for embedding in the wrapper
|
|
56
|
-
# Use base64 to safely embed arbitrary commands
|
|
57
|
-
original_cmd_b64=$(echo "$original_cmd" | base64)
|
|
58
|
-
|
|
59
|
-
# Create wrapper that uses temp file approach (more portable)
|
|
60
|
-
read -r -d '' wrapped_cmd << 'WRAPPER_EOF' || true
|
|
61
|
-
__cmd_b64="BASE64_CMD_HERE"
|
|
62
|
-
__cmd=$(echo "$__cmd_b64" | base64 -d)
|
|
63
|
-
__tmpfile="TEMP_FILE_HERE"
|
|
64
|
-
__overflow="OVERFLOW_FILE_HERE"
|
|
65
|
-
__max=MAX_CHARS_HERE
|
|
66
|
-
|
|
67
|
-
# Run command and capture to temp file
|
|
68
|
-
eval "$__cmd" > "$__tmpfile" 2>&1
|
|
69
|
-
__exit_code=$?
|
|
70
|
-
|
|
71
|
-
# Check size
|
|
72
|
-
__size=$(wc -c < "$__tmpfile" | tr -d ' ')
|
|
73
|
-
|
|
74
|
-
if [ "$__size" -gt "$__max" ]; then
|
|
75
|
-
# Save full output
|
|
76
|
-
cp "$__tmpfile" "$__overflow"
|
|
77
|
-
__lines=$(wc -l < "$__tmpfile" | tr -d ' ')
|
|
78
|
-
__tokens=$((__size / 4))
|
|
79
|
-
|
|
80
|
-
echo "=== OUTPUT TRUNCATED (${__lines} lines, ~${__tokens} tokens) ==="
|
|
81
|
-
echo ""
|
|
82
|
-
echo "--- First 5 lines ---"
|
|
83
|
-
head -n 5 "$__tmpfile"
|
|
84
|
-
echo ""
|
|
85
|
-
echo "--- Last 5 lines ---"
|
|
86
|
-
tail -n 5 "$__tmpfile"
|
|
87
|
-
echo ""
|
|
88
|
-
echo "=== Full output saved to: $__overflow ==="
|
|
89
|
-
echo "Use Grep(pattern, path) or Read(file_path, offset, limit) to explore"
|
|
90
|
-
else
|
|
91
|
-
cat "$__tmpfile"
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
rm -f "$__tmpfile"
|
|
95
|
-
exit $__exit_code
|
|
96
|
-
WRAPPER_EOF
|
|
97
|
-
|
|
98
|
-
# Substitute placeholders
|
|
99
|
-
wrapped_cmd="${wrapped_cmd//BASE64_CMD_HERE/$original_cmd_b64}"
|
|
100
|
-
wrapped_cmd="${wrapped_cmd//TEMP_FILE_HERE/$temp_file}"
|
|
101
|
-
wrapped_cmd="${wrapped_cmd//OVERFLOW_FILE_HERE/$overflow_file}"
|
|
102
|
-
wrapped_cmd="${wrapped_cmd//MAX_CHARS_HERE/$MAX_CHARS}"
|
|
103
|
-
|
|
104
|
-
# Return the modified command
|
|
105
|
-
cat << EOF
|
|
106
|
-
{
|
|
107
|
-
"hookSpecificOutput": {
|
|
108
|
-
"hookEventName": "PreToolUse",
|
|
109
|
-
"permissionDecision": "allow",
|
|
110
|
-
"updatedInput": {
|
|
111
|
-
"command": $(echo "$wrapped_cmd" | jq -Rs .)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
EOF
|
|
116
|
-
|
|
117
|
-
exit 0
|