memax-cli 0.0.1 → 0.1.0-alpha.10

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 (91) hide show
  1. package/.vscode/mcp.json +8 -0
  2. package/dist/commands/auth.d.ts +6 -0
  3. package/dist/commands/auth.d.ts.map +1 -0
  4. package/dist/commands/auth.js +62 -0
  5. package/dist/commands/auth.js.map +1 -0
  6. package/dist/commands/capture.d.ts +17 -0
  7. package/dist/commands/capture.d.ts.map +1 -0
  8. package/dist/commands/capture.js +61 -0
  9. package/dist/commands/capture.js.map +1 -0
  10. package/dist/commands/config.d.ts +3 -0
  11. package/dist/commands/config.d.ts.map +1 -0
  12. package/dist/commands/config.js +24 -0
  13. package/dist/commands/config.js.map +1 -0
  14. package/dist/commands/delete.d.ts +4 -0
  15. package/dist/commands/delete.d.ts.map +1 -0
  16. package/dist/commands/delete.js +45 -0
  17. package/dist/commands/delete.js.map +1 -0
  18. package/dist/commands/hook.d.ts +2 -0
  19. package/dist/commands/hook.d.ts.map +1 -0
  20. package/dist/commands/hook.js +189 -0
  21. package/dist/commands/hook.js.map +1 -0
  22. package/dist/commands/list.d.ts +8 -0
  23. package/dist/commands/list.d.ts.map +1 -0
  24. package/dist/commands/list.js +23 -0
  25. package/dist/commands/list.js.map +1 -0
  26. package/dist/commands/login.d.ts +4 -0
  27. package/dist/commands/login.d.ts.map +1 -0
  28. package/dist/commands/login.js +131 -0
  29. package/dist/commands/login.js.map +1 -0
  30. package/dist/commands/mcp.d.ts +3 -0
  31. package/dist/commands/mcp.d.ts.map +1 -0
  32. package/dist/commands/mcp.js +384 -0
  33. package/dist/commands/mcp.js.map +1 -0
  34. package/dist/commands/push.d.ts +11 -0
  35. package/dist/commands/push.d.ts.map +1 -0
  36. package/dist/commands/push.js +98 -0
  37. package/dist/commands/push.js.map +1 -0
  38. package/dist/commands/recall.d.ts +12 -0
  39. package/dist/commands/recall.d.ts.map +1 -0
  40. package/dist/commands/recall.js +107 -0
  41. package/dist/commands/recall.js.map +1 -0
  42. package/dist/commands/setup.d.ts +16 -0
  43. package/dist/commands/setup.d.ts.map +1 -0
  44. package/dist/commands/setup.js +869 -0
  45. package/dist/commands/setup.js.map +1 -0
  46. package/dist/commands/show.d.ts +2 -0
  47. package/dist/commands/show.d.ts.map +1 -0
  48. package/dist/commands/show.js +29 -0
  49. package/dist/commands/show.js.map +1 -0
  50. package/dist/commands/sync.d.ts +12 -0
  51. package/dist/commands/sync.d.ts.map +1 -0
  52. package/dist/commands/sync.js +414 -0
  53. package/dist/commands/sync.js.map +1 -0
  54. package/dist/index.d.ts +3 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +168 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/lib/api.d.ts +4 -0
  59. package/dist/lib/api.d.ts.map +1 -0
  60. package/dist/lib/api.js +95 -0
  61. package/dist/lib/api.js.map +1 -0
  62. package/dist/lib/config.d.ts +10 -0
  63. package/dist/lib/config.d.ts.map +1 -0
  64. package/dist/lib/config.js +49 -0
  65. package/dist/lib/config.js.map +1 -0
  66. package/dist/lib/credentials.d.ts +11 -0
  67. package/dist/lib/credentials.d.ts.map +1 -0
  68. package/dist/lib/credentials.js +36 -0
  69. package/dist/lib/credentials.js.map +1 -0
  70. package/package.json +39 -4
  71. package/src/commands/auth.ts +92 -0
  72. package/src/commands/capture.ts +86 -0
  73. package/src/commands/config.ts +27 -0
  74. package/src/commands/delete.ts +58 -0
  75. package/src/commands/hook.ts +243 -0
  76. package/src/commands/list.ts +38 -0
  77. package/src/commands/login.ts +164 -0
  78. package/src/commands/mcp.ts +490 -0
  79. package/src/commands/push.ts +137 -0
  80. package/src/commands/recall.ts +163 -0
  81. package/src/commands/setup.ts +1129 -0
  82. package/src/commands/show.ts +35 -0
  83. package/src/commands/sync.ts +506 -0
  84. package/src/index.ts +223 -0
  85. package/src/lib/api.ts +110 -0
  86. package/src/lib/config.ts +61 -0
  87. package/src/lib/credentials.ts +42 -0
  88. package/tsconfig.json +9 -0
  89. package/LICENSE +0 -24
  90. package/README.md +0 -2
  91. package/bin/memax.js +0 -13
