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.
Files changed (130) hide show
  1. package/README.md +250 -113
  2. package/dist/board/App.js +340 -0
  3. package/dist/board/board-log.js +18 -0
  4. package/dist/board/components/ActionButtons.js +45 -0
  5. package/dist/board/components/BoardButton.js +58 -0
  6. package/dist/board/components/ConfirmModal.js +36 -0
  7. package/dist/board/components/CreateForm.js +309 -0
  8. package/dist/board/components/CreateProjectModal.js +104 -0
  9. package/dist/board/components/DeleteProjectConfirm.js +53 -0
  10. package/dist/board/components/DetailView.js +25 -0
  11. package/dist/board/components/EditProjectModal.js +106 -0
  12. package/dist/board/components/FilterBar.js +16 -0
  13. package/dist/board/components/Footer.js +52 -0
  14. package/dist/board/components/Header.js +46 -0
  15. package/dist/board/components/HelpModal.js +36 -0
  16. package/dist/board/components/Inspector.js +12 -0
  17. package/dist/board/components/LogModal.js +58 -0
  18. package/dist/board/components/Navigator.js +84 -0
  19. package/dist/board/components/ProjectsModal.js +70 -0
  20. package/dist/board/components/ProjectsPage.js +105 -0
  21. package/dist/board/components/RunHistory.js +83 -0
  22. package/dist/board/components/TaskBrowser.js +95 -0
  23. package/dist/board/components/TaskFilterBar.js +6 -0
  24. package/dist/board/components/TaskForm.js +177 -0
  25. package/dist/board/components/Timeline.js +5 -0
  26. package/dist/board/daemon.js +117 -0
  27. package/dist/board/format.js +102 -0
  28. package/dist/board/hooks/useBoardKeybindings.js +299 -0
  29. package/dist/board/hooks/useBreakpoint.js +6 -0
  30. package/dist/board/hooks/useHoverState.js +11 -0
  31. package/dist/board/hooks/useLogStream.js +32 -0
  32. package/dist/board/hooks/useLoopPolling.js +28 -0
  33. package/dist/board/hooks/useTaskKeybindings.js +114 -0
  34. package/dist/board/index.js +32 -0
  35. package/dist/board/router.js +16 -0
  36. package/dist/board/state.js +99 -0
  37. package/dist/board/toast.js +65 -0
  38. package/dist/board/types.js +1 -0
  39. package/dist/cli.js +134 -93
  40. package/dist/cli.js.map +1 -1
  41. package/dist/client/commands.d.ts +11 -0
  42. package/dist/client/commands.d.ts.map +1 -0
  43. package/dist/client/commands.js +317 -0
  44. package/dist/client/commands.js.map +1 -0
  45. package/dist/client/ipc.d.ts +5 -0
  46. package/dist/client/ipc.d.ts.map +1 -0
  47. package/dist/client/ipc.js +96 -0
  48. package/dist/client/ipc.js.map +1 -0
  49. package/dist/config/constants.js +29 -0
  50. package/dist/config/paths.js +49 -0
  51. package/dist/core/command-runner.js +82 -0
  52. package/dist/core/foreground-loop.js +81 -0
  53. package/dist/core/log-parser.js +29 -0
  54. package/dist/core/log-rotator.js +25 -0
  55. package/dist/core/loop-controller.js +429 -0
  56. package/dist/daemon/daemon-log.js +11 -0
  57. package/dist/daemon/index.d.ts +2 -0
  58. package/dist/daemon/index.d.ts.map +1 -0
  59. package/dist/daemon/index.js +43 -0
  60. package/dist/daemon/index.js.map +1 -0
  61. package/dist/daemon/manager.d.ts +17 -0
  62. package/dist/daemon/manager.d.ts.map +1 -0
  63. package/dist/daemon/manager.js +260 -0
  64. package/dist/daemon/manager.js.map +1 -0
  65. package/dist/daemon/projects.js +104 -0
  66. package/dist/daemon/server.d.ts +15 -0
  67. package/dist/daemon/server.d.ts.map +1 -0
  68. package/dist/daemon/server.js +345 -0
  69. package/dist/daemon/server.js.map +1 -0
  70. package/dist/daemon/spawner.d.ts +3 -0
  71. package/dist/daemon/spawner.d.ts.map +1 -0
  72. package/dist/daemon/spawner.js +82 -0
  73. package/dist/daemon/spawner.js.map +1 -0
  74. package/dist/daemon/state.d.ts +15 -0
  75. package/dist/daemon/state.d.ts.map +1 -0
  76. package/dist/daemon/state.js +151 -0
  77. package/dist/daemon/state.js.map +1 -0
  78. package/dist/daemon/task-manager.js +59 -0
  79. package/dist/duration.js +4 -4
  80. package/dist/entry.js +11 -0
  81. package/dist/esm-loader.js +36 -0
  82. package/dist/i18n/en.json +386 -0
  83. package/dist/i18n/index.js +11 -0
  84. package/dist/ipc/handlers/logs-stream.js +65 -0
  85. package/dist/ipc/send.js +5 -0
  86. package/dist/logger.js +0 -1
  87. package/dist/loop-config.d.ts +13 -0
  88. package/dist/loop-config.d.ts.map +1 -0
  89. package/dist/loop-config.js +83 -0
  90. package/dist/loop-config.js.map +1 -0
  91. package/dist/loop.d.ts +43 -2
  92. package/dist/loop.d.ts.map +1 -1
  93. package/dist/loop.js +247 -2
  94. package/dist/loop.js.map +1 -1
  95. package/dist/open-board.d.ts +2 -0
  96. package/dist/open-board.d.ts.map +1 -0
  97. package/dist/open-board.js +19 -0
  98. package/dist/open-board.js.map +1 -0
  99. package/dist/shared/clipboard.js +19 -0
  100. package/dist/shared/fs-utils.js +11 -0
  101. package/dist/shared/sleep.js +17 -0
  102. package/dist/shared/tail.js +4 -0
  103. package/dist/tui/app.d.ts +57 -0
  104. package/dist/tui/app.d.ts.map +1 -0
  105. package/dist/tui/app.js +167 -0
  106. package/dist/tui/app.js.map +1 -0
  107. package/dist/tui/create-loop.d.ts +3 -0
  108. package/dist/tui/create-loop.d.ts.map +1 -0
  109. package/dist/tui/create-loop.js +90 -0
  110. package/dist/tui/create-loop.js.map +1 -0
  111. package/dist/tui/dashboard.d.ts +2 -0
  112. package/dist/tui/dashboard.d.ts.map +1 -0
  113. package/dist/tui/dashboard.js +45 -0
  114. package/dist/tui/dashboard.js.map +1 -0
  115. package/dist/tui/events.d.ts +3 -0
  116. package/dist/tui/events.d.ts.map +1 -0
  117. package/dist/tui/events.js +244 -0
  118. package/dist/tui/events.js.map +1 -0
  119. package/dist/tui/popup.d.ts +3 -0
  120. package/dist/tui/popup.d.ts.map +1 -0
  121. package/dist/tui/popup.js +58 -0
  122. package/dist/tui/popup.js.map +1 -0
  123. package/dist/tui/render.d.ts +9 -0
  124. package/dist/tui/render.d.ts.map +1 -0
  125. package/dist/tui/render.js +305 -0
  126. package/dist/tui/render.js.map +1 -0
  127. package/dist/types.d.ts +74 -0
  128. package/dist/types.d.ts.map +1 -1
  129. package/dist/types.js +0 -1
  130. 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 { parseDuration } from "./duration.js";
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
- const args = process.argv.slice(2);
6
- const loopOptions = {
7
- immediate: false,
8
- maxRuns: null,
9
- verbose: false,
10
- };
11
- const commandParts = [];
12
- let i = 0;
13
- while (i < args.length) {
14
- const arg = args[i];
15
- if (arg === "--now") {
16
- loopOptions.immediate = true;
17
- i++;
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
- else if (arg === "--max-runs") {
20
- i++;
21
- const n = parseInt(args[i], 10);
22
- if (isNaN(n) || n < 1) {
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
- else if (arg === "--version" || arg === "-V") {
54
- console.log("1.0.0");
55
- process.exit(0);
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
- else if (arg === "--") {
58
- i++;
59
- commandParts.push(...args.slice(i));
60
- break;
61
- }
62
- else {
63
- commandParts.push(arg);
64
- i++;
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
- if (commandParts.length < 2) {
68
- console.error("Error: missing required arguments");
69
- console.error("Usage: loop-task [options] <interval> <command>");
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":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAEtE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,uDAAuD,CAAC;KACpE,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AAEjD,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,gCAAgC,CAAC;KAC7C,QAAQ,CAAC,YAAY,EAAE,0CAA0C,CAAC;KAClE,QAAQ,CAAC,cAAc,EAAE,oBAAoB,CAAC;KAC9C,MAAM,CAAC,OAAO,EAAE,gCAAgC,EAAE,KAAK,CAAC;KACxD,MAAM,CAAC,gBAAgB,EAAE,yBAAyB,EAAE,QAAQ,CAAC;KAC7D,MAAM,CAAC,WAAW,EAAE,wBAAwB,EAAE,KAAK,CAAC;KACpD,MAAM,CACL,KAAK,EACH,WAAmB,EACnB,OAAiB,EACjB,IAA0D,EAC1D,EAAE;IACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAChF,MAAM,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;AACtD,CAAC,CACF,CAAC;AAEJ,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,8BAA8B,CAAC;KAC3C,QAAQ,CAAC,YAAY,EAAE,0CAA0C,CAAC;KAClE,QAAQ,CAAC,cAAc,EAAE,oBAAoB,CAAC;KAC9C,MAAM,CAAC,OAAO,EAAE,gCAAgC,CAAC;KACjD,MAAM,CAAC,gBAAgB,EAAE,yBAAyB,CAAC;KACnD,MAAM,CAAC,WAAW,EAAE,wBAAwB,CAAC;KAC7C,MAAM,CACL,KAAK,EACH,WAA+B,EAC/B,OAA6B,EAC7B,IAA4D,EAC5D,EAAE;IACF,IAAI,CAAC,WAAW,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,OAAO,CAAC,IAAI,EAAE,CAAC;QACf,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAEhF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;IAEhD,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CACF,CAAC;AAEJ,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;IACxB,MAAM,eAAe,EAAE,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -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
+ }