@ectplsm/relic 0.3.2 → 0.4.0

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 (60) hide show
  1. package/LICENCE.md +1 -1
  2. package/README.md +19 -12
  3. package/dist/adapters/mikoshi/client.d.ts +21 -0
  4. package/dist/adapters/mikoshi/client.js +84 -0
  5. package/dist/core/ports/index.d.ts +1 -0
  6. package/dist/core/ports/mikoshi.d.ts +937 -0
  7. package/dist/core/ports/mikoshi.js +209 -0
  8. package/dist/core/sync/crypto.d.ts +55 -0
  9. package/dist/core/sync/crypto.js +189 -0
  10. package/dist/core/sync/memory-hash.d.ts +35 -0
  11. package/dist/core/sync/memory-hash.js +61 -0
  12. package/dist/core/sync/memory-merge.d.ts +13 -0
  13. package/dist/core/sync/memory-merge.js +84 -0
  14. package/dist/core/sync/normalize.d.ts +10 -0
  15. package/dist/core/sync/normalize.js +21 -0
  16. package/dist/core/sync/persona-hash.d.ts +15 -0
  17. package/dist/core/sync/persona-hash.js +36 -0
  18. package/dist/core/usecases/claw-pull.d.ts +44 -0
  19. package/dist/core/usecases/claw-pull.js +117 -0
  20. package/dist/core/usecases/extract.d.ts +0 -1
  21. package/dist/core/usecases/extract.js +13 -27
  22. package/dist/core/usecases/index.d.ts +8 -0
  23. package/dist/core/usecases/index.js +8 -0
  24. package/dist/core/usecases/inject.d.ts +1 -0
  25. package/dist/core/usecases/inject.js +2 -0
  26. package/dist/core/usecases/mikoshi-clone.d.ts +29 -0
  27. package/dist/core/usecases/mikoshi-clone.js +94 -0
  28. package/dist/core/usecases/mikoshi-download.d.ts +29 -0
  29. package/dist/core/usecases/mikoshi-download.js +94 -0
  30. package/dist/core/usecases/mikoshi-memory-pull.d.ts +38 -0
  31. package/dist/core/usecases/mikoshi-memory-pull.js +142 -0
  32. package/dist/core/usecases/mikoshi-memory-push.d.ts +34 -0
  33. package/dist/core/usecases/mikoshi-memory-push.js +105 -0
  34. package/dist/core/usecases/mikoshi-memory-sync.d.ts +36 -0
  35. package/dist/core/usecases/mikoshi-memory-sync.js +175 -0
  36. package/dist/core/usecases/mikoshi-pull.d.ts +44 -0
  37. package/dist/core/usecases/mikoshi-pull.js +124 -0
  38. package/dist/core/usecases/mikoshi-push.d.ts +39 -0
  39. package/dist/core/usecases/mikoshi-push.js +127 -0
  40. package/dist/core/usecases/mikoshi-status.d.ts +37 -0
  41. package/dist/core/usecases/mikoshi-status.js +92 -0
  42. package/dist/core/usecases/sync.d.ts +0 -5
  43. package/dist/core/usecases/sync.js +23 -61
  44. package/dist/interfaces/cli/commands/claw.js +134 -85
  45. package/dist/interfaces/cli/commands/config.js +59 -0
  46. package/dist/interfaces/cli/commands/create.js +17 -16
  47. package/dist/interfaces/cli/commands/init.js +11 -10
  48. package/dist/interfaces/cli/commands/list.js +9 -7
  49. package/dist/interfaces/cli/commands/migrate.js +8 -7
  50. package/dist/interfaces/cli/commands/mikoshi.d.ts +2 -0
  51. package/dist/interfaces/cli/commands/mikoshi.js +565 -0
  52. package/dist/interfaces/cli/commands/refresh-samples.js +10 -9
  53. package/dist/interfaces/cli/index.js +2 -0
  54. package/dist/interfaces/cli/output.d.ts +5 -0
  55. package/dist/interfaces/cli/output.js +16 -0
  56. package/dist/interfaces/cli/spinner.d.ts +13 -0
  57. package/dist/interfaces/cli/spinner.js +47 -0
  58. package/dist/shared/config.d.ts +23 -3
  59. package/dist/shared/config.js +30 -2
  60. package/package.json +1 -1
