loop-task 2.0.5 → 2.0.8

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.
@@ -7,7 +7,7 @@ function quoteArg(arg) {
7
7
  return /[\s"]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
8
8
  }
9
9
  function formatCommandLine(command, commandArgs) {
10
- return [command, ...commandArgs.map((a) => quoteArg(a))].join(" ").trim();
10
+ return [command, ...commandArgs.map((a) => quoteArg(a.replace(/\n/g, " ").replace(/\r/g, " ")))].join(" ").trim();
11
11
  }
12
12
  export function extractExitCode(error) {
13
13
  return error && typeof error === "object" && "exitCode" in error
@@ -25,7 +25,8 @@ export async function executeCommand(command, commandArgs, cwd, logStream, signa
25
25
  logStream.write(t("loop.exitMarker", { code: 1, duration: formatDuration(0) }));
26
26
  return { exitCode: 1, duration: 0, startedAt, endedAt };
27
27
  }
28
- const child = execa(command, commandArgs, {
28
+ const shellCommand = formatCommandLine(command, commandArgs);
29
+ const child = execa(shellCommand, {
29
30
  stdout: "pipe",
30
31
  stderr: "pipe",
31
32
  stdin: "ignore",
@@ -371,7 +371,7 @@ export class LoopController extends EventEmitter {
371
371
  break;
372
372
  if (this.logStream) {
373
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() }));
374
+ this.logStream.write(t("loop.commandLine", { command: [chainTask.command, ...chainTask.commandArgs.map((a) => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a).map((a) => a.replace(/\n/g, " ").replace(/\r/g, " "))].join(" ").trim() }));
375
375
  }
376
376
  const chainStartedAt = new Date().toISOString();
377
377
  const chainOffset = fs.existsSync(this.logPath) ? fs.statSync(this.logPath).size : 0;
package/dist/i18n/en.json CHANGED
@@ -191,8 +191,8 @@
191
191
  "board.logModalRunning": "Running",
192
192
  "board.logModalExit": "exit {code}",
193
193
  "board.logModalDuration": "duration",
194
- "board.logModalSearchHint": "type to filter, enter to apply, esc to close",
195
- "board.logModalFooterHints": "/:search c:copy up/down:scroll esc:close",
194
+ "board.logModalSearchHint": "type to filter, enter to apply, esc to exit search",
195
+ "board.logModalFooterHints": "/:search ctrl+x:copy up/down:scroll esc:close",
196
196
  "board.logModalEscClose": "esc to close",
197
197
  "board.logModalRunning": "running…",
198
198
  "board.detailTitle": " Loop Detail ",
@@ -1,8 +1,7 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { darkTheme as theme } from "../theme.js";
5
- import { Modal } from "./Modal.js";
6
5
  import { t } from "../../i18n/index.js";
7
6
  import { streamRunLog } from "../daemon.js";
8
7
  import { formatDate } from "../format.js";
@@ -69,7 +68,7 @@ export function LogModal(props) {
69
68
  return;
70
69
  }
71
70
  if (key.escape) {
72
- // Escape handled by App's global popLayer(); no-op here to avoid double-fire
71
+ props.onClose();
73
72
  return;
74
73
  }
75
74
  if (input === "/") {
@@ -77,7 +76,7 @@ export function LogModal(props) {
77
76
  setSearchQuery("");
78
77
  return;
79
78
  }
80
- if (input === "c" && !key.ctrl) {
79
+ if (key.ctrl && input === "x") {
81
80
  copyToClipboard(lines.join("\n"));
82
81
  props.onCopy?.();
83
82
  return;
@@ -104,20 +103,18 @@ export function LogModal(props) {
104
103
  : props.run.exitCode === 0
105
104
  ? theme.semantic.success
106
105
  : theme.semantic.danger;
107
- return (_jsxs(Modal, { title: `Run #${props.run.runNumber} - ${formatDate(props.run.startedAt)}`, onClose: props.onClose, width: "95%", height: "70%", children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: statusColor, children: [statusIcon, " ", props.run.status === "running"
108
- ? t("board.logModalRunning")
109
- : t("board.logModalExit", { code: props.run.exitCode })] }), props.run.duration > 0 ? (_jsxs(Text, { color: theme.text.muted, children: [" ", t("board.logModalDuration"), " ", props.run.duration, "ms"] })) : null, _jsx(Text, { color: theme.text.muted, children: searchMode
110
- ? `/${searchQuery}`
111
- : follow
112
- ? "f follow"
113
- : `[${startIdx}-${endIdx}/${totalLines}]` })] }), searchMode ? (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.text.muted, children: "Search: " }), _jsxs(Text, { color: theme.text.primary, children: [searchQuery, "_"] })] })) : null, _jsx(Box, { flexDirection: "column", children: visible.length === 0 ? (_jsx(Text, { color: theme.text.muted, children: isLoading
114
- ? t("board.logModalLoading")
115
- : searchQuery
116
- ? t("board.logModalNoMatches")
117
- : t("board.logModalEmpty") })) : (visible.map((line, i) => {
118
- const realIdx = startIdx + i;
119
- return (_jsx(Text, { color: colorForLine(line, props.run), wrap: "truncate", children: line }, realIdx));
120
- })) }), _jsx(Box, { marginTop: 1, justifyContent: "space-between", children: _jsx(Text, { color: theme.text.muted, children: searchMode
121
- ? t("board.logModalSearchHint")
122
- : t("board.logModalFooterHints") }) })] }));
106
+ return (_jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: _jsxs(Box, { borderStyle: "round", borderColor: theme.accent.brand, backgroundColor: theme.bg.elevated, paddingX: 2, paddingY: 1, flexDirection: "column", width: "95%", height: "70%", children: [_jsx(Text, { color: theme.accent.brand, bold: true, children: `Run #${props.run.runNumber} - ${formatDate(props.run.startedAt)}` }), _jsxs(Box, { flexDirection: "column", marginTop: 1, flexGrow: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: statusColor, children: [statusIcon, " ", props.run.status === "running"
107
+ ? t("board.logModalRunning")
108
+ : t("board.logModalExit", { code: props.run.exitCode })] }), props.run.duration > 0 ? (_jsxs(Text, { color: theme.text.muted, children: [" ", t("board.logModalDuration"), " ", props.run.duration, "ms"] })) : null, _jsx(Text, { color: theme.text.muted, children: searchMode
109
+ ? `/${searchQuery}`
110
+ : `[${startIdx}-${endIdx}/${totalLines}]` })] }), searchMode ? (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.text.muted, children: "Search: " }), _jsxs(Text, { color: theme.text.primary, children: [searchQuery, "_"] })] })) : null, _jsx(Box, { flexDirection: "column", flexGrow: 1, children: visible.length === 0 ? (_jsx(Text, { color: theme.text.muted, children: isLoading
111
+ ? t("board.logModalLoading")
112
+ : searchQuery
113
+ ? t("board.logModalNoMatches")
114
+ : t("board.logModalEmpty") })) : (visible.map((line, i) => {
115
+ const realIdx = startIdx + i;
116
+ return (_jsx(Text, { color: colorForLine(line, props.run), wrap: "truncate", children: line }, realIdx));
117
+ })) }), _jsx(Box, { marginTop: 1, justifyContent: "space-between", children: _jsx(Text, { color: theme.text.muted, children: searchMode
118
+ ? t("board.logModalSearchHint")
119
+ : t("board.logModalFooterHints") }) })] })] }) }));
123
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loop-task",
3
- "version": "2.0.5",
3
+ "version": "2.0.8",
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": {