@phamvuhoang/otto-core 0.1.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.
Files changed (103) hide show
  1. package/README.md +53 -0
  2. package/dist/branch.d.ts +61 -0
  3. package/dist/branch.d.ts.map +1 -0
  4. package/dist/branch.js +215 -0
  5. package/dist/branch.js.map +1 -0
  6. package/dist/cli-help.d.ts +68 -0
  7. package/dist/cli-help.d.ts.map +1 -0
  8. package/dist/cli-help.js +365 -0
  9. package/dist/cli-help.js.map +1 -0
  10. package/dist/detach.d.ts +36 -0
  11. package/dist/detach.d.ts.map +1 -0
  12. package/dist/detach.js +52 -0
  13. package/dist/detach.js.map +1 -0
  14. package/dist/gh-main.d.ts +5 -0
  15. package/dist/gh-main.d.ts.map +1 -0
  16. package/dist/gh-main.js +16 -0
  17. package/dist/gh-main.js.map +1 -0
  18. package/dist/git.d.ts +14 -0
  19. package/dist/git.d.ts.map +1 -0
  20. package/dist/git.js +50 -0
  21. package/dist/git.js.map +1 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +8 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/keepalive.d.ts +24 -0
  27. package/dist/keepalive.d.ts.map +1 -0
  28. package/dist/keepalive.js +138 -0
  29. package/dist/keepalive.js.map +1 -0
  30. package/dist/loop.d.ts +42 -0
  31. package/dist/loop.d.ts.map +1 -0
  32. package/dist/loop.js +238 -0
  33. package/dist/loop.js.map +1 -0
  34. package/dist/main.d.ts +5 -0
  35. package/dist/main.d.ts.map +1 -0
  36. package/dist/main.js +16 -0
  37. package/dist/main.js.map +1 -0
  38. package/dist/notify.d.ts +28 -0
  39. package/dist/notify.d.ts.map +1 -0
  40. package/dist/notify.js +119 -0
  41. package/dist/notify.js.map +1 -0
  42. package/dist/pacing.d.ts +8 -0
  43. package/dist/pacing.d.ts.map +1 -0
  44. package/dist/pacing.js +33 -0
  45. package/dist/pacing.js.map +1 -0
  46. package/dist/panel.d.ts +24 -0
  47. package/dist/panel.d.ts.map +1 -0
  48. package/dist/panel.js +202 -0
  49. package/dist/panel.js.map +1 -0
  50. package/dist/rate-limit.d.ts +16 -0
  51. package/dist/rate-limit.d.ts.map +1 -0
  52. package/dist/rate-limit.js +35 -0
  53. package/dist/rate-limit.js.map +1 -0
  54. package/dist/render.d.ts +8 -0
  55. package/dist/render.d.ts.map +1 -0
  56. package/dist/render.js +130 -0
  57. package/dist/render.js.map +1 -0
  58. package/dist/retry.d.ts +17 -0
  59. package/dist/retry.d.ts.map +1 -0
  60. package/dist/retry.js +34 -0
  61. package/dist/retry.js.map +1 -0
  62. package/dist/run-bin.d.ts +35 -0
  63. package/dist/run-bin.d.ts.map +1 -0
  64. package/dist/run-bin.js +241 -0
  65. package/dist/run-bin.js.map +1 -0
  66. package/dist/runner.d.ts +55 -0
  67. package/dist/runner.d.ts.map +1 -0
  68. package/dist/runner.js +297 -0
  69. package/dist/runner.js.map +1 -0
  70. package/dist/stage-exec.d.ts +16 -0
  71. package/dist/stage-exec.d.ts.map +1 -0
  72. package/dist/stage-exec.js +35 -0
  73. package/dist/stage-exec.js.map +1 -0
  74. package/dist/stages.d.ts +38 -0
  75. package/dist/stages.d.ts.map +1 -0
  76. package/dist/stages.js +38 -0
  77. package/dist/stages.js.map +1 -0
  78. package/dist/state.d.ts +25 -0
  79. package/dist/state.d.ts.map +1 -0
  80. package/dist/state.js +30 -0
  81. package/dist/state.js.map +1 -0
  82. package/dist/stream-render.d.ts +68 -0
  83. package/dist/stream-render.d.ts.map +1 -0
  84. package/dist/stream-render.js +162 -0
  85. package/dist/stream-render.js.map +1 -0
  86. package/dist/watch.d.ts +22 -0
  87. package/dist/watch.d.ts.map +1 -0
  88. package/dist/watch.js +93 -0
  89. package/dist/watch.js.map +1 -0
  90. package/package.json +67 -0
  91. package/templates/afk.md +21 -0
  92. package/templates/apply-review.md +71 -0
  93. package/templates/ghafk-issue.md +29 -0
  94. package/templates/ghafk.md +29 -0
  95. package/templates/ghprompt-workflow.md +83 -0
  96. package/templates/ghprompt.md +39 -0
  97. package/templates/prompt.md +97 -0
  98. package/templates/review-lens.md +41 -0
  99. package/templates/review-synth.md +29 -0
  100. package/templates/review-verify.md +52 -0
  101. package/templates/review.md +62 -0
  102. package/templates/superpowers.md +70 -0
  103. package/templates/verify.md +74 -0
