@alecsibilia/luca 13.0.0-alpha.1 → 13.0.0-alpha.2

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.
@@ -7,7 +7,10 @@ async function executeDoctor(options = {}) {
7
7
  logger.info("Running environment diagnostics...\n");
8
8
  const { bunRuntimeCheck } = await import('./bun-runtime.mjs');
9
9
  const { muninndbHealthCheck } = await import('./muninndb-health.mjs');
10
+ const { muninnMcpCheck } = await import('./muninn-mcp.mjs');
10
11
  const { staleMcpServerCheck } = await import('./stale-mcp-server.mjs');
12
+ const { staleGlobalSymlinksCheck } = await import('./stale-global-symlinks.mjs');
13
+ const { legacyPackageCheck } = await import('./legacy-package.mjs');
11
14
  const { strayLocalInstallCheck } = await import('./stray-local-install.mjs');
12
15
  const allChecks = [
13
16
  // Prerequisites
@@ -15,6 +18,9 @@ async function executeDoctor(options = {}) {
15
18
  staleMcpServerCheck,
16
19
  // Global
17
20
  muninndbHealthCheck,
21
+ muninnMcpCheck,
22
+ staleGlobalSymlinksCheck,
23
+ legacyPackageCheck,
18
24
  // Project (cwd-dependent)
19
25
  strayLocalInstallCheck
20
26
  ];
@@ -10,9 +10,9 @@ import 'node:url';
10
10
  import 'node:child_process';
11
11
  import { g as generateRunId } from '../shared/luca.naWEcQ4B.mjs';
12
12
  import { LUCA_VERSION } from '../index.mjs';
13
- import { d as defaultClaudeHome, r as resolveBundledArtifactsForHooks, i as installSkills } from '../shared/luca.B3saVjJm.mjs';
13
+ import { d as defaultClaudeHome, r as resolveBundledArtifactsForHooks, i as installSkills } from '../shared/luca.BHov6l1O.mjs';
14
14
  import { l as logger } from '../shared/luca.dM-MKlNE.mjs';
15
- import { M as MuninndbInstallResultSchema, r as resolvePlatformTarget, g as getLucaHomePaths, a as MUNINNDB_BINARY_NAME, b as getCommonBinaryPaths, c as resolveMuninndbPort, d as checkMuninndbService, e as MuninndbServiceStatusSchema, w as waitForMuninndbHealthy, f as ensureLucaHome, h as checkMuninndbBinary, i as MUNINNDB_DEFAULT_PORT } from '../shared/luca.BYdjkfnz.mjs';
15
+ import { M as MuninndbInstallResultSchema, r as resolvePlatformTarget, g as getLucaHomePaths, a as MUNINNDB_BINARY_NAME, b as getCommonBinaryPaths, c as resolveMuninndbPort, d as checkMuninndbService, e as MuninndbServiceStatusSchema, w as waitForMuninndbHealthy, f as ensureLucaHome, h as checkMuninndbBinary } from '../shared/luca.BfjhRHhj.mjs';
16
16
  import { join as join$1 } from 'pathe';
17
17
  import { c as checkPrerequisites, p as promptBunInstall } from '../shared/luca.DTomPq7I.mjs';
18
18
  import 'zod';
@@ -689,12 +689,11 @@ const initCommand = defineCommand({
689
689
  readout.push(
690
690
  " stores them in MuninnDB; downstream pipeline modes consult them)"
691
691
  );
692
- const mcpPort = muninndbPort ?? MUNINNDB_DEFAULT_PORT;
693
692
  readout.push(
694
693
  " To expose MuninnDB to Claude Code: register it as an MCP server,"
695
694
  );
696
695
  readout.push(
697
- ` e.g. claude mcp add --transport http muninn http://localhost:${mcpPort}/mcp \\`
696
+ ` e.g. claude mcp add --transport sse muninn http://localhost:8750/mcp \\`
698
697
  );
699
698
  readout.push(
700
699
  ' --header "Authorization: Bearer <your-muninn-api-key>"'
@@ -0,0 +1,47 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ const CHECK_NAME = "Legacy global package";
6
+ const LEGACY = "@alecsibilia/luca-framework";
7
+ const UMBRELLA = "@alecsibilia/luca";
8
+ function bunGlobalNodeModules() {
9
+ const bunInstall = process.env.BUN_INSTALL ?? join(homedir(), ".bun");
10
+ return join(bunInstall, "install", "global", "node_modules");
11
+ }
12
+ function isInstalled(nodeModules, pkg) {
13
+ return existsSync(join(nodeModules, pkg, "package.json"));
14
+ }
15
+ const legacyPackageCheck = {
16
+ name: CHECK_NAME,
17
+ scope: "global",
18
+ async run() {
19
+ const nm = bunGlobalNodeModules();
20
+ const legacyInstalled = isInstalled(nm, LEGACY);
21
+ if (!legacyInstalled) {
22
+ return {
23
+ name: CHECK_NAME,
24
+ status: "pass",
25
+ message: `no legacy ${LEGACY} global install`,
26
+ fixCommand: null,
27
+ details: null
28
+ };
29
+ }
30
+ const umbrellaInstalled = isInstalled(nm, UMBRELLA);
31
+ const conflict = umbrellaInstalled ? `Both ${UMBRELLA} and ${LEGACY} are installed globally \u2014 they provide the same \`luca\` binary, so the active one is whichever was installed last.` : `The pre-v13 ${LEGACY} is still installed globally; install ${UMBRELLA} for the v13 CLI.`;
32
+ const message = umbrellaInstalled ? `legacy ${LEGACY} installed globally (conflicts with ${UMBRELLA})` : `legacy ${LEGACY} still installed globally (pre-v13)`;
33
+ return {
34
+ name: CHECK_NAME,
35
+ status: "warning",
36
+ message,
37
+ fixCommand: `bun rm -g ${LEGACY}`,
38
+ details: [
39
+ conflict,
40
+ `Remove the legacy package: bun rm -g ${LEGACY}`,
41
+ `(reversible \u2014 \`bun add -g ${LEGACY}\` reinstalls it).`
42
+ ].join("\n ")
43
+ };
44
+ }
45
+ };
46
+
47
+ export { legacyPackageCheck };
@@ -0,0 +1,104 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+
4
+ const CHECK_NAME = "MuninnDB MCP wiring";
5
+ const MCP_URL = "http://127.0.0.1:8750/mcp";
6
+ const ADD_COMMAND = 'claude mcp add --transport sse muninn http://localhost:8750/mcp --header "Authorization: Bearer <your-muninn-api-key>"';
7
+ async function readJsonObject(path) {
8
+ try {
9
+ const file = Bun.file(path);
10
+ if (!await file.exists()) return null;
11
+ const parsed = JSON.parse(await file.text());
12
+ return parsed !== null && typeof parsed === "object" ? parsed : null;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+ function hasMuninnEntry(mcpServers) {
18
+ return mcpServers !== null && typeof mcpServers === "object" && "muninn" in mcpServers;
19
+ }
20
+ async function isMuninnRegistered(cwd) {
21
+ const projectMcp = await readJsonObject(join(cwd, ".mcp.json"));
22
+ if (hasMuninnEntry(projectMcp?.mcpServers)) return true;
23
+ const userConfig = await readJsonObject(join(homedir(), ".claude.json"));
24
+ if (hasMuninnEntry(userConfig?.mcpServers)) return true;
25
+ const projects = userConfig?.projects;
26
+ if (projects !== null && typeof projects === "object") {
27
+ const project = projects[cwd];
28
+ if (project !== null && typeof project === "object" && hasMuninnEntry(project.mcpServers)) {
29
+ return true;
30
+ }
31
+ }
32
+ return false;
33
+ }
34
+ async function isMcpReachable() {
35
+ try {
36
+ const res = await fetch(MCP_URL, {
37
+ method: "GET",
38
+ signal: AbortSignal.timeout(2500)
39
+ });
40
+ return res.status >= 200 && res.status < 300 || res.status === 401 || res.status === 403;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ const muninnMcpCheck = {
46
+ name: CHECK_NAME,
47
+ scope: "global",
48
+ async run() {
49
+ const [reachable, registered] = await Promise.all([
50
+ isMcpReachable(),
51
+ isMuninnRegistered(process.cwd())
52
+ ]);
53
+ if (reachable && registered) {
54
+ return {
55
+ name: CHECK_NAME,
56
+ status: "pass",
57
+ message: "muninn MCP registered and reachable on :8750",
58
+ fixCommand: null,
59
+ details: null
60
+ };
61
+ }
62
+ if (reachable && !registered) {
63
+ return {
64
+ name: CHECK_NAME,
65
+ status: "warning",
66
+ message: "MuninnDB MCP is up but not registered with Claude Code",
67
+ fixCommand: ADD_COMMAND,
68
+ details: [
69
+ "The MCP endpoint on :8750 is reachable, but no `muninn`",
70
+ "server is registered, so the pipeline cannot use memory.",
71
+ "Register it (use the key from `luca vault:init` / .env):",
72
+ ` ${ADD_COMMAND}`
73
+ ].join("\n ")
74
+ };
75
+ }
76
+ if (!reachable && registered) {
77
+ return {
78
+ name: CHECK_NAME,
79
+ status: "warning",
80
+ message: "muninn MCP registered but endpoint unreachable on :8750",
81
+ fixCommand: "luca init",
82
+ details: [
83
+ "A `muninn` server is registered, but nothing is answering",
84
+ "on :8750. Is MuninnDB running? Start it with `luca init`",
85
+ "or `muninn start`, then restart Claude Code."
86
+ ].join("\n ")
87
+ };
88
+ }
89
+ return {
90
+ name: CHECK_NAME,
91
+ status: "warning",
92
+ message: "MuninnDB MCP not running and not registered (optional)",
93
+ fixCommand: ADD_COMMAND,
94
+ details: [
95
+ "MuninnDB provides cross-session memory for the pipeline. It is",
96
+ "optional, but recommended. Start it (`luca init`) and register",
97
+ "the MCP server:",
98
+ ` ${ADD_COMMAND}`
99
+ ].join("\n ")
100
+ };
101
+ }
102
+ };
103
+
104
+ export { muninnMcpCheck };
@@ -1,4 +1,4 @@
1
- import { h as checkMuninndbBinary, d as checkMuninndbService } from '../shared/luca.BYdjkfnz.mjs';
1
+ import { h as checkMuninndbBinary, d as checkMuninndbService } from '../shared/luca.BfjhRHhj.mjs';
2
2
  import 'pathe';
3
3
  import 'node:fs';
4
4
  import 'node:fs/promises';
@@ -0,0 +1,78 @@
1
+ import { lstat, rm, readdir, stat } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ const CHECK_NAME = "Global ~/.claude symlinks";
6
+ const SCANNED_SUBDIRS = ["skills", "commands", "agents", "hooks"];
7
+ async function findBrokenSymlinks() {
8
+ const claudeRoot = join(homedir(), ".claude");
9
+ const broken = [];
10
+ for (const sub of SCANNED_SUBDIRS) {
11
+ const dir = join(claudeRoot, sub);
12
+ let entries;
13
+ try {
14
+ entries = await readdir(dir, { withFileTypes: true });
15
+ } catch {
16
+ continue;
17
+ }
18
+ for (const entry of entries) {
19
+ if (!entry.isSymbolicLink()) continue;
20
+ const path = join(dir, entry.name);
21
+ const resolves = await stat(path).then(
22
+ () => true,
23
+ () => false
24
+ );
25
+ if (!resolves) broken.push(path);
26
+ }
27
+ }
28
+ return broken;
29
+ }
30
+ function label(path) {
31
+ return path.replace(homedir(), "~");
32
+ }
33
+ const staleGlobalSymlinksCheck = {
34
+ name: CHECK_NAME,
35
+ scope: "global",
36
+ async run() {
37
+ const broken = await findBrokenSymlinks();
38
+ if (broken.length === 0) {
39
+ return {
40
+ name: CHECK_NAME,
41
+ status: "pass",
42
+ message: "no broken symlinks in ~/.claude",
43
+ fixCommand: null,
44
+ details: null
45
+ };
46
+ }
47
+ return {
48
+ name: CHECK_NAME,
49
+ status: "warning",
50
+ message: `${broken.length} broken symlink(s) in ~/.claude (can block 'luca init')`,
51
+ fixCommand: "luca doctor --fix",
52
+ details: [
53
+ "Dangling symlinks (target no longer exists) left by an older",
54
+ "dev install. They make 'luca init' fail with EEXIST when it",
55
+ "tries to create a skill/command/agent at the same path:",
56
+ ...broken.map((p) => `- ${label(p)}`)
57
+ ].join("\n ")
58
+ };
59
+ },
60
+ async fix() {
61
+ const applied = [];
62
+ const errors = [];
63
+ for (const path of await findBrokenSymlinks()) {
64
+ try {
65
+ await lstat(path);
66
+ await rm(path, { force: true });
67
+ applied.push(`removed broken symlink ${label(path)}`);
68
+ } catch (err) {
69
+ errors.push(
70
+ `could not remove ${label(path)}: ${err.message}`
71
+ );
72
+ }
73
+ }
74
+ return { applied, errors };
75
+ }
76
+ };
77
+
78
+ export { staleGlobalSymlinksCheck };
@@ -1,4 +1,4 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, lstatSync } from 'node:fs';
2
2
  import { rm, writeFile, readdir, rmdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import '../shared/luca.CRmaAfXR.mjs';
@@ -7,13 +7,21 @@ import 'node:module';
7
7
  import 'node:url';
8
8
  import 'node:child_process';
9
9
  import '../index.mjs';
10
- import { l as listBundledArtifacts } from '../shared/luca.B3saVjJm.mjs';
10
+ import { l as listBundledArtifacts } from '../shared/luca.BHov6l1O.mjs';
11
11
  import 'zod';
12
12
  import 'node:os';
13
13
  import 'citty';
14
14
  import 'pathe';
15
15
 
16
16
  const CHECK_NAME = "Stray local install";
17
+ function pathPresent(p) {
18
+ try {
19
+ lstatSync(p);
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
17
25
  async function readJsonObject(path) {
18
26
  try {
19
27
  const file = Bun.file(path);
@@ -43,7 +51,7 @@ async function scanStray(cwd) {
43
51
  if (bundled) {
44
52
  for (const name of bundled.commands) {
45
53
  const path = join(claudeDir, "commands", name);
46
- if (existsSync(path)) {
54
+ if (pathPresent(path)) {
47
55
  items.push({
48
56
  path,
49
57
  label: `.claude/commands/${name}`,
@@ -53,7 +61,7 @@ async function scanStray(cwd) {
53
61
  }
54
62
  for (const name of bundled.agents) {
55
63
  const path = join(claudeDir, "agents", name);
56
- if (existsSync(path)) {
64
+ if (pathPresent(path)) {
57
65
  items.push({
58
66
  path,
59
67
  label: `.claude/agents/${name}`,
@@ -63,7 +71,7 @@ async function scanStray(cwd) {
63
71
  }
64
72
  for (const name of bundled.skills) {
65
73
  const path = join(claudeDir, "skills", name);
66
- if (existsSync(path)) {
74
+ if (pathPresent(path)) {
67
75
  items.push({
68
76
  path,
69
77
  label: `.claude/skills/${name}/`,
@@ -73,7 +81,7 @@ async function scanStray(cwd) {
73
81
  }
74
82
  }
75
83
  const hookScript = join(claudeDir, "hooks", "stage-gate.sh");
76
- if (existsSync(hookScript)) {
84
+ if (pathPresent(hookScript)) {
77
85
  items.push({
78
86
  path: hookScript,
79
87
  label: ".claude/hooks/stage-gate.sh",
@@ -11,7 +11,7 @@ import * as p from '@clack/prompts';
11
11
  import { defineCommand } from 'citty';
12
12
  import { join, basename } from 'pathe';
13
13
  import { z } from 'zod';
14
- import { d as checkMuninndbService, c as resolveMuninndbPort } from '../shared/luca.BYdjkfnz.mjs';
14
+ import { d as checkMuninndbService, c as resolveMuninndbPort } from '../shared/luca.BfjhRHhj.mjs';
15
15
  import 'node:os';
16
16
  import '../shared/luca.DTomPq7I.mjs';
17
17
  import 'semver';
@@ -87,7 +87,9 @@ const VaultConfigSchema = z.object({
87
87
  /** MuninnDB API key for authentication. */
88
88
  apiKey: z.string().min(1)
89
89
  });
90
- const MUNINNDB_WEB_UI_URL = "http://localhost:8477";
90
+ function muninndbWebUiUrl() {
91
+ return `http://127.0.0.1:${resolveMuninndbPort()}`;
92
+ }
91
93
  function suggestVaultName(context, cwd = process.cwd()) {
92
94
  const raw = context.projectName ?? basename(cwd);
93
95
  return sanitizeVaultName(raw);
@@ -125,7 +127,7 @@ async function runVaultWizard(context, cwd = process.cwd()) {
125
127
  [
126
128
  "To generate an API key, open the MuninnDB Web UI:",
127
129
  "",
128
- ` ${MUNINNDB_WEB_UI_URL}`,
130
+ ` ${muninndbWebUiUrl()}`,
129
131
  "",
130
132
  "Navigate to Settings > API Keys and create a new key.",
131
133
  "If MuninnDB is not running, you can set this up later."