troxy-cli 1.29.17 → 1.29.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 CHANGED
@@ -127,23 +127,36 @@ switch (command) {
127
127
  process.exit(1);
128
128
  }
129
129
 
130
- // ── Token-efficiency instructions in AGENTS.md (opt-in at init) ────────
130
+ // ── Token-efficiency instructions in AGENTS.md / global CLAUDE.md ──────
131
+ // --project scopes enable/disable/status to just this directory's
132
+ // AGENTS.md; default (no flag) is the global ~/.claude/CLAUDE.md, matching
133
+ // what the init/update prompt now recommends by default.
131
134
  case 'concise': {
132
- const { maybeEnableConciseInstructions, removeConciseInstructions, conciseInstructionsConfigured } = await import('../src/init.js');
135
+ const {
136
+ maybeEnableConciseInstructions, removeConciseInstructions, conciseInstructionsConfigured,
137
+ enableGlobalConciseInstructions, removeGlobalConciseInstructions, globalConciseConfigured,
138
+ } = await import('../src/init.js');
133
139
  const dir = process.cwd();
134
- if (sub === 'enable') { await maybeEnableConciseInstructions(true, dir); break; }
140
+ const projectOnly = Boolean(flags.project);
141
+
142
+ if (sub === 'enable') {
143
+ if (projectOnly) { await maybeEnableConciseInstructions('project', dir); }
144
+ else { console.log(`\n Added token-efficiency instructions to ${enableGlobalConciseInstructions()} (all projects)\n`); }
145
+ break;
146
+ }
135
147
  if (sub === 'disable') {
136
- const removed = removeConciseInstructions(dir);
137
- console.log(removed ? '\n Removed ✓\n' : '\n Not enabled in this project.\n');
148
+ const removed = projectOnly ? removeConciseInstructions(dir) : removeGlobalConciseInstructions();
149
+ console.log(removed ? '\n Removed ✓\n' : `\n Not enabled ${projectOnly ? 'in this project' : 'globally'}.\n`);
138
150
  break;
139
151
  }
140
152
  if (sub === 'status' || !sub) {
141
- console.log(conciseInstructionsConfigured(dir)
142
- ? '\n Enabled in this project.\n'
143
- : '\n Not enabled in this project. Run "troxy concise enable" to add it.\n');
153
+ console.log('');
154
+ console.log(` Global (~/.claude/CLAUDE.md): ${globalConciseConfigured() ? 'enabled' : 'not enabled'}`);
155
+ console.log(` This project (./AGENTS.md): ${conciseInstructionsConfigured(dir) ? 'enabled' : 'not enabled'}`);
156
+ console.log('');
144
157
  break;
145
158
  }
146
- console.error(` Unknown: troxy concise ${sub}. Try: status, enable, disable\n`);
159
+ console.error(` Unknown: troxy concise ${sub}. Try: status, enable, disable (add --project to scope to this directory)\n`);
147
160
  process.exit(1);
148
161
  }
149
162
 
