troxy-cli 1.30.17 → 1.30.19
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/troxy.js +15 -0
- package/package.json +1 -1
- package/src/action-classification.js +4 -6
- package/src/init.js +5 -0
- package/src/slim.js +69 -0
- package/src/tests/action-classification.test.js +4 -0
- package/src/tests/slim.test.js +43 -0
- package/src/uninstall.js +2 -0
package/bin/troxy.js
CHANGED
|
@@ -159,6 +159,20 @@ switch (command) {
|
|
|
159
159
|
process.exit(1);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// ── Unused Claude Code tools (see src/slim.js) ───────────────
|
|
163
|
+
case 'slim': {
|
|
164
|
+
const { TOOLS, setToolOff, printToolsStatus } = await import('../src/slim.js');
|
|
165
|
+
if (sub === 'status' || !sub) { printToolsStatus(); break; }
|
|
166
|
+
if (TOOLS.some(t => t.id === sub)) {
|
|
167
|
+
const undo = Boolean(flags.undo);
|
|
168
|
+
setToolOff(sub, !undo);
|
|
169
|
+
console.log(`\n ${sub} ${undo ? 'on' : 'off'} ✓ Start a new Claude Code session to apply.\n`);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
console.error(` Usage: troxy slim [status] | troxy slim <${TOOLS.map(t => t.id).join('|')}> [--undo]\n`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
// ── Auth ──────────────────────────────────────────────────────
|
|
163
177
|
case 'login':
|
|
164
178
|
await runLogin(flags);
|
|
@@ -775,6 +789,7 @@ switch (command) {
|
|
|
775
789
|
troxy status Health and connection status
|
|
776
790
|
troxy update Update to the latest version
|
|
777
791
|
troxy uninstall Remove Troxy from this machine
|
|
792
|
+
troxy slim Turn off Claude Code tools you never use
|
|
778
793
|
troxy login Start a 12h CLI session for the commands below
|
|
779
794
|
troxy usage-bar [--period 7] Your usage and what Troxy saved
|
|
780
795
|
troxy activity [--limit 50] Recent requests
|
package/package.json
CHANGED
|
@@ -116,12 +116,10 @@ const RULES = [
|
|
|
116
116
|
const paths = pathArgs(segment, 2).filter((w) => !w.startsWith('-'));
|
|
117
117
|
return { action_verb: 'delete', resource: summarizeResource(paths, 'a branch'), item_count: 1 };
|
|
118
118
|
}
|
|
119
|
-
if (/^git\s+push\b/.test(segment) && (/\s--delete\b/.test(segment) || /\s
|
|
120
|
-
// `git push origin :branch`
|
|
121
|
-
// `git push origin
|
|
122
|
-
|
|
123
|
-
// touched, which is what matters here regardless of which it deletes.
|
|
124
|
-
const target = segment.match(/--delete\s+(\S+)/)?.[1] || segment.match(/\s\S*:(\S+)/)?.[1];
|
|
119
|
+
if (/^git\s+push\b/.test(segment) && (/\s--delete\b/.test(segment) || /\s:\S+/.test(segment))) {
|
|
120
|
+
// Only an empty source (`git push origin :branch`) deletes.
|
|
121
|
+
// `git push origin HEAD:main` is a normal push, not a delete.
|
|
122
|
+
const target = segment.match(/--delete\s+(\S+)/)?.[1] || segment.match(/\s:(\S+)/)?.[1];
|
|
125
123
|
return { action_verb: 'delete', resource: target || 'a remote branch', item_count: 1 };
|
|
126
124
|
}
|
|
127
125
|
return null;
|
package/src/init.js
CHANGED
|
@@ -566,6 +566,11 @@ export async function runInit({ key, name, proxy, concise } = {}) {
|
|
|
566
566
|
|
|
567
567
|
await reprovisionKeyConsumers(key, agentName, proxyOptIn, conciseOptIn);
|
|
568
568
|
|
|
569
|
+
if (hasClaudeCode()) {
|
|
570
|
+
const { askToSlimTools } = await import('./slim.js');
|
|
571
|
+
await askToSlimTools();
|
|
572
|
+
}
|
|
573
|
+
|
|
569
574
|
// Scan for local AI coding tools so the dashboard's "My AI tools" view has
|
|
570
575
|
// something to show. Non-fatal - a scan failure never breaks setup.
|
|
571
576
|
try {
|
package/src/slim.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { claudeCodeSettingsPath, prompt } from './init.js';
|
|
3
|
+
|
|
4
|
+
// `troxy slim`: turn off Claude Code tools the user never uses. Every tool
|
|
5
|
+
// Claude Code loads is part of the fixed start of each request, re-read on
|
|
6
|
+
// every call. Measured 2026-11-05: with Chrome and Artifact off, the first
|
|
7
|
+
// call of an interactive session went from about 52.2k to 37.5k tokens.
|
|
8
|
+
//
|
|
9
|
+
// Both live in ~/.claude/settings.json, so turning them back on is just
|
|
10
|
+
// removing what we added:
|
|
11
|
+
// chrome: env CLAUDE_CODE_ENABLE_CFC=0 (same as starting with --no-chrome)
|
|
12
|
+
// artifact: permissions.deny "Artifact" (same as --disallowedTools Artifact,
|
|
13
|
+
// which removes the tool from the prompt, not just blocks calls)
|
|
14
|
+
export const TOOLS = [
|
|
15
|
+
{ id: 'chrome', label: 'Claude in Chrome (lets Claude control your browser)' },
|
|
16
|
+
{ id: 'artifact', label: 'Artifacts (lets Claude publish pages to claude.ai)' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function read(file) {
|
|
20
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return {}; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function write(file, s) {
|
|
24
|
+
fs.writeFileSync(file, JSON.stringify(s, null, 2) + '\n');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function toolIsOff(id, file = claudeCodeSettingsPath()) {
|
|
28
|
+
const s = read(file);
|
|
29
|
+
if (id === 'chrome') return s.env?.CLAUDE_CODE_ENABLE_CFC === '0';
|
|
30
|
+
if (id === 'artifact') return (s.permissions?.deny || []).includes('Artifact');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function setToolOff(id, off, file = claudeCodeSettingsPath()) {
|
|
35
|
+
const s = read(file);
|
|
36
|
+
if (id === 'chrome') {
|
|
37
|
+
if (off) s.env = { ...s.env, CLAUDE_CODE_ENABLE_CFC: '0' };
|
|
38
|
+
else if (s.env) { delete s.env.CLAUDE_CODE_ENABLE_CFC; if (!Object.keys(s.env).length) delete s.env; }
|
|
39
|
+
} else if (id === 'artifact') {
|
|
40
|
+
const deny = (s.permissions?.deny || []).filter(t => t !== 'Artifact');
|
|
41
|
+
if (off) deny.push('Artifact');
|
|
42
|
+
s.permissions = { ...s.permissions, deny };
|
|
43
|
+
if (!deny.length) delete s.permissions.deny;
|
|
44
|
+
if (!Object.keys(s.permissions).length) delete s.permissions;
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error(`Unknown tool "${id}". Try: ${TOOLS.map(t => t.id).join(', ')}`);
|
|
47
|
+
}
|
|
48
|
+
write(file, s);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function printToolsStatus(file = claudeCodeSettingsPath()) {
|
|
52
|
+
console.log('');
|
|
53
|
+
for (const t of TOOLS) console.log(` ${t.id.padEnd(9)} ${toolIsOff(t.id, file) ? 'off' : 'on '} ${t.label}`);
|
|
54
|
+
console.log('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Asked once from `troxy init`. Default is keep: a tool turned off by surprise
|
|
58
|
+
// is worse than a few thousand tokens.
|
|
59
|
+
export async function askToSlimTools(file = claudeCodeSettingsPath()) {
|
|
60
|
+
const left = TOOLS.filter(t => !toolIsOff(t.id, file));
|
|
61
|
+
if (!left.length || !process.stdin.isTTY) return;
|
|
62
|
+
console.log('\n Claude Code loads these tools into every request, even when unused.');
|
|
63
|
+
console.log(' Turning off the ones you never use makes each call lighter.');
|
|
64
|
+
console.log(' Turn back on any time with: troxy slim <name> --undo\n');
|
|
65
|
+
for (const t of left) {
|
|
66
|
+
const ans = (await prompt(` Turn off ${t.label}? (y/N): `)).toLowerCase();
|
|
67
|
+
if (ans === 'y') { setToolOff(t.id, true, file); console.log(` ${t.id} off ✓`); }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -86,6 +86,10 @@ describe('actionFor - git force push / branch delete', () => {
|
|
|
86
86
|
assert.equal(bash('git push origin --delete old-feature').action_verb, 'delete');
|
|
87
87
|
assert.equal(bash('git push origin :old-feature').action_verb, 'delete');
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
it('does not treat a normal src:dst push as a delete', () => {
|
|
91
|
+
assert.equal(bash('git push -q origin HEAD:main'), null);
|
|
92
|
+
});
|
|
89
93
|
});
|
|
90
94
|
|
|
91
95
|
describe('actionFor - terraform / kubectl / docker / aws', () => {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { TOOLS, toolIsOff, setToolOff } from '../slim.js';
|
|
8
|
+
|
|
9
|
+
// Turning a tool off and back on must leave the user's own settings.json
|
|
10
|
+
// exactly as it was, including their own env vars and deny rules.
|
|
11
|
+
let dir, file;
|
|
12
|
+
beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'troxy-slim-')); file = path.join(dir, 'settings.json'); });
|
|
13
|
+
afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); });
|
|
14
|
+
|
|
15
|
+
const read = () => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
16
|
+
|
|
17
|
+
describe('troxy slim', () => {
|
|
18
|
+
it('turns each tool off and back on, keeping the user settings', () => {
|
|
19
|
+
const theirs = { env: { HTTPS_PROXY: 'x' }, permissions: { deny: ['Bash(rm:*)'], allow: ['Read'] }, model: 'opus' };
|
|
20
|
+
fs.writeFileSync(file, JSON.stringify(theirs));
|
|
21
|
+
for (const t of TOOLS) {
|
|
22
|
+
assert.equal(toolIsOff(t.id, file), false);
|
|
23
|
+
setToolOff(t.id, true, file);
|
|
24
|
+
setToolOff(t.id, true, file);
|
|
25
|
+
assert.equal(toolIsOff(t.id, file), true);
|
|
26
|
+
}
|
|
27
|
+
assert.equal(read().env.CLAUDE_CODE_ENABLE_CFC, '0');
|
|
28
|
+
assert.deepEqual(read().permissions.deny, ['Bash(rm:*)', 'Artifact']);
|
|
29
|
+
for (const t of TOOLS) setToolOff(t.id, false, file);
|
|
30
|
+
assert.deepEqual(read(), theirs);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('cleans up empty keys it created', () => {
|
|
34
|
+
fs.writeFileSync(file, '{}');
|
|
35
|
+
for (const t of TOOLS) setToolOff(t.id, true, file);
|
|
36
|
+
for (const t of TOOLS) setToolOff(t.id, false, file);
|
|
37
|
+
assert.deepEqual(read(), {});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('rejects an unknown tool', () => {
|
|
41
|
+
assert.throws(() => setToolOff('nope', true, file), /Unknown tool/);
|
|
42
|
+
});
|
|
43
|
+
});
|
package/src/uninstall.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
uninstallTroxyGuardMod,
|
|
13
13
|
} from './init.js';
|
|
14
14
|
import { launchdUserDomain } from './launchd.js';
|
|
15
|
+
import { TOOLS, toolIsOff, setToolOff } from './slim.js';
|
|
15
16
|
|
|
16
17
|
const MCP_CLIENTS = [
|
|
17
18
|
{
|
|
@@ -149,6 +150,7 @@ export async function runUninstall() {
|
|
|
149
150
|
unpatchClaudeCodeProxy(claudeCodeSettingsPath());
|
|
150
151
|
unpatchClaudeCodeInterception(claudeCodeSettingsPath());
|
|
151
152
|
uninstallTroxyGuardMod();
|
|
153
|
+
for (const t of TOOLS) if (toolIsOff(t.id)) setToolOff(t.id, false);
|
|
152
154
|
console.log('✓');
|
|
153
155
|
} catch (err) {
|
|
154
156
|
console.log('✗');
|