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.
- package/LICENSE +18 -0
- package/README.md +117 -0
- package/dist/application/chat.js +1 -0
- package/dist/application/filters.js +27 -0
- package/dist/application/import.js +1 -0
- package/dist/application/ports.js +1 -0
- package/dist/application/repository-root.js +18 -0
- package/dist/application/services/backup-service.js +93 -0
- package/dist/application/services/folder-service.js +116 -0
- package/dist/application/services/git-workflow-service.js +593 -0
- package/dist/application/services/index.js +24 -0
- package/dist/application/services/project-service.js +129 -0
- package/dist/application/services/session-service.js +52 -0
- package/dist/application/services/task-service.js +350 -0
- package/dist/application/services/update-service.js +60 -0
- package/dist/application/services/workspace-service.js +81 -0
- package/dist/application/timeline.js +83 -0
- package/dist/application/types.js +1 -0
- package/dist/application/validation.js +30 -0
- package/dist/bootstrap.js +91 -0
- package/dist/cli/commands/access.js +60 -0
- package/dist/cli/commands/account.js +203 -0
- package/dist/cli/commands/args.js +27 -0
- package/dist/cli/commands/backup.js +70 -0
- package/dist/cli/commands/chat.js +133 -0
- package/dist/cli/commands/dev.js +59 -0
- package/dist/cli/commands/folders.js +59 -0
- package/dist/cli/commands/git-ops.js +200 -0
- package/dist/cli/commands/import.js +48 -0
- package/dist/cli/commands/info.js +82 -0
- package/dist/cli/commands/project.js +108 -0
- package/dist/cli/commands/start.js +34 -0
- package/dist/cli/commands/sync.js +43 -0
- package/dist/cli/commands/task.js +309 -0
- package/dist/cli/commands/tui.js +43 -0
- package/dist/cli/commands/update.js +20 -0
- package/dist/cli/commands/workspace.js +55 -0
- package/dist/cli/index.js +103 -0
- package/dist/cli/output.js +56 -0
- package/dist/cli/prompt.js +28 -0
- package/dist/cli/runtime.js +43 -0
- package/dist/config/config.js +103 -0
- package/dist/config/credentials.js +68 -0
- package/dist/config/paths.js +18 -0
- package/dist/config/server.js +21 -0
- package/dist/config/version.js +7 -0
- package/dist/data/local/activity.js +33 -0
- package/dist/data/local/clock.js +1 -0
- package/dist/data/local/comments.js +27 -0
- package/dist/data/local/index.js +48 -0
- package/dist/data/local/projects.js +53 -0
- package/dist/data/local/tasks.js +104 -0
- package/dist/data/local/users.js +38 -0
- package/dist/data/local/workspaces.js +55 -0
- package/dist/data/remote/api-client.js +103 -0
- package/dist/data/remote/live.js +93 -0
- package/dist/data/repositories.js +1 -0
- package/dist/data/sync/chat.js +242 -0
- package/dist/data/sync/engine.js +459 -0
- package/dist/data/sync/importer.js +156 -0
- package/dist/data/sync/index.js +109 -0
- package/dist/data/sync/services.js +263 -0
- package/dist/data/sync/store.js +200 -0
- package/dist/database/client.js +76 -0
- package/dist/database/migrate.js +27 -0
- package/dist/database/migrations/0000_init.sql +100 -0
- package/dist/database/migrations/0001_git-operations.sql +19 -0
- package/dist/database/migrations/0002_merge-detection.sql +19 -0
- package/dist/database/migrations/0003_sync-replica.sql +40 -0
- package/dist/database/migrations/0004_chat-replica.sql +52 -0
- package/dist/database/migrations/0005_pr-events.sql +18 -0
- package/dist/database/migrations/0006_archive.sql +20 -0
- package/dist/database/migrations/meta/0000_snapshot.json +710 -0
- package/dist/database/migrations/meta/0001_snapshot.json +717 -0
- package/dist/database/migrations/meta/0002_snapshot.json +724 -0
- package/dist/database/migrations/meta/0003_snapshot.json +972 -0
- package/dist/database/migrations/meta/0004_snapshot.json +1168 -0
- package/dist/database/migrations/meta/0005_snapshot.json +1168 -0
- package/dist/database/migrations/meta/0006_snapshot.json +1183 -0
- package/dist/database/migrations/meta/_journal.json +55 -0
- package/dist/database/schema.js +170 -0
- package/dist/dev/seed.js +84 -0
- package/dist/domain/activity.js +22 -0
- package/dist/domain/chat.js +21 -0
- package/dist/domain/entities.js +1 -0
- package/dist/domain/errors.js +23 -0
- package/dist/domain/naming.js +33 -0
- package/dist/domain/task.js +140 -0
- package/dist/domain/workflow.js +23 -0
- package/dist/git/cli-git.js +363 -0
- package/dist/git/console.js +37 -0
- package/dist/git/diagnose.js +145 -0
- package/dist/git/merge-evidence.js +23 -0
- package/dist/git/releases.js +95 -0
- package/dist/git/types.js +16 -0
- package/dist/ui/App.js +71 -0
- package/dist/ui/Shell.js +211 -0
- package/dist/ui/app-state.js +213 -0
- package/dist/ui/branding/CompactLogo.js +8 -0
- package/dist/ui/branding/Logo.js +22 -0
- package/dist/ui/branding/Splash.js +21 -0
- package/dist/ui/branding/brand.js +23 -0
- package/dist/ui/chrome/Sidebar.js +50 -0
- package/dist/ui/chrome/StatusBar.js +26 -0
- package/dist/ui/chrome/Toast.js +11 -0
- package/dist/ui/chrome/TopBar.js +10 -0
- package/dist/ui/chrome/context.js +33 -0
- package/dist/ui/components/ConsoleLines.js +16 -0
- package/dist/ui/components/EmptyState.js +8 -0
- package/dist/ui/components/Labels.js +25 -0
- package/dist/ui/components/PullRequestLabel.js +27 -0
- package/dist/ui/components/TaskTable.js +41 -0
- package/dist/ui/components/TextField.js +11 -0
- package/dist/ui/components/TextInput.js +29 -0
- package/dist/ui/components/task-columns.js +27 -0
- package/dist/ui/copy.js +23 -0
- package/dist/ui/hooks/use-commands.js +58 -0
- package/dist/ui/hooks/use-flows.js +254 -0
- package/dist/ui/hooks/use-layout.js +36 -0
- package/dist/ui/hooks/use-list.js +28 -0
- package/dist/ui/hooks/use-measure.js +22 -0
- package/dist/ui/hooks/use-query.js +29 -0
- package/dist/ui/hooks/use-task-actions.js +450 -0
- package/dist/ui/hooks/use-terminal-size.js +19 -0
- package/dist/ui/input/KeyProvider.js +101 -0
- package/dist/ui/input/dispatcher.js +26 -0
- package/dist/ui/input/list-navigation.js +38 -0
- package/dist/ui/input/mouse.js +78 -0
- package/dist/ui/input/text-editing.js +62 -0
- package/dist/ui/kit/Badge.js +8 -0
- package/dist/ui/kit/Button.js +13 -0
- package/dist/ui/kit/Clickable.js +14 -0
- package/dist/ui/kit/Gauge.js +9 -0
- package/dist/ui/kit/Keycap.js +7 -0
- package/dist/ui/kit/Panel.js +30 -0
- package/dist/ui/kit/ScrollBar.js +14 -0
- package/dist/ui/kit/Spinner.js +9 -0
- package/dist/ui/kit/Tabs.js +12 -0
- package/dist/ui/mascot/AgentScene.js +30 -0
- package/dist/ui/mascot/Mascot.js +26 -0
- package/dist/ui/mascot/ansi.js +25 -0
- package/dist/ui/mascot/engine.js +146 -0
- package/dist/ui/mascot/index.js +6 -0
- package/dist/ui/mascot/palette.js +18 -0
- package/dist/ui/mascot/scenes/bugHunt.js +115 -0
- package/dist/ui/mascot/scenes/celebrate.js +76 -0
- package/dist/ui/mascot/scenes/cleaning.js +156 -0
- package/dist/ui/mascot/scenes/clockIn.js +105 -0
- package/dist/ui/mascot/scenes/deadline.js +125 -0
- package/dist/ui/mascot/scenes/empty.js +149 -0
- package/dist/ui/mascot/scenes/fire.js +138 -0
- package/dist/ui/mascot/scenes/index.js +25 -0
- package/dist/ui/mascot/scenes/planning.js +73 -0
- package/dist/ui/mascot/scenes/resting.js +90 -0
- package/dist/ui/mascot/scenes/syncing.js +116 -0
- package/dist/ui/mascot/scenes/working.js +193 -0
- package/dist/ui/mascot/selection.js +32 -0
- package/dist/ui/mascot/states.js +25 -0
- package/dist/ui/navigation/routes.js +17 -0
- package/dist/ui/overlays/CommitOverlay.js +93 -0
- package/dist/ui/overlays/ConfirmOverlay.js +42 -0
- package/dist/ui/overlays/GitLogOverlay.js +22 -0
- package/dist/ui/overlays/GitRunOverlay.js +94 -0
- package/dist/ui/overlays/NewTaskOverlay.js +117 -0
- package/dist/ui/overlays/OverlayFrame.js +35 -0
- package/dist/ui/overlays/PickerOverlay.js +72 -0
- package/dist/ui/overlays/PromptOverlay.js +51 -0
- package/dist/ui/overlays/SearchOverlay.js +49 -0
- package/dist/ui/overlays/options.js +29 -0
- package/dist/ui/overlays/types.js +1 -0
- package/dist/ui/screens/AccessScreen.js +91 -0
- package/dist/ui/screens/ChatScreen.js +450 -0
- package/dist/ui/screens/HelpScreen.js +99 -0
- package/dist/ui/screens/ProjectsScreen.js +66 -0
- package/dist/ui/screens/ScreenFrame.js +14 -0
- package/dist/ui/screens/SetupScreen.js +79 -0
- package/dist/ui/screens/TaskListScreen.js +152 -0
- package/dist/ui/screens/TaskScreen.js +193 -0
- package/dist/ui/screens/WelcomeScreen.js +254 -0
- package/dist/ui/screens/WorkspacesScreen.js +64 -0
- package/dist/ui/theme/detect.js +59 -0
- package/dist/ui/theme/theme.js +135 -0
- package/dist/utils/editor.js +37 -0
- package/dist/utils/errors.js +23 -0
- package/dist/utils/text.js +60 -0
- package/dist/utils/time.js +25 -0
- package/docs/NPM_PUBLISH.md +35 -0
- package/docs/PRIVACY.md +38 -0
- 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
|
+
}
|