@memoring/cli 0.4.0 → 0.5.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 CHANGED
@@ -57,6 +57,7 @@ Os itens enviados nascem **PENDING_REVIEW** — nada entra nas suas listagens ne
57
57
  | `memoring stage` | schema (o contrato + seus limites por tipo) / caps (mostra/grava os limites — valem para o app e para os agentes) / create (envia o lote, `--file` ou stdin, `--replace` esvazia a caixa antes; `--max-*` vence a preferência por lote) |
58
58
  | `memoring inbox` | list / accept `<id>` / accept-all / discard `<id>` |
59
59
  | `memoring update` | atualiza a CLI para a última versão (via npm) |
60
+ | `memoring skill` | imprime o guia completo da CLI para agentes de IA (o SKILL.md empacotado) |
60
61
  | `memoring version` | versão da CLI |
61
62
 
62
63
  ## Flags
@@ -92,11 +93,11 @@ Cada item do lote (1 a 50, all-or-nothing):
92
93
 
93
94
  - `type`: `NOTE` (nota, exige `content` em markdown; `NORMAL` legado é aceito e mapeado para `NOTE`), `FLASHCARD` ou `ONE_SHOT` (exigem `front` + `back`).
94
95
  - `source`: origem do material — default `"Importação assistida (CLI)"`.
95
- - Limites por tipo: `memoring stage caps` mostra/grava sua preferência (0 = não gerar o tipo) — o `stage schema` a expõe ao agente e o `create` a impõe; flags `--max-*` no `create` vencem por lote.
96
+ - Limites por tipo: `memoring stage caps` mostra/grava sua preferência (`livre`/`-1` = à vontade — o padrão; `0` = não gerar o tipo) — o `stage schema` a expõe ao agente (`userCaps`) e o `create` a impõe; flags `--max-*` no `create` vencem por lote.
96
97
 
97
98
  ## Para agentes de IA
98
99
 
99
- Este pacote inclui uma skill em `skills/memoring-cli/SKILL.md` com o fluxo completo (auth → schema → gerar → stage → avisar o usuário). Aponte o agente para ela, ou simplesmente peça "use a cli do memoring" — os comandos são auto-descritivos (`--help` em todos).
100
+ Este pacote inclui uma skill em `skills/memoring-cli/SKILL.md` com o fluxo completo (auth → schema → gerar → stage → avisar o usuário). O jeito mais fácil de um agente aprender a CLI de cabo a rabo é rodar **`memoring skill`** — o comando imprime o guia completo empacotado. Ou simplesmente peça "use a cli do memoring" — os comandos são auto-descritivos (`--help` em todos).
100
101
 
101
102
  ## Dev (backend local)
102
103
 
@@ -0,0 +1,20 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fail } from './shared.js';
3
+ /**
4
+ * `memoring skill` — imprime o SKILL.md empacotado no npm (guia completo da CLI para
5
+ * agentes de IA: fluxo, contrato, limites por tipo, armadilhas). É como um agente
6
+ * aprende a CLI de cabo a rabo sem precisar caçar o arquivo no node_modules.
7
+ */
8
+ export function skillCommand() {
9
+ let content;
10
+ try {
11
+ content = readFileSync(new URL('../../skills/memoring-cli/SKILL.md', import.meta.url), 'utf8');
12
+ }
13
+ catch {
14
+ fail('não encontrei o SKILL.md empacotado — reinstale com `npm i -g @memoring/cli@latest`.');
15
+ }
16
+ process.stdout.write(content);
17
+ if (!content.endsWith('\n')) {
18
+ process.stdout.write('\n');
19
+ }
20
+ }
@@ -8,6 +8,28 @@ const TYPES = ['NOTE', 'FLASHCARD', 'ONE_SHOT'];
8
8
  const LEGACY_TYPE_ALIASES = { NORMAL: 'NOTE' };
9
9
  const MAX_ITEMS = 50;
10
10
  const DEFAULT_SOURCE = 'Importação assistida (CLI)';
