3meel 0.1.0

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 +726 -0
  2. package/package.json +44 -0
package/dist/index.js ADDED
@@ -0,0 +1,726 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import * as readline from "readline";
6
+ import { stdin as input, stdout as output } from "process";
7
+ import fs2 from "fs";
8
+ import path2 from "path";
9
+
10
+ // src/config.ts
11
+ import fs from "fs";
12
+
13
+ // src/paths.ts
14
+ import { homedir } from "os";
15
+ import path from "path";
16
+ function configDir() {
17
+ const xdg = process.env.XDG_CONFIG_HOME;
18
+ if (xdg) return path.join(xdg, "3meel");
19
+ return path.join(homedir(), ".config", "3meel");
20
+ }
21
+ function configFilePath() {
22
+ return path.join(configDir(), "config.json");
23
+ }
24
+
25
+ // src/config.ts
26
+ var DEFAULT_ENVIRONMENTS = {
27
+ local: {
28
+ apiBase: "http://localhost:3000",
29
+ mcpBase: "http://localhost:3002"
30
+ },
31
+ staging: {
32
+ apiBase: "https://staging.3meel.ai",
33
+ mcpBase: "https://mcp.staging.3meel.ai"
34
+ },
35
+ prod: {
36
+ apiBase: "https://3meel.ai",
37
+ mcpBase: "https://mcp.3meel.ai"
38
+ }
39
+ };
40
+ function mergeWithDefaults(data) {
41
+ return {
42
+ defaultEnv: data?.defaultEnv ?? "prod",
43
+ environments: { ...DEFAULT_ENVIRONMENTS, ...data?.environments ?? {} },
44
+ bearerTokens: { ...data?.bearerTokens ?? {} }
45
+ };
46
+ }
47
+ function loadConfig() {
48
+ try {
49
+ const raw = fs.readFileSync(configFilePath(), "utf8");
50
+ const parsed = JSON.parse(raw);
51
+ return mergeWithDefaults(parsed);
52
+ } catch {
53
+ return mergeWithDefaults(null);
54
+ }
55
+ }
56
+ function saveConfig(cfg) {
57
+ fs.mkdirSync(configDir(), { recursive: true });
58
+ fs.writeFileSync(configFilePath(), JSON.stringify(cfg, null, 2), "utf8");
59
+ }
60
+ function getActiveProfile(cfg, envName2) {
61
+ const name = envName2 ?? cfg.defaultEnv;
62
+ const profile = cfg.environments[name];
63
+ if (!profile) {
64
+ throw new Error(
65
+ `Unknown environment "${name}". Define it in ${configFilePath()} or use --env local|staging|prod`
66
+ );
67
+ }
68
+ return { name, profile };
69
+ }
70
+ function getBearerToken(cfg, envName2) {
71
+ return cfg.bearerTokens?.[envName2];
72
+ }
73
+ function setBearerToken(cfg, envName2, token) {
74
+ const next = { ...cfg, bearerTokens: { ...cfg.bearerTokens ?? {} } };
75
+ if (token === void 0) {
76
+ delete next.bearerTokens[envName2];
77
+ } else {
78
+ next.bearerTokens[envName2] = token;
79
+ }
80
+ return next;
81
+ }
82
+
83
+ // src/client.ts
84
+ var ApiError = class extends Error {
85
+ constructor(message, status, body) {
86
+ super(message);
87
+ this.status = status;
88
+ this.body = body;
89
+ this.name = "ApiError";
90
+ }
91
+ };
92
+ function apiUrl(profile, path3) {
93
+ const base = profile.apiBase.replace(/\/$/, "");
94
+ const p = path3.startsWith("/") ? path3 : `/${path3}`;
95
+ return `${base}${p}`;
96
+ }
97
+ async function apiFetch(opts, path3, init = {}) {
98
+ const headers = new Headers(init.headers);
99
+ headers.set("Accept", "application/json");
100
+ headers.set("Origin", opts.profile.apiBase);
101
+ if (opts.bearerToken) {
102
+ headers.set("Authorization", `Bearer ${opts.bearerToken}`);
103
+ }
104
+ if (init.body && typeof init.body === "string" && !headers.has("Content-Type")) {
105
+ headers.set("Content-Type", "application/json");
106
+ }
107
+ const url = apiUrl(opts.profile, path3);
108
+ const res = await fetch(url, { ...init, headers });
109
+ const text = await res.text();
110
+ let parsed = null;
111
+ if (text) {
112
+ try {
113
+ parsed = JSON.parse(text);
114
+ } catch {
115
+ parsed = text;
116
+ }
117
+ }
118
+ if (!res.ok) {
119
+ const msg = typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error?.message === "string" ? parsed.error.message : `HTTP ${res.status} ${res.statusText}`;
120
+ throw new ApiError(msg, res.status, parsed);
121
+ }
122
+ return parsed;
123
+ }
124
+ async function signInEmail(profile, email, password) {
125
+ const url = apiUrl(profile, "/api/auth/sign-in/email");
126
+ const res = await fetch(url, {
127
+ method: "POST",
128
+ headers: { "Content-Type": "application/json", Accept: "application/json", Origin: profile.apiBase },
129
+ body: JSON.stringify({ email, password })
130
+ });
131
+ const text = await res.text();
132
+ let body = null;
133
+ if (text) {
134
+ try {
135
+ body = JSON.parse(text);
136
+ } catch {
137
+ body = text;
138
+ }
139
+ }
140
+ if (!res.ok) {
141
+ const msg = typeof body === "object" && body !== null && "message" in body && typeof body.message === "string" ? body.message : `Sign-in failed (${res.status})`;
142
+ throw new ApiError(msg, res.status, body);
143
+ }
144
+ const token = res.headers.get("set-auth-token") ?? res.headers.get("Set-Auth-Token") ?? null;
145
+ return { token, raw: body };
146
+ }
147
+
148
+ // src/output.ts
149
+ var Out = class {
150
+ constructor(mode2, isInteractive) {
151
+ this.mode = mode2;
152
+ this.isInteractive = isInteractive;
153
+ }
154
+ /** Agent-safe: no color, no decorative noise */
155
+ get useColor() {
156
+ return this.mode === "human" && this.isInteractive && !process.env.NO_COLOR;
157
+ }
158
+ json(obj) {
159
+ console.log(JSON.stringify(obj, null, this.mode === "human" ? 2 : void 0));
160
+ }
161
+ lineNdjson(obj) {
162
+ console.log(JSON.stringify(obj));
163
+ }
164
+ log(msg) {
165
+ if (this.mode !== "human") return;
166
+ console.log(msg);
167
+ }
168
+ err(msg) {
169
+ console.error(msg);
170
+ }
171
+ table(rows, columns) {
172
+ if (this.mode !== "human") {
173
+ this.json({ rows });
174
+ return;
175
+ }
176
+ if (rows.length === 0) {
177
+ console.log("(empty)");
178
+ return;
179
+ }
180
+ const widths = columns.map(
181
+ (c) => Math.max(c.length, ...rows.map((r) => String(r[c] ?? "").length))
182
+ );
183
+ const sep = columns.map((c, i) => "-".repeat(widths[i])).join("-+-");
184
+ console.log(
185
+ columns.map((c, i) => c.padEnd(widths[i])).join(" | ")
186
+ );
187
+ console.log(sep);
188
+ for (const r of rows) {
189
+ console.log(
190
+ columns.map((c, i) => String(r[c] ?? "").padEnd(widths[i])).join(" | ")
191
+ );
192
+ }
193
+ }
194
+ };
195
+ function stdoutIsTTY() {
196
+ return Boolean(process.stdout.isTTY);
197
+ }
198
+
199
+ // src/index.ts
200
+ var EXIT = { ok: 0, error: 1, usage: 2, auth: 3 };
201
+ function globalOpts(cmd) {
202
+ return cmd.optsWithGlobals();
203
+ }
204
+ function mode(g, tty) {
205
+ if (g.ndjson) return "ndjson";
206
+ if (g.json) return "json";
207
+ return "human";
208
+ }
209
+ function envName(cfg, g) {
210
+ return g.env ?? cfg.defaultEnv;
211
+ }
212
+ async function readPassword() {
213
+ return new Promise((resolve, reject) => {
214
+ const stdin = input;
215
+ const chunks = [];
216
+ if (stdin.setRawMode) stdin.setRawMode(true);
217
+ const onData = (b) => {
218
+ const s = b.toString("utf8");
219
+ const code = s.charCodeAt(0);
220
+ if (code === 3) {
221
+ reject(new Error("interrupted"));
222
+ }
223
+ if (s === "\n" || s === "\r") {
224
+ if (stdin.setRawMode) stdin.setRawMode(false);
225
+ stdin.removeListener("data", onData);
226
+ output.write("\n");
227
+ resolve(Buffer.concat(chunks).toString("utf8"));
228
+ return;
229
+ }
230
+ if (code === 127 || code === 8) {
231
+ chunks.pop();
232
+ return;
233
+ }
234
+ chunks.push(b);
235
+ };
236
+ stdin.on("data", onData);
237
+ });
238
+ }
239
+ function requireAuth(cfg, en, out) {
240
+ const { profile, name } = getActiveProfile(cfg, en);
241
+ const token = getBearerToken(cfg, name);
242
+ if (!token) {
243
+ if (out.mode === "human") {
244
+ output.write("Not authenticated. Run `3meel auth login`.\n");
245
+ } else {
246
+ console.log(
247
+ JSON.stringify({
248
+ error: {
249
+ code: "unauthorized",
250
+ hint: "3meel auth login"
251
+ }
252
+ })
253
+ );
254
+ }
255
+ process.exit(EXIT.auth);
256
+ }
257
+ return { profile, bearerToken: token };
258
+ }
259
+ var TERMINAL_DOC = /* @__PURE__ */ new Set(["complete", "failed"]);
260
+ async function main() {
261
+ const tty = stdoutIsTTY() && Boolean(input.isTTY);
262
+ const program = new Command();
263
+ program.name("3meel");
264
+ program.description(
265
+ "3meel CLI \u2014 terminal interface to the same APIs as the web app. Use --json or --ndjson for agents."
266
+ );
267
+ program.configureHelp({ sortSubcommands: true });
268
+ program.option("--env <name>", "Environment: local | staging | prod (or a custom name from config)").option("--json", "Structured JSON output").option("--ndjson", "NDJSON events (for watch)");
269
+ const authCmd = program.command("auth").description("Authenticate and inspect session");
270
+ authCmd.command("login").description("Sign in with email/password; stores Bearer token").option("--email <email>").option("--password <password>").action(async function(opts, cmd) {
271
+ const g = globalOpts(cmd);
272
+ const out = new Out(mode(g, tty), tty);
273
+ const cfg = loadConfig();
274
+ const { name, profile } = getActiveProfile(cfg, envName(cfg, g));
275
+ const email = opts.email?.trim() || process.env.THREE_MEEL_EMAIL?.trim() || (tty && out.mode === "human" ? await new Promise((resolve) => {
276
+ const rl = readline.createInterface({ input, output });
277
+ rl.question("Email: ", (answer) => {
278
+ rl.close();
279
+ resolve(answer);
280
+ });
281
+ }) : "");
282
+ if (!email) {
283
+ out.err("Email required: --email or THREE_MEEL_EMAIL");
284
+ process.exit(EXIT.usage);
285
+ }
286
+ let password = opts.password || process.env.THREE_MEEL_PASSWORD || "";
287
+ if (!password && tty && out.mode === "human") {
288
+ output.write("Password: ");
289
+ password = await readPassword();
290
+ }
291
+ if (!password) {
292
+ out.err("Password: --password, THREE_MEEL_PASSWORD, or interactive");
293
+ process.exit(EXIT.usage);
294
+ }
295
+ try {
296
+ const { token, raw } = await signInEmail(profile, email, password);
297
+ if (!token) {
298
+ if (out.mode === "human") {
299
+ out.err(
300
+ "No Set-Auth-Token header. Ensure Better Auth bearer plugin is enabled on the server."
301
+ );
302
+ } else {
303
+ out.json({ error: { code: "no_token", raw } });
304
+ }
305
+ process.exit(EXIT.error);
306
+ }
307
+ let next = setBearerToken(cfg, name, token);
308
+ next = { ...next, defaultEnv: name };
309
+ saveConfig(next);
310
+ if (out.mode === "human") out.log(`Logged in (${name}) as ${email}`);
311
+ else
312
+ out.json({
313
+ ok: true,
314
+ environment: name,
315
+ user: raw?.user
316
+ });
317
+ } catch (e) {
318
+ if (e instanceof ApiError) {
319
+ if (out.mode === "human") out.err(e.message);
320
+ else out.json({ error: { message: e.message, body: e.body } });
321
+ process.exit(EXIT.auth);
322
+ }
323
+ throw e;
324
+ }
325
+ });
326
+ authCmd.command("logout").action(async function(_, cmd) {
327
+ const g = globalOpts(cmd);
328
+ const out = new Out(mode(g, tty), tty);
329
+ const cfg = loadConfig();
330
+ const { name } = getActiveProfile(cfg, envName(cfg, g));
331
+ saveConfig(setBearerToken(cfg, name, void 0));
332
+ if (out.mode === "human") out.log(`Logged out (${name})`);
333
+ else out.json({ ok: true, environment: name });
334
+ });
335
+ authCmd.command("whoami").action(async function(_, cmd) {
336
+ const g = globalOpts(cmd);
337
+ const out = new Out(mode(g, tty), tty);
338
+ const cfg = loadConfig();
339
+ const en = envName(cfg, g);
340
+ const c = requireAuth(cfg, en, out);
341
+ try {
342
+ const data = await apiFetch(
343
+ c,
344
+ "/api/cli/v1/whoami"
345
+ );
346
+ if (out.mode === "human") {
347
+ out.log(JSON.stringify(data.user, null, 2));
348
+ out.log(`Environment: ${getActiveProfile(cfg, en).name}`);
349
+ } else out.json({ ...data, environment: getActiveProfile(cfg, en).name });
350
+ } catch (e) {
351
+ if (e instanceof ApiError && e.status === 401) process.exit(EXIT.auth);
352
+ throw e;
353
+ }
354
+ });
355
+ authCmd.command("environment").alias("env").description("Show resolved API and MCP base URLs").action(async function(_, cmd) {
356
+ const g = globalOpts(cmd);
357
+ const out = new Out(mode(g, tty), tty);
358
+ const cfg = loadConfig();
359
+ const { name, profile } = getActiveProfile(cfg, envName(cfg, g));
360
+ if (out.mode === "human") {
361
+ out.log(`${name}
362
+ API ${profile.apiBase}
363
+ MCP ${profile.mcpBase}`);
364
+ } else {
365
+ out.json({
366
+ environment: name,
367
+ apiBase: profile.apiBase,
368
+ mcpBase: profile.mcpBase
369
+ });
370
+ }
371
+ });
372
+ const kb = program.command("kb").description("Knowledge bases");
373
+ kb.command("list").action(async function(_, cmd) {
374
+ const g = globalOpts(cmd);
375
+ const out = new Out(mode(g, tty), tty);
376
+ const cfg = loadConfig();
377
+ const c = requireAuth(cfg, envName(cfg, g), out);
378
+ const { knowledgeBases } = await apiFetch(c, "/api/cli/v1/kbs");
379
+ if (out.mode === "human") {
380
+ out.table(
381
+ knowledgeBases.map((k) => ({
382
+ id: k.id,
383
+ name: k.name,
384
+ documents: k.documentCount,
385
+ createdAt: k.createdAt
386
+ })),
387
+ ["id", "name", "documents", "createdAt"]
388
+ );
389
+ } else out.json({ knowledgeBases });
390
+ });
391
+ kb.command("create").argument("<name>", "Knowledge base name").action(async function(name, _opts, cmd) {
392
+ const g = globalOpts(cmd);
393
+ const out = new Out(mode(g, tty), tty);
394
+ const cfg = loadConfig();
395
+ const c = requireAuth(cfg, envName(cfg, g), out);
396
+ const { knowledgeBase } = await apiFetch(
397
+ c,
398
+ "/api/cli/v1/kbs",
399
+ { method: "POST", body: JSON.stringify({ name }) }
400
+ );
401
+ if (out.mode === "human") out.log(JSON.stringify(knowledgeBase, null, 2));
402
+ else out.json({ knowledgeBase });
403
+ });
404
+ kb.command("get").argument("<kbId>", "Knowledge base UUID").action(async function(kbId, _opts, cmd) {
405
+ const g = globalOpts(cmd);
406
+ const out = new Out(mode(g, tty), tty);
407
+ const cfg = loadConfig();
408
+ const c = requireAuth(cfg, envName(cfg, g), out);
409
+ const { knowledgeBase } = await apiFetch(
410
+ c,
411
+ `/api/cli/v1/kbs/${kbId}`
412
+ );
413
+ if (out.mode === "human") out.log(JSON.stringify(knowledgeBase, null, 2));
414
+ else out.json({ knowledgeBase });
415
+ });
416
+ kb.command("delete").argument("<kbId>", "Knowledge base UUID").action(async function(kbId, _opts, cmd) {
417
+ const g = globalOpts(cmd);
418
+ const out = new Out(mode(g, tty), tty);
419
+ const cfg = loadConfig();
420
+ const c = requireAuth(cfg, envName(cfg, g), out);
421
+ await apiFetch(c, `/api/cli/v1/kbs/${kbId}`, { method: "DELETE" });
422
+ if (out.mode === "human") out.log("Deleted.");
423
+ else out.json({ deleted: true, kbId });
424
+ });
425
+ const docs = program.command("docs").description("Documents (PDF)");
426
+ docs.command("list").requiredOption("--kb <uuid>", "Knowledge base id").action(async function(opts, cmd) {
427
+ const g = globalOpts(cmd);
428
+ const out = new Out(mode(g, tty), tty);
429
+ const cfg = loadConfig();
430
+ const c = requireAuth(cfg, envName(cfg, g), out);
431
+ const { documents } = await apiFetch(c, `/api/cli/v1/kbs/${opts.kb}/documents`);
432
+ if (out.mode === "human") {
433
+ out.table(
434
+ documents.map((d) => ({
435
+ id: d.id,
436
+ name: d.name,
437
+ status: d.status,
438
+ error: d.errorMessage ?? "",
439
+ createdAt: d.createdAt
440
+ })),
441
+ ["id", "name", "status", "error", "createdAt"]
442
+ );
443
+ } else out.json({ documents });
444
+ });
445
+ docs.command("upload").argument("<file>", "Path to PDF").requiredOption("--kb <uuid>", "Knowledge base id").action(async function(file, opts, cmd) {
446
+ const g = globalOpts(cmd);
447
+ const out = new Out(mode(g, tty), tty);
448
+ const cfg = loadConfig();
449
+ const c = requireAuth(cfg, envName(cfg, g), out);
450
+ const abs = path2.resolve(file);
451
+ if (!abs.toLowerCase().endsWith(".pdf")) {
452
+ out.err("Only .pdf files are supported.");
453
+ process.exit(EXIT.usage);
454
+ }
455
+ const st = fs2.statSync(abs);
456
+ const fileName = path2.basename(abs);
457
+ const presign = await apiFetch(c, "/api/cli/v1/upload/presign", {
458
+ method: "POST",
459
+ body: JSON.stringify({
460
+ knowledgeBaseId: opts.kb,
461
+ fileName,
462
+ fileSize: st.size
463
+ })
464
+ });
465
+ const buf = fs2.readFileSync(abs);
466
+ const put = await fetch(presign.presignedUrl, {
467
+ method: "PUT",
468
+ headers: { "Content-Type": "application/pdf" },
469
+ body: buf
470
+ });
471
+ if (!put.ok) {
472
+ const t = await put.text();
473
+ if (out.mode === "human") out.err(`Upload to storage failed: ${put.status} ${t}`);
474
+ else out.json({ error: { code: "upload_failed", status: put.status, body: t } });
475
+ process.exit(EXIT.error);
476
+ }
477
+ await apiFetch(c, "/api/cli/v1/upload/confirm", {
478
+ method: "POST",
479
+ body: JSON.stringify({ documentId: presign.documentId })
480
+ });
481
+ if (out.mode === "human") {
482
+ out.log(`Uploaded. documentId=${presign.documentId}`);
483
+ out.log(`Run: 3meel watch ${presign.documentId}`);
484
+ } else {
485
+ out.json({
486
+ documentId: presign.documentId,
487
+ status: "queued",
488
+ knowledgeBaseId: opts.kb
489
+ });
490
+ }
491
+ });
492
+ docs.command("get").argument("<documentId>", "Document UUID").action(async function(documentId, _opts, cmd) {
493
+ const g = globalOpts(cmd);
494
+ const out = new Out(mode(g, tty), tty);
495
+ const cfg = loadConfig();
496
+ const c = requireAuth(cfg, envName(cfg, g), out);
497
+ const { document } = await apiFetch(
498
+ c,
499
+ `/api/cli/v1/documents/${documentId}`
500
+ );
501
+ if (out.mode === "human") out.log(JSON.stringify(document, null, 2));
502
+ else out.json({ document });
503
+ });
504
+ docs.command("delete").argument("<documentId>", "Document UUID").action(async function(documentId, _opts, cmd) {
505
+ const g = globalOpts(cmd);
506
+ const out = new Out(mode(g, tty), tty);
507
+ const cfg = loadConfig();
508
+ const c = requireAuth(cfg, envName(cfg, g), out);
509
+ await apiFetch(c, `/api/cli/v1/documents/${documentId}`, { method: "DELETE" });
510
+ if (out.mode === "human") out.log("Deleted.");
511
+ else out.json({ deleted: true, documentId });
512
+ });
513
+ docs.command("retry").argument("<documentId>", "Document UUID (failed only)").action(async function(documentId, _opts, cmd) {
514
+ const g = globalOpts(cmd);
515
+ const out = new Out(mode(g, tty), tty);
516
+ const cfg = loadConfig();
517
+ const c = requireAuth(cfg, envName(cfg, g), out);
518
+ const r = await apiFetch(
519
+ c,
520
+ `/api/cli/v1/documents/${documentId}/retry`,
521
+ { method: "POST", body: "{}" }
522
+ );
523
+ if (out.mode === "human") out.log("Re-queued for processing.");
524
+ else out.json(r);
525
+ });
526
+ program.command("query").argument("<question>", "Question to ask").requiredOption("--kb <uuid>", "Knowledge base id").action(async function(question, opts, cmd) {
527
+ const g = globalOpts(cmd);
528
+ const out = new Out(mode(g, tty), tty);
529
+ const cfg = loadConfig();
530
+ const c = requireAuth(cfg, envName(cfg, g), out);
531
+ const data = await apiFetch(c, "/api/cli/v1/query", {
532
+ method: "POST",
533
+ body: JSON.stringify({
534
+ knowledgeBaseId: opts.kb,
535
+ question
536
+ })
537
+ });
538
+ if (out.mode === "human") {
539
+ out.log(data.answer);
540
+ out.log("\n--- Citations ---");
541
+ for (const cit of data.citations) {
542
+ out.log(JSON.stringify(cit));
543
+ }
544
+ } else {
545
+ out.json(data);
546
+ }
547
+ });
548
+ const mcp = program.command("mcp").description("MCP URLs and client snippets");
549
+ mcp.command("url").description("Print unified MCP URL (needs API key)").option("--api-key <key>", "Account API key (rak_...) or THREE_MEEL_API_KEY").action(async function(opts, cmd) {
550
+ const g = globalOpts(cmd);
551
+ const out = new Out(mode(g, tty), tty);
552
+ const cfg = loadConfig();
553
+ const { profile } = getActiveProfile(cfg, envName(cfg, g));
554
+ const key = opts.apiKey || process.env.THREE_MEEL_API_KEY;
555
+ if (!key) {
556
+ out.err("Set --api-key or THREE_MEEL_API_KEY (plaintext rak_ key).");
557
+ process.exit(EXIT.usage);
558
+ }
559
+ const u = `${profile.mcpBase.replace(/\/$/, "")}/mcp/unified/${key}`;
560
+ if (out.mode === "human") out.log(u);
561
+ else out.json({ url: u, kind: "unified" });
562
+ });
563
+ mcp.command("config").argument("<client>", "claude | cursor | generic").option("--api-key <key>", "or THREE_MEEL_API_KEY").action(async function(client, opts, cmd) {
564
+ const g = globalOpts(cmd);
565
+ const out = new Out(mode(g, tty), tty);
566
+ const cfg = loadConfig();
567
+ const { profile } = getActiveProfile(cfg, envName(cfg, g));
568
+ const key = opts.apiKey || process.env.THREE_MEEL_API_KEY;
569
+ if (!key) {
570
+ out.err("Set --api-key or THREE_MEEL_API_KEY");
571
+ process.exit(EXIT.usage);
572
+ }
573
+ const url = `${profile.mcpBase.replace(/\/$/, "")}/mcp/unified/${key}`;
574
+ let block;
575
+ if (client === "claude") {
576
+ block = JSON.stringify(
577
+ {
578
+ mcpServers: {
579
+ "3meel": { type: "streamable-http", url }
580
+ }
581
+ },
582
+ null,
583
+ 2
584
+ );
585
+ } else if (client === "cursor") {
586
+ block = JSON.stringify(
587
+ { mcpServers: { "3meel": { url } } },
588
+ null,
589
+ 2
590
+ );
591
+ } else {
592
+ block = JSON.stringify({ type: "streamable-http", url }, null, 2);
593
+ }
594
+ if (out.mode === "human") {
595
+ out.log(block);
596
+ out.log("\nMerge into your existing MCP config if needed.");
597
+ } else {
598
+ out.json({ client, configText: block, url });
599
+ }
600
+ });
601
+ mcp.command("test").action(async function(_, cmd) {
602
+ const g = globalOpts(cmd);
603
+ const out = new Out(mode(g, tty), tty);
604
+ const cfg = loadConfig();
605
+ const { profile } = getActiveProfile(cfg, envName(cfg, g));
606
+ const healthUrl = `${profile.mcpBase.replace(/\/$/, "")}/health`;
607
+ try {
608
+ const res = await fetch(healthUrl);
609
+ const text = await res.text();
610
+ const ok = res.ok;
611
+ if (out.mode === "human") {
612
+ out.log(`${ok ? "OK" : "FAIL"} ${healthUrl} \u2192 ${res.status} ${text.slice(0, 200)}`);
613
+ } else {
614
+ out.json({ ok, status: res.status, url: healthUrl, body: text });
615
+ }
616
+ if (!ok) process.exit(EXIT.error);
617
+ } catch (e) {
618
+ const msg = e instanceof Error ? e.message : String(e);
619
+ if (out.mode === "human") out.err(`Request failed: ${msg}`);
620
+ else out.json({ ok: false, error: msg });
621
+ process.exit(EXIT.error);
622
+ }
623
+ });
624
+ program.command("watch").argument("<documentId>", "Document UUID").option("-i, --interval <ms>", "Poll interval", "2000").action(async function(documentId, opts, cmd) {
625
+ const g = globalOpts(cmd);
626
+ const out = new Out(mode(g, tty), tty);
627
+ const cfg = loadConfig();
628
+ const c = requireAuth(cfg, envName(cfg, g), out);
629
+ const ms = Math.max(500, Number(opts.interval) || 2e3);
630
+ let lastStatus = "";
631
+ for (; ; ) {
632
+ const { document } = await apiFetch(c, `/api/cli/v1/documents/${documentId}`);
633
+ const st = document.status;
634
+ if (st !== lastStatus) {
635
+ lastStatus = st;
636
+ if (out.mode === "ndjson") {
637
+ out.lineNdjson({
638
+ type: "document_status",
639
+ documentId,
640
+ status: st,
641
+ at: (/* @__PURE__ */ new Date()).toISOString()
642
+ });
643
+ } else if (out.mode === "human") {
644
+ out.log(`[${documentId}] ${st}`);
645
+ }
646
+ }
647
+ if (TERMINAL_DOC.has(st)) {
648
+ if (out.mode === "ndjson") {
649
+ out.lineNdjson({
650
+ type: "document_done",
651
+ documentId,
652
+ status: st,
653
+ errorMessage: document.errorMessage ?? null
654
+ });
655
+ } else if (out.mode === "human") {
656
+ if (st === "failed") {
657
+ out.err(
658
+ document.errorMessage ? `Failed: ${document.errorMessage}
659
+ Try: 3meel docs retry ${documentId}` : `Failed. Try: 3meel docs retry ${documentId}`
660
+ );
661
+ process.exit(EXIT.error);
662
+ }
663
+ out.log("Complete.");
664
+ } else {
665
+ out.json({
666
+ documentId,
667
+ status: st,
668
+ errorMessage: document.errorMessage ?? null
669
+ });
670
+ }
671
+ return;
672
+ }
673
+ await new Promise((r) => setTimeout(r, ms));
674
+ }
675
+ });
676
+ const conf = program.command("config").description("CLI config file and environment");
677
+ conf.command("path").action(async function(_, cmd) {
678
+ const g = globalOpts(cmd);
679
+ const out = new Out(mode(g, tty), tty);
680
+ const p = configFilePath();
681
+ if (out.mode === "human") out.log(p);
682
+ else out.json({ path: p });
683
+ });
684
+ conf.command("show").action(async function(_, cmd) {
685
+ const g = globalOpts(cmd);
686
+ const out = new Out(mode(g, tty), tty);
687
+ const cfg = loadConfig();
688
+ const redacted = {
689
+ ...cfg,
690
+ bearerTokens: cfg.bearerTokens ? Object.fromEntries(
691
+ Object.keys(cfg.bearerTokens).map((k) => [k, "***"])
692
+ ) : {}
693
+ };
694
+ if (out.mode === "human") out.log(JSON.stringify(redacted, null, 2));
695
+ else out.json(redacted);
696
+ });
697
+ conf.command("set-default-env").argument("<name>").action(async function(name, _opts, cmd) {
698
+ const g = globalOpts(cmd);
699
+ const out = new Out(mode(g, tty), tty);
700
+ const cfg = loadConfig();
701
+ if (!cfg.environments[name]) {
702
+ out.err(`Unknown environment "${name}". Edit ${configFilePath()} to add it.`);
703
+ process.exit(EXIT.usage);
704
+ }
705
+ saveConfig({ ...cfg, defaultEnv: name });
706
+ if (out.mode === "human") out.log(`Default environment: ${name}`);
707
+ else out.json({ ok: true, defaultEnv: name });
708
+ });
709
+ program.showHelpAfterError("(add --help for usage)");
710
+ try {
711
+ await program.parseAsync(process.argv);
712
+ } catch (e) {
713
+ const msg = e instanceof Error ? e.message : String(e);
714
+ console.error(msg);
715
+ process.exit(EXIT.usage);
716
+ }
717
+ }
718
+ main().catch((e) => {
719
+ if (e instanceof ApiError) {
720
+ console.error(e.message);
721
+ if (e.status === 401) process.exit(EXIT.auth);
722
+ process.exit(EXIT.error);
723
+ }
724
+ console.error(e);
725
+ process.exit(EXIT.error);
726
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "3meel",
3
+ "version": "0.1.0",
4
+ "description": "CLI for 3meel.ai — manage knowledge bases, upload documents, and query via MCP",
5
+ "type": "module",
6
+ "bin": {
7
+ "3meel": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup",
11
+ "typecheck": "tsc --noEmit",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "rag",
17
+ "knowledge-base",
18
+ "ai",
19
+ "claude",
20
+ "pdf",
21
+ "3meel"
22
+ ],
23
+ "author": "Ali Almadhoob",
24
+ "license": "MIT",
25
+ "homepage": "https://3meel.ai",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/AliProgrammin/rag-as-service-mcp"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.19.3",
32
+ "tsup": "^8.5.1",
33
+ "typescript": "5.9.3"
34
+ },
35
+ "dependencies": {
36
+ "commander": "^13.1.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=22.0.0"
40
+ },
41
+ "files": [
42
+ "dist"
43
+ ]
44
+ }