@bibobonking/opensol 3.0.1 → 3.0.2

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.
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process'
3
+ import { open, readFile, stat, unlink } from 'node:fs/promises'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { DATA_DIR, ensureDataDir } from './lib/store.mjs'
7
+ import { daemonStatus, startServer, stopDaemon } from './daemon.mjs'
8
+
9
+ const HERE = path.dirname(fileURLToPath(import.meta.url))
10
+ const DAEMON = path.join(HERE, 'daemon.mjs')
11
+ const LOG_FILE = path.join(DATA_DIR, 'daemon.log')
12
+ const PID_FILE = path.join(DATA_DIR, 'daemon.json')
13
+
14
+ const HELP = `OpenSol — local coding agent daemon
15
+
16
+ Usage:
17
+ opensol start [--port=4174] [--open] [--foreground]
18
+ opensol stop
19
+ opensol status
20
+ opensol open
21
+ opensol logs
22
+
23
+ The daemon serves the built app from dist/ and keeps your settings, chats and
24
+ encrypted provider keys in ${DATA_DIR}. It listens on 127.0.0.1 only.
25
+ `
26
+
27
+ function parseFlags(args) {
28
+ const flags = { port: 4174, open: false, foreground: false }
29
+ for (const arg of args) {
30
+ if (arg === '--open') flags.open = true
31
+ else if (arg === '--foreground') flags.foreground = true
32
+ else if (arg.startsWith('--port=')) {
33
+ const value = Number(arg.slice('--port='.length))
34
+ if (!Number.isInteger(value) || value < 0 || value > 65535) {
35
+ throw new Error('Use --port=<1-65535>.')
36
+ }
37
+ flags.port = value
38
+ } else if (arg === '--help' || arg === '-h') {
39
+ flags.help = true
40
+ } else {
41
+ throw new Error(`Unknown option: ${arg}`)
42
+ }
43
+ }
44
+ return flags
45
+ }
46
+
47
+ function openerFor(url) {
48
+ const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open'
49
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url]
50
+ const child = spawn(command, args, { detached: true, stdio: 'ignore' })
51
+ child.on('error', () => console.log(`Open ${url} in your browser.`))
52
+ child.unref()
53
+ }
54
+
55
+ async function tailLog(lines = 40) {
56
+ const info = await stat(LOG_FILE).catch(() => null)
57
+ if (!info) {
58
+ console.log('No daemon log yet.')
59
+ return
60
+ }
61
+ const raw = await readFile(LOG_FILE, 'utf8')
62
+ const slice = raw.trimEnd().split('\n').slice(-lines)
63
+ console.log(slice.join('\n'))
64
+ }
65
+
66
+ async function rotateLog() {
67
+ const info = await stat(LOG_FILE).catch(() => null)
68
+ if (info && info.size > 5 * 1024 * 1024) {
69
+ const previous = `${LOG_FILE}.1`
70
+ await unlink(previous).catch(() => undefined)
71
+ const { rename } = await import('node:fs/promises')
72
+ await rename(LOG_FILE, previous).catch(() => undefined)
73
+ }
74
+ }
75
+
76
+ async function waitForDaemon(pid, timeoutMs = 15_000) {
77
+ const started = Date.now()
78
+ while (Date.now() - started < timeoutMs) {
79
+ const status = await daemonStatus()
80
+ if (status.running) return status
81
+ if (pid) {
82
+ try {
83
+ process.kill(pid, 0)
84
+ } catch {
85
+ return { running: false, info: null }
86
+ }
87
+ }
88
+ await new Promise((resolve) => setTimeout(resolve, 250))
89
+ }
90
+ return { running: false, info: null }
91
+ }
92
+
93
+ async function commandStart(args) {
94
+ const flags = parseFlags(args)
95
+ if (flags.help) {
96
+ console.log(HELP)
97
+ return
98
+ }
99
+ await ensureDataDir()
100
+ const existing = await daemonStatus()
101
+ if (existing.running) {
102
+ console.log(`OpenSol is already running at ${existing.info.url}`)
103
+ if (flags.open) openerFor(existing.info.url)
104
+ return
105
+ }
106
+ await unlink(PID_FILE).catch(() => undefined)
107
+
108
+ if (flags.foreground) {
109
+ await startServer({ port: flags.port, open: flags.open })
110
+ return
111
+ }
112
+
113
+ await rotateLog()
114
+ const handle = await open(LOG_FILE, 'a', 0o600)
115
+ const child = spawn(process.execPath, [DAEMON, `--port=${flags.port}`], {
116
+ detached: true,
117
+ stdio: ['ignore', handle.fd, handle.fd],
118
+ cwd: path.resolve(HERE, '..'),
119
+ env: { ...process.env, OPENSOL_DATA: DATA_DIR, MAXCODE_DATA: DATA_DIR },
120
+ })
121
+ child.unref()
122
+ await handle.close()
123
+
124
+ const status = await waitForDaemon(child.pid)
125
+ if (!status.running) {
126
+ console.error('OpenSol did not start. Recent log:')
127
+ await tailLog(20)
128
+ process.exitCode = 1
129
+ return
130
+ }
131
+ console.log(`OpenSol is running at ${status.info.url}`)
132
+ console.log(`Data: ${DATA_DIR}`)
133
+ console.log(`Log: ${LOG_FILE}`)
134
+ if (flags.open) openerFor(status.info.url)
135
+ }
136
+
137
+ async function commandStatus() {
138
+ const status = await daemonStatus()
139
+ if (!status.running) {
140
+ console.log('OpenSol is not running.')
141
+ return
142
+ }
143
+ console.log(`OpenSol is running at ${status.info.url}`)
144
+ console.log(`PID: ${status.info.pid}`)
145
+ console.log(`Data: ${status.info.dataDir ?? DATA_DIR}`)
146
+ console.log(`Dist ready: ${status.info.dist ? 'yes' : 'no'}`)
147
+ }
148
+
149
+ async function commandOpen() {
150
+ const status = await daemonStatus()
151
+ if (!status.running) {
152
+ console.error('OpenSol is not running. Start it with: opensol start')
153
+ process.exitCode = 1
154
+ return
155
+ }
156
+ openerFor(status.info.url)
157
+ console.log(`Opened ${status.info.url}`)
158
+ }
159
+
160
+ async function commandStop() {
161
+ const result = await stopDaemon()
162
+ if (!result.stopped) {
163
+ console.log(result.reason ?? 'OpenSol is not running.')
164
+ return
165
+ }
166
+ console.log(`Stopped OpenSol${result.pid ? ` (pid ${result.pid})` : ''}.`)
167
+ }
168
+
169
+ async function main() {
170
+ const [command = 'help', ...args] = process.argv.slice(2)
171
+ switch (command) {
172
+ case 'start':
173
+ await commandStart(args)
174
+ break
175
+ case 'stop':
176
+ await commandStop()
177
+ break
178
+ case 'status':
179
+ await commandStatus()
180
+ break
181
+ case 'open':
182
+ await commandOpen()
183
+ break
184
+ case 'logs':
185
+ await tailLog()
186
+ break
187
+ case 'help':
188
+ case '--help':
189
+ case '-h':
190
+ console.log(HELP)
191
+ break
192
+ default:
193
+ console.error(`Unknown command: ${command}\n`)
194
+ console.log(HELP)
195
+ process.exitCode = 1
196
+ }
197
+ }
198
+
199
+ main().catch((error) => {
200
+ console.error(error instanceof Error ? error.message : error)
201
+ process.exitCode = 1
202
+ })
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "maxcode",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "bin": {
7
+ "maxcode": "./server/maxcode.mjs"
8
+ },
9
+ "scripts": {
10
+ "dev": "node server/maxcode.mjs start && vite",
11
+ "build": "tsc -b && vite build",
12
+ "preview": "vite preview",
13
+ "typecheck": "tsc -b --pretty",
14
+ "start": "node server/maxcode.mjs start --open",
15
+ "daemon": "node server/maxcode.mjs start",
16
+ "daemon:foreground": "node server/daemon.mjs",
17
+ "daemon:stop": "node server/maxcode.mjs stop",
18
+ "daemon:status": "node server/maxcode.mjs status",
19
+ "daemon:logs": "node server/maxcode.mjs logs"
20
+ },
21
+ "dependencies": {
22
+ "clsx": "^2.1.1",
23
+ "highlight.js": "^11.11.1",
24
+ "lucide-react": "^0.544.0",
25
+ "react": "^19.1.1",
26
+ "react-dom": "^19.1.1",
27
+ "react-markdown": "^10.1.0",
28
+ "rehype-highlight": "^7.0.2",
29
+ "remark-gfm": "^4.0.1",
30
+ "zustand": "^5.0.8"
31
+ },
32
+ "devDependencies": {
33
+ "@types/react": "^19.1.10",
34
+ "@types/react-dom": "^19.1.7",
35
+ "@vitejs/plugin-react": "^5.0.0",
36
+ "autoprefixer": "^10.4.21",
37
+ "postcss": "^8.5.6",
38
+ "tailwindcss": "^3.4.19",
39
+ "typescript": "^5.9.2",
40
+ "vite": "^7.1.3"
41
+ }
42
+ }