11
+ const UNLIMITED = -1;
12
+ /** Rótulo de exibição de um teto: -1 → "à vontade". */
13
+ function capLabel(max) {
14
+ return max === UNLIMITED ? 'à vontade' : `até ${max}`;
15
+ }
16
+ /**
17
+ * Teto vindo de flag ou preferência: inteiro -1..50, ou a palavra "livre" (= -1, à vontade).
18
+ * `null` quando a flag está ausente.
19
+ */
20
+ function parseCapFlag(raw, flagName) {
21
+ if (raw === undefined) {
22
+ return null;
23
+ }
24
+ if (raw.trim().toLowerCase() === 'livre') {
25
+ return UNLIMITED;
26
+ }
27
+ const value = Number(raw);
28
+ if (!Number.isInteger(value) || value < UNLIMITED || value > 50) {
29
+ fail(`--${flagName} inválido: use "livre" ou um inteiro entre -1 (à vontade) e 50 (veio ${JSON.stringify(raw)}).`);
30
+ }
31
+ return value;
32
+ }
11
33
  /**
12
34
  * GET /notes/import/caps, fail-open: sem sessão ou sem rede devolve null (o schema
13
35
  * continua útil deslogado e o create só perde a validação extra). Só chama a API
@@ -44,23 +66,19 @@ const CAPS_FLAG_BY_FIELD = {
44
66
  };
45
67
  /**
46
68
  * Teto por tipo no lote (validação local): a flag --max-<tipo> vence; sem flag, vale a
47
- * preferência gravada do usuário (`memoring stage caps` / app). Tipo sem flag nem
48
- * preferência fica livre.
69
+ * preferência gravada do usuário (`memoring stage caps` / app). "livre"/-1 (à vontade)
70
+ * não limita; tipo sem flag nem preferência fica livre.
49
71
  */
