@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 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.1");
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
  }
@@ -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.1");
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
@@ -44,6 +44,7 @@ export interface AgentTarget {
44
44
  cwd?: string;
45
45
  model?: string;
46
46
  agent?: string;
47
+ authProfile?: string;
47
48
  extraArgs?: string[];
48
49
  timeoutMs?: number;
49
50
  configIsolation?: AgentConfigIsolation;
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
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",