@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.
- package/README.md +54 -31
- package/dist/adapters/local/local-engram-repository.d.ts +5 -1
- package/dist/adapters/local/local-engram-repository.js +53 -19
- package/dist/core/entities/engram.d.ts +56 -14
- package/dist/core/entities/engram.js +15 -6
- package/dist/core/entities/index.d.ts +1 -1
- package/dist/core/entities/index.js +1 -1
- package/dist/core/usecases/create-engram.d.ts +26 -0
- package/dist/core/usecases/create-engram.js +54 -0
- package/dist/core/usecases/delete-engram.d.ts +24 -0
- package/dist/core/usecases/delete-engram.js +46 -0
- package/dist/core/usecases/index.d.ts +2 -0
- package/dist/core/usecases/index.js +2 -0
- package/dist/core/usecases/migrate-engrams.d.ts +18 -0
- package/dist/core/usecases/migrate-engrams.js +49 -0
- package/dist/core/usecases/refresh-samples.d.ts +21 -0
- package/dist/core/usecases/refresh-samples.js +60 -0
- package/dist/interfaces/cli/commands/create.d.ts +2 -0
- package/dist/interfaces/cli/commands/create.js +143 -0
- package/dist/interfaces/cli/commands/delete.d.ts +2 -0
- package/dist/interfaces/cli/commands/delete.js +113 -0
- package/dist/interfaces/cli/commands/migrate.d.ts +2 -0
- package/dist/interfaces/cli/commands/migrate.js +31 -0
- package/dist/interfaces/cli/commands/refresh-samples.d.ts +2 -0
- package/dist/interfaces/cli/commands/refresh-samples.js +24 -0
- package/dist/interfaces/cli/index.js +8 -0
- package/dist/interfaces/mcp/index.js +68 -0
- package/dist/shared/config.d.ts +2 -1
- package/dist/shared/config.js +13 -7
- package/dist/shared/templates.d.ts +6 -0
- package/dist/shared/templates.js +65 -0
- package/package.json +1 -1
- package/templates/engrams/johnny/IDENTITY.md +10 -0
- package/templates/engrams/johnny/SOUL.md +41 -0
- package/templates/engrams/motoko/IDENTITY.md +19 -1
- 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,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,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,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,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"),
|
package/dist/shared/config.d.ts
CHANGED
|
@@ -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 };
|