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.
@@ -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
  /**
@@ -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, OUTPUT_PREFIX, PACKAGE } from "../constants.js";
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.print(`${OUTPUT_PREFIX}${worktree} ya existe; bórralo (git worktree remove ${worktree}) o continúa ahí con ${RESUME_COMMAND}`);
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.print(`${OUTPUT_PREFIX}la rama ${branch} ya existe; bórrala (git branch -D ${branch}) o corre prepare otro día`);
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.print(`${OUTPUT_PREFIX}no se pudo crear el worktree: ${message(err)}`);
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.print(line);
287
+ say(deps, initKind(line), unprefixed(line));
275
288
  if (!result.ok) {
276
- deps.print(stopped);
289
+ say(deps, "warn", stopped);
277
290
  return false;
278
291
  }
279
292
  }
280
293
  catch (err) {
281
- deps.print(`${OUTPUT_PREFIX}${message(err)}`);
282
- deps.print(stopped);
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.print(stopped);
304
+ say(deps, "warn", stopped);
292
305
  return false;
293
306
  }
294
307
  const problem = configureProblem(worktree);
295
308
  if (problem !== undefined) {
296
- deps.print(`${OUTPUT_PREFIX}configure no terminó: ${problem}`);
297
- deps.print(resumeLine("prepare se detuvo", worktree));
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.print(`${OUTPUT_PREFIX}el agente local también cambió:`);
337
+ say(deps, "warn", `el agente local también cambió:`);
325
338
  for (const path of others)
326
- deps.print(` ${path}`);
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.print(`${OUTPUT_PREFIX}quedó sin commitear en ${worktree}`);
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.print(`${OUTPUT_PREFIX}no hay nada nuevo que commitear`);
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.print(`${OUTPUT_PREFIX}no se pudo hacer commit y push: ${message(err)}; corrígelo en ${worktree} y corre ${RESUME_COMMAND} ahí`);
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.print(`${OUTPUT_PREFIX}se reutiliza el PR abierto #${existing}`);
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.print(`${OUTPUT_PREFIX}PR en borrador: ${url.trim()}`);
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.print(`${OUTPUT_PREFIX}no se pudo abrir el PR con gh; ábrelo a mano${link ? `: ${link}` : ` desde la rama ${branch}`}`);
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.print(`${OUTPUT_PREFIX}${problem}`);
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.print(interruptedLine(progress));
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.print(interruptedLine(progress));
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({}); // "agent-cloud-kit: interrumpido; no se creó nada"
471
+ * interruptedLine({}); // "interrumpido; no se creó nada"
458
472
  */
459
473
  function interruptedLine(progress) {
460
474
  if (progress.here)
461
- return `${OUTPUT_PREFIX}interrumpido; lo que alcanzó a cambiar quedó sin commit en tu rama`;
462
- return progress.worktree === undefined ? `${OUTPUT_PREFIX}interrumpido; no se creó nada` : resumeLine("interrumpido", progress.worktree);
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.print(`${OUTPUT_PREFIX}no hay nada que reanudar: corre prepare --resume en el worktree *${WORKTREE_SUFFIX}, sobre una rama ${BRANCH_PREFIX}*`);
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.print(`${OUTPUT_PREFIX}configure no terminó en ${worktree}: ${problem}; corre claude "/cloud-ready configure" ahí primero y luego ${RESUME_COMMAND}`);
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.print(`${OUTPUT_PREFIX}no se cambió nada`);
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
- deps.print(renderEnvironmentSection(config, deps.packageVersion));
512
- await deps.waitEnter(WAIT_TEXT);
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.print(`${OUTPUT_PREFIX}falló claude --cloud: ${message(err)}; corre ${RESUME_COMMAND} en ${worktree} para reintentar`);
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.print(`${OUTPUT_PREFIX}la sesión cloud avisará con un push a ${branch} y un comentario en el PR`);
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 = `${OUTPUT_PREFIX}prepare se detuvo; lo que alcanzó a cambiar quedó sin commit en tu rama`;
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.print(line);
570
+ say(deps, initKind(line), unprefixed(line));
556
571
  if (!result.ok) {
557
- deps.print(stopped);
572
+ say(deps, "warn", stopped);
558
573
  return EXIT_FAIL;
559
574
  }
560
575
  }
561
576
  catch (err) {
562
- deps.print(`${OUTPUT_PREFIX}${message(err)}`);
563
- deps.print(stopped);
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.print(stopped);
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.print(`${OUTPUT_PREFIX}configure no terminó: ${problem}; corre claude "/cloud-ready configure" y luego prepare otra vez`);
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.print(`${OUTPUT_PREFIX}no se pudo escribir ${MANUAL_FILE}: ${message(err)}`);
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
- deps.print(renderEnvironmentSection(config, deps.packageVersion));
590
- deps.print(`${OUTPUT_PREFIX}listo, sin commit. Revisa los cambios con git diff, haz commit y push, configura el entorno en claude.ai (${MANUAL_FILE}) y corre ${VERIFY_COMMAND}`);
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.print(`${OUTPUT_PREFIX}${problem}`);
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.print(`${OUTPUT_PREFIX}no estás en una rama; cámbiate a la rama que quieres verificar`);
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.print(`${OUTPUT_PREFIX}el repo no está preparado (${problem}); corre npx ${PACKAGE} prepare primero`);
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.print(`${OUTPUT_PREFIX}hay cambios sin commit en la preparación; haz commit y push: la nube solo ve lo que está en origin`);
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.print(`${OUTPUT_PREFIX}la rama ${branch} no está subida o tiene commits sin subir; haz git push y vuelve a correr verify`);
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.print(`${OUTPUT_PREFIX}falló claude --cloud: ${message(err)}`);
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.print(`${OUTPUT_PREFIX}la sesión cloud avisará con un push a ${branch}${pr ? " y un comentario en el PR" : ""}`);
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-cloud-kit",
3
- "version": "1.0.0-rc.3",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "Zero-dependency CLI that prepares Claude Code cloud sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",