suemo 0.0.5 → 0.0.7
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 +125 -9
- package/package.json +8 -5
- 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 +737 -3
- 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
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
> [!CAUTION]
|
|
6
6
|
> **Bun-only.** This project will not run on Node.js and there are no plans to support it.
|
|
7
7
|
>
|
|
8
|
-
> suemo is experimental software built for the author's personal agent infrastructure. APIs may change without notice.
|
|
8
|
+
> suemo is experimental software built for the author's personal agent infrastructure. APIs may change without notice.
|
|
9
|
+
>
|
|
10
|
+
> Most of the code is AI-generated and only tested briefly by a single person. Use at your own risk.
|
|
9
11
|
|
|
10
12
|
suemo gives AI agents a memory that survives across sessions, models, and runtimes. Write observations from a Telegram bot, query them from OpenCode, consolidate overnight — all agents share one source of truth in SurrealDB.
|
|
11
13
|
|
|
@@ -17,7 +19,7 @@ suemo gives AI agents a memory that survives across sessions, models, and runtim
|
|
|
17
19
|
- **Bi-temporal nodes** — every fact has `valid_from` / `valid_until`; nothing is hard-deleted
|
|
18
20
|
- **Contradiction detection** — calling `believe()` with a conflicting belief automatically invalidates the old one and links them with a `contradicts` edge
|
|
19
21
|
- **Two-phase consolidation** — NREM clusters and compresses redundant observations via LLM; REM integrates new summaries into the broader graph with auto-scored relations
|
|
20
|
-
- **SurrealKV time-travel** — `VERSION d'...'` queries let you inspect any node's state at any past datetime (requires `SURREAL_DATASTORE_RETENTION=90d`)
|
|
22
|
+
- **SurrealKV time-travel** — `VERSION d'...'` queries let you inspect any node's state at any past datetime (requires `SURREAL_DATASTORE_RETENTION=90d` / `0` for infinite)
|
|
21
23
|
- **Namespace isolation** — one SurrealDB instance, multiple agents, zero collision (`namespace` = agent group, `database` = agent identity)
|
|
22
24
|
- **MCP + CLI** — both interfaces are thin shells over the same domain functions; no business logic duplication
|
|
23
25
|
- **Lightweight** — no bundled vector store, no embedded database process, no OpenAI SDK; embedding via `fn::embed()` runs server-side in SurrealDB
|
|
@@ -34,12 +36,25 @@ suemo gives AI agents a memory that survives across sessions, models, and runtim
|
|
|
34
36
|
SurrealDB must be started with SurrealKV and a retention window:
|
|
35
37
|
|
|
36
38
|
```sh
|
|
37
|
-
SURREAL_DATASTORE_RETENTION=90d surreal start \
|
|
39
|
+
SURREAL_DATASTORE_VERSIONED=true SURREAL_DATASTORE_RETENTION=90d surreal start \
|
|
38
40
|
--bind 0.0.0.0:8000 \
|
|
39
|
-
|
|
41
|
+
--allow-funcs "*" \
|
|
42
|
+
-- surrealkv:///path/to/data
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
For suemo, the critical capability is allowing custom functions:
|
|
46
|
+
|
|
47
|
+
- `fn::*`
|
|
48
|
+
- `time::*`
|
|
49
|
+
- `vector::*`
|
|
50
|
+
- `search::*` (for `search::score`)
|
|
51
|
+
- `math::*` (for `math::mean`, `math::min`)
|
|
52
|
+
- `rand::*` (used in `wander` query)
|
|
53
|
+
|
|
54
|
+
If you run with strict capability mode, use an allowlist like:
|
|
55
|
+
|
|
56
|
+
- for **openai-compatible/stub**: `fn,time,vector,search,math,rand`
|
|
57
|
+
- for **surreal** provider (with `ml::...` in `fn::embed` wrapper): add `ml`
|
|
43
58
|
|
|
44
59
|
---
|
|
45
60
|
|
|
@@ -64,6 +79,69 @@ bun run src/cli/index.ts init config
|
|
|
64
79
|
|
|
65
80
|
This writes `~/.suemo/suemo.ts`. Edit it with your SurrealDB URL, credentials, and LLM endpoint.
|
|
66
81
|
|
|
82
|
+
Set project scope discovery in config (recommended):
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
export default defineConfig({
|
|
86
|
+
main: {
|
|
87
|
+
projectDir: '.ua',
|
|
88
|
+
},
|
|
89
|
+
// ...
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With this, suemo resolves default scope from nearest `.ua/suemo.json` (auto-created if missing).
|
|
94
|
+
|
|
95
|
+
### Optional: one-command local service setup (Arch Linux)
|
|
96
|
+
|
|
97
|
+
**NOTE:** These init commands are Arch Linux–specific (author's primary development environment). They rely on `pacman` package checks and systemd paths matching Arch Linux layouts.
|
|
98
|
+
|
|
99
|
+
For local host deployments, suemo can install systemd service assets directly.
|
|
100
|
+
|
|
101
|
+
These commands **must be run as root**:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
sudo suemo init surreal 2gb --dry-run
|
|
105
|
+
sudo suemo init surreal 6gb --dry-run
|
|
106
|
+
sudo suemo init fastembed --dry-run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If `suemo` is not on root PATH, use `sudo bunx suemo ...`.
|
|
110
|
+
|
|
111
|
+
Drop `--dry-run` to apply changes.
|
|
112
|
+
|
|
113
|
+
`suemo init surreal` also creates `/opt/suemo/surrealdb/local.env` with commented overrides and does not overwrite it if it already exists. If defined in `local.env`, these values override `common.env`:
|
|
114
|
+
|
|
115
|
+
- `SURREAL_USER`
|
|
116
|
+
- `SURREAL_PASS`
|
|
117
|
+
- `SURREAL_BIND`
|
|
118
|
+
- `SURREAL_PATH`
|
|
119
|
+
|
|
120
|
+
Add `--force` to regenerate managed env files (`common.env` and profile env).
|
|
121
|
+
|
|
122
|
+
What these commands do:
|
|
123
|
+
|
|
124
|
+
- `suemo init surreal <2gb|6gb>`
|
|
125
|
+
- checks `surrealdb` package via `pacman -Q`
|
|
126
|
+
- creates `/opt/suemo/surrealdb/common.env` + profile env (`2gb.env` or `6gb.env`)
|
|
127
|
+
- creates `/opt/suemo/surrealdb/local.env` once (user override file; not overwritten)
|
|
128
|
+
- writes `/etc/systemd/system/suemo-surrealdb@.service`
|
|
129
|
+
- enables + starts `suemo-surrealdb@<profile>.service`
|
|
130
|
+
- includes `VERSIONED` + retention config and strict capability allowlist:
|
|
131
|
+
- `SURREAL_DATASTORE_VERSIONED=true`
|
|
132
|
+
- `SURREAL_DATASTORE_RETENTION=90d`
|
|
133
|
+
- `SURREAL_CAPS_DENY_ALL=true`
|
|
134
|
+
- `SURREAL_CAPS_ALLOW_FUNC=fn,time,vector,search,math,rand,ml`
|
|
135
|
+
|
|
136
|
+
- `suemo init fastembed`
|
|
137
|
+
- checks `python-fastembed`, `python-fastapi`, `python-uvicorn` via `pacman -Q`
|
|
138
|
+
- installs `data/fastembed-server.py` to `/opt/suemo/fastembed-server.py`
|
|
139
|
+
- creates `/opt/fastembed/local.env` once (user override file; not overwritten)
|
|
140
|
+
- writes `/etc/systemd/system/suemo-fastembed.service`
|
|
141
|
+
- enables + starts `suemo-fastembed.service`
|
|
142
|
+
|
|
143
|
+
`--dry-run` prints all generated file content and planned commands to stdout without writing anything.
|
|
144
|
+
|
|
67
145
|
**2. Apply schema**
|
|
68
146
|
|
|
69
147
|
Apply schema after you set/edit namespace/database:
|
|
@@ -106,6 +184,30 @@ suemo serve --dev
|
|
|
106
184
|
|
|
107
185
|
---
|
|
108
186
|
|
|
187
|
+
## OpenCode setup (only integration target)
|
|
188
|
+
|
|
189
|
+
Print copy-paste setup snippets:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
suemo init opencode
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
This prints:
|
|
196
|
+
|
|
197
|
+
- MCP config snippet (`command: suemo`, `args: ["serve", "--stdio", ...]`)
|
|
198
|
+
- minimal AGENTS.md guidance snippet
|
|
199
|
+
|
|
200
|
+
To fetch the latest skill docs directly from your local checkout:
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
suemo skill
|
|
204
|
+
suemo skill core-workflow
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
No files are auto-written by this command.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
109
211
|
## CLI Reference
|
|
110
212
|
|
|
111
213
|
```
|
|
@@ -115,10 +217,13 @@ Global flags (inherited by all commands):
|
|
|
115
217
|
-c, --config <path> Path to config file
|
|
116
218
|
-d, --debug Verbose debug logging
|
|
117
219
|
|
|
118
|
-
Commands:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
220
|
+
Commands:
|
|
221
|
+
init Show init subcommands and usage guidance
|
|
222
|
+
init config Create/update ~/.suemo/suemo.ts
|
|
223
|
+
init schema Apply DB schema from current config (with confirm)
|
|
224
|
+
init surreal Install systemd SurrealDB profile (2gb/6gb) with VERSIONED + allowlist config
|
|
225
|
+
init fastembed Install systemd fastembed service
|
|
226
|
+
skill Print suemo skill docs (or specific reference)
|
|
122
227
|
serve Start the MCP server (HTTP or stdio)
|
|
123
228
|
observe <content> Store an observation
|
|
124
229
|
believe <content> Store a belief (triggers contradiction detection)
|
|
@@ -277,12 +382,16 @@ This prints your active target (`url`, `namespace`, `database`) and step-by-step
|
|
|
277
382
|
| `recall` | Fetch a node and its 1-hop neighbourhood; ticks FSRS |
|
|
278
383
|
| `wander` | Spreading-activation walk through the graph |
|
|
279
384
|
| `timeline` | Chronological memory slice with optional date range |
|
|
385
|
+
| `context` | Recover recent session context for current/default scope |
|
|
280
386
|
| `episode_start` | Begin a bounded session window |
|
|
281
387
|
| `episode_end` | Close a session, optionally with a summary |
|
|
282
388
|
| `goal_set` | Create a goal node |
|
|
283
389
|
| `goal_resolve` | Mark a goal achieved |
|
|
284
390
|
| `goal_list` | List active (or all) goals |
|
|
285
391
|
| `upsert_by_key` | Upsert a memory node by stable topic key |
|
|
392
|
+
| `update` | Update an existing memory node by ID (re-embeds if content changed) |
|
|
393
|
+
| `suggest_topic_key` | Suggest deterministic canonical topic key from free text |
|
|
394
|
+
| `skill` | Return current suemo skill docs or one named reference |
|
|
286
395
|
| `capture_prompt` | Capture raw prompt and link derived observations |
|
|
287
396
|
| `session_context_get` | Fetch open episode summary/context by session ID |
|
|
288
397
|
| `session_context_set` | Update open episode summary/context by session ID |
|
|
@@ -293,6 +402,13 @@ This prints your active target (`url`, `namespace`, `database`) and step-by-step
|
|
|
293
402
|
|
|
294
403
|
Agents never supply temporal fields (`valid_from`, `valid_until`). These are system-managed.
|
|
295
404
|
|
|
405
|
+
### Scope and longevity notes
|
|
406
|
+
|
|
407
|
+
- Default inferred project scope now uses nearest `<projectDir>/suemo.json` with `main.projectDir` defaulting to `.ua`.
|
|
408
|
+
- Semantic operations are scope-aware (dedup, contradiction detection, upsert-by-key, consolidation).
|
|
409
|
+
- Retention probe is fail-closed on unexpected probe errors.
|
|
410
|
+
- Episode records are included in sync and export/import flows.
|
|
411
|
+
|
|
296
412
|
---
|
|
297
413
|
|
|
298
414
|
## Architecture
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suemo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Persistent semantic memory for AI agents — backed by SurrealDB.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Umar Alfarouk",
|
|
@@ -37,13 +37,16 @@
|
|
|
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",
|
|
44
|
-
"@crustjs/plugins": "^0.0.
|
|
45
|
-
"@crustjs/prompts": "^0.0.
|
|
46
|
-
"@crustjs/style": "^0.0.
|
|
47
|
+
"@crustjs/plugins": "^0.0.20",
|
|
48
|
+
"@crustjs/prompts": "^0.0.10",
|
|
49
|
+
"@crustjs/style": "^0.0.6",
|
|
47
50
|
"@logtape/file": "^2.0.4",
|
|
48
51
|
"@logtape/logtape": "^2.0.4",
|
|
49
52
|
"@surrealdb/node": "^3.0.3",
|
|
@@ -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') {
|