@memoring/cli 0.12.1 → 0.15.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 +163 -8
- package/dist/api.js +90 -4
- package/dist/args.js +14 -1
- package/dist/auth.js +1 -1
- package/dist/commands/batch.js +327 -0
- package/dist/commands/inbox.js +3 -1
- package/dist/commands/materialReferences.js +359 -0
- package/dist/commands/plan-analyze.js +256 -0
- package/dist/commands/plan-diagnostics.js +65 -0
- package/dist/commands/plan-exams.js +122 -0
- package/dist/commands/plan-goal.js +131 -0
- package/dist/commands/plan-shared.js +133 -0
- package/dist/commands/plan.js +219 -0
- package/dist/commands/planDrafts.js +162 -0
- package/dist/commands/scratch.js +632 -0
- package/dist/commands/stage.js +274 -73
- package/dist/config.js +2 -0
- package/dist/main.js +43 -2
- package/dist/pdf/batchValidation.js +72 -0
- package/dist/pdf/documentCache.js +68 -0
- package/dist/pdf/quoteLocate.js +181 -0
- package/package.json +8 -2
- package/skills/memoring-cli/SKILL.md +58 -7
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { apiFetch } from '../api.js';
|
|
4
|
+
import { openBrowser } from '../auth.js';
|
|
5
|
+
import { loadConfig } from '../config.js';
|
|
6
|
+
import { fail, green, printJson, stringFlag, yellow } from './shared.js';
|
|
7
|
+
const DRAFT_POLL_INTERVAL_MS = 2_000;
|
|
8
|
+
const DRAFT_REVIEW_TIMEOUT_SECONDS = 10 * 60;
|
|
9
|
+
function object(value, message) {
|
|
10
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
11
|
+
fail(message);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function draftId(raw) {
|
|
16
|
+
if (!raw || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)) {
|
|
17
|
+
fail('draftId deve ser um UUID.');
|
|
18
|
+
}
|
|
19
|
+
return raw;
|
|
20
|
+
}
|
|
21
|
+
function planningDraft(value) {
|
|
22
|
+
const draft = object(value, 'resposta inválida ao consultar rascunho de planejamento.');
|
|
23
|
+
const id = draftId(typeof draft.id === 'string' ? draft.id : undefined);
|
|
24
|
+
if (draft.kind !== 'PLAN' && draft.kind !== 'GOAL') {
|
|
25
|
+
fail('resposta inválida: kind do rascunho deve ser PLAN ou GOAL.');
|
|
26
|
+
}
|
|
27
|
+
if (draft.status !== 'OPEN' && draft.status !== 'REVIEWED') {
|
|
28
|
+
fail('resposta inválida: status do rascunho deve ser OPEN ou REVIEWED.');
|
|
29
|
+
}
|
|
30
|
+
if (draft.planId !== null && (typeof draft.planId !== 'number' || !Number.isSafeInteger(draft.planId) || draft.planId < 1)) {
|
|
31
|
+
fail('resposta inválida: planId do rascunho deve ser um inteiro positivo ou null.');
|
|
32
|
+
}
|
|
33
|
+
for (const field of ['createdAt', 'updatedAt', 'expiresAt']) {
|
|
34
|
+
if (typeof draft[field] !== 'string') {
|
|
35
|
+
fail(`resposta inválida: ${field} do rascunho é obrigatório.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
kind: draft.kind,
|
|
41
|
+
planId: draft.planId,
|
|
42
|
+
payload: object(draft.payload, 'resposta inválida: payload do rascunho deve ser um objeto.'),
|
|
43
|
+
status: draft.status,
|
|
44
|
+
createdAt: draft.createdAt,
|
|
45
|
+
updatedAt: draft.updatedAt,
|
|
46
|
+
expiresAt: draft.expiresAt,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function draftReviewUrl(draft) {
|
|
50
|
+
const webBase = loadConfig().webBase.replace(/\/+$/, '');
|
|
51
|
+
if (draft.kind === 'PLAN') {
|
|
52
|
+
return `${webBase}/planning/new?draft=${encodeURIComponent(draft.id)}`;
|
|
53
|
+
}
|
|
54
|
+
if (draft.planId === null) {
|
|
55
|
+
fail('resposta inválida: rascunho de Meta sem planId.');
|
|
56
|
+
}
|
|
57
|
+
return `${webBase}/planning/${draft.planId}?goalDraft=${encodeURIComponent(draft.id)}`;
|
|
58
|
+
}
|
|
59
|
+
function nextDraftCommand(draft) {
|
|
60
|
+
return draft.kind === 'PLAN'
|
|
61
|
+
? `memoring plan create --draft ${draft.id} --json`
|
|
62
|
+
: `memoring plan next ${draft.planId} --draft ${draft.id} --json`;
|
|
63
|
+
}
|
|
64
|
+
function reviewTimeoutSeconds(parsed) {
|
|
65
|
+
const raw = stringFlag(parsed.flags, 'timeout');
|
|
66
|
+
if (raw === undefined) {
|
|
67
|
+
return DRAFT_REVIEW_TIMEOUT_SECONDS;
|
|
68
|
+
}
|
|
69
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
70
|
+
fail('--timeout deve ser um inteiro.');
|
|
71
|
+
}
|
|
72
|
+
const value = Number(raw);
|
|
73
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 86_400) {
|
|
74
|
+
fail('--timeout deve estar entre 1 e 86400.');
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
export async function readPlanningDraft(rawId) {
|
|
79
|
+
const id = draftId(rawId);
|
|
80
|
+
return planningDraft(await apiFetch(`/planning/drafts/${id}`));
|
|
81
|
+
}
|
|
82
|
+
export function requireReviewed(draft, kind, planId) {
|
|
83
|
+
if (draft.kind !== kind) {
|
|
84
|
+
fail(`o rascunho ${draft.id} é do tipo ${draft.kind}; era esperado ${kind}.`);
|
|
85
|
+
}
|
|
86
|
+
if (draft.status !== 'REVIEWED') {
|
|
87
|
+
fail(`o rascunho ${draft.id} ainda está ${draft.status}; conclua a revisão web antes de criar.`);
|
|
88
|
+
}
|
|
89
|
+
if (kind === 'PLAN' && draft.planId !== null) {
|
|
90
|
+
fail('o rascunho de planejamento não pode estar associado a um planId.');
|
|
91
|
+
}
|
|
92
|
+
if (kind === 'GOAL' && draft.planId !== planId) {
|
|
93
|
+
fail(`o rascunho ${draft.id} pertence ao planejamento #${draft.planId ?? 'desconhecido'}, não ao #${planId}.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export async function createDraftHandoff(kind, planId, payload, parsed) {
|
|
97
|
+
const draft = planningDraft(await apiFetch('/planning/drafts', {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
body: { kind, planId, payload },
|
|
100
|
+
}));
|
|
101
|
+
const reviewUrl = draftReviewUrl(draft);
|
|
102
|
+
const handoff = { draft, reviewUrl, nextCommand: nextDraftCommand(draft) };
|
|
103
|
+
if (!parsed.flags['no-open']) {
|
|
104
|
+
openBrowser(reviewUrl);
|
|
105
|
+
}
|
|
106
|
+
if (parsed.flags.json) {
|
|
107
|
+
printJson(handoff);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
process.stdout.write(green('Rascunho criado. Nenhum planejamento ou Meta foi criado.\n'));
|
|
111
|
+
process.stdout.write(`Revise no navegador: ${reviewUrl}\n`);
|
|
112
|
+
process.stdout.write('O botão da página apenas conclui a revisão; depois volte ao terminal.\n');
|
|
113
|
+
process.stdout.write(`Consulte ou aguarde: memoring plan review ${draft.id} --wait\n`);
|
|
114
|
+
process.stdout.write(`Quando estiver REVIEWED, rode: ${handoff.nextCommand}\n`);
|
|
115
|
+
}
|
|
116
|
+
export async function deleteDraftAfterCreation(draft) {
|
|
117
|
+
try {
|
|
118
|
+
await apiFetch(`/planning/drafts/${draft.id}`, { method: 'DELETE' });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
process.stderr.write(yellow(`Aviso: a criação foi concluída, mas não foi possível excluir o rascunho ${draft.id} ` +
|
|
122
|
+
`(${error instanceof Error ? error.message : String(error)}). Ele expira em ${draft.expiresAt}.\n`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export async function reviewDraft(parsed, rawId) {
|
|
126
|
+
let draft = await readPlanningDraft(rawId);
|
|
127
|
+
if (parsed.flags.wait && draft.status === 'OPEN') {
|
|
128
|
+
const timeoutSeconds = reviewTimeoutSeconds(parsed);
|
|
129
|
+
const deadline = Date.now() + timeoutSeconds * 1_000;
|
|
130
|
+
if (!parsed.flags.json) {
|
|
131
|
+
process.stderr.write(`Aguardando a revisão do rascunho ${draft.id} por até ${timeoutSeconds}s…\n`);
|
|
132
|
+
}
|
|
133
|
+
while (draft.status === 'OPEN') {
|
|
134
|
+
if (Date.now() >= deadline) {
|
|
135
|
+
fail(`a revisão não foi concluída em ${timeoutSeconds}s; o rascunho continua OPEN. Consulte depois com memoring plan review ${draft.id}.`);
|
|
136
|
+
}
|
|
137
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, DRAFT_POLL_INTERVAL_MS));
|
|
138
|
+
draft = await readPlanningDraft(draft.id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const output = stringFlag(parsed.flags, 'out');
|
|
142
|
+
if (output && draft.status !== 'REVIEWED') {
|
|
143
|
+
fail(`o rascunho ${draft.id} ainda está OPEN; use --wait ou conclua a revisão antes de exportar o payload.`);
|
|
144
|
+
}
|
|
145
|
+
if (output) {
|
|
146
|
+
writeFileSync(resolve(output), `${JSON.stringify(draft.payload, null, 2)}\n`, 'utf8');
|
|
147
|
+
}
|
|
148
|
+
if (parsed.flags.json) {
|
|
149
|
+
printJson(draft);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
process.stdout.write(`${draft.kind} ${draft.id}: ${draft.status} (expira em ${draft.expiresAt}).\n`);
|
|
153
|
+
if (draft.status === 'OPEN') {
|
|
154
|
+
process.stdout.write(`Revise em ${draftReviewUrl(draft)}\n`);
|
|
155
|
+
process.stdout.write(`Para aguardar: memoring plan review ${draft.id} --wait\n`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (output) {
|
|
159
|
+
process.stdout.write(`Payload revisado salvo em ${output}.\n`);
|
|
160
|
+
}
|
|
161
|
+
process.stdout.write(`Próximo passo: ${nextDraftCommand(draft)}\n`);
|
|
162
|
+
}
|