@massu/core 1.15.1 → 1.15.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.
@@ -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");
@@ -1274,14 +1343,14 @@ async function main() {
1274
1343
  process.exit(0);
1275
1344
  }
1276
1345
  function readStdin() {
1277
- return new Promise((resolve3) => {
1346
+ return new Promise((resolve4) => {
1278
1347
  let data = "";
1279
1348
  process.stdin.setEncoding("utf-8");
1280
1349
  process.stdin.on("data", (chunk) => {
1281
1350
  data += chunk;
1282
1351
  });
1283
- process.stdin.on("end", () => resolve3(data));
1284
- setTimeout(() => resolve3(data), 3e3);
1352
+ process.stdin.on("end", () => resolve4(data));
1353
+ setTimeout(() => resolve4(data), 3e3);
1285
1354
  });
1286
1355
  }
1287
1356
  main();
@@ -2,14 +2,78 @@
2
2
  import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
3
3
 
4
4
  // src/hooks/rule-enforcement-pipeline.ts
5
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
6
- import { basename, resolve as resolve2 } from "path";
5
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
6
+ import { basename, resolve as resolve3 } from "path";
7
7
 
8
8
  // src/config.ts
9
- import { resolve, dirname } from "path";
10
- import { existsSync, readFileSync } from "fs";
9
+ import { resolve as resolve2, dirname } from "path";
10
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
11
11
  import { parse as parseYaml } from "yaml";
12
12
  import { z } from "zod";
13
+
14
+ // src/credentials.ts
15
+ import { homedir } from "os";
16
+ import { resolve } from "path";
17
+ import {
18
+ existsSync,
19
+ readFileSync,
20
+ writeFileSync,
21
+ mkdirSync,
22
+ rmSync,
23
+ chmodSync
24
+ } from "fs";
25
+ var MASSU_ENV_API_KEY = "MASSU_API_KEY";
26
+ var MASSU_ENV_CLOUD_ENDPOINT = "MASSU_CLOUD_ENDPOINT";
27
+ var DEFAULT_CLOUD_ENDPOINT = "https://api.massu.ai/v1";
28
+ function credentialsDir(home = homedir()) {
29
+ return resolve(home, ".massu");
30
+ }
31
+ function credentialsPath(home = homedir()) {
32
+ return resolve(credentialsDir(home), "credentials");
33
+ }
34
+ function readUserCredentials(home = homedir()) {
35
+ try {
36
+ const p = credentialsPath(home);
37
+ if (!existsSync(p)) return void 0;
38
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
39
+ const key = typeof parsed?.apiKey === "string" ? parsed.apiKey.trim() : "";
40
+ return key.length > 0 ? key : void 0;
41
+ } catch {
42
+ return void 0;
43
+ }
44
+ }
45
+ function isUnresolvedLiteral(v) {
46
+ return /^\$\{[^}]+\}$/.test(v);
47
+ }
48
+ function resolveApiKey(opts = {}) {
49
+ const env = opts.env ?? process.env;
50
+ const home = opts.home ?? homedir();
51
+ const cfg = typeof opts.configApiKey === "string" ? opts.configApiKey.trim() : "";
52
+ if (cfg.length > 0 && !isUnresolvedLiteral(cfg)) {
53
+ return { apiKey: cfg, source: "config" };
54
+ }
55
+ const envRaw = env[MASSU_ENV_API_KEY];
56
+ const envKey = typeof envRaw === "string" ? envRaw.trim() : "";
57
+ if (envKey.length > 0) {
58
+ return { apiKey: envKey, source: "env" };
59
+ }
60
+ const fileKey = readUserCredentials(home);
61
+ if (fileKey) {
62
+ return { apiKey: fileKey, source: "user-file" };
63
+ }
64
+ return { source: "none" };
65
+ }
66
+ function resolveEndpoint(opts = {}) {
67
+ const env = opts.env ?? process.env;
68
+ const cfg = typeof opts.configEndpoint === "string" ? opts.configEndpoint.trim() : "";
69
+ if (cfg.length > 0) return cfg;
70
+ const envRaw = env[MASSU_ENV_CLOUD_ENDPOINT];
71
+ const envEp = typeof envRaw === "string" ? envRaw.trim() : "";
72
+ if (envEp.length > 0) return envEp;
73
+ return DEFAULT_CLOUD_ENDPOINT;
74
+ }
75
+
76
+ // src/config.ts
13
77
  var DomainConfigSchema = z.object({
14
78
  name: z.string().default("Unknown"),
15
79
  routers: z.array(z.string()).default([]),
@@ -391,12 +455,13 @@ var RawConfigSchema = z.object({
391
455
  lsp: LSPConfigSchema.optional()
392
456
  }).passthrough();
393
457
  var _config = null;
458
+ var _resolvedApiKeySource = "none";
394
459
  var _projectRoot = null;
395
460
  function findProjectRoot() {
396
461
  const cwd = process.cwd();
397
462
  let dir = cwd;
398
463
  while (true) {
399
- if (existsSync(resolve(dir, "massu.config.yaml"))) {
464
+ if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
400
465
  return dir;
401
466
  }
402
467
  const parent = dirname(dir);
@@ -405,10 +470,10 @@ function findProjectRoot() {
405
470
  }
406
471
  dir = cwd;
407
472
  while (true) {
408
- if (existsSync(resolve(dir, "package.json"))) {
473
+ if (existsSync2(resolve2(dir, "package.json"))) {
409
474
  return dir;
410
475
  }
411
- if (existsSync(resolve(dir, ".git"))) {
476
+ if (existsSync2(resolve2(dir, ".git"))) {
412
477
  return dir;
413
478
  }
414
479
  const parent = dirname(dir);
@@ -426,10 +491,10 @@ function getProjectRoot() {
426
491
  function getConfig() {
427
492
  if (_config) return _config;
428
493
  const root = getProjectRoot();
429
- const configPath = resolve(root, "massu.config.yaml");
494
+ const configPath = resolve2(root, "massu.config.yaml");
430
495
  let rawYaml = {};
431
- if (existsSync(configPath)) {
432
- const content = readFileSync(configPath, "utf-8");
496
+ if (existsSync2(configPath)) {
497
+ const content = readFileSync2(configPath, "utf-8");
433
498
  rawYaml = parseYaml(content) ?? {};
434
499
  }
435
500
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -446,7 +511,7 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
446
511
  );
447
512
  }
448
513
  const parsed = result.data;
449
- const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
514
+ const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve2(root, parsed.project.root);
450
515
  const fw = parsed.framework;
451
516
  let router = fw.router;
452
517
  let orm = fw.orm;
@@ -504,13 +569,19 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
504
569
  telemetry: parsed.telemetry,
505
570
  lsp: parsed.lsp
506
571
  };
507
- if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
572
+ const resolvedKey = resolveApiKey({ configApiKey: _config.cloud?.apiKey });
573
+ _resolvedApiKeySource = resolvedKey.source;
574
+ const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
575
+ if (resolvedKey.apiKey) {
508
576
  _config.cloud = {
509
577
  enabled: true,
510
578
  sync: { memory: true, analytics: true, audit: true },
511
579
  ..._config.cloud,
512
- apiKey: process.env.MASSU_API_KEY
580
+ apiKey: resolvedKey.apiKey,
581
+ endpoint: resolvedEndpoint
513
582
  };
583
+ } else if (_config.cloud) {
584
+ _config.cloud = { ..._config.cloud, endpoint: _config.cloud.endpoint ?? resolvedEndpoint };
514
585
  }
515
586
  return _config;
516
587
  }
@@ -549,7 +620,7 @@ async function main() {
549
620
  process.exit(0);
550
621
  return;
551
622
  }
552
- if (!existsSync2(filePath)) {
623
+ if (!existsSync3(filePath)) {
553
624
  process.exit(0);
554
625
  return;
555
626
  }
@@ -557,18 +628,18 @@ async function main() {
557
628
  process.exit(0);
558
629
  return;
559
630
  }
560
- const content = readFileSync2(filePath, "utf-8");
631
+ const content = readFileSync3(filePath, "utf-8");
561
632
  const nameMatch = content.match(/^name:\s*(.+)/m);
562
633
  const descMatch = content.match(/^description:\s*(.+)/m);
563
634
  const ruleName = nameMatch?.[1]?.trim() ?? fileName;
564
635
  const ruleDesc = descMatch?.[1]?.trim() ?? "";
565
- const enforcementDirAbs = resolve2(root, enforcementDir);
636
+ const enforcementDirAbs = resolve3(root, enforcementDir);
566
637
  let hasEnforcement = false;
567
- if (existsSync2(enforcementDirAbs)) {
638
+ if (existsSync3(enforcementDirAbs)) {
568
639
  const hookFiles = readdirSync(enforcementDirAbs).filter((f) => f.endsWith(".sh") || f.endsWith(".ts") || f.endsWith(".js"));
569
640
  for (const hookFile of hookFiles) {
570
641
  try {
571
- const hookContent = readFileSync2(resolve2(enforcementDirAbs, hookFile), "utf-8");
642
+ const hookContent = readFileSync3(resolve3(enforcementDirAbs, hookFile), "utf-8");
572
643
  if (hookContent.includes(fileName)) {
573
644
  hasEnforcement = true;
574
645
  break;
@@ -624,14 +695,14 @@ async function main() {
624
695
  process.exit(0);
625
696
  }
626
697
  function readStdin() {
627
- return new Promise((resolve3) => {
698
+ return new Promise((resolve4) => {
628
699
  let data = "";
629
700
  process.stdin.setEncoding("utf-8");
630
701
  process.stdin.on("data", (chunk) => {
631
702
  data += chunk;
632
703
  });
633
- process.stdin.on("end", () => resolve3(data));
634
- setTimeout(() => resolve3(data), 3e3);
704
+ process.stdin.on("end", () => resolve4(data));
705
+ setTimeout(() => resolve4(data), 3e3);
635
706
  });
636
707
  }
637
708
  main();