50
72
  function enforceTypeCaps(items, flags, stored) {
51
73
  const storedByType = stored
52
74
  ? { NOTE: stored.maxNotes, FLASHCARD: stored.maxFlashcards, ONE_SHOT: stored.maxOneShots }
53
75
  : {};
54
76
  for (const [type, flag] of Object.entries(MAX_FLAG_BY_TYPE)) {
55
- const raw = stringFlag(flags, flag);
77
+ const fromFlag = parseCapFlag(stringFlag(flags, flag), flag);
56
78
  let max;
57
79
  let source;
58
- if (raw !== undefined) {
59
- const parsed = Number(raw);
60
- if (!Number.isInteger(parsed) || parsed < 0) {
61
- fail(`--${flag} inválido: use um inteiro >= 0 (veio ${JSON.stringify(raw)}).`);
62
- }
63
- max = parsed;
80
+ if (fromFlag !== null) {
81
+ max = fromFlag;
64
82
  source = `--${flag}`;
65
83
  }
66
84
  else if (storedByType[type] !== undefined) {
@@ -70,9 +88,12 @@ function enforceTypeCaps(items, flags, stored) {
70
88
  else {
71
89
  continue;
72
90
  }
91
+ if (max === UNLIMITED) {
92
+ continue;
93
+ }
73
94
  const count = items.filter((item) => item.type === type).length;
74
95
  if (count > max) {
75
- fail(`lote tem ${count} ${TYPE_LABEL[type]} e o limite é ${max} (${source}). Ajuste o lote ou o limite. ` +
96
+ fail(`lote tem ${count} ${TYPE_LABEL[type]} e o limite é ${max} (${source}). Ajuste o lote ou o limite ("livre" = à vontade). ` +
76
97
  'Se um agente gerou este lote, peça a ele para respeitar os limites por tipo.');
77
98
  }
78
99
  }
@@ -101,7 +122,7 @@ const SCHEMA_JSON = {
101
122
  source: 'Apostila constitucional.pdf',
102
123
  },
103
124
  ],
104
- typeCaps: 'preferência por usuário (`memoring stage caps`, vale para app e CLI) — o `stage schema` mostra os limites ao agente; flags do `stage create` (--max-notes N | --max-flashcards N | --max-one-shots N) vencem a preferência por lote.',
125
+ typeCaps: 'preferência por usuário (`memoring stage caps`, vale para app e CLI) — o `stage schema` mostra os limites ao agente (userCaps: -1 = à vontade, 0 = não gerar o tipo); flags do `stage create` (--max-notes N | --max-flashcards N | --max-one-shots N, ou "livre") vencem a preferência por lote.',
105
126
  };
106
127
  const SCHEMA_TEXT = `Contrato dos itens da caixa de revisão (POST /api/notes/review-inbox):
107
128
 
@@ -118,7 +139,7 @@ Lote: 1 a ${MAX_ITEMS} itens, all-or-nothing — qualquer item inválido rejeita
118
139
  Os itens nascem PENDING_REVIEW: o usuário aceita, edita ou recusa em /cards/inbox.
119
140
  Dica: FLASHCARD entra na fila de revisão espaçada (FSRS); ONE_SHOT é pergunta de resposta única.
120
141
  Limites por tipo: a preferência do usuário (abaixo, quando logado) vale por padrão;
121
- --max-notes N | --max-flashcards N | --max-one-shots N no create vencem por lote.`;
142
+ --max-notes N | --max-flashcards N | --max-one-shots N no create vencem por lote ("livre" = à vontade).`;
122
143
  async function readStdin() {
123
144
  const chunks = [];
124
145
  for await (const chunk of process.stdin) {
@@ -202,8 +223,8 @@ export async function stageCommand(parsed) {
202
223
  process.stdout.write(SCHEMA_TEXT + '\n');
203
224
  if (caps) {
204
225
  process.stdout.write(`\nLimites por tipo configurados por você — o agente DEVE respeitá-los ao gerar o lote: ` +
205
- `até ${caps.maxNotes} nota(s), até ${caps.maxFlashcards} flashcard(s), até ${caps.maxOneShots} 1-shot(s).\n` +
206
- dim('(altere com `memoring stage caps`; QUESTION não é estagiável pela CLI)\n'));
226
+ `notas: ${capLabel(caps.maxNotes)}, flashcards: ${capLabel(caps.maxFlashcards)}, 1-shots: ${capLabel(caps.maxOneShots)}.\n` +
227
+ dim('("à vontade" = crie quantos o material justificar; altere com `memoring stage caps`; QUESTION não é estagiável pela CLI)\n'));
207
228
  }
208
229
  else {
209
230
  process.stdout.write(dim('\n(limites por tipo indisponíveis — faça `memoring auth login` ou verifique a conexão)\n'));
@@ -216,14 +237,10 @@ export async function stageCommand(parsed) {
216
237
  const current = (await apiFetch('/notes/import/caps'));
217
238
  const updates = {};
218
239
  for (const [field, flag] of Object.entries(CAPS_FLAG_BY_FIELD)) {
219
- const raw = stringFlag(parsed.flags, flag);
220
- if (raw === undefined) {
240
+ const value = parseCapFlag(stringFlag(parsed.flags, flag), flag);
241
+ if (value === null) {
221
242
  continue;
222
243
  }
223
- const value = Number(raw);
224
- if (!Number.isInteger(value) || value < 0 || value > 50) {
225
- fail(`--${flag} inválido: use um inteiro entre 0 e 50 (veio ${JSON.stringify(raw)}).`);
226
- }
227
244
  updates[field] = value;
228
245
  }
229
246
  const hasUpdates = Object.keys(updates).length > 0;
@@ -236,10 +253,10 @@ export async function stageCommand(parsed) {
236
253
  }
237
254
  process.stdout.write((hasUpdates ? green('Limites gravados') : 'Limites por tipo') +
238
255
  ' — valem para a importação do app e para os agentes via CLI:\n');
239
- process.stdout.write(` notas: até ${effective.maxNotes} flashcards: até ${effective.maxFlashcards} ` +
240
- `1-shots: até ${effective.maxOneShots} questões: até ${effective.maxQuestions}\n`);
256
+ process.stdout.write(` notas: ${capLabel(effective.maxNotes)} flashcards: ${capLabel(effective.maxFlashcards)} ` +
257
+ `1-shots: ${capLabel(effective.maxOneShots)} questões: ${capLabel(effective.maxQuestions)}\n`);
241
258
  if (!hasUpdates) {
242
- process.stdout.write(dim('Altere com: memoring stage caps --max-flashcards 30 (0 = não gerar o tipo)\n'));
259
+ process.stdout.write(dim('Altere com: memoring stage caps --max-flashcards 30 (0 = não gerar o tipo; "livre" = à vontade)\n'));
243
260
  }
244
261
  return;
245
262
  }
package/dist/main.js CHANGED
@@ -5,9 +5,10 @@ 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 { updateCommand } from './commands/update.js';
8
+ import { skillCommand } from './commands/skill.js';
8
9
  import { fail, parseArgs as parse } from './commands/shared.js';
9
10
  import { newerVersionAvailable, scheduleUpdateCheck } from './update.js';
10
- const VERSION = '0.4.0';
11
+ const VERSION = '0.5.0';
11
12
  const HELP = `Memoring CLI — flashcards e notas por IA, direto do terminal.
12
13
 
13
14
  Uso: memoring <comando> [subcomando] [flags]
@@ -22,6 +23,7 @@ Comandos:
22
23
  inbox accept <id>|accept-all aceita itens (entram nas listagens e na fila FSRS)
23
24
  inbox discard <id> descarta um item pendente
24
25
  update atualiza a CLI para a versão mais recente (via npm)
26
+ skill guia completo da CLI para agentes de IA (imprime o SKILL.md)
25
27
  version versão da CLI
26
28
 
27
29
  Flags globais:
@@ -85,6 +87,9 @@ async function main() {
85
87
  case 'update':
86
88
  await updateCommand(VERSION);
87
89
  return;
90
+ case 'skill':
91
+ skillCommand();
92
+ return;
88
93
  case 'version':
89
94
  process.stdout.write(`memoring-cli ${VERSION}\n`);
90
95
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoring/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI do Memoring — envie flashcards/notas gerados por IA para a sua caixa de revisão, direto do terminal.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,38 +3,62 @@ name: memoring-cli
3
3
  description: Gere flashcards e notas a partir de materiais de estudo locais (PDFs, textos, anotações) e envie para a caixa de revisão do Memoring usando a CLI `memoring`. Use quando o usuário pedir para "usar a cli do memoring", criar flashcards/notas de uma pasta ou arquivo, ou enviar material de estudo para o Memoring.
4
4
  ---
5
5
 
6
- # Memoring CLI — geração de flashcards/notas por agente
6
+ # Memoring CLI — guia completo para agentes
7
7
 
8
- O Memoring é um app de estudos com revisão espaçada (FSRS). A CLI `memoring` é a ponte entre você (o agente, que lê o material e gera o conteúdo) e a conta do usuário no app. Você NÃO chama a API HTTP diretamente — use sempre a CLI.
8
+ O Memoring é um app de estudos com revisão espaçada (FSRS). A CLI `memoring` é a ponte entre você (o agente, que lê o material e gera o conteúdo) e a conta do usuário no app. Você NÃO chama a API HTTP diretamente — use sempre a CLI. Este guia está sempre disponível via `memoring skill`.
9
9
 
10
10
  ## Pré-requisitos
11
11
 
12
12
  1. CLI instalada: `memoring version` deve responder. Se não, peça ao usuário: `npm install -g @memoring/cli`.
13
13
  2. Sessão ativa: `memoring auth status --json` → `authenticated: true`. Se não, peça ao usuário para rodar `memoring auth login` (device flow — ele aprova no browser; você não consegue fazer isso por ele).
14
14
 
15
- ## Fluxo
15
+ ## Mapa dos comandos
16
16
 
17
- 1. **Entenda o pedido** — qual pasta/arquivo/material virar flashcards/notas.
18
- 2. **Leia o contrato**: `memoring stage schema --json`.
17
+ | Comando | O que faz |
18
+ | --- | --- |
19
+ | `memoring auth login\|logout\|status` | sessão (device flow — sem senha no terminal) |
20
+ | `memoring stage schema [--json]` | O CONTRATO dos itens + os limites por tipo do usuário — leia SEMPRE antes de gerar |
21
+ | `memoring stage caps [--max-* N\|livre]` | limites por tipo do usuário (preferência DELE — nunca escreva sem ele pedir) |
22
+ | `memoring stage create --file f [--replace] [--max-* N]` | envia o lote (1 a 50 itens) para a caixa de revisão |
23
+ | `memoring inbox list` | itens pendentes de revisão |
24
+ | `memoring inbox accept <id>\|accept-all` | aceita itens (entram nas listagens e na fila FSRS) |
25
+ | `memoring inbox discard <id>` | descarta um item pendente |
26
+ | `memoring skill` | imprime este guia |
27
+ | `memoring update` | atualiza a CLI (decisão do usuário — nunca rode por conta própria) |
28
+
29
+ Flags globais: `--json` (saída legível por máquina), `--no-color`, `--api-base`.
30
+
31
+ ## Fluxo padrão
32
+
33
+ 1. **Entenda o pedido** — qual pasta/arquivo/material vira flashcards/notas e se o usuário pediu quantidades.
34
+ 2. **Leia o contrato e os limites**: `memoring stage schema --json`. Além do contrato, a saída traz `userCaps` — os limites por tipo DO USUÁRIO (`-1` = à vontade, `0` = não gerar o tipo, `N` = teto). Respeite-os SEMPRE, mesmo sem pedido explícito no chat.
19
35
  3. **Leia os materiais** e gere os itens no contrato, num arquivo JSON (array ou `{ "items": [...] }`).
20
- 4. **Envie**: `memoring stage create --file <arquivo> --json`.
36
+ 4. **Envie**: `memoring stage create --file <arquivo> --json`. Se o lote for rejeitado por limite de tipo, ajuste o LOTE — não relaxe o limite do usuário.
21
37
  5. **Avise o usuário**: quantos itens foram para a caixa de revisão e onde aceitar — no app em `/cards/inbox` ou `memoring inbox list` / `memoring inbox accept-all`.
22
38
 
23
- ## Regras do contrato (resumo — a fonte da verdade é `stage schema`)
39
+ ## O contrato (a fonte da verdade é `stage schema`)
24
40
 
25
41
  - `type`: `NOTE` (nota, exige `content` markdown; `NORMAL` legado é aceito e mapeado para `NOTE`), `FLASHCARD` ou `ONE_SHOT` (exigem `front` + `back`).
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.
28
- - O `stage schema` mostra os **limites por tipo do usuário** (`userCaps` no JSON; texto no fim da saída em modo humano) — respeite-os ao gerar, mesmo sem pedido explícito no chat. Sem limites visíveis, o create ainda pode rejeitar pela preferência gravada: ajuste o lote e reenvie. Para MUDAR a preferência permanente, o usuário roda `memoring stage caps` — não rode por conta própria.
29
- - `ONE_SHOT` = pergunta de resposta única; prefira `FLASHCARD` para memorização.
42
+ - `title` sempre obrigatório; lote de 1 a 50 itens, all-or-nothing — qualquer item inválido rejeita o lote inteiro.
43
+ - `ONE_SHOT` = pergunta de resposta única (uma palavra ou termo curto); prefira `FLASHCARD` para memorização.
30
44
  - Preencha `source` com a origem real do material (nome do arquivo/pasta) — não use o default quando souber a origem.
31
45
  - `subjectName`/`subtopicName` quando o material tiver matéria/subtópico claros; `tags` curtas e em minúsculas.
32
46
  - NUNCA invente campos fora do contrato — o servidor rejeita o lote inteiro.
33
47
 
48
+ ## Quantidades (limites por tipo)
49
+
50
+ - O default do usuário costuma ser **à vontade** (`userCaps` com `-1`): crie QUANTOS itens o material justificar — qualidade acima de quantidade, sem encher cota e sem repetir o mesmo fato em variações triviais.
51
+ - Se `userCaps` trouxer tetos numéricos, distribua a geração para caber neles; `0` proíbe o tipo.
52
+ - Se o usuário pedir quantidades no chat (ex.: "quero 30 flashcards e 5 notas"), o pedido vence o `userCaps`: gere e valide com `--max-flashcards 30 --max-notes 5` no `stage create`.
53
+ - Lotes grandes: divida em vários arquivos de até 50 itens e envie sequencialmente.
54
+
55
+ ## Qualidade dos flashcards (o padrão que o app espera)
56
+
57
+ Cartões atômicos: UMA ideia por cartão — se um conceito tem 3 partes cobráveis, crie 3 cartões, nunca um cartão com lista como resposta. A frente é uma pergunta direta que força LEMBRAR (não reconhecer). A resposta é auto-contida (faz sentido sem o material) e curta. Pares confundíveis (prazos, siglas, exceções) levam na frente o traço que os diferencia. Não invente conteúdo que não esteja no material.
58
+
34
59
  ## Armadilhas
35
60
 
36
61
  - **Nunca** use `--replace` sem o usuário pedir explicitamente — ele apaga os itens pendentes existentes da caixa.
37
62
  - Se aparecer um aviso de versão nova no stderr, ignore e siga o fluxo — **não** rode `memoring update` por conta própria; atualizar a CLI é decisão do usuário.
63
+ - **Não** altere os limites do usuário (`stage caps`) por conta própria — é preferência dele, compartilhada com o app.
38
64
  - Se `stage create` falhar com erro de validação do servidor, corrija os itens indicados e reenvie — não relaxe o contrato.
39
- - Lotes grandes: divida em vários arquivos de até 50 itens e envie sequencialmente.
40
- - Flashcards bons são atômicos: uma ideia por cartão, pergunta direta, resposta completa mas curta.