suemo 0.0.1 → 0.0.2

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 CHANGED
@@ -58,23 +58,24 @@ bun install
58
58
  **1. Create config**
59
59
 
60
60
  ```sh
61
- bun run src/cli/index.ts init
62
- # or, once linked: suemo init
61
+ bun run src/cli/index.ts init config
62
+ # or, once linked: suemo init config
63
63
  ```
64
64
 
65
65
  This writes `~/.suemo/suemo.ts`. Edit it with your SurrealDB URL, credentials, and LLM endpoint.
66
66
 
67
67
  **2. Apply schema**
68
68
 
69
- `suemo init` applies the schema automatically if `SURREAL_*` env vars are set. Otherwise:
69
+ Apply schema after you set/edit namespace/database:
70
70
 
71
71
  ```sh
72
- SURREAL_URL=ws://localhost:8000 \
73
- SURREAL_USER=root \
74
- SURREAL_PASS=root \
75
- suemo init
72
+ suemo init schema
76
73
  ```
77
74
 
75
+ Pass `--yes` to skip schema confirmation in non-interactive flows.
76
+
77
+ `suemo init` now shows guidance by default; run `init config` and `init schema` explicitly.
78
+
78
79
  **3. Store a memory**
79
80
 
80
81
  ```sh
@@ -107,8 +108,10 @@ Global flags (inherited by all commands):
107
108
  -d, --debug Verbose debug logging
108
109
 
109
110
  Commands:
110
- init Create config template and apply DB schema
111
- serve Start the MCP server
111
+ init Show init subcommands and usage guidance
112
+ init config Create/update ~/.suemo/suemo.ts
113
+ init schema Apply DB schema from current config (with confirm)
114
+ serve Start the MCP server (HTTP or stdio)
112
115
  observe <content> Store an observation
113
116
  believe <content> Store a belief (triggers contradiction detection)
114
117
  query <input> Hybrid semantic search
@@ -119,6 +122,7 @@ Commands:
119
122
  goal list List active goals
120
123
  goal resolve <id> Mark a goal achieved
121
124
  consolidate Run NREM + REM consolidation pipeline
125
+ doctor embed Diagnose fn::embed setup and print fix steps
122
126
  health Memory health report
123
127
  health vitals Last 10 consolidation runs + node counts
124
128
  sync Push memories to a remote SurrealDB instance
@@ -130,7 +134,16 @@ Commands:
130
134
 
131
135
  ## Config
132
136
 
133
- Config lives at `~/.suemo/suemo.ts` (user-level) or `suemo.config.ts` in the project root. Project-local takes precedence. Override with `--config <path>`.
137
+ Config lives at `~/.suemo/suemo.ts` (user-level) or `suemo.config.ts` in the project root. Project-local takes precedence.
138
+
139
+ Resolution order:
140
+
141
+ 1. `--config <path>`
142
+ 2. `SUEMO_CONFIG_PATH`
143
+ 3. project-local (`suemo.config.ts` / `suemo.config.js`)
144
+ 4. user-level (`~/.suemo/suemo.ts`)
145
+
146
+ Set `SUEMO_DEBUG=1` (or `true`, `yes`, `on`) to enable debug logging globally for CLI commands.
134
147
 
135
148
  ```ts
136
149
  // ~/.suemo/suemo.ts
