@lawrence369/loop-cli 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 (105) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -0
  4. package/dist/agent/activity.d.ts +64 -0
  5. package/dist/agent/activity.js +265 -0
  6. package/dist/agent/launcher.d.ts +42 -0
  7. package/dist/agent/launcher.js +243 -0
  8. package/dist/agent/pty-session.d.ts +113 -0
  9. package/dist/agent/pty-session.js +490 -0
  10. package/dist/agent/ready-detector.d.ts +46 -0
  11. package/dist/agent/ready-detector.js +86 -0
  12. package/dist/agent/wrapper.d.ts +18 -0
  13. package/dist/agent/wrapper.js +110 -0
  14. package/dist/bin/lclaude.d.ts +3 -0
  15. package/dist/bin/lclaude.js +7 -0
  16. package/dist/bin/lcodex.d.ts +3 -0
  17. package/dist/bin/lcodex.js +7 -0
  18. package/dist/bin/lgemini.d.ts +3 -0
  19. package/dist/bin/lgemini.js +7 -0
  20. package/dist/bus/daemon.d.ts +56 -0
  21. package/dist/bus/daemon.js +135 -0
  22. package/dist/bus/event-bus.d.ts +105 -0
  23. package/dist/bus/event-bus.js +157 -0
  24. package/dist/bus/message.d.ts +48 -0
  25. package/dist/bus/message.js +129 -0
  26. package/dist/bus/queue.d.ts +50 -0
  27. package/dist/bus/queue.js +100 -0
  28. package/dist/bus/store.d.ts +88 -0
  29. package/dist/bus/store.js +212 -0
  30. package/dist/bus/subscriber.d.ts +76 -0
  31. package/dist/bus/subscriber.js +187 -0
  32. package/dist/config/index.d.ts +8 -0
  33. package/dist/config/index.js +72 -0
  34. package/dist/config/schema.d.ts +18 -0
  35. package/dist/config/schema.js +58 -0
  36. package/dist/core/conversation.d.ts +34 -0
  37. package/dist/core/conversation.js +289 -0
  38. package/dist/core/engine.d.ts +40 -0
  39. package/dist/core/engine.js +288 -0
  40. package/dist/core/loop.d.ts +33 -0
  41. package/dist/core/loop.js +209 -0
  42. package/dist/core/protocol.d.ts +60 -0
  43. package/dist/core/protocol.js +162 -0
  44. package/dist/core/scoring.d.ts +34 -0
  45. package/dist/core/scoring.js +69 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.js +408 -0
  48. package/dist/orchestrator/daemon.d.ts +74 -0
  49. package/dist/orchestrator/daemon.js +294 -0
  50. package/dist/orchestrator/group.d.ts +73 -0
  51. package/dist/orchestrator/group.js +166 -0
  52. package/dist/orchestrator/ipc-server.d.ts +60 -0
  53. package/dist/orchestrator/ipc-server.js +166 -0
  54. package/dist/orchestrator/scheduler.d.ts +32 -0
  55. package/dist/orchestrator/scheduler.js +95 -0
  56. package/dist/plan/context.d.ts +8 -0
  57. package/dist/plan/context.js +42 -0
  58. package/dist/plan/decisions.d.ts +18 -0
  59. package/dist/plan/decisions.js +143 -0
  60. package/dist/plan/shared-plan.d.ts +33 -0
  61. package/dist/plan/shared-plan.js +211 -0
  62. package/dist/skills/executor.d.ts +7 -0
  63. package/dist/skills/executor.js +11 -0
  64. package/dist/skills/loader.d.ts +16 -0
  65. package/dist/skills/loader.js +80 -0
  66. package/dist/skills/registry.d.ts +13 -0
  67. package/dist/skills/registry.js +54 -0
  68. package/dist/terminal/adapter.d.ts +61 -0
  69. package/dist/terminal/adapter.js +42 -0
  70. package/dist/terminal/detect.d.ts +30 -0
  71. package/dist/terminal/detect.js +77 -0
  72. package/dist/terminal/iterm2-adapter.d.ts +19 -0
  73. package/dist/terminal/iterm2-adapter.js +120 -0
  74. package/dist/terminal/pty-adapter.d.ts +18 -0
  75. package/dist/terminal/pty-adapter.js +84 -0
  76. package/dist/terminal/terminal-adapter.d.ts +17 -0
  77. package/dist/terminal/terminal-adapter.js +94 -0
  78. package/dist/terminal/tmux-adapter.d.ts +18 -0
  79. package/dist/terminal/tmux-adapter.js +127 -0
  80. package/dist/ui/banner.d.ts +3 -0
  81. package/dist/ui/banner.js +145 -0
  82. package/dist/ui/colors.d.ts +41 -0
  83. package/dist/ui/colors.js +65 -0
  84. package/dist/ui/dashboard.d.ts +32 -0
  85. package/dist/ui/dashboard.js +138 -0
  86. package/dist/ui/input.d.ts +10 -0
  87. package/dist/ui/input.js +96 -0
  88. package/dist/ui/interactive.d.ts +13 -0
  89. package/dist/ui/interactive.js +230 -0
  90. package/dist/ui/renderer.d.ts +33 -0
  91. package/dist/ui/renderer.js +106 -0
  92. package/dist/utils/ansi.d.ts +11 -0
  93. package/dist/utils/ansi.js +16 -0
  94. package/dist/utils/fs.d.ts +34 -0
  95. package/dist/utils/fs.js +115 -0
  96. package/dist/utils/lock.d.ts +12 -0
  97. package/dist/utils/lock.js +116 -0
  98. package/dist/utils/process.d.ts +31 -0
  99. package/dist/utils/process.js +111 -0
  100. package/dist/utils/pty-filter.d.ts +31 -0
  101. package/dist/utils/pty-filter.js +187 -0
  102. package/package.json +71 -0
  103. package/skills/loop/SKILL.md +19 -0
  104. package/skills/plan/SKILL.md +9 -0
  105. package/skills/review/SKILL.md +14 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * PTY output classification and filtering.
