ctxman 3.0.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.
Files changed (190) hide show
  1. package/.ctxman/custom-profiles.example.json +20 -0
  2. package/.ctxman/llm-profiles.json +131 -0
  3. package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
  4. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  5. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  6. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  7. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  8. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  9. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  10. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  11. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  12. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  13. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  14. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  15. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  16. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  17. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  18. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  19. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  20. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  21. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  22. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  23. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  24. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  25. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  26. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  27. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  28. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  29. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  30. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  31. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  32. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  33. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  34. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  35. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  36. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  37. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  38. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  39. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  40. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  41. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  42. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  43. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  44. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  45. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  46. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  47. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  48. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  49. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  50. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  51. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  52. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  53. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  54. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  55. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  56. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  57. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  58. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  59. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  60. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  61. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  62. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  63. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  64. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  65. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  66. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
  67. package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
  68. package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
  69. package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
  70. package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
  71. package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
  72. package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
  73. package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
  74. package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
  75. package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
  76. package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
  77. package/.ctxman/wizard-profiles/full/.contextignore +23 -0
  78. package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
  79. package/.ctxman/wizard-profiles/full/.methodignore +5 -0
  80. package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
  81. package/.ctxman/wizard-profiles/full/profile.json +28 -0
  82. package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
  83. package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
  84. package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
  85. package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
  86. package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
  87. package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
  88. package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
  89. package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
  90. package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
  91. package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
  92. package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
  93. package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
  94. package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
  95. package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
  96. package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
  97. package/README.md +1167 -0
  98. package/bin/cli.js +650 -0
  99. package/bin/cm-ask.js +95 -0
  100. package/bin/cm-gitingest.js +134 -0
  101. package/bin/cm-update.js +175 -0
  102. package/bin/mcp-server.js +31 -0
  103. package/ctxman.js +236 -0
  104. package/examples/README.md +298 -0
  105. package/examples/custom-llm-profiles.example.json +19 -0
  106. package/examples/wizard-profiles/code-review/.contextignore +41 -0
  107. package/examples/wizard-profiles/code-review/.contextinclude +30 -0
  108. package/examples/wizard-profiles/code-review/.methodignore +26 -0
  109. package/examples/wizard-profiles/code-review/.methodinclude +47 -0
  110. package/examples/wizard-profiles/code-review/profile.json +26 -0
  111. package/examples/wizard-profiles/documentation/.contextignore +21 -0
  112. package/examples/wizard-profiles/documentation/.contextinclude +24 -0
  113. package/examples/wizard-profiles/documentation/.methodignore +19 -0
  114. package/examples/wizard-profiles/documentation/.methodinclude +44 -0
  115. package/examples/wizard-profiles/documentation/profile.json +27 -0
  116. package/examples/wizard-profiles/full/.contextignore +23 -0
  117. package/examples/wizard-profiles/full/.contextinclude +38 -0
  118. package/examples/wizard-profiles/full/.methodignore +5 -0
  119. package/examples/wizard-profiles/full/.methodinclude +5 -0
  120. package/examples/wizard-profiles/full/profile.json +28 -0
  121. package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
  122. package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
  123. package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
  124. package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
  125. package/examples/wizard-profiles/llm-explain/profile.json +27 -0
  126. package/examples/wizard-profiles/minimal/.contextignore +27 -0
  127. package/examples/wizard-profiles/minimal/.contextinclude +14 -0
  128. package/examples/wizard-profiles/minimal/.methodignore +22 -0
  129. package/examples/wizard-profiles/minimal/.methodinclude +18 -0
  130. package/examples/wizard-profiles/minimal/profile.json +26 -0
  131. package/examples/wizard-profiles/security-audit/.contextignore +36 -0
  132. package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
  133. package/examples/wizard-profiles/security-audit/.methodignore +29 -0
  134. package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
  135. package/examples/wizard-profiles/security-audit/profile.json +34 -0
  136. package/index.js +68 -0
  137. package/lib/agents/pr-failure-agent.js +201 -0
  138. package/lib/analyzers/go-method-analyzer.js +126 -0
  139. package/lib/analyzers/method-analyzer.js +457 -0
  140. package/lib/analyzers/token-calculator.js +577 -0
  141. package/lib/api/mcp/prompts.js +319 -0
  142. package/lib/api/mcp/resources.js +269 -0
  143. package/lib/api/mcp/server.js +339 -0
  144. package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
  145. package/lib/api/rest/server.js +405 -0
  146. package/lib/cache/CacheManager.js +388 -0
  147. package/lib/core/Analyzer.js +180 -0
  148. package/lib/core/ContextBuilder.js +325 -0
  149. package/lib/core/Reporter.js +270 -0
  150. package/lib/core/Scanner.js +129 -0
  151. package/lib/formatters/format-registry.js +419 -0
  152. package/lib/formatters/gitingest-formatter.js +746 -0
  153. package/lib/formatters/toon-formatter-v1.3.js +731 -0
  154. package/lib/formatters/toon-formatter.js +291 -0
  155. package/lib/integrations/git/BlameTracker.js +142 -0
  156. package/lib/integrations/git/DiffAnalyzer.js +260 -0
  157. package/lib/integrations/git/GitClient.js +336 -0
  158. package/lib/parsers/gitignore-parser.js +127 -0
  159. package/lib/parsers/method-filter-parser.js +50 -0
  160. package/lib/plugins/ExporterPlugin.js +102 -0
  161. package/lib/plugins/LanguagePlugin.js +100 -0
  162. package/lib/plugins/PluginManager.js +302 -0
  163. package/lib/rag/DataSourcePlugin.js +31 -0
  164. package/lib/rag/EmbeddingProvider.js +75 -0
  165. package/lib/rag/Indexer.js +83 -0
  166. package/lib/rag/VectorStoreFactory.js +24 -0
  167. package/lib/rag/sources/FileDataSource.js +70 -0
  168. package/lib/rag/stores/BaseVectorStore.js +43 -0
  169. package/lib/rag/stores/ChromaDBStore.js +85 -0
  170. package/lib/rag/stores/LanceDBStore.js +109 -0
  171. package/lib/rag/stores/PineconeStore.js +74 -0
  172. package/lib/ui/dashboard.js +109 -0
  173. package/lib/ui/index.js +15 -0
  174. package/lib/ui/progress-bar.js +110 -0
  175. package/lib/ui/select-input.js +51 -0
  176. package/lib/ui/wizard.js +359 -0
  177. package/lib/utils/clipboard-utils.js +66 -0
  178. package/lib/utils/config-utils.js +94 -0
  179. package/lib/utils/error-handler.js +145 -0
  180. package/lib/utils/file-utils.js +89 -0
  181. package/lib/utils/format-converter.js +325 -0
  182. package/lib/utils/git-utils.js +337 -0
  183. package/lib/utils/llm-detector.js +409 -0
  184. package/lib/utils/logger.js +278 -0
  185. package/lib/utils/token-utils.js +200 -0
  186. package/lib/utils/tokenizer-adapter.js +454 -0
  187. package/lib/utils/updater.js +565 -0
  188. package/lib/watch/FileWatcher.js +208 -0
  189. package/lib/watch/IncrementalAnalyzer.js +206 -0
  190. package/package.json +118 -0
