@crmy/cli 0.5.7 → 0.5.9

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 (2) hide show
  1. package/dist/index.js +101 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9,8 +9,8 @@ import path5 from "path";
9
9
  // src/commands/init.ts
10
10
  import { Command } from "commander";
11
11
  import crypto from "crypto";
12
- import fs from "fs";
13
- import path from "path";
12
+ import fs2 from "fs";
13
+ import path2 from "path";
14
14
 
15
15
  // src/spinner.ts
16
16
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -69,6 +69,67 @@ function createSpinner(initialMessage) {
69
69
  };
70
70
  }
71
71
 
72
+ // src/config.ts
73
+ import fs from "fs";
74
+ import path from "path";
75
+ import os from "os";
76
+ var CRMY_DIR = path.join(os.homedir(), ".crmy");
77
+ var GLOBAL_CONFIG = path.join(CRMY_DIR, "config.json");
78
+ var AUTH_FILE = path.join(CRMY_DIR, "auth.json");
79
+ function loadConfigFile(explicitPath) {
80
+ if (explicitPath) {
81
+ try {
82
+ return JSON.parse(fs.readFileSync(explicitPath, "utf-8"));
83
+ } catch {
84
+ return {};
85
+ }
86
+ }
87
+ const localPath = path.join(process.cwd(), ".crmy.json");
88
+ if (fs.existsSync(localPath)) {
89
+ try {
90
+ return JSON.parse(fs.readFileSync(localPath, "utf-8"));
91
+ } catch {
92
+ }
93
+ }
94
+ try {
95
+ return JSON.parse(fs.readFileSync(GLOBAL_CONFIG, "utf-8"));
96
+ } catch {
97
+ return {};
98
+ }
99
+ }
100
+ function saveConfigFile(config) {
101
+ const json = JSON.stringify(config, null, 2) + "\n";
102
+ fs.mkdirSync(CRMY_DIR, { recursive: true });
103
+ fs.writeFileSync(GLOBAL_CONFIG, json, { mode: 384 });
104
+ const localPath = path.join(process.cwd(), ".crmy.json");
105
+ fs.writeFileSync(localPath, json);
106
+ }
107
+ function loadAuthState() {
108
+ try {
109
+ const raw = fs.readFileSync(AUTH_FILE, "utf-8");
110
+ const state = JSON.parse(raw);
111
+ if (state.expiresAt && new Date(state.expiresAt) < /* @__PURE__ */ new Date()) {
112
+ return null;
113
+ }
114
+ return state;
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+ function saveAuthState(state) {
120
+ fs.mkdirSync(CRMY_DIR, { recursive: true });
121
+ fs.writeFileSync(AUTH_FILE, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
122
+ }
123
+ function clearAuthState() {
124
+ try {
125
+ fs.unlinkSync(AUTH_FILE);
126
+ } catch {
127
+ }
128
+ }
129
+ function resolveServerUrl() {
130
+ return process.env.CRMY_SERVER_URL ?? loadAuthState()?.serverUrl ?? loadConfigFile().serverUrl;
131
+ }
132
+
72
133
  // src/commands/init.ts
73
134
  function validateEmail(input) {
74
135
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.trim()) ? true : "Please enter a valid email address (e.g. you@example.com)";
@@ -86,8 +147,8 @@ function initCommand() {
86
147
  console.log(" Step 1 \u2014 Connect to your PostgreSQL database");
87
148
  console.log(" Step 2 \u2014 Create all CRMy tables (migrations)");
88
149
  console.log(" Step 3 \u2014 Create your admin account\n");
89
- const configPath = path.join(process.cwd(), ".crmy.json");
90
- if (fs.existsSync(configPath)) {
150
+ const localConfigPath = path2.join(process.cwd(), ".crmy.json");
151
+ if (fs2.existsSync(localConfigPath)) {
91
152
  console.log(" \x1B[33m\u26A0\x1B[0m A .crmy.json already exists in this directory.\n");
92
153
  const { overwrite } = await inquirer.prompt([
93
154
  {
@@ -243,11 +304,11 @@ function initCommand() {
243
304
  autoApproveSeconds: 0
244
305
  }
245
306
  };
246
- fs.writeFileSync(configPath, JSON.stringify(crmmyConfig, null, 2) + "\n");
247
- const gitignorePath = path.join(process.cwd(), ".gitignore");
248
- const gitignoreContent = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf-8") : "";
307
+ saveConfigFile(crmmyConfig);
308
+ const gitignorePath = path2.join(process.cwd(), ".gitignore");
309
+ const gitignoreContent = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf-8") : "";
249
310
  if (!gitignoreContent.includes(".crmy.json")) {
250
- fs.appendFileSync(gitignorePath, "\n.crmy.json\n");
311
+ fs2.appendFileSync(gitignorePath, "\n.crmy.json\n");
251
312
  }
252
313
  } catch (err) {
253
314
  spinner.fail("Failed to create admin account");
@@ -277,47 +338,6 @@ import { createRequire } from "module";
277
338
  import { fileURLToPath } from "url";
278
339
  import path4 from "path";
279
340
 
280
- // src/config.ts
281
- import fs2 from "fs";
282
- import path2 from "path";
283
- import os from "os";
284
- var AUTH_DIR = path2.join(os.homedir(), ".crmy");
285
- var AUTH_FILE = path2.join(AUTH_DIR, "auth.json");
286
- function loadConfigFile() {
287
- const configPath = path2.join(process.cwd(), ".crmy.json");
288
- try {
289
- const raw = fs2.readFileSync(configPath, "utf-8");
290
- return JSON.parse(raw);
291
- } catch {
292
- return {};
293
- }
294
- }
295
- function loadAuthState() {
296
- try {
297
- const raw = fs2.readFileSync(AUTH_FILE, "utf-8");
298
- const state = JSON.parse(raw);
299
- if (state.expiresAt && new Date(state.expiresAt) < /* @__PURE__ */ new Date()) {
300
- return null;
301
- }
302
- return state;
303
- } catch {
304
- return null;
305
- }
306
- }
307
- function saveAuthState(state) {
308
- fs2.mkdirSync(AUTH_DIR, { recursive: true });
309
- fs2.writeFileSync(AUTH_FILE, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
310
- }
311
- function clearAuthState() {
312
- try {
313
- fs2.unlinkSync(AUTH_FILE);
314
- } catch {
315
- }
316
- }
317
- function resolveServerUrl() {
318
- return process.env.CRMY_SERVER_URL ?? loadAuthState()?.serverUrl ?? loadConfigFile().serverUrl;
319
- }
320
-
321
341
  // src/banner.ts
322
342
  var ASCII_ART = `
323
343
 
@@ -537,19 +557,35 @@ function serverCommand() {
537
557
  import { Command as Command3 } from "commander";
538
558
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
539
559
  function mcpCommand() {
540
- return new Command3("mcp").description("Start stdio MCP server (for Claude Code)").action(async () => {
541
- const config = loadConfigFile();
560
+ return new Command3("mcp").description("Start stdio MCP server (for Claude Code)").option("--config <path>", "Explicit path to a .crmy.json config file").action(async (opts) => {
561
+ const config = loadConfigFile(opts.config);
542
562
  const databaseUrl = process.env.DATABASE_URL ?? config.database?.url;
543
563
  const apiKey = process.env.CRMY_API_KEY ?? config.apiKey;
544
564
  if (!databaseUrl) {
545
- console.error("No database URL. Run `crmy init` first or set DATABASE_URL.");
565
+ process.stderr.write(
566
+ "[crmy mcp] No database URL found.\n Run `npx @crmy/cli init` first, or pass --config <path>.\n Config lookup order:\n 1. process.cwd()/.crmy.json\n 2. ~/.crmy/config.json (written by init)\n"
567
+ );
546
568
  process.exit(1);
547
569
  }
548
570
  process.env.CRMY_IMPORTED = "1";
549
- const { initPool, createMcpServer } = await import("@crmy/server");
550
- const { runMigrations } = await import("@crmy/server");
551
- const db = await initPool(databaseUrl);
552
- await runMigrations(db);
571
+ const { initPool, createMcpServer, runMigrations } = await import("@crmy/server");
572
+ let db;
573
+ try {
574
+ db = await initPool(databaseUrl);
575
+ } catch (err) {
576
+ process.stderr.write(
577
+ `[crmy mcp] Failed to connect to database: ${err.message}
578
+ `
579
+ );
580
+ process.exit(1);
581
+ }
582
+ try {
583
+ await runMigrations(db);
584
+ } catch (err) {
585
+ process.stderr.write(`[crmy mcp] Migration error: ${err.message}
586
+ `);
587
+ process.exit(1);
588
+ }
553
589
  let actor = {
554
590
  tenant_id: "",
555
591
  actor_id: "cli-agent",
@@ -576,11 +612,19 @@ function mcpCommand() {
576
612
  }
577
613
  }
578
614
  if (!actor.tenant_id) {
579
- const tenantResult = await db.query("SELECT id FROM tenants WHERE slug = 'default' LIMIT 1");
615
+ const tenantResult = await db.query(
616
+ "SELECT id FROM tenants WHERE slug = 'default' LIMIT 1"
617
+ );
580
618
  if (tenantResult.rows.length > 0) {
581
619
  actor.tenant_id = tenantResult.rows[0].id;
582
620
  }
583
621
  }
622
+ if (!actor.tenant_id) {
623
+ process.stderr.write(
624
+ "[crmy mcp] No tenant found in database.\n Run `npx @crmy/cli init` to set up the database.\n"
625
+ );
626
+ process.exit(1);
627
+ }
584
628
  const server = createMcpServer(db, () => actor);
585
629
  const transport = new StdioServerTransport();
586
630
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crmy/cli",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "CRMy CLI — Local CLI + stdio MCP server",
5
5
  "type": "module",
6
6
  "files": [