@@ -181,6 +194,20 @@ export default {
181
194
  suemo serve --config ~/.suemo/opencode.ts
182
195
  ```
183
196
 
197
+ For local stdio transport (no network port), run:
198
+
199
+ ```sh
200
+ suemo serve --stdio
201
+ ```
202
+
203
+ If stdio startup fails with an embedding preflight error, run:
204
+
205
+ ```sh
206
+ suemo doctor embed
207
+ ```
208
+
209
+ This prints your active target (`url`, `namespace`, `database`) and step-by-step commands to import a model and define `fn::embed()` for that exact database.
210
+
184
211
  ---
185
212
 
186
213
  ## MCP Tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suemo",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Persistent semantic memory for AI agents — backed by SurrealDB.",
5
5
  "author": {
6
6
  "name": "Umar Alfarouk",
@@ -1,8 +1,10 @@
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 { believe } from '../../memory/write.ts'
5
- import { app } from '../shared.ts'
5
+ import { app, initCliCommand } from '../shared.ts'
6
+
7
+ const log = getLogger(['suemo', 'cli', 'believe'])
6
8
 
7
9
  export const believeCmd = app.sub('believe')
8
10
  .meta({ description: 'Store a belief (triggers contradiction detection)' })
@@ -12,16 +14,24 @@ export const believeCmd = app.sub('believe')
12
14
  confidence: { type: 'number', description: 'Confidence 0.0–1.0', default: 1.0 },
13
15
  })
14
16
  .run(async ({ args, flags }) => {
15
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
17
+ await initCliCommand('believe', { debug: flags.debug, config: flags.config })
16
18
  const config = await loadConfig(process.cwd(), flags.config)
17
19
  const db = await connect(config.surreal)
18
- const { node, contradicted } = await believe(db, {
19
- content: args.content,
20
- scope: flags.scope,
21
- confidence: flags.confidence,
22
- })
23
- await disconnect()
24
- const out: Record<string, unknown> = { id: node.id, valid_from: node.valid_from }
25
- if (contradicted) out.contradicted = contradicted.id
26
- console.log(JSON.stringify(out, null, 2))
20
+ try {
21
+ log.debug('Running believe command', {
22
+ hasScope: Boolean(flags.scope),
23
+ confidence: flags.confidence,
24
+ contentLength: args.content.length,
25
+ })
26
+ const { node, contradicted } = await believe(db, {
27
+ content: args.content,
28
+ scope: flags.scope,
29
+ confidence: flags.confidence,
30
+ }, config)
31
+ const out: Record<string, unknown> = { id: node.id, valid_from: node.valid_from }
32
+ if (contradicted) out.contradicted = contradicted.id
33
+ console.log(JSON.stringify(out, null, 2))
34
+ } finally {
35
+ await disconnect()
36
+ }
27
37
  })
@@ -1,8 +1,10 @@
1
1
  import { consolidate } from '../../cognitive/consolidate.ts'
2
2
  import { loadConfig } from '../../config.ts'
3
3
  import { connect, disconnect } from '../../db/client.ts'
4
- import { initLogger } from '../../logger.ts'
5
- import { app } from '../shared.ts'
4
+ import { getLogger } from '../../logger.ts'
5
+ import { app, initCliCommand } from '../shared.ts'
6
+
7
+ const log = getLogger(['suemo', 'cli', 'consolidate'])
6
8
 
7
9
  export const consolidateCmd = app.sub('consolidate')
8
10
  .meta({ description: 'Manually trigger memory consolidation (NREM + REM)' })
@@ -10,15 +12,20 @@ export const consolidateCmd = app.sub('consolidate')
10
12
  'nrem-only': { type: 'boolean', description: 'Run only NREM (compression) phase', default: false },
11
13
  })
12
14
  .run(async ({ flags }) => {
13
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
15
+ await initCliCommand('consolidate', { debug: flags.debug, config: flags.config })
16
+ log.debug('Running consolidate command', { nremOnly: flags['nrem-only'] })
14
17
  const config = await loadConfig(process.cwd(), flags.config)
15
18
  const db = await connect(config.surreal)
16
- const run = await consolidate(db, {
17
- nremOnly: flags['nrem-only'],
18
- nremSimilarityThreshold: config.consolidation.nremSimilarityThreshold,
19
- remRelationThreshold: config.consolidation.remRelationThreshold,
20
- llm: config.consolidation.llm,
21
- })
22
- await disconnect()
23
- console.log(JSON.stringify(run, null, 2))
19
+ try {
20
+ const run = await consolidate(db, {
21
+ nremOnly: flags['nrem-only'],
22
+ nremSimilarityThreshold: config.consolidation.nremSimilarityThreshold,
23
+ remRelationThreshold: config.consolidation.remRelationThreshold,
24
+ llm: config.consolidation.llm,
25
+ embedding: config.embedding,
26
+ })
27
+ console.log(JSON.stringify(run, null, 2))
28
+ } finally {
29
+ await disconnect()
30
+ }
24
31
  })
@@ -0,0 +1,123 @@
1
+ import type { Surreal } from 'surrealdb'
2
+ import { loadConfig } from '../../config.ts'
3
+ import { connect, disconnect } from '../../db/client.ts'
4
+ import { checkCompatibility } from '../../db/preflight.ts'
5
+ import { getLogger } from '../../logger.ts'
6
+ import { app, initCliCommand } from '../shared.ts'
7
+
8
+ const log = getLogger(['suemo', 'cli', 'doctor'])
9
+
10
+ const doctor = app.sub('doctor')
11
+ .meta({ description: 'Diagnostics and setup guidance' })
12
+
13
+ function toCliEndpoint(url: string): string {
14
+ if (url.startsWith('ws://')) return `http://${url.slice('ws://'.length)}`
15
+ if (url.startsWith('wss://')) return `https://${url.slice('wss://'.length)}`
16
+ return url
17
+ }
18
+
19
+ function printStatus(label: string, ok: boolean, detail?: string): void {
20
+ const prefix = ok ? '✓' : '✗'
21
+ if (detail) {
22
+ console.log(`${prefix} ${label}: ${detail}`)
23
+ return
24
+ }
25
+ console.log(`${prefix} ${label}`)
26
+ }
27
+
28
+ async function probeEmbed(db: Surreal): Promise<{ ok: boolean; error?: string }> {
29
+ try {
30
+ await db.query('RETURN fn::embed("suemo doctor embed probe")')
31
+ return { ok: true }
32
+ } catch (error) {
33
+ return { ok: false, error: String(error) }
34
+ }
35
+ }
36
+
37
+ async function detectModelNames(db: Surreal): Promise<string[]> {
38
+ try {
39
+ const result = await db.query<unknown[]>('INFO FOR DB')
40
+ const first = Array.isArray(result) ? result[0] : null
41
+ const row = Array.isArray(first) ? first[0] : first
42
+ if (!row || typeof row !== 'object') return []
43
+ const models = (row as { models?: unknown }).models
44
+ if (!models || typeof models !== 'object') return []
45
+ return Object.keys(models as Record<string, unknown>)
46
+ } catch {
47
+ return []
48
+ }
49
+ }
50
+
51
+ const doctorEmbedCmd = doctor.sub('embed')
52
+ .meta({ description: 'Diagnose fn::embed() and show setup steps' })
53
+ .run(async ({ flags }) => {
54
+ await initCliCommand('doctor embed', { debug: flags.debug, config: flags.config })
55
+
56
+ const config = await loadConfig(process.cwd(), flags.config)
57
+ const endpoint = toCliEndpoint(config.surreal.url)
58
+
59
+ console.log('Embedding diagnostics target:')
60
+ console.log(` url: ${config.surreal.url}`)
61
+ console.log(` namespace: ${config.surreal.namespace}`)
62
+ console.log(` database: ${config.surreal.database}`)
63
+ console.log()
64
+
65
+ let db: Surreal | undefined
66
+ try {
67
+ db = await connect(config.surreal)
68
+
69
+ const compat = await checkCompatibility(db, {
70
+ requireEmbedding: false,
71
+ context: 'cli:doctor-embed',
72
+ })
73
+ const embedProbe = await probeEmbed(db)
74
+ const modelNames = await detectModelNames(db)
75
+
76
+ printStatus('SurrealDB version', compat.surrealVersion !== 'unknown', compat.surrealVersion)
77
+ printStatus('SurrealKV', compat.surrealkv)
78
+ printStatus('Retention >= 90d', compat.retention_ok)
79
+ printStatus('fn::embed()', embedProbe.ok, embedProbe.ok ? 'callable' : 'not callable')
80
+ printStatus(
81
+ 'Imported models in DB',
82
+ modelNames.length > 0,
83
+ modelNames.length > 0 ? modelNames.join(', ') : 'none detected',
84
+ )
85
+
86
+ if (embedProbe.ok) {
87
+ console.log('\n✅ fn::embed() is configured. You should be able to run:')
88
+ console.log(` suemo serve --stdio --config ${flags.config ?? '~/.suemo/suemo.ts'}`)
89
+ return
90
+ }
91
+
92
+ log.warn('fn::embed probe failed', { error: embedProbe.error ?? 'unknown' })
93
+ console.log('\nHow to set up fn::embed() (step-by-step):')
94
+ console.log('\n1) Ensure SurrealDB CLI exposes ML commands:')
95
+ console.log(' surreal ml --help')
96
+ console.log('\n2) Import a .surml embedding model into this exact NS/DB:')
97
+ console.log(
98
+ ` surreal ml import --conn ${endpoint} --user <USER> --pass <PASS> --ns ${config.surreal.namespace} --db ${config.surreal.database} path/to/your-model.surml`,
99
+ )
100
+ console.log('\n3) Define fn::embed wrapper to your imported model:')
101
+ console.log(
102
+ ` surreal sql --conn ${endpoint} --user <USER> --pass <PASS> --ns ${config.surreal.namespace} --db ${config.surreal.database} --query "DEFINE FUNCTION OVERWRITE fn::embed(\\$text: string) { RETURN ml::your_model<1.0.0>(\\$text); };"`,
103
+ )
104
+ console.log('\n4) Verify function now works:')
105
+ console.log(
106
+ ` surreal sql --conn ${endpoint} --user <USER> --pass <PASS> --ns ${config.surreal.namespace} --db ${config.surreal.database} --query 'RETURN fn::embed("suemo test");'`,
107
+ )
108
+ console.log('\n5) Start MCP stdio server:')
109
+ console.log(` suemo serve --stdio --config ${flags.config ?? '~/.suemo/suemo.ts'}`)
110
+ console.log('\nIf step 4 still fails, rerun: suemo doctor embed --config ~/.suemo/suemo.ts --debug')
111
+ } finally {
112
+ if (db) await disconnect()
113
+ }
114
+ })
115
+
116
+ export const doctorCmd = doctor
117
+ .command(doctorEmbedCmd)
118
+ .run(async ({ flags }) => {
119
+ await initCliCommand('doctor', { debug: flags.debug, config: flags.config })
120
+ console.log('Use one of:')
121
+ console.log(' suemo doctor embed')
122
+ console.log('\nRun `suemo doctor --help` for full details.')
123
+ })
@@ -2,9 +2,11 @@ import { createReadStream } from 'node:fs'
2
2
  import { createInterface } from 'node:readline'
