troxy-cli 1.30.21 → 1.30.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.30.21",
3
+ "version": "1.30.22",
4
4
  "description": "Make your Claude Code usage go further: lighter requests, less usage, less money",
5
5
  "homepage": "https://troxy.io",
6
6
  "bugs": {
package/src/init.js CHANGED
@@ -325,14 +325,14 @@ async function printHostedAgentSetup(key) {
325
325
  // thing as a bare ~/AGENTS.md, which (per our own testing) a project working
326
326
  // directory does not walk up to. This is the one path that actually applies
327
327
  // everywhere, so it's the file the "all projects" option below writes to.
328
- function globalClaudeMdPath() {
328
+ export function globalClaudeMdPath() {
329
329
  return path.join(os.homedir(), '.claude', 'CLAUDE.md');
330
330
  }
331
331
 
332
332
  // Same merge algorithm as writeAgentsFile (marker-delimited, never clobbers
333
333
  // content outside its own span), generalized to any file path so both the
334
334
  // per-project AGENTS.md and the global CLAUDE.md can share it.
335
- function writeMarkedBlock(file, text, startMarker, endMarker) {
335
+ export function writeMarkedBlock(file, text, startMarker, endMarker) {
336
336
  const block = `${startMarker}\n${text}\n${endMarker}`;
337
337
  let existing = '';
338
338
  try { existing = fs.readFileSync(file, 'utf8'); } catch {}
@@ -352,7 +352,7 @@ function writeMarkedBlock(file, text, startMarker, endMarker) {
352
352
  return file;
353
353
  }
354
354
 
355
- function removeMarkedBlock(file, startMarker, endMarker) {
355
+ export function removeMarkedBlock(file, startMarker, endMarker) {
356
356
  let existing;
357
357
  try { existing = fs.readFileSync(file, 'utf8'); } catch { return false; }
358
358
  if (!existing.includes(startMarker) || !existing.includes(endMarker)) return false;
package/src/slim.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
- import { claudeCodeSettingsPath } from './init.js';
4
+ import { claudeCodeSettingsPath, globalClaudeMdPath, writeMarkedBlock, removeMarkedBlock } from './init.js';
5
5
  import { loadConfig, saveConfig } from './config.js';
6
6
  import { api } from './api.js';
7
7
 
@@ -21,9 +21,9 @@ import { api } from './api.js';
21
21
  // of the tool in a transcript; tool definitions are in transcripts too, so
22
22
  // matching the bare name would find every session.
23
23
  export const TOOLS = [
24
- { id: 'chrome', label: 'Claude in Chrome (lets Claude control your browser)', tokens: 7200,
24
+ { id: 'chrome', label: 'Claude in Chrome (lets Claude control your browser)', tokens: 7200, asks: 'to use or control a web browser',
25
25
  used: /"type":"tool_use","id":"[^"]+","name":"mcp__claude-in-chrome__/ },
26
- { id: 'artifact', label: 'Artifacts (lets Claude publish pages to claude.ai)', tokens: 7500,
26
+ { id: 'artifact', label: 'Artifacts (lets Claude publish pages to claude.ai)', tokens: 7500, asks: 'for an artifact or a page published to claude.ai',
27
27
  used: /"type":"tool_use","id":"[^"]+","name":"Artifact"/ },
28
28
  ];
29
29
 
@@ -57,6 +57,23 @@ export function setToolOff(id, off, file = claudeCodeSettingsPath()) {
57
57
  throw new Error(`Unknown tool "${id}". Try: ${TOOLS.map(t => t.id).join(', ')}`);
58
58
  }
59
59
  write(file, s);
60
+ if (file === claudeCodeSettingsPath()) syncOffNote(file);
61
+ }
62
+
63
+ const NOTE_START = '<!-- troxy:slim:start -->';
64
+ const NOTE_END = '<!-- troxy:slim:end -->';
65
+
66
+ // A turned-off tool is simply gone from Claude's tool list, so nothing errors:
67
+ // Claude would only say it can't do that. This note in the global CLAUDE.md
68
+ // tells Claude why and what to tell the user. About 50 tokens per tool, vs
69
+ // the ~7k the tool itself costs.
70
+ export function syncOffNote(file = claudeCodeSettingsPath(), noteFile = globalClaudeMdPath()) {
71
+ const off = TOOLS.filter(t => toolIsOff(t.id, file));
72
+ if (!off.length) { removeMarkedBlock(noteFile, NOTE_START, NOTE_END); return; }
73
+ writeMarkedBlock(noteFile, '## Turned off by Troxy\n\n' + off.map(t =>
74
+ `- ${t.label.split(' (')[0]} is turned off by Troxy to make every call smaller. If the user asks you ${t.asks}, ` +
75
+ `tell them it is off and that they can turn it back on by running \`troxy slim ${t.id} --undo\` in a terminal and then starting a new Claude Code session.`
76
+ ).join('\n'), NOTE_START, NOTE_END);
60
77
  }
61
78
 
62
79
  export function printToolsStatus(file = claudeCodeSettingsPath()) {
@@ -102,7 +119,9 @@ export async function reportSlim(apiKey = loadConfig()?.apiKey, file = claudeCod
102
119
  // back on with --undo stays on.
103
120
  export async function autoSlim({ file = claudeCodeSettingsPath(), projectsDir } = {}) {
104
121
  const cfg = loadConfig();
105
- if (!cfg || cfg.slimChecked) return;
122
+ if (!cfg) return;
123
+ if (file === claudeCodeSettingsPath()) syncOffNote(file); // also for tools turned off before the note existed
124
+ if (cfg.slimChecked) return;
106
125
  const used = usedTools(projectsDir);
107
126
  for (const t of TOOLS) {
108
127
  if (used.has(t.id) || toolIsOff(t.id, file)) continue;
@@ -4,7 +4,7 @@ import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
 
7
- import { TOOLS, toolIsOff, setToolOff, usedTools } from '../slim.js';
7
+ import { TOOLS, toolIsOff, setToolOff, usedTools, syncOffNote } from '../slim.js';
8
8
 
9
9
  // Turning a tool off and back on must leave the user's own settings.json
10
10
  // exactly as it was, including their own env vars and deny rules.
@@ -53,4 +53,18 @@ describe('troxy slim', () => {
53
53
  assert.deepEqual([...usedTools(path.join(dir, 'projects'))], ['chrome']);
54
54
  assert.deepEqual([...usedTools(path.join(dir, 'missing'))], []);
55
55
  });
56
+
57
+ it('keeps a note for Claude listing only the tools that are off, and keeps the user text', () => {
58
+ const note = path.join(dir, 'CLAUDE.md');
59
+ fs.writeFileSync(note, '# mine\n');
60
+ setToolOff('chrome', true, file);
61
+ syncOffNote(file, note);
62
+ let text = fs.readFileSync(note, 'utf8');
63
+ assert.match(text, /^# mine/);
64
+ assert.match(text, /troxy slim chrome --undo/);
65
+ assert.doesNotMatch(text, /troxy slim artifact/);
66
+ setToolOff('chrome', false, file);
67
+ syncOffNote(file, note);
68
+ assert.equal(fs.readFileSync(note, 'utf8').trim(), '# mine');
69
+ });
56
70
  });