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.
- package/README.md +127 -27
- package/package.json +1 -1
- package/src/cli/commands/believe.ts +22 -12
- package/src/cli/commands/consolidate.ts +18 -11
- package/src/cli/commands/doctor.ts +123 -0
- package/src/cli/commands/export-import.ts +83 -47
- package/src/cli/commands/goal.ts +52 -27
- package/src/cli/commands/health.ts +53 -18
- package/src/cli/commands/init.ts +155 -75
- package/src/cli/commands/observe.ts +26 -13
- package/src/cli/commands/query.ts +23 -7
- package/src/cli/commands/recall.ts +12 -6
- package/src/cli/commands/serve.ts +25 -6
- package/src/cli/commands/sync.ts +44 -10
- package/src/cli/commands/timeline.ts +30 -18
- package/src/cli/commands/wander.ts +27 -16
- package/src/cli/index.ts +3 -4
- package/src/cli/shared.ts +34 -0
- package/src/cognitive/consolidate.ts +48 -19
- package/src/cognitive/contradiction.ts +19 -7
- package/src/cognitive/health.ts +61 -1
- package/src/config.template.ts +58 -0
- package/src/config.ts +124 -14
- package/src/db/preflight.ts +32 -6
- package/src/db/schema.surql +30 -9
- package/src/db/schema.ts +6 -3
- package/src/embedding/index.ts +52 -0
- package/src/embedding/openai-compatible.ts +43 -0
- package/src/goal.ts +3 -1
- package/src/index.ts +5 -1
- package/src/mcp/dispatch.ts +232 -0
- package/src/mcp/server.ts +150 -4
- package/src/mcp/stdio.ts +385 -0
- package/src/mcp/tools.ts +13 -90
- package/src/memory/episode.ts +92 -0
- package/src/memory/read.ts +76 -19
- package/src/memory/write.ts +253 -20
- package/src/sync.ts +310 -66
- package/src/types.ts +30 -5
- package/src/cli/commands/shared.ts +0 -20
package/src/cli/commands/goal.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
27
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
})
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -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 {
|
|
8
|
-
import { app } from '../shared.ts'
|
|
8
|
+
import { getLogger } from '../../logger.ts'
|
|
9
|
+
import { app, initCliCommand } from '../shared.ts'
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
120
|
+
force: { type: 'boolean', description: 'Overwrite existing config file' },
|
|
14
121
|
})
|
|
15
122
|
.run(async ({ flags }) => {
|
|
16
|
-
await
|
|
123
|
+
await initCliCommand('init config', { debug: flags.debug, config: flags.config })
|
|
124
|
+
writeConfig(Boolean(flags.force), configOutputPath(flags))
|
|
125
|
+
})
|
|
17
126
|
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 {
|
|
3
|
+
import { getLogger } from '../../logger.ts'
|
|
4
4
|
import { query } from '../../memory/read.ts'
|
|
5
|
-
import {
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 {
|
|
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
|
|
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
|
})
|