primocode 7.0.2 → 7.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/README.md +78 -76
- package/bin/primocode.js +243 -225
- package/lib/act.js +127 -74
- package/lib/api.js +110 -17
- package/lib/browser.js +492 -492
- package/lib/config.js +105 -52
- package/lib/desktop.js +142 -142
- package/lib/tools.js +147 -104
- package/lib/ui.js +165 -161
- package/package.json +5 -3
package/lib/tools.js
CHANGED
|
@@ -1,104 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ferramentas de arquivo, shell e browser para o modo Act.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { exec } = require('child_process');
|
|
10
|
-
const os = require('os');
|
|
11
|
-
|
|
12
|
-
function fmtBytes(n) { if (n<1024) return `${n} B`; if (n<1024*1024) return `${(n/1024).toFixed(1)} KB`; return `${(n/(1024*1024)).toFixed(1)} MB`; }
|
|
13
|
-
|
|
14
|
-
function treeDir(dir, maxDepth=2, currentDepth=0, prefix='') {
|
|
15
|
-
if (currentDepth>maxDepth) return '';
|
|
16
|
-
let result='';
|
|
17
|
-
try { const items=fs.readdirSync(dir); const dirs=[], files=[]; for (const item of items) { if (item.startsWith('.') && item!=='.env') continue; const fullPath=path.join(dir,item); const stat=fs.statSync(fullPath); if (stat.isDirectory()) dirs.push(item); else files.push(item); } dirs.sort(); files.sort(); const all=[...dirs,...files]; for (let i=0;i<all.length;i++) { const item=all[i]; const isLast=i===all.length-1; const connector=isLast?'└── ':'├── '; result+=prefix+connector+item+'\n'; const fullPath=path.join(dir,item); if (fs.statSync(fullPath).isDirectory() && currentDepth<maxDepth) { const newPrefix=prefix+(isLast?' ':'│ '); result+=treeDir(fullPath,maxDepth,currentDepth+1,newPrefix); } } } catch { return ''; }
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function applyCodeBlocks(response) {
|
|
22
|
-
const regex = /```[\w]*\n([\s\S]*?)```/g;
|
|
23
|
-
const applied = [];
|
|
24
|
-
let match;
|
|
25
|
-
while ((match = regex.exec(response)) !== null) {
|
|
26
|
-
const block = match[1];
|
|
27
|
-
const filenameMatch = block.match(/\/\/\s*filename:\s*(.+)/);
|
|
28
|
-
if (filenameMatch) { const filename=filenameMatch[1].trim(); const content=block.replace(/\/\/\s*filename:.+\n?/,''); const created=!fs.existsSync(filename); fs.mkdirSync(path.dirname(filename),{recursive:true}); fs.writeFileSync(filename,content,'utf-8'); applied.push({path:filename,created}); }
|
|
29
|
-
}
|
|
30
|
-
return applied;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function runShell(command, timeout=60000) {
|
|
34
|
-
return new Promise((resolve) => {
|
|
35
|
-
const child = exec(command, {timeout}, (error, stdout, stderr) => {
|
|
36
|
-
if (error) resolve({ok:false, code:error.code, stdout, stderr, killed:error.killed});
|
|
37
|
-
else resolve({ok:true, code:0, stdout, stderr});
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function openInBrowser(target) {
|
|
43
|
-
const platform=os.platform(); let cmd;
|
|
44
|
-
if (platform==='darwin') cmd=`open "${target}"`;
|
|
45
|
-
else if (platform==='win32') cmd=`start "" "${target}"`;
|
|
46
|
-
else cmd=`xdg-open "${target}"`;
|
|
47
|
-
try { await runShell(cmd); return {ok:true, opener:cmd.split(' ')[0]}; } catch (e) { return {ok:false, error:e.message}; }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function runInNewTerminal(command, cwd) {
|
|
51
|
-
const platform=os.platform(); let cmd;
|
|
52
|
-
if (platform==='darwin') cmd=`osascript -e 'tell application "Terminal" to do script "cd ${cwd||'.'} && ${command}"'`;
|
|
53
|
-
else if (platform==='win32') cmd=`start cmd /k "cd ${cwd||'.'} && ${command}"`;
|
|
54
|
-
else { const terminals=['gnome-terminal','konsole','xterm','terminator']; let term='xterm'; for (const t of terminals) { try { require('child_process').execSync(`which ${t}`,{stdio:'ignore'}); term=t; break; } catch {} } cmd=`${term} -e "bash -c 'cd ${cwd||'.'} && ${command}; exec bash'"`; }
|
|
55
|
-
try { await runShell(cmd); return {ok:true, opener:cmd.split(' ')[0]}; } catch (e) { return {ok:false, error:e.message}; }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Ferramentas de arquivo, shell e browser para o modo Act.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { exec } = require('child_process');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
function fmtBytes(n) { if (n<1024) return `${n} B`; if (n<1024*1024) return `${(n/1024).toFixed(1)} KB`; return `${(n/(1024*1024)).toFixed(1)} MB`; }
|
|
13
|
+
|
|
14
|
+
function treeDir(dir, maxDepth=2, currentDepth=0, prefix='') {
|
|
15
|
+
if (currentDepth>maxDepth) return '';
|
|
16
|
+
let result='';
|
|
17
|
+
try { const items=fs.readdirSync(dir); const dirs=[], files=[]; for (const item of items) { if (item.startsWith('.') && item!=='.env') continue; const fullPath=path.join(dir,item); const stat=fs.statSync(fullPath); if (stat.isDirectory()) dirs.push(item); else files.push(item); } dirs.sort(); files.sort(); const all=[...dirs,...files]; for (let i=0;i<all.length;i++) { const item=all[i]; const isLast=i===all.length-1; const connector=isLast?'└── ':'├── '; result+=prefix+connector+item+'\n'; const fullPath=path.join(dir,item); if (fs.statSync(fullPath).isDirectory() && currentDepth<maxDepth) { const newPrefix=prefix+(isLast?' ':'│ '); result+=treeDir(fullPath,maxDepth,currentDepth+1,newPrefix); } } } catch { return ''; }
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function applyCodeBlocks(response) {
|
|
22
|
+
const regex = /```[\w]*\n([\s\S]*?)```/g;
|
|
23
|
+
const applied = [];
|
|
24
|
+
let match;
|
|
25
|
+
while ((match = regex.exec(response)) !== null) {
|
|
26
|
+
const block = match[1];
|
|
27
|
+
const filenameMatch = block.match(/\/\/\s*filename:\s*(.+)/);
|
|
28
|
+
if (filenameMatch) { const filename=filenameMatch[1].trim(); const content=block.replace(/\/\/\s*filename:.+\n?/,''); const created=!fs.existsSync(filename); fs.mkdirSync(path.dirname(filename),{recursive:true}); fs.writeFileSync(filename,content,'utf-8'); applied.push({path:filename,created}); }
|
|
29
|
+
}
|
|
30
|
+
return applied;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function runShell(command, timeout=60000, cwd) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const child = exec(command, {timeout, cwd: cwd || process.cwd()}, (error, stdout, stderr) => {
|
|
36
|
+
if (error) resolve({ok:false, code:error.code, stdout, stderr, killed:error.killed});
|
|
37
|
+
else resolve({ok:true, code:0, stdout, stderr});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function openInBrowser(target) {
|
|
43
|
+
const platform=os.platform(); let cmd;
|
|
44
|
+
if (platform==='darwin') cmd=`open "${target}"`;
|
|
45
|
+
else if (platform==='win32') cmd=`start "" "${target}"`;
|
|
46
|
+
else cmd=`xdg-open "${target}"`;
|
|
47
|
+
try { await runShell(cmd); return {ok:true, opener:cmd.split(' ')[0]}; } catch (e) { return {ok:false, error:e.message}; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runInNewTerminal(command, cwd) {
|
|
51
|
+
const platform=os.platform(); let cmd;
|
|
52
|
+
if (platform==='darwin') cmd=`osascript -e 'tell application "Terminal" to do script "cd ${cwd||'.'} && ${command}"'`;
|
|
53
|
+
else if (platform==='win32') cmd=`start cmd /k "cd ${cwd||'.'} && ${command}"`;
|
|
54
|
+
else { const terminals=['gnome-terminal','konsole','xterm','terminator']; let term='xterm'; for (const t of terminals) { try { require('child_process').execSync(`which ${t}`,{stdio:'ignore'}); term=t; break; } catch {} } cmd=`${term} -e "bash -c 'cd ${cwd||'.'} && ${command}; exec bash'"`; }
|
|
55
|
+
try { await runShell(cmd); return {ok:true, opener:cmd.split(' ')[0]}; } catch (e) { return {ok:false, error:e.message}; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Todo caminho vindo do modelo é resolvido DENTRO da pasta do projeto.
|
|
59
|
+
// Sem isso o agente espalha arquivo por qualquer lugar do disco — e um
|
|
60
|
+
// "../../.." no path viraria escrita fora da área de trabalho.
|
|
61
|
+
function resolveInProject(rawPath, projectDir) {
|
|
62
|
+
const base = projectDir || process.cwd();
|
|
63
|
+
const target = path.resolve(base, rawPath || '.');
|
|
64
|
+
const rel = path.relative(base, target);
|
|
65
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
66
|
+
throw new Error(`Caminho fora do projeto: ${rawPath}`);
|
|
67
|
+
}
|
|
68
|
+
return target;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function execute(name, args, ctx) {
|
|
72
|
+
const projectDir = (ctx && ctx.projectDir) || process.cwd();
|
|
73
|
+
args = args || {};
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
switch (name) {
|
|
77
|
+
case 'write_file': {
|
|
78
|
+
const filePath = resolveInProject(args.path, projectDir);
|
|
79
|
+
const content = args.content || '';
|
|
80
|
+
const created = !fs.existsSync(filePath);
|
|
81
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
82
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
83
|
+
return { ok: true, created, path: path.relative(projectDir, filePath), bytes: content.length };
|
|
84
|
+
}
|
|
85
|
+
case 'read_file': {
|
|
86
|
+
const filePath = resolveInProject(args.path, projectDir);
|
|
87
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
88
|
+
const truncated = content.length > 10000;
|
|
89
|
+
return { ok: true, content: truncated ? content.slice(0, 10000) : content, bytes: content.length, truncated };
|
|
90
|
+
}
|
|
91
|
+
case 'list_dir': {
|
|
92
|
+
const dirPath = resolveInProject(args.path, projectDir);
|
|
93
|
+
return { ok: true, tree: treeDir(dirPath, 2) };
|
|
94
|
+
}
|
|
95
|
+
case 'run_shell':
|
|
96
|
+
return runShell(args.command, args.timeout_ms || 60000, projectDir);
|
|
97
|
+
case 'run_in_new_terminal':
|
|
98
|
+
return runInNewTerminal(args.command, args.cwd || projectDir);
|
|
99
|
+
case 'open_in_browser': {
|
|
100
|
+
const target = /^https?:\/\//i.test(args.target || '')
|
|
101
|
+
? args.target
|
|
102
|
+
: resolveInProject(args.target, projectDir);
|
|
103
|
+
return openInBrowser(target);
|
|
104
|
+
}
|
|
105
|
+
default:
|
|
106
|
+
return { ok: false, error: `Tool não implementada: ${name}` };
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return { ok: false, error: e.message };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function uninstall() {
|
|
114
|
+
const os = require('os');
|
|
115
|
+
const path = require('path');
|
|
116
|
+
const { execSync } = require('child_process');
|
|
117
|
+
const fs = require('fs');
|
|
118
|
+
|
|
119
|
+
const home = os.homedir();
|
|
120
|
+
const configDir = path.join(home, '.primocode');
|
|
121
|
+
|
|
122
|
+
console.log('🗑️ Removendo PrimoCode...');
|
|
123
|
+
|
|
124
|
+
// Remover configurações
|
|
125
|
+
try {
|
|
126
|
+
if (fs.existsSync(configDir)) {
|
|
127
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
128
|
+
console.log(' ✅ Configurações removidas');
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.log(' ⚠️ Não foi possível remover configurações:', e.message);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Remover link global npm
|
|
135
|
+
try {
|
|
136
|
+
execSync('npm unlink -g primocode', { stdio: 'ignore' });
|
|
137
|
+
console.log(' ✅ Link global removido');
|
|
138
|
+
} catch {
|
|
139
|
+
// ignora se não estava linkado
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log('\n✅ PrimoCode removido com sucesso.');
|
|
143
|
+
console.log(' Para reinstalar: npm install -g primocode');
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { fmtBytes, treeDir, applyCodeBlocks, runShell, openInBrowser, runInNewTerminal, execute, uninstall };
|
package/lib/ui.js
CHANGED
|
@@ -1,161 +1,165 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI components — estilo Claude Code
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
|
|
9
|
-
const PALETTE = {
|
|
10
|
-
brand: '\x1b[38;5;183m',
|
|
11
|
-
brandDim: '\x1b[38;5;146m',
|
|
12
|
-
white: '\x1b[38;5;253m',
|
|
13
|
-
muted: '\x1b[38;5;245m',
|
|
14
|
-
dim: '\x1b[38;5;240m',
|
|
15
|
-
ok: '\x1b[38;5;114m',
|
|
16
|
-
warn: '\x1b[38;5;180m',
|
|
17
|
-
err: '\x1b[38;5;174m',
|
|
18
|
-
magenta: '\x1b[38;5;176m',
|
|
19
|
-
cyan: '\x1b[38;5;152m',
|
|
20
|
-
bold: '\x1b[1m',
|
|
21
|
-
reset: '\x1b[0m',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const c = {
|
|
25
|
-
...PALETTE,
|
|
26
|
-
brand: (s) => `${PALETTE.brand}${s}${PALETTE.reset}`,
|
|
27
|
-
brandDim: (s) => `${PALETTE.brandDim}${s}${PALETTE.reset}`,
|
|
28
|
-
white: (s) => `${PALETTE.white}${s}${PALETTE.reset}`,
|
|
29
|
-
muted: (s) => `${PALETTE.muted}${s}${PALETTE.reset}`,
|
|
30
|
-
dim: (s) => `${PALETTE.dim}${s}${PALETTE.reset}`,
|
|
31
|
-
ok: (s) => `${PALETTE.ok}${s}${PALETTE.reset}`,
|
|
32
|
-
warn: (s) => `${PALETTE.warn}${s}${PALETTE.reset}`,
|
|
33
|
-
err: (s) => `${PALETTE.err}${s}${PALETTE.reset}`,
|
|
34
|
-
magenta: (s) => `${PALETTE.magenta}${s}${PALETTE.reset}`,
|
|
35
|
-
cyan: (s) => `${PALETTE.cyan}${s}${PALETTE.reset}`,
|
|
36
|
-
bold: (s) => `${PALETTE.bold}${s}${PALETTE.reset}`,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
'',
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
rl
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
1
|
+
/**
|
|
2
|
+
* UI components — estilo Claude Code
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
|
|
9
|
+
const PALETTE = {
|
|
10
|
+
brand: '\x1b[38;5;183m',
|
|
11
|
+
brandDim: '\x1b[38;5;146m',
|
|
12
|
+
white: '\x1b[38;5;253m',
|
|
13
|
+
muted: '\x1b[38;5;245m',
|
|
14
|
+
dim: '\x1b[38;5;240m',
|
|
15
|
+
ok: '\x1b[38;5;114m',
|
|
16
|
+
warn: '\x1b[38;5;180m',
|
|
17
|
+
err: '\x1b[38;5;174m',
|
|
18
|
+
magenta: '\x1b[38;5;176m',
|
|
19
|
+
cyan: '\x1b[38;5;152m',
|
|
20
|
+
bold: '\x1b[1m',
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const c = {
|
|
25
|
+
...PALETTE,
|
|
26
|
+
brand: (s) => `${PALETTE.brand}${s}${PALETTE.reset}`,
|
|
27
|
+
brandDim: (s) => `${PALETTE.brandDim}${s}${PALETTE.reset}`,
|
|
28
|
+
white: (s) => `${PALETTE.white}${s}${PALETTE.reset}`,
|
|
29
|
+
muted: (s) => `${PALETTE.muted}${s}${PALETTE.reset}`,
|
|
30
|
+
dim: (s) => `${PALETTE.dim}${s}${PALETTE.reset}`,
|
|
31
|
+
ok: (s) => `${PALETTE.ok}${s}${PALETTE.reset}`,
|
|
32
|
+
warn: (s) => `${PALETTE.warn}${s}${PALETTE.reset}`,
|
|
33
|
+
err: (s) => `${PALETTE.err}${s}${PALETTE.reset}`,
|
|
34
|
+
magenta: (s) => `${PALETTE.magenta}${s}${PALETTE.reset}`,
|
|
35
|
+
cyan: (s) => `${PALETTE.cyan}${s}${PALETTE.reset}`,
|
|
36
|
+
bold: (s) => `${PALETTE.bold}${s}${PALETTE.reset}`,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Primo — o mascote. Robozinho de antena, 4 linhas.
|
|
40
|
+
const MASCOT = [' ╷ ', '╭─┴─╮', '│◕ ◕│', '╰───╯'];
|
|
41
|
+
|
|
42
|
+
function welcomeBox(version, project) {
|
|
43
|
+
const side = [
|
|
44
|
+
c.brand(c.bold('PrimoCode')),
|
|
45
|
+
c.muted('v' + version),
|
|
46
|
+
c.muted(project ? `projeto: ${project}` : 'nenhum projeto ativo'),
|
|
47
|
+
];
|
|
48
|
+
const rows = MASCOT.map((art, i) => {
|
|
49
|
+
const text = i === 0 ? '' : side[i - 1] || '';
|
|
50
|
+
return ` ${c.brand(art)} ${text}`;
|
|
51
|
+
});
|
|
52
|
+
return [
|
|
53
|
+
'',
|
|
54
|
+
...rows,
|
|
55
|
+
'',
|
|
56
|
+
` ${c.magenta('●')} ${c.muted('Primo Code')} ${c.brand('●')} ${c.muted('Chat')} ${c.dim('/help para comandos')}`,
|
|
57
|
+
'',
|
|
58
|
+
].join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class Spinner {
|
|
62
|
+
constructor(text='pensando') {
|
|
63
|
+
this.text = text;
|
|
64
|
+
this.frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
65
|
+
this.i = 0; this.timer = null;
|
|
66
|
+
}
|
|
67
|
+
start() {
|
|
68
|
+
// Fora de um terminal (pipe, CI) a animação só sujaria a saída.
|
|
69
|
+
if (!process.stdout.isTTY) return this;
|
|
70
|
+
this.timer = setInterval(() => {
|
|
71
|
+
process.stdout.write(`\r ${c.muted(this.frames[this.i])} ${c.muted(this.text)}`);
|
|
72
|
+
this.i = (this.i+1) % this.frames.length;
|
|
73
|
+
}, 80);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
stop() {
|
|
77
|
+
if (this.timer) { clearInterval(this.timer); this.timer = null; process.stdout.write(`\r${' '.repeat(80)}\r`); }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Barra de progresso estilo Claude Code — silenciosa, elegante
|
|
82
|
+
class ProgressBar {
|
|
83
|
+
constructor(label = 'Instalando') {
|
|
84
|
+
this.label = label;
|
|
85
|
+
this.width = 30;
|
|
86
|
+
this.progress = 0;
|
|
87
|
+
this.timer = null;
|
|
88
|
+
this.done = false;
|
|
89
|
+
}
|
|
90
|
+
start() {
|
|
91
|
+
process.stdout.write(` ${c.muted(this.label)} [`);
|
|
92
|
+
this.timer = setInterval(() => {
|
|
93
|
+
if (this.done) return;
|
|
94
|
+
this.progress = Math.min(this.progress + 1, this.width);
|
|
95
|
+
const filled = '█'.repeat(this.progress);
|
|
96
|
+
const empty = '░'.repeat(this.width - this.progress);
|
|
97
|
+
process.stdout.write(`\r ${c.muted(this.label)} [${c.brand(filled)}${c.dim(empty)}] ${c.muted(Math.round((this.progress/this.width)*100) + '%')}`);
|
|
98
|
+
}, 200);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
tick() {
|
|
102
|
+
this.progress = Math.min(this.progress + 2, this.width);
|
|
103
|
+
const filled = '█'.repeat(this.progress);
|
|
104
|
+
const empty = '░'.repeat(this.width - this.progress);
|
|
105
|
+
process.stdout.write(`\r ${c.muted(this.label)} [${c.brand(filled)}${c.dim(empty)}] ${c.muted(Math.round((this.progress/this.width)*100) + '%')}`);
|
|
106
|
+
}
|
|
107
|
+
stop() {
|
|
108
|
+
this.done = true;
|
|
109
|
+
if (this.timer) { clearInterval(this.timer); this.timer = null; }
|
|
110
|
+
process.stdout.write(`\r ${c.muted(this.label)} [${c.ok('██████████ DONE ██████████')}] ${c.ok('100%')}\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function actionHeader(name, args) {
|
|
115
|
+
const nice = {
|
|
116
|
+
read_file:'Read', write_file:'Write', list_dir:'List', run_shell:'Bash',
|
|
117
|
+
run_in_new_terminal:'Terminal', open_in_browser:'Open', browser_open:'Browser',
|
|
118
|
+
browser_navigate:'Navigate', browser_click:'Click', browser_type:'Type',
|
|
119
|
+
browser_key:'Key', browser_wait_for:'Wait', browser_wait:'Wait',
|
|
120
|
+
browser_screenshot:'Screenshot', browser_eval:'Eval', browser_scroll:'Scroll',
|
|
121
|
+
browser_url:'URL', finish:'Finish',
|
|
122
|
+
desktop_click:'Click', desktop_move:'Move', desktop_drag:'Drag',
|
|
123
|
+
desktop_type:'Type', desktop_key:'Key', desktop_scroll:'Scroll',
|
|
124
|
+
desktop_screenshot:'Screenshot', desktop_screen_size:'Screen',
|
|
125
|
+
};
|
|
126
|
+
const label = nice[name] || name;
|
|
127
|
+
const argStr = args ? (args.path || args.command || args.target || args.url || args.selector || JSON.stringify(args).slice(0,60)) : '';
|
|
128
|
+
return ` ${c.dim('⎿')} ${c.brand(label)}${argStr ? ' '+c.muted(argStr) : ''}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function toolLine(text) { return ` ${c.dim('⎿')} ${c.muted(text)}`; }
|
|
132
|
+
|
|
133
|
+
function toolResult(result) {
|
|
134
|
+
if (!result) return '';
|
|
135
|
+
if (result.ok === false) return c.err(` ✗ ${result.error || 'failed'}`);
|
|
136
|
+
if (result.created !== undefined) return c.ok(` ${result.created?'created':'updated'} · ${fmtBytes(result.bytes||0)}`);
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function fmtBytes(n) {
|
|
141
|
+
if (n < 1024) return `${n} B`;
|
|
142
|
+
if (n < 1024*1024) return `${(n/1024).toFixed(1)} KB`;
|
|
143
|
+
return `${(n/(1024*1024)).toFixed(1)} MB`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function renderMarkdown(text) {
|
|
147
|
+
return text.replace(/\*\*([^*]+)\*\*/g,'$1').replace(/__([^_]+)__/g,'$1').replace(/`([^`]+)`/g,'$1').replace(/^#{1,6}\s+/gm,'');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Pergunta Yes/No — aguarda resposta real do usuário
|
|
151
|
+
function askYesNo(question) {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
const rl = readline.createInterface({
|
|
154
|
+
input: process.stdin,
|
|
155
|
+
output: process.stdout,
|
|
156
|
+
});
|
|
157
|
+
rl.question(`${c.brand('?')} ${question} ${c.muted('(Y/n)')} `, (answer) => {
|
|
158
|
+
rl.close();
|
|
159
|
+
const a = (answer || '').trim().toLowerCase();
|
|
160
|
+
resolve(a === 'y' || a === 'yes' || a === '');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = { c, welcomeBox, Spinner, ProgressBar, actionHeader, toolLine, toolResult, fmtBytes, renderMarkdown, askYesNo };
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "7.0
|
|
4
|
-
"description": "PrimoCode
|
|
3
|
+
"version": "7.1.0",
|
|
4
|
+
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modo primocode cria arquivos, controla o desktop e navegador. Modo chat é conversa pura.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
|
-
"bin":
|
|
6
|
+
"bin": {
|
|
7
|
+
"primocode": "bin/primocode.js"
|
|
8
|
+
},
|
|
7
9
|
"scripts": {
|
|
8
10
|
"start": "node bin/primocode.js"
|
|
9
11
|
},
|