@memoring/cli 0.1.0 → 0.2.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 +2 -2
- package/dist/commands/stage.js +55 -9
- package/dist/main.js +1 -1
- package/package.json +6 -1
- package/skills/memoring-cli/SKILL.md +2 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Os itens enviados nascem **PENDING_REVIEW** — nada entra nas suas listagens ne
|
|
|
44
44
|
| Command | Purpose |
|
|
45
45
|
| --- | --- |
|
|
46
46
|
| `memoring auth` | login / logout / status |
|
|
47
|
-
| `memoring stage` | schema (o contrato) / create (envia o lote, `--file` ou stdin, `--replace` esvazia a caixa antes) |
|
|
47
|
+
| `memoring stage` | schema (o contrato) / create (envia o lote, `--file` ou stdin, `--replace` esvazia a caixa antes, `--max-notes\|--max-flashcards\|--max-one-shots` limitam cada tipo) |
|
|
48
48
|
| `memoring inbox` | list / accept `<id>` / accept-all / discard `<id>` |
|
|
49
49
|
| `memoring version` | versão da CLI |
|
|
50
50
|
|
|
@@ -79,7 +79,7 @@ Cada item do lote (1 a 50, all-or-nothing):
|
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
- `type`: `
|
|
82
|
+
- `type`: `NOTE` (nota, exige `content` em markdown; `NORMAL` legado é aceito e mapeado para `NOTE`), `FLASHCARD` ou `ONE_SHOT` (exigem `front` + `back`).
|
|
83
83
|
- `source`: origem do material — default `"Importação assistida (CLI)"`.
|
|
84
84
|
|
|
85
85
|
## Para agentes de IA
|
package/dist/commands/stage.js
CHANGED
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { apiFetch, ApiError } from '../api.js';
|
|
3
3
|
import { dim, fail, green, printJson, stringFlag } from './shared.js';
|
|
4
|
-
const TYPES = ['
|
|
4
|
+
const TYPES = ['NOTE', 'FLASHCARD', 'ONE_SHOT'];
|
|
5
|
+
// Retrocompat: itens.json antigos com "type": "NORMAL" são normalizados para "NOTE".
|
|
6
|
+
const LEGACY_TYPE_ALIASES = { NORMAL: 'NOTE' };
|
|
5
7
|
const MAX_ITEMS = 50;
|
|
6
8
|
const DEFAULT_SOURCE = 'Importação assistida (CLI)';
|
|
9
|
+
/** Flags --max-<tipo> do `stage create`: teto por tipo no lote (validação local). */
|
|
10
|
+
const MAX_FLAG_BY_TYPE = {
|
|
11
|
+
NOTE: 'max-notes',
|
|
12
|
+
FLASHCARD: 'max-flashcards',
|
|
13
|
+
ONE_SHOT: 'max-one-shots',
|
|
14
|
+
};
|
|
15
|
+
const TYPE_LABEL = {
|
|
16
|
+
NOTE: 'notas',
|
|
17
|
+
FLASHCARD: 'flashcards',
|
|
18
|
+
ONE_SHOT: '1-shots',
|
|
19
|
+
};
|
|
20
|
+
function enforceTypeCaps(items, flags) {
|
|
21
|
+
const caps = Object.entries(MAX_FLAG_BY_TYPE)
|
|
22
|
+
.map(([type, flag]) => {
|
|
23
|
+
const raw = stringFlag(flags, flag);
|
|
24
|
+
if (raw === undefined) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const max = Number(raw);
|
|
28
|
+
if (!Number.isInteger(max) || max < 0) {
|
|
29
|
+
fail(`--${flag} inválido: use um inteiro >= 0 (veio ${JSON.stringify(raw)}).`);
|
|
30
|
+
}
|
|
31
|
+
return { type, flag, max };
|
|
32
|
+
})
|
|
33
|
+
.filter((cap) => cap !== null);
|
|
34
|
+
if (caps.length === 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const cap of caps) {
|
|
38
|
+
const count = items.filter((item) => item.type === cap.type).length;
|
|
39
|
+
if (count > cap.max) {
|
|
40
|
+
fail(`lote tem ${count} ${TYPE_LABEL[cap.type]} e --${cap.flag}=${cap.max}: ajuste o lote ou o limite. ` +
|
|
41
|
+
'Se um agente gerou este lote, peça a ele para respeitar os limites por tipo.');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
7
45
|
const SCHEMA_JSON = {
|
|
8
46
|
description: 'Lote de 1..50 itens para POST /api/notes/review-inbox. All-or-nothing: qualquer item inválido rejeita o lote. Os itens nascem PENDING_REVIEW — o usuário aceita/edita/recusa em /cards/inbox.',
|
|
9
47
|
item: {
|
|
10
|
-
type: '
|
|
48
|
+
type: 'NOTE | FLASHCARD | ONE_SHOT (obrigatório; NORMAL legado é aceito e mapeado para NOTE)',
|
|
11
49
|
title: 'string (obrigatório)',
|
|
12
|
-
content: 'string — corpo da nota em markdown (obrigatório quando type=
|
|
50
|
+
content: 'string — corpo da nota em markdown (obrigatório quando type=NOTE)',
|
|
13
51
|
front: 'string — pergunta (obrigatório quando type=FLASHCARD|ONE_SHOT)',
|
|
14
52
|
back: 'string — resposta (obrigatório quando type=FLASHCARD|ONE_SHOT)',
|
|
15
53
|
subjectName: 'string? — matéria (ex.: "Direito Constitucional")',
|
|
@@ -28,12 +66,13 @@ const SCHEMA_JSON = {
|
|
|
28
66
|
source: 'Apostila constitucional.pdf',
|
|
29
67
|
},
|
|
30
68
|
],
|
|
69
|
+
typeCaps: 'flags opcionais do stage create: --max-notes N | --max-flashcards N | --max-one-shots N — o lote é rejeitado se algum tipo exceder o limite.',
|
|
31
70
|
};
|
|
32
71
|
const SCHEMA_TEXT = `Contrato dos itens da caixa de revisão (POST /api/notes/review-inbox):
|
|
33
72
|
|
|
34
|
-
type
|
|
73
|
+
type NOTE | FLASHCARD | ONE_SHOT (obrigatório; NORMAL legado é mapeado para NOTE)
|
|
35
74
|
title string (obrigatório)
|
|
36
|
-
content string — corpo da nota (markdown) (obrigatório quando type=
|
|
75
|
+
content string — corpo da nota (markdown) (obrigatório quando type=NOTE)
|
|
37
76
|
front, back string — pergunta e resposta (obrigatório quando type=FLASHCARD|ONE_SHOT)
|
|
38
77
|
subjectName? string — matéria (ex.: "Direito Constitucional")
|
|
39
78
|
subtopicName? string — subtópico
|
|
@@ -42,7 +81,8 @@ const SCHEMA_TEXT = `Contrato dos itens da caixa de revisão (POST /api/notes/re
|
|
|
42
81
|
|
|
43
82
|
Lote: 1 a ${MAX_ITEMS} itens, all-or-nothing — qualquer item inválido rejeita o lote inteiro.
|
|
44
83
|
Os itens nascem PENDING_REVIEW: o usuário aceita, edita ou recusa em /cards/inbox.
|
|
45
|
-
Dica: FLASHCARD entra na fila de revisão espaçada (FSRS); ONE_SHOT é pergunta de resposta única
|
|
84
|
+
Dica: FLASHCARD entra na fila de revisão espaçada (FSRS); ONE_SHOT é pergunta de resposta única.
|
|
85
|
+
Limites por tipo: --max-notes N | --max-flashcards N | --max-one-shots N (o lote é rejeitado se exceder).`;
|
|
46
86
|
async function readStdin() {
|
|
47
87
|
const chunks = [];
|
|
48
88
|
for await (const chunk of process.stdin) {
|
|
@@ -64,14 +104,17 @@ function validateItems(raw) {
|
|
|
64
104
|
fail(`${where}: esperado objeto, veio ${typeof item}.`);
|
|
65
105
|
}
|
|
66
106
|
const it = item;
|
|
107
|
+
if (typeof it.type === 'string' && LEGACY_TYPE_ALIASES[it.type] !== undefined) {
|
|
108
|
+
it.type = LEGACY_TYPE_ALIASES[it.type];
|
|
109
|
+
}
|
|
67
110
|
if (typeof it.type !== 'string' || !TYPES.includes(it.type)) {
|
|
68
111
|
fail(`${where}: type deve ser ${TYPES.join(' | ')} (veio ${JSON.stringify(it.type)}).`);
|
|
69
112
|
}
|
|
70
113
|
if (typeof it.title !== 'string' || it.title.trim() === '') {
|
|
71
114
|
fail(`${where}: title é obrigatório.`);
|
|
72
115
|
}
|
|
73
|
-
if (it.type === '
|
|
74
|
-
fail(`${where} (${it.title}): type=
|
|
116
|
+
if (it.type === 'NOTE' && (typeof it.content !== 'string' || it.content.trim() === '')) {
|
|
117
|
+
fail(`${where} (${it.title}): type=NOTE exige content.`);
|
|
75
118
|
}
|
|
76
119
|
if ((it.type === 'FLASHCARD' || it.type === 'ONE_SHOT') &&
|
|
77
120
|
(typeof it.front !== 'string' || it.front.trim() === '' || typeof it.back !== 'string' || it.back.trim() === '')) {
|
|
@@ -122,6 +165,7 @@ export async function stageCommand(parsed) {
|
|
|
122
165
|
fail(`JSON inválido: ${e.message}`);
|
|
123
166
|
}
|
|
124
167
|
const items = validateItems(parsedJson);
|
|
168
|
+
enforceTypeCaps(items, parsed.flags);
|
|
125
169
|
if (parsed.flags.replace) {
|
|
126
170
|
const removed = await emptyInbox();
|
|
127
171
|
if (!parsed.flags.json) {
|
|
@@ -153,7 +197,9 @@ export async function stageCommand(parsed) {
|
|
|
153
197
|
process.stdout.write('Uso: memoring stage <schema|create> [--file itens.json] [--replace] [--json]\n' +
|
|
154
198
|
' schema imprime o contrato JSON dos itens (para humanos e agentes)\n' +
|
|
155
199
|
' create envia o lote para a caixa de revisão (arquivo ou stdin)\n' +
|
|
156
|
-
' --replace esvazia a caixa antes de enviar\n'
|
|
200
|
+
' --replace esvazia a caixa antes de enviar\n' +
|
|
201
|
+
' --max-notes N | --max-flashcards N | --max-one-shots N\n' +
|
|
202
|
+
' limitam a quantidade de cada tipo no lote (validação local)\n');
|
|
157
203
|
if (sub) {
|
|
158
204
|
process.exitCode = 1;
|
|
159
205
|
}
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@ import { authCommand } from './commands/auth.js';
|
|
|
5
5
|
import { inboxCommand } from './commands/inbox.js';
|
|
6
6
|
import { stageCommand } from './commands/stage.js';
|
|
7
7
|
import { fail, parseArgs as parse } from './commands/shared.js';
|
|
8
|
-
const VERSION = '0.
|
|
8
|
+
const VERSION = '0.2.0';
|
|
9
9
|
const HELP = `Memoring CLI — flashcards e notas por IA, direto do terminal.
|
|
10
10
|
|
|
11
11
|
Uso: memoring <comando> [subcomando] [flags]
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoring/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI do Memoring — envie flashcards/notas gerados por IA para a sua caixa de revisão, direto do terminal.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/caiomestres/memoring.git",
|
|
8
|
+
"directory": "cli"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"bin": {
|
|
7
12
|
"memoring": "dist/main.js"
|
|
@@ -22,8 +22,9 @@ O Memoring é um app de estudos com revisão espaçada (FSRS). A CLI `memoring`
|
|
|
22
22
|
|
|
23
23
|
## Regras do contrato (resumo — a fonte da verdade é `stage schema`)
|
|
24
24
|
|
|
25
|
-
- `type`: `
|
|
25
|
+
- `type`: `NOTE` (nota, exige `content` markdown; `NORMAL` legado é aceito e mapeado para `NOTE`), `FLASHCARD` ou `ONE_SHOT` (exigem `front` + `back`).
|
|
26
26
|
- `title` sempre obrigatório; lote de 1 a 50 itens, all-or-nothing.
|
|
27
|
+
- Se o usuário pedir limites por tipo (ex.: "no máximo 20 flashcards e 5 notas"), respeite-os ao gerar e valide com `--max-notes N --max-flashcards N --max-one-shots N` no `stage create` — o lote é rejeitado se exceder.
|
|
27
28
|
- `ONE_SHOT` = pergunta de resposta única; prefira `FLASHCARD` para memorização.
|
|
28
29
|
- Preencha `source` com a origem real do material (nome do arquivo/pasta) — não use o default quando souber a origem.
|
|
29
30
|
- `subjectName`/`subtopicName` quando o material tiver matéria/subtópico claros; `tags` curtas e em minúsculas.
|