loop-task 1.1.0 → 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/README.md +250 -113
- package/dist/board/App.js +340 -0
- package/dist/board/board-log.js +18 -0
- package/dist/board/components/ActionButtons.js +45 -0
- package/dist/board/components/BoardButton.js +58 -0
- package/dist/board/components/ConfirmModal.js +36 -0
- package/dist/board/components/CreateForm.js +309 -0
- package/dist/board/components/CreateProjectModal.js +104 -0
- package/dist/board/components/DeleteProjectConfirm.js +53 -0
- package/dist/board/components/DetailView.js +25 -0
- package/dist/board/components/EditProjectModal.js +106 -0
- package/dist/board/components/FilterBar.js +16 -0
- package/dist/board/components/Footer.js +52 -0
- package/dist/board/components/Header.js +46 -0
- package/dist/board/components/HelpModal.js +36 -0
- package/dist/board/components/Inspector.js +12 -0
- package/dist/board/components/LogModal.js +58 -0
- package/dist/board/components/Navigator.js +84 -0
- package/dist/board/components/ProjectsModal.js +70 -0
- package/dist/board/components/ProjectsPage.js +105 -0
- package/dist/board/components/RunHistory.js +83 -0
- package/dist/board/components/TaskBrowser.js +95 -0
- package/dist/board/components/TaskFilterBar.js +6 -0
- package/dist/board/components/TaskForm.js +177 -0
- package/dist/board/components/Timeline.js +5 -0
- package/dist/board/daemon.js +117 -0
- package/dist/board/format.js +102 -0
- package/dist/board/hooks/useBoardKeybindings.js +299 -0
- package/dist/board/hooks/useBreakpoint.js +6 -0
- package/dist/board/hooks/useHoverState.js +11 -0
- package/dist/board/hooks/useLogStream.js +32 -0
- package/dist/board/hooks/useLoopPolling.js +28 -0
- package/dist/board/hooks/useTaskKeybindings.js +114 -0
- package/dist/board/index.js +32 -0
- package/dist/board/router.js +16 -0
- package/dist/board/state.js +99 -0
- package/dist/board/toast.js +65 -0
- package/dist/board/types.js +1 -0
- package/dist/cli.js +134 -93
- package/dist/cli.js.map +1 -1
- package/dist/client/commands.d.ts +11 -0
- package/dist/client/commands.d.ts.map +1 -0
- package/dist/client/commands.js +317 -0
- package/dist/client/commands.js.map +1 -0
- package/dist/client/ipc.d.ts +5 -0
- package/dist/client/ipc.d.ts.map +1 -0
- package/dist/client/ipc.js +96 -0
- package/dist/client/ipc.js.map +1 -0
- package/dist/config/constants.js +29 -0
- package/dist/config/paths.js +49 -0
- package/dist/core/command-runner.js +82 -0
- package/dist/core/foreground-loop.js +81 -0
- package/dist/core/log-parser.js +29 -0
- package/dist/core/log-rotator.js +25 -0
- package/dist/core/loop-controller.js +429 -0
- package/dist/daemon/daemon-log.js +11 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +43 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/manager.d.ts +17 -0
- package/dist/daemon/manager.d.ts.map +1 -0
- package/dist/daemon/manager.js +260 -0
- package/dist/daemon/manager.js.map +1 -0
- package/dist/daemon/projects.js +104 -0
- package/dist/daemon/server.d.ts +15 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +345 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/spawner.d.ts +3 -0
- package/dist/daemon/spawner.d.ts.map +1 -0
- package/dist/daemon/spawner.js +82 -0
- package/dist/daemon/spawner.js.map +1 -0
- package/dist/daemon/state.d.ts +15 -0
- package/dist/daemon/state.d.ts.map +1 -0
- package/dist/daemon/state.js +151 -0
- package/dist/daemon/state.js.map +1 -0
- package/dist/daemon/task-manager.js +59 -0
- package/dist/duration.js +4 -4
- package/dist/entry.js +11 -0
- package/dist/esm-loader.js +36 -0
- package/dist/i18n/en.json +386 -0
- package/dist/i18n/index.js +11 -0
- package/dist/ipc/handlers/logs-stream.js +65 -0
- package/dist/ipc/send.js +5 -0
- package/dist/logger.js +0 -1
- package/dist/loop-config.d.ts +13 -0
- package/dist/loop-config.d.ts.map +1 -0
- package/dist/loop-config.js +83 -0
- package/dist/loop-config.js.map +1 -0
- package/dist/loop.d.ts +43 -2
- package/dist/loop.d.ts.map +1 -1
- package/dist/loop.js +247 -2
- package/dist/loop.js.map +1 -1
- package/dist/open-board.d.ts +2 -0
- package/dist/open-board.d.ts.map +1 -0
- package/dist/open-board.js +19 -0
- package/dist/open-board.js.map +1 -0
- package/dist/shared/clipboard.js +19 -0
- package/dist/shared/fs-utils.js +11 -0
- package/dist/shared/sleep.js +17 -0
- package/dist/shared/tail.js +4 -0
- package/dist/tui/app.d.ts +57 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +167 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/create-loop.d.ts +3 -0
- package/dist/tui/create-loop.d.ts.map +1 -0
- package/dist/tui/create-loop.js +90 -0
- package/dist/tui/create-loop.js.map +1 -0
- package/dist/tui/dashboard.d.ts +2 -0
- package/dist/tui/dashboard.d.ts.map +1 -0
- package/dist/tui/dashboard.js +45 -0
- package/dist/tui/dashboard.js.map +1 -0
- package/dist/tui/events.d.ts +3 -0
- package/dist/tui/events.d.ts.map +1 -0
- package/dist/tui/events.js +244 -0
- package/dist/tui/events.js.map +1 -0
- package/dist/tui/popup.d.ts +3 -0
- package/dist/tui/popup.d.ts.map +1 -0
- package/dist/tui/popup.js +58 -0
- package/dist/tui/popup.js.map +1 -0
- package/dist/tui/render.d.ts +9 -0
- package/dist/tui/render.d.ts.map +1 -0
- package/dist/tui/render.js +305 -0
- package/dist/tui/render.js.map +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/package.json +64 -55
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { TOAST_MAX, TOAST_TIMEOUT_MS } from "../config/constants.js";
|
|
4
|
+
export function useToasts() {
|
|
5
|
+
const [toasts, setToasts] = useState([]);
|
|
6
|
+
const idRef = useRef(0);
|
|
7
|
+
const timers = useRef(new Map());
|
|
8
|
+
const dismiss = useCallback((id) => {
|
|
9
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
10
|
+
const timer = timers.current.get(id);
|
|
11
|
+
if (timer) {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
timers.current.delete(id);
|
|
14
|
+
}
|
|
15
|
+
}, []);
|
|
16
|
+
const push = useCallback((kind, message) => {
|
|
17
|
+
const id = (idRef.current += 1);
|
|
18
|
+
setToasts((prev) => [...prev, { id, kind, message }].slice(-TOAST_MAX));
|
|
19
|
+
timers.current.set(id, setTimeout(() => dismiss(id), TOAST_TIMEOUT_MS));
|
|
20
|
+
}, [dismiss]);
|
|
21
|
+
return { toasts, push, dismiss };
|
|
22
|
+
}
|
|
23
|
+
function toastColor(kind) {
|
|
24
|
+
switch (kind) {
|
|
25
|
+
case "success":
|
|
26
|
+
return "#4ade80";
|
|
27
|
+
case "error":
|
|
28
|
+
return "#f87171";
|
|
29
|
+
default:
|
|
30
|
+
return "#38bdf8";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function toastIcon(kind) {
|
|
34
|
+
switch (kind) {
|
|
35
|
+
case "success":
|
|
36
|
+
return "✓";
|
|
37
|
+
case "error":
|
|
38
|
+
return "✗";
|
|
39
|
+
default:
|
|
40
|
+
return "ℹ";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function ToastStack(props) {
|
|
44
|
+
if (props.toasts.length === 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return (_jsx("box", { style: {
|
|
48
|
+
position: "absolute",
|
|
49
|
+
bottom: 1,
|
|
50
|
+
right: 2,
|
|
51
|
+
flexDirection: "column",
|
|
52
|
+
alignItems: "flex-end",
|
|
53
|
+
zIndex: 50,
|
|
54
|
+
}, children: props.toasts.map((toast) => {
|
|
55
|
+
const color = toastColor(toast.kind);
|
|
56
|
+
return (_jsx("box", { border: true, style: {
|
|
57
|
+
borderColor: color,
|
|
58
|
+
backgroundColor: "#0b1220",
|
|
59
|
+
paddingLeft: 1,
|
|
60
|
+
paddingRight: 1,
|
|
61
|
+
marginTop: 1,
|
|
62
|
+
minWidth: 24,
|
|
63
|
+
}, children: _jsxs("text", { children: [_jsxs("span", { fg: color, children: [toastIcon(toast.kind), " "] }), _jsx("span", { fg: "#e5e7eb", children: toast.message })] }) }, toast.id));
|
|
64
|
+
}) }));
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,99 +1,140 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import { Logger } from "./logger.js";
|
|
4
|
-
import { runLoop } from "./loop.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
import { runLoop } from "./core/foreground-loop.js";
|
|
6
|
+
import { buildLoopOptions } from "./loop-config.js";
|
|
7
|
+
import { startLoop } from "./client/commands.js";
|
|
8
|
+
import { listProjectsCli, createProjectCli, renameProjectCli, setProjectColorCli, deleteProjectCli, resolveProjectId, } from "./client/commands.js";
|
|
9
|
+
import { t } from "./i18n/index.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const packageJson = require("../package.json");
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program
|
|
14
|
+
.name("loop-task")
|
|
15
|
+
.description(t("cli.programDescription"))
|
|
16
|
+
.version(packageJson.version, "-V, --version");
|
|
17
|
+
program
|
|
18
|
+
.command("start")
|
|
19
|
+
.description(t("cli.startDescription"))
|
|
20
|
+
.action(async () => {
|
|
21
|
+
const { ensureDaemon } = await import("./daemon/spawner.js");
|
|
22
|
+
ensureDaemon();
|
|
23
|
+
console.log(t("cli.daemonStarted"));
|
|
24
|
+
});
|
|
25
|
+
program
|
|
26
|
+
.command("new")
|
|
27
|
+
.description(t("cli.newDescription"))
|
|
28
|
+
.argument("<interval>", t("cli.argInterval"))
|
|
29
|
+
.argument("<command...>", t("cli.argCommand"))
|
|
30
|
+
.option("--now", t("cli.optNow"), false)
|
|
31
|
+
.option("--max-runs <n>", t("cli.optMaxRuns"), parseInt)
|
|
32
|
+
.option("--verbose", t("cli.optVerbose"), false)
|
|
33
|
+
.option("--cwd <dir>", t("cli.optCwd"))
|
|
34
|
+
.option("--project <name>", t("cli.optProject"))
|
|
35
|
+
.action(async (intervalStr, cmdArgs, opts) => {
|
|
36
|
+
try {
|
|
37
|
+
const projectId = opts.project
|
|
38
|
+
? await resolveProjectId(opts.project)
|
|
39
|
+
: undefined;
|
|
40
|
+
const built = buildLoopOptions(intervalStr, {
|
|
41
|
+
...opts,
|
|
42
|
+
command: cmdArgs[0],
|
|
43
|
+
commandArgs: cmdArgs.slice(1),
|
|
44
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
45
|
+
projectId,
|
|
46
|
+
});
|
|
47
|
+
await startLoop(built.options, built.intervalHuman);
|
|
18
48
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.error("Error: --max-runs must be a positive integer");
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
loopOptions.maxRuns = n;
|
|
27
|
-
i++;
|
|
28
|
-
}
|
|
29
|
-
else if (arg === "--verbose") {
|
|
30
|
-
loopOptions.verbose = true;
|
|
31
|
-
i++;
|
|
32
|
-
}
|
|
33
|
-
else if (arg === "--help" || arg === "-h") {
|
|
34
|
-
console.log(`
|
|
35
|
-
Usage:
|
|
36
|
-
loop-task [options] <interval> <command>
|
|
37
|
-
|
|
38
|
-
Examples:
|
|
39
|
-
loop-task 30m npm test
|
|
40
|
-
loop-task 1h opencode --prompt '/ob-init'
|
|
41
|
-
loop-task 30s --now -- echo hello
|
|
42
|
-
loop-task --max-runs 5 5m npm test
|
|
43
|
-
|
|
44
|
-
Options:
|
|
45
|
-
--now Run immediately before waiting
|
|
46
|
-
--max-runs <n> Stop after N executions
|
|
47
|
-
--verbose Show execution details
|
|
48
|
-
-h, --help Display help
|
|
49
|
-
-V, --version Display version
|
|
50
|
-
`);
|
|
51
|
-
process.exit(0);
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
console.error(t("cli.error", { message }));
|
|
52
|
+
process.exit(1);
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
});
|
|
55
|
+
program
|
|
56
|
+
.command("run")
|
|
57
|
+
.description(t("cli.runDescription"))
|
|
58
|
+
.argument("<interval>", t("cli.argInterval"))
|
|
59
|
+
.argument("<command...>", t("cli.argCommand"))
|
|
60
|
+
.option("--now", t("cli.optNow"))
|
|
61
|
+
.option("--max-runs <n>", t("cli.optMaxRuns"))
|
|
62
|
+
.option("--verbose", t("cli.optVerbose"))
|
|
63
|
+
.option("--cwd <dir>", t("cli.optCwd"))
|
|
64
|
+
.action(async (intervalStr, cmdArgs, opts) => {
|
|
65
|
+
if (!intervalStr || !cmdArgs || cmdArgs.length === 0) {
|
|
66
|
+
program.help();
|
|
67
|
+
return;
|
|
56
68
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
const logger = new Logger(opts.verbose ?? false);
|
|
70
|
+
const built = buildLoopOptions(intervalStr, {
|
|
71
|
+
...opts,
|
|
72
|
+
command: cmdArgs[0],
|
|
73
|
+
commandArgs: cmdArgs.slice(1),
|
|
74
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
75
|
+
});
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
process.on("SIGINT", () => controller.abort());
|
|
78
|
+
process.on("SIGTERM", () => controller.abort());
|
|
79
|
+
await runLoop(built.options, logger, controller.signal);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
});
|
|
82
|
+
const projectCmd = program.command("project").description(t("cli.projectDescription"));
|
|
83
|
+
projectCmd
|
|
84
|
+
.command("list")
|
|
85
|
+
.description(t("cli.projectListDescription"))
|
|
86
|
+
.action(async () => {
|
|
87
|
+
await listProjectsCli();
|
|
88
|
+
});
|
|
89
|
+
projectCmd
|
|
90
|
+
.command("new")
|
|
91
|
+
.description(t("cli.projectNewDescription"))
|
|
92
|
+
.argument("<name>", t("cli.projectArgName"))
|
|
93
|
+
.option("--color <color>", t("cli.optProjectColor"))
|
|
94
|
+
.action(async (name, opts) => {
|
|
95
|
+
await createProjectCli(name, opts.color);
|
|
96
|
+
});
|
|
97
|
+
projectCmd
|
|
98
|
+
.command("rename")
|
|
99
|
+
.description(t("cli.projectRenameDescription"))
|
|
100
|
+
.argument("<id-or-name>", t("cli.projectArgIdOrName"))
|
|
101
|
+
.argument("<new-name>", t("cli.projectArgNewName"))
|
|
102
|
+
.action(async (idOrName, newName) => {
|
|
103
|
+
await renameProjectCli(idOrName, newName);
|
|
104
|
+
});
|
|
105
|
+
projectCmd
|
|
106
|
+
.command("color")
|
|
107
|
+
.description(t("cli.projectColorDescription"))
|
|
108
|
+
.argument("<id-or-name>", t("cli.projectArgIdOrName"))
|
|
109
|
+
.argument("<color>", t("cli.projectArgColor"))
|
|
110
|
+
.action(async (idOrName, color) => {
|
|
111
|
+
await setProjectColorCli(idOrName, color);
|
|
112
|
+
});
|
|
113
|
+
projectCmd
|
|
114
|
+
.command("delete")
|
|
115
|
+
.description(t("cli.projectDeleteDescription"))
|
|
116
|
+
.argument("<id-or-name>", t("cli.projectArgIdOrName"))
|
|
117
|
+
.action(async (idOrName) => {
|
|
118
|
+
await deleteProjectCli(idOrName);
|
|
119
|
+
});
|
|
120
|
+
program.action(async () => {
|
|
121
|
+
if (!process.execPath.includes("bun")) {
|
|
122
|
+
const { spawn } = await import("node:child_process");
|
|
123
|
+
const child = spawn("bun", [process.argv[1]], {
|
|
124
|
+
stdio: "inherit",
|
|
125
|
+
env: { ...process.env },
|
|
126
|
+
shell: true,
|
|
127
|
+
});
|
|
128
|
+
child.on("error", () => {
|
|
129
|
+
console.error("The board requires the Bun runtime for OpenTUI native FFI.\n" +
|
|
130
|
+
"Install Bun: npm install -g bun\n" +
|
|
131
|
+
"Then run: loop-task");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
135
|
+
return;
|
|
65
136
|
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
const intervalStr = commandParts[0];
|
|
73
|
-
const cmdArgs = commandParts.slice(1);
|
|
74
|
-
const command = cmdArgs[0];
|
|
75
|
-
const commandArgs = cmdArgs.slice(1);
|
|
76
|
-
const logger = new Logger(loopOptions.verbose);
|
|
77
|
-
let interval;
|
|
78
|
-
try {
|
|
79
|
-
interval = parseDuration(intervalStr);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
-
logger.error(`Error: ${message}`);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
const options = {
|
|
87
|
-
interval,
|
|
88
|
-
command,
|
|
89
|
-
commandArgs,
|
|
90
|
-
immediate: loopOptions.immediate,
|
|
91
|
-
maxRuns: loopOptions.maxRuns,
|
|
92
|
-
verbose: loopOptions.verbose,
|
|
93
|
-
};
|
|
94
|
-
const controller = new AbortController();
|
|
95
|
-
process.on("SIGINT", () => controller.abort());
|
|
96
|
-
process.on("SIGTERM", () => controller.abort());
|
|
97
|
-
await runLoop(options, logger, controller.signal);
|
|
98
|
-
process.exit(0);
|
|
99
|
-
//# sourceMappingURL=cli.js.map
|
|
137
|
+
const { launchBoard } = await import("./board/index.js");
|
|
138
|
+
await launchBoard();
|
|
139
|
+
});
|
|
140
|
+
await program.parseAsync(process.argv);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LoopOptions } from "../types.js";
|
|
2
|
+
export declare function createBackgroundLoop(options: LoopOptions, intervalHuman: string): Promise<string>;
|
|
3
|
+
export declare function startLoop(options: LoopOptions, intervalHuman: string): Promise<void>;
|
|
4
|
+
export declare function listLoops(): Promise<void>;
|
|
5
|
+
export declare function showStatus(id: string): Promise<void>;
|
|
6
|
+
export declare function pauseLoop(id: string): Promise<void>;
|
|
7
|
+
export declare function resumeLoop(id: string): Promise<void>;
|
|
8
|
+
export declare function deleteLoop(id: string): Promise<void>;
|
|
9
|
+
export declare function viewLogs(id: string, follow: boolean, tail: number): Promise<void>;
|
|
10
|
+
export declare function attachLoop(id: string): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/client/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,aAAa,CAAC;AAIzD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CA4C/C;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B1D;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1D;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1D;AAED,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB1D"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { formatDuration } from "../duration.js";
|
|
2
|
+
import { PROJECT_COLORS } from "../config/constants.js";
|
|
3
|
+
import { t } from "../i18n/index.js";
|
|
4
|
+
import { sendRequest, streamRequest } from "./ipc.js";
|
|
5
|
+
export async function createBackgroundLoop(options, intervalHuman) {
|
|
6
|
+
const response = await sendRequest({
|
|
7
|
+
type: "start",
|
|
8
|
+
payload: { ...options, intervalHuman },
|
|
9
|
+
});
|
|
10
|
+
if (response.type !== "ok") {
|
|
11
|
+
throw new Error(response.message);
|
|
12
|
+
}
|
|
13
|
+
return response.data.id;
|
|
14
|
+
}
|
|
15
|
+
export async function startLoop(options, intervalHuman) {
|
|
16
|
+
const id = await createBackgroundLoop(options, intervalHuman).catch((error) => {
|
|
17
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18
|
+
console.error(t("cli.error", { message }));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
console.log(t("cli.startedTitle"));
|
|
22
|
+
console.log(t("cli.startedId", { id }));
|
|
23
|
+
console.log(t("cli.startedCommand", { command: options.command }));
|
|
24
|
+
console.log(t("cli.startedInterval", { interval: intervalHuman }));
|
|
25
|
+
console.log(t("cli.startedStatus"));
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(t("cli.startedHint"));
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
export async function listLoops() {
|
|
31
|
+
const response = await sendRequest({ type: "list" });
|
|
32
|
+
if (response.type !== "ok") {
|
|
33
|
+
console.error(t("cli.error", { message: response.message }));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const loops = response.data;
|
|
37
|
+
if (loops.length === 0) {
|
|
38
|
+
console.log(t("cli.noLoops"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const idW = Math.max(4, ...loops.map((l) => l.id.length)) + 2;
|
|
42
|
+
const cmdW = Math.max(7, ...loops.map((l) => formatCmd(l.command, l.commandArgs).length)) + 2;
|
|
43
|
+
const intW = 10;
|
|
44
|
+
const statusW = 10;
|
|
45
|
+
const runsW = 6;
|
|
46
|
+
const header = pad(t("cli.headerId"), idW) +
|
|
47
|
+
pad(t("cli.headerCommand"), cmdW) +
|
|
48
|
+
pad(t("cli.headerInterval"), intW) +
|
|
49
|
+
pad(t("cli.headerStatus"), statusW) +
|
|
50
|
+
pad(t("cli.headerRuns"), runsW) +
|
|
51
|
+
t("cli.headerLastExit");
|
|
52
|
+
console.log(header);
|
|
53
|
+
console.log("-".repeat(header.length));
|
|
54
|
+
for (const loop of loops) {
|
|
55
|
+
const exit = loop.lastExitCode !== null ? String(loop.lastExitCode) : "-";
|
|
56
|
+
console.log(pad(loop.id, idW) +
|
|
57
|
+
pad(formatCmd(loop.command, loop.commandArgs), cmdW) +
|
|
58
|
+
pad(loop.intervalHuman, intW) +
|
|
59
|
+
pad(loop.status, statusW) +
|
|
60
|
+
pad(String(loop.runCount), runsW) +
|
|
61
|
+
exit);
|
|
62
|
+
}
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
export async function showStatus(id) {
|
|
66
|
+
const response = await sendRequest({ type: "status", payload: { id } });
|
|
67
|
+
if (response.type !== "ok") {
|
|
68
|
+
console.error(t("cli.error", { message: response.message }));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const loop = response.data;
|
|
72
|
+
const cmd = `${loop.command} ${loop.commandArgs.join(" ")}`.trim();
|
|
73
|
+
const maxRuns = loop.maxRuns !== null ? String(loop.maxRuns) : t("cli.unlimited");
|
|
74
|
+
console.log(t("cli.statusTitle", { id: loop.id }));
|
|
75
|
+
console.log(t("cli.statusCommand", { command: cmd }));
|
|
76
|
+
console.log(t("cli.statusInterval", { interval: loop.intervalHuman, duration: formatDuration(loop.interval) }));
|
|
77
|
+
console.log(t("cli.statusStatus", { status: loop.status }));
|
|
78
|
+
console.log(t("cli.statusRuns", { runs: loop.runCount, maxRuns }));
|
|
79
|
+
console.log(t("cli.statusCreated", { created: loop.createdAt }));
|
|
80
|
+
if (loop.lastRunAt) {
|
|
81
|
+
const exitInfo = loop.lastExitCode !== null ? t("cli.exitInfo", { code: loop.lastExitCode }) : "";
|
|
82
|
+
const durInfo = loop.lastDuration !== null ? formatDuration(loop.lastDuration) : "";
|
|
83
|
+
console.log(t("cli.statusLastRun", { lastRun: loop.lastRunAt, exit: exitInfo, duration: durInfo }).trim());
|
|
84
|
+
}
|
|
85
|
+
if (loop.nextRunAt) {
|
|
86
|
+
console.log(t("cli.statusNextRun", { nextRun: loop.nextRunAt }));
|
|
87
|
+
}
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
export async function pauseLoop(id) {
|
|
91
|
+
const response = await sendRequest({ type: "pause", payload: { id } });
|
|
92
|
+
if (response.type !== "ok") {
|
|
93
|
+
console.error(t("cli.error", { message: response.message }));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
console.log(t("cli.paused", { id }));
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
export async function resumeLoop(id) {
|
|
100
|
+
const response = await sendRequest({ type: "resume", payload: { id } });
|
|
101
|
+
if (response.type !== "ok") {
|
|
102
|
+
console.error(t("cli.error", { message: response.message }));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
console.log(t("cli.resumed", { id }));
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
export async function deleteLoop(id) {
|
|
109
|
+
const response = await sendRequest({ type: "delete", payload: { id } });
|
|
110
|
+
if (response.type !== "ok") {
|
|
111
|
+
console.error(t("cli.error", { message: response.message }));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log(t("cli.deleted", { id }));
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
export async function viewLogs(id, follow, tail) {
|
|
118
|
+
if (!follow) {
|
|
119
|
+
const response = await sendRequest({
|
|
120
|
+
type: "logs",
|
|
121
|
+
payload: { id, follow: false, tail },
|
|
122
|
+
});
|
|
123
|
+
if (response.type !== "ok") {
|
|
124
|
+
console.error(t("cli.error", { message: response.message }));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const content = response.data;
|
|
128
|
+
if (content) {
|
|
129
|
+
process.stdout.write(content);
|
|
130
|
+
if (!content.endsWith("\n"))
|
|
131
|
+
process.stdout.write("\n");
|
|
132
|
+
}
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
const socket = streamRequest({ type: "logs", payload: { id, follow: true, tail } }, (line) => console.log(line), () => process.exit(0), (err) => {
|
|
136
|
+
console.error(t("cli.error", { message: err.message }));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
});
|
|
139
|
+
process.on("SIGINT", () => {
|
|
140
|
+
socket.destroy();
|
|
141
|
+
process.exit(0);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export async function attachLoop(id) {
|
|
145
|
+
console.log(t("cli.attaching", { id }));
|
|
146
|
+
const socket = streamRequest({ type: "attach", payload: { id } }, (line) => console.log(line), () => process.exit(0), (err) => {
|
|
147
|
+
console.error(t("cli.error", { message: err.message }));
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
|
150
|
+
process.on("SIGINT", () => {
|
|
151
|
+
console.log(t("cli.detached"));
|
|
152
|
+
socket.destroy();
|
|
153
|
+
process.exit(0);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function formatCmd(command, args) {
|
|
157
|
+
const full = `${command} ${args.join(" ")}`.trim();
|
|
158
|
+
return full.length > 30 ? full.slice(0, 27) + "..." : full;
|
|
159
|
+
}
|
|
160
|
+
function pad(str, width) {
|
|
161
|
+
return str.length >= width ? str + " " : str + " ".repeat(width - str.length);
|
|
162
|
+
}
|
|
163
|
+
async function fetchProjects() {
|
|
164
|
+
const response = await sendRequest({ type: "project-list" });
|
|
165
|
+
if (response.type !== "ok") {
|
|
166
|
+
throw new Error(response.message);
|
|
167
|
+
}
|
|
168
|
+
return response.data;
|
|
169
|
+
}
|
|
170
|
+
export function resolveColor(input) {
|
|
171
|
+
if (PROJECT_COLORS[input])
|
|
172
|
+
return PROJECT_COLORS[input];
|
|
173
|
+
if (/^#[0-9a-fA-F]{6}$/.test(input))
|
|
174
|
+
return input;
|
|
175
|
+
throw new Error(t("cli.projectInvalidColor", {
|
|
176
|
+
color: input,
|
|
177
|
+
valid: Object.keys(PROJECT_COLORS).join(", "),
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
export async function resolveProjectId(idOrName) {
|
|
181
|
+
const projects = await fetchProjects();
|
|
182
|
+
const byId = projects.find((p) => p.id === idOrName);
|
|
183
|
+
if (byId)
|
|
184
|
+
return byId.id;
|
|
185
|
+
const matches = projects.filter((p) => p.name.toLowerCase() === idOrName.toLowerCase());
|
|
186
|
+
if (matches.length === 1)
|
|
187
|
+
return matches[0].id;
|
|
188
|
+
if (matches.length === 0)
|
|
189
|
+
throw new Error(t("cli.projectNotFound", { name: idOrName }));
|
|
190
|
+
throw new Error(t("cli.projectAmbiguous", { name: idOrName }));
|
|
191
|
+
}
|
|
192
|
+
export async function listProjectsCli() {
|
|
193
|
+
try {
|
|
194
|
+
const projects = await fetchProjects();
|
|
195
|
+
if (projects.length === 0) {
|
|
196
|
+
console.log(t("cli.noProjects"));
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
const loopResponse = await sendRequest({ type: "list" });
|
|
200
|
+
const loops = loopResponse.type === "ok" ? loopResponse.data : [];
|
|
201
|
+
const counts = new Map();
|
|
202
|
+
for (const loop of loops) {
|
|
203
|
+
counts.set(loop.projectId, (counts.get(loop.projectId) ?? 0) + 1);
|
|
204
|
+
}
|
|
205
|
+
const nameW = Math.max(t("cli.projectHeaderName").length, ...projects.map((p) => p.name.length)) + 2;
|
|
206
|
+
const idW = Math.max(t("cli.projectHeaderId").length, ...projects.map((p) => p.id.length)) + 2;
|
|
207
|
+
const colorW = Math.max(t("cli.projectHeaderColor").length, ...projects.map((p) => p.color.length)) + 2;
|
|
208
|
+
const loopsW = t("cli.projectHeaderLoops").length + 2;
|
|
209
|
+
const header = pad(t("cli.projectHeaderName"), nameW) +
|
|
210
|
+
pad(t("cli.projectHeaderId"), idW) +
|
|
211
|
+
pad(t("cli.projectHeaderColor"), colorW) +
|
|
212
|
+
pad(t("cli.projectHeaderLoops"), loopsW) +
|
|
213
|
+
t("cli.projectHeaderSystem");
|
|
214
|
+
console.log(header);
|
|
215
|
+
console.log("-".repeat(header.length));
|
|
216
|
+
for (const project of projects) {
|
|
217
|
+
console.log(pad(project.name, nameW) +
|
|
218
|
+
pad(project.id, idW) +
|
|
219
|
+
pad(project.color, colorW) +
|
|
220
|
+
pad(String(counts.get(project.id) ?? 0), loopsW) +
|
|
221
|
+
(project.isSystem ? "yes" : ""));
|
|
222
|
+
}
|
|
223
|
+
process.exit(0);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
227
|
+
console.error(t("cli.error", { message }));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
export async function createProjectCli(name, colorInput) {
|
|
232
|
+
try {
|
|
233
|
+
const color = colorInput ? resolveColor(colorInput) : PROJECT_COLORS.cyan;
|
|
234
|
+
const response = await sendRequest({
|
|
235
|
+
type: "project-create",
|
|
236
|
+
payload: { name, color },
|
|
237
|
+
});
|
|
238
|
+
if (response.type !== "ok") {
|
|
239
|
+
throw new Error(response.message);
|
|
240
|
+
}
|
|
241
|
+
const id = response.data.id;
|
|
242
|
+
console.log(t("cli.projectCreated", { name, id }));
|
|
243
|
+
process.exit(0);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
247
|
+
console.error(t("cli.error", { message }));
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
export async function renameProjectCli(idOrName, newName) {
|
|
252
|
+
try {
|
|
253
|
+
const id = await resolveProjectId(idOrName);
|
|
254
|
+
const response = await sendRequest({
|
|
255
|
+
type: "project-update",
|
|
256
|
+
payload: { id, name: newName },
|
|
257
|
+
});
|
|
258
|
+
if (response.type !== "ok") {
|
|
259
|
+
throw new Error(response.message);
|
|
260
|
+
}
|
|
261
|
+
console.log(t("cli.projectRenamed", { name: newName }));
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
266
|
+
console.error(t("cli.error", { message }));
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export async function setProjectColorCli(idOrName, colorInput) {
|
|
271
|
+
try {
|
|
272
|
+
const projects = await fetchProjects();
|
|
273
|
+
const byId = projects.find((p) => p.id === idOrName);
|
|
274
|
+
const matches = byId
|
|
275
|
+
? [byId]
|
|
276
|
+
: projects.filter((p) => p.name.toLowerCase() === idOrName.toLowerCase());
|
|
277
|
+
if (matches.length === 0)
|
|
278
|
+
throw new Error(t("cli.projectNotFound", { name: idOrName }));
|
|
279
|
+
if (matches.length > 1)
|
|
280
|
+
throw new Error(t("cli.projectAmbiguous", { name: idOrName }));
|
|
281
|
+
const project = matches[0];
|
|
282
|
+
const color = resolveColor(colorInput);
|
|
283
|
+
const response = await sendRequest({
|
|
284
|
+
type: "project-update",
|
|
285
|
+
payload: { id: project.id, name: project.name, color },
|
|
286
|
+
});
|
|
287
|
+
if (response.type !== "ok") {
|
|
288
|
+
throw new Error(response.message);
|
|
289
|
+
}
|
|
290
|
+
console.log(t("cli.projectColorSet", { name: project.name, color }));
|
|
291
|
+
process.exit(0);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
295
|
+
console.error(t("cli.error", { message }));
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
export async function deleteProjectCli(idOrName) {
|
|
300
|
+
try {
|
|
301
|
+
const id = await resolveProjectId(idOrName);
|
|
302
|
+
const response = await sendRequest({
|
|
303
|
+
type: "project-delete",
|
|
304
|
+
payload: { id },
|
|
305
|
+
});
|
|
306
|
+
if (response.type !== "ok") {
|
|
307
|
+
throw new Error(response.message);
|
|
308
|
+
}
|
|
309
|
+
console.log(t("cli.projectDeleted", { name: idOrName }));
|
|
310
|
+
process.exit(0);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
314
|
+
console.error(t("cli.error", { message }));
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
}
|