@hasna/loops 0.3.1 → 0.3.2
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 +13 -1
- package/dist/cli/index.js +18 -3
- package/dist/daemon/index.js +7 -2
- package/dist/index.js +6 -1
- package/dist/lib/store.js +5 -0
- package/dist/sdk/index.js +6 -1
- package/dist/types.d.ts +1 -0
- package/docs/USAGE.md +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,17 @@ loops create agent supply-chain-watch \
|
|
|
76
76
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
Run a Codewith loop with a Codewith-native auth profile:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
loops create agent supply-chain-watch \
|
|
83
|
+
--provider codewith \
|
|
84
|
+
--auth-profile account001 \
|
|
85
|
+
--every 15m \
|
|
86
|
+
--cwd /path/to/repo \
|
|
87
|
+
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
88
|
+
```
|
|
89
|
+
|
|
79
90
|
For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
|
|
80
91
|
|
|
81
92
|
```bash
|
|
@@ -157,7 +168,7 @@ loops remove <id-or-name>
|
|
|
157
168
|
loops run-now <id-or-name>
|
|
158
169
|
```
|
|
159
170
|
|
|
160
|
-
Use `--json` for machine-readable output. Prompt bodies and run stdout/stderr are redacted by default in status output.
|
|
171
|
+
Use `--json` for machine-readable output. Prompt bodies and run stdout/stderr are redacted by default in status output. `loops run-now` exits non-zero when the recorded run fails or times out.
|
|
161
172
|
|
|
162
173
|
## Daemon
|
|
163
174
|
|
|
@@ -208,6 +219,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
208
219
|
- Cursor is CLI-first for now via `cursor-agent -p`; treat output as less stable until a stronger public SDK contract is selected.
|
|
209
220
|
- Codex uses `codex exec --json --ephemeral --ask-for-approval never`.
|
|
210
221
|
- 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.
|
|
222
|
+
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `exec`; they do not call OpenAccounts.
|
|
211
223
|
- Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
|
|
212
224
|
|
|
213
225
|
For production loops that can mutate repos, prefer disposable worktrees and explicit prompts that name allowed write scope.
|
package/dist/cli/index.js
CHANGED
|
@@ -260,6 +260,11 @@ function validateTarget(value, label) {
|
|
|
260
260
|
const providers = ["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"];
|
|
261
261
|
if (!providers.includes(value.provider))
|
|
262
262
|
throw new Error(`${label}.provider must be one of ${providers.join(", ")}`);
|
|
263
|
+
if (value.authProfile !== undefined) {
|
|
264
|
+
assertString(value.authProfile, `${label}.authProfile`);
|
|
265
|
+
if (value.provider !== "codewith")
|
|
266
|
+
throw new Error(`${label}.authProfile is currently supported only for provider codewith`);
|
|
267
|
+
}
|
|
263
268
|
return value;
|
|
264
269
|
}
|
|
265
270
|
throw new Error(`${label}.type must be command or agent`);
|
|
@@ -1621,7 +1626,7 @@ function agentArgs(target) {
|
|
|
1621
1626
|
args.push(...target.extraArgs ?? [], target.prompt);
|
|
1622
1627
|
return args;
|
|
1623
1628
|
case "codewith":
|
|
1624
|
-
args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1629
|
+
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : [], "--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1625
1630
|
if (isolation === "safe")
|
|
1626
1631
|
args.push("--ignore-rules");
|
|
1627
1632
|
if (target.cwd)
|
|
@@ -2610,7 +2615,7 @@ function runDoctor(store) {
|
|
|
2610
2615
|
|
|
2611
2616
|
// src/cli/index.ts
|
|
2612
2617
|
var program = new Command;
|
|
2613
|
-
program.name("loops").description("Persistent local loops for commands and headless coding agents").version("0.3.
|
|
2618
|
+
program.name("loops").description("Persistent local loops for commands and headless coding agents").version("0.3.2");
|
|
2614
2619
|
program.option("-j, --json", "print JSON");
|
|
2615
2620
|
function isJson() {
|
|
2616
2621
|
return Boolean(program.opts().json);
|
|
@@ -2704,6 +2709,13 @@ function accountFromOpts(opts) {
|
|
|
2704
2709
|
throw new Error("--account-tool requires --account");
|
|
2705
2710
|
return opts.account ? { profile: opts.account, tool: opts.accountTool } : undefined;
|
|
2706
2711
|
}
|
|
2712
|
+
function providerAuthProfileFromOpts(opts, provider) {
|
|
2713
|
+
if (!opts.authProfile)
|
|
2714
|
+
return;
|
|
2715
|
+
if (provider !== "codewith")
|
|
2716
|
+
throw new Error("--auth-profile is currently supported only for --provider codewith");
|
|
2717
|
+
return opts.authProfile;
|
|
2718
|
+
}
|
|
2707
2719
|
var create = program.command("create").description("create loops");
|
|
2708
2720
|
addAccountOptions(addScheduleOptions(create.command("command <name>").description("create a deterministic shell command loop").requiredOption("--cmd <command>", "command string to execute").option("--cwd <dir>", "working directory").option("--timeout <duration>", "run timeout").option("--no-shell", "execute without a shell"))).action((name, opts) => {
|
|
2709
2721
|
const store = new Store;
|
|
@@ -2722,7 +2734,7 @@ addAccountOptions(addScheduleOptions(create.command("command <name>").descriptio
|
|
|
2722
2734
|
store.close();
|
|
2723
2735
|
}
|
|
2724
2736
|
});
|
|
2725
|
-
addAccountOptions(addScheduleOptions(create.command("agent <name>").description("create a headless coding-agent loop").requiredOption("--provider <provider>", "claude, cursor, codewith, aicopilot, opencode, or codex").requiredOption("--prompt <prompt>", "agent prompt").option("--cwd <dir>", "working directory").option("--model <model>", "model").option("--agent <agent>", "provider-specific agent").option("--timeout <duration>", "run timeout").option("--config-isolation <mode>", "safe or none", "safe"))).action((name, opts) => {
|
|
2737
|
+
addAccountOptions(addScheduleOptions(create.command("agent <name>").description("create a headless coding-agent loop").requiredOption("--provider <provider>", "claude, cursor, codewith, aicopilot, opencode, or codex").requiredOption("--prompt <prompt>", "agent prompt").option("--cwd <dir>", "working directory").option("--model <model>", "model").option("--agent <agent>", "provider-specific agent").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--timeout <duration>", "run timeout").option("--config-isolation <mode>", "safe or none", "safe"))).action((name, opts) => {
|
|
2726
2738
|
const provider = opts.provider;
|
|
2727
2739
|
if (!["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"].includes(provider)) {
|
|
2728
2740
|
throw new Error("unsupported provider");
|
|
@@ -2739,6 +2751,7 @@ addAccountOptions(addScheduleOptions(create.command("agent <name>").description(
|
|
|
2739
2751
|
cwd: opts.cwd,
|
|
2740
2752
|
model: opts.model,
|
|
2741
2753
|
agent: opts.agent,
|
|
2754
|
+
authProfile: providerAuthProfileFromOpts(opts, provider),
|
|
2742
2755
|
timeoutMs: opts.timeout ? parseDuration(opts.timeout) : undefined,
|
|
2743
2756
|
configIsolation: opts.configIsolation,
|
|
2744
2757
|
account: accountFromOpts(opts)
|
|
@@ -3005,6 +3018,8 @@ program.command("run-now <idOrName>").option("--show-output", "show stdout/stder
|
|
|
3005
3018
|
print(publicRun(run, opts.showOutput), `${run.id} ${run.status}`);
|
|
3006
3019
|
if (!isJson() && opts.showOutput)
|
|
3007
3020
|
printTextOutput(run);
|
|
3021
|
+
if (run.status !== "succeeded")
|
|
3022
|
+
process.exitCode = 1;
|
|
3008
3023
|
} finally {
|
|
3009
3024
|
store.close();
|
|
3010
3025
|
}
|
package/dist/daemon/index.js
CHANGED
|
@@ -260,6 +260,11 @@ function validateTarget(value, label) {
|
|
|
260
260
|
const providers = ["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"];
|
|
261
261
|
if (!providers.includes(value.provider))
|
|
262
262
|
throw new Error(`${label}.provider must be one of ${providers.join(", ")}`);
|
|
263
|
+
if (value.authProfile !== undefined) {
|
|
264
|
+
assertString(value.authProfile, `${label}.authProfile`);
|
|
265
|
+
if (value.provider !== "codewith")
|
|
266
|
+
throw new Error(`${label}.authProfile is currently supported only for provider codewith`);
|
|
267
|
+
}
|
|
263
268
|
return value;
|
|
264
269
|
}
|
|
265
270
|
throw new Error(`${label}.type must be command or agent`);
|
|
@@ -1556,7 +1561,7 @@ function agentArgs(target) {
|
|
|
1556
1561
|
args.push(...target.extraArgs ?? [], target.prompt);
|
|
1557
1562
|
return args;
|
|
1558
1563
|
case "codewith":
|
|
1559
|
-
args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1564
|
+
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : [], "--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1560
1565
|
if (isolation === "safe")
|
|
1561
1566
|
args.push("--ignore-rules");
|
|
1562
1567
|
if (target.cwd)
|
|
@@ -2467,7 +2472,7 @@ function enableStartup(result) {
|
|
|
2467
2472
|
|
|
2468
2473
|
// src/daemon/index.ts
|
|
2469
2474
|
var program = new Command;
|
|
2470
|
-
program.name("loops-daemon").description("OpenLoops daemon helper").version("0.3.
|
|
2475
|
+
program.name("loops-daemon").description("OpenLoops daemon helper").version("0.3.2");
|
|
2471
2476
|
program.command("run").option("--interval-ms <ms>", "tick interval", (value) => Number(value)).action(async (opts) => runDaemon({ intervalMs: opts.intervalMs }));
|
|
2472
2477
|
program.command("start").action(async () => {
|
|
2473
2478
|
const result = await startDaemon({ cliEntry: process.argv[1] ?? "loops-daemon", args: ["run"] });
|
package/dist/index.js
CHANGED
|
@@ -258,6 +258,11 @@ function validateTarget(value, label) {
|
|
|
258
258
|
const providers = ["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"];
|
|
259
259
|
if (!providers.includes(value.provider))
|
|
260
260
|
throw new Error(`${label}.provider must be one of ${providers.join(", ")}`);
|
|
261
|
+
if (value.authProfile !== undefined) {
|
|
262
|
+
assertString(value.authProfile, `${label}.authProfile`);
|
|
263
|
+
if (value.provider !== "codewith")
|
|
264
|
+
throw new Error(`${label}.authProfile is currently supported only for provider codewith`);
|
|
265
|
+
}
|
|
261
266
|
return value;
|
|
262
267
|
}
|
|
263
268
|
throw new Error(`${label}.type must be command or agent`);
|
|
@@ -1546,7 +1551,7 @@ function agentArgs(target) {
|
|
|
1546
1551
|
args.push(...target.extraArgs ?? [], target.prompt);
|
|
1547
1552
|
return args;
|
|
1548
1553
|
case "codewith":
|
|
1549
|
-
args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1554
|
+
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : [], "--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1550
1555
|
if (isolation === "safe")
|
|
1551
1556
|
args.push("--ignore-rules");
|
|
1552
1557
|
if (target.cwd)
|
package/dist/lib/store.js
CHANGED
|
@@ -258,6 +258,11 @@ function validateTarget(value, label) {
|
|
|
258
258
|
const providers = ["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"];
|
|
259
259
|
if (!providers.includes(value.provider))
|
|
260
260
|
throw new Error(`${label}.provider must be one of ${providers.join(", ")}`);
|
|
261
|
+
if (value.authProfile !== undefined) {
|
|
262
|
+
assertString(value.authProfile, `${label}.authProfile`);
|
|
263
|
+
if (value.provider !== "codewith")
|
|
264
|
+
throw new Error(`${label}.authProfile is currently supported only for provider codewith`);
|
|
265
|
+
}
|
|
261
266
|
return value;
|
|
262
267
|
}
|
|
263
268
|
throw new Error(`${label}.type must be command or agent`);
|
package/dist/sdk/index.js
CHANGED
|
@@ -258,6 +258,11 @@ function validateTarget(value, label) {
|
|
|
258
258
|
const providers = ["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"];
|
|
259
259
|
if (!providers.includes(value.provider))
|
|
260
260
|
throw new Error(`${label}.provider must be one of ${providers.join(", ")}`);
|
|
261
|
+
if (value.authProfile !== undefined) {
|
|
262
|
+
assertString(value.authProfile, `${label}.authProfile`);
|
|
263
|
+
if (value.provider !== "codewith")
|
|
264
|
+
throw new Error(`${label}.authProfile is currently supported only for provider codewith`);
|
|
265
|
+
}
|
|
261
266
|
return value;
|
|
262
267
|
}
|
|
263
268
|
throw new Error(`${label}.type must be command or agent`);
|
|
@@ -1546,7 +1551,7 @@ function agentArgs(target) {
|
|
|
1546
1551
|
args.push(...target.extraArgs ?? [], target.prompt);
|
|
1547
1552
|
return args;
|
|
1548
1553
|
case "codewith":
|
|
1549
|
-
args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1554
|
+
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : [], "--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", "workspace-write", "--skip-git-repo-check");
|
|
1550
1555
|
if (isolation === "safe")
|
|
1551
1556
|
args.push("--ignore-rules");
|
|
1552
1557
|
if (target.cwd)
|
package/dist/types.d.ts
CHANGED
package/docs/USAGE.md
CHANGED
|
@@ -76,6 +76,17 @@ loops create agent supply-chain-watch \
|
|
|
76
76
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
Run a Codewith loop with a Codewith-native auth profile:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
loops create agent supply-chain-watch \
|
|
83
|
+
--provider codewith \
|
|
84
|
+
--auth-profile account001 \
|
|
85
|
+
--every 15m \
|
|
86
|
+
--cwd /path/to/repo \
|
|
87
|
+
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
88
|
+
```
|
|
89
|
+
|
|
79
90
|
For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
|
|
80
91
|
|
|
81
92
|
```bash
|
|
@@ -157,7 +168,7 @@ loops remove <id-or-name>
|
|
|
157
168
|
loops run-now <id-or-name>
|
|
158
169
|
```
|
|
159
170
|
|
|
160
|
-
Use `--json` for machine-readable output. Prompt bodies and run stdout/stderr are redacted by default in status output.
|
|
171
|
+
Use `--json` for machine-readable output. Prompt bodies and run stdout/stderr are redacted by default in status output. `loops run-now` exits non-zero when the recorded run fails or times out.
|
|
161
172
|
|
|
162
173
|
## Daemon
|
|
163
174
|
|
|
@@ -208,6 +219,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
208
219
|
- Cursor is CLI-first for now via `cursor-agent -p`; treat output as less stable until a stronger public SDK contract is selected.
|
|
209
220
|
- Codex uses `codex exec --json --ephemeral --ask-for-approval never`.
|
|
210
221
|
- 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.
|
|
222
|
+
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `exec`; they do not call OpenAccounts.
|
|
211
223
|
- Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
|
|
212
224
|
|
|
213
225
|
For production loops that can mutate repos, prefer disposable worktrees and explicit prompts that name allowed write scope.
|