ctxman 3.0.0 → 3.1.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/.ctxman/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +380 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -13
- package/bin/cm-audit.js +124 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +683 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +675 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +113 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +294 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +52 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/sources/FileDataSource.js +6 -6
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +243 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +255 -0
- package/lib/wizards/project-detector.js +571 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Token Counter
|
|
3
|
+
* Exact Claude token counts from Anthropic's count_tokens endpoint.
|
|
4
|
+
*
|
|
5
|
+
* tiktoken's encodings are OpenAI's. Claude models use their own tokenizer, which
|
|
6
|
+
* produces more tokens for the same text (especially for code), and Anthropic does not
|
|
7
|
+
* publish an offline tokenizer for current models: the API is the only exact source.
|
|
8
|
+
* Counting generates nothing and is free of charge, but it sends the text to Anthropic,
|
|
9
|
+
* so callers must only use it when the user asked for it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import TokenUtils from './token-utils.js';
|
|
13
|
+
|
|
14
|
+
// Texts are grouped into requests of about this many cl100k_base tokens. Claude counts
|
|
15
|
+
// a little more than that, which still leaves room in the smallest (200K) window.
|
|
16
|
+
const DEFAULT_BATCH_TOKENS = 120000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Split a text that is larger than one request into line-aligned pieces
|
|
20
|
+
* @returns {{text: string, tokens: number}[]}
|
|
21
|
+
*/
|
|
22
|
+
function splitByLines(text, filePath, maxTokens) {
|
|
23
|
+
const tokens = TokenUtils.calculate(text, filePath);
|
|
24
|
+
if (tokens <= maxTokens) return [{ text, tokens }];
|
|
25
|
+
|
|
26
|
+
const pieces = [];
|
|
27
|
+
let lines = [];
|
|
28
|
+
let pieceTokens = 0;
|
|
29
|
+
for (const line of text.split('\n')) {
|
|
30
|
+
const lineTokens = TokenUtils.calculate(line + '\n', filePath);
|
|
31
|
+
if (lines.length > 0 && pieceTokens + lineTokens > maxTokens) {
|
|
32
|
+
pieces.push({ text: lines.join('\n'), tokens: pieceTokens });
|
|
33
|
+
lines = [];
|
|
34
|
+
pieceTokens = 0;
|
|
35
|
+
}
|
|
36
|
+
lines.push(line);
|
|
37
|
+
pieceTokens += lineTokens;
|
|
38
|
+
}
|
|
39
|
+
if (lines.length > 0) pieces.push({ text: lines.join('\n'), tokens: pieceTokens });
|
|
40
|
+
return pieces;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class ClaudeTokenCounter {
|
|
44
|
+
/**
|
|
45
|
+
* @param {object} options
|
|
46
|
+
* @param {string} options.model - Claude model id, e.g. `claude-opus-5-5`
|
|
47
|
+
* @param {object} options.client - Anthropic SDK client (or a stand-in with
|
|
48
|
+
* `messages.countTokens`)
|
|
49
|
+
*/
|
|
50
|
+
constructor({ model, client }) {
|
|
51
|
+
if (!model) throw new Error('A Claude model id is required');
|
|
52
|
+
this.model = model;
|
|
53
|
+
this.client = client;
|
|
54
|
+
this.requests = 0;
|
|
55
|
+
this.framing = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a counter with the official SDK, which reads ANTHROPIC_API_KEY (or another
|
|
60
|
+
* configured credential) from the environment
|
|
61
|
+
* @param {object} options
|
|
62
|
+
* @param {string} options.model - Claude model id
|
|
63
|
+
* @param {object} [options.client] - Pre-built client (tests)
|
|
64
|
+
* @returns {Promise<ClaudeTokenCounter>}
|
|
65
|
+
*/
|
|
66
|
+
static async create({ model, client } = {}) {
|
|
67
|
+
if (!client) {
|
|
68
|
+
let Anthropic;
|
|
69
|
+
try {
|
|
70
|
+
({ default: Anthropic } = await import('@anthropic-ai/sdk'));
|
|
71
|
+
} catch (_error) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'Counting with the Anthropic API needs the official SDK: npm install @anthropic-ai/sdk'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
client = new Anthropic();
|
|
77
|
+
}
|
|
78
|
+
return new ClaudeTokenCounter({ model, client });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Count one text as a single user message
|
|
83
|
+
* @param {string} text - Non-empty text
|
|
84
|
+
* @returns {Promise<number>} input_tokens reported by the API
|
|
85
|
+
*/
|
|
86
|
+
async countMessage(text) {
|
|
87
|
+
this.requests++;
|
|
88
|
+
const response = await this.client.messages.countTokens({
|
|
89
|
+
model: this.model,
|
|
90
|
+
messages: [{ role: 'user', content: text }],
|
|
91
|
+
});
|
|
92
|
+
return response.input_tokens;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Tokens the API adds for the message framing, measured once with a one-token text
|
|
97
|
+
* @returns {Promise<number>}
|
|
98
|
+
*/
|
|
99
|
+
async getFraming() {
|
|
100
|
+
this.framing ??= Math.max(0, (await this.countMessage('.')) - 1);
|
|
101
|
+
return this.framing;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Count a text, minus the message framing
|
|
106
|
+
* @param {string} text
|
|
107
|
+
* @returns {Promise<number>}
|
|
108
|
+
*/
|
|
109
|
+
async countText(text) {
|
|
110
|
+
if (!text) return 0;
|
|
111
|
+
const framing = await this.getFraming();
|
|
112
|
+
return Math.max(0, (await this.countMessage(text)) - framing);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Count many documents in as few requests as the batch size allows
|
|
117
|
+
* @param {{path: string, content: string}[]} documents
|
|
118
|
+
* @param {object} [options]
|
|
119
|
+
* @param {number} [options.batchTokens] - cl100k_base tokens per request
|
|
120
|
+
* @param {(done: number, total: number) => void} [options.onProgress]
|
|
121
|
+
* @returns {Promise<{total: number, requests: number, batches: number}>}
|
|
122
|
+
*/
|
|
123
|
+
async countDocuments(documents, { batchTokens = DEFAULT_BATCH_TOKENS, onProgress } = {}) {
|
|
124
|
+
const batches = [];
|
|
125
|
+
let current = [];
|
|
126
|
+
let currentTokens = 0;
|
|
127
|
+
for (const doc of documents) {
|
|
128
|
+
if (!doc.content) continue;
|
|
129
|
+
for (const piece of splitByLines(doc.content, doc.path, batchTokens)) {
|
|
130
|
+
if (current.length > 0 && currentTokens + piece.tokens > batchTokens) {
|
|
131
|
+
batches.push(current);
|
|
132
|
+
current = [];
|
|
133
|
+
currentTokens = 0;
|
|
134
|
+
}
|
|
135
|
+
current.push(piece.text);
|
|
136
|
+
currentTokens += piece.tokens;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (current.length > 0) batches.push(current);
|
|
140
|
+
|
|
141
|
+
const before = this.requests;
|
|
142
|
+
let total = 0;
|
|
143
|
+
for (let i = 0; i < batches.length; i++) {
|
|
144
|
+
total += await this.countText(batches[i].join('\n'));
|
|
145
|
+
onProgress?.(i + 1, batches.length);
|
|
146
|
+
}
|
|
147
|
+
return { total, requests: this.requests - before, batches: batches.length };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default ClaudeTokenCounter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command-line output helpers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* End the process quietly when the reader of stdout goes away (`ctxman pr | head`): the
|
|
7
|
+
* rest of the output has nowhere to go, and Node's default is an unhandled EPIPE error.
|
|
8
|
+
* @param {import('stream').Writable} [stream=process.stdout]
|
|
9
|
+
*/
|
|
10
|
+
export function exitOnBrokenPipe(stream = process.stdout) {
|
|
11
|
+
stream.on('error', (error) => {
|
|
12
|
+
if (error.code === 'EPIPE') process.exit(0);
|
|
13
|
+
throw error;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default exitOnBrokenPipe;
|
|
@@ -6,61 +6,60 @@
|
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
7
|
|
|
8
8
|
class ClipboardUtils {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Copy text to system clipboard
|
|
11
|
+
* @param {string} text - Text to copy
|
|
12
|
+
* @returns {boolean} Success status
|
|
13
|
+
*/
|
|
14
|
+
static copy(text) {
|
|
15
|
+
try {
|
|
16
|
+
if (process.platform === 'darwin') {
|
|
17
|
+
// macOS
|
|
18
|
+
execSync('pbcopy', { input: text, encoding: 'utf8' });
|
|
19
|
+
} else if (process.platform === 'linux') {
|
|
20
|
+
// Linux - try xclip first, then xsel
|
|
15
21
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} else if (process.platform === 'linux') {
|
|
20
|
-
// Linux - try xclip first, then xsel
|
|
21
|
-
try {
|
|
22
|
-
execSync('xclip -selection clipboard', { input: text, encoding: 'utf8' });
|
|
23
|
-
} catch {
|
|
24
|
-
execSync('xsel --clipboard --input', { input: text, encoding: 'utf8' });
|
|
25
|
-
}
|
|
26
|
-
} else if (process.platform === 'win32') {
|
|
27
|
-
// Windows
|
|
28
|
-
execSync('clip', { input: text, encoding: 'utf8' });
|
|
29
|
-
} else {
|
|
30
|
-
throw new Error('Unsupported platform for clipboard');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log('✅ Context copied to clipboard!');
|
|
34
|
-
console.log(`📋 ${text.length} characters copied`);
|
|
35
|
-
return true;
|
|
36
|
-
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error('❌ Failed to copy to clipboard:', error.message);
|
|
39
|
-
return false;
|
|
22
|
+
execSync('xclip -selection clipboard', { input: text, encoding: 'utf8' });
|
|
23
|
+
} catch {
|
|
24
|
+
execSync('xsel --clipboard --input', { input: text, encoding: 'utf8' });
|
|
40
25
|
}
|
|
41
|
-
|
|
26
|
+
} else if (process.platform === 'win32') {
|
|
27
|
+
// Windows
|
|
28
|
+
execSync('clip', { input: text, encoding: 'utf8' });
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error('Unsupported platform for clipboard');
|
|
31
|
+
}
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return ['darwin', 'linux', 'win32'].includes(platform);
|
|
33
|
+
console.log('✅ Context copied to clipboard!');
|
|
34
|
+
console.log(`📋 ${text.length} characters copied`);
|
|
35
|
+
return true;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('❌ Failed to copy to clipboard:', error.message);
|
|
38
|
+
return false;
|
|
50
39
|
}
|
|
40
|
+
}
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Check if clipboard is available on current platform
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
static isAvailable() {
|
|
47
|
+
const platform = process.platform;
|
|
48
|
+
return ['darwin', 'linux', 'win32'].includes(platform);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get clipboard command for current platform
|
|
53
|
+
* @returns {string} Command name
|
|
54
|
+
*/
|
|
55
|
+
static getCommand() {
|
|
56
|
+
const commands = {
|
|
57
|
+
darwin: 'pbcopy',
|
|
58
|
+
linux: 'xclip',
|
|
59
|
+
win32: 'clip',
|
|
60
|
+
};
|
|
61
|
+
return commands[process.platform] || 'unknown';
|
|
62
|
+
}
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
export default ClipboardUtils;
|
|
@@ -14,81 +14,96 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
14
14
|
const __dirname = dirname(__filename);
|
|
15
15
|
|
|
16
16
|
class ConfigUtils {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Find configuration file in standard locations
|
|
19
|
+
* @param {string} projectRoot - Project root directory
|
|
20
|
+
* @param {string} filename - Config filename
|
|
21
|
+
* @returns {string|undefined} Config file path or undefined
|
|
22
|
+
*/
|
|
23
|
+
static findConfigFile(projectRoot, filename) {
|
|
24
|
+
const locations = [
|
|
25
|
+
path.join(__dirname, '..', '..', filename), // Package root
|
|
26
|
+
path.join(projectRoot, filename), // Project root
|
|
27
|
+
];
|
|
28
|
+
return locations.find((loc) => fs.existsSync(loc));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize method filter parser
|
|
33
|
+
* @param {string} projectRoot - Project root directory
|
|
34
|
+
* @returns {object|null} MethodFilterParser instance or null
|
|
35
|
+
*/
|
|
36
|
+
static initMethodFilter(projectRoot) {
|
|
37
|
+
const paths = {
|
|
38
|
+
methodInclude: this.findConfigFile(projectRoot, '.methodinclude'),
|
|
39
|
+
methodIgnore: this.findConfigFile(projectRoot, '.methodignore'),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!paths.methodInclude && !paths.methodIgnore) {
|
|
43
|
+
return null;
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* @param {string} projectRoot - Project root directory
|
|
34
|
-
* @returns {object|null} MethodFilterParser instance or null
|
|
35
|
-
*/
|
|
36
|
-
static initMethodFilter(projectRoot) {
|
|
37
|
-
const paths = {
|
|
38
|
-
methodInclude: this.findConfigFile(projectRoot, '.methodinclude'),
|
|
39
|
-
methodIgnore: this.findConfigFile(projectRoot, '.methodignore')
|
|
40
|
-
};
|
|
46
|
+
return new MethodFilterParser(paths.methodInclude, paths.methodIgnore);
|
|
47
|
+
}
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Detect if method filtering is enabled
|
|
51
|
+
* @param {string} projectRoot - Project root directory
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
static detectMethodFilters(projectRoot) {
|
|
55
|
+
const methodIncludePath = this.findConfigFile(projectRoot, '.methodinclude');
|
|
56
|
+
const methodIgnorePath = this.findConfigFile(projectRoot, '.methodignore');
|
|
57
|
+
return !!(methodIncludePath || methodIgnorePath);
|
|
58
|
+
}
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Initialize git ignore parser
|
|
62
|
+
* @param {string} projectRoot - Project root directory
|
|
63
|
+
* @returns {object} GitIgnoreParser instance
|
|
64
|
+
*/
|
|
65
|
+
static initGitIgnore(projectRoot) {
|
|
66
|
+
const paths = {
|
|
67
|
+
contextIgnore: this.findConfigFile(projectRoot, '.contextignore'),
|
|
68
|
+
contextInclude: this.findConfigFile(projectRoot, '.contextinclude'),
|
|
69
|
+
};
|
|
48
70
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const methodIncludePath = this.findConfigFile(projectRoot, '.methodinclude');
|
|
56
|
-
const methodIgnorePath = this.findConfigFile(projectRoot, '.methodignore');
|
|
57
|
-
return !!(methodIncludePath || methodIgnorePath);
|
|
58
|
-
}
|
|
71
|
+
return new GitIgnoreParser(
|
|
72
|
+
path.join(projectRoot, '.gitignore'),
|
|
73
|
+
paths.contextIgnore,
|
|
74
|
+
paths.contextInclude
|
|
75
|
+
);
|
|
76
|
+
}
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Get all config file paths
|
|
80
|
+
* @param {string} projectRoot - Project root directory
|
|
81
|
+
* @returns {object} Config paths
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Load the user configuration (~/.ctxman/config.json, see docs/LOGGING-AND-UPDATES.md)
|
|
85
|
+
* @returns {object|null} Parsed config, or null when missing or invalid
|
|
86
|
+
*/
|
|
87
|
+
static loadUserConfig() {
|
|
88
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
89
|
+
if (!home) return null;
|
|
70
90
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
);
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(fs.readFileSync(path.join(home, '.ctxman', 'config.json'), 'utf8'));
|
|
93
|
+
} catch (_error) {
|
|
94
|
+
return null;
|
|
76
95
|
}
|
|
96
|
+
}
|
|
77
97
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
contextInclude: this.findConfigFile(projectRoot, '.contextinclude'),
|
|
88
|
-
methodInclude: this.findConfigFile(projectRoot, '.methodinclude'),
|
|
89
|
-
methodIgnore: this.findConfigFile(projectRoot, '.methodignore')
|
|
90
|
-
};
|
|
91
|
-
}
|
|
98
|
+
static getConfigPaths(projectRoot) {
|
|
99
|
+
return {
|
|
100
|
+
gitignore: path.join(projectRoot, '.gitignore'),
|
|
101
|
+
contextIgnore: this.findConfigFile(projectRoot, '.contextignore'),
|
|
102
|
+
contextInclude: this.findConfigFile(projectRoot, '.contextinclude'),
|
|
103
|
+
methodInclude: this.findConfigFile(projectRoot, '.methodinclude'),
|
|
104
|
+
methodIgnore: this.findConfigFile(projectRoot, '.methodignore'),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
export default ConfigUtils;
|