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