@ectplsm/relic 0.2.1 → 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.
Files changed (36) hide show
  1. package/README.md +54 -31
  2. package/dist/adapters/local/local-engram-repository.d.ts +5 -1
  3. package/dist/adapters/local/local-engram-repository.js +53 -19
  4. package/dist/core/entities/engram.d.ts +56 -14
  5. package/dist/core/entities/engram.js +15 -6
  6. package/dist/core/entities/index.d.ts +1 -1
  7. package/dist/core/entities/index.js +1 -1
  8. package/dist/core/usecases/create-engram.d.ts +26 -0
  9. package/dist/core/usecases/create-engram.js +54 -0
  10. package/dist/core/usecases/delete-engram.d.ts +24 -0
  11. package/dist/core/usecases/delete-engram.js +46 -0
  12. package/dist/core/usecases/index.d.ts +2 -0
  13. package/dist/core/usecases/index.js +2 -0
  14. package/dist/core/usecases/migrate-engrams.d.ts +18 -0
  15. package/dist/core/usecases/migrate-engrams.js +49 -0
  16. package/dist/core/usecases/refresh-samples.d.ts +21 -0
  17. package/dist/core/usecases/refresh-samples.js +60 -0
  18. package/dist/interfaces/cli/commands/create.d.ts +2 -0
  19. package/dist/interfaces/cli/commands/create.js +143 -0
  20. package/dist/interfaces/cli/commands/delete.d.ts +2 -0
  21. package/dist/interfaces/cli/commands/delete.js +113 -0
  22. package/dist/interfaces/cli/commands/migrate.d.ts +2 -0
  23. package/dist/interfaces/cli/commands/migrate.js +31 -0
  24. package/dist/interfaces/cli/commands/refresh-samples.d.ts +2 -0
  25. package/dist/interfaces/cli/commands/refresh-samples.js +24 -0
  26. package/dist/interfaces/cli/index.js +8 -0
  27. package/dist/interfaces/mcp/index.js +68 -0
  28. package/dist/shared/config.d.ts +2 -1
  29. package/dist/shared/config.js +13 -7
  30. package/dist/shared/templates.d.ts +6 -0
  31. package/dist/shared/templates.js +65 -0
  32. package/package.json +1 -1
  33. package/templates/engrams/johnny/IDENTITY.md +10 -0
  34. package/templates/engrams/johnny/SOUL.md +41 -0
  35. package/templates/engrams/motoko/IDENTITY.md +19 -1
  36. package/templates/engrams/motoko/SOUL.md +58 -1
@@ -1,6 +1,8 @@
1
1
  export { Summon, EngramNotFoundError, type SummonResult } from "./summon.js";
2
2
  export { ListEngrams } from "./list-engrams.js";
3
3
  export { Init, type InitResult } from "./init.js";
4
+ export { MigrateEngrams, type MigrateEngramsResult, } from "./migrate-engrams.js";
5
+ export { RefreshSamples, type RefreshSamplesResult, } from "./refresh-samples.js";
4
6
  export { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, type InjectPersonaFileDiff, type InjectPersonaDiffResult, type InjectResult, } from "./inject.js";
5
7
  export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, type ExtractPersonaFileDiff, type ExtractPersonaDiffResult, type ExtractResult, } from "./extract.js";
6
8
  export { MemoryWrite, MemoryWriteEngramNotFoundError, type MemoryWriteResult, } from "./memory-write.js";
@@ -1,6 +1,8 @@
1
1
  export { Summon, EngramNotFoundError } from "./summon.js";
2
2
  export { ListEngrams } from "./list-engrams.js";
3
3
  export { Init } from "./init.js";
4
+ export { MigrateEngrams, } from "./migrate-engrams.js";
5
+ export { RefreshSamples, } from "./refresh-samples.js";
4
6
  export { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, } from "./inject.js";
5
7
  export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, } from "./extract.js";
6
8
  export { MemoryWrite, MemoryWriteEngramNotFoundError, } from "./memory-write.js";
