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.
- package/LICENSE +55 -0
- package/README.md +219 -14
- package/dist/analyze-LULBI4ZC.mjs +7 -0
- package/dist/chunk-ASGAT3Z5.mjs +756 -0
- package/dist/chunk-EBO3CZXG.mjs +15 -0
- package/dist/cli/index.js +1946 -200
- package/dist/cli/index.mjs +1192 -221
- package/dist/index.mjs +2 -1
- package/package.json +27 -8
- /package/dist/{chunk-7HVQNURM.mjs → chunk-LXEEBTWT.mjs} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
13
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
14
|
+
};
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
9
19
|
var __copyProps = (to, from, except, desc) => {
|
|
10
20
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
21
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,20 +33,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
33
|
mod
|
|
24
34
|
));
|
|
25
35
|
|
|
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
36
|
// 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
37
|
function ensureConfigDir() {
|
|
41
38
|
if (!import_fs.default.existsSync(CONFIG_DIR)) {
|
|
42
39
|
import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -99,98 +96,875 @@ function isAuthenticated() {
|
|
|
99
96
|
const token = getToken();
|
|
100
97
|
return !!token;
|
|
101
98
|
}
|
|
99
|
+
function getBaseUrl() {
|
|
100
|
+
if (process.env.CODMIR_API_URL) {
|
|
101
|
+
return process.env.CODMIR_API_URL.replace(/\/$/, "");
|
|
102
|
+
}
|
|
103
|
+
const config = readConfig();
|
|
104
|
+
if (config.baseUrl) {
|
|
105
|
+
return config.baseUrl.replace(/\/$/, "");
|
|
106
|
+
}
|
|
107
|
+
return "https://codmir.com";
|
|
108
|
+
}
|
|
109
|
+
function getProjectConfig() {
|
|
110
|
+
return readProjectConfig();
|
|
111
|
+
}
|
|
112
|
+
function getExecutionContext() {
|
|
113
|
+
const projectConfig = readProjectConfig();
|
|
114
|
+
const isLinkedProject = projectConfig !== null;
|
|
115
|
+
return {
|
|
116
|
+
mode: isLinkedProject ? "local" : "global",
|
|
117
|
+
isLinkedProject,
|
|
118
|
+
projectConfig: projectConfig || void 0,
|
|
119
|
+
workingDirectory: process.cwd()
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
var import_fs, import_path, import_os, CONFIG_DIR, CONFIG_FILE, PROJECT_CONFIG_FILE;
|
|
123
|
+
var init_config = __esm({
|
|
124
|
+
"src/cli/utils/config.ts"() {
|
|
125
|
+
"use strict";
|
|
126
|
+
import_fs = __toESM(require("fs"));
|
|
127
|
+
import_path = __toESM(require("path"));
|
|
128
|
+
import_os = __toESM(require("os"));
|
|
129
|
+
CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".codmir");
|
|
130
|
+
CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
|
|
131
|
+
PROJECT_CONFIG_FILE = ".codmir.json";
|
|
132
|
+
}
|
|
133
|
+
});
|
|
102
134
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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;
|
|
135
|
+
// package.json
|
|
136
|
+
var require_package = __commonJS({
|
|
137
|
+
"package.json"(exports2, module2) {
|
|
138
|
+
module2.exports = {
|
|
139
|
+
name: "codmir",
|
|
140
|
+
version: "0.3.3",
|
|
141
|
+
description: "Official codmir SDK - CLI and SDK for AI-powered development: codebase analysis, task replication, usage tracking, and intelligent automation",
|
|
142
|
+
main: "dist/index.js",
|
|
143
|
+
module: "dist/index.mjs",
|
|
144
|
+
types: "dist/index.d.ts",
|
|
145
|
+
bin: {
|
|
146
|
+
codmir: "dist/cli/index.js"
|
|
147
|
+
},
|
|
148
|
+
exports: {
|
|
149
|
+
".": {
|
|
150
|
+
types: "./dist/index.d.ts",
|
|
151
|
+
require: "./dist/index.js",
|
|
152
|
+
import: "./dist/index.mjs"
|
|
139
153
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
154
|
+
},
|
|
155
|
+
files: [
|
|
156
|
+
"dist",
|
|
157
|
+
"README.md",
|
|
158
|
+
"LICENSE",
|
|
159
|
+
"runkit-example.js"
|
|
160
|
+
],
|
|
161
|
+
scripts: {
|
|
162
|
+
build: "tsup src/index.ts src/cli/index.ts --format cjs,esm --dts --clean",
|
|
163
|
+
dev: "tsup src/index.ts src/cli/index.ts --format cjs,esm --dts --watch",
|
|
164
|
+
prepublishOnly: "pnpm build",
|
|
165
|
+
test: "jest",
|
|
166
|
+
"test:login": "node scripts/test/login.mjs",
|
|
167
|
+
"test:link": "node scripts/test/link.mjs",
|
|
168
|
+
"test:projects": "node scripts/test/projects.mjs",
|
|
169
|
+
"test:whoami": "node scripts/test/whoami.mjs",
|
|
170
|
+
"test:logout": "node scripts/test/logout.mjs",
|
|
171
|
+
"test:all": "npm run test:logout && npm run test:login && npm run test:whoami && npm run test:projects"
|
|
172
|
+
},
|
|
173
|
+
keywords: [
|
|
174
|
+
"codmir",
|
|
175
|
+
"sdk",
|
|
176
|
+
"cli",
|
|
177
|
+
"ai",
|
|
178
|
+
"codebase-analysis",
|
|
179
|
+
"knowledge-base",
|
|
180
|
+
"task-replication",
|
|
181
|
+
"usage-tracking",
|
|
182
|
+
"observability",
|
|
183
|
+
"project-management",
|
|
184
|
+
"tickets",
|
|
185
|
+
"tasks",
|
|
186
|
+
"automation",
|
|
187
|
+
"multi-agent",
|
|
188
|
+
"ai-assistant"
|
|
189
|
+
],
|
|
190
|
+
author: "codmir",
|
|
191
|
+
license: "MIT",
|
|
192
|
+
repository: {
|
|
193
|
+
type: "git",
|
|
194
|
+
url: "https://github.com/codmir/codmir.git",
|
|
195
|
+
directory: "apps/web/packages/codmir-client"
|
|
196
|
+
},
|
|
197
|
+
bugs: {
|
|
198
|
+
url: "https://github.com/codmir/codmir/issues"
|
|
199
|
+
},
|
|
200
|
+
homepage: "https://codmir.com",
|
|
201
|
+
runkit: {
|
|
202
|
+
example: "runkit-example.js"
|
|
203
|
+
},
|
|
204
|
+
devDependencies: {
|
|
205
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
206
|
+
"@semantic-release/commit-analyzer": "^13.0.0",
|
|
207
|
+
"@semantic-release/git": "^10.0.1",
|
|
208
|
+
"@semantic-release/github": "^11.0.0",
|
|
209
|
+
"@semantic-release/npm": "^12.0.1",
|
|
210
|
+
"@semantic-release/release-notes-generator": "^14.0.0",
|
|
211
|
+
"@types/node": "^20.10.0",
|
|
212
|
+
"@types/prompts": "^2.4.9",
|
|
213
|
+
"conventional-changelog-conventionalcommits": "^8.0.0",
|
|
214
|
+
"semantic-release": "^24.0.0",
|
|
215
|
+
tsup: "^8.0.1",
|
|
216
|
+
typescript: "^5.8.3"
|
|
217
|
+
},
|
|
218
|
+
dependencies: {
|
|
219
|
+
chalk: "^5.3.0",
|
|
220
|
+
clipboardy: "^5.0.1",
|
|
221
|
+
commander: "^12.0.0",
|
|
222
|
+
"form-data": "^4.0.5",
|
|
223
|
+
glob: "^10.3.10",
|
|
224
|
+
open: "^10.0.0",
|
|
225
|
+
ora: "^9.0.0",
|
|
226
|
+
prompts: "^2.4.2",
|
|
227
|
+
ws: "^8.18.3"
|
|
228
|
+
},
|
|
229
|
+
engines: {
|
|
230
|
+
node: ">=18.0.0"
|
|
176
231
|
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// src/cli/utils/codebase-analyzer/local.ts
|
|
237
|
+
async function analyzeLocal(projectPath) {
|
|
238
|
+
const startTime = Date.now();
|
|
239
|
+
const [structure, techStack, entryPoints, documentation, metadata] = await Promise.all([
|
|
240
|
+
buildFileTree(projectPath),
|
|
241
|
+
detectTechStack(projectPath),
|
|
242
|
+
findEntryPoints(projectPath),
|
|
243
|
+
extractDocumentation(projectPath),
|
|
244
|
+
collectMetadata(projectPath)
|
|
245
|
+
]);
|
|
246
|
+
const analysis = {
|
|
247
|
+
structure,
|
|
248
|
+
techStack,
|
|
249
|
+
entryPoints,
|
|
250
|
+
documentation,
|
|
251
|
+
metadata,
|
|
252
|
+
analyzedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
253
|
+
};
|
|
254
|
+
const duration = Date.now() - startTime;
|
|
255
|
+
console.log(`Analysis complete in ${duration}ms`);
|
|
256
|
+
return analysis;
|
|
257
|
+
}
|
|
258
|
+
async function buildFileTree(projectPath) {
|
|
259
|
+
const stats = import_fs3.default.statSync(projectPath);
|
|
260
|
+
const name = import_path3.default.basename(projectPath);
|
|
261
|
+
if (!stats.isDirectory()) {
|
|
262
|
+
return {
|
|
263
|
+
name,
|
|
264
|
+
path: projectPath,
|
|
265
|
+
type: "file",
|
|
266
|
+
size: stats.size,
|
|
267
|
+
extension: import_path3.default.extname(projectPath)
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const children = [];
|
|
271
|
+
const entries = import_fs3.default.readdirSync(projectPath);
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
if (shouldIgnore(entry)) continue;
|
|
274
|
+
const fullPath = import_path3.default.join(projectPath, entry);
|
|
275
|
+
try {
|
|
276
|
+
const childNode = await buildFileTree(fullPath);
|
|
277
|
+
children.push(childNode);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
name,
|
|
284
|
+
path: projectPath,
|
|
285
|
+
type: "directory",
|
|
286
|
+
children: children.sort((a, b) => {
|
|
287
|
+
if (a.type !== b.type) {
|
|
288
|
+
return a.type === "directory" ? -1 : 1;
|
|
289
|
+
}
|
|
290
|
+
return a.name.localeCompare(b.name);
|
|
291
|
+
})
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function detectTechStack(projectPath) {
|
|
295
|
+
const techStack = {
|
|
296
|
+
languages: {},
|
|
297
|
+
frameworks: [],
|
|
298
|
+
libraries: [],
|
|
299
|
+
tools: [],
|
|
300
|
+
packageManager: null
|
|
301
|
+
};
|
|
302
|
+
if (import_fs3.default.existsSync(import_path3.default.join(projectPath, "pnpm-lock.yaml"))) {
|
|
303
|
+
techStack.packageManager = "pnpm";
|
|
304
|
+
} else if (import_fs3.default.existsSync(import_path3.default.join(projectPath, "yarn.lock"))) {
|
|
305
|
+
techStack.packageManager = "yarn";
|
|
306
|
+
} else if (import_fs3.default.existsSync(import_path3.default.join(projectPath, "package-lock.json"))) {
|
|
307
|
+
techStack.packageManager = "npm";
|
|
308
|
+
} else if (import_fs3.default.existsSync(import_path3.default.join(projectPath, "bun.lockb"))) {
|
|
309
|
+
techStack.packageManager = "bun";
|
|
310
|
+
}
|
|
311
|
+
const files = await (0, import_glob.glob)("**/*", {
|
|
312
|
+
cwd: projectPath,
|
|
313
|
+
ignore: IGNORE_PATTERNS,
|
|
314
|
+
nodir: true
|
|
315
|
+
});
|
|
316
|
+
const languageCounts = {};
|
|
317
|
+
let totalCodeFiles = 0;
|
|
318
|
+
for (const file of files) {
|
|
319
|
+
const ext = import_path3.default.extname(file);
|
|
320
|
+
const language = LANGUAGE_EXTENSIONS[ext];
|
|
321
|
+
if (language) {
|
|
322
|
+
languageCounts[language] = (languageCounts[language] || 0) + 1;
|
|
323
|
+
totalCodeFiles++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
for (const [language, count] of Object.entries(languageCounts)) {
|
|
327
|
+
techStack.languages[language] = parseFloat(
|
|
328
|
+
(count / totalCodeFiles * 100).toFixed(1)
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
const packageJsonPath = import_path3.default.join(projectPath, "package.json");
|
|
332
|
+
if (import_fs3.default.existsSync(packageJsonPath)) {
|
|
333
|
+
const packageJson = JSON.parse(import_fs3.default.readFileSync(packageJsonPath, "utf-8"));
|
|
334
|
+
const dependencies = {
|
|
335
|
+
...packageJson.dependencies,
|
|
336
|
+
...packageJson.devDependencies
|
|
337
|
+
};
|
|
338
|
+
if (dependencies["next"]) techStack.frameworks.push("Next.js");
|
|
339
|
+
if (dependencies["react"]) techStack.frameworks.push("React");
|
|
340
|
+
if (dependencies["vue"]) techStack.frameworks.push("Vue");
|
|
341
|
+
if (dependencies["svelte"]) techStack.frameworks.push("Svelte");
|
|
342
|
+
if (dependencies["express"]) techStack.frameworks.push("Express");
|
|
343
|
+
if (dependencies["fastify"]) techStack.frameworks.push("Fastify");
|
|
344
|
+
if (dependencies["nestjs"]) techStack.frameworks.push("NestJS");
|
|
345
|
+
if (dependencies["@remix-run/react"]) techStack.frameworks.push("Remix");
|
|
346
|
+
if (dependencies["gatsby"]) techStack.frameworks.push("Gatsby");
|
|
347
|
+
if (dependencies["nuxt"]) techStack.frameworks.push("Nuxt");
|
|
348
|
+
if (dependencies["prisma"]) techStack.libraries.push("Prisma");
|
|
349
|
+
if (dependencies["@tanstack/react-query"]) techStack.libraries.push("TanStack Query");
|
|
350
|
+
if (dependencies["tailwindcss"]) techStack.libraries.push("Tailwind CSS");
|
|
351
|
+
if (dependencies["shadcn/ui"]) techStack.libraries.push("shadcn/ui");
|
|
352
|
+
if (dependencies["zod"]) techStack.libraries.push("Zod");
|
|
353
|
+
if (dependencies["react-hook-form"]) techStack.libraries.push("React Hook Form");
|
|
354
|
+
if (dependencies["typescript"]) techStack.tools.push("TypeScript");
|
|
355
|
+
if (dependencies["eslint"]) techStack.tools.push("ESLint");
|
|
356
|
+
if (dependencies["prettier"]) techStack.tools.push("Prettier");
|
|
357
|
+
if (dependencies["jest"]) techStack.tools.push("Jest");
|
|
358
|
+
if (dependencies["vitest"]) techStack.tools.push("Vitest");
|
|
359
|
+
if (dependencies["playwright"]) techStack.tools.push("Playwright");
|
|
360
|
+
}
|
|
361
|
+
return techStack;
|
|
362
|
+
}
|
|
363
|
+
async function findEntryPoints(projectPath) {
|
|
364
|
+
const entryPoints = [];
|
|
365
|
+
const packageJsonPath = import_path3.default.join(projectPath, "package.json");
|
|
366
|
+
if (import_fs3.default.existsSync(packageJsonPath)) {
|
|
367
|
+
const packageJson = JSON.parse(import_fs3.default.readFileSync(packageJsonPath, "utf-8"));
|
|
368
|
+
if (packageJson.scripts) {
|
|
369
|
+
for (const [name, script] of Object.entries(packageJson.scripts)) {
|
|
370
|
+
entryPoints.push({
|
|
371
|
+
type: "script",
|
|
372
|
+
name,
|
|
373
|
+
path: "package.json",
|
|
374
|
+
description: script
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (packageJson.main) {
|
|
379
|
+
entryPoints.push({
|
|
380
|
+
type: "main",
|
|
381
|
+
name: "main",
|
|
382
|
+
path: packageJson.main,
|
|
383
|
+
description: "Main entry point"
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const commonEntries = [
|
|
388
|
+
"src/index.ts",
|
|
389
|
+
"src/index.js",
|
|
390
|
+
"src/main.ts",
|
|
391
|
+
"src/main.js",
|
|
392
|
+
"src/app/page.tsx",
|
|
393
|
+
"src/app/layout.tsx",
|
|
394
|
+
"app/page.tsx",
|
|
395
|
+
"app/layout.tsx",
|
|
396
|
+
"pages/index.tsx",
|
|
397
|
+
"pages/index.js",
|
|
398
|
+
"pages/_app.tsx",
|
|
399
|
+
"pages/_app.js"
|
|
400
|
+
];
|
|
401
|
+
for (const entry of commonEntries) {
|
|
402
|
+
const fullPath = import_path3.default.join(projectPath, entry);
|
|
403
|
+
if (import_fs3.default.existsSync(fullPath)) {
|
|
404
|
+
entryPoints.push({
|
|
405
|
+
type: entry.includes("page") ? "page" : "main",
|
|
406
|
+
name: import_path3.default.basename(entry),
|
|
407
|
+
path: entry,
|
|
408
|
+
description: getEntryDescription(entry)
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return entryPoints;
|
|
413
|
+
}
|
|
414
|
+
async function extractDocumentation(projectPath) {
|
|
415
|
+
const documentation = [];
|
|
416
|
+
const docFiles = [
|
|
417
|
+
{ pattern: "README.md", type: "readme" },
|
|
418
|
+
{ pattern: "CHANGELOG.md", type: "changelog" },
|
|
419
|
+
{ pattern: "CONTRIBUTING.md", type: "contributing" },
|
|
420
|
+
{ pattern: "LICENSE", type: "license" },
|
|
421
|
+
{ pattern: "LICENSE.md", type: "license" },
|
|
422
|
+
{ pattern: "docs/**/*.md", type: "other" }
|
|
423
|
+
];
|
|
424
|
+
for (const { pattern, type } of docFiles) {
|
|
425
|
+
const files = await (0, import_glob.glob)(pattern, {
|
|
426
|
+
cwd: projectPath,
|
|
427
|
+
nodir: true
|
|
177
428
|
});
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
429
|
+
for (const file of files) {
|
|
430
|
+
const fullPath = import_path3.default.join(projectPath, file);
|
|
431
|
+
const content = import_fs3.default.readFileSync(fullPath, "utf-8");
|
|
432
|
+
documentation.push({
|
|
433
|
+
type,
|
|
434
|
+
path: file,
|
|
435
|
+
title: extractTitle(content),
|
|
436
|
+
content: content.substring(0, 1e3)
|
|
437
|
+
// First 1000 chars
|
|
186
438
|
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return documentation;
|
|
442
|
+
}
|
|
443
|
+
async function collectMetadata(projectPath) {
|
|
444
|
+
const metadata = {
|
|
445
|
+
name: import_path3.default.basename(projectPath),
|
|
446
|
+
totalFiles: 0,
|
|
447
|
+
totalLines: 0
|
|
448
|
+
};
|
|
449
|
+
const packageJsonPath = import_path3.default.join(projectPath, "package.json");
|
|
450
|
+
if (import_fs3.default.existsSync(packageJsonPath)) {
|
|
451
|
+
const packageJson = JSON.parse(import_fs3.default.readFileSync(packageJsonPath, "utf-8"));
|
|
452
|
+
metadata.name = packageJson.name || metadata.name;
|
|
453
|
+
metadata.version = packageJson.version;
|
|
454
|
+
metadata.description = packageJson.description;
|
|
455
|
+
metadata.author = packageJson.author;
|
|
456
|
+
metadata.license = packageJson.license;
|
|
457
|
+
metadata.scripts = packageJson.scripts;
|
|
458
|
+
if (packageJson.repository) {
|
|
459
|
+
metadata.repository = typeof packageJson.repository === "string" ? packageJson.repository : packageJson.repository.url;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const files = await (0, import_glob.glob)("**/*", {
|
|
463
|
+
cwd: projectPath,
|
|
464
|
+
ignore: IGNORE_PATTERNS,
|
|
465
|
+
nodir: true
|
|
466
|
+
});
|
|
467
|
+
metadata.totalFiles = files.length;
|
|
468
|
+
for (const file of files) {
|
|
469
|
+
const ext = import_path3.default.extname(file);
|
|
470
|
+
if (LANGUAGE_EXTENSIONS[ext]) {
|
|
471
|
+
const fullPath = import_path3.default.join(projectPath, file);
|
|
472
|
+
const content = import_fs3.default.readFileSync(fullPath, "utf-8");
|
|
473
|
+
metadata.totalLines += content.split("\n").length;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const gitPath = import_path3.default.join(projectPath, ".git");
|
|
477
|
+
if (import_fs3.default.existsSync(gitPath)) {
|
|
478
|
+
try {
|
|
479
|
+
const headPath = import_path3.default.join(gitPath, "HEAD");
|
|
480
|
+
const head = import_fs3.default.readFileSync(headPath, "utf-8").trim();
|
|
481
|
+
metadata.gitBranch = head.replace("ref: refs/heads/", "");
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return metadata;
|
|
486
|
+
}
|
|
487
|
+
function shouldIgnore(name) {
|
|
488
|
+
const ignoreList = [
|
|
489
|
+
"node_modules",
|
|
490
|
+
".git",
|
|
491
|
+
"dist",
|
|
492
|
+
"build",
|
|
493
|
+
".next",
|
|
494
|
+
"coverage",
|
|
495
|
+
".cache",
|
|
496
|
+
"tmp",
|
|
497
|
+
".turbo",
|
|
498
|
+
".vercel",
|
|
499
|
+
".DS_Store"
|
|
500
|
+
];
|
|
501
|
+
return ignoreList.includes(name) || name.startsWith(".");
|
|
502
|
+
}
|
|
503
|
+
function getEntryDescription(entryPath) {
|
|
504
|
+
if (entryPath.includes("layout")) return "Root layout component";
|
|
505
|
+
if (entryPath.includes("page")) return "Page component";
|
|
506
|
+
if (entryPath.includes("_app")) return "App component";
|
|
507
|
+
if (entryPath.includes("index")) return "Main entry point";
|
|
508
|
+
return "Entry file";
|
|
509
|
+
}
|
|
510
|
+
function extractTitle(content) {
|
|
511
|
+
const lines = content.split("\n");
|
|
512
|
+
for (const line of lines) {
|
|
513
|
+
if (line.startsWith("# ")) {
|
|
514
|
+
return line.substring(2).trim();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return void 0;
|
|
518
|
+
}
|
|
519
|
+
var import_fs3, import_path3, import_glob, IGNORE_PATTERNS, LANGUAGE_EXTENSIONS;
|
|
520
|
+
var init_local = __esm({
|
|
521
|
+
"src/cli/utils/codebase-analyzer/local.ts"() {
|
|
522
|
+
"use strict";
|
|
523
|
+
import_fs3 = __toESM(require("fs"));
|
|
524
|
+
import_path3 = __toESM(require("path"));
|
|
525
|
+
import_glob = require("glob");
|
|
526
|
+
IGNORE_PATTERNS = [
|
|
527
|
+
"**/node_modules/**",
|
|
528
|
+
"**/.git/**",
|
|
529
|
+
"**/dist/**",
|
|
530
|
+
"**/build/**",
|
|
531
|
+
"**/.next/**",
|
|
532
|
+
"**/coverage/**",
|
|
533
|
+
"**/.cache/**",
|
|
534
|
+
"**/tmp/**",
|
|
535
|
+
"**/.turbo/**",
|
|
536
|
+
"**/.vercel/**"
|
|
537
|
+
];
|
|
538
|
+
LANGUAGE_EXTENSIONS = {
|
|
539
|
+
".ts": "TypeScript",
|
|
540
|
+
".tsx": "TypeScript",
|
|
541
|
+
".js": "JavaScript",
|
|
542
|
+
".jsx": "JavaScript",
|
|
543
|
+
".py": "Python",
|
|
544
|
+
".rb": "Ruby",
|
|
545
|
+
".go": "Go",
|
|
546
|
+
".rs": "Rust",
|
|
547
|
+
".java": "Java",
|
|
548
|
+
".kt": "Kotlin",
|
|
549
|
+
".swift": "Swift",
|
|
550
|
+
".php": "PHP",
|
|
551
|
+
".cs": "C#",
|
|
552
|
+
".cpp": "C++",
|
|
553
|
+
".c": "C",
|
|
554
|
+
".css": "CSS",
|
|
555
|
+
".scss": "SCSS",
|
|
556
|
+
".sass": "Sass",
|
|
557
|
+
".html": "HTML",
|
|
558
|
+
".vue": "Vue",
|
|
559
|
+
".svelte": "Svelte",
|
|
560
|
+
".sql": "SQL",
|
|
561
|
+
".sh": "Shell",
|
|
562
|
+
".yaml": "YAML",
|
|
563
|
+
".yml": "YAML",
|
|
564
|
+
".json": "JSON",
|
|
565
|
+
".md": "Markdown"
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// src/cli/utils/knowledge-base/storage.ts
|
|
571
|
+
function getKnowledgeBaseDir(projectPath) {
|
|
572
|
+
return import_path4.default.join(projectPath, KNOWLEDGE_BASE_DIR);
|
|
573
|
+
}
|
|
574
|
+
function ensureKnowledgeBaseDir(projectPath) {
|
|
575
|
+
const kbDir = getKnowledgeBaseDir(projectPath);
|
|
576
|
+
if (!import_fs4.default.existsSync(kbDir)) {
|
|
577
|
+
import_fs4.default.mkdirSync(kbDir, { recursive: true });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function saveKnowledgeBase(projectPath, knowledgeBase) {
|
|
581
|
+
ensureKnowledgeBaseDir(projectPath);
|
|
582
|
+
const kbDir = getKnowledgeBaseDir(projectPath);
|
|
583
|
+
const indexPath = import_path4.default.join(kbDir, INDEX_FILE);
|
|
584
|
+
import_fs4.default.writeFileSync(indexPath, JSON.stringify(knowledgeBase, null, 2));
|
|
585
|
+
if (knowledgeBase.localAnalysis) {
|
|
586
|
+
saveComponent(kbDir, "local-analysis.json", knowledgeBase.localAnalysis);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function loadKnowledgeBase(projectPath) {
|
|
590
|
+
const indexPath = import_path4.default.join(projectPath, KNOWLEDGE_BASE_DIR, INDEX_FILE);
|
|
591
|
+
if (!import_fs4.default.existsSync(indexPath)) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
const content = import_fs4.default.readFileSync(indexPath, "utf-8");
|
|
596
|
+
const knowledgeBase = JSON.parse(content);
|
|
597
|
+
const kbDir = getKnowledgeBaseDir(projectPath);
|
|
598
|
+
if (knowledgeBase.analysisType === "local") {
|
|
599
|
+
const localAnalysisPath = import_path4.default.join(kbDir, "local-analysis.json");
|
|
600
|
+
if (import_fs4.default.existsSync(localAnalysisPath)) {
|
|
601
|
+
knowledgeBase.localAnalysis = JSON.parse(
|
|
602
|
+
import_fs4.default.readFileSync(localAnalysisPath, "utf-8")
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return knowledgeBase;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
console.error("Error loading knowledge base:", error);
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function hasKnowledgeBase(projectPath) {
|
|
613
|
+
const indexPath = import_path4.default.join(projectPath, KNOWLEDGE_BASE_DIR, INDEX_FILE);
|
|
614
|
+
return import_fs4.default.existsSync(indexPath);
|
|
615
|
+
}
|
|
616
|
+
function getKnowledgeBaseStats(projectPath) {
|
|
617
|
+
const kb = loadKnowledgeBase(projectPath);
|
|
618
|
+
if (!kb) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
version: kb.version,
|
|
623
|
+
analyzedAt: kb.analyzedAt,
|
|
624
|
+
analysisType: kb.analysisType,
|
|
625
|
+
totalFiles: kb.localAnalysis?.metadata.totalFiles || 0,
|
|
626
|
+
totalLines: kb.localAnalysis?.metadata.totalLines || 0,
|
|
627
|
+
languages: kb.localAnalysis?.techStack.languages || {},
|
|
628
|
+
frameworks: kb.localAnalysis?.techStack.frameworks || []
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function saveComponent(kbDir, filename, data) {
|
|
632
|
+
const filePath = import_path4.default.join(kbDir, filename);
|
|
633
|
+
import_fs4.default.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
634
|
+
}
|
|
635
|
+
function initializeKnowledgeBase(projectPath, projectId, projectName, localAnalysis) {
|
|
636
|
+
const knowledgeBase = {
|
|
637
|
+
version: "1.0.0",
|
|
638
|
+
projectId,
|
|
639
|
+
projectName,
|
|
640
|
+
analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
641
|
+
analysisType: "local",
|
|
642
|
+
localAnalysis,
|
|
643
|
+
queries: {}
|
|
644
|
+
};
|
|
645
|
+
saveKnowledgeBase(projectPath, knowledgeBase);
|
|
646
|
+
return knowledgeBase;
|
|
647
|
+
}
|
|
648
|
+
var import_fs4, import_path4, KNOWLEDGE_BASE_DIR, INDEX_FILE;
|
|
649
|
+
var init_storage = __esm({
|
|
650
|
+
"src/cli/utils/knowledge-base/storage.ts"() {
|
|
651
|
+
"use strict";
|
|
652
|
+
import_fs4 = __toESM(require("fs"));
|
|
653
|
+
import_path4 = __toESM(require("path"));
|
|
654
|
+
KNOWLEDGE_BASE_DIR = ".codmir/knowledge";
|
|
655
|
+
INDEX_FILE = "index.json";
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// src/cli/commands/analyze.ts
|
|
660
|
+
var analyze_exports = {};
|
|
661
|
+
__export(analyze_exports, {
|
|
662
|
+
analyzeCommand: () => analyzeCommand
|
|
663
|
+
});
|
|
664
|
+
async function analyzeCommand(options = {}) {
|
|
665
|
+
console.log(import_chalk4.default.bold("\n\u{1F50D} Codebase Analysis\n"));
|
|
666
|
+
const projectConfig = getProjectConfig();
|
|
667
|
+
if (!projectConfig) {
|
|
668
|
+
console.error(import_chalk4.default.red("\u274C Project not linked"));
|
|
669
|
+
console.log(import_chalk4.default.dim(" Run"), import_chalk4.default.cyan("codmir link"), import_chalk4.default.dim("first"));
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
const projectPath = process.cwd();
|
|
673
|
+
const existingKB = hasKnowledgeBase(projectPath);
|
|
674
|
+
if (existingKB && !options.force) {
|
|
675
|
+
const stats = getKnowledgeBaseStats(projectPath);
|
|
676
|
+
console.log(import_chalk4.default.yellow("\u26A0\uFE0F Knowledge base already exists"));
|
|
677
|
+
console.log(import_chalk4.default.dim(` Last analyzed: ${new Date(stats.analyzedAt).toLocaleString()}`));
|
|
678
|
+
console.log(import_chalk4.default.dim(` Files: ${stats.totalFiles}, Lines: ${stats.totalLines}`));
|
|
679
|
+
const { reanalyze } = await (0, import_prompts.default)({
|
|
680
|
+
type: "confirm",
|
|
681
|
+
name: "reanalyze",
|
|
682
|
+
message: "Re-analyze codebase?",
|
|
683
|
+
initial: false
|
|
684
|
+
});
|
|
685
|
+
if (!reanalyze) {
|
|
686
|
+
console.log(import_chalk4.default.dim("\n Use"), import_chalk4.default.cyan("--force"), import_chalk4.default.dim("to force re-analysis"));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
let analysisMode = options.mode || "local";
|
|
691
|
+
if (!options.mode) {
|
|
692
|
+
const { mode } = await (0, import_prompts.default)({
|
|
693
|
+
type: "select",
|
|
694
|
+
name: "mode",
|
|
695
|
+
message: "Choose analysis mode",
|
|
696
|
+
choices: [
|
|
697
|
+
{
|
|
698
|
+
title: "\u{1F4BB} Local (Quick)",
|
|
699
|
+
value: "local",
|
|
700
|
+
description: "Fast structural analysis (1-5 seconds)"
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
title: "\u{1F682} Railway (Deep)",
|
|
704
|
+
value: "railway",
|
|
705
|
+
description: "Comprehensive analysis with embeddings (~2 minutes)"
|
|
706
|
+
}
|
|
707
|
+
],
|
|
708
|
+
initial: 0
|
|
709
|
+
});
|
|
710
|
+
analysisMode = mode;
|
|
711
|
+
}
|
|
712
|
+
if (analysisMode === "local") {
|
|
713
|
+
await runLocalAnalysis(projectPath, projectConfig.projectId, projectConfig.projectName);
|
|
714
|
+
} else {
|
|
715
|
+
await runRailwayAnalysis(projectPath, projectConfig.projectId);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
async function runLocalAnalysis(projectPath, projectId, projectName) {
|
|
719
|
+
const spinner = (0, import_ora.default)({
|
|
720
|
+
text: import_chalk4.default.dim("Analyzing project structure..."),
|
|
721
|
+
color: "cyan"
|
|
722
|
+
}).start();
|
|
723
|
+
try {
|
|
724
|
+
const analysis = await analyzeLocal(projectPath);
|
|
725
|
+
spinner.text = import_chalk4.default.dim("Saving knowledge base...");
|
|
726
|
+
initializeKnowledgeBase(projectPath, projectId, projectName, analysis);
|
|
727
|
+
spinner.succeed(import_chalk4.default.green("Analysis complete!"));
|
|
728
|
+
console.log();
|
|
729
|
+
console.log(import_chalk4.default.bold("\u{1F4CA} Results:"));
|
|
730
|
+
console.log();
|
|
731
|
+
console.log(import_chalk4.default.dim(" Project:"), import_chalk4.default.cyan(projectName));
|
|
732
|
+
console.log(import_chalk4.default.dim(" Files:"), import_chalk4.default.white(analysis.metadata.totalFiles));
|
|
733
|
+
console.log(import_chalk4.default.dim(" Lines:"), import_chalk4.default.white(analysis.metadata.totalLines));
|
|
734
|
+
console.log();
|
|
735
|
+
if (Object.keys(analysis.techStack.languages).length > 0) {
|
|
736
|
+
console.log(import_chalk4.default.bold("\u{1F5E3}\uFE0F Languages:"));
|
|
737
|
+
for (const [lang, percent] of Object.entries(analysis.techStack.languages)) {
|
|
738
|
+
const bar = createProgressBar(percent, 20);
|
|
739
|
+
console.log(` ${import_chalk4.default.cyan(lang.padEnd(15))} ${bar} ${percent}%`);
|
|
740
|
+
}
|
|
741
|
+
console.log();
|
|
742
|
+
}
|
|
743
|
+
if (analysis.techStack.frameworks.length > 0) {
|
|
744
|
+
console.log(import_chalk4.default.bold("\u26A1 Frameworks:"));
|
|
745
|
+
analysis.techStack.frameworks.forEach((fw) => {
|
|
746
|
+
console.log(` \u2022 ${import_chalk4.default.green(fw)}`);
|
|
747
|
+
});
|
|
748
|
+
console.log();
|
|
749
|
+
}
|
|
750
|
+
if (analysis.entryPoints.length > 0) {
|
|
751
|
+
console.log(import_chalk4.default.bold("\u{1F680} Entry Points:"));
|
|
752
|
+
analysis.entryPoints.slice(0, 5).forEach((ep) => {
|
|
753
|
+
console.log(` \u2022 ${import_chalk4.default.yellow(ep.name)} ${import_chalk4.default.dim(`(${ep.path})`)}`);
|
|
754
|
+
});
|
|
755
|
+
if (analysis.entryPoints.length > 5) {
|
|
756
|
+
console.log(import_chalk4.default.dim(` ... and ${analysis.entryPoints.length - 5} more`));
|
|
757
|
+
}
|
|
758
|
+
console.log();
|
|
759
|
+
}
|
|
760
|
+
if (analysis.documentation.length > 0) {
|
|
761
|
+
console.log(import_chalk4.default.bold("\u{1F4DA} Documentation:"));
|
|
762
|
+
analysis.documentation.forEach((doc) => {
|
|
763
|
+
const icon = getDocIcon(doc.type);
|
|
764
|
+
console.log(` ${icon} ${import_chalk4.default.cyan(doc.path)}`);
|
|
765
|
+
});
|
|
766
|
+
console.log();
|
|
767
|
+
}
|
|
768
|
+
console.log(import_chalk4.default.dim(" Knowledge base saved to:"), import_chalk4.default.cyan(".codmir/knowledge/"));
|
|
769
|
+
console.log();
|
|
770
|
+
console.log(import_chalk4.default.dim(" Try:"), import_chalk4.default.cyan('codmir ai --context "Explain the project structure"'));
|
|
771
|
+
console.log();
|
|
772
|
+
} catch (error) {
|
|
773
|
+
spinner.fail(import_chalk4.default.red("Analysis failed"));
|
|
774
|
+
console.error(error);
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async function runRailwayAnalysis(projectPath, projectId) {
|
|
779
|
+
const token = getToken();
|
|
780
|
+
if (!token) {
|
|
781
|
+
console.error(import_chalk4.default.red("\u274C Not authenticated"));
|
|
782
|
+
console.log(import_chalk4.default.dim(" Run"), import_chalk4.default.cyan("codmir login"), import_chalk4.default.dim("first"));
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
const baseUrl = getBaseUrl();
|
|
786
|
+
const gitRemote = await getGitRemote(projectPath);
|
|
787
|
+
if (!gitRemote) {
|
|
788
|
+
console.error(import_chalk4.default.red("\u274C No git remote found"));
|
|
789
|
+
console.log(import_chalk4.default.dim(" Project must be a git repository with remote"));
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
console.log(import_chalk4.default.dim(" Repository:"), import_chalk4.default.cyan(gitRemote));
|
|
793
|
+
console.log();
|
|
794
|
+
const spinner = (0, import_ora.default)({
|
|
795
|
+
text: import_chalk4.default.dim("Starting Railway analysis..."),
|
|
796
|
+
color: "cyan"
|
|
797
|
+
}).start();
|
|
798
|
+
try {
|
|
799
|
+
const response = await fetch(`${baseUrl}/api/analysis/deep`, {
|
|
800
|
+
method: "POST",
|
|
801
|
+
headers: {
|
|
802
|
+
"Content-Type": "application/json",
|
|
803
|
+
"X-API-Key": token
|
|
804
|
+
},
|
|
805
|
+
body: JSON.stringify({
|
|
806
|
+
projectId,
|
|
807
|
+
repoUrl: gitRemote,
|
|
808
|
+
branch: await getGitBranch(projectPath)
|
|
809
|
+
})
|
|
810
|
+
});
|
|
811
|
+
if (!response.ok) {
|
|
812
|
+
throw new Error(`API error: ${response.status}`);
|
|
813
|
+
}
|
|
814
|
+
const { jobId, estimatedTime } = await response.json();
|
|
815
|
+
spinner.text = import_chalk4.default.dim(`Analysis in progress (${estimatedTime})...`);
|
|
816
|
+
await pollAnalysisStatus(baseUrl, token, jobId, spinner);
|
|
817
|
+
spinner.succeed(import_chalk4.default.green("Deep analysis complete!"));
|
|
818
|
+
console.log();
|
|
819
|
+
console.log(import_chalk4.default.dim(" Knowledge base synced from Railway"));
|
|
820
|
+
console.log(import_chalk4.default.dim(" Includes:"));
|
|
821
|
+
console.log(import_chalk4.default.dim(" \u2022 Full AST parsing"));
|
|
822
|
+
console.log(import_chalk4.default.dim(" \u2022 Code embeddings"));
|
|
823
|
+
console.log(import_chalk4.default.dim(" \u2022 Best practices report"));
|
|
824
|
+
console.log(import_chalk4.default.dim(" \u2022 Generated documentation"));
|
|
825
|
+
console.log();
|
|
826
|
+
} catch (error) {
|
|
827
|
+
spinner.fail(import_chalk4.default.red("Railway analysis failed"));
|
|
828
|
+
console.error(error);
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
async function pollAnalysisStatus(baseUrl, token, jobId, spinner) {
|
|
833
|
+
const maxAttempts = 120;
|
|
834
|
+
let attempts = 0;
|
|
835
|
+
while (attempts < maxAttempts) {
|
|
836
|
+
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
837
|
+
const response = await fetch(`${baseUrl}/api/analysis/deep/${jobId}`, {
|
|
838
|
+
headers: { "X-API-Key": token }
|
|
839
|
+
});
|
|
840
|
+
const { status, progress } = await response.json();
|
|
841
|
+
spinner.text = import_chalk4.default.dim(`${status}... ${progress || ""}%`);
|
|
842
|
+
if (status === "complete") {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (status === "failed") {
|
|
846
|
+
throw new Error("Analysis failed on Railway");
|
|
847
|
+
}
|
|
848
|
+
attempts++;
|
|
849
|
+
}
|
|
850
|
+
throw new Error("Analysis timeout");
|
|
851
|
+
}
|
|
852
|
+
async function getGitRemote(projectPath) {
|
|
853
|
+
try {
|
|
854
|
+
const { execSync } = require("child_process");
|
|
855
|
+
const remote = execSync("git config --get remote.origin.url", {
|
|
856
|
+
cwd: projectPath,
|
|
857
|
+
encoding: "utf-8"
|
|
858
|
+
}).trim();
|
|
859
|
+
return remote;
|
|
860
|
+
} catch {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
async function getGitBranch(projectPath) {
|
|
865
|
+
try {
|
|
866
|
+
const { execSync } = require("child_process");
|
|
867
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
868
|
+
cwd: projectPath,
|
|
869
|
+
encoding: "utf-8"
|
|
870
|
+
}).trim();
|
|
871
|
+
return branch;
|
|
872
|
+
} catch {
|
|
873
|
+
return "main";
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function createProgressBar(percent, width) {
|
|
877
|
+
const filled = Math.round(percent / 100 * width);
|
|
878
|
+
const empty = width - filled;
|
|
879
|
+
return import_chalk4.default.cyan("\u2588".repeat(filled)) + import_chalk4.default.dim("\u2591".repeat(empty));
|
|
880
|
+
}
|
|
881
|
+
function getDocIcon(type) {
|
|
882
|
+
const icons = {
|
|
883
|
+
readme: "\u{1F4D6}",
|
|
884
|
+
changelog: "\u{1F4DD}",
|
|
885
|
+
contributing: "\u{1F91D}",
|
|
886
|
+
license: "\u2696\uFE0F",
|
|
887
|
+
other: "\u{1F4C4}"
|
|
888
|
+
};
|
|
889
|
+
return icons[type] || "\u{1F4C4}";
|
|
890
|
+
}
|
|
891
|
+
var import_chalk4, import_ora, import_prompts;
|
|
892
|
+
var init_analyze = __esm({
|
|
893
|
+
"src/cli/commands/analyze.ts"() {
|
|
894
|
+
"use strict";
|
|
895
|
+
import_chalk4 = __toESM(require("chalk"));
|
|
896
|
+
import_ora = __toESM(require("ora"));
|
|
897
|
+
import_prompts = __toESM(require("prompts"));
|
|
898
|
+
init_config();
|
|
899
|
+
init_local();
|
|
900
|
+
init_storage();
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// src/cli/index.ts
|
|
905
|
+
var import_commander = require("commander");
|
|
906
|
+
|
|
907
|
+
// src/cli/utils/auth.ts
|
|
908
|
+
var import_open = __toESM(require("open"));
|
|
909
|
+
var readline = __toESM(require("readline"));
|
|
910
|
+
init_config();
|
|
911
|
+
function promptForToken() {
|
|
912
|
+
return new Promise((resolve) => {
|
|
913
|
+
const rl = readline.createInterface({
|
|
914
|
+
input: process.stdin,
|
|
915
|
+
output: process.stdout
|
|
916
|
+
});
|
|
917
|
+
rl.question("\nPaste your authentication token: ", (token) => {
|
|
918
|
+
rl.close();
|
|
919
|
+
resolve(token.trim());
|
|
187
920
|
});
|
|
188
|
-
setTimeout(() => {
|
|
189
|
-
server.close();
|
|
190
|
-
reject(new Error("Authentication timeout"));
|
|
191
|
-
}, 5 * 60 * 1e3);
|
|
192
921
|
});
|
|
193
922
|
}
|
|
923
|
+
async function startOAuthFlow(baseUrl = "https://codmir.com") {
|
|
924
|
+
const authUrl = `${baseUrl}/cli/auth`;
|
|
925
|
+
console.log("\u{1F510} Opening browser for authentication...");
|
|
926
|
+
console.log(` If browser doesn't open, visit: ${authUrl}`);
|
|
927
|
+
console.log();
|
|
928
|
+
try {
|
|
929
|
+
await (0, import_open.default)(authUrl);
|
|
930
|
+
} catch {
|
|
931
|
+
console.log("\u26A0\uFE0F Could not open browser automatically.");
|
|
932
|
+
console.log(` Please visit: ${authUrl}`);
|
|
933
|
+
console.log();
|
|
934
|
+
}
|
|
935
|
+
console.log("\u{1F4CB} After authenticating in your browser:");
|
|
936
|
+
console.log(" 1. Copy the token displayed");
|
|
937
|
+
console.log(" 2. Return here and paste it below");
|
|
938
|
+
console.log();
|
|
939
|
+
const token = await promptForToken();
|
|
940
|
+
if (!token) {
|
|
941
|
+
throw new Error("No token provided");
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const userResponse = await fetch(`${baseUrl}/api/user/profile`, {
|
|
945
|
+
headers: {
|
|
946
|
+
"X-API-Key": token
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
if (!userResponse.ok) {
|
|
950
|
+
throw new Error("Invalid token or authentication failed");
|
|
951
|
+
}
|
|
952
|
+
const response = await userResponse.json();
|
|
953
|
+
const user = response.data || response;
|
|
954
|
+
return {
|
|
955
|
+
token,
|
|
956
|
+
user: {
|
|
957
|
+
id: user.id,
|
|
958
|
+
email: user.email,
|
|
959
|
+
name: user.name
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
} catch (error) {
|
|
963
|
+
throw new Error(
|
|
964
|
+
error instanceof Error ? error.message : "Failed to validate token"
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
194
968
|
async function authenticateWithToken(token, baseUrl = "https://codmir.com") {
|
|
195
969
|
const response = await fetch(`${baseUrl}/api/user/profile`, {
|
|
196
970
|
headers: {
|
|
@@ -200,7 +974,8 @@ async function authenticateWithToken(token, baseUrl = "https://codmir.com") {
|
|
|
200
974
|
if (!response.ok) {
|
|
201
975
|
throw new Error("Invalid token");
|
|
202
976
|
}
|
|
203
|
-
const
|
|
977
|
+
const responseData = await response.json();
|
|
978
|
+
const user = responseData.data || responseData;
|
|
204
979
|
return {
|
|
205
980
|
token,
|
|
206
981
|
user: {
|
|
@@ -223,6 +998,7 @@ function saveAuth(authResult) {
|
|
|
223
998
|
}
|
|
224
999
|
|
|
225
1000
|
// src/cli/commands/login.ts
|
|
1001
|
+
init_config();
|
|
226
1002
|
var import_chalk = __toESM(require("chalk"));
|
|
227
1003
|
async function loginCommand(options) {
|
|
228
1004
|
if (isAuthenticated()) {
|
|
@@ -235,11 +1011,16 @@ async function loginCommand(options) {
|
|
|
235
1011
|
console.log(import_chalk.default.dim(" Authenticate to start using codmir CLI\n"));
|
|
236
1012
|
try {
|
|
237
1013
|
let authResult;
|
|
1014
|
+
const baseUrl = getBaseUrl();
|
|
1015
|
+
if (baseUrl !== "https://codmir.com") {
|
|
1016
|
+
console.log(import_chalk.default.dim(` Connecting to: ${baseUrl}
|
|
1017
|
+
`));
|
|
1018
|
+
}
|
|
238
1019
|
if (options.token) {
|
|
239
1020
|
console.log(import_chalk.default.dim(" Authenticating with provided token..."));
|
|
240
|
-
authResult = await authenticateWithToken(options.token);
|
|
1021
|
+
authResult = await authenticateWithToken(options.token, baseUrl);
|
|
241
1022
|
} else {
|
|
242
|
-
authResult = await startOAuthFlow();
|
|
1023
|
+
authResult = await startOAuthFlow(baseUrl);
|
|
243
1024
|
}
|
|
244
1025
|
saveAuth(authResult);
|
|
245
1026
|
console.log(import_chalk.default.green("\n\u2705 Successfully logged in!"));
|
|
@@ -252,6 +1033,42 @@ async function loginCommand(options) {
|
|
|
252
1033
|
}
|
|
253
1034
|
}
|
|
254
1035
|
|
|
1036
|
+
// src/cli/commands/logout.ts
|
|
1037
|
+
init_config();
|
|
1038
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
1039
|
+
async function logoutCommand() {
|
|
1040
|
+
if (!isAuthenticated()) {
|
|
1041
|
+
console.log(import_chalk2.default.yellow("\u26A0\uFE0F Not logged in"));
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const config = readConfig();
|
|
1045
|
+
clearConfig();
|
|
1046
|
+
console.log(import_chalk2.default.green("\u2705 Successfully logged out"));
|
|
1047
|
+
if (config.email) {
|
|
1048
|
+
console.log(import_chalk2.default.dim(" Goodbye,"), import_chalk2.default.bold(config.email));
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/cli/commands/whoami.ts
|
|
1053
|
+
init_config();
|
|
1054
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
1055
|
+
async function whoamiCommand() {
|
|
1056
|
+
if (!isAuthenticated()) {
|
|
1057
|
+
console.error(import_chalk3.default.red("\u274C Not authenticated"));
|
|
1058
|
+
console.log(import_chalk3.default.dim(" Run"), import_chalk3.default.cyan("codmir login"), import_chalk3.default.dim("first"));
|
|
1059
|
+
process.exit(1);
|
|
1060
|
+
}
|
|
1061
|
+
const config = readConfig();
|
|
1062
|
+
console.log(import_chalk3.default.bold("\n\u{1F464} Current User\n"));
|
|
1063
|
+
console.log(import_chalk3.default.dim(" Name:"), import_chalk3.default.bold(config.name || "N/A"));
|
|
1064
|
+
console.log(import_chalk3.default.dim(" Email:"), import_chalk3.default.bold(config.email || "N/A"));
|
|
1065
|
+
console.log(import_chalk3.default.dim(" User ID:"), import_chalk3.default.dim(config.userId || "N/A"));
|
|
1066
|
+
if (config.lastLogin) {
|
|
1067
|
+
const lastLogin = new Date(config.lastLogin);
|
|
1068
|
+
console.log(import_chalk3.default.dim(" Last Login:"), import_chalk3.default.dim(lastLogin.toLocaleString()));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
255
1072
|
// src/client.ts
|
|
256
1073
|
var CodmirClient = class {
|
|
257
1074
|
constructor(config) {
|
|
@@ -267,8 +1084,8 @@ var CodmirClient = class {
|
|
|
267
1084
|
/**
|
|
268
1085
|
* Make an HTTP request to the codmir API
|
|
269
1086
|
*/
|
|
270
|
-
async request(method,
|
|
271
|
-
const url = `${this.config.baseUrl}${
|
|
1087
|
+
async request(method, path8, body) {
|
|
1088
|
+
const url = `${this.config.baseUrl}${path8}`;
|
|
272
1089
|
const headers = {
|
|
273
1090
|
"Content-Type": "application/json",
|
|
274
1091
|
...this.config.headers
|
|
@@ -325,8 +1142,8 @@ var TicketsAPI = class {
|
|
|
325
1142
|
constructor(config) {
|
|
326
1143
|
this.config = config;
|
|
327
1144
|
}
|
|
328
|
-
async request(method,
|
|
329
|
-
const url = `${this.config.baseUrl}${
|
|
1145
|
+
async request(method, path8, body) {
|
|
1146
|
+
const url = `${this.config.baseUrl}${path8}`;
|
|
330
1147
|
const headers = {
|
|
331
1148
|
"Content-Type": "application/json",
|
|
332
1149
|
...this.config.headers
|
|
@@ -405,8 +1222,8 @@ var TestCasesAPI = class {
|
|
|
405
1222
|
constructor(config) {
|
|
406
1223
|
this.config = config;
|
|
407
1224
|
}
|
|
408
|
-
async request(method,
|
|
409
|
-
const url = `${this.config.baseUrl}${
|
|
1225
|
+
async request(method, path8, body) {
|
|
1226
|
+
const url = `${this.config.baseUrl}${path8}`;
|
|
410
1227
|
const headers = {
|
|
411
1228
|
"Content-Type": "application/json",
|
|
412
1229
|
...this.config.headers
|
|
@@ -523,59 +1340,129 @@ var TestCasesAPI = class {
|
|
|
523
1340
|
};
|
|
524
1341
|
|
|
525
1342
|
// src/cli/commands/link.ts
|
|
526
|
-
var
|
|
527
|
-
var
|
|
1343
|
+
var import_prompts2 = __toESM(require("prompts"));
|
|
1344
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1345
|
+
init_config();
|
|
1346
|
+
|
|
1347
|
+
// src/cli/utils/machine-id.ts
|
|
1348
|
+
var import_fs2 = __toESM(require("fs"));
|
|
1349
|
+
var import_path2 = __toESM(require("path"));
|
|
1350
|
+
var import_os2 = __toESM(require("os"));
|
|
1351
|
+
var import_crypto = __toESM(require("crypto"));
|
|
1352
|
+
init_config();
|
|
1353
|
+
var MACHINE_ID_FILE = import_path2.default.join(import_os2.default.homedir(), ".codmir", "machine-id");
|
|
1354
|
+
function getMachineId() {
|
|
1355
|
+
ensureConfigDir();
|
|
1356
|
+
if (import_fs2.default.existsSync(MACHINE_ID_FILE)) {
|
|
1357
|
+
try {
|
|
1358
|
+
return import_fs2.default.readFileSync(MACHINE_ID_FILE, "utf-8").trim();
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
console.error("Error reading machine ID:", error);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
const machineId = import_crypto.default.randomBytes(16).toString("hex");
|
|
1364
|
+
try {
|
|
1365
|
+
import_fs2.default.writeFileSync(MACHINE_ID_FILE, machineId);
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
console.error("Error saving machine ID:", error);
|
|
1368
|
+
}
|
|
1369
|
+
return machineId;
|
|
1370
|
+
}
|
|
1371
|
+
function getMachineInfo() {
|
|
1372
|
+
return {
|
|
1373
|
+
machineId: getMachineId(),
|
|
1374
|
+
hostname: import_os2.default.hostname(),
|
|
1375
|
+
platform: import_os2.default.platform(),
|
|
1376
|
+
arch: import_os2.default.arch(),
|
|
1377
|
+
nodeVersion: process.version,
|
|
1378
|
+
cliVersion: getPackageVersion()
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
function getPackageVersion() {
|
|
1382
|
+
try {
|
|
1383
|
+
const pkg = require_package();
|
|
1384
|
+
return pkg.version;
|
|
1385
|
+
} catch {
|
|
1386
|
+
return "unknown";
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async function registerMachine(projectId, token, baseUrl) {
|
|
1390
|
+
const machineInfo = getMachineInfo();
|
|
1391
|
+
try {
|
|
1392
|
+
const response = await fetch(`${baseUrl}/api/cli/register`, {
|
|
1393
|
+
method: "POST",
|
|
1394
|
+
headers: {
|
|
1395
|
+
"Content-Type": "application/json",
|
|
1396
|
+
"X-API-Key": token
|
|
1397
|
+
},
|
|
1398
|
+
body: JSON.stringify({
|
|
1399
|
+
projectId,
|
|
1400
|
+
...machineInfo,
|
|
1401
|
+
workingDirectory: process.cwd()
|
|
1402
|
+
})
|
|
1403
|
+
});
|
|
1404
|
+
if (!response.ok) {
|
|
1405
|
+
throw new Error(`Failed to register machine: ${response.statusText}`);
|
|
1406
|
+
}
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
console.error("[codmir] Failed to register machine:", error);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/cli/commands/link.ts
|
|
528
1413
|
async function linkCommand(options) {
|
|
529
1414
|
const token = getToken();
|
|
530
1415
|
if (!token) {
|
|
531
|
-
console.error(
|
|
532
|
-
console.log(
|
|
1416
|
+
console.error(import_chalk5.default.red("\u274C Not authenticated"));
|
|
1417
|
+
console.log(import_chalk5.default.dim(" Run"), import_chalk5.default.cyan("codmir login"), import_chalk5.default.dim("first"));
|
|
533
1418
|
process.exit(1);
|
|
534
1419
|
}
|
|
535
|
-
console.log(
|
|
536
|
-
console.log(
|
|
1420
|
+
console.log(import_chalk5.default.bold("\n\u{1F517} codmir link"));
|
|
1421
|
+
console.log(import_chalk5.default.dim(" Link this directory to a codmir project\n"));
|
|
537
1422
|
const existing = readProjectConfig();
|
|
538
1423
|
if (existing) {
|
|
539
|
-
console.log(
|
|
540
|
-
console.log(
|
|
1424
|
+
console.log(import_chalk5.default.yellow("\u26A0\uFE0F This directory is already linked to:"));
|
|
1425
|
+
console.log(import_chalk5.default.dim(" Project ID:"), import_chalk5.default.bold(existing.projectId));
|
|
541
1426
|
if (existing.projectName) {
|
|
542
|
-
console.log(
|
|
1427
|
+
console.log(import_chalk5.default.dim(" Project:"), import_chalk5.default.bold(existing.projectName));
|
|
543
1428
|
}
|
|
544
|
-
const { overwrite } = await (0,
|
|
1429
|
+
const { overwrite } = await (0, import_prompts2.default)({
|
|
545
1430
|
type: "confirm",
|
|
546
1431
|
name: "overwrite",
|
|
547
1432
|
message: "Do you want to overwrite this configuration?",
|
|
548
1433
|
initial: false
|
|
549
1434
|
});
|
|
550
1435
|
if (!overwrite) {
|
|
551
|
-
console.log(
|
|
1436
|
+
console.log(import_chalk5.default.dim(" Cancelled"));
|
|
552
1437
|
return;
|
|
553
1438
|
}
|
|
554
1439
|
}
|
|
555
1440
|
try {
|
|
1441
|
+
const baseUrl = getBaseUrl();
|
|
556
1442
|
const client = new CodmirClient({
|
|
557
1443
|
apiKey: token,
|
|
558
|
-
baseUrl
|
|
1444
|
+
baseUrl
|
|
559
1445
|
});
|
|
560
1446
|
let projectId = options.project;
|
|
561
1447
|
let orgId = options.org;
|
|
562
|
-
console.log(
|
|
563
|
-
const response = await fetch(`${
|
|
1448
|
+
console.log(import_chalk5.default.dim(" Fetching your projects...\n"));
|
|
1449
|
+
const response = await fetch(`${baseUrl}/api/projects`, {
|
|
564
1450
|
headers: {
|
|
565
|
-
"
|
|
1451
|
+
"X-API-Key": token
|
|
566
1452
|
}
|
|
567
1453
|
});
|
|
568
1454
|
if (!response.ok) {
|
|
569
1455
|
throw new Error("Failed to fetch projects");
|
|
570
1456
|
}
|
|
571
|
-
const
|
|
1457
|
+
const responseData = await response.json();
|
|
1458
|
+
const projects = responseData.data || responseData;
|
|
572
1459
|
if (!projectId) {
|
|
573
1460
|
if (projects.length === 0) {
|
|
574
|
-
console.log(
|
|
575
|
-
console.log(
|
|
1461
|
+
console.log(import_chalk5.default.yellow("\u26A0\uFE0F You don't have any projects yet"));
|
|
1462
|
+
console.log(import_chalk5.default.dim(" Create a project at"), import_chalk5.default.cyan("https://codmir.com"));
|
|
576
1463
|
process.exit(1);
|
|
577
1464
|
}
|
|
578
|
-
const { selectedProject } = await (0,
|
|
1465
|
+
const { selectedProject } = await (0, import_prompts2.default)({
|
|
579
1466
|
type: "select",
|
|
580
1467
|
name: "selectedProject",
|
|
581
1468
|
message: "Select a project to link:",
|
|
@@ -586,14 +1473,14 @@ async function linkCommand(options) {
|
|
|
586
1473
|
}))
|
|
587
1474
|
});
|
|
588
1475
|
if (!selectedProject) {
|
|
589
|
-
console.log(
|
|
1476
|
+
console.log(import_chalk5.default.dim(" Cancelled"));
|
|
590
1477
|
return;
|
|
591
1478
|
}
|
|
592
1479
|
projectId = selectedProject;
|
|
593
1480
|
}
|
|
594
1481
|
const project = projects.find((p) => p.id === projectId);
|
|
595
1482
|
if (!project) {
|
|
596
|
-
console.error(
|
|
1483
|
+
console.error(import_chalk5.default.red("\u274C Project not found"));
|
|
597
1484
|
process.exit(1);
|
|
598
1485
|
}
|
|
599
1486
|
writeProjectConfig({
|
|
@@ -601,67 +1488,52 @@ async function linkCommand(options) {
|
|
|
601
1488
|
organizationId: project.organizationId || orgId,
|
|
602
1489
|
projectName: project.name
|
|
603
1490
|
});
|
|
604
|
-
console.log(
|
|
605
|
-
console.log(
|
|
606
|
-
console.log(
|
|
607
|
-
console.log(
|
|
608
|
-
console.log(
|
|
1491
|
+
console.log(import_chalk5.default.green("\n\u2705 Successfully linked!"));
|
|
1492
|
+
console.log(import_chalk5.default.dim(" Project:"), import_chalk5.default.bold(project.name));
|
|
1493
|
+
console.log(import_chalk5.default.dim(" ID:"), import_chalk5.default.bold(project.id));
|
|
1494
|
+
console.log(import_chalk5.default.dim("\n Configuration saved to"), import_chalk5.default.cyan(".codmir.json"));
|
|
1495
|
+
console.log(import_chalk5.default.dim(" You can now use the codmir package in your project"));
|
|
1496
|
+
await registerMachine(project.id, token, baseUrl);
|
|
1497
|
+
console.log();
|
|
1498
|
+
const { analyze } = await (0, import_prompts2.default)({
|
|
1499
|
+
type: "confirm",
|
|
1500
|
+
name: "analyze",
|
|
1501
|
+
message: "Analyze codebase now? (Recommended)",
|
|
1502
|
+
initial: true
|
|
1503
|
+
});
|
|
1504
|
+
if (analyze) {
|
|
1505
|
+
console.log();
|
|
1506
|
+
const { analyzeCommand: analyzeCommand2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
1507
|
+
await analyzeCommand2({ mode: "local" });
|
|
1508
|
+
} else {
|
|
1509
|
+
console.log();
|
|
1510
|
+
console.log(import_chalk5.default.dim(" You can analyze later with:"), import_chalk5.default.cyan("codmir analyze"));
|
|
1511
|
+
console.log();
|
|
1512
|
+
}
|
|
609
1513
|
} catch (error) {
|
|
610
|
-
console.
|
|
611
|
-
|
|
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"));
|
|
1514
|
+
console.log(import_chalk5.default.red("\n\u274C Failed to link project"));
|
|
1515
|
+
console.log(import_chalk5.default.dim(error.message));
|
|
621
1516
|
process.exit(1);
|
|
622
1517
|
}
|
|
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
1518
|
}
|
|
648
1519
|
|
|
649
1520
|
// src/cli/commands/init.ts
|
|
650
|
-
|
|
651
|
-
var
|
|
652
|
-
var
|
|
1521
|
+
init_config();
|
|
1522
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
1523
|
+
var import_fs5 = __toESM(require("fs"));
|
|
1524
|
+
var import_path5 = __toESM(require("path"));
|
|
653
1525
|
async function initCommand() {
|
|
654
|
-
console.log(
|
|
1526
|
+
console.log(import_chalk6.default.bold("\n\u{1F389} Initialize codmir\n"));
|
|
655
1527
|
const config = readProjectConfig();
|
|
656
1528
|
if (!config) {
|
|
657
|
-
console.log(
|
|
658
|
-
console.log(
|
|
1529
|
+
console.log(import_chalk6.default.yellow("\u26A0\uFE0F Project not linked"));
|
|
1530
|
+
console.log(import_chalk6.default.dim(" Run"), import_chalk6.default.cyan("codmir link"), import_chalk6.default.dim("to link this project first"));
|
|
659
1531
|
return;
|
|
660
1532
|
}
|
|
661
|
-
console.log(
|
|
662
|
-
const exampleFile =
|
|
663
|
-
if (
|
|
664
|
-
console.log(
|
|
1533
|
+
console.log(import_chalk6.default.dim(" Project:"), import_chalk6.default.bold(config.projectName || config.projectId));
|
|
1534
|
+
const exampleFile = import_path5.default.join(process.cwd(), "codmir.example.ts");
|
|
1535
|
+
if (import_fs5.default.existsSync(exampleFile)) {
|
|
1536
|
+
console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F codmir.example.ts already exists"));
|
|
665
1537
|
} else {
|
|
666
1538
|
const example = `import { CodmirClient } from 'codmir';
|
|
667
1539
|
|
|
@@ -703,56 +1575,916 @@ async function createTestCase() {
|
|
|
703
1575
|
createTicket().catch(console.error);
|
|
704
1576
|
createTestCase().catch(console.error);
|
|
705
1577
|
`;
|
|
706
|
-
|
|
707
|
-
console.log(
|
|
1578
|
+
import_fs5.default.writeFileSync(exampleFile, example);
|
|
1579
|
+
console.log(import_chalk6.default.green("\n\u2705 Created"), import_chalk6.default.cyan("codmir.example.ts"));
|
|
708
1580
|
}
|
|
709
|
-
console.log(
|
|
710
|
-
console.log(
|
|
711
|
-
console.log(
|
|
712
|
-
console.log(
|
|
713
|
-
console.log(
|
|
714
|
-
console.log(
|
|
1581
|
+
console.log(import_chalk6.default.bold("\n\u{1F4DA} Next Steps:\n"));
|
|
1582
|
+
console.log(import_chalk6.default.dim(" 1."), "Install the package:", import_chalk6.default.cyan("npm install codmir"));
|
|
1583
|
+
console.log(import_chalk6.default.dim(" 2."), "Get your API token from:", import_chalk6.default.cyan("https://codmir.com/settings/tokens"));
|
|
1584
|
+
console.log(import_chalk6.default.dim(" 3."), "Set environment variable:", import_chalk6.default.cyan("CODMIR_API_KEY=your-token"));
|
|
1585
|
+
console.log(import_chalk6.default.dim(" 4."), "Check", import_chalk6.default.cyan("codmir.example.ts"), "for usage examples");
|
|
1586
|
+
console.log(import_chalk6.default.dim("\n \u{1F4D6} Documentation:"), import_chalk6.default.cyan("https://codmir.com/docs"));
|
|
715
1587
|
}
|
|
716
1588
|
|
|
717
1589
|
// src/cli/commands/projects.ts
|
|
718
|
-
|
|
1590
|
+
init_config();
|
|
1591
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
719
1592
|
async function projectsCommand() {
|
|
720
1593
|
const token = getToken();
|
|
721
1594
|
if (!token) {
|
|
722
|
-
console.error(
|
|
723
|
-
console.log(
|
|
1595
|
+
console.error(import_chalk7.default.red("\u274C Not authenticated"));
|
|
1596
|
+
console.log(import_chalk7.default.dim(" Run"), import_chalk7.default.cyan("codmir login"), import_chalk7.default.dim("first"));
|
|
724
1597
|
process.exit(1);
|
|
725
1598
|
}
|
|
726
|
-
console.log(
|
|
1599
|
+
console.log(import_chalk7.default.bold("\n\u{1F4C1} Your Projects\n"));
|
|
727
1600
|
try {
|
|
728
|
-
const
|
|
1601
|
+
const baseUrl = getBaseUrl();
|
|
1602
|
+
const response = await fetch(`${baseUrl}/api/projects`, {
|
|
729
1603
|
headers: {
|
|
730
|
-
"
|
|
1604
|
+
"X-API-Key": token
|
|
731
1605
|
}
|
|
732
1606
|
});
|
|
733
1607
|
if (!response.ok) {
|
|
734
1608
|
throw new Error("Failed to fetch projects");
|
|
735
1609
|
}
|
|
736
|
-
const
|
|
1610
|
+
const responseData = await response.json();
|
|
1611
|
+
const projects = responseData.data || responseData;
|
|
737
1612
|
if (projects.length === 0) {
|
|
738
|
-
console.log(
|
|
739
|
-
console.log(
|
|
1613
|
+
console.log(import_chalk7.default.yellow(" No projects found"));
|
|
1614
|
+
console.log(import_chalk7.default.dim(" Create one at"), import_chalk7.default.cyan("https://codmir.com"));
|
|
740
1615
|
return;
|
|
741
1616
|
}
|
|
742
1617
|
projects.forEach((project, index) => {
|
|
743
|
-
console.log(
|
|
744
|
-
console.log(
|
|
1618
|
+
console.log(import_chalk7.default.bold(` ${index + 1}. ${project.name}`), import_chalk7.default.dim(`(${project.key})`));
|
|
1619
|
+
console.log(import_chalk7.default.dim(" ID:"), project.id);
|
|
745
1620
|
if (project.description) {
|
|
746
|
-
console.log(
|
|
1621
|
+
console.log(import_chalk7.default.dim(" Description:"), project.description);
|
|
1622
|
+
}
|
|
1623
|
+
console.log("");
|
|
1624
|
+
});
|
|
1625
|
+
console.log(import_chalk7.default.dim(" Link a project:"), import_chalk7.default.cyan("codmir link"));
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
console.error(import_chalk7.default.red("\n\u274C Failed to fetch projects:"), error instanceof Error ? error.message : "Unknown error");
|
|
1628
|
+
process.exit(1);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/cli/commands/assistant.ts
|
|
1633
|
+
var import_chalk8 = __toESM(require("chalk"));
|
|
1634
|
+
var import_ora2 = __toESM(require("ora"));
|
|
1635
|
+
var import_prompts3 = __toESM(require("prompts"));
|
|
1636
|
+
var import_clipboardy = __toESM(require("clipboardy"));
|
|
1637
|
+
var import_fs6 = __toESM(require("fs"));
|
|
1638
|
+
var import_path6 = __toESM(require("path"));
|
|
1639
|
+
var import_form_data = __toESM(require("form-data"));
|
|
1640
|
+
init_config();
|
|
1641
|
+
async function assistantCommand(query, options = {}) {
|
|
1642
|
+
const token = getToken();
|
|
1643
|
+
if (!token) {
|
|
1644
|
+
console.error(import_chalk8.default.red("\u274C Not authenticated"));
|
|
1645
|
+
console.log(import_chalk8.default.dim(" Run"), import_chalk8.default.cyan("codmir login"), import_chalk8.default.dim("first"));
|
|
1646
|
+
process.exit(1);
|
|
1647
|
+
}
|
|
1648
|
+
const baseUrl = getBaseUrl();
|
|
1649
|
+
const conversationHistory = [];
|
|
1650
|
+
const context = getExecutionContext();
|
|
1651
|
+
if (context.isLinkedProject) {
|
|
1652
|
+
console.log(import_chalk8.default.dim(` \u{1F4C1} Project: ${context.projectConfig?.projectName || context.projectConfig?.projectId}`));
|
|
1653
|
+
console.log(import_chalk8.default.dim(` \u{1F517} Mode: ${import_chalk8.default.cyan("local")} (linked project)
|
|
1654
|
+
`));
|
|
1655
|
+
} else {
|
|
1656
|
+
console.log(import_chalk8.default.dim(` \u{1F310} Mode: ${import_chalk8.default.yellow("global")}
|
|
1657
|
+
`));
|
|
1658
|
+
}
|
|
1659
|
+
conversationHistory.push({
|
|
1660
|
+
role: "system",
|
|
1661
|
+
content: `You are codmir, an AI assistant for developers. You help with:
|
|
1662
|
+
- Code questions and debugging
|
|
1663
|
+
- Project setup and configuration
|
|
1664
|
+
- Best practices and patterns
|
|
1665
|
+
- Task automation
|
|
1666
|
+
|
|
1667
|
+
Be concise, practical, and code-focused. Use lowercase "codmir" for the brand.`
|
|
1668
|
+
});
|
|
1669
|
+
console.log(import_chalk8.default.bold("\n\u{1F916} codmir"));
|
|
1670
|
+
console.log(import_chalk8.default.dim(" AI assistant for developers\n"));
|
|
1671
|
+
if (query) {
|
|
1672
|
+
await handleQuery(query, conversationHistory, baseUrl, token, options);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
console.log(import_chalk8.default.dim(" Type your question or"), import_chalk8.default.cyan("exit"), import_chalk8.default.dim("to quit"));
|
|
1676
|
+
console.log(import_chalk8.default.dim(" \u{1F4CB} Tip: Copy images to clipboard before asking for automatic upload"));
|
|
1677
|
+
console.log(import_chalk8.default.dim(' Example: "How do I setup authentication?"\n'));
|
|
1678
|
+
while (true) {
|
|
1679
|
+
const { userInput } = await (0, import_prompts3.default)({
|
|
1680
|
+
type: "text",
|
|
1681
|
+
name: "userInput",
|
|
1682
|
+
message: import_chalk8.default.cyan("codmir"),
|
|
1683
|
+
validate: (value) => value.length > 0 || "Please enter a question"
|
|
1684
|
+
});
|
|
1685
|
+
if (!userInput || userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") {
|
|
1686
|
+
console.log(import_chalk8.default.dim("\n Goodbye! \u{1F44B}\n"));
|
|
1687
|
+
break;
|
|
1688
|
+
}
|
|
1689
|
+
await handleQuery(userInput, conversationHistory, baseUrl, token, options);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
async function handleQuery(query, conversationHistory, baseUrl, token, options) {
|
|
1693
|
+
let imagePaths;
|
|
1694
|
+
const hasClipboardImage = await checkClipboardForImage();
|
|
1695
|
+
if (hasClipboardImage) {
|
|
1696
|
+
const { attachImage } = await (0, import_prompts3.default)({
|
|
1697
|
+
type: "confirm",
|
|
1698
|
+
name: "attachImage",
|
|
1699
|
+
message: import_chalk8.default.cyan("\u{1F4CB} Image detected in clipboard. Attach to message?"),
|
|
1700
|
+
initial: true
|
|
1701
|
+
});
|
|
1702
|
+
if (attachImage) {
|
|
1703
|
+
const imagePath = await saveClipboardImage();
|
|
1704
|
+
if (imagePath) {
|
|
1705
|
+
imagePaths = [imagePath];
|
|
1706
|
+
console.log(import_chalk8.default.green(" \u2713 Image attached from clipboard\n"));
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
conversationHistory.push({
|
|
1711
|
+
role: "user",
|
|
1712
|
+
content: query,
|
|
1713
|
+
images: imagePaths
|
|
1714
|
+
});
|
|
1715
|
+
const spinner = (0, import_ora2.default)({
|
|
1716
|
+
text: import_chalk8.default.dim("Thinking..."),
|
|
1717
|
+
color: "cyan",
|
|
1718
|
+
spinner: {
|
|
1719
|
+
interval: 80,
|
|
1720
|
+
frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
|
|
1721
|
+
}
|
|
1722
|
+
}).start();
|
|
1723
|
+
try {
|
|
1724
|
+
let response;
|
|
1725
|
+
if (imagePaths && imagePaths.length > 0) {
|
|
1726
|
+
const formData = new import_form_data.default();
|
|
1727
|
+
formData.append("query", query);
|
|
1728
|
+
formData.append("model", options.model || "gpt-4-turbo-preview");
|
|
1729
|
+
if (options.project) formData.append("project", options.project);
|
|
1730
|
+
if (options.context) formData.append("includeContext", "true");
|
|
1731
|
+
formData.append("messages", JSON.stringify(conversationHistory));
|
|
1732
|
+
for (const imagePath of imagePaths) {
|
|
1733
|
+
formData.append("images", import_fs6.default.createReadStream(imagePath));
|
|
1734
|
+
}
|
|
1735
|
+
response = await fetch(`${baseUrl}/api/assistant/chat`, {
|
|
1736
|
+
method: "POST",
|
|
1737
|
+
headers: {
|
|
1738
|
+
"X-API-Key": token,
|
|
1739
|
+
...formData.getHeaders()
|
|
1740
|
+
},
|
|
1741
|
+
body: formData
|
|
1742
|
+
});
|
|
1743
|
+
} else {
|
|
1744
|
+
response = await fetch(`${baseUrl}/api/assistant/chat`, {
|
|
1745
|
+
method: "POST",
|
|
1746
|
+
headers: {
|
|
1747
|
+
"Content-Type": "application/json",
|
|
1748
|
+
"X-API-Key": token
|
|
1749
|
+
},
|
|
1750
|
+
body: JSON.stringify({
|
|
1751
|
+
messages: conversationHistory,
|
|
1752
|
+
model: options.model || "gpt-4-turbo-preview",
|
|
1753
|
+
project: options.project,
|
|
1754
|
+
includeContext: options.context
|
|
1755
|
+
})
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
spinner.stop();
|
|
1759
|
+
if (!response.ok) {
|
|
1760
|
+
throw new Error(`API error: ${response.status}`);
|
|
1761
|
+
}
|
|
1762
|
+
const data = await response.json();
|
|
1763
|
+
const assistantMessage = data.message || data.content || "Sorry, I couldn't process that.";
|
|
1764
|
+
conversationHistory.push({
|
|
1765
|
+
role: "assistant",
|
|
1766
|
+
content: assistantMessage
|
|
1767
|
+
});
|
|
1768
|
+
console.log("\n" + renderResponse(assistantMessage) + "\n");
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
spinner.stop();
|
|
1771
|
+
console.error(import_chalk8.default.red("\n\u274C Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
1772
|
+
console.log(import_chalk8.default.dim(" Try again or check your connection\n"));
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
function renderResponse(message) {
|
|
1776
|
+
const indicator = getIndicator();
|
|
1777
|
+
const lines = message.split("\n");
|
|
1778
|
+
let output = "";
|
|
1779
|
+
lines.forEach((line, index) => {
|
|
1780
|
+
if (index === 0) {
|
|
1781
|
+
output += `${indicator} ${import_chalk8.default.white(line)}
|
|
1782
|
+
`;
|
|
1783
|
+
} else {
|
|
1784
|
+
output += ` ${import_chalk8.default.white(line)}
|
|
1785
|
+
`;
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
return output;
|
|
1789
|
+
}
|
|
1790
|
+
function getIndicator() {
|
|
1791
|
+
const isDark = isTerminalDark();
|
|
1792
|
+
if (isDark) {
|
|
1793
|
+
return import_chalk8.default.whiteBright("\u25CF");
|
|
1794
|
+
} else {
|
|
1795
|
+
return import_chalk8.default.black("\u25CF");
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
function isTerminalDark() {
|
|
1799
|
+
const colorScheme = process.env.COLORFGBG;
|
|
1800
|
+
if (colorScheme) {
|
|
1801
|
+
const parts = colorScheme.split(";");
|
|
1802
|
+
const bg = parseInt(parts[1] || "0");
|
|
1803
|
+
return bg < 8;
|
|
1804
|
+
}
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
async function quickQueryCommand(query, options = {}) {
|
|
1808
|
+
const conversationHistory = [{
|
|
1809
|
+
role: "system",
|
|
1810
|
+
content: "You are codmir, a concise AI assistant for developers. Give short, practical answers."
|
|
1811
|
+
}];
|
|
1812
|
+
const token = getToken();
|
|
1813
|
+
if (!token) {
|
|
1814
|
+
console.error(import_chalk8.default.red("\u274C Not authenticated"));
|
|
1815
|
+
process.exit(1);
|
|
1816
|
+
}
|
|
1817
|
+
await handleQuery(query, conversationHistory, getBaseUrl(), token, options);
|
|
1818
|
+
}
|
|
1819
|
+
async function checkClipboardForImage() {
|
|
1820
|
+
try {
|
|
1821
|
+
const clipboardContent = await import_clipboardy.default.read();
|
|
1822
|
+
return clipboardContent.startsWith("data:image/") || clipboardContent.match(/\.(png|jpg|jpeg|gif|webp)$/i) !== null;
|
|
1823
|
+
} catch {
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
async function saveClipboardImage() {
|
|
1828
|
+
try {
|
|
1829
|
+
const clipboardContent = await import_clipboardy.default.read();
|
|
1830
|
+
if (clipboardContent.startsWith("data:image/")) {
|
|
1831
|
+
const matches = clipboardContent.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
1832
|
+
if (!matches) return null;
|
|
1833
|
+
const ext = matches[1];
|
|
1834
|
+
const base64Data = matches[2];
|
|
1835
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
1836
|
+
const tmpPath = import_path6.default.join("/tmp", `codmir-assistant-${Date.now()}.${ext}`);
|
|
1837
|
+
import_fs6.default.writeFileSync(tmpPath, buffer);
|
|
1838
|
+
return tmpPath;
|
|
1839
|
+
}
|
|
1840
|
+
if (import_fs6.default.existsSync(clipboardContent)) {
|
|
1841
|
+
return clipboardContent;
|
|
1842
|
+
}
|
|
1843
|
+
return null;
|
|
1844
|
+
} catch (error) {
|
|
1845
|
+
console.error(import_chalk8.default.dim(" Failed to save clipboard image"));
|
|
1846
|
+
return null;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// src/cli/commands/ticket.ts
|
|
1851
|
+
var import_chalk9 = __toESM(require("chalk"));
|
|
1852
|
+
var import_prompts4 = __toESM(require("prompts"));
|
|
1853
|
+
var import_clipboardy2 = __toESM(require("clipboardy"));
|
|
1854
|
+
var import_fs7 = __toESM(require("fs"));
|
|
1855
|
+
var import_path7 = __toESM(require("path"));
|
|
1856
|
+
var import_form_data2 = __toESM(require("form-data"));
|
|
1857
|
+
init_config();
|
|
1858
|
+
async function createTicketCommand() {
|
|
1859
|
+
const token = getToken();
|
|
1860
|
+
if (!token) {
|
|
1861
|
+
console.error(import_chalk9.default.red("\u274C Not authenticated"));
|
|
1862
|
+
console.log(import_chalk9.default.dim(" Run"), import_chalk9.default.cyan("codmir login"), import_chalk9.default.dim("first"));
|
|
1863
|
+
process.exit(1);
|
|
1864
|
+
}
|
|
1865
|
+
console.log(import_chalk9.default.bold("\n\u{1F3AB} Create Ticket\n"));
|
|
1866
|
+
try {
|
|
1867
|
+
const linkedProject = getProjectConfig();
|
|
1868
|
+
let selectedProject = null;
|
|
1869
|
+
let selectedOrg = null;
|
|
1870
|
+
if (linkedProject) {
|
|
1871
|
+
const { useLinked } = await (0, import_prompts4.default)({
|
|
1872
|
+
type: "confirm",
|
|
1873
|
+
name: "useLinked",
|
|
1874
|
+
message: `Use linked project: ${import_chalk9.default.cyan(linkedProject.name)}?`,
|
|
1875
|
+
initial: true
|
|
1876
|
+
});
|
|
1877
|
+
if (useLinked) {
|
|
1878
|
+
selectedProject = linkedProject;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (!selectedProject) {
|
|
1882
|
+
const selection = await navigateAndSelectProject(token);
|
|
1883
|
+
if (!selection) {
|
|
1884
|
+
console.log(import_chalk9.default.yellow("\n\u26A0\uFE0F Cancelled"));
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
selectedProject = selection.project;
|
|
1888
|
+
selectedOrg = selection.organization;
|
|
1889
|
+
}
|
|
1890
|
+
console.log(import_chalk9.default.green("\n\u2713 Project:"), import_chalk9.default.cyan(selectedProject.name));
|
|
1891
|
+
const ticketData = await collectTicketDetails();
|
|
1892
|
+
if (!ticketData) {
|
|
1893
|
+
console.log(import_chalk9.default.yellow("\n\u26A0\uFE0F Cancelled"));
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
const hasClipboardImage = await checkClipboardForImage2();
|
|
1897
|
+
if (hasClipboardImage) {
|
|
1898
|
+
const { attachImage } = await (0, import_prompts4.default)({
|
|
1899
|
+
type: "confirm",
|
|
1900
|
+
name: "attachImage",
|
|
1901
|
+
message: import_chalk9.default.cyan("\u{1F4CB} Image detected in clipboard. Attach to ticket?"),
|
|
1902
|
+
initial: true
|
|
1903
|
+
});
|
|
1904
|
+
if (attachImage) {
|
|
1905
|
+
const imagePath = await saveClipboardImage2();
|
|
1906
|
+
if (imagePath) {
|
|
1907
|
+
ticketData.images = [imagePath];
|
|
1908
|
+
console.log(import_chalk9.default.green("\u2713 Image attached"));
|
|
1909
|
+
}
|
|
747
1910
|
}
|
|
1911
|
+
}
|
|
1912
|
+
await createTicket(token, selectedProject.id, ticketData);
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
console.error(import_chalk9.default.red("\n\u274C Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
1915
|
+
process.exit(1);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
async function navigateAndSelectProject(token) {
|
|
1919
|
+
const baseUrl = getBaseUrl();
|
|
1920
|
+
console.log(import_chalk9.default.dim("\n Loading organizations..."));
|
|
1921
|
+
const orgsResponse = await fetch(`${baseUrl}/api/user/organizations`, {
|
|
1922
|
+
headers: { "X-API-Key": token }
|
|
1923
|
+
});
|
|
1924
|
+
if (!orgsResponse.ok) {
|
|
1925
|
+
throw new Error("Failed to fetch organizations");
|
|
1926
|
+
}
|
|
1927
|
+
const { organizations } = await orgsResponse.json();
|
|
1928
|
+
if (!organizations || organizations.length === 0) {
|
|
1929
|
+
console.log(import_chalk9.default.yellow("\n\u26A0\uFE0F No organizations found"));
|
|
1930
|
+
return null;
|
|
1931
|
+
}
|
|
1932
|
+
console.log(import_chalk9.default.bold("\n\u{1F579}\uFE0F Select Organization"));
|
|
1933
|
+
console.log(import_chalk9.default.dim(" Use \u2191\u2193 arrows to navigate, Enter to select\n"));
|
|
1934
|
+
const { orgIndex } = await (0, import_prompts4.default)({
|
|
1935
|
+
type: "select",
|
|
1936
|
+
name: "orgIndex",
|
|
1937
|
+
message: import_chalk9.default.cyan("Organization"),
|
|
1938
|
+
choices: organizations.map((org, index) => ({
|
|
1939
|
+
title: `${getJoystickIndicator(index)} ${org.name}`,
|
|
1940
|
+
value: index,
|
|
1941
|
+
description: org.slug
|
|
1942
|
+
})),
|
|
1943
|
+
initial: 0
|
|
1944
|
+
});
|
|
1945
|
+
if (orgIndex === void 0) return null;
|
|
1946
|
+
const selectedOrg = organizations[orgIndex];
|
|
1947
|
+
console.log(import_chalk9.default.dim("\n Loading projects..."));
|
|
1948
|
+
const projectsResponse = await fetch(
|
|
1949
|
+
`${baseUrl}/api/organizations/${selectedOrg.id}/projects`,
|
|
1950
|
+
{ headers: { "X-API-Key": token } }
|
|
1951
|
+
);
|
|
1952
|
+
if (!projectsResponse.ok) {
|
|
1953
|
+
throw new Error("Failed to fetch projects");
|
|
1954
|
+
}
|
|
1955
|
+
const projectsData = await projectsResponse.json();
|
|
1956
|
+
const projects = projectsData.data || projectsData;
|
|
1957
|
+
if (!projects || projects.length === 0) {
|
|
1958
|
+
console.log(import_chalk9.default.yellow("\n\u26A0\uFE0F No projects found in this organization"));
|
|
1959
|
+
return null;
|
|
1960
|
+
}
|
|
1961
|
+
console.log(import_chalk9.default.bold("\n\u{1F579}\uFE0F Select Project"));
|
|
1962
|
+
console.log(import_chalk9.default.dim(" Use \u2191\u2193 arrows to navigate, Enter to select\n"));
|
|
1963
|
+
const { projectIndex } = await (0, import_prompts4.default)({
|
|
1964
|
+
type: "select",
|
|
1965
|
+
name: "projectIndex",
|
|
1966
|
+
message: import_chalk9.default.cyan("Project"),
|
|
1967
|
+
choices: projects.map((project, index) => ({
|
|
1968
|
+
title: `${getJoystickIndicator(index)} ${project.name}`,
|
|
1969
|
+
value: index,
|
|
1970
|
+
description: project.key
|
|
1971
|
+
})),
|
|
1972
|
+
initial: 0
|
|
1973
|
+
});
|
|
1974
|
+
if (projectIndex === void 0) return null;
|
|
1975
|
+
return {
|
|
1976
|
+
organization: selectedOrg,
|
|
1977
|
+
project: projects[projectIndex]
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function getJoystickIndicator(index) {
|
|
1981
|
+
const indicators = ["\u25B8", "\u25B9", "\u25B8", "\u25B9"];
|
|
1982
|
+
return import_chalk9.default.cyan(indicators[index % indicators.length]);
|
|
1983
|
+
}
|
|
1984
|
+
async function collectTicketDetails() {
|
|
1985
|
+
console.log(import_chalk9.default.bold("\n\u{1F4DD} Ticket Details\n"));
|
|
1986
|
+
const responses = await (0, import_prompts4.default)([
|
|
1987
|
+
{
|
|
1988
|
+
type: "text",
|
|
1989
|
+
name: "title",
|
|
1990
|
+
message: import_chalk9.default.cyan("Title"),
|
|
1991
|
+
validate: (value) => value.length > 0 || "Title is required"
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
type: "text",
|
|
1995
|
+
name: "description",
|
|
1996
|
+
message: import_chalk9.default.cyan("Description"),
|
|
1997
|
+
initial: ""
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
type: "select",
|
|
2001
|
+
name: "type",
|
|
2002
|
+
message: import_chalk9.default.cyan("Type"),
|
|
2003
|
+
choices: [
|
|
2004
|
+
{ title: "\u{1F41B} Bug", value: "BUG" },
|
|
2005
|
+
{ title: "\u2728 Feature", value: "FEATURE" },
|
|
2006
|
+
{ title: "\u{1F4CB} Task", value: "TASK" },
|
|
2007
|
+
{ title: "\u{1F527} Improvement", value: "IMPROVEMENT" }
|
|
2008
|
+
],
|
|
2009
|
+
initial: 0
|
|
2010
|
+
},
|
|
2011
|
+
{
|
|
2012
|
+
type: "select",
|
|
2013
|
+
name: "priority",
|
|
2014
|
+
message: import_chalk9.default.cyan("Priority"),
|
|
2015
|
+
choices: [
|
|
2016
|
+
{ title: "\u{1F7E2} Low", value: "LOW" },
|
|
2017
|
+
{ title: "\u{1F7E1} Medium", value: "MEDIUM" },
|
|
2018
|
+
{ title: "\u{1F7E0} High", value: "HIGH" },
|
|
2019
|
+
{ title: "\u{1F534} Critical", value: "CRITICAL" }
|
|
2020
|
+
],
|
|
2021
|
+
initial: 1
|
|
2022
|
+
}
|
|
2023
|
+
]);
|
|
2024
|
+
if (!responses.title) return null;
|
|
2025
|
+
return responses;
|
|
2026
|
+
}
|
|
2027
|
+
async function checkClipboardForImage2() {
|
|
2028
|
+
try {
|
|
2029
|
+
const clipboardContent = await import_clipboardy2.default.read();
|
|
2030
|
+
return clipboardContent.startsWith("data:image/") || clipboardContent.match(/\.(png|jpg|jpeg|gif|webp)$/i) !== null;
|
|
2031
|
+
} catch {
|
|
2032
|
+
return false;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
async function saveClipboardImage2() {
|
|
2036
|
+
try {
|
|
2037
|
+
const clipboardContent = await import_clipboardy2.default.read();
|
|
2038
|
+
if (clipboardContent.startsWith("data:image/")) {
|
|
2039
|
+
const matches = clipboardContent.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
2040
|
+
if (!matches) return null;
|
|
2041
|
+
const ext = matches[1];
|
|
2042
|
+
const base64Data = matches[2];
|
|
2043
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
2044
|
+
const tmpPath = import_path7.default.join("/tmp", `codmir-ticket-${Date.now()}.${ext}`);
|
|
2045
|
+
import_fs7.default.writeFileSync(tmpPath, buffer);
|
|
2046
|
+
return tmpPath;
|
|
2047
|
+
}
|
|
2048
|
+
if (import_fs7.default.existsSync(clipboardContent)) {
|
|
2049
|
+
return clipboardContent;
|
|
2050
|
+
}
|
|
2051
|
+
return null;
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
console.error(import_chalk9.default.dim(" Failed to save clipboard image"));
|
|
2054
|
+
return null;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
async function createTicket(token, projectId, ticketData) {
|
|
2058
|
+
const baseUrl = getBaseUrl();
|
|
2059
|
+
console.log(import_chalk9.default.dim("\n Creating ticket..."));
|
|
2060
|
+
try {
|
|
2061
|
+
let response;
|
|
2062
|
+
if (ticketData.images && ticketData.images.length > 0) {
|
|
2063
|
+
const formData = new import_form_data2.default();
|
|
2064
|
+
formData.append("title", ticketData.title);
|
|
2065
|
+
if (ticketData.description) formData.append("description", ticketData.description);
|
|
2066
|
+
if (ticketData.type) formData.append("type", ticketData.type);
|
|
2067
|
+
if (ticketData.priority) formData.append("priority", ticketData.priority);
|
|
2068
|
+
for (const imagePath of ticketData.images) {
|
|
2069
|
+
formData.append("images", import_fs7.default.createReadStream(imagePath));
|
|
2070
|
+
}
|
|
2071
|
+
response = await fetch(`${baseUrl}/api/project/${projectId}/tickets`, {
|
|
2072
|
+
method: "POST",
|
|
2073
|
+
headers: {
|
|
2074
|
+
"X-API-Key": token,
|
|
2075
|
+
...formData.getHeaders()
|
|
2076
|
+
},
|
|
2077
|
+
body: formData
|
|
2078
|
+
});
|
|
2079
|
+
} else {
|
|
2080
|
+
response = await fetch(`${baseUrl}/api/project/${projectId}/tickets`, {
|
|
2081
|
+
method: "POST",
|
|
2082
|
+
headers: {
|
|
2083
|
+
"Content-Type": "application/json",
|
|
2084
|
+
"X-API-Key": token
|
|
2085
|
+
},
|
|
2086
|
+
body: JSON.stringify(ticketData)
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
if (!response.ok) {
|
|
2090
|
+
const error = await response.text();
|
|
2091
|
+
throw new Error(`Failed to create ticket: ${error}`);
|
|
2092
|
+
}
|
|
2093
|
+
const ticket = await response.json();
|
|
2094
|
+
const ticketData_response = ticket.data || ticket;
|
|
2095
|
+
console.log(import_chalk9.default.green("\n\u2705 Ticket created successfully!\n"));
|
|
2096
|
+
console.log(import_chalk9.default.bold(" Ticket:"), import_chalk9.default.cyan(`#${ticketData_response.key || ticketData_response.id}`));
|
|
2097
|
+
console.log(import_chalk9.default.bold(" Title:"), ticketData_response.title);
|
|
2098
|
+
console.log(import_chalk9.default.bold(" Type:"), getTypeEmoji(ticketData_response.type), ticketData_response.type);
|
|
2099
|
+
console.log(import_chalk9.default.bold(" Priority:"), getPriorityEmoji(ticketData_response.priority), ticketData_response.priority);
|
|
2100
|
+
if (ticketData.images && ticketData.images.length > 0) {
|
|
2101
|
+
console.log(import_chalk9.default.bold(" Attachments:"), import_chalk9.default.green(`${ticketData.images.length} image(s)`));
|
|
2102
|
+
}
|
|
2103
|
+
console.log();
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
throw error;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
function getTypeEmoji(type) {
|
|
2109
|
+
const emojis = {
|
|
2110
|
+
BUG: "\u{1F41B}",
|
|
2111
|
+
FEATURE: "\u2728",
|
|
2112
|
+
TASK: "\u{1F4CB}",
|
|
2113
|
+
IMPROVEMENT: "\u{1F527}"
|
|
2114
|
+
};
|
|
2115
|
+
return emojis[type] || "\u{1F4CB}";
|
|
2116
|
+
}
|
|
2117
|
+
function getPriorityEmoji(priority) {
|
|
2118
|
+
const emojis = {
|
|
2119
|
+
LOW: "\u{1F7E2}",
|
|
2120
|
+
MEDIUM: "\u{1F7E1}",
|
|
2121
|
+
HIGH: "\u{1F7E0}",
|
|
2122
|
+
CRITICAL: "\u{1F534}"
|
|
2123
|
+
};
|
|
2124
|
+
return emojis[priority] || "\u{1F7E1}";
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// src/cli/commands/council.ts
|
|
2128
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
2129
|
+
var import_ora3 = __toESM(require("ora"));
|
|
2130
|
+
var import_prompts5 = __toESM(require("prompts"));
|
|
2131
|
+
var import_ws = __toESM(require("ws"));
|
|
2132
|
+
init_config();
|
|
2133
|
+
var ROLE_ICONS = {
|
|
2134
|
+
architect: "\u{1F3D7}\uFE0F ",
|
|
2135
|
+
security: "\u{1F512}",
|
|
2136
|
+
implementer: "\u{1F4BB}",
|
|
2137
|
+
reviewer: "\u{1F50D}"
|
|
2138
|
+
};
|
|
2139
|
+
var ROLE_COLORS = {
|
|
2140
|
+
architect: import_chalk10.default.blue,
|
|
2141
|
+
security: import_chalk10.default.red,
|
|
2142
|
+
implementer: import_chalk10.default.green,
|
|
2143
|
+
reviewer: import_chalk10.default.yellow
|
|
2144
|
+
};
|
|
2145
|
+
async function councilCommand(task, options = {}) {
|
|
2146
|
+
try {
|
|
2147
|
+
printHeader();
|
|
2148
|
+
if (!task) {
|
|
2149
|
+
const response = await (0, import_prompts5.default)({
|
|
2150
|
+
type: "text",
|
|
2151
|
+
name: "task",
|
|
2152
|
+
message: import_chalk10.default.cyan("\u{1F916} What would you like the council to build?"),
|
|
2153
|
+
validate: (value) => value.length > 0 || "Task cannot be empty"
|
|
2154
|
+
});
|
|
2155
|
+
if (!response.task) {
|
|
2156
|
+
console.log(import_chalk10.default.yellow("\nCouncil session cancelled."));
|
|
2157
|
+
process.exit(0);
|
|
2158
|
+
}
|
|
2159
|
+
task = response.task;
|
|
2160
|
+
}
|
|
2161
|
+
console.log("");
|
|
2162
|
+
const token = getToken();
|
|
2163
|
+
const baseUrl = getBaseUrl();
|
|
2164
|
+
if (!token) {
|
|
2165
|
+
console.log(import_chalk10.default.red("\u274C Not authenticated. Please run: codmir login"));
|
|
2166
|
+
process.exit(1);
|
|
2167
|
+
}
|
|
2168
|
+
const mode = options.local ? "local" : "lambda";
|
|
2169
|
+
const apiUrl = baseUrl.replace("http", "ws") + "/council";
|
|
2170
|
+
console.log(import_chalk10.default.gray(`Mode: ${mode === "lambda" ? "AWS Lambda (Serverless)" : "Local LAN Cluster"}`));
|
|
2171
|
+
console.log("");
|
|
2172
|
+
const ws = new import_ws.default(apiUrl, {
|
|
2173
|
+
headers: {
|
|
2174
|
+
Authorization: `Bearer ${token}`
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
let sessionId = null;
|
|
2178
|
+
let currentSpinner = null;
|
|
2179
|
+
let roundResponses = [];
|
|
2180
|
+
ws.on("open", () => {
|
|
2181
|
+
ws.send(JSON.stringify({
|
|
2182
|
+
type: "create-session",
|
|
2183
|
+
problem: task,
|
|
2184
|
+
mode,
|
|
2185
|
+
options: {
|
|
2186
|
+
initialMembers: parseInt(options.members || "2"),
|
|
2187
|
+
timeout: parseInt(options.timeout || "300") * 1e3
|
|
2188
|
+
}
|
|
2189
|
+
}));
|
|
2190
|
+
});
|
|
2191
|
+
ws.on("message", (data) => {
|
|
2192
|
+
const message = JSON.parse(data.toString());
|
|
2193
|
+
handleCouncilMessage(message, {
|
|
2194
|
+
currentSpinner,
|
|
2195
|
+
roundResponses,
|
|
2196
|
+
onSpinnerUpdate: (spinner) => {
|
|
2197
|
+
currentSpinner = spinner;
|
|
2198
|
+
},
|
|
2199
|
+
onExit: () => process.exit(0)
|
|
2200
|
+
});
|
|
2201
|
+
});
|
|
2202
|
+
ws.on("error", (error) => {
|
|
2203
|
+
if (currentSpinner) currentSpinner.fail();
|
|
2204
|
+
console.log(import_chalk10.default.red(`
|
|
2205
|
+
\u274C Connection error: ${error.message}`));
|
|
2206
|
+
process.exit(1);
|
|
2207
|
+
});
|
|
2208
|
+
ws.on("close", () => {
|
|
2209
|
+
if (currentSpinner) currentSpinner.stop();
|
|
2210
|
+
});
|
|
2211
|
+
} catch (error) {
|
|
2212
|
+
console.log(import_chalk10.default.red(`
|
|
2213
|
+
\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
2214
|
+
process.exit(1);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
function printHeader() {
|
|
2218
|
+
console.log("");
|
|
2219
|
+
console.log(import_chalk10.default.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"));
|
|
2220
|
+
console.log(import_chalk10.default.blue.bold("\u2551 Claude Council - AI Collaboration \u2551"));
|
|
2221
|
+
console.log(import_chalk10.default.blue.bold('\u2551 "Preventing wasted engineering time" \u2551'));
|
|
2222
|
+
console.log(import_chalk10.default.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"));
|
|
2223
|
+
console.log("");
|
|
2224
|
+
}
|
|
2225
|
+
function handleCouncilMessage(message, context) {
|
|
2226
|
+
const { currentSpinner, roundResponses, onSpinnerUpdate, onExit } = context;
|
|
2227
|
+
switch (message.type) {
|
|
2228
|
+
case "session-created":
|
|
2229
|
+
console.log(import_chalk10.default.yellow("\u{1F3AD} Assembling Claude Council..."));
|
|
748
2230
|
console.log("");
|
|
2231
|
+
break;
|
|
2232
|
+
case "spawning-member":
|
|
2233
|
+
const spinner = (0, import_ora3.default)({
|
|
2234
|
+
text: `Spawning ${message.role} in Docker container on ${message.location || "Lambda"}...`,
|
|
2235
|
+
color: "cyan"
|
|
2236
|
+
}).start();
|
|
2237
|
+
onSpinnerUpdate(spinner);
|
|
2238
|
+
break;
|
|
2239
|
+
case "member-ready":
|
|
2240
|
+
if (currentSpinner) {
|
|
2241
|
+
currentSpinner.succeed(
|
|
2242
|
+
import_chalk10.default.green(`\u2713 ${capitalize(message.role)} ready (${message.memberId})`)
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
break;
|
|
2246
|
+
case "all-members-ready":
|
|
2247
|
+
console.log("");
|
|
2248
|
+
console.log(import_chalk10.default.cyan("\u{1F4AC} Council Session Started"));
|
|
2249
|
+
console.log(import_chalk10.default.gray(` Session ID: ${message.sessionId}`));
|
|
2250
|
+
console.log(import_chalk10.default.gray(` Members: ${message.memberCount}`));
|
|
2251
|
+
console.log("");
|
|
2252
|
+
break;
|
|
2253
|
+
case "round-start":
|
|
2254
|
+
printRoundHeader(message.round);
|
|
2255
|
+
roundResponses.length = 0;
|
|
2256
|
+
break;
|
|
2257
|
+
case "member-thinking":
|
|
2258
|
+
const thinkSpinner = (0, import_ora3.default)({
|
|
2259
|
+
text: `${ROLE_ICONS[message.role]} ${capitalize(message.role)} is analyzing...`,
|
|
2260
|
+
color: "yellow"
|
|
2261
|
+
}).start();
|
|
2262
|
+
onSpinnerUpdate(thinkSpinner);
|
|
2263
|
+
break;
|
|
2264
|
+
case "member-response":
|
|
2265
|
+
if (currentSpinner) currentSpinner.stop();
|
|
2266
|
+
const roleColor = ROLE_COLORS[message.role] || import_chalk10.default.white;
|
|
2267
|
+
const icon = ROLE_ICONS[message.role] || "\u{1F916}";
|
|
2268
|
+
console.log("");
|
|
2269
|
+
console.log(roleColor.bold(`${icon} ${capitalize(message.role)}:`));
|
|
2270
|
+
const formatted = formatResponse(message.content);
|
|
2271
|
+
console.log(import_chalk10.default.gray(` ${formatted}`));
|
|
2272
|
+
console.log("");
|
|
2273
|
+
roundResponses.push(message);
|
|
2274
|
+
break;
|
|
2275
|
+
case "round-complete":
|
|
2276
|
+
const agreementCount = message.agreements || 0;
|
|
2277
|
+
const totalMembers = message.totalMembers || roundResponses.length;
|
|
2278
|
+
const agreementPercent = Math.round(agreementCount / totalMembers * 100);
|
|
2279
|
+
console.log(import_chalk10.default.gray(` Agreement: ${agreementCount}/${totalMembers} (${agreementPercent}%)`));
|
|
2280
|
+
console.log("");
|
|
2281
|
+
break;
|
|
2282
|
+
case "consensus-reached":
|
|
2283
|
+
console.log(import_chalk10.default.green.bold("\n\u2705 CONSENSUS REACHED!"));
|
|
2284
|
+
console.log(import_chalk10.default.gray(` Rounds: ${message.rounds}`));
|
|
2285
|
+
console.log(import_chalk10.default.gray(` Time: ${message.time}s`));
|
|
2286
|
+
console.log("");
|
|
2287
|
+
break;
|
|
2288
|
+
case "spawning-additional-member":
|
|
2289
|
+
console.log(import_chalk10.default.yellow(`
|
|
2290
|
+
\u26A0\uFE0F Discussion needs more input. Spawning ${message.role}...`));
|
|
2291
|
+
break;
|
|
2292
|
+
case "generating-solution":
|
|
2293
|
+
const genSpinner = (0, import_ora3.default)({
|
|
2294
|
+
text: "Synthesizing final solution from council discussion...",
|
|
2295
|
+
color: "green"
|
|
2296
|
+
}).start();
|
|
2297
|
+
onSpinnerUpdate(genSpinner);
|
|
2298
|
+
break;
|
|
2299
|
+
case "solution-ready":
|
|
2300
|
+
if (currentSpinner) currentSpinner.succeed("Solution generated");
|
|
2301
|
+
console.log("");
|
|
2302
|
+
console.log(import_chalk10.default.blue.bold("\u2500".repeat(65)));
|
|
2303
|
+
console.log(import_chalk10.default.blue.bold("\u{1F4CB} Solution"));
|
|
2304
|
+
console.log(import_chalk10.default.blue.bold("\u2500".repeat(65)));
|
|
2305
|
+
console.log("");
|
|
2306
|
+
console.log(message.solution);
|
|
2307
|
+
console.log("");
|
|
2308
|
+
if (message.files && message.files.length > 0) {
|
|
2309
|
+
console.log(import_chalk10.default.cyan("\u{1F4C1} Files created:"));
|
|
2310
|
+
message.files.forEach((file) => {
|
|
2311
|
+
console.log(import_chalk10.default.gray(` \u251C\u2500 ${file}`));
|
|
2312
|
+
});
|
|
2313
|
+
console.log("");
|
|
2314
|
+
}
|
|
2315
|
+
break;
|
|
2316
|
+
case "stats":
|
|
2317
|
+
printStats(message.stats);
|
|
2318
|
+
break;
|
|
2319
|
+
case "cleanup-start":
|
|
2320
|
+
console.log(import_chalk10.default.yellow("\u{1F9F9} Cleaning up council members..."));
|
|
2321
|
+
break;
|
|
2322
|
+
case "member-destroyed":
|
|
2323
|
+
console.log(import_chalk10.default.gray(` \u251C\u2500 Destroying ${message.role}... \u2713`));
|
|
2324
|
+
break;
|
|
2325
|
+
case "session-complete":
|
|
2326
|
+
console.log("");
|
|
2327
|
+
console.log(import_chalk10.default.green.bold("\u2728 Council session complete!"));
|
|
2328
|
+
console.log("");
|
|
2329
|
+
onExit();
|
|
2330
|
+
break;
|
|
2331
|
+
case "timeout":
|
|
2332
|
+
console.log(import_chalk10.default.yellow("\n\u23F1\uFE0F Discussion timeout reached"));
|
|
2333
|
+
console.log(import_chalk10.default.gray(" The council needs your input to continue."));
|
|
2334
|
+
break;
|
|
2335
|
+
case "error":
|
|
2336
|
+
if (currentSpinner) currentSpinner.fail();
|
|
2337
|
+
console.log(import_chalk10.default.red(`
|
|
2338
|
+
\u274C Error: ${message.error}`));
|
|
2339
|
+
onExit();
|
|
2340
|
+
break;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function printRoundHeader(round) {
|
|
2344
|
+
console.log(import_chalk10.default.cyan("\u2500".repeat(65)));
|
|
2345
|
+
console.log(import_chalk10.default.cyan(`Round ${round} - Council Discussion`));
|
|
2346
|
+
console.log(import_chalk10.default.cyan("\u2500".repeat(65)));
|
|
2347
|
+
}
|
|
2348
|
+
function formatResponse(content) {
|
|
2349
|
+
const maxLength = 300;
|
|
2350
|
+
if (content.length > maxLength) {
|
|
2351
|
+
return content.substring(0, maxLength) + "...";
|
|
2352
|
+
}
|
|
2353
|
+
return content;
|
|
2354
|
+
}
|
|
2355
|
+
function printStats(stats) {
|
|
2356
|
+
console.log(import_chalk10.default.blue.bold("\u2500".repeat(65)));
|
|
2357
|
+
console.log(import_chalk10.default.blue.bold("\u{1F4CA} Session Statistics"));
|
|
2358
|
+
console.log(import_chalk10.default.blue.bold("\u2500".repeat(65)));
|
|
2359
|
+
console.log("");
|
|
2360
|
+
console.log(import_chalk10.default.gray(` Council members: ${stats.members || 0}`));
|
|
2361
|
+
console.log(import_chalk10.default.gray(` Discussion rounds: ${stats.rounds || 0}`));
|
|
2362
|
+
console.log(import_chalk10.default.gray(` Total time: ${stats.time || 0}s`));
|
|
2363
|
+
console.log(import_chalk10.default.gray(` API cost: $${(stats.cost || 0).toFixed(2)}`));
|
|
2364
|
+
console.log(import_chalk10.default.gray(` Lines of code: ${stats.linesOfCode || 0}`));
|
|
2365
|
+
console.log("");
|
|
2366
|
+
}
|
|
2367
|
+
function capitalize(str) {
|
|
2368
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// src/cli/index.ts
|
|
2372
|
+
init_analyze();
|
|
2373
|
+
|
|
2374
|
+
// src/cli/commands/usage.ts
|
|
2375
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
2376
|
+
var import_prompts6 = __toESM(require("prompts"));
|
|
2377
|
+
init_config();
|
|
2378
|
+
async function usageCommand(options = {}) {
|
|
2379
|
+
const token = getToken();
|
|
2380
|
+
if (!token) {
|
|
2381
|
+
console.error(import_chalk11.default.red("\u274C Not authenticated"));
|
|
2382
|
+
console.log(import_chalk11.default.dim(" Run"), import_chalk11.default.cyan("codmir login"), import_chalk11.default.dim("first"));
|
|
2383
|
+
process.exit(1);
|
|
2384
|
+
}
|
|
2385
|
+
console.log(import_chalk11.default.bold("\n\u{1F4CA} API Usage & Billing\n"));
|
|
2386
|
+
const baseUrl = getBaseUrl();
|
|
2387
|
+
const month = options.month || (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
2388
|
+
try {
|
|
2389
|
+
const response = await fetch(`${baseUrl}/api/keys`, {
|
|
2390
|
+
headers: { "X-API-Key": token }
|
|
749
2391
|
});
|
|
750
|
-
|
|
2392
|
+
if (!response.ok) {
|
|
2393
|
+
throw new Error(`Failed to fetch API keys: ${response.status}`);
|
|
2394
|
+
}
|
|
2395
|
+
const { keys } = await response.json();
|
|
2396
|
+
if (keys.length === 0) {
|
|
2397
|
+
console.log(import_chalk11.default.yellow("\u26A0\uFE0F No API keys found"));
|
|
2398
|
+
console.log(import_chalk11.default.dim(" Create one at:"), import_chalk11.default.cyan("https://codmir.com/settings/api-keys"));
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
console.log(import_chalk11.default.bold(`\u{1F4C5} Month: ${month}
|
|
2402
|
+
`));
|
|
2403
|
+
let totalRequests = 0;
|
|
2404
|
+
let totalAICalls = 0;
|
|
2405
|
+
let totalTokens = 0;
|
|
2406
|
+
let totalCostCents = 0;
|
|
2407
|
+
for (const key of keys) {
|
|
2408
|
+
const usage = key.monthlyUsage[0];
|
|
2409
|
+
const costDollars = usage ? (usage.totalCostCents / 100).toFixed(2) : "0.00";
|
|
2410
|
+
const tokenCount = Number(usage?.totalTokens || 0);
|
|
2411
|
+
totalRequests += usage?.totalRequests || 0;
|
|
2412
|
+
totalAICalls += usage?.totalAICalls || 0;
|
|
2413
|
+
totalTokens += tokenCount;
|
|
2414
|
+
totalCostCents += usage?.totalCostCents || 0;
|
|
2415
|
+
console.log(import_chalk11.default.cyan.bold(`\u{1F511} ${key.name}`));
|
|
2416
|
+
console.log(import_chalk11.default.dim(` Key: ${key.keyPrefix}...`));
|
|
2417
|
+
console.log(import_chalk11.default.dim(` Scope: ${key.scope}`));
|
|
2418
|
+
console.log();
|
|
2419
|
+
console.log(import_chalk11.default.bold(" Usage:"));
|
|
2420
|
+
console.log(import_chalk11.default.dim(` Total Requests: ${(usage?.totalRequests || 0).toLocaleString()}`));
|
|
2421
|
+
console.log(import_chalk11.default.dim(` AI Calls: ${(usage?.totalAICalls || 0).toLocaleString()}`));
|
|
2422
|
+
console.log(import_chalk11.default.dim(` Tokens Used: ${tokenCount.toLocaleString()}`));
|
|
2423
|
+
const costColor = getCostColor(usage?.totalCostCents || 0);
|
|
2424
|
+
console.log(import_chalk11.default.bold(" Cost:"));
|
|
2425
|
+
console.log(costColor(` This Month: $${costDollars}`));
|
|
2426
|
+
if (key.lastUsedAt) {
|
|
2427
|
+
const lastUsed = new Date(key.lastUsedAt);
|
|
2428
|
+
const daysAgo = Math.floor((Date.now() - lastUsed.getTime()) / (1e3 * 60 * 60 * 24));
|
|
2429
|
+
console.log(import_chalk11.default.dim(` Last Used: ${lastUsed.toLocaleDateString()} (${daysAgo} days ago)`));
|
|
2430
|
+
} else {
|
|
2431
|
+
console.log(import_chalk11.default.dim(" Last Used: Never"));
|
|
2432
|
+
}
|
|
2433
|
+
if (options.detailed && usage) {
|
|
2434
|
+
console.log();
|
|
2435
|
+
console.log(import_chalk11.default.bold(" Provider Breakdown:"));
|
|
2436
|
+
if (usage.openaiTokens > 0) {
|
|
2437
|
+
console.log(import_chalk11.default.dim(` OpenAI: ${Number(usage.openaiTokens).toLocaleString()} tokens ($${(usage.openaiCostCents / 100).toFixed(2)})`));
|
|
2438
|
+
}
|
|
2439
|
+
if (usage.anthropicTokens > 0) {
|
|
2440
|
+
console.log(import_chalk11.default.dim(` Anthropic: ${Number(usage.anthropicTokens).toLocaleString()} tokens ($${(usage.anthropicCostCents / 100).toFixed(2)})`));
|
|
2441
|
+
}
|
|
2442
|
+
if (usage.localTokens > 0) {
|
|
2443
|
+
console.log(import_chalk11.default.dim(` Local: ${Number(usage.localTokens).toLocaleString()} tokens (free)`));
|
|
2444
|
+
}
|
|
2445
|
+
if (usage.totalAnalyses > 0) {
|
|
2446
|
+
console.log();
|
|
2447
|
+
console.log(import_chalk11.default.bold(" Codebase Analysis:"));
|
|
2448
|
+
console.log(import_chalk11.default.dim(` Total Analyses: ${usage.totalAnalyses}`));
|
|
2449
|
+
console.log(import_chalk11.default.dim(` Local: ${usage.localAnalyses}`));
|
|
2450
|
+
console.log(import_chalk11.default.dim(` Deep (Railway): ${usage.deepAnalyses} ($${(usage.railwayCostCents / 100).toFixed(2)})`));
|
|
2451
|
+
console.log(import_chalk11.default.dim(` Files Processed: ${Number(usage.filesProcessed).toLocaleString()}`));
|
|
2452
|
+
console.log(import_chalk11.default.dim(` Lines Processed: ${Number(usage.linesProcessed).toLocaleString()}`));
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
console.log();
|
|
2456
|
+
console.log(import_chalk11.default.dim(" \u2500".repeat(50)));
|
|
2457
|
+
console.log();
|
|
2458
|
+
}
|
|
2459
|
+
console.log(import_chalk11.default.bold.cyan("\u{1F4C8} Summary\n"));
|
|
2460
|
+
console.log(import_chalk11.default.dim(` Total Requests: ${totalRequests.toLocaleString()}`));
|
|
2461
|
+
console.log(import_chalk11.default.dim(` AI Calls: ${totalAICalls.toLocaleString()}`));
|
|
2462
|
+
console.log(import_chalk11.default.dim(` Tokens Used: ${totalTokens.toLocaleString()}`));
|
|
2463
|
+
console.log();
|
|
2464
|
+
console.log(import_chalk11.default.bold.green(` Total Cost: $${(totalCostCents / 100).toFixed(2)}`));
|
|
2465
|
+
console.log();
|
|
2466
|
+
if (totalCostCents > 5e3) {
|
|
2467
|
+
console.log(import_chalk11.default.yellow(" \u26A0\uFE0F High usage detected this month"));
|
|
2468
|
+
console.log(import_chalk11.default.dim(" Consider optimizing AI calls or using local mode"));
|
|
2469
|
+
console.log();
|
|
2470
|
+
}
|
|
2471
|
+
if (!options.detailed && keys.length > 0) {
|
|
2472
|
+
console.log(import_chalk11.default.dim(" \u{1F4A1} Tip: Use"), import_chalk11.default.cyan("--detailed"), import_chalk11.default.dim("for provider breakdown"));
|
|
2473
|
+
}
|
|
2474
|
+
console.log(import_chalk11.default.dim(" \u{1F4C5} View other months:"), import_chalk11.default.cyan("codmir usage --month 2025-01"));
|
|
2475
|
+
console.log();
|
|
751
2476
|
} catch (error) {
|
|
752
|
-
console.error(
|
|
2477
|
+
console.error(import_chalk11.default.red("\n\u274C Failed to get usage stats"));
|
|
2478
|
+
console.error(import_chalk11.default.dim(" Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
753
2479
|
process.exit(1);
|
|
754
2480
|
}
|
|
755
2481
|
}
|
|
2482
|
+
function getCostColor(costCents) {
|
|
2483
|
+
if (costCents === 0) return import_chalk11.default.dim;
|
|
2484
|
+
if (costCents < 100) return import_chalk11.default.green;
|
|
2485
|
+
if (costCents < 1e3) return import_chalk11.default.yellow;
|
|
2486
|
+
return import_chalk11.default.red;
|
|
2487
|
+
}
|
|
756
2488
|
|
|
757
2489
|
// src/cli/index.ts
|
|
758
2490
|
var program = new import_commander.Command();
|
|
@@ -762,7 +2494,21 @@ program.command("link").description("Link current directory to a codmir project"
|
|
|
762
2494
|
program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
|
|
763
2495
|
program.command("logout").description("Log out of codmir").action(logoutCommand);
|
|
764
2496
|
program.command("init").description("Initialize codmir in your project").action(initCommand);
|
|
765
|
-
program.command("projects").alias("ls").description("List your projects").action(projectsCommand);
|
|
2497
|
+
program.command("projects").alias("ls").description("List all your projects").action(projectsCommand);
|
|
2498
|
+
program.command("ticket").alias("create").description("\u{1F3AB} Create a new ticket with interactive navigation").action(createTicketCommand);
|
|
2499
|
+
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) => {
|
|
2500
|
+
const queryString = query ? query.join(" ") : void 0;
|
|
2501
|
+
assistantCommand(queryString, options);
|
|
2502
|
+
});
|
|
2503
|
+
program.command("ask <query...>").description("Quick AI query without interactive mode").option("-p, --project <id>", "Include project context").action((query, options) => {
|
|
2504
|
+
quickQueryCommand(query.join(" "), options);
|
|
2505
|
+
});
|
|
2506
|
+
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);
|
|
2507
|
+
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);
|
|
2508
|
+
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) => {
|
|
2509
|
+
const taskString = task ? task.join(" ") : void 0;
|
|
2510
|
+
councilCommand(taskString, options);
|
|
2511
|
+
});
|
|
766
2512
|
program.parse(process.argv);
|
|
767
2513
|
if (!process.argv.slice(2).length) {
|
|
768
2514
|
program.outputHelp();
|