@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.
- package/package.json +1 -1
- package/server/README.md +75 -0
- package/server/daemon.mjs +1082 -0
- package/server/lib/checkpoints.mjs +124 -0
- package/server/lib/checkpoints.test.mjs +110 -0
- package/server/lib/deploy.mjs +436 -0
- package/server/lib/http.mjs +237 -0
- package/server/lib/plugins.mjs +321 -0
- package/server/lib/plugins.test.mjs +140 -0
- package/server/lib/providers.mjs +594 -0
- package/server/lib/store.mjs +491 -0
- package/server/lib/tools.mjs +990 -0
- package/server/opensol.mjs +202 -0
- package/server/package.json +42 -0
|
@@ -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
|
+
}
|