supcoflow 0.7.0 → 0.8.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 +209 -180
- package/dist/alias.js +60 -0
- package/dist/api.js +41 -19
- package/dist/argumentos.js +92 -85
- package/dist/cli.js +40 -35
- package/dist/comandos/clave-vigente.js +2 -2
- package/dist/comandos/claves.js +25 -25
- package/dist/comandos/configurar.js +40 -40
- package/dist/comandos/datos-y-politica.js +35 -23
- package/dist/comandos/describir.js +161 -151
- package/dist/comandos/guia.js +148 -61
- package/dist/comandos/instalacion.js +71 -40
- package/dist/comandos/login.js +25 -24
- package/dist/comandos/logout.js +17 -16
- package/dist/comandos/probar.js +141 -62
- package/dist/comandos/proyecto-del-repo.js +1 -1
- package/dist/comandos/proyecto.js +16 -16
- package/dist/comandos/quien-soy.js +8 -7
- package/dist/comandos/repo-conectar.js +9 -9
- package/dist/comandos/widget.js +9 -5
- package/dist/diagnostico.js +48 -0
- package/dist/entorno.js +19 -18
- package/dist/flags-por-comando.js +25 -21
- package/dist/guardado/archivo.js +2 -2
- package/dist/guardado/index.js +12 -8
- package/dist/guardado/llavero.js +5 -5
- package/dist/guia.js +3 -1
- package/dist/idempotencia.js +6 -6
- package/dist/instalacion.js +50 -11
- package/dist/lectura.js +10 -0
- package/dist/pruebas-guardadas.js +16 -10
- package/dist/repo-local.js +6 -6
- package/dist/secreto-a-archivo.js +6 -6
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -9,7 +9,7 @@ export function urlDeLaApi(env) {
|
|
|
9
9
|
const crudo = env.SUPCOFLOW_API_URL;
|
|
10
10
|
if (crudo === undefined || crudo === "")
|
|
11
11
|
return new URL(API_DE_PRODUCCION);
|
|
12
|
-
const invalida = () => new FalloDeLaTerminal(SALIDA.entradaInvalida, "api_url_invalida", "SUPCOFLOW_API_URL
|
|
12
|
+
const invalida = () => new FalloDeLaTerminal(SALIDA.entradaInvalida, "api_url_invalida", "SUPCOFLOW_API_URL must be https, or http against localhost.");
|
|
13
13
|
let u;
|
|
14
14
|
try {
|
|
15
15
|
u = new URL(crudo);
|
|
@@ -23,7 +23,7 @@ export function urlDeLaApi(env) {
|
|
|
23
23
|
}
|
|
24
24
|
// A mano a propósito: tiene que coincidir con package.json (lo pinza test/describir.test.ts); sólo
|
|
25
25
|
// identifica a la terminal en los logs del motor, no decide nada.
|
|
26
|
-
const VERSION = "0.
|
|
26
|
+
const VERSION = "0.8.0";
|
|
27
27
|
const PLAZO_POR_DEFECTO_MS = 15_000;
|
|
28
28
|
// Los dos POST que crean algo: detrás, el motor acuña con Vercel Connect y le pregunta a GitHub varias
|
|
29
29
|
// veces (instalación, repos, comandos, rama). Con 15 s una respuesta lenta pero buena se cortaba y
|
|
@@ -49,7 +49,7 @@ async function pedir(api, ruta, init, f, plazoMs) {
|
|
|
49
49
|
texto = await res.text();
|
|
50
50
|
}
|
|
51
51
|
catch {
|
|
52
|
-
throw new FalloDeLaTerminal(SALIDA.inesperado, "motor_inalcanzable", "
|
|
52
|
+
throw new FalloDeLaTerminal(SALIDA.inesperado, "motor_inalcanzable", "I couldn't reach the engine (the network is down or it took too long).");
|
|
53
53
|
}
|
|
54
54
|
let cuerpo;
|
|
55
55
|
try {
|
|
@@ -59,15 +59,15 @@ async function pedir(api, ruta, init, f, plazoMs) {
|
|
|
59
59
|
cuerpo = undefined;
|
|
60
60
|
}
|
|
61
61
|
if (typeof cuerpo !== "object" || cuerpo === null || Array.isArray(cuerpo)) {
|
|
62
|
-
throw new FalloDeLaTerminal(SALIDA.inesperado, "respuesta_inesperada", `
|
|
62
|
+
throw new FalloDeLaTerminal(SALIDA.inesperado, "respuesta_inesperada", `The engine answered ${res.status} without JSON.`);
|
|
63
63
|
}
|
|
64
64
|
return { status: res.status, cuerpo: cuerpo };
|
|
65
65
|
}
|
|
66
66
|
function sinServicio(status) {
|
|
67
|
-
return new FalloDeLaTerminal(SALIDA.inesperado, "motor_inalcanzable", `
|
|
67
|
+
return new FalloDeLaTerminal(SALIDA.inesperado, "motor_inalcanzable", `The engine answered ${status}; try again in a few minutes.`);
|
|
68
68
|
}
|
|
69
69
|
function inesperada(status) {
|
|
70
|
-
return new FalloDeLaTerminal(SALIDA.inesperado, "respuesta_inesperada", `
|
|
70
|
+
return new FalloDeLaTerminal(SALIDA.inesperado, "respuesta_inesperada", `The engine answered ${status} with something I didn't expect.`);
|
|
71
71
|
}
|
|
72
72
|
const texto = (v) => (typeof v === "string" ? v : null);
|
|
73
73
|
const CLIENT_ID = "supcoflow-cli";
|
|
@@ -76,7 +76,7 @@ const FORMULARIO = { "content-type": "application/x-www-form-urlencoded" };
|
|
|
76
76
|
export async function pedirCodigo(api, maquina, f) {
|
|
77
77
|
const { status, cuerpo: c } = await pedir(api, "/api/dispositivo/codigo", { method: "POST", headers: FORMULARIO, body: new URLSearchParams({ client_id: CLIENT_ID, maquina }).toString() }, f);
|
|
78
78
|
if (status === 429 || status === 503) {
|
|
79
|
-
throw new FalloDeLaTerminal(SALIDA.inesperado, texto(c.error) ?? "demasiados_pedidos", "
|
|
79
|
+
throw new FalloDeLaTerminal(SALIDA.inesperado, texto(c.error) ?? "demasiados_pedidos", "The engine isn't accepting more requests right now; try again in a few minutes.");
|
|
80
80
|
}
|
|
81
81
|
if (status >= 500)
|
|
82
82
|
throw sinServicio(status);
|
|
@@ -112,7 +112,7 @@ const bearer = (clave) => ({ authorization: `Bearer ${clave}` });
|
|
|
112
112
|
export async function quienSoy(api, clave, f) {
|
|
113
113
|
const { status, cuerpo: c } = await pedir(api, "/api/yo", { method: "GET", headers: bearer(clave) }, f);
|
|
114
114
|
if (status === 401) {
|
|
115
|
-
throw new FalloDeLaTerminal(SALIDA.faltaLogin, "clave_invalida", texto(c.message) ?? "
|
|
115
|
+
throw new FalloDeLaTerminal(SALIDA.faltaLogin, "clave_invalida", texto(c.message) ?? "The key is no longer valid: log in again.");
|
|
116
116
|
}
|
|
117
117
|
if (status >= 500)
|
|
118
118
|
throw sinServicio(status);
|
|
@@ -146,16 +146,16 @@ async function conClave(api, clave, ruta, init, f, plazoMs) {
|
|
|
146
146
|
const error = texto(r.cuerpo.error) ?? "respuesta_inesperada";
|
|
147
147
|
const message = (porDefecto) => taparSecretos(texto(r.cuerpo.message) ?? porDefecto);
|
|
148
148
|
if (r.status === 401) {
|
|
149
|
-
throw new FalloDeLaTerminal(SALIDA.faltaLogin, "clave_invalida", message("
|
|
149
|
+
throw new FalloDeLaTerminal(SALIDA.faltaLogin, "clave_invalida", message("The key is no longer valid: log in again."));
|
|
150
150
|
}
|
|
151
151
|
if (r.status === 403 && (error === "requiere_persona" || error === "clave_de_consulta")) {
|
|
152
|
-
throw new FalloDeLaTerminal(SALIDA.haceFaltaUnaPersona, error, message("
|
|
152
|
+
throw new FalloDeLaTerminal(SALIDA.haceFaltaUnaPersona, error, message("A person who is logged in has to do this."));
|
|
153
153
|
}
|
|
154
154
|
if (r.status === 422 && error === "cuenta_sin_github") {
|
|
155
|
-
throw new FalloDeLaTerminal(SALIDA.haceFaltaUnaPersona, error, message("
|
|
155
|
+
throw new FalloDeLaTerminal(SALIDA.haceFaltaUnaPersona, error, message("Your account has no GitHub linked."));
|
|
156
156
|
}
|
|
157
157
|
if (r.status === 400 && error === "repo_invalido") {
|
|
158
|
-
throw new FalloDeLaTerminal(SALIDA.entradaInvalida, error, message("
|
|
158
|
+
throw new FalloDeLaTerminal(SALIDA.entradaInvalida, error, message("The repo must be owner/repo."));
|
|
159
159
|
}
|
|
160
160
|
if (r.status >= 500)
|
|
161
161
|
throw sinServicio(r.status);
|
|
@@ -181,7 +181,7 @@ function aProyecto(v, status) {
|
|
|
181
181
|
function rechazada(r) {
|
|
182
182
|
const error = texto(r.cuerpo.error);
|
|
183
183
|
if (r.status >= 400 && r.status < 500 && error) {
|
|
184
|
-
return new FalloDeLaTerminal(SALIDA.entradaInvalida, error, taparSecretos(texto(r.cuerpo.message) ?? `
|
|
184
|
+
return new FalloDeLaTerminal(SALIDA.entradaInvalida, error, taparSecretos(texto(r.cuerpo.message) ?? `The engine rejected the request (${r.status}).`));
|
|
185
185
|
}
|
|
186
186
|
return inesperada(r.status);
|
|
187
187
|
}
|
|
@@ -309,6 +309,10 @@ export function publicarGuia(api, clave, proyectoId, cuerpo, f) {
|
|
|
309
309
|
export function leerGuia(api, clave, proyectoId, f) {
|
|
310
310
|
return conClave(api, clave, `${rutaDeLaGuia(proyectoId)}?comprobar=1`, { method: "GET" }, f, PLAZO_DE_ALTA_MS);
|
|
311
311
|
}
|
|
312
|
+
// Las publicaciones del repo y la guía que rige ahora (`guide versions`): sólo lectura, la misma clase que `leerGuia`.
|
|
313
|
+
export function leerVersionesDeLaGuia(api, clave, proyectoId, f) {
|
|
314
|
+
return conClave(api, clave, `${rutaDeLaGuia(proyectoId)}/versiones`, { method: "GET" }, f, PLAZO_DE_ALTA_MS);
|
|
315
|
+
}
|
|
312
316
|
export function elegirOrigen(api, clave, proyectoId, origen, f) {
|
|
313
317
|
return conClave(api, clave, `${rutaDeLaGuia(proyectoId)}/origen`, { method: "POST", headers: JSON_BODY, body: JSON.stringify({ origen }) }, f);
|
|
314
318
|
}
|
|
@@ -320,17 +324,30 @@ function aWidget(v, status) {
|
|
|
320
324
|
const [tenantId, projectId, backendUrl, version, instalar, csp] = [v.tenant_id, v.project_id, v.backend_url, v.version, v.instalar, v.csp].map(texto);
|
|
321
325
|
const [archivo, contenido, uso] = [n.archivo_sugerido, n.contenido, n.uso].map(texto);
|
|
322
326
|
const linea = texto(s.linea);
|
|
327
|
+
// AD9: `archivos_anteriores` vive sólo dentro de `nextjs`, al lado de `archivo_sugerido`; arriba de todo no se lee.
|
|
328
|
+
const anteriores = listaDeTextos(n.archivos_anteriores);
|
|
329
|
+
if (anteriores === null)
|
|
330
|
+
throw inesperada(status);
|
|
323
331
|
if (!tenantId || !projectId || !backendUrl || !version || !instalar || !csp || v.paquete !== "@supcoflow/chat"
|
|
324
332
|
|| (n.estado !== "probando" && n.estado !== "soportado") || !archivo || !contenido || !uso
|
|
325
333
|
|| s.estado !== "disponible_sin_soporte" || !linea)
|
|
326
334
|
throw inesperada(status);
|
|
327
335
|
return {
|
|
328
336
|
tenant_id: tenantId, project_id: projectId, backend_url: backendUrl, paquete: "@supcoflow/chat", version, instalar,
|
|
329
|
-
nextjs: { estado: n.estado, archivo_sugerido: archivo, contenido, uso },
|
|
337
|
+
nextjs: { estado: n.estado, archivo_sugerido: archivo, archivos_anteriores: anteriores, contenido, uso },
|
|
330
338
|
script: { estado: "disponible_sin_soporte", linea },
|
|
331
339
|
csp,
|
|
332
340
|
};
|
|
333
341
|
}
|
|
342
|
+
// Una lista de textos no vacíos; ausente es lista vacía (un motor sin el campo), y cualquier otra forma es null.
|
|
343
|
+
function listaDeTextos(v) {
|
|
344
|
+
if (v === undefined || v === null)
|
|
345
|
+
return [];
|
|
346
|
+
if (!Array.isArray(v))
|
|
347
|
+
return null;
|
|
348
|
+
const textos = v.map(texto);
|
|
349
|
+
return textos.every((t) => t !== null && t !== "") ? textos : null;
|
|
350
|
+
}
|
|
334
351
|
export async function leerWidget(api, clave, proyectoId, f) {
|
|
335
352
|
const r = await conClave(api, clave, `/api/proyectos/${encodeURIComponent(proyectoId)}/widget`, { method: "GET" }, f);
|
|
336
353
|
if (r.status !== 200)
|
|
@@ -354,7 +371,10 @@ export async function leerDiagnostico(api, clave, proyectoId, f) {
|
|
|
354
371
|
throw inesperada(r.status);
|
|
355
372
|
return {
|
|
356
373
|
listo: c.listo,
|
|
357
|
-
puntos: puntos.map((p) => ({
|
|
374
|
+
puntos: puntos.map((p) => ({
|
|
375
|
+
punto: p.punto, ok: p.ok, mensaje: taparSecretos(p.mensaje),
|
|
376
|
+
...(p.grupo === "instalacion" || p.grupo === "prueba" ? { grupo: p.grupo } : {}),
|
|
377
|
+
})),
|
|
358
378
|
politica: c.politica,
|
|
359
379
|
techoUsd: c.techo_usd,
|
|
360
380
|
saldoUsd: saldo, topeUsd: tope, comprometidoUsd: comprometido,
|
|
@@ -369,6 +389,8 @@ export async function pedirPrueba(api, clave, proyectoId, intento, f) {
|
|
|
369
389
|
const cuerpo = { techo_usd: intento.techoUsd };
|
|
370
390
|
if (intento.mensaje !== null)
|
|
371
391
|
cuerpo.mensaje = intento.mensaje;
|
|
392
|
+
if (intento.politicaEsperada !== null)
|
|
393
|
+
cuerpo.politica_esperada = intento.politicaEsperada;
|
|
372
394
|
let r;
|
|
373
395
|
try {
|
|
374
396
|
r = await pedir(api, `${rutaDelProyecto(proyectoId)}/pruebas`, {
|
|
@@ -376,23 +398,23 @@ export async function pedirPrueba(api, clave, proyectoId, intento, f) {
|
|
|
376
398
|
}, f, PLAZO_DE_ALTA_MS);
|
|
377
399
|
}
|
|
378
400
|
catch (e) {
|
|
379
|
-
const fallo = e instanceof FalloDeLaTerminal ? e : new FalloDeLaTerminal(SALIDA.inesperado, "motor_inalcanzable", "
|
|
401
|
+
const fallo = e instanceof FalloDeLaTerminal ? e : new FalloDeLaTerminal(SALIDA.inesperado, "motor_inalcanzable", "I couldn't reach the engine.");
|
|
380
402
|
return { tipo: "ambigua", error: fallo.error, message: fallo.message };
|
|
381
403
|
}
|
|
382
404
|
const error = texto(r.cuerpo.error);
|
|
383
|
-
const message = taparSecretos(texto(r.cuerpo.message) ?? `
|
|
405
|
+
const message = taparSecretos(texto(r.cuerpo.message) ?? `The engine answered ${r.status}.`);
|
|
384
406
|
if (r.status === 202 || r.status === 200 || r.status === 201) {
|
|
385
407
|
const [pruebaId, reporteId, estado] = [r.cuerpo.prueba_id, r.cuerpo.reporte_id, r.cuerpo.estado].map(texto);
|
|
386
408
|
if (pruebaId && /^prb_[0-9a-f]{16}$/.test(pruebaId) && reporteId && estado)
|
|
387
409
|
return { tipo: "aceptada", pruebaId, reporteId, estado };
|
|
388
|
-
return { tipo: "ambigua", error: "respuesta_inesperada", message: `
|
|
410
|
+
return { tipo: "ambigua", error: "respuesta_inesperada", message: `The engine answered ${r.status} without the test.` };
|
|
389
411
|
}
|
|
390
412
|
if (r.status >= 500 || (r.status === 409 && error === "idempotency_en_curso")) {
|
|
391
413
|
return { tipo: "ambigua", error: error ?? "motor_inalcanzable", message };
|
|
392
414
|
}
|
|
393
415
|
if (r.status >= 400)
|
|
394
416
|
return { tipo: "rechazo", status: r.status, error: error ?? "rechazado", message, cuerpo: r.cuerpo };
|
|
395
|
-
return { tipo: "ambigua", error: "respuesta_inesperada", message: `
|
|
417
|
+
return { tipo: "ambigua", error: "respuesta_inesperada", message: `The engine answered ${r.status} with something I didn't expect.` };
|
|
396
418
|
}
|
|
397
419
|
export async function leerPrueba(api, clave, proyectoId, pruebaId, f, plazoMs) {
|
|
398
420
|
const r = await conClave(api, clave, `${rutaDelProyecto(proyectoId)}/pruebas/${encodeURIComponent(pruebaId)}`, { method: "GET" }, f, plazoMs);
|
package/dist/argumentos.js
CHANGED
|
@@ -1,115 +1,122 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
+
import { COMANDOS_VIEJOS, FLAGS_VIEJOS } from "./alias.js";
|
|
2
3
|
import { FalloDeLaTerminal, SALIDA } from "./contrato.js";
|
|
3
4
|
export const COMANDOS_COMPUESTOS = [
|
|
4
|
-
"repo
|
|
5
|
-
"chat
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
5
|
+
"repo connect", "project create", "project show",
|
|
6
|
+
"chat on", "chat off", "chat brand", "chat logo", "sites add", "sites remove",
|
|
7
|
+
"data set", "policy set", "keys create", "keys list", "keys revoke",
|
|
8
|
+
"guide check", "guide publish", "guide show", "guide use", "guide versions",
|
|
9
|
+
"install record",
|
|
9
10
|
];
|
|
10
|
-
// Sólo estas primeras palabras admiten una segunda: cualquier otro par sigue siendo «de más».
|
|
11
|
-
|
|
11
|
+
// Sólo estas primeras palabras admiten una segunda: cualquier otro par sigue siendo «de más». Entran también las de
|
|
12
|
+
// los nombres viejos (`guia publicar`), que se resuelven más abajo.
|
|
13
|
+
const PRIMERAS_PALABRAS = new Set([...COMANDOS_COMPUESTOS, ...Object.keys(COMANDOS_VIEJOS)].filter((c) => c.includes(" ")).map((c) => c.split(" ")[0]));
|
|
12
14
|
const FLAGS_CON_VALOR = [
|
|
13
|
-
"repo", "install", "test", "
|
|
14
|
-
"
|
|
15
|
+
"repo", "install", "test", "site", "title", "greeting", "file", "allow", "mode", "name", "save-to", "id", "source", "skill", "app",
|
|
16
|
+
"message", "test-id", "ceiling", "expect-policy",
|
|
15
17
|
];
|
|
18
|
+
const FLAGS_BOOLEANOS = ["json", "dry-run", "help", "start", "wait", "from-github", "authorize-spend"];
|
|
19
|
+
// El flag que el token nombró, ya llevado a su nombre canónico (el viejo y el nuevo cuentan como el mismo).
|
|
20
|
+
const canonico = (nombre) => FLAGS_VIEJOS[nombre] ?? nombre;
|
|
21
|
+
const invalida = (error, message) => new FalloDeLaTerminal(SALIDA.entradaInvalida, error, message);
|
|
22
|
+
function opciones() {
|
|
23
|
+
const o = {};
|
|
24
|
+
for (const f of FLAGS_BOOLEANOS)
|
|
25
|
+
o[f] = f === "help" ? { type: "boolean", short: "h" } : { type: "boolean" };
|
|
26
|
+
for (const f of FLAGS_CON_VALOR)
|
|
27
|
+
o[f] = { type: "string" };
|
|
28
|
+
o.theme = { type: "string", multiple: true };
|
|
29
|
+
for (const [viejo, nuevo] of Object.entries(FLAGS_VIEJOS))
|
|
30
|
+
o[viejo] = { ...o[nuevo] };
|
|
31
|
+
return o;
|
|
32
|
+
}
|
|
16
33
|
// strict: un flag mal escrito (`--estart`) no se ignora en silencio: un asistente creería que
|
|
17
34
|
// pidió algo que no pidió.
|
|
18
35
|
export function leerArgumentos(argv) {
|
|
19
36
|
let parsed;
|
|
20
37
|
try {
|
|
21
|
-
parsed = parseArgs({
|
|
22
|
-
args: argv,
|
|
23
|
-
strict: true,
|
|
24
|
-
allowPositionals: true,
|
|
25
|
-
options: {
|
|
26
|
-
json: { type: "boolean" },
|
|
27
|
-
"dry-run": { type: "boolean" },
|
|
28
|
-
help: { type: "boolean", short: "h" },
|
|
29
|
-
start: { type: "boolean" },
|
|
30
|
-
wait: { type: "boolean" },
|
|
31
|
-
repo: { type: "string" },
|
|
32
|
-
install: { type: "string" },
|
|
33
|
-
test: { type: "string" },
|
|
34
|
-
sitio: { type: "string" },
|
|
35
|
-
titulo: { type: "string" },
|
|
36
|
-
saludo: { type: "string" },
|
|
37
|
-
archivo: { type: "string" },
|
|
38
|
-
permitir: { type: "string" },
|
|
39
|
-
modo: { type: "string" },
|
|
40
|
-
nombre: { type: "string" },
|
|
41
|
-
"guardar-en": { type: "string" },
|
|
42
|
-
id: { type: "string" },
|
|
43
|
-
origen: { type: "string" },
|
|
44
|
-
"desde-github": { type: "boolean" },
|
|
45
|
-
skill: { type: "string" },
|
|
46
|
-
app: { type: "string" },
|
|
47
|
-
"autorizo-gasto": { type: "boolean" },
|
|
48
|
-
mensaje: { type: "string" },
|
|
49
|
-
prueba: { type: "string" },
|
|
50
|
-
tema: { type: "string", multiple: true },
|
|
51
|
-
},
|
|
52
|
-
tokens: true,
|
|
53
|
-
});
|
|
38
|
+
parsed = parseArgs({ args: argv, strict: true, allowPositionals: true, options: opciones(), tokens: true });
|
|
54
39
|
}
|
|
55
40
|
catch (e) {
|
|
56
41
|
const code = e.code;
|
|
57
42
|
if (code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
|
58
43
|
const m = /'(--?[^']+)'/.exec(e.message);
|
|
59
|
-
throw
|
|
44
|
+
throw invalida("flag_desconocido", `Unknown flag ${m ? m[1] : "you passed"}. Try \`supcoflow --help\`.`);
|
|
60
45
|
}
|
|
61
|
-
throw
|
|
46
|
+
throw invalida("argumentos_invalidos", `${e.message} Try \`supcoflow --help\`.`);
|
|
62
47
|
}
|
|
63
48
|
const pos = parsed.positionals;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
49
|
+
if (pos.length > 2 || (pos.length === 2 && !PRIMERAS_PALABRAS.has(pos[0]))) {
|
|
50
|
+
throw invalida("argumentos_de_mas", "You passed more than one command. Try `supcoflow --help`.");
|
|
51
|
+
}
|
|
52
|
+
const tokens = (parsed.tokens ?? []).filter((t) => t.kind === "option");
|
|
53
|
+
const viejos = [];
|
|
54
|
+
// `parseArgs` se queda con el último valor de un flag repetido: un asistente que repite `--repo` (o que pasa el
|
|
55
|
+
// nombre viejo y el nuevo de un mismo flag) creería que se usó el primero. Se mira en los tokens y se rechaza.
|
|
69
56
|
for (const nombre of FLAGS_CON_VALOR) {
|
|
70
|
-
const toks =
|
|
71
|
-
if (toks.length > 1 || toks.some((t) => t.
|
|
72
|
-
throw
|
|
57
|
+
const toks = tokens.filter((t) => canonico(t.name) === nombre);
|
|
58
|
+
if (toks.length > 1 || toks.some((t) => t.value === "")) {
|
|
59
|
+
throw invalida("argumentos_invalidos", `--${nombre} goes once, with a value. Try \`supcoflow --help\`.`);
|
|
73
60
|
}
|
|
74
61
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
for (const t of tokens)
|
|
63
|
+
if (t.name in FLAGS_VIEJOS)
|
|
64
|
+
viejos.push(`--${t.name}`);
|
|
65
|
+
// `--theme` sí se repite (uno por clave), pero ninguno vacío: un `--theme ""` es un error de quien arma
|
|
66
|
+
// el comando, no «sin tema». Se junta desde los tokens para conservar el orden entre el nombre viejo y el nuevo.
|
|
67
|
+
const tema = tokens.filter((t) => canonico(t.name) === "theme").map((t) => t.value ?? "");
|
|
68
|
+
if (tema.some((t) => t === ""))
|
|
69
|
+
throw invalida("argumentos_invalidos", "--theme goes with a key=value. Try `supcoflow --help`.");
|
|
80
70
|
// Un par desconocido (`repo borrar`) queda como la primera palabra sola: el despacho la rechaza
|
|
81
71
|
// como comando desconocido, con el mismo mensaje que cualquier otra.
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
72
|
+
const escrito = pos.length === 2 ? `${pos[0]} ${pos[1]}` : (pos[0] ?? null);
|
|
73
|
+
let comando;
|
|
74
|
+
if (escrito !== null && escrito in COMANDOS_VIEJOS) {
|
|
75
|
+
comando = COMANDOS_VIEJOS[escrito];
|
|
76
|
+
viejos.unshift(escrito);
|
|
77
|
+
}
|
|
78
|
+
else if (pos.length === 2 && COMANDOS_COMPUESTOS.includes(escrito)) {
|
|
79
|
+
comando = escrito;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
comando = pos[0] ?? null;
|
|
83
|
+
}
|
|
84
|
+
const un = (nombre) => {
|
|
85
|
+
const t = tokens.find((x) => canonico(x.name) === nombre);
|
|
86
|
+
return t === undefined ? null : (t.value ?? null);
|
|
87
|
+
};
|
|
88
|
+
const hay = (nombre) => tokens.some((x) => canonico(x.name) === nombre);
|
|
85
89
|
return {
|
|
86
90
|
comando,
|
|
91
|
+
viejos,
|
|
87
92
|
flags: {
|
|
88
|
-
json:
|
|
89
|
-
dryRun:
|
|
90
|
-
help:
|
|
91
|
-
start:
|
|
92
|
-
wait:
|
|
93
|
-
repo:
|
|
94
|
-
install:
|
|
95
|
-
test:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
id:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
skill:
|
|
108
|
-
app:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
93
|
+
json: hay("json"),
|
|
94
|
+
dryRun: hay("dry-run"),
|
|
95
|
+
help: hay("help"),
|
|
96
|
+
start: hay("start"),
|
|
97
|
+
wait: hay("wait"),
|
|
98
|
+
repo: un("repo"),
|
|
99
|
+
install: un("install"),
|
|
100
|
+
test: un("test"),
|
|
101
|
+
site: un("site"),
|
|
102
|
+
title: un("title"),
|
|
103
|
+
greeting: un("greeting"),
|
|
104
|
+
file: un("file"),
|
|
105
|
+
allow: un("allow"),
|
|
106
|
+
mode: un("mode"),
|
|
107
|
+
name: un("name"),
|
|
108
|
+
saveTo: un("save-to"),
|
|
109
|
+
id: un("id"),
|
|
110
|
+
source: un("source"),
|
|
111
|
+
fromGithub: hay("from-github"),
|
|
112
|
+
skill: un("skill"),
|
|
113
|
+
app: un("app"),
|
|
114
|
+
authorizeSpend: hay("authorize-spend"),
|
|
115
|
+
message: un("message"),
|
|
116
|
+
testId: un("test-id"),
|
|
117
|
+
ceiling: un("ceiling"),
|
|
118
|
+
expectPolicy: un("expect-policy"),
|
|
119
|
+
theme: tema,
|
|
113
120
|
},
|
|
114
121
|
};
|
|
115
122
|
}
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { revocarPropia } from "./api.js";
|
|
|
6
6
|
import { leerArgumentos } from "./argumentos.js";
|
|
7
7
|
import { clavesCrear, clavesListar, clavesRevocar } from "./comandos/claves.js";
|
|
8
8
|
import { chatLogo, chatMarca, chatPrenderApagar, comandoConfiguracion, sitios } from "./comandos/configurar.js";
|
|
9
|
-
import { guiaPublicar, guiaRevisar, guiaUsar, guiaVer } from "./comandos/guia.js";
|
|
9
|
+
import { guiaPublicar, guiaRevisar, guiaUsar, guiaVer, guiaVersiones } from "./comandos/guia.js";
|
|
10
10
|
import { instalacionAnotar } from "./comandos/instalacion.js";
|
|
11
11
|
import { comandoCatalogo, datosFijar, politicaFijar } from "./comandos/datos-y-politica.js";
|
|
12
12
|
import { ayuda, comandoDescribir, DESCRIPCION } from "./comandos/describir.js";
|
|
@@ -22,15 +22,16 @@ import { entornoReal } from "./entorno.js";
|
|
|
22
22
|
import { flagQueNoAplica } from "./flags-por-comando.js";
|
|
23
23
|
const COMANDOS = new Set(DESCRIPCION.comandos.map((c) => c.nombre));
|
|
24
24
|
const invalida = (error, message) => new FalloDeLaTerminal(SALIDA.entradaInvalida, error, message);
|
|
25
|
+
// Los nombres que la terminal tenía antes de la 0.8.0 siguen valiendo (`src/alias.ts`), pero no salen en `describe`.
|
|
25
26
|
async function correr(argv) {
|
|
26
27
|
// Si los argumentos no se pueden leer no se sabe qué flags hay: se mira `--json` a mano para que
|
|
27
28
|
// el error salga en el formato que pidió quien llama.
|
|
28
29
|
let json = argv.includes("--json");
|
|
29
30
|
try {
|
|
30
|
-
const { comando, flags } = leerArgumentos(argv);
|
|
31
|
+
const { comando, flags, viejos } = leerArgumentos(argv);
|
|
31
32
|
json = flags.json;
|
|
32
33
|
if (comando !== null && !COMANDOS.has(comando)) {
|
|
33
|
-
throw invalida("comando_desconocido", `
|
|
34
|
+
throw invalida("comando_desconocido", `Unknown command «${comando}». Try \`supcoflow --help\`.`);
|
|
34
35
|
}
|
|
35
36
|
if (flags.help || comando === null) {
|
|
36
37
|
const texto = ayuda(comando);
|
|
@@ -38,25 +39,26 @@ async function correr(argv) {
|
|
|
38
39
|
}
|
|
39
40
|
const presentes = {
|
|
40
41
|
start: flags.start, wait: flags.wait, repo: flags.repo !== null, install: flags.install !== null, test: flags.test !== null,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
site: flags.site !== null, title: flags.title !== null, greeting: flags.greeting !== null, theme: flags.theme.length > 0,
|
|
43
|
+
file: flags.file !== null, allow: flags.allow !== null, mode: flags.mode !== null,
|
|
44
|
+
name: flags.name !== null, saveTo: flags.saveTo !== null, id: flags.id !== null, source: flags.source !== null, fromGithub: flags.fromGithub,
|
|
44
45
|
skill: flags.skill !== null, app: flags.app !== null,
|
|
45
|
-
|
|
46
|
+
authorizeSpend: flags.authorizeSpend, message: flags.message !== null, testId: flags.testId !== null,
|
|
47
|
+
ceiling: flags.ceiling !== null, expectPolicy: flags.expectPolicy !== null,
|
|
46
48
|
};
|
|
47
49
|
const noAplica = flagQueNoAplica(comando, Object.keys(presentes).filter((k) => presentes[k]));
|
|
48
50
|
if (noAplica !== null)
|
|
49
51
|
throw invalida("flag_no_aplica", noAplica);
|
|
50
52
|
if (flags.start && flags.wait) {
|
|
51
|
-
throw invalida("flags_incompatibles", "--start
|
|
53
|
+
throw invalida("flags_incompatibles", "--start and --wait go separately: first one and then the other.");
|
|
52
54
|
}
|
|
53
|
-
if (comando === "
|
|
55
|
+
if (comando === "describe")
|
|
54
56
|
return { r: comandoDescribir(), json };
|
|
55
57
|
if (comando === "login") {
|
|
56
58
|
// ANTES de armar el entorno: no hay cómo simular un canje sin consumirlo, y este error no
|
|
57
59
|
// puede depender de leer nada guardado ni de llamar al motor.
|
|
58
60
|
if (flags.wait && flags.dryRun) {
|
|
59
|
-
throw invalida("dry_run_no_soportado", "`login --wait` no
|
|
61
|
+
throw invalida("dry_run_no_soportado", "`login --wait` has no --dry-run: polling consumes the code. Try `supcoflow login --start --dry-run`.");
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
const e = entornoReal();
|
|
@@ -78,9 +80,9 @@ async function correr(argv) {
|
|
|
78
80
|
}, flags),
|
|
79
81
|
json,
|
|
80
82
|
};
|
|
81
|
-
case "
|
|
83
|
+
case "whoami":
|
|
82
84
|
return { r: await comandoQuienSoy({ api: e.api, guardado: e.guardado, quienSoy: e.quienSoy, avisar: e.avisar }), json };
|
|
83
|
-
case "repo
|
|
85
|
+
case "repo connect":
|
|
84
86
|
if (flags.start)
|
|
85
87
|
return { r: await repoConectarStart(e.conexion, flags), json };
|
|
86
88
|
// `--wait` sólo lee (un GET por tramo): con --dry-run haría lo mismo, así que no se rechaza.
|
|
@@ -90,54 +92,57 @@ async function correr(argv) {
|
|
|
90
92
|
if (flags.dryRun)
|
|
91
93
|
return { r: await repoConectarStart(e.conexion, { repo: flags.repo, dryRun: true }), json };
|
|
92
94
|
return { r: await repoConectarInteractivo({ ...e.conexion, esTty: e.esTty, mostrar: e.mostrar }, flags), json };
|
|
93
|
-
case "
|
|
95
|
+
case "project create":
|
|
94
96
|
return { r: await proyectoCrear(e.proyecto, flags), json };
|
|
95
|
-
case "
|
|
97
|
+
case "project show":
|
|
96
98
|
return { r: await proyectoVer(e.proyecto, flags), json };
|
|
97
|
-
case "
|
|
99
|
+
case "catalog":
|
|
98
100
|
return { r: await comandoCatalogo(e.configurar), json };
|
|
99
|
-
case "
|
|
101
|
+
case "config":
|
|
100
102
|
return { r: await comandoConfiguracion(e.configurar, flags), json };
|
|
101
|
-
case "chat
|
|
103
|
+
case "chat on":
|
|
102
104
|
return { r: await chatPrenderApagar(e.configurar, flags, true), json };
|
|
103
|
-
case "chat
|
|
105
|
+
case "chat off":
|
|
104
106
|
return { r: await chatPrenderApagar(e.configurar, flags, false), json };
|
|
105
|
-
case "chat
|
|
107
|
+
case "chat brand":
|
|
106
108
|
return { r: await chatMarca(e.configurar, flags), json };
|
|
107
109
|
case "chat logo":
|
|
108
110
|
return { r: await chatLogo(e.configurar, flags), json };
|
|
109
|
-
case "
|
|
111
|
+
case "sites add":
|
|
110
112
|
return { r: await sitios(e.configurar, flags, "agregar"), json };
|
|
111
|
-
case "
|
|
113
|
+
case "sites remove":
|
|
112
114
|
return { r: await sitios(e.configurar, flags, "quitar"), json };
|
|
113
|
-
case "
|
|
115
|
+
case "data set":
|
|
114
116
|
return { r: await datosFijar(e.configurar, flags), json };
|
|
115
|
-
case "
|
|
117
|
+
case "policy set":
|
|
116
118
|
return { r: await politicaFijar(e.configurar, flags), json };
|
|
117
|
-
case "
|
|
119
|
+
case "keys create":
|
|
118
120
|
return { r: await clavesCrear(e.claves, flags), json };
|
|
119
|
-
case "
|
|
121
|
+
case "keys list":
|
|
120
122
|
return { r: await clavesListar(e.claves, flags), json };
|
|
121
|
-
case "
|
|
123
|
+
case "keys revoke":
|
|
122
124
|
return { r: await clavesRevocar(e.claves, flags), json };
|
|
123
|
-
case "
|
|
125
|
+
case "guide check":
|
|
124
126
|
return { r: await guiaRevisar(e.guia, flags), json };
|
|
125
|
-
case "
|
|
127
|
+
case "guide publish":
|
|
126
128
|
return { r: await guiaPublicar(e.guia, flags), json };
|
|
127
|
-
case "
|
|
129
|
+
case "guide show":
|
|
128
130
|
return { r: await guiaVer(e.guia, flags), json };
|
|
129
|
-
case "
|
|
131
|
+
case "guide use":
|
|
130
132
|
return { r: await guiaUsar(e.guia, flags), json };
|
|
133
|
+
case "guide versions":
|
|
134
|
+
return { r: await guiaVersiones(e.guia, flags), json };
|
|
131
135
|
case "widget":
|
|
132
136
|
return { r: await widget(e.widget, flags), json };
|
|
133
|
-
case "
|
|
137
|
+
case "install record":
|
|
134
138
|
return { r: await instalacionAnotar(e.instalacion, flags), json };
|
|
135
|
-
case "
|
|
139
|
+
case "diagnose":
|
|
136
140
|
return { r: await diagnosticar(e.prueba, flags), json };
|
|
137
|
-
case "
|
|
138
|
-
|
|
141
|
+
case "test":
|
|
142
|
+
// El alias viejo (`probar`, o `--autorizo-gasto` con cualquier nombre) no puede ampliar lo autorizado: lo marca.
|
|
143
|
+
return { r: await probar(e.prueba, { ...flags, viejo: viejos.includes("probar") || viejos.includes("--autorizo-gasto") }), json };
|
|
139
144
|
}
|
|
140
|
-
throw new Error(`
|
|
145
|
+
throw new Error(`command without dispatch: ${comando}`);
|
|
141
146
|
}
|
|
142
147
|
catch (e) {
|
|
143
148
|
return { r: resultadoDeFallo(e), json };
|
|
@@ -7,11 +7,11 @@ export async function claveVigente(guardado, origen) {
|
|
|
7
7
|
// «Iniciá sesión» con la clave guardada en un llavero que no contestó invita a un login que la
|
|
8
8
|
// pisaría: no es «sin sesión», es «no pude mirar» (Review Focus 12).
|
|
9
9
|
if (vigente === null && !lectura.completa) {
|
|
10
|
-
const message = "
|
|
10
|
+
const message = "I couldn't read the saved key (the keychain didn't respond). Unlock it and try again.";
|
|
11
11
|
return { ok: false, resultado: { codigo: SALIDA.inesperado, json: { ok: false, error: "llavero_no_responde", message }, texto: message } };
|
|
12
12
|
}
|
|
13
13
|
if (vigente === null) {
|
|
14
|
-
const message = "
|
|
14
|
+
const message = "Log in with `supcoflow login`.";
|
|
15
15
|
return { ok: false, resultado: { codigo: SALIDA.faltaLogin, json: { ok: false, error: "sin_sesion", message }, texto: message } };
|
|
16
16
|
}
|
|
17
17
|
return { ok: true, vigente: { clave: vigente.clave, fuente: vigente.fuente, completa: lectura.completa } };
|