@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.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/dist/agent/activity.d.ts +64 -0
- package/dist/agent/activity.js +265 -0
- package/dist/agent/launcher.d.ts +42 -0
- package/dist/agent/launcher.js +243 -0
- package/dist/agent/pty-session.d.ts +113 -0
- package/dist/agent/pty-session.js +490 -0
- package/dist/agent/ready-detector.d.ts +46 -0
- package/dist/agent/ready-detector.js +86 -0
- package/dist/agent/wrapper.d.ts +18 -0
- package/dist/agent/wrapper.js +110 -0
- package/dist/bin/lclaude.d.ts +3 -0
- package/dist/bin/lclaude.js +7 -0
- package/dist/bin/lcodex.d.ts +3 -0
- package/dist/bin/lcodex.js +7 -0
- package/dist/bin/lgemini.d.ts +3 -0
- package/dist/bin/lgemini.js +7 -0
- package/dist/bus/daemon.d.ts +56 -0
- package/dist/bus/daemon.js +135 -0
- package/dist/bus/event-bus.d.ts +105 -0
- package/dist/bus/event-bus.js +157 -0
- package/dist/bus/message.d.ts +48 -0
- package/dist/bus/message.js +129 -0
- package/dist/bus/queue.d.ts +50 -0
- package/dist/bus/queue.js +100 -0
- package/dist/bus/store.d.ts +88 -0
- package/dist/bus/store.js +212 -0
- package/dist/bus/subscriber.d.ts +76 -0
- package/dist/bus/subscriber.js +187 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.js +72 -0
- package/dist/config/schema.d.ts +18 -0
- package/dist/config/schema.js +58 -0
- package/dist/core/conversation.d.ts +34 -0
- package/dist/core/conversation.js +289 -0
- package/dist/core/engine.d.ts +40 -0
- package/dist/core/engine.js +288 -0
- package/dist/core/loop.d.ts +33 -0
- package/dist/core/loop.js +209 -0
- package/dist/core/protocol.d.ts +60 -0
- package/dist/core/protocol.js +162 -0
- package/dist/core/scoring.d.ts +34 -0
- package/dist/core/scoring.js +69 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +408 -0
- package/dist/orchestrator/daemon.d.ts +74 -0
- package/dist/orchestrator/daemon.js +294 -0
- package/dist/orchestrator/group.d.ts +73 -0
- package/dist/orchestrator/group.js +166 -0
- package/dist/orchestrator/ipc-server.d.ts +60 -0
- package/dist/orchestrator/ipc-server.js +166 -0
- package/dist/orchestrator/scheduler.d.ts +32 -0
- package/dist/orchestrator/scheduler.js +95 -0
- package/dist/plan/context.d.ts +8 -0
- package/dist/plan/context.js +42 -0
- package/dist/plan/decisions.d.ts +18 -0
- package/dist/plan/decisions.js +143 -0
- package/dist/plan/shared-plan.d.ts +33 -0
- package/dist/plan/shared-plan.js +211 -0
- package/dist/skills/executor.d.ts +7 -0
- package/dist/skills/executor.js +11 -0
- package/dist/skills/loader.d.ts +16 -0
- package/dist/skills/loader.js +80 -0
- package/dist/skills/registry.d.ts +13 -0
- package/dist/skills/registry.js +54 -0
- package/dist/terminal/adapter.d.ts +61 -0
- package/dist/terminal/adapter.js +42 -0
- package/dist/terminal/detect.d.ts +30 -0
- package/dist/terminal/detect.js +77 -0
- package/dist/terminal/iterm2-adapter.d.ts +19 -0
- package/dist/terminal/iterm2-adapter.js +120 -0
- package/dist/terminal/pty-adapter.d.ts +18 -0
- package/dist/terminal/pty-adapter.js +84 -0
- package/dist/terminal/terminal-adapter.d.ts +17 -0
- package/dist/terminal/terminal-adapter.js +94 -0
- package/dist/terminal/tmux-adapter.d.ts +18 -0
- package/dist/terminal/tmux-adapter.js +127 -0
- package/dist/ui/banner.d.ts +3 -0
- package/dist/ui/banner.js +145 -0
- package/dist/ui/colors.d.ts +41 -0
- package/dist/ui/colors.js +65 -0
- package/dist/ui/dashboard.d.ts +32 -0
- package/dist/ui/dashboard.js +138 -0
- package/dist/ui/input.d.ts +10 -0
- package/dist/ui/input.js +96 -0
- package/dist/ui/interactive.d.ts +13 -0
- package/dist/ui/interactive.js +230 -0
- package/dist/ui/renderer.d.ts +33 -0
- package/dist/ui/renderer.js +106 -0
- package/dist/utils/ansi.d.ts +11 -0
- package/dist/utils/ansi.js +16 -0
- package/dist/utils/fs.d.ts +34 -0
- package/dist/utils/fs.js +115 -0
- package/dist/utils/lock.d.ts +12 -0
- package/dist/utils/lock.js +116 -0
- package/dist/utils/process.d.ts +31 -0
- package/dist/utils/process.js +111 -0
- package/dist/utils/pty-filter.d.ts +31 -0
- package/dist/utils/pty-filter.js +187 -0
- package/package.json +71 -0
- package/skills/loop/SKILL.md +19 -0
- package/skills/plan/SKILL.md +9 -0
- 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,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.
|