@@ -0,0 +1,109 @@
1
+ import * as lancedb from '@lancedb/lancedb';
2
+ import fs from 'fs/promises';
3
+ import { BaseVectorStore } from './BaseVectorStore.js';
4
+
5
+ export class LanceDBStore extends BaseVectorStore {
6
+ /**
7
+ * @param {import('../EmbeddingProvider').EmbeddingProvider} embeddingProvider
8
+ * @param {string} storagePath - Path to store the LanceDB data
9
+ */
10
+ constructor(embeddingProvider, storagePath = '.ctxman/rag-store') {
11
+ super(embeddingProvider);
12
+ this.storagePath = storagePath;
13
+ this.db = null;
14
+ this.table = null;
15
+ this.tableName = 'documents';
16
+ this.initialized = false;
17
+ }
18
+
19
+ async init() {
20
+ if (this.initialized) return;
21
+
22
+ // Ensure directory exists
23
+ await fs.mkdir(this.storagePath, { recursive: true });
24
+
25
+ this.db = await lancedb.connect(this.storagePath);
26
+
27
+ const tableNames = await this.db.tableNames();
28
+ if (tableNames.includes(this.tableName)) {
29
+ this.table = await this.db.openTable(this.tableName);
30
+ }
31
+
32
+ this.initialized = true;
33
+ }
34
+
35
+ /**
36
+ * Adds a single document to the store.
37
+ * @param {string} text
38
+ * @param {Object} metadata
39
+ */
40
+ async addDocument(text, metadata = {}) {
41
+ return this.addDocuments([{ text, metadata }]);
42
+ }
43
+
44
+ /**
45
+ * Adds multiple documents to the store.
46
+ * @param {Array<{text: string, metadata: Object}>} docs
47
+ */
48
+ async addDocuments(docs) {
49
+ if (docs.length === 0) return;
50
+ if (!this.initialized) await this.init();
51
+
52
+ const data = [];
53
+ for (const doc of docs) {
54
+ const vector = await this.embeddingProvider.embed(doc.text);
55
+ data.push({
56
+ vector,
57
+ text: doc.text,
58
+ id: crypto.randomUUID(),
59
+ timestamp: Date.now(),
60
+ ...doc.metadata
61
+ });
62
+ }
63
+
64
+ if (!this.table) {
65
+ // Create table with the first batch
66
+ this.table = await this.db.createTable(this.tableName, data);
67
+ } else {
68
+ await this.table.add(data);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Semantic search for documents.
74
+ * @param {string} queryText
75
+ * @param {number} limit
76
+ * @returns {Promise<Array<{text: string, score: number, metadata: Object}>>}
77
+ */
78
+ async search(queryText, limit = 5) {
79
+ if (!this.initialized) await this.init();
80
+ if (!this.table) return [];
81
+
82
+ const queryVector = await this.embeddingProvider.embed(queryText);
83
+
84
+ // LanceDB returns _distance by default (L2 distance usually).
85
+ // Lower is better.
86
+ const results = await this.table.search(queryVector)
87
+ .limit(limit)
88
+ .toArray();
89
+
90
+ return results.map(row => {
91
+ const { vector, text, _distance, ...metadata } = row;
92
+ return {
93
+ text,
94
+ score: _distance,
95
+ metadata
96
+ };
97
+ });
98
+ }
99
+
100
+ async deleteTable() {
101
+ if (!this.initialized) await this.init();
102
+ try {
103
+ await this.db.dropTable(this.tableName);
104
+ this.table = null;
105
+ } catch (e) {
106
+ // Ignore if not exists
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,74 @@
1
+ import { BaseVectorStore } from './BaseVectorStore.js';
2
+
3
+ export class PineconeStore extends BaseVectorStore {
4
+ /**
5
+ * @param {import('../EmbeddingProvider').EmbeddingProvider} embeddingProvider
6
+ * @param {Object} config
7
+ * @param {string} config.apiKey
8
+ * @param {string} config.indexName
9
+ */
10
+ constructor(embeddingProvider, config = {}) {
11
+ super(embeddingProvider);
12
+ this.apiKey = config.apiKey;
13
+ this.indexName = config.indexName;
14
+ this.client = null;
15
+ this.index = null;
16
+ }
17
+
18
+ async init() {
19
+ if (this.client) return;
20
+ if (!this.apiKey) throw new Error('Pinecone API key required');
21
+
22
+ try {
23
+ const { Pinecone } = await import('@pinecone-database/pinecone');
24
+ this.client = new Pinecone({ apiKey: this.apiKey });
25
+ this.index = this.client.index(this.indexName);
26
+ } catch (error) {
27
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
28
+ throw new Error('Please install @pinecone-database/pinecone to use PineconeStore: npm install @pinecone-database/pinecone');
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ async addDocuments(docs) {
35
+ if (!this.index) await this.init();
36
+ if (docs.length === 0) return;
37
+
38
+ const vectors = [];
39
+ for (const doc of docs) {
40
+ const values = await this.embeddingProvider.embed(doc.text);
41
+ vectors.push({
42
+ id: doc.id || crypto.randomUUID(),
43
+ values,
44
+ metadata: {
45
+ ...doc.metadata,
46
+ text: doc.text // Pinecone metadata is limited, but storing text is useful
47
+ }
48
+ });
49
+ }
50
+
51
+ // Pinecone likes batches
52
+ const batchSize = 100;
53
+ for (let i = 0; i < vectors.length; i += batchSize) {
54
+ await this.index.upsert(vectors.slice(i, i + batchSize));
55
+ }
56
+ }
57
+
58
+ async search(queryText, limit = 5) {
59
+ if (!this.index) await this.init();
60
+
61
+ const vector = await this.embeddingProvider.embed(queryText);
62
+ const results = await this.index.query({
63
+ vector,
64
+ topK: limit,
65
+ includeMetadata: true
66
+ });
67
+
68
+ return results.matches.map(match => ({
69
+ text: match.metadata.text || '', // Assuming text is stored in metadata
70
+ score: match.score,
71
+ metadata: match.metadata
72
+ }));
73
+ }
74
+ }
@@ -0,0 +1,109 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text, useInput, useStdin } from 'ink';
3
+
4
+ /**
5
+ * Live Analysis Dashboard
6
+ * Real-time statistics and progress display
7
+ */
8
+ export default function Dashboard({ stats, topFiles = [], status = 'analyzing' }) {
9
+ const [shouldRefresh, setShouldRefresh] = useState(false);
10
+ const { isRawModeSupported } = useStdin();
11
+
12
+ // Keyboard controls
13
+ useInput((input, key) => {
14
+ if (input === 'r' || input === 'R') {
15
+ setShouldRefresh(!shouldRefresh);
16
+ }
17
+ if (input === 'q' || input === 'Q') {
18
+ process.exit(0);
19
+ }
20
+ }, { isActive: isRawModeSupported });
21
+
22
+ const {
23
+ totalFiles = 0,
24
+ totalTokens = 0,
25
+ totalBytes = 0,
26
+ totalLines = 0,
27
+ byExtension = {}
28
+ } = stats;
29
+
30
+ const avgTokensPerFile = totalFiles > 0 ? Math.round(totalTokens / totalFiles) : 0;
31
+ const sizeInMB = (totalBytes / (1024 * 1024)).toFixed(2);
32
+
33
+ // Get dominant language
34
+ const extensions = Object.entries(byExtension);
35
+ const dominant = extensions.length > 0
36
+ ? extensions.sort((a, b) => b[1].count - a[1].count)[0]
37
+ : null;
38
+
39
+ return React.createElement(Box, {
40
+ flexDirection: 'column',
41
+ padding: 1,
42
+ borderStyle: 'round',
43
+ borderColor: 'green',
44
+ width: 76 // Max width to prevent scrollbar overlap (reduced from 80)
45
+ },
46
+ React.createElement(Box, { marginBottom: 1 },
47
+ React.createElement(Text, { bold: true, color: 'green' }, '📊 Live Analysis Dashboard'),
48
+ React.createElement(Text, { color: 'gray' }, ` (${status})`)
49
+ ),
50
+
51
+ // Summary Stats
52
+ React.createElement(Box, { flexDirection: 'column', marginBottom: 1 },
53
+ React.createElement(Box, null,
54
+ React.createElement(Text, null, 'Files: '),
55
+ React.createElement(Text, { bold: true, color: 'cyan' }, totalFiles.toLocaleString()),
56
+ React.createElement(Text, null, ' Methods: '),
57
+ React.createElement(Text, { bold: true, color: 'cyan' }, '-'),
58
+ React.createElement(Text, null, ' Tokens: '),
59
+ React.createElement(Text, { bold: true, color: 'cyan' }, totalTokens.toLocaleString())
60
+ ),
61
+
62
+ React.createElement(Box, null,
63
+ React.createElement(Text, null, 'Size: '),
64
+ React.createElement(Text, { bold: true, color: 'cyan' }, `${sizeInMB} MB`),
65
+ React.createElement(Text, null, ' Lines: '),
66
+ React.createElement(Text, { bold: true, color: 'cyan' }, totalLines.toLocaleString()),
67
+ React.createElement(Text, null, ' Avg: '),
68
+ React.createElement(Text, { bold: true, color: 'cyan' }, `${avgTokensPerFile.toLocaleString()} tok/file`)
69
+ )
70
+ ),
71
+
72
+ // Top Languages
73
+ dominant && React.createElement(Box, { flexDirection: 'column', marginBottom: 1 },
74
+ React.createElement(Text, { bold: true }, 'Top Languages:'),
75
+ React.createElement(Box, { marginLeft: 2 },
76
+ React.createElement(Text, { color: 'green' }, '▓'.repeat(30)),
77
+ React.createElement(Text, null, ` ${dominant[0]} (${dominant[1].count} files)`)
78
+ )
79
+ ),
80
+
81
+ // Largest Files
82
+ topFiles.length > 0 && React.createElement(Box, { flexDirection: 'column', marginBottom: 1 },
83
+ React.createElement(Text, { bold: true }, 'Largest Files:'),
84
+ ...topFiles.slice(0, 3).map((file, index) => {
85
+ const barLength = Math.round((file.tokens / topFiles[0].tokens) * 20);
86
+ const percentage = ((file.tokens / totalTokens) * 100).toFixed(1);
87
+ const fileName = (file.name || file.relativePath || file.path || 'unknown').substring(0, 30);
88
+
89
+ return React.createElement(Box, { key: index, marginLeft: 2 },
90
+ React.createElement(Text, null, '• '),
91
+ React.createElement(Text, { color: 'cyan' }, fileName),
92
+ React.createElement(Text, null, ` ${file.tokens.toLocaleString()} tokens `),
93
+ React.createElement(Text, { color: 'green' }, '▓'.repeat(barLength)),
94
+ React.createElement(Text, { color: 'gray' }, ` (${percentage}%)`)
95
+ );
96
+ })
97
+ ),
98
+
99
+ // Controls
100
+ React.createElement(Box, {
101
+ marginTop: 1,
102
+ borderStyle: 'single',
103
+ borderColor: 'gray',
104
+ padding: 1
105
+ },
106
+ React.createElement(Text, { dimColor: true }, '[R] Refresh [S] Save [E] Export [Q] Quit')
107
+ )
108
+ );
109
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Ink-based Terminal UI Components
3
+ * v2.3.0 - Modern, interactive CLI experience
4
+ */
5
+
6
+ import { ProgressBar, SpinnerWithText } from './progress-bar.js';
7
+ import Wizard from './wizard.js';
8
+ import Dashboard from './dashboard.js';
9
+
10
+ export {
11
+ ProgressBar,
12
+ SpinnerWithText,
13
+ Wizard,
14
+ Dashboard
15
+ };
@@ -0,0 +1,110 @@
1
+ import React from 'react.js';
2
+
3
+ /**
4
+ * Progress Bar Component for Ink Terminal UI
5
+ * Shows analysis progress with file count and token count
6
+ *
7
+ * Note: Components are passed via props to avoid ESM import issues
8
+ */
9
+ const ProgressBar = ({ current, total, tokens, maxTokens, currentFile, components }) => {
10
+ const { Box, Text } = components || {};
11
+
12
+ if (!Box || !Text) {
13
+ throw new Error('Box and Text components required in props');
14
+ }
15
+
16
+ const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
17
+ const barWidth = 40;
18
+ const filled = Math.round((percentage / 100) * barWidth);
19
+ const empty = barWidth - filled;
20
+
21
+ const bar = '━'.repeat(filled) + '─'.repeat(empty);
22
+ const tokenPercentage = maxTokens > 0 ? ((tokens / maxTokens) * 100).toFixed(1) : 0;
23
+
24
+ const children = [];
25
+
26
+ // Title
27
+ children.push(
28
+ React.createElement(Box, { key: 'title' },
29
+ React.createElement(Text, { bold: true, color: 'cyan' }, '📊 Project Analysis')
30
+ )
31
+ );
32
+
33
+ // Progress bar
34
+ children.push(
35
+ React.createElement(Box, { key: 'bar', marginTop: 1 },
36
+ React.createElement(Text, { color: 'green' }, bar),
37
+ React.createElement(Text, { color: 'yellow' }, ` ${percentage}%`),
38
+ React.createElement(Text, { color: 'gray' }, ` (${current}/${total} files)`)
39
+ )
40
+ );
41
+
42
+ // Current file
43
+ if (currentFile) {
44
+ children.push(
45
+ React.createElement(Box, { key: 'current', marginTop: 1 },
46
+ React.createElement(Text, { color: 'gray' }, 'Current: '),
47
+ React.createElement(Text, null, currentFile)
48
+ )
49
+ );
50
+ }
51
+
52
+ // Tokens
53
+ const tokenColor = tokenPercentage > 90 ? 'red' : 'green';
54
+ const tokenChildren = [
55
+ React.createElement(Text, { key: 't1', color: 'gray' }, 'Tokens: '),
56
+ React.createElement(Text, { key: 't2', color: tokenColor }, tokens.toLocaleString())
57
+ ];
58
+
59
+ if (maxTokens > 0) {
60
+ tokenChildren.push(
61
+ React.createElement(Text, { key: 't3', color: 'gray' },
62
+ ` / ${maxTokens.toLocaleString()} (${tokenPercentage}%)`)
63
+ );
64
+ }
65
+
66
+ children.push(
67
+ React.createElement(Box, { key: 'tokens', marginTop: 1 }, ...tokenChildren)
68
+ );
69
+
70
+ return React.createElement(Box, { flexDirection: 'column' }, ...children);
71
+ };
72
+
73
+ /**
74
+ * Spinner Component with message
75
+ */
76
+ const SpinnerWithText = ({ text, status = 'pending', components }) => {
77
+ const { Box, Text, Spinner } = components || {};
78
+
79
+ if (!Box || !Text) {
80
+ throw new Error('Box and Text components required in props');
81
+ }
82
+
83
+ const statusIcons = {
84
+ pending: '○',
85
+ active: Spinner ? React.createElement(Spinner, { type: 'dots' }) : '⏳',
86
+ complete: '✓',
87
+ error: '✗'
88
+ };
89
+
90
+ const statusColors = {
91
+ pending: 'gray',
92
+ active: 'cyan',
93
+ complete: 'green',
94
+ error: 'red'
95
+ };
96
+
97
+ const icon = status === 'active' && Spinner
98
+ ? React.createElement(Spinner, { type: 'dots' })
99
+ : statusIcons[status];
100
+
101
+ return React.createElement(Box, null,
102
+ React.createElement(Text, { color: statusColors[status] },
103
+ icon,
104
+ ' ',
105
+ text
106
+ )
107
+ );
108
+ };
109
+
110
+ export { ProgressBar, SpinnerWithText };
@@ -0,0 +1,51 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { Box, Text, useInput, useStdin } from 'ink';
3
+
4
+ /**
5
+ * Custom SelectInput Component for Ink 6.x
6
+ * Replaces deprecated ink-select-input package
7
+ * Based on official Ink examples/select-input implementation
8
+ *
9
+ * @param {Array} items - Array of {label, value} objects
10
+ * @param {Function} onSelect - Callback when item is selected
11
+ */
12
+ export default function SelectInput({ items = [], onSelect }) {
13
+ const [selectedIndex, setSelectedIndex] = useState(0);
14
+ const { isRawModeSupported } = useStdin();
15
+
16
+ useInput(useCallback((input, key) => {
17
+ if (key.upArrow) {
18
+ setSelectedIndex(prev =>
19
+ prev === 0 ? items.length - 1 : prev - 1
20
+ );
21
+ }
22
+
23
+ if (key.downArrow) {
24
+ setSelectedIndex(prev =>
25
+ prev === items.length - 1 ? 0 : prev + 1
26
+ );
27
+ }
28
+
29
+ if (key.return) {
30
+ if (items[selectedIndex] && onSelect) {
31
+ onSelect(items[selectedIndex]);
32
+ }
33
+ }
34
+
35
+ if (key.escape) {
36
+ process.exit(0);
37
+ }
38
+ }, [selectedIndex, items, onSelect]), { isActive: isRawModeSupported });
39
+
40
+ // Show ALL items (no scrolling/limit) - per Ink docs
41
+ return React.createElement(Box, { flexDirection: 'column' },
42
+ ...items.map((item, index) => {
43
+ const isSelected = index === selectedIndex;
44
+
45
+ return React.createElement(Text, {
46
+ key: item.value || item.label || index, // Use unique value as key
47
+ color: isSelected ? 'cyan' : undefined
48
+ }, `${isSelected ? '❯ ' : ' '}${item.label}`);
49
+ })
50
+ );
51
+ }