soja-cli 1.4.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.
Files changed (189) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +117 -0
  3. package/dist/application/chat.js +1 -0
  4. package/dist/application/filters.js +27 -0
  5. package/dist/application/import.js +1 -0
  6. package/dist/application/ports.js +1 -0
  7. package/dist/application/repository-root.js +18 -0
  8. package/dist/application/services/backup-service.js +93 -0
  9. package/dist/application/services/folder-service.js +116 -0
  10. package/dist/application/services/git-workflow-service.js +593 -0
  11. package/dist/application/services/index.js +24 -0
  12. package/dist/application/services/project-service.js +129 -0
  13. package/dist/application/services/session-service.js +52 -0
  14. package/dist/application/services/task-service.js +350 -0
  15. package/dist/application/services/update-service.js +60 -0
  16. package/dist/application/services/workspace-service.js +81 -0
  17. package/dist/application/timeline.js +83 -0
  18. package/dist/application/types.js +1 -0
  19. package/dist/application/validation.js +30 -0
  20. package/dist/bootstrap.js +91 -0
  21. package/dist/cli/commands/access.js +60 -0
  22. package/dist/cli/commands/account.js +203 -0
  23. package/dist/cli/commands/args.js +27 -0
  24. package/dist/cli/commands/backup.js +70 -0
  25. package/dist/cli/commands/chat.js +133 -0
  26. package/dist/cli/commands/dev.js +59 -0
  27. package/dist/cli/commands/folders.js +59 -0
  28. package/dist/cli/commands/git-ops.js +200 -0
  29. package/dist/cli/commands/import.js +48 -0
  30. package/dist/cli/commands/info.js +82 -0
  31. package/dist/cli/commands/project.js +108 -0
  32. package/dist/cli/commands/start.js +34 -0
  33. package/dist/cli/commands/sync.js +43 -0
  34. package/dist/cli/commands/task.js +309 -0
  35. package/dist/cli/commands/tui.js +43 -0
  36. package/dist/cli/commands/update.js +20 -0
  37. package/dist/cli/commands/workspace.js +55 -0
  38. package/dist/cli/index.js +103 -0
  39. package/dist/cli/output.js +56 -0
  40. package/dist/cli/prompt.js +28 -0
  41. package/dist/cli/runtime.js +43 -0
  42. package/dist/config/config.js +103 -0
  43. package/dist/config/credentials.js +68 -0
  44. package/dist/config/paths.js +18 -0
  45. package/dist/config/server.js +21 -0
  46. package/dist/config/version.js +7 -0
  47. package/dist/data/local/activity.js +33 -0
  48. package/dist/data/local/clock.js +1 -0
  49. package/dist/data/local/comments.js +27 -0
  50. package/dist/data/local/index.js +48 -0
  51. package/dist/data/local/projects.js +53 -0
  52. package/dist/data/local/tasks.js +104 -0
  53. package/dist/data/local/users.js +38 -0
  54. package/dist/data/local/workspaces.js +55 -0
  55. package/dist/data/remote/api-client.js +103 -0
  56. package/dist/data/remote/live.js +93 -0
  57. package/dist/data/repositories.js +1 -0
  58. package/dist/data/sync/chat.js +242 -0
  59. package/dist/data/sync/engine.js +459 -0
  60. package/dist/data/sync/importer.js +156 -0
  61. package/dist/data/sync/index.js +109 -0
  62. package/dist/data/sync/services.js +263 -0
  63. package/dist/data/sync/store.js +200 -0
  64. package/dist/database/client.js +76 -0
  65. package/dist/database/migrate.js +27 -0
  66. package/dist/database/migrations/0000_init.sql +100 -0
  67. package/dist/database/migrations/0001_git-operations.sql +19 -0
  68. package/dist/database/migrations/0002_merge-detection.sql +19 -0
  69. package/dist/database/migrations/0003_sync-replica.sql +40 -0
  70. package/dist/database/migrations/0004_chat-replica.sql +52 -0
  71. package/dist/database/migrations/0005_pr-events.sql +18 -0
  72. package/dist/database/migrations/0006_archive.sql +20 -0
  73. package/dist/database/migrations/meta/0000_snapshot.json +710 -0
  74. package/dist/database/migrations/meta/0001_snapshot.json +717 -0
  75. package/dist/database/migrations/meta/0002_snapshot.json +724 -0
  76. package/dist/database/migrations/meta/0003_snapshot.json +972 -0
  77. package/dist/database/migrations/meta/0004_snapshot.json +1168 -0
  78. package/dist/database/migrations/meta/0005_snapshot.json +1168 -0
  79. package/dist/database/migrations/meta/0006_snapshot.json +1183 -0
  80. package/dist/database/migrations/meta/_journal.json +55 -0
  81. package/dist/database/schema.js +170 -0
  82. package/dist/dev/seed.js +84 -0
  83. package/dist/domain/activity.js +22 -0
  84. package/dist/domain/chat.js +21 -0
  85. package/dist/domain/entities.js +1 -0
  86. package/dist/domain/errors.js +23 -0
  87. package/dist/domain/naming.js +33 -0
  88. package/dist/domain/task.js +140 -0
  89. package/dist/domain/workflow.js +23 -0
  90. package/dist/git/cli-git.js +363 -0
  91. package/dist/git/console.js +37 -0
  92. package/dist/git/diagnose.js +145 -0
  93. package/dist/git/merge-evidence.js +23 -0
  94. package/dist/git/releases.js +95 -0
  95. package/dist/git/types.js +16 -0
  96. package/dist/ui/App.js +71 -0
  97. package/dist/ui/Shell.js +211 -0
  98. package/dist/ui/app-state.js +213 -0
  99. package/dist/ui/branding/CompactLogo.js +8 -0
  100. package/dist/ui/branding/Logo.js +22 -0
  101. package/dist/ui/branding/Splash.js +21 -0
  102. package/dist/ui/branding/brand.js +23 -0
  103. package/dist/ui/chrome/Sidebar.js +50 -0
  104. package/dist/ui/chrome/StatusBar.js +26 -0
  105. package/dist/ui/chrome/Toast.js +11 -0
  106. package/dist/ui/chrome/TopBar.js +10 -0
  107. package/dist/ui/chrome/context.js +33 -0
  108. package/dist/ui/components/ConsoleLines.js +16 -0
  109. package/dist/ui/components/EmptyState.js +8 -0
  110. package/dist/ui/components/Labels.js +25 -0
  111. package/dist/ui/components/PullRequestLabel.js +27 -0
  112. package/dist/ui/components/TaskTable.js +41 -0
  113. package/dist/ui/components/TextField.js +11 -0
  114. package/dist/ui/components/TextInput.js +29 -0
  115. package/dist/ui/components/task-columns.js +27 -0
  116. package/dist/ui/copy.js +23 -0
  117. package/dist/ui/hooks/use-commands.js +58 -0
  118. package/dist/ui/hooks/use-flows.js +254 -0
  119. package/dist/ui/hooks/use-layout.js +36 -0
  120. package/dist/ui/hooks/use-list.js +28 -0
  121. package/dist/ui/hooks/use-measure.js +22 -0
  122. package/dist/ui/hooks/use-query.js +29 -0
  123. package/dist/ui/hooks/use-task-actions.js +450 -0
  124. package/dist/ui/hooks/use-terminal-size.js +19 -0
  125. package/dist/ui/input/KeyProvider.js +101 -0
  126. package/dist/ui/input/dispatcher.js +26 -0
  127. package/dist/ui/input/list-navigation.js +38 -0
  128. package/dist/ui/input/mouse.js +78 -0
  129. package/dist/ui/input/text-editing.js +62 -0
  130. package/dist/ui/kit/Badge.js +8 -0
  131. package/dist/ui/kit/Button.js +13 -0
  132. package/dist/ui/kit/Clickable.js +14 -0
  133. package/dist/ui/kit/Gauge.js +9 -0
  134. package/dist/ui/kit/Keycap.js +7 -0
  135. package/dist/ui/kit/Panel.js +30 -0
  136. package/dist/ui/kit/ScrollBar.js +14 -0
  137. package/dist/ui/kit/Spinner.js +9 -0
  138. package/dist/ui/kit/Tabs.js +12 -0
  139. package/dist/ui/mascot/AgentScene.js +30 -0
  140. package/dist/ui/mascot/Mascot.js +26 -0
  141. package/dist/ui/mascot/ansi.js +25 -0
  142. package/dist/ui/mascot/engine.js +146 -0
  143. package/dist/ui/mascot/index.js +6 -0
  144. package/dist/ui/mascot/palette.js +18 -0
  145. package/dist/ui/mascot/scenes/bugHunt.js +115 -0
  146. package/dist/ui/mascot/scenes/celebrate.js +76 -0
  147. package/dist/ui/mascot/scenes/cleaning.js +156 -0
  148. package/dist/ui/mascot/scenes/clockIn.js +105 -0
  149. package/dist/ui/mascot/scenes/deadline.js +125 -0
  150. package/dist/ui/mascot/scenes/empty.js +149 -0
  151. package/dist/ui/mascot/scenes/fire.js +138 -0
  152. package/dist/ui/mascot/scenes/index.js +25 -0
  153. package/dist/ui/mascot/scenes/planning.js +73 -0
  154. package/dist/ui/mascot/scenes/resting.js +90 -0
  155. package/dist/ui/mascot/scenes/syncing.js +116 -0
  156. package/dist/ui/mascot/scenes/working.js +193 -0
  157. package/dist/ui/mascot/selection.js +32 -0
  158. package/dist/ui/mascot/states.js +25 -0
  159. package/dist/ui/navigation/routes.js +17 -0
  160. package/dist/ui/overlays/CommitOverlay.js +93 -0
  161. package/dist/ui/overlays/ConfirmOverlay.js +42 -0
  162. package/dist/ui/overlays/GitLogOverlay.js +22 -0
  163. package/dist/ui/overlays/GitRunOverlay.js +94 -0
  164. package/dist/ui/overlays/NewTaskOverlay.js +117 -0
  165. package/dist/ui/overlays/OverlayFrame.js +35 -0
  166. package/dist/ui/overlays/PickerOverlay.js +72 -0
  167. package/dist/ui/overlays/PromptOverlay.js +51 -0
  168. package/dist/ui/overlays/SearchOverlay.js +49 -0
  169. package/dist/ui/overlays/options.js +29 -0
  170. package/dist/ui/overlays/types.js +1 -0
  171. package/dist/ui/screens/AccessScreen.js +91 -0
  172. package/dist/ui/screens/ChatScreen.js +450 -0
  173. package/dist/ui/screens/HelpScreen.js +99 -0
  174. package/dist/ui/screens/ProjectsScreen.js +66 -0
  175. package/dist/ui/screens/ScreenFrame.js +14 -0
  176. package/dist/ui/screens/SetupScreen.js +79 -0
  177. package/dist/ui/screens/TaskListScreen.js +152 -0
  178. package/dist/ui/screens/TaskScreen.js +193 -0
  179. package/dist/ui/screens/WelcomeScreen.js +254 -0
  180. package/dist/ui/screens/WorkspacesScreen.js +64 -0
  181. package/dist/ui/theme/detect.js +59 -0
  182. package/dist/ui/theme/theme.js +135 -0
  183. package/dist/utils/editor.js +37 -0
  184. package/dist/utils/errors.js +23 -0
  185. package/dist/utils/text.js +60 -0
  186. package/dist/utils/time.js +25 -0
  187. package/docs/NPM_PUBLISH.md +35 -0
  188. package/docs/PRIVACY.md +38 -0
  189. package/package.json +74 -0