@@ -0,0 +1,490 @@
1
+ import { Command } from "commander";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+ import { apiGet, apiPost, apiDelete } from "../lib/api.js";
9
+
10
+ interface RecallResult {
11
+ notes: Array<{
12
+ id: string;
13
+ title: string;
14
+ chunk_content: string;
15
+ heading_chain: string;
16
+ relevance_score: number;
17
+ category: string;
18
+ source: string;
19
+ age: string;
20
+ }>;
21
+ query_metadata: {
22
+ intent: string;
23
+ total_candidates: number;
24
+ latency_ms: number;
25
+ };
26
+ }
27
+
28
+ interface NoteResult {
29
+ id: string;
30
+ title: string;
31
+ category: string;
32
+ source: string;
33
+ tags: string[];
34
+ summary: string;
35
+ created_at: string;
36
+ }
37
+
38
+ function createServer(): Server {
39
+ const server = new Server(
40
+ { name: "memax", version: "0.0.1" },
41
+ { capabilities: { tools: {} } },
42
+ );
43
+
44
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
45
+ tools: [
46
+ {
47
+ name: "memax_recall",
48
+ description:
49
+ "Search the user's Memax knowledge base with a natural language query. " +
50
+ "Returns relevant memories ranked by relevance. Use this when you need " +
51
+ "background information about the project, team processes, or past decisions.",
52
+ inputSchema: {
53
+ type: "object" as const,
54
+ properties: {
55
+ query: {
56
+ type: "string",
57
+ description: "Natural language search query",
58
+ },
59
+ limit: {
60
+ type: "number",
61
+ description: "Max results to return (default 5)",
62
+ },
63
+ category: {
64
+ type: "string",
65
+ description:
66
+ "Filter by category prefix (e.g. 'core', 'decisions', 'process')",
67
+ },
68
+ },
69
+ required: ["query"],
70
+ },
71
+ },
72
+ {
73
+ name: "memax_push",
74
+ description:
75
+ "Save a piece of knowledge to the user's Memax knowledge base. " +
76
+ "Auto-categorized by AI if no category is provided.",
77
+ inputSchema: {
78
+ type: "object" as const,
79
+ properties: {
80
+ content: {
81
+ type: "string",
82
+ description: "The knowledge content to save",
83
+ },
84
+ title: {
85
+ type: "string",
86
+ description: "Optional title (auto-generated if omitted)",
87
+ },
88
+ category: {
89
+ type: "string",
90
+ description:
91
+ "Category (e.g. 'decisions/adr', 'core/architecture'). Auto-detected if omitted.",
92
+ },
93
+ tags: {
94
+ type: "array",
95
+ items: { type: "string" },
96
+ description: "Tags for the memory",
97
+ },
98
+ },
99
+ required: ["content"],
100
+ },
101
+ },
102
+ {
103
+ name: "memax_get",
104
+ description:
105
+ "Get the full content of a specific note by ID. " +
106
+ "Use this after memax_recall to read the complete content of a relevant memory.",
107
+ inputSchema: {
108
+ type: "object" as const,
109
+ properties: {
110
+ id: {
111
+ type: "string",
112
+ description:
113
+ "The note ID (from memax_recall or memax_search results)",
114
+ },
115
+ },
116
+ required: ["id"],
117
+ },
118
+ },
119
+ {
120
+ name: "memax_search",
121
+ description:
122
+ "List notes filtered by category or tags. " +
123
+ "For browsing and structured lookups, not semantic search. " +
124
+ "Use memax_recall for natural language queries instead.",
125
+ inputSchema: {
126
+ type: "object" as const,
127
+ properties: {
128
+ category: {
129
+ type: "string",
130
+ description: "Filter by category prefix",
131
+ },
132
+ limit: {
133
+ type: "number",
134
+ description: "Max results to return (default 10)",
135
+ },
136
+ },
137
+ },
138
+ },
139
+ {
140
+ name: "memax_forget",
141
+ description:
142
+ "Delete a note from the user's Memax knowledge base by ID. " +
143
+ "Use this to remove outdated, incorrect, or duplicate memories.",
144
+ inputSchema: {
145
+ type: "object" as const,
146
+ properties: {
147
+ id: {
148
+ type: "string",
149
+ description:
150
+ "The note ID to delete (from memax_recall or memax_search results)",
151
+ },
152
+ },
153
+ required: ["id"],
154
+ },
155
+ },
156
+ {
157
+ name: "memax_capture",
158
+ description:
159
+ "Capture key decisions, learnings, and context from this session. " +
160
+ "Call at the end of a significant work session to save what was accomplished " +
161
+ "and what should be remembered for future sessions. More selective than " +
162
+ "auto-capture — you decide what's worth remembering.",
163
+ inputSchema: {
164
+ type: "object" as const,
165
+ properties: {
166
+ summary: {
167
+ type: "string",
168
+ description:
169
+ "Brief summary of what was accomplished in this session",
170
+ },
171
+ decisions: {
172
+ type: "array",
173
+ items: { type: "string" },
174
+ description:
175
+ "Key decisions made (e.g., 'Chose PostgreSQL over MongoDB')",
176
+ },
177
+ learnings: {
178
+ type: "array",
179
+ items: { type: "string" },
180
+ description:
181
+ "Things learned (e.g., 'pg_trgm is language-agnostic')",
182
+ },
183
+ },
184
+ required: ["summary"],
185
+ },
186
+ },
187
+ ],
188
+ }));
189
+
190
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
191
+ const { name, arguments: args } = request.params;
192
+
193
+ switch (name) {
194
+ case "memax_recall": {
195
+ const typedArgs = args as {
196
+ query: string;
197
+ limit?: number;
198
+ category?: string;
199
+ };
200
+ try {
201
+ const result = await apiPost<RecallResult>("/v1/recall", {
202
+ query: typedArgs.query,
203
+ limit: typedArgs.limit ?? 5,
204
+ category: typedArgs.category ?? "",
205
+ source: "mcp",
206
+ working_dir: process.cwd(),
207
+ });
208
+
209
+ const notes = result.notes ?? [];
210
+ if (notes.length === 0) {
211
+ return {
212
+ content: [{ type: "text" as const, text: "No results found." }],
213
+ };
214
+ }
215
+
216
+ const formatted = notes
217
+ .map((n, i) => {
218
+ const score = (n.relevance_score * 100).toFixed(0);
219
+ const heading = n.heading_chain ? ` — ${n.heading_chain}` : "";
220
+ return (
221
+ `[${i + 1}] ${n.title} [${n.category}, ${score}%, ${n.age}] (id: ${n.id})${heading}\n` +
222
+ n.chunk_content
223
+ );
224
+ })
225
+ .join("\n\n");
226
+
227
+ return { content: [{ type: "text" as const, text: formatted }] };
228
+ } catch (err) {
229
+ return {
230
+ content: [
231
+ {
232
+ type: "text" as const,
233
+ text: `Recall failed: ${(err as Error).message}`,
234
+ },
235
+ ],
236
+ isError: true,
237
+ };
238
+ }
239
+ }
240
+
241
+ case "memax_push": {
242
+ const typedArgs = args as {
243
+ content: string;
244
+ title?: string;
245
+ category?: string;
246
+ tags?: string[];
247
+ };
248
+ try {
249
+ const note = await apiPost<NoteResult>("/v1/notes", {
250
+ content: typedArgs.content,
251
+ title: typedArgs.title ?? "",
252
+ category: typedArgs.category ?? "",
253
+ tags: typedArgs.tags ?? [],
254
+ source: "mcp",
255
+ });
256
+
257
+ return {
258
+ content: [
259
+ {
260
+ type: "text" as const,
261
+ text: `Saved: ${note.title} (id: ${note.id})`,
262
+ },
263
+ ],
264
+ };
265
+ } catch (err) {
266
+ return {
267
+ content: [
268
+ {
269
+ type: "text" as const,
270
+ text: `Push failed: ${(err as Error).message}`,
271
+ },
272
+ ],
273
+ isError: true,
274
+ };
275
+ }
276
+ }
277
+
278
+ case "memax_get": {
279
+ const typedArgs = args as { id: string };
280
+ try {
281
+ const note = await apiGet<{
282
+ id: string;
283
+ title: string;
284
+ content: string;
285
+ summary: string;
286
+ category: string;
287
+ tags: string[];
288
+ source: string;
289
+ source_path: string;
290
+ created_at: string;
291
+ updated_at: string;
292
+ }>(`/v1/notes/${typedArgs.id}`);
293
+
294
+ const parts = [
295
+ `# ${note.title}`,
296
+ `Category: ${note.category} | Source: ${note.source} | Created: ${note.created_at}`,
297
+ ];
298
+ if (note.tags?.length > 0) {
299
+ parts.push(`Tags: ${note.tags.join(", ")}`);
300
+ }
301
+ if (note.source_path) {
302
+ parts.push(`Source: ${note.source_path}`);
303
+ }
304
+ if (note.summary) {
305
+ parts.push(`\n## Summary\n${note.summary}`);
306
+ }
307
+ parts.push(`\n## Content\n${note.content}`);
308
+
309
+ return {
310
+ content: [{ type: "text" as const, text: parts.join("\n") }],
311
+ };
312
+ } catch (err) {
313
+ return {
314
+ content: [
315
+ {
316
+ type: "text" as const,
317
+ text: `Get failed: ${(err as Error).message}`,
318
+ },
319
+ ],
320
+ isError: true,
321
+ };
322
+ }
323
+ }
324
+
325
+ case "memax_search": {
326
+ const typedArgs = args as {
327
+ category?: string;
328
+ limit?: number;
329
+ };
330
+ try {
331
+ const notes = (await apiGet<NoteResult[]>("/v1/notes")) ?? [];
332
+
333
+ let filtered = notes;
334
+ if (typedArgs.category) {
335
+ filtered = notes.filter((n) =>
336
+ n.category.startsWith(typedArgs.category!),
337
+ );
338
+ }
339
+
340
+ const limited = filtered.slice(0, typedArgs.limit ?? 10);
341
+
342
+ if (limited.length === 0) {
343
+ return {
344
+ content: [{ type: "text" as const, text: "No notes found." }],
345
+ };
346
+ }
347
+
348
+ const formatted = limited
349
+ .map(
350
+ (n) => `- ${n.title} [${n.category}] — ${n.source} (id: ${n.id})`,
351
+ )
352
+ .join("\n");
353
+
354
+ return { content: [{ type: "text" as const, text: formatted }] };
355
+ } catch (err) {
356
+ return {
357
+ content: [
358
+ {
359
+ type: "text" as const,
360
+ text: `Search failed: ${(err as Error).message}`,
361
+ },
362
+ ],
363
+ isError: true,
364
+ };
365
+ }
366
+ }
367
+
368
+ case "memax_forget": {
369
+ const typedArgs = args as { id: string };
370
+ if (!typedArgs.id) {
371
+ return {
372
+ content: [{ type: "text" as const, text: "Note ID is required." }],
373
+ isError: true,
374
+ };
375
+ }
376
+ try {
377
+ await apiDelete(`/v1/notes/${typedArgs.id}`);
378
+ return {
379
+ content: [
380
+ {
381
+ type: "text" as const,
382
+ text: `Forgotten: ${typedArgs.id}`,
383
+ },
384
+ ],
385
+ };
386
+ } catch (err) {
387
+ return {
388
+ content: [
389
+ {
390
+ type: "text" as const,
391
+ text: `Forget failed: ${(err as Error).message}`,
392
+ },
393
+ ],
394
+ isError: true,
395
+ };
396
+ }
397
+ }
398
+
399
+ case "memax_capture": {
400
+ const typedArgs = args as {
401
+ summary: string;
402
+ decisions?: string[];
403
+ learnings?: string[];
404
+ };
405
+ if (!typedArgs.summary) {
406
+ return {
407
+ content: [{ type: "text" as const, text: "Summary is required." }],
408
+ isError: true,
409
+ };
410
+ }
411
+
412
+ // Build structured content for the extraction pipeline
413
+ let content = `## Session Summary\n${typedArgs.summary}\n`;
414
+ if (typedArgs.decisions?.length) {
415
+ content += `\n## Decisions Made\n${typedArgs.decisions.map((d) => `- ${d}`).join("\n")}\n`;
416
+ }
417
+ if (typedArgs.learnings?.length) {
418
+ content += `\n## Learnings\n${typedArgs.learnings.map((l) => `- ${l}`).join("\n")}\n`;
419
+ }
420
+
421
+ try {
422
+ const note = await apiPost<{ id: string; title: string }>(
423
+ "/v1/notes",
424
+ {
425
+ content,
426
+ title: `Session capture — ${new Date().toLocaleDateString()}`,
427
+ content_type: "transcript",
428
+ source: "mcp/capture",
429
+ category: "chat/agent",
430
+ },
431
+ );
432
+ return {
433
+ content: [
434
+ {
435
+ type: "text" as const,
436
+ text: `Session captured (id: ${note.id}). Key facts will be extracted and stored as separate memories.`,
437
+ },
438
+ ],
439
+ };
440
+ } catch (err) {
441
+ return {
442
+ content: [
443
+ {
444
+ type: "text" as const,
445
+ text: `Capture failed: ${(err as Error).message}`,
446
+ },
447
+ ],
448
+ isError: true,
449
+ };
450
+ }
451
+ }
452
+
453
+ default:
454
+ return {
455
+ content: [{ type: "text" as const, text: `Unknown tool: ${name}` }],
456
+ isError: true,
457
+ };
458
+ }
459
+ });
460
+
461
+ return server;
462
+ }
463
+
464
+ async function mcpServeCommand(): Promise<void> {
465
+ const server = createServer();
466
+ const transport = new StdioServerTransport();
467
+ await server.connect(transport);
468
+
469
+ // Keep the process alive — some agent launchers close stdin early
470
+ // which makes Node think the event loop is empty and exit.
471
+ await new Promise<void>((resolve) => {
472
+ process.on("SIGINT", resolve);
473
+ process.on("SIGTERM", resolve);
474
+ // Also resolve if stdin closes (transport disconnected)
475
+ process.stdin.on("end", resolve);
476
+ });
477
+
478
+ await server.close();
479
+ }
480
+
481
+ export function registerMcpCommand(program: Command): void {
482
+ const mcp = program
483
+ .command("mcp")
484
+ .description("Model Context Protocol server for AI agents");
485
+
486
+ mcp
487
+ .command("serve")
488
+ .description("Start MCP server on stdio")
489
+ .action(mcpServeCommand);
490
+ }
@@ -0,0 +1,137 @@
1
+ import chalk from "chalk";
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { basename } from "node:path";
4
+ import { apiPost } from "../lib/api.js";
5
+ import type { Note } from "@memaxlabs/shared";
6
+
7
+ const MAX_SIZE = 3 * 1024 * 1024; // 3MB — must match server's MAX_BODY_SIZE default
8
+
9
+ function formatSize(bytes: number): string {
10
+ if (bytes < 1024) return `${bytes} B`;
11
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
12
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13
+ }
14
+
15
+ interface PushOptions {
16
+ file?: string;
17
+ category?: string;
18
+ tags?: string;
19
+ ttl?: string;
20
+ stdin?: boolean;
21
+ title?: string;
22
+ }
23
+
24
+ export async function pushCommand(
25
+ inlineContent: string | undefined,
26
+ options: PushOptions,
27
+ ): Promise<void> {
28
+ let content: string;
29
+ let title = options.title ?? "";
30
+ let sourcePath = "";
31
+
32
+ if (options.file) {
33
+ try {
34
+ // Check file size before reading
35
+ const stat = statSync(options.file);
36
+ if (stat.size > MAX_SIZE) {
37
+ console.error(
38
+ chalk.red(
39
+ `File too large: ${formatSize(stat.size)} (limit: ${formatSize(MAX_SIZE)})`,
40
+ ),
41
+ );
42
+ console.error(
43
+ chalk.gray(
44
+ " Tip: split large files into smaller sections, or ask your admin to increase MAX_BODY_SIZE on the server.",
45
+ ),
46
+ );
47
+ process.exit(1);
48
+ }
49
+ content = readFileSync(options.file, "utf-8");
50
+ sourcePath = options.file;
51
+ if (!title) {
52
+ title = basename(options.file);
53
+ }
54
+ } catch (err) {
55
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
56
+ console.error(chalk.red(`File not found: ${options.file}`));
57
+ } else if ((err as { exitCode?: number }).exitCode) {
58
+ throw err; // Re-throw our own process.exit calls
59
+ } else {
60
+ console.error(chalk.red(`Error reading file: ${options.file}`));
61
+ }
62
+ process.exit(1);
63
+ }
64
+ } else if (inlineContent) {
65
+ // Positional argument: memax push "some content"
66
+ content = inlineContent;
67
+ } else if (options.stdin || !process.stdin.isTTY) {
68
+ const chunks: Buffer[] = [];
69
+ for await (const chunk of process.stdin) {
70
+ chunks.push(chunk);
71
+ }
72
+ content = Buffer.concat(chunks).toString("utf-8");
73
+ } else {
74
+ console.error(
75
+ chalk.red(
76
+ 'Provide content, --file <path>, or pipe via stdin:\n memax push "your content here"\n memax push --file ./doc.md\n echo "content" | memax push',
77
+ ),
78
+ );
79
+ process.exit(1);
80
+ }
81
+
82
+ if (!content.trim()) {
83
+ console.error(chalk.red("No content to push"));
84
+ process.exit(1);
85
+ }
86
+
87
+ // Check size before sending
88
+ const contentBytes = Buffer.byteLength(content, "utf-8");
89
+ if (contentBytes > MAX_SIZE) {
90
+ console.error(
91
+ chalk.red(
92
+ `File too large: ${formatSize(contentBytes)} (limit: ${formatSize(MAX_SIZE)})`,
93
+ ),
94
+ );
95
+ console.error(
96
+ chalk.gray(
97
+ " Tip: split large files into smaller sections, or ask your admin to increase MAX_BODY_SIZE on the server.",
98
+ ),
99
+ );
100
+ process.exit(1);
101
+ }
102
+
103
+ const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : [];
104
+
105
+ // Auto-detect URL content: single-line http(s) URL -> content_type "link"
106
+ const trimmed = content.trim();
107
+ const isURL =
108
+ (trimmed.startsWith("http://") || trimmed.startsWith("https://")) &&
109
+ !trimmed.includes("\n");
110
+
111
+ let contentType = sourcePath.endsWith(".md") ? "markdown" : "text";
112
+ if (isURL) {
113
+ contentType = "link";
114
+ }
115
+
116
+ try {
117
+ const note = await apiPost<Note>("/v1/notes", {
118
+ content,
119
+ title,
120
+ category: options.category ?? "",
121
+ tags,
122
+ source: "cli",
123
+ source_path: sourcePath,
124
+ content_type: contentType,
125
+ });
126
+
127
+ console.log(chalk.green("Saved"), chalk.bold(note.title));
128
+ console.log(
129
+ chalk.gray(
130
+ ` id: ${note.id} category: ${note.category} source: ${note.source}`,
131
+ ),
132
+ );
133
+ } catch (err) {
134
+ console.error(chalk.red(`Push failed: ${(err as Error).message}`));
135
+ process.exit(1);
136
+ }
137
+ }