@massu/core 1.15.1 → 1.15.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.
@@ -10,12 +10,12 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  // src/memory-db.ts
11
11
  import Database from "better-sqlite3";
12
12
  import { dirname as dirname2, basename } from "path";
13
- import { existsSync as existsSync2, mkdirSync } from "fs";
13
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
14
14
 
15
15
  // src/config.ts
16
- import { resolve, dirname } from "path";
17
- import { existsSync, readFileSync } from "fs";
18
- import { homedir } from "os";
16
+ import { resolve as resolve2, dirname } from "path";
17
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
18
+ import { homedir as homedir2 } from "os";
19
19
  import { parse as parseYaml } from "yaml";
20
20
  import { z } from "zod";
21
21
 
@@ -24,6 +24,68 @@ function encodeMemoryDirName(projectRoot) {
24
24
  return projectRoot.replace(/\//g, "-");
25
25
  }
26
26
 
27
+ // src/credentials.ts
28
+ import { homedir } from "os";
29
+ import { resolve } from "path";
30
+ import {
31
+ existsSync,
32
+ readFileSync,
33
+ writeFileSync,
34
+ mkdirSync,
35
+ rmSync,
36
+ chmodSync
37
+ } from "fs";
38
+ var MASSU_ENV_API_KEY = "MASSU_API_KEY";
39
+ var MASSU_ENV_CLOUD_ENDPOINT = "MASSU_CLOUD_ENDPOINT";
40
+ var DEFAULT_CLOUD_ENDPOINT = "https://api.massu.ai/v1";
41
+ function credentialsDir(home = homedir()) {
42
+ return resolve(home, ".massu");
43
+ }
44
+ function credentialsPath(home = homedir()) {
45
+ return resolve(credentialsDir(home), "credentials");
46
+ }
47
+ function readUserCredentials(home = homedir()) {
48
+ try {
49
+ const p = credentialsPath(home);
50
+ if (!existsSync(p)) return void 0;
51
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
52
+ const key = typeof parsed?.apiKey === "string" ? parsed.apiKey.trim() : "";
53
+ return key.length > 0 ? key : void 0;
54
+ } catch {
55
+ return void 0;
56
+ }
57
+ }
58
+ function isUnresolvedLiteral(v) {
59
+ return /^\$\{[^}]+\}$/.test(v);
60
+ }
61
+ function resolveApiKey(opts = {}) {
62
+ const env = opts.env ?? process.env;
63
+ const home = opts.home ?? homedir();
64
+ const cfg = typeof opts.configApiKey === "string" ? opts.configApiKey.trim() : "";
65
+ if (cfg.length > 0 && !isUnresolvedLiteral(cfg)) {
66
+ return { apiKey: cfg, source: "config" };
67
+ }
68
+ const envRaw = env[MASSU_ENV_API_KEY];
69
+ const envKey = typeof envRaw === "string" ? envRaw.trim() : "";
70
+ if (envKey.length > 0) {
71
+ return { apiKey: envKey, source: "env" };
72
+ }
73
+ const fileKey = readUserCredentials(home);
74
+ if (fileKey) {
75
+ return { apiKey: fileKey, source: "user-file" };
76
+ }
77
+ return { source: "none" };
78
+ }
79
+ function resolveEndpoint(opts = {}) {
80
+ const env = opts.env ?? process.env;
81
+ const cfg = typeof opts.configEndpoint === "string" ? opts.configEndpoint.trim() : "";
82
+ if (cfg.length > 0) return cfg;
83
+ const envRaw = env[MASSU_ENV_CLOUD_ENDPOINT];
84
+ const envEp = typeof envRaw === "string" ? envRaw.trim() : "";
85
+ if (envEp.length > 0) return envEp;
86
+ return DEFAULT_CLOUD_ENDPOINT;
87
+ }
88
+
27
89
  // src/config.ts
