suemo 0.0.1 → 0.0.3

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.
Files changed (40) hide show
  1. package/README.md +127 -27
  2. package/package.json +1 -1
  3. package/src/cli/commands/believe.ts +22 -12
  4. package/src/cli/commands/consolidate.ts +18 -11
  5. package/src/cli/commands/doctor.ts +123 -0
  6. package/src/cli/commands/export-import.ts +83 -47
  7. package/src/cli/commands/goal.ts +52 -27
  8. package/src/cli/commands/health.ts +53 -18
  9. package/src/cli/commands/init.ts +155 -75
  10. package/src/cli/commands/observe.ts +26 -13
  11. package/src/cli/commands/query.ts +23 -7
  12. package/src/cli/commands/recall.ts +12 -6
  13. package/src/cli/commands/serve.ts +25 -6
  14. package/src/cli/commands/sync.ts +44 -10
  15. package/src/cli/commands/timeline.ts +30 -18
  16. package/src/cli/commands/wander.ts +27 -16
  17. package/src/cli/index.ts +3 -4
  18. package/src/cli/shared.ts +34 -0
  19. package/src/cognitive/consolidate.ts +48 -19
  20. package/src/cognitive/contradiction.ts +19 -7
  21. package/src/cognitive/health.ts +61 -1
  22. package/src/config.template.ts +58 -0
  23. package/src/config.ts +124 -14
  24. package/src/db/preflight.ts +32 -6
  25. package/src/db/schema.surql +30 -9
  26. package/src/db/schema.ts +6 -3
  27. package/src/embedding/index.ts +52 -0
  28. package/src/embedding/openai-compatible.ts +43 -0
  29. package/src/goal.ts +3 -1
  30. package/src/index.ts +5 -1
  31. package/src/mcp/dispatch.ts +232 -0
  32. package/src/mcp/server.ts +150 -4
  33. package/src/mcp/stdio.ts +385 -0
  34. package/src/mcp/tools.ts +13 -90
  35. package/src/memory/episode.ts +92 -0
  36. package/src/memory/read.ts +76 -19
  37. package/src/memory/write.ts +253 -20
  38. package/src/sync.ts +310 -66
  39. package/src/types.ts +30 -5
  40. package/src/cli/commands/shared.ts +0 -20
@@ -1,8 +1,11 @@
1
+ import type { Surreal } from 'surrealdb'
1
2
  import { loadConfig } from '../../config.ts'
2
3
  import { connect, disconnect } from '../../db/client.ts'
3
4
  import { goalList, goalResolve, goalSet } from '../../goal.ts'
4
- import { initLogger } from '../../logger.ts'
5
- import { app } from '../shared.ts'
5
+ import { getLogger } from '../../logger.ts'
6
+ import { app, initCliCommand } from '../shared.ts'
7
+
8
+ const log = getLogger(['suemo', 'cli', 'goal'])
6
9
 
7
10
  const goal = app.sub('goal')
8
11
  .meta({ description: 'Manage goal nodes' })
@@ -16,15 +19,24 @@ const setCmd = goal.sub('set')
16
19
  tags: { type: 'string', short: 't', description: 'Comma-separated tags' },
17
20
  })
18
21
  .run(async ({ args, flags }) => {
19
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
20
- const config = await loadConfig(process.cwd(), flags.config)
21
- const db = await connect(config.surreal)
22
- const node = await goalSet(db, args.content, {
23
- ...(flags.scope ? { scope: flags.scope } : {}),
24
- tags: flags.tags ? flags.tags.split(',').map((t) => t.trim()) : [],
22
+ await initCliCommand('goal set', { debug: flags.debug, config: flags.config })
23
+ log.debug('Running goal set command', {
24
+ hasScope: Boolean(flags.scope),
25
+ tagCount: flags.tags ? flags.tags.split(',').filter(Boolean).length : 0,
26
+ contentLength: args.content.length,
25
27
  })
26
- await disconnect()
27
- console.log(JSON.stringify({ id: node.id, content: node.content }, null, 2))
28
+ const config = await loadConfig(process.cwd(), flags.config)
29
+ let db: Surreal | undefined
30
+ try {
31
+ db = await connect(config.surreal)
32
+ const node = await goalSet(db, args.content, config, {
33
+ ...(flags.scope ? { scope: flags.scope } : {}),
34
+ tags: flags.tags ? flags.tags.split(',').map((t) => t.trim()) : [],
35
+ })
36
+ console.log(JSON.stringify({ id: node.id, content: node.content }, null, 2))
37
+ } finally {
38
+ if (db) await disconnect()
39
+ }
28
40
  })
29
41
 
30
42
  // suemo goal list
@@ -35,19 +47,27 @@ const listCmd = goal.sub('list')
35
47
  resolved: { type: 'boolean', description: 'Include resolved goals', default: false },
36
48
  })
