loop-task 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Quique Fdez Guerra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # loop-task
2
+
3
+ A cross-platform CLI that repeatedly executes a shell command at a human-readable interval.
4
+
5
+ Inspired by agent loops, but intentionally simple.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g loop-task
11
+ ```
12
+
13
+ Or use directly with npx:
14
+
15
+ ```bash
16
+ npx loop-task 30m npm test
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```bash
22
+ loop [options] <interval> <command>
23
+ ```
24
+
25
+ ### Basic examples
26
+
27
+ ```bash
28
+ loop 30m npm test
29
+ loop 1h opencode --prompt '/ob-init'
30
+ loop 1d node sync.js
31
+ ```
32
+
33
+ ### With npx
34
+
35
+ ```bash
36
+ npx loop-task 30m npm test
37
+ npx loop-task 1h opencode --prompt '/ob-init'
38
+ ```
39
+
40
+ ### Options
41
+
42
+ Options must come before the interval:
43
+
44
+ | Option | Description |
45
+ | ----------------- | -------------------------------- |
46
+ | `--immediate` | Run immediately before waiting |
47
+ | `--max-runs <n>` | Stop after N executions |
48
+ | `--verbose` | Show execution details |
49
+ | `-h, --help` | Display help |
50
+ | `-V, --version` | Display version |
51
+
52
+ ## Examples
53
+
54
+ ### Agent workflows
55
+
56
+ ```bash
57
+ npx loop-task 30m opencode --prompt "search for missing translation text and translate them, 3 maximum" --model "opencode/big-pickle"
58
+ ```
59
+
60
+ ### Run tests every 30 minutes
61
+
62
+ ```bash
63
+ loop 30m npm test
64
+ ```
65
+
66
+ ### Run immediately, then every hour
67
+
68
+ ```bash
69
+ loop --immediate 1h npm test
70
+ ```
71
+
72
+ ### Run up to 5 times then stop
73
+
74
+ ```bash
75
+ loop --max-runs 5 5m npm test
76
+ ```
77
+
78
+ ### Verbose mode
79
+
80
+ ```bash
81
+ loop --verbose 30m npm test
82
+ ```
83
+
84
+ Shows start/end timestamps, exit code, execution duration, and next scheduled run.
85
+
86
+ ## Supported intervals
87
+
88
+ | Format | Description |
89
+ | ------ | ----------- |
90
+ | `10s` | 10 seconds |
91
+ | `5m` | 5 minutes |
92
+ | `1h` | 1 hour |
93
+ | `1d` | 1 day |
94
+ | `1w` | 1 week |
95
+
96
+ ## Behavior
97
+
98
+ - **No overlapping**: waits for the command to finish before starting the next interval
99
+ - **Resilient**: continues looping even if a command exits with a non-zero code
100
+ - **Graceful shutdown**: finishes current execution on Ctrl+C, then exits cleanly
101
+
102
+ ## Development
103
+
104
+ ```bash
105
+ pnpm install
106
+ pnpm run build
107
+ pnpm run test
108
+ pnpm run lint
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { parseDuration } from "./duration.js";
4
+ import { Logger } from "./logger.js";
5
+ import { runLoop } from "./loop.js";
6
+ const program = new Command();
7
+ program
8
+ .name("loop")
9
+ .description("Repeatedly execute a shell command at a human-readable interval")
10
+ .version("1.0.0")
11
+ .argument("<interval>", "Interval between runs (e.g. 10s, 5m, 1h, 1d, 1w)")
12
+ .argument("<command...>", "Command to execute")
13
+ .option("--immediate", "Run immediately before waiting", false)
14
+ .option("--max-runs <n>", "Stop after N executions", (v) => {
15
+ const n = parseInt(v, 10);
16
+ if (isNaN(n) || n < 1) {
17
+ throw new Error("--max-runs must be a positive integer");
18
+ }
19
+ return n;
20
+ }, null)
21
+ .option("--verbose", "Show execution details", false)
22
+ .enablePositionalOptions()
23
+ .passThroughOptions()
24
+ .addHelpText("before", `
25
+ Usage:
26
+ loop [options] <interval> <command>
27
+
28
+ Examples:
29
+ loop 30m npm test
30
+ loop 1h opencode --prompt '/ob-init'
31
+ loop --immediate 1h npm test
32
+ loop --max-runs 5 5m npm test
33
+ `)
34
+ .action(async (intervalStr, commandParts, opts) => {
35
+ const logger = new Logger(opts.verbose);
36
+ let interval;
37
+ try {
38
+ interval = parseDuration(intervalStr);
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ logger.error(`Error: ${message}`);
43
+ process.exit(1);
44
+ }
45
+ const command = commandParts.join(" ");
46
+ const options = {
47
+ interval,
48
+ command,
49
+ immediate: opts.immediate,
50
+ maxRuns: opts.maxRuns,
51
+ verbose: opts.verbose,
52
+ };
53
+ const controller = new AbortController();
54
+ process.on("SIGINT", () => controller.abort());
55
+ process.on("SIGTERM", () => controller.abort());
56
+ await runLoop(options, logger, controller.signal);
57
+ process.exit(0);
58
+ });
59
+ program.parse();
60
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
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,eAAe,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CACV,iEAAiE,CAClE;KACA,OAAO,CAAC,OAAO,CAAC;KAChB,QAAQ,CAAC,YAAY,EAAE,kDAAkD,CAAC;KAC1E,QAAQ,CAAC,cAAc,EAAE,oBAAoB,CAAC;KAC9C,MAAM,CAAC,aAAa,EAAE,gCAAgC,EAAE,KAAK,CAAC;KAC9D,MAAM,CAAC,gBAAgB,EAAE,yBAAyB,EAAE,CAAC,CAAC,EAAE,EAAE;IACzD,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1B,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC,EAAE,IAAI,CAAC;KACP,MAAM,CAAC,WAAW,EAAE,wBAAwB,EAAE,KAAK,CAAC;KACpD,uBAAuB,EAAE;KACzB,kBAAkB,EAAE;KACpB,WAAW,CACV,QAAQ,EACR;;;;;;;;;CASH,CACE;KACA,MAAM,CAAC,KAAK,EAAE,WAAmB,EAAE,YAAsB,EAAE,IAA6B,EAAE,EAAE;IAC3F,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAkB,CAAC,CAAC;IAEnD,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEvC,MAAM,OAAO,GAAgB;QAC3B,QAAQ;QACR,OAAO;QACP,SAAS,EAAE,IAAI,CAAC,SAAoB;QACpC,OAAO,EAAE,IAAI,CAAC,OAAwB;QACtC,OAAO,EAAE,IAAI,CAAC,OAAkB;KACjC,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IAEzC,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,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function parseDuration(input: string): number;
2
+ export declare function formatDuration(value: number): string;
3
+ //# sourceMappingURL=duration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBnD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD"}
@@ -0,0 +1,19 @@
1
+ import ms from "ms";
2
+ export function parseDuration(input) {
3
+ const trimmed = input.trim();
4
+ if (!trimmed) {
5
+ throw new Error("Duration cannot be empty");
6
+ }
7
+ const result = ms(trimmed);
8
+ if (typeof result !== "number" || isNaN(result)) {
9
+ throw new Error(`Invalid duration: "${input}". Use formats like 10s, 5m, 1h, 1d, 1w`);
10
+ }
11
+ if (result <= 0) {
12
+ throw new Error(`Duration must be positive, got: "${input}"`);
13
+ }
14
+ return result;
15
+ }
16
+ export function formatDuration(value) {
17
+ return ms(value, { long: true });
18
+ }
19
+ //# sourceMappingURL=duration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.js","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,CAAC,OAA8C,CAAC,CAAC;IAElE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,sBAAsB,KAAK,yCAAyC,CAAC,CAAC;IACxF,CAAC;IAED,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,oCAAoC,KAAK,GAAG,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,11 @@
1
+ export declare class Logger {
2
+ private verbose;
3
+ constructor(verbose?: boolean);
4
+ info(message: string): void;
5
+ error(message: string): void;
6
+ success(message: string): void;
7
+ debug(message: string): void;
8
+ warn(message: string): void;
9
+ timestamp(): string;
10
+ }
11
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,qBAAa,MAAM;IACjB,OAAO,CAAC,OAAO,CAAU;gBAEb,OAAO,UAAQ;IAI3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAM5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,SAAS,IAAI,MAAM;CAGpB"}
package/dist/logger.js ADDED
@@ -0,0 +1,27 @@
1
+ export class Logger {
2
+ verbose;
3
+ constructor(verbose = false) {
4
+ this.verbose = verbose;
5
+ }
6
+ info(message) {
7
+ console.log(message);
8
+ }
9
+ error(message) {
10
+ console.error(message);
11
+ }
12
+ success(message) {
13
+ console.log(message);
14
+ }
15
+ debug(message) {
16
+ if (this.verbose) {
17
+ console.log(`[verbose] ${message}`);
18
+ }
19
+ }
20
+ warn(message) {
21
+ console.warn(message);
22
+ }
23
+ timestamp() {
24
+ return new Date().toISOString();
25
+ }
26
+ }
27
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,MAAM;IACT,OAAO,CAAU;IAEzB,YAAY,OAAO,GAAG,KAAK;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,OAAe;QACrB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClC,CAAC;CACF"}
package/dist/loop.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { LoopOptions, ExecutionResult } from "./types.js";
2
+ import { Logger } from "./logger.js";
3
+ export declare function executeCommand(command: string, logger: Logger): Promise<ExecutionResult>;
4
+ export declare function runLoop(options: LoopOptions, logger: Logger, signal: AbortSignal): Promise<void>;
5
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAa,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAwBrC,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,CAiC1B;AAED,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAyFf"}
package/dist/loop.js ADDED
@@ -0,0 +1,118 @@
1
+ import { execaCommand } from "execa";
2
+ import { formatDuration } from "./duration.js";
3
+ function sleep(ms, signal) {
4
+ return new Promise((resolve, reject) => {
5
+ if (signal.aborted) {
6
+ reject(signal.reason);
7
+ return;
8
+ }
9
+ const timer = setTimeout(() => {
10
+ signal.removeEventListener("abort", onAbort);
11
+ resolve();
12
+ }, ms);
13
+ const onAbort = () => {
14
+ clearTimeout(timer);
15
+ reject(signal.reason);
16
+ };
17
+ signal.addEventListener("abort", onAbort, { once: true });
18
+ });
19
+ }
20
+ export async function executeCommand(command, logger) {
21
+ const startedAt = new Date();
22
+ logger.debug(`Executing: ${command}`);
23
+ try {
24
+ const result = await execaCommand(command, {
25
+ stdout: "inherit",
26
+ stderr: "inherit",
27
+ stdin: "inherit",
28
+ });
29
+ const endedAt = new Date();
30
+ return {
31
+ exitCode: result.exitCode ?? 0,
32
+ duration: endedAt.getTime() - startedAt.getTime(),
33
+ startedAt,
34
+ endedAt,
35
+ };
36
+ }
37
+ catch (error) {
38
+ const endedAt = new Date();
39
+ const exitCode = error && typeof error === "object" && "exitCode" in error
40
+ ? error.exitCode
41
+ : 1;
42
+ return {
43
+ exitCode,
44
+ duration: endedAt.getTime() - startedAt.getTime(),
45
+ startedAt,
46
+ endedAt,
47
+ };
48
+ }
49
+ }
50
+ export async function runLoop(options, logger, signal) {
51
+ const state = {
52
+ running: false,
53
+ runCount: 0,
54
+ shuttingDown: false,
55
+ };
56
+ const onSignal = () => {
57
+ state.shuttingDown = true;
58
+ logger.info("\nShutting down gracefully...");
59
+ };
60
+ process.on("SIGINT", onSignal);
61
+ process.on("SIGTERM", onSignal);
62
+ try {
63
+ let isFirstRun = true;
64
+ while (!state.shuttingDown) {
65
+ if (options.maxRuns !== null &&
66
+ state.runCount >= options.maxRuns) {
67
+ logger.info(`Completed ${state.runCount} run(s). Exiting.`);
68
+ break;
69
+ }
70
+ if (isFirstRun && !options.immediate) {
71
+ logger.info(`Waiting ${formatDuration(options.interval)} before first run...`);
72
+ try {
73
+ await sleep(options.interval, signal);
74
+ }
75
+ catch {
76
+ break;
77
+ }
78
+ if (state.shuttingDown)
79
+ break;
80
+ }
81
+ isFirstRun = false;
82
+ state.running = true;
83
+ state.runCount++;
84
+ logger.info(`\n--- Run ${state.runCount}${options.maxRuns !== null ? `/${options.maxRuns}` : ""} ---`);
85
+ const result = await executeCommand(options.command, logger);
86
+ state.running = false;
87
+ if (result.exitCode !== 0) {
88
+ logger.error(`Command failed with exit code ${result.exitCode}`);
89
+ }
90
+ if (options.verbose) {
91
+ logger.debug(`Started at: ${result.startedAt.toISOString()}`);
92
+ logger.debug(`Ended at: ${result.endedAt.toISOString()}`);
93
+ logger.debug(`Exit code: ${result.exitCode}`);
94
+ logger.debug(`Duration: ${formatDuration(result.duration)}`);
95
+ }
96
+ if (state.shuttingDown)
97
+ break;
98
+ if (options.maxRuns !== null &&
99
+ state.runCount >= options.maxRuns) {
100
+ logger.info(`Completed ${state.runCount} run(s). Exiting.`);
101
+ break;
102
+ }
103
+ const nextRun = new Date(Date.now() + options.interval);
104
+ logger.info(`Next run in ${formatDuration(options.interval)} (at ${nextRun.toLocaleTimeString()})`);
105
+ try {
106
+ await sleep(options.interval, signal);
107
+ }
108
+ catch {
109
+ break;
110
+ }
111
+ }
112
+ }
113
+ finally {
114
+ process.removeListener("SIGINT", onSignal);
115
+ process.removeListener("SIGTERM", onSignal);
116
+ }
117
+ }
118
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface LoopOptions {
2
+ interval: number;
3
+ command: string;
4
+ immediate: boolean;
5
+ maxRuns: number | null;
6
+ verbose: boolean;
7
+ }
8
+ export interface ExecutionResult {
9
+ exitCode: number;
10
+ duration: number;
11
+ startedAt: Date;
12
+ endedAt: Date;
13
+ }
14
+ export interface LoopState {
15
+ running: boolean;
16
+ runCount: number;
17
+ shuttingDown: boolean;
18
+ }
19
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;CACvB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "loop-task",
3
+ "version": "1.0.0",
4
+ "description": "A cross-platform CLI that repeatedly executes a shell command at a human-readable interval",
5
+ "type": "module",
6
+ "bin": {
7
+ "loop": "dist/cli.js"
8
+ },
9
+ "main": "dist/cli.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [
14
+ "cli",
15
+ "loop",
16
+ "repeat",
17
+ "interval",
18
+ "schedule",
19
+ "timer",
20
+ "cron",
21
+ "automation",
22
+ "devops",
23
+ "agent"
24
+ ],
25
+ "author": "Quique Fdez Guerra",
26
+ "license": "MIT",
27
+ "engines": {
28
+ "node": ">=20.0.0"
29
+ },
30
+ "dependencies": {
31
+ "commander": "^13.1.0",
32
+ "execa": "^9.6.0",
33
+ "ms": "^2.1.3"
34
+ },
35
+ "devDependencies": {
36
+ "@types/ms": "^2.1.0",
37
+ "@types/node": "^22.15.0",
38
+ "@vitest/coverage-v8": "^3.1.0",
39
+ "eslint": "^9.25.0",
40
+ "typescript": "^5.8.0",
41
+ "typescript-eslint": "^8.30.0",
42
+ "vitest": "^3.1.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc",
46
+ "dev": "tsc --watch",
47
+ "start": "node dist/cli.js",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest",
50
+ "test:coverage": "vitest run --coverage",
51
+ "lint": "eslint src/ tests/",
52
+ "typecheck": "tsc --noEmit",
53
+ "release:dry": "pnpm run build && pnpm publish --dry-run",
54
+ "release": "pnpm run build && pnpm publish"
55
+ }
56
+ }