@markjaquith/agency 1.3.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.
package/cli.ts CHANGED
@@ -409,11 +409,17 @@ const commands: Record<string, Command> = {
409
409
  loop: {
410
410
  name: "loop",
411
411
  description: "Run a Ralph Wiggum loop to complete all tasks",
412
- run: async (_args: string[], options: Record<string, any>) => {
412
+ run: async (args: string[], options: Record<string, any>) => {
413
413
  if (options.help) {
414
414
  console.log(loopHelp)
415
415
  return
416
416
  }
417
+
418
+ // Extract extra args (anything after --)
419
+ // Note: parseArgs with strict:false and allowPositionals:true will put
420
+ // args after -- in the positionals array
421
+ const extraArgs = args.length > 0 ? args : undefined
422
+
417
423
  await runCommand(
418
424
  loop({
419
425
  silent: options.silent,
@@ -422,6 +428,7 @@ const commands: Record<string, Command> = {
422
428
  minLoops: options["min-loops"],
423
429
  opencode: options.opencode,
424
430
  claude: options.claude,
431
+ extraArgs,
425
432
  }),
426
433
  )
427
434
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Manages personal agents files",
5
5
  "license": "MIT",
6
6
  "author": "Mark Jaquith",
@@ -523,4 +523,99 @@ describe("loop command", () => {
523
523
  expect(capturedOptions.stderr).toBe("pipe")
524
524
  })
525
525
  })
526
+
527
+ describe("extra args pass-through", () => {
528
+ test("passes extra args to opencode", async () => {
529
+ const taskPath = join(tempDir, "TASK.md")
530
+ writeFileSync(taskPath, "# Test Task\n\n- [x] Already done")
531
+
532
+ let capturedArgs: string[] = []
533
+ const restore = mockCliTools({
534
+ onSpawn: (args) => {
535
+ capturedArgs = args
536
+ return {
537
+ exited: Promise.resolve(0),
538
+ exitCode: 0,
539
+ stdout: createEmptyStream(),
540
+ stderr: createEmptyStream(),
541
+ }
542
+ },
543
+ })
544
+
545
+ await runTestEffect(
546
+ loop({
547
+ silent: true,
548
+ maxLoops: 1,
549
+ extraArgs: ["--agent", "deep"],
550
+ }),
551
+ )
552
+ restore()
553
+
554
+ expect(capturedArgs[0]).toBe("opencode")
555
+ expect(capturedArgs[1]).toBe("run")
556
+ expect(capturedArgs[2]).toContain("Find the next logical task")
557
+ expect(capturedArgs.slice(-2)).toEqual(["--agent", "deep"])
558
+ })
559
+
560
+ test("passes extra args to claude", async () => {
561
+ const taskPath = join(tempDir, "TASK.md")
562
+ writeFileSync(taskPath, "# Test Task\n\n- [x] Already done")
563
+
564
+ let capturedArgs: string[] = []
565
+ const restore = mockCliTools({
566
+ hasClaude: true,
567
+ onSpawn: (args) => {
568
+ capturedArgs = args
569
+ return {
570
+ exited: Promise.resolve(0),
571
+ exitCode: 0,
572
+ stdout: createEmptyStream(),
573
+ stderr: createEmptyStream(),
574
+ }
575
+ },
576
+ })
577
+
578
+ await runTestEffect(
579
+ loop({
580
+ silent: true,
581
+ maxLoops: 1,
582
+ claude: true,
583
+ extraArgs: ["--arbitrary", "switches"],
584
+ }),
585
+ )
586
+ restore()
587
+
588
+ expect(capturedArgs[0]).toBe("claude")
589
+ expect(capturedArgs[1]).toContain("Find the next logical task")
590
+ expect(capturedArgs.slice(-2)).toEqual(["--arbitrary", "switches"])
591
+ })
592
+
593
+ test("works without extra args", async () => {
594
+ const taskPath = join(tempDir, "TASK.md")
595
+ writeFileSync(taskPath, "# Test Task\n\n- [x] Already done")
596
+
597
+ let capturedArgs: string[] = []
598
+ const restore = mockCliTools({
599
+ onSpawn: (args) => {
600
+ capturedArgs = args
601
+ return {
602
+ exited: Promise.resolve(0),
603
+ exitCode: 0,
604
+ stdout: createEmptyStream(),
605
+ stderr: createEmptyStream(),
606
+ }
607
+ },
608
+ })
609
+
610
+ await runTestEffect(loop({ silent: true, maxLoops: 1 }))
611
+ restore()
612
+
613
+ // Should just have the base args without extra args
614
+ expect(capturedArgs).toEqual([
615
+ "opencode",
616
+ "run",
617
+ expect.stringContaining("Find the next logical task"),
618
+ ])
619
+ })
620
+ })
526
621
  })
@@ -31,6 +31,10 @@ interface LoopOptions extends BaseCommandOptions {
31
31
  * Force use of Claude Code CLI
32
32
  */
33
33
  claude?: boolean
34
+ /**
35
+ * Additional arguments to pass to the CLI tool
36
+ */
37
+ extraArgs?: string[]
34
38
  }
35
39
 
36
40
  interface LoopState {
@@ -193,6 +197,7 @@ export const loop = (options: LoopOptions = {}) =>
193
197
  gitRoot,
194
198
  options.verbose ?? false,
195
199
  verboseLog,
200
+ options.extraArgs,
196
201
  ).pipe(
197
202
  Effect.map(() => true),
198
203
  Effect.catchAll(() => Effect.succeed(false)),
@@ -210,6 +215,7 @@ export const loop = (options: LoopOptions = {}) =>
210
215
  gitRoot,
211
216
  options.verbose ?? false,
212
217
  verboseLog,
218
+ options.extraArgs,
213
219
  ).pipe(
214
220
  Effect.map(() => true),
215
221
  Effect.catchAll(() => Effect.succeed(false)),
@@ -317,6 +323,7 @@ function runHarness(
317
323
  gitRoot: string,
318
324
  verbose: boolean,
319
325
  verboseLog: (msg: string) => void,
326
+ extraArgs?: string[],
320
327
  ): Effect.Effect<void, Error> {
321
328
  return Effect.gen(function* () {
322
329
  const prompt =
@@ -326,9 +333,13 @@ function runHarness(
326
333
  // claude: use `claude "prompt"` (positional argument)
327
334
  const baseArgs = useOpencode ? [cliName, "run", prompt] : [cliName, prompt]
328
335
 
336
+ // Append extra args if provided
337
+ const cliArgs =
338
+ extraArgs && extraArgs.length > 0 ? [...baseArgs, ...extraArgs] : baseArgs
339
+
329
340
  // Log the command being run when verbose
330
341
  // Quote arguments that contain spaces for proper display
331
- const quotedArgs = baseArgs.map((arg) =>
342
+ const quotedArgs = cliArgs.map((arg) =>
332
343
  arg.includes(" ") ? `"${arg}"` : arg,
333
344
  )
334
345
  verboseLog(`Running: ${quotedArgs.join(" ")}`)
@@ -336,7 +347,7 @@ function runHarness(
336
347
  // When verbose, stream output directly to the terminal
337
348
  // When not verbose, capture output silently
338
349
  // Always inherit stdin so the process doesn't hang waiting for input
339
- const result = yield* spawnProcess(baseArgs, {
350
+ const result = yield* spawnProcess(cliArgs, {
340
351
  cwd: gitRoot,
341
352
  stdin: "inherit",
342
353
  stdout: verbose ? "inherit" : "pipe",
@@ -356,7 +367,7 @@ function runHarness(
356
367
  }
357
368
 
358
369
  export const help = `
359
- Usage: agency loop [options]
370
+ Usage: agency loop [options] [-- extra-args...]
360
371
 
361
372
  Run a Ralph Wiggum loop that repeatedly invokes the harness (opencode or claude)
362
373
  to work on tasks defined in TASK.md until all tasks are complete.
@@ -377,15 +388,19 @@ Options:
377
388
  -s, --silent Suppress output messages
378
389
  -v, --verbose Stream harness output to terminal
379
390
 
391
+ Pass-through Arguments:
392
+ Use -- to pass additional arguments to the underlying CLI tool.
393
+ Everything after -- will be forwarded to opencode or claude.
394
+
380
395
  Examples:
381
- agency loop # Run until all tasks are done
382
- agency loop --max-loops 5 # Stop after 5 iterations
383
- agency loop --min-loops 3 # Require at least 3 iterations
384
- agency loop --min-loops 3 --max-loops 10 # Between 3-10 iterations
396
+ agency loop # Run until all tasks are done
397
+ agency loop --max-loops 5 # Stop after 5 iterations
398
+ agency loop -- --agent deep # Pass --agent deep to harness
385
399
 
386
400
  Notes:
387
401
  - Requires TASK.md to exist (run 'agency task' first)
388
402
  - Each iteration calls the harness which handles its own commit
389
403
  - The loop validates that completion claims match actual task status
390
404
  - Use --min-loops as a safeguard if you know the minimum work required
405
+ - Arguments after -- are passed directly to the underlying tool
391
406
  `