codmir 0.3.1 → 0.3.3

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.
@@ -1,175 +1,188 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CodmirClient
4
- } from "../chunk-7HVQNURM.mjs";
4
+ } from "../chunk-LXEEBTWT.mjs";
5
+ import {
6
+ analyzeCommand,
7
+ clearConfig,
8
+ ensureConfigDir,
9
+ getBaseUrl,
10
+ getExecutionContext,
11
+ getProjectConfig,
12
+ getToken,
13
+ isAuthenticated,
14
+ readConfig,
15
+ readProjectConfig,
16
+ writeConfig,
17
+ writeProjectConfig
18
+ } from "../chunk-ASGAT3Z5.mjs";
19
+ import {
20
+ __commonJS
21
+ } from "../chunk-EBO3CZXG.mjs";
22
+
23
+ // package.json
24
+ var require_package = __commonJS({
25
+ "package.json"(exports, module) {
26
+ module.exports = {
27
+ name: "codmir",
28
+ version: "0.3.3",
29
+ description: "Official codmir SDK - CLI and SDK for AI-powered development: codebase analysis, task replication, usage tracking, and intelligent automation",
30
+ main: "dist/index.js",
31
+ module: "dist/index.mjs",
32
+ types: "dist/index.d.ts",
33
+ bin: {
34
+ codmir: "dist/cli/index.js"
35
+ },
36
+ exports: {
37
+ ".": {
38
+ types: "./dist/index.d.ts",
39
+ require: "./dist/index.js",
40
+ import: "./dist/index.mjs"
41
+ }
42
+ },
43
+ files: [
44
+ "dist",
45
+ "README.md",
46
+ "LICENSE",
47
+ "runkit-example.js"
48
+ ],
49
+ scripts: {
50
+ build: "tsup src/index.ts src/cli/index.ts --format cjs,esm --dts --clean",
51
+ dev: "tsup src/index.ts src/cli/index.ts --format cjs,esm --dts --watch",
52
+ prepublishOnly: "pnpm build",
53
+ test: "jest",
54
+ "test:login": "node scripts/test/login.mjs",
55
+ "test:link": "node scripts/test/link.mjs",
56
+ "test:projects": "node scripts/test/projects.mjs",
57
+ "test:whoami": "node scripts/test/whoami.mjs",
58
+ "test:logout": "node scripts/test/logout.mjs",
59
+ "test:all": "npm run test:logout && npm run test:login && npm run test:whoami && npm run test:projects"
60
+ },
61
+ keywords: [
62
+ "codmir",
63
+ "sdk",
64
+ "cli",
65
+ "ai",
66
+ "codebase-analysis",
67
+ "knowledge-base",
68
+ "task-replication",
69
+ "usage-tracking",
70
+ "observability",
71
+ "project-management",
72
+ "tickets",
73
+ "tasks",
74
+ "automation",
75
+ "multi-agent",
76
+ "ai-assistant"
77
+ ],
78
+ author: "codmir",
79
+ license: "MIT",
80
+ repository: {
81
+ type: "git",
82
+ url: "https://github.com/codmir/codmir.git",
83
+ directory: "apps/web/packages/codmir-client"
84
+ },
85
+ bugs: {
86
+ url: "https://github.com/codmir/codmir/issues"
87
+ },
88
+ homepage: "https://codmir.com",
89
+ runkit: {
90
+ example: "runkit-example.js"
91
+ },
92
+ devDependencies: {
93
+ "@semantic-release/changelog": "^6.0.3",
94
+ "@semantic-release/commit-analyzer": "^13.0.0",
95
+ "@semantic-release/git": "^10.0.1",
96
+ "@semantic-release/github": "^11.0.0",
97
+ "@semantic-release/npm": "^12.0.1",
98
+ "@semantic-release/release-notes-generator": "^14.0.0",
99
+ "@types/node": "^20.10.0",
100
+ "@types/prompts": "^2.4.9",
101
+ "conventional-changelog-conventionalcommits": "^8.0.0",
102
+ "semantic-release": "^24.0.0",
103
+ tsup: "^8.0.1",
104
+ typescript: "^5.8.3"
105
+ },
106
+ dependencies: {
107
+ chalk: "^5.3.0",
108
+ clipboardy: "^5.0.1",
109
+ commander: "^12.0.0",
110
+ "form-data": "^4.0.5",
111
+ glob: "^10.3.10",
112
+ open: "^10.0.0",
113
+ ora: "^9.0.0",
114
+ prompts: "^2.4.2",
115
+ ws: "^8.18.3"
116
+ },
117
+ engines: {
118
+ node: ">=18.0.0"
119
+ }
120
+ };
121
+ }
122
+ });
5
123
 
6
124
  // src/cli/index.ts
7
125
  import { Command } from "commander";
8
126
 
9
127
  // src/cli/utils/auth.ts
10
- import http from "http";
11
128
  import open from "open";
