@neuralnomads/codenomad-dev 0.10.3-dev-20260213-ba418a85 → 0.10.3-dev-20260213-e9f281a6
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/public/apple-touch-icon-180x180.png +0 -0
- package/public/assets/{main-CSlDZj4f.js → main-crtt5pqm.js} +82 -80
- package/public/index.html +1 -1
- package/public/sw.js +1 -1
- package/public/ui-version.json +1 -1
- package/dist/integrations/github/bot-signature.js +0 -11
- package/dist/integrations/github/git-ops.js +0 -133
- package/dist/integrations/github/github-types.js +0 -1
- package/dist/integrations/github/job-runner.js +0 -608
- package/dist/integrations/github/octokit.js +0 -58
- package/dist/integrations/github/sanitize-webhook.js +0 -42
- package/dist/integrations/github/webhook-verify.js +0 -21
- package/dist/integrations/github/workspace-context.js +0 -10
- package/dist/integrations/github/worktree-context.js +0 -15
- package/dist/opencode/request-context.js +0 -39
- package/dist/opencode/worktree-directory.js +0 -42
- package/dist/opencode-config-template/README.md +0 -32
- package/dist/opencode-config-template/opencode.jsonc +0 -3
- package/dist/opencode-config-template/plugin/codenomad.ts +0 -40
- package/dist/opencode-config-template/plugin/lib/background-process.ts +0 -160
- package/dist/opencode-config-template/plugin/lib/client.ts +0 -165
- package/dist/server/routes/github-plugin.js +0 -215
- package/dist/server/routes/github-webhook.js +0 -32
- package/scripts/copy-auth-pages.mjs +0 -22
- package/scripts/copy-opencode-config.mjs +0 -61
- package/scripts/copy-ui-dist.mjs +0 -21
- package/src/api-types.ts +0 -326
- package/src/auth/auth-store.ts +0 -175
- package/src/auth/http-auth.ts +0 -38
- package/src/auth/manager.ts +0 -163
- package/src/auth/password-hash.ts +0 -49
- package/src/auth/session-manager.ts +0 -23
- package/src/auth/token-manager.ts +0 -32
- package/src/background-processes/manager.ts +0 -519
- package/src/bin.ts +0 -29
- package/src/config/binaries.ts +0 -192
- package/src/config/location.ts +0 -78
- package/src/config/schema.ts +0 -104
- package/src/config/store.ts +0 -244
- package/src/events/bus.ts +0 -45
- package/src/filesystem/__tests__/search-cache.test.ts +0 -61
- package/src/filesystem/browser.ts +0 -353
- package/src/filesystem/search-cache.ts +0 -66
- package/src/filesystem/search.ts +0 -184
- package/src/index.ts +0 -540
- package/src/launcher.ts +0 -177
- package/src/loader.ts +0 -21
- package/src/logger.ts +0 -133
- package/src/opencode-config.ts +0 -31
- package/src/plugins/channel.ts +0 -55
- package/src/plugins/handlers.ts +0 -36
- package/src/releases/dev-release-monitor.ts +0 -118
- package/src/releases/release-monitor.ts +0 -149
- package/src/server/http-server.ts +0 -693
- package/src/server/network-addresses.ts +0 -75
- package/src/server/routes/auth-pages/login.html +0 -134
- package/src/server/routes/auth-pages/token.html +0 -93
- package/src/server/routes/auth.ts +0 -164
- package/src/server/routes/background-processes.ts +0 -85
- package/src/server/routes/config.ts +0 -76
- package/src/server/routes/events.ts +0 -61
- package/src/server/routes/filesystem.ts +0 -54
- package/src/server/routes/meta.ts +0 -58
- package/src/server/routes/plugin.ts +0 -75
- package/src/server/routes/storage.ts +0 -66
- package/src/server/routes/workspaces.ts +0 -113
- package/src/server/routes/worktrees.ts +0 -195
- package/src/server/tls.ts +0 -283
- package/src/storage/instance-store.ts +0 -64
- package/src/ui/__tests__/remote-ui.test.ts +0 -58
- package/src/ui/remote-ui.ts +0 -571
- package/src/workspaces/git-worktrees.ts +0 -241
- package/src/workspaces/instance-events.ts +0 -226
- package/src/workspaces/manager.ts +0 -493
- package/src/workspaces/opencode-auth.ts +0 -22
- package/src/workspaces/runtime.ts +0 -428
- package/src/workspaces/worktree-map.ts +0 -129
- package/tsconfig.json +0 -17
package/src/launcher.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { spawn } from "child_process"
|
|
2
|
-
import os from "os"
|
|
3
|
-
import path from "path"
|
|
4
|
-
import type { Logger } from "./logger"
|
|
5
|
-
|
|
6
|
-
interface BrowserCandidate {
|
|
7
|
-
name: string
|
|
8
|
-
command: string
|
|
9
|
-
args: (url: string) => string[]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const APP_ARGS = (url: string) => [`--app=${url}`, "--new-window"]
|
|
13
|
-
|
|
14
|
-
export async function launchInBrowser(url: string, logger: Logger): Promise<boolean> {
|
|
15
|
-
const { platform, candidates, manualExamples } = buildPlatformCandidates(url)
|
|
16
|
-
|
|
17
|
-
console.log(`Attempting to launch browser (${platform}) using:`)
|
|
18
|
-
candidates.forEach((candidate) => console.log(` - ${candidate.name}: ${candidate.command}`))
|
|
19
|
-
|
|
20
|
-
for (const candidate of candidates) {
|
|
21
|
-
const success = await tryLaunch(candidate, url, logger)
|
|
22
|
-
if (success) {
|
|
23
|
-
return true
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.error(
|
|
28
|
-
"No supported browser found to launch. Run without --launch and use one of the commands below or install a compatible browser.",
|
|
29
|
-
)
|
|
30
|
-
if (manualExamples.length > 0) {
|
|
31
|
-
console.error("Manual launch commands:")
|
|
32
|
-
manualExamples.forEach((line) => console.error(` ${line}`))
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return false
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function tryLaunch(candidate: BrowserCandidate, url: string, logger: Logger): Promise<boolean> {
|
|
39
|
-
return new Promise((resolve) => {
|
|
40
|
-
let resolved = false
|
|
41
|
-
try {
|
|
42
|
-
const args = candidate.args(url)
|
|
43
|
-
const child = spawn(candidate.command, args, { stdio: "ignore", detached: true })
|
|
44
|
-
|
|
45
|
-
child.once("error", (error) => {
|
|
46
|
-
if (resolved) return
|
|
47
|
-
resolved = true
|
|
48
|
-
logger.debug({ err: error, candidate: candidate.name, command: candidate.command, args }, "Browser launch failed")
|
|
49
|
-
resolve(false)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
child.once("spawn", () => {
|
|
53
|
-
if (resolved) return
|
|
54
|
-
resolved = true
|
|
55
|
-
logger.info(
|
|
56
|
-
{
|
|
57
|
-
browser: candidate.name,
|
|
58
|
-
command: candidate.command,
|
|
59
|
-
args,
|
|
60
|
-
fullCommand: [candidate.command, ...args].join(" "),
|
|
61
|
-
},
|
|
62
|
-
"Launched browser in app mode",
|
|
63
|
-
)
|
|
64
|
-
child.unref()
|
|
65
|
-
resolve(true)
|
|
66
|
-
})
|
|
67
|
-
} catch (error) {
|
|
68
|
-
if (resolved) return
|
|
69
|
-
resolved = true
|
|
70
|
-
logger.debug({ err: error, candidate: candidate.name, command: candidate.command }, "Browser spawn threw")
|
|
71
|
-
resolve(false)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function buildPlatformCandidates(url: string) {
|
|
77
|
-
switch (os.platform()) {
|
|
78
|
-
case "darwin":
|
|
79
|
-
return {
|
|
80
|
-
platform: "macOS",
|
|
81
|
-
candidates: buildMacCandidates(),
|
|
82
|
-
manualExamples: buildMacManualExamples(url),
|
|
83
|
-
}
|
|
84
|
-
case "win32":
|
|
85
|
-
return {
|
|
86
|
-
platform: "Windows",
|
|
87
|
-
candidates: buildWindowsCandidates(),
|
|
88
|
-
manualExamples: buildWindowsManualExamples(url),
|
|
89
|
-
}
|
|
90
|
-
default:
|
|
91
|
-
return {
|
|
92
|
-
platform: "Linux",
|
|
93
|
-
candidates: buildLinuxCandidates(),
|
|
94
|
-
manualExamples: buildLinuxManualExamples(url),
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function buildMacCandidates(): BrowserCandidate[] {
|
|
100
|
-
const apps = [
|
|
101
|
-
{ name: "Google Chrome", path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" },
|
|
102
|
-
{ name: "Google Chrome Canary", path: "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary" },
|
|
103
|
-
{ name: "Microsoft Edge", path: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" },
|
|
104
|
-
{ name: "Brave Browser", path: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" },
|
|
105
|
-
{ name: "Chromium", path: "/Applications/Chromium.app/Contents/MacOS/Chromium" },
|
|
106
|
-
{ name: "Vivaldi", path: "/Applications/Vivaldi.app/Contents/MacOS/Vivaldi" },
|
|
107
|
-
{ name: "Arc", path: "/Applications/Arc.app/Contents/MacOS/Arc" },
|
|
108
|
-
]
|
|
109
|
-
|
|
110
|
-
return apps.map((entry) => ({ name: entry.name, command: entry.path, args: APP_ARGS }))
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function buildWindowsCandidates(): BrowserCandidate[] {
|
|
114
|
-
const programFiles = process.env["ProgramFiles"]
|
|
115
|
-
const programFilesX86 = process.env["ProgramFiles(x86)"]
|
|
116
|
-
const localAppData = process.env["LocalAppData"]
|
|
117
|
-
|
|
118
|
-
const paths = [
|
|
119
|
-
[programFiles, "Google/Chrome/Application/chrome.exe", "Google Chrome"],
|
|
120
|
-
[programFilesX86, "Google/Chrome/Application/chrome.exe", "Google Chrome (x86)"],
|
|
121
|
-
[localAppData, "Google/Chrome/Application/chrome.exe", "Google Chrome (User)"],
|
|
122
|
-
[programFiles, "Microsoft/Edge/Application/msedge.exe", "Microsoft Edge"],
|
|
123
|
-
[programFilesX86, "Microsoft/Edge/Application/msedge.exe", "Microsoft Edge (x86)"],
|
|
124
|
-
[localAppData, "Microsoft/Edge/Application/msedge.exe", "Microsoft Edge (User)"],
|
|
125
|
-
[programFiles, "BraveSoftware/Brave-Browser/Application/brave.exe", "Brave"],
|
|
126
|
-
[localAppData, "BraveSoftware/Brave-Browser/Application/brave.exe", "Brave (User)"],
|
|
127
|
-
[programFiles, "Chromium/Application/chromium.exe", "Chromium"],
|
|
128
|
-
] as const
|
|
129
|
-
|
|
130
|
-
return paths
|
|
131
|
-
.filter(([root]) => Boolean(root))
|
|
132
|
-
.map(([root, rel, name]) => ({
|
|
133
|
-
name,
|
|
134
|
-
command: path.join(root as string, rel),
|
|
135
|
-
args: APP_ARGS,
|
|
136
|
-
}))
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function buildLinuxCandidates(): BrowserCandidate[] {
|
|
140
|
-
const names = [
|
|
141
|
-
"google-chrome",
|
|
142
|
-
"google-chrome-stable",
|
|
143
|
-
"chromium",
|
|
144
|
-
"chromium-browser",
|
|
145
|
-
"brave-browser",
|
|
146
|
-
"microsoft-edge",
|
|
147
|
-
"microsoft-edge-stable",
|
|
148
|
-
"vivaldi",
|
|
149
|
-
]
|
|
150
|
-
|
|
151
|
-
return names.map((name) => ({ name, command: name, args: APP_ARGS }))
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function buildMacManualExamples(url: string) {
|
|
155
|
-
return [
|
|
156
|
-
`"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --app="${url}" --new-window`,
|
|
157
|
-
`"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" --app="${url}" --new-window`,
|
|
158
|
-
`"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" --app="${url}" --new-window`,
|
|
159
|
-
]
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function buildWindowsManualExamples(url: string) {
|
|
163
|
-
return [
|
|
164
|
-
`"%ProgramFiles%\\Google\\Chrome\\Application\\chrome.exe" --app="${url}" --new-window`,
|
|
165
|
-
`"%ProgramFiles%\\Microsoft\\Edge\\Application\\msedge.exe" --app="${url}" --new-window`,
|
|
166
|
-
`"%ProgramFiles%\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" --app="${url}" --new-window`,
|
|
167
|
-
]
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function buildLinuxManualExamples(url: string) {
|
|
171
|
-
return [
|
|
172
|
-
`google-chrome --app="${url}" --new-window`,
|
|
173
|
-
`chromium --app="${url}" --new-window`,
|
|
174
|
-
`brave-browser --app="${url}" --new-window`,
|
|
175
|
-
`microsoft-edge --app="${url}" --new-window`,
|
|
176
|
-
]
|
|
177
|
-
}
|
package/src/loader.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export async function resolve(specifier: string, context: any, defaultResolve: any) {
|
|
2
|
-
try {
|
|
3
|
-
return await defaultResolve(specifier, context, defaultResolve)
|
|
4
|
-
} catch (error: any) {
|
|
5
|
-
if (shouldRetry(specifier, error)) {
|
|
6
|
-
const retried = specifier.endsWith(".js") ? specifier : `${specifier}.js`
|
|
7
|
-
return defaultResolve(retried, context, defaultResolve)
|
|
8
|
-
}
|
|
9
|
-
throw error
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function shouldRetry(specifier: string, error: any) {
|
|
14
|
-
if (!error || error.code !== "ERR_MODULE_NOT_FOUND") {
|
|
15
|
-
return false
|
|
16
|
-
}
|
|
17
|
-
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
18
|
-
return true
|
|
19
|
-
}
|
|
20
|
-
return false
|
|
21
|
-
}
|
package/src/logger.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { Transform } from "node:stream"
|
|
2
|
-
import pino, { Logger as PinoLogger } from "pino"
|
|
3
|
-
|
|
4
|
-
export type Logger = PinoLogger
|
|
5
|
-
|
|
6
|
-
interface LoggerOptions {
|
|
7
|
-
level?: string
|
|
8
|
-
destination?: string
|
|
9
|
-
component?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const LEVEL_LABELS: Record<number, string> = {
|
|
13
|
-
10: "trace",
|
|
14
|
-
20: "debug",
|
|
15
|
-
30: "info",
|
|
16
|
-
40: "warn",
|
|
17
|
-
50: "error",
|
|
18
|
-
60: "fatal",
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const LIFECYCLE_COMPONENTS = new Set(["app", "workspace"])
|
|
22
|
-
const OMITTED_FIELDS = new Set(["time", "msg", "level", "component", "module"])
|
|
23
|
-
|
|
24
|
-
export function createLogger(options: LoggerOptions = {}): Logger {
|
|
25
|
-
const level = (options.level ?? process.env.CLI_LOG_LEVEL ?? "info").toLowerCase()
|
|
26
|
-
const destination = options.destination ?? process.env.CLI_LOG_DESTINATION ?? "stdout"
|
|
27
|
-
const baseComponent = options.component ?? "app"
|
|
28
|
-
const loggerOptions = {
|
|
29
|
-
level,
|
|
30
|
-
base: { component: baseComponent },
|
|
31
|
-
timestamp: false,
|
|
32
|
-
} as const
|
|
33
|
-
|
|
34
|
-
if (destination && destination !== "stdout") {
|
|
35
|
-
const stream = pino.destination({ dest: destination, mkdir: true, sync: false })
|
|
36
|
-
return pino(loggerOptions, stream)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const lifecycleStream = new LifecycleLogStream({ restrictInfoToLifecycle: level === "info" })
|
|
40
|
-
lifecycleStream.pipe(process.stdout)
|
|
41
|
-
return pino(loggerOptions, lifecycleStream)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface LifecycleStreamOptions {
|
|
45
|
-
restrictInfoToLifecycle: boolean
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
class LifecycleLogStream extends Transform {
|
|
49
|
-
private buffer = ""
|
|
50
|
-
|
|
51
|
-
constructor(private readonly options: LifecycleStreamOptions) {
|
|
52
|
-
super()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
_transform(chunk: Buffer, _encoding: BufferEncoding, callback: () => void) {
|
|
56
|
-
this.buffer += chunk.toString()
|
|
57
|
-
let newlineIndex = this.buffer.indexOf("\n")
|
|
58
|
-
while (newlineIndex >= 0) {
|
|
59
|
-
const line = this.buffer.slice(0, newlineIndex)
|
|
60
|
-
this.buffer = this.buffer.slice(newlineIndex + 1)
|
|
61
|
-
this.pushFormatted(line)
|
|
62
|
-
newlineIndex = this.buffer.indexOf("\n")
|
|
63
|
-
}
|
|
64
|
-
callback()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
_flush(callback: () => void) {
|
|
68
|
-
if (this.buffer.length > 0) {
|
|
69
|
-
this.pushFormatted(this.buffer)
|
|
70
|
-
this.buffer = ""
|
|
71
|
-
}
|
|
72
|
-
callback()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private pushFormatted(line: string) {
|
|
76
|
-
if (!line.trim()) {
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let entry: Record<string, unknown>
|
|
81
|
-
try {
|
|
82
|
-
entry = JSON.parse(line)
|
|
83
|
-
} catch {
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const levelNumber = typeof entry.level === "number" ? entry.level : 30
|
|
88
|
-
const levelLabel = LEVEL_LABELS[levelNumber] ?? "info"
|
|
89
|
-
const component = (entry.component as string | undefined) ?? (entry.module as string | undefined) ?? "app"
|
|
90
|
-
|
|
91
|
-
if (this.options.restrictInfoToLifecycle && levelNumber <= 30 && !LIFECYCLE_COMPONENTS.has(component)) {
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const message = typeof entry.msg === "string" ? entry.msg : ""
|
|
96
|
-
const metadata = this.formatMetadata(entry)
|
|
97
|
-
const formatted = metadata.length > 0 ? `[${levelLabel.toUpperCase()}] [${component}] ${message} ${metadata}` : `[${levelLabel.toUpperCase()}] [${component}] ${message}`
|
|
98
|
-
this.push(`${formatted}\n`)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private formatMetadata(entry: Record<string, unknown>): string {
|
|
102
|
-
const pairs: string[] = []
|
|
103
|
-
for (const [key, value] of Object.entries(entry)) {
|
|
104
|
-
if (OMITTED_FIELDS.has(key)) {
|
|
105
|
-
continue
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (key === "err" && value && typeof value === "object") {
|
|
109
|
-
const err = value as { type?: string; message?: string; stack?: string }
|
|
110
|
-
const errLabel = err.type ?? "Error"
|
|
111
|
-
const errMessage = err.message ? `: ${err.message}` : ""
|
|
112
|
-
pairs.push(`err=${errLabel}${errMessage}`)
|
|
113
|
-
if (err.stack) {
|
|
114
|
-
pairs.push(`stack="${err.stack}"`)
|
|
115
|
-
}
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
pairs.push(`${key}=${this.stringifyValue(value)}`)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return pairs.join(" ").trim()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private stringifyValue(value: unknown): string {
|
|
126
|
-
if (value === undefined) return "undefined"
|
|
127
|
-
if (value === null) return "null"
|
|
128
|
-
if (typeof value === "string") return value
|
|
129
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value)
|
|
130
|
-
if (value instanceof Error) return value.message ?? value.name
|
|
131
|
-
return JSON.stringify(value)
|
|
132
|
-
}
|
|
133
|
-
}
|
package/src/opencode-config.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "fs"
|
|
2
|
-
import path from "path"
|
|
3
|
-
import { fileURLToPath } from "url"
|
|
4
|
-
import { createLogger } from "./logger"
|
|
5
|
-
|
|
6
|
-
const log = createLogger({ component: "opencode-config" })
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
-
const __dirname = path.dirname(__filename)
|
|
9
|
-
const devTemplateDir = path.resolve(__dirname, "../../opencode-config")
|
|
10
|
-
const resourcesPath = (process as NodeJS.Process & { resourcesPath?: string }).resourcesPath
|
|
11
|
-
const prodTemplateDirs = [
|
|
12
|
-
resourcesPath ? path.resolve(resourcesPath, "opencode-config") : undefined,
|
|
13
|
-
path.resolve(__dirname, "opencode-config"),
|
|
14
|
-
].filter((dir): dir is string => Boolean(dir))
|
|
15
|
-
|
|
16
|
-
const isDevBuild = Boolean(process.env.CODENOMAD_DEV ?? process.env.CLI_UI_DEV_SERVER) || existsSync(devTemplateDir)
|
|
17
|
-
const templateDir = isDevBuild
|
|
18
|
-
? devTemplateDir
|
|
19
|
-
: prodTemplateDirs.find((dir) => existsSync(dir)) ?? prodTemplateDirs[0]
|
|
20
|
-
|
|
21
|
-
export function getOpencodeConfigDir(): string {
|
|
22
|
-
if (!existsSync(templateDir)) {
|
|
23
|
-
throw new Error(`CodeNomad Opencode config template missing at ${templateDir}`)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (isDevBuild) {
|
|
27
|
-
log.debug({ templateDir }, "Using Opencode config template directly (dev mode)")
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return templateDir
|
|
31
|
-
}
|
package/src/plugins/channel.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { FastifyReply } from "fastify"
|
|
2
|
-
import type { Logger } from "../logger"
|
|
3
|
-
|
|
4
|
-
export interface PluginOutboundEvent {
|
|
5
|
-
type: string
|
|
6
|
-
properties?: Record<string, unknown>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface ClientConnection {
|
|
10
|
-
reply: FastifyReply
|
|
11
|
-
workspaceId: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class PluginChannelManager {
|
|
15
|
-
private readonly clients = new Set<ClientConnection>()
|
|
16
|
-
|
|
17
|
-
constructor(private readonly logger: Logger) {}
|
|
18
|
-
|
|
19
|
-
register(workspaceId: string, reply: FastifyReply) {
|
|
20
|
-
const connection: ClientConnection = { workspaceId, reply }
|
|
21
|
-
this.clients.add(connection)
|
|
22
|
-
this.logger.debug({ workspaceId }, "Plugin SSE client connected")
|
|
23
|
-
|
|
24
|
-
let closed = false
|
|
25
|
-
const close = () => {
|
|
26
|
-
if (closed) return
|
|
27
|
-
closed = true
|
|
28
|
-
this.clients.delete(connection)
|
|
29
|
-
this.logger.debug({ workspaceId }, "Plugin SSE client disconnected")
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return { close }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
send(workspaceId: string, event: PluginOutboundEvent) {
|
|
36
|
-
for (const client of this.clients) {
|
|
37
|
-
if (client.workspaceId !== workspaceId) continue
|
|
38
|
-
this.write(client.reply, event)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
broadcast(event: PluginOutboundEvent) {
|
|
43
|
-
for (const client of this.clients) {
|
|
44
|
-
this.write(client.reply, event)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private write(reply: FastifyReply, event: PluginOutboundEvent) {
|
|
49
|
-
try {
|
|
50
|
-
reply.raw.write(`data: ${JSON.stringify(event)}\n\n`)
|
|
51
|
-
} catch (error) {
|
|
52
|
-
this.logger.warn({ err: error }, "Failed to write plugin SSE event")
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
package/src/plugins/handlers.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { EventBus } from "../events/bus"
|
|
2
|
-
import type { WorkspaceManager } from "../workspaces/manager"
|
|
3
|
-
import type { Logger } from "../logger"
|
|
4
|
-
import type { PluginOutboundEvent } from "./channel"
|
|
5
|
-
|
|
6
|
-
export interface PluginInboundEvent {
|
|
7
|
-
type: string
|
|
8
|
-
properties?: Record<string, unknown>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface HandlerDeps {
|
|
12
|
-
workspaceManager: WorkspaceManager
|
|
13
|
-
eventBus: EventBus
|
|
14
|
-
logger: Logger
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function handlePluginEvent(workspaceId: string, event: PluginInboundEvent, deps: HandlerDeps) {
|
|
18
|
-
switch (event.type) {
|
|
19
|
-
case "codenomad.pong":
|
|
20
|
-
deps.logger.debug({ workspaceId, properties: event.properties }, "Plugin pong received")
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
default:
|
|
24
|
-
deps.logger.debug({ workspaceId, eventType: event.type }, "Unhandled plugin event")
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function buildPingEvent(): PluginOutboundEvent {
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
type: "codenomad.ping",
|
|
32
|
-
properties: {
|
|
33
|
-
ts: Date.now(),
|
|
34
|
-
},
|
|
35
|
-
}
|
|
36
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { fetch } from "undici"
|
|
2
|
-
import type { LatestReleaseInfo } from "../api-types"
|
|
3
|
-
import type { Logger } from "../logger"
|
|
4
|
-
import { compareVersionStrings, stripTagPrefix } from "./release-monitor"
|
|
5
|
-
|
|
6
|
-
interface DevReleaseMonitorOptions {
|
|
7
|
-
/** Current running server version (from package.json). */
|
|
8
|
-
currentVersion: string
|
|
9
|
-
/** GitHub repo in the form "owner/name". */
|
|
10
|
-
repo: string
|
|
11
|
-
logger: Logger
|
|
12
|
-
onUpdate: (release: LatestReleaseInfo | null) => void
|
|
13
|
-
pollIntervalMs?: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface GithubReleaseListItem {
|
|
17
|
-
tag_name?: string
|
|
18
|
-
name?: string
|
|
19
|
-
html_url?: string
|
|
20
|
-
body?: string
|
|
21
|
-
published_at?: string
|
|
22
|
-
created_at?: string
|
|
23
|
-
prerelease?: boolean
|
|
24
|
-
draft?: boolean
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface DevReleaseMonitor {
|
|
28
|
-
stop(): void
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const DEFAULT_POLL_INTERVAL_MS = 15 * 60 * 1000
|
|
32
|
-
|
|
33
|
-
export function startDevReleaseMonitor(options: DevReleaseMonitorOptions): DevReleaseMonitor {
|
|
34
|
-
let stopped = false
|
|
35
|
-
let timer: ReturnType<typeof setInterval> | null = null
|
|
36
|
-
|
|
37
|
-
const pollIntervalMs =
|
|
38
|
-
Number.isFinite(options.pollIntervalMs) && (options.pollIntervalMs ?? 0) > 0
|
|
39
|
-
? (options.pollIntervalMs as number)
|
|
40
|
-
: DEFAULT_POLL_INTERVAL_MS
|
|
41
|
-
|
|
42
|
-
const refresh = async () => {
|
|
43
|
-
if (stopped) return
|
|
44
|
-
try {
|
|
45
|
-
const release = await fetchLatestPrerelease({
|
|
46
|
-
repo: options.repo,
|
|
47
|
-
currentVersion: options.currentVersion,
|
|
48
|
-
})
|
|
49
|
-
options.onUpdate(release)
|
|
50
|
-
} catch (error) {
|
|
51
|
-
options.logger.debug({ err: error }, "Failed to refresh dev prerelease information")
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
void refresh()
|
|
56
|
-
timer = setInterval(() => void refresh(), pollIntervalMs)
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
stop() {
|
|
60
|
-
stopped = true
|
|
61
|
-
if (timer) {
|
|
62
|
-
clearInterval(timer)
|
|
63
|
-
timer = null
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function fetchLatestPrerelease(args: {
|
|
70
|
-
repo: string
|
|
71
|
-
currentVersion: string
|
|
72
|
-
}): Promise<LatestReleaseInfo | null> {
|
|
73
|
-
const normalizedRepo = args.repo.trim()
|
|
74
|
-
if (!/^[^/\s]+\/[^/\s]+$/.test(normalizedRepo)) {
|
|
75
|
-
throw new Error(`Invalid GitHub repo: ${args.repo}`)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const apiUrl = `https://api.github.com/repos/${normalizedRepo}/releases?per_page=20`
|
|
79
|
-
const response = await fetch(apiUrl, {
|
|
80
|
-
headers: {
|
|
81
|
-
Accept: "application/vnd.github+json",
|
|
82
|
-
"User-Agent": "CodeNomad-CLI",
|
|
83
|
-
},
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
throw new Error(`GitHub releases API responded with ${response.status}`)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const list = (await response.json()) as GithubReleaseListItem[]
|
|
91
|
-
const latest = list.find((r) => r && r.prerelease === true && r.draft !== true)
|
|
92
|
-
if (!latest) {
|
|
93
|
-
return null
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const tag = latest.tag_name || latest.name
|
|
97
|
-
if (!tag) {
|
|
98
|
-
return null
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const normalizedVersion = stripTagPrefix(tag)
|
|
102
|
-
if (!normalizedVersion) {
|
|
103
|
-
return null
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (compareVersionStrings(normalizedVersion, args.currentVersion) <= 0) {
|
|
107
|
-
return null
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
version: normalizedVersion,
|
|
112
|
-
tag,
|
|
113
|
-
url: latest.html_url ?? `https://github.com/${normalizedRepo}/releases/tag/${encodeURIComponent(tag)}`,
|
|
114
|
-
channel: "dev",
|
|
115
|
-
publishedAt: latest.published_at ?? latest.created_at,
|
|
116
|
-
notes: latest.body,
|
|
117
|
-
}
|
|
118
|
-
}
|