3
3
  import { loadConfig } from '../../config.ts'
4
4
  import { connect, disconnect } from '../../db/client.ts'
5
- import { initLogger } from '../../logger.ts'
5
+ import { getLogger } from '../../logger.ts'
6
6
  import type { MemoryNode, Relation } from '../../types.ts'
7
- import { app } from '../shared.ts'
7
+ import { app, initCliCommand } from '../shared.ts'
8
+
9
+ const log = getLogger(['suemo', 'cli', 'export-import'])
8
10
 
9
11
  // ── export ─────────────────────────────────────────────────────────────────
10
12
  export const exportCmd = app.sub('export')
@@ -14,31 +16,36 @@ export const exportCmd = app.sub('export')
14
16
  all: { type: 'boolean', description: 'Include invalidated nodes' },
15
17
  })
16
18
  .run(async ({ flags }) => {
17
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
19
+ await initCliCommand('export', { debug: flags.debug, config: flags.config })
20
+ log.debug('Running export command', {
21
+ hasScope: Boolean(flags.scope),
22
+ includeInvalidated: Boolean(flags.all),
23
+ })
18
24
  const config = await loadConfig(process.cwd(), flags.config)
19
25
  const db = await connect(config.surreal)
26
+ try {
27
+ const activeFilter = flags.all ? 'true' : '(valid_until = NONE OR valid_until > time::now())'
28
+ const scopeFilter = '($scope = NONE OR scope = $scope)'
20
29
 
21
- const activeFilter = flags.all ? 'true' : '(valid_until = NONE OR valid_until > time::now())'
22
- const scopeFilter = '($scope = NONE OR scope = $scope)'
23
-
24
- const [nodesResult, relationsResult] = await Promise.all([
25
- db.query<[MemoryNode[]]>(
26
- `
30
+ const [nodesResult, relationsResult] = await Promise.all([
31
+ db.query<[MemoryNode[]]>(
32
+ `
27
33
  SELECT * FROM memory WHERE ${activeFilter} AND ${scopeFilter} ORDER BY created_at ASC
28
34
  `,
29
- { scope: flags.scope ?? null },
30
- ),
31
- db.query<[Relation[]]>('SELECT * FROM relates_to ORDER BY created_at ASC'),
32
- ])
35
+ { scope: flags.scope ?? null },
36
+ ),
37
+ db.query<[Relation[]]>('SELECT * FROM relates_to ORDER BY created_at ASC'),
38
+ ])
33
39
 
34
- for (const node of nodesResult[0] ?? []) {
35
- process.stdout.write(JSON.stringify({ _type: 'memory', ...node }) + '\n')
36
- }
37
- for (const rel of relationsResult[0] ?? []) {
38
- process.stdout.write(JSON.stringify({ _type: 'relation', ...rel }) + '\n')
40
+ for (const node of nodesResult[0] ?? []) {
41
+ process.stdout.write(JSON.stringify({ _type: 'memory', ...node }) + '\n')
42
+ }
43
+ for (const rel of relationsResult[0] ?? []) {
44
+ process.stdout.write(JSON.stringify({ _type: 'relation', ...rel }) + '\n')
45
+ }
46
+ } finally {
47
+ await disconnect()
39
48
  }
40
-
41
- await disconnect()
42
49
  })
43
50
 
44
51
  // ── import ──────────────────────────────────────────────────────────────────
@@ -46,7 +53,8 @@ export const importCmd = app.sub('import')
46
53
  .meta({ description: 'Import memories from a JSONL file' })
47
54
  .args([{ name: 'file', type: 'string', required: true }])
48
55
  .run(async ({ args, flags }) => {
49
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
56
+ await initCliCommand('import', { debug: flags.debug, config: flags.config })
57
+ log.debug('Running import command', { file: args.file })
50
58
  const config = await loadConfig(process.cwd(), flags.config)
51
59
  const db = await connect(config.surreal)
52
60
 
@@ -56,36 +64,39 @@ export const importCmd = app.sub('import')
56
64
  let skipped = 0
57
65
  let errors = 0
58
66
 
59
- for await (const line of rl) {
60
- lineNum++
61
- if (!line.trim()) continue
62
- let row: Record<string, unknown>
63
- try {
64
- row = JSON.parse(line)
65
- } catch {
66
- console.error(`Line ${lineNum}: invalid JSON — stopping`)
67
- break
68
- }
67
+ try {
68
+ for await (const line of rl) {
69
+ lineNum++
70
+ if (!line.trim()) continue
71
+ let row: Record<string, unknown>
72
+ try {
73
+ row = JSON.parse(line)
74
+ } catch {
75
+ console.error(`Line ${lineNum}: invalid JSON — stopping`)
76
+ break
77
+ }
69
78
 
70
- const type = row['_type']
71
- delete row['_type']
79
+ const type = row['_type']
80
+ delete row['_type']
72
81
 
73
- try {
74
- if (type === 'memory') {
75
- await db.query('INSERT IGNORE INTO memory $row', { row })
76
- imported++
77
- } else if (type === 'relation') {
78
- await db.query('INSERT IGNORE INTO relates_to $row', { row })
79
- imported++
80
- } else {
81
- skipped++
82
+ try {
83
+ if (type === 'memory') {
84
+ await db.query('INSERT IGNORE INTO memory $row', { row })
85
+ imported++
86
+ } else if (type === 'relation') {
87
+ await db.query('INSERT IGNORE INTO relates_to $row', { row })
88
+ imported++
89
+ } else {
90
+ skipped++
91
+ }
92
+ } catch (e) {
93
+ console.error(`Line ${lineNum}: insert failed — ${String(e)}`)
94
+ errors++
82
95
  }
83
- } catch (e) {
84
- console.error(`Line ${lineNum}: insert failed — ${String(e)}`)
85
- errors++
86
96
  }
97
+ } finally {
98
+ rl.close()
99
+ await disconnect()
87
100
  }
88
-
89
- await disconnect()
90
101
  console.log(JSON.stringify({ imported, skipped, errors, lines: lineNum }, null, 2))
91
102
  })
@@ -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 type { Surreal } from 'surrealdb'
1
2
  import { healthReport, 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,23 +13,33 @@ 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')
25
34
  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))
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
+ }
30
43
  })
31
44
 
32
45
  export const healthCmd = health
@@ -34,10 +47,15 @@ export const healthCmd = health
34
47
  .command(vitalsCmd)
35
48
  // Default: run the report when just `suemo health` is called
36
49
  .run(async ({ flags }) => {
37
- await initLogger({ level: flags.debug ? 'debug' : 'info' })
50
+ await initCliCommand('health', { debug: flags.debug, config: flags.config })
51
+ log.debug('Running default health command')
38
52
  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))
53
+ let db: Surreal | undefined
54
+ try {
55
+ db = await connect(config.surreal)
56
+ const report = await healthReport(db)
57
+ console.log(JSON.stringify(report, null, 2))
58
+ } finally {
59
+ if (db) await disconnect()
60
+ }
43
61
  })