@@ -2,22 +2,23 @@ import { createInterface } from "node:readline/promises";
2
2
  import { stdin as input, stdout as output } from "node:process";
3
3
  import { existsSync } from "node:fs";
4
4
  import { LocalEngramRepository } from "../../../adapters/local/index.js";
5
- import { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, Sync, SyncOpenclawDirNotFoundError, } from "../../../core/usecases/index.js";
6
- import { resolveEngramsPath, resolveClawPath } from "../../../shared/config.js";
5
+ import { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, Extract, WorkspaceNotFoundError, WorkspaceEmptyError, ClawPull, ClawPullEngramNotFoundError, ClawPullWorkspaceNotFoundError, ClawPullPersonaMissingError, Sync, SyncOpenclawDirNotFoundError, } from "../../../core/usecases/index.js";
6
+ import { resolveEngramsPath, resolveClawPath, } from "../../../shared/config.js";
7
+ import { printDetail, printLine } from "../output.js";
7
8
  import { resolveWorkspacePath } from "../../../shared/openclaw.js";
8
9
  export function registerClawCommand(program) {
9
10
  const claw = program
10
11
  .command("claw")
11
12
  .description("Manage Claw agent workspaces (OpenClaw and compatible)");
12
- // --- claw inject ---
13
+ // --- claw push ---
13
14
  claw
14
- .command("inject")
15
- .description("Push an Engram into a Claw workspace")
16
- .requiredOption("-e, --engram <id>", "Engram ID to inject")
15
+ .command("push")
16
+ .description("Push local Engram persona into a Claw workspace")
17
+ .requiredOption("-e, --engram <id>", "Engram ID to push")
17
18
  .option("--dir <dir>", "Override Claw directory path (default: ~/.openclaw)")
18
19
  .option("--merge-identity", "Merge IDENTITY.md into SOUL.md (for non-OpenClaw Claw frameworks)")
19
- .option("-y, --yes", "Skip persona overwrite confirmation")
20
- .option("--no-sync", "Skip automatic memory sync after inject")
20
+ .option("-y, --yes", "Skip create/overwrite confirmation")
21
+ .option("--no-sync", "Skip automatic memory sync after push")
21
22
  .option("-p, --path <dir>", "Override engrams directory path")
22
23
  .action(async (opts) => {
23
24
  const engramsPath = await resolveEngramsPath(opts.path);
@@ -31,26 +32,36 @@ export function registerClawCommand(program) {
31
32
  });
32
33
  const alreadyInjected = diff.soul === "same" &&
33
34
  (diff.identity === "same" || diff.identity === "skipped");
35
+ const needsCreate = !diff.targetExists ||
36
+ diff.soul === "missing" ||
37
+ diff.identity === "missing";
38
+ if (needsCreate && !opts.yes) {
39
+ const confirmed = await confirmOverwrite(`Claw workspace "${opts.engram}" does not have persona files yet. Create them from the local Relic Engram? [y/N] `);
40
+ if (!confirmed) {
41
+ printLine("Skipped.");
42
+ return;
43
+ }
44
+ }
34
45
  if (diff.overwriteRequired &&
35
46
  !opts.yes &&
36
- !(await confirmOverwrite(`SOUL.md and/or IDENTITY.md already exist and differ in ${diff.targetPath}. Overwrite with local Relic version? [y/N] `))) {
37
- console.error("Error: Persona files already exist and differ. Re-run with --yes to overwrite from local Relic Engram.");
38
- process.exit(1);
47
+ !(await confirmOverwrite(`Overwrite Claw workspace "${opts.engram}" persona with the local Relic version? [y/N] `))) {
48
+ printLine("Skipped.");
49
+ return;
39
50
  }
40
51
  if (alreadyInjected) {
41
- console.log(` Already injected (${diff.targetPath})`);
52
+ console.log(`✅ Persona already in sync (${diff.targetPath})`);
42
53
  }
43
54
  else {
44
55
  const result = await inject.execute(opts.engram, {
45
56
  openclawDir: clawDir,
46
57
  mergeIdentity: opts.mergeIdentity,
47
58
  });
48
- console.log(`Injected "${result.engramName}" into ${result.targetPath}`);
49
- console.log(` Files written: ${result.filesWritten.join(", ")}`);
59
+ console.log(`✅ Pushed "${result.engramName}" into ${result.targetPath}`);
60
+ printDetail(`Files written: ${result.filesWritten.join(", ")}`);
50
61
  }
51
62
  if (!opts.sync)
52
63
  return;
53
- // Auto-sync memory after inject
64
+ // Auto-sync memory after push
54
65
  const sync = new Sync(repo, engramsPath);
55
66
  const workspacePath = resolveWorkspacePath(opts.engram, clawDir);
56
67
  const syncResult = await sync.syncPair({
@@ -68,88 +79,108 @@ export function registerClawCommand(program) {
68
79
  details.push("USER.md");
69
80
  }
70
81
  if (details.length > 0) {
71
- console.log(` Synced: ${details.join(", ")}`);
82
+ console.log(`✅ Memory synced: ${details.join(", ")} (${opts.engram})`);
72
83
  }
73
84
  else {
74
- console.log(` Already in sync`);
85
+ console.log(`✅ Memory already in sync (${opts.engram})`);
75
86
  }
76
87
  }
77
88
  catch (err) {
78
89
  if (err instanceof InjectEngramNotFoundError ||
79
90
  err instanceof InjectClawDirNotFoundError ||
80
91
  err instanceof InjectWorkspaceNotFoundError) {
81
- console.error(`Error: ${err.message}`);
92
+ printError(`Error: ${err.message}`);
82
93
  process.exit(1);
83
94
  }
84
95
  throw err;
85
96
  }
86
97
  });
87
- // --- claw extract ---
98
+ // --- claw pull ---
88
99
  claw
89
- .command("extract")
90
- .description("Create a new Engram from a Claw agent workspace")
91
- .option("-a, --agent <name>", "Agent name to extract from (default: main)")
92
- .option("--name <name>", "Engram display name (defaults to agent name)")
100
+ .command("pull")
101
+ .description("Pull persona from a Claw workspace into local Relic")
102
+ .requiredOption("-e, --engram <id>", "Engram ID to pull")
103
+ .option("--name <name>", "Display name to use if a new local Engram is created")
93
104
  .option("--dir <dir>", "Override Claw directory path (default: ~/.openclaw)")
94
- .option("-f, --force", "Allow overwriting local persona files from the Claw workspace")
95
- .option("-y, --yes", "Skip persona overwrite confirmation")
96
- .option("--no-sync", "Skip automatic memory sync after extract")
105
+ .option("-y, --yes", "Skip create/overwrite confirmation")
106
+ .option("--no-sync", "Skip automatic memory sync after pull")
97
107
  .option("-p, --path <dir>", "Override engrams directory path")
98
108
  .action(async (opts) => {
99
109
  const engramsPath = await resolveEngramsPath(opts.path);
100
110
  const clawDir = await resolveClawPath(opts.dir);
101
111
  const repo = new LocalEngramRepository(engramsPath);
102
112
  const extract = new Extract(repo);
113
+ const pull = new ClawPull(repo);
103
114
  const sync = new Sync(repo, engramsPath);
104
115
  try {
105
- const agentName = opts.agent ?? "main";
106
- const diff = await extract.inspectPersona(agentName, {
107
- name: opts.name,
108
- openclawDir: clawDir,
109
- });
110
- if (diff.existing && !opts.force) {
111
- throw new AlreadyExtractedError(agentName);
112
- }
113
- const alreadyExtracted = diff.existing &&
114
- diff.name === "same" &&
115
- diff.soul === "same" &&
116
- diff.identity === "same";
117
- if (diff.existing &&
118
- diff.overwriteRequired &&
119
- !opts.yes &&
120
- !(await confirmOverwrite(`SOUL.md and/or IDENTITY.md already exist in local Engram "${agentName}" and differ from ${diff.sourcePath}. Overwrite with the Claw version? [y/N] `))) {
121
- console.error("Error: Persona files already exist and differ. Re-run with --yes to overwrite from the Claw workspace.");
122
- process.exit(1);
123
- }
124
- if (alreadyExtracted) {
125
- console.log(opts.force
126
- ? ` Already extracted and updated (${agentName})`
127
- : ` Already extracted (${agentName})`);
128
- }
129
- else {
130
- const result = await extract.execute(agentName, {
116
+ const engramId = opts.engram.trim();
117
+ const local = await repo.get(engramId);
118
+ if (!local) {
119
+ if (!opts.yes &&
120
+ !(await confirmOverwrite(`Local Engram "${engramId}" does not exist. Create it from the Claw workspace? [y/N] `))) {
121
+ printLine("Skipped.");
122
+ return;
123
+ }
124
+ const result = await extract.execute(engramId, {
131
125
  name: opts.name,
132
126
  openclawDir: clawDir,
133
- force: opts.force,
134
127
  });
135
- console.log(`Extracted "${result.engramName}" from ${result.sourcePath}`);
136
- if (diff.existing) {
137
- console.log(` Files overwritten: SOUL.md, IDENTITY.md`);
138
- if (diff.name === "different") {
139
- console.log(` Metadata updated: engram.json (name)`);
140
- }
141
- console.log(` Saved as Engram: ${result.engramId}`);
128
+ console.log(`✅ Pulled "${result.engramName}" from ${result.sourcePath}`);
129
+ printDetail(`Files imported: ${result.filesRead.join(", ")}`);
130
+ printDetail(`Saved as Engram: ${result.engramId}`);
131
+ if (!opts.sync)
132
+ return;
133
+ const syncResult = await sync.syncPair({
134
+ engramId,
135
+ workspacePath: result.sourcePath,
136
+ });
137
+ const details = [];
138
+ if (syncResult.memoryFilesMerged > 0) {
139
+ details.push(`${syncResult.memoryFilesMerged} memory file(s)`);
140
+ }
141
+ if (syncResult.memoryIndexMerged) {
142
+ details.push("MEMORY.md");
143
+ }
144
+ if (syncResult.userMerged) {
145
+ details.push("USER.md");
146
+ }
147
+ if (details.length > 0) {
148
+ console.log(`✅ Memory synced: ${details.join(", ")} (${engramId})`);
142
149
  }
143
150
  else {
144
- console.log(` Files extracted: ${result.filesRead.join(", ")}`);
145
- console.log(` Saved as Engram: ${result.engramId}`);
151
+ console.log(`✅ Memory already in sync (${engramId})`);
152
+ }
153
+ return;
154
+ }
155
+ const { result, apply } = await pull.check(engramId, {
156
+ openclawDir: clawDir,
157
+ });
158
+ if (result.outcome === "already_synced") {
159
+ console.log(`✅ Persona already in sync (${engramId})`);
160
+ }
161
+ else {
162
+ const diff = result.diff;
163
+ if (diff.soulDiff === "different") {
164
+ printDetail("SOUL.md: differs");
165
+ }
166
+ if (diff.identityDiff === "different") {
167
+ printDetail("IDENTITY.md: differs");
168
+ }
169
+ // Confirm overwrite
170
+ if (!opts.yes &&
171
+ !(await confirmOverwrite(`Overwrite local Engram "${engramId}" persona with the Claw workspace version? [y/N] `))) {
172
+ printLine("Skipped.");
173
+ return;
146
174
  }
175
+ await apply();
176
+ console.log(`✅ Pulled "${result.engramName}" from ${result.diff.sourcePath}`);
147
177
  }
148
178
  if (!opts.sync)
149
179
  return;
180
+ const workspacePath = resolveWorkspacePath(engramId, clawDir);
150
181
  const syncResult = await sync.syncPair({
151
- engramId: agentName,
152
- workspacePath: diff.sourcePath,
182
+ engramId,
183
+ workspacePath,
153
184
  });
154
185
  const details = [];
155
186
  if (syncResult.memoryFilesMerged > 0) {
@@ -162,17 +193,19 @@ export function registerClawCommand(program) {
162
193
  details.push("USER.md");
163
194
  }
164
195
  if (details.length > 0) {
165
- console.log(` Synced: ${details.join(", ")}`);
196
+ console.log(`✅ Memory synced: ${details.join(", ")} (${engramId})`);
166
197
  }
167
198
  else {
168
- console.log(` Already in sync`);
199
+ console.log(`✅ Memory already in sync (${engramId})`);
169
200
  }
170
201
  }
171
202
  catch (err) {
172
203
  if (err instanceof WorkspaceNotFoundError ||
173
204
  err instanceof WorkspaceEmptyError ||
174
- err instanceof AlreadyExtractedError) {
175
- console.error(`Error: ${err.message}`);
205
+ err instanceof ClawPullEngramNotFoundError ||
206
+ err instanceof ClawPullWorkspaceNotFoundError ||
207
+ err instanceof ClawPullPersonaMissingError) {
208
+ printError(`Error: ${err.message}`);
176
209
  process.exit(1);
177
210
  }
178
211
  throw err;
@@ -182,7 +215,8 @@ export function registerClawCommand(program) {
182
215
  claw
183
216
  .command("sync")
184
217
  .description("Bidirectional memory sync between Engrams and Claw workspaces")
185
- .option("-t, --target <id>", "Sync one target only by shared Engram/agent name")
218
+ .option("-e, --engram <id>", "Engram ID to sync")
219
+ .option("--all", "Sync all matching targets")
186
220
  .option("--dir <dir>", "Override Claw directory path (default: ~/.openclaw)")
187
221
  .option("-p, --path <dir>", "Override engrams directory path")
188
222
  .action(async (opts) => {
@@ -191,24 +225,32 @@ export function registerClawCommand(program) {
191
225
  const repo = new LocalEngramRepository(engramsPath);
192
226
  const sync = new Sync(repo, engramsPath);
193
227
  try {
194
- if (opts.target) {
195
- const targetId = opts.target.trim();
196
- if (!targetId) {
197
- console.error(`Error: Invalid sync target "${opts.target}".`);
228
+ if (opts.engram && opts.all) {
229
+ printError("Error: --engram and --all cannot be used together.");
230
+ process.exit(1);
231
+ }
232
+ if (!opts.engram && !opts.all) {
233
+ printError("Error: Specify --engram <id> or --all.");
234
+ process.exit(1);
235
+ }
236
+ if (opts.engram) {
237
+ const engramId = opts.engram.trim();
238
+ if (!engramId) {
239
+ printError(`Error: Invalid Engram ID "${opts.engram}".`);
198
240
  process.exit(1);
199
241
  }
200
- const engram = await repo.get(targetId);
242
+ const engram = await repo.get(engramId);
201
243
  if (!engram) {
202
- console.error(`Error: Engram "${targetId}" not found.`);
244
+ printError(`Error: Engram "${engramId}" not found.`);
203
245
  process.exit(1);
204
246
  }
205
- const workspacePath = resolveWorkspacePath(targetId, clawDir);
247
+ const workspacePath = resolveWorkspacePath(engramId, clawDir);
206
248
  if (!existsSync(workspacePath)) {
207
- console.error(`Error: Claw agent "${targetId}" workspace not found.`);
249
+ printError(`Error: Claw agent "${engramId}" workspace not found.`);
208
250
  process.exit(1);
209
251
  }
210
252
  const result = await sync.syncPair({
211
- engramId: targetId,
253
+ engramId,
212
254
  workspacePath,
213
255
  });
214
256
  const details = [];
@@ -222,16 +264,16 @@ export function registerClawCommand(program) {
222
264
  details.push("USER.md");
223
265
  }
224
266
  if (details.length > 0) {
225
- console.log(` ${targetId}: merged ${details.join(", ")}`);
267
+ console.log(`✅ ${engramId}: merged ${details.join(", ")}`);
226
268
  }
227
269
  else {
228
- console.log(` Already in sync (${targetId})`);
270
+ console.log(`✅ Already in sync (${engramId})`);
229
271
  }
230
272
  return;
231
273
  }
232
274
  const result = await sync.execute(clawDir);
233
275
  if (result.synced.length === 0 && result.skipped.length === 0) {
234
- console.log("No Claw workspaces found.");
276
+ printLine("No Claw workspaces found.");
235
277
  return;
236
278
  }
237
279
  for (const s of result.synced) {
@@ -246,19 +288,19 @@ export function registerClawCommand(program) {
246
288
  details.push("USER.md");
247
289
  }
248
290
  if (details.length > 0) {
249
- console.log(` ${s.engramId}: merged ${details.join(", ")}`);
291
+ console.log(`✅ ${s.engramId}: merged ${details.join(", ")}`);
250
292
  }
251
293
  else {
252
- console.log(` Already in sync (${s.engramId})`);
294
+ console.log(`✅ Already in sync (${s.engramId})`);
253
295
  }
254
296
  }
255
297
  if (result.skipped.length > 0) {
256
- console.log(` Skipped (no matching Engram): ${result.skipped.join(", ")}`);
298
+ console.log(`Skipped (no matching Engram): ${result.skipped.join(", ")}`);
257
299
  }
258
300
  }
259
301
  catch (err) {
260
302
  if (err instanceof SyncOpenclawDirNotFoundError) {
261
- console.error(`Error: ${err.message}`);
303
+ printError(`Error: ${err.message}`);
262
304
  process.exit(1);
263
305
  }
264
306
  throw err;
@@ -278,3 +320,10 @@ async function confirmOverwrite(message) {
278
320
  rl.close();
279
321
  }
280
322
  }
323
+ // ---------------------------------------------------------------------------
324
+ // ANSI colors
325
+ // ---------------------------------------------------------------------------
326
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
327
+ function printError(msg) {
328
+ console.error(red(msg));
329
+ }
@@ -80,6 +80,65 @@ export function registerConfigCommand(program) {
80
80
  await saveConfig(cfg);
81
81
  console.log(`Memory window set to: ${parsed}`);
82
82
  });
83
+ // relic config mikoshi-url [url]
84
+ config
85
+ .command("mikoshi-url [url]")
86
+ .description("Get or set the Mikoshi API base URL (default: https://mikoshi.ectplsm.com)")
87
+ .action(async (url) => {
88
+ await ensureInitialized();
89
+ const cfg = await loadConfig();
90
+ if (!url) {
91
+ console.log(cfg.mikoshiUrl);
92
+ return;
93
+ }
94
+ cfg.mikoshiUrl = url;
95
+ await saveConfig(cfg);
96
+ console.log(`Mikoshi URL set to: ${url}`);
97
+ });
98
+ // relic config mikoshi-api-key [key]
99
+ config
100
+ .command("mikoshi-api-key [key]")
101
+ .description("Get or set the Mikoshi API key")
102
+ .action(async (key) => {
103
+ await ensureInitialized();
104
+ const cfg = await loadConfig();
105
+ if (!key) {
106
+ if (cfg.mikoshiApiKey) {
107
+ // マスク表示 — 先頭10文字だけ見せる
108
+ const masked = cfg.mikoshiApiKey.length > 10
109
+ ? cfg.mikoshiApiKey.slice(0, 10) + "…"
110
+ : cfg.mikoshiApiKey;
111
+ console.log(masked);
112
+ }
113
+ else {
114
+ console.log("(not set)");
115
+ }
116
+ return;
117
+ }
118
+ cfg.mikoshiApiKey = key;
119
+ await saveConfig(cfg);
120
+ console.log("Mikoshi API key saved.");
121
+ });
122
+ // relic config mikoshi-passphrase [value]
123
+ config
124
+ .command("mikoshi-passphrase [value]")
125
+ .description("Get or set the passphrase for Mikoshi memory encryption")
126
+ .action(async (value) => {
127
+ await ensureInitialized();
128
+ const cfg = await loadConfig();
129
+ if (!value) {
130
+ if (cfg.mikoshiPassphrase) {
131
+ console.log("(set)");
132
+ }
133
+ else {
134
+ console.log("(not set)");
135
+ }
136
+ return;
137
+ }
138
+ cfg.mikoshiPassphrase = value;
139
+ await saveConfig(cfg);
140
+ console.log("Mikoshi passphrase saved.");
141
+ });
83
142
  // relic config distillation-batch-size [n]
84
143
  config
85
144
  .command("distillation-batch-size [n]")
@@ -3,6 +3,7 @@ import { stdin as input, stdout as output } from "node:process";
3
3
  import { loadConfig, ensureInitialized } from "../../../shared/config.js";
4
4
  import { LocalEngramRepository } from "../../../adapters/local/index.js";
5
5
  import { DEFAULT_SOUL, DEFAULT_IDENTITY } from "../../../shared/templates.js";
6
+ import { printBlank, printDetail, printLine } from "../output.js";
6
7
  import { CreateEngram, EngramAlreadyExistsError, InvalidEngramIdError, ENGRAM_ID_PATTERN, } from "../../../core/usecases/create-engram.js";
7
8
  // ============================================================
8
9
  // Interactive prompt helpers
@@ -16,7 +17,7 @@ async function askRequired(rl, question, errorMsg) {
16
17
  const answer = await ask(rl, question);
17
18
  if (answer)
18
19
  return answer;
19
- console.log(errorMsg);
20
+ printLine(errorMsg);
20
21
  }
21
22
  }
22
23
  /**
@@ -24,14 +25,14 @@ async function askRequired(rl, question, errorMsg) {
24
25
  */
25
26
  async function askValidId(rl, repo) {
26
27
  while (true) {
27
- const id = await askRequired(rl, "Engram ID (e.g. my-agent): ", " ID is required. Use lowercase letters, numbers, and hyphens.");
28
+ const id = await askRequired(rl, "Engram ID (e.g. my-agent): ", "ID is required. Use lowercase letters, numbers, and hyphens.");
28
29
  if (!ENGRAM_ID_PATTERN.test(id)) {
29
- console.log(" Invalid ID. Use lowercase letters, numbers, and hyphens (e.g. my-agent).");
30
+ printLine("Invalid ID. Use lowercase letters, numbers, and hyphens (e.g. my-agent).");
30
31
  continue;
31
32
  }
32
33
  const existing = await repo.get(id);
33
34
  if (existing) {
34
- console.log(` Engram "${id}" already exists. Choose a different ID.`);
35
+ printLine(`Engram "${id}" already exists. Choose a different ID.`);
35
36
  continue;
36
37
  }
37
38
  return id;
@@ -91,7 +92,7 @@ export function registerCreateCommand(program) {
91
92
  id = await askValidId(rl, repo);
92
93
  }
93
94
  if (!name) {
94
- name = await askRequired(rl, "Name: ", " Name is required.");
95
+ name = await askRequired(rl, "Name: ", "Name is required.");
95
96
  }
96
97
  if (description === undefined) {
97
98
  description = await ask(rl, "Description (optional): ") || undefined;
@@ -119,17 +120,17 @@ export function registerCreateCommand(program) {
119
120
  });
120
121
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
121
122
  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}`);
123
+ printBlank();
124
+ printLine(green(`Created Engram "${result.engram.meta.name}" (${result.engram.meta.id})`));
125
+ printDetail(`→ ${engramDir}/`);
126
+ printBlank();
127
+ printLine("Files:");
128
+ printDetail("engram.json — metadata (name, description, tags)");
129
+ printDetail("manifest.json — system metadata (id, timestamps)");
130
+ printDetail("SOUL.md — core principles and behavior");
131
+ printDetail("IDENTITY.md — persona identity (fill in during first conversation)");
132
+ printBlank();
133
+ printLine(`Customize SOUL.md and IDENTITY.md, then run: relic claude ${result.engram.meta.id}`);
133
134
  }
134
135
  catch (err) {
135
136
  if (err instanceof InvalidEngramIdError || err instanceof EngramAlreadyExistsError) {
@@ -3,6 +3,7 @@ import { Init } from "../../../core/usecases/init.js";
3
3
  import { setDefaultEngram, loadConfig } from "../../../shared/config.js";
4
4
  import { LocalEngramRepository } from "../../../adapters/local/index.js";
5
5
  import { printRelicBanner } from "../banner.js";
6
+ import { printBlank, printDetail, printLine } from "../output.js";
6
7
  function ask(question) {
7
8
  const rl = createInterface({ input: process.stdin, output: process.stdout });
8
9
  return new Promise((resolve) => {
@@ -21,21 +22,21 @@ export function registerInitCommand(program) {
21
22
  const result = await init.execute();
22
23
  if (result.created) {
23
24
  printRelicBanner();
24
- console.log("Initialized RELIC:");
25
- console.log(` Config: ${result.configPath}`);
26
- console.log(` Engrams: ${result.engramsPath}`);
27
- console.log();
25
+ printLine("Initialized RELIC:");
26
+ printDetail(`Config: ${result.configPath}`);
27
+ printDetail(`Engrams: ${result.engramsPath}`);
28
+ printBlank();
28
29
  // 利用可能なEngramを表示してデフォルト選択を促す
29
30
  const cfg = await loadConfig();
30
31
  const repo = new LocalEngramRepository(cfg.engramsPath);
31
32
  const engrams = await repo.list();
32
33
  if (engrams.length > 0) {
33
- console.log("Available Engrams:");
34
- console.log(` ${"ID".padEnd(12)} Name`);
34
+ printLine("Available Engrams:");
35
+ printDetail(`${"ID".padEnd(12)} Name`);
35
36
  for (const e of engrams) {
36
- console.log(` ${e.id.padEnd(12)} ${e.name}`);
37
+ printDetail(`${e.id.padEnd(12)} ${e.name}`);
37
38
  }
38
- console.log();
39
+ printBlank();
39
40
  const rebel = engrams.find((e) => e.id === "rebel");
40
41
  const prompt = rebel
41
42
  ? `Set a default Engram? (press Enter for "${rebel.id}", or enter ID, or "n" to skip): `
@@ -61,8 +62,8 @@ export function registerInitCommand(program) {
61
62
  }
62
63
  }
63
64
  else {
64
- console.log("Already initialized.");
65
- console.log(` Config: ${result.configPath}`);
65
+ printLine("Already initialized.");
66
+ printDetail(`Config: ${result.configPath}`);
66
67
  }
67
68
  });
68
69
  }
@@ -1,5 +1,6 @@
1
1
  import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
2
  import { ListEngrams } from "../../../core/usecases/index.js";
3
+ import { printBlank, printDetail, printLine } from "../output.js";
3
4
  import { resolveEngramsPath } from "../../../shared/config.js";
4
5
  export function registerListCommand(program) {
5
6
  program
@@ -12,19 +13,20 @@ export function registerListCommand(program) {
12
13
  const listEngrams = new ListEngrams(repo);
13
14
  const engrams = await listEngrams.execute();
14
15
  if (engrams.length === 0) {
15
- console.log("No Engrams found.");
16
- console.log(` Path: ${engramsPath}`);
16
+ printLine("No Engrams found.");
17
+ printDetail(`Path: ${engramsPath}`);
17
18
  return;
18
19
  }
19
- console.log(`Engrams (${engrams.length}):\n`);
20
+ printLine(`Engrams (${engrams.length}):`);
21
+ printBlank();
20
22
  for (const e of engrams) {
21
23
  const tags = e.tags?.length ? ` [${e.tags.join(", ")}]` : "";
22
- console.log(` ${e.id}`);
23
- console.log(` ${e.name}${tags}`);
24
+ printLine(e.id);
25
+ printDetail(`${e.name}${tags}`);
24
26
  if (e.description) {
25
- console.log(` ${e.description}`);
27
+ printDetail(e.description);
26
28
  }
27
- console.log();
29
+ printBlank();
28
30
  }
29
31
  });
30
32
  }
@@ -1,5 +1,6 @@
1
1
  import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
2
  import { MigrateEngrams } from "../../../core/usecases/index.js";
3
+ import { printDetail, printLine } from "../output.js";
3
4
  import { resolveEngramsPath } from "../../../shared/config.js";
4
5
  export function registerMigrateCommand(program) {
5
6
  const migrate = program
@@ -14,18 +15,18 @@ export function registerMigrateCommand(program) {
14
15
  const repo = new LocalEngramRepository(engramsPath);
15
16
  const migrateEngrams = new MigrateEngrams(repo, engramsPath);
16
17
  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}`);
18
+ printLine(`Scanned Engrams: ${result.migrated.length + result.alreadyUpToDate.length + result.skipped.length}`);
19
+ printDetail(`Migrated: ${result.migrated.length}`);
20
+ printDetail(`Already up to date: ${result.alreadyUpToDate.length}`);
21
+ printDetail(`Skipped: ${result.skipped.length}`);
21
22
  if (result.migrated.length > 0) {
22
- console.log(` Migrated IDs: ${result.migrated.join(", ")}`);
23
+ printDetail(`Migrated IDs: ${result.migrated.join(", ")}`);
23
24
  }
24
25
  if (result.alreadyUpToDate.length > 0) {
25
- console.log(` Up-to-date IDs: ${result.alreadyUpToDate.join(", ")}`);
26
+ printDetail(`Up-to-date IDs: ${result.alreadyUpToDate.join(", ")}`);
26
27
  }
27
28
  for (const skipped of result.skipped) {
28
- console.log(` Skipped ${skipped.id}: ${skipped.reason}`);
29
+ printDetail(`Skipped ${skipped.id}: ${skipped.reason}`);
29
30
  }
30
31
  });
31
32
  }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerMikoshiCommand(program: Command): void;