@whoz-oss/coday-server 0.235.0 → 0.238.0
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 +10 -1
- package/postinstall.js +215 -0
- package/server.js +0 -2308
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whoz-oss/coday-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.238.0",
|
|
4
4
|
"repository": "https://github.com/whoz-oss/coday",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"coday-server": "./bin.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin.js",
|
|
12
|
+
"postinstall.js",
|
|
13
|
+
"server/",
|
|
14
|
+
"coday-client/"
|
|
15
|
+
],
|
|
10
16
|
"dependencies": {
|
|
11
17
|
"@vscode/ripgrep": "1.15.9",
|
|
12
18
|
"express": "5.1.0",
|
|
@@ -23,5 +29,8 @@
|
|
|
23
29
|
},
|
|
24
30
|
"publishConfig": {
|
|
25
31
|
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"postinstall": "node postinstall.js"
|
|
26
35
|
}
|
|
27
36
|
}
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall.js — downloads AgentOS JARs from the matching GitHub Release.
|
|
4
|
+
*
|
|
5
|
+
* The JARs are too large to bundle in the npm tarball (they are already-compressed
|
|
6
|
+
* ZIP archives and do not benefit from gzip). Instead, they are uploaded as assets
|
|
7
|
+
* on the GitHub Release that corresponds to the version of this package, and this
|
|
8
|
+
* script fetches them on first install.
|
|
9
|
+
*
|
|
10
|
+
* The script is intentionally dependency-free (only Node built-ins) so it works
|
|
11
|
+
* in any environment without a prior `npm install`.
|
|
12
|
+
*
|
|
13
|
+
* Environment variables:
|
|
14
|
+
* AGENTOS_HOSTNAME if set, an external AgentOS instance is configured — skip download
|
|
15
|
+
* AGENTOS_PORT if set, an external AgentOS instance is configured — skip download
|
|
16
|
+
* CODAY_AGENTOS_VERSION override the version used to find the GitHub Release (e.g. for testing)
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from 'crypto'
|
|
19
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync } from 'fs'
|
|
20
|
+
import { pipeline } from 'stream/promises'
|
|
21
|
+
import { dirname, resolve } from 'path'
|
|
22
|
+
import { fileURLToPath } from 'url'
|
|
23
|
+
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
25
|
+
|
|
26
|
+
const REPO = 'whoz-oss/coday'
|
|
27
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+$/
|
|
28
|
+
const ALLOWED_DOWNLOAD_HOSTS = ['github.com', 'objects.githubusercontent.com']
|
|
29
|
+
// First release that includes a checksums.sha256 manifest — older releases are exempt
|
|
30
|
+
const CHECKSUM_MANDATORY_FROM = [0, 238, 0]
|
|
31
|
+
|
|
32
|
+
// JARs expected relative to this script's directory after download
|
|
33
|
+
const JARS = [
|
|
34
|
+
{ asset: 'agentos-service.jar', dest: 'agentos/agentos-service.jar' },
|
|
35
|
+
// Plugin JARs are uploaded with versioned names (e.g. agentos-bash-plugin-1.2.3.jar).
|
|
36
|
+
// We match by prefix and rename to a stable filename on download.
|
|
37
|
+
{ assetPrefix: 'agentos-bash-plugin-', dest: 'agentos/plugins/agentos-bash-plugin.jar' },
|
|
38
|
+
{ assetPrefix: 'agentos-file-plugin-', dest: 'agentos/plugins/agentos-file-plugin.jar' },
|
|
39
|
+
{ assetPrefix: 'agentos-mcp-plugin-', dest: 'agentos/plugins/agentos-mcp-plugin.jar' },
|
|
40
|
+
{ assetPrefix: 'agentos-tmux-plugin-', dest: 'agentos/plugins/agentos-tmux-plugin.jar' },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
async function fetchJson(url) {
|
|
44
|
+
const res = await fetch(url, {
|
|
45
|
+
headers: { Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28' },
|
|
46
|
+
})
|
|
47
|
+
if (!res.ok) throw new Error(`GET ${url} → ${res.status} ${res.statusText}`)
|
|
48
|
+
return res.json()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateDownloadUrl(url) {
|
|
52
|
+
const parsed = new URL(url)
|
|
53
|
+
if (!ALLOWED_DOWNLOAD_HOSTS.includes(parsed.hostname)) {
|
|
54
|
+
throw new Error(`Refusing to download from unexpected host: ${parsed.hostname}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function downloadFile(url, destPath) {
|
|
59
|
+
validateDownloadUrl(url)
|
|
60
|
+
mkdirSync(dirname(destPath), { recursive: true })
|
|
61
|
+
const res = await fetch(url, { headers: { Accept: 'application/octet-stream' }, redirect: 'follow' })
|
|
62
|
+
if (!res.ok) throw new Error(`Download ${url} → ${res.status} ${res.statusText}`)
|
|
63
|
+
await pipeline(res.body, createWriteStream(destPath))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sha256File(filePath) {
|
|
67
|
+
const hash = createHash('sha256')
|
|
68
|
+
hash.update(readFileSync(filePath))
|
|
69
|
+
return hash.digest('hex')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isChecksumMandatory(version) {
|
|
73
|
+
const [major, minor, patch] = version.split('.').map(Number)
|
|
74
|
+
const [mMajor, mMinor, mPatch] = CHECKSUM_MANDATORY_FROM
|
|
75
|
+
if (major !== mMajor) return major > mMajor
|
|
76
|
+
if (minor !== mMinor) return minor > mMinor
|
|
77
|
+
return patch >= mPatch
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function fetchChecksums(assets, tag, version) {
|
|
81
|
+
const checksumAsset = assets.find((a) => a.name === 'checksums.sha256')
|
|
82
|
+
if (!checksumAsset) {
|
|
83
|
+
if (isChecksumMandatory(version)) {
|
|
84
|
+
const err = new Error(`No checksums.sha256 found in release ${tag} — aborting download to prevent supply-chain attack`)
|
|
85
|
+
err.securityFailure = true
|
|
86
|
+
throw err
|
|
87
|
+
}
|
|
88
|
+
console.warn(`[coday-server] No checksums.sha256 in release ${tag} (pre-${CHECKSUM_MANDATORY_FROM.join('.')} release) — skipping integrity check`)
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
validateDownloadUrl(checksumAsset.browser_download_url)
|
|
92
|
+
const res = await fetch(checksumAsset.browser_download_url, {
|
|
93
|
+
headers: { Accept: 'application/octet-stream' },
|
|
94
|
+
redirect: 'follow',
|
|
95
|
+
})
|
|
96
|
+
if (!res.ok) throw new Error(`Download checksums.sha256 → ${res.status} ${res.statusText}`)
|
|
97
|
+
const text = await res.text()
|
|
98
|
+
// Parse "<hash> <filename>" lines
|
|
99
|
+
const map = {}
|
|
100
|
+
for (const line of text.split('\n')) {
|
|
101
|
+
const parts = line.trim().split(/\s+/)
|
|
102
|
+
if (parts.length >= 2) map[parts[1]] = parts[0]
|
|
103
|
+
}
|
|
104
|
+
return map
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
if (process.env.AGENTOS_HOSTNAME || process.env.AGENTOS_PORT) {
|
|
109
|
+
console.log('[coday-server] External AgentOS configured — skipping JAR download')
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Read version from package.json sitting next to this script (or use override)
|
|
114
|
+
const { createRequire } = await import('module')
|
|
115
|
+
const require = createRequire(import.meta.url)
|
|
116
|
+
const { version: pkgVersion } = require('./package.json')
|
|
117
|
+
const rawVersion = process.env.CODAY_AGENTOS_VERSION ?? pkgVersion
|
|
118
|
+
if (!SEMVER_RE.test(rawVersion)) {
|
|
119
|
+
console.warn(`[coday-server] Invalid version format: "${rawVersion}" — skipping JAR download`)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
const version = rawVersion
|
|
123
|
+
|
|
124
|
+
const tag = `release/${version}`
|
|
125
|
+
const allPresent = JARS.every(({ dest }) => existsSync(resolve(__dirname, dest)))
|
|
126
|
+
if (allPresent) {
|
|
127
|
+
console.log(`[coday-server] AgentOS JARs already present (v${version}) — skipping download`)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(`[coday-server] Downloading AgentOS JARs v${version} from GitHub Release ${tag}...`)
|
|
132
|
+
|
|
133
|
+
let assets
|
|
134
|
+
try {
|
|
135
|
+
const release = await fetchJson(`https://api.github.com/repos/${REPO}/releases/tags/${tag}`)
|
|
136
|
+
assets = release.assets
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.warn(`[coday-server] Could not fetch release ${tag}: ${err.message}`)
|
|
139
|
+
console.warn('[coday-server] AgentOS will not be available.')
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const checksums = await fetchChecksums(assets, tag, version)
|
|
144
|
+
|
|
145
|
+
let failed = false
|
|
146
|
+
for (const { asset, assetPrefix, dest } of JARS) {
|
|
147
|
+
const destPath = resolve(__dirname, dest)
|
|
148
|
+
if (existsSync(destPath)) {
|
|
149
|
+
console.log(`[coday-server] ✓ ${dest} already present`)
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const found = asset
|
|
154
|
+
? assets.find((a) => a.name === asset)
|
|
155
|
+
: assets.find((a) => a.name.startsWith(assetPrefix) && a.name.endsWith('.jar'))
|
|
156
|
+
const assetLabel = asset ?? assetPrefix + '*'
|
|
157
|
+
if (!found) {
|
|
158
|
+
console.warn(`[coday-server] ✗ asset ${assetLabel} not found in release ${tag}`)
|
|
159
|
+
failed = true
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
process.stdout.write(`[coday-server] ↓ ${found.name}...`)
|
|
164
|
+
try {
|
|
165
|
+
await downloadFile(found.browser_download_url, destPath)
|
|
166
|
+
|
|
167
|
+
// Verify checksum if manifest is available
|
|
168
|
+
if (checksums) {
|
|
169
|
+
// The manifest uses the stable filename (basename of dest)
|
|
170
|
+
const stableFilename = dest.split('/').pop()
|
|
171
|
+
const expectedHash = checksums[stableFilename]
|
|
172
|
+
if (!expectedHash) {
|
|
173
|
+
process.stdout.write(` INTEGRITY FAILURE\n`)
|
|
174
|
+
console.error(`[coday-server] ✗ No checksum entry for ${stableFilename} in manifest — aborting`)
|
|
175
|
+
import('fs').then(({ unlinkSync }) => { try { unlinkSync(destPath) } catch {} })
|
|
176
|
+
failed = true
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
const actualHash = sha256File(destPath)
|
|
180
|
+
if (actualHash !== expectedHash) {
|
|
181
|
+
process.stdout.write(` INTEGRITY FAILURE\n`)
|
|
182
|
+
console.error(`[coday-server] ✗ Checksum mismatch for ${stableFilename}:`)
|
|
183
|
+
console.error(`[coday-server] expected: ${expectedHash}`)
|
|
184
|
+
console.error(`[coday-server] actual: ${actualHash}`)
|
|
185
|
+
// Remove the corrupted file
|
|
186
|
+
import('fs').then(({ unlinkSync }) => { try { unlinkSync(destPath) } catch {} })
|
|
187
|
+
failed = true
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
process.stdout.write(' ✓\n')
|
|
191
|
+
} else {
|
|
192
|
+
process.stdout.write(' done\n')
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
process.stdout.write(` FAILED: ${err.message}\n`)
|
|
196
|
+
failed = true
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (failed) {
|
|
201
|
+
console.warn('[coday-server] Some JARs could not be downloaded. AgentOS may not be available.')
|
|
202
|
+
} else {
|
|
203
|
+
console.log('[coday-server] AgentOS JARs downloaded successfully.')
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
main().catch((err) => {
|
|
208
|
+
if (err.securityFailure) {
|
|
209
|
+
// Hard fail on integrity/security errors — a missing checksum manifest is a red flag
|
|
210
|
+
console.error('[coday-server] SECURITY ERROR:', err.message)
|
|
211
|
+
process.exit(1)
|
|
212
|
+
}
|
|
213
|
+
// Never fail the install for network/availability issues — missing JARs just means AgentOS won't start
|
|
214
|
+
console.warn('[coday-server] postinstall error:', err.message)
|
|
215
|
+
})
|