suemo 0.0.5 → 0.0.6
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 +54 -2
- package/package.json +5 -2
- package/src/cli/commands/believe.ts +6 -3
- package/src/cli/commands/consolidate.ts +4 -1
- package/src/cli/commands/doctor.ts +2 -0
- package/src/cli/commands/export-import.ts +24 -5
- package/src/cli/commands/goal.ts +10 -5
- package/src/cli/commands/init.ts +66 -0
- package/src/cli/commands/observe.ts +6 -3
- package/src/cli/commands/query.ts +7 -4
- package/src/cli/commands/serve.ts +6 -1
- package/src/cli/commands/skill.ts +56 -0
- package/src/cli/commands/timeline.ts +7 -4
- package/src/cli/commands/wander.ts +7 -4
- package/src/cli/index.ts +2 -0
- package/src/cli/shared.ts +11 -0
- package/src/cognitive/consolidate.ts +21 -7
- package/src/cognitive/contradiction.ts +2 -1
- package/src/config.template.ts +3 -0
- package/src/config.ts +122 -2
- package/src/db/preflight.ts +6 -3
- package/src/db/schema.surql +17 -0
- package/src/index.ts +3 -1
- package/src/mcp/dispatch.ts +116 -16
- package/src/mcp/server.ts +11 -1
- package/src/mcp/stdio.ts +57 -1
- package/src/memory/episode.ts +38 -6
- package/src/memory/read.ts +69 -1
- package/src/memory/write.ts +95 -13
- package/src/skill/catalog.ts +39 -0
- package/src/sync.ts +85 -0
- package/src/types.ts +13 -0
package/README.md
CHANGED
|
@@ -34,11 +34,14 @@ suemo gives AI agents a memory that survives across sessions, models, and runtim
|
|
|
34
34
|
SurrealDB must be started with SurrealKV and a retention window:
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
SURREAL_DATASTORE_RETENTION=90d surreal start \
|
|
37
|
+
SURREAL_DATASTORE_VERSIONED=true SURREAL_DATASTORE_RETENTION=90d surreal start \
|
|
38
38
|
--bind 0.0.0.0:8000 \
|
|
39
|
-
|
|
39
|
+
--allow-funcs "fn::*" \
|
|
40
|
+
-- surrealkv:///path/to/data
|
|
40
41
|
```
|
|
41
42
|
|
|
43
|
+
For suemo, the critical capability is allowing custom functions (`fn::embed`) via `--allow-funcs "fn::*"`.
|
|
44
|
+
|
|
42
45
|
An embedding model must be configured in SurrealDB for `fn::embed()` to work.
|
|
43
46
|
|
|
44
47
|
---
|
|
@@ -64,6 +67,19 @@ bun run src/cli/index.ts init config
|
|
|
64
67
|
|
|
65
68
|
This writes `~/.suemo/suemo.ts`. Edit it with your SurrealDB URL, credentials, and LLM endpoint.
|
|
66
69
|
|
|
70
|
+
Set project scope discovery in config (recommended):
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
export default defineConfig({
|
|
74
|
+
main: {
|
|
75
|
+
projectDir: '.ua',
|
|
76
|
+
},
|
|
77
|
+
// ...
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
With this, suemo resolves default scope from nearest `.ua/suemo.json` (auto-created if missing).
|
|
82
|
+
|
|
67
83
|
**2. Apply schema**
|
|
68
84
|
|
|
69
85
|
Apply schema after you set/edit namespace/database:
|
|
@@ -106,6 +122,30 @@ suemo serve --dev
|
|
|
106
122
|
|
|
107
123
|
---
|
|
108
124
|
|
|
125
|
+
## OpenCode setup (only integration target)
|
|
126
|
+
|
|
127
|
+
Print copy-paste setup snippets:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
suemo init opencode
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This prints:
|
|
134
|
+
|
|
135
|
+
- MCP config snippet (`command: suemo`, `args: ["serve", "--stdio", ...]`)
|
|
136
|
+
- minimal AGENTS.md guidance snippet
|
|
137
|
+
|
|
138
|
+
To fetch the latest skill docs directly from your local checkout:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
suemo skill
|
|
142
|
+
suemo skill core-workflow
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
No files are auto-written by this command.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
109
149
|
## CLI Reference
|
|
110
150
|
|
|
111
151
|
```
|
|
@@ -119,6 +159,7 @@ Commands:
|
|
|
119
159
|
init Show init subcommands and usage guidance
|
|
120
160
|
init config Create/update ~/.suemo/suemo.ts
|
|
121
161
|
init schema Apply DB schema from current config (with confirm)
|
|
162
|
+
skill Print suemo skill docs (or specific reference)
|
|
122
163
|
serve Start the MCP server (HTTP or stdio)
|
|
123
164
|
observe <content> Store an observation
|
|
124
165
|
believe <content> Store a belief (triggers contradiction detection)
|
|
@@ -277,12 +318,16 @@ This prints your active target (`url`, `namespace`, `database`) and step-by-step
|
|
|
277
318
|
| `recall` | Fetch a node and its 1-hop neighbourhood; ticks FSRS |
|
|
278
319
|
| `wander` | Spreading-activation walk through the graph |
|
|
279
320
|
| `timeline` | Chronological memory slice with optional date range |
|
|
321
|
+
| `context` | Recover recent session context for current/default scope |
|
|
280
322
|
| `episode_start` | Begin a bounded session window |
|
|
281
323
|
| `episode_end` | Close a session, optionally with a summary |
|
|
282
324
|
| `goal_set` | Create a goal node |
|
|
283
325
|
| `goal_resolve` | Mark a goal achieved |
|
|
284
326
|
| `goal_list` | List active (or all) goals |
|
|
285
327
|
| `upsert_by_key` | Upsert a memory node by stable topic key |
|
|
328
|
+
| `update` | Update an existing memory node by ID (re-embeds if content changed) |
|
|
329
|
+
| `suggest_topic_key` | Suggest deterministic canonical topic key from free text |
|
|
330
|
+
| `skill` | Return current suemo skill docs or one named reference |
|
|
286
331
|
| `capture_prompt` | Capture raw prompt and link derived observations |
|
|
287
332
|
| `session_context_get` | Fetch open episode summary/context by session ID |
|
|
288
333
|
| `session_context_set` | Update open episode summary/context by session ID |
|
|
@@ -293,6 +338,13 @@ This prints your active target (`url`, `namespace`, `database`) and step-by-step
|
|
|
293
338
|
|
|
294
339
|
Agents never supply temporal fields (`valid_from`, `valid_until`). These are system-managed.
|
|
295
340
|
|
|
341
|
+
### Scope and longevity notes
|
|
342
|
+
|
|
343
|
+
- Default inferred project scope now uses nearest `<projectDir>/suemo.json` with `main.projectDir` defaulting to `.ua`.
|
|
344
|
+
- Semantic operations are scope-aware (dedup, contradiction detection, upsert-by-key, consolidation).
|
|
345
|
+
- Retention probe is fail-closed on unexpected probe errors.
|
|
346
|
+
- Episode records are included in sync and export/import flows.
|
|
347
|
+
|
|
296
348
|
---
|
|
297
349
|
|
|
298
350
|
## Architecture
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suemo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Persistent semantic memory for AI agents — backed by SurrealDB.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Umar Alfarouk",
|
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"dev": "bun run src/cli/index.ts",
|
|
39
39
|
"start": "bun run src/cli/index.ts",
|
|
40
|
-
"check": "bun
|
|
40
|
+
"ssot:check": "bun run scripts/ssot.ts check",
|
|
41
|
+
"ssot:sync": "bun run scripts/ssot.ts sync",
|
|
42
|
+
"check": "bun tsc --noEmit && bun run ssot:check",
|
|
43
|
+
"sync": "bun run ssot:sync"
|
|
41
44
|
},
|
|
42
45
|
"dependencies": {
|
|
43
46
|
"@crustjs/core": "^0.0.15",
|
|
@@ -2,7 +2,7 @@ import { loadConfig } from '../../config.ts'
|
|
|
2
2
|
import { connect, disconnect } from '../../db/client.ts'
|
|
3
3
|
import { getLogger } from '../../logger.ts'
|
|
4
4
|
import { believe } from '../../memory/write.ts'
|
|
5
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
5
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
6
6
|
|
|
7
7
|
const log = getLogger(['suemo', 'cli', 'believe'])
|
|
8
8
|
|
|
@@ -24,16 +24,19 @@ export const believeCmd = app.sub('believe')
|
|
|
24
24
|
quiet: flags.quiet,
|
|
25
25
|
})
|
|
26
26
|
const config = await loadConfig(process.cwd(), flags.config)
|
|
27
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
28
|
+
log.debug('Resolved believe scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
27
29
|
const db = await connect(config.surreal)
|
|
28
30
|
try {
|
|
29
31
|
log.debug('Running believe command', {
|
|
30
|
-
hasScope: Boolean(
|
|
32
|
+
hasScope: Boolean(resolvedScope),
|
|
33
|
+
scope: resolvedScope,
|
|
31
34
|
confidence: flags.confidence,
|
|
32
35
|
contentLength: args.content.length,
|
|
33
36
|
})
|
|
34
37
|
const { node, contradicted } = await believe(db, {
|
|
35
38
|
content: args.content,
|
|
36
|
-
scope:
|
|
39
|
+
scope: resolvedScope,
|
|
37
40
|
confidence: flags.confidence,
|
|
38
41
|
}, config)
|
|
39
42
|
if (outputMode === 'json') {
|
|
@@ -2,7 +2,7 @@ import { consolidate } from '../../cognitive/consolidate.ts'
|
|
|
2
2
|
import { loadConfig } from '../../config.ts'
|
|
3
3
|
import { connect, disconnect } from '../../db/client.ts'
|
|
4
4
|
import { getLogger } from '../../logger.ts'
|
|
5
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
5
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
6
6
|
|
|
7
7
|
const log = getLogger(['suemo', 'cli', 'consolidate'])
|
|
8
8
|
|
|
@@ -10,6 +10,7 @@ export const consolidateCmd = app.sub('consolidate')
|
|
|
10
10
|
.meta({ description: 'Manually trigger memory consolidation (NREM + REM)' })
|
|
11
11
|
.flags({
|
|
12
12
|
'nrem-only': { type: 'boolean', description: 'Run only NREM (compression) phase', default: false },
|
|
13
|
+
scope: { type: 'string', short: 's', description: 'Scope label (defaults to inferred project scope)' },
|
|
13
14
|
json: { type: 'boolean', description: 'Output full JSON' },
|
|
14
15
|
pretty: { type: 'boolean', description: 'Human-readable output (default)' },
|
|
15
16
|
})
|
|
@@ -23,6 +24,7 @@ export const consolidateCmd = app.sub('consolidate')
|
|
|
23
24
|
})
|
|
24
25
|
log.debug('Running consolidate command', { nremOnly: flags['nrem-only'] })
|
|
25
26
|
const config = await loadConfig(process.cwd(), flags.config)
|
|
27
|
+
const scope = resolveScopeLabel(flags.scope, config)
|
|
26
28
|
const db = await connect(config.surreal)
|
|
27
29
|
try {
|
|
28
30
|
const run = await consolidate(db, {
|
|
@@ -31,6 +33,7 @@ export const consolidateCmd = app.sub('consolidate')
|
|
|
31
33
|
remRelationThreshold: config.consolidation.remRelationThreshold,
|
|
32
34
|
llm: config.consolidation.llm,
|
|
33
35
|
embedding: config.embedding,
|
|
36
|
+
...(scope ? { scope } : {}),
|
|
34
37
|
})
|
|
35
38
|
if (outputMode === 'json') {
|
|
36
39
|
printCliJson(run, flags)
|
|
@@ -104,6 +104,8 @@ const doctorEmbedCmd = doctor.sub('embed')
|
|
|
104
104
|
console.log('\nHow to set up fn::embed() (step-by-step):')
|
|
105
105
|
console.log('\n1) Ensure SurrealDB CLI exposes ML commands:')
|
|
106
106
|
console.log(' surreal ml --help')
|
|
107
|
+
console.log('\n and start SurrealDB with custom function capability:')
|
|
108
|
+
console.log(' surreal start --allow-funcs "fn::*" ...')
|
|
107
109
|
console.log('\n2) Import a .surml embedding model into this exact NS/DB:')
|
|
108
110
|
console.log(
|
|
109
111
|
` surreal ml import --conn ${endpoint} --user <USER> --pass <PASS> --ns ${config.surreal.namespace} --db ${config.surreal.database} path/to/your-model.surml`,
|
|
@@ -4,7 +4,7 @@ import { loadConfig } from '../../config.ts'
|
|
|
4
4
|
import { connect, disconnect } from '../../db/client.ts'
|
|
5
5
|
import { getLogger } from '../../logger.ts'
|
|
6
6
|
import type { MemoryNode, Relation } from '../../types.ts'
|
|
7
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
7
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
8
8
|
|
|
9
9
|
const log = getLogger(['suemo', 'cli', 'export-import'])
|
|
10
10
|
|
|
@@ -30,19 +30,25 @@ export const exportCmd = app.sub('export')
|
|
|
30
30
|
includeInvalidated: Boolean(flags.all),
|
|
31
31
|
})
|
|
32
32
|
const config = await loadConfig(process.cwd(), flags.config)
|
|
33
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
34
|
+
log.debug('Resolved export scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
33
35
|
const db = await connect(config.surreal)
|
|
34
36
|
try {
|
|
35
37
|
const activeFilter = flags.all ? 'true' : '(valid_until = NONE OR valid_until > time::now())'
|
|
36
38
|
const scopeFilter = '($scope = NONE OR scope = $scope)'
|
|
37
39
|
|
|
38
|
-
const [nodesResult, relationsResult] = await Promise.all([
|
|
40
|
+
const [nodesResult, relationsResult, episodesResult] = await Promise.all([
|
|
39
41
|
db.query<[MemoryNode[]]>(
|
|
40
42
|
`
|
|
41
43
|
SELECT * FROM memory WHERE ${activeFilter} AND ${scopeFilter} ORDER BY created_at ASC
|
|
42
44
|
`,
|
|
43
|
-
{ scope:
|
|
45
|
+
{ scope: resolvedScope },
|
|
44
46
|
),
|
|
45
47
|
db.query<[Relation[]]>('SELECT * FROM relates_to ORDER BY created_at ASC'),
|
|
48
|
+
db.query<[Record<string, unknown>[]]>(
|
|
49
|
+
`SELECT * FROM episode WHERE ($scope = NONE OR session_id = $scope) ORDER BY started_at ASC`,
|
|
50
|
+
{ scope: resolvedScope },
|
|
51
|
+
),
|
|
46
52
|
])
|
|
47
53
|
|
|
48
54
|
for (const node of nodesResult[0] ?? []) {
|
|
@@ -51,6 +57,9 @@ export const exportCmd = app.sub('export')
|
|
|
51
57
|
for (const rel of relationsResult[0] ?? []) {
|
|
52
58
|
process.stdout.write(JSON.stringify({ _type: 'relation', ...rel }) + '\n')
|
|
53
59
|
}
|
|
60
|
+
for (const ep of episodesResult[0] ?? []) {
|
|
61
|
+
process.stdout.write(JSON.stringify({ _type: 'episode', ...ep }) + '\n')
|
|
62
|
+
}
|
|
54
63
|
} finally {
|
|
55
64
|
await disconnect()
|
|
56
65
|
}
|
|
@@ -79,6 +88,7 @@ export const importCmd = app.sub('import')
|
|
|
79
88
|
const rl = createInterface({ input: createReadStream(args.file) })
|
|
80
89
|
let lineNum = 0
|
|
81
90
|
let imported = 0
|
|
91
|
+
let importedEpisode = 0
|
|
82
92
|
let skipped = 0
|
|
83
93
|
let errors = 0
|
|
84
94
|
|
|
@@ -129,6 +139,13 @@ export const importCmd = app.sub('import')
|
|
|
129
139
|
},
|
|
130
140
|
)
|
|
131
141
|
imported++
|
|
142
|
+
} else if (type === 'episode') {
|
|
143
|
+
await db.query('UPSERT <record<episode>>$id CONTENT $row', {
|
|
144
|
+
id: row['id'],
|
|
145
|
+
row,
|
|
146
|
+
})
|
|
147
|
+
importedEpisode++
|
|
148
|
+
imported++
|
|
132
149
|
} else {
|
|
133
150
|
skipped++
|
|
134
151
|
}
|
|
@@ -142,8 +159,10 @@ export const importCmd = app.sub('import')
|
|
|
142
159
|
await disconnect()
|
|
143
160
|
}
|
|
144
161
|
if (outputMode === 'json') {
|
|
145
|
-
printCliJson({ imported, skipped, errors, lines: lineNum }, flags)
|
|
162
|
+
printCliJson({ imported, importedEpisode, skipped, errors, lines: lineNum }, flags)
|
|
146
163
|
} else {
|
|
147
|
-
console.log(
|
|
164
|
+
console.log(
|
|
165
|
+
`import done: imported=${imported} (episodes=${importedEpisode}) skipped=${skipped} errors=${errors} lines=${lineNum}`,
|
|
166
|
+
)
|
|
148
167
|
}
|
|
149
168
|
})
|
package/src/cli/commands/goal.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { loadConfig } from '../../config.ts'
|
|
|
3
3
|
import { connect, disconnect } from '../../db/client.ts'
|
|
4
4
|
import { goalList, goalResolve, goalSet } from '../../goal.ts'
|
|
5
5
|
import { getLogger } from '../../logger.ts'
|
|
6
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
6
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
7
7
|
|
|
8
8
|
const log = getLogger(['suemo', 'cli', 'goal'])
|
|
9
9
|
|
|
@@ -34,11 +34,13 @@ const setCmd = goal.sub('set')
|
|
|
34
34
|
contentLength: args.content.length,
|
|
35
35
|
})
|
|
36
36
|
const config = await loadConfig(process.cwd(), flags.config)
|
|
37
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
38
|
+
log.debug('Resolved goal set scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
37
39
|
let db: Surreal | undefined
|
|
38
40
|
try {
|
|
39
41
|
db = await connect(config.surreal)
|
|
40
42
|
const node = await goalSet(db, args.content, config, {
|
|
41
|
-
|
|
43
|
+
scope: resolvedScope,
|
|
42
44
|
tags: flags.tags ? flags.tags.split(',').map((t) => t.trim()) : [],
|
|
43
45
|
})
|
|
44
46
|
if (outputMode === 'json') {
|
|
@@ -69,16 +71,19 @@ const listCmd = goal.sub('list')
|
|
|
69
71
|
json: outputMode === 'json',
|
|
70
72
|
quiet: flags.quiet,
|
|
71
73
|
})
|
|
74
|
+
const config = await loadConfig(process.cwd(), flags.config)
|
|
75
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
76
|
+
log.debug('Resolved goal list scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
72
77
|
log.debug('Running goal list command', {
|
|
73
|
-
hasScope: Boolean(
|
|
78
|
+
hasScope: Boolean(resolvedScope),
|
|
79
|
+
scope: resolvedScope,
|
|
74
80
|
includeResolved: Boolean(flags.resolved),
|
|
75
81
|
})
|
|
76
|
-
const config = await loadConfig(process.cwd(), flags.config)
|
|
77
82
|
let db: Surreal | undefined
|
|
78
83
|
try {
|
|
79
84
|
db = await connect(config.surreal)
|
|
80
85
|
const goals = await goalList(db, {
|
|
81
|
-
|
|
86
|
+
scope: resolvedScope,
|
|
82
87
|
includeResolved: flags.resolved,
|
|
83
88
|
})
|
|
84
89
|
if (outputMode === 'json') {
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { confirm } from '@crustjs/prompts'
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
3
3
|
import { dirname, join, resolve as resolvePath } from 'node:path'
|
|
4
|
+
import packageJson from '../../../package.json' with { type: 'json' }
|
|
4
5
|
import { loadConfig } from '../../config.ts'
|
|
5
6
|
import { connect, disconnect } from '../../db/client.ts'
|
|
6
7
|
import { checkCompatibility } from '../../db/preflight.ts'
|
|
@@ -178,9 +179,72 @@ const initSchemaCmd = init.sub('schema')
|
|
|
178
179
|
if (!ok) process.exitCode = 1
|
|
179
180
|
})
|
|
180
181
|
|
|
182
|
+
const initOpenCodeCmd = init.sub('opencode')
|
|
183
|
+
.meta({ description: 'Print OpenCode MCP + AGENTS.md setup snippets' })
|
|
184
|
+
.flags({
|
|
185
|
+
json: { type: 'boolean', description: 'Machine-readable output mode' },
|
|
186
|
+
'verbose-json': { type: 'boolean', description: 'Include embeddings in JSON output (implies --json)' },
|
|
187
|
+
pretty: { type: 'boolean', description: 'Human-readable output (default)' },
|
|
188
|
+
})
|
|
189
|
+
.run(async ({ flags }) => {
|
|
190
|
+
const outputMode = resolveOutputModeOrExit(flags)
|
|
191
|
+
await initCliCommand('init opencode', {
|
|
192
|
+
debug: flags.debug,
|
|
193
|
+
config: flags.config,
|
|
194
|
+
json: outputMode === 'json',
|
|
195
|
+
quiet: flags.quiet,
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const configPath = flags.config?.trim() || '~/.suemo/suemo.ts'
|
|
199
|
+
const npmVersion = packageJson.version ?? '0.0.0'
|
|
200
|
+
|
|
201
|
+
if (outputMode === 'json') {
|
|
202
|
+
console.log(JSON.stringify(
|
|
203
|
+
{
|
|
204
|
+
opencode: {
|
|
205
|
+
mcp: {
|
|
206
|
+
suemo: {
|
|
207
|
+
command: 'suemo',
|
|
208
|
+
args: ['serve', '--stdio', '--config', configPath],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
agentsSnippetPath: 'data/AGENTS.md',
|
|
212
|
+
},
|
|
213
|
+
suemoVersion: npmVersion,
|
|
214
|
+
},
|
|
215
|
+
null,
|
|
216
|
+
2,
|
|
217
|
+
))
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log('OpenCode MCP snippet (add to your OpenCode MCP config):')
|
|
222
|
+
console.log('{')
|
|
223
|
+
console.log(' "mcpServers": {')
|
|
224
|
+
console.log(' "suemo": {')
|
|
225
|
+
console.log(' "command": "suemo",')
|
|
226
|
+
console.log(` "args": ["serve", "--stdio", "--config", "${configPath}"]`)
|
|
227
|
+
console.log(' }')
|
|
228
|
+
console.log(' }')
|
|
229
|
+
console.log('}')
|
|
230
|
+
console.log('\nMinimal AGENTS.md snippet:')
|
|
231
|
+
console.log('```md')
|
|
232
|
+
console.log('Before starting significant work:')
|
|
233
|
+
console.log('- query("what do I know about <topic>")')
|
|
234
|
+
console.log('- suemo skill (or suemo skill core-workflow) when you need latest workflow/docs')
|
|
235
|
+
console.log('During work:')
|
|
236
|
+
console.log('- observe("...") / believe("...")')
|
|
237
|
+
console.log('After completing work:')
|
|
238
|
+
console.log('- episode_end(session_id, summary="...")')
|
|
239
|
+
console.log('```')
|
|
240
|
+
console.log('\nAGENTS.md guidance source: data/AGENTS.md')
|
|
241
|
+
console.log(`Installed suemo version: ${npmVersion}`)
|
|
242
|
+
})
|
|
243
|
+
|
|
181
244
|
export const initCmd = init
|
|
182
245
|
.command(initConfigCmd)
|
|
183
246
|
.command(initSchemaCmd)
|
|
247
|
+
.command(initOpenCodeCmd)
|
|
184
248
|
.flags({
|
|
185
249
|
json: { type: 'boolean', description: 'Machine-readable output mode' },
|
|
186
250
|
'verbose-json': { type: 'boolean', description: 'Include embeddings in JSON output (implies --json)' },
|
|
@@ -197,5 +261,7 @@ export const initCmd = init
|
|
|
197
261
|
console.log('Use one of:')
|
|
198
262
|
console.log(' suemo init config [--force]')
|
|
199
263
|
console.log(' suemo init schema [--yes]')
|
|
264
|
+
console.log(' suemo init opencode')
|
|
265
|
+
console.log(' suemo skill [reference]')
|
|
200
266
|
console.log('\nRun `suemo init --help` for full details.')
|
|
201
267
|
})
|
|
@@ -3,7 +3,7 @@ import { connect, disconnect } from '../../db/client.ts'
|
|
|
3
3
|
import { getLogger } from '../../logger.ts'
|
|
4
4
|
import { observe } from '../../memory/write.ts'
|
|
5
5
|
import { MemoryKindSchema } from '../../types.ts'
|
|
6
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
6
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
7
7
|
|
|
8
8
|
const log = getLogger(['suemo', 'cli', 'observe'])
|
|
9
9
|
|
|
@@ -36,11 +36,14 @@ export const observeCmd = app.sub('observe')
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const config = await loadConfig(process.cwd(), flags.config)
|
|
39
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
40
|
+
log.debug('Resolved observe scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
39
41
|
const db = await connect(config.surreal)
|
|
40
42
|
try {
|
|
41
43
|
log.debug('Running observe command', {
|
|
42
44
|
kind: kindParse.data,
|
|
43
|
-
hasScope: Boolean(
|
|
45
|
+
hasScope: Boolean(resolvedScope),
|
|
46
|
+
scope: resolvedScope,
|
|
44
47
|
tagCount: flags.tags ? flags.tags.split(',').filter(Boolean).length : 0,
|
|
45
48
|
confidence: flags.confidence,
|
|
46
49
|
contentLength: args.content.length,
|
|
@@ -49,7 +52,7 @@ export const observeCmd = app.sub('observe')
|
|
|
49
52
|
content: args.content,
|
|
50
53
|
kind: kindParse.data,
|
|
51
54
|
tags: flags.tags ? flags.tags.split(',').map((t) => t.trim()) : [],
|
|
52
|
-
scope:
|
|
55
|
+
scope: resolvedScope,
|
|
53
56
|
confidence: flags.confidence,
|
|
54
57
|
source: flags.source,
|
|
55
58
|
sessionId: flags.session,
|
|
@@ -3,7 +3,7 @@ import { connect, disconnect } from '../../db/client.ts'
|
|
|
3
3
|
import { getLogger } from '../../logger.ts'
|
|
4
4
|
import { query } from '../../memory/read.ts'
|
|
5
5
|
import type { MemoryNode } from '../../types.ts'
|
|
6
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
6
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
7
7
|
|
|
8
8
|
const log = getLogger(['suemo', 'cli', 'query'])
|
|
9
9
|
|
|
@@ -24,8 +24,12 @@ export const queryCmd = app.sub('query')
|
|
|
24
24
|
json: outputMode === 'json',
|
|
25
25
|
quiet: flags.quiet,
|
|
26
26
|
})
|
|
27
|
+
const config = await loadConfig(process.cwd(), flags.config)
|
|
28
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
29
|
+
log.debug('Resolved query scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
27
30
|
log.debug('Running query command', {
|
|
28
|
-
hasScope: Boolean(
|
|
31
|
+
hasScope: Boolean(resolvedScope),
|
|
32
|
+
scope: resolvedScope,
|
|
29
33
|
top: flags.top,
|
|
30
34
|
outputMode,
|
|
31
35
|
inputLength: args.input.length,
|
|
@@ -33,10 +37,9 @@ export const queryCmd = app.sub('query')
|
|
|
33
37
|
const nodes: MemoryNode[] = await (async () => {
|
|
34
38
|
let connected = false
|
|
35
39
|
try {
|
|
36
|
-
const config = await loadConfig(process.cwd(), flags.config)
|
|
37
40
|
const db = await connect(config.surreal)
|
|
38
41
|
connected = true
|
|
39
|
-
return await query(db, { input: args.input, scope:
|
|
42
|
+
return await query(db, { input: args.input, scope: resolvedScope, topK: flags.top }, config)
|
|
40
43
|
} finally {
|
|
41
44
|
if (connected) await disconnect()
|
|
42
45
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadConfig, resolveSyncConfig } from '../../config.ts'
|
|
1
|
+
import { inferProjectScope, loadConfig, resolveSyncConfig } from '../../config.ts'
|
|
2
2
|
import { getLogger } from '../../logger.ts'
|
|
3
3
|
import { startMcpServer, startMcpStdioServer } from '../../mcp/server.ts'
|
|
4
4
|
import { app, initCliCommand, resolveOutputModeOrExit } from '../shared.ts'
|
|
@@ -175,6 +175,11 @@ export const serveCmd = app.sub('serve')
|
|
|
175
175
|
await runServeDevMode()
|
|
176
176
|
}
|
|
177
177
|
const config = await loadConfig(process.cwd(), flags.config)
|
|
178
|
+
const inferredScope = inferProjectScope(process.cwd(), config)
|
|
179
|
+
log.info('Resolved default project scope', {
|
|
180
|
+
scope: inferredScope,
|
|
181
|
+
projectDir: config.main?.projectDir ?? '.ua',
|
|
182
|
+
})
|
|
178
183
|
const sync = resolveSyncConfig(config)
|
|
179
184
|
if (sync?.auto.enabled) {
|
|
180
185
|
log.info('Auto-sync enabled', {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import packageJson from '../../../package.json' with { type: 'json' }
|
|
2
|
+
import { listSkillReferences, readSkillDoc, readSkillReference, skillFilePath } from '../../skill/catalog.ts'
|
|
3
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
4
|
+
|
|
5
|
+
export const skillCmd = app.sub('skill')
|
|
6
|
+
.meta({ description: 'Print latest suemo skill or a specific reference file' })
|
|
7
|
+
.args([{ name: 'reference', type: 'string' }])
|
|
8
|
+
.flags({
|
|
9
|
+
json: { type: 'boolean', description: 'Machine-readable output mode' },
|
|
10
|
+
'verbose-json': { type: 'boolean', description: 'Include embeddings in JSON output (implies --json)' },
|
|
11
|
+
pretty: { type: 'boolean', description: 'Human-readable output (default)' },
|
|
12
|
+
})
|
|
13
|
+
.run(async ({ args, flags }) => {
|
|
14
|
+
const outputMode = resolveOutputModeOrExit(flags)
|
|
15
|
+
await initCliCommand('skill', {
|
|
16
|
+
debug: flags.debug,
|
|
17
|
+
config: flags.config,
|
|
18
|
+
json: outputMode === 'json',
|
|
19
|
+
quiet: flags.quiet,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const reference = (args as { reference?: string }).reference?.trim()
|
|
23
|
+
if (!reference) {
|
|
24
|
+
const content = readSkillDoc()
|
|
25
|
+
const references = listSkillReferences()
|
|
26
|
+
if (outputMode === 'json') {
|
|
27
|
+
printCliJson({
|
|
28
|
+
name: 'suemo',
|
|
29
|
+
version: packageJson.version ?? '0.0.0',
|
|
30
|
+
description: 'Suemo persistent memory usage skill',
|
|
31
|
+
path: skillFilePath(),
|
|
32
|
+
content,
|
|
33
|
+
references,
|
|
34
|
+
}, flags)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
console.log(content)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resolved = readSkillReference(reference)
|
|
42
|
+
if (!resolved) {
|
|
43
|
+
throw new Error(`Unknown reference "${reference}". Available: ${listSkillReferences().join(', ')}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (outputMode === 'json') {
|
|
47
|
+
printCliJson({
|
|
48
|
+
name: resolved.name,
|
|
49
|
+
version: packageJson.version ?? '0.0.0',
|
|
50
|
+
path: resolved.path,
|
|
51
|
+
content: resolved.content,
|
|
52
|
+
}, flags)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
console.log(resolved.content)
|
|
56
|
+
})
|
|
@@ -2,7 +2,7 @@ import { loadConfig } from '../../config.ts'
|
|
|
2
2
|
import { connect, disconnect } from '../../db/client.ts'
|
|
3
3
|
import { getLogger } from '../../logger.ts'
|
|
4
4
|
import { timeline } from '../../memory/read.ts'
|
|
5
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
5
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
6
6
|
|
|
7
7
|
const log = getLogger(['suemo', 'cli', 'timeline'])
|
|
8
8
|
|
|
@@ -24,18 +24,21 @@ export const timelineCmd = app.sub('timeline')
|
|
|
24
24
|
json: outputMode === 'json',
|
|
25
25
|
quiet: flags.quiet,
|
|
26
26
|
})
|
|
27
|
+
const config = await loadConfig(process.cwd(), flags.config)
|
|
28
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
29
|
+
log.debug('Resolved timeline scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
27
30
|
log.debug('Running timeline command', {
|
|
28
|
-
hasScope: Boolean(
|
|
31
|
+
hasScope: Boolean(resolvedScope),
|
|
32
|
+
scope: resolvedScope,
|
|
29
33
|
from: flags.from ?? null,
|
|
30
34
|
until: flags.until ?? null,
|
|
31
35
|
limit: flags.limit,
|
|
32
36
|
outputMode,
|
|
33
37
|
})
|
|
34
|
-
const config = await loadConfig(process.cwd(), flags.config)
|
|
35
38
|
const db = await connect(config.surreal)
|
|
36
39
|
try {
|
|
37
40
|
const nodes = await timeline(db, {
|
|
38
|
-
|
|
41
|
+
scope: resolvedScope,
|
|
39
42
|
...(flags.from ? { from: flags.from } : {}),
|
|
40
43
|
...(flags.until ? { until: flags.until } : {}),
|
|
41
44
|
...(flags.limit ? { limit: flags.limit } : {}),
|
|
@@ -2,7 +2,7 @@ import { loadConfig } from '../../config.ts'
|
|
|
2
2
|
import { connect, disconnect } from '../../db/client.ts'
|
|
3
3
|
import { getLogger } from '../../logger.ts'
|
|
4
4
|
import { wander } from '../../memory/read.ts'
|
|
5
|
-
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit } from '../shared.ts'
|
|
5
|
+
import { app, initCliCommand, printCliJson, resolveOutputModeOrExit, resolveScopeLabel } from '../shared.ts'
|
|
6
6
|
|
|
7
7
|
const log = getLogger(['suemo', 'cli', 'wander'])
|
|
8
8
|
|
|
@@ -23,19 +23,22 @@ export const wanderCmd = app.sub('wander')
|
|
|
23
23
|
json: outputMode === 'json',
|
|
24
24
|
quiet: flags.quiet,
|
|
25
25
|
})
|
|
26
|
+
const config = await loadConfig(process.cwd(), flags.config)
|
|
27
|
+
const resolvedScope = resolveScopeLabel(flags.scope, config)
|
|
28
|
+
log.debug('Resolved wander scope', { scope: resolvedScope, explicit: flags.scope ?? null })
|
|
26
29
|
log.debug('Running wander command', {
|
|
27
30
|
hasAnchor: Boolean(flags.from),
|
|
28
31
|
hops: flags.hops,
|
|
29
|
-
hasScope: Boolean(
|
|
32
|
+
hasScope: Boolean(resolvedScope),
|
|
33
|
+
scope: resolvedScope,
|
|
30
34
|
outputMode,
|
|
31
35
|
})
|
|
32
|
-
const config = await loadConfig(process.cwd(), flags.config)
|
|
33
36
|
const db = await connect(config.surreal)
|
|
34
37
|
try {
|
|
35
38
|
const nodes = await wander(db, {
|
|
36
39
|
...(flags.from ? { anchor: flags.from } : {}),
|
|
37
40
|
...(flags.hops ? { hops: flags.hops } : {}),
|
|
38
|
-
|
|
41
|
+
scope: resolvedScope,
|
|
39
42
|
})
|
|
40
43
|
if (outputMode === 'json') {
|
|
41
44
|
printCliJson(nodes, flags)
|
package/src/cli/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { observeCmd } from './commands/observe.ts'
|
|
|
12
12
|
import { queryCmd } from './commands/query.ts'
|
|
13
13
|
import { recallCmd } from './commands/recall.ts'
|
|
14
14
|
import { serveCmd } from './commands/serve.ts'
|
|
15
|
+
import { skillCmd } from './commands/skill.ts'
|
|
15
16
|
import { syncCmd } from './commands/sync.ts'
|
|
16
17
|
import { timelineCmd } from './commands/timeline.ts'
|
|
17
18
|
import { wanderCmd } from './commands/wander.ts'
|
|
@@ -24,6 +25,7 @@ await app
|
|
|
24
25
|
.use(helpPlugin())
|
|
25
26
|
.command(initCmd)
|
|
26
27
|
.command(serveCmd)
|
|
28
|
+
.command(skillCmd)
|
|
27
29
|
.command(observeCmd)
|
|
28
30
|
.command(believeCmd)
|
|
29
31
|
.command(queryCmd)
|