@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 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
- - Bun's speed makes the agent loop faster and more responsive
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
- # One-off usage (recommended)
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-20250514` | Model to use |
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.2.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>