@surph_ai/cli 0.0.2 → 0.0.4

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/cli.js CHANGED
@@ -17,6 +17,7 @@ const AdmZip = require('adm-zip')
17
17
  // const auth = require('./auth')
18
18
  const deploy = require('./deploy')
19
19
  const scaffold = require('./scaffold')
20
+ const { ensureChat } = require('./toolserver/ensure-chat')
20
21
  const utils = require('./utils')
21
22
  const packageJson = require('./package.json')
22
23
 
@@ -161,7 +162,11 @@ program.command('chatserver')
161
162
  .description('run chat server')
162
163
  .action(async (cmd, options) => {
163
164
  try {
164
- shell.exec('nodemon --quiet ./node_modules/@surph_ai/cli/toolserver/chat')
165
+ const chatPath = await ensureChat({
166
+ cliVersion: VERSION,
167
+ log: (msg) => console.log(colors.cyan(`\n${msg}\n`))
168
+ })
169
+ shell.exec(`nodemon --quiet "${chatPath}"`)
165
170
  } catch (error) {
166
171
  utils.printError(error)
167
172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surph_ai/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "./types.d.ts",
@@ -0,0 +1,133 @@
1
+ const crypto = require('crypto')
2
+ const fs = require('fs')
3
+ const os = require('os')
4
+ const path = require('path')
5
+
6
+ const MANIFEST_URL = 'https://surph.s3.us-east-1.amazonaws.com/tools/cli/manifest.json'
7
+ const CHAT_URL = 'https://surph.s3.us-east-1.amazonaws.com/tools/cli/chat.js'
8
+ const CACHE_DIR = path.join(os.homedir(), '.surph', 'cache')
9
+ const CACHE_FILE = path.join(CACHE_DIR, 'chat.js')
10
+ const CACHE_META = path.join(CACHE_DIR, 'chat.meta.json')
11
+ const BUNDLED_FALLBACK = path.join(__dirname, 'chat.js')
12
+
13
+ const parseVersion = (value) => String(value || '0').split('.').map((part) => {
14
+ const n = parseInt(part, 10)
15
+ return Number.isFinite(n) ? n : 0
16
+ })
17
+
18
+ const isCliTooOld = (cliVersion, minCliVersion) => {
19
+ const current = parseVersion(cliVersion)
20
+ const required = parseVersion(minCliVersion)
21
+ const length = Math.max(current.length, required.length)
22
+ for (let i = 0; i < length; i++) {
23
+ const a = current[i] || 0
24
+ const b = required[i] || 0
25
+ if (a < b) return true
26
+ if (a > b) return false
27
+ }
28
+ return false
29
+ }
30
+
31
+ const sha256 = (buf) => crypto.createHash('sha256').update(buf).digest('hex')
32
+
33
+ const readJsonFile = (filepath) => {
34
+ try {
35
+ return JSON.parse(fs.readFileSync(filepath, 'utf8'))
36
+ } catch {
37
+ return null
38
+ }
39
+ }
40
+
41
+ const cacheMatches = (manifest) => {
42
+ const meta = readJsonFile(CACHE_META)
43
+ if (!meta || meta.sha256 !== manifest.sha256) return false
44
+ if (!fs.existsSync(CACHE_FILE)) return false
45
+ if (manifest.size) {
46
+ const { size } = fs.statSync(CACHE_FILE)
47
+ if (size !== manifest.size) return false
48
+ }
49
+ return true
50
+ }
51
+
52
+ const fetchManifest = async () => {
53
+ const resp = await fetch(MANIFEST_URL, {
54
+ cache: 'no-store',
55
+ headers: { Accept: 'application/json' }
56
+ })
57
+ if (!resp.ok) {
58
+ throw new Error(`Failed to fetch chat server manifest (${resp.status})`)
59
+ }
60
+ const manifest = await resp.json()
61
+ if (!manifest || typeof manifest.sha256 !== 'string' || manifest.sha256.length !== 64) {
62
+ throw new Error('Invalid chat server manifest')
63
+ }
64
+ return manifest
65
+ }
66
+
67
+ const downloadChat = async (manifest) => {
68
+ const resp = await fetch(CHAT_URL, { cache: 'no-store' })
69
+ if (!resp.ok) {
70
+ throw new Error(`Failed to download chat server (${resp.status})`)
71
+ }
72
+
73
+ const buf = Buffer.from(await resp.arrayBuffer())
74
+ if (manifest.size && buf.length !== manifest.size) {
75
+ throw new Error(`Chat server size mismatch (expected ${manifest.size}, got ${buf.length})`)
76
+ }
77
+
78
+ const hash = sha256(buf)
79
+ if (hash !== manifest.sha256) {
80
+ throw new Error('Chat server checksum mismatch')
81
+ }
82
+
83
+ fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 })
84
+ const tmp = `${CACHE_FILE}.tmp`
85
+ fs.writeFileSync(tmp, buf, { mode: 0o600 })
86
+ fs.renameSync(tmp, CACHE_FILE)
87
+ fs.writeFileSync(CACHE_META, JSON.stringify(manifest, null, 2) + '\n', { mode: 0o600 })
88
+ }
89
+
90
+ const fallbackPath = () => {
91
+ if (fs.existsSync(CACHE_FILE)) return CACHE_FILE
92
+ if (fs.existsSync(BUNDLED_FALLBACK)) return BUNDLED_FALLBACK
93
+ return null
94
+ }
95
+
96
+ const ensureChat = async ({ cliVersion, log } = {}) => {
97
+ let manifest
98
+ try {
99
+ manifest = await fetchManifest()
100
+ } catch (error) {
101
+ const fallback = fallbackPath()
102
+ if (fallback) {
103
+ if (log) log('Using cached chat server (offline).')
104
+ return fallback
105
+ }
106
+ throw error
107
+ }
108
+
109
+ if (manifest.minCliVersion && isCliTooOld(cliVersion, manifest.minCliVersion)) {
110
+ throw new Error(
111
+ `Chat server requires CLI ${manifest.minCliVersion} or newer (current: ${cliVersion}). Update with: npm i -D @surph_ai/cli@latest`
112
+ )
113
+ }
114
+
115
+ if (cacheMatches(manifest)) {
116
+ return CACHE_FILE
117
+ }
118
+
119
+ if (log) log('Downloading latest chat server...')
120
+ try {
121
+ await downloadChat(manifest)
122
+ return CACHE_FILE
123
+ } catch (error) {
124
+ const fallback = fallbackPath()
125
+ if (fallback) {
126
+ if (log) log('Download failed, using cached chat server.')
127
+ return fallback
128
+ }
129
+ throw error
130
+ }
131
+ }
132
+
133
+ module.exports = { ensureChat }