mio-agent-runtime 0.5.15 → 0.5.17
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/README.md +144 -236
- package/adapters/codex.js +122 -134
- package/adapters/hermes.js +122 -140
- package/adapters/host-utils.js +61 -61
- package/adapters/opencode.js +176 -215
- package/adapters/workbuddy.js +196 -242
- package/bin/mio.js +423 -424
- package/observe/observer.js +1326 -1335
- package/package.json +38 -25
- package/scripts/verify-packed-runtime.js +371 -0
- package/server/creativity-engine.js +397 -0
- package/server/evolution-cutover.js +119 -0
- package/server/host-capabilities.js +50 -0
- package/server/mio-intelligence-mcp/README.md +184 -173
- package/server/mio-intelligence-mcp/index.js +621 -300
- package/server/mio-intelligence-mcp/package.json +17 -17
- package/server/mio-intelligence-mcp/phase0.js +381 -381
- package/server/runtime-modules.js +111 -0
- package/server/mio-intelligence-mcp/dashboard-server.js +0 -171
package/bin/mio.js
CHANGED
|
@@ -1,478 +1,477 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const os = require('os')
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
const SERVER_SCRIPT = path.resolve(__dirname, '..', 'server', 'mio-intelligence-mcp', 'index.js')
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
hermes: require('../adapters/hermes.js'),
|
|
20
|
-
}
|
|
7
|
+
const { execFileSync } = require('child_process')
|
|
8
|
+
|
|
9
|
+
const SERVER_SCRIPT = path.resolve(__dirname, '..', 'server', 'mio-intelligence-mcp', 'index.js')
|
|
10
|
+
const MIO_HOME = process.env.MIO_HOME || path.join(os.homedir(), '.mio-intelligence')
|
|
11
|
+
const CONFIG_FILE = path.join(MIO_HOME, 'config.json')
|
|
12
|
+
|
|
13
|
+
const adapters = {
|
|
14
|
+
codex: require('../adapters/codex.js'),
|
|
15
|
+
opencode: require('../adapters/opencode.js'),
|
|
16
|
+
workbuddy: require('../adapters/workbuddy.js'),
|
|
17
|
+
hermes: require('../adapters/hermes.js'),
|
|
18
|
+
}
|
|
21
19
|
const observer = require('../observe/observer.js')
|
|
20
|
+
const { getEvolutionStatus, formatEvolutionStatusText } = require('../server/runtime-modules.js')
|
|
21
|
+
const { createEvolutionCutoverTools } = require('../server/evolution-cutover.js')
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
fs.mkdirSync(
|
|
25
|
-
fs.
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function readConfig() {
|
|
29
|
-
const fallback = { version: 1, home: MIO_HOME, agents: {} }
|
|
30
|
-
if (!fs.existsSync(CONFIG_FILE)) return fallback
|
|
31
|
-
try {
|
|
32
|
-
return { ...fallback, ...JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) }
|
|
33
|
-
} catch (_) {
|
|
34
|
-
return fallback
|
|
35
|
-
}
|
|
23
|
+
function appendJsonl(file, value) {
|
|
24
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
25
|
+
fs.appendFileSync(file, `${JSON.stringify(value)}\n`, 'utf8')
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
fs
|
|
28
|
+
function readJsonl(file) {
|
|
29
|
+
if (!fs.existsSync(file)) return []
|
|
30
|
+
return fs
|
|
31
|
+
.readFileSync(file, 'utf8')
|
|
32
|
+
.split(/\r?\n/)
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.map((line) => {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(line)
|
|
37
|
+
} catch (_) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
.filter(Boolean)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
function projectName() {
|
|
45
|
+
try {
|
|
46
|
+
const commonDir = execFileSync(
|
|
47
|
+
'git',
|
|
48
|
+
['rev-parse', '--path-format=absolute', '--git-common-dir'],
|
|
49
|
+
{
|
|
50
|
+
cwd: process.cwd(),
|
|
51
|
+
encoding: 'utf8',
|
|
52
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
53
|
+
}
|
|
54
|
+
).trim()
|
|
55
|
+
if (commonDir) return path.basename(path.dirname(commonDir))
|
|
56
|
+
} catch (_) {}
|
|
57
|
+
return path.basename(process.cwd())
|
|
49
58
|
}
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
const evolutionCutover = createEvolutionCutoverTools({
|
|
61
|
+
dataDir: MIO_HOME,
|
|
62
|
+
appendJsonl,
|
|
63
|
+
readJsonl,
|
|
64
|
+
projectName,
|
|
65
|
+
})
|
|
55
66
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
writeConfig(config)
|
|
61
|
-
console.log(`MIO_HOME ready: ${MIO_HOME}`)
|
|
67
|
+
function optionValue(args, name) {
|
|
68
|
+
const index = args.indexOf(name)
|
|
69
|
+
if (index === -1) return undefined
|
|
70
|
+
return args[index + 1]
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
const codexRestart = adapters.codex.restartWarningMessage()
|
|
67
|
-
const opencodeRestart = adapters.opencode.restartWarningMessage()
|
|
68
|
-
const installed = {
|
|
69
|
-
codex: adapters.codex.isInstalled(),
|
|
70
|
-
opencode: adapters.opencode.isInstalled(),
|
|
71
|
-
workbuddy: adapters.workbuddy.isInstalled(),
|
|
72
|
-
hermes: adapters.hermes.isInstalled(),
|
|
73
|
-
}
|
|
74
|
-
const payload = {
|
|
75
|
-
version: config.version,
|
|
76
|
-
home: MIO_HOME,
|
|
77
|
-
serverScript: SERVER_SCRIPT,
|
|
78
|
-
serverScriptExists: fs.existsSync(SERVER_SCRIPT),
|
|
79
|
-
agents: config.agents || {},
|
|
80
|
-
codexInstalled: installed.codex,
|
|
81
|
-
opencodeInstalled: installed.opencode,
|
|
82
|
-
workbuddyInstalled: installed.workbuddy,
|
|
83
|
-
hermesInstalled: installed.hermes,
|
|
84
|
-
codexNeedsRestart: Boolean(codexRestart),
|
|
85
|
-
opencodeNeedsRestart: Boolean(opencodeRestart),
|
|
86
|
-
codexRestartHint: codexRestart,
|
|
87
|
-
opencodeRestartHint: opencodeRestart,
|
|
88
|
-
hermesObserverReady: fs.existsSync(observer.hermesDbPath()),
|
|
89
|
-
opencodeObserverReady: fs.existsSync(observer.opencodeDbPath()),
|
|
90
|
-
observerRunning: observer.isRunning(MIO_HOME),
|
|
91
|
-
observerPid: observer.isRunning(MIO_HOME) ? observer.readPid(MIO_HOME) : null,
|
|
92
|
-
}
|
|
93
|
-
if (useJson) {
|
|
94
|
-
jsonOrText(payload, true)
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
console.log(`MIO_HOME: ${MIO_HOME}`)
|
|
98
|
-
console.log(`MCP Server: ${SERVER_SCRIPT}`)
|
|
99
|
-
console.log(`Codex MCP: ${installed.codex ? 'installed' : 'not installed'}`)
|
|
100
|
-
if (codexRestart) console.log(`Codex app: running with an older Mio config - fully restart Codex to load it`)
|
|
101
|
-
console.log(`OpenCode MCP: ${installed.opencode ? 'installed' : 'not installed'}`)
|
|
102
|
-
if (opencodeRestart) console.log(`OpenCode Desktop: running with an older Mio config - restart it to load it`)
|
|
103
|
-
console.log(`OpenCode observer: ${fs.existsSync(observer.opencodeDbPath()) ? 'ready' : 'no database'}`)
|
|
104
|
-
console.log(`WorkBuddy MCP: ${installed.workbuddy ? 'installed' : 'not installed'}`)
|
|
105
|
-
console.log(`Hermes MCP: ${installed.hermes ? 'installed' : 'not installed'}`)
|
|
106
|
-
console.log(`Hermes observer: ${fs.existsSync(observer.hermesDbPath()) ? 'ready' : 'no database'}`)
|
|
107
|
-
console.log(`Observer: ${observer.isRunning(MIO_HOME) ? 'running (pid ' + observer.readPid(MIO_HOME) + ')' : 'not running'}`)
|
|
73
|
+
function flagPresent(args, name) {
|
|
74
|
+
return args.includes(name)
|
|
108
75
|
}
|
|
109
76
|
|
|
110
|
-
function
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
if (useJson) {
|
|
114
|
-
jsonOrText(agents, true)
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
const names = Object.keys(agents)
|
|
118
|
-
if (names.length === 0) {
|
|
119
|
-
console.log('No agents installed.')
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
for (const name of names) {
|
|
123
|
-
console.log(`${name}\t${agents[name].installedAt || ''}`)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function installCommand(host, useJson) {
|
|
128
|
-
if (!host) {
|
|
129
|
-
console.error('Usage: mio install <codex|opencode|workbuddy|hermes>')
|
|
130
|
-
process.exitCode = 1
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
const adapter = adapters[host]
|
|
134
|
-
if (!adapter) {
|
|
135
|
-
console.error(`Unknown host: ${host}`)
|
|
136
|
-
process.exitCode = 1
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
ensureHome()
|
|
140
|
-
const config = readConfig()
|
|
141
|
-
let result
|
|
77
|
+
function parseJsonOption(args, name) {
|
|
78
|
+
const raw = optionValue(args, name)
|
|
79
|
+
if (raw === undefined) throw new Error(`${name} is required`)
|
|
142
80
|
try {
|
|
143
|
-
|
|
144
|
-
node: process.execPath,
|
|
145
|
-
serverScript: SERVER_SCRIPT,
|
|
146
|
-
home: MIO_HOME,
|
|
147
|
-
workspace: process.cwd(),
|
|
148
|
-
})
|
|
81
|
+
return JSON.parse(raw)
|
|
149
82
|
} catch (error) {
|
|
150
|
-
|
|
151
|
-
process.exitCode = 1
|
|
152
|
-
return
|
|
83
|
+
throw new Error(`${name} must be valid JSON`)
|
|
153
84
|
}
|
|
154
|
-
if (result.changed) {
|
|
155
|
-
config.agents = config.agents || {}
|
|
156
|
-
config.agents[host] = {
|
|
157
|
-
installedAt: new Date().toISOString(),
|
|
158
|
-
configPath: result.configPath || null,
|
|
159
|
-
}
|
|
160
|
-
writeConfig(config)
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
observer.runCycle(MIO_HOME, observer.loadState(MIO_HOME), observer.createSinks(MIO_HOME))
|
|
164
|
-
} catch (_) {}
|
|
165
|
-
let observerStarted = false
|
|
166
|
-
try {
|
|
167
|
-
observerStarted = observer.startBackground(MIO_HOME).started
|
|
168
|
-
} catch (_) {}
|
|
169
|
-
if (useJson) {
|
|
170
|
-
result.observer = {
|
|
171
|
-
running: observer.isRunning(MIO_HOME),
|
|
172
|
-
started: observerStarted,
|
|
173
|
-
}
|
|
174
|
-
jsonOrText(result, true)
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
console.log(result.message || `${host} adapter completed.`)
|
|
178
|
-
console.log(
|
|
179
|
-
observer.isRunning(MIO_HOME)
|
|
180
|
-
? 'Observer daemon running.'
|
|
181
|
-
: 'Observer daemon not running. Run `mio observe --start`.'
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function mcpCommand() {
|
|
186
|
-
require(SERVER_SCRIPT)
|
|
187
85
|
}
|
|
188
86
|
|
|
189
|
-
function
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
console.log(result.started ? 'Observer started.' : 'Observer already running.')
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
if (flags.includes('--stop')) {
|
|
199
|
-
const result = observer.stopBackground(MIO_HOME)
|
|
200
|
-
if (useJson) return jsonOrText(result, true)
|
|
201
|
-
console.log(result.stopped ? 'Observer stopped.' : 'Observer was not running.')
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
if (flags.includes('--status')) {
|
|
205
|
-
const running = observer.isRunning(MIO_HOME)
|
|
206
|
-
const pid = observer.readPid(MIO_HOME)
|
|
207
|
-
if (useJson) return jsonOrText({ running, pid }, true)
|
|
208
|
-
console.log(running ? 'Observer: running (pid ' + pid + ')' : 'Observer: not running')
|
|
209
|
-
return
|
|
210
|
-
}
|
|
211
|
-
if (flags.includes('--once')) {
|
|
212
|
-
const res = observer.runCycle(
|
|
213
|
-
MIO_HOME,
|
|
214
|
-
observer.loadState(MIO_HOME),
|
|
215
|
-
observer.createSinks(MIO_HOME)
|
|
216
|
-
)
|
|
217
|
-
if (useJson) return jsonOrText(res, true)
|
|
218
|
-
console.log('Observed files=' + res.files + ' lines=' + res.lines + ' outcomes=' + res.outcomes)
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
console.log('Observing WorkBuddy transcripts into ' + MIO_HOME + ' (Ctrl+C to stop)...')
|
|
222
|
-
observer.startForeground(MIO_HOME)
|
|
87
|
+
function parseNumberOption(args, name) {
|
|
88
|
+
const raw = optionValue(args, name)
|
|
89
|
+
if (raw === undefined) return undefined
|
|
90
|
+
const value = Number(raw)
|
|
91
|
+
if (!Number.isFinite(value)) throw new Error(`${name} must be a number`)
|
|
92
|
+
return value
|
|
223
93
|
}
|
|
224
94
|
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
console.error('Usage: mio uninstall <codex|opencode|workbuddy|hermes>')
|
|
228
|
-
process.exitCode = 1
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
const adapter = adapters[host]
|
|
232
|
-
if (!adapter) {
|
|
233
|
-
console.error(`Unknown host: ${host}`)
|
|
234
|
-
process.exitCode = 1
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
let result
|
|
238
|
-
try {
|
|
239
|
-
result = adapter.uninstall({
|
|
240
|
-
node: process.execPath,
|
|
241
|
-
serverScript: SERVER_SCRIPT,
|
|
242
|
-
home: MIO_HOME,
|
|
243
|
-
workspace: process.cwd(),
|
|
244
|
-
})
|
|
245
|
-
} catch (error) {
|
|
246
|
-
console.error(`Failed to uninstall ${host}: ${error.message || error}`)
|
|
247
|
-
process.exitCode = 1
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
if (result.changed) {
|
|
251
|
-
const config = readConfig()
|
|
252
|
-
if (config.agents && config.agents[host]) {
|
|
253
|
-
delete config.agents[host]
|
|
254
|
-
writeConfig(config)
|
|
255
|
-
}
|
|
256
|
-
const remaining = Object.keys(config.agents || {}).length
|
|
257
|
-
if (remaining === 0 && observer.isRunning(MIO_HOME)) {
|
|
258
|
-
observer.stopBackground(MIO_HOME)
|
|
259
|
-
}
|
|
260
|
-
}
|
|
95
|
+
function evolutionStatus(useJson) {
|
|
96
|
+
const payload = getEvolutionStatus()
|
|
261
97
|
if (useJson) {
|
|
262
|
-
jsonOrText(
|
|
98
|
+
jsonOrText(payload, true)
|
|
263
99
|
return
|
|
264
100
|
}
|
|
265
|
-
console.log(
|
|
101
|
+
console.log(formatEvolutionStatusText(payload))
|
|
266
102
|
}
|
|
267
103
|
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
} catch (e) {
|
|
274
|
-
return { ok: false, error: e.message, stdout: '', stderr: '' }
|
|
275
|
-
}
|
|
104
|
+
function printCutoverReadiness(readiness, useJson) {
|
|
105
|
+
if (useJson) return jsonOrText(readiness, true)
|
|
106
|
+
console.log(`Cutover readiness for ${readiness.project}: ${readiness.status}`)
|
|
107
|
+
console.log(`shadow=${readiness.shadowRuns} dual-write=${readiness.dualWriteRuns}`)
|
|
108
|
+
if (readiness.reasons.length > 0) console.log(`reasons: ${readiness.reasons.join('; ')}`)
|
|
276
109
|
}
|
|
277
110
|
|
|
278
|
-
function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
111
|
+
function printAuthorityPlan(plan, useJson) {
|
|
112
|
+
if (useJson) return jsonOrText(plan, true)
|
|
113
|
+
console.log(`Authority switch ${plan.from} -> ${plan.to}: ${plan.approved ? 'ready' : 'blocked'}`)
|
|
114
|
+
for (const action of plan.actions) console.log(`- ${action.type}`)
|
|
115
|
+
for (const blocker of plan.blockers) console.log(`blocked: ${blocker}`)
|
|
282
116
|
}
|
|
283
117
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
118
|
+
function printMigrationPlan(plan, useJson) {
|
|
119
|
+
if (useJson) return jsonOrText(plan, true)
|
|
120
|
+
console.log(`Migration plan: ${plan.ready ? 'ready' : 'conflicts'}`)
|
|
121
|
+
console.log(
|
|
122
|
+
`legacy=${plan.summary.legacy} modular=${plan.summary.modular} toCreate=${plan.summary.toCreate} conflicts=${plan.summary.conflicts}`
|
|
123
|
+
)
|
|
288
124
|
}
|
|
289
125
|
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (useJson) return jsonOrText({ ok: false, error: msg }, true)
|
|
296
|
-
console.error(msg)
|
|
297
|
-
process.exitCode = 1
|
|
298
|
-
return
|
|
299
|
-
}
|
|
300
|
-
const after = npmGlobalVersion()
|
|
301
|
-
const prefix = (() => { const r = runNpm(['config', 'get', 'prefix']); return r.stdout ? r.stdout.trim() : '' })()
|
|
302
|
-
const isWin = process.platform === 'win32'
|
|
303
|
-
const globalServer = prefix ? path.join(prefix, 'node_modules', 'mio-agent-runtime', 'server', 'mio-intelligence-mcp', 'index.js') : SERVER_SCRIPT
|
|
304
|
-
const globalNode = prefix ? (isWin ? path.join(prefix, 'node.exe') : path.join(prefix, 'bin', 'node')) : process.execPath
|
|
305
|
-
const nodeBin = fs.existsSync(globalNode) ? globalNode : process.execPath
|
|
306
|
-
const config = readConfig()
|
|
307
|
-
const hosts = Object.keys(config.agents || {})
|
|
308
|
-
const rewired = []
|
|
309
|
-
for (const host of hosts) {
|
|
310
|
-
const adapter = adapters[host]
|
|
311
|
-
if (!adapter) continue
|
|
312
|
-
try {
|
|
313
|
-
const result = adapter.install({ node: nodeBin, serverScript: globalServer, home: MIO_HOME, workspace: process.cwd() })
|
|
314
|
-
if (result.changed) rewired.push(host)
|
|
315
|
-
} catch (_) {}
|
|
316
|
-
}
|
|
317
|
-
let observerStarted = false
|
|
318
|
-
try { observerStarted = observer.startBackground(MIO_HOME).started } catch (_) {}
|
|
319
|
-
const payload = { ok: true, before, after, rewiredHosts: rewired, observerStarted }
|
|
320
|
-
if (useJson) return jsonOrText(payload, true)
|
|
321
|
-
console.log('Upgraded Mio: ' + (before || '?') + ' -> ' + (after || '?'))
|
|
322
|
-
if (rewired.length) console.log('Re-wired hosts: ' + rewired.join(', '))
|
|
323
|
-
else console.log('All installed hosts already up to date.')
|
|
324
|
-
console.log(observerStarted ? 'Observer started.' : 'Observer already running.')
|
|
126
|
+
function printCutoverDryRun(result, useJson) {
|
|
127
|
+
if (useJson) return jsonOrText(result, true)
|
|
128
|
+
console.log(`Cutover apply dry-run: ${result.status}`)
|
|
129
|
+
console.log(`applied=${result.applied}`)
|
|
130
|
+
for (const action of result.actions) console.log(`- ${action.type}`)
|
|
325
131
|
}
|
|
326
132
|
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (fs.existsSync(MIO_HOME)) {
|
|
332
|
-
try { fs.accessSync(MIO_HOME, fs.constants.W_OK); add('MIO_HOME writable', true) }
|
|
333
|
-
catch (_) { add('MIO_HOME writable', false, 'no write permission') }
|
|
334
|
-
}
|
|
335
|
-
add('MCP server present', fs.existsSync(SERVER_SCRIPT), SERVER_SCRIPT)
|
|
336
|
-
for (const host of ['codex', 'opencode', 'workbuddy', 'hermes']) {
|
|
337
|
-
const adapter = adapters[host]
|
|
338
|
-
const installed = adapter ? adapter.isInstalled() : false
|
|
339
|
-
add('host:' + host + ' installed', installed)
|
|
340
|
-
}
|
|
341
|
-
const obsRunning = observer.isRunning(MIO_HOME)
|
|
342
|
-
add('observer running', obsRunning, obsRunning ? 'pid ' + observer.readPid(MIO_HOME) : 'not running')
|
|
343
|
-
const installed = npmGlobalVersion()
|
|
344
|
-
const latest = npmLatestVersion()
|
|
345
|
-
add('npm up to date', installed === latest, 'installed ' + installed + ' / latest ' + latest)
|
|
346
|
-
const prefix = (() => { const r = runNpm(['config', 'get', 'prefix']); return r.stdout ? r.stdout.trim() : '' })()
|
|
347
|
-
const execPrefix = path.dirname(process.execPath)
|
|
348
|
-
add('global bin on PATH', prefix && execPrefix && prefix.toLowerCase() === execPrefix.toLowerCase(), 'npm prefix ' + prefix + ' / node ' + execPrefix)
|
|
349
|
-
const stray0 = path.join(process.cwd(), '0')
|
|
350
|
-
add('no stray 0 file', !fs.existsSync(stray0), fs.existsSync(stray0) ? stray0 : '')
|
|
351
|
-
const failed = checks.filter(function (c) { return !c.ok })
|
|
352
|
-
const payload = { ok: failed.length === 0, checks: checks, failedCount: failed.length }
|
|
353
|
-
if (useJson) return jsonOrText(payload, true)
|
|
354
|
-
for (const c of checks) {
|
|
355
|
-
console.log((c.ok ? 'OK ' : 'FAIL ') + c.name + (c.detail ? ' (' + c.detail + ')' : ''))
|
|
356
|
-
}
|
|
357
|
-
console.log('')
|
|
358
|
-
console.log(failed.length === 0 ? 'All checks passed.' : failed.length + ' check(s) failed. Run `mio upgrade` or reinstall affected hosts.')
|
|
133
|
+
function printShadowRecord(record, useJson) {
|
|
134
|
+
if (useJson) return jsonOrText(record, true)
|
|
135
|
+
console.log(`Shadow comparison ${record.id}: ${record.matched ? 'matched' : 'mismatch'}`)
|
|
136
|
+
if (record.diffs.length > 0) console.log(`diffs=${record.diffs.length}`)
|
|
359
137
|
}
|
|
360
138
|
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
try { process.kill(parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10), 0); return true } catch (_) { return false }
|
|
139
|
+
function printDualWriteRecord(record, useJson) {
|
|
140
|
+
if (useJson) return jsonOrText(record, true)
|
|
141
|
+
console.log(`Dual-write sample ${record.id}: authoritative=${record.authoritative}`)
|
|
142
|
+
console.log(`shadow=${record.shadow.matched ? 'matched' : 'mismatch'}`)
|
|
366
143
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
144
|
+
|
|
145
|
+
async function evolutionCommand(args, useJson) {
|
|
146
|
+
if (args[1] === 'status') return evolutionStatus(useJson)
|
|
147
|
+
try {
|
|
148
|
+
if (args[1] === 'shadow' && args[2] === 'record') {
|
|
149
|
+
return printShadowRecord(
|
|
150
|
+
await evolutionCutover.recordShadowComparison({
|
|
151
|
+
project: optionValue(args, '--project'),
|
|
152
|
+
label: optionValue(args, '--label'),
|
|
153
|
+
legacy: parseJsonOption(args, '--legacy'),
|
|
154
|
+
modular: parseJsonOption(args, '--modular'),
|
|
155
|
+
input: optionValue(args, '--input') === undefined ? undefined : parseJsonOption(args, '--input'),
|
|
156
|
+
}),
|
|
157
|
+
useJson,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
if (args[1] === 'dual-write' && args[2] === 'record') {
|
|
161
|
+
return printDualWriteRecord(
|
|
162
|
+
await evolutionCutover.recordDualWrite({
|
|
163
|
+
project: optionValue(args, '--project'),
|
|
164
|
+
label: optionValue(args, '--label'),
|
|
165
|
+
authoritative: optionValue(args, '--authoritative'),
|
|
166
|
+
legacyResult: parseJsonOption(args, '--legacy-result'),
|
|
167
|
+
modularResult: parseJsonOption(args, '--modular-result'),
|
|
168
|
+
record: optionValue(args, '--record') === undefined ? undefined : parseJsonOption(args, '--record'),
|
|
169
|
+
}),
|
|
170
|
+
useJson,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
if (args[1] === 'cutover' && args[2] === 'readiness') {
|
|
174
|
+
return printCutoverReadiness(
|
|
175
|
+
evolutionCutover.cutoverReadiness({
|
|
176
|
+
project: optionValue(args, '--project'),
|
|
177
|
+
minShadowRuns: parseNumberOption(args, '--min-shadow-runs'),
|
|
178
|
+
maxMismatchRate: parseNumberOption(args, '--max-mismatch-rate'),
|
|
179
|
+
}),
|
|
180
|
+
useJson,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
if (args[1] === 'cutover' && args[2] === 'apply') {
|
|
184
|
+
return printCutoverDryRun(
|
|
185
|
+
evolutionCutover.applyCutover({
|
|
186
|
+
dryRun: flagPresent(args, '--dry-run'),
|
|
187
|
+
project: optionValue(args, '--project'),
|
|
188
|
+
plan: parseJsonOption(args, '--plan'),
|
|
189
|
+
}),
|
|
190
|
+
useJson,
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
if (args[1] === 'authority' && args[2] === 'plan') {
|
|
194
|
+
return printAuthorityPlan(
|
|
195
|
+
evolutionCutover.authorityPlan({
|
|
196
|
+
readiness: parseJsonOption(args, '--readiness'),
|
|
197
|
+
from: optionValue(args, '--from'),
|
|
198
|
+
to: optionValue(args, '--to'),
|
|
199
|
+
}),
|
|
200
|
+
useJson,
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
if (args[1] === 'migration' && args[2] === 'plan') {
|
|
204
|
+
return printMigrationPlan(
|
|
205
|
+
evolutionCutover.migrationPlan({
|
|
206
|
+
legacyRecords: parseJsonOption(args, '--legacy-records'),
|
|
207
|
+
modularRecords: parseJsonOption(args, '--modular-records'),
|
|
208
|
+
}),
|
|
209
|
+
useJson,
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(error.message || error)
|
|
214
|
+
process.exitCode = 1
|
|
410
215
|
return
|
|
411
216
|
}
|
|
412
|
-
|
|
413
|
-
|
|
217
|
+
console.error('Usage: mio evolution status|shadow record|dual-write record|cutover readiness|cutover apply|authority plan|migration plan')
|
|
218
|
+
process.exitCode = 1
|
|
414
219
|
}
|
|
415
|
-
|
|
416
|
-
function
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
220
|
+
|
|
221
|
+
function ensureHome() {
|
|
222
|
+
fs.mkdirSync(MIO_HOME, { recursive: true })
|
|
223
|
+
fs.mkdirSync(path.join(MIO_HOME, 'logs'), { recursive: true })
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function readConfig() {
|
|
227
|
+
const fallback = { version: 1, home: MIO_HOME, agents: {} }
|
|
228
|
+
if (!fs.existsSync(CONFIG_FILE)) return fallback
|
|
229
|
+
try {
|
|
230
|
+
return { ...fallback, ...JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) }
|
|
231
|
+
} catch (_) {
|
|
232
|
+
return fallback
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function writeConfig(config) {
|
|
237
|
+
ensureHome()
|
|
238
|
+
fs.writeFileSync(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function jsonOrText(payload, useJson) {
|
|
242
|
+
if (useJson) {
|
|
243
|
+
console.log(JSON.stringify(payload, null, 2))
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
return payload
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function initCommand() {
|
|
250
|
+
ensureHome()
|
|
251
|
+
const config = readConfig()
|
|
252
|
+
if (!config.createdAt) config.createdAt = new Date().toISOString()
|
|
253
|
+
writeConfig(config)
|
|
254
|
+
console.log(`MIO_HOME ready: ${MIO_HOME}`)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function statusCommand(useJson) {
|
|
258
|
+
const config = readConfig()
|
|
259
|
+
const codexRestart = adapters.codex.restartWarningMessage()
|
|
260
|
+
const opencodeRestart = adapters.opencode.restartWarningMessage()
|
|
261
|
+
const installed = {
|
|
262
|
+
codex: adapters.codex.isInstalled(),
|
|
263
|
+
opencode: adapters.opencode.isInstalled(),
|
|
264
|
+
workbuddy: adapters.workbuddy.isInstalled(),
|
|
265
|
+
hermes: adapters.hermes.isInstalled(),
|
|
266
|
+
}
|
|
267
|
+
const payload = {
|
|
268
|
+
version: config.version,
|
|
269
|
+
home: MIO_HOME,
|
|
270
|
+
serverScript: SERVER_SCRIPT,
|
|
271
|
+
serverScriptExists: fs.existsSync(SERVER_SCRIPT),
|
|
272
|
+
agents: config.agents || {},
|
|
273
|
+
codexInstalled: installed.codex,
|
|
274
|
+
opencodeInstalled: installed.opencode,
|
|
275
|
+
workbuddyInstalled: installed.workbuddy,
|
|
276
|
+
hermesInstalled: installed.hermes,
|
|
277
|
+
codexNeedsRestart: Boolean(codexRestart),
|
|
278
|
+
opencodeNeedsRestart: Boolean(opencodeRestart),
|
|
279
|
+
codexRestartHint: codexRestart,
|
|
280
|
+
opencodeRestartHint: opencodeRestart,
|
|
281
|
+
hermesObserverReady: fs.existsSync(observer.hermesDbPath()),
|
|
282
|
+
opencodeObserverReady: fs.existsSync(observer.opencodeDbPath()),
|
|
283
|
+
observerRunning: observer.isRunning(MIO_HOME),
|
|
284
|
+
observerPid: observer.isRunning(MIO_HOME) ? observer.readPid(MIO_HOME) : null,
|
|
285
|
+
}
|
|
286
|
+
if (useJson) {
|
|
287
|
+
jsonOrText(payload, true)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
console.log(`MIO_HOME: ${MIO_HOME}`)
|
|
291
|
+
console.log(`MCP Server: ${SERVER_SCRIPT}`)
|
|
292
|
+
console.log(`Codex MCP: ${installed.codex ? 'installed' : 'not installed'}`)
|
|
293
|
+
if (codexRestart) console.log(`Codex app: running with an older Mio config - fully restart Codex to load it`)
|
|
294
|
+
console.log(`OpenCode MCP: ${installed.opencode ? 'installed' : 'not installed'}`)
|
|
295
|
+
if (opencodeRestart) console.log(`OpenCode Desktop: running with an older Mio config - restart it to load it`)
|
|
296
|
+
console.log(`OpenCode observer: ${fs.existsSync(observer.opencodeDbPath()) ? 'ready' : 'no database'}`)
|
|
297
|
+
console.log(`WorkBuddy MCP: ${installed.workbuddy ? 'installed' : 'not installed'}`)
|
|
298
|
+
console.log(`Hermes MCP: ${installed.hermes ? 'installed' : 'not installed'}`)
|
|
299
|
+
console.log(`Hermes observer: ${fs.existsSync(observer.hermesDbPath()) ? 'ready' : 'no database'}`)
|
|
300
|
+
console.log(`Observer: ${observer.isRunning(MIO_HOME) ? 'running (pid ' + observer.readPid(MIO_HOME) + ')' : 'not running'}`)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function agentsCommand(useJson) {
|
|
304
|
+
const config = readConfig()
|
|
305
|
+
const agents = config.agents || {}
|
|
306
|
+
if (useJson) {
|
|
307
|
+
jsonOrText(agents, true)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
const names = Object.keys(agents)
|
|
311
|
+
if (names.length === 0) {
|
|
312
|
+
console.log('No agents installed.')
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
for (const name of names) {
|
|
316
|
+
console.log(`${name}\t${agents[name].installedAt || ''}`)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function installCommand(host, useJson) {
|
|
321
|
+
if (!host) {
|
|
322
|
+
console.error('Usage: mio install <codex|opencode|workbuddy|hermes>')
|
|
323
|
+
process.exitCode = 1
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
const adapter = adapters[host]
|
|
327
|
+
if (!adapter) {
|
|
328
|
+
console.error(`Unknown host: ${host}`)
|
|
329
|
+
process.exitCode = 1
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
ensureHome()
|
|
333
|
+
const config = readConfig()
|
|
334
|
+
let result
|
|
335
|
+
try {
|
|
336
|
+
result = adapter.install({
|
|
337
|
+
node: process.execPath,
|
|
338
|
+
serverScript: SERVER_SCRIPT,
|
|
339
|
+
home: MIO_HOME,
|
|
340
|
+
workspace: process.cwd(),
|
|
341
|
+
})
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.error(`Failed to install ${host}: ${error.message || error}`)
|
|
344
|
+
process.exitCode = 1
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
if (result.changed) {
|
|
348
|
+
config.agents = config.agents || {}
|
|
349
|
+
config.agents[host] = {
|
|
350
|
+
installedAt: new Date().toISOString(),
|
|
351
|
+
configPath: result.configPath || null,
|
|
352
|
+
}
|
|
353
|
+
writeConfig(config)
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
observer.runCycle(MIO_HOME, observer.loadState(MIO_HOME), observer.createSinks(MIO_HOME))
|
|
357
|
+
} catch (_) {}
|
|
358
|
+
let observerStarted = false
|
|
359
|
+
try {
|
|
360
|
+
observerStarted = observer.startBackground(MIO_HOME).started
|
|
361
|
+
} catch (_) {}
|
|
362
|
+
if (useJson) {
|
|
363
|
+
result.observer = {
|
|
364
|
+
running: observer.isRunning(MIO_HOME),
|
|
365
|
+
started: observerStarted,
|
|
366
|
+
}
|
|
367
|
+
jsonOrText(result, true)
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
console.log(result.message || `${host} adapter completed.`)
|
|
371
|
+
console.log(
|
|
372
|
+
observer.isRunning(MIO_HOME)
|
|
373
|
+
? 'Observer daemon running.'
|
|
374
|
+
: 'Observer daemon not running. Run `mio observe --start`.'
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function mcpCommand() {
|
|
379
|
+
require(SERVER_SCRIPT)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function observeCommand(args) {
|
|
383
|
+
const flags = args.slice(1)
|
|
384
|
+
const useJson = flags.includes('--json')
|
|
385
|
+
if (flags.includes('--start')) {
|
|
386
|
+
const result = observer.startBackground(MIO_HOME)
|
|
387
|
+
if (useJson) return jsonOrText(result, true)
|
|
388
|
+
console.log(result.started ? 'Observer started.' : 'Observer already running.')
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
if (flags.includes('--stop')) {
|
|
392
|
+
const result = observer.stopBackground(MIO_HOME)
|
|
393
|
+
if (useJson) return jsonOrText(result, true)
|
|
394
|
+
console.log(result.stopped ? 'Observer stopped.' : 'Observer was not running.')
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
if (flags.includes('--status')) {
|
|
398
|
+
const running = observer.isRunning(MIO_HOME)
|
|
399
|
+
const pid = observer.readPid(MIO_HOME)
|
|
400
|
+
if (useJson) return jsonOrText({ running, pid }, true)
|
|
401
|
+
console.log(running ? 'Observer: running (pid ' + pid + ')' : 'Observer: not running')
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
if (flags.includes('--once')) {
|
|
405
|
+
const res = observer.runCycle(
|
|
406
|
+
MIO_HOME,
|
|
407
|
+
observer.loadState(MIO_HOME),
|
|
408
|
+
observer.createSinks(MIO_HOME)
|
|
409
|
+
)
|
|
410
|
+
if (useJson) return jsonOrText(res, true)
|
|
411
|
+
console.log('Observed files=' + res.files + ' lines=' + res.lines + ' outcomes=' + res.outcomes)
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
console.log('Observing WorkBuddy transcripts into ' + MIO_HOME + ' (Ctrl+C to stop)...')
|
|
415
|
+
observer.startForeground(MIO_HOME)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function help() {
|
|
419
|
+
console.log(`Mio Agent Runtime CLI
|
|
420
|
+
|
|
421
|
+
Usage:
|
|
422
|
+
mio init Initialize MIO_HOME
|
|
423
|
+
mio mcp Start Mio MCP server (stdio)
|
|
424
|
+
mio install <host> Install Mio into a host (codex|opencode|workbuddy|hermes)
|
|
427
425
|
mio status Show runtime and adapter status
|
|
428
426
|
mio agents List installed agents
|
|
427
|
+
mio evolution status Show composed evolution module health
|
|
428
|
+
mio evolution shadow record Record a shadow comparison sample
|
|
429
|
+
mio evolution dual-write record Record a dual-write comparison sample
|
|
430
|
+
mio evolution cutover readiness Assess shadow/dual-write cutover readiness
|
|
431
|
+
mio evolution authority plan Preview a gated authority switch plan
|
|
432
|
+
mio evolution migration plan Preview state migration diffs
|
|
433
|
+
mio evolution cutover apply --dry-run Dry-run a cutover plan without switching authority
|
|
429
434
|
mio observe Watch WorkBuddy transcripts and auto-ingest task outcomes
|
|
430
435
|
mio observe --start|--stop|--status|--once Manage the background observer daemon
|
|
431
|
-
mio dashboard [--start|--stop|--status|--open] Start the live dashboard HTTP server
|
|
432
436
|
mio --json status Machine-readable status
|
|
433
437
|
mio --json agents Machine-readable agents
|
|
438
|
+
mio --json evolution status Machine-readable evolution module health
|
|
434
439
|
`)
|
|
435
440
|
}
|
|
436
|
-
|
|
437
|
-
function main() {
|
|
438
|
-
const args = process.argv.slice(2)
|
|
439
|
-
const jsonIndex = args.indexOf('--json')
|
|
440
|
-
const useJson = jsonIndex !== -1
|
|
441
|
-
if (useJson) args.splice(jsonIndex, 1)
|
|
442
|
-
const command = args[0] || 'help'
|
|
443
|
-
|
|
444
|
-
switch (command) {
|
|
445
|
-
case '
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
return uninstallCommand(args[1], useJson)
|
|
456
|
-
case 'upgrade':
|
|
457
|
-
return upgradeCommand(useJson)
|
|
458
|
-
case 'doctor':
|
|
459
|
-
return doctorCommand(useJson)
|
|
460
|
-
case 'status':
|
|
461
|
-
return statusCommand(useJson)
|
|
462
|
-
case 'agents':
|
|
463
|
-
return agentsCommand(useJson)
|
|
441
|
+
|
|
442
|
+
async function main() {
|
|
443
|
+
const args = process.argv.slice(2)
|
|
444
|
+
const jsonIndex = args.indexOf('--json')
|
|
445
|
+
const useJson = jsonIndex !== -1
|
|
446
|
+
if (useJson) args.splice(jsonIndex, 1)
|
|
447
|
+
const command = args[0] || 'help'
|
|
448
|
+
|
|
449
|
+
switch (command) {
|
|
450
|
+
case 'init':
|
|
451
|
+
return initCommand()
|
|
452
|
+
case 'mcp':
|
|
453
|
+
return mcpCommand()
|
|
454
|
+
case 'install':
|
|
455
|
+
return installCommand(args[1], useJson)
|
|
456
|
+
case 'status':
|
|
457
|
+
return statusCommand(useJson)
|
|
458
|
+
case 'agents':
|
|
459
|
+
return agentsCommand(useJson)
|
|
464
460
|
case 'observe':
|
|
465
461
|
return observeCommand(args)
|
|
466
|
-
case '
|
|
467
|
-
return
|
|
468
|
-
case 'help':
|
|
469
|
-
case '--help':
|
|
470
|
-
return help()
|
|
471
|
-
default:
|
|
472
|
-
console.error(`Unknown command: ${command}`)
|
|
473
|
-
help()
|
|
474
|
-
process.exitCode = 1
|
|
475
|
-
}
|
|
462
|
+
case 'evolution':
|
|
463
|
+
return evolutionCommand(args, useJson)
|
|
464
|
+
case 'help':
|
|
465
|
+
case '--help':
|
|
466
|
+
return help()
|
|
467
|
+
default:
|
|
468
|
+
console.error(`Unknown command: ${command}`)
|
|
469
|
+
help()
|
|
470
|
+
process.exitCode = 1
|
|
471
|
+
}
|
|
476
472
|
}
|
|
477
473
|
|
|
478
|
-
main()
|
|
474
|
+
main().catch((error) => {
|
|
475
|
+
console.error(error.message || error)
|
|
476
|
+
process.exitCode = 1
|
|
477
|
+
})
|