@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.
- package/dist/index.js +101 -57
- 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
|
|
13
|
-
import
|
|
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
|
|
90
|
-
if (
|
|
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
|
-
|
|
247
|
-
const gitignorePath =
|
|
248
|
-
const gitignoreContent =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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(
|
|
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);
|