28
90
  var DomainConfigSchema = z.object({
29
91
  name: z.string().default("Unknown"),
@@ -406,12 +468,13 @@ var RawConfigSchema = z.object({
406
468
  lsp: LSPConfigSchema.optional()
407
469
  }).passthrough();
408
470
  var _config = null;
471
+ var _resolvedApiKeySource = "none";
409
472
  var _projectRoot = null;
410
473
  function findProjectRoot() {
411
474
  const cwd = process.cwd();
412
475
  let dir = cwd;
413
476
  while (true) {
414
- if (existsSync(resolve(dir, "massu.config.yaml"))) {
477
+ if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
415
478
  return dir;
416
479
  }
417
480
  const parent = dirname(dir);
@@ -420,10 +483,10 @@ function findProjectRoot() {
420
483
  }
421
484
  dir = cwd;
422
485
  while (true) {
423
- if (existsSync(resolve(dir, "package.json"))) {
486
+ if (existsSync2(resolve2(dir, "package.json"))) {
424
487
  return dir;
425
488
  }
426
- if (existsSync(resolve(dir, ".git"))) {
489
+ if (existsSync2(resolve2(dir, ".git"))) {
427
490
  return dir;
428
491
  }
429
492
  const parent = dirname(dir);
@@ -441,10 +504,10 @@ function getProjectRoot() {
441
504
  function getConfig() {
442
505
  if (_config) return _config;
443
506
  const root = getProjectRoot();
444
- const configPath = resolve(root, "massu.config.yaml");
507
+ const configPath = resolve2(root, "massu.config.yaml");
445
508
  let rawYaml = {};
446
- if (existsSync(configPath)) {
447
- const content = readFileSync(configPath, "utf-8");
509
+ if (existsSync2(configPath)) {
510
+ const content = readFileSync2(configPath, "utf-8");
448
511
  rawYaml = parseYaml(content) ?? {};
449
512
  }
450
513
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -461,7 +524,7 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
461
524
  );
462
525
  }
463
526
  const parsed = result.data;
464
- const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
527
+ const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve2(root, parsed.project.root);
465
528
  const fw = parsed.framework;
466
529
  let router = fw.router;
467
530
  let orm = fw.orm;
@@ -519,13 +582,19 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
519
582
  telemetry: parsed.telemetry,
520
583
  lsp: parsed.lsp
521
584
  };
522
- if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
585
+ const resolvedKey = resolveApiKey({ configApiKey: _config.cloud?.apiKey });
586
+ _resolvedApiKeySource = resolvedKey.source;
587
+ const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
588
+ if (resolvedKey.apiKey) {
523
589
  _config.cloud = {
524
590
  enabled: true,
525
591
  sync: { memory: true, analytics: true, audit: true },
526
592
  ..._config.cloud,
527
- apiKey: process.env.MASSU_API_KEY
593
+ apiKey: resolvedKey.apiKey,
594
+ endpoint: resolvedEndpoint
528
595
  };
596
+ } else if (_config.cloud) {
597
+ _config.cloud = { ..._config.cloud, endpoint: _config.cloud.endpoint ?? resolvedEndpoint };
529
598
  }
530
599
  return _config;
531
600
  }
@@ -534,34 +603,34 @@ function getResolvedPaths() {
534
603
  const root = getProjectRoot();
535
604
  const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
536
605
  return {
537
- codegraphDbPath: resolve(root, ".codegraph/codegraph.db"),
538
- dataDbPath: resolve(root, ".massu/data.db"),
539
- prismaSchemaPath: resolve(root, config.paths.schema ?? "prisma/schema.prisma"),
540
- rootRouterPath: resolve(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
541
- routersDir: resolve(root, config.paths.routers ?? "src/server/api/routers"),
542
- srcDir: resolve(root, config.paths.source),
606
+ codegraphDbPath: resolve2(root, ".codegraph/codegraph.db"),
607
+ dataDbPath: resolve2(root, ".massu/data.db"),
608
+ prismaSchemaPath: resolve2(root, config.paths.schema ?? "prisma/schema.prisma"),
609
+ rootRouterPath: resolve2(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
610
+ routersDir: resolve2(root, config.paths.routers ?? "src/server/api/routers"),
611
+ srcDir: resolve2(root, config.paths.source),
543
612
  pathAlias: Object.fromEntries(
544
613
  Object.entries(config.paths.aliases).map(([alias, target]) => [
545
614
  alias,
546
- resolve(root, target)
615
+ resolve2(root, target)
547
616
  ])
548
617
  ),
549
618
  extensions: [".ts", ".tsx", ".js", ".jsx"],
550
619
  indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
551
- patternsDir: resolve(root, claudeDirName, "patterns"),
552
- claudeMdPath: resolve(root, claudeDirName, "CLAUDE.md"),
553
- docsMapPath: resolve(root, ".massu/docs-map.json"),
554
- helpSitePath: resolve(root, "../" + config.project.name + "-help"),
555
- memoryDbPath: resolve(root, ".massu/memory.db"),
556
- knowledgeDbPath: resolve(root, ".massu/knowledge.db"),
557
- plansDir: resolve(root, "docs/plans"),
558
- docsDir: resolve(root, "docs"),
559
- claudeDir: resolve(root, claudeDirName),
560
- memoryDir: resolve(homedir(), claudeDirName, "projects", encodeMemoryDirName(root), "memory"),
561
- sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
562
- sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
563
- mcpJsonPath: resolve(root, ".mcp.json"),
564
- settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
620
+ patternsDir: resolve2(root, claudeDirName, "patterns"),
621
+ claudeMdPath: resolve2(root, claudeDirName, "CLAUDE.md"),
622
+ docsMapPath: resolve2(root, ".massu/docs-map.json"),
623
+ helpSitePath: resolve2(root, "../" + config.project.name + "-help"),
624
+ memoryDbPath: resolve2(root, ".massu/memory.db"),
625
+ knowledgeDbPath: resolve2(root, ".massu/knowledge.db"),
626
+ plansDir: resolve2(root, "docs/plans"),
627
+ docsDir: resolve2(root, "docs"),
628
+ claudeDir: resolve2(root, claudeDirName),
629
+ memoryDir: resolve2(homedir2(), claudeDirName, "projects", encodeMemoryDirName(root), "memory"),
630
+ sessionStatePath: resolve2(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
631
+ sessionArchivePath: resolve2(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
632
+ mcpJsonPath: resolve2(root, ".mcp.json"),
633
+ settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
565
634
  };
566
635
  }
567
636
 
@@ -569,8 +638,8 @@ function getResolvedPaths() {
569
638
  function getMemoryDb() {
570
639
  const dbPath = getResolvedPaths().memoryDbPath;
571
640
  const dir = dirname2(dbPath);
572
- if (!existsSync2(dir)) {
573
- mkdirSync(dir, { recursive: true });
641
+ if (!existsSync3(dir)) {
642
+ mkdirSync2(dir, { recursive: true });
574
643
  }
575
644
  const db = new Database(dbPath);
576
645
  db.pragma("journal_mode = WAL");
@@ -1315,7 +1384,7 @@ function detectDecisionPatterns(text) {
1315
1384
  }
1316
1385
 
1317
1386
  // src/observation-extractor.ts
1318
- import { homedir as homedir2 } from "os";
1387
+ import { homedir as homedir3 } from "os";
1319
1388
  var PRIVATE_PATTERNS = [
1320
1389
  /\/Users\/\w+/,
1321
1390
  // Absolute macOS paths
@@ -1506,7 +1575,7 @@ function shortenPath(filePath) {
1506
1575
  if (filePath.startsWith(root + "/")) {
1507
1576
  return filePath.slice(root.length + 1);
1508
1577
  }
1509
- const home = homedir2();
1578
+ const home = homedir3();
1510
1579
  if (filePath.startsWith(home + "/")) {
1511
1580
  return "~/" + filePath.slice(home.length + 1);
1512
1581
  }
@@ -1646,14 +1715,14 @@ function recordTestResult(db, featureKey, passing, failing) {
1646
1715
  }
1647
1716
 
1648
1717
  // src/import-resolver.ts
1649
- import { readFileSync as readFileSync2, existsSync as existsSync3, statSync } from "fs";
1650
- import { resolve as resolve4, dirname as dirname3, join } from "path";
1718
+ import { readFileSync as readFileSync3, existsSync as existsSync4, statSync } from "fs";
1719
+ import { resolve as resolve5, dirname as dirname3, join } from "path";
1651
1720
 
1652
1721
  // src/security-utils.ts
1653
- import { resolve as resolve3, normalize } from "path";
1722
+ import { resolve as resolve4, normalize } from "path";
1654
1723
  function ensureWithinRoot(filePath, projectRoot) {
1655
- const resolvedRoot = resolve3(projectRoot);
1656
- const resolvedPath = resolve3(resolvedRoot, filePath);
1724
+ const resolvedRoot = resolve4(projectRoot);
1725
+ const resolvedPath = resolve4(resolvedRoot, filePath);
1657
1726
  const normalizedPath = normalize(resolvedPath);
1658
1727
  const normalizedRoot = normalize(resolvedRoot);
1659
1728
  if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
@@ -1703,23 +1772,23 @@ function resolveImportPath(specifier, fromFile) {
1703
1772
  let basePath;
1704
1773
  if (specifier.startsWith("@/")) {
1705
1774
  const paths = getResolvedPaths();
1706
- basePath = resolve4(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
1775
+ basePath = resolve5(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
1707
1776
  } else {
1708
- basePath = resolve4(dirname3(fromFile), specifier);
1777
+ basePath = resolve5(dirname3(fromFile), specifier);
1709
1778
  }
1710
- if (existsSync3(basePath) && !isDirectory(basePath)) {
1779
+ if (existsSync4(basePath) && !isDirectory(basePath)) {
1711
1780
  return toRelative(basePath);
1712
1781
  }
1713
1782
  const resolvedPaths = getResolvedPaths();
1714
1783
  for (const ext of resolvedPaths.extensions) {
1715
1784
  const withExt = basePath + ext;
1716
- if (existsSync3(withExt)) {
1785
+ if (existsSync4(withExt)) {
1717
1786
  return toRelative(withExt);
1718
1787
  }
1719
1788
  }
1720
1789
  for (const indexFile of resolvedPaths.indexFiles) {
1721
1790
  const indexPath = join(basePath, indexFile);
1722
- if (existsSync3(indexPath)) {
1791
+ if (existsSync4(indexPath)) {
1723
1792
  return toRelative(indexPath);
1724
1793
  }
1725
1794
  }
@@ -1741,7 +1810,7 @@ function toRelative(absPath) {
1741
1810
  }
1742
1811
 
1743
1812
  // src/validation-engine.ts
1744
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1813
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1745
1814
  function getValidationChecks() {
1746
1815
  return getConfig().governance?.validation?.checks ?? {
1747
1816
  rule_compliance: true,
@@ -1769,7 +1838,7 @@ function validateFile(filePath, projectRoot) {
1769
1838
  });
1770
1839
  return checks;
1771
1840
  }
1772
- if (!existsSync4(absPath)) {
1841
+ if (!existsSync5(absPath)) {
1773
1842
  checks.push({
1774
1843
  name: "file_exists",
1775
1844
  severity: "error",
@@ -1778,7 +1847,7 @@ function validateFile(filePath, projectRoot) {
1778
1847
  });
1779
1848
  return checks;
1780
1849
  }
1781
- const source = readFileSync3(absPath, "utf-8");
1850
+ const source = readFileSync4(absPath, "utf-8");
1782
1851
  const lines = source.split("\n");
1783
1852
  if (activeChecks.rule_compliance !== false) {
1784
1853
  for (const ruleSet of config.rules) {
@@ -1876,7 +1945,7 @@ function storeValidationResult(db, filePath, checks, sessionId, validationType =
1876
1945
  }
1877
1946
 
1878
1947
  // src/security-scorer.ts
1879
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1948
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1880
1949
  var DEFAULT_SECURITY_PATTERNS = [
1881
1950
  {
1882
1951
  regex: /\bexec\s*\(\s*[`"'].*\$\{/,
@@ -1967,12 +2036,12 @@ function scoreFileSecurity(filePath, projectRoot) {
1967
2036
  }]
1968
2037
  };
1969
2038
  }
1970
- if (!existsSync5(absPath)) {
2039
+ if (!existsSync6(absPath)) {
1971
2040
  return { riskScore: 0, findings: [] };
1972
2041
  }
1973
2042
  let source;
1974
2043
  try {
1975
- source = readFileSync4(absPath, "utf-8");
2044
+ source = readFileSync5(absPath, "utf-8");
1976
2045
  } catch {
1977
2046
  return { riskScore: 0, findings: [] };
1978
2047
  }
@@ -2011,7 +2080,7 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
2011
2080
  }
2012
2081
 
2013
2082
  // src/hooks/post-tool-use.ts
2014
- import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2, statSync as statSync2 } from "fs";
2083
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
2015
2084
  import { join as join2 } from "path";
2016
2085
 
2017
2086
  // src/lib/recurrence-incrementer.ts
@@ -2073,11 +2142,11 @@ function incrementRecurrenceCountsForScannerFailures(db, sessionId, scannerStdou
2073
2142
  }
2074
2143
 
2075
2144
  // src/memory-file-ingest.ts
2076
- import { readFileSync as readFileSync5, existsSync as existsSync6, readdirSync } from "fs";
2145
+ import { readFileSync as readFileSync6, existsSync as existsSync7, readdirSync } from "fs";
2077
2146
  import { parse as parseYaml2 } from "yaml";
2078
2147
  function ingestMemoryFile(db, sessionId, filePath) {
2079
- if (!existsSync6(filePath)) return "skipped";
2080
- const content = readFileSync5(filePath, "utf-8");
2148
+ if (!existsSync7(filePath)) return "skipped";
2149
+ const content = readFileSync6(filePath, "utf-8");
2081
2150
  const basename2 = (filePath.split("/").pop() ?? "").replace(".md", "");
2082
2151
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
2083
2152
  let name = basename2;
@@ -2291,11 +2360,11 @@ async function main() {
2291
2360
  try {
2292
2361
  const projectRoot = process.cwd();
2293
2362
  const dir = join2(projectRoot, ".massu", "rule-candidates");
2294
- if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2363
+ if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
2295
2364
  const logPath = join2(dir, ".cr53-increment-failures.jsonl");
2296
- const pre = existsSync7(logPath) ? readFileSync6(logPath, "utf-8") : "";
2365
+ const pre = existsSync8(logPath) ? readFileSync7(logPath, "utf-8") : "";
2297
2366
  const sep = pre && !pre.endsWith("\n") ? "\n" : "";
2298
- writeFileSync(logPath, pre + sep + JSON.stringify({
2367
+ writeFileSync2(logPath, pre + sep + JSON.stringify({
2299
2368
  session_id,
2300
2369
  scanner_output_excerpt: (tool_response ?? "").slice(0, 200),
2301
2370
  error: err instanceof Error ? err.message : String(err),
@@ -2377,14 +2446,14 @@ function parseTestRunOutput(output) {
2377
2446
  return null;
2378
2447
  }
2379
2448
  function readStdin() {
2380
- return new Promise((resolve5) => {
2449
+ return new Promise((resolve6) => {
2381
2450
  let data = "";
2382
2451
  process.stdin.setEncoding("utf-8");
2383
2452
  process.stdin.on("data", (chunk) => {
2384
2453
  data += chunk;
2385
2454
  });
2386
- process.stdin.on("end", () => resolve5(data));
2387
- setTimeout(() => resolve5(data), 3e3);
2455
+ process.stdin.on("end", () => resolve6(data));
2456
+ setTimeout(() => resolve6(data), 3e3);
2388
2457
  });
2389
2458
  }
2390
2459
  var _cachedConventions = null;
@@ -2398,12 +2467,12 @@ function readConventions(cwd) {
2398
2467
  try {
2399
2468
  const projectRoot = cwd ?? process.cwd();
2400
2469
  const configPath = join2(projectRoot, "massu.config.yaml");
2401
- if (!existsSync7(configPath)) return _conventionDefaults;
2470
+ if (!existsSync8(configPath)) return _conventionDefaults;
2402
2471
  const mtimeMs = statSync2(configPath).mtimeMs;
2403
2472
  if (_cachedConventions !== null && _cachedConventionsPath === configPath && _cachedConventionsMtimeMs === mtimeMs) {
2404
2473
  return _cachedConventions;
2405
2474
  }
2406
- const content = readFileSync6(configPath, "utf-8");
2475
+ const content = readFileSync7(configPath, "utf-8");
2407
2476
  const parsed = parseYaml3(content);
2408
2477
  if (!parsed || typeof parsed !== "object") return _conventionDefaults;
2409
2478
  const conventions = parsed.conventions;
@@ -2435,11 +2504,11 @@ function isKnowledgeSourceFile(filePath) {
2435
2504
  function checkMemoryFileIntegrity(filePath) {
2436
2505
  const issues = [];
2437
2506
  try {
2438
- if (!existsSync7(filePath)) {
2507
+ if (!existsSync8(filePath)) {
2439
2508
  issues.push("MEMORY.md file does not exist after write");
2440
2509
  return issues;
2441
2510
  }
2442
- const content = readFileSync6(filePath, "utf-8");
2511
+ const content = readFileSync7(filePath, "utf-8");
2443
2512
  const lines = content.split("\n");
2444
2513
  const MAX_LINES = 200;
2445
2514
  if (lines.length > MAX_LINES) {
@@ -4,12 +4,12 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
4
4
  // src/memory-db.ts
5
5
  import Database from "better-sqlite3";
6
6
  import { dirname as dirname2, basename } from "path";
7
- import { existsSync as existsSync2, mkdirSync } from "fs";
7
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
8
8
 
9
9
  // src/config.ts
10
- import { resolve, dirname } from "path";
11
- import { existsSync, readFileSync } from "fs";
12
- import { homedir } from "os";
10
+ import { resolve as resolve2, dirname } from "path";
11
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
12
+ import { homedir as homedir2 } from "os";
13
13
  import { parse as parseYaml } from "yaml";
14
14
  import { z } from "zod";
15
15
 
@@ -18,6 +18,68 @@ function encodeMemoryDirName(projectRoot) {
18
18
  return projectRoot.replace(/\//g, "-");
19
19
  }
20
20
 
21
+ // src/credentials.ts
22
+ import { homedir } from "os";
23
+ import { resolve } from "path";
24
+ import {
25
+ existsSync,
26
+ readFileSync,
27
+ writeFileSync,
28
+ mkdirSync,
29
+ rmSync,
30
+ chmodSync
31
+ } from "fs";
32
+ var MASSU_ENV_API_KEY = "MASSU_API_KEY";
33
+ var MASSU_ENV_CLOUD_ENDPOINT = "MASSU_CLOUD_ENDPOINT";
34
+ var DEFAULT_CLOUD_ENDPOINT = "https://api.massu.ai/v1";
35
+ function credentialsDir(home = homedir()) {
36
+ return resolve(home, ".massu");
37
+ }
38
+ function credentialsPath(home = homedir()) {
39
+ return resolve(credentialsDir(home), "credentials");
40
+ }
41
+ function readUserCredentials(home = homedir()) {
42
+ try {
43
+ const p = credentialsPath(home);
44
+ if (!existsSync(p)) return void 0;
45
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
46
+ const key = typeof parsed?.apiKey === "string" ? parsed.apiKey.trim() : "";
47
+ return key.length > 0 ? key : void 0;
48
+ } catch {
49
+ return void 0;
50
+ }
51
+ }
52
+ function isUnresolvedLiteral(v) {
53
+ return /^\$\{[^}]+\}$/.test(v);
54
+ }
55
+ function resolveApiKey(opts = {}) {
56
+ const env = opts.env ?? process.env;
57
+ const home = opts.home ?? homedir();
58
+ const cfg = typeof opts.configApiKey === "string" ? opts.configApiKey.trim() : "";
59
+ if (cfg.length > 0 && !isUnresolvedLiteral(cfg)) {
60
+ return { apiKey: cfg, source: "config" };
61
+ }
62
+ const envRaw = env[MASSU_ENV_API_KEY];
63
+ const envKey = typeof envRaw === "string" ? envRaw.trim() : "";
64
+ if (envKey.length > 0) {
65
+ return { apiKey: envKey, source: "env" };
66
+ }
67
+ const fileKey = readUserCredentials(home);
68
+ if (fileKey) {
69
+ return { apiKey: fileKey, source: "user-file" };
70
+ }
71
+ return { source: "none" };
72
+ }
73
+ function resolveEndpoint(opts = {}) {
74
+ const env = opts.env ?? process.env;
75
+ const cfg = typeof opts.configEndpoint === "string" ? opts.configEndpoint.trim() : "";
76
+ if (cfg.length > 0) return cfg;
77
+ const envRaw = env[MASSU_ENV_CLOUD_ENDPOINT];
78
+ const envEp = typeof envRaw === "string" ? envRaw.trim() : "";
79
+ if (envEp.length > 0) return envEp;
80
+ return DEFAULT_CLOUD_ENDPOINT;
81
+ }
82
+
21
83
  // src/config.ts
22
84
  var DomainConfigSchema = z.object({
23
85
  name: z.string().default("Unknown"),
@@ -400,12 +462,13 @@ var RawConfigSchema = z.object({
400
462
  lsp: LSPConfigSchema.optional()
401
463
  }).passthrough();
402
464
  var _config = null;
465
+ var _resolvedApiKeySource = "none";
403
466
  var _projectRoot = null;
404
467
  function findProjectRoot() {
405
468
  const cwd = process.cwd();
406
469
  let dir = cwd;
407
470
  while (true) {
408
- if (existsSync(resolve(dir, "massu.config.yaml"))) {
471
+ if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
409
472
  return dir;
410
473
  }
411
474
  const parent = dirname(dir);
@@ -414,10 +477,10 @@ function findProjectRoot() {
414
477
  }
415
478
  dir = cwd;
416
479
  while (true) {
417
- if (existsSync(resolve(dir, "package.json"))) {
480
+ if (existsSync2(resolve2(dir, "package.json"))) {
418
481
  return dir;
419
482
  }
420
- if (existsSync(resolve(dir, ".git"))) {
483
+ if (existsSync2(resolve2(dir, ".git"))) {
421
484
  return dir;
422
485
  }
423
486
  const parent = dirname(dir);
@@ -435,10 +498,10 @@ function getProjectRoot() {
435
498
  function getConfig() {
436
499
  if (_config) return _config;
437
500
  const root = getProjectRoot();
438
- const configPath = resolve(root, "massu.config.yaml");
501
+ const configPath = resolve2(root, "massu.config.yaml");
439
502
  let rawYaml = {};
440
- if (existsSync(configPath)) {
441
- const content = readFileSync(configPath, "utf-8");
503
+ if (existsSync2(configPath)) {
504
+ const content = readFileSync2(configPath, "utf-8");
442
505
  rawYaml = parseYaml(content) ?? {};
443
506
  }
444
507
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -455,7 +518,7 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
455
518
  );
456
519
  }
457
520
  const parsed = result.data;
458
- const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
521
+ const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve2(root, parsed.project.root);
459
522
  const fw = parsed.framework;
460
523
  let router = fw.router;
461
524
  let orm = fw.orm;
@@ -513,13 +576,19 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
513
576
  telemetry: parsed.telemetry,
514
577
  lsp: parsed.lsp
515
578
  };
516
- if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
579
+ const resolvedKey = resolveApiKey({ configApiKey: _config.cloud?.apiKey });
580
+ _resolvedApiKeySource = resolvedKey.source;
581
+ const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
582
+ if (resolvedKey.apiKey) {
517
583
  _config.cloud = {
518
584
  enabled: true,
519
585
  sync: { memory: true, analytics: true, audit: true },
520
586
  ..._config.cloud,
521
- apiKey: process.env.MASSU_API_KEY
587
+ apiKey: resolvedKey.apiKey,
588
+ endpoint: resolvedEndpoint
522
589
  };
590
+ } else if (_config.cloud) {
591
+ _config.cloud = { ..._config.cloud, endpoint: _config.cloud.endpoint ?? resolvedEndpoint };
523
592
  }
524
593
  return _config;
525
594
  }
@@ -528,34 +597,34 @@ function getResolvedPaths() {
528
597
  const root = getProjectRoot();
529
598
  const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
530
599
  return {
531
- codegraphDbPath: resolve(root, ".codegraph/codegraph.db"),
532
- dataDbPath: resolve(root, ".massu/data.db"),
533
- prismaSchemaPath: resolve(root, config.paths.schema ?? "prisma/schema.prisma"),
534
- rootRouterPath: resolve(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
535
- routersDir: resolve(root, config.paths.routers ?? "src/server/api/routers"),
536
- srcDir: resolve(root, config.paths.source),
600
+ codegraphDbPath: resolve2(root, ".codegraph/codegraph.db"),
601
+ dataDbPath: resolve2(root, ".massu/data.db"),
602
+ prismaSchemaPath: resolve2(root, config.paths.schema ?? "prisma/schema.prisma"),
603
+ rootRouterPath: resolve2(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
604
+ routersDir: resolve2(root, config.paths.routers ?? "src/server/api/routers"),
605
+ srcDir: resolve2(root, config.paths.source),
537
606
  pathAlias: Object.fromEntries(
538
607
  Object.entries(config.paths.aliases).map(([alias, target]) => [
539
608
  alias,
540
- resolve(root, target)
609
+ resolve2(root, target)
541
610
  ])
542
611
  ),
543
612
  extensions: [".ts", ".tsx", ".js", ".jsx"],
544
613
  indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
545
- patternsDir: resolve(root, claudeDirName, "patterns"),
546
- claudeMdPath: resolve(root, claudeDirName, "CLAUDE.md"),
547
- docsMapPath: resolve(root, ".massu/docs-map.json"),
548
- helpSitePath: resolve(root, "../" + config.project.name + "-help"),
549
- memoryDbPath: resolve(root, ".massu/memory.db"),
550
- knowledgeDbPath: resolve(root, ".massu/knowledge.db"),
551
- plansDir: resolve(root, "docs/plans"),
552
- docsDir: resolve(root, "docs"),
553
- claudeDir: resolve(root, claudeDirName),
554
- memoryDir: resolve(homedir(), claudeDirName, "projects", encodeMemoryDirName(root), "memory"),
555
- sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
556
- sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
557
- mcpJsonPath: resolve(root, ".mcp.json"),
558
- settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
614
+ patternsDir: resolve2(root, claudeDirName, "patterns"),
615
+ claudeMdPath: resolve2(root, claudeDirName, "CLAUDE.md"),
616
+ docsMapPath: resolve2(root, ".massu/docs-map.json"),
617
+ helpSitePath: resolve2(root, "../" + config.project.name + "-help"),
618
+ memoryDbPath: resolve2(root, ".massu/memory.db"),
619
+ knowledgeDbPath: resolve2(root, ".massu/knowledge.db"),
620
+ plansDir: resolve2(root, "docs/plans"),
621
+ docsDir: resolve2(root, "docs"),
622
+ claudeDir: resolve2(root, claudeDirName),
623
+ memoryDir: resolve2(homedir2(), claudeDirName, "projects", encodeMemoryDirName(root), "memory"),
624
+ sessionStatePath: resolve2(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
625
+ sessionArchivePath: resolve2(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
626
+ mcpJsonPath: resolve2(root, ".mcp.json"),
627
+ settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
559
628
  };
560
629
  }
561
630
 
@@ -563,8 +632,8 @@ function getResolvedPaths() {
563
632
  function getMemoryDb() {
564
633
  const dbPath = getResolvedPaths().memoryDbPath;
565
634
  const dir = dirname2(dbPath);
566
- if (!existsSync2(dir)) {
567
- mkdirSync(dir, { recursive: true });
635
+ if (!existsSync3(dir)) {
636
+ mkdirSync2(dir, { recursive: true });
568
637
  }
569
638
  const db = new Database(dbPath);
570
639
  db.pragma("journal_mode = WAL");
@@ -1392,14 +1461,14 @@ function safeParseJson(json, fallback) {
1392
1461
  }
1393
1462
  }
1394
1463
  function readStdin() {
1395
- return new Promise((resolve3) => {
1464
+ return new Promise((resolve4) => {
1396
1465
  let data = "";
1397
1466
  process.stdin.setEncoding("utf-8");
1398
1467
  process.stdin.on("data", (chunk) => {
1399
1468
  data += chunk;
1400
1469
  });
1401
- process.stdin.on("end", () => resolve3(data));
1402
- setTimeout(() => resolve3(data), 3e3);
1470
+ process.stdin.on("end", () => resolve4(data));
1471
+ setTimeout(() => resolve4(data), 3e3);
1403
1472
  });
1404
1473
  }
1405
1474
  main();