loop-task 2.0.4 → 2.0.5
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.
|
@@ -3,6 +3,12 @@ import { execa } from "execa";
|
|
|
3
3
|
import { formatDuration } from "../duration.js";
|
|
4
4
|
import { t } from "../i18n/index.js";
|
|
5
5
|
import { MAX_CONTEXT_STDOUT_BYTES } from "../config/constants.js";
|
|
6
|
+
function quoteArg(arg) {
|
|
7
|
+
return /[\s"]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
8
|
+
}
|
|
9
|
+
function formatCommandLine(command, commandArgs) {
|
|
10
|
+
return [command, ...commandArgs.map((a) => quoteArg(a))].join(" ").trim();
|
|
11
|
+
}
|
|
6
12
|
export function extractExitCode(error) {
|
|
7
13
|
return error && typeof error === "object" && "exitCode" in error
|
|
8
14
|
? error.exitCode
|
|
@@ -12,6 +18,7 @@ export async function executeCommand(command, commandArgs, cwd, logStream, signa
|
|
|
12
18
|
const startedAt = new Date();
|
|
13
19
|
const header = t("loop.runHeader", { timestamp: startedAt.toLocaleString(), runNumber: runNumber ?? 0 });
|
|
14
20
|
logStream.write(header);
|
|
21
|
+
logStream.write(t("loop.commandLine", { command: formatCommandLine(command, commandArgs) }));
|
|
15
22
|
if (cwd && !fs.existsSync(cwd)) {
|
|
16
23
|
const endedAt = new Date();
|
|
17
24
|
logStream.write(t("loop.cwdMissingLog", { cwd }));
|
|
@@ -363,12 +363,15 @@ export class LoopController extends EventEmitter {
|
|
|
363
363
|
if (mainRecord)
|
|
364
364
|
mainRecord.chainGroupId = chainGroupId;
|
|
365
365
|
let currentTargetId = chainTargetId;
|
|
366
|
+
let prevBranch = result.exitCode === 0 ? "onSuccess" : "onFailure";
|
|
367
|
+
let prevExit = result.exitCode;
|
|
366
368
|
while (currentTargetId) {
|
|
367
369
|
const chainTask = this.taskResolver(currentTargetId);
|
|
368
370
|
if (!chainTask)
|
|
369
371
|
break;
|
|
370
372
|
if (this.logStream) {
|
|
371
|
-
this.logStream.write(t("loop.chainHeader", { name: chainTask.name }));
|
|
373
|
+
this.logStream.write(t("loop.chainHeader", { name: chainTask.name, branch: prevBranch, prevExit }));
|
|
374
|
+
this.logStream.write(t("loop.commandLine", { command: [chainTask.command, ...chainTask.commandArgs.map((a) => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a)].join(" ").trim() }));
|
|
372
375
|
}
|
|
373
376
|
const chainStartedAt = new Date().toISOString();
|
|
374
377
|
const chainOffset = fs.existsSync(this.logPath) ? fs.statSync(this.logPath).size : 0;
|
|
@@ -403,6 +406,8 @@ export class LoopController extends EventEmitter {
|
|
|
403
406
|
this.lastExitCode = chainResult.exitCode;
|
|
404
407
|
this.lastDuration = (this.lastDuration ?? 0) + chainResult.duration;
|
|
405
408
|
currentTargetId = (chainResult.exitCode === 0 ? chainTask.onSuccessTaskId : chainTask.onFailureTaskId) ?? null;
|
|
409
|
+
prevBranch = chainResult.exitCode === 0 ? "onSuccess" : "onFailure";
|
|
410
|
+
prevExit = chainResult.exitCode;
|
|
406
411
|
}
|
|
407
412
|
}
|
|
408
413
|
if (this.runHistory.length > 50) {
|
package/dist/i18n/en.json
CHANGED
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
"cli.projectAmbiguous": "Multiple projects named \"{name}\" - use the id instead",
|
|
66
66
|
"cli.projectInvalidColor": "Invalid color \"{color}\". Valid names: {valid}, or a #rrggbb hex",
|
|
67
67
|
"loop.runHeader": "\n[Run #{runNumber} - {timestamp}]\n",
|
|
68
|
-
"loop.
|
|
68
|
+
"loop.commandLine": "$ {command}\n",
|
|
69
|
+
"loop.chainHeader": "\n--- Chain: {name} ({branch}, prev exit {prevExit}) ---\n",
|
|
69
70
|
"loop.exitMarker": "[exit {code} · {duration}]\n",
|
|
70
71
|
"loop.cwdMissingLog": "[error] working directory does not exist: {cwd}\n",
|
|
71
72
|
"loop.cwdMissing": "Working directory does not exist: {cwd}",
|
|
@@ -69,9 +69,12 @@ export function CodeEditorModal(props) {
|
|
|
69
69
|
redo();
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
|
-
// Ctrl+
|
|
73
|
-
if (key.ctrl && input === "
|
|
72
|
+
// Ctrl+X → cut (copy all to clipboard, then clear)
|
|
73
|
+
if (key.ctrl && input === "x") {
|
|
74
74
|
copyToClipboard(value);
|
|
75
|
+
setValue("");
|
|
76
|
+
setCursorRow(0);
|
|
77
|
+
setCursorCol(0);
|
|
75
78
|
setFlashMsg(t("codeEditor.copied"));
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
@@ -90,27 +93,8 @@ export function CodeEditorModal(props) {
|
|
|
90
93
|
}
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
93
|
-
//
|
|
94
|
-
if (key.
|
|
95
|
-
copyToClipboard(value);
|
|
96
|
-
setFlashMsg(t("codeEditor.copied"));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (key.shift && !key.ctrl && !key.meta && input === "V") {
|
|
100
|
-
const clip = readFromClipboard();
|
|
101
|
-
if (clip) {
|
|
102
|
-
const pasted = sanitizePaste(clip);
|
|
103
|
-
if (pasted) {
|
|
104
|
-
const next = [...lines];
|
|
105
|
-
const line = next[cursorRow] ?? "";
|
|
106
|
-
next[cursorRow] =
|
|
107
|
-
line.slice(0, cursorCol) + pasted + line.slice(cursorCol);
|
|
108
|
-
applyMutation(next, cursorRow, cursorCol + pasted.length);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (key.shift && !key.ctrl && !key.meta && input === "X") {
|
|
96
|
+
// Ctrl+L → clear all
|
|
97
|
+
if (key.ctrl && input === "l") {
|
|
114
98
|
setValue("");
|
|
115
99
|
setCursorRow(0);
|
|
116
100
|
setCursorCol(0);
|
|
@@ -241,5 +225,5 @@ export function CodeEditorModal(props) {
|
|
|
241
225
|
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [lineNum, " "] }), _jsx(Text, { children: " " })] }, rowIdx));
|
|
242
226
|
}
|
|
243
227
|
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [lineNum, " "] }), segments.map((seg, ti) => (_jsx(Text, { color: seg.color, children: seg.value }, ti)))] }, rowIdx));
|
|
244
|
-
}) }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [t("codeEditor.previewLabel"), " "] }), _jsx(Text, { color: theme.text.secondary, children: truncatedPreview || t("codeEditor.emptyPlaceholder") })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: theme.accent.brand, children: t("codeEditor.buttonCopy") }), _jsx(Text, { color: theme.text.muted, children: "
|
|
228
|
+
}) }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [t("codeEditor.previewLabel"), " "] }), _jsx(Text, { color: theme.text.secondary, children: truncatedPreview || t("codeEditor.emptyPlaceholder") })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: theme.accent.brand, children: t("codeEditor.buttonCopy") }), _jsx(Text, { color: theme.text.muted, children: "ctrl+x" }), _jsx(Text, { color: theme.accent.brand, children: t("codeEditor.buttonPaste") }), _jsx(Text, { color: theme.text.muted, children: "ctrl+v" }), _jsx(Text, { color: theme.accent.brand, children: t("codeEditor.buttonClear") }), _jsx(Text, { color: theme.text.muted, children: "ctrl+l" })] }), flashMsg ? (_jsx(Text, { color: theme.semantic.success, children: flashMsg })) : null] }), _jsx(Box, { children: _jsx(Text, { color: theme.text.muted, children: t("codeEditor.hint") }) })] }) }));
|
|
245
229
|
}
|
|
@@ -11,6 +11,8 @@ const MAX_VISIBLE_LINES = 20;
|
|
|
11
11
|
function colorForLine(line, run) {
|
|
12
12
|
if (line.includes("[Run #"))
|
|
13
13
|
return theme.accent.loop;
|
|
14
|
+
if (line.startsWith("$ "))
|
|
15
|
+
return "#f0abfc";
|
|
14
16
|
if (line.includes("--- Chain:"))
|
|
15
17
|
return theme.accent.task;
|
|
16
18
|
if (line.trimStart().startsWith("[exit")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loop-task",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Loop engineering toolkit. Run any command on a cadence, in the background, managed from a terminal board. Schedule tests, builds, syncs, or agent prompts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|