codmir 0.1.0 → 0.2.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.
@@ -0,0 +1,769 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/cli/utils/auth.ts
30
+ var import_http = __toESM(require("http"));
31
+ var import_open = __toESM(require("open"));
32
+
33
+ // src/cli/utils/config.ts
34
+ var import_fs = __toESM(require("fs"));
35
+ var import_path = __toESM(require("path"));
36
+ var import_os = __toESM(require("os"));
37
+ var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".codmir");
38
+ var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
39
+ var PROJECT_CONFIG_FILE = ".codmir.json";
40
+ function ensureConfigDir() {
41
+ if (!import_fs.default.existsSync(CONFIG_DIR)) {
42
+ import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
43
+ }
44
+ }
45
+ function readConfig() {
46
+ ensureConfigDir();
47
+ if (!import_fs.default.existsSync(CONFIG_FILE)) {
48
+ return {};
49
+ }
50
+ try {
51
+ const data = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
52
+ return JSON.parse(data);
53
+ } catch (error) {
54
+ console.error("Error reading config:", error);
55
+ return {};
56
+ }
57
+ }
58
+ function writeConfig(config) {
59
+ ensureConfigDir();
60
+ import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
61
+ }
62
+ function clearConfig() {
63
+ if (import_fs.default.existsSync(CONFIG_FILE)) {
64
+ import_fs.default.unlinkSync(CONFIG_FILE);
65
+ }
66
+ }
67
+ function readProjectConfig(cwd = process.cwd()) {
68
+ const configPath = import_path.default.join(cwd, PROJECT_CONFIG_FILE);
69
+ if (!import_fs.default.existsSync(configPath)) {
70
+ return null;
71
+ }
72
+ try {
73
+ const data = import_fs.default.readFileSync(configPath, "utf-8");
74
+ return JSON.parse(data);
75
+ } catch (error) {
76
+ console.error("Error reading project config:", error);
77
+ return null;
78
+ }
79
+ }
80
+ function writeProjectConfig(config, cwd = process.cwd()) {
81
+ const configPath = import_path.default.join(cwd, PROJECT_CONFIG_FILE);
82
+ import_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
83
+ const gitignorePath = import_path.default.join(cwd, ".gitignore");
84
+ if (import_fs.default.existsSync(gitignorePath)) {
85
+ const gitignore = import_fs.default.readFileSync(gitignorePath, "utf-8");
86
+ if (!gitignore.includes(PROJECT_CONFIG_FILE)) {
87
+ import_fs.default.appendFileSync(gitignorePath, `
88
+ # codmir
89
+ ${PROJECT_CONFIG_FILE}
90
+ `);
91
+ }
92
+ }
93
+ }
94
+ function getToken() {
95
+ const config = readConfig();
96
+ return config.token || null;
97
+ }
98
+ function isAuthenticated() {
99
+ const token = getToken();
100
+ return !!token;
101
+ }
102
+
103
+ // src/cli/utils/auth.ts
104
+ async function startOAuthFlow(baseUrl = "https://codmir.com") {
105
+ return new Promise((resolve, reject) => {
106
+ const server = import_http.default.createServer(async (req, res) => {
107
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
108
+ if (url.pathname === "/callback") {
109
+ const token = url.searchParams.get("token");
110
+ const error = url.searchParams.get("error");
111
+ if (error) {
112
+ res.writeHead(200, { "Content-Type": "text/html" });
113
+ res.end(`
114
+ <html>
115
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
116
+ <h1>\u274C Authentication Failed</h1>
117
+ <p>${error}</p>
118
+ <p>You can close this window.</p>
119
+ </body>
120
+ </html>
121
+ `);
122
+ server.close();
123
+ reject(new Error(error));
124
+ return;
125
+ }
126
+ if (!token) {
127
+ res.writeHead(400, { "Content-Type": "text/html" });
128
+ res.end(`
129
+ <html>
130
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
131
+ <h1>\u274C No token received</h1>
132
+ <p>You can close this window.</p>
133
+ </body>
134
+ </html>
135
+ `);
136
+ server.close();
137
+ reject(new Error("No token received"));
138
+ return;
139
+ }
140
+ res.writeHead(200, { "Content-Type": "text/html" });
141
+ res.end(`
142
+ <html>
143
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
144
+ <h1>\u2705 Authentication Successful!</h1>
145
+ <p>You can now close this window and return to your terminal.</p>
146
+ <script>setTimeout(() => window.close(), 2000);</script>
147
+ </body>
148
+ </html>
149
+ `);
150
+ try {
151
+ const userResponse = await fetch(`${baseUrl}/api/user/profile`, {
152
+ headers: {
153
+ "Authorization": `Bearer ${token}`
154
+ }
155
+ });
156
+ if (!userResponse.ok) {
157
+ throw new Error("Failed to fetch user info");
158
+ }
159
+ const user = await userResponse.json();
160
+ server.close();
161
+ resolve({
162
+ token,
163
+ user: {
164
+ id: user.id,
165
+ email: user.email,
166
+ name: user.name
167
+ }
168
+ });
169
+ } catch (error2) {
170
+ server.close();
171
+ reject(error2);
172
+ }
173
+ } else {
174
+ res.writeHead(404);
175
+ res.end("Not found");
176
+ }
177
+ });
178
+ const port = 3333;
179
+ server.listen(port, () => {
180
+ const authUrl = `${baseUrl}/cli/auth?port=${port}`;
181
+ console.log("\u{1F510} Opening browser for authentication...");
182
+ console.log(` If browser doesn't open, visit: ${authUrl}`);
183
+ (0, import_open.default)(authUrl).catch(() => {
184
+ console.log("\n\u26A0\uFE0F Could not open browser automatically.");
185
+ console.log(` Please visit: ${authUrl}`);
186
+ });
187
+ });
188
+ setTimeout(() => {
189
+ server.close();
190
+ reject(new Error("Authentication timeout"));
191
+ }, 5 * 60 * 1e3);
192
+ });
193
+ }
194
+ async function authenticateWithToken(token, baseUrl = "https://codmir.com") {
195
+ const response = await fetch(`${baseUrl}/api/user/profile`, {
196
+ headers: {
197
+ "Authorization": `Bearer ${token}`
198
+ }
199
+ });
200
+ if (!response.ok) {
201
+ throw new Error("Invalid token");
202
+ }
203
+ const user = await response.json();
204
+ return {
205
+ token,
206
+ user: {
207
+ id: user.id,
208
+ email: user.email,
209
+ name: user.name
210
+ }
211
+ };
212
+ }
213
+ function saveAuth(authResult) {
214
+ const config = readConfig();
215
+ writeConfig({
216
+ ...config,
217
+ token: authResult.token,
218
+ userId: authResult.user.id,
219
+ email: authResult.user.email,
220
+ name: authResult.user.name,
221
+ lastLogin: (/* @__PURE__ */ new Date()).toISOString()
222
+ });
223
+ }
224
+
225
+ // src/cli/commands/login.ts
226
+ var import_chalk = __toESM(require("chalk"));
227
+ async function loginCommand(options) {
228
+ if (isAuthenticated()) {
229
+ const config = readConfig();
230
+ console.log(import_chalk.default.yellow("\u26A0\uFE0F Already logged in as:"), import_chalk.default.bold(config.email));
231
+ console.log(import_chalk.default.dim(" Run"), import_chalk.default.cyan("codmir logout"), import_chalk.default.dim("to log out first"));
232
+ return;
233
+ }
234
+ console.log(import_chalk.default.bold("\u{1F680} codmir login"));
235
+ console.log(import_chalk.default.dim(" Authenticate to start using codmir CLI\n"));
236
+ try {
237
+ let authResult;
238
+ if (options.token) {
239
+ console.log(import_chalk.default.dim(" Authenticating with provided token..."));
240
+ authResult = await authenticateWithToken(options.token);
241
+ } else {
242
+ authResult = await startOAuthFlow();
243
+ }
244
+ saveAuth(authResult);
245
+ console.log(import_chalk.default.green("\n\u2705 Successfully logged in!"));
246
+ console.log(import_chalk.default.dim(" User:"), import_chalk.default.bold(authResult.user.name));
247
+ console.log(import_chalk.default.dim(" Email:"), import_chalk.default.bold(authResult.user.email));
248
+ console.log(import_chalk.default.dim("\n You can now use"), import_chalk.default.cyan("codmir link"), import_chalk.default.dim("to connect your project"));
249
+ } catch (error) {
250
+ console.error(import_chalk.default.red("\n\u274C Login failed:"), error instanceof Error ? error.message : "Unknown error");
251
+ process.exit(1);
252
+ }
253
+ }
254
+
255
+ // src/client.ts
256
+ var CodmirClient = class {
257
+ constructor(config) {
258
+ this.config = {
259
+ apiKey: config.apiKey || "",
260
+ baseUrl: config.baseUrl || "https://codmir.com",
261
+ timeout: config.timeout || 3e4,
262
+ headers: config.headers || {}
263
+ };
264
+ this.tickets = new TicketsAPI(this.config);
265
+ this.testCases = new TestCasesAPI(this.config);
266
+ }
267
+ /**
268
+ * Make an HTTP request to the codmir API
269
+ */
270
+ async request(method, path3, body) {
271
+ const url = `${this.config.baseUrl}${path3}`;
272
+ const headers = {
273
+ "Content-Type": "application/json",
274
+ ...this.config.headers
275
+ };
276
+ if (this.config.apiKey) {
277
+ headers["X-API-Key"] = this.config.apiKey;
278
+ }
279
+ const controller = new AbortController();
280
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
281
+ try {
282
+ const response = await fetch(url, {
283
+ method,
284
+ headers,
285
+ body: body ? JSON.stringify(body) : void 0,
286
+ signal: controller.signal
287
+ });
288
+ clearTimeout(timeoutId);
289
+ if (!response.ok) {
290
+ const errorData = await response.json().catch(() => ({}));
291
+ throw this.createError(
292
+ errorData.error || `HTTP ${response.status}: ${response.statusText}`,
293
+ response.status,
294
+ errorData
295
+ );
296
+ }
297
+ if (response.status === 204) {
298
+ return {};
299
+ }
300
+ return await response.json();
301
+ } catch (error) {
302
+ clearTimeout(timeoutId);
303
+ if (error.name === "AbortError") {
304
+ throw this.createError("Request timeout", 408);
305
+ }
306
+ if (error instanceof Error && "statusCode" in error) {
307
+ throw error;
308
+ }
309
+ throw this.createError(
310
+ error.message || "Network error occurred",
311
+ void 0,
312
+ error
313
+ );
314
+ }
315
+ }
316
+ createError(message, statusCode, response) {
317
+ const error = new Error(message);
318
+ error.name = "CodmirError";
319
+ error.statusCode = statusCode;
320
+ error.response = response;
321
+ return error;
322
+ }
323
+ };
324
+ var TicketsAPI = class {
325
+ constructor(config) {
326
+ this.config = config;
327
+ }
328
+ async request(method, path3, body) {
329
+ const url = `${this.config.baseUrl}${path3}`;
330
+ const headers = {
331
+ "Content-Type": "application/json",
332
+ ...this.config.headers
333
+ };
334
+ if (this.config.apiKey) {
335
+ headers["X-API-Key"] = this.config.apiKey;
336
+ }
337
+ const controller = new AbortController();
338
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
339
+ try {
340
+ const response = await fetch(url, {
341
+ method,
342
+ headers,
343
+ body: body ? JSON.stringify(body) : void 0,
344
+ signal: controller.signal
345
+ });
346
+ clearTimeout(timeoutId);
347
+ if (!response.ok) {
348
+ const errorData = await response.json().catch(() => ({}));
349
+ const error = new Error(errorData.error || `HTTP ${response.status}`);
350
+ error.statusCode = response.status;
351
+ error.response = errorData;
352
+ throw error;
353
+ }
354
+ if (response.status === 204) {
355
+ return {};
356
+ }
357
+ return await response.json();
358
+ } catch (error) {
359
+ clearTimeout(timeoutId);
360
+ if (error.name === "AbortError") {
361
+ const timeoutError = new Error("Request timeout");
362
+ timeoutError.statusCode = 408;
363
+ throw timeoutError;
364
+ }
365
+ throw error;
366
+ }
367
+ }
368
+ /**
369
+ * Create a ticket in a board
370
+ *
371
+ * @param projectId - The project ID
372
+ * @param boardId - The board ID
373
+ * @param data - Ticket data
374
+ */
375
+ async createInBoard(projectId, boardId, data) {
376
+ const response = await this.request(
377
+ "POST",
378
+ `/api/project/${projectId}/board/${boardId}/ticket`,
379
+ data
380
+ );
381
+ return response.ticket;
382
+ }
383
+ /**
384
+ * Create a ticket in a workspace
385
+ *
386
+ * @param projectId - The project ID
387
+ * @param workspaceId - The workspace ID
388
+ * @param data - Ticket data
389
+ */
390
+ async createInWorkspace(projectId, workspaceId, data) {
391
+ return this.request(
392
+ "POST",
393
+ `/api/project/${projectId}/workspaces/${workspaceId}/tickets`,
394
+ data
395
+ );
396
+ }
397
+ /**
398
+ * Alias for createInBoard - creates a ticket in a board
399
+ */
400
+ async create(projectId, boardId, data) {
401
+ return this.createInBoard(projectId, boardId, data);
402
+ }
403
+ };
404
+ var TestCasesAPI = class {
405
+ constructor(config) {
406
+ this.config = config;
407
+ }
408
+ async request(method, path3, body) {
409
+ const url = `${this.config.baseUrl}${path3}`;
410
+ const headers = {
411
+ "Content-Type": "application/json",
412
+ ...this.config.headers
413
+ };
414
+ if (this.config.apiKey) {
415
+ headers["X-API-Key"] = this.config.apiKey;
416
+ }
417
+ const controller = new AbortController();
418
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
419
+ try {
420
+ const response = await fetch(url, {
421
+ method,
422
+ headers,
423
+ body: body ? JSON.stringify(body) : void 0,
424
+ signal: controller.signal
425
+ });
426
+ clearTimeout(timeoutId);
427
+ if (!response.ok) {
428
+ const errorData = await response.json().catch(() => ({}));
429
+ const error = new Error(errorData.error || `HTTP ${response.status}`);
430
+ error.statusCode = response.status;
431
+ error.response = errorData;
432
+ throw error;
433
+ }
434
+ if (response.status === 204) {
435
+ return {};
436
+ }
437
+ return await response.json();
438
+ } catch (error) {
439
+ clearTimeout(timeoutId);
440
+ if (error.name === "AbortError") {
441
+ const timeoutError = new Error("Request timeout");
442
+ timeoutError.statusCode = 408;
443
+ throw timeoutError;
444
+ }
445
+ throw error;
446
+ }
447
+ }
448
+ /**
449
+ * List all test cases for a project
450
+ *
451
+ * @param projectId - The project ID
452
+ */
453
+ async list(projectId) {
454
+ const response = await this.request(
455
+ "GET",
456
+ `/api/project/${projectId}/test-cases`
457
+ );
458
+ return response.data || [];
459
+ }
460
+ /**
461
+ * Get a specific test case by ID
462
+ *
463
+ * @param projectId - The project ID
464
+ * @param testCaseId - The test case ID (numeric UD)
465
+ */
466
+ async get(projectId, testCaseId) {
467
+ const response = await this.request(
468
+ "GET",
469
+ `/api/project/${projectId}/test-cases/${testCaseId}`
470
+ );
471
+ if (!response.data) {
472
+ throw new Error("Test case not found");
473
+ }
474
+ return response.data;
475
+ }
476
+ /**
477
+ * Create a new test case
478
+ *
479
+ * @param projectId - The project ID
480
+ * @param data - Test case data
481
+ */
482
+ async create(projectId, data) {
483
+ const response = await this.request(
484
+ "POST",
485
+ `/api/project/${projectId}/test-cases`,
486
+ data
487
+ );
488
+ if (!response.data) {
489
+ throw new Error("Failed to create test case");
490
+ }
491
+ return response.data;
492
+ }
493
+ /**
494
+ * Update an existing test case
495
+ *
496
+ * @param projectId - The project ID
497
+ * @param testCaseId - The test case ID (numeric UD)
498
+ * @param data - Test case update data
499
+ */
500
+ async update(projectId, testCaseId, data) {
501
+ const response = await this.request(
502
+ "PATCH",
503
+ `/api/project/${projectId}/test-cases/${testCaseId}`,
504
+ data
505
+ );
506
+ if (!response.data) {
507
+ throw new Error("Failed to update test case");
508
+ }
509
+ return response.data;
510
+ }
511
+ /**
512
+ * Delete a test case
513
+ *
514
+ * @param projectId - The project ID
515
+ * @param testCaseId - The test case ID (numeric UD)
516
+ */
517
+ async delete(projectId, testCaseId) {
518
+ await this.request(
519
+ "DELETE",
520
+ `/api/project/${projectId}/test-cases/${testCaseId}`
521
+ );
522
+ }
523
+ };
524
+
525
+ // src/cli/commands/link.ts
526
+ var import_chalk2 = __toESM(require("chalk"));
527
+ var import_prompts = __toESM(require("prompts"));
528
+ async function linkCommand(options) {
529
+ const token = getToken();
530
+ if (!token) {
531
+ console.error(import_chalk2.default.red("\u274C Not authenticated"));
532
+ console.log(import_chalk2.default.dim(" Run"), import_chalk2.default.cyan("codmir login"), import_chalk2.default.dim("first"));
533
+ process.exit(1);
534
+ }
535
+ console.log(import_chalk2.default.bold("\n\u{1F517} codmir link"));
536
+ console.log(import_chalk2.default.dim(" Link this directory to a codmir project\n"));
537
+ const existing = readProjectConfig();
538
+ if (existing) {
539
+ console.log(import_chalk2.default.yellow("\u26A0\uFE0F This directory is already linked to:"));
540
+ console.log(import_chalk2.default.dim(" Project ID:"), import_chalk2.default.bold(existing.projectId));
541
+ if (existing.projectName) {
542
+ console.log(import_chalk2.default.dim(" Project:"), import_chalk2.default.bold(existing.projectName));
543
+ }
544
+ const { overwrite } = await (0, import_prompts.default)({
545
+ type: "confirm",
546
+ name: "overwrite",
547
+ message: "Do you want to overwrite this configuration?",
548
+ initial: false
549
+ });
550
+ if (!overwrite) {
551
+ console.log(import_chalk2.default.dim(" Cancelled"));
552
+ return;
553
+ }
554
+ }
555
+ try {
556
+ const client = new CodmirClient({
557
+ apiKey: token,
558
+ baseUrl: process.env.CODMIR_API_URL || "https://codmir.com"
559
+ });
560
+ let projectId = options.project;
561
+ let orgId = options.org;
562
+ console.log(import_chalk2.default.dim(" Fetching your projects...\n"));
563
+ const response = await fetch(`${process.env.CODMIR_API_URL || "https://codmir.com"}/api/projects`, {
564
+ headers: {
565
+ "Authorization": `Bearer ${token}`
566
+ }
567
+ });
568
+ if (!response.ok) {
569
+ throw new Error("Failed to fetch projects");
570
+ }
571
+ const projects = await response.json();
572
+ if (!projectId) {
573
+ if (projects.length === 0) {
574
+ console.log(import_chalk2.default.yellow("\u26A0\uFE0F You don't have any projects yet"));
575
+ console.log(import_chalk2.default.dim(" Create a project at"), import_chalk2.default.cyan("https://codmir.com"));
576
+ process.exit(1);
577
+ }
578
+ const { selectedProject } = await (0, import_prompts.default)({
579
+ type: "select",
580
+ name: "selectedProject",
581
+ message: "Select a project to link:",
582
+ choices: projects.map((p) => ({
583
+ title: `${p.name} (${p.key})`,
584
+ value: p.id,
585
+ description: p.description || void 0
586
+ }))
587
+ });
588
+ if (!selectedProject) {
589
+ console.log(import_chalk2.default.dim(" Cancelled"));
590
+ return;
591
+ }
592
+ projectId = selectedProject;
593
+ }
594
+ const project = projects.find((p) => p.id === projectId);
595
+ if (!project) {
596
+ console.error(import_chalk2.default.red("\u274C Project not found"));
597
+ process.exit(1);
598
+ }
599
+ writeProjectConfig({
600
+ projectId: project.id,
601
+ organizationId: project.organizationId || orgId,
602
+ projectName: project.name
603
+ });
604
+ console.log(import_chalk2.default.green("\n\u2705 Successfully linked!"));
605
+ console.log(import_chalk2.default.dim(" Project:"), import_chalk2.default.bold(project.name));
606
+ console.log(import_chalk2.default.dim(" ID:"), import_chalk2.default.bold(project.id));
607
+ console.log(import_chalk2.default.dim("\n Configuration saved to"), import_chalk2.default.cyan(".codmir.json"));
608
+ console.log(import_chalk2.default.dim(" You can now use the codmir package in your project"));
609
+ } catch (error) {
610
+ console.error(import_chalk2.default.red("\n\u274C Link failed:"), error instanceof Error ? error.message : "Unknown error");
611
+ process.exit(1);
612
+ }
613
+ }
614
+
615
+ // src/cli/commands/whoami.ts
616
+ var import_chalk3 = __toESM(require("chalk"));
617
+ async function whoamiCommand() {
618
+ if (!isAuthenticated()) {
619
+ console.error(import_chalk3.default.red("\u274C Not authenticated"));
620
+ console.log(import_chalk3.default.dim(" Run"), import_chalk3.default.cyan("codmir login"), import_chalk3.default.dim("first"));
621
+ process.exit(1);
622
+ }
623
+ const config = readConfig();
624
+ console.log(import_chalk3.default.bold("\n\u{1F464} Current User\n"));
625
+ console.log(import_chalk3.default.dim(" Name:"), import_chalk3.default.bold(config.name || "N/A"));
626
+ console.log(import_chalk3.default.dim(" Email:"), import_chalk3.default.bold(config.email || "N/A"));
627
+ console.log(import_chalk3.default.dim(" User ID:"), import_chalk3.default.dim(config.userId || "N/A"));
628
+ if (config.lastLogin) {
629
+ const lastLogin = new Date(config.lastLogin);
630
+ console.log(import_chalk3.default.dim(" Last Login:"), import_chalk3.default.dim(lastLogin.toLocaleString()));
631
+ }
632
+ }
633
+
634
+ // src/cli/commands/logout.ts
635
+ var import_chalk4 = __toESM(require("chalk"));
636
+ async function logoutCommand() {
637
+ if (!isAuthenticated()) {
638
+ console.log(import_chalk4.default.yellow("\u26A0\uFE0F Not logged in"));
639
+ return;
640
+ }
641
+ const config = readConfig();
642
+ clearConfig();
643
+ console.log(import_chalk4.default.green("\u2705 Successfully logged out"));
644
+ if (config.email) {
645
+ console.log(import_chalk4.default.dim(" Goodbye,"), import_chalk4.default.bold(config.email));
646
+ }
647
+ }
648
+
649
+ // src/cli/commands/init.ts
650
+ var import_chalk5 = __toESM(require("chalk"));
651
+ var import_fs2 = __toESM(require("fs"));
652
+ var import_path2 = __toESM(require("path"));
653
+ async function initCommand() {
654
+ console.log(import_chalk5.default.bold("\n\u{1F389} Initialize codmir\n"));
655
+ const config = readProjectConfig();
656
+ if (!config) {
657
+ console.log(import_chalk5.default.yellow("\u26A0\uFE0F Project not linked"));
658
+ console.log(import_chalk5.default.dim(" Run"), import_chalk5.default.cyan("codmir link"), import_chalk5.default.dim("to link this project first"));
659
+ return;
660
+ }
661
+ console.log(import_chalk5.default.dim(" Project:"), import_chalk5.default.bold(config.projectName || config.projectId));
662
+ const exampleFile = import_path2.default.join(process.cwd(), "codmir.example.ts");
663
+ if (import_fs2.default.existsSync(exampleFile)) {
664
+ console.log(import_chalk5.default.yellow("\n\u26A0\uFE0F codmir.example.ts already exists"));
665
+ } else {
666
+ const example = `import { CodmirClient } from 'codmir';
667
+
668
+ // Initialize the client
669
+ const codmir = new CodmirClient({
670
+ apiKey: process.env.CODMIR_API_KEY!, // Get token from: codmir login
671
+ });
672
+
673
+ // Example: Create a ticket
674
+ async function createTicket() {
675
+ const ticket = await codmir.tickets.create(
676
+ '${config.projectId}',
677
+ 'board-id',
678
+ {
679
+ title: 'Example ticket',
680
+ description: 'Created from codmir package',
681
+ priority: 'HIGH',
682
+ }
683
+ );
684
+
685
+ console.log('Created ticket:', ticket.id);
686
+ }
687
+
688
+ // Example: Create a test case
689
+ async function createTestCase() {
690
+ const testCase = await codmir.testCases.create('${config.projectId}', {
691
+ title: 'Example test case',
692
+ suiteId: 'suite-id',
693
+ template: 'steps',
694
+ steps: [
695
+ { action: 'Do something', expected: 'It works' },
696
+ ],
697
+ });
698
+
699
+ console.log('Created test case:', testCase.id);
700
+ }
701
+
702
+ // Run examples
703
+ createTicket().catch(console.error);
704
+ createTestCase().catch(console.error);
705
+ `;
706
+ import_fs2.default.writeFileSync(exampleFile, example);
707
+ console.log(import_chalk5.default.green("\n\u2705 Created"), import_chalk5.default.cyan("codmir.example.ts"));
708
+ }
709
+ console.log(import_chalk5.default.bold("\n\u{1F4DA} Next Steps:\n"));
710
+ console.log(import_chalk5.default.dim(" 1."), "Install the package:", import_chalk5.default.cyan("npm install codmir"));
711
+ console.log(import_chalk5.default.dim(" 2."), "Get your API token from:", import_chalk5.default.cyan("https://codmir.com/settings/tokens"));
712
+ console.log(import_chalk5.default.dim(" 3."), "Set environment variable:", import_chalk5.default.cyan("CODMIR_API_KEY=your-token"));
713
+ console.log(import_chalk5.default.dim(" 4."), "Check", import_chalk5.default.cyan("codmir.example.ts"), "for usage examples");
714
+ console.log(import_chalk5.default.dim("\n \u{1F4D6} Documentation:"), import_chalk5.default.cyan("https://codmir.com/docs"));
715
+ }
716
+
717
+ // src/cli/commands/projects.ts
718
+ var import_chalk6 = __toESM(require("chalk"));
719
+ async function projectsCommand() {
720
+ const token = getToken();
721
+ if (!token) {
722
+ console.error(import_chalk6.default.red("\u274C Not authenticated"));
723
+ console.log(import_chalk6.default.dim(" Run"), import_chalk6.default.cyan("codmir login"), import_chalk6.default.dim("first"));
724
+ process.exit(1);
725
+ }
726
+ console.log(import_chalk6.default.bold("\n\u{1F4C1} Your Projects\n"));
727
+ try {
728
+ const response = await fetch(`${process.env.CODMIR_API_URL || "https://codmir.com"}/api/projects`, {
729
+ headers: {
730
+ "Authorization": `Bearer ${token}`
731
+ }
732
+ });
733
+ if (!response.ok) {
734
+ throw new Error("Failed to fetch projects");
735
+ }
736
+ const projects = await response.json();
737
+ if (projects.length === 0) {
738
+ console.log(import_chalk6.default.yellow(" No projects found"));
739
+ console.log(import_chalk6.default.dim(" Create one at"), import_chalk6.default.cyan("https://codmir.com"));
740
+ return;
741
+ }
742
+ projects.forEach((project, index) => {
743
+ console.log(import_chalk6.default.bold(` ${index + 1}. ${project.name}`), import_chalk6.default.dim(`(${project.key})`));
744
+ console.log(import_chalk6.default.dim(" ID:"), project.id);
745
+ if (project.description) {
746
+ console.log(import_chalk6.default.dim(" Description:"), project.description);
747
+ }
748
+ console.log("");
749
+ });
750
+ console.log(import_chalk6.default.dim(" Link a project:"), import_chalk6.default.cyan("codmir link"));
751
+ } catch (error) {
752
+ console.error(import_chalk6.default.red("\n\u274C Failed to fetch projects:"), error instanceof Error ? error.message : "Unknown error");
753
+ process.exit(1);
754
+ }
755
+ }
756
+
757
+ // src/cli/index.ts
758
+ var program = new import_commander.Command();
759
+ program.name("codmir").description("codmir CLI - the AI that prevents wasted engineering time").version("0.1.0");
760
+ program.command("login").description("Authenticate with codmir").option("--token <token>", "Use an API token directly").action(loginCommand);
761
+ program.command("link").description("Link current directory to a codmir project").option("--project <projectId>", "Project ID to link").option("--org <orgId>", "Organization ID").action(linkCommand);
762
+ program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
763
+ program.command("logout").description("Log out of codmir").action(logoutCommand);
764
+ program.command("init").description("Initialize codmir in your project").action(initCommand);
765
+ program.command("projects").alias("ls").description("List your projects").action(projectsCommand);
766
+ program.parse(process.argv);
767
+ if (!process.argv.slice(2).length) {
768
+ program.outputHelp();
769
+ }