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 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
- surrealkv:///path/to/data
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.5",
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 tsc --noEmit"
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(flags.scope),
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: flags.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: flags.scope ?? null },
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(`import done: imported=${imported} skipped=${skipped} errors=${errors} lines=${lineNum}`)
164
+ console.log(
165
+ `import done: imported=${imported} (episodes=${importedEpisode}) skipped=${skipped} errors=${errors} lines=${lineNum}`,
166
+ )
148
167
  }
149
168
  })
@@ -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
- ...(flags.scope ? { scope: flags.scope } : {}),
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(flags.scope),
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
- ...(flags.scope ? { scope: flags.scope } : {}),
86
+ scope: resolvedScope,
82
87
  includeResolved: flags.resolved,
83
88
  })
84
89
  if (outputMode === 'json') {
@@ -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(flags.scope),
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: flags.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(flags.scope),
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: flags.scope, topK: flags.top }, config)
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(flags.scope),
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
- ...(flags.scope ? { scope: flags.scope } : {}),
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(flags.scope),
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
- ...(flags.scope ? { scope: flags.scope } : {}),
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)