@ectplsm/relic 0.5.2 → 0.5.4
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 +28 -5
- package/dist/adapters/shells/codex-hook.d.ts +3 -3
- package/dist/adapters/shells/codex-hook.js +12 -11
- package/dist/adapters/shells/codex-paths.d.ts +11 -0
- package/dist/adapters/shells/codex-paths.js +23 -0
- package/dist/adapters/shells/codex-shell.js +7 -5
- package/dist/interfaces/cli/index.js +9 -0
- package/dist/interfaces/mcp/index.js +0 -0
- package/package.json +7 -4
- package/dist/adapters/shells/copilot-shell.d.ts +0 -14
- package/dist/adapters/shells/copilot-shell.js +0 -43
- package/dist/adapters/shells/trust-registrar.d.ts +0 -19
- package/dist/adapters/shells/trust-registrar.js +0 -141
- package/dist/core/usecases/inbox-search.d.ts +0 -20
- package/dist/core/usecases/inbox-search.js +0 -46
- package/dist/core/usecases/inbox-write.d.ts +0 -27
- package/dist/core/usecases/inbox-write.js +0 -72
- package/dist/core/usecases/memory-search.d.ts +0 -23
- package/dist/core/usecases/memory-search.js +0 -59
- package/dist/core/usecases/mikoshi-clone.d.ts +0 -29
- package/dist/core/usecases/mikoshi-clone.js +0 -94
- package/dist/interfaces/cli/commands/extract.d.ts +0 -2
- package/dist/interfaces/cli/commands/extract.js +0 -37
- package/dist/interfaces/cli/commands/inject.d.ts +0 -2
- package/dist/interfaces/cli/commands/inject.js +0 -34
- package/dist/interfaces/cli/commands/sync.d.ts +0 -2
- package/dist/interfaces/cli/commands/sync.js +0 -47
- package/dist/shared/memory-inbox.d.ts +0 -78
- package/dist/shared/memory-inbox.js +0 -168
- package/dist/shared/session-recorder.d.ts +0 -47
- package/dist/shared/session-recorder.js +0 -112
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|:---:|:---:|
|
|
3
3
|
|
|
4
4
|
# Relic: AI Persona Injection System
|
|
5
|
-
](https://www.npmjs.com/package/@ectplsm/relic)
|
|
6
|
+
[](https://www.npmjs.com/package/@ectplsm/relic)
|
|
7
|
+
[](https://github.com/ectplsm/relic/blob/main/LICENCE.md)
|
|
6
8
|
|
|
7
9
|
<img src="assets/relic-hero.svg" alt="PROJECT RELIC" width="720">
|
|
8
10
|
|
|
@@ -25,12 +27,10 @@ Relic manages AI **Engrams** (memory + personality) and injects them across codi
|
|
|
25
27
|
|
|
26
28
|
## Requirements
|
|
27
29
|
|
|
28
|
-
- Node.js 18 or later
|
|
30
|
+
- Node.js 18.14.1 or later
|
|
29
31
|
|
|
30
32
|
## Install
|
|
31
33
|
|
|
32
|
-
<img alt="version badge" src="https://img.shields.io/github/v/release/ectplsm/relic?filter=*.*.*">
|
|
33
|
-
|
|
34
34
|
```bash
|
|
35
35
|
npm install -g @ectplsm/relic
|
|
36
36
|
```
|
|
@@ -208,6 +208,29 @@ Upcoming milestones that shape where Relic is heading:
|
|
|
208
208
|
- **Structured memory decomposition** — break `MEMORY.md` down into episodic, semantic, and procedural memory so the Construct can recall the right kind of knowledge at the right time.
|
|
209
209
|
- **Memory Wiki on llm-wiki** — build a navigable memory wiki on top of [llm-wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f), turning long-term memory into a browsable, linkable knowledge base instead of flat Markdown.
|
|
210
210
|
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
We welcome contributions! To set up the project locally for development:
|
|
214
|
+
|
|
215
|
+
1. Clone the repository:
|
|
216
|
+
```bash
|
|
217
|
+
git clone https://github.com/ectplsm/relic.git
|
|
218
|
+
cd relic
|
|
219
|
+
```
|
|
220
|
+
2. Install dependencies:
|
|
221
|
+
```bash
|
|
222
|
+
npm install
|
|
223
|
+
```
|
|
224
|
+
3. Available scripts:
|
|
225
|
+
- `npm run build`: Compile TypeScript into the `dist/` directory.
|
|
226
|
+
- `npm run typecheck`: Run the TypeScript compiler without emitting files.
|
|
227
|
+
- `npm run dev:cli`: Run the CLI in development mode (using `tsx`).
|
|
228
|
+
- `npm run dev:mcp`: Run the MCP server in development mode (using `tsx`).
|
|
229
|
+
- `npm run release:prepare -- <version>`: Generate release notes, validate the package, create and push the version commit and annotated tag, then create a draft GitHub Release. Codex generates the notes by default; use `--no-ai-notes`, `--notes-file <path>`, or `--dry-run` when needed.
|
|
230
|
+
- After reviewing the draft, run `gh workflow run release.yml --ref main -f tag=v<version>` to publish the package and GitHub Release. Before the first release, create a protected GitHub Environment named `npm`: restrict deployment branches to `main`, limit required reviewers to trusted release maintainers, and disable administrator bypass. Then configure npm Trusted Publishing for `ectplsm/relic`, workflow `release.yml`, environment `npm`, with direct publishing enabled. No long-lived npm token is used.
|
|
231
|
+
|
|
232
|
+
Before contributing, please read [CLAUDE.md](./CLAUDE.md) for strict coding conventions, parent documents, and PR rules.
|
|
233
|
+
|
|
211
234
|
## License
|
|
212
235
|
|
|
213
|
-
[
|
|
236
|
+
This project is licensed under the MIT License - see the [LICENCE.md](./LICENCE.md) file for details.
|
|
@@ -5,11 +5,11 @@ export declare const CODEX_HOOK_SCRIPT_PATH: string;
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function writeCodexHookScript(): void;
|
|
7
7
|
/**
|
|
8
|
-
* Codex CLI の Stop フックを
|
|
8
|
+
* Codex CLI の Stop フックを hooks.json に登録する。
|
|
9
9
|
* 既にセットアップ済みの場合はスキップ。
|
|
10
10
|
*/
|
|
11
|
-
export declare function setupCodexHook(): void;
|
|
11
|
+
export declare function setupCodexHook(cwd?: string): void;
|
|
12
12
|
/**
|
|
13
13
|
* Stop フックがセットアップ済みか確認する。
|
|
14
14
|
*/
|
|
15
|
-
export declare function isCodexHookSetup(): boolean;
|
|
15
|
+
export declare function isCodexHookSetup(cwd?: string): boolean;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
+
import { resolveCodexHome, resolveCodexHooksPath } from "./codex-paths.js";
|
|
4
5
|
const RELIC_DIR = join(homedir(), ".relic");
|
|
5
6
|
const HOOKS_DIR = join(RELIC_DIR, "hooks");
|
|
6
7
|
export const CODEX_HOOK_SCRIPT_PATH = join(HOOKS_DIR, "codex-stop.js");
|
|
7
|
-
const CODEX_HOOKS_PATH = join(homedir(), ".codex", "hooks.json");
|
|
8
8
|
const RELIC_HOOK_COMMAND = `node ${join(HOOKS_DIR, "codex-stop.js")}`;
|
|
9
9
|
/**
|
|
10
10
|
* Stop hook スクリプトの内容。
|
|
@@ -88,17 +88,17 @@ export function writeCodexHookScript() {
|
|
|
88
88
|
writeFileSync(CODEX_HOOK_SCRIPT_PATH, HOOK_SCRIPT, { encoding: "utf-8", mode: 0o755 });
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Codex CLI の Stop フックを
|
|
91
|
+
* Codex CLI の Stop フックを hooks.json に登録する。
|
|
92
92
|
* 既にセットアップ済みの場合はスキップ。
|
|
93
93
|
*/
|
|
94
|
-
export function setupCodexHook() {
|
|
95
|
-
|
|
96
|
-
const
|
|
94
|
+
export function setupCodexHook(cwd = process.cwd()) {
|
|
95
|
+
const codexDir = resolveCodexHome(cwd);
|
|
96
|
+
const codexHooksPath = resolveCodexHooksPath(cwd);
|
|
97
97
|
mkdirSync(codexDir, { recursive: true });
|
|
98
98
|
let hooksConfig = {};
|
|
99
|
-
if (existsSync(
|
|
99
|
+
if (existsSync(codexHooksPath)) {
|
|
100
100
|
try {
|
|
101
|
-
hooksConfig = JSON.parse(readFileSync(
|
|
101
|
+
hooksConfig = JSON.parse(readFileSync(codexHooksPath, "utf-8"));
|
|
102
102
|
}
|
|
103
103
|
catch {
|
|
104
104
|
hooksConfig = {};
|
|
@@ -123,18 +123,19 @@ export function setupCodexHook() {
|
|
|
123
123
|
},
|
|
124
124
|
];
|
|
125
125
|
hooksConfig.hooks = hooks;
|
|
126
|
-
writeFileSync(
|
|
126
|
+
writeFileSync(codexHooksPath, JSON.stringify(hooksConfig, null, 2), "utf-8");
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
129
|
* Stop フックがセットアップ済みか確認する。
|
|
130
130
|
*/
|
|
131
|
-
export function isCodexHookSetup() {
|
|
131
|
+
export function isCodexHookSetup(cwd = process.cwd()) {
|
|
132
|
+
const codexHooksPath = resolveCodexHooksPath(cwd);
|
|
132
133
|
if (!existsSync(CODEX_HOOK_SCRIPT_PATH))
|
|
133
134
|
return false;
|
|
134
|
-
if (!existsSync(
|
|
135
|
+
if (!existsSync(codexHooksPath))
|
|
135
136
|
return false;
|
|
136
137
|
try {
|
|
137
|
-
const hooksConfig = JSON.parse(readFileSync(
|
|
138
|
+
const hooksConfig = JSON.parse(readFileSync(codexHooksPath, "utf-8"));
|
|
138
139
|
const stopHooks = hooksConfig.hooks?.Stop ?? [];
|
|
139
140
|
return stopHooks.some((group) => group.hooks?.some((h) => h.command === RELIC_HOOK_COMMAND));
|
|
140
141
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex の state root を解決する。
|
|
3
|
+
*
|
|
4
|
+
* CODEX_HOME が未設定または空文字の場合は ~/.codex を使う。
|
|
5
|
+
* 相対パスは、実際に Codex を起動する作業ディレクトリを基準に解決する。
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveCodexHome(cwd?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Codex の user-level hooks.json の絶対パスを返す。
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveCodexHooksPath(cwd?: string): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
const DEFAULT_CODEX_HOME = join(homedir(), ".codex");
|
|
4
|
+
/**
|
|
5
|
+
* Codex の state root を解決する。
|
|
6
|
+
*
|
|
7
|
+
* CODEX_HOME が未設定または空文字の場合は ~/.codex を使う。
|
|
8
|
+
* 相対パスは、実際に Codex を起動する作業ディレクトリを基準に解決する。
|
|
9
|
+
*/
|
|
10
|
+
export function resolveCodexHome(cwd = process.cwd()) {
|
|
11
|
+
const configuredHome = process.env.CODEX_HOME;
|
|
12
|
+
if (!configuredHome)
|
|
13
|
+
return DEFAULT_CODEX_HOME;
|
|
14
|
+
return isAbsolute(configuredHome)
|
|
15
|
+
? resolve(configuredHome)
|
|
16
|
+
: resolve(cwd, configuredHome);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Codex の user-level hooks.json の絶対パスを返す。
|
|
20
|
+
*/
|
|
21
|
+
export function resolveCodexHooksPath(cwd = process.cwd()) {
|
|
22
|
+
return join(resolveCodexHome(cwd), "hooks.json");
|
|
23
|
+
}
|
|
@@ -3,6 +3,7 @@ import { promisify } from "node:util";
|
|
|
3
3
|
import { spawnShell } from "./spawn-shell.js";
|
|
4
4
|
import { wrapWithOverride } from "./override-preamble.js";
|
|
5
5
|
import { setupCodexHook, isCodexHookSetup, writeCodexHookScript } from "./codex-hook.js";
|
|
6
|
+
import { resolveCodexHooksPath } from "./codex-paths.js";
|
|
6
7
|
const execAsync = promisify(exec);
|
|
7
8
|
/**
|
|
8
9
|
* Codex CLI アダプター
|
|
@@ -29,13 +30,14 @@ export class CodexShell {
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
async launch(prompt, options) {
|
|
33
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
32
34
|
// フックスクリプトを毎回最新に更新
|
|
33
35
|
writeCodexHookScript();
|
|
34
36
|
// hooks.json への登録は初回のみ
|
|
35
|
-
if (!isCodexHookSetup()) {
|
|
37
|
+
if (!isCodexHookSetup(cwd)) {
|
|
36
38
|
console.log("Setting up Codex CLI Stop hook (first run only)...");
|
|
37
|
-
setupCodexHook();
|
|
38
|
-
console.log(
|
|
39
|
+
setupCodexHook(cwd);
|
|
40
|
+
console.log(`Hook registered to ${resolveCodexHooksPath(cwd)}`);
|
|
39
41
|
console.log();
|
|
40
42
|
}
|
|
41
43
|
const args = [];
|
|
@@ -43,10 +45,10 @@ export class CodexShell {
|
|
|
43
45
|
if (!options?.skipInjection) {
|
|
44
46
|
args.push("-c", `developer_instructions=${JSON.stringify(wrapWithOverride(prompt))}`);
|
|
45
47
|
}
|
|
46
|
-
args.push(
|
|
48
|
+
args.push(...(options?.extraArgs ?? []));
|
|
47
49
|
const env = {};
|
|
48
50
|
if (options?.engramId)
|
|
49
51
|
env.RELIC_ENGRAM_ID = options.engramId;
|
|
50
|
-
await spawnShell(this.command, args,
|
|
52
|
+
await spawnShell(this.command, args, cwd, Object.keys(env).length > 0 ? env : undefined);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { resolve, dirname } from "node:path";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
+
import updateNotifier from "update-notifier";
|
|
6
7
|
import { registerInitCommand } from "./commands/init.js";
|
|
7
8
|
import { registerListCommand } from "./commands/list.js";
|
|
8
9
|
import { registerShowCommand } from "./commands/show.js";
|
|
@@ -16,6 +17,14 @@ import { registerDeleteCommand } from "./commands/delete.js";
|
|
|
16
17
|
import { registerMikoshiCommand } from "./commands/mikoshi.js";
|
|
17
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../../package.json"), "utf-8"));
|
|
20
|
+
if (process.stdout.isTTY) {
|
|
21
|
+
updateNotifier({
|
|
22
|
+
pkg: {
|
|
23
|
+
name: pkg.name,
|
|
24
|
+
version: pkg.version,
|
|
25
|
+
},
|
|
26
|
+
}).notify({ defer: false });
|
|
27
|
+
}
|
|
19
28
|
const program = new Command();
|
|
20
29
|
program
|
|
21
30
|
.name("relic")
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ectplsm/relic",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "PROJECT RELIC — Engram injection system for AI constructs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,22 +24,25 @@
|
|
|
24
24
|
"dev:cli": "tsx src/interfaces/cli/index.ts",
|
|
25
25
|
"dev:mcp": "tsx src/interfaces/mcp/index.ts",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
|
-
"build": "tsc"
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"release:prepare": "node scripts/prepare-release.mjs"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"dist",
|
|
31
32
|
"templates"
|
|
32
33
|
],
|
|
33
34
|
"engines": {
|
|
34
|
-
"node": ">=18"
|
|
35
|
+
"node": ">=18.14.1"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@modelcontextprotocol/sdk": "
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
38
39
|
"commander": "^13.0.0",
|
|
40
|
+
"update-notifier": "^7.3.1",
|
|
39
41
|
"zod": "^3.23.0"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/node": "^22.0.0",
|
|
45
|
+
"@types/update-notifier": "^6.0.8",
|
|
43
46
|
"tsx": "^4.19.0",
|
|
44
47
|
"typescript": "^5.7.0"
|
|
45
48
|
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ShellLauncher, InjectionMode, ShellLaunchOptions } from "../../core/ports/shell-launcher.js";
|
|
2
|
-
/**
|
|
3
|
-
* GitHub Copilot CLI アダプター (copilot コマンド)
|
|
4
|
-
* --interactive フラグでEngramを初回プロンプトとして注入し、
|
|
5
|
-
* インタラクティブモードを継続する。
|
|
6
|
-
*/
|
|
7
|
-
export declare class CopilotShell implements ShellLauncher {
|
|
8
|
-
private readonly command;
|
|
9
|
-
readonly name = "GitHub Copilot CLI";
|
|
10
|
-
readonly injectionMode: InjectionMode;
|
|
11
|
-
constructor(command?: string);
|
|
12
|
-
isAvailable(): Promise<boolean>;
|
|
13
|
-
launch(prompt: string, options?: ShellLaunchOptions): Promise<void>;
|
|
14
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
|
-
import { spawnShell, writeTempPrompt } from "./spawn-shell.js";
|
|
5
|
-
import { wrapWithOverride } from "./override-preamble.js";
|
|
6
|
-
const execAsync = promisify(exec);
|
|
7
|
-
/**
|
|
8
|
-
* GitHub Copilot CLI アダプター (copilot コマンド)
|
|
9
|
-
* --interactive フラグでEngramを初回プロンプトとして注入し、
|
|
10
|
-
* インタラクティブモードを継続する。
|
|
11
|
-
*/
|
|
12
|
-
export class CopilotShell {
|
|
13
|
-
command;
|
|
14
|
-
name = "GitHub Copilot CLI";
|
|
15
|
-
injectionMode = "user-message";
|
|
16
|
-
constructor(command = "copilot") {
|
|
17
|
-
this.command = command;
|
|
18
|
-
}
|
|
19
|
-
async isAvailable() {
|
|
20
|
-
try {
|
|
21
|
-
await execAsync(`which ${this.command}`);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
async launch(prompt, options) {
|
|
29
|
-
const tmp = writeTempPrompt(wrapWithOverride(prompt));
|
|
30
|
-
try {
|
|
31
|
-
const fileContent = readFileSync(tmp.path, "utf-8");
|
|
32
|
-
const args = [
|
|
33
|
-
"--interactive",
|
|
34
|
-
fileContent,
|
|
35
|
-
...(options?.extraArgs ?? []),
|
|
36
|
-
];
|
|
37
|
-
await spawnShell(this.command, args, options?.cwd);
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
tmp.cleanup();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TrustRegistrar — 各Shell CLIに ~/.relic/ ディレクトリの信頼設定を登録する
|
|
3
|
-
*
|
|
4
|
-
* `relic init` 時に一度だけ呼ばれ、以降のセッションで
|
|
5
|
-
* inbox.md 等への書き込みが確認なしで通るようになる。
|
|
6
|
-
*
|
|
7
|
-
* 対応Shell:
|
|
8
|
-
* - Claude Code: ~/.claude/settings.json (sandbox.filesystem.allowWrite)
|
|
9
|
-
* - Codex CLI: ~/.codex/config.toml ([sandbox] writable_roots)
|
|
10
|
-
* - Gemini CLI: ~/.gemini/trustedFolders.json (TRUST_FOLDER)
|
|
11
|
-
*/
|
|
12
|
-
interface TrustResult {
|
|
13
|
-
/** 設定を追加したShell名の一覧 */
|
|
14
|
-
registered: string[];
|
|
15
|
-
/** 既に設定済みでスキップしたShell名の一覧 */
|
|
16
|
-
skipped: string[];
|
|
17
|
-
}
|
|
18
|
-
export declare function registerTrustedFolders(relicEngramsPath: string): Promise<TrustResult>;
|
|
19
|
-
export {};
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
export async function registerTrustedFolders(relicEngramsPath) {
|
|
6
|
-
const result = { registered: [], skipped: [] };
|
|
7
|
-
await Promise.all([
|
|
8
|
-
registerClaude(relicEngramsPath, result),
|
|
9
|
-
registerCodex(relicEngramsPath, result),
|
|
10
|
-
registerGemini(relicEngramsPath, result),
|
|
11
|
-
]);
|
|
12
|
-
return result;
|
|
13
|
-
}
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Claude Code — ~/.claude/settings.json
|
|
16
|
-
// { "sandbox": { "filesystem": { "allowWrite": ["~/.relic/engrams"] } } }
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
async function registerClaude(engramsPath, result) {
|
|
19
|
-
const claudeDir = join(homedir(), ".claude");
|
|
20
|
-
const configPath = join(claudeDir, "settings.json");
|
|
21
|
-
if (!existsSync(claudeDir)) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
-
let settings = {};
|
|
27
|
-
if (existsSync(configPath)) {
|
|
28
|
-
const raw = await readFile(configPath, "utf-8");
|
|
29
|
-
settings = JSON.parse(raw);
|
|
30
|
-
}
|
|
31
|
-
// sandbox.filesystem.allowWrite を確保
|
|
32
|
-
settings.sandbox ??= {};
|
|
33
|
-
settings.sandbox.filesystem ??= {};
|
|
34
|
-
settings.sandbox.filesystem.allowWrite ??= [];
|
|
35
|
-
const allowWrite = settings.sandbox.filesystem.allowWrite;
|
|
36
|
-
// permissions.allow — ツール承認の自動化
|
|
37
|
-
settings.permissions ??= {};
|
|
38
|
-
settings.permissions.allow ??= [];
|
|
39
|
-
const allow = settings.permissions.allow;
|
|
40
|
-
const tildeForm = engramsPath.replace(homedir(), "~");
|
|
41
|
-
// sandbox: allowWrite
|
|
42
|
-
const sandboxNeeded = !allowWrite.includes(engramsPath) && !allowWrite.includes(tildeForm);
|
|
43
|
-
if (sandboxNeeded) {
|
|
44
|
-
allowWrite.push(tildeForm);
|
|
45
|
-
}
|
|
46
|
-
// permissions: Edit allow
|
|
47
|
-
const editRule = `Edit(${tildeForm}/**)`;
|
|
48
|
-
const permNeeded = !allow.includes(editRule);
|
|
49
|
-
if (permNeeded) {
|
|
50
|
-
allow.push(editRule);
|
|
51
|
-
}
|
|
52
|
-
if (!sandboxNeeded && !permNeeded) {
|
|
53
|
-
result.skipped.push("Claude Code");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
await writeFile(configPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
57
|
-
result.registered.push("Claude Code");
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// best effort
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// Codex CLI — ~/.codex/config.toml
|
|
65
|
-
// [sandbox]
|
|
66
|
-
// writable_roots = ["~/.relic/engrams"]
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
async function registerCodex(engramsPath, result) {
|
|
69
|
-
const codexDir = join(homedir(), ".codex");
|
|
70
|
-
const configPath = join(codexDir, "config.toml");
|
|
71
|
-
if (!existsSync(codexDir)) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
let content = "";
|
|
76
|
-
if (existsSync(configPath)) {
|
|
77
|
-
content = await readFile(configPath, "utf-8");
|
|
78
|
-
}
|
|
79
|
-
const tildeForm = engramsPath.replace(homedir(), "~");
|
|
80
|
-
// 既に writable_roots に含まれているかチェック
|
|
81
|
-
if (content.includes(engramsPath) || content.includes(tildeForm)) {
|
|
82
|
-
result.skipped.push("Codex CLI");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
// [sandbox] セクションが存在するか
|
|
86
|
-
const sandboxMatch = content.match(/^\[sandbox\]\s*$/m);
|
|
87
|
-
if (sandboxMatch) {
|
|
88
|
-
// [sandbox] セクション内の writable_roots を探す
|
|
89
|
-
const writableMatch = content.match(/^(writable_roots\s*=\s*\[)(.*?)(\])/m);
|
|
90
|
-
if (writableMatch) {
|
|
91
|
-
// 既存の writable_roots に追加
|
|
92
|
-
const existing = writableMatch[2].trim();
|
|
93
|
-
const newValue = existing
|
|
94
|
-
? `${existing}, "${tildeForm}"`
|
|
95
|
-
: `"${tildeForm}"`;
|
|
96
|
-
content = content.replace(/^(writable_roots\s*=\s*\[)(.*?)(\])/m, `$1${newValue}$3`);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
// [sandbox] セクションに writable_roots を追加
|
|
100
|
-
content = content.replace(/^\[sandbox\]\s*$/m, `[sandbox]\nwritable_roots = ["${tildeForm}"]`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
// [sandbox] セクションごと追加
|
|
105
|
-
content =
|
|
106
|
-
content.trimEnd() + `\n\n[sandbox]\nwritable_roots = ["${tildeForm}"]\n`;
|
|
107
|
-
}
|
|
108
|
-
await writeFile(configPath, content, "utf-8");
|
|
109
|
-
result.registered.push("Codex CLI");
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// best effort
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
// Gemini CLI — ~/.gemini/trustedFolders.json
|
|
117
|
-
// { "/path/to/engrams": "TRUST_FOLDER" }
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
async function registerGemini(engramsPath, result) {
|
|
120
|
-
const configPath = join(homedir(), ".gemini", "trustedFolders.json");
|
|
121
|
-
if (!existsSync(join(homedir(), ".gemini"))) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
let folders = {};
|
|
126
|
-
if (existsSync(configPath)) {
|
|
127
|
-
const raw = await readFile(configPath, "utf-8");
|
|
128
|
-
folders = JSON.parse(raw);
|
|
129
|
-
}
|
|
130
|
-
if (folders[engramsPath]) {
|
|
131
|
-
result.skipped.push("Gemini CLI");
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
folders[engramsPath] = "TRUST_FOLDER";
|
|
135
|
-
await writeFile(configPath, JSON.stringify(folders, null, 2), "utf-8");
|
|
136
|
-
result.registered.push("Gemini CLI");
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
// best effort
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface InboxSearchResult {
|
|
2
|
-
/** マッチしたエントリの内容 */
|
|
3
|
-
entry: string;
|
|
4
|
-
/** エントリのインデックス(新しい順: 0が最新) */
|
|
5
|
-
index: number;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* InboxSearch — inbox.md をキーワード検索する
|
|
9
|
-
*
|
|
10
|
-
* inbox.md は全セッションログ + [memory] エントリを含む生データ。
|
|
11
|
-
* memory/*.md(蒸留済み)より情報量が多いため、検索先として優れている。
|
|
12
|
-
*/
|
|
13
|
-
export declare class InboxSearch {
|
|
14
|
-
private readonly engramsPath;
|
|
15
|
-
constructor(engramsPath: string);
|
|
16
|
-
search(engramId: string, query: string, limit?: number): Promise<InboxSearchResult[]>;
|
|
17
|
-
}
|
|
18
|
-
export declare class InboxSearchEngramNotFoundError extends Error {
|
|
19
|
-
constructor(id: string);
|
|
20
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
const ENTRY_SEPARATOR = /\n---\n/;
|
|
5
|
-
/**
|
|
6
|
-
* InboxSearch — inbox.md をキーワード検索する
|
|
7
|
-
*
|
|
8
|
-
* inbox.md は全セッションログ + [memory] エントリを含む生データ。
|
|
9
|
-
* memory/*.md(蒸留済み)より情報量が多いため、検索先として優れている。
|
|
10
|
-
*/
|
|
11
|
-
export class InboxSearch {
|
|
12
|
-
engramsPath;
|
|
13
|
-
constructor(engramsPath) {
|
|
14
|
-
this.engramsPath = engramsPath;
|
|
15
|
-
}
|
|
16
|
-
async search(engramId, query, limit = 5) {
|
|
17
|
-
const inboxPath = join(this.engramsPath, engramId, "inbox.md");
|
|
18
|
-
if (!existsSync(inboxPath)) {
|
|
19
|
-
throw new InboxSearchEngramNotFoundError(engramId);
|
|
20
|
-
}
|
|
21
|
-
const raw = await readFile(inboxPath, "utf-8");
|
|
22
|
-
if (!raw.trim())
|
|
23
|
-
return [];
|
|
24
|
-
const entries = raw
|
|
25
|
-
.split(ENTRY_SEPARATOR)
|
|
26
|
-
.map((e) => e.trim())
|
|
27
|
-
.filter((e) => e.length > 0)
|
|
28
|
-
.reverse(); // 新しい順
|
|
29
|
-
const queryLower = query.toLowerCase();
|
|
30
|
-
const results = [];
|
|
31
|
-
for (let i = 0; i < entries.length; i++) {
|
|
32
|
-
if (entries[i].toLowerCase().includes(queryLower)) {
|
|
33
|
-
results.push({ entry: entries[i], index: i });
|
|
34
|
-
}
|
|
35
|
-
if (results.length >= limit)
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
return results;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export class InboxSearchEngramNotFoundError extends Error {
|
|
42
|
-
constructor(id) {
|
|
43
|
-
super(`Engram "${id}" inbox not found`);
|
|
44
|
-
this.name = "InboxSearchEngramNotFoundError";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export interface InboxWriteResult {
|
|
2
|
-
engramId: string;
|
|
3
|
-
/** inbox.md に追記されたエントリ数 */
|
|
4
|
-
totalEntries: number;
|
|
5
|
-
/** memory/*.md に永続化されたメモリ数 */
|
|
6
|
-
memoriesSaved: number;
|
|
7
|
-
/** ログとして記録されたエントリ数 */
|
|
8
|
-
logsRecorded: number;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* InboxWrite — MCP経由で inbox.md にエントリを追記する
|
|
12
|
-
*
|
|
13
|
-
* CLI の MemoryInbox (fs.watch) と同じフォーマットで inbox.md に書き込む。
|
|
14
|
-
* `[memory]` タグ付きエントリは即座に memory/*.md にも永続化する。
|
|
15
|
-
*
|
|
16
|
-
* これにより CLI Shell と MCP Desktop で同一の inbox.md フォーマットを共有し、
|
|
17
|
-
* セッションログ + メモリ永続化の二重の役割を果たす。
|
|
18
|
-
*/
|
|
19
|
-
export declare class InboxWrite {
|
|
20
|
-
private readonly engramsPath;
|
|
21
|
-
constructor(engramsPath: string);
|
|
22
|
-
execute(engramId: string, content: string): Promise<InboxWriteResult>;
|
|
23
|
-
private appendToInbox;
|
|
24
|
-
}
|
|
25
|
-
export declare class InboxWriteEngramNotFoundError extends Error {
|
|
26
|
-
constructor(id: string);
|
|
27
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
4
|
-
import { MemoryWrite } from "./memory-write.js";
|
|
5
|
-
const ENTRY_SEPARATOR = /\n---\n/;
|
|
6
|
-
const MEMORY_TAG = /^\[memory\]\s*/i;
|
|
7
|
-
/**
|
|
8
|
-
* InboxWrite — MCP経由で inbox.md にエントリを追記する
|
|
9
|
-
*
|
|
10
|
-
* CLI の MemoryInbox (fs.watch) と同じフォーマットで inbox.md に書き込む。
|
|
11
|
-
* `[memory]` タグ付きエントリは即座に memory/*.md にも永続化する。
|
|
12
|
-
*
|
|
13
|
-
* これにより CLI Shell と MCP Desktop で同一の inbox.md フォーマットを共有し、
|
|
14
|
-
* セッションログ + メモリ永続化の二重の役割を果たす。
|
|
15
|
-
*/
|
|
16
|
-
export class InboxWrite {
|
|
17
|
-
engramsPath;
|
|
18
|
-
constructor(engramsPath) {
|
|
19
|
-
this.engramsPath = engramsPath;
|
|
20
|
-
}
|
|
21
|
-
async execute(engramId, content) {
|
|
22
|
-
const engramDir = join(this.engramsPath, engramId);
|
|
23
|
-
if (!existsSync(engramDir)) {
|
|
24
|
-
throw new InboxWriteEngramNotFoundError(engramId);
|
|
25
|
-
}
|
|
26
|
-
// inbox.md に追記
|
|
27
|
-
const inboxPath = join(engramDir, "inbox.md");
|
|
28
|
-
await this.appendToInbox(inboxPath, content);
|
|
29
|
-
// エントリをパースして [memory] タグ付きを永続化
|
|
30
|
-
const entries = content
|
|
31
|
-
.split(ENTRY_SEPARATOR)
|
|
32
|
-
.map((e) => e.trim())
|
|
33
|
-
.filter((e) => e.length > 0);
|
|
34
|
-
const writer = new MemoryWrite(this.engramsPath);
|
|
35
|
-
let memoriesSaved = 0;
|
|
36
|
-
let logsRecorded = 0;
|
|
37
|
-
for (const entry of entries) {
|
|
38
|
-
if (MEMORY_TAG.test(entry)) {
|
|
39
|
-
const memContent = entry.replace(MEMORY_TAG, "").trim();
|
|
40
|
-
await writer.execute(engramId, memContent);
|
|
41
|
-
memoriesSaved++;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
logsRecorded++;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
engramId,
|
|
49
|
-
totalEntries: entries.length,
|
|
50
|
-
memoriesSaved,
|
|
51
|
-
logsRecorded,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
async appendToInbox(inboxPath, content) {
|
|
55
|
-
await mkdir(join(inboxPath, ".."), { recursive: true });
|
|
56
|
-
let existing = "";
|
|
57
|
-
if (existsSync(inboxPath)) {
|
|
58
|
-
existing = await readFile(inboxPath, "utf-8");
|
|
59
|
-
}
|
|
60
|
-
// 既存内容があれば --- で区切って追記
|
|
61
|
-
const separator = existing.length > 0 && !existing.endsWith("\n---\n")
|
|
62
|
-
? "\n---\n"
|
|
63
|
-
: "";
|
|
64
|
-
await writeFile(inboxPath, existing + separator + content + "\n", "utf-8");
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
export class InboxWriteEngramNotFoundError extends Error {
|
|
68
|
-
constructor(id) {
|
|
69
|
-
super(`Engram "${id}" not found`);
|
|
70
|
-
this.name = "InboxWriteEngramNotFoundError";
|
|
71
|
-
}
|
|
72
|
-
}
|