troxy-cli 1.29.16 → 1.29.18

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,6 +127,39 @@ switch (command) {
127
127
  process.exit(1);
128
128
  }
129
129
 
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.
134
+ case 'concise': {
135
+ const {
136
+ maybeEnableConciseInstructions, removeConciseInstructions, conciseInstructionsConfigured,
137
+ enableGlobalConciseInstructions, removeGlobalConciseInstructions, globalConciseConfigured,
138
+ } = await import('../src/init.js');
139
+ const dir = process.cwd();
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
+ }
147
+ if (sub === 'disable') {
148
+ const removed = projectOnly ? removeConciseInstructions(dir) : removeGlobalConciseInstructions();
149
+ console.log(removed ? '\n Removed ✓\n' : `\n Not enabled ${projectOnly ? 'in this project' : 'globally'}.\n`);
150
+ break;
151
+ }
152
+ if (sub === 'status' || !sub) {
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('');
157
+ break;
158
+ }
159
+ console.error(` Unknown: troxy concise ${sub}. Try: status, enable, disable (add --project to scope to this directory)\n`);
160
+ process.exit(1);
161
+ }
162
+
130
163
  // ── Auth ──────────────────────────────────────────────────────
131
164
  case 'login':
132
165
  await runLogin(flags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.29.16",
3
+ "version": "1.29.18",
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": {
package/src/init.js CHANGED
@@ -81,7 +81,7 @@ export async function refreshHostedInstructions(key) {
81
81
  return true;
82
82
  }
83
83
 
84
- export async function reprovisionKeyConsumers(key, agentName, proxyOptIn = null) {
84
+ export async function reprovisionKeyConsumers(key, agentName, proxyOptIn = null, conciseOptIn = null) {
85
85
  const platform = process.platform;
86
86
  const { detected, hasOpenClaw, hasClaude, none } = detectMcpClients();
87
87
 
@@ -163,6 +163,13 @@ export async function reprovisionKeyConsumers(key, agentName, proxyOptIn = null)
163
163
  console.log('\n Restart your MCP client to activate Troxy.');
164
164
  }
165
165
 
166
+ // Own opt-in, outside the client-detection if/else above: AGENTS.md is a
167
+ // cross-tool convention, not tied to which specific MCP client got patched.
168
+ // Idempotent (conciseInstructionsConfigured short-circuits), so it's safe
169
+ // to run on every reprovision - fresh init AND `troxy update`/rotate-key -
170
+ // not just the first-ever connect, without re-prompting once it's set.
171
+ await maybeEnableConciseInstructions(conciseOptIn, process.cwd());
172
+
166
173
  console.log('\n Setting up background service...');
167
174
  const viaEphemeralNpx = ranViaEphemeralNpx();
168
175
  try {
@@ -195,6 +202,23 @@ export async function reprovisionKeyConsumers(key, agentName, proxyOptIn = null)
195
202
  const AGENTS_START = '<!-- troxy:start -->';
196
203
  const AGENTS_END = '<!-- troxy:end -->';
197
204
 
205
+ // A separate marker pair, not a section inside AGENTS_START/END: the security
206
+ // checkpoint block above is load-bearing (Troxy doesn't function without it)
207
+ // and always written. Concise instructions are a style preference the user
208
+ // opts into separately - same file, independently addable/removable without
209
+ // touching or re-flowing the checkpoint block.
210
+ const CONCISE_START = '<!-- troxy:concise:start -->';
211
+ const CONCISE_END = '<!-- troxy:concise:end -->';
212
+
213
+ const CONCISE_INSTRUCTIONS = `## Style (via Troxy)
214
+
215
+ - Read existing files before writing. Don't re-read unless changed.
216
+ - Thorough in reasoning, concise in output.
217
+ - Skip files over 100KB unless required.
218
+ - No sycophantic openers or closing fluff.
219
+ - No emojis or em-dashes.
220
+ - Do not guess APIs, versions, flags, commit SHAs, or package names. Verify by reading code or docs before asserting.`;
221
+
198
222
  // The endpoint deliberately does not echo the caller's key back, so the live
199
223
  // key is substituted here, on the machine that already has it. Split/join
200
224
  // rather than a regex because a key is user-controlled input and would other-
@@ -240,29 +264,122 @@ async function printHostedAgentSetup(key) {
240
264
  console.log(' Then follow it before you pay, email, log in, or delete anything.\n');
241
265
  }
242
266
 
243
- // Idempotent, and never clobbers instructions the user wrote themselves: the
244
- // Troxy section lives between markers, so re-running init (or rotate-key, which
245
- // changes the key inside the block) replaces only that span.
246
- export function writeAgentsFile(dir, text) {
247
- const file = path.join(dir, 'AGENTS.md');
248
- const block = `${AGENTS_START}\n${text}\n${AGENTS_END}`;
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) {
279
+ const block = `${startMarker}\n${text}\n${endMarker}`;
249
280
  let existing = '';
250
281
  try { existing = fs.readFileSync(file, 'utf8'); } catch {}
251
282
 
252
283
  let next;
253
- if (existing.includes(AGENTS_START) && existing.includes(AGENTS_END)) {
254
- const head = existing.slice(0, existing.indexOf(AGENTS_START));
255
- const tail = existing.slice(existing.indexOf(AGENTS_END) + AGENTS_END.length);
284
+ if (existing.includes(startMarker) && existing.includes(endMarker)) {
285
+ const head = existing.slice(0, existing.indexOf(startMarker));
286
+ const tail = existing.slice(existing.indexOf(endMarker) + endMarker.length);
256
287
  next = head + block + tail;
257
288
  } else if (existing.trim()) {
258
289
  next = existing.replace(/\s*$/, '') + '\n\n' + block + '\n';
259
290
  } else {
260
291
  next = block + '\n';
261
292
  }
293
+ fs.mkdirSync(path.dirname(file), { recursive: true });
262
294
  fs.writeFileSync(file, next);
263
295
  return file;
264
296
  }
265
297
 
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) {
309
+ try {
310
+ const text = fs.readFileSync(file, 'utf8');
311
+ return text.includes(startMarker) && text.includes(endMarker);
312
+ } catch { return false; }
313
+ }
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
+
328
+ export function removeConciseInstructions(dir) {
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);
334
+ }
335
+
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.
352
+ export async function maybeEnableConciseInstructions(conciseOptIn, dir) {
353
+ if (globalConciseConfigured() || conciseInstructionsConfigured(dir)) return;
354
+
355
+ let choice;
356
+ if (conciseOptIn === 'global' || conciseOptIn === 'project' || conciseOptIn === false) {
357
+ choice = conciseOptIn;
358
+ } else if (!process.stdin.isTTY) {
359
+ choice = false;
360
+ } else {
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';
371
+ }
372
+
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
+ }
381
+ }
382
+
266
383
  export function prompt(question) {
267
384
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
268
385
  return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
@@ -317,10 +434,20 @@ const MCP_CLIENTS = [
317
434
  },
318
435
  ];
319
436
 
320
- export async function runInit({ key, name, proxy } = {}) {
437
+ export async function runInit({ key, name, proxy, concise } = {}) {
321
438
  // undefined (flag never passed) -> null, so reprovisionKeyConsumers can
322
439
  // tell "not decided, ask interactively" apart from an explicit --proxy.
323
440
  const proxyOptIn = proxy === undefined ? null : Boolean(proxy);
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;
324
451
  if (!key || !key.startsWith('txy-')) {
325
452
  console.error('\n Error: --key is required and must start with txy-');
326
453
  console.error(' Usage: npx troxy-cli init --key txy-...\n');
@@ -381,7 +508,7 @@ export async function runInit({ key, name, proxy } = {}) {
381
508
  // Non-fatal: the daemon's own heartbeat will retry this later.
382
509
  }
383
510
 
384
- await reprovisionKeyConsumers(key, agentName, proxyOptIn);
511
+ await reprovisionKeyConsumers(key, agentName, proxyOptIn, conciseOptIn);
385
512
 
386
513
  // Scan for local AI coding tools so the dashboard's "My AI tools" view has
387
514
  // something to show. Non-fatal - a scan failure never breaks setup.
@@ -269,7 +269,7 @@ describe('wiring: api.js / bin/troxy.js / init.js', () => {
269
269
  });
270
270
 
271
271
  it('runInit calls tool detection after reprovisionKeyConsumers, wrapped so a scan failure never blocks init', () => {
272
- const reprovisionIdx = initSrc.indexOf('reprovisionKeyConsumers(key, agentName, proxyOptIn)');
272
+ const reprovisionIdx = initSrc.indexOf('reprovisionKeyConsumers(key, agentName, proxyOptIn, conciseOptIn)');
273
273
  const toolIdx = initSrc.indexOf('runToolDetection');
274
274
  assert.ok(reprovisionIdx !== -1, 'reprovisionKeyConsumers call not found in init.js');
275
275
  assert.ok(toolIdx !== -1, 'runToolDetection not wired into runInit');