claude-token-saver 2.13.3 β 2.14.0
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/cli.js +75 -16
- package/package.json +1 -1
- package/src/harness.js +16 -7
package/bin/cli.js
CHANGED
|
@@ -488,10 +488,35 @@ async function main() {
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
if (sub === 'promote') {
|
|
491
|
-
|
|
491
|
+
// Parse scope flags before stripping. Accepts: --global, --project,
|
|
492
|
+
// --scope=global|project, --scope global|project
|
|
493
|
+
const promoteArgs = args.slice(2);
|
|
494
|
+
let scope = null;
|
|
495
|
+
const scopeFlags = new Set();
|
|
496
|
+
for (let i = 0; i < promoteArgs.length; i++) {
|
|
497
|
+
const a = promoteArgs[i];
|
|
498
|
+
if (a === '--global') { scope = 'global'; scopeFlags.add(i); }
|
|
499
|
+
else if (a === '--project') { scope = 'project'; scopeFlags.add(i); }
|
|
500
|
+
else if (a === '--scope' && promoteArgs[i + 1]) {
|
|
501
|
+
const v = promoteArgs[i + 1];
|
|
502
|
+
if (v !== 'global' && v !== 'project') {
|
|
503
|
+
console.error(`Invalid --scope value: ${v} (expected "global" or "project")`);
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
scope = v; scopeFlags.add(i); scopeFlags.add(i + 1); i++;
|
|
507
|
+
} else if (a.startsWith('--scope=')) {
|
|
508
|
+
const v = a.slice('--scope='.length);
|
|
509
|
+
if (v !== 'global' && v !== 'project') {
|
|
510
|
+
console.error(`Invalid --scope value: ${v} (expected "global" or "project")`);
|
|
511
|
+
process.exit(1);
|
|
512
|
+
}
|
|
513
|
+
scope = v; scopeFlags.add(i);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const raw = promoteArgs.filter((_, i) => !scopeFlags.has(i)).join(' ').trim();
|
|
492
517
|
if (!raw) {
|
|
493
|
-
console.error('Usage: claude-token-saver harness promote <N> # from statusline π
·β ratchet? #N');
|
|
494
|
-
console.error(' or: claude-token-saver harness promote "<rule text>"');
|
|
518
|
+
console.error('Usage: claude-token-saver harness promote [--global|--project] <N> # from statusline π
·β ratchet? #N');
|
|
519
|
+
console.error(' or: claude-token-saver harness promote [--global|--project] "<rule text>"');
|
|
495
520
|
process.exit(1);
|
|
496
521
|
}
|
|
497
522
|
let rule = raw;
|
|
@@ -515,8 +540,33 @@ async function main() {
|
|
|
515
540
|
}
|
|
516
541
|
rule = `λ°λ³΅ κ°μ§ Γ${cand.count}: ${cand.pattern} β TODO: μμΈΒ·μλ°©μ±
ν μ€λ‘`;
|
|
517
542
|
}
|
|
518
|
-
|
|
519
|
-
|
|
543
|
+
// Scope resolution: explicit flag wins. Otherwise prompt interactively
|
|
544
|
+
// when running on a TTY; in non-TTY (CI/scripts) require an explicit
|
|
545
|
+
// flag so the choice is never silently made for the caller.
|
|
546
|
+
if (!scope) {
|
|
547
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
548
|
+
const readline = await import('node:readline');
|
|
549
|
+
const { homedir: hd } = await import('node:os');
|
|
550
|
+
const { findProjectRoot: fpr } = await import('../src/harness.js');
|
|
551
|
+
const projPath = `${fpr()}/.claude/ratchet.md`;
|
|
552
|
+
const globPath = `${hd()}/.claude/ratchet.md`;
|
|
553
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
554
|
+
const ask = (q) => new Promise((res) => rl.question(q, res));
|
|
555
|
+
console.log('Where should this rule live?');
|
|
556
|
+
console.log(` [1] project (${projPath})`);
|
|
557
|
+
console.log(` [2] global (${globPath})`);
|
|
558
|
+
const ans = (await ask('Choose [1/2] (default 1): ')).trim();
|
|
559
|
+
rl.close();
|
|
560
|
+
scope = (ans === '2' || ans.toLowerCase() === 'global' || ans.toLowerCase() === 'g')
|
|
561
|
+
? 'global' : 'project';
|
|
562
|
+
} else {
|
|
563
|
+
console.error('Scope required in non-interactive mode.');
|
|
564
|
+
console.error('Pass --project or --global (or --scope=project|global).');
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const r = harnessPromote(rule, { scope });
|
|
569
|
+
console.log(`Appended to ${r.path} [${r.scope}]:`);
|
|
520
570
|
console.log(` - ${rule}`);
|
|
521
571
|
if (/^\d+$/.test(raw)) {
|
|
522
572
|
console.log('\nπ ratchet.mdλ₯Ό μ΄μ΄ TODO λΆλΆμ μ€μ λ£°λ‘ λ€λ¬μ΄μ£ΌμΈμ.');
|
|
@@ -570,21 +620,30 @@ async function main() {
|
|
|
570
620
|
}
|
|
571
621
|
|
|
572
622
|
if (sub === 'list' || sub === 'ls') {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
623
|
+
const wantGlobal = hasFlag('--global');
|
|
624
|
+
const wantProject = hasFlag('--project') || !wantGlobal;
|
|
625
|
+
const print = (scope) => {
|
|
626
|
+
const { path, rules } = harnessListRules({ scope });
|
|
627
|
+
if (!rules.length) {
|
|
628
|
+
console.log(`No ratchet rules in ${path} [${scope}]`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
console.log(`π Ratchet rules [${scope}] β ${path}\n`);
|
|
632
|
+
for (const r of rules) console.log(` #${r.index} ${r.text}`);
|
|
633
|
+
console.log('');
|
|
634
|
+
};
|
|
635
|
+
if (wantProject) print('project');
|
|
636
|
+
if (wantGlobal) print('global');
|
|
637
|
+
console.log('Remove with: claude-token-saver harness rm [--global|--project] <N>');
|
|
581
638
|
return;
|
|
582
639
|
}
|
|
583
640
|
|
|
584
641
|
if (sub === 'rm') {
|
|
585
|
-
const
|
|
642
|
+
const rmScope = hasFlag('--global') ? 'global' : 'project';
|
|
643
|
+
const rmArgs = args.slice(2).filter((a) => a !== '--global' && a !== '--project');
|
|
644
|
+
const raw = (rmArgs[0] || '').trim();
|
|
586
645
|
if (!/^\d+$/.test(raw)) {
|
|
587
|
-
console.error('Usage: claude-token-saver harness rm <N> # N from `harness list`');
|
|
646
|
+
console.error('Usage: claude-token-saver harness rm [--global|--project] <N> # N from `harness list`');
|
|
588
647
|
process.exit(1);
|
|
589
648
|
}
|
|
590
649
|
const n = parseInt(raw, 10);
|
|
@@ -598,7 +657,7 @@ async function main() {
|
|
|
598
657
|
console.log(' - λ£°μ΄ λ무 μ’μμ κ±°μ λ°λ μ λλ? β κ·Έλ₯ λκΈ° (λΉμ© 0)');
|
|
599
658
|
console.log(' - μ λ§ μλͺ»λ λ£°μ΄λΌ νμ ? β κ·Έλλ§ μμ ');
|
|
600
659
|
console.log('');
|
|
601
|
-
const r = harnessRmRule(n);
|
|
660
|
+
const r = harnessRmRule(n, { scope: rmScope });
|
|
602
661
|
if (!r.ok) {
|
|
603
662
|
console.error(`β ${r.error}`);
|
|
604
663
|
if (r.rules) {
|
package/package.json
CHANGED
package/src/harness.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
|
|
11
11
|
import { join, resolve, dirname } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
12
13
|
import { createRequire } from 'node:module';
|
|
13
14
|
import {
|
|
14
15
|
HARNESS_SECTIONS,
|
|
@@ -58,6 +59,14 @@ function ratchetMdPath(root) {
|
|
|
58
59
|
return join(root, '.claude', 'ratchet.md');
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function globalRatchetMdPath() {
|
|
63
|
+
return join(homedir(), '.claude', 'ratchet.md');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveRatchetPath(scope, root) {
|
|
67
|
+
return scope === 'global' ? globalRatchetMdPath() : ratchetMdPath(root);
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
/**
|
|
62
71
|
* Count how many of the 5 harness sections appear in the project's CLAUDE.md.
|
|
63
72
|
* Returns { configured, total, missing, hasBlock }. Cheap enough to call from
|
|
@@ -217,8 +226,8 @@ export function harnessUninit({ root = findProjectRoot(), purgeRatchet = false }
|
|
|
217
226
|
* harness promote β append a one-line rule to .claude/ratchet.md.
|
|
218
227
|
* Creates the file from the initial template if missing.
|
|
219
228
|
*/
|
|
220
|
-
export function harnessPromote(ruleText, { root = findProjectRoot() } = {}) {
|
|
221
|
-
const rmPath =
|
|
229
|
+
export function harnessPromote(ruleText, { root = findProjectRoot(), scope = 'project' } = {}) {
|
|
230
|
+
const rmPath = resolveRatchetPath(scope, root);
|
|
222
231
|
let existing = '';
|
|
223
232
|
if (existsSync(rmPath)) {
|
|
224
233
|
existing = readFileSync(rmPath, 'utf8');
|
|
@@ -228,15 +237,15 @@ export function harnessPromote(ruleText, { root = findProjectRoot() } = {}) {
|
|
|
228
237
|
}
|
|
229
238
|
const next = appendRatchetRule(existing, ruleText);
|
|
230
239
|
writeFileSync(rmPath, next);
|
|
231
|
-
return { path: rmPath, root };
|
|
240
|
+
return { path: rmPath, root, scope };
|
|
232
241
|
}
|
|
233
242
|
|
|
234
243
|
/**
|
|
235
244
|
* harness list β return numbered ratchet rules from .claude/ratchet.md.
|
|
236
245
|
* Numbering is 1-based and matches `harness rm <N>`.
|
|
237
246
|
*/
|
|
238
|
-
export function harnessListRules({ root = findProjectRoot() } = {}) {
|
|
239
|
-
const rmPath =
|
|
247
|
+
export function harnessListRules({ root = findProjectRoot(), scope = 'project' } = {}) {
|
|
248
|
+
const rmPath = resolveRatchetPath(scope, root);
|
|
240
249
|
if (!existsSync(rmPath)) return { path: rmPath, rules: [] };
|
|
241
250
|
const lines = readFileSync(rmPath, 'utf8').split('\n');
|
|
242
251
|
const rules = [];
|
|
@@ -260,8 +269,8 @@ export function harnessListRules({ root = findProjectRoot() } = {}) {
|
|
|
260
269
|
* value is one-way accumulation; deleting should feel deliberate. The CLI
|
|
261
270
|
* surfaces a "narrow the condition instead" reminder around this call.
|
|
262
271
|
*/
|
|
263
|
-
export function harnessRmRule(n, { root = findProjectRoot() } = {}) {
|
|
264
|
-
const { path: rmPath, rules } = harnessListRules({ root });
|
|
272
|
+
export function harnessRmRule(n, { root = findProjectRoot(), scope = 'project' } = {}) {
|
|
273
|
+
const { path: rmPath, rules } = harnessListRules({ root, scope });
|
|
265
274
|
if (!existsSync(rmPath)) {
|
|
266
275
|
return { ok: false, error: `ratchet.md not found at ${rmPath}` };
|
|
267
276
|
}
|