agent-cloud-kit 1.0.0-rc.3 → 1.0.0-rc.4
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/dist/prepare/banner.js +11 -0
- package/dist/prepare/index.js +69 -51
- package/dist/prepare/ui.js +128 -0
- package/package.json +1 -1
package/dist/prepare/banner.js
CHANGED
|
@@ -59,6 +59,17 @@ export function renderBanner(input) {
|
|
|
59
59
|
export function enterLine(color) {
|
|
60
60
|
return ` ${paint(color, "1;32", "⏎ Enter")} abre Claude Code ${paint(color, "2", "· Ctrl+C cancela")} `;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The line asked while the user configures the environment in claude.ai.
|
|
64
|
+
*
|
|
65
|
+
* @param color - Whether to use ANSI colors.
|
|
66
|
+
* @returns The text.
|
|
67
|
+
* @example
|
|
68
|
+
* continueLine(false); // "\n ⏎ Enter cuando hayas configurado el entorno en claude.ai · abre la sesión cloud que lo verifica "
|
|
69
|
+
*/
|
|
70
|
+
export function continueLine(color) {
|
|
71
|
+
return `\n ${paint(color, "1;32", "⏎ Enter")} cuando hayas configurado el entorno en claude.ai ${paint(color, "2", "· abre la sesión cloud que lo verifica")} `;
|
|
72
|
+
}
|
|
62
73
|
/** Config file claude-repo-guard installs; the free mode is used only when it is there. */
|
|
63
74
|
const GUARD_CONFIG = ".claude/repo-guard.config.json";
|
|
64
75
|
/**
|
package/dist/prepare/index.js
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
import { existsSync, lstatSync, statSync } from "node:fs";
|
|
7
7
|
import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
8
8
|
import { readConfig } from "../config.js";
|
|
9
|
-
import { CONFIG_FILE,
|
|
9
|
+
import { CONFIG_FILE, PACKAGE } from "../constants.js";
|
|
10
10
|
import { defaultBranch } from "../detect.js";
|
|
11
11
|
import { init } from "../init.js";
|
|
12
12
|
import { MANUAL_FILE, renderEnvironmentSection, writeManual } from "../manual.js";
|
|
13
|
-
import { claudeCommand, enterLine, hasFreeScript, renderBanner } from "./banner.js";
|
|
13
|
+
import { claudeCommand, continueLine, enterLine, hasFreeScript, renderBanner } from "./banner.js";
|
|
14
14
|
import { preflight } from "./detect.js";
|
|
15
|
+
import { formatLine, initKind, nextSteps, styleMarkdown, unprefixed } from "./ui.js";
|
|
15
16
|
import { configurePrompt, verifyPrompt } from "./prompts.js";
|
|
16
17
|
/** Prefix of the branch `prepare` creates; the day follows it. */
|
|
17
18
|
const BRANCH_PREFIX = "chore/cloud-ready-";
|
|
@@ -36,7 +37,6 @@ const INCLUDE_QUESTION = "¿Incluir estos cambios en el commit? [s/N]";
|
|
|
36
37
|
/** Answers to `INCLUDE_QUESTION` that mean yes. */
|
|
37
38
|
const YES_ANSWER = /^(s|si|sí|y|yes)$/i;
|
|
38
39
|
/** Text shown while the user configures the environment in claude.ai. */
|
|
39
|
-
const WAIT_TEXT = "Configura el entorno en claude.ai y luego presiona Enter ";
|
|
40
40
|
/** Matches a GitHub remote URL (https or ssh) and captures `owner/repo`. */
|
|
41
41
|
const GITHUB_REMOTE = /github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/;
|
|
42
42
|
/** Matches a pull request URL and captures its number. */
|
|
@@ -75,6 +75,18 @@ export function branchDay(date) {
|
|
|
75
75
|
const pad = (n) => String(n).padStart(2, "0");
|
|
76
76
|
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}`;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Prints one line with the icon and color of its kind (or the plain prefix without color).
|
|
80
|
+
*
|
|
81
|
+
* @param deps - Dependencies (`print`, `color`).
|
|
82
|
+
* @param kind - Kind of line.
|
|
83
|
+
* @param text - Text, without prefix.
|
|
84
|
+
* @example
|
|
85
|
+
* say(deps, "ok", "PR en borrador: https://github.com/acme/app/pull/7");
|
|
86
|
+
*/
|
|
87
|
+
function say(deps, kind, text) {
|
|
88
|
+
deps.print(formatLine(kind, text, deps.color ?? false));
|
|
89
|
+
}
|
|
78
90
|
/**
|
|
79
91
|
* Says where the worktree is and how to continue there.
|
|
80
92
|
*
|
|
@@ -250,11 +262,11 @@ async function findResume(repo, deps) {
|
|
|
250
262
|
*/
|
|
251
263
|
async function createAndConfigure(repo, branch, worktree, deps, progress) {
|
|
252
264
|
if (existsSync(worktree)) {
|
|
253
|
-
deps
|
|
265
|
+
say(deps, "fail", `${worktree} ya existe; bórralo (git worktree remove ${worktree}) o continúa ahí con ${RESUME_COMMAND}`);
|
|
254
266
|
return false;
|
|
255
267
|
}
|
|
256
268
|
if ((await tryCapture(deps, "git", ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], repo)) !== undefined) {
|
|
257
|
-
deps
|
|
269
|
+
say(deps, "fail", `la rama ${branch} ya existe; bórrala (git branch -D ${branch}) o corre prepare otro día`);
|
|
258
270
|
return false;
|
|
259
271
|
}
|
|
260
272
|
const base = `origin/${defaultBranch(repo)}`;
|
|
@@ -262,24 +274,25 @@ async function createAndConfigure(repo, branch, worktree, deps, progress) {
|
|
|
262
274
|
await deps.run("git", ["fetch", "-q", "origin"], { cwd: repo });
|
|
263
275
|
await deps.run("git", ["worktree", "add", "-b", branch, worktree, base], { cwd: repo });
|
|
264
276
|
progress.worktree = worktree;
|
|
277
|
+
say(deps, "ok", `se creó el worktree ${worktree} en la rama ${branch}`);
|
|
265
278
|
}
|
|
266
279
|
catch (err) {
|
|
267
|
-
deps
|
|
280
|
+
say(deps, "fail", `no se pudo crear el worktree: ${message(err)}`);
|
|
268
281
|
return false;
|
|
269
282
|
}
|
|
270
283
|
const stopped = resumeLine("el agente local se detuvo", worktree);
|
|
271
284
|
try {
|
|
272
285
|
const result = init(worktree, deps.packageVersion, deps.packageRoot);
|
|
273
286
|
for (const line of result.lines)
|
|
274
|
-
deps
|
|
287
|
+
say(deps, initKind(line), unprefixed(line));
|
|
275
288
|
if (!result.ok) {
|
|
276
|
-
deps
|
|
289
|
+
say(deps, "warn", stopped);
|
|
277
290
|
return false;
|
|
278
291
|
}
|
|
279
292
|
}
|
|
280
293
|
catch (err) {
|
|
281
|
-
deps
|
|
282
|
-
deps
|
|
294
|
+
say(deps, "fail", `${message(err)}`);
|
|
295
|
+
say(deps, "warn", stopped);
|
|
283
296
|
return false;
|
|
284
297
|
}
|
|
285
298
|
try {
|
|
@@ -288,13 +301,13 @@ async function createAndConfigure(repo, branch, worktree, deps, progress) {
|
|
|
288
301
|
catch (err) {
|
|
289
302
|
if (err instanceof InterruptedError)
|
|
290
303
|
throw err;
|
|
291
|
-
deps
|
|
304
|
+
say(deps, "warn", stopped);
|
|
292
305
|
return false;
|
|
293
306
|
}
|
|
294
307
|
const problem = configureProblem(worktree);
|
|
295
308
|
if (problem !== undefined) {
|
|
296
|
-
deps
|
|
297
|
-
deps
|
|
309
|
+
say(deps, "fail", `configure no terminó: ${problem}`);
|
|
310
|
+
say(deps, "warn", resumeLine("prepare se detuvo", worktree));
|
|
298
311
|
return false;
|
|
299
312
|
}
|
|
300
313
|
return true;
|
|
@@ -321,44 +334,45 @@ async function commitAndPush(worktree, branch, deps) {
|
|
|
321
334
|
const status = await deps.capture("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], { cwd: worktree });
|
|
322
335
|
const others = statusPaths(status).filter((path) => !covered(path, known));
|
|
323
336
|
if (others.length) {
|
|
324
|
-
deps
|
|
337
|
+
say(deps, "warn", `el agente local también cambió:`);
|
|
325
338
|
for (const path of others)
|
|
326
|
-
deps.print(`
|
|
339
|
+
deps.print(` ${path}`);
|
|
327
340
|
if (YES_ANSWER.test((await deps.ask(INCLUDE_QUESTION, "")).trim())) {
|
|
328
341
|
await deps.run("git", ["add", "--", ...others], { cwd: worktree });
|
|
329
342
|
}
|
|
330
343
|
else {
|
|
331
344
|
await deps.run("git", ["reset", "-q", "--", ...others], { cwd: worktree });
|
|
332
|
-
deps
|
|
345
|
+
say(deps, "warn", `quedó sin commitear en ${worktree}`);
|
|
333
346
|
}
|
|
334
347
|
}
|
|
335
348
|
const staged = await deps.capture("git", ["diff", "--cached", "--name-only"], { cwd: worktree });
|
|
336
349
|
if (staged.trim())
|
|
337
350
|
await deps.run("git", ["commit", "-m", COMMIT_MESSAGE], { cwd: worktree });
|
|
338
351
|
else
|
|
339
|
-
deps
|
|
352
|
+
say(deps, "same", `no hay nada nuevo que commitear`);
|
|
340
353
|
await deps.run("git", ["push", "-u", "origin", branch], { cwd: worktree });
|
|
354
|
+
say(deps, "ok", `se subió la rama ${branch}`);
|
|
341
355
|
}
|
|
342
356
|
catch (err) {
|
|
343
357
|
if (err instanceof InterruptedError)
|
|
344
358
|
throw err;
|
|
345
|
-
deps
|
|
359
|
+
say(deps, "fail", `no se pudo hacer commit y push: ${message(err)}; corrígelo en ${worktree} y corre ${RESUME_COMMAND} ahí`);
|
|
346
360
|
return false;
|
|
347
361
|
}
|
|
348
362
|
const existing = await tryCapture(deps, "gh", ["pr", "view", branch, "--json", "number", "--jq", ".number"], worktree);
|
|
349
363
|
if (existing !== undefined && PR_NUMBER.test(existing)) {
|
|
350
|
-
deps
|
|
364
|
+
say(deps, "ok", `se reutiliza el PR abierto #${existing}`);
|
|
351
365
|
return { pr: existing };
|
|
352
366
|
}
|
|
353
367
|
try {
|
|
354
368
|
const url = await deps.capture("gh", ["pr", "create", "--draft", "--fill"], { cwd: worktree });
|
|
355
|
-
deps
|
|
369
|
+
say(deps, "ok", `PR en borrador: ${url.trim()}`);
|
|
356
370
|
return { pr: PR_URL.exec(url)?.[1] };
|
|
357
371
|
}
|
|
358
372
|
catch {
|
|
359
373
|
const origin = await tryCapture(deps, "git", ["remote", "get-url", "origin"], worktree);
|
|
360
374
|
const link = compareLink(origin, branch);
|
|
361
|
-
deps
|
|
375
|
+
say(deps, "warn", `no se pudo abrir el PR con gh; ábrelo a mano${link ? `: ${link}` : ` desde la rama ${branch}`}`);
|
|
362
376
|
return {};
|
|
363
377
|
}
|
|
364
378
|
}
|
|
@@ -383,14 +397,14 @@ export async function prepare(repo, deps, options = {}) {
|
|
|
383
397
|
const problems = preflight({ isTty: deps.isTty, gitRoot, hasOrigin, claudeHelp });
|
|
384
398
|
if (problems.length || gitRoot === undefined) {
|
|
385
399
|
for (const problem of problems)
|
|
386
|
-
deps
|
|
400
|
+
say(deps, "fail", `${problem}`);
|
|
387
401
|
return EXIT_FAIL;
|
|
388
402
|
}
|
|
389
403
|
const progress = {};
|
|
390
404
|
const stop = deps.onInterrupt?.(() => {
|
|
391
405
|
if (progress.childOwnsTerminal)
|
|
392
406
|
return false;
|
|
393
|
-
deps
|
|
407
|
+
say(deps, "warn", interruptedLine(progress));
|
|
394
408
|
return true;
|
|
395
409
|
});
|
|
396
410
|
try {
|
|
@@ -399,7 +413,7 @@ export async function prepare(repo, deps, options = {}) {
|
|
|
399
413
|
catch (err) {
|
|
400
414
|
if (!(err instanceof InterruptedError))
|
|
401
415
|
throw err;
|
|
402
|
-
deps
|
|
416
|
+
say(deps, "warn", interruptedLine(progress));
|
|
403
417
|
return EXIT_INTERRUPTED;
|
|
404
418
|
}
|
|
405
419
|
finally {
|
|
@@ -454,12 +468,12 @@ async function runConfigure(deps, progress, place, cwd) {
|
|
|
454
468
|
* @param progress - What exists so far.
|
|
455
469
|
* @returns The line.
|
|
456
470
|
* @example
|
|
457
|
-
* interruptedLine({}); // "
|
|
471
|
+
* interruptedLine({}); // "interrumpido; no se creó nada"
|
|
458
472
|
*/
|
|
459
473
|
function interruptedLine(progress) {
|
|
460
474
|
if (progress.here)
|
|
461
|
-
return
|
|
462
|
-
return progress.worktree === undefined ?
|
|
475
|
+
return "interrumpido; lo que alcanzó a cambiar quedó sin commit en tu rama";
|
|
476
|
+
return progress.worktree === undefined ? "interrumpido; no se creó nada" : resumeLine("interrumpido", progress.worktree);
|
|
463
477
|
}
|
|
464
478
|
/**
|
|
465
479
|
* Everything after the preflight: resume or create the worktree, commit, wait, launch.
|
|
@@ -479,21 +493,21 @@ async function prepareSteps(gitRoot, deps, options, progress) {
|
|
|
479
493
|
if (options.resume) {
|
|
480
494
|
const found = await findResume(gitRoot, deps);
|
|
481
495
|
if (!found) {
|
|
482
|
-
deps
|
|
496
|
+
say(deps, "fail", `no hay nada que reanudar: corre prepare --resume en el worktree *${WORKTREE_SUFFIX}, sobre una rama ${BRANCH_PREFIX}*`);
|
|
483
497
|
return EXIT_FAIL;
|
|
484
498
|
}
|
|
485
499
|
({ worktree, branch } = found);
|
|
486
500
|
progress.worktree = worktree;
|
|
487
501
|
const problem = configureProblem(worktree);
|
|
488
502
|
if (problem) {
|
|
489
|
-
deps
|
|
503
|
+
say(deps, "fail", `configure no terminó en ${worktree}: ${problem}; corre claude "/cloud-ready configure" ahí primero y luego ${RESUME_COMMAND}`);
|
|
490
504
|
return EXIT_FAIL;
|
|
491
505
|
}
|
|
492
506
|
}
|
|
493
507
|
else {
|
|
494
508
|
const place = options.place ?? (await askPlace(gitRoot, deps));
|
|
495
509
|
if (place === null) {
|
|
496
|
-
deps
|
|
510
|
+
say(deps, "same", `no se cambió nada`);
|
|
497
511
|
return EXIT_FAIL;
|
|
498
512
|
}
|
|
499
513
|
if (place === "here")
|
|
@@ -508,16 +522,17 @@ async function prepareSteps(gitRoot, deps, options, progress) {
|
|
|
508
522
|
return EXIT_FAIL;
|
|
509
523
|
const config = readConfig(worktree).config;
|
|
510
524
|
if (config)
|
|
511
|
-
|
|
512
|
-
|
|
525
|
+
for (const line of styleMarkdown(renderEnvironmentSection(config, deps.packageVersion), deps.color ?? false))
|
|
526
|
+
deps.print(line);
|
|
527
|
+
await deps.waitEnter(continueLine(deps.color ?? false));
|
|
513
528
|
try {
|
|
514
529
|
await runInteractive(deps, progress, { cmd: "claude", args: ["--cloud", verifyPrompt({ branch, pr: pushed.pr })] }, worktree);
|
|
515
530
|
}
|
|
516
531
|
catch (err) {
|
|
517
|
-
deps
|
|
532
|
+
say(deps, "fail", `falló claude --cloud: ${message(err)}; corre ${RESUME_COMMAND} en ${worktree} para reintentar`);
|
|
518
533
|
return EXIT_FAIL;
|
|
519
534
|
}
|
|
520
|
-
deps
|
|
535
|
+
say(deps, "ok", `la sesión cloud avisará con un push a ${branch} y un comentario en el PR`);
|
|
521
536
|
return EXIT_OK;
|
|
522
537
|
}
|
|
523
538
|
/**
|
|
@@ -548,19 +563,19 @@ async function askPlace(gitRoot, deps) {
|
|
|
548
563
|
*/
|
|
549
564
|
async function prepareHere(gitRoot, deps, progress) {
|
|
550
565
|
progress.here = true;
|
|
551
|
-
const stopped =
|
|
566
|
+
const stopped = `prepare se detuvo; lo que alcanzó a cambiar quedó sin commit en tu rama`;
|
|
552
567
|
try {
|
|
553
568
|
const result = init(gitRoot, deps.packageVersion, deps.packageRoot);
|
|
554
569
|
for (const line of result.lines)
|
|
555
|
-
deps
|
|
570
|
+
say(deps, initKind(line), unprefixed(line));
|
|
556
571
|
if (!result.ok) {
|
|
557
|
-
deps
|
|
572
|
+
say(deps, "warn", stopped);
|
|
558
573
|
return EXIT_FAIL;
|
|
559
574
|
}
|
|
560
575
|
}
|
|
561
576
|
catch (err) {
|
|
562
|
-
deps
|
|
563
|
-
deps
|
|
577
|
+
say(deps, "fail", `${message(err)}`);
|
|
578
|
+
say(deps, "warn", stopped);
|
|
564
579
|
return EXIT_FAIL;
|
|
565
580
|
}
|
|
566
581
|
try {
|
|
@@ -569,25 +584,28 @@ async function prepareHere(gitRoot, deps, progress) {
|
|
|
569
584
|
catch (err) {
|
|
570
585
|
if (err instanceof InterruptedError)
|
|
571
586
|
throw err;
|
|
572
|
-
deps
|
|
587
|
+
say(deps, "warn", stopped);
|
|
573
588
|
return EXIT_FAIL;
|
|
574
589
|
}
|
|
575
590
|
const problem = configureProblem(gitRoot);
|
|
576
591
|
if (problem !== undefined) {
|
|
577
|
-
deps
|
|
592
|
+
say(deps, "fail", `configure no terminó: ${problem}; corre claude "/cloud-ready configure" y luego prepare otra vez`);
|
|
578
593
|
return EXIT_FAIL;
|
|
579
594
|
}
|
|
580
595
|
try {
|
|
581
596
|
writeManual(gitRoot, deps.packageVersion);
|
|
582
597
|
}
|
|
583
598
|
catch (err) {
|
|
584
|
-
deps
|
|
599
|
+
say(deps, "fail", `no se pudo escribir ${MANUAL_FILE}: ${message(err)}`);
|
|
585
600
|
return EXIT_FAIL;
|
|
586
601
|
}
|
|
587
602
|
const config = readConfig(gitRoot).config;
|
|
588
603
|
if (config)
|
|
589
|
-
|
|
590
|
-
|
|
604
|
+
for (const line of styleMarkdown(renderEnvironmentSection(config, deps.packageVersion), deps.color ?? false))
|
|
605
|
+
deps.print(line);
|
|
606
|
+
const steps = ["Revisa los cambios con git diff", "Haz commit y push", `Configura el entorno en claude.ai (${MANUAL_FILE})`, `Corre ${VERIFY_COMMAND}`];
|
|
607
|
+
for (const line of nextSteps("Listo, sin commit", steps, deps.color ?? false))
|
|
608
|
+
deps.print(line);
|
|
591
609
|
return EXIT_OK;
|
|
592
610
|
}
|
|
593
611
|
/**
|
|
@@ -608,30 +626,30 @@ export async function verifyBranch(repo, deps) {
|
|
|
608
626
|
const problems = preflight({ isTty: deps.isTty, gitRoot, hasOrigin, claudeHelp }, "verify");
|
|
609
627
|
if (problems.length || gitRoot === undefined) {
|
|
610
628
|
for (const problem of problems)
|
|
611
|
-
deps
|
|
629
|
+
say(deps, "fail", `${problem}`);
|
|
612
630
|
return EXIT_FAIL;
|
|
613
631
|
}
|
|
614
632
|
const branch = await tryCapture(deps, "git", ["rev-parse", "--abbrev-ref", "HEAD"], gitRoot);
|
|
615
633
|
if (branch === undefined || branch === "HEAD") {
|
|
616
|
-
deps
|
|
634
|
+
say(deps, "fail", `no estás en una rama; cámbiate a la rama que quieres verificar`);
|
|
617
635
|
return EXIT_FAIL;
|
|
618
636
|
}
|
|
619
637
|
const problem = configureProblem(gitRoot);
|
|
620
638
|
if (problem !== undefined) {
|
|
621
|
-
deps
|
|
639
|
+
say(deps, "fail", `el repo no está preparado (${problem}); corre npx ${PACKAGE} prepare primero`);
|
|
622
640
|
return EXIT_FAIL;
|
|
623
641
|
}
|
|
624
642
|
const notes = readConfig(gitRoot).config?.notes ?? DEFAULT_NOTES_FILE;
|
|
625
643
|
const files = [CONFIG_FILE, MANUAL_FILE, notes].filter((path) => entryExists(join(gitRoot, path)));
|
|
626
644
|
const dirty = await tryCapture(deps, "git", ["status", "--porcelain", "--", ...files], gitRoot);
|
|
627
645
|
if (dirty) {
|
|
628
|
-
deps
|
|
646
|
+
say(deps, "fail", `hay cambios sin commit en la preparación; haz commit y push: la nube solo ve lo que está en origin`);
|
|
629
647
|
return EXIT_FAIL;
|
|
630
648
|
}
|
|
631
649
|
const head = await tryCapture(deps, "git", ["rev-parse", "HEAD"], gitRoot);
|
|
632
650
|
const remote = (await tryCapture(deps, "git", ["ls-remote", "origin", `refs/heads/${branch}`], gitRoot))?.split(/\s/)[0];
|
|
633
651
|
if (!head || remote !== head) {
|
|
634
|
-
deps
|
|
652
|
+
say(deps, "fail", `la rama ${branch} no está subida o tiene commits sin subir; haz git push y vuelve a correr verify`);
|
|
635
653
|
return EXIT_FAIL;
|
|
636
654
|
}
|
|
637
655
|
const found = await tryCapture(deps, "gh", ["pr", "view", branch, "--json", "number", "--jq", ".number"], gitRoot);
|
|
@@ -642,12 +660,12 @@ export async function verifyBranch(repo, deps) {
|
|
|
642
660
|
await runInteractive(deps, progress, { cmd: "claude", args: ["--cloud", verifyPrompt({ branch, pr })] }, gitRoot);
|
|
643
661
|
}
|
|
644
662
|
catch (err) {
|
|
645
|
-
deps
|
|
663
|
+
say(deps, "fail", `falló claude --cloud: ${message(err)}`);
|
|
646
664
|
return EXIT_FAIL;
|
|
647
665
|
}
|
|
648
666
|
finally {
|
|
649
667
|
stop?.();
|
|
650
668
|
}
|
|
651
|
-
deps
|
|
669
|
+
say(deps, "ok", `la sesión cloud avisará con un push a ${branch}${pr ? " y un comentario en el PR" : ""}`);
|
|
652
670
|
return EXIT_OK;
|
|
653
671
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file How `prepare` and `verify` show their lines: an icon and a color per kind in a terminal,
|
|
3
|
+
* or the plain `agent-cloud-kit: ` prefix otherwise (logs, pipes, tests).
|
|
4
|
+
*/
|
|
5
|
+
import { OUTPUT_PREFIX } from "../constants.js";
|
|
6
|
+
/** Icon and SGR color code of each kind. */
|
|
7
|
+
const STYLE = {
|
|
8
|
+
ok: { icon: "✔", code: "32" },
|
|
9
|
+
same: { icon: "●", code: "2" },
|
|
10
|
+
info: { icon: "›", code: "36" },
|
|
11
|
+
warn: { icon: "▲", code: "33" },
|
|
12
|
+
fail: { icon: "✖", code: "31" },
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Wraps a text in an SGR code when color is on.
|
|
16
|
+
*
|
|
17
|
+
* @param on - Whether color is on.
|
|
18
|
+
* @param code - SGR code.
|
|
19
|
+
* @param text - Text.
|
|
20
|
+
* @returns The text, colored or not.
|
|
21
|
+
* @example
|
|
22
|
+
* paint(true, "1", "a"); // "\x1b[1ma\x1b[0m"
|
|
23
|
+
*/
|
|
24
|
+
export function paint(on, code, text) {
|
|
25
|
+
return on ? `\x1b[${code}m${text}\x1b[0m` : text;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Formats one line.
|
|
29
|
+
*
|
|
30
|
+
* @param kind - Kind of line.
|
|
31
|
+
* @param text - Text, without prefix.
|
|
32
|
+
* @param color - Whether the output is a color terminal.
|
|
33
|
+
* @returns ` ✔ text` (icon colored; the text dimmed for `same`) or `agent-cloud-kit: text`.
|
|
34
|
+
* @example
|
|
35
|
+
* formatLine("ok", "se creó .claude/cloud.json", false); // "agent-cloud-kit: se creó .claude/cloud.json"
|
|
36
|
+
*/
|
|
37
|
+
export function formatLine(kind, text, color) {
|
|
38
|
+
if (!color)
|
|
39
|
+
return `${OUTPUT_PREFIX}${text}`;
|
|
40
|
+
const { icon, code } = STYLE[kind];
|
|
41
|
+
return ` ${paint(true, code, icon)} ${kind === "same" ? paint(true, "2", text) : text}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Guesses the kind of a line `init` printed, from its wording.
|
|
45
|
+
*
|
|
46
|
+
* @param text - Line, with or without the prefix.
|
|
47
|
+
* @returns `ok` for something done, `same` for something already there, `warn` otherwise.
|
|
48
|
+
* @example
|
|
49
|
+
* initKind("agent-cloud-kit: se creó .claude/cloud.json"); // "ok"
|
|
50
|
+
*/
|
|
51
|
+
export function initKind(text) {
|
|
52
|
+
const plain = text.startsWith(OUTPUT_PREFIX) ? text.slice(OUTPUT_PREFIX.length) : text;
|
|
53
|
+
if (/^se (creó|agregó|instaló|enlazó|actualizó|escribió)(\s|$)/.test(plain))
|
|
54
|
+
return "ok";
|
|
55
|
+
if (/\bya (existe|tiene|está)\b/.test(plain))
|
|
56
|
+
return "same";
|
|
57
|
+
return "warn";
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Removes the `agent-cloud-kit: ` prefix of a line, if it has it.
|
|
61
|
+
*
|
|
62
|
+
* @param text - Line.
|
|
63
|
+
* @returns The line without prefix.
|
|
64
|
+
* @example
|
|
65
|
+
* unprefixed("agent-cloud-kit: hola"); // "hola"
|
|
66
|
+
*/
|
|
67
|
+
export function unprefixed(text) {
|
|
68
|
+
return text.startsWith(OUTPUT_PREFIX) ? text.slice(OUTPUT_PREFIX.length) : text;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Styles a Markdown section for the terminal: headings in bold (and cyan for `##`), code fences
|
|
72
|
+
* dropped and their lines indented, list items as bullets. Without color, the text is unchanged.
|
|
73
|
+
*
|
|
74
|
+
* @param markdown - Section text.
|
|
75
|
+
* @param color - Whether the output is a color terminal.
|
|
76
|
+
* @returns The lines.
|
|
77
|
+
* @example
|
|
78
|
+
* styleMarkdown("## A\n- b", true); // ["", " \x1b[1;36mA\x1b[0m", " • b"]
|
|
79
|
+
*/
|
|
80
|
+
export function styleMarkdown(markdown, color) {
|
|
81
|
+
if (!color)
|
|
82
|
+
return markdown.split("\n");
|
|
83
|
+
const out = [];
|
|
84
|
+
let inCode = false;
|
|
85
|
+
for (const line of markdown.split("\n")) {
|
|
86
|
+
if (line.startsWith("```")) {
|
|
87
|
+
inCode = !inCode;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (inCode)
|
|
91
|
+
out.push(` ${paint(true, "33", line)}`);
|
|
92
|
+
else if (line.startsWith("### "))
|
|
93
|
+
out.push(` ${paint(true, "1", line.slice(4))}`);
|
|
94
|
+
else if (line.startsWith("## "))
|
|
95
|
+
out.push("", ` ${paint(true, "1;36", line.slice(3))}`);
|
|
96
|
+
else if (line.startsWith("- "))
|
|
97
|
+
out.push(` ${paint(true, "36", "•")} ${inline(line.slice(2))}`);
|
|
98
|
+
else
|
|
99
|
+
out.push(line === "" ? "" : ` ${inline(line)}`);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Styles inline Markdown: `**bold**` in bold and `` `code` `` in yellow, without the marks.
|
|
105
|
+
*
|
|
106
|
+
* @param text - One line.
|
|
107
|
+
* @returns The styled line.
|
|
108
|
+
* @example
|
|
109
|
+
* inline("Elige **Custom**"); // "Elige \x1b[1mCustom\x1b[0m"
|
|
110
|
+
*/
|
|
111
|
+
function inline(text) {
|
|
112
|
+
return text.replace(/\*\*([^*]+)\*\*/g, (_m, bold) => paint(true, "1", bold)).replace(/`([^`]+)`/g, (_m, code) => paint(true, "33", code));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Renders a "what now" block: a title and numbered steps.
|
|
116
|
+
*
|
|
117
|
+
* @param title - Title.
|
|
118
|
+
* @param steps - Steps, in order.
|
|
119
|
+
* @param color - Whether the output is a color terminal.
|
|
120
|
+
* @returns The lines (without color: one prefixed line with the steps joined).
|
|
121
|
+
* @example
|
|
122
|
+
* nextSteps("Listo", ["haz commit"], false); // ["agent-cloud-kit: Listo. 1) haz commit"]
|
|
123
|
+
*/
|
|
124
|
+
export function nextSteps(title, steps, color) {
|
|
125
|
+
if (!color)
|
|
126
|
+
return [`${OUTPUT_PREFIX}${title}. ${steps.map((s, i) => `${i + 1}) ${s}`).join("; ")}`];
|
|
127
|
+
return ["", ` ${paint(true, "1;32", "✔")} ${paint(true, "1", title)}`, "", ...steps.map((s, i) => ` ${paint(true, "1;35", String(i + 1))} ${s}`), ""];
|
|
128
|
+
}
|