@@ -0,0 +1,116 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion -- scene arrays are indexed within their declared bounds. */
2
+ import { blink, drawCloud, drawGround, mirror, rng, soilLine, withFace } from '../engine.js';
3
+ const W = 78;
4
+ const H = 12;
5
+ const G = 10;
6
+ const LAPTOP = [' .-------. ', ' | | ', ' |_______| ', '/_________\\'];
7
+ const SERVER = [' .------. ', ' |[ ]| ', ' |[ ]| ', ' |[ ]| ', " '------' "];
8
+ const LOAD_REACH = mirror([' .-. ', '(o.o) ', '( )_', " ' ' "]);
9
+ const LOAD_PUT = [' .-. ', '(o.o) ', '( )_', " ' ' "];
10
+ const BELT_L = 18;
11
+ const BELT_R = 60;
12
+ const WIRE_Y = 2;
13
+ export const syncing = {
14
+ id: 'syncing',
15
+ title: 'Sincronizando',
16
+ useFor: 'Cargando / sincronizando datos',
17
+ width: W,
18
+ height: H,
19
+ create(seed = 7) {
20
+ const r = rng(seed);
21
+ const soil = [soilLine(W, r)];
22
+ let t = 0;
23
+ let progress = 0;
24
+ let doneT = 0;
25
+ let up = [];
26
+ let down = [];
27
+ let loadT = 0;
28
+ let unloadT = 0;
29
+ return {
30
+ step() {
31
+ t++;
32
+ if (doneT) {
33
+ doneT--;
34
+ if (!doneT)
35
+ progress = 0;
36
+ return;
37
+ }
38
+ if (t % 14 === 0) {
39
+ up.push({ x: BELT_L });
40
+ loadT = 6;
41
+ }
42
+ if (t % 22 === 11)
43
+ down.push({ x: 66 });
44
+ for (const p of up)
45
+ p.x += 0.5;
46
+ for (const p of down)
47
+ p.x -= 0.6;
48
+ if (up.length && up[0].x >= BELT_R - 1) {
49
+ up.shift();
50
+ unloadT = 6;
51
+ progress = Math.min(100, progress + 7);
52
+ }
53
+ if (down.length && down[0].x <= 10) {
54
+ down.shift();
55
+ progress = Math.min(100, progress + 3);
56
+ }
57
+ if (loadT)
58
+ loadT--;
59
+ if (unloadT)
60
+ unloadT--;
61
+ if (progress >= 100) {
62
+ doneT = 30;
63
+ up = [];
64
+ down = [];
65
+ }
66
+ },
67
+ draw(c) {
68
+ drawCloud(c, t, 0.05, 20, 0, false);
69
+ drawGround(c, G, soil);
70
+ c.sprite(0, G - 4, LAPTOP, 'rock');
71
+ const bars = Math.floor(t / 2) % 5;
72
+ c.put(3, G - 3, '>'.padEnd(bars + 1, '_'), 'accent');
73
+ c.sprite(68, G - 5, SERVER, 'rock');
74
+ for (let i = 0; i < 3; i++) {
75
+ const on = (Math.floor(t / 3) + i) % 3 === 0 || doneT > 0;
76
+ c.put(71, G - 4 + i, on ? '■■■■' : '····', on ? 'good' : 'cloud');
77
+ }
78
+ for (let x = 6; x < 73; x++)
79
+ c.put(x, WIRE_Y, '-', 'cloud');
80
+ c.put(5, WIRE_Y, '+', 'rock');
81
+ c.put(73, WIRE_Y, '+', 'rock');
82
+ for (let y = WIRE_Y + 1; y < G - 4; y++) {
83
+ c.put(5, y, '|', 'rock');
84
+ c.put(73, y, '|', 'rock');
85
+ }
86
+ for (const p of down) {
87
+ c.put(p.x + 1, WIRE_Y, 'T', 'rock');
88
+ c.put(p.x, WIRE_Y + 1, '[=]', 'accent');
89
+ }
90
+ const phase = Math.floor(t / 2) % 3;
91
+ for (let x = BELT_L; x <= BELT_R + 2; x++)
92
+ c.put(x, G - 1, (x + phase) % 3 === 0 ? 'o' : '=', 'rock');
93
+ for (const p of up)
94
+ c.put(p.x, G - 2, '[#]', 'dirt');
95
+ c.sprite(11, G - 4, withFace(loadT > 3 ? LOAD_PUT : LOAD_REACH, doneT ? '^.^' : blink(t, 1)), 'agent');
96
+ c.sprite(62, G - 4, withFace(unloadT > 3 ? mirror(LOAD_PUT) : LOAD_PUT, doneT ? '^.^' : blink(t, 2)), 'agent');
97
+ const cx = 36 + Math.round(Math.sin(t * 0.05) * 6);
98
+ c.sprite(cx, G - 7, withFace([' .-. ', '(o.o)', '( )[='], doneT ? '^.^' : blink(t, 3, 'o.O')), 'agent');
99
+ c.put(cx + 1, G - 4, "' '", 'agent');
100
+ for (let y = WIRE_Y + 1; y < G - 3; y++) {
101
+ c.put(cx - 1, y, '|', 'rock');
102
+ c.put(cx + 7, y, '|', 'rock');
103
+ }
104
+ c.put(cx - 1, WIRE_Y, 'Y', 'rock');
105
+ c.put(cx + 7, WIRE_Y, 'Y', 'rock');
106
+ c.put(cx - 1, G - 3, '\\_______/', 'wood');
107
+ if (doneT && t % 10 < 6)
108
+ c.put(cx + 9, G - 7, '(listo!)', 'good');
109
+ },
110
+ status() {
111
+ const lv = Math.round(progress / 20);
112
+ return doneT ? 'sincronizado ✓' : `sincronizando ${'▮'.repeat(lv)}${'▯'.repeat(5 - lv)} ${progress}%`;
113
+ },
114
+ };
115
+ },
116
+ };
@@ -0,0 +1,193 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion -- scene arrays are indexed within their declared bounds. */
2
+ import { blink, drawCloud, drawGround, mirror, rng, soilLine, withFace, } from '../engine.js';
3
+ const W = 78;
4
+ const H = 15;
5
+ const G = 10;
6
+ const PICK_UP = [' .-. _.', '(o.o)/ ', '( ) ', " ' ' "];
7
+ const PICK_DOWN = [' .-. ', '(o.o) ', '( )\\_ ', " ' ' ' "];
8
+ const SHOVEL_DIG = [' .-. ', '(o.o) ', '( )\\ ', " ' ' \\_ "];
9
+ const SHOVEL_LIFT = [' .-. _.:_', '(o.o)/ ', '( ) ', " ' ' "];
10
+ const CARRY_A = [' .-. ', '(o.o) ', '( )=', " ' ' "];
11
+ const CARRY_B = [' .-. ', '(o.o) ', '( )=', " ' ' "];
12
+ const PLACE = [' .-. ', '(o.o)__ ', '( ) ', " ' ' "];
13
+ const BACK_A = mirror([' .-. ', '(o.o)', '( )', " ' ' "]);
14
+ const BACK_B = mirror([' .-. ', '(o.o)', '( )', " ' '"]);
15
+ const CROUCH = mirror([' ', ' .-. ', '(o.o)\\', " ' ' "]);
16
+ const ROCK = [' ▄▓▄ ', ' ▓▓▒▓▓ ', '▓▒▓▓▓▒▓', '▓▓▓▒▓▓▓'];
17
+ const BLOCKS = ['▓▒▓', '▓▓▒', '▒▓▓'];
18
+ const PILE = [
19
+ [],
20
+ [' . '],
21
+ [' .:. '],
22
+ ['.:::.', ' . '],
23
+ [':::::', '.:::.', ' .:. '],
24
+ [':::::::', ':::::', '.:::.', ' .:. '],
25
+ ];
26
+ const WALL_X = 68;
27
+ const WALL_COLS = 3;
28
+ const WALL_MAX = 15;
29
+ export const working = {
30
+ id: 'working',
31
+ title: 'Trabajando',
32
+ useFor: 'Tareas en progreso',
33
+ width: W,
34
+ height: H,
35
+ create(seed = 1) {
36
+ const r = rng(seed);
37
+ const soil = Array.from({ length: H - G - 1 }, () => soilLine(W, r));
38
+ const miners = [
39
+ { x: 1, off: 0, m: false, sx: 8, n: 1 },
40
+ { x: 14, off: 5, m: true, sx: 14, n: 2 },
41
+ ];
42
+ const shovelers = [
43
+ { x: 24, off: 0, m: false, hole: 29, px: 31, vx: 0.55, n: 3 },
44
+ { x: 37, off: 6, m: true, hole: 38, px: 39, vx: -0.55, n: 4 },
45
+ ];
46
+ const carriers = [
47
+ { x: 52, st: 'pick', k: 0, n: 5, happy: -99 },
48
+ { x: 59, st: 'back', k: 0, n: 6, happy: -99 },
49
+ ];
50
+ let t = 0;
51
+ let placed = 0;
52
+ let dirt = 0;
53
+ let hits = 0;
54
+ let hold = 0;
55
+ let msg = '';
56
+ let parts = [];
57
+ let sparks = [];
58
+ return {
59
+ step() {
60
+ t++;
61
+ for (const a of miners) {
62
+ if ((t + a.off) % 10 === 6) {
63
+ hits++;
64
+ sparks.push({ x: a.sx, y: G - 2, l: 2 });
65
+ for (let i = 0; i < 2; i++) {
66
+ parts.push({
67
+ x: a.sx,
68
+ y: G - 3,
69
+ vx: (a.m ? 1 : -1) * (0.3 + r() * 0.5),
70
+ vy: -0.5 - r() * 0.3,
71
+ ch: ".'`"[i % 3],
72
+ tone: 'rock',
73
+ });
74
+ }
75
+ msg = `agente-0${a.n}: picando roca`;
76
+ }
77
+ }
78
+ for (const a of shovelers) {
79
+ const p = (t + a.off) % 12;
80
+ if (p === 6 || p === 7) {
81
+ parts.push({ x: a.px, y: G - 4, vx: a.vx + (r() - 0.5) * 0.1, vy: -0.5, ch: p === 6 ? ':' : '.', tone: 'dirt' });
82
+ if (p === 6) {
83
+ dirt++;
84
+ msg = `agente-0${a.n}: sacando tierra`;
85
+ }
86
+ }
87
+ }
88
+ for (const a of carriers) {
89
+ a.k++;
90
+ if (a.st === 'pick' && a.k > 4) {
91
+ a.st = 'go';
92
+ a.k = 0;
93
+ }
94
+ else if (a.st === 'go') {
95
+ a.x += 0.5;
96
+ if (a.x >= 59) {
97
+ a.st = 'place';
98
+ a.k = 0;
99
+ }
100
+ }
101
+ else if (a.st === 'place' && a.k === 2) {
102
+ if (placed < WALL_MAX && !hold) {
103
+ placed++;
104
+ a.happy = t;
105
+ msg = `agente-0${a.n}: bloque ${placed} colocado`;
106
+ }
107
+ }
108
+ else if (a.st === 'place' && a.k > 4) {
109
+ a.st = 'back';
110
+ a.k = 0;
111
+ }
112
+ else if (a.st === 'back') {
113
+ a.x -= 0.5;
114
+ if (a.x <= 52) {
115
+ a.st = 'pick';
116
+ a.k = 0;
117
+ }
118
+ }
119
+ }
120
+ if (placed >= WALL_MAX && !hold) {
121
+ hold = 30;
122
+ msg = 'muro completo';
123
+ }
124
+ if (hold) {
125
+ hold--;
126
+ if (!hold) {
127
+ placed = 0;
128
+ dirt = 0;
129
+ }
130
+ }
131
+ for (const p of parts) {
132
+ p.x += p.vx;
133
+ p.y += p.vy;
134
+ p.vy += 0.12;
135
+ }
136
+ parts = parts.filter((p) => p.y < G - 0.5 && p.x >= 0 && p.x < W);
137
+ for (const s of sparks)
138
+ s.l--;
139
+ sparks = sparks.filter((s) => s.l > 0);
140
+ },
141
+ draw(c) {
142
+ drawCloud(c, t, 0.06, 0, 1);
143
+ drawCloud(c, t, 0.04, 40, 3, false);
144
+ drawGround(c, G, soil);
145
+ for (const a of shovelers) {
146
+ for (let i = a.hole; i < a.hole + 3; i++) {
147
+ c.set(i, G, ' ');
148
+ c.set(i, G + 1, '▀', 'soil');
149
+ }
150
+ }
151
+ c.sprite(8, G - 4, ROCK, 'rock');
152
+ const pile = PILE[Math.min(5, Math.floor(dirt / 4))];
153
+ pile.forEach((l, i) => c.put(34 - Math.floor(l.length / 2), G - 1 - i, l, 'dirt'));
154
+ c.put(46, G - 1, '▓▒▓▓▓▒', 'rock');
155
+ c.put(47, G - 2, '▓▓▒', 'rock');
156
+ for (let i = 0; i < placed; i++) {
157
+ c.put(WALL_X + 3 * (i % WALL_COLS), G - 1 - Math.floor(i / WALL_COLS), BLOCKS[i % 3], 'rock');
158
+ }
159
+ if (hold) {
160
+ c.put(WALL_X + 4, G - 7, '|', 'good');
161
+ c.put(WALL_X + 4, G - 8, '|>', 'good');
162
+ }
163
+ miners.forEach((a, i) => {
164
+ let sp = (t + a.off) % 10 < 6 ? PICK_UP : PICK_DOWN;
165
+ if (a.m)
166
+ sp = mirror(sp);
167
+ c.sprite(a.x, G - 4, withFace(sp, blink(t, i)), 'agent');
168
+ });
169
+ shovelers.forEach((a, i) => {
170
+ let sp = (t + a.off) % 12 < 6 ? SHOVEL_DIG : SHOVEL_LIFT;
171
+ if (a.m)
172
+ sp = mirror(sp);
173
+ c.sprite(a.x, G - 4, withFace(sp, blink(t, i + 2)), 'agent');
174
+ });
175
+ carriers.forEach((a, i) => {
176
+ const face = t - a.happy < 8 ? '^.^' : blink(t, i + 4);
177
+ const w = Math.floor(t / 2) % 2;
178
+ const sp = a.st === 'pick' ? CROUCH : a.st === 'go' ? (w ? CARRY_A : CARRY_B) : a.st === 'place' ? PLACE : w ? BACK_A : BACK_B;
179
+ c.sprite(a.x, G - 4, withFace(sp, face), 'agent');
180
+ if (a.st === 'go')
181
+ c.put(a.x + 6, G - 2, '▓▒▓', 'rock');
182
+ });
183
+ for (const p of parts)
184
+ c.put(p.x, p.y, p.ch, p.tone);
185
+ for (const s of sparks)
186
+ c.put(s.x, s.y, '*', 'spark');
187
+ },
188
+ status() {
189
+ return `bloques ${placed}/${WALL_MAX} · golpes ${hits} · paladas ${dirt}${msg ? ' · ' + msg : ''}`;
190
+ },
191
+ };
192
+ },
193
+ };
@@ -0,0 +1,32 @@
1
+ export function sceneForTask(task) {
2
+ if (task.status === 'done')
3
+ return 'celebrate';
4
+ if (task.status === 'blocked')
5
+ return 'bugHunt';
6
+ if (task.priority === 'urgent')
7
+ return 'fire';
8
+ switch (task.status) {
9
+ case 'in_progress': return 'working';
10
+ case 'review': return 'planning';
11
+ case 'cancelled': return 'cleaning';
12
+ case 'backlog':
13
+ case 'todo': return 'resting';
14
+ }
15
+ }
16
+ export function sceneForTaskList(tasks, filter, syncing) {
17
+ if (syncing)
18
+ return 'syncing';
19
+ if (tasks.length === 0)
20
+ return 'empty';
21
+ if (tasks.some((task) => task.priority === 'urgent'))
22
+ return 'fire';
23
+ if (tasks.some((task) => task.status === 'blocked'))
24
+ return 'bugHunt';
25
+ if (filter === 'archived' || filter === 'done')
26
+ return 'cleaning';
27
+ if (tasks.some((task) => task.status === 'in_progress'))
28
+ return 'working';
29
+ if (tasks.some((task) => task.status === 'review'))
30
+ return 'planning';
31
+ return 'resting';
32
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Sugerencia de qué escena mostrar para cada estado de un task manager.
3
+ * Ajusta las claves a los estados reales de tu app.
4
+ */
5
+ export const TASK_STATE_SCENES = {
6
+ inProgress: 'working',
7
+ idle: 'resting',
8
+ paused: 'resting',
9
+ booting: 'clockIn',
10
+ planning: 'planning',
11
+ urgent: 'fire',
12
+ incident: 'fire',
13
+ completed: 'celebrate',
14
+ syncing: 'syncing',
15
+ loading: 'syncing',
16
+ error: 'bugHunt',
17
+ blocked: 'bugHunt',
18
+ empty: 'empty',
19
+ overdue: 'deadline',
20
+ dueSoon: 'deadline',
21
+ archiving: 'cleaning',
22
+ };
23
+ export function sceneForState(state) {
24
+ return TASK_STATE_SCENES[state];
25
+ }
@@ -0,0 +1,17 @@
1
+ const HOME = { name: 'home' };
2
+ function sameRoute(a, b) {
3
+ return JSON.stringify(a) === JSON.stringify(b);
4
+ }
5
+ /** A stack of screens. Home is always at the bottom; you cannot pop past it. */
6
+ export function navigate(stack, action) {
7
+ switch (action.type) {
8
+ case 'push':
9
+ return sameRoute(stack.at(-1), action.route) ? [...stack] : [...stack, action.route];
10
+ case 'pop':
11
+ return stack.length > 1 ? stack.slice(0, -1) : [...stack];
12
+ case 'reset':
13
+ if (!action.route)
14
+ return [HOME];
15
+ return action.route.name === 'home' ? [action.route] : [HOME, action.route];
16
+ }
17
+ }
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useState } from 'react';
4
+ import { useAppState } from '../app-state.js';
5
+ import { useList } from '../hooks/use-list.js';
6
+ import { useQuery } from '../hooks/use-query.js';
7
+ import { Layer } from '../input/dispatcher.js';
8
+ import { useKeys } from '../input/KeyProvider.js';
9
+ import { Button } from '../kit/Button.js';
10
+ import { Clickable } from '../kit/Clickable.js';
11
+ import { palette, symbols, toneColors } from '../theme/theme.js';
12
+ import { OverlayFrame, useOverlayHeight } from './OverlayFrame.js';
13
+ const KIND = {
14
+ modified: { letter: 'M', tone: 'warning' },
15
+ added: { letter: 'A', tone: 'success' },
16
+ untracked: { letter: '?', tone: 'success' },
17
+ deleted: { letter: 'D', tone: 'danger' },
18
+ renamed: { letter: 'R', tone: 'info' },
19
+ copied: { letter: 'C', tone: 'info' },
20
+ typechange: { letter: 'T', tone: 'warning' },
21
+ conflicted: { letter: 'U', tone: 'danger' },
22
+ };
23
+ /** Step 1 of a commit: choose files (all selected by default). Step 2 asks for the message. */
24
+ export function CommitOverlay({ spec }) {
25
+ const { services, session, cwd, closeOverlay, openOverlay, notify } = useAppState();
26
+ const task = spec.task;
27
+ const state = useQuery(() => services.git.workingState(session, task, cwd), `commit:${task.id}`);
28
+ const files = state.data?.files ?? [];
29
+ const [excluded, setExcluded] = useState(new Set());
30
+ const height = useOverlayHeight() - 3;
31
+ const list = useList(files.length, height);
32
+ const selected = files.filter((file) => !excluded.has(file.path));
33
+ const toggle = (path) => setExcluded((previous) => {
34
+ const next = new Set(previous);
35
+ if (next.has(path))
36
+ next.delete(path);
37
+ else
38
+ next.add(path);
39
+ return next;
40
+ });
41
+ const proceed = () => {
42
+ if (selected.length === 0) {
43
+ notify('Select at least one file.', 'error');
44
+ return;
45
+ }
46
+ const paths = selected.map((file) => file.path);
47
+ openOverlay({
48
+ kind: 'prompt',
49
+ title: 'Commit message',
50
+ context: `${task.ref} · ${selected.length} file${selected.length === 1 ? '' : 's'} · “(${task.ref})” is added`,
51
+ placeholder: 'Deduplicate Stripe webhook events',
52
+ onSubmit: (message) => openOverlay({
53
+ kind: 'git-run',
54
+ title: 'Commit',
55
+ context: task.ref,
56
+ run: async () => {
57
+ const result = await services.git.commit(session, task, { cwd, message, paths });
58
+ return `Committed ${result.hash.slice(0, 7)} “${result.subject}” (${result.files} file${result.files === 1 ? '' : 's'})`;
59
+ },
60
+ }),
61
+ });
62
+ };
63
+ useKeys(Layer.overlay, (input, key) => {
64
+ if (key.escape)
65
+ closeOverlay(spec);
66
+ else if (list.handleKey(input, key))
67
+ return true;
68
+ else if (input === ' ') {
69
+ const file = files[list.index];
70
+ if (file)
71
+ toggle(file.path);
72
+ }
73
+ else if (input === 'a')
74
+ setExcluded(selected.length === files.length ? new Set(files.map((file) => file.path)) : new Set());
75
+ else if (key.return)
76
+ proceed();
77
+ return true;
78
+ });
79
+ const context = state.data;
80
+ const onTaskBranch = context ? context.currentBranch === task.branch && task.branch !== null : true;
81
+ return (_jsxs(OverlayFrame, { title: "Commit", context: `${task.ref} · ${selected.length}/${files.length} selected`, width: 88, hints: [
82
+ ['j/k', 'move'],
83
+ ['space', 'toggle'],
84
+ ['a', 'all/none'],
85
+ ['enter', 'message'],
86
+ ['esc', 'cancel'],
87
+ ], children: [state.error ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: palette.danger, children: `${symbols.cross} ${state.error.message}` }), state.error.hint ? _jsx(Text, { dimColor: true, children: ` ${state.error.hint}` }) : null] })) : null, context && !onTaskBranch ? (_jsx(Text, { color: palette.warning, children: `${symbols.cross} You are on ${context.currentBranch ?? 'a detached HEAD'}, not on ${task.branch ?? 'the task branch'}. Press b in the task first.` })) : null, context?.merging ? _jsx(Text, { color: palette.warning, children: "A merge is in progress in this repository." }) : null, context && files.length === 0 ? _jsx(Text, { dimColor: true, children: "Nothing to commit. The working tree is clean." }) : null, files.slice(list.offset, list.offset + height).map((file, position) => {
88
+ const index = list.offset + position;
89
+ const kind = KIND[file.kind];
90
+ const checked = !excluded.has(file.path);
91
+ return (_jsx(Clickable, { onClick: () => { list.select(index); toggle(file.path); }, layer: Layer.overlay, children: _jsxs(Box, { gap: 1, flexGrow: 1, ...(index === list.index ? { backgroundColor: palette.selection } : {}), children: [_jsx(Text, { color: palette.accent, children: index === list.index ? symbols.pointer : ' ' }), _jsx(Text, { color: checked ? palette.accent : palette.faint, children: checked ? '☑' : '☐' }), _jsx(Text, { color: kind.tone ? toneColors(kind.tone).fg : palette.muted, bold: true, children: kind.letter }), _jsx(Text, { color: checked ? palette.text : palette.muted, bold: index === list.index, wrap: "truncate-end", children: file.previousPath ? `${file.previousPath} ${symbols.arrow} ${file.path}` : file.path })] }) }, file.path));
92
+ }), files.length ? (_jsxs(Box, { marginTop: 1, justifyContent: "flex-end", gap: 2, children: [_jsx(Button, { label: "Cancel", onPress: () => closeOverlay(spec) }), _jsx(Button, { label: `Write message ${symbols.arrow}`, variant: "primary", onPress: proceed })] })) : null] }));
93
+ }
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useState } from 'react';
4
+ import { useAppState } from '../app-state.js';
5
+ import { Layer } from '../input/dispatcher.js';
6
+ import { useKeys } from '../input/KeyProvider.js';
7
+ import { Button } from '../kit/Button.js';
8
+ import { palette } from '../theme/theme.js';
9
+ import { wrapText } from '../../utils/text.js';
10
+ import { OverlayFrame, useOverlayWidth } from './OverlayFrame.js';
11
+ const WIDTH = 60;
12
+ export function ConfirmOverlay({ spec }) {
13
+ const { closeOverlay } = useAppState();
14
+ // 0 = Cancel (the default), 1 = the action.
15
+ const [choice, setChoice] = useState(0);
16
+ const inner = useOverlayWidth(WIDTH);
17
+ const confirm = async () => {
18
+ const result = await spec.onConfirm();
19
+ if (result !== false)
20
+ closeOverlay(spec);
21
+ };
22
+ const decide = (yes) => (yes ? void confirm() : closeOverlay(spec));
23
+ useKeys(Layer.overlay, (input, key) => {
24
+ if (key.escape || input === 'n' || input === '1')
25
+ decide(false);
26
+ else if (input === 'y' || input === '2')
27
+ decide(true);
28
+ else if (key.leftArrow || key.rightArrow || key.tab || input === 'h' || input === 'l')
29
+ setChoice(choice === 0 ? 1 : 0);
30
+ else if (key.return)
31
+ decide(choice === 1);
32
+ return true;
33
+ });
34
+ return (_jsxs(OverlayFrame, { title: spec.title, context: spec.context, width: WIDTH, hints: [
35
+ ['←→', 'choose'],
36
+ ['enter', choice === 1 ? spec.confirmLabel : 'cancel'],
37
+ ['y/n', 'answer'],
38
+ ['esc', 'cancel'],
39
+ ], children: [spec.message
40
+ ? wrapText(spec.message, inner).map((line, index) => (_jsx(Text, { color: palette.text, children: line }, index)))
41
+ : null, _jsxs(Box, { marginTop: spec.message ? 1 : 0, justifyContent: "flex-end", gap: 2, children: [_jsx(Button, { label: "Cancel", focused: choice === 0, onPress: () => decide(false) }), _jsx(Button, { label: spec.confirmLabel, tone: spec.tone ?? 'success', focused: choice === 1, onPress: () => decide(true) })] })] }));
42
+ }
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { useEffect, useState } from 'react';
4
+ import { useAppState } from '../app-state.js';
5
+ import { ConsoleLines } from '../components/ConsoleLines.js';
6
+ import { Layer } from '../input/dispatcher.js';
7
+ import { useKeys } from '../input/KeyProvider.js';
8
+ import { palette } from '../theme/theme.js';
9
+ import { OverlayFrame, useOverlayHeight } from './OverlayFrame.js';
10
+ /** Everything SOJA has run in Git during this session, updating live. */
11
+ export function GitLogOverlay({ spec }) {
12
+ const { services, closeOverlay } = useAppState();
13
+ const [lines, setLines] = useState(() => services.gitConsole.since());
14
+ const height = Math.min(20, useOverlayHeight());
15
+ useEffect(() => services.gitConsole.subscribe(() => setLines(services.gitConsole.since())), [services.gitConsole]);
16
+ useKeys(Layer.overlay, (_input, key) => {
17
+ if (key.escape || key.return)
18
+ closeOverlay(spec);
19
+ return true;
20
+ });
21
+ return (_jsx(OverlayFrame, { title: "Git log", context: "commands SOJA ran this session", width: 100, hints: [['esc', 'close']], children: lines.length === 0 ? _jsx(Text, { color: palette.faint, children: "Nothing yet. Git operations you run from SOJA show up here, live." }) : _jsx(ConsoleLines, { lines: lines.slice(-height) }) }));
22
+ }
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { GitError } from '../../git/types.js';
5
+ import { toDisplayError } from '../../utils/errors.js';
6
+ import { useAppState } from '../app-state.js';
7
+ import { ConsoleLines } from '../components/ConsoleLines.js';
8
+ import { Layer } from '../input/dispatcher.js';
9
+ import { useExternalTerminal, useKeys } from '../input/KeyProvider.js';
10
+ import { Button } from '../kit/Button.js';
11
+ import { Spinner } from '../kit/Spinner.js';
12
+ import { palette, symbols } from '../theme/theme.js';
13
+ import { OverlayFrame, useOverlayHeight } from './OverlayFrame.js';
14
+ export function GitRunOverlay({ spec }) {
15
+ const { services, closeOverlay, refresh } = useAppState();
16
+ const external = useExternalTerminal();
17
+ const [startId] = useState(() => services.gitConsole.lastId);
18
+ const [lines, setLines] = useState([]);
19
+ const [outcome, setOutcome] = useState({ phase: 'running' });
20
+ const started = useRef(false);
21
+ const height = useOverlayHeight();
22
+ useEffect(() => services.gitConsole.subscribe(() => setLines(services.gitConsole.since(startId))), [services.gitConsole, startId]);
23
+ const execute = async (mode) => {
24
+ try {
25
+ let message = '';
26
+ if (mode === 'normal') {
27
+ message = await spec.run(false);
28
+ }
29
+ else {
30
+ // Hand the terminal to Git/gh so they can ask for credentials, then come back.
31
+ await external(async () => {
32
+ process.stdout.write(`\nSOJA is paused. ${mode === 'login' ? 'Log in to GitHub below.' : 'Git may ask for your credentials.'}\n\n`);
33
+ if (mode === 'login')
34
+ await services.git.loginGitHub();
35
+ message = mode === 'login' ? await spec.run(false) : await spec.run(true);
36
+ });
37
+ }
38
+ services.gitConsole.write('success', message);
39
+ setOutcome({ phase: 'done', message });
40
+ }
41
+ catch (error) {
42
+ const display = toDisplayError(error);
43
+ setOutcome({
44
+ phase: 'failed',
45
+ message: display.message,
46
+ suggestions: error instanceof GitError ? error.suggestions : display.hint ? [display.hint] : [],
47
+ code: error instanceof GitError ? error.code : null,
48
+ });
49
+ }
50
+ finally {
51
+ refresh();
52
+ }
53
+ };
54
+ useEffect(() => {
55
+ if (started.current)
56
+ return;
57
+ started.current = true;
58
+ void execute('normal');
59
+ // Runs once when the overlay opens.
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
61
+ }, []);
62
+ const retry = (mode) => {
63
+ setOutcome({ phase: 'running' });
64
+ void execute(mode);
65
+ };
66
+ const recovery = [];
67
+ if (outcome.phase === 'failed') {
68
+ if (outcome.code === 'auth_required')
69
+ recovery.push({ key: 'i', label: 'retry with credentials', action: () => retry('interactive') });
70
+ if (outcome.code === 'gh_auth')
71
+ recovery.push({ key: 'i', label: 'gh auth login and retry', action: () => retry('login') });
72
+ const custom = outcome.code ? spec.recover?.[outcome.code] : undefined;
73
+ if (custom)
74
+ recovery.push(custom);
75
+ recovery.push({ key: 'r', label: 'retry', action: () => retry('normal') });
76
+ }
77
+ const actions = outcome.phase === 'done' ? (spec.next ?? []) : recovery;
78
+ useKeys(Layer.overlay, (input, key) => {
79
+ if (outcome.phase === 'running')
80
+ return true;
81
+ if (key.escape || key.return)
82
+ closeOverlay(spec);
83
+ else
84
+ actions.find((action) => action.key === input)?.action();
85
+ return true;
86
+ });
87
+ const suggestions = outcome.phase === 'failed' ? outcome.suggestions : [];
88
+ // The success line is shown below; the console keeps it for the Git log.
89
+ const visible = lines.filter((line) => line.kind !== 'success').slice(-Math.max(3, height - 3 - suggestions.length));
90
+ const hints = outcome.phase === 'running'
91
+ ? [['', 'running…']]
92
+ : [...actions.map((action) => [action.key, action.label]), ['esc', 'close']];
93
+ return (_jsxs(OverlayFrame, { title: spec.title, context: spec.context, hints: hints, width: 96, children: [_jsx(ConsoleLines, { lines: visible, minRows: 3 }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: outcome.phase === 'running' ? (_jsx(Spinner, { label: "Working\u2026" })) : outcome.phase === 'done' ? (_jsx(Text, { color: palette.success, bold: true, wrap: "truncate-end", children: `${symbols.check} ${outcome.message}` })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: palette.danger, bold: true, wrap: "truncate-end", children: `${symbols.cross} ${outcome.message}` }), suggestions.map((suggestion) => (_jsx(Text, { color: palette.muted, wrap: "truncate-end", children: ` ${symbols.arrow} ${suggestion}` }, suggestion)))] })) }), outcome.phase !== 'running' ? (_jsxs(Box, { marginTop: 1, justifyContent: "flex-end", gap: 2, children: [actions.map((action) => (_jsx(Button, { label: `${action.key} ${action.label}`, onPress: () => action.action() }, action.key))), _jsx(Button, { label: "Close", variant: "primary", onPress: () => closeOverlay(spec) })] })) : null] }));
94
+ }