@@ -214,7 +227,7 @@ switch (command) {
214
227
 
215
228
  // Re-point every real consumer (MCP client configs, the background service) at the new
216
229
  // key BEFORE revoking the old one — otherwise every consumer, which holds the key via
217
- // ENV rather than config.json, keeps presenting the now-revoked key and payment control
230
+ // ENV rather than config.json, keeps presenting the now-revoked key and policy enforcement
218
231
  // goes down until the user thinks to re-run init. (H6)
219
232
  await reprovisionKeyConsumers(newKey, existing.agentName || 'my-agent');
220
233
 
@@ -734,7 +747,7 @@ switch (command) {
734
747
  Everything else requires: troxy login
735
748
 
736
749
  MCP
737
- troxy pause Pause this MCP (blocks all payments)
750
+ troxy pause Pause this MCP (blocks all checkpoints)
738
751
  troxy resume Resume this MCP
739
752
  troxy mcps list All MCPs in your account
740
753
  troxy mcps rename --name "x" Rename this machine's MCP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.29.17",
3
+ "version": "1.29.19",
4
4
  "description": "A secure control layer for AI agents: policies across payments, messages, logins, destructive actions, model usage, and secrets, all enforceable from the CLI",
5
5
  "homepage": "https://troxy.io",
6
6
  "bugs": {
@@ -11,6 +11,7 @@
11
11
  "troxy": "bin/troxy.js"
12
12
  },
13
13
  "scripts": {
14
+ "preinstall": "node scripts/check-node.js",
14
15
  "test": "node --test src/tests/*.test.js"
15
16
  },
16
17
  "dependencies": {
@@ -30,7 +31,8 @@
30
31
  },
31
32
  "files": [
32
33
  "bin/",
33
- "src/"
34
+ "src/",
35
+ "scripts/check-node.js"
34
36
  ],
35
37
  "keywords": [
36
38
  "mcp",
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ const major = parseInt(process.versions.node.split('.')[0], 10);
3
+ if (major < 18) {
4
+ console.error(`\ntroxy-cli requires Node >=18, you have ${process.version}.\nInstall a newer Node (e.g. via nvm: https://github.com/nvm-sh/nvm) and retry.\n`);
5
+ process.exit(1);
6
+ }
package/src/init.js CHANGED
@@ -264,13 +264,18 @@ async function printHostedAgentSetup(key) {
264
264
  console.log(' Then follow it before you pay, email, log in, or delete anything.\n');
265
265
  }
266
266
 
267
- // Idempotent, and never clobbers instructions the user wrote themselves: the
268
- // Troxy section lives between markers, so re-running init (or rotate-key, which
269
- // changes the key inside the block) replaces only that span. Each marker pair
270
- // is independent - writing/removing one never touches another's span or
271
- // anything the user wrote outside all of them.
272
- export function writeAgentsFile(dir, text, startMarker = AGENTS_START, endMarker = AGENTS_END) {
273
- const file = path.join(dir, 'AGENTS.md');
267
+ // Claude Code's real, documented global instructions file - not the same
268
+ // thing as a bare ~/AGENTS.md, which (per our own testing) a project working
269
+ // directory does not walk up to. This is the one path that actually applies
270
+ // everywhere, so it's the file the "all projects" option below writes to.
271
+ function globalClaudeMdPath() {
272
+ return path.join(os.homedir(), '.claude', 'CLAUDE.md');
273
+ }
274
+
275
+ // Same merge algorithm as writeAgentsFile (marker-delimited, never clobbers
276
+ // content outside its own span), generalized to any file path so both the
277
+ // per-project AGENTS.md and the global CLAUDE.md can share it.
278
+ function writeMarkedBlock(file, text, startMarker, endMarker) {
274
279
  const block = `${startMarker}\n${text}\n${endMarker}`;
275
280
  let existing = '';
276
281
  try { existing = fs.readFileSync(file, 'utf8'); } catch {}
@@ -285,55 +290,94 @@ export function writeAgentsFile(dir, text, startMarker = AGENTS_START, endMarker
285
290
  } else {
286
291
  next = block + '\n';
287
292
  }
293
+ fs.mkdirSync(path.dirname(file), { recursive: true });
288
294
  fs.writeFileSync(file, next);
289
295
  return file;
290
296
  }
291
297
 
292
- export function conciseInstructionsConfigured(dir) {
298
+ function removeMarkedBlock(file, startMarker, endMarker) {
299
+ let existing;
300
+ try { existing = fs.readFileSync(file, 'utf8'); } catch { return false; }
301
+ if (!existing.includes(startMarker) || !existing.includes(endMarker)) return false;
302
+ const head = existing.slice(0, existing.indexOf(startMarker));
303
+ const tail = existing.slice(existing.indexOf(endMarker) + endMarker.length);
304
+ fs.writeFileSync(file, (head + tail).replace(/\n{3,}/g, '\n\n'));
305
+ return true;
306
+ }
307
+
308
+ function hasMarkedBlock(file, startMarker, endMarker) {
293
309
  try {
294
- const text = fs.readFileSync(path.join(dir, 'AGENTS.md'), 'utf8');
295
- return text.includes(CONCISE_START) && text.includes(CONCISE_END);
310
+ const text = fs.readFileSync(file, 'utf8');
311
+ return text.includes(startMarker) && text.includes(endMarker);
296
312
  } catch { return false; }
297
313
  }
298
314
 
315
+ // Idempotent, and never clobbers instructions the user wrote themselves: the
316
+ // Troxy section lives between markers, so re-running init (or rotate-key,
317
+ // which changes the key inside the block) replaces only that span. Each
318
+ // marker pair is independent - writing/removing one never touches another's
319
+ // span or anything the user wrote outside all of them.
320
+ export function writeAgentsFile(dir, text, startMarker = AGENTS_START, endMarker = AGENTS_END) {
321
+ return writeMarkedBlock(path.join(dir, 'AGENTS.md'), text, startMarker, endMarker);
322
+ }
323
+
324
+ export function conciseInstructionsConfigured(dir) {
325
+ return hasMarkedBlock(path.join(dir, 'AGENTS.md'), CONCISE_START, CONCISE_END);
326
+ }
327
+
299
328
  export function removeConciseInstructions(dir) {
300
- const file = path.join(dir, 'AGENTS.md');
301
- let existing;
302
- try { existing = fs.readFileSync(file, 'utf8'); } catch { return false; }
303
- if (!existing.includes(CONCISE_START) || !existing.includes(CONCISE_END)) return false;
304
- const head = existing.slice(0, existing.indexOf(CONCISE_START));
305
- const tail = existing.slice(existing.indexOf(CONCISE_END) + CONCISE_END.length);
306
- fs.writeFileSync(file, (head + tail).replace(/\n{3,}/g, '\n\n'));
307
- return true;
329
+ return removeMarkedBlock(path.join(dir, 'AGENTS.md'), CONCISE_START, CONCISE_END);
330
+ }
331
+
332
+ export function globalConciseConfigured() {
333
+ return hasMarkedBlock(globalClaudeMdPath(), CONCISE_START, CONCISE_END);
308
334
  }
309
335
 
310
- // Mirrors maybeEnableTroxyInterception's opt-in shape exactly: explain what
311
- // and the real trade-off before asking, respect a --concise flag for
312
- // non-interactive runs (no --no-concise - this parser doesn't auto-negate,
313
- // see --no-confirm, and --proxy has no negation flag either; omit the flag
314
- // to get the default), never prompt if stdin isn't a TTY, and skip asking
315
- // again once it's already configured.
336
+ export function removeGlobalConciseInstructions() {
337
+ return removeMarkedBlock(globalClaudeMdPath(), CONCISE_START, CONCISE_END);
338
+ }
339
+
340
+ export function enableGlobalConciseInstructions() {
341
+ return writeMarkedBlock(globalClaudeMdPath(), CONCISE_INSTRUCTIONS, CONCISE_START, CONCISE_END);
342
+ }
343
+
344
+ // conciseOptIn is one of: 'global', 'project', false (explicitly skip), or
345
+ // null (ask interactively). Global first, and recommended by default in the
346
+ // prompt: it's the one location Claude Code always reads regardless of which
347
+ // directory you're in, so it structurally can't land in the wrong place the
348
+ // way the project-scoped option did on its first real run. Never claims a
349
+ // specific percentage in the copy below - the one number we measured for
350
+ // this came from a flawed test (subagents don't reliably load these files
351
+ // the way a real session does) and shouldn't be repeated as fact.
316
352
  export async function maybeEnableConciseInstructions(conciseOptIn, dir) {
317
- if (conciseInstructionsConfigured(dir)) return;
353
+ if (globalConciseConfigured() || conciseInstructionsConfigured(dir)) return;
318
354
 
319
- let enable;
320
- if (conciseOptIn !== null) {
321
- enable = conciseOptIn;
355
+ let choice;
356
+ if (conciseOptIn === 'global' || conciseOptIn === 'project' || conciseOptIn === false) {
357
+ choice = conciseOptIn;
322
358
  } else if (!process.stdin.isTTY) {
323
- enable = false;
359
+ choice = false;
324
360
  } else {
325
- console.log('\n Add token-efficiency instructions to this project\'s AGENTS.md?');
326
- console.log(' Makes Claude Code\'s responses more concise - in one real test, ~30%');
327
- console.log(' shorter output on the same task. Trade-off: less verbose can mean');
328
- console.log(' less detail. Only touches this project.');
329
- console.log(' Remove any time with: troxy concise disable');
330
- const ans = await prompt(' Enable? (y/N): ');
331
- enable = /^y(es)?$/i.test(ans);
361
+ console.log('\n Add token-efficiency instructions to make Claude Code\'s responses');
362
+ console.log(' more concise? Trade-off: less verbose can mean less detail.');
363
+ console.log('');
364
+ console.log(' [G] All projects (recommended) - writes once to ~/.claude/CLAUDE.md');
365
+ console.log(' [P] This project only - writes to ./AGENTS.md');
366
+ console.log(' [N] Skip');
367
+ console.log('');
368
+ console.log(' Remove any time with: troxy concise disable (add --project for just this one)');
369
+ const ans = (await prompt(' Choice (G/p/n): ')).toLowerCase();
370
+ choice = ans === 'p' ? 'project' : ans === 'n' ? false : 'global';
332
371
  }
333
372
 
334
- if (!enable) return;
335
- const file = writeAgentsFile(dir, CONCISE_INSTRUCTIONS, CONCISE_START, CONCISE_END);
336
- console.log(` Added token-efficiency instructions to ${file}`);
373
+ if (choice === false) return;
374
+ if (choice === 'global') {
375
+ const file = enableGlobalConciseInstructions();
376
+ console.log(` Added token-efficiency instructions to ${file} (all projects)`);
377
+ } else {
378
+ const file = writeAgentsFile(dir, CONCISE_INSTRUCTIONS, CONCISE_START, CONCISE_END);
379
+ console.log(` Added token-efficiency instructions to ${file} (this project)`);
380
+ }
337
381
  }
338
382
 
339
383
  export function prompt(question) {
@@ -394,10 +438,16 @@ export async function runInit({ key, name, proxy, concise } = {}) {
394
438
  // undefined (flag never passed) -> null, so reprovisionKeyConsumers can
395
439
  // tell "not decided, ask interactively" apart from an explicit --proxy.
396
440
  const proxyOptIn = proxy === undefined ? null : Boolean(proxy);
397
- // Same null-means-ask-interactively shape as proxyOptIn. --concise is
398
- // presence-only, like --proxy - neither has a negation flag in this CLI;
399
- // omit it to get the default (interactive ask, or off when non-interactive).
400
- const conciseOptIn = concise === undefined ? null : Boolean(concise);
441
+ // Same null-means-ask-interactively shape as proxyOptIn, but three-valued:
442
+ // --concise alone (bare flag, parses as boolean true) means the
443
+ // recommended default, 'global'; --concise project or --concise global
444
+ // pick explicitly; --concise false skips. Omit the flag entirely for the
445
+ // default (interactive ask, or off when non-interactive).
446
+ const conciseOptIn =
447
+ concise === undefined ? null :
448
+ concise === true ? 'global' :
449
+ concise === 'false' ? false :
450
+ concise;
401
451
  if (!key || !key.startsWith('txy-')) {
402
452
  console.error('\n Error: --key is required and must start with txy-');
403
453
  console.error(' Usage: npx troxy-cli init --key txy-...\n');