trace.ai-cli 1.0.3 → 1.0.4
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/README.md +90 -0
- package/cli/traceAI.js +486 -0
- package/index.js +14 -642
- package/package.json +1 -1
- package/services/aiService.js +50 -0
- package/services/fileAnalyzer.js +60 -0
- package/services/folderAnalyzer.js +164 -0
- package/services/imageService.js +66 -0
- package/utils/encryption.js +29 -0
- package/utils/fileUtils.js +101 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fetch = require('node-fetch');
|
|
2
|
+
const { encryptData, decryptData } = require('../utils/encryption');
|
|
3
|
+
|
|
4
|
+
async function processWithAI(prompt, context = '') {
|
|
5
|
+
try {
|
|
6
|
+
const models = ['kimi', 'mvrk', 'gma3', 'dsv3', 'qw32b', 'ms24b', 'll70b', 'qw3', 'mp4', 'nlm3'];
|
|
7
|
+
|
|
8
|
+
const modelRequests = models.map(model =>
|
|
9
|
+
fetch('https://traceai.dukeindustries7.workers.dev/', {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: { 'Content-Type': 'application/json' },
|
|
12
|
+
body: encryptData({
|
|
13
|
+
a: model,
|
|
14
|
+
q: prompt,
|
|
15
|
+
r: [],
|
|
16
|
+
i: [],
|
|
17
|
+
c: context
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
.then(res => res.text())
|
|
21
|
+
.then(decryptData)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const responses = await Promise.all(modelRequests);
|
|
25
|
+
const responseTexts = responses.map(r => r.text);
|
|
26
|
+
|
|
27
|
+
const finalResponse = await fetch('https://traceai.dukeindustries7.workers.dev/', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: encryptData({
|
|
31
|
+
a: 'gfinal',
|
|
32
|
+
q: prompt,
|
|
33
|
+
r: responseTexts.filter(text => text && typeof text === 'string'),
|
|
34
|
+
i: [],
|
|
35
|
+
c: context
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const encryptedResult = await finalResponse.text();
|
|
40
|
+
const decryptedResult = decryptData(encryptedResult);
|
|
41
|
+
return decryptedResult.text || 'No response generated';
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('❌ Processing error:', error.message);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
processWithAI
|
|
50
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { getFileType, readFileContent } = require('../utils/fileUtils');
|
|
3
|
+
const { processWithAI } = require('./aiService');
|
|
4
|
+
const { extractTextFromImage } = require('./imageService');
|
|
5
|
+
|
|
6
|
+
async function analyzeFile(filePath, query = '') {
|
|
7
|
+
try {
|
|
8
|
+
const fileType = getFileType(filePath);
|
|
9
|
+
|
|
10
|
+
if (fileType === 'image') {
|
|
11
|
+
return await extractTextFromImage(filePath);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const content = await readFileContent(filePath);
|
|
15
|
+
const fileName = path.basename(filePath);
|
|
16
|
+
|
|
17
|
+
let prompt;
|
|
18
|
+
if (query) {
|
|
19
|
+
prompt = `File: ${fileName} (${fileType})
|
|
20
|
+
Content:
|
|
21
|
+
\`\`\`
|
|
22
|
+
${content}
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
User Question: ${query}`;
|
|
26
|
+
} else {
|
|
27
|
+
if (fileType !== 'Unknown' && fileType !== 'Text' && fileType !== 'Markdown') {
|
|
28
|
+
prompt = `Analyze this ${fileType} code file (${fileName}):
|
|
29
|
+
|
|
30
|
+
\`\`\`
|
|
31
|
+
${content}
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
Please provide:
|
|
35
|
+
1. Code overview and purpose
|
|
36
|
+
2. Key functions/components
|
|
37
|
+
3. Potential issues or improvements
|
|
38
|
+
4. Code quality assessment
|
|
39
|
+
5. Suggestions for optimization`;
|
|
40
|
+
} else {
|
|
41
|
+
prompt = `Analyze this file content (${fileName}):
|
|
42
|
+
|
|
43
|
+
\`\`\`
|
|
44
|
+
${content}
|
|
45
|
+
\`\`\`
|
|
46
|
+
|
|
47
|
+
Please provide a summary and analysis of the content.`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await processWithAI(prompt);
|
|
52
|
+
return { text: result || 'No response generated' }; // Ensure consistent return format
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
analyzeFile
|
|
60
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getFileType } = require('../utils/fileUtils');
|
|
4
|
+
const { processWithAI } = require('./aiService');
|
|
5
|
+
|
|
6
|
+
function buildSimpleFileTree(folderPath, items, statsCache, depth = 0) {
|
|
7
|
+
let tree = '';
|
|
8
|
+
const indent = ' '.repeat(depth);
|
|
9
|
+
|
|
10
|
+
for (const item of items) {
|
|
11
|
+
const itemPath = path.join(folderPath, item);
|
|
12
|
+
const stats = statsCache.get(itemPath);
|
|
13
|
+
tree += `${indent}${item}${stats.isDirectory() ? '/' : ''}\n`;
|
|
14
|
+
|
|
15
|
+
if (stats.isDirectory()) {
|
|
16
|
+
try {
|
|
17
|
+
const subItems = fs.readdirSync(itemPath);
|
|
18
|
+
const subStatsCache = new Map();
|
|
19
|
+
for (const subItem of subItems) {
|
|
20
|
+
const subItemPath = path.join(itemPath, subItem);
|
|
21
|
+
subStatsCache.set(subItemPath, fs.statSync(subItemPath));
|
|
22
|
+
}
|
|
23
|
+
tree += buildSimpleFileTree(itemPath, subItems, subStatsCache, depth + 1);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
tree += `${indent} (error accessing directory)\n`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return tree;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function analyzeFolder(folderPath, maxDepth = 2, currentDepth = 0) {
|
|
34
|
+
try {
|
|
35
|
+
const stats = await fs.stat(folderPath);
|
|
36
|
+
if (!stats.isDirectory()) {
|
|
37
|
+
throw new Error('Path is not a directory');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const analysis = {
|
|
41
|
+
path: folderPath,
|
|
42
|
+
files: [],
|
|
43
|
+
folders: [],
|
|
44
|
+
fileTree: '', // Store simple file tree
|
|
45
|
+
summary: {
|
|
46
|
+
totalFiles: 0,
|
|
47
|
+
totalFolders: 0,
|
|
48
|
+
fileTypes: {},
|
|
49
|
+
languages: {},
|
|
50
|
+
size: 0
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const items = await fs.readdir(folderPath);
|
|
55
|
+
const statsCache = new Map();
|
|
56
|
+
|
|
57
|
+
// Cache stats for all items
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
const itemPath = path.join(folderPath, item);
|
|
60
|
+
statsCache.set(itemPath, await fs.stat(itemPath));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build simple file tree
|
|
64
|
+
analysis.fileTree = buildSimpleFileTree(folderPath, items, statsCache);
|
|
65
|
+
|
|
66
|
+
for (const item of items) {
|
|
67
|
+
const itemPath = path.join(folderPath, item);
|
|
68
|
+
const itemStats = statsCache.get(itemPath);
|
|
69
|
+
|
|
70
|
+
if (itemStats.isDirectory()) {
|
|
71
|
+
analysis.folders.push(item);
|
|
72
|
+
analysis.summary.totalFolders++;
|
|
73
|
+
|
|
74
|
+
if (currentDepth < maxDepth) {
|
|
75
|
+
try {
|
|
76
|
+
const subAnalysis = await analyzeFolder(itemPath, maxDepth, currentDepth + 1);
|
|
77
|
+
analysis.summary.totalFiles += subAnalysis.summary.totalFiles;
|
|
78
|
+
analysis.summary.totalFolders += subAnalysis.summary.totalFolders;
|
|
79
|
+
analysis.summary.size += subAnalysis.summary.size;
|
|
80
|
+
|
|
81
|
+
Object.keys(subAnalysis.summary.fileTypes).forEach(type => {
|
|
82
|
+
analysis.summary.fileTypes[type] = (analysis.summary.fileTypes[type] || 0) + subAnalysis.summary.fileTypes[type];
|
|
83
|
+
});
|
|
84
|
+
Object.keys(subAnalysis.summary.languages).forEach(lang => {
|
|
85
|
+
analysis.summary.languages[lang] = (analysis.summary.languages[lang] || 0) + subAnalysis.summary.languages[lang];
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(`Warning: Cannot access folder ${itemPath}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const fileType = getFileType(itemPath);
|
|
93
|
+
analysis.files.push({
|
|
94
|
+
name: item,
|
|
95
|
+
type: fileType,
|
|
96
|
+
size: itemStats.size,
|
|
97
|
+
modified: itemStats.mtime
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
analysis.summary.totalFiles++;
|
|
101
|
+
analysis.summary.size += itemStats.size;
|
|
102
|
+
analysis.summary.fileTypes[fileType] = (analysis.summary.fileTypes[fileType] || 0) + 1;
|
|
103
|
+
|
|
104
|
+
if (fileType !== 'image' && fileType !== 'Unknown') {
|
|
105
|
+
analysis.summary.languages[fileType] = (analysis.summary.languages[fileType] || 0) + 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return analysis;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error.code === 'ENOENT') {
|
|
113
|
+
throw new Error(`Folder not found: ${folderPath}`);
|
|
114
|
+
}
|
|
115
|
+
if (error.code === 'EACCES') {
|
|
116
|
+
throw new Error(`Access denied: ${folderPath}`);
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function analyzeFolderStructure(folderPath, query = '') {
|
|
123
|
+
try {
|
|
124
|
+
const analysis = await analyzeFolder(folderPath);
|
|
125
|
+
|
|
126
|
+
const structureText = `
|
|
127
|
+
Folder: ${analysis.path}
|
|
128
|
+
Files: ${analysis.summary.totalFiles}
|
|
129
|
+
Folders: ${analysis.summary.totalFolders}
|
|
130
|
+
Total Size: ${(analysis.summary.size / 1024).toFixed(2)} KB
|
|
131
|
+
|
|
132
|
+
Files and Directories:
|
|
133
|
+
${analysis.fileTree}
|
|
134
|
+
|
|
135
|
+
File Types:
|
|
136
|
+
${Object.entries(analysis.summary.fileTypes).map(([type, count]) => `- ${type}: ${count}`).join('\n')}
|
|
137
|
+
|
|
138
|
+
Languages/Technologies:
|
|
139
|
+
${Object.entries(analysis.summary.languages).map(([lang, count]) => `- ${lang}: ${count} files`).join('\n')}
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
let prompt;
|
|
143
|
+
if (query) {
|
|
144
|
+
prompt = `Project Structure:
|
|
145
|
+
${structureText}
|
|
146
|
+
|
|
147
|
+
User Question: ${query}`;
|
|
148
|
+
} else {
|
|
149
|
+
prompt = `Analyze this project structure:
|
|
150
|
+
${structureText}
|
|
151
|
+
|
|
152
|
+
Provide a brief overview of the project based on its file structure.`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result = await processWithAI(prompt);
|
|
156
|
+
return { text: result || 'No response generated' }; // Ensure text property is always defined
|
|
157
|
+
} catch (error) {
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
analyzeFolderStructure
|
|
164
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const fetch = require('node-fetch');
|
|
3
|
+
const { encryptData, decryptData } = require('../utils/encryption');
|
|
4
|
+
const { getMimeType } = require('../utils/fileUtils');
|
|
5
|
+
|
|
6
|
+
async function convertImageToBase64(imagePath) {
|
|
7
|
+
try {
|
|
8
|
+
const imageBuffer = await fs.readFile(imagePath);
|
|
9
|
+
const base64 = imageBuffer.toString('base64');
|
|
10
|
+
const mimeType = getMimeType(imagePath);
|
|
11
|
+
return { base64, mimeType };
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Failed to read image: ${error.message}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function extractTextFromImage(imagePath, question = '') {
|
|
18
|
+
try {
|
|
19
|
+
const { base64, mimeType } = await convertImageToBase64(imagePath);
|
|
20
|
+
const visionModels = ['kimi', 'mvrk', 'gma3', 'qvl72', 'llama-vision'];
|
|
21
|
+
|
|
22
|
+
const modelRequests = visionModels.map(model =>
|
|
23
|
+
fetch('https://traceai.dukeindustries7.workers.dev/', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: encryptData({
|
|
27
|
+
a: model,
|
|
28
|
+
q: 'Extract and return only the text from this image. Format it naturally.',
|
|
29
|
+
r: [],
|
|
30
|
+
i: [{ inlineData: { data: base64, mimeType } }],
|
|
31
|
+
c: ''
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
.then(res => res.text())
|
|
35
|
+
.then(decryptData)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const responses = await Promise.all(modelRequests);
|
|
39
|
+
const responseTexts = responses.map(r => r.text);
|
|
40
|
+
|
|
41
|
+
const finalResponse = await fetch('https://traceai.dukeindustries7.workers.dev/', {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: encryptData({
|
|
45
|
+
a: 'gfinal',
|
|
46
|
+
q: question ?
|
|
47
|
+
`First extract and combine the text from this image, then answer this question about the extracted text: ${question}` :
|
|
48
|
+
'Combine these extracted texts from an image into a single, coherent, and naturally formatted text. Remove duplicates and ensure clarity.',
|
|
49
|
+
r: responseTexts,
|
|
50
|
+
i: [{ inlineData: { data: base64, mimeType } }],
|
|
51
|
+
c: ''
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const encryptedResult = await finalResponse.text();
|
|
56
|
+
const decryptedResult = decryptData(encryptedResult);
|
|
57
|
+
return decryptedResult.text || 'Unable to analyze image'
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('❌ Error analyzing image:', error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
extractTextFromImage
|
|
66
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const ENCRYPTION_KEY = 'AlzaSyCVKlzUxK';
|
|
2
|
+
|
|
3
|
+
function encodeUnicode(str) {
|
|
4
|
+
const utf8Bytes = new TextEncoder().encode(str);
|
|
5
|
+
const base64 = Buffer.from(utf8Bytes).toString('base64');
|
|
6
|
+
return base64;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function decodeUnicode(str) {
|
|
10
|
+
const bytes = Buffer.from(str, 'base64');
|
|
11
|
+
return new TextDecoder().decode(bytes);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function encryptData(data) {
|
|
15
|
+
const jsonStr = JSON.stringify(data);
|
|
16
|
+
const encoded = encodeUnicode(jsonStr);
|
|
17
|
+
return encoded.split('').reverse().join('');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function decryptData(encoded) {
|
|
21
|
+
const reversed = encoded.split('').reverse().join('');
|
|
22
|
+
const decoded = decodeUnicode(reversed);
|
|
23
|
+
return JSON.parse(decoded);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
encryptData,
|
|
28
|
+
decryptData
|
|
29
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// File type detection
|
|
5
|
+
function getFileType(filePath) {
|
|
6
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
7
|
+
const codeExtensions = {
|
|
8
|
+
'.js': 'JavaScript',
|
|
9
|
+
'.jsx': 'React/JavaScript',
|
|
10
|
+
'.ts': 'TypeScript',
|
|
11
|
+
'.tsx': 'React/TypeScript',
|
|
12
|
+
'.py': 'Python',
|
|
13
|
+
'.java': 'Java',
|
|
14
|
+
'.cpp': 'C++',
|
|
15
|
+
'.c': 'C',
|
|
16
|
+
'.cs': 'C#',
|
|
17
|
+
'.php': 'PHP',
|
|
18
|
+
'.rb': 'Ruby',
|
|
19
|
+
'.go': 'Go',
|
|
20
|
+
'.rs': 'Rust',
|
|
21
|
+
'.swift': 'Swift',
|
|
22
|
+
'.kt': 'Kotlin',
|
|
23
|
+
'.scala': 'Scala',
|
|
24
|
+
'.html': 'HTML',
|
|
25
|
+
'.css': 'CSS',
|
|
26
|
+
'.scss': 'SCSS',
|
|
27
|
+
'.sass': 'SASS',
|
|
28
|
+
'.less': 'LESS',
|
|
29
|
+
'.vue': 'Vue.js',
|
|
30
|
+
'.svelte': 'Svelte',
|
|
31
|
+
'.json': 'JSON',
|
|
32
|
+
'.xml': 'XML',
|
|
33
|
+
'.yaml': 'YAML',
|
|
34
|
+
'.yml': 'YAML',
|
|
35
|
+
'.toml': 'TOML',
|
|
36
|
+
'.ini': 'INI',
|
|
37
|
+
'.conf': 'Config',
|
|
38
|
+
'.md': 'Markdown',
|
|
39
|
+
'.txt': 'Text',
|
|
40
|
+
'.sql': 'SQL',
|
|
41
|
+
'.sh': 'Shell Script',
|
|
42
|
+
'.bash': 'Bash Script',
|
|
43
|
+
'.zsh': 'Zsh Script',
|
|
44
|
+
'.ps1': 'PowerShell',
|
|
45
|
+
'.bat': 'Batch Script',
|
|
46
|
+
'.dockerfile': 'Dockerfile',
|
|
47
|
+
'.gitignore': 'Git Ignore',
|
|
48
|
+
'.env': 'Environment Variables'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'];
|
|
52
|
+
|
|
53
|
+
if (imageExtensions.includes(ext)) {
|
|
54
|
+
return 'image';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return codeExtensions[ext] || 'Unknown';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getMimeType(filePath) {
|
|
61
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
62
|
+
const mimeTypes = {
|
|
63
|
+
'.jpg': 'image/jpeg',
|
|
64
|
+
'.jpeg': 'image/jpeg',
|
|
65
|
+
'.png': 'image/png',
|
|
66
|
+
'.gif': 'image/gif',
|
|
67
|
+
'.webp': 'image/webp',
|
|
68
|
+
'.bmp': 'image/bmp',
|
|
69
|
+
'.svg': 'image/svg+xml'
|
|
70
|
+
};
|
|
71
|
+
return mimeTypes[ext] || 'image/jpeg';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// File reading and processing
|
|
75
|
+
async function readFileContent(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
const stats = await fs.stat(filePath);
|
|
78
|
+
if (stats.isDirectory()) {
|
|
79
|
+
throw new Error('Path is a directory, not a file');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check file size (limit to 1MB for safety)
|
|
83
|
+
if (stats.size > 1024 * 1024) {
|
|
84
|
+
throw new Error('File too large. Maximum size is 1MB');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
88
|
+
return content;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error.code === 'ENOENT') {
|
|
91
|
+
throw new Error(`File not found: ${filePath}`);
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
getFileType,
|
|
99
|
+
getMimeType,
|
|
100
|
+
readFileContent
|
|
101
|
+
};
|