@@ -0,0 +1,18 @@
1
+ import type { EngramRepository } from "../ports/engram-repository.js";
2
+ export interface MigrateEngramsResult {
3
+ migrated: string[];
4
+ alreadyUpToDate: string[];
5
+ skipped: Array<{
6
+ id: string;
7
+ reason: string;
8
+ }>;
9
+ }
10
+ /**
11
+ * MigrateEngrams — 既存Engramを走査し、新形式の manifest.json へ前倒し移行する
12
+ */
13
+ export declare class MigrateEngrams {
14
+ private readonly repository;
15
+ private readonly basePath;
16
+ constructor(repository: EngramRepository, basePath: string);
17
+ execute(): Promise<MigrateEngramsResult>;
18
+ }
@@ -0,0 +1,49 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ /**
5
+ * MigrateEngrams — 既存Engramを走査し、新形式の manifest.json へ前倒し移行する
6
+ */
7
+ export class MigrateEngrams {
8
+ repository;
9
+ basePath;
10
+ constructor(repository, basePath) {
11
+ this.repository = repository;
12
+ this.basePath = basePath;
13
+ }
14
+ async execute() {
15
+ if (!existsSync(this.basePath)) {
16
+ return { migrated: [], alreadyUpToDate: [], skipped: [] };
17
+ }
18
+ const entries = await readdir(this.basePath, { withFileTypes: true });
19
+ const dirs = entries.filter((entry) => entry.isDirectory());
20
+ const result = {
21
+ migrated: [],
22
+ alreadyUpToDate: [],
23
+ skipped: [],
24
+ };
25
+ for (const dir of dirs) {
26
+ const id = dir.name;
27
+ const engramDir = join(this.basePath, id);
28
+ const profilePath = join(engramDir, "engram.json");
29
+ const manifestPath = join(engramDir, "manifest.json");
30
+ if (!existsSync(profilePath)) {
31
+ result.skipped.push({ id, reason: "engram.json not found" });
32
+ continue;
33
+ }
34
+ const hadManifest = existsSync(manifestPath);
35
+ const engram = await this.repository.get(id);
36
+ if (!engram) {
37
+ result.skipped.push({ id, reason: "failed to load engram metadata" });
38
+ continue;
39
+ }
40
+ if (!hadManifest && existsSync(manifestPath)) {
41
+ result.migrated.push(id);
42
+ }
43
+ else {
44
+ result.alreadyUpToDate.push(id);
45
+ }
46
+ }
47
+ return result;
48
+ }
49
+ }
@@ -0,0 +1,21 @@
1
+ import type { EngramRepository } from "../ports/engram-repository.js";
2
+ export interface RefreshSamplesResult {
3
+ refreshed: string[];
4
+ skipped: Array<{
5
+ id: string;
6
+ reason: string;
7
+ }>;
8
+ }
9
+ /**
10
+ * RefreshSamples — サンプルEngramの人格ファイルを最新テンプレートで上書きする
11
+ *
12
+ * memory / USER / archive などの運用データは保持し、
13
+ * SOUL.md / IDENTITY.md と updatedAt のみを更新する。
14
+ */
15
+ export declare class RefreshSamples {
16
+ private readonly repository;
17
+ private readonly templatesDir;
18
+ constructor(repository: EngramRepository, templatesDir: string);
19
+ execute(targetIds?: string[]): Promise<RefreshSamplesResult>;
20
+ private listTemplateIds;
21
+ }
@@ -0,0 +1,60 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, readdir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ /**
5
+ * RefreshSamples — サンプルEngramの人格ファイルを最新テンプレートで上書きする
6
+ *
7
+ * memory / USER / archive などの運用データは保持し、
8
+ * SOUL.md / IDENTITY.md と updatedAt のみを更新する。
9
+ */
10
+ export class RefreshSamples {
11
+ repository;
12
+ templatesDir;
13
+ constructor(repository, templatesDir) {
14
+ this.repository = repository;
15
+ this.templatesDir = templatesDir;
16
+ }
17
+ async execute(targetIds) {
18
+ const ids = targetIds && targetIds.length > 0
19
+ ? targetIds
20
+ : await this.listTemplateIds();
21
+ const result = {
22
+ refreshed: [],
23
+ skipped: [],
24
+ };
25
+ for (const id of ids) {
26
+ const templateDir = join(this.templatesDir, id);
27
+ if (!existsSync(templateDir)) {
28
+ result.skipped.push({ id, reason: "sample template not found" });
29
+ continue;
30
+ }
31
+ const engram = await this.repository.get(id);
32
+ if (!engram) {
33
+ result.skipped.push({ id, reason: "local Engram not found" });
34
+ continue;
35
+ }
36
+ const soulPath = join(templateDir, "SOUL.md");
37
+ const identityPath = join(templateDir, "IDENTITY.md");
38
+ if (!existsSync(soulPath) || !existsSync(identityPath)) {
39
+ result.skipped.push({ id, reason: "template persona files are incomplete" });
40
+ continue;
41
+ }
42
+ engram.files.soul = await readFile(soulPath, "utf-8");
43
+ engram.files.identity = await readFile(identityPath, "utf-8");
44
+ engram.meta.updatedAt = new Date().toISOString();
45
+ await this.repository.save(engram);
46
+ result.refreshed.push(id);
47
+ }
48
+ return result;
49
+ }
50
+ async listTemplateIds() {
51
+ if (!existsSync(this.templatesDir)) {
52
+ return [];
53
+ }
54
+ const entries = await readdir(this.templatesDir, { withFileTypes: true });
55
+ return entries
56
+ .filter((entry) => entry.isDirectory())
57
+ .map((entry) => entry.name)
58
+ .sort();
59
+ }
60
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerMigrateCommand(program: Command): void;
@@ -0,0 +1,31 @@
1
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
+ import { MigrateEngrams } from "../../../core/usecases/index.js";
3
+ import { resolveEngramsPath } from "../../../shared/config.js";
4
+ export function registerMigrateCommand(program) {
5
+ const migrate = program
6
+ .command("migrate")
7
+ .description("Run one-off migrations for local Relic data");
8
+ migrate
9
+ .command("engrams")
10
+ .description("Migrate Engram metadata from legacy engram.json to manifest.json")
11
+ .option("-p, --path <dir>", "Override engrams directory path")
12
+ .action(async (opts) => {
13
+ const engramsPath = await resolveEngramsPath(opts.path);
14
+ const repo = new LocalEngramRepository(engramsPath);
15
+ const migrateEngrams = new MigrateEngrams(repo, engramsPath);
16
+ const result = await migrateEngrams.execute();
17
+ console.log(`Scanned Engrams: ${result.migrated.length + result.alreadyUpToDate.length + result.skipped.length}`);
18
+ console.log(` Migrated: ${result.migrated.length}`);
19
+ console.log(` Already up to date: ${result.alreadyUpToDate.length}`);
20
+ console.log(` Skipped: ${result.skipped.length}`);
21
+ if (result.migrated.length > 0) {
22
+ console.log(` Migrated IDs: ${result.migrated.join(", ")}`);
23
+ }
24
+ if (result.alreadyUpToDate.length > 0) {
25
+ console.log(` Up-to-date IDs: ${result.alreadyUpToDate.join(", ")}`);
26
+ }
27
+ for (const skipped of result.skipped) {
28
+ console.log(` Skipped ${skipped.id}: ${skipped.reason}`);
29
+ }
30
+ });
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerRefreshSamplesCommand(program: Command): void;
@@ -0,0 +1,24 @@
1
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
+ import { RefreshSamples } from "../../../core/usecases/index.js";
3
+ import { resolveEngramsPath, TEMPLATES_DIR } from "../../../shared/config.js";
4
+ export function registerRefreshSamplesCommand(program) {
5
+ program
6
+ .command("refresh-samples")
7
+ .description("Refresh sample Engrams from the latest bundled templates")
8
+ .option("-e, --engram <id>", "Refresh one sample Engram only")
9
+ .option("-p, --path <dir>", "Override engrams directory path")
10
+ .action(async (opts) => {
11
+ const engramsPath = await resolveEngramsPath(opts.path);
12
+ const repo = new LocalEngramRepository(engramsPath);
13
+ const refreshSamples = new RefreshSamples(repo, TEMPLATES_DIR);
14
+ const result = await refreshSamples.execute(opts.engram ? [opts.engram] : undefined);
15
+ console.log(`Refreshed: ${result.refreshed.length}`);
16
+ if (result.refreshed.length > 0) {
17
+ console.log(` IDs: ${result.refreshed.join(", ")}`);
18
+ }
19
+ console.log(`Skipped: ${result.skipped.length}`);
20
+ for (const skipped of result.skipped) {
21
+ console.log(` ${skipped.id}: ${skipped.reason}`);
22
+ }
23
+ });
24
+ }
@@ -9,6 +9,10 @@ import { registerShowCommand } from "./commands/show.js";
9
9
  import { registerShellCommands } from "./commands/shell.js";
10
10
  import { registerClawCommand } from "./commands/claw.js";
11
11
  import { registerConfigCommand } from "./commands/config.js";
12
+ import { registerMigrateCommand } from "./commands/migrate.js";
13
+ import { registerRefreshSamplesCommand } from "./commands/refresh-samples.js";
14
+ import { registerCreateCommand } from "./commands/create.js";
15
+ import { registerDeleteCommand } from "./commands/delete.js";
12
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
17
  const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../../package.json"), "utf-8"));
14
18
  const program = new Command();
@@ -17,9 +21,13 @@ program
17
21
  .description("PROJECT RELIC — Engram injection system for AI constructs")
18
22
  .version(pkg.version);
19
23
  registerInitCommand(program);
24
+ registerCreateCommand(program);
25
+ registerDeleteCommand(program);
20
26
  registerListCommand(program);
21
27
  registerShowCommand(program);
22
28
  registerShellCommands(program);
23
29
  registerClawCommand(program);
24
30
  registerConfigCommand(program);
31
+ registerMigrateCommand(program);
32
+ registerRefreshSamplesCommand(program);
25
33
  program.parse();
@@ -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"),
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ declare const TEMPLATES_DIR: string;
2
3
  declare const RELIC_DIR: string;
3
4
  declare const CONFIG_PATH: string;
4
5
  export declare const RelicConfigSchema: z.ZodObject<{
@@ -65,4 +66,4 @@ export declare function resolveClawPath(cliOverride?: string): Promise<string |
65
66
  * 優先順位: config.memoryWindowSize > 2 (デフォルト)
66
67
  */
67
68
  export declare function resolveMemoryWindowSize(): Promise<number>;
68
- export { CONFIG_PATH, RELIC_DIR };
69
+ export { CONFIG_PATH, RELIC_DIR, TEMPLATES_DIR };