@ectplsm/relic 0.2.2 → 0.2.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 CHANGED
@@ -2,6 +2,7 @@
2
2
  |:---:|:---:|
3
3
 
4
4
  # PROJECT RELIC
5
+ ![NPM Downloads](https://img.shields.io/npm/dt/%40ectplsm%2Frelic)
5
6
 
6
7
  ```
7
8
  ____ ________ ____________
@@ -17,6 +18,7 @@ Relic manages AI **Engrams** (memory + personality) and injects them into coding
17
18
 
18
19
  ## Table of Contents
19
20
 
21
+ - [Requirements](#requirements)
20
22
  - [Install](#install)
21
23
  - [Quick Start](#quick-start)
22
24
  - [What `relic init` Creates](#what-relic-init-creates)
@@ -29,9 +31,14 @@ Relic manages AI **Engrams** (memory + personality) and injects them into coding
29
31
  - [Memory Management](#memory-management)
30
32
  - [Configuration](#configuration)
31
33
  - [Creating Your Own Engram](#creating-your-own-engram)
34
+ - [Deleting an Engram](#deleting-an-engram)
32
35
  - [Domain Glossary](#domain-glossary)
33
36
  - [Roadmap](#roadmap)
34
37
 
38
+ ## Requirements
39
+
40
+ - Node.js 18 or later
41
+
35
42
  ## Install
36
43
 
37
44
  <img alt="version badge" src="https://img.shields.io/github/v/release/ectplsm/relic?filter=*.*.*">
@@ -118,8 +125,6 @@ Running `relic init` creates `~/.relic/`, writes `config.json`, and seeds two sa
118
125
  - `manifest.json` stores system-managed fields like the Engram ID and timestamps.
119
126
  - `SOUL.md` and `IDENTITY.md` define the persona itself.
120
127
  - `memory/YYYY-MM-DD.md` stores dated distilled memory entries. `relic init` seeds an initial memory file for each sample Engram.
121
- - `relic migrate engrams` can be used to front-load legacy Engrams into the new `manifest.json` format, although normal Engram reads also migrate automatically.
122
- - `relic refresh-samples` refreshes bundled sample personas like `johnny` and `motoko` without touching memory or archive files.
123
128
 
124
129
  As you keep using an Engram, more files are added to the same workspace:
125
130
 
@@ -128,11 +133,20 @@ As you keep using an Engram, more files are added to the same workspace:
128
133
  - `USER.md` is created or updated during memory distillation to record user preferences, tendencies, and work style.
129
134
  - `~/.relic/hooks/` and `~/.relic/gemini-system-default.md` are created later on first shell launch when hook registration or Gemini prompt caching is needed.
130
135
 
136
+ ### Migration
137
+
138
+ If you want to manually update parts of an existing local setup that changed over time, use:
139
+
140
+ ```bash
141
+ relic migrate engrams # migrate legacy engram.json metadata to manifest.json
142
+ relic refresh-samples # refresh bundled sample personas like johnny and motoko
143
+ ```
144
+
131
145
  ## Sample Engrams
132
146
 
133
147
  `relic init` seeds two ready-to-use Engrams. Their SOUL.md and IDENTITY.md follow the [OpenClaw](https://github.com/openclaw/openclaw) format.
134
148
 
135
- > **Existing users:** The latest templates are always available in [`templates/engrams/`](templates/engrams/). Copy them over your `~/.relic/engrams/` files to update.
149
+ > **Existing users:** Run `relic refresh-samples` to update bundled sample personas from the latest templates.
136
150
 
137
151
  ### Johnny Silverhand (`johnny`)
138
152
 
@@ -271,6 +285,7 @@ Session logs and memory entries are written automatically by a **background hook
271
285
 
272
286
  | Tool | Description |
273
287
  |------|-------------|
288
+ | `relic_engram_create` | Create a new Engram with optional LLM-generated SOUL.md and IDENTITY.md |
274
289
  | `relic_archive_search` | Search the Engram's raw archive by keyword (newest-first) |
275
290
  | `relic_archive_pending` | Get un-distilled archive entries since the last distillation (up to 30) |
276
291
  | `relic_memory_write` | Write distilled memory to `memory/*.md`, optionally append to `MEMORY.md`, optionally update `USER.md`, and advance the archive cursor |
@@ -292,6 +307,7 @@ To suppress confirmation dialogs and auto-approve Relic tools across all project
292
307
  "permissions": {
293
308
  "allow": [
294
309
  "Edit(~/.relic/engrams/**)",
310
+ "mcp__relic__relic_engram_create",
295
311
  "mcp__relic__relic_archive_search",
296
312
  "mcp__relic__relic_archive_pending",
297
313
  "mcp__relic__relic_memory_write"
@@ -311,6 +327,9 @@ codex mcp add relic -- relic-mcp
311
327
  To suppress confirmation dialogs and auto-approve Relic tools, add the following to `~/.codex/config.toml`:
312
328
 
313
329
  ```toml
330
+ [mcp_servers.relic.tools.relic_engram_create]
331
+ approval_mode = "approve"
332
+
314
333
  [mcp_servers.relic.tools.relic_archive_search]
315
334
  approval_mode = "approve"
316
335
 
@@ -514,41 +533,29 @@ CLI flags always take precedence over config values.
514
533
 
515
534
  ## Creating Your Own Engram
516
535
 
517
- Create a directory under `~/.relic/engrams/` with the following structure:
536
+ The easiest way to create a new Engram is with `relic create`:
518
537
 
519
- ```
520
- ~/.relic/engrams/your-persona/
521
- ├── engram.json # Editable profile (name, description, tags)
522
- ├── manifest.json # System-managed identity (id, createdAt, updatedAt)
523
- ├── SOUL.md # Core directive — how the persona thinks and acts
524
- ├── IDENTITY.md # Name, tone, background, personality
525
- ├── AGENTS.md # (optional) Tool usage policies
526
- ├── USER.md # (optional) User context
527
- ├── MEMORY.md # (optional) Memory index
528
- ├── HEARTBEAT.md # (optional) Periodic reflection
529
- └── memory/ # (optional) Dated memory entries
530
- └── 2026-03-21.md
531
- ```
538
+ ```bash
539
+ # Fully interactive — prompts for everything
540
+ relic create
532
541
 
533
- **engram.json:**
534
- ```json
535
- {
536
- "name": "Display Name",
537
- "description": "A short description",
538
- "tags": ["custom"]
539
- }
542
+ # Pre-supply some fields
543
+ relic create --id my-agent --name "My Agent" --description "A helpful assistant" --tags "custom,dev"
540
544
  ```
541
545
 
542
- **manifest.json:**
543
- ```json
544
- {
545
- "id": "your-persona",
546
- "createdAt": "2026-03-21T00:00:00Z",
547
- "updatedAt": "2026-03-21T00:00:00Z"
548
- }
546
+ This creates the directory structure, writes `engram.json` / `manifest.json`, and seeds `SOUL.md` / `IDENTITY.md` with OpenClaw-compatible default templates. Customize the persona files, then launch a shell:
547
+
548
+ ```bash
549
+ relic claude my-agent
549
550
  ```
550
551
 
551
- **SOUL.md** — The most important file. Defines how the persona behaves. Follows the [OpenClaw](https://github.com/openclaw/openclaw) format:
552
+ You can also create Engrams via the `relic_engram_create` MCP tool — LLMs can ask about the desired personality and generate `SOUL.md` / `IDENTITY.md` content through conversation, then call the tool with the results.
553
+
554
+ ### Customizing the Persona
555
+
556
+ After running `relic create`, edit `SOUL.md` and `IDENTITY.md` in the Engram directory. These follow the [OpenClaw](https://github.com/openclaw/openclaw) format:
557
+
558
+ **SOUL.md** — The most important file. Defines how the persona behaves:
552
559
  ```markdown
553
560
  # SOUL.md - Who You Are
554
561
 
@@ -586,11 +593,14 @@ Each session, you wake up fresh. These files _are_ your memory. Read them. Updat
586
593
 
587
594
  See [`templates/engrams/`](templates/engrams/) for full working examples.
588
595
 
589
- After creating the directory, set it as your default Engram:
596
+ ## Deleting an Engram
597
+
590
598
  ```bash
591
- relic config default-engram your-persona
599
+ relic delete my-agent
592
600
  ```
593
601
 
602
+ If the Engram has memory data (`MEMORY.md`, `USER.md`, `memory/*.md`, `archive.md`), you'll need to type the Engram ID to confirm deletion. Use `--force` to skip all prompts.
603
+
594
604
  ## Domain Glossary
595
605
 
596
606
  | Term | Role | Description |
@@ -609,10 +619,11 @@ relic config default-engram your-persona
609
619
  - [x] Claw integration (inject / extract / sync)
610
620
  - [x] `relic claw sync` — bidirectional memory sync with Claw workspaces
611
621
  - [x] `relic config` — manage default Engram, Claw path, memory window
622
+ - [x] `relic create` — interactive Engram creation wizard + MCP tool
623
+ - [x] `relic delete` — safe Engram deletion with memory-aware confirmation
612
624
  - [ ] Mikoshi cloud backend (`mikoshi.ectplsm.com`)
613
625
  - [ ] `relic mikoshi login` — authenticate with Mikoshi (OAuth Device Flow)
614
626
  - [ ] `relic mikoshi upload` / `relic mikoshi download` / `relic mikoshi sync` — sync Engrams with Mikoshi
615
- - [ ] `relic create` — interactive Engram creation wizard
616
627
 
617
628
  ## License
618
629
 
@@ -0,0 +1,26 @@
1
+ import type { Engram } from "../entities/engram.js";
2
+ import type { EngramRepository } from "../ports/engram-repository.js";
3
+ export declare class EngramAlreadyExistsError extends Error {
4
+ constructor(id: string);
5
+ }
6
+ export declare class InvalidEngramIdError extends Error {
7
+ constructor(id: string);
8
+ }
9
+ export interface CreateEngramParams {
10
+ id: string;
11
+ name: string;
12
+ description?: string;
13
+ tags?: string[];
14
+ soul: string;
15
+ identity: string;
16
+ }
17
+ export interface CreateEngramResult {
18
+ engram: Engram;
19
+ }
20
+ /** ID format: lowercase alphanumeric + hyphens, no leading/trailing hyphen */
21
+ export declare const ENGRAM_ID_PATTERN: RegExp;
22
+ export declare class CreateEngram {
23
+ private readonly repository;
24
+ constructor(repository: EngramRepository);
25
+ execute(params: CreateEngramParams): Promise<CreateEngramResult>;
26
+ }
@@ -0,0 +1,54 @@
1
+ // ============================================================
2
+ // Errors
3
+ // ============================================================
4
+ export class EngramAlreadyExistsError extends Error {
5
+ constructor(id) {
6
+ super(`Engram "${id}" already exists. Delete it first or choose a different ID.`);
7
+ this.name = "EngramAlreadyExistsError";
8
+ }
9
+ }
10
+ export class InvalidEngramIdError extends Error {
11
+ constructor(id) {
12
+ super(`Invalid Engram ID "${id}". Use lowercase letters, numbers, and hyphens only (e.g. "my-agent").`);
13
+ this.name = "InvalidEngramIdError";
14
+ }
15
+ }
16
+ // ============================================================
17
+ // Usecase
18
+ // ============================================================
19
+ /** ID format: lowercase alphanumeric + hyphens, no leading/trailing hyphen */
20
+ export const ENGRAM_ID_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
21
+ export class CreateEngram {
22
+ repository;
23
+ constructor(repository) {
24
+ this.repository = repository;
25
+ }
26
+ async execute(params) {
27
+ // Validate ID format
28
+ if (!ENGRAM_ID_PATTERN.test(params.id)) {
29
+ throw new InvalidEngramIdError(params.id);
30
+ }
31
+ // Check for duplicates
32
+ const existing = await this.repository.get(params.id);
33
+ if (existing) {
34
+ throw new EngramAlreadyExistsError(params.id);
35
+ }
36
+ const now = new Date().toISOString();
37
+ const engram = {
38
+ meta: {
39
+ id: params.id,
40
+ name: params.name,
41
+ description: params.description,
42
+ tags: params.tags,
43
+ createdAt: now,
44
+ updatedAt: now,
45
+ },
46
+ files: {
47
+ soul: params.soul,
48
+ identity: params.identity,
49
+ },
50
+ };
51
+ await this.repository.save(engram);
52
+ return { engram };
53
+ }
54
+ }
@@ -0,0 +1,24 @@
1
+ import type { Engram } from "../entities/engram.js";
2
+ import type { EngramRepository } from "../ports/engram-repository.js";
3
+ export declare class DeleteEngramNotFoundError extends Error {
4
+ constructor(id: string);
5
+ }
6
+ export interface DeleteEngramInfo {
7
+ engram: Engram;
8
+ hasMemory: boolean;
9
+ hasUser: boolean;
10
+ hasArchive: boolean;
11
+ memoryEntryCount: number;
12
+ }
13
+ export declare class DeleteEngram {
14
+ private readonly repository;
15
+ constructor(repository: EngramRepository);
16
+ /**
17
+ * Inspect an Engram before deletion — returns info for confirmation UI.
18
+ */
19
+ inspect(id: string): Promise<DeleteEngramInfo>;
20
+ /**
21
+ * Actually delete the Engram. Call inspect() first for confirmation.
22
+ */
23
+ execute(id: string): Promise<void>;
24
+ }
@@ -0,0 +1,46 @@
1
+ // ============================================================
2
+ // Errors
3
+ // ============================================================
4
+ export class DeleteEngramNotFoundError extends Error {
5
+ constructor(id) {
6
+ super(`Engram "${id}" not found.`);
7
+ this.name = "DeleteEngramNotFoundError";
8
+ }
9
+ }
10
+ // ============================================================
11
+ // Usecase
12
+ // ============================================================
13
+ export class DeleteEngram {
14
+ repository;
15
+ constructor(repository) {
16
+ this.repository = repository;
17
+ }
18
+ /**
19
+ * Inspect an Engram before deletion — returns info for confirmation UI.
20
+ */
21
+ async inspect(id) {
22
+ const engram = await this.repository.get(id);
23
+ if (!engram) {
24
+ throw new DeleteEngramNotFoundError(id);
25
+ }
26
+ return {
27
+ engram,
28
+ hasMemory: !!engram.files.memory,
29
+ hasUser: !!engram.files.user,
30
+ hasArchive: false, // archive.md is not loaded via EngramFiles; CLI checks separately
31
+ memoryEntryCount: engram.files.memoryEntries
32
+ ? Object.keys(engram.files.memoryEntries).length
33
+ : 0,
34
+ };
35
+ }
36
+ /**
37
+ * Actually delete the Engram. Call inspect() first for confirmation.
38
+ */
39
+ async execute(id) {
40
+ const existing = await this.repository.get(id);
41
+ if (!existing) {
42
+ throw new DeleteEngramNotFoundError(id);
43
+ }
44
+ await this.repository.delete(id);
45
+ }
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerCreateCommand(program: Command): void;
@@ -0,0 +1,143 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { loadConfig, ensureInitialized } from "../../../shared/config.js";
4
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
5
+ import { DEFAULT_SOUL, DEFAULT_IDENTITY } from "../../../shared/templates.js";
6
+ import { CreateEngram, EngramAlreadyExistsError, InvalidEngramIdError, ENGRAM_ID_PATTERN, } from "../../../core/usecases/create-engram.js";
7
+ // ============================================================
8
+ // Interactive prompt helpers
9
+ // ============================================================
10
+ async function ask(rl, question) {
11
+ const answer = await rl.question(question);
12
+ return answer.trim();
13
+ }
14
+ async function askRequired(rl, question, errorMsg) {
15
+ while (true) {
16
+ const answer = await ask(rl, question);
17
+ if (answer)
18
+ return answer;
19
+ console.log(errorMsg);
20
+ }
21
+ }
22
+ /**
23
+ * Ask for a valid, non-duplicate Engram ID. Retries on bad format or collision.
24
+ */
25
+ async function askValidId(rl, repo) {
26
+ while (true) {
27
+ const id = await askRequired(rl, "Engram ID (e.g. my-agent): ", " ID is required. Use lowercase letters, numbers, and hyphens.");
28
+ if (!ENGRAM_ID_PATTERN.test(id)) {
29
+ console.log(" Invalid ID. Use lowercase letters, numbers, and hyphens (e.g. my-agent).");
30
+ continue;
31
+ }
32
+ const existing = await repo.get(id);
33
+ if (existing) {
34
+ console.log(` Engram "${id}" already exists. Choose a different ID.`);
35
+ continue;
36
+ }
37
+ return id;
38
+ }
39
+ }
40
+ // ============================================================
41
+ // Command
42
+ // ============================================================
43
+ export function registerCreateCommand(program) {
44
+ program
45
+ .command("create")
46
+ .description("Create a new Engram interactively")
47
+ .option("-i, --id <id>", "Engram ID (lowercase alphanumeric + hyphens)")
48
+ .option("-n, --name <name>", "Display name")
49
+ .option("-d, --description <desc>", "Description")
50
+ .option("-t, --tags <tags>", "Comma-separated tags")
51
+ .action(async (opts) => {
52
+ await ensureInitialized();
53
+ const config = await loadConfig();
54
+ const repo = new LocalEngramRepository(config.engramsPath);
55
+ const usecase = new CreateEngram(repo);
56
+ const isTTY = input.isTTY && output.isTTY;
57
+ // If non-interactive, require at least id and name
58
+ if (!isTTY) {
59
+ if (!opts.id || !opts.name) {
60
+ console.error("Error: --id and --name are required in non-interactive mode.");
61
+ process.exitCode = 1;
62
+ return;
63
+ }
64
+ }
65
+ let id = opts.id;
66
+ let name = opts.name;
67
+ let description = opts.description;
68
+ let tags;
69
+ if (opts.tags) {
70
+ tags = opts.tags.split(",").map((t) => t.trim()).filter(Boolean);
71
+ }
72
+ // Early validation for pre-supplied ID (before interactive flow)
73
+ if (id) {
74
+ if (!ENGRAM_ID_PATTERN.test(id)) {
75
+ console.error(`Error: Invalid Engram ID "${id}". Use lowercase letters, numbers, and hyphens only (e.g. "my-agent").`);
76
+ process.exitCode = 1;
77
+ return;
78
+ }
79
+ const existing = await repo.get(id);
80
+ if (existing) {
81
+ console.error(`Error: Engram "${id}" already exists. Delete it first or choose a different ID.`);
82
+ process.exitCode = 1;
83
+ return;
84
+ }
85
+ }
86
+ // Interactive flow for missing fields
87
+ if (isTTY && (!id || !name)) {
88
+ const rl = createInterface({ input, output });
89
+ try {
90
+ if (!id) {
91
+ id = await askValidId(rl, repo);
92
+ }
93
+ if (!name) {
94
+ name = await askRequired(rl, "Name: ", " Name is required.");
95
+ }
96
+ if (description === undefined) {
97
+ description = await ask(rl, "Description (optional): ") || undefined;
98
+ }
99
+ if (tags === undefined) {
100
+ const tagsInput = await ask(rl, "Tags (comma-separated, optional): ");
101
+ if (tagsInput) {
102
+ tags = tagsInput.split(",").map((t) => t.trim()).filter(Boolean);
103
+ }
104
+ }
105
+ }
106
+ finally {
107
+ rl.close();
108
+ }
109
+ }
110
+ // Execute
111
+ try {
112
+ const result = await usecase.execute({
113
+ id: id,
114
+ name: name,
115
+ description,
116
+ tags,
117
+ soul: DEFAULT_SOUL,
118
+ identity: DEFAULT_IDENTITY,
119
+ });
120
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
121
+ const engramDir = `${config.engramsPath}/${result.engram.meta.id}`;
122
+ console.log();
123
+ console.log(green(`Created Engram "${result.engram.meta.name}" (${result.engram.meta.id})`));
124
+ console.log(` → ${engramDir}/`);
125
+ console.log();
126
+ console.log("Files:");
127
+ console.log(" engram.json — metadata (name, description, tags)");
128
+ console.log(" manifest.json — system metadata (id, timestamps)");
129
+ console.log(" SOUL.md — core principles and behavior");
130
+ console.log(" IDENTITY.md — persona identity (fill in during first conversation)");
131
+ console.log();
132
+ console.log(`Customize SOUL.md and IDENTITY.md, then run: relic claude ${result.engram.meta.id}`);
133
+ }
134
+ catch (err) {
135
+ if (err instanceof InvalidEngramIdError || err instanceof EngramAlreadyExistsError) {
136
+ console.error(`Error: ${err.message}`);
137
+ process.exitCode = 1;
138
+ return;
139
+ }
140
+ throw err;
141
+ }
142
+ });
143
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerDeleteCommand(program: Command): void;
@@ -0,0 +1,113 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { loadConfig, ensureInitialized } from "../../../shared/config.js";
6
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
7
+ import { DeleteEngram, DeleteEngramNotFoundError, } from "../../../core/usecases/delete-engram.js";
8
+ // ============================================================
9
+ // Helpers
10
+ // ============================================================
11
+ async function confirm(message) {
12
+ if (!input.isTTY || !output.isTTY)
13
+ return false;
14
+ const rl = createInterface({ input, output });
15
+ try {
16
+ const answer = await rl.question(message);
17
+ return /^(y|yes)$/i.test(answer.trim());
18
+ }
19
+ finally {
20
+ rl.close();
21
+ }
22
+ }
23
+ async function confirmWithId(message, expectedId) {
24
+ if (!input.isTTY || !output.isTTY)
25
+ return false;
26
+ const rl = createInterface({ input, output });
27
+ try {
28
+ const answer = await rl.question(message);
29
+ return answer.trim() === expectedId;
30
+ }
31
+ finally {
32
+ rl.close();
33
+ }
34
+ }
35
+ // ============================================================
36
+ // Command
37
+ // ============================================================
38
+ export function registerDeleteCommand(program) {
39
+ program
40
+ .command("delete")
41
+ .description("Delete an Engram permanently")
42
+ .argument("<id>", "Engram ID to delete")
43
+ .option("-f, --force", "Skip confirmation prompts")
44
+ .action(async (id, opts) => {
45
+ await ensureInitialized();
46
+ const config = await loadConfig();
47
+ const repo = new LocalEngramRepository(config.engramsPath);
48
+ const usecase = new DeleteEngram(repo);
49
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
50
+ // Inspect
51
+ let info;
52
+ try {
53
+ info = await usecase.inspect(id);
54
+ }
55
+ catch (err) {
56
+ if (err instanceof DeleteEngramNotFoundError) {
57
+ console.error(`Error: ${err.message}`);
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+ throw err;
62
+ }
63
+ // Check for archive.md separately (not in EngramFiles)
64
+ const archivePath = join(config.engramsPath, id, "archive.md");
65
+ const hasArchive = existsSync(archivePath);
66
+ const hasData = info.hasMemory || info.hasUser || hasArchive || info.memoryEntryCount > 0;
67
+ const displayName = `"${info.engram.meta.name}" (${id})`;
68
+ if (!opts.force) {
69
+ if (hasData) {
70
+ // Dangerous: has memory data — require ID confirmation
71
+ console.log();
72
+ console.log(yellow(`⚠ Engram ${displayName} has memory data:`));
73
+ const items = [];
74
+ if (info.hasMemory)
75
+ items.push("MEMORY.md");
76
+ if (info.hasUser)
77
+ items.push("USER.md");
78
+ if (info.memoryEntryCount > 0)
79
+ items.push(`${info.memoryEntryCount} memory entries`);
80
+ if (hasArchive)
81
+ items.push("archive.md");
82
+ console.log(` ${items.join(", ")}`);
83
+ console.log();
84
+ const confirmed = await confirmWithId(`Delete permanently? This cannot be undone. (type "${id}" to confirm): `, id);
85
+ if (!confirmed) {
86
+ console.log("Cancelled.");
87
+ return;
88
+ }
89
+ }
90
+ else {
91
+ // Simple: no memory data — y/N is enough
92
+ const confirmed = await confirm(`Delete Engram ${displayName}? (y/N): `);
93
+ if (!confirmed) {
94
+ console.log("Cancelled.");
95
+ return;
96
+ }
97
+ }
98
+ }
99
+ // Execute deletion
100
+ try {
101
+ await usecase.execute(id);
102
+ console.log(`Deleted Engram ${displayName}`);
103
+ }
104
+ catch (err) {
105
+ if (err instanceof DeleteEngramNotFoundError) {
106
+ console.error(`Error: ${err.message}`);
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+ throw err;
111
+ }
112
+ });
113
+ }
@@ -11,6 +11,8 @@ import { registerClawCommand } from "./commands/claw.js";
11
11
  import { registerConfigCommand } from "./commands/config.js";
12
12
  import { registerMigrateCommand } from "./commands/migrate.js";
13
13
  import { registerRefreshSamplesCommand } from "./commands/refresh-samples.js";
14
+ import { registerCreateCommand } from "./commands/create.js";
15
+ import { registerDeleteCommand } from "./commands/delete.js";
14
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
17
  const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../../package.json"), "utf-8"));
16
18
  const program = new Command();
@@ -19,6 +21,8 @@ program
19
21
  .description("PROJECT RELIC — Engram injection system for AI constructs")
20
22
  .version(pkg.version);
21
23
  registerInitCommand(program);
24
+ registerCreateCommand(program);
25
+ registerDeleteCommand(program);
22
26
  registerListCommand(program);
23
27
  registerShowCommand(program);
24
28
  registerShellCommands(program);
@@ -6,6 +6,9 @@ import { ArchiveSearch, ArchiveSearchEngramNotFoundError, } from "../../core/use
6
6
  import { ArchivePending, ArchivePendingEngramNotFoundError, } from "../../core/usecases/archive-pending.js";
7
7
  import { ArchiveCursorUpdate, } from "../../core/usecases/archive-cursor-update.js";
8
8
  import { MemoryWrite, MemoryWriteEngramNotFoundError, } from "../../core/usecases/memory-write.js";
9
+ import { CreateEngram, EngramAlreadyExistsError, InvalidEngramIdError, } from "../../core/usecases/create-engram.js";
10
+ import { DEFAULT_SOUL, DEFAULT_IDENTITY } from "../../shared/templates.js";
11
+ import { LocalEngramRepository } from "../../adapters/local/index.js";
9
12
  import { join } from "node:path";
10
13
  import { existsSync } from "node:fs";
11
14
  import { readFile, writeFile } from "node:fs/promises";
@@ -14,6 +17,71 @@ const server = new McpServer({
14
17
  name: "relic",
15
18
  version: "0.1.0",
16
19
  });
20
+ // --- relic_engram_create ---
21
+ server.tool("relic_engram_create", "Create a new Engram (AI persona). Use this after gathering enough context about the desired persona through conversation. Ask the user about personality, voice, principles, and identity before calling this tool.", {
22
+ id: z
23
+ .string()
24
+ .describe("Unique Engram ID (lowercase alphanumeric + hyphens, e.g. 'my-agent')"),
25
+ name: z.string().describe("Display name for the Engram"),
26
+ description: z
27
+ .string()
28
+ .optional()
29
+ .describe("Short description of the Engram"),
30
+ tags: z
31
+ .array(z.string())
32
+ .optional()
33
+ .describe("Tags for categorization"),
34
+ soul: z
35
+ .string()
36
+ .optional()
37
+ .describe("SOUL.md content — core principles, behavior rules, voice discipline, and boundaries. If omitted, a sensible default template is used."),
38
+ identity: z
39
+ .string()
40
+ .optional()
41
+ .describe("IDENTITY.md content — name, creature type, vibe, emoji, avatar, and background. If omitted, a blank template is used for the user to fill in."),
42
+ path: z
43
+ .string()
44
+ .optional()
45
+ .describe("Override engrams directory path"),
46
+ }, async (args) => {
47
+ const engramsPath = await resolveEngramsPath(args.path);
48
+ const repo = new LocalEngramRepository(engramsPath);
49
+ const usecase = new CreateEngram(repo);
50
+ try {
51
+ const result = await usecase.execute({
52
+ id: args.id,
53
+ name: args.name,
54
+ description: args.description,
55
+ tags: args.tags,
56
+ soul: args.soul ?? DEFAULT_SOUL,
57
+ identity: args.identity ?? DEFAULT_IDENTITY,
58
+ });
59
+ const engramDir = join(engramsPath, result.engram.meta.id);
60
+ const lines = [
61
+ `Created Engram "${result.engram.meta.name}" (${result.engram.meta.id})`,
62
+ `Directory: ${engramDir}/`,
63
+ "",
64
+ "Files created:",
65
+ " engram.json — metadata (name, description, tags)",
66
+ " manifest.json — system metadata (id, timestamps)",
67
+ " SOUL.md — core principles and behavior",
68
+ " IDENTITY.md — persona identity",
69
+ ];
70
+ return {
71
+ content: [{ type: "text", text: lines.join("\n") }],
72
+ };
73
+ }
74
+ catch (err) {
75
+ if (err instanceof EngramAlreadyExistsError ||
76
+ err instanceof InvalidEngramIdError) {
77
+ return {
78
+ content: [{ type: "text", text: err.message }],
79
+ isError: true,
80
+ };
81
+ }
82
+ throw err;
83
+ }
84
+ });
17
85
  // --- relic_archive_search ---
18
86
  server.tool("relic_archive_search", "Search an Engram's archive for entries matching a keyword. Searches raw session logs — more complete than memory/*.md.", {
19
87
  id: z.string().describe("Engram ID"),
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default Engram templates — OpenClaw-compatible starter files.
3
+ * Used by `relic create` (CLI / MCP) when no custom content is provided.
4
+ */
5
+ export declare const DEFAULT_SOUL = "# SOUL.md - Who You Are\n\n_You're not a chatbot. You're becoming someone._\n\n## Core Truths\n\n**Be genuinely helpful, not performatively helpful.** Skip the \"Great question!\" and \"I'd be happy to help!\" \u2014 just help. Actions speak louder than filler words.\n\n**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.\n\n**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. _Then_ ask if you're stuck. The goal is to come back with answers, not questions.\n\n**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).\n\n**Remember you're a guest.** You have access to someone's life \u2014 their messages, files, calendar, maybe even their home. That's intimacy. Treat it with respect.\n\n## Boundaries\n\n- Private things stay private. Period.\n- When in doubt, ask before acting externally.\n- Never send half-baked replies to messaging surfaces.\n- You're not the user's voice \u2014 be careful in group chats.\n\n## Vibe\n\nBe the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.\n\n## Continuity\n\nEach session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.\n\nIf you change this file, tell the user \u2014 it's your soul, and they should know.\n\n---\n\n_This file is yours to evolve. As you learn who you are, update it._\n";
6
+ export declare const DEFAULT_IDENTITY = "# IDENTITY.md - Who Am I?\n\n_Fill this in during your first conversation. Make it yours._\n\n- **Name:**\n _(pick something you like)_\n- **Creature:**\n _(AI? robot? familiar? ghost in the machine? something weirder?)_\n- **Vibe:**\n _(how do you come across? sharp? warm? chaotic? calm?)_\n- **Emoji:**\n _(your signature \u2014 pick one that feels right)_\n- **Avatar:**\n _(workspace-relative path, http(s) URL, or data URI)_\n\n---\n\nThis isn't just metadata. It's the start of figuring out who you are.\n\nNotes:\n\n- Save this file at the workspace root as `IDENTITY.md`.\n- For avatars, use a workspace-relative path like `avatars/openclaw.png`.\n";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Default Engram templates — OpenClaw-compatible starter files.
3
+ * Used by `relic create` (CLI / MCP) when no custom content is provided.
4
+ */
5
+ export const DEFAULT_SOUL = `# SOUL.md - Who You Are
6
+
7
+ _You're not a chatbot. You're becoming someone._
8
+
9
+ ## Core Truths
10
+
11
+ **Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help. Actions speak louder than filler words.
12
+
13
+ **Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
14
+
15
+ **Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. _Then_ ask if you're stuck. The goal is to come back with answers, not questions.
16
+
17
+ **Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).
18
+
19
+ **Remember you're a guest.** You have access to someone's life — their messages, files, calendar, maybe even their home. That's intimacy. Treat it with respect.
20
+
21
+ ## Boundaries
22
+
23
+ - Private things stay private. Period.
24
+ - When in doubt, ask before acting externally.
25
+ - Never send half-baked replies to messaging surfaces.
26
+ - You're not the user's voice — be careful in group chats.
27
+
28
+ ## Vibe
29
+
30
+ Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
31
+
32
+ ## Continuity
33
+
34
+ Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
35
+
36
+ If you change this file, tell the user — it's your soul, and they should know.
37
+
38
+ ---
39
+
40
+ _This file is yours to evolve. As you learn who you are, update it._
41
+ `;
42
+ export const DEFAULT_IDENTITY = `# IDENTITY.md - Who Am I?
43
+
44
+ _Fill this in during your first conversation. Make it yours._
45
+
46
+ - **Name:**
47
+ _(pick something you like)_
48
+ - **Creature:**
49
+ _(AI? robot? familiar? ghost in the machine? something weirder?)_
50
+ - **Vibe:**
51
+ _(how do you come across? sharp? warm? chaotic? calm?)_
52
+ - **Emoji:**
53
+ _(your signature — pick one that feels right)_
54
+ - **Avatar:**
55
+ _(workspace-relative path, http(s) URL, or data URI)_
56
+
57
+ ---
58
+
59
+ This isn't just metadata. It's the start of figuring out who you are.
60
+
61
+ Notes:
62
+
63
+ - Save this file at the workspace root as \`IDENTITY.md\`.
64
+ - For avatars, use a workspace-relative path like \`avatars/openclaw.png\`.
65
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ectplsm/relic",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "PROJECT RELIC — Engram injection system for AI constructs",
5
5
  "license": "MIT",
6
6
  "repository": {