@hasna/loops 0.1.0 → 0.3.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 +88 -3
- package/dist/cli/index.js +1479 -75
- package/dist/daemon/daemon.d.ts +1 -0
- package/dist/daemon/index.js +1156 -52
- package/dist/daemon/install.d.ts +8 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1335 -57
- package/dist/lib/accounts.d.ts +4 -0
- package/dist/lib/doctor.d.ts +13 -0
- package/dist/lib/executor.d.ts +20 -1
- package/dist/lib/format.d.ts +5 -1
- package/dist/lib/scheduler.d.ts +12 -0
- package/dist/lib/store.d.ts +47 -2
- package/dist/lib/store.js +627 -6
- package/dist/lib/workflow-runner.d.ts +14 -0
- package/dist/lib/workflow-spec.d.ts +5 -0
- package/dist/sdk/index.js +1126 -57
- package/dist/types.d.ts +88 -2
- package/docs/USAGE.md +89 -4
- package/package.json +2 -2
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export type RunStatus = "running" | "succeeded" | "failed" | "timed_out" | "aban
|
|
|
3
3
|
export type CatchUpPolicy = "none" | "latest" | "all";
|
|
4
4
|
export type OverlapPolicy = "skip" | "allow";
|
|
5
5
|
export type IntervalAnchor = "fixed_rate" | "fixed_delay";
|
|
6
|
+
export interface AccountRef {
|
|
7
|
+
profile: string;
|
|
8
|
+
tool?: string;
|
|
9
|
+
}
|
|
6
10
|
export interface OnceSchedule {
|
|
7
11
|
type: "once";
|
|
8
12
|
at: string;
|
|
@@ -29,8 +33,9 @@ export interface CommandTarget {
|
|
|
29
33
|
shell?: boolean;
|
|
30
34
|
env?: Record<string, string>;
|
|
31
35
|
timeoutMs?: number;
|
|
36
|
+
account?: AccountRef;
|
|
32
37
|
}
|
|
33
|
-
export type AgentProvider = "claude" | "cursor" | "codewith" | "aicopilot" | "opencode";
|
|
38
|
+
export type AgentProvider = "claude" | "cursor" | "codewith" | "aicopilot" | "opencode" | "codex";
|
|
34
39
|
export type AgentConfigIsolation = "safe" | "none";
|
|
35
40
|
export interface AgentTarget {
|
|
36
41
|
type: "agent";
|
|
@@ -42,8 +47,89 @@ export interface AgentTarget {
|
|
|
42
47
|
extraArgs?: string[];
|
|
43
48
|
timeoutMs?: number;
|
|
44
49
|
configIsolation?: AgentConfigIsolation;
|
|
50
|
+
account?: AccountRef;
|
|
51
|
+
}
|
|
52
|
+
export interface WorkflowTarget {
|
|
53
|
+
type: "workflow";
|
|
54
|
+
workflowId: string;
|
|
55
|
+
input?: Record<string, string>;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
}
|
|
58
|
+
export type ExecutableTarget = CommandTarget | AgentTarget;
|
|
59
|
+
export type LoopTarget = ExecutableTarget | WorkflowTarget;
|
|
60
|
+
export type WorkflowStatus = "active" | "archived";
|
|
61
|
+
export type WorkflowRunStatus = "running" | "succeeded" | "failed" | "timed_out" | "cancelled";
|
|
62
|
+
export type WorkflowStepRunStatus = "pending" | "running" | "succeeded" | "failed" | "timed_out" | "skipped" | "cancelled";
|
|
63
|
+
export interface WorkflowStep {
|
|
64
|
+
id: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
target: ExecutableTarget;
|
|
68
|
+
dependsOn?: string[];
|
|
69
|
+
continueOnFailure?: boolean;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
account?: AccountRef;
|
|
72
|
+
}
|
|
73
|
+
export interface WorkflowSpec {
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
version: number;
|
|
78
|
+
status: WorkflowStatus;
|
|
79
|
+
steps: WorkflowStep[];
|
|
80
|
+
createdAt: string;
|
|
81
|
+
updatedAt: string;
|
|
82
|
+
}
|
|
83
|
+
export interface CreateWorkflowInput {
|
|
84
|
+
name: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
steps: WorkflowStep[];
|
|
87
|
+
version?: number;
|
|
88
|
+
}
|
|
89
|
+
export interface WorkflowRun {
|
|
90
|
+
id: string;
|
|
91
|
+
workflowId: string;
|
|
92
|
+
workflowName: string;
|
|
93
|
+
loopId?: string;
|
|
94
|
+
loopRunId?: string;
|
|
95
|
+
scheduledFor?: string;
|
|
96
|
+
idempotencyKey?: string;
|
|
97
|
+
status: WorkflowRunStatus;
|
|
98
|
+
startedAt?: string;
|
|
99
|
+
finishedAt?: string;
|
|
100
|
+
durationMs?: number;
|
|
101
|
+
error?: string;
|
|
102
|
+
createdAt: string;
|
|
103
|
+
updatedAt: string;
|
|
104
|
+
}
|
|
105
|
+
export interface WorkflowStepRun {
|
|
106
|
+
id: string;
|
|
107
|
+
workflowRunId: string;
|
|
108
|
+
stepId: string;
|
|
109
|
+
sequence: number;
|
|
110
|
+
status: WorkflowStepRunStatus;
|
|
111
|
+
startedAt?: string;
|
|
112
|
+
finishedAt?: string;
|
|
113
|
+
exitCode?: number;
|
|
114
|
+
pid?: number;
|
|
115
|
+
durationMs?: number;
|
|
116
|
+
stdout?: string;
|
|
117
|
+
stderr?: string;
|
|
118
|
+
error?: string;
|
|
119
|
+
accountProfile?: string;
|
|
120
|
+
accountTool?: string;
|
|
121
|
+
createdAt: string;
|
|
122
|
+
updatedAt: string;
|
|
123
|
+
}
|
|
124
|
+
export interface WorkflowEvent {
|
|
125
|
+
id: string;
|
|
126
|
+
workflowRunId: string;
|
|
127
|
+
sequence: number;
|
|
128
|
+
eventType: string;
|
|
129
|
+
stepId?: string;
|
|
130
|
+
payload?: Record<string, unknown>;
|
|
131
|
+
createdAt: string;
|
|
45
132
|
}
|
|
46
|
-
export type LoopTarget = CommandTarget | AgentTarget;
|
|
47
133
|
export interface Loop {
|
|
48
134
|
id: string;
|
|
49
135
|
name: string;
|
package/docs/USAGE.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
# OpenLoops
|
|
1
|
+
# OpenLoops
|
|
2
2
|
|
|
3
|
-
OpenLoops is a local CLI and daemon for persistent loops: scheduled or recurring work that survives process restarts and records every run.
|
|
3
|
+
OpenLoops is a local CLI and daemon for persistent loops and workflows: scheduled or recurring work that survives process restarts and records every run.
|
|
4
4
|
|
|
5
|
-
It supports deterministic command loops
|
|
5
|
+
It supports deterministic command loops, JSON-defined workflows, and guarded CLI adapters for headless coding agents:
|
|
6
6
|
|
|
7
7
|
- `claude`
|
|
8
8
|
- `cursor-agent`
|
|
9
9
|
- `codewith exec`
|
|
10
10
|
- `aicopilot run`
|
|
11
11
|
- `opencode run`
|
|
12
|
+
- `codex exec`
|
|
12
13
|
|
|
13
14
|
## Install
|
|
14
15
|
|
|
@@ -53,6 +54,18 @@ loops create agent morning-check \
|
|
|
53
54
|
--prompt "Check whether this repo is healthy and summarize required action."
|
|
54
55
|
```
|
|
55
56
|
|
|
57
|
+
Run a Claude loop with an isolated OpenAccounts profile:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
loops create agent morning-check \
|
|
61
|
+
--provider claude \
|
|
62
|
+
--account work \
|
|
63
|
+
--account-tool claude \
|
|
64
|
+
--cron "0 8 * * *" \
|
|
65
|
+
--cwd /path/to/repo \
|
|
66
|
+
--prompt "Check whether this repo is healthy and summarize required action."
|
|
67
|
+
```
|
|
68
|
+
|
|
56
69
|
Run a Codewith loop every 15 minutes:
|
|
57
70
|
|
|
58
71
|
```bash
|
|
@@ -63,6 +76,74 @@ loops create agent supply-chain-watch \
|
|
|
63
76
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
64
77
|
```
|
|
65
78
|
|
|
79
|
+
For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
accounts tools add codewith --label "Codewith" --env-var CODEWITH_HOME --bin codewith
|
|
83
|
+
accounts tools add aicopilot --label "AI Copilot" --env-var AICOPILOT_CONFIG_DIR --bin aicopilot
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Workflows
|
|
87
|
+
|
|
88
|
+
Create a workflow JSON file:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"name": "repo-morning",
|
|
93
|
+
"steps": [
|
|
94
|
+
{
|
|
95
|
+
"id": "status",
|
|
96
|
+
"target": {
|
|
97
|
+
"type": "command",
|
|
98
|
+
"command": "git",
|
|
99
|
+
"args": ["status", "--short"],
|
|
100
|
+
"cwd": "/path/to/repo"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "review",
|
|
105
|
+
"dependsOn": ["status"],
|
|
106
|
+
"target": {
|
|
107
|
+
"type": "agent",
|
|
108
|
+
"provider": "codex",
|
|
109
|
+
"account": { "profile": "work", "tool": "codex" },
|
|
110
|
+
"cwd": "/path/to/repo",
|
|
111
|
+
"prompt": "Review the repository status and summarize concrete next actions."
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Save, run, inspect, and schedule it:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
loops workflows validate repo-morning.json
|
|
122
|
+
loops workflows validate repo-morning.json --preflight
|
|
123
|
+
loops workflows create repo-morning.json
|
|
124
|
+
loops workflows run repo-morning --show-output
|
|
125
|
+
loops workflows runs repo-morning
|
|
126
|
+
loops workflows inspect <workflow-run-id>
|
|
127
|
+
loops workflows events <workflow-run-id>
|
|
128
|
+
loops workflows cancel <workflow-run-id> --reason "no longer needed"
|
|
129
|
+
loops workflows recover <workflow-run-id>
|
|
130
|
+
loops create workflow repo-morning-loop --workflow repo-morning --cron "0 8 * * *"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Workflow specs are stored separately from loops. A loop can schedule a workflow, but workflow runs and step runs have their own durable rows and events. Steps run in dependency order and a scheduled workflow run is idempotent per loop slot.
|
|
134
|
+
|
|
135
|
+
For command steps, `command` is the executable when `shell` is not true. Put flags in `args`:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{ "type": "command", "command": "git", "args": ["status", "--short"] }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use `shell: true` only when you intentionally want shell parsing:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{ "type": "command", "command": "git status --short", "shell": true }
|
|
145
|
+
```
|
|
146
|
+
|
|
66
147
|
## Manage
|
|
67
148
|
|
|
68
149
|
```bash
|
|
@@ -85,6 +166,7 @@ loops daemon start
|
|
|
85
166
|
loops daemon status
|
|
86
167
|
loops daemon logs
|
|
87
168
|
loops daemon stop
|
|
169
|
+
loops doctor
|
|
88
170
|
```
|
|
89
171
|
|
|
90
172
|
Run in the foreground for supervised environments:
|
|
@@ -97,9 +179,10 @@ Install startup integration:
|
|
|
97
179
|
|
|
98
180
|
```bash
|
|
99
181
|
loops daemon install
|
|
182
|
+
loops daemon install --enable
|
|
100
183
|
```
|
|
101
184
|
|
|
102
|
-
On Linux this writes a user systemd service. On macOS it writes a LaunchAgent plist. The command prints the exact enable/load commands to run.
|
|
185
|
+
On Linux this writes a user systemd service. On macOS it writes a LaunchAgent plist. The command prints the exact enable/load commands to run. `--enable` runs the user-service enable/start command when supported.
|
|
103
186
|
|
|
104
187
|
## Scheduling Contract
|
|
105
188
|
|
|
@@ -123,5 +206,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
123
206
|
- Codewith uses `codewith exec --json --ephemeral --ask-for-approval never`.
|
|
124
207
|
- AI Copilot and OpenCode use `run --format json --pure`.
|
|
125
208
|
- Cursor is CLI-first for now via `cursor-agent -p`; treat output as less stable until a stronger public SDK contract is selected.
|
|
209
|
+
- Codex uses `codex exec --json --ephemeral --ask-for-approval never`.
|
|
210
|
+
- When `--account` or a step `account` is set, OpenLoops resolves `accounts env <profile> --tool <tool>` before spawning the target, strips inherited tool home/API-key variables, and applies the selected profile only to that process. Missing account profiles fail before the provider binary receives the prompt.
|
|
126
211
|
|
|
127
212
|
For production loops that can mutate repos, prefer disposable worktrees and explicit prompts that name allowed write scope.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/loops",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Persistent local loop runner for deterministic commands and headless AI coding agents",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|