mio-agent-runtime 0.5.16 → 0.5.22

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/bin/mio.js CHANGED
@@ -1,24 +1,28 @@
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
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
- }
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
+ claude: require('../adapters/claude-code.js'),
19
+ }
19
20
  const observer = require('../observe/observer.js')
20
21
  const { getEvolutionStatus, formatEvolutionStatusText } = require('../server/runtime-modules.js')
21
22
  const { createEvolutionCutoverTools } = require('../server/evolution-cutover.js')
23
+ const { createMemoryStore } = require('../server/memory-store.js')
24
+ const { createRetention } = require('../server/retention.js')
25
+ const { createDigest } = require('../server/digest.js')
22
26
 
23
27
  function appendJsonl(file, value) {
24
28
  fs.mkdirSync(path.dirname(file), { recursive: true })
@@ -217,211 +221,440 @@ async function evolutionCommand(args, useJson) {
217
221
  console.error('Usage: mio evolution status|shadow record|dual-write record|cutover readiness|cutover apply|authority plan|migration plan')
218
222
  process.exitCode = 1
219
223
  }
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)
224
+
225
+ function ensureHome() {
226
+ fs.mkdirSync(MIO_HOME, { recursive: true })
227
+ fs.mkdirSync(path.join(MIO_HOME, 'logs'), { recursive: true })
228
+ }
229
+
230
+ // CLI memory store: same ranking/persistence as the MCP server, pointed at
231
+ // MIO_HOME. No recordQuery hook — a terminal command cannot be followed by a
232
+ // task_outcome, so a recorded query would only be phase-0 noise.
233
+ function cliMemoryStore() {
234
+ return createMemoryStore({
235
+ dataDir: MIO_HOME,
236
+ projectName,
237
+ agentId: () => 'cli',
238
+ })
239
+ }
240
+
241
+ function splitTagsOption(args, name) {
242
+ const raw = optionValue(args, name)
243
+ if (raw === undefined) return []
244
+ return raw
245
+ .split(',')
246
+ .map((tag) => tag.trim())
247
+ .filter(Boolean)
248
+ }
249
+
250
+ function recallCommand(args, useJson) {
251
+ const query = args[1]
252
+ if (!query) {
253
+ console.error('Usage: mio recall "<query>" [--project name] [--scope project|global|all] [--kind kind] [--tags a,b] [--limit n] [--json]')
254
+ process.exitCode = 1
255
+ return
256
+ }
257
+ const flags = args.slice(2)
258
+ const store = cliMemoryStore()
259
+ let result
260
+ try {
261
+ result = store.queryMemory({
262
+ query,
263
+ project: optionValue(flags, '--project'),
264
+ scope: optionValue(flags, '--scope'),
265
+ kind: optionValue(flags, '--kind'),
266
+ tags: splitTagsOption(flags, '--tags'),
267
+ limit: parseNumberOption(flags, '--limit'),
268
+ })
269
+ } catch (error) {
270
+ console.error(error.message || error)
271
+ process.exitCode = 1
272
+ return
273
+ }
274
+ if (useJson) return jsonOrText(result, true)
275
+ console.log(
276
+ `Mio memory: ${result.count} result(s) for "${result.query}" (project=${result.project}, scope=${result.scope})`
277
+ )
278
+ result.results.forEach((record, index) => {
279
+ const date = (record.timestamp || '').slice(0, 10)
280
+ console.log(`${index + 1}. [${record.kind || 'note'}] ${record.content}`)
281
+ const meta = [
282
+ (record.tags || []).length > 0 ? `tags: ${record.tags.join(', ')}` : null,
283
+ record.source ? `source: ${record.source}` : null,
284
+ date,
285
+ record.id,
286
+ ]
287
+ .filter(Boolean)
288
+ .join(' | ')
289
+ console.log(` ${meta}`)
290
+ })
291
+ }
292
+
293
+ function tracesCommand(args, useJson) {
294
+ const flags = args.slice(1)
295
+ const store = cliMemoryStore()
296
+ let result
297
+ try {
298
+ result = store.queryTraces({
299
+ event_type: optionValue(flags, '--type'),
300
+ outcome: optionValue(flags, '--outcome'),
301
+ project: optionValue(flags, '--project'),
302
+ agent: optionValue(flags, '--agent'),
303
+ host: optionValue(flags, '--host'),
304
+ since: optionValue(flags, '--since'),
305
+ until: optionValue(flags, '--until'),
306
+ limit: parseNumberOption(flags, '--limit'),
307
+ include_payload: !flagPresent(flags, '--compact'),
308
+ })
309
+ } catch (error) {
310
+ console.error(error.message || error)
311
+ process.exitCode = 1
312
+ return
313
+ }
314
+ if (useJson) return jsonOrText(result, true)
315
+ const filters = Object.entries(result.filters)
316
+ .filter(([, value]) => value !== null && value !== undefined)
317
+ .map(([key, value]) => `${key}=${value}`)
318
+ .join(', ')
319
+ console.log(
320
+ `Mio traces: ${result.matched} matched / ${result.total} total${filters ? ' (' + filters + ')' : ''}`
321
+ )
322
+ const summary = Object.entries(result.summary.byOutcome)
323
+ .map(([key, value]) => `${key}=${value}`)
324
+ .join(' ')
325
+ if (summary) console.log(`outcomes: ${summary}`)
326
+ result.results.forEach((trace, index) => {
327
+ const time = (trace.timestamp || '').replace('T', ' ').slice(0, 19)
328
+ const brief = trace.payload && trace.payload.summary ? ' | ' + String(trace.payload.summary).slice(0, 100) : ''
329
+ console.log(
330
+ `${index + 1}. ${time} [${trace.event_type || '?'}${trace.outcome ? '/' + trace.outcome : ''}] ${trace.agent || '?'} @ ${trace.project || '?'}${brief}`
331
+ )
332
+ console.log(` ${trace.id} trace_id=${trace.trace_id || '-'}`)
333
+ })
334
+ }
335
+
336
+ function digestCommand(args, useJson) {
337
+ const flags = args.slice(1)
338
+ const days = parseNumberOption(flags, '--days') || 7
339
+ const writeBack = flagPresent(flags, '--write-back')
340
+ const outDir = path.join(MIO_HOME, 'digest')
341
+
342
+ const report = createDigest({ home: MIO_HOME }).generate({
343
+ days,
344
+ project: optionValue(flags, '--project'),
345
+ })
346
+
347
+ if (writeBack) {
348
+ // Feed the actionable lines back into each active workspace context file
349
+ // (AGENTS.md / CLAUDE.md MIO_CONTEXT block), so every agent passively
350
+ // receives the digest's value on its next session.
351
+ const stateFile = path.join(MIO_HOME, 'observe-state.json')
352
+ let state = {}
353
+ try {
354
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf8'))
355
+ } catch (_) {}
356
+ state.contexts = state.contexts || {}
357
+ const stamp = new Date().toLocaleString('zh-CN', { hour12: false })
358
+ const rate =
359
+ report.overview.tasks > 0
360
+ ? Math.round(((report.overview.byOutcome.success || 0) / report.overview.tasks) * 100)
361
+ : 0
362
+ const headline =
363
+ `digest(${days}d): 任务 ${report.overview.tasks} 条 成功率 ${rate}%, error ${report.overview.errorTraces}` +
364
+ (report.suggestions.length > 0 ? ' | ' + report.suggestions[0] : '')
365
+ const targetCwds = optionValue(flags, '--cwd')
366
+ ? [optionValue(flags, '--cwd')]
367
+ : Object.keys(state.contexts)
368
+ let written = 0
369
+ for (const cwd of targetCwds) {
370
+ try {
371
+ observer.updateProjectContext(state, cwd, stamp + ' | ' + headline, 'CLAUDE.md')
372
+ observer.updateProjectContext(state, cwd, stamp + ' | ' + headline, 'AGENTS.md')
373
+ written += 1
374
+ } catch (_) {}
375
+ }
376
+ fs.mkdirSync(MIO_HOME, { recursive: true })
377
+ fs.writeFileSync(stateFile, JSON.stringify(state) + '\n', 'utf8')
378
+ report.writeBack = { workspaces: written, headline }
379
+ }
380
+
381
+ // Persist the report so it becomes part of the data asset itself.
382
+ fs.mkdirSync(outDir, { recursive: true })
383
+ const reportFile = path.join(outDir, 'digest-' + report.generatedAt.slice(0, 10) + '.md')
384
+ fs.writeFileSync(reportFile, report.markdown, 'utf8')
385
+ report.reportFile = reportFile
386
+
387
+ if (useJson) return jsonOrText(report, true)
388
+ console.log(report.markdown)
389
+ console.log(`Report saved: ${reportFile}`)
390
+ if (report.writeBack) {
391
+ console.log(`Write-back: ${report.writeBack.workspaces} workspace(s) <- "${report.writeBack.headline}"`)
392
+ }
393
+ }
394
+
395
+ function pruneCommand(args, useJson) {
396
+ const flags = args.slice(1)
397
+ const days = parseNumberOption(flags, '--days') || 30
398
+ const includeMemory = flagPresent(flags, '--memory')
399
+ const dryRun = flagPresent(flags, '--dry-run')
400
+ if (includeMemory && !dryRun && !flagPresent(flags, '--yes')) {
401
+ console.error('Pruning memory.jsonl is destructive. Re-run with --yes to confirm (a .bak-prune-* backup is still written).')
402
+ process.exitCode = 1
403
+ return
404
+ }
405
+ const retention = createRetention({ home: MIO_HOME })
406
+ let result
407
+ try {
408
+ result = retention.prune({ days, includeMemory, dryRun })
409
+ } catch (error) {
410
+ console.error(error.message || error)
411
+ process.exitCode = 1
412
+ return
413
+ }
414
+ if (useJson) return jsonOrText(result, true)
415
+ console.log(
416
+ `Prune ${dryRun ? 'plan (dry-run)' : 'result'}: cutoff ${result.cutoff}, days=${days}${includeMemory ? ', memory included' : ''}`
417
+ )
418
+ for (const file of result.files) {
419
+ console.log(`- ${file.file}: ${file.total} total -> keep ${file.kept}, remove ${file.removed}`)
420
+ }
421
+ console.log(`Total removed: ${result.totalRemoved}`)
422
+ if (dryRun) console.log('Dry run - nothing written. Re-run without --dry-run to apply.')
423
+ else console.log('Backups written as <file>.bak-prune-<ts> before rewriting.')
424
+ }
425
+
426
+ function rememberCommand(args, useJson) {
427
+ const content = args[1]
428
+ if (!content) {
429
+ console.error('Usage: mio remember "<content>" [--kind decision|context|problem|note] [--tags a,b] [--scope project|global] [--project name] [--json]')
430
+ process.exitCode = 1
431
+ return
432
+ }
433
+ const flags = args.slice(2)
434
+ const store = cliMemoryStore()
435
+ let record
436
+ try {
437
+ record = store.recordMemory({
438
+ content,
439
+ kind: optionValue(flags, '--kind'),
440
+ tags: splitTagsOption(flags, '--tags'),
441
+ scope: optionValue(flags, '--scope'),
442
+ project: optionValue(flags, '--project'),
443
+ source: 'cli',
444
+ })
445
+ } catch (error) {
446
+ console.error(error.message || error)
447
+ process.exitCode = 1
448
+ return
449
+ }
450
+ if (useJson) return jsonOrText(record, true)
451
+ console.log(`Recorded ${record.id} (kind=${record.kind}, project=${record.project || 'global'}, scope=${record.scope})`)
452
+ }
453
+
454
+ function readConfig() {
455
+ const fallback = { version: 1, home: MIO_HOME, agents: {} }
456
+ if (!fs.existsSync(CONFIG_FILE)) return fallback
457
+ try {
458
+ return { ...fallback, ...JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) }
459
+ } catch (_) {
460
+ return fallback
461
+ }
462
+ }
463
+
464
+ function writeConfig(config) {
465
+ ensureHome()
466
+ fs.writeFileSync(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
467
+ }
468
+
469
+ function jsonOrText(payload, useJson) {
470
+ if (useJson) {
471
+ console.log(JSON.stringify(payload, null, 2))
472
+ return
473
+ }
474
+ return payload
475
+ }
476
+
477
+ function initCommand() {
478
+ ensureHome()
479
+ const config = readConfig()
480
+ if (!config.createdAt) config.createdAt = new Date().toISOString()
481
+ writeConfig(config)
482
+ console.log(`MIO_HOME ready: ${MIO_HOME}`)
483
+ }
484
+
485
+ function statusCommand(useJson) {
486
+ const config = readConfig()
487
+ const codexRestart = adapters.codex.restartWarningMessage()
488
+ const opencodeRestart = adapters.opencode.restartWarningMessage()
489
+ const installed = {
490
+ codex: adapters.codex.isInstalled(),
491
+ opencode: adapters.opencode.isInstalled(),
492
+ workbuddy: adapters.workbuddy.isInstalled(),
493
+ hermes: adapters.hermes.isInstalled(),
494
+ claude: adapters.claude.isInstalled(),
495
+ }
496
+ const payload = {
497
+ version: config.version,
498
+ home: MIO_HOME,
499
+ serverScript: SERVER_SCRIPT,
500
+ serverScriptExists: fs.existsSync(SERVER_SCRIPT),
501
+ agents: config.agents || {},
502
+ codexInstalled: installed.codex,
503
+ opencodeInstalled: installed.opencode,
504
+ workbuddyInstalled: installed.workbuddy,
505
+ hermesInstalled: installed.hermes,
506
+ claudeInstalled: installed.claude,
507
+ codexNeedsRestart: Boolean(codexRestart),
508
+ opencodeNeedsRestart: Boolean(opencodeRestart),
509
+ codexRestartHint: codexRestart,
510
+ opencodeRestartHint: opencodeRestart,
511
+ hermesObserverReady: fs.existsSync(observer.hermesDbPath()),
512
+ claudeObserverReady: fs.existsSync(observer.claudeProjectsDir()),
513
+ opencodeObserverReady: fs.existsSync(observer.opencodeDbPath()),
514
+ observerRunning: observer.isRunning(MIO_HOME),
515
+ observerPid: observer.isRunning(MIO_HOME) ? observer.readPid(MIO_HOME) : null,
516
+ }
517
+ if (useJson) {
518
+ jsonOrText(payload, true)
519
+ return
520
+ }
521
+ console.log(`MIO_HOME: ${MIO_HOME}`)
522
+ console.log(`MCP Server: ${SERVER_SCRIPT}`)
523
+ console.log(`Codex MCP: ${installed.codex ? 'installed' : 'not installed'}`)
524
+ if (codexRestart) console.log(`Codex app: running with an older Mio config - fully restart Codex to load it`)
525
+ console.log(`OpenCode MCP: ${installed.opencode ? 'installed' : 'not installed'}`)
526
+ if (opencodeRestart) console.log(`OpenCode Desktop: running with an older Mio config - restart it to load it`)
527
+ console.log(`OpenCode observer: ${fs.existsSync(observer.opencodeDbPath()) ? 'ready' : 'no database'}`)
528
+ console.log(`WorkBuddy MCP: ${installed.workbuddy ? 'installed' : 'not installed'}`)
529
+ console.log(`Hermes MCP: ${installed.hermes ? 'installed' : 'not installed'}`)
530
+ console.log(`Hermes observer: ${fs.existsSync(observer.hermesDbPath()) ? 'ready' : 'no database'}`)
531
+ console.log(`Claude Code MCP: ${installed.claude ? 'installed' : 'not installed'}`)
532
+ console.log(`Claude Code observer: ${fs.existsSync(observer.claudeProjectsDir()) ? 'ready' : 'no transcripts'}`)
533
+ console.log(`Observer: ${observer.isRunning(MIO_HOME) ? 'running (pid ' + observer.readPid(MIO_HOME) + ')' : 'not running'}`)
534
+ }
535
+
536
+ function agentsCommand(useJson) {
537
+ const config = readConfig()
538
+ const agents = config.agents || {}
539
+ if (useJson) {
540
+ jsonOrText(agents, true)
541
+ return
542
+ }
543
+ const names = Object.keys(agents)
544
+ if (names.length === 0) {
545
+ console.log('No agents installed.')
546
+ return
547
+ }
548
+ for (const name of names) {
549
+ console.log(`${name}\t${agents[name].installedAt || ''}`)
550
+ }
551
+ }
552
+
553
+ function installCommand(host, useJson) {
554
+ if (!host) {
555
+ console.error('Usage: mio install <codex|opencode|workbuddy|hermes|claude>')
556
+ process.exitCode = 1
557
+ return
558
+ }
559
+ const adapter = adapters[host]
560
+ if (!adapter) {
561
+ console.error(`Unknown host: ${host}`)
562
+ process.exitCode = 1
563
+ return
564
+ }
565
+ ensureHome()
566
+ const config = readConfig()
567
+ let result
568
+ try {
569
+ result = adapter.install({
570
+ node: process.execPath,
571
+ serverScript: SERVER_SCRIPT,
572
+ home: MIO_HOME,
573
+ workspace: process.cwd(),
574
+ })
575
+ } catch (error) {
576
+ console.error(`Failed to install ${host}: ${error.message || error}`)
577
+ process.exitCode = 1
578
+ return
579
+ }
580
+ if (result.changed) {
581
+ config.agents = config.agents || {}
582
+ config.agents[host] = {
583
+ installedAt: new Date().toISOString(),
584
+ configPath: result.configPath || null,
585
+ }
586
+ writeConfig(config)
587
+ }
588
+ try {
589
+ observer.runCycle(MIO_HOME, observer.loadState(MIO_HOME), observer.createSinks(MIO_HOME))
590
+ } catch (_) {}
591
+ let observerStarted = false
592
+ try {
593
+ observerStarted = observer.startBackground(MIO_HOME).started
594
+ } catch (_) {}
595
+ if (useJson) {
596
+ result.observer = {
597
+ running: observer.isRunning(MIO_HOME),
598
+ started: observerStarted,
599
+ }
600
+ jsonOrText(result, true)
601
+ return
602
+ }
603
+ console.log(result.message || `${host} adapter completed.`)
604
+ console.log(
605
+ observer.isRunning(MIO_HOME)
606
+ ? 'Observer daemon running.'
607
+ : 'Observer daemon not running. Run `mio observe --start`.'
608
+ )
609
+ }
610
+
611
+ function mcpCommand() {
612
+ require(SERVER_SCRIPT)
613
+ }
614
+
615
+ function observeCommand(args) {
616
+ const flags = args.slice(1)
617
+ const useJson = flags.includes('--json')
618
+ if (flags.includes('--start')) {
619
+ const result = observer.startBackground(MIO_HOME)
620
+ if (useJson) return jsonOrText(result, true)
621
+ console.log(result.started ? 'Observer started.' : 'Observer already running.')
622
+ return
623
+ }
624
+ if (flags.includes('--stop')) {
625
+ const result = observer.stopBackground(MIO_HOME)
626
+ if (useJson) return jsonOrText(result, true)
627
+ console.log(result.stopped ? 'Observer stopped.' : 'Observer was not running.')
628
+ return
629
+ }
630
+ if (flags.includes('--status')) {
631
+ const running = observer.isRunning(MIO_HOME)
632
+ const pid = observer.readPid(MIO_HOME)
633
+ if (useJson) return jsonOrText({ running, pid }, true)
634
+ console.log(running ? 'Observer: running (pid ' + pid + ')' : 'Observer: not running')
635
+ return
636
+ }
637
+ if (flags.includes('--once')) {
638
+ const res = observer.runCycle(
639
+ MIO_HOME,
640
+ observer.loadState(MIO_HOME),
641
+ observer.createSinks(MIO_HOME)
642
+ )
643
+ if (useJson) return jsonOrText(res, true)
644
+ console.log('Observed files=' + res.files + ' lines=' + res.lines + ' outcomes=' + res.outcomes)
645
+ return
646
+ }
647
+ console.log('Observing WorkBuddy transcripts into ' + MIO_HOME + ' (Ctrl+C to stop)...')
648
+ observer.startForeground(MIO_HOME)
649
+ }
650
+
651
+ function help() {
652
+ console.log(`Mio Agent Runtime CLI
653
+
654
+ Usage:
655
+ mio init Initialize MIO_HOME
656
+ mio mcp Start Mio MCP server (stdio)
657
+ mio install <host> Install Mio into a host (codex|opencode|workbuddy|hermes|claude)
425
658
  mio status Show runtime and adapter status
426
659
  mio agents List installed agents
427
660
  mio evolution status Show composed evolution module health
@@ -433,42 +666,57 @@ Usage:
433
666
  mio evolution cutover apply --dry-run Dry-run a cutover plan without switching authority
434
667
  mio observe Watch WorkBuddy transcripts and auto-ingest task outcomes
435
668
  mio observe --start|--stop|--status|--once Manage the background observer daemon
669
+ mio recall "<query>" Search Mio memory from the terminal (same ranking as mio.memory.query)
670
+ mio traces Show recent observer traces (--type/--outcome/--agent/--since/--limit/--compact)
671
+ mio remember "<content>" Write a memory record from the terminal (same schema as mio.memory.record)
672
+ mio prune --days 30 Trim old traces/queries/reuse records and observe.log (--dry-run to preview; --memory needs --yes)
673
+ mio digest --days 7 Aggregate traces/memory/reuse into an actionable report (--write-back feeds agent context files; --json)
436
674
  mio --json status Machine-readable status
437
675
  mio --json agents Machine-readable agents
438
676
  mio --json evolution status Machine-readable evolution module health
439
677
  `)
440
678
  }
441
-
679
+
442
680
  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)
