troxy-cli 1.29.16 → 1.29.17

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,26 @@ switch (command) {
127
127
  process.exit(1);
128
128
  }
129
129
 
130
+ // ── Token-efficiency instructions in AGENTS.md (opt-in at init) ────────
131
+ case 'concise': {
132
+ const { maybeEnableConciseInstructions, removeConciseInstructions, conciseInstructionsConfigured } = await import('../src/init.js');
133
+ const dir = process.cwd();
134
+ if (sub === 'enable') { await maybeEnableConciseInstructions(true, dir); break; }
135
+ if (sub === 'disable') {
136
+ const removed = removeConciseInstructions(dir);
137
+ console.log(removed ? '\n Removed ✓\n' : '\n Not enabled in this project.\n');
138
+ break;
139
+ }
140
+ 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');
144
+ break;
145
+ }
146
+ console.error(` Unknown: troxy concise ${sub}. Try: status, enable, disable\n`);
147
+ process.exit(1);
148
+ }
149
+
130
150
  // ── Auth ──────────────────────────────────────────────────────
131
151
  case 'login':
132
152
  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.17",
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-
@@ -242,17 +266,19 @@ async function printHostedAgentSetup(key) {
242
266
 
243
267
  // Idempotent, and never clobbers instructions the user wrote themselves: the
244
268
  // 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) {
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) {
247
273
  const file = path.join(dir, 'AGENTS.md');
248
- const block = `${AGENTS_START}\n${text}\n${AGENTS_END}`;
274
+ const block = `${startMarker}\n${text}\n${endMarker}`;
249
275
  let existing = '';
250
276
  try { existing = fs.readFileSync(file, 'utf8'); } catch {}
251
277
 
252
278
  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);
279
+ if (existing.includes(startMarker) && existing.includes(endMarker)) {
280
+ const head = existing.slice(0, existing.indexOf(startMarker));
281
+ const tail = existing.slice(existing.indexOf(endMarker) + endMarker.length);
256
282
  next = head + block + tail;
257
283
  } else if (existing.trim()) {
258
284
  next = existing.replace(/\s*$/, '') + '\n\n' + block + '\n';
@@ -263,6 +289,53 @@ export function writeAgentsFile(dir, text) {
263
289
  return file;
264
290
  }
265
291
 
292
+ export function conciseInstructionsConfigured(dir) {
293
+ try {
294
+ const text = fs.readFileSync(path.join(dir, 'AGENTS.md'), 'utf8');
295
+ return text.includes(CONCISE_START) && text.includes(CONCISE_END);
296
+ } catch { return false; }
297
+ }
298
+
299
+ 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;
308
+ }
309
+
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.
316
+ export async function maybeEnableConciseInstructions(conciseOptIn, dir) {
317
+ if (conciseInstructionsConfigured(dir)) return;
318
+
319
+ let enable;
320
+ if (conciseOptIn !== null) {
321
+ enable = conciseOptIn;
322
+ } else if (!process.stdin.isTTY) {
323
+ enable = false;
324
+ } 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);
332
+ }
333
+
334
+ if (!enable) return;
335
+ const file = writeAgentsFile(dir, CONCISE_INSTRUCTIONS, CONCISE_START, CONCISE_END);
336
+ console.log(` Added token-efficiency instructions to ${file}`);
337
+ }
338
+
266
339
  export function prompt(question) {
267
340
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
268
341
  return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
@@ -317,10 +390,14 @@ const MCP_CLIENTS = [
317
390
  },
318
391
  ];
319
392
 
320
- export async function runInit({ key, name, proxy } = {}) {
393
+ export async function runInit({ key, name, proxy, concise } = {}) {
321
394
  // undefined (flag never passed) -> null, so reprovisionKeyConsumers can
322
395
  // tell "not decided, ask interactively" apart from an explicit --proxy.
323
396
  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);
324
401
  if (!key || !key.startsWith('txy-')) {
325
402
  console.error('\n Error: --key is required and must start with txy-');
326
403
  console.error(' Usage: npx troxy-cli init --key txy-...\n');
@@ -381,7 +458,7 @@ export async function runInit({ key, name, proxy } = {}) {
381
458
  // Non-fatal: the daemon's own heartbeat will retry this later.
382
459
  }
383
460
 
384
- await reprovisionKeyConsumers(key, agentName, proxyOptIn);
461
+ await reprovisionKeyConsumers(key, agentName, proxyOptIn, conciseOptIn);
385
462
 
386
463
  // Scan for local AI coding tools so the dashboard's "My AI tools" view has
387
464
  // 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');