12
-
13
- // src/cli/utils/config.ts
14
- import fs from "fs";
15
- import path from "path";
16
- import os from "os";
17
- var CONFIG_DIR = path.join(os.homedir(), ".codmir");
18
- var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
19
- var PROJECT_CONFIG_FILE = ".codmir.json";
20
- function ensureConfigDir() {
21
- if (!fs.existsSync(CONFIG_DIR)) {
22
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
23
- }
129
+ import * as readline from "readline";
130
+ function promptForToken() {
131
+ return new Promise((resolve) => {
132
+ const rl = readline.createInterface({
133
+ input: process.stdin,
134
+ output: process.stdout
135
+ });
136
+ rl.question("\nPaste your authentication token: ", (token) => {
137
+ rl.close();
138
+ resolve(token.trim());
139
+ });
140
+ });
24
141
  }
25
- function readConfig() {
26
- ensureConfigDir();
27
- if (!fs.existsSync(CONFIG_FILE)) {
28
- return {};
29
- }
142
+ async function startOAuthFlow(baseUrl = "https://codmir.com") {
143
+ const authUrl = `${baseUrl}/cli/auth`;
144
+ console.log("\u{1F510} Opening browser for authentication...");
145
+ console.log(` If browser doesn't open, visit: ${authUrl}`);
146
+ console.log();
30
147
  try {
31
- const data = fs.readFileSync(CONFIG_FILE, "utf-8");
32
- return JSON.parse(data);
33
- } catch (error) {
34
- console.error("Error reading config:", error);
35
- return {};
36
- }
37
- }
38
- function writeConfig(config) {
39
- ensureConfigDir();
40
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
41
- }
42
- function clearConfig() {
43
- if (fs.existsSync(CONFIG_FILE)) {
44
- fs.unlinkSync(CONFIG_FILE);
148
+ await open(authUrl);
149
+ } catch {
150
+ console.log("\u26A0\uFE0F Could not open browser automatically.");
151
+ console.log(` Please visit: ${authUrl}`);
152
+ console.log();
45
153
  }
46
- }
47
- function readProjectConfig(cwd = process.cwd()) {
48
- const configPath = path.join(cwd, PROJECT_CONFIG_FILE);
49
- if (!fs.existsSync(configPath)) {
50
- return null;
154
+ console.log("\u{1F4CB} After authenticating in your browser:");
155
+ console.log(" 1. Copy the token displayed");
156
+ console.log(" 2. Return here and paste it below");
157
+ console.log();
158
+ const token = await promptForToken();
159
+ if (!token) {
160
+ throw new Error("No token provided");
51
161
  }
52
162
  try {
53
- const data = fs.readFileSync(configPath, "utf-8");
54
- return JSON.parse(data);
55
- } catch (error) {
56
- console.error("Error reading project config:", error);
57
- return null;
58
- }
59
- }
60
- function writeProjectConfig(config, cwd = process.cwd()) {
61
- const configPath = path.join(cwd, PROJECT_CONFIG_FILE);
62
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
63
- const gitignorePath = path.join(cwd, ".gitignore");
64
- if (fs.existsSync(gitignorePath)) {
65
- const gitignore = fs.readFileSync(gitignorePath, "utf-8");
66
- if (!gitignore.includes(PROJECT_CONFIG_FILE)) {
67
- fs.appendFileSync(gitignorePath, `
68
- # codmir
69
- ${PROJECT_CONFIG_FILE}
70
- `);
71
- }
72
- }
73
- }
74
- function getToken() {
75
- const config = readConfig();
76
- return config.token || null;
77
- }
78
- function isAuthenticated() {
79
- const token = getToken();
80
- return !!token;
81
- }
82
-
83
- // src/cli/utils/auth.ts
84
- async function startOAuthFlow(baseUrl = "https://codmir.com") {
85
- return new Promise((resolve, reject) => {
86
- const server = http.createServer(async (req, res) => {
87
- const url = new URL(req.url || "/", `http://localhost:${port}`);
88
- if (url.pathname === "/callback") {
89
- const token = url.searchParams.get("token");
90
- const error = url.searchParams.get("error");
91
- if (error) {
92
- res.writeHead(200, { "Content-Type": "text/html" });
93
- res.end(`
94
- <html>
95
- <body style="font-family: system-ui; padding: 40px; text-align: center;">
96
- <h1>\u274C Authentication Failed</h1>
97
- <p>${error}</p>
98
- <p>You can close this window.</p>
99
- </body>
100
- </html>
101
- `);
102
- server.close();
103
- reject(new Error(error));
104
- return;
105
- }
106
- if (!token) {
107
- res.writeHead(400, { "Content-Type": "text/html" });
108
- res.end(`
109
- <html>
110
- <body style="font-family: system-ui; padding: 40px; text-align: center;">
111
- <h1>\u274C No token received</h1>
112
- <p>You can close this window.</p>
113
- </body>
114
- </html>
115
- `);
116
- server.close();
117
- reject(new Error("No token received"));
118
- return;
119
- }
120
- res.writeHead(200, { "Content-Type": "text/html" });
121
- res.end(`
122
- <html>
123
- <body style="font-family: system-ui; padding: 40px; text-align: center;">
124
- <h1>\u2705 Authentication Successful!</h1>
125
- <p>You can now close this window and return to your terminal.</p>
126
- <script>setTimeout(() => window.close(), 2000);</script>
127
- </body>
128
- </html>
129
- `);
130
- try {
131
- const userResponse = await fetch(`${baseUrl}/api/user/profile`, {
132
- headers: {
133
- "Authorization": `Bearer ${token}`
134
- }
135
- });
136
- if (!userResponse.ok) {
137
- throw new Error("Failed to fetch user info");
138
- }
139
- const user = await userResponse.json();
140
- server.close();
141
- resolve({
142
- token,
143
- user: {
144
- id: user.id,
145
- email: user.email,
146
- name: user.name
147
- }
148
- });
149
- } catch (error2) {
150
- server.close();
151
- reject(error2);
152
- }
153
- } else {
154
- res.writeHead(404);
155
- res.end("Not found");
163
+ const userResponse = await fetch(`${baseUrl}/api/user/profile`, {
164
+ headers: {
165
+ "X-API-Key": token
156
166
  }
157
167
  });
158
- const port = 3333;
159
- server.listen(port, () => {
160
- const authUrl = `${baseUrl}/cli/auth?port=${port}`;
161
- console.log("\u{1F510} Opening browser for authentication...");
162
- console.log(` If browser doesn't open, visit: ${authUrl}`);
163
- open(authUrl).catch(() => {
164
- console.log("\n\u26A0\uFE0F Could not open browser automatically.");
165
- console.log(` Please visit: ${authUrl}`);
166
- });
167
- });
168
- setTimeout(() => {
169
- server.close();
170
- reject(new Error("Authentication timeout"));
171
- }, 5 * 60 * 1e3);
172
- });
168
+ if (!userResponse.ok) {
169
+ throw new Error("Invalid token or authentication failed");
170
+ }
171
+ const response = await userResponse.json();
172
+ const user = response.data || response;
173
+ return {
174
+ token,
175
+ user: {
176
+ id: user.id,
177
+ email: user.email,
178
+ name: user.name
179
+ }
180
+ };
181
+ } catch (error) {
182
+ throw new Error(
183
+ error instanceof Error ? error.message : "Failed to validate token"
184
+ );
185
+ }
173
186
  }
174
187
  async function authenticateWithToken(token, baseUrl = "https://codmir.com") {
175
188
  const response = await fetch(`${baseUrl}/api/user/profile`, {
@@ -180,7 +193,8 @@ async function authenticateWithToken(token, baseUrl = "https://codmir.com") {
180
193
  if (!response.ok) {
181
194
  throw new Error("Invalid token");
182
195
  }
183
- const user = await response.json();
196
+ const responseData = await response.json();
197
+ const user = responseData.data || responseData;
184
198
  return {
185
199
  token,
186
200
  user: {
@@ -215,11 +229,16 @@ async function loginCommand(options) {
215
229
  console.log(chalk.dim(" Authenticate to start using codmir CLI\n"));
216
230
  try {
217
231
  let authResult;
232
+ const baseUrl = getBaseUrl();
233
+ if (baseUrl !== "https://codmir.com") {
234
+ console.log(chalk.dim(` Connecting to: ${baseUrl}
235
+ `));
236
+ }
218
237
  if (options.token) {
219
238
  console.log(chalk.dim(" Authenticating with provided token..."));
220
- authResult = await authenticateWithToken(options.token);
239
+ authResult = await authenticateWithToken(options.token, baseUrl);
221
240
  } else {
222
- authResult = await startOAuthFlow();
241
+ authResult = await startOAuthFlow(baseUrl);
223
242
  }
224
243
  saveAuth(authResult);
225
244
  console.log(chalk.green("\n\u2705 Successfully logged in!"));
@@ -232,24 +251,124 @@ async function loginCommand(options) {
232
251
  }
233
252
  }
234
253
 
235
- // src/cli/commands/link.ts
254
+ // src/cli/commands/logout.ts
236
255
  import chalk2 from "chalk";
256
+ async function logoutCommand() {
257
+ if (!isAuthenticated()) {
258
+ console.log(chalk2.yellow("\u26A0\uFE0F Not logged in"));
259
+ return;
260
+ }
261
+ const config = readConfig();
262
+ clearConfig();
263
+ console.log(chalk2.green("\u2705 Successfully logged out"));
264
+ if (config.email) {
265
+ console.log(chalk2.dim(" Goodbye,"), chalk2.bold(config.email));
266
+ }
267
+ }
268
+
269
+ // src/cli/commands/whoami.ts
270
+ import chalk3 from "chalk";
271
+ async function whoamiCommand() {
272
+ if (!isAuthenticated()) {
273
+ console.error(chalk3.red("\u274C Not authenticated"));
274
+ console.log(chalk3.dim(" Run"), chalk3.cyan("codmir login"), chalk3.dim("first"));
275
+ process.exit(1);
276
+ }
277
+ const config = readConfig();
278
+ console.log(chalk3.bold("\n\u{1F464} Current User\n"));
279
+ console.log(chalk3.dim(" Name:"), chalk3.bold(config.name || "N/A"));
280
+ console.log(chalk3.dim(" Email:"), chalk3.bold(config.email || "N/A"));
281
+ console.log(chalk3.dim(" User ID:"), chalk3.dim(config.userId || "N/A"));
282
+ if (config.lastLogin) {
283
+ const lastLogin = new Date(config.lastLogin);
284
+ console.log(chalk3.dim(" Last Login:"), chalk3.dim(lastLogin.toLocaleString()));
285
+ }
286
+ }
287
+
288
+ // src/cli/commands/link.ts
237
289
  import prompts from "prompts";
290
+ import chalk4 from "chalk";
291
+
292
+ // src/cli/utils/machine-id.ts
293
+ import fs from "fs";
294
+ import path from "path";
295
+ import os from "os";
296
+ import crypto from "crypto";
297
+ var MACHINE_ID_FILE = path.join(os.homedir(), ".codmir", "machine-id");
298
+ function getMachineId() {
299
+ ensureConfigDir();
300
+ if (fs.existsSync(MACHINE_ID_FILE)) {
301
+ try {
302
+ return fs.readFileSync(MACHINE_ID_FILE, "utf-8").trim();
303
+ } catch (error) {
304
+ console.error("Error reading machine ID:", error);
305
+ }
306
+ }
307
+ const machineId = crypto.randomBytes(16).toString("hex");
308
+ try {
309
+ fs.writeFileSync(MACHINE_ID_FILE, machineId);
310
+ } catch (error) {
311
+ console.error("Error saving machine ID:", error);
312
+ }
313
+ return machineId;
314
+ }
315
+ function getMachineInfo() {
316
+ return {
317
+ machineId: getMachineId(),
318
+ hostname: os.hostname(),
319
+ platform: os.platform(),
320
+ arch: os.arch(),
321
+ nodeVersion: process.version,
322
+ cliVersion: getPackageVersion()
323
+ };
324
+ }
325
+ function getPackageVersion() {
326
+ try {
327
+ const pkg = require_package();
328
+ return pkg.version;
329
+ } catch {
330
+ return "unknown";
331
+ }
332
+ }
333
+ async function registerMachine(projectId, token, baseUrl) {
334
+ const machineInfo = getMachineInfo();
335
+ try {
336
+ const response = await fetch(`${baseUrl}/api/cli/register`, {
337
+ method: "POST",
338
+ headers: {
339
+ "Content-Type": "application/json",
340
+ "X-API-Key": token
341
+ },
342
+ body: JSON.stringify({
343
+ projectId,
344
+ ...machineInfo,
345
+ workingDirectory: process.cwd()
346
+ })
347
+ });
348
+ if (!response.ok) {
349
+ throw new Error(`Failed to register machine: ${response.statusText}`);
350
+ }
351
+ } catch (error) {
352
+ console.error("[codmir] Failed to register machine:", error);
353
+ }
354
+ }
355
+
356
+ // src/cli/commands/link.ts
238
357
  async function linkCommand(options) {
239
358
  const token = getToken();
240
359
  if (!token) {
241
- console.error(chalk2.red("\u274C Not authenticated"));
242
- console.log(chalk2.dim(" Run"), chalk2.cyan("codmir login"), chalk2.dim("first"));
360
+ console.error(chalk4.red("\u274C Not authenticated"));
361
+ console.log(chalk4.dim(" Run"), chalk4.cyan("codmir login"), chalk4.dim("first"));
243
362
  process.exit(1);
244
363
  }
245
- console.log(chalk2.bold("\n\u{1F517} codmir link"));
246
- console.log(chalk2.dim(" Link this directory to a codmir project\n"));
364
+ console.log(chalk4.bold("\n\u{1F517} codmir link"));
365
+ console.log(chalk4.dim(" Link this directory to a codmir project\n"));
247
366
  const existing = readProjectConfig();
248
367
  if (existing) {
249
- console.log(chalk2.yellow("\u26A0\uFE0F This directory is already linked to:"));
250
- console.log(chalk2.dim(" Project ID:"), chalk2.bold(existing.projectId));
368
+ console.log(chalk4.yellow("\u26A0\uFE0F This directory is already linked to:"));
369
+ console.log(chalk4.dim(" Project ID:"), chalk4.bold(existing.projectId));
251
370
  if (existing.projectName) {
252
- console.log(chalk2.dim(" Project:"), chalk2.bold(existing.projectName));
371
+ console.log(chalk4.dim(" Project:"), chalk4.bold(existing.projectName));
253
372
  }
254
373
  const { overwrite } = await prompts({
255
374
  type: "confirm",
@@ -258,31 +377,33 @@ async function linkCommand(options) {
258
377
  initial: false
259
378
  });
260
379
  if (!overwrite) {
261
- console.log(chalk2.dim(" Cancelled"));
380
+ console.log(chalk4.dim(" Cancelled"));
262
381
  return;
263
382
  }
264
383
  }
265
384
  try {
385
+ const baseUrl = getBaseUrl();
266
386
  const client = new CodmirClient({
267
387
  apiKey: token,
268
- baseUrl: process.env.CODMIR_API_URL || "https://codmir.com"
388
+ baseUrl
269
389
  });
270
390
  let projectId = options.project;
271
391
  let orgId = options.org;
272
- console.log(chalk2.dim(" Fetching your projects...\n"));
273
- const response = await fetch(`${process.env.CODMIR_API_URL || "https://codmir.com"}/api/projects`, {
392
+ console.log(chalk4.dim(" Fetching your projects...\n"));
393
+ const response = await fetch(`${baseUrl}/api/projects`, {
274
394
  headers: {
275
- "Authorization": `Bearer ${token}`
395
+ "X-API-Key": token
276
396
  }
277
397
  });
278
398
  if (!response.ok) {
279
399
  throw new Error("Failed to fetch projects");
280
400
  }
281
- const projects = await response.json();
401
+ const responseData = await response.json();
402
+ const projects = responseData.data || responseData;
282
403
  if (!projectId) {
283
404
  if (projects.length === 0) {
284
- console.log(chalk2.yellow("\u26A0\uFE0F You don't have any projects yet"));
285
- console.log(chalk2.dim(" Create a project at"), chalk2.cyan("https://codmir.com"));
405
+ console.log(chalk4.yellow("\u26A0\uFE0F You don't have any projects yet"));
406
+ console.log(chalk4.dim(" Create a project at"), chalk4.cyan("https://codmir.com"));
286
407
  process.exit(1);
287
408
  }
288
409
  const { selectedProject } = await prompts({
@@ -296,14 +417,14 @@ async function linkCommand(options) {
296
417
  }))
297
418
  });
298
419
  if (!selectedProject) {
299
- console.log(chalk2.dim(" Cancelled"));
420
+ console.log(chalk4.dim(" Cancelled"));
300
421
  return;
301
422
  }
302
423
  projectId = selectedProject;
303
424
  }
304
425
  const project = projects.find((p) => p.id === projectId);
305
426
  if (!project) {
306
- console.error(chalk2.red("\u274C Project not found"));
427
+ console.error(chalk4.red("\u274C Project not found"));
307
428
  process.exit(1);
308
429
  }
309
430
  writeProjectConfig({
@@ -311,51 +432,35 @@ async function linkCommand(options) {
311
432
  organizationId: project.organizationId || orgId,
312
433
  projectName: project.name
313
434
  });
314
- console.log(chalk2.green("\n\u2705 Successfully linked!"));
315
- console.log(chalk2.dim(" Project:"), chalk2.bold(project.name));
316
- console.log(chalk2.dim(" ID:"), chalk2.bold(project.id));
317
- console.log(chalk2.dim("\n Configuration saved to"), chalk2.cyan(".codmir.json"));
318
- console.log(chalk2.dim(" You can now use the codmir package in your project"));
435
+ console.log(chalk4.green("\n\u2705 Successfully linked!"));
436
+ console.log(chalk4.dim(" Project:"), chalk4.bold(project.name));
437
+ console.log(chalk4.dim(" ID:"), chalk4.bold(project.id));
438
+ console.log(chalk4.dim("\n Configuration saved to"), chalk4.cyan(".codmir.json"));
439
+ console.log(chalk4.dim(" You can now use the codmir package in your project"));
440
+ await registerMachine(project.id, token, baseUrl);
441
+ console.log();
442
+ const { analyze } = await prompts({
443
+ type: "confirm",
444
+ name: "analyze",
445
+ message: "Analyze codebase now? (Recommended)",
446
+ initial: true
447
+ });
448
+ if (analyze) {
449
+ console.log();
450
+ const { analyzeCommand: analyzeCommand2 } = await import("../analyze-LULBI4ZC.mjs");
451
+ await analyzeCommand2({ mode: "local" });
452
+ } else {
453
+ console.log();
454
+ console.log(chalk4.dim(" You can analyze later with:"), chalk4.cyan("codmir analyze"));
455
+ console.log();
456
+ }
319
457
  } catch (error) {
320
- console.error(chalk2.red("\n\u274C Link failed:"), error instanceof Error ? error.message : "Unknown error");
458
+ console.log(chalk4.red("\n\u274C Failed to link project"));
459
+ console.log(chalk4.dim(error.message));
321
460
  process.exit(1);
322
461
  }
323
462
  }
324
463
 
325
- // src/cli/commands/whoami.ts
326
- import chalk3 from "chalk";
327
- async function whoamiCommand() {
328
- if (!isAuthenticated()) {
329
- console.error(chalk3.red("\u274C Not authenticated"));
330
- console.log(chalk3.dim(" Run"), chalk3.cyan("codmir login"), chalk3.dim("first"));
331
- process.exit(1);
332
- }
333
- const config = readConfig();
334
- console.log(chalk3.bold("\n\u{1F464} Current User\n"));
335
- console.log(chalk3.dim(" Name:"), chalk3.bold(config.name || "N/A"));
336
- console.log(chalk3.dim(" Email:"), chalk3.bold(config.email || "N/A"));
337
- console.log(chalk3.dim(" User ID:"), chalk3.dim(config.userId || "N/A"));
338
- if (config.lastLogin) {
339
- const lastLogin = new Date(config.lastLogin);
340
- console.log(chalk3.dim(" Last Login:"), chalk3.dim(lastLogin.toLocaleString()));
341
- }
342
- }
343
-
344
- // src/cli/commands/logout.ts
345
- import chalk4 from "chalk";
346
- async function logoutCommand() {
347
- if (!isAuthenticated()) {
348
- console.log(chalk4.yellow("\u26A0\uFE0F Not logged in"));
349
- return;
350
- }
351
- const config = readConfig();
352
- clearConfig();
353
- console.log(chalk4.green("\u2705 Successfully logged out"));
354
- if (config.email) {
355
- console.log(chalk4.dim(" Goodbye,"), chalk4.bold(config.email));
356
- }
357
- }
358
-
359
464
  // src/cli/commands/init.ts
360
465
  import chalk5 from "chalk";
361
466
  import fs2 from "fs";
@@ -435,15 +540,17 @@ async function projectsCommand() {
435
540
  }
436
541
  console.log(chalk6.bold("\n\u{1F4C1} Your Projects\n"));
437
542
  try {
438
- const response = await fetch(`${process.env.CODMIR_API_URL || "https://codmir.com"}/api/projects`, {
543
+ const baseUrl = getBaseUrl();
544
+ const response = await fetch(`${baseUrl}/api/projects`, {
439
545
  headers: {
440
- "Authorization": `Bearer ${token}`
546
+ "X-API-Key": token
441
547
  }
442
548
  });
443
549
  if (!response.ok) {
444
550
  throw new Error("Failed to fetch projects");
445
551
  }
446
- const projects = await response.json();
552
+ const responseData = await response.json();
553
+ const projects = responseData.data || responseData;
447
554
  if (projects.length === 0) {
448
555
  console.log(chalk6.yellow(" No projects found"));
449
556
  console.log(chalk6.dim(" Create one at"), chalk6.cyan("https://codmir.com"));
@@ -464,6 +571,856 @@ async function projectsCommand() {
464
571
  }
465
572
  }
466
573
 
574
+ // src/cli/commands/assistant.ts
575
+ import chalk7 from "chalk";
576
+ import ora from "ora";
577
+ import prompts2 from "prompts";
578
+ import clipboardy from "clipboardy";
579
+ import fs3 from "fs";
580
+ import path3 from "path";
581
+ import FormData from "form-data";
582
+ async function assistantCommand(query, options = {}) {
583
+ const token = getToken();
584
+ if (!token) {
585
+ console.error(chalk7.red("\u274C Not authenticated"));
586
+ console.log(chalk7.dim(" Run"), chalk7.cyan("codmir login"), chalk7.dim("first"));
587
+ process.exit(1);
588
+ }
589
+ const baseUrl = getBaseUrl();
590
+ const conversationHistory = [];
591
+ const context = getExecutionContext();
592
+ if (context.isLinkedProject) {
593
+ console.log(chalk7.dim(` \u{1F4C1} Project: ${context.projectConfig?.projectName || context.projectConfig?.projectId}`));
594
+ console.log(chalk7.dim(` \u{1F517} Mode: ${chalk7.cyan("local")} (linked project)
595
+ `));
596
+ } else {
597
+ console.log(chalk7.dim(` \u{1F310} Mode: ${chalk7.yellow("global")}
598
+ `));
599
+ }
600
+ conversationHistory.push({
601
+ role: "system",
602
+ content: `You are codmir, an AI assistant for developers. You help with:
603
+ - Code questions and debugging
604
+ - Project setup and configuration
605
+ - Best practices and patterns
606
+ - Task automation
607
+
608
+ Be concise, practical, and code-focused. Use lowercase "codmir" for the brand.`
609
+ });
610
+ console.log(chalk7.bold("\n\u{1F916} codmir"));
611
+ console.log(chalk7.dim(" AI assistant for developers\n"));
612
+ if (query) {
613
+ await handleQuery(query, conversationHistory, baseUrl, token, options);
614
+ return;
615
+ }
616
+ console.log(chalk7.dim(" Type your question or"), chalk7.cyan("exit"), chalk7.dim("to quit"));
617
+ console.log(chalk7.dim(" \u{1F4CB} Tip: Copy images to clipboard before asking for automatic upload"));
618
+ console.log(chalk7.dim(' Example: "How do I setup authentication?"\n'));
619
+ while (true) {
620
+ const { userInput } = await prompts2({
621
+ type: "text",
622
+ name: "userInput",
623
+ message: chalk7.cyan("codmir"),
624
+ validate: (value) => value.length > 0 || "Please enter a question"
625
+ });
626
+ if (!userInput || userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") {
627
+ console.log(chalk7.dim("\n Goodbye! \u{1F44B}\n"));
628
+ break;
629
+ }
630
+ await handleQuery(userInput, conversationHistory, baseUrl, token, options);
631
+ }
632
+ }
633
+ async function handleQuery(query, conversationHistory, baseUrl, token, options) {
634
+ let imagePaths;
635
+ const hasClipboardImage = await checkClipboardForImage();
636
+ if (hasClipboardImage) {
637
+ const { attachImage } = await prompts2({
638
+ type: "confirm",
639
+ name: "attachImage",
640
+ message: chalk7.cyan("\u{1F4CB} Image detected in clipboard. Attach to message?"),
641
+ initial: true
642
+ });
643
+ if (attachImage) {
644
+ const imagePath = await saveClipboardImage();
645
+ if (imagePath) {
646
+ imagePaths = [imagePath];
647
+ console.log(chalk7.green(" \u2713 Image attached from clipboard\n"));
648
+ }
649
+ }
650
+ }
651
+ conversationHistory.push({
652
+ role: "user",
653
+ content: query,
654
+ images: imagePaths
655
+ });
656
+ const spinner = ora({
657
+ text: chalk7.dim("Thinking..."),
658
+ color: "cyan",
659
+ spinner: {
660
+ interval: 80,
661
+ frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
662
+ }
663
+ }).start();
664
+ try {
665
+ let response;
666
+ if (imagePaths && imagePaths.length > 0) {
667
+ const formData = new FormData();
668
+ formData.append("query", query);
669
+ formData.append("model", options.model || "gpt-4-turbo-preview");
670
+ if (options.project) formData.append("project", options.project);
671
+ if (options.context) formData.append("includeContext", "true");
672
+ formData.append("messages", JSON.stringify(conversationHistory));
673
+ for (const imagePath of imagePaths) {
674
+ formData.append("images", fs3.createReadStream(imagePath));
675
+ }
676
+ response = await fetch(`${baseUrl}/api/assistant/chat`, {
677
+ method: "POST",
678
+ headers: {
679
+ "X-API-Key": token,
680
+ ...formData.getHeaders()
681
+ },
682
+ body: formData
683
+ });
684
+ } else {
685
+ response = await fetch(`${baseUrl}/api/assistant/chat`, {
686
+ method: "POST",
687
+ headers: {
688
+ "Content-Type": "application/json",
689
+ "X-API-Key": token
690
+ },
691
+ body: JSON.stringify({
692
+ messages: conversationHistory,
693
+ model: options.model || "gpt-4-turbo-preview",
694
+ project: options.project,
695
+ includeContext: options.context
696
+ })
697
+ });
698
+ }
699
+ spinner.stop();
700
+ if (!response.ok) {
701
+ throw new Error(`API error: ${response.status}`);
702
+ }
703
+ const data = await response.json();
704
+ const assistantMessage = data.message || data.content || "Sorry, I couldn't process that.";
705
+ conversationHistory.push({
706
+ role: "assistant",
707
+ content: assistantMessage
708
+ });
709
+ console.log("\n" + renderResponse(assistantMessage) + "\n");
710
+ } catch (error) {
711
+ spinner.stop();
712
+ console.error(chalk7.red("\n\u274C Error:"), error instanceof Error ? error.message : "Unknown error");
713
+ console.log(chalk7.dim(" Try again or check your connection\n"));
714
+ }
715
+ }
716
+ function renderResponse(message) {
717
+ const indicator = getIndicator();
718
+ const lines = message.split("\n");
719
+ let output = "";
720
+ lines.forEach((line, index) => {
721
+ if (index === 0) {
722
+ output += `${indicator} ${chalk7.white(line)}
723
+ `;
724
+ } else {
725
+ output += ` ${chalk7.white(line)}
726
+ `;
727
+ }
728
+ });
729
+ return output;
730
+ }
731
+ function getIndicator() {
732
+ const isDark = isTerminalDark();
733
+ if (isDark) {
734
+ return chalk7.whiteBright("\u25CF");
735
+ } else {
736
+ return chalk7.black("\u25CF");
737
+ }
738
+ }
739
+ function isTerminalDark() {
740
+ const colorScheme = process.env.COLORFGBG;
741
+ if (colorScheme) {
742
+ const parts = colorScheme.split(";");
743
+ const bg = parseInt(parts[1] || "0");
744
+ return bg < 8;
745
+ }
746
+ return true;
747
+ }
748
+ async function quickQueryCommand(query, options = {}) {
749
+ const conversationHistory = [{
750
+ role: "system",
751
+ content: "You are codmir, a concise AI assistant for developers. Give short, practical answers."
752
+ }];
753
+ const token = getToken();
754
+ if (!token) {
755
+ console.error(chalk7.red("\u274C Not authenticated"));
756
+ process.exit(1);
757
+ }
758
+ await handleQuery(query, conversationHistory, getBaseUrl(), token, options);
759
+ }
760
+ async function checkClipboardForImage() {
761
+ try {
762
+ const clipboardContent = await clipboardy.read();
763
+ return clipboardContent.startsWith("data:image/") || clipboardContent.match(/\.(png|jpg|jpeg|gif|webp)$/i) !== null;
764
+ } catch {
765
+ return false;
766
+ }
767
+ }
768
+ async function saveClipboardImage() {
769
+ try {
770
+ const clipboardContent = await clipboardy.read();
771
+ if (clipboardContent.startsWith("data:image/")) {
772
+ const matches = clipboardContent.match(/^data:image\/(\w+);base64,(.+)$/);
773
+ if (!matches) return null;
774
+ const ext = matches[1];
775
+ const base64Data = matches[2];
776
+ const buffer = Buffer.from(base64Data, "base64");
777
+ const tmpPath = path3.join("/tmp", `codmir-assistant-${Date.now()}.${ext}`);
778
+ fs3.writeFileSync(tmpPath, buffer);
779
+ return tmpPath;
780
+ }
781
+ if (fs3.existsSync(clipboardContent)) {
782
+ return clipboardContent;
783
+ }
784
+ return null;
785
+ } catch (error) {
786
+ console.error(chalk7.dim(" Failed to save clipboard image"));
787
+ return null;
788
+ }
789
+ }
790
+
791
+ // src/cli/commands/ticket.ts
792
+ import chalk8 from "chalk";
793
+ import prompts3 from "prompts";
794
+ import clipboardy2 from "clipboardy";
795
+ import fs4 from "fs";
796
+ import path4 from "path";
797
+ import FormData2 from "form-data";
798
+ async function createTicketCommand() {
799
+ const token = getToken();
800
+ if (!token) {
801
+ console.error(chalk8.red("\u274C Not authenticated"));
802
+ console.log(chalk8.dim(" Run"), chalk8.cyan("codmir login"), chalk8.dim("first"));
803
+ process.exit(1);
804
+ }
805
+ console.log(chalk8.bold("\n\u{1F3AB} Create Ticket\n"));
806
+ try {
807
+ const linkedProject = getProjectConfig();
808
+ let selectedProject = null;
809
+ let selectedOrg = null;
810
+ if (linkedProject) {
811
+ const { useLinked } = await prompts3({
812
+ type: "confirm",
813
+ name: "useLinked",
814
+ message: `Use linked project: ${chalk8.cyan(linkedProject.name)}?`,
815
+ initial: true
816
+ });
817
+ if (useLinked) {
818
+ selectedProject = linkedProject;
819
+ }
820
+ }
821
+ if (!selectedProject) {
822
+ const selection = await navigateAndSelectProject(token);
823
+ if (!selection) {
824
+ console.log(chalk8.yellow("\n\u26A0\uFE0F Cancelled"));
825
+ return;
826
+ }
827
+ selectedProject = selection.project;
828
+ selectedOrg = selection.organization;
829
+ }
830
+ console.log(chalk8.green("\n\u2713 Project:"), chalk8.cyan(selectedProject.name));
831
+ const ticketData = await collectTicketDetails();
832
+ if (!ticketData) {
833
+ console.log(chalk8.yellow("\n\u26A0\uFE0F Cancelled"));
834
+ return;
835
+ }
836
+ const hasClipboardImage = await checkClipboardForImage2();
837
+ if (hasClipboardImage) {
838
+ const { attachImage } = await prompts3({
839
+ type: "confirm",
840
+ name: "attachImage",
841
+ message: chalk8.cyan("\u{1F4CB} Image detected in clipboard. Attach to ticket?"),
842
+ initial: true
843
+ });
844
+ if (attachImage) {
845
+ const imagePath = await saveClipboardImage2();
846
+ if (imagePath) {
847
+ ticketData.images = [imagePath];
848
+ console.log(chalk8.green("\u2713 Image attached"));
849
+ }
850
+ }
851
+ }
852
+ await createTicket(token, selectedProject.id, ticketData);
853
+ } catch (error) {
854
+ console.error(chalk8.red("\n\u274C Error:"), error instanceof Error ? error.message : "Unknown error");
855
+ process.exit(1);
856
+ }
857
+ }
858
+ async function navigateAndSelectProject(token) {
859
+ const baseUrl = getBaseUrl();
860
+ console.log(chalk8.dim("\n Loading organizations..."));
861
+ const orgsResponse = await fetch(`${baseUrl}/api/user/organizations`, {
862
+ headers: { "X-API-Key": token }
863
+ });
864
+ if (!orgsResponse.ok) {
865
+ throw new Error("Failed to fetch organizations");
866
+ }
867
+ const { organizations } = await orgsResponse.json();
868
+ if (!organizations || organizations.length === 0) {
869
+ console.log(chalk8.yellow("\n\u26A0\uFE0F No organizations found"));
870
+ return null;
871
+ }
872
+ console.log(chalk8.bold("\n\u{1F579}\uFE0F Select Organization"));
873
+ console.log(chalk8.dim(" Use \u2191\u2193 arrows to navigate, Enter to select\n"));
874
+ const { orgIndex } = await prompts3({
875
+ type: "select",
876
+ name: "orgIndex",
877
+ message: chalk8.cyan("Organization"),
878
+ choices: organizations.map((org, index) => ({
879
+ title: `${getJoystickIndicator(index)} ${org.name}`,
880
+ value: index,
881
+ description: org.slug
882
+ })),
883
+ initial: 0
884
+ });
885
+ if (orgIndex === void 0) return null;
886
+ const selectedOrg = organizations[orgIndex];
887
+ console.log(chalk8.dim("\n Loading projects..."));
888
+ const projectsResponse = await fetch(
889
+ `${baseUrl}/api/organizations/${selectedOrg.id}/projects`,
890
+ { headers: { "X-API-Key": token } }
891
+ );
892
+ if (!projectsResponse.ok) {
893
+ throw new Error("Failed to fetch projects");
894
+ }
895
+ const projectsData = await projectsResponse.json();
896
+ const projects = projectsData.data || projectsData;
897
+ if (!projects || projects.length === 0) {
898
+ console.log(chalk8.yellow("\n\u26A0\uFE0F No projects found in this organization"));
899
+ return null;
900
+ }
901
+ console.log(chalk8.bold("\n\u{1F579}\uFE0F Select Project"));
902
+ console.log(chalk8.dim(" Use \u2191\u2193 arrows to navigate, Enter to select\n"));
903
+ const { projectIndex } = await prompts3({
904
+ type: "select",
905
+ name: "projectIndex",
906
+ message: chalk8.cyan("Project"),
907
+ choices: projects.map((project, index) => ({
908
+ title: `${getJoystickIndicator(index)} ${project.name}`,
909
+ value: index,
910
+ description: project.key
911
+ })),
912
+ initial: 0
913
+ });
914
+ if (projectIndex === void 0) return null;
915
+ return {
916
+ organization: selectedOrg,
917
+ project: projects[projectIndex]
918
+ };
919
+ }
920
+ function getJoystickIndicator(index) {
921
+ const indicators = ["\u25B8", "\u25B9", "\u25B8", "\u25B9"];
922
+ return chalk8.cyan(indicators[index % indicators.length]);
923
+ }
924
+ async function collectTicketDetails() {
925
+ console.log(chalk8.bold("\n\u{1F4DD} Ticket Details\n"));
926
+ const responses = await prompts3([
927
+ {
928
+ type: "text",
929
+ name: "title",
930
+ message: chalk8.cyan("Title"),
931
+ validate: (value) => value.length > 0 || "Title is required"
932
+ },
933
+ {
934
+ type: "text",
935
+ name: "description",
936
+ message: chalk8.cyan("Description"),
937
+ initial: ""
938
+ },
939
+ {
940
+ type: "select",
941
+ name: "type",
942
+ message: chalk8.cyan("Type"),
943
+ choices: [
944
+ { title: "\u{1F41B} Bug", value: "BUG" },
945
+ { title: "\u2728 Feature", value: "FEATURE" },
946
+ { title: "\u{1F4CB} Task", value: "TASK" },
947
+ { title: "\u{1F527} Improvement", value: "IMPROVEMENT" }
948
+ ],
949
+ initial: 0
950
+ },
951
+ {
952
+ type: "select",
953
+ name: "priority",
954
+ message: chalk8.cyan("Priority"),
955
+ choices: [
956
+ { title: "\u{1F7E2} Low", value: "LOW" },
957
+ { title: "\u{1F7E1} Medium", value: "MEDIUM" },
958
+ { title: "\u{1F7E0} High", value: "HIGH" },
959
+ { title: "\u{1F534} Critical", value: "CRITICAL" }
960
+ ],
961
+ initial: 1
962
+ }
963
+ ]);
964
+ if (!responses.title) return null;
965
+ return responses;
966
+ }
967
+ async function checkClipboardForImage2() {
968
+ try {
969
+ const clipboardContent = await clipboardy2.read();
970
+ return clipboardContent.startsWith("data:image/") || clipboardContent.match(/\.(png|jpg|jpeg|gif|webp)$/i) !== null;
971
+ } catch {
972
+ return false;
973
+ }
974
+ }
975
+ async function saveClipboardImage2() {
976
+ try {
977
+ const clipboardContent = await clipboardy2.read();
978
+ if (clipboardContent.startsWith("data:image/")) {
979
+ const matches = clipboardContent.match(/^data:image\/(\w+);base64,(.+)$/);
980
+ if (!matches) return null;
981
+ const ext = matches[1];
982
+ const base64Data = matches[2];
983
+ const buffer = Buffer.from(base64Data, "base64");
984
+ const tmpPath = path4.join("/tmp", `codmir-ticket-${Date.now()}.${ext}`);
985
+ fs4.writeFileSync(tmpPath, buffer);
986
+ return tmpPath;
987
+ }
988
+ if (fs4.existsSync(clipboardContent)) {
989
+ return clipboardContent;
990
+ }
991
+ return null;
992
+ } catch (error) {
993
+ console.error(chalk8.dim(" Failed to save clipboard image"));
994
+ return null;
995
+ }
996
+ }
997
+ async function createTicket(token, projectId, ticketData) {
998
+ const baseUrl = getBaseUrl();
999
+ console.log(chalk8.dim("\n Creating ticket..."));
1000
+ try {
1001
+ let response;
1002
+ if (ticketData.images && ticketData.images.length > 0) {
1003
+ const formData = new FormData2();
1004
+ formData.append("title", ticketData.title);
1005
+ if (ticketData.description) formData.append("description", ticketData.description);
1006
+ if (ticketData.type) formData.append("type", ticketData.type);
1007
+ if (ticketData.priority) formData.append("priority", ticketData.priority);
1008
+ for (const imagePath of ticketData.images) {
1009
+ formData.append("images", fs4.createReadStream(imagePath));
1010
+ }
1011
+ response = await fetch(`${baseUrl}/api/project/${projectId}/tickets`, {
1012
+ method: "POST",
1013
+ headers: {
1014
+ "X-API-Key": token,
1015
+ ...formData.getHeaders()
1016
+ },
1017
+ body: formData
1018
+ });
1019
+ } else {
1020
+ response = await fetch(`${baseUrl}/api/project/${projectId}/tickets`, {
1021
+ method: "POST",
1022
+ headers: {
1023
+ "Content-Type": "application/json",
1024
+ "X-API-Key": token
1025
+ },
1026
+ body: JSON.stringify(ticketData)
1027
+ });
1028
+ }
1029
+ if (!response.ok) {
1030
+ const error = await response.text();
1031
+ throw new Error(`Failed to create ticket: ${error}`);
1032
+ }
1033
+ const ticket = await response.json();
1034
+ const ticketData_response = ticket.data || ticket;
1035
+ console.log(chalk8.green("\n\u2705 Ticket created successfully!\n"));
1036
+ console.log(chalk8.bold(" Ticket:"), chalk8.cyan(`#${ticketData_response.key || ticketData_response.id}`));
1037
+ console.log(chalk8.bold(" Title:"), ticketData_response.title);
1038
+ console.log(chalk8.bold(" Type:"), getTypeEmoji(ticketData_response.type), ticketData_response.type);
1039
+ console.log(chalk8.bold(" Priority:"), getPriorityEmoji(ticketData_response.priority), ticketData_response.priority);
1040
+ if (ticketData.images && ticketData.images.length > 0) {
1041
+ console.log(chalk8.bold(" Attachments:"), chalk8.green(`${ticketData.images.length} image(s)`));
1042
+ }
1043
+ console.log();
1044
+ } catch (error) {
1045
+ throw error;
1046
+ }
1047
+ }
1048
+ function getTypeEmoji(type) {
1049
+ const emojis = {
1050
+ BUG: "\u{1F41B}",
1051
+ FEATURE: "\u2728",
1052
+ TASK: "\u{1F4CB}",
1053
+ IMPROVEMENT: "\u{1F527}"
1054
+ };
1055
+ return emojis[type] || "\u{1F4CB}";
1056
+ }
1057
+ function getPriorityEmoji(priority) {
1058
+ const emojis = {
1059
+ LOW: "\u{1F7E2}",
1060
+ MEDIUM: "\u{1F7E1}",
1061
+ HIGH: "\u{1F7E0}",
1062
+ CRITICAL: "\u{1F534}"
1063
+ };
1064
+ return emojis[priority] || "\u{1F7E1}";
1065
+ }
1066
+
1067
+ // src/cli/commands/council.ts
1068
+ import chalk9 from "chalk";
1069
+ import ora2 from "ora";
1070
+ import prompts4 from "prompts";
1071
+ import WebSocket from "ws";
1072
+ var ROLE_ICONS = {
1073
+ architect: "\u{1F3D7}\uFE0F ",
1074
+ security: "\u{1F512}",
1075
+ implementer: "\u{1F4BB}",
1076
+ reviewer: "\u{1F50D}"
1077
+ };
1078
+ var ROLE_COLORS = {
1079
+ architect: chalk9.blue,
1080
+ security: chalk9.red,
1081
+ implementer: chalk9.green,
1082
+ reviewer: chalk9.yellow
1083
+ };
1084
+ async function councilCommand(task, options = {}) {
1085
+ try {
1086
+ printHeader();
1087
+ if (!task) {
1088
+ const response = await prompts4({
1089
+ type: "text",
1090
+ name: "task",
1091
+ message: chalk9.cyan("\u{1F916} What would you like the council to build?"),
1092
+ validate: (value) => value.length > 0 || "Task cannot be empty"
1093
+ });
1094
+ if (!response.task) {
1095
+ console.log(chalk9.yellow("\nCouncil session cancelled."));
1096
+ process.exit(0);
1097
+ }
1098
+ task = response.task;
1099
+ }
1100
+ console.log("");
1101
+ const token = getToken();
1102
+ const baseUrl = getBaseUrl();
1103
+ if (!token) {
1104
+ console.log(chalk9.red("\u274C Not authenticated. Please run: codmir login"));
1105
+ process.exit(1);
1106
+ }
1107
+ const mode = options.local ? "local" : "lambda";
1108
+ const apiUrl = baseUrl.replace("http", "ws") + "/council";
1109
+ console.log(chalk9.gray(`Mode: ${mode === "lambda" ? "AWS Lambda (Serverless)" : "Local LAN Cluster"}`));
1110
+ console.log("");
1111
+ const ws = new WebSocket(apiUrl, {
1112
+ headers: {
1113
+ Authorization: `Bearer ${token}`
1114
+ }
1115
+ });
1116
+ let sessionId = null;
1117
+ let currentSpinner = null;
1118
+ let roundResponses = [];
1119
+ ws.on("open", () => {
1120
+ ws.send(JSON.stringify({
1121
+ type: "create-session",
1122
+ problem: task,
1123
+ mode,
1124
+ options: {
1125
+ initialMembers: parseInt(options.members || "2"),
1126
+ timeout: parseInt(options.timeout || "300") * 1e3
1127
+ }
1128
+ }));
1129
+ });
1130
+ ws.on("message", (data) => {
1131
+ const message = JSON.parse(data.toString());
1132
+ handleCouncilMessage(message, {
1133
+ currentSpinner,
1134
+ roundResponses,
1135
+ onSpinnerUpdate: (spinner) => {
1136
+ currentSpinner = spinner;
1137
+ },
1138
+ onExit: () => process.exit(0)
1139
+ });
1140
+ });
1141
+ ws.on("error", (error) => {
1142
+ if (currentSpinner) currentSpinner.fail();
1143
+ console.log(chalk9.red(`
1144
+ \u274C Connection error: ${error.message}`));
1145
+ process.exit(1);
1146
+ });
1147
+ ws.on("close", () => {
1148
+ if (currentSpinner) currentSpinner.stop();
1149
+ });
1150
+ } catch (error) {
1151
+ console.log(chalk9.red(`
1152
+ \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1153
+ process.exit(1);
1154
+ }
1155
+ }
1156
+ function printHeader() {
1157
+ console.log("");
1158
+ console.log(chalk9.blue.bold("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
1159
+ console.log(chalk9.blue.bold("\u2551 Claude Council - AI Collaboration \u2551"));
1160
+ console.log(chalk9.blue.bold('\u2551 "Preventing wasted engineering time" \u2551'));
1161
+ console.log(chalk9.blue.bold("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1162
+ console.log("");
1163
+ }
1164
+ function handleCouncilMessage(message, context) {
1165
+ const { currentSpinner, roundResponses, onSpinnerUpdate, onExit } = context;
1166
+ switch (message.type) {
1167
+ case "session-created":
1168
+ console.log(chalk9.yellow("\u{1F3AD} Assembling Claude Council..."));
1169
+ console.log("");
1170
+ break;
1171
+ case "spawning-member":
1172
+ const spinner = ora2({
1173
+ text: `Spawning ${message.role} in Docker container on ${message.location || "Lambda"}...`,
1174
+ color: "cyan"
1175
+ }).start();
1176
+ onSpinnerUpdate(spinner);
1177
+ break;
1178
+ case "member-ready":
1179
+ if (currentSpinner) {
1180
+ currentSpinner.succeed(
1181
+ chalk9.green(`\u2713 ${capitalize(message.role)} ready (${message.memberId})`)
1182
+ );
1183
+ }
1184
+ break;
1185
+ case "all-members-ready":
1186
+ console.log("");
1187
+ console.log(chalk9.cyan("\u{1F4AC} Council Session Started"));
1188
+ console.log(chalk9.gray(` Session ID: ${message.sessionId}`));
1189
+ console.log(chalk9.gray(` Members: ${message.memberCount}`));
1190
+ console.log("");
1191
+ break;
1192
+ case "round-start":
1193
+ printRoundHeader(message.round);
1194
+ roundResponses.length = 0;
1195
+ break;
1196
+ case "member-thinking":
1197
+ const thinkSpinner = ora2({
1198
+ text: `${ROLE_ICONS[message.role]} ${capitalize(message.role)} is analyzing...`,
1199
+ color: "yellow"
1200
+ }).start();
1201
+ onSpinnerUpdate(thinkSpinner);
1202
+ break;
1203
+ case "member-response":
1204
+ if (currentSpinner) currentSpinner.stop();
1205
+ const roleColor = ROLE_COLORS[message.role] || chalk9.white;
1206
+ const icon = ROLE_ICONS[message.role] || "\u{1F916}";
1207
+ console.log("");
1208
+ console.log(roleColor.bold(`${icon} ${capitalize(message.role)}:`));
1209
+ const formatted = formatResponse(message.content);
1210
+ console.log(chalk9.gray(` ${formatted}`));
1211
+ console.log("");
1212
+ roundResponses.push(message);
1213
+ break;
1214
+ case "round-complete":
1215
+ const agreementCount = message.agreements || 0;
1216
+ const totalMembers = message.totalMembers || roundResponses.length;
1217
+ const agreementPercent = Math.round(agreementCount / totalMembers * 100);
1218
+ console.log(chalk9.gray(` Agreement: ${agreementCount}/${totalMembers} (${agreementPercent}%)`));
1219
+ console.log("");
1220
+ break;
1221
+ case "consensus-reached":
1222
+ console.log(chalk9.green.bold("\n\u2705 CONSENSUS REACHED!"));
1223
+ console.log(chalk9.gray(` Rounds: ${message.rounds}`));
1224
+ console.log(chalk9.gray(` Time: ${message.time}s`));
1225
+ console.log("");
1226
+ break;
1227
+ case "spawning-additional-member":
1228
+ console.log(chalk9.yellow(`
1229
+ \u26A0\uFE0F Discussion needs more input. Spawning ${message.role}...`));
1230
+ break;
1231
+ case "generating-solution":
1232
+ const genSpinner = ora2({
1233
+ text: "Synthesizing final solution from council discussion...",
1234
+ color: "green"
1235
+ }).start();
1236
+ onSpinnerUpdate(genSpinner);
1237
+ break;
1238
+ case "solution-ready":
1239
+ if (currentSpinner) currentSpinner.succeed("Solution generated");
1240
+ console.log("");
1241
+ console.log(chalk9.blue.bold("\u2500".repeat(65)));
1242
+ console.log(chalk9.blue.bold("\u{1F4CB} Solution"));
1243
+ console.log(chalk9.blue.bold("\u2500".repeat(65)));
1244
+ console.log("");
1245
+ console.log(message.solution);
1246
+ console.log("");
1247
+ if (message.files && message.files.length > 0) {
1248
+ console.log(chalk9.cyan("\u{1F4C1} Files created:"));
1249
+ message.files.forEach((file) => {
1250
+ console.log(chalk9.gray(` \u251C\u2500 ${file}`));
1251
+ });
1252
+ console.log("");
1253
+ }
1254
+ break;
1255
+ case "stats":
1256
+ printStats(message.stats);
1257
+ break;
1258
+ case "cleanup-start":
1259
+ console.log(chalk9.yellow("\u{1F9F9} Cleaning up council members..."));
1260
+ break;
1261
+ case "member-destroyed":
1262
+ console.log(chalk9.gray(` \u251C\u2500 Destroying ${message.role}... \u2713`));
1263
+ break;
1264
+ case "session-complete":
1265
+ console.log("");
1266
+ console.log(chalk9.green.bold("\u2728 Council session complete!"));
1267
+ console.log("");
1268
+ onExit();
1269
+ break;
1270
+ case "timeout":
1271
+ console.log(chalk9.yellow("\n\u23F1\uFE0F Discussion timeout reached"));
1272
+ console.log(chalk9.gray(" The council needs your input to continue."));
1273
+ break;
1274
+ case "error":
1275
+ if (currentSpinner) currentSpinner.fail();
1276
+ console.log(chalk9.red(`
1277
+ \u274C Error: ${message.error}`));
1278
+ onExit();
1279
+ break;
1280
+ }
1281
+ }
1282
+ function printRoundHeader(round) {
1283
+ console.log(chalk9.cyan("\u2500".repeat(65)));
1284
+ console.log(chalk9.cyan(`Round ${round} - Council Discussion`));
1285
+ console.log(chalk9.cyan("\u2500".repeat(65)));
1286
+ }
1287
+ function formatResponse(content) {
1288
+ const maxLength = 300;
1289
+ if (content.length > maxLength) {
1290
+ return content.substring(0, maxLength) + "...";
1291
+ }
1292
+ return content;
1293
+ }
1294
+ function printStats(stats) {
1295
+ console.log(chalk9.blue.bold("\u2500".repeat(65)));
1296
+ console.log(chalk9.blue.bold("\u{1F4CA} Session Statistics"));
1297
+ console.log(chalk9.blue.bold("\u2500".repeat(65)));
1298
+ console.log("");
1299
+ console.log(chalk9.gray(` Council members: ${stats.members || 0}`));
1300
+ console.log(chalk9.gray(` Discussion rounds: ${stats.rounds || 0}`));
1301
+ console.log(chalk9.gray(` Total time: ${stats.time || 0}s`));
1302
+ console.log(chalk9.gray(` API cost: $${(stats.cost || 0).toFixed(2)}`));
1303
+ console.log(chalk9.gray(` Lines of code: ${stats.linesOfCode || 0}`));
1304
+ console.log("");
1305
+ }
1306
+ function capitalize(str) {
1307
+ return str.charAt(0).toUpperCase() + str.slice(1);
1308
+ }
1309
+
1310
+ // src/cli/commands/usage.ts
1311
+ import chalk10 from "chalk";
1312
+ import prompts5 from "prompts";
1313
+ async function usageCommand(options = {}) {
1314
+ const token = getToken();
1315
+ if (!token) {
1316
+ console.error(chalk10.red("\u274C Not authenticated"));
1317
+ console.log(chalk10.dim(" Run"), chalk10.cyan("codmir login"), chalk10.dim("first"));
1318
+ process.exit(1);
1319
+ }
1320
+ console.log(chalk10.bold("\n\u{1F4CA} API Usage & Billing\n"));
1321
+ const baseUrl = getBaseUrl();
1322
+ const month = options.month || (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
1323
+ try {
1324
+ const response = await fetch(`${baseUrl}/api/keys`, {
1325
+ headers: { "X-API-Key": token }
1326
+ });
1327
+ if (!response.ok) {
1328
+ throw new Error(`Failed to fetch API keys: ${response.status}`);
1329
+ }
1330
+ const { keys } = await response.json();
1331
+ if (keys.length === 0) {
1332
+ console.log(chalk10.yellow("\u26A0\uFE0F No API keys found"));
1333
+ console.log(chalk10.dim(" Create one at:"), chalk10.cyan("https://codmir.com/settings/api-keys"));
1334
+ return;
1335
+ }
1336
+ console.log(chalk10.bold(`\u{1F4C5} Month: ${month}
1337
+ `));
1338
+ let totalRequests = 0;
1339
+ let totalAICalls = 0;
1340
+ let totalTokens = 0;
1341
+ let totalCostCents = 0;
1342
+ for (const key of keys) {
1343
+ const usage = key.monthlyUsage[0];
1344
+ const costDollars = usage ? (usage.totalCostCents / 100).toFixed(2) : "0.00";
1345
+ const tokenCount = Number(usage?.totalTokens || 0);
1346
+ totalRequests += usage?.totalRequests || 0;
1347
+ totalAICalls += usage?.totalAICalls || 0;
1348
+ totalTokens += tokenCount;
1349
+ totalCostCents += usage?.totalCostCents || 0;
1350
+ console.log(chalk10.cyan.bold(`\u{1F511} ${key.name}`));
1351
+ console.log(chalk10.dim(` Key: ${key.keyPrefix}...`));
1352
+ console.log(chalk10.dim(` Scope: ${key.scope}`));
1353
+ console.log();
1354
+ console.log(chalk10.bold(" Usage:"));
1355
+ console.log(chalk10.dim(` Total Requests: ${(usage?.totalRequests || 0).toLocaleString()}`));
1356
+ console.log(chalk10.dim(` AI Calls: ${(usage?.totalAICalls || 0).toLocaleString()}`));
1357
+ console.log(chalk10.dim(` Tokens Used: ${tokenCount.toLocaleString()}`));
1358
+ const costColor = getCostColor(usage?.totalCostCents || 0);
1359
+ console.log(chalk10.bold(" Cost:"));
1360
+ console.log(costColor(` This Month: $${costDollars}`));
1361
+ if (key.lastUsedAt) {
1362
+ const lastUsed = new Date(key.lastUsedAt);
1363
+ const daysAgo = Math.floor((Date.now() - lastUsed.getTime()) / (1e3 * 60 * 60 * 24));
1364
+ console.log(chalk10.dim(` Last Used: ${lastUsed.toLocaleDateString()} (${daysAgo} days ago)`));
1365
+ } else {
1366
+ console.log(chalk10.dim(" Last Used: Never"));
1367
+ }
1368
+ if (options.detailed && usage) {
1369
+ console.log();
1370
+ console.log(chalk10.bold(" Provider Breakdown:"));
1371
+ if (usage.openaiTokens > 0) {
1372
+ console.log(chalk10.dim(` OpenAI: ${Number(usage.openaiTokens).toLocaleString()} tokens ($${(usage.openaiCostCents / 100).toFixed(2)})`));
1373
+ }
1374
+ if (usage.anthropicTokens > 0) {
1375
+ console.log(chalk10.dim(` Anthropic: ${Number(usage.anthropicTokens).toLocaleString()} tokens ($${(usage.anthropicCostCents / 100).toFixed(2)})`));
1376
+ }
1377
+ if (usage.localTokens > 0) {
1378
+ console.log(chalk10.dim(` Local: ${Number(usage.localTokens).toLocaleString()} tokens (free)`));
1379
+ }
1380
+ if (usage.totalAnalyses > 0) {
1381
+ console.log();
1382
+ console.log(chalk10.bold(" Codebase Analysis:"));
1383
+ console.log(chalk10.dim(` Total Analyses: ${usage.totalAnalyses}`));
1384
+ console.log(chalk10.dim(` Local: ${usage.localAnalyses}`));
1385
+ console.log(chalk10.dim(` Deep (Railway): ${usage.deepAnalyses} ($${(usage.railwayCostCents / 100).toFixed(2)})`));
1386
+ console.log(chalk10.dim(` Files Processed: ${Number(usage.filesProcessed).toLocaleString()}`));
1387
+ console.log(chalk10.dim(` Lines Processed: ${Number(usage.linesProcessed).toLocaleString()}`));
1388
+ }
1389
+ }
1390
+ console.log();
1391
+ console.log(chalk10.dim(" \u2500".repeat(50)));
1392
+ console.log();
1393
+ }
1394
+ console.log(chalk10.bold.cyan("\u{1F4C8} Summary\n"));
1395
+ console.log(chalk10.dim(` Total Requests: ${totalRequests.toLocaleString()}`));
1396
+ console.log(chalk10.dim(` AI Calls: ${totalAICalls.toLocaleString()}`));
1397
+ console.log(chalk10.dim(` Tokens Used: ${totalTokens.toLocaleString()}`));
1398
+ console.log();
1399
+ console.log(chalk10.bold.green(` Total Cost: $${(totalCostCents / 100).toFixed(2)}`));
1400
+ console.log();
1401
+ if (totalCostCents > 5e3) {
1402
+ console.log(chalk10.yellow(" \u26A0\uFE0F High usage detected this month"));
1403
+ console.log(chalk10.dim(" Consider optimizing AI calls or using local mode"));
1404
+ console.log();
1405
+ }
1406
+ if (!options.detailed && keys.length > 0) {
1407
+ console.log(chalk10.dim(" \u{1F4A1} Tip: Use"), chalk10.cyan("--detailed"), chalk10.dim("for provider breakdown"));
1408
+ }
1409
+ console.log(chalk10.dim(" \u{1F4C5} View other months:"), chalk10.cyan("codmir usage --month 2025-01"));
1410
+ console.log();
1411
+ } catch (error) {
1412
+ console.error(chalk10.red("\n\u274C Failed to get usage stats"));
1413
+ console.error(chalk10.dim(" Error:"), error instanceof Error ? error.message : "Unknown error");
1414
+ process.exit(1);
1415
+ }
1416
+ }
1417
+ function getCostColor(costCents) {
1418
+ if (costCents === 0) return chalk10.dim;
1419
+ if (costCents < 100) return chalk10.green;
1420
+ if (costCents < 1e3) return chalk10.yellow;
1421
+ return chalk10.red;
1422
+ }
1423
+
467
1424
  // src/cli/index.ts
468
1425
  var program = new Command();
469
1426
  program.name("codmir").description("codmir CLI - the AI that prevents wasted engineering time").version("0.1.0");
@@ -472,7 +1429,21 @@ program.command("link").description("Link current directory to a codmir project"
472
1429
  program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
473
1430
  program.command("logout").description("Log out of codmir").action(logoutCommand);
474
1431
  program.command("init").description("Initialize codmir in your project").action(initCommand);
475
- program.command("projects").alias("ls").description("List your projects").action(projectsCommand);
1432
+ program.command("projects").alias("ls").description("List all your projects").action(projectsCommand);
1433
+ program.command("ticket").alias("create").description("\u{1F3AB} Create a new ticket with interactive navigation").action(createTicketCommand);
1434
+ program.command("codmir [query...]").alias("ai").description("\u{1F916} AI assistant for developers").option("-p, --project <id>", "Include project context").option("-c, --context", "Include current directory context").option("-m, --model <name>", "AI model to use (default: gpt-4-turbo-preview)").action((query, options) => {
1435
+ const queryString = query ? query.join(" ") : void 0;
1436
+ assistantCommand(queryString, options);
1437
+ });
1438
+ program.command("ask <query...>").description("Quick AI query without interactive mode").option("-p, --project <id>", "Include project context").action((query, options) => {
1439
+ quickQueryCommand(query.join(" "), options);
1440
+ });
1441
+ program.command("analyze").description("\u{1F50D} Analyze codebase and generate knowledge base").option("--full", "Run full deep analysis").option("--force", "Force re-analysis even if knowledge base exists").option("--mode <mode>", "Analysis mode: local or railway").action(analyzeCommand);
1442
+ program.command("usage").description("\u{1F4CA} View API usage and billing stats").option("--month <YYYY-MM>", "Month to view (default: current month)").option("--detailed", "Show detailed provider breakdown").action(usageCommand);
1443
+ program.command("council [task...]").description("\u{1F3AD} Assemble a council of Claude AI agents to collaborate on complex tasks").option("-m, --members <count>", "Number of initial council members (2-4)", "2").option("-t, --timeout <seconds>", "Max discussion time in seconds", "300").option("--local", "Run on local LAN cluster instead of Lambda").action((task, options) => {
1444
+ const taskString = task ? task.join(" ") : void 0;
1445
+ councilCommand(taskString, options);
1446
+ });
476
1447
  program.parse(process.argv);
477
1448
  if (!process.argv.slice(2).length) {
478
1449
  program.outputHelp();