37
49
  .run(async ({ flags }) => {
38
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
39
- const config = await loadConfig(process.cwd(), flags.config)
40
- const db = await connect(config.surreal)
41
- const goals = await goalList(db, {
42
- ...(flags.scope ? { scope: flags.scope } : {}),
43
- includeResolved: flags.resolved,
50
+ await initCliCommand('goal list', { debug: flags.debug, config: flags.config })
51
+ log.debug('Running goal list command', {
52
+ hasScope: Boolean(flags.scope),
53
+ includeResolved: Boolean(flags.resolved),
44
54
  })
45
- await disconnect()
46
- for (const g of goals) {
47
- const status = g.valid_until ? `resolved ${g.valid_until}` : 'active'
48
- console.log(`[${status}] ${g.id}`)
49
- console.log(` ${g.content}`)
50
- console.log()
55
+ const config = await loadConfig(process.cwd(), flags.config)
56
+ let db: Surreal | undefined
57
+ try {
58
+ db = await connect(config.surreal)
59
+ const goals = await goalList(db, {
60
+ ...(flags.scope ? { scope: flags.scope } : {}),
61
+ includeResolved: flags.resolved,
62
+ })
63
+ for (const g of goals) {
64
+ const status = g.valid_until ? `resolved ${g.valid_until}` : 'active'
65
+ console.log(`[${status}] ${g.id}`)
66
+ console.log(` ${g.content}`)
67
+ console.log()
68
+ }
69
+ } finally {
70
+ if (db) await disconnect()
51
71
  }
52
72
  })
53
73
 
@@ -56,12 +76,17 @@ const resolveCmd = goal.sub('resolve')
56
76
  .meta({ description: 'Mark a goal as resolved' })
57
77
  .args([{ name: 'goalId', type: 'string', required: true }])
58
78
  .run(async ({ args, flags }) => {
59
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
79
+ await initCliCommand('goal resolve', { debug: flags.debug, config: flags.config })
80
+ log.debug('Running goal resolve command', { goalId: args.goalId })
60
81
  const config = await loadConfig(process.cwd(), flags.config)
61
- const db = await connect(config.surreal)
62
- await goalResolve(db, args.goalId)
63
- await disconnect()
64
- console.log(`✓ Goal ${args.goalId} resolved`)
82
+ let db: Surreal | undefined
83
+ try {
84
+ db = await connect(config.surreal)
85
+ await goalResolve(db, args.goalId)
86
+ console.log(`✓ Goal ${args.goalId} resolved`)
87
+ } finally {
88
+ if (db) await disconnect()
89
+ }
65
90
  })
66
91
 
67
92
  // Export the container with its subcommands registered
@@ -1,8 +1,11 @@
1
- import { healthReport, vitals } from '../../cognitive/health.ts'
1
+ import type { Surreal } from 'surrealdb'
2
+ import { healthReport, suemoStats, vitals } from '../../cognitive/health.ts'
2
3
  import { loadConfig } from '../../config.ts'
3
4
  import { connect, disconnect } from '../../db/client.ts'
4
- import { initLogger } from '../../logger.ts'
5
- import { app } from '../shared.ts'
5
+ import { getLogger } from '../../logger.ts'
6
+ import { app, initCliCommand } from '../shared.ts'
7
+
8
+ const log = getLogger(['suemo', 'cli', 'health'])
6
9
 
7
10
  const health = app.sub('health')
8
11
  .meta({ description: 'Memory health report and vitals' })
@@ -10,34 +13,66 @@ const health = app.sub('health')
10
13
  const reportCmd = health.sub('report')
11
14
  .meta({ description: 'Full health report (default)' })
12
15
  .run(async ({ flags }) => {
13
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
16
+ await initCliCommand('health report', { debug: flags.debug, config: flags.config })
17
+ log.debug('Running health report command')
14
18
  const config = await loadConfig(process.cwd(), flags.config)
15
- const db = await connect(config.surreal)
16
- const report = await healthReport(db)
17
- await disconnect()
18
- console.log(JSON.stringify(report, null, 2))
19
+ let db: Surreal | undefined
20
+ try {
21
+ db = await connect(config.surreal)
22
+ const report = await healthReport(db)
23
+ console.log(JSON.stringify(report, null, 2))
24
+ } finally {
25
+ if (db) await disconnect()
26
+ }
19
27
  })
20
28
 
21
29
  const vitalsCmd = health.sub('vitals')
22
30
  .meta({ description: 'Last 10 consolidation runs + node counts' })
23
31
  .run(async ({ flags }) => {
24
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
32
+ await initCliCommand('health vitals', { debug: flags.debug, config: flags.config })
33
+ log.debug('Running health vitals command')
34
+ const config = await loadConfig(process.cwd(), flags.config)
35
+ let db: Surreal | undefined
36
+ try {
37
+ db = await connect(config.surreal)
38
+ const v = await vitals(db)
39
+ console.log(JSON.stringify(v, null, 2))
40
+ } finally {
41
+ if (db) await disconnect()
42
+ }
43
+ })
44
+
45
+ const statsCmd = health.sub('stats')
46
+ .meta({ description: 'Lightweight usage stats' })
47
+ .run(async ({ flags }) => {
48
+ await initCliCommand('health stats', { debug: flags.debug, config: flags.config })
49
+ log.debug('Running health stats command')
25
50
  const config = await loadConfig(process.cwd(), flags.config)
26
- const db = await connect(config.surreal)
27
- const v = await vitals(db)
28
- await disconnect()
29
- console.log(JSON.stringify(v, null, 2))
51
+ let db: Surreal | undefined
52
+ try {
53
+ db = await connect(config.surreal)
54
+ const s = await suemoStats(db)
55
+ console.log(JSON.stringify(s, null, 2))
56
+ } finally {
57
+ if (db) await disconnect()
58
+ }
30
59
  })
31
60
 
32
61
  export const healthCmd = health
33
62
  .command(reportCmd)
34
63
  .command(vitalsCmd)
64
+ .command(statsCmd)
35
65
  // Default: run the report when just `suemo health` is called
36
66
  .run(async ({ flags }) => {
37
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
67
+ await initCliCommand('health', { debug: flags.debug, config: flags.config })
68
+ log.debug('Running default health command')
38
69
  const config = await loadConfig(process.cwd(), flags.config)
39
- const db = await connect(config.surreal)
40
- const report = await healthReport(db)
41
- await disconnect()
42
- console.log(JSON.stringify(report, null, 2))
70
+ let db: Surreal | undefined
71
+ try {
72
+ db = await connect(config.surreal)
73
+ const report = await healthReport(db)
74
+ console.log(JSON.stringify(report, null, 2))
75
+ } finally {
76
+ if (db) await disconnect()
77
+ }
43
78
  })
@@ -1,90 +1,170 @@
1
+ import { confirm } from '@crustjs/prompts'
1
2
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
2
- import { join } from 'node:path'
3
+ import { dirname, join, resolve as resolvePath } from 'node:path'
3
4
  import { loadConfig } from '../../config.ts'
4
5
  import { connect, disconnect } from '../../db/client.ts'
5
6
  import { checkCompatibility } from '../../db/preflight.ts'
6
7
  import { runSchema } from '../../db/schema.ts'
7
- import { initLogger } from '../../logger.ts'
8
- import { app } from '../shared.ts'
8
+ import { getLogger } from '../../logger.ts'
9
+ import { app, initCliCommand } from '../shared.ts'
9
10
 
10
- export const initCmd = app.sub('init')
11
- .meta({ description: 'Initialize suemo: create config template and apply DB schema' })
11
+ import template from '../../config.template.ts' with { type: 'text' }
12
+
13
+ interface InitFlags {
14
+ debug?: boolean | undefined
15
+ config?: string | undefined
16
+ force?: boolean | undefined
17
+ yes?: boolean | undefined
18
+ }
19
+
20
+ const log = getLogger(['suemo', 'cli', 'init'])
21
+
22
+ const init = app.sub('init')
23
+ .meta({ description: 'Initialize suemo config and/or database schema' })
24
+
25
+ function homeConfigPath(): string {
26
+ const home = process.env.HOME ?? process.env.USERPROFILE
27
+ if (!home) throw new Error('HOME/USERPROFILE is not set; cannot resolve ~/.suemo path')
28
+ return join(home, '.suemo', 'suemo.ts')
29
+ }
30
+
31
+ function writeConfig(force: boolean, outputPath?: string): 'written' | 'skipped' {
32
+ const configPath = outputPath ? resolveConfigOutputPath(outputPath) : homeConfigPath()
33
+ log.debug('Preparing config template write', { configPath, force })
34
+ if (existsSync(configPath) && !force) {
35
+ console.log(`Config already exists at ${configPath}`)
36
+ console.log('Pass --force to overwrite.')
37
+ return 'skipped'
38
+ }
39
+
40
+ mkdirSync(dirname(configPath), { recursive: true })
41
+ writeFileSync(configPath, template as unknown as string, 'utf-8')
42
+ console.log(`✓ Config written to ${configPath}`)
43
+ return 'written'
44
+ }
45
+
46
+ function configOutputPath(flags: InitFlags): string | undefined {
47
+ const fromFlag = flags.config?.trim()
48
+ if (fromFlag) return fromFlag
49
+ const fromEnv = process.env.SUEMO_CONFIG_PATH?.trim()
50
+ return fromEnv || undefined
51
+ }
52
+
53
+ function resolveConfigOutputPath(configPath: string): string {
54
+ if (configPath.startsWith('~/')) {
55
+ const home = process.env.HOME ?? process.env.USERPROFILE
56
+ if (!home) throw new Error('HOME/USERPROFILE is not set; cannot expand ~ in config path')
57
+ return resolvePath(home, configPath.slice(2))
58
+ }
59
+ return resolvePath(configPath)
60
+ }
61
+
62
+ async function applySchema(configPath?: string): Promise<boolean> {
63
+ let connected = false
64
+ try {
65
+ log.debug('Loading config for schema apply', { cwd: process.cwd(), overridePath: configPath ?? null })
66
+ const config = await loadConfig(process.cwd(), configPath)
67
+ log.debug('Resolved schema apply target', {
68
+ namespace: config.surreal.namespace,
69
+ database: config.surreal.database,
70
+ url: config.surreal.url,
71
+ })
72
+ log.debug('Connecting for schema apply', {
73
+ url: config.surreal.url,
74
+ namespace: config.surreal.namespace,
75
+ database: config.surreal.database,
76
+ })
77
+ const db = await connect(config.surreal)
78
+ connected = true
79
+
80
+ const compat = await checkCompatibility(db, {
81
+ requireEmbedding: false,
82
+ context: 'cli:init-schema',
83
+ })
84
+ log.debug('Preflight result for init schema', {
85
+ ok: compat.ok,
86
+ surrealVersion: compat.surrealVersion,
87
+ surrealkv: compat.surrealkv,
88
+ retention_ok: compat.retention_ok,
89
+ embedding: compat.embedding,
90
+ errorCount: compat.errors.length,
91
+ })
92
+ if (!compat.ok) {
93
+ console.error('\n✗ Compatibility check failed:')
94
+ for (const e of compat.errors) console.error(` • ${e}`)
95
+ return false
96
+ }
97
+
98
+ if (!compat.embedding) {
99
+ console.warn(
100
+ '\n⚠ fn::embed() is unavailable in this DB. Schema can still be applied, but observe/query/consolidate will fail until embedding model is configured.',
101
+ )
102
+ }
103
+
104
+ await runSchema(db)
105
+ console.log('✓ Schema applied to SurrealDB')
106
+ return true
107
+ } catch (e) {
108
+ console.error(`✗ Schema not applied: ${String(e)}`)
109
+ return false
110
+ } finally {
111
+ if (connected) {
112
+ await disconnect()
113
+ }
114
+ }
115
+ }
116
+
117
+ const initConfigCmd = init.sub('config')
118
+ .meta({ description: 'Create/update ~/.suemo/suemo.ts config template' })
12
119
  .flags({
13
- force: { type: 'boolean', description: 'Overwrite existing config without prompting' },
120
+ force: { type: 'boolean', description: 'Overwrite existing config file' },
14
121
  })
15
122
  .run(async ({ flags }) => {
16
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
123
+ await initCliCommand('init config', { debug: flags.debug, config: flags.config })
124
+ writeConfig(Boolean(flags.force), configOutputPath(flags))
125
+ })
17
126
 
18
- const homeConfig = join(process.env.HOME ?? '~', '.suemo', 'suemo.ts')
19
- const configExists = existsSync(homeConfig)
127
+ const initSchemaCmd = init.sub('schema')
128
+ .meta({ description: 'Apply database schema using current config' })
129
+ .flags({
130
+ yes: { type: 'boolean', short: 'y', description: 'Skip schema confirmation prompt' },
131
+ })
132
+ .run(async ({ flags }) => {
133
+ await initCliCommand('init schema', { debug: flags.debug, config: flags.config })
20
134
 
21
- if (configExists && !flags.force) {
22
- console.log(`Config already exists at ${homeConfig}`)
23
- console.log('Pass --force to overwrite.')
24
- return
135
+ const confirmOptions: {
136
+ message: string
137
+ default: boolean
138
+ active: string
139
+ inactive: string
140
+ initial?: boolean
141
+ } = {
142
+ message: 'Apply schema to the configured SurrealDB now?',
143
+ default: false,
144
+ active: 'Apply',
145
+ inactive: 'Cancel',
25
146
  }
147
+ if (flags.yes !== undefined) {
148
+ confirmOptions.initial = flags.yes
149
+ }
150
+ const proceed = await confirm(confirmOptions)
26
151
 
27
- const template = `import { defineConfig } from "suemo";
28
-
29
- export default defineConfig({
30
- surreal: {
31
- url: process.env.SURREAL_URL ?? "ws://localhost:8000",
32
- namespace: process.env.SURREAL_NS ?? "myagents",
33
- database: process.env.SURREAL_DB ?? "default",
34
- auth: {
35
- user: process.env.SURREAL_USER!,
36
- pass: process.env.SURREAL_PASS!,
37
- },
38
- },
39
- embedding: {
40
- provider: "surreal",
41
- dimension: 1536,
42
- },
43
- consolidation: {
44
- trigger: "timer",
45
- intervalMinutes: 30,
46
- reactiveThreshold: 50,
47
- nremSimilarityThreshold: 0.85,
48
- remRelationThreshold: 0.4,
49
- llm: {
50
- url: process.env.LLM_URL!,
51
- model: process.env.LLM_MODEL ?? "gpt-4o",
52
- apiKey: process.env.LLM_API_KEY!,
53
- },
54
- },
55
- retrieval: {
56
- weights: { vector: 0.5, bm25: 0.25, graph: 0.15, temporal: 0.1 },
57
- },
58
- mcp: {
59
- port: Number(process.env.SUEMO_PORT) || 4242,
60
- host: "127.0.0.1",
61
- },
62
- });
63
- `
64
-
65
- mkdirSync(join(process.env.HOME ?? '~', '.suemo'), { recursive: true })
66
- writeFileSync(homeConfig, template, 'utf-8')
67
- console.log(`✓ Config written to ${homeConfig}`)
68
-
69
- // Apply schema if SURREAL_* env vars are present
70
- if (process.env.SURREAL_URL && process.env.SURREAL_USER) {
71
- try {
72
- const config = await loadConfig(process.cwd(), flags.config)
73
- const db = await connect(config.surreal)
74
- const compat = await checkCompatibility(db)
75
- if (!compat.ok) {
76
- console.error('\n✗ Compatibility check failed:')
77
- for (const e of compat.errors) console.error(` • ${e}`)
78
- await disconnect()
79
- return
80
- }
81
- await runSchema(db)
82
- await disconnect()
83
- console.log('✓ Schema applied to SurrealDB')
84
- } catch (e) {
85
- console.warn(` (Schema not applied — configure SURREAL_* env vars first: ${String(e)})`)
86
- }
87
- } else {
88
- console.log(' Set SURREAL_URL, SURREAL_USER, SURREAL_PASS, then run `suemo init` again to apply schema.')
152
+ if (!proceed) {
153
+ console.log('Schema apply cancelled.')
154
+ return
89
155
  }
156
+
157
+ const ok = await applySchema(flags.config)
158
+ if (!ok) process.exitCode = 1
159
+ })
160
+
161
+ export const initCmd = init
162
+ .command(initConfigCmd)
163
+ .command(initSchemaCmd)
164
+ .run(async ({ flags }) => {
165
+ await initCliCommand('init', { debug: flags.debug, config: flags.config })
166
+ console.log('Use one of:')
167
+ console.log(' suemo init config [--force]')
168
+ console.log(' suemo init schema [--yes]')
169
+ console.log('\nRun `suemo init --help` for full details.')
90
170
  })
@@ -1,9 +1,11 @@
1
1
  import { loadConfig } from '../../config.ts'
2
2
  import { connect, disconnect } from '../../db/client.ts'
3
- import { initLogger } from '../../logger.ts'
3
+ import { getLogger } from '../../logger.ts'
4
4
  import { observe } from '../../memory/write.ts'
5
5
  import { MemoryKindSchema } from '../../types.ts'
6
- import { app } from '../shared.ts'
6
+ import { app, initCliCommand } from '../shared.ts'
7
+
8
+ const log = getLogger(['suemo', 'cli', 'observe'])
7
9
 
8
10
  export const observeCmd = app.sub('observe')
9
11
  .meta({ description: 'Store a new observation in memory' })
@@ -17,7 +19,7 @@ export const observeCmd = app.sub('observe')
17
19
  session: { type: 'string', description: 'Session ID (attach to open episode)' },
18
20
  })
19
21
  .run(async ({ args, flags }) => {
20
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
22
+ await initCliCommand('observe', { debug: flags.debug, config: flags.config })
21
23
 
22
24
  const kindParse = MemoryKindSchema.safeParse(flags.kind)
23
25
  if (!kindParse.success) {
@@ -27,14 +29,25 @@ export const observeCmd = app.sub('observe')
27
29
 
28
30
  const config = await loadConfig(process.cwd(), flags.config)
29
31
  const db = await connect(config.surreal)
30
- const node = await observe(db, {
31
- content: args.content,
32
- kind: kindParse.data,
33
- tags: flags.tags ? flags.tags.split(',').map((t) => t.trim()) : [],
34
- scope: flags.scope,
35
- confidence: flags.confidence,
36
- source: flags.source,
37
- })
38
- await disconnect()
39
- console.log(JSON.stringify({ id: node.id, kind: node.kind, valid_from: node.valid_from }, null, 2))
32
+ try {
33
+ log.debug('Running observe command', {
34
+ kind: kindParse.data,
35
+ hasScope: Boolean(flags.scope),
36
+ tagCount: flags.tags ? flags.tags.split(',').filter(Boolean).length : 0,
37
+ confidence: flags.confidence,
38
+ contentLength: args.content.length,
39
+ })
40
+ const node = await observe(db, {
41
+ content: args.content,
42
+ kind: kindParse.data,
43
+ tags: flags.tags ? flags.tags.split(',').map((t) => t.trim()) : [],
44
+ scope: flags.scope,
45
+ confidence: flags.confidence,
46
+ source: flags.source,
47
+ sessionId: flags.session,
48
+ }, config)
49
+ console.log(JSON.stringify({ id: node.id, kind: node.kind, valid_from: node.valid_from }, null, 2))
50
+ } finally {
51
+ await disconnect()
52
+ }
40
53
  })
@@ -1,8 +1,11 @@
1
1
  import { loadConfig } from '../../config.ts'
2
2
  import { connect, disconnect } from '../../db/client.ts'
3
- import { initLogger } from '../../logger.ts'
3
+ import { getLogger } from '../../logger.ts'
4
4
  import { query } from '../../memory/read.ts'
5
- import { app } from '../shared.ts'
5
+ import type { MemoryNode } from '../../types.ts'
6
+ import { app, initCliCommand } from '../shared.ts'
7
+
8
+ const log = getLogger(['suemo', 'cli', 'query'])
6
9
 
7
10
  export const queryCmd = app.sub('query')
8
11
  .meta({ description: 'Hybrid semantic search over memories' })
@@ -13,11 +16,24 @@ export const queryCmd = app.sub('query')
13
16
  json: { type: 'boolean', short: 'j', description: 'Output full JSON nodes' },
14
17
  })
15
18
  .run(async ({ args, flags }) => {
16
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
17
- const config = await loadConfig(process.cwd(), flags.config)
18
- const db = await connect(config.surreal)
19
- const nodes = await query(db, { input: args.input, scope: flags.scope, topK: flags.top })
20
- await disconnect()
19
+ await initCliCommand('query', { debug: flags.debug, config: flags.config })
20
+ log.debug('Running query command', {
21
+ hasScope: Boolean(flags.scope),
22
+ top: flags.top,
23
+ json: Boolean(flags.json),
24
+ inputLength: args.input.length,
25
+ })
26
+ const nodes: MemoryNode[] = await (async () => {
27
+ let connected = false
28
+ try {
29
+ const config = await loadConfig(process.cwd(), flags.config)
30
+ const db = await connect(config.surreal)
31
+ connected = true
32
+ return await query(db, { input: args.input, scope: flags.scope, topK: flags.top }, config)
33
+ } finally {
34
+ if (connected) await disconnect()
35
+ }
36
+ })()
21
37
  if (flags.json) {
22
38
  console.log(JSON.stringify(nodes, null, 2))
23
39
  } else {
@@ -1,17 +1,23 @@
1
1
  import { loadConfig } from '../../config.ts'
2
2
  import { connect, disconnect } from '../../db/client.ts'
3
- import { initLogger } from '../../logger.ts'
3
+ import { getLogger } from '../../logger.ts'
4
4
  import { recall } from '../../memory/read.ts'
5
- import { app } from '../shared.ts'
5
+ import { app, initCliCommand } from '../shared.ts'
6
+
7
+ const log = getLogger(['suemo', 'cli', 'recall'])
6
8
 
7
9
  export const recallCmd = app.sub('recall')
8
10
  .meta({ description: 'Fetch a single node + its neighbours (ticks FSRS)' })
9
11
  .args([{ name: 'nodeId', type: 'string', required: true }])
10
12
  .run(async ({ args, flags }) => {
11
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
13
+ await initCliCommand('recall', { debug: flags.debug, config: flags.config })
14
+ log.debug('Running recall command', { nodeId: args.nodeId })
12
15
  const config = await loadConfig(process.cwd(), flags.config)
13
16
  const db = await connect(config.surreal)
14
- const result = await recall(db, args.nodeId)
15
- await disconnect()
16
- console.log(JSON.stringify(result, null, 2))
17
+ try {
18
+ const result = await recall(db, args.nodeId)
19
+ console.log(JSON.stringify(result, null, 2))
20
+ } finally {
21
+ await disconnect()
22
+ }
17
23
  })
@@ -1,19 +1,38 @@
1
- import { loadConfig } from '../../config.ts'
2
- import { initLogger } from '../../logger.ts'
3
- import { startMcpServer } from '../../mcp/server.ts'
4
- import { app } from '../shared.ts'
1
+ import { loadConfig, resolveSyncConfig } from '../../config.ts'
2
+ import { getLogger } from '../../logger.ts'
3
+ import { startMcpServer, startMcpStdioServer } from '../../mcp/server.ts'
4
+ import { app, initCliCommand } from '../shared.ts'
5
+
6
+ const log = getLogger(['suemo', 'cli', 'serve'])
5
7
 
6
8
  export const serveCmd = app.sub('serve')
7
- .meta({ description: 'Start the MCP server' })
9
+ .meta({ description: 'Start the MCP server (HTTP or stdio)' })
8
10
  .flags({
9
11
  port: { type: 'number', short: 'p', description: 'Port to listen on (overrides config)' },
10
12
  host: { type: 'string', description: 'Host to bind to (overrides config)' },
13
+ stdio: { type: 'boolean', description: 'Use stdio transport instead of HTTP' },
11
14
  })
12
15
  .run(async ({ flags }) => {
13
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
16
+ await initCliCommand('serve', { debug: flags.debug, config: flags.config })
14
17
  const config = await loadConfig(process.cwd(), flags.config)
18
+ const sync = resolveSyncConfig(config)
19
+ if (sync?.auto.enabled) {
20
+ log.info('Auto-sync enabled', {
21
+ remote: sync.auto.remote,
22
+ direction: sync.auto.direction,
23
+ intervalSeconds: sync.auto.intervalSeconds,
24
+ onWrite: sync.auto.onWrite,
25
+ minWriteIntervalSeconds: sync.auto.minWriteIntervalSeconds,
26
+ })
27
+ }
28
+ if (flags.stdio) {
29
+ log.debug('Starting MCP stdio transport')
30
+ await startMcpStdioServer(config)
31
+ return
32
+ }
15
33
  if (flags.port) config.mcp.port = flags.port
16
34
  if (flags.host) config.mcp.host = flags.host
35
+ log.debug('Starting MCP HTTP transport', { host: config.mcp.host, port: config.mcp.port })
17
36
  await startMcpServer(config)
18
37
  // Server runs indefinitely — no disconnect
19
38
  })