@@ -0,0 +1,35 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { dirname, join, posix } from "node:path";
3
+ import { renderTemplate } from "./render.js";
4
+ import { DEFAULT_BACKOFF_MS, backoffFor, withRetries } from "./retry.js";
5
+ import { runStage, stageLogPath } from "./runner.js";
6
+ import { USE_COLOR, dim } from "./stream-render.js";
7
+ /** Render a stage's template (inside the retry, so flaky shell tags retry) and run it. */
8
+ export async function executeStage(opts) {
9
+ const { stage, vars, workspaceDir, packageDir, iteration, maxRetries, signal, } = opts;
10
+ const label = opts.logLabel ?? stage.name;
11
+ const spillRel = `spill-${process.pid}-${iteration}-${label}-${Date.now()}`;
12
+ const spillHostDir = join(workspaceDir, ".otto-tmp", spillRel);
13
+ const spillRefPath = posix.join(".otto-tmp", spillRel);
14
+ const stageLog = stageLogPath(workspaceDir, iteration, label);
15
+ mkdirSync(dirname(stageLog), { recursive: true });
16
+ return withRetries(() => {
17
+ const prompt = renderTemplate(join(packageDir, "templates", stage.template), vars, { cwd: workspaceDir, spillHostDir, spillRefPath });
18
+ return runStage(stage, prompt, workspaceDir, iteration, spillHostDir, stageLog, { signal });
19
+ }, {
20
+ max: maxRetries,
21
+ backoffMs: DEFAULT_BACKOFF_MS,
22
+ onAttempt: (attempt, err) => {
23
+ const wait = backoffFor(DEFAULT_BACKOFF_MS, attempt);
24
+ const marker = `[retry] attempt ${attempt} of ${maxRetries} after ${wait} ms`;
25
+ process.stderr.write(`${USE_COLOR ? dim(marker) : marker} ${dim("(" + err.message + ")")}\n`);
26
+ try {
27
+ appendFileSync(stageLog, marker + "\n");
28
+ }
29
+ catch {
30
+ // log file may be unwritable; never crash on the marker.
31
+ }
32
+ },
33
+ });
34
+ }
35
+ //# sourceMappingURL=stage-exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage-exec.js","sourceRoot":"","sources":["../src/stage-exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAoB,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAepD,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAyB;IAEzB,MAAM,EACJ,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAU,EACV,MAAM,GACP,GAAG,IAAI,CAAC;IACT,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC;IAC1C,MAAM,QAAQ,GAAG,SAAS,OAAO,CAAC,GAAG,IAAI,SAAS,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC5E,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,YAAY,CAAC,YAAY,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC9D,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAElD,OAAO,WAAW,CAChB,GAAG,EAAE;QACH,MAAM,MAAM,GAAG,cAAc,CAC3B,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,EAC7C,IAAI,EACJ,EAAE,GAAG,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,CAClD,CAAC;QACF,OAAO,QAAQ,CACb,KAAK,EACL,MAAM,EACN,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,EAAE,MAAM,EAAE,CACX,CAAC;IACJ,CAAC,EACD;QACE,GAAG,EAAE,UAAU;QACf,SAAS,EAAE,kBAAkB;QAC7B,SAAS,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;YAC1B,MAAM,IAAI,GAAG,UAAU,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;YACrD,MAAM,MAAM,GAAG,mBAAmB,OAAO,OAAO,UAAU,UAAU,IAAI,KAAK,CAAC;YAC9E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,GAAI,GAAa,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CACnF,CAAC;YACF,IAAI,CAAC;gBACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,yDAAyD;YAC3D,CAAC;QACH,CAAC;KACF,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,38 @@
1
+ export type Stage = {
2
+ name: string;
3
+ template: string;
4
+ permissionMode?: string;
5
+ };
6
+ export declare const STAGES: {
7
+ implementer: {
8
+ name: string;
9
+ template: string;
10
+ permissionMode: string;
11
+ };
12
+ ghafkImplementer: {
13
+ name: string;
14
+ template: string;
15
+ permissionMode: string;
16
+ };
17
+ ghafkIssueImplementer: {
18
+ name: string;
19
+ template: string;
20
+ permissionMode: string;
21
+ };
22
+ verifier: {
23
+ name: string;
24
+ template: string;
25
+ permissionMode: string;
26
+ };
27
+ applyReviewImplementer: {
28
+ name: string;
29
+ template: string;
30
+ permissionMode: string;
31
+ };
32
+ reviewer: {
33
+ name: string;
34
+ template: string;
35
+ permissionMode: string;
36
+ };
37
+ };
38
+ //# sourceMappingURL=stages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stages.d.ts","sourceRoot":"","sources":["../src/stages.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAOF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlB,CAAC"}
package/dist/stages.js ADDED
@@ -0,0 +1,38 @@
1
+ // Every stage runs `claude --permission-mode bypassPermissions` so bash + edits
2
+ // auto-approve for non-interactive AFK. Blast radius is bounded by the runner
3
+ // (see resolveRunner in runner.ts): the default `sandbox` runner confines writes
4
+ // to the workspace via the native OS sandbox; `OTTO_RUNNER=host` runs unsandboxed
5
+ // (git-recoverable workspace only). See the spec under docs/superpowers/specs/.
6
+ export const STAGES = {
7
+ implementer: {
8
+ name: "implementer",
9
+ template: "afk.md",
10
+ permissionMode: "bypassPermissions",
11
+ },
12
+ ghafkImplementer: {
13
+ name: "ghafk-implementer",
14
+ template: "ghafk.md",
15
+ permissionMode: "bypassPermissions",
16
+ },
17
+ ghafkIssueImplementer: {
18
+ name: "ghafk-issue-implementer",
19
+ template: "ghafk-issue.md",
20
+ permissionMode: "bypassPermissions",
21
+ },
22
+ verifier: {
23
+ name: "verifier",
24
+ template: "verify.md",
25
+ permissionMode: "bypassPermissions",
26
+ },
27
+ applyReviewImplementer: {
28
+ name: "apply-review-implementer",
29
+ template: "apply-review.md",
30
+ permissionMode: "bypassPermissions",
31
+ },
32
+ reviewer: {
33
+ name: "reviewer",
34
+ template: "review.md",
35
+ permissionMode: "bypassPermissions",
36
+ },
37
+ };
38
+ //# sourceMappingURL=stages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stages.js","sourceRoot":"","sources":["../src/stages.ts"],"names":[],"mappings":"AAMA,gFAAgF;AAChF,8EAA8E;AAC9E,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,QAAQ;QAClB,cAAc,EAAE,mBAAmB;KACpB;IACjB,gBAAgB,EAAE;QAChB,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE,UAAU;QACpB,cAAc,EAAE,mBAAmB;KACpB;IACjB,qBAAqB,EAAE;QACrB,IAAI,EAAE,yBAAyB;QAC/B,QAAQ,EAAE,gBAAgB;QAC1B,cAAc,EAAE,mBAAmB;KACpB;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,WAAW;QACrB,cAAc,EAAE,mBAAmB;KACpB;IACjB,sBAAsB,EAAE;QACtB,IAAI,EAAE,0BAA0B;QAChC,QAAQ,EAAE,iBAAiB;QAC3B,cAAc,EAAE,mBAAmB;KACpB;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,WAAW;QACrB,cAAc,EAAE,mBAAmB;KACpB;CAClB,CAAC"}
@@ -0,0 +1,25 @@
1
+ export type RunStatus = "running" | "waiting-rate-limit" | "interrupted" | "complete";
2
+ export type RunState = {
3
+ bin: string;
4
+ mode: string;
5
+ inputs: string;
6
+ iteration: number;
7
+ of: number;
8
+ status: RunStatus;
9
+ resetsAt?: number | null;
10
+ startedAt: string;
11
+ updatedAt: string;
12
+ };
13
+ /** Read .otto/state.json. Absent or malformed → null (never throws). */
14
+ export declare function readState(workspaceDir: string): RunState | null;
15
+ /** Write .otto/state.json (creates .otto/). */
16
+ export declare function writeState(workspaceDir: string, s: RunState): void;
17
+ /** Remove .otto/state.json; ignore if absent. */
18
+ export declare function clearState(workspaceDir: string): void;
19
+ /** Resume iff a prior unfinished run matches this invocation's identity. */
20
+ export declare function matchesResume(prev: RunState | null, cur: {
21
+ bin: string;
22
+ mode: string;
23
+ inputs: string;
24
+ }): boolean;
25
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,oBAAoB,GACpB,aAAa,GACb,UAAU,CAAC;AAEf,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAIF,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAQ/D;AAED,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAMlE;AAED,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAErD;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,GAAG,IAAI,EACrB,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAQT"}
package/dist/state.js ADDED
@@ -0,0 +1,30 @@
1
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const STATE_REL = join(".otto", "state.json");
4
+ /** Read .otto/state.json. Absent or malformed → null (never throws). */
5
+ export function readState(workspaceDir) {
6
+ try {
7
+ return JSON.parse(readFileSync(join(workspaceDir, STATE_REL), "utf8"));
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ /** Write .otto/state.json (creates .otto/). */
14
+ export function writeState(workspaceDir, s) {
15
+ mkdirSync(join(workspaceDir, ".otto"), { recursive: true });
16
+ writeFileSync(join(workspaceDir, STATE_REL), JSON.stringify(s, null, 2) + "\n");
17
+ }
18
+ /** Remove .otto/state.json; ignore if absent. */
19
+ export function clearState(workspaceDir) {
20
+ rmSync(join(workspaceDir, STATE_REL), { force: true });
21
+ }
22
+ /** Resume iff a prior unfinished run matches this invocation's identity. */
23
+ export function matchesResume(prev, cur) {
24
+ return (prev != null &&
25
+ prev.status !== "complete" &&
26
+ prev.bin === cur.bin &&
27
+ prev.mode === cur.mode &&
28
+ prev.inputs === cur.inputs);
29
+ }
30
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAoBjC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AAE9C,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,YAAoB;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CACf,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CACxC,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,CAAW;IAC1D,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,aAAa,CACX,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,EAC7B,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAClC,CAAC;AACJ,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,UAAU,CAAC,YAAoB;IAC7C,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAC3B,IAAqB,EACrB,GAAkD;IAElD,OAAO,CACL,IAAI,IAAI,IAAI;QACZ,IAAI,CAAC,MAAM,KAAK,UAAU;QAC1B,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG;QACpB,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI;QACtB,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,CAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Terminal pretty-printer for the Claude CLI's NDJSON stream, plus the TTY-gated
3
+ * ANSI styling primitives. Extracted from runner.ts: this module has no runner
4
+ * dependency — `renderEvent` consumes an already-parsed stream event and writes
5
+ * assistant text to stdout and tool/diagnostic events to stderr.
6
+ */
7
+ /** Controls ANSI codes on stderr (tool events, banners, subprocess output). */
8
+ export declare const USE_COLOR: boolean;
9
+ export declare const dim: (s: string) => string;
10
+ export declare const bold: (s: string) => string;
11
+ export declare const green: (s: string) => string;
12
+ export declare const red: (s: string) => string;
13
+ export declare const boldOut: (s: string) => string;
14
+ export declare const greenOut: (s: string) => string;
15
+ export declare const dimOut: (s: string) => string;
16
+ export declare const SYM: {
17
+ bullet: string;
18
+ cont: string;
19
+ check: string;
20
+ cross: string;
21
+ rule: string;
22
+ ellip: string;
23
+ };
24
+ export declare const SYM_OUT: {
25
+ bullet: string;
26
+ };
27
+ type AssistantBlock = {
28
+ type: string;
29
+ text?: string;
30
+ name?: string;
31
+ input?: unknown;
32
+ id?: string;
33
+ };
34
+ type UserBlock = {
35
+ type: string;
36
+ content?: unknown;
37
+ is_error?: boolean;
38
+ tool_use_id?: string;
39
+ };
40
+ export type StreamJson = {
41
+ type: "assistant";
42
+ message?: {
43
+ content?: AssistantBlock[];
44
+ };
45
+ } | {
46
+ type: "user";
47
+ message?: {
48
+ content?: UserBlock[];
49
+ };
50
+ } | {
51
+ type: "system";
52
+ subtype?: string;
53
+ [k: string]: unknown;
54
+ } | {
55
+ type: "result";
56
+ result?: string;
57
+ is_error?: boolean;
58
+ } | {
59
+ type: string;
60
+ [k: string]: unknown;
61
+ };
62
+ export type ToolTrack = {
63
+ name: string;
64
+ startedAt: number;
65
+ };
66
+ export declare function renderEvent(ev: StreamJson, toolMap: Map<string, ToolTrack>): void;
67
+ export {};
68
+ //# sourceMappingURL=stream-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-render.d.ts","sourceRoot":"","sources":["../src/stream-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,+EAA+E;AAC/E,eAAO,MAAM,SAAS,SAAiD,CAAC;AAWxE,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAmB,CAAC;AACpD,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,KAAG,MAAmB,CAAC;AAErD,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,KAAG,MAAoB,CAAC;AACvD,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAoB,CAAC;AACrD,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,KAAG,MAAsB,CAAC;AAE3D,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,KAAG,MAAuB,CAAC;AAC7D,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,KAAG,MAAsB,CAAC;AAE1D,eAAO,MAAM,GAAG;;;;;;;CASX,CAAC;AAEN,eAAO,MAAM,OAAO;;CAAuD,CAAC;AAE5E,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AACF,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AACF,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,SAAS,EAAE,CAAA;KAAE,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE3C,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,wBAAgB,WAAW,CACzB,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAC9B,IAAI,CAqFN"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Terminal pretty-printer for the Claude CLI's NDJSON stream, plus the TTY-gated
3
+ * ANSI styling primitives. Extracted from runner.ts: this module has no runner
4
+ * dependency — `renderEvent` consumes an already-parsed stream event and writes
5
+ * assistant text to stdout and tool/diagnostic events to stderr.
6
+ */
7
+ const TOOL_INPUT_PREVIEW = 200;
8
+ const TOOL_RESULT_PREVIEW = 120;
9
+ const TOOL_ERROR_PREVIEW = 400;
10
+ /* ── TTY-gated styling ────────────────────────────────────────────────── */
11
+ const NO_COLOR_ENV = process.env.NO_COLOR != null || process.env.TERM === "dumb";
12
+ /** Controls ANSI codes on stderr (tool events, banners, subprocess output). */
13
+ export const USE_COLOR = process.stderr.isTTY === true && !NO_COLOR_ENV;
14
+ /** Controls ANSI codes on stdout (assistant text bullets, completion line).
15
+ * Separate from USE_COLOR so `otto-ghafk 1 > out.txt` stays clean even
16
+ * when stderr is still a TTY. */
17
+ const USE_COLOR_STDOUT = process.stdout.isTTY === true && !NO_COLOR_ENV;
18
+ const c = (code, s) => USE_COLOR ? `\x1b[${code}m${s}\x1b[0m` : s;
19
+ const cOut = (code, s) => USE_COLOR_STDOUT ? `\x1b[${code}m${s}\x1b[0m` : s;
20
+ export const dim = (s) => c("2", s);
21
+ export const bold = (s) => c("1", s);
22
+ const cyan = (s) => c("36", s);
23
+ export const green = (s) => c("32", s);
24
+ export const red = (s) => c("31", s);
25
+ export const boldOut = (s) => cOut("1", s);
26
+ const cyanOut = (s) => cOut("36", s);
27
+ export const greenOut = (s) => cOut("32", s);
28
+ export const dimOut = (s) => cOut("2", s);
29
+ export const SYM = USE_COLOR
30
+ ? { bullet: "●", cont: "⎿", check: "✓", cross: "✗", rule: "━", ellip: "…" }
31
+ : {
32
+ bullet: "*",
33
+ cont: " >",
34
+ check: "ok",
35
+ cross: "FAIL",
36
+ rule: "=",
37
+ ellip: "...",
38
+ };
39
+ export const SYM_OUT = USE_COLOR_STDOUT ? { bullet: "●" } : { bullet: "*" };
40
+ export function renderEvent(ev, toolMap) {
41
+ switch (ev.type) {
42
+ case "system": {
43
+ const sub = ev.subtype;
44
+ if (sub === "init") {
45
+ const model = ev.model ?? "?";
46
+ const cwd = ev.cwd ?? "?";
47
+ process.stderr.write(`${dim("───")} ${bold("init")} ${dim(`model=${model} cwd=${cwd}`)}\n`);
48
+ }
49
+ return;
50
+ }
51
+ case "assistant": {
52
+ const content = ev.message?.content ??
53
+ [];
54
+ for (const block of content) {
55
+ if (block.type === "text" && typeof block.text === "string") {
56
+ const lines = block.text.split("\n");
57
+ const formatted = lines
58
+ .map((l, idx) => idx === 0 ? `${boldOut(cyanOut(SYM_OUT.bullet))} ${l}` : ` ${l}`)
59
+ .join("\r\n");
60
+ process.stdout.write(formatted + "\r\n\n");
61
+ }
62
+ else if (block.type === "thinking") {
63
+ process.stderr.write(`${dim(SYM.bullet + " thinking" + SYM.ellip)}\n`);
64
+ }
65
+ else if (block.type === "tool_use") {
66
+ const name = block.name ?? "?";
67
+ const preview = previewInput(name, block.input);
68
+ if (block.id) {
69
+ toolMap.set(block.id, { name, startedAt: Date.now() });
70
+ }
71
+ process.stderr.write(`${cyan(SYM.bullet)} ${bold(name)} ${dim(preview)}\n`);
72
+ }
73
+ }
74
+ return;
75
+ }
76
+ case "user": {
77
+ const content = ev.message?.content ?? [];
78
+ for (const block of content) {
79
+ if (block.type !== "tool_result")
80
+ continue;
81
+ const text = stringifyToolResult(block.content);
82
+ const tracked = block.tool_use_id
83
+ ? toolMap.get(block.tool_use_id)
84
+ : undefined;
85
+ const toolName = tracked?.name ?? "tool";
86
+ const elapsed = tracked ? ` (${Date.now() - tracked.startedAt}ms)` : "";
87
+ if (block.tool_use_id)
88
+ toolMap.delete(block.tool_use_id);
89
+ if (block.is_error) {
90
+ const snippet = text
91
+ .replace(/\s+/g, " ")
92
+ .trim()
93
+ .slice(0, TOOL_ERROR_PREVIEW);
94
+ process.stderr.write(`${dim(SYM.cont)} ${red(SYM.cross)} ${bold(toolName)}${red(" failed")}\n ${red(snippet)}${text.length > snippet.length ? " " + SYM.ellip : ""}\n`);
95
+ }
96
+ else {
97
+ const snippet = text
98
+ .replace(/\s+/g, " ")
99
+ .trim()
100
+ .slice(0, TOOL_RESULT_PREVIEW);
101
+ process.stderr.write(`${dim(SYM.cont)} ${green(SYM.check)} ${bold(toolName)}${dim(elapsed)} ${dim(snippet)}${text.length > snippet.length ? " " + SYM.ellip : ""}\n`);
102
+ }
103
+ }
104
+ return;
105
+ }
106
+ case "result": {
107
+ const isError = ev.is_error;
108
+ if (isError)
109
+ process.stderr.write(`${red(SYM.bullet + " result errored")}\n`);
110
+ return;
111
+ }
112
+ default:
113
+ return;
114
+ }
115
+ }
116
+ function previewInput(toolName, input) {
117
+ if (input == null || typeof input !== "object")
118
+ return "";
119
+ const obj = input;
120
+ // Pick the most informative field per tool.
121
+ const keyOrder = {
122
+ Bash: ["command"],
123
+ Edit: ["file_path"],
124
+ Write: ["file_path"],
125
+ Read: ["file_path"],
126
+ Glob: ["pattern", "path"],
127
+ Grep: ["pattern", "path"],
128
+ TodoWrite: [],
129
+ };
130
+ const keys = keyOrder[toolName] ?? Object.keys(obj).slice(0, 2);
131
+ const parts = [];
132
+ for (const k of keys) {
133
+ const v = obj[k];
134
+ if (v == null)
135
+ continue;
136
+ const s = typeof v === "string" ? v : JSON.stringify(v);
137
+ parts.push(`${k}=${truncate(s, TOOL_INPUT_PREVIEW)}`);
138
+ }
139
+ return parts.join(" ");
140
+ }
141
+ function stringifyToolResult(content) {
142
+ if (typeof content === "string")
143
+ return content;
144
+ if (Array.isArray(content)) {
145
+ return content
146
+ .map((c) => {
147
+ if (typeof c === "string")
148
+ return c;
149
+ if (c && typeof c === "object" && "text" in c)
150
+ return String(c.text ?? "");
151
+ return JSON.stringify(c);
152
+ })
153
+ .join("\n");
154
+ }
155
+ return JSON.stringify(content ?? "");
156
+ }
157
+ function truncate(s, max) {
158
+ if (s.length <= max)
159
+ return s;
160
+ return s.slice(0, max - 1) + SYM.ellip;
161
+ }
162
+ //# sourceMappingURL=stream-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-render.js","sourceRoot":"","sources":["../src/stream-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Stage } from "./stages.js";
2
+ /** Count open issues carrying `label`, via gh. Returns 0 on any failure (keep polling). */
3
+ export declare function openIssueCount(label: string, cwd: string): number;
4
+ export type RunWatchOptions = {
5
+ stages: [Stage, ...Stage[]];
6
+ iterations: number;
7
+ workspaceDir: string;
8
+ packageDir: string;
9
+ watchIntervalSec: number;
10
+ watchLabel: string;
11
+ budgetUsd?: number;
12
+ cooldownMs?: number;
13
+ maxRetries?: number;
14
+ reviewLenses?: string[];
15
+ notify?: boolean;
16
+ bin?: string;
17
+ cliVersion?: string;
18
+ /** Injectable for tests; defaults to openIssueCount. */
19
+ countIssues?: (label: string, cwd: string) => number;
20
+ };
21
+ export declare function runWatch(opts: RunWatchOptions): Promise<void>;
22
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CA0BjE;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD,CAAC;AAEF,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFnE"}
package/dist/watch.js ADDED
@@ -0,0 +1,93 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { acquire } from "./keepalive.js";
3
+ import { runLoop } from "./loop.js";
4
+ import { notifyComplete, notifyError } from "./notify.js";
5
+ import { sleep } from "./pacing.js";
6
+ import { bold, dim, greenOut, boldOut, dimOut, SYM_OUT, USE_COLOR, } from "./stream-render.js";
7
+ /** Count open issues carrying `label`, via gh. Returns 0 on any failure (keep polling). */
8
+ export function openIssueCount(label, cwd) {
9
+ try {
10
+ // execFileSync (no shell) so `label` is passed as a literal argv entry — a
11
+ // value like `$(rm -rf ~)` can never be shell-evaluated. See SECURITY.md.
12
+ const out = execFileSync("gh", [
13
+ "issue",
14
+ "list",
15
+ "--state",
16
+ "open",
17
+ "--label",
18
+ label,
19
+ "--json",
20
+ "number",
21
+ ], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
22
+ const arr = JSON.parse(out);
23
+ return Array.isArray(arr) ? arr.length : 0;
24
+ }
25
+ catch {
26
+ process.stderr.write(`${dim(`gh issue poll failed (label ${label}) — treating as no work`)}\n`);
27
+ return 0;
28
+ }
29
+ }
30
+ export async function runWatch(opts) {
31
+ const { stages, iterations, workspaceDir, packageDir, watchIntervalSec, watchLabel, budgetUsd, cooldownMs, maxRetries, reviewLenses, notify = false, bin = "otto-ghafk", countIssues = openIssueCount, } = opts;
32
+ const releaser = acquire({ reason: `${bin} watch` });
33
+ let released = false;
34
+ const releaseOnce = () => {
35
+ if (!released) {
36
+ released = true;
37
+ releaser.release();
38
+ }
39
+ };
40
+ const daemonAbort = new AbortController();
41
+ const onSig = (code) => () => {
42
+ daemonAbort.abort();
43
+ if (notify)
44
+ notifyError(`watch stopped (signal)`);
45
+ releaseOnce();
46
+ process.exit(code);
47
+ };
48
+ const onSigint = onSig(130);
49
+ const onSigterm = onSig(143);
50
+ process.on("SIGINT", onSigint);
51
+ process.on("SIGTERM", onSigterm);
52
+ process.stderr.write(`${USE_COLOR ? dim("watching") + " " + bold(`label:${watchLabel} every ${watchIntervalSec}s`) : `watching label:${watchLabel} every ${watchIntervalSec}s`}\n`);
53
+ let cumulativeCost = 0;
54
+ try {
55
+ for (;;) {
56
+ if (budgetUsd != null && cumulativeCost >= budgetUsd) {
57
+ process.stdout.write(`${greenOut(SYM_OUT.bullet)} ${boldOut("watch budget reached")}${dimOut(` $${cumulativeCost.toFixed(2)} ≥ $${budgetUsd.toFixed(2)} — stopping`)}\n`);
58
+ if (notify)
59
+ notifyComplete(0, false);
60
+ return;
61
+ }
62
+ const count = countIssues(watchLabel, workspaceDir);
63
+ if (count > 0) {
64
+ process.stderr.write(`${dim(`${count} open issue(s) labelled ${watchLabel} — running loop`)}\n`);
65
+ const remaining = budgetUsd != null ? budgetUsd - cumulativeCost : undefined;
66
+ const outcome = await runLoop({
67
+ stages,
68
+ inputs: "",
69
+ iterations,
70
+ workspaceDir,
71
+ packageDir,
72
+ budgetUsd: remaining,
73
+ cooldownMs,
74
+ maxRetries,
75
+ reviewLenses,
76
+ noKeepAlive: true,
77
+ signal: daemonAbort.signal,
78
+ bin,
79
+ cliVersion: opts.cliVersion,
80
+ });
81
+ cumulativeCost += outcome.costUsd;
82
+ process.stderr.write(`${dim(`watch run done — cumulative $${cumulativeCost.toFixed(2)}`)}\n`);
83
+ }
84
+ await sleep(watchIntervalSec * 1000, daemonAbort.signal);
85
+ }
86
+ }
87
+ finally {
88
+ process.off("SIGINT", onSigint);
89
+ process.off("SIGTERM", onSigterm);
90
+ releaseOnce();
91
+ }
92
+ }
93
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@phamvuhoang/otto-core",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code AFK orchestration: iteration loop, native-sandbox runner, template renderer.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Henry Pham <phamvuhoang@gmail.com>",
8
+ "homepage": "https://github.com/phamvuhoang/otto/tree/main/packages/core#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/phamvuhoang/otto.git",
12
+ "directory": "packages/core"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/phamvuhoang/otto/issues"
16
+ },
17
+ "keywords": [
18
+ "claude-code",
19
+ "claude",
20
+ "ai-agent",
21
+ "afk",
22
+ "automation",
23
+ "code-review",
24
+ "orchestration"
25
+ ],
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ },
33
+ "./loop": {
34
+ "types": "./dist/loop.d.ts",
35
+ "import": "./dist/loop.js"
36
+ },
37
+ "./runner": {
38
+ "types": "./dist/runner.d.ts",
39
+ "import": "./dist/runner.js"
40
+ },
41
+ "./stages": {
42
+ "types": "./dist/stages.d.ts",
43
+ "import": "./dist/stages.js"
44
+ }
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "templates",
49
+ "README.md"
50
+ ],
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.json",
53
+ "clean": "node -e \"const fs=require('node:fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true})\"",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit",
55
+ "test": "vitest run",
56
+ "prepublishOnly": "pnpm run clean && pnpm run build"
57
+ },
58
+ "engines": {
59
+ "node": ">=20"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "devDependencies": {
65
+ "vitest": "^4.1.7"
66
+ }
67
+ }