primocode 9.8.0-beta.5 → 9.8.0-beta.7
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 +15 -4
- package/app/ansi.js +55 -0
- package/app/ide.css +345 -0
- package/app/ide.html +183 -0
- package/app/ide.js +639 -0
- package/bin/primocode.js +338 -88
- package/kits/acolhedor/index.html +130 -0
- package/kits/acolhedor/script.js +25 -0
- package/kits/acolhedor/style.css +146 -0
- package/kits/editorial/index.html +115 -0
- package/kits/editorial/script.js +19 -0
- package/kits/editorial/style.css +112 -0
- package/kits/premium/index.html +83 -0
- package/kits/premium/script.js +11 -0
- package/kits/premium/style.css +87 -0
- package/kits/vitrine/index.html +108 -0
- package/kits/vitrine/script.js +20 -0
- package/kits/vitrine/style.css +117 -0
- package/lib/act.js +66 -7
- package/lib/app.js +41 -6
- package/lib/browser.js +79 -3
- package/lib/checkpoint-git.js +218 -0
- package/lib/checkpoint.js +159 -44
- package/lib/comandos-extra.js +481 -0
- package/lib/config.js +5 -2
- package/lib/ide.js +337 -0
- package/lib/kits.js +165 -54
- package/lib/pre-leitura.js +118 -0
- package/lib/repeticao.js +4 -2
- package/lib/skills.js +6 -1
- package/lib/tempos.js +62 -0
- package/lib/ui.js +171 -17
- package/package.json +2 -2
- package/skills/apresentacao-pptx/SKILL.md +52 -0
- package/skills/cartaz-de-arte/LICENSE.txt +202 -0
- package/skills/cartaz-de-arte/SKILL.md +47 -0
- package/skills/criar-skill/LICENSE.txt +202 -0
- package/skills/criar-skill/SKILL.md +49 -0
- package/skills/depurar-de-verdade/LICENSE.txt +21 -0
- package/skills/depurar-de-verdade/SKILL.md +14 -5
- package/skills/documento-word/SKILL.md +57 -0
- package/skills/interface-revisada/SKILL.md +47 -0
- package/skills/pdf-bonito/SKILL.md +48 -0
- package/skills/planilha-excel/SKILL.md +47 -0
- package/skills/react-sem-dor/SKILL.md +48 -0
- package/skills/revisao-antes-de-entregar/LICENSE.txt +21 -0
- package/skills/revisao-antes-de-entregar/SKILL.md +6 -1
- package/skills/temas-prontos/LICENSE.txt +202 -0
- package/skills/temas-prontos/SKILL.md +50 -0
- package/skills/testar-o-site/LICENSE.txt +202 -0
- package/skills/testar-o-site/SKILL.md +46 -0
- package/skills/video-no-remotion/SKILL.md +48 -0
package/lib/checkpoint.js
CHANGED
|
@@ -13,9 +13,18 @@
|
|
|
13
13
|
* agente só avisa `antesDeMudar(arquivo, pasta)` — o estado fica aqui, no
|
|
14
14
|
* módulo, e o subagente (mesmo processo) cai no mesmo checkpoint.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Dois jeitos de guardar, o melhor que a máquina permitir:
|
|
17
|
+
* COM git — no `iniciar`, uma foto do projeto INTEIRO num git sombra
|
|
18
|
+
* (lib/checkpoint-git.js). Cobre o que o run_shell apaga, move ou
|
|
19
|
+
* gera, que a tool de arquivo nunca vê.
|
|
20
|
+
* SEM git — o de antes: a cópia de cada arquivo que as tools de arquivo
|
|
21
|
+
* vão mexer. Também cobre, com git, o que a foto não pega
|
|
22
|
+
* (arquivo ignorado, como dist/, e pasta fora do projeto).
|
|
23
|
+
*
|
|
24
|
+
* Onde fica: ~/.primocode/checkpoints/<projeto>-<hash>/
|
|
25
|
+
* git/ o repositório sombra (só com git)
|
|
26
|
+
* <id>/manifest.json o pedido, a hora, o sha da foto e os arquivos
|
|
27
|
+
* <id>/0, 1, 2… a cópia original de cada arquivo guardado um a um
|
|
19
28
|
* Só os últimos MAXIMO por projeto: é rede de segurança, não arquivo morto.
|
|
20
29
|
*/
|
|
21
30
|
|
|
@@ -25,13 +34,20 @@ const fs = require('fs');
|
|
|
25
34
|
const os = require('os');
|
|
26
35
|
const path = require('path');
|
|
27
36
|
const crypto = require('crypto');
|
|
37
|
+
const foto = require('./checkpoint-git');
|
|
28
38
|
|
|
29
39
|
const MAXIMO = 20;
|
|
30
40
|
/** As tools de arquivo que mudam o disco. run_shell fica de fora: não dá para
|
|
31
|
-
saber, antes de rodar, que arquivos um comando vai tocar
|
|
41
|
+
saber, antes de rodar, que arquivos um comando vai tocar — quem cobre o
|
|
42
|
+
run_shell é a foto do projeto inteiro, tirada antes do pedido. */
|
|
32
43
|
const MUDAM_ARQUIVO = new Set(['write_file', 'edit_file', 'multi_edit']);
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
/* O pedido em andamento:
|
|
46
|
+
{ pedido, git: { projectDir, gitDir, dir, manifest } | null,
|
|
47
|
+
arq: { dir, manifest } | null }
|
|
48
|
+
`git` é o checkpoint com foto; `arq` é o por arquivo, para o que cai fora
|
|
49
|
+
da foto (ou para tudo, quando não há git). */
|
|
50
|
+
let atual = null;
|
|
35
51
|
|
|
36
52
|
/* os.homedir() lido na hora, não no require: o teste troca o HOME. */
|
|
37
53
|
function raiz() { return path.join(os.homedir(), '.primocode', 'checkpoints'); }
|
|
@@ -45,46 +61,94 @@ function pastaDoProjeto(projectDir) {
|
|
|
45
61
|
return path.join(raiz(), `${nome}-${hash}`);
|
|
46
62
|
}
|
|
47
63
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
/** Pasta que merece foto. A casa da pessoa e o ~/primocode sem projeto
|
|
65
|
+
ficam de fora: fotografar o disco inteiro a cada pedido seria lento,
|
|
66
|
+
encheria o ~/.primocode e desfaria coisas que não são do pedido. */
|
|
67
|
+
function podeFotografar(projectDir) {
|
|
68
|
+
if (!projectDir || !foto.temGit()) return false;
|
|
69
|
+
const abs = path.resolve(projectDir);
|
|
70
|
+
const casa = path.resolve(os.homedir());
|
|
71
|
+
if (abs === casa || abs === path.join(casa, 'primocode') || abs === path.parse(abs).root) return false;
|
|
72
|
+
try { return fs.statSync(abs).isDirectory(); } catch { return false; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Uma pasta de checkpoint nova. O id é sempre maior que o anterior: a
|
|
76
|
+
ordem dos ids É a ordem dos pedidos. */
|
|
77
|
+
function novoCheckpoint(projectDir, extra) {
|
|
78
|
+
const base = pastaDoProjeto(projectDir);
|
|
79
|
+
fs.mkdirSync(base, { recursive: true });
|
|
80
|
+
const ultimo = fs.readdirSync(base).filter((n) => /^\d+$/.test(n)).reduce((m, n) => Math.max(m, Number(n)), 0);
|
|
81
|
+
const dir = path.join(base, String(Math.max(Date.now(), ultimo + 1)));
|
|
82
|
+
fs.mkdirSync(dir);
|
|
83
|
+
const manifest = { pedido: atual.pedido, quando: new Date().toISOString(),
|
|
84
|
+
projectDir: path.resolve(projectDir), ...extra, arquivos: [] };
|
|
85
|
+
const cp = { dir, manifest };
|
|
86
|
+
gravarManifest(cp);
|
|
87
|
+
podar(base);
|
|
88
|
+
return cp;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function gravarManifest(cp) {
|
|
92
|
+
fs.writeFileSync(path.join(cp.dir, 'manifest.json'), JSON.stringify(cp.manifest, null, 2));
|
|
52
93
|
}
|
|
53
94
|
|
|
54
|
-
|
|
95
|
+
/** Começa o checkpoint de um pedido. Com git e uma pasta de projeto, a foto
|
|
96
|
+
é tirada AGORA — antes de qualquer tool rodar. Sem git (ou sem pasta),
|
|
97
|
+
nada vai ao disco ainda: pedido que não mexe em arquivo não deixa rastro. */
|
|
98
|
+
function iniciar(pedido, projectDir) {
|
|
99
|
+
atual = { pedido: String(pedido || '').replace(/\s+/g, ' ').trim(), git: null, arq: null };
|
|
100
|
+
if (!podeFotografar(projectDir)) return;
|
|
101
|
+
try {
|
|
102
|
+
const abs = path.resolve(projectDir);
|
|
103
|
+
const gitDir = path.join(pastaDoProjeto(abs), 'git');
|
|
104
|
+
const sha = foto.fotografar(gitDir, abs, atual.pedido);
|
|
105
|
+
if (sha) atual.git = { projectDir: abs, gitDir, ...novoCheckpoint(abs, { git: { sha } }) };
|
|
106
|
+
} catch { /* sem foto, o checkpoint por arquivo segura o pedido */ }
|
|
107
|
+
}
|
|
55
108
|
|
|
56
|
-
|
|
57
|
-
|
|
109
|
+
/** Fecha o pedido. Com foto, anota o que mudou (é o que o `/desfazer lista`
|
|
110
|
+
mostra) — e, se nada mudou, o checkpoint some: uma pergunta não pode
|
|
111
|
+
ocupar o lugar de um pedido que o /desfazer deveria voltar. */
|
|
112
|
+
function encerrar() {
|
|
113
|
+
const g = atual && atual.git;
|
|
114
|
+
atual = null;
|
|
115
|
+
if (!g) return;
|
|
116
|
+
try {
|
|
117
|
+
const m = foto.mudancas(g.gitDir, g.projectDir, g.manifest.git.sha);
|
|
118
|
+
if (!m.length && !g.manifest.arquivos.length) { fs.rmSync(g.dir, { recursive: true, force: true }); return; }
|
|
119
|
+
g.manifest.mudancas = m;
|
|
120
|
+
gravarManifest(g);
|
|
121
|
+
} catch { /* sem a conta, a lista calcula na hora */ }
|
|
58
122
|
}
|
|
59
123
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
124
|
+
/** Guarda o original de um arquivo no checkpoint `cp`, uma vez só: a segunda
|
|
125
|
+
edição do mesmo arquivo não pode sobrescrever o original já editado. */
|
|
126
|
+
function guardarOriginal(cp, abs) {
|
|
127
|
+
if (cp.manifest.arquivos.some((a) => a.caminho === abs)) return;
|
|
128
|
+
const existia = fs.existsSync(abs) && fs.statSync(abs).isFile();
|
|
129
|
+
const item = { caminho: abs, existia };
|
|
130
|
+
if (existia) {
|
|
131
|
+
item.copia = String(cp.manifest.arquivos.length);
|
|
132
|
+
fs.copyFileSync(abs, path.join(cp.dir, item.copia));
|
|
133
|
+
}
|
|
134
|
+
cp.manifest.arquivos.push(item);
|
|
135
|
+
gravarManifest(cp);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Chamado pelo loop ANTES de uma tool de arquivo rodar. Se a foto do pedido
|
|
139
|
+
já cobre o arquivo, não há o que fazer; senão, guarda o original. */
|
|
63
140
|
function antesDeMudar(caminhoAbs, projectDir) {
|
|
64
141
|
if (!atual || !caminhoAbs) return;
|
|
65
142
|
try {
|
|
66
|
-
if (!atual.dir) {
|
|
67
|
-
const base = pastaDoProjeto(projectDir);
|
|
68
|
-
fs.mkdirSync(base, { recursive: true });
|
|
69
|
-
// Sempre maior que o anterior: a ordem dos ids É a ordem dos pedidos.
|
|
70
|
-
const ultimo = fs.readdirSync(base).filter((n) => /^\d+$/.test(n)).reduce((m, n) => Math.max(m, Number(n)), 0);
|
|
71
|
-
const id = String(Math.max(Date.now(), ultimo + 1));
|
|
72
|
-
atual.dir = path.join(base, id);
|
|
73
|
-
fs.mkdirSync(atual.dir);
|
|
74
|
-
atual.manifest = { pedido: atual.pedido, quando: new Date().toISOString(),
|
|
75
|
-
projectDir: path.resolve(projectDir), arquivos: [] };
|
|
76
|
-
podar(base);
|
|
77
|
-
}
|
|
78
143
|
const abs = path.resolve(caminhoAbs);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
fs.copyFileSync(abs, path.join(atual.dir, item.copia));
|
|
144
|
+
const g = atual.git;
|
|
145
|
+
if (g && (abs + path.sep).startsWith(g.projectDir + path.sep)) {
|
|
146
|
+
if (!foto.ignorado(g.gitDir, g.projectDir, abs)) return;
|
|
147
|
+
guardarOriginal(g, abs); // dist/, .gitignore: fora da foto, mas com volta
|
|
148
|
+
return;
|
|
85
149
|
}
|
|
86
|
-
atual.
|
|
87
|
-
|
|
150
|
+
if (!atual.arq) atual.arq = novoCheckpoint(projectDir || path.dirname(abs), {});
|
|
151
|
+
guardarOriginal(atual.arq, abs);
|
|
88
152
|
} catch { /* checkpoint nunca derruba o trabalho do agente */ }
|
|
89
153
|
}
|
|
90
154
|
|
|
@@ -93,7 +157,23 @@ function podar(base) {
|
|
|
93
157
|
for (const velho of ids.slice(MAXIMO)) fs.rmSync(path.join(base, velho), { recursive: true, force: true });
|
|
94
158
|
}
|
|
95
159
|
|
|
96
|
-
|
|
160
|
+
const ACAO_DA_FOTO = { A: 'criado', M: 'mudou', D: 'apagado' };
|
|
161
|
+
|
|
162
|
+
/** O que o pedido mudou, num formato só para os dois jeitos de guardar. */
|
|
163
|
+
function mudancasDe(cp) {
|
|
164
|
+
const vistos = new Map();
|
|
165
|
+
let daFoto = cp.mudancas;
|
|
166
|
+
if (cp.git && !daFoto) {
|
|
167
|
+
// Pedido interrompido (ou o que está rodando): a conta é feita agora.
|
|
168
|
+
try { daFoto = foto.mudancas(path.join(path.dirname(cp.dir), 'git'), cp.projectDir, cp.git.sha); } catch { daFoto = []; }
|
|
169
|
+
}
|
|
170
|
+
for (const m of daFoto || []) vistos.set(path.join(cp.projectDir, m.rel), ACAO_DA_FOTO[m.status] || 'mudou');
|
|
171
|
+
for (const a of cp.arquivos || []) if (!vistos.has(a.caminho)) vistos.set(a.caminho, a.existia ? 'mudou' : 'criado');
|
|
172
|
+
return [...vistos].map(([caminho, acao]) => ({ caminho, acao }));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Os checkpoints do projeto, o mais novo primeiro. Cada um traz `mudou`:
|
|
176
|
+
os arquivos que o pedido criou, mudou ou apagou. */
|
|
97
177
|
function lista(projectDir) {
|
|
98
178
|
const base = pastaDoProjeto(projectDir);
|
|
99
179
|
let ids = [];
|
|
@@ -101,34 +181,69 @@ function lista(projectDir) {
|
|
|
101
181
|
return ids.sort((a, b) => Number(b) - Number(a)).map((id) => {
|
|
102
182
|
try {
|
|
103
183
|
const m = JSON.parse(fs.readFileSync(path.join(base, id, 'manifest.json'), 'utf8'));
|
|
104
|
-
|
|
184
|
+
const cp = { id, dir: path.join(base, id), ...m };
|
|
185
|
+
cp.mudou = mudancasDe(cp);
|
|
186
|
+
return cp;
|
|
105
187
|
} catch { return null; }
|
|
106
188
|
}).filter(Boolean);
|
|
107
189
|
}
|
|
108
190
|
|
|
191
|
+
/** O que o /desfazer N vai fazer, para a pessoa ler ANTES de confirmar.
|
|
192
|
+
Com foto, a conta é contra o disco de agora (inclui o que mudou depois do
|
|
193
|
+
pedido, que a restauração também volta). */
|
|
194
|
+
function previa(projectDir, n = 1) {
|
|
195
|
+
const cps = lista(projectDir).slice(0, Math.max(1, n));
|
|
196
|
+
const vistos = new Map();
|
|
197
|
+
const maisVelhoComFoto = [...cps].reverse().find((cp) => cp.git);
|
|
198
|
+
if (maisVelhoComFoto) {
|
|
199
|
+
try {
|
|
200
|
+
for (const m of foto.mudancas(path.join(path.dirname(maisVelhoComFoto.dir), 'git'),
|
|
201
|
+
maisVelhoComFoto.projectDir, maisVelhoComFoto.git.sha)) {
|
|
202
|
+
vistos.set(path.join(maisVelhoComFoto.projectDir, m.rel), m.status === 'A' ? 'apaga (é novo)' : m.status === 'D' ? 'recria (tinha sumido)' : 'volta ao original');
|
|
203
|
+
}
|
|
204
|
+
} catch {}
|
|
205
|
+
}
|
|
206
|
+
for (const cp of cps) for (const a of cp.arquivos || []) {
|
|
207
|
+
if (!vistos.has(a.caminho)) vistos.set(a.caminho, a.existia ? 'volta ao original' : 'apaga (era novo)');
|
|
208
|
+
}
|
|
209
|
+
return [...vistos].map(([caminho, acao]) => ({ caminho, acao }));
|
|
210
|
+
}
|
|
211
|
+
|
|
109
212
|
/** Desfaz os N pedidos mais recentes, do mais novo para o mais velho — assim,
|
|
110
213
|
se dois pedidos mexeram no mesmo arquivo, o que sobra é o original do mais
|
|
111
214
|
antigo. Cada checkpoint desfeito é apagado: o próximo /desfazer volta mais
|
|
112
215
|
um pedido, em vez de repetir o mesmo. */
|
|
113
216
|
function desfazer(projectDir, n = 1) {
|
|
114
|
-
const feitos =
|
|
217
|
+
const feitos = new Map(); // um arquivo, uma linha: vale a última ação
|
|
218
|
+
const anotar = (caminho, acao, erro) => { feitos.delete(caminho); feitos.set(caminho, erro ? { caminho, acao, erro } : { caminho, acao }); };
|
|
115
219
|
for (const cp of lista(projectDir).slice(0, Math.max(1, n))) {
|
|
220
|
+
if (cp.git) {
|
|
221
|
+
try {
|
|
222
|
+
for (const f of foto.restaurar(path.join(path.dirname(cp.dir), 'git'), cp.projectDir, cp.git.sha)) {
|
|
223
|
+
anotar(path.join(cp.projectDir, f.rel), f.acao, f.erro);
|
|
224
|
+
}
|
|
225
|
+
} catch (e) { anotar(cp.projectDir, 'falhou', e.message); }
|
|
226
|
+
}
|
|
116
227
|
for (const a of cp.arquivos) {
|
|
117
228
|
try {
|
|
118
229
|
if (a.existia) {
|
|
119
230
|
fs.mkdirSync(path.dirname(a.caminho), { recursive: true });
|
|
120
231
|
fs.copyFileSync(path.join(cp.dir, a.copia), a.caminho);
|
|
121
|
-
|
|
232
|
+
anotar(a.caminho, 'restaurado');
|
|
122
233
|
} else {
|
|
123
234
|
fs.rmSync(a.caminho, { force: true });
|
|
124
|
-
|
|
235
|
+
anotar(a.caminho, 'apagado');
|
|
125
236
|
}
|
|
126
|
-
} catch (e) {
|
|
237
|
+
} catch (e) { anotar(a.caminho, 'falhou', e.message); }
|
|
127
238
|
}
|
|
128
239
|
fs.rmSync(cp.dir, { recursive: true, force: true });
|
|
129
|
-
if (atual && atual.dir === cp.dir) atual = null;
|
|
240
|
+
if (atual && atual.git && atual.git.dir === cp.dir) atual.git = null;
|
|
241
|
+
if (atual && atual.arq && atual.arq.dir === cp.dir) atual.arq = null;
|
|
130
242
|
}
|
|
131
|
-
return feitos;
|
|
243
|
+
return [...feitos.values()];
|
|
132
244
|
}
|
|
133
245
|
|
|
134
|
-
|
|
246
|
+
/** Qual jeito de guardar vale nesta pasta — o /desfazer lista conta à pessoa. */
|
|
247
|
+
function modo(projectDir) { return podeFotografar(projectDir) ? 'foto' : 'arquivo'; }
|
|
248
|
+
|
|
249
|
+
module.exports = { MAXIMO, MUDAM_ARQUIVO, iniciar, encerrar, antesDeMudar, lista, previa, desfazer, modo, pastaDoProjeto };
|