3
+ *
4
+ * Ported from iterloop's pty-session.ts — classifies raw terminal output lines
5
+ * into content, status updates, or ignorable TUI noise so that only meaningful
6
+ * text reaches the reviewer and transcript.
7
+ */
8
+ // ── Status keywords ──────────────────────────────────
9
+ const STATUS_KEYWORDS = [
10
+ "harmonizing",
11
+ "thinking",
12
+ "planning",
13
+ "generating",
14
+ "searching",
15
+ "analyzing",
16
+ "burrowing",
17
+ "contemplating",
18
+ "flibbertigibbeting",
19
+ "running stop hook",
20
+ ];
21
+ // Note: ⏺ is NOT a spinner char — Claude CLI uses it as a content output marker
22
+ const SPINNER_CHARS = /^[\s✳✶✻✽✢·⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏●○◆◇▪▸▹►☐☑✓✗✔✘⏵]/;
23
+ // ── Line classifier ──────────────────────────────────
24
+ /**
25
+ * Classify a single (ANSI-stripped) line of PTY output.
26
+ *
27
+ * - `"content"` — meaningful AI output to keep
28
+ * - `"status"` — transient status / spinner text
29
+ * - `"ignore"` — TUI chrome, empty lines, noise
30
+ */
31
+ export function classifyLine(line, engine) {
32
+ const trimmed = line.trim();
33
+ if (!trimmed)
34
+ return "ignore";
35
+ // Claude CLI content marker: ⏺ followed by text = actual AI output
36
+ if (/^⏺/.test(trimmed)) {
37
+ return trimmed.length > 1 ? "content" : "ignore";
38
+ }
39
+ // Short fragments (< 5 chars) from TUI cursor repositioning are artifacts
40
+ if (trimmed.length < 5)
41
+ return "ignore";
42
+ // ── Known CLI UI elements to ignore ──
43
+ // Prompt marker (with or without text)
44
+ if (/^❯/.test(trimmed))
45
+ return "ignore";
46
+ // Permission mode indicator
47
+ if (/^⏵⏵/.test(trimmed))
48
+ return "ignore";
49
+ // Update notices
50
+ if (/update\s*avai|brew\s*upgrade/i.test(trimmed))
51
+ return "ignore";
52
+ // Keyboard hints
53
+ if (/shift\+tab/i.test(trimmed))
54
+ return "ignore";
55
+ // Mode indicator
56
+ if (/fast\s*mode/i.test(trimmed))
57
+ return "ignore";
58
+ // Truncated UI text
59
+ if (/^…\s/.test(trimmed) && trimmed.length < 80)
60
+ return "ignore";
61
+ // TUI box layout: lines inside boxes (start with │ or box corners)
62
+ if (/^[│╭╰]/.test(trimmed))
63
+ return "ignore";
64
+ // Status bar indicators (▪▪▪ pattern)
65
+ if (/▪▪▪/.test(trimmed))
66
+ return "ignore";
67
+ // Block element art (CLI logos, decorative)
68
+ if (/^[\s▐▛▜▝▘█▌▪·↯]+$/.test(trimmed))
69
+ return "ignore";
70
+ // Lines that are primarily horizontal rules/separators
71
+ if (/^[─═\s▪·↯]+$/.test(trimmed))
72
+ return "ignore";
73
+ // Box-drawing / UI chrome (pure box chars)
74
+ if (/^[\s─│┌┐└┘├┤┬┴┼═╔╗╚╝╠╣╦╩╬┃┗┛┏┓╭╮╰╯▐▛▜▝]+$/.test(trimmed))
75
+ return "ignore";
76
+ // Pure spinner characters
77
+ if (/^[\s✳✶✻✽✢·⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]+$/.test(trimmed))
78
+ return "ignore";
79
+ // ── Status detection ──
80
+ const lower = trimmed.toLowerCase();
81
+ for (const kw of STATUS_KEYWORDS) {
82
+ if (lower.includes(kw))
83
+ return "status";
84
+ }
85
+ // Lines starting with spinner chars — only classify as status if it looks like
86
+ // a real spinner update (very short text after the spinner prefix)
87
+ if (SPINNER_CHARS.test(trimmed) && trimmed.length < 80) {
88
+ const withoutPrefix = trimmed.replace(/^[\s✳✶✻✽✢·⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏●○◆◇▪▸▹►☐☑✓✗✔✘⏵]+\s*/, "");
89
+ if (withoutPrefix.length > 0 && withoutPrefix.length < 60)
90
+ return "status";
91
+ if (withoutPrefix.length === 0)
92
+ return "ignore";
93
+ }
94
+ // For Claude engine: only ⏺-prefixed lines are true content.
95
+ // Everything else that reaches here is TUI rendering noise.
96
+ if (engine === "claude")
97
+ return "ignore";
98
+ return "content";
99
+ }
100
+ // ── Output filtering ─────────────────────────────────
101
+ /**
102
+ * Filter raw PTY output to extract only meaningful content.
103
+ *
104
+ * Strips TUI chrome, spinners, box-drawing, update notices, and other noise.
105
+ * For Claude output, strips the ⏺ content marker prefix.
106
+ *
107
+ * Result is capped at ~50 KB (last 50 KB on overflow).
108
+ */
109
+ export function filterOutput(raw, _engine) {
110
+ const lines = raw.split("\n");
111
+ const filtered = lines
112
+ .map((line) => {
113
+ // Strip ⏺ content marker prefix from Claude CLI output
114
+ const t = line.trim();
115
+ if (t.startsWith("⏺"))
116
+ return t.slice(1).trimStart();
117
+ return line;
118
+ })
119
+ .filter((line) => {
120
+ const trimmed = line.trim();
121
+ if (trimmed === "")
122
+ return true;
123
+ // Short TUI fragments
124
+ if (trimmed.length < 5)
125
+ return false;
126
+ if (/^[\s⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏●○◆◇✳✶✻✽✢·]+$/.test(trimmed))
127
+ return false;
128
+ if (/^[\s─│┌┐└┘├┤┬┴┼═╔╗╚╝╠╣╦╩╬┃┗┛┏┓╭╮╰╯▐▛▜▝]+$/.test(trimmed))
129
+ return false;
130
+ // Known UI noise
131
+ if (/^❯/.test(trimmed))
132
+ return false;
133
+ if (/^⏵⏵/.test(trimmed))
134
+ return false;
135
+ if (/^[│╭╰]/.test(trimmed))
136
+ return false;
137
+ if (/▪▪▪/.test(trimmed))
138
+ return false;
139
+ if (/^[\s▐▛▜▝▘█▌▪·↯]+$/.test(trimmed))
140
+ return false;
141
+ if (/^[─═\s▪·↯]+$/.test(trimmed))
142
+ return false;
143
+ if (/update\s*avai|brew\s*upgrade/i.test(trimmed))
144
+ return false;
145
+ if (/shift\+tab/i.test(trimmed))
146
+ return false;
147
+ if (/fast\s*mode/i.test(trimmed))
148
+ return false;
149
+ if (/^…\s/.test(trimmed) && trimmed.length < 80)
150
+ return false;
151
+ // Status lines
152
+ const lower = trimmed.toLowerCase();
153
+ if (STATUS_KEYWORDS.some((kw) => lower.includes(kw)) && trimmed.length < 80)
154
+ return false;
155
+ return true;
156
+ });
157
+ // Limit to last ~50 KB
158
+ const MAX_BYTES = 50 * 1024;
159
+ let result = filtered.join("\n");
160
+ if (Buffer.byteLength(result) > MAX_BYTES) {
161
+ const buf = Buffer.from(result);
162
+ result = buf.subarray(buf.length - MAX_BYTES).toString("utf-8");
163
+ const firstNewline = result.indexOf("\n");
164
+ if (firstNewline > 0) {
165
+ result = result.slice(firstNewline + 1);
166
+ }
167
+ }
168
+ return result.trim();
169
+ }
170
+ // ── Deduplication ────────────────────────────────────
171
+ /**
172
+ * Remove duplicate consecutive lines (TUI re-renders can emit the same
173
+ * content multiple times). Comparison ignores whitespace differences.
174
+ */
175
+ export function deduplicateLines(lines) {
176
+ const result = [];
177
+ let lastNorm = "";
178
+ for (const line of lines) {
179
+ const norm = line.replace(/\s+/g, "");
180
+ if (norm !== lastNorm || norm === "") {
181
+ result.push(line);
182
+ lastNorm = norm;
183
+ }
184
+ }
185
+ return result;
186
+ }
187
+ //# sourceMappingURL=pty-filter.js.map
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@lawrence369/loop-cli",
3
+ "version": "0.1.0",
4
+ "description": "Iterative Multi-Engine AI Orchestration",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "loop": "./dist/index.js",
10
+ "lclaude": "./dist/bin/lclaude.js",
11
+ "lgemini": "./dist/bin/lgemini.js",
12
+ "lcodex": "./dist/bin/lcodex.js"
13
+ },
14
+ "scripts": {
15
+ "dev": "tsx src/index.ts",
16
+ "build": "tsc",
17
+ "start": "node dist/index.js",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "test:coverage": "vitest run --coverage"
21
+ },
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "files": [
26
+ "dist/**/*.js",
27
+ "dist/**/*.d.ts",
28
+ "skills/",
29
+ "README.md",
30
+ "LICENSE",
31
+ "CHANGELOG.md"
32
+ ],
33
+ "keywords": [
34
+ "cli",
35
+ "ai",
36
+ "orchestration",
37
+ "multi-agent",
38
+ "claude",
39
+ "gemini",
40
+ "codex",
41
+ "iteration",
42
+ "loop"
43
+ ],
44
+ "author": "loop-cli contributors",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/lawrence3699/loop.git"
49
+ },
50
+ "homepage": "https://github.com/lawrence3699/loop",
51
+ "bugs": {
52
+ "url": "https://github.com/lawrence3699/loop/issues"
53
+ },
54
+ "dependencies": {
55
+ "@clack/prompts": "^0.10.0",
56
+ "blessed": "^0.1.81",
57
+ "commander": "^12.0.0",
58
+ "gradient-string": "^3.0.0",
59
+ "gray-matter": "^4.0.3",
60
+ "node-pty": "^1.0.0",
61
+ "strip-ansi": "^7.2.0"
62
+ },
63
+ "devDependencies": {
64
+ "@types/blessed": "^0.1.25",
65
+ "@types/node": "^22.0.0",
66
+ "tsx": "^4.0.0",
67
+ "typescript": "^5.5.0",
68
+ "vitest": "^3.0.0",
69
+ "@vitest/coverage-v8": "^3.0.0"
70
+ }
71
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: loop
3
+ description: Core iteration loop protocol
4
+ ---
5
+
6
+ # Loop Protocol
7
+
8
+ You are participating in an iterative review loop. Your output will be reviewed and scored.
9
+
10
+ ## As Executor
11
+ - Focus on the task requirements
12
+ - Address ALL reviewer feedback from previous iterations
13
+ - Show your work: list files changed and commands executed
14
+
15
+ ## As Reviewer
16
+ - Score the output from 1-10
17
+ - List specific issues found
18
+ - Provide actionable suggestions
19
+ - Write "APPROVED" on its own line if score >= 9
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: plan
3
+ description: Plan management
4
+ ---
5
+
6
+ # Plan Protocol
7
+
8
+ Track decisions and iteration history in the shared plan file.
9
+ Reference previous iteration feedback when making changes.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: review
3
+ description: Code review workflow
4
+ ---
5
+
6
+ # Review Protocol
7
+
8
+ When reviewing code output:
9
+ 1. Check correctness: Does it solve the stated task?
10
+ 2. Check completeness: Are all requirements addressed?
11
+ 3. Check quality: Is the code clean, well-structured, and maintainable?
12
+ 4. Check edge cases: Are error cases handled?
13
+
14
+ Score 1-10. Write "APPROVED" if satisfied.