681
+ const args = process.argv.slice(2)
682
+ const jsonIndex = args.indexOf('--json')
683
+ const useJson = jsonIndex !== -1
684
+ if (useJson) args.splice(jsonIndex, 1)
685
+ const command = args[0] || 'help'
686
+
687
+ switch (command) {
688
+ case 'init':
689
+ return initCommand()
690
+ case 'mcp':
691
+ return mcpCommand()
692
+ case 'install':
693
+ return installCommand(args[1], useJson)
694
+ case 'status':
695
+ return statusCommand(useJson)
696
+ case 'agents':
697
+ return agentsCommand(useJson)
460
698
  case 'observe':
461
699
  return observeCommand(args)
700
+ case 'recall':
701
+ return recallCommand(args, useJson)
702
+ case 'traces':
703
+ return tracesCommand(args, useJson)
704
+ case 'remember':
705
+ return rememberCommand(args, useJson)
706
+ case 'prune':
707
+ return pruneCommand(args, useJson)
708
+ case 'digest':
709
+ return digestCommand(args, useJson)
462
710
  case 'evolution':
463
711
  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
- }
712
+ case 'help':
713
+ case '--help':
714
+ return help()
715
+ default:
716
+ console.error(`Unknown command: ${command}`)
717
+ help()
718
+ process.exitCode = 1
719
+ }
472
720
  }
473
721
 
474
722
  main().catch((error) => {