@cephalization/math 0.2.0 → 0.3.1
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 +28 -6
- package/package.json +3 -1
- package/src/ui/app.tsx +329 -0
- package/src/ui/index.html +30 -0
package/README.md
CHANGED
|
@@ -24,20 +24,33 @@ curl -fsSL https://bun.sh/install | bash
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Why Bun?
|
|
27
|
+
|
|
27
28
|
- This tool is written in TypeScript and uses Bun's native TypeScript execution (no compilation step)
|
|
28
29
|
- The CLI uses a `#!/usr/bin/env bun` shebang for direct execution
|
|
29
|
-
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
**[OpenCode](https://opencode.ai) is required** to run this tool.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Install OpenCode
|
|
36
|
+
curl -fsSL https://opencode.ai/install | bash
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Why OpenCode?
|
|
40
|
+
|
|
41
|
+
- OpenCode provides a consistent and reliable interface for running the agent loop
|
|
42
|
+
- It supports many models, is easy to use, and is free to use
|
|
30
43
|
|
|
31
44
|
## Installation
|
|
32
45
|
|
|
33
46
|
### From npm (recommended)
|
|
34
47
|
|
|
35
48
|
```bash
|
|
36
|
-
#
|
|
37
|
-
bunx @cephalization/math <command>
|
|
38
|
-
|
|
39
|
-
# Global install
|
|
49
|
+
# Global install (recommended)
|
|
40
50
|
bun install -g @cephalization/math
|
|
51
|
+
|
|
52
|
+
# One-off usage
|
|
53
|
+
bunx @cephalization/math <command>
|
|
41
54
|
```
|
|
42
55
|
|
|
43
56
|
### From source (for development)
|
|
@@ -94,6 +107,15 @@ Options:
|
|
|
94
107
|
- `--max-iterations <n>` - Safety limit (default: 100)
|
|
95
108
|
- `--pause <seconds>` - Pause between iterations (default: 3)
|
|
96
109
|
|
|
110
|
+
Iteratively run the agent loop until all tasks are complete. Each iteration will:
|
|
111
|
+
|
|
112
|
+
- Read the `TASKS.md` file to find the next task to complete
|
|
113
|
+
- Invoke the agent with the `PROMPT.md` file and the `TASKS.md` file
|
|
114
|
+
- The agent will complete the task and update the `TASKS.md` file
|
|
115
|
+
- The agent will log its learnings to the `LEARNINGS.md` file
|
|
116
|
+
- The agent will commit the changes to the repository
|
|
117
|
+
- The agent will exit
|
|
118
|
+
|
|
97
119
|
### Check status
|
|
98
120
|
|
|
99
121
|
```bash
|
|
@@ -178,7 +200,7 @@ Signs accumulate over time, making the agent increasingly reliable.
|
|
|
178
200
|
|
|
179
201
|
| Variable | Default | Description |
|
|
180
202
|
|----------|---------|-------------|
|
|
181
|
-
| `MODEL` | `anthropic/claude-opus-4-
|
|
203
|
+
| `MODEL` | `anthropic/claude-opus-4-5` | Model to use |
|
|
182
204
|
|
|
183
205
|
## Credits
|
|
184
206
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cephalization/math",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"author": "Tony Powell <powell.anthonyd@proton.me>",
|
|
5
5
|
"access": "public",
|
|
6
6
|
"repository": {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"index.ts",
|
|
21
21
|
"src/**/*.ts",
|
|
22
|
+
"src/**/*.tsx",
|
|
23
|
+
"src/**/*.html",
|
|
22
24
|
"README.md"
|
|
23
25
|
],
|
|
24
26
|
"scripts": {
|
package/src/ui/app.tsx
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React app for the Math Agent UI.
|
|
3
|
+
* Connects to WebSocket server and displays loop logs and agent output.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useState, useRef, useCallback } from "react";
|
|
7
|
+
import { createRoot } from "react-dom/client";
|
|
8
|
+
import type { BufferLogEntry, BufferAgentOutput } from "./buffer";
|
|
9
|
+
import type { WebSocketMessage } from "./server";
|
|
10
|
+
import type { LogCategory } from "../agent";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Connection state for the WebSocket.
|
|
14
|
+
*/
|
|
15
|
+
type ConnectionState = "connecting" | "connected" | "disconnected";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reconnection interval in milliseconds.
|
|
19
|
+
*/
|
|
20
|
+
const RECONNECT_INTERVAL = 3000;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Color mapping for log categories matching terminal colors.
|
|
24
|
+
*/
|
|
25
|
+
const categoryColors: Record<LogCategory, string> = {
|
|
26
|
+
info: "#60a5fa", // blue
|
|
27
|
+
success: "#4ade80", // green
|
|
28
|
+
warning: "#facc15", // yellow
|
|
29
|
+
error: "#f87171", // red
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Main App component that displays loop status and agent output.
|
|
34
|
+
*/
|
|
35
|
+
function App() {
|
|
36
|
+
const [logs, setLogs] = useState<BufferLogEntry[]>([]);
|
|
37
|
+
const [output, setOutput] = useState<BufferAgentOutput[]>([]);
|
|
38
|
+
const [connectionState, setConnectionState] = useState<ConnectionState>("connecting");
|
|
39
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
40
|
+
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
41
|
+
const logContainerRef = useRef<HTMLDivElement>(null);
|
|
42
|
+
const outputContainerRef = useRef<HTMLDivElement>(null);
|
|
43
|
+
|
|
44
|
+
// Auto-scroll to bottom when logs change
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (logContainerRef.current) {
|
|
47
|
+
logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
|
|
48
|
+
}
|
|
49
|
+
}, [logs]);
|
|
50
|
+
|
|
51
|
+
// Auto-scroll to bottom when output changes
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (outputContainerRef.current) {
|
|
54
|
+
outputContainerRef.current.scrollTop = outputContainerRef.current.scrollHeight;
|
|
55
|
+
}
|
|
56
|
+
}, [output]);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a WebSocket connection and set up event handlers.
|
|
60
|
+
*/
|
|
61
|
+
const connect = useCallback(() => {
|
|
62
|
+
// Clear any existing reconnect timer
|
|
63
|
+
if (reconnectTimerRef.current) {
|
|
64
|
+
clearTimeout(reconnectTimerRef.current);
|
|
65
|
+
reconnectTimerRef.current = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Close existing connection if any
|
|
69
|
+
if (wsRef.current) {
|
|
70
|
+
wsRef.current.close();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setConnectionState("connecting");
|
|
74
|
+
|
|
75
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
76
|
+
const wsUrl = `${protocol}//${window.location.host}/ws`;
|
|
77
|
+
const ws = new WebSocket(wsUrl);
|
|
78
|
+
wsRef.current = ws;
|
|
79
|
+
|
|
80
|
+
ws.onopen = () => {
|
|
81
|
+
setConnectionState("connected");
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
ws.onclose = () => {
|
|
85
|
+
setConnectionState("disconnected");
|
|
86
|
+
// Schedule reconnection attempt
|
|
87
|
+
reconnectTimerRef.current = setTimeout(() => {
|
|
88
|
+
connect();
|
|
89
|
+
}, RECONNECT_INTERVAL);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
ws.onerror = () => {
|
|
93
|
+
// onclose will be called after onerror, so we don't need to handle reconnection here
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
ws.onmessage = (event) => {
|
|
97
|
+
const message: WebSocketMessage = JSON.parse(event.data);
|
|
98
|
+
|
|
99
|
+
switch (message.type) {
|
|
100
|
+
case "connected":
|
|
101
|
+
// Connection confirmed
|
|
102
|
+
break;
|
|
103
|
+
case "history":
|
|
104
|
+
// Full history received - replace existing state
|
|
105
|
+
setLogs(message.logs);
|
|
106
|
+
setOutput(message.output);
|
|
107
|
+
break;
|
|
108
|
+
case "log":
|
|
109
|
+
// New log entry
|
|
110
|
+
setLogs((prev) => [...prev, message.entry]);
|
|
111
|
+
break;
|
|
112
|
+
case "output":
|
|
113
|
+
// New agent output
|
|
114
|
+
setOutput((prev) => [...prev, message.entry]);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
connect();
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
// Clean up on unmount
|
|
125
|
+
if (reconnectTimerRef.current) {
|
|
126
|
+
clearTimeout(reconnectTimerRef.current);
|
|
127
|
+
}
|
|
128
|
+
if (wsRef.current) {
|
|
129
|
+
wsRef.current.close();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}, [connect]);
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the color for a log category.
|
|
136
|
+
*/
|
|
137
|
+
const getCategoryColor = (category: LogCategory): string => {
|
|
138
|
+
return categoryColors[category] || "#e0e0e0";
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get the status display properties based on connection state.
|
|
143
|
+
*/
|
|
144
|
+
const getStatusDisplay = () => {
|
|
145
|
+
switch (connectionState) {
|
|
146
|
+
case "connecting":
|
|
147
|
+
return { color: "#facc15", text: "Connecting..." }; // yellow
|
|
148
|
+
case "connected":
|
|
149
|
+
return { color: "#4ade80", text: "Connected" }; // green
|
|
150
|
+
case "disconnected":
|
|
151
|
+
return { color: "#f87171", text: "Disconnected" }; // red
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const statusDisplay = getStatusDisplay();
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div style={styles.container}>
|
|
159
|
+
{/* Disconnected banner */}
|
|
160
|
+
{connectionState === "disconnected" && (
|
|
161
|
+
<div style={styles.disconnectedBanner}>
|
|
162
|
+
Connection lost. Reconnecting...
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
<header style={styles.header}>
|
|
167
|
+
<h1 style={styles.title}>Math Agent UI</h1>
|
|
168
|
+
<div style={styles.statusContainer}>
|
|
169
|
+
<span
|
|
170
|
+
style={{
|
|
171
|
+
...styles.statusDot,
|
|
172
|
+
backgroundColor: statusDisplay.color,
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
<span style={{ ...styles.statusText, color: statusDisplay.color }}>
|
|
176
|
+
{statusDisplay.text}
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
</header>
|
|
180
|
+
|
|
181
|
+
<div style={styles.content}>
|
|
182
|
+
<section style={styles.section}>
|
|
183
|
+
<h2 style={styles.sectionTitle}>Loop Status</h2>
|
|
184
|
+
<div ref={logContainerRef} style={styles.logContainer}>
|
|
185
|
+
{logs.length === 0 ? (
|
|
186
|
+
<p style={styles.empty}>No logs yet</p>
|
|
187
|
+
) : (
|
|
188
|
+
logs.map((log, index) => (
|
|
189
|
+
<div key={index} style={styles.logEntry}>
|
|
190
|
+
<span style={{ ...styles.timestamp, color: getCategoryColor(log.category) }}>
|
|
191
|
+
{new Date(log.timestamp).toLocaleTimeString()}
|
|
192
|
+
</span>
|
|
193
|
+
<span style={{ ...styles.category, color: getCategoryColor(log.category) }}>
|
|
194
|
+
[{log.category}]
|
|
195
|
+
</span>
|
|
196
|
+
<span style={styles.message}>{log.message}</span>
|
|
197
|
+
</div>
|
|
198
|
+
))
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
</section>
|
|
202
|
+
|
|
203
|
+
<section style={styles.section}>
|
|
204
|
+
<h2 style={styles.sectionTitle}>Agent Output</h2>
|
|
205
|
+
<div ref={outputContainerRef} style={styles.outputContainer}>
|
|
206
|
+
{output.length === 0 ? (
|
|
207
|
+
<p style={styles.empty}>No output yet</p>
|
|
208
|
+
) : (
|
|
209
|
+
output.map((out, index) => (
|
|
210
|
+
<pre key={index} style={styles.outputEntry}>
|
|
211
|
+
{out.text}
|
|
212
|
+
</pre>
|
|
213
|
+
))
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
</section>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Inline styles for the app components.
|
|
224
|
+
*/
|
|
225
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
226
|
+
container: {
|
|
227
|
+
height: "100%",
|
|
228
|
+
display: "flex",
|
|
229
|
+
flexDirection: "column",
|
|
230
|
+
padding: "16px",
|
|
231
|
+
},
|
|
232
|
+
header: {
|
|
233
|
+
display: "flex",
|
|
234
|
+
justifyContent: "space-between",
|
|
235
|
+
alignItems: "center",
|
|
236
|
+
marginBottom: "16px",
|
|
237
|
+
paddingBottom: "16px",
|
|
238
|
+
borderBottom: "1px solid #333",
|
|
239
|
+
},
|
|
240
|
+
title: {
|
|
241
|
+
fontSize: "24px",
|
|
242
|
+
fontWeight: "bold",
|
|
243
|
+
margin: 0,
|
|
244
|
+
},
|
|
245
|
+
statusContainer: {
|
|
246
|
+
display: "flex",
|
|
247
|
+
alignItems: "center",
|
|
248
|
+
gap: "8px",
|
|
249
|
+
},
|
|
250
|
+
statusDot: {
|
|
251
|
+
width: "10px",
|
|
252
|
+
height: "10px",
|
|
253
|
+
borderRadius: "50%",
|
|
254
|
+
},
|
|
255
|
+
statusText: {
|
|
256
|
+
fontSize: "14px",
|
|
257
|
+
},
|
|
258
|
+
disconnectedBanner: {
|
|
259
|
+
backgroundColor: "#7f1d1d",
|
|
260
|
+
color: "#fecaca",
|
|
261
|
+
padding: "12px 16px",
|
|
262
|
+
marginBottom: "16px",
|
|
263
|
+
borderRadius: "8px",
|
|
264
|
+
textAlign: "center",
|
|
265
|
+
fontWeight: "500",
|
|
266
|
+
},
|
|
267
|
+
content: {
|
|
268
|
+
display: "flex",
|
|
269
|
+
flex: 1,
|
|
270
|
+
gap: "16px",
|
|
271
|
+
overflow: "hidden",
|
|
272
|
+
},
|
|
273
|
+
section: {
|
|
274
|
+
flex: 1,
|
|
275
|
+
display: "flex",
|
|
276
|
+
flexDirection: "column",
|
|
277
|
+
backgroundColor: "#252525",
|
|
278
|
+
borderRadius: "8px",
|
|
279
|
+
padding: "16px",
|
|
280
|
+
overflow: "hidden",
|
|
281
|
+
},
|
|
282
|
+
sectionTitle: {
|
|
283
|
+
fontSize: "18px",
|
|
284
|
+
fontWeight: "600",
|
|
285
|
+
marginBottom: "12px",
|
|
286
|
+
color: "#a0a0a0",
|
|
287
|
+
},
|
|
288
|
+
logContainer: {
|
|
289
|
+
flex: 1,
|
|
290
|
+
overflow: "auto",
|
|
291
|
+
fontFamily: "monospace",
|
|
292
|
+
fontSize: "13px",
|
|
293
|
+
},
|
|
294
|
+
outputContainer: {
|
|
295
|
+
flex: 1,
|
|
296
|
+
overflow: "auto",
|
|
297
|
+
fontFamily: "monospace",
|
|
298
|
+
fontSize: "13px",
|
|
299
|
+
},
|
|
300
|
+
logEntry: {
|
|
301
|
+
marginBottom: "4px",
|
|
302
|
+
lineHeight: "1.4",
|
|
303
|
+
},
|
|
304
|
+
timestamp: {
|
|
305
|
+
marginRight: "8px",
|
|
306
|
+
},
|
|
307
|
+
category: {
|
|
308
|
+
marginRight: "8px",
|
|
309
|
+
fontWeight: "bold",
|
|
310
|
+
},
|
|
311
|
+
message: {
|
|
312
|
+
color: "#e0e0e0",
|
|
313
|
+
},
|
|
314
|
+
outputEntry: {
|
|
315
|
+
margin: 0,
|
|
316
|
+
padding: "4px 0",
|
|
317
|
+
whiteSpace: "pre-wrap",
|
|
318
|
+
wordBreak: "break-word",
|
|
319
|
+
color: "#e0e0e0",
|
|
320
|
+
},
|
|
321
|
+
empty: {
|
|
322
|
+
color: "#666",
|
|
323
|
+
fontStyle: "italic",
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Mount the React app
|
|
328
|
+
const root = createRoot(document.getElementById("root")!);
|
|
329
|
+
root.render(<App />);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Math Agent UI</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html, body, #root {
|
|
15
|
+
height: 100%;
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
background-color: #1a1a1a;
|
|
21
|
+
color: #e0e0e0;
|
|
22
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<div id="root"></div>
|
|
28
|
+
<script type="module" src="./app.tsx"></script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|