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,146 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion -- canvas operations validate coordinates before writing. */
2
+ /**
3
+ * Motor de escenas ASCII, sin dependencias.
4
+ * Una escena dibuja en un Canvas de celdas (carácter + tono) y el renderer
5
+ * (Ink, ANSI o navegador) traduce cada tono a un color.
6
+ */
7
+ export class Canvas {
8
+ width;
9
+ height;
10
+ chars;
11
+ tones;
12
+ constructor(width, height) {
13
+ this.width = width;
14
+ this.height = height;
15
+ this.chars = new Array(width * height).fill(' ');
16
+ this.tones = new Array(width * height).fill(null);
17
+ }
18
+ /** Escribe un texto; los espacios son transparentes. */
19
+ put(x, y, text, tone = null) {
20
+ const rx = Math.round(x);
21
+ const ry = Math.round(y);
22
+ if (ry < 0 || ry >= this.height)
23
+ return;
24
+ for (let i = 0; i < text.length; i++) {
25
+ const ch = text[i];
26
+ const cx = rx + i;
27
+ if (ch === ' ' || cx < 0 || cx >= this.width)
28
+ continue;
29
+ this.chars[ry * this.width + cx] = ch;
30
+ this.tones[ry * this.width + cx] = tone;
31
+ }
32
+ }
33
+ /** Escribe un solo carácter, aunque sea un espacio (sirve para borrar). */
34
+ set(x, y, ch, tone = null) {
35
+ const rx = Math.round(x);
36
+ const ry = Math.round(y);
37
+ if (rx < 0 || rx >= this.width || ry < 0 || ry >= this.height)
38
+ return;
39
+ this.chars[ry * this.width + rx] = ch;
40
+ this.tones[ry * this.width + rx] = tone;
41
+ }
42
+ /** Dibuja un sprite de varias líneas con la parte superior en `top`. */
43
+ sprite(x, top, lines, tone) {
44
+ lines.forEach((line, i) => this.put(x, top + i, line, tone));
45
+ }
46
+ /** Filas agrupadas en segmentos del mismo tono, listas para renderizar. */
47
+ rows() {
48
+ const out = [];
49
+ for (let y = 0; y < this.height; y++) {
50
+ const row = [];
51
+ let tone;
52
+ let buf = '';
53
+ for (let x = 0; x < this.width; x++) {
54
+ const i = y * this.width + x;
55
+ const t = this.chars[i] === ' ' ? null : this.tones[i];
56
+ if (t !== tone && buf) {
57
+ row.push({ text: buf, tone: tone ?? null });
58
+ buf = '';
59
+ }
60
+ tone = t;
61
+ buf += this.chars[i];
62
+ }
63
+ if (buf)
64
+ row.push({ text: buf, tone: tone ?? null });
65
+ out.push(row);
66
+ }
67
+ return out;
68
+ }
69
+ toString() {
70
+ const lines = [];
71
+ for (let y = 0; y < this.height; y++) {
72
+ lines.push(this.chars.slice(y * this.width, (y + 1) * this.width).join(''));
73
+ }
74
+ return lines.join('\n');
75
+ }
76
+ }
77
+ /* ---------- utilidades compartidas ---------- */
78
+ /** PRNG determinista (mulberry32) para que cada escena sea reproducible. */
79
+ export function rng(seed = 1) {
80
+ let a = seed >>> 0;
81
+ return () => {
82
+ a = (a + 0x6d2b79f5) >>> 0;
83
+ let t = a;
84
+ t = Math.imul(t ^ (t >>> 15), t | 1);
85
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
86
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
87
+ };
88
+ }
89
+ const MIRROR = {
90
+ '/': '\\',
91
+ '\\': '/',
92
+ '[': ']',
93
+ ']': '[',
94
+ '(': ')',
95
+ ')': '(',
96
+ '<': '>',
97
+ '>': '<',
98
+ };
99
+ /** Voltea un sprite horizontalmente. */
100
+ export function mirror(lines) {
101
+ const w = Math.max(...lines.map((l) => l.length));
102
+ return lines.map((l) => l
103
+ .padEnd(w)
104
+ .split('')
105
+ .reverse()
106
+ .map((c) => MIRROR[c] ?? c)
107
+ .join(''));
108
+ }
109
+ /** Sustituye la cara `o.o` del sprite. */
110
+ export function withFace(lines, face) {
111
+ return lines.map((l) => l.replace('o.o', face));
112
+ }
113
+ /** Cara con parpadeo ocasional, desfasado por agente. */
114
+ export function blink(t, i, face = 'o.o') {
115
+ return (t + i * 37) % 70 < 2 ? '-.-' : face;
116
+ }
117
+ export function soilLine(width, r) {
118
+ let s = '';
119
+ for (let i = 0; i < width; i++) {
120
+ const q = r();
121
+ s += q < 0.08 ? '.' : q < 0.12 ? ':' : q < 0.15 ? "'" : ' ';
122
+ }
123
+ return s;
124
+ }
125
+ export function drawGround(c, groundY, soil) {
126
+ for (let x = 0; x < c.width; x++)
127
+ c.set(x, groundY, '▀', 'soil');
128
+ soil.forEach((line, i) => c.put(0, groundY + 1 + i, line, 'soil'));
129
+ }
130
+ export function drawCloud(c, t, speed, offset, y, big = true) {
131
+ const x = ((t * speed + offset) % (c.width + 14)) - 12;
132
+ if (big) {
133
+ c.put(x, y, ' .--.', 'cloud');
134
+ c.put(x, y + 1, '.( ).', 'cloud');
135
+ }
136
+ else {
137
+ c.put(x, y, ' .-. ', 'cloud');
138
+ c.put(x, y + 1, '( )', 'cloud');
139
+ }
140
+ }
141
+ /** Sprites base del agente (5 de ancho, cara `o.o`). */
142
+ export const AGENT = {
143
+ stand: [' .-. ', '(o.o)', '( )', " ' ' "],
144
+ walk: [' .-. ', '(o.o)', '( )', " ' '"],
145
+ sit: [' .-. ', '(o.o)', '(___)'],
146
+ };
@@ -0,0 +1,6 @@
1
+ export { AgentScene } from './AgentScene.js';
2
+ export { SCENES, SCENE_IDS } from './scenes/index.js';
3
+ export { TASK_STATE_SCENES, sceneForState } from './states.js';
4
+ export { defaultPalette } from './palette.js';
5
+ export { frameToAnsi, frameToText } from './ansi.js';
6
+ export { Canvas } from './engine.js';
@@ -0,0 +1,18 @@
1
+ export const defaultPalette = {
2
+ agent: '#D85A30',
3
+ rock: '#9A9893',
4
+ soil: '#6B6A66',
5
+ dirt: '#C8913A',
6
+ spark: '#F2C14E',
7
+ fire: '#E24B4A',
8
+ flame: '#F2A93B',
9
+ water: '#4FA3E0',
10
+ good: '#63B34A',
11
+ bad: '#E24B4A',
12
+ accent: '#5AA9E6',
13
+ cloud: '#7D7C77',
14
+ wood: '#A9743F',
15
+ paper: '#E8E6DF',
16
+ leaf: '#5FAE54',
17
+ text: '#CFCDC6',
18
+ };
@@ -0,0 +1,115 @@
1
+ import { blink, drawGround, mirror, rng, soilLine, withFace } from '../engine.js';
2
+ const W = 78;
3
+ const H = 12;
4
+ const G = 10;
5
+ const BUG_A = [' \\ / ', '=(oo)=', ' / \\ '];
6
+ const BUG_B = [' / \\ ', '=(oo)=', ' \\ / '];
7
+ const NET_UP = [' .-. (@)', '(o.o)/ ', '( ) ', " ' ' "];
8
+ const NET_DOWN = [' .-. ', '(o.o) ', '( )\\ ', " ' ' (@)"];
9
+ const MAGNIFY = [' .-. ', '(o.o) ', '( )-O', " ' ' "];
10
+ const SCARED = ['\\.-./', '(O.O)', '( )', " ' ' "];
11
+ const CRATE_X = 36;
12
+ export const bugHunt = {
13
+ id: 'bugHunt',
14
+ title: 'Cazando bugs',
15
+ useFor: 'Errores / tareas bloqueadas',
16
+ width: W,
17
+ height: H,
18
+ create(seed = 8) {
19
+ const r = rng(seed);
20
+ const soil = [soilLine(W, r)];
21
+ let t = 0;
22
+ let bx = 60;
23
+ let bv = -0.7;
24
+ let netX = 10;
25
+ let magX = 2;
26
+ let swing = 0;
27
+ let caught = 0;
28
+ let respawn = 0;
29
+ let caughtFlash = 0;
30
+ let dust = [];
31
+ return {
32
+ step() {
33
+ t++;
34
+ if (respawn) {
35
+ respawn--;
36
+ if (!respawn) {
37
+ bx = r() < 0.5 ? 2 : W - 8;
38
+ bv = bx < 10 ? 0.7 : -0.7;
39
+ }
40
+ }
41
+ else {
42
+ if (r() < 0.04)
43
+ bv = -bv;
44
+ if (r() < 0.02)
45
+ bv *= 1.8;
46
+ bv = Math.max(-1.3, Math.min(1.3, bv));
47
+ if (Math.abs(bv) > 0.8 && r() < 0.1)
48
+ bv *= 0.6;
49
+ bx += bv;
50
+ if (bx < 1 || bx > W - 7) {
51
+ bv = -bv;
52
+ bx = Math.max(1, Math.min(W - 7, bx));
53
+ }
54
+ if (t % 3 === 0)
55
+ dust.push({ x: bx + (bv > 0 ? 0 : 5), y: G - 1, vx: -bv * 0.3, vy: -0.1, ch: '.', tone: 'soil', life: 0 });
56
+ }
57
+ const target = bx + 3;
58
+ const netFacesRight = target >= netX + 3;
59
+ const goal = netFacesRight ? target - 6 : target;
60
+ netX += Math.sign(goal - netX) * Math.min(0.6, Math.abs(goal - netX));
61
+ magX += Math.sign(netX - 9 - magX) * Math.min(0.35, Math.abs(netX - 9 - magX));
62
+ if (swing)
63
+ swing--;
64
+ else if (!respawn && Math.abs(goal - netX) < 3 && t % 8 === 0)
65
+ swing = 6;
66
+ if (swing === 3 && !respawn) {
67
+ const netPoint = netFacesRight ? netX + 6 : netX - 1;
68
+ if (Math.abs(netPoint - target) < 2.5) {
69
+ caught++;
70
+ respawn = 30;
71
+ caughtFlash = 14;
72
+ }
73
+ }
74
+ if (caughtFlash)
75
+ caughtFlash--;
76
+ for (const d of dust) {
77
+ d.x += d.vx;
78
+ d.y += d.vy;
79
+ d.life = (d.life ?? 0) + 1;
80
+ }
81
+ dust = dust.filter((d) => (d.life ?? 0) < 6);
82
+ },
83
+ draw(c) {
84
+ drawGround(c, G, soil);
85
+ c.put(CRATE_X - 1, G - 2, '[_____]', 'wood');
86
+ c.put(CRATE_X - 1, G - 1, '[_____]', 'wood');
87
+ const near = !respawn && Math.abs(bx + 3 - (CRATE_X + 2)) < 10;
88
+ c.sprite(CRATE_X, G - 6, near ? SCARED : withFace([' .-. ', '(o.o)', '( )', " ' ' "], blink(t, 3)), 'agent');
89
+ if (near && t % 8 < 5)
90
+ c.put(CRATE_X + 6, G - 7, '(!!)', 'cloud');
91
+ if (!respawn) {
92
+ c.sprite(bx, G - 3, Math.floor(t / 2) % 2 ? BUG_A : BUG_B, 'leaf');
93
+ }
94
+ for (const d of dust)
95
+ c.put(d.x, d.y, d.ch, d.tone);
96
+ const right = bx + 3 >= netX + 3;
97
+ let net = swing > 3 ? NET_UP : swing > 0 ? NET_DOWN : NET_UP;
98
+ if (!right)
99
+ net = mirror(net);
100
+ const nx = right ? netX : netX - 3;
101
+ c.sprite(nx, G - 4, withFace(net, caughtFlash ? '^.^' : '>.<'), 'agent');
102
+ if (caughtFlash) {
103
+ const px = right ? nx + 5 : nx;
104
+ c.put(px, G - 5, '(@)', 'leaf');
105
+ if (t % 6 < 4)
106
+ c.put(nx - 1, G - 6, '(te tengo!)', 'good');
107
+ }
108
+ c.sprite(magX, G - 4, withFace(MAGNIFY, blink(t, 5, 'o.O')), 'agent');
109
+ },
110
+ status() {
111
+ return respawn ? `bug atrapado · total ${caught}` : `persiguiendo bug · atrapados ${caught}`;
112
+ },
113
+ };
114
+ },
115
+ };
@@ -0,0 +1,76 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion -- scene arrays are indexed within their declared bounds. */
2
+ import { AGENT, blink, drawGround, mirror, rng, soilLine, withFace } from '../engine.js';
3
+ const W = 78;
4
+ const H = 13;
5
+ const G = 11;
6
+ const JUMP = ['\\.-./', '(^.^)', '( )', " / \\ "];
7
+ const CHEER = [' .-. ', '(^.^)', '( )/', " ' ' "];
8
+ const BOX = [" .----------. ", " | | ", " | // | ", " | \\\\ // | ", " | \\\\ // | ", " | \\V/ | ", " '----------' "];
9
+ const CONFETTI = ['*', '+', '.', 'o', '~', "'"];
10
+ const TONES = ['agent', 'accent', 'good', 'spark', 'water', 'flame'];
11
+ export const celebrate = {
12
+ id: 'celebrate',
13
+ title: 'Celebrando',
14
+ useFor: 'Tarea completada / meta alcanzada',
15
+ width: W,
16
+ height: H,
17
+ create(seed = 6) {
18
+ const r = rng(seed);
19
+ const soil = [soilLine(W, r)];
20
+ const agents = [3, 12, 21, 52, 61, 70].map((x, i) => ({ x, off: i * 3, m: i >= 3 }));
21
+ let t = 0;
22
+ let streak = 1;
23
+ let confetti = [];
24
+ let bursts = [];
25
+ return {
26
+ step() {
27
+ t++;
28
+ for (let i = 0; i < 2; i++) {
29
+ confetti.push({ x: r() * W, y: -1, vx: (r() - 0.5) * 0.3, vy: 0.15 + r() * 0.2, ch: CONFETTI[Math.floor(r() * CONFETTI.length)], tone: TONES[Math.floor(r() * TONES.length)] });
30
+ }
31
+ for (const p of confetti) {
32
+ p.x += p.vx + Math.sin((t + p.y * 3) * 0.3) * 0.15;
33
+ p.y += p.vy;
34
+ }
35
+ confetti = confetti.filter((p) => p.y < G);
36
+ if (t % 18 === 0) {
37
+ bursts.push({ x: 8 + r() * (W - 16), y: 1 + r() * 3, l: 0, tone: TONES[Math.floor(r() * TONES.length)] });
38
+ if (t % 90 === 0)
39
+ streak++;
40
+ }
41
+ for (const b of bursts)
42
+ b.l++;
43
+ bursts = bursts.filter((b) => b.l < 8);
44
+ },
45
+ draw(c) {
46
+ drawGround(c, G, soil);
47
+ for (const b of bursts) {
48
+ const rad = b.l * 0.9;
49
+ const ch = b.l < 3 ? '*' : b.l < 6 ? '+' : '.';
50
+ for (let a = 0; a < 8; a++) {
51
+ const ang = (a / 8) * Math.PI * 2;
52
+ c.put(b.x + Math.cos(ang) * rad * 2, b.y + Math.sin(ang) * rad * 0.8, ch, b.tone);
53
+ }
54
+ if (b.l < 2)
55
+ c.put(b.x, b.y, 'o', 'spark');
56
+ }
57
+ const pulse = Math.floor(t / 6) % 2;
58
+ c.sprite(32, G - 7, BOX, 'rock');
59
+ c.sprite(32, G - 7, BOX.map((l) => l.replace(/[.\-|']/g, ' ')), pulse ? 'good' : 'leaf');
60
+ agents.forEach((a, i) => {
61
+ const ph = (t + a.off) % 12;
62
+ const up = ph < 6 ? Math.round(Math.sin((ph / 6) * Math.PI) * 2) : 0;
63
+ let sp = up > 0 ? JUMP : i % 2 ? CHEER : withFace(AGENT.stand, '^.^');
64
+ if (a.m)
65
+ sp = mirror(sp);
66
+ c.sprite(a.x, G - 4 - up, withFace(sp, blink(t, i, '^.^')), 'agent');
67
+ });
68
+ for (const p of confetti)
69
+ c.put(p.x, p.y, p.ch, p.tone);
70
+ },
71
+ status() {
72
+ return `tarea completada · racha x${streak}`;
73
+ },
74
+ };
75
+ },
76
+ };
@@ -0,0 +1,156 @@
1
+ /* eslint-disable prefer-const -- particle arrays are mutated in place by the scene engine. */
2
+ import { AGENT, blink, drawGround, mirror, rng, soilLine, withFace } from '../engine.js';
3
+ const W = 78;
4
+ const H = 12;
5
+ const G = 10;
6
+ const CABINET = [' .------. ', ' |[ -- ]| ', ' |[ -- ]| ', ' |[ -- ]| ', " '------' "];
7
+ const BIN = ['._____.', '| |', '| |', "'-----'"];
8
+ const SWEEP_A = [' .-. ', '(o.o) ', '( )\\ ', " ' ' #"];
9
+ const SWEEP_B = [' .-. ', '(o.o) ', '( )| ', " ' ' #"];
10
+ const BROOM_UP = mirror([' .-. #', '(o.o)|', '( )|', " ' ' "]);
11
+ const CARRY = [' .-. ', '(o.o) ', '( )=', " ' ' "];
12
+ const CARRY_B = [' .-. ', '(o.o) ', '( )=', " ' ' "];
13
+ const SWEEP_L = 22;
14
+ const BIN_X = 70;
15
+ const BOX_X = 16;
16
+ const BOX_MAX = 6;
17
+ export const cleaning = {
18
+ id: 'cleaning',
19
+ title: 'Limpiando',
20
+ useFor: 'Archivar / limpiar tareas completadas',
21
+ width: W,
22
+ height: H,
23
+ create(seed = 11) {
24
+ const r = rng(seed);
25
+ const soil = [soilLine(W, r)];
26
+ let t = 0;
27
+ let dust = new Set();
28
+ const sprinkle = (n) => {
29
+ for (let i = 0; i < n; i++)
30
+ dust.add(SWEEP_L + 6 + Math.floor(r() * (BIN_X - SWEEP_L - 8)));
31
+ };
32
+ sprinkle(18);
33
+ const sweepers = [
34
+ { x: SWEEP_L, dir: 1, load: 0, n: 1 },
35
+ { x: SWEEP_L + 20, dir: 1, load: 0, n: 2 },
36
+ ];
37
+ let bin = 0;
38
+ let archived = 0;
39
+ let boxes = BOX_MAX;
40
+ const arch = { x: BOX_X - 5, st: 'pick', k: 0 };
41
+ let puffs = [];
42
+ return {
43
+ step() {
44
+ t++;
45
+ for (const s of sweepers) {
46
+ if (s.dir > 0) {
47
+ s.x += 0.3;
48
+ const head = Math.round(s.x + 6);
49
+ for (const d of [head, head + 1]) {
50
+ if (dust.has(d)) {
51
+ dust.delete(d);
52
+ s.load++;
53
+ puffs.push({ x: d, y: G - 2, vx: (r() - 0.3) * 0.4, vy: -0.3 - r() * 0.2, ch: r() < 0.5 ? '.' : "'", tone: 'soil', life: 0 });
54
+ }
55
+ }
56
+ if (t % 5 === s.n)
57
+ puffs.push({ x: s.x + 6, y: G - 1, vx: r() * 0.4, vy: -0.25, ch: '.', tone: 'cloud', life: 0 });
58
+ if (s.x + 7 >= BIN_X) {
59
+ bin = Math.min(100, bin + s.load * 4);
60
+ s.load = 0;
61
+ s.dir = -1;
62
+ }
63
+ }
64
+ else {
65
+ s.x -= 0.45;
66
+ if (s.x <= SWEEP_L) {
67
+ s.dir = 1;
68
+ sprinkle(5);
69
+ }
70
+ }
71
+ }
72
+ if (bin >= 100 && t % 40 === 0)
73
+ bin = 0;
74
+ arch.k++;
75
+ if (arch.st === 'pick' && arch.k > 5) {
76
+ if (boxes > 0) {
77
+ boxes--;
78
+ arch.st = 'go';
79
+ }
80
+ arch.k = 0;
81
+ }
82
+ else if (arch.st === 'go') {
83
+ arch.x -= 0.4;
84
+ if (arch.x <= 9) {
85
+ arch.st = 'drop';
86
+ arch.k = 0;
87
+ }
88
+ }
89
+ else if (arch.st === 'drop' && arch.k > 5) {
90
+ archived++;
91
+ arch.st = 'back';
92
+ arch.k = 0;
93
+ }
94
+ else if (arch.st === 'back') {
95
+ arch.x += 0.4;
96
+ if (arch.x >= BOX_X - 5) {
97
+ arch.st = 'pick';
98
+ arch.k = 0;
99
+ if (boxes === 0)
100
+ boxes = BOX_MAX;
101
+ }
102
+ }
103
+ for (const p of puffs) {
104
+ p.x += p.vx;
105
+ p.y += p.vy;
106
+ p.life = (p.life ?? 0) + 1;
107
+ }
108
+ puffs = puffs.filter((p) => (p.life ?? 0) < 7);
109
+ },
110
+ draw(c) {
111
+ drawGround(c, G, soil);
112
+ c.sprite(0, G - 5, CABINET, 'rock');
113
+ const drawer = arch.st === 'drop' ? 1 : -1;
114
+ if (drawer > 0)
115
+ c.put(2, G - 3, '[====]', 'paper');
116
+ for (let i = 0; i < boxes; i++)
117
+ c.put(BOX_X + (i % 2) * 3, G - 1 - Math.floor(i / 2), '[▤]', 'paper');
118
+ c.sprite(BIN_X, G - 4, BIN, 'rock');
119
+ const fill = Math.round((bin / 100) * 2);
120
+ for (let i = 0; i < fill; i++)
121
+ c.put(BIN_X + 1, G - 2 - i, ':.:.:', 'soil');
122
+ if (bin >= 100 && t % 8 < 5)
123
+ c.put(BIN_X - 1, G - 6, '(lleno)', 'cloud');
124
+ for (const d of dust)
125
+ c.put(d, G - 1, t % 20 < 10 ? '.' : ',', 'soil');
126
+ sweepers.forEach((s, i) => {
127
+ if (s.dir > 0) {
128
+ const sp = Math.floor(t / 3) % 2 ? SWEEP_A : SWEEP_B;
129
+ c.sprite(s.x, G - 4, withFace(sp, blink(t, i)), 'agent');
130
+ if (s.load > 0)
131
+ c.put(s.x + 7, G - 1, s.load > 4 ? '::' : ':', 'soil');
132
+ }
133
+ else {
134
+ c.sprite(s.x, G - 4, withFace(BROOM_UP, Math.floor(t / 2) % 2 ? '^.^' : blink(t, i, '^.^')), 'agent');
135
+ }
136
+ });
137
+ const w = Math.floor(t / 2) % 2;
138
+ if (arch.st === 'go') {
139
+ c.sprite(arch.x, G - 4, withFace(mirror(w ? CARRY : CARRY_B), blink(t, 5)), 'agent');
140
+ c.put(arch.x - 3, G - 2, '[▤]', 'paper');
141
+ }
142
+ else if (arch.st === 'drop') {
143
+ c.sprite(arch.x, G - 4, withFace(mirror(CARRY), '^.^'), 'agent');
144
+ }
145
+ else {
146
+ c.sprite(arch.x, G - 4, withFace(arch.st === 'back' && w ? AGENT.walk : AGENT.stand, blink(t, 5)), 'agent');
147
+ }
148
+ for (const p of puffs)
149
+ c.put(p.x, p.y, p.ch, p.tone);
150
+ },
151
+ status() {
152
+ return `archivadas ${archived} · basura ${bin}%`;
153
+ },
154
+ };
155
+ },
156
+ };
@@ -0,0 +1,105 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion, prefer-const -- scene arrays are indexed within bounds; particle arrays are mutated in place. */
2
+ import { AGENT, blink, drawCloud, drawGround, rng, soilLine, withFace } from '../engine.js';
3
+ const W = 78;
4
+ const H = 12;
5
+ const G = 10;
6
+ const FRONT = 57;
7
+ const GAP = 7;
8
+ const CLOCK = 64;
9
+ const PUNCH = [' .-. ', '(o.o) ', '( )=]', " ' ' "];
10
+ const TAP = [' .-. ', '(o.o)', '( )', " ' ."];
11
+ const TALK = ['(zzz)', '(ya?)', '(café..)', '(...)', '(lunes)'];
12
+ export const clockIn = {
13
+ id: 'clockIn',
14
+ title: 'Ponchando entrada',
15
+ useFor: 'Inicio del día / cargando la app',
16
+ width: W,
17
+ height: H,
18
+ create(seed = 3) {
19
+ const r = rng(seed);
20
+ const soil = [soilLine(W, r)];
21
+ let t = 0;
22
+ let q = [];
23
+ let gone = [];
24
+ let nid = 6;
25
+ let punched = 0;
26
+ let flash = 0;
27
+ let mins = 7 * 60 + 52;
28
+ for (let i = 0; i < 5; i++)
29
+ q.push({ x: FRONT - GAP * i, k: 0, id: i + 1 });
30
+ return {
31
+ step() {
32
+ t++;
33
+ if (t % 25 === 0) {
34
+ mins++;
35
+ if (mins > 8 * 60 + 10)
36
+ mins = 7 * 60 + 52;
37
+ }
38
+ q.forEach((a, i) => {
39
+ const tx = FRONT - GAP * i;
40
+ a.moving = a.x < tx;
41
+ if (a.moving)
42
+ a.x = Math.min(tx, a.x + 0.5);
43
+ });
44
+ const f = q[0];
45
+ if (f && f.x === FRONT) {
46
+ f.k++;
47
+ if (f.k === 9) {
48
+ punched++;
49
+ flash = 10;
50
+ }
51
+ if (f.k > 16) {
52
+ gone.push(q.shift());
53
+ q.push({ x: -6, k: 0, id: nid++ });
54
+ }
55
+ }
56
+ for (const a of gone)
57
+ a.x += 0.5;
58
+ gone = gone.filter((a) => a.x < W);
59
+ if (flash)
60
+ flash--;
61
+ },
62
+ draw(c) {
63
+ drawCloud(c, t, 0.05, 50, 0);
64
+ drawGround(c, G, soil);
65
+ c.put(28, 1, ' ______________ ', 'wood');
66
+ c.put(28, 2, '| entrada 8:00 |', 'wood');
67
+ c.put(28, 3, "'--------------'", 'wood');
68
+ c.put(28, 2, '|', 'wood');
69
+ c.put(30, 2, 'entrada 8:00', 'text');
70
+ c.put(74, G - 6, '____', 'wood');
71
+ for (let y = G - 5; y < G; y++)
72
+ c.put(74, y, '| |', 'wood');
73
+ c.put(76, G - 3, 'o', 'spark');
74
+ const talker = Math.floor(t / 20) % 5;
75
+ q.forEach((a, i) => {
76
+ const w = Math.floor(t / 2) % 2;
77
+ let sp = a.moving ? (w ? AGENT.stand : AGENT.walk) : AGENT.stand;
78
+ if (i === 0 && a.k >= 4 && a.k <= 14)
79
+ sp = PUNCH;
80
+ if (!a.moving && a.id % 3 === 2 && i > 0)
81
+ sp = Math.floor(t / 3) % 2 ? AGENT.stand : TAP;
82
+ const face = i === 0 && a.k > 9 ? '^.^' : a.id % 3 === 0 ? blink(t, a.id, '-.o') : blink(t, a.id);
83
+ c.sprite(a.x, G - 4, withFace(sp, face), 'agent');
84
+ if (i === talker && i > 0 && !a.moving)
85
+ c.put(a.x - 1, G - 5, TALK[a.id % 5], 'cloud');
86
+ });
87
+ for (const a of gone)
88
+ c.sprite(a.x, G - 4, withFace(Math.floor(t / 2) % 2 ? AGENT.stand : AGENT.walk, '^.^'), 'agent');
89
+ c.put(CLOCK, G - 5, ' ______ ', 'rock');
90
+ c.put(CLOCK, G - 4, '| |', 'rock');
91
+ c.put(CLOCK, G - 3, '| |', 'rock');
92
+ c.put(CLOCK, G - 2, '|[____]|', 'rock');
93
+ c.put(CLOCK, G - 1, "'------'", 'rock');
94
+ const hh = String(Math.floor(mins / 60)).padStart(2, '0');
95
+ const mm = String(mins % 60).padStart(2, '0');
96
+ c.put(CLOCK + 1, G - 4, hh + (t % 10 < 5 ? ':' : ' ') + mm, 'accent');
97
+ if (flash)
98
+ c.put(CLOCK + 3, G - 3, 'ok', 'good');
99
+ },
100
+ status() {
101
+ return `ponchados ${punched} · en fila ${q.length}`;
102
+ },
103
+ };
104
+ },
105
+ };