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 +20 -0
- package/package.json +1 -1
- package/src/init.js +86 -9
- package/src/tests/tool_detect.test.js +1 -1
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.
|
|
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
|
-
|
|
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 = `${
|
|
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(
|
|
254
|
-
const head = existing.slice(0, existing.indexOf(
|
|
255
|
-
const tail = existing.slice(existing.indexOf(
|
|
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');
|