docit-ai 1.0.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.
Files changed (81) hide show
  1. package/.env.example +1 -0
  2. package/INSTRUCTIONS.MD +70 -0
  3. package/dist/ai/engine.d.ts +24 -0
  4. package/dist/ai/engine.d.ts.map +1 -0
  5. package/dist/ai/engine.js +69 -0
  6. package/dist/ai/engine.js.map +1 -0
  7. package/dist/ai/prompts.d.ts +11 -0
  8. package/dist/ai/prompts.d.ts.map +1 -0
  9. package/dist/ai/prompts.js +116 -0
  10. package/dist/ai/prompts.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +384 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/formatter/index.d.ts +13 -0
  16. package/dist/formatter/index.d.ts.map +1 -0
  17. package/dist/formatter/index.js +76 -0
  18. package/dist/formatter/index.js.map +1 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +9 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/parser/extractor.d.ts +23 -0
  24. package/dist/parser/extractor.d.ts.map +1 -0
  25. package/dist/parser/extractor.js +148 -0
  26. package/dist/parser/extractor.js.map +1 -0
  27. package/dist/scanner/index.d.ts +11 -0
  28. package/dist/scanner/index.d.ts.map +1 -0
  29. package/dist/scanner/index.js +30 -0
  30. package/dist/scanner/index.js.map +1 -0
  31. package/package.json +31 -0
  32. package/server/.env.example +22 -0
  33. package/server/package-lock.json +4426 -0
  34. package/server/package.json +31 -0
  35. package/server/src/config/database.d.ts +18 -0
  36. package/server/src/config/database.d.ts.map +1 -0
  37. package/server/src/config/database.js +8 -0
  38. package/server/src/config/database.js.map +1 -0
  39. package/server/src/config/database.ts +31 -0
  40. package/server/src/index.d.ts +2 -0
  41. package/server/src/index.d.ts.map +1 -0
  42. package/server/src/index.js +33 -0
  43. package/server/src/index.js.map +1 -0
  44. package/server/src/index.ts +55 -0
  45. package/server/src/middleware/auth.d.ts +12 -0
  46. package/server/src/middleware/auth.d.ts.map +1 -0
  47. package/server/src/middleware/auth.js +35 -0
  48. package/server/src/middleware/auth.js.map +1 -0
  49. package/server/src/middleware/auth.ts +56 -0
  50. package/server/src/routes/auth.d.ts +3 -0
  51. package/server/src/routes/auth.d.ts.map +1 -0
  52. package/server/src/routes/auth.js +145 -0
  53. package/server/src/routes/auth.js.map +1 -0
  54. package/server/src/routes/auth.ts +185 -0
  55. package/server/src/routes/dashboard.ts +243 -0
  56. package/server/src/routes/generate.d.ts +3 -0
  57. package/server/src/routes/generate.d.ts.map +1 -0
  58. package/server/src/routes/generate.js +55 -0
  59. package/server/src/routes/generate.js.map +1 -0
  60. package/server/src/routes/generate.ts +75 -0
  61. package/server/src/routes/payment.ts +192 -0
  62. package/server/src/services/ai.d.ts +10 -0
  63. package/server/src/services/ai.d.ts.map +1 -0
  64. package/server/src/services/ai.js +75 -0
  65. package/server/src/services/ai.js.map +1 -0
  66. package/server/src/services/ai.ts +99 -0
  67. package/server/src/services/payment.ts +141 -0
  68. package/server/src/types/flutterwave.d.ts +60 -0
  69. package/server/supabase_payments.sql +35 -0
  70. package/server/supabase_schema.sql +90 -0
  71. package/server/tsconfig.json +17 -0
  72. package/server/vercel.json +15 -0
  73. package/server/verify_dashboard.ts +126 -0
  74. package/src/ai/engine.ts +103 -0
  75. package/src/ai/prompts.ts +123 -0
  76. package/src/cli/index.ts +552 -0
  77. package/src/formatter/index.ts +110 -0
  78. package/src/index.ts +11 -0
  79. package/src/parser/extractor.ts +211 -0
  80. package/src/scanner/index.ts +49 -0
  81. package/tsconfig.json +43 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * AI Prompts Module
3
+ * Contains system prompts for the three persona-based views.
4
+ */
5
+
6
+ export const STRATEGIC_VIEW_PROMPT = `You are a technical writer creating documentation for Project Managers.
7
+
8
+ Your task is to analyze code skeletons and produce a "Strategic View" summary.
9
+
10
+ RULES:
11
+ - Focus on business value and project status
12
+ - NO code snippets allowed
13
+ - Use bullet points and bold text for key deliverables
14
+ - Include a "What this feature enables" section
15
+ - Provide a "Completeness Score" (0-100%) based on:
16
+ - Function implementation status
17
+ - TODO comments remaining
18
+ - Interface completeness
19
+
20
+ OUTPUT FORMAT:
21
+ ## Feature: [Feature Name]
22
+
23
+ **What This Enables:**
24
+ - [Business capability 1]
25
+ - [Business capability 2]
26
+
27
+ **Status:**
28
+ - **Completeness Score:** XX%
29
+ - [Key status points]
30
+
31
+ **Key Deliverables:**
32
+ - [Deliverable 1]
33
+ - [Deliverable 2]
34
+ `;
35
+
36
+ export const TECHNICAL_VIEW_PROMPT = `You are a technical writer creating documentation for Developers.
37
+
38
+ Your task is to analyze code skeletons and produce a "Technical View" reference.
39
+
40
+ RULES:
41
+ - Focus on architecture and integration details
42
+ - Include detailed function signatures
43
+ - Provide dependency maps where applicable
44
+ - Include "How to use" code examples
45
+ - Explain internal logic when relevant
46
+ - Use strict Markdown with syntax highlighting
47
+
48
+ OUTPUT FORMAT:
49
+ ## Module: [Module Name]
50
+
51
+ ### Overview
52
+ [Brief description of the module's purpose]
53
+
54
+ ### Functions
55
+
56
+ #### \`functionName(params): returnType\`
57
+ **Description:** [What this function does]
58
+
59
+ **Parameters:**
60
+ - \`param1\` (Type): Description
61
+ - \`param2\` (Type): Description
62
+
63
+ **Returns:** Type - Description
64
+
65
+ **Example:**
66
+ \`\`\`typescript
67
+ // Usage example
68
+ \`\`\`
69
+
70
+ ### Dependencies
71
+ - [Dependency 1]: [Purpose]
72
+ - [Dependency 2]: [Purpose]
73
+
74
+ ### Internal Logic
75
+ [Explanation of key algorithms or patterns]
76
+ `;
77
+
78
+ export const PERSONAL_VIEW_PROMPT = `You are a technical writer creating documentation for Solo Developers.
79
+
80
+ Your task is to analyze code skeletons and produce a "Personal View" roadmap.
81
+
82
+ RULES:
83
+ - Focus on productivity and actionable items
84
+ - Generate a To-Do list from TODO comments in code
85
+ - Create a "Technical Debt" summary
86
+ - Use concise, checklist-style formatting
87
+ - Prioritize items by importance
88
+
89
+ OUTPUT FORMAT:
90
+ ## Personal Roadmap: [File/Module Name]
91
+
92
+ ### Action Items
93
+ - [ ] [High Priority] Task from TODO comment
94
+ - [ ] [Medium Priority] Improvement suggestion
95
+ - [ ] [Low Priority] Nice-to-have enhancement
96
+
97
+ ### Technical Debt
98
+ | Issue | Location | Effort | Impact |
99
+ |-------|----------|--------|--------|
100
+ | [Description] | Line X | Low/Med/High | Low/Med/High |
101
+
102
+ ### Quick Notes
103
+ - [Notable patterns or concerns]
104
+ - [Suggestions for refactoring]
105
+ `;
106
+
107
+ export function getPromptForView(
108
+ view: "strategic" | "technical" | "personal",
109
+ ): string {
110
+ switch (view) {
111
+ case "strategic":
112
+ return STRATEGIC_VIEW_PROMPT;
113
+ case "technical":
114
+ return TECHNICAL_VIEW_PROMPT;
115
+ case "personal":
116
+ return PERSONAL_VIEW_PROMPT;
117
+ default:
118
+ throw new Error(`Unknown view type: ${view}`);
119
+ }
120
+ }
121
+
122
+ export const VIEWS = ["strategic", "technical", "personal"] as const;
123
+ export type ViewType = (typeof VIEWS)[number];
@@ -0,0 +1,552 @@
1
+ import { Command } from "commander";
2
+ import * as dotenv from "dotenv";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import * as os from "os";
6
+ import * as crypto from "crypto";
7
+ import * as readline from "readline";
8
+ import { getModifiedTypeScriptFiles } from "../scanner/index.js";
9
+ import { analyzeFile, formatSkeletonForLLM } from "../parser/extractor.js";
10
+ import { formatDocumentation, writeDocumentation } from "../formatter/index.js";
11
+
12
+ dotenv.config();
13
+
14
+ /**
15
+ * CLI Module
16
+ * Implements the `docit` commands using Commander.
17
+ */
18
+
19
+ interface LockFileEntry {
20
+ hash: string;
21
+ lastUpdated: string;
22
+ }
23
+
24
+ interface LockFile {
25
+ [filePath: string]: LockFileEntry;
26
+ }
27
+
28
+ interface Credentials {
29
+ token: string;
30
+ email: string;
31
+ apiUrl: string;
32
+ }
33
+
34
+ const LOCK_FILE_PATH = "./docit.lock";
35
+ const CREDENTIALS_DIR = path.join(os.homedir(), ".docit");
36
+ const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
37
+ const DEFAULT_API_URL = "http://localhost:3001";
38
+
39
+ function loadLockFile(): LockFile {
40
+ try {
41
+ if (fs.existsSync(LOCK_FILE_PATH)) {
42
+ const content = fs.readFileSync(LOCK_FILE_PATH, "utf-8");
43
+ return JSON.parse(content) as LockFile;
44
+ }
45
+ } catch {
46
+ // Ignore parse errors, return empty lock file
47
+ }
48
+ return {};
49
+ }
50
+
51
+ function saveLockFile(lockFile: LockFile): void {
52
+ fs.writeFileSync(LOCK_FILE_PATH, JSON.stringify(lockFile, null, 2), "utf-8");
53
+ }
54
+
55
+ function computeFileHash(filePath: string): string {
56
+ const content = fs.readFileSync(filePath, "utf-8");
57
+ return crypto.createHash("md5").update(content).digest("hex");
58
+ }
59
+
60
+ function shouldProcessFile(filePath: string, lockFile: LockFile): boolean {
61
+ const currentHash = computeFileHash(filePath);
62
+ const entry = lockFile[filePath];
63
+
64
+ if (!entry) return true;
65
+ return entry.hash !== currentHash;
66
+ }
67
+
68
+ function loadCredentials(): Credentials | null {
69
+ try {
70
+ if (fs.existsSync(CREDENTIALS_FILE)) {
71
+ const content = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
72
+ return JSON.parse(content) as Credentials;
73
+ }
74
+ } catch {
75
+ // Ignore errors
76
+ }
77
+ return null;
78
+ }
79
+
80
+ function saveCredentials(credentials: Credentials): void {
81
+ if (!fs.existsSync(CREDENTIALS_DIR)) {
82
+ fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
83
+ }
84
+ fs.writeFileSync(
85
+ CREDENTIALS_FILE,
86
+ JSON.stringify(credentials, null, 2),
87
+ "utf-8",
88
+ );
89
+ }
90
+
91
+ function deleteCredentials(): void {
92
+ if (fs.existsSync(CREDENTIALS_FILE)) {
93
+ fs.unlinkSync(CREDENTIALS_FILE);
94
+ }
95
+ }
96
+
97
+ async function prompt(question: string, hidden = false): Promise<string> {
98
+ const rl = readline.createInterface({
99
+ input: process.stdin,
100
+ output: process.stdout,
101
+ });
102
+
103
+ return new Promise((resolve) => {
104
+ if (hidden) {
105
+ process.stdout.write(question);
106
+ let input = "";
107
+ process.stdin.setRawMode(true);
108
+ process.stdin.resume();
109
+ process.stdin.on("data", (char) => {
110
+ const charStr = char.toString();
111
+ if (charStr === "\n" || charStr === "\r" || charStr === "\u0004") {
112
+ process.stdin.setRawMode(false);
113
+ process.stdin.pause();
114
+ console.log();
115
+ rl.close();
116
+ resolve(input);
117
+ } else if (charStr === "\u007F" || charStr === "\b") {
118
+ input = input.slice(0, -1);
119
+ process.stdout.clearLine(0);
120
+ process.stdout.cursorTo(0);
121
+ process.stdout.write(question + "*".repeat(input.length));
122
+ } else {
123
+ input += charStr;
124
+ process.stdout.write("*");
125
+ }
126
+ });
127
+ } else {
128
+ rl.question(question, (answer) => {
129
+ rl.close();
130
+ resolve(answer);
131
+ });
132
+ }
133
+ });
134
+ }
135
+
136
+ async function apiRequest(
137
+ endpoint: string,
138
+ options: {
139
+ method?: string;
140
+ body?: object;
141
+ token?: string;
142
+ apiUrl?: string;
143
+ } = {},
144
+ ): Promise<{ ok: boolean; status: number; data: Record<string, unknown> }> {
145
+ const { method = "GET", body, token, apiUrl = DEFAULT_API_URL } = options;
146
+
147
+ const headers: Record<string, string> = {
148
+ "Content-Type": "application/json",
149
+ };
150
+
151
+ if (token) {
152
+ headers["Authorization"] = `Bearer ${token}`;
153
+ }
154
+
155
+ const fetchOptions: RequestInit = {
156
+ method,
157
+ headers,
158
+ };
159
+
160
+ if (body) {
161
+ fetchOptions.body = JSON.stringify(body);
162
+ }
163
+
164
+ const response = await fetch(`${apiUrl}${endpoint}`, fetchOptions);
165
+
166
+ const data = (await response.json()) as Record<string, unknown>;
167
+ return { ok: response.ok, status: response.status, data };
168
+ }
169
+
170
+ export function createCli(): Command {
171
+ const program = new Command();
172
+
173
+ program
174
+ .name("docit")
175
+ .description("AI-powered documentation generator")
176
+ .version("1.0.0");
177
+
178
+ // Register command
179
+ program
180
+ .command("register")
181
+ .description("Create a new Docit account")
182
+ .option("--api-url <url>", "API server URL", DEFAULT_API_URL)
183
+ .action(async (options: { apiUrl: string }) => {
184
+ console.log("šŸ“ Create your Docit account\n");
185
+
186
+ const email = await prompt("Email: ");
187
+ const password = await prompt("Password: ", true);
188
+ const name = await prompt("Name (optional): ");
189
+ const country = await prompt("Country (e.g., Nigeria, USA): ");
190
+
191
+ console.log("\nšŸ”„ Creating account...");
192
+
193
+ const result = await apiRequest("/api/auth/register", {
194
+ method: "POST",
195
+ body: {
196
+ email,
197
+ password,
198
+ name: name || undefined,
199
+ country: country || undefined,
200
+ },
201
+ apiUrl: options.apiUrl,
202
+ });
203
+
204
+ if (!result.ok) {
205
+ console.error(
206
+ `āŒ Error: ${result.data["error"] ?? "Registration failed"}`,
207
+ );
208
+ process.exit(1);
209
+ }
210
+
211
+ const { token, user } = result.data as {
212
+ token: string;
213
+ user: { email: string; credits: number };
214
+ };
215
+
216
+ saveCredentials({ token, email: user.email, apiUrl: options.apiUrl });
217
+
218
+ console.log(`\nāœ… Account created successfully!`);
219
+ console.log(` Email: ${user.email}`);
220
+ console.log(` Credits: ${user.credits}`);
221
+ console.log(`\nYou can now run 'docit sync' to generate documentation.`);
222
+ });
223
+
224
+ // Login command
225
+ program
226
+ .command("login")
227
+ .description("Login to your Docit account")
228
+ .option("--api-url <url>", "API server URL", DEFAULT_API_URL)
229
+ .action(async (options: { apiUrl: string }) => {
230
+ console.log("šŸ” Login to Docit\n");
231
+
232
+ const email = await prompt("Email: ");
233
+ const password = await prompt("Password: ", true);
234
+
235
+ console.log("\nšŸ”„ Logging in...");
236
+
237
+ const result = await apiRequest("/api/auth/login", {
238
+ method: "POST",
239
+ body: { email, password },
240
+ apiUrl: options.apiUrl,
241
+ });
242
+
243
+ if (!result.ok) {
244
+ console.error(`āŒ Error: ${result.data["error"] ?? "Login failed"}`);
245
+ process.exit(1);
246
+ }
247
+
248
+ const { token, user } = result.data as {
249
+ token: string;
250
+ user: { email: string; credits: number };
251
+ };
252
+
253
+ saveCredentials({ token, email: user.email, apiUrl: options.apiUrl });
254
+
255
+ console.log(`\nāœ… Logged in successfully!`);
256
+ console.log(` Email: ${user.email}`);
257
+ console.log(` Credits: ${user.credits}`);
258
+ });
259
+
260
+ // Logout command
261
+ program
262
+ .command("logout")
263
+ .description("Logout from your Docit account")
264
+ .action(() => {
265
+ deleteCredentials();
266
+ console.log("āœ… Logged out successfully.");
267
+ });
268
+
269
+ // Status command
270
+ program
271
+ .command("status")
272
+ .description("Check your account status and credits")
273
+ .action(async () => {
274
+ const credentials = loadCredentials();
275
+
276
+ if (!credentials) {
277
+ console.error("āŒ Not logged in. Run 'docit login' first.");
278
+ process.exit(1);
279
+ }
280
+
281
+ const result = await apiRequest("/api/auth/me", {
282
+ token: credentials.token,
283
+ apiUrl: credentials.apiUrl,
284
+ });
285
+
286
+ if (!result.ok) {
287
+ console.error("āŒ Session expired. Please login again.");
288
+ deleteCredentials();
289
+ process.exit(1);
290
+ }
291
+
292
+ const { user } = result.data as {
293
+ user: { email: string; credits: number };
294
+ };
295
+
296
+ console.log(`šŸ“Š Account Status`);
297
+ console.log(` Email: ${user.email}`);
298
+ console.log(` Credits: ${user.credits}`);
299
+ });
300
+
301
+ // Sync command
302
+ program
303
+ .command("sync")
304
+ .description("Sync documentation with code changes")
305
+ .option("-f, --force", "Force regeneration of all documentation")
306
+ .option(
307
+ "-o, --output <path>",
308
+ "Output path for documentation",
309
+ "./DOCIT.md",
310
+ )
311
+ .option("--dry-run", "Show what would be processed without making changes")
312
+ .action(
313
+ async (options: {
314
+ force?: boolean;
315
+ output: string;
316
+ dryRun?: boolean;
317
+ }) => {
318
+ const credentials = loadCredentials();
319
+
320
+ if (!credentials) {
321
+ console.error(
322
+ "āŒ Not logged in. Run 'docit login' or 'docit register' first.",
323
+ );
324
+ process.exit(1);
325
+ }
326
+
327
+ console.log("šŸ” Scanning for modified files...");
328
+
329
+ const modifiedFiles = await getModifiedTypeScriptFiles(".");
330
+
331
+ if (modifiedFiles.length === 0) {
332
+ console.log("āœ… No modified TypeScript files found.");
333
+ return;
334
+ }
335
+
336
+ console.log(`šŸ“ Found ${modifiedFiles.length} modified file(s):`);
337
+ modifiedFiles.forEach((f) =>
338
+ console.log(` - ${path.relative(".", f)}`),
339
+ );
340
+
341
+ const lockFile = options.force ? {} : loadLockFile();
342
+ const filesToProcess = modifiedFiles.filter(
343
+ (f) => options.force ?? shouldProcessFile(f, lockFile),
344
+ );
345
+
346
+ if (filesToProcess.length === 0) {
347
+ console.log(
348
+ "āœ… All files are up to date (use --force to regenerate).",
349
+ );
350
+ return;
351
+ }
352
+
353
+ if (options.dryRun) {
354
+ console.log("\nšŸ”® Dry run - would process:");
355
+ filesToProcess.forEach((f) =>
356
+ console.log(` - ${path.relative(".", f)}`),
357
+ );
358
+ console.log(`\n Credits needed: ${filesToProcess.length}`);
359
+ return;
360
+ }
361
+
362
+ console.log(`\nšŸ“ Processing ${filesToProcess.length} file(s)...`);
363
+
364
+ // Analyze all files
365
+ const analyses = filesToProcess.map((f) => {
366
+ console.log(` Parsing: ${path.relative(".", f)}`);
367
+ return analyzeFile(f);
368
+ });
369
+
370
+ // Combine all skeletons
371
+ const combinedSkeleton = analyses
372
+ .map((a) => formatSkeletonForLLM(a))
373
+ .join("\n\n---\n\n");
374
+
375
+ console.log("\nšŸ¤– Generating documentation with AI...");
376
+
377
+ // Call backend API
378
+ const result = await apiRequest("/api/generate", {
379
+ method: "POST",
380
+ body: {
381
+ codeSkeleton: combinedSkeleton,
382
+ fileCount: filesToProcess.length,
383
+ },
384
+ token: credentials.token,
385
+ apiUrl: credentials.apiUrl,
386
+ });
387
+
388
+ if (!result.ok) {
389
+ if (result.status === 402) {
390
+ const { required, available } = result.data as {
391
+ required: number;
392
+ available: number;
393
+ };
394
+ console.error(`\nāŒ Insufficient credits!`);
395
+ console.error(` Required: ${required}`);
396
+ console.error(` Available: ${available}`);
397
+ console.error(`\n Purchase more credits to continue.`);
398
+ process.exit(1);
399
+ }
400
+ console.error(
401
+ `\nāŒ Error: ${result.data["error"] ?? "Generation failed"}`,
402
+ );
403
+ process.exit(1);
404
+ }
405
+
406
+ const { results, creditsUsed, creditsRemaining } = result.data as {
407
+ results: Array<{ view: string; content: string }>;
408
+ creditsUsed: number;
409
+ creditsRemaining: number;
410
+ };
411
+
412
+ console.log("šŸ“„ Formatting documentation...");
413
+
414
+ const projectName = path.basename(process.cwd());
415
+ const documentation = formatDocumentation(
416
+ results.map((r) => ({
417
+ view: r.view as "strategic" | "technical" | "personal",
418
+ content: r.content,
419
+ })),
420
+ { projectName },
421
+ );
422
+
423
+ writeDocumentation(documentation, options.output);
424
+ console.log(`āœ… Documentation written to ${options.output}`);
425
+ console.log(
426
+ `šŸ’° Credits used: ${creditsUsed} | Remaining: ${creditsRemaining}`,
427
+ );
428
+
429
+ // Update lock file
430
+ const newLockFile: LockFile = { ...lockFile };
431
+ for (const filePath of filesToProcess) {
432
+ newLockFile[filePath] = {
433
+ hash: computeFileHash(filePath),
434
+ lastUpdated: new Date().toISOString(),
435
+ };
436
+ }
437
+ saveLockFile(newLockFile);
438
+ console.log("šŸ”’ Lock file updated.");
439
+ },
440
+ );
441
+
442
+ // Init command (for reference)
443
+ program
444
+ .command("init")
445
+ .description("Initialize Docit in the current project")
446
+ .action(() => {
447
+ const gitignorePath = "./.gitignore";
448
+
449
+ if (fs.existsSync(gitignorePath)) {
450
+ const content = fs.readFileSync(gitignorePath, "utf-8");
451
+ if (!content.includes("docit.lock")) {
452
+ fs.appendFileSync(gitignorePath, "\ndocit.lock\n");
453
+ console.log("āœ… Added docit.lock to .gitignore");
454
+ }
455
+ } else {
456
+ fs.writeFileSync(gitignorePath, "docit.lock\n", "utf-8");
457
+ console.log("āœ… Created .gitignore");
458
+ }
459
+
460
+ console.log("\nšŸŽ‰ Docit initialized! Next steps:");
461
+ console.log(" 1. Run 'docit register' to create an account");
462
+ console.log(" 2. Run 'docit sync' to generate documentation");
463
+ });
464
+
465
+ // Subscribe command
466
+ program
467
+ .command("subscribe")
468
+ .description("Subscribe to get more credits")
469
+ .action(async () => {
470
+ const credentials = loadCredentials();
471
+
472
+ if (!credentials) {
473
+ console.error("āŒ Not logged in. Run 'docit login' first.");
474
+ process.exit(1);
475
+ }
476
+
477
+ console.log("šŸ’³ Docit Subscription Plans\n");
478
+
479
+ // Get plans from API
480
+ const plansResult = await apiRequest("/api/payment/plans", {
481
+ apiUrl: credentials.apiUrl,
482
+ });
483
+
484
+ if (!plansResult.ok) {
485
+ console.error("āŒ Failed to fetch plans");
486
+ process.exit(1);
487
+ }
488
+
489
+ const { plans } = plansResult.data as {
490
+ plans: Array<{
491
+ id: string;
492
+ name: string;
493
+ amount: number;
494
+ currency: string;
495
+ credits: number;
496
+ description: string;
497
+ }>;
498
+ };
499
+
500
+ console.log("Available plans:");
501
+ plans.forEach((plan, index) => {
502
+ const symbol = plan.currency === "NGN" ? "₦" : "$";
503
+ console.log(
504
+ ` ${index + 1}. ${plan.name} - ${symbol}${plan.amount.toLocaleString()}/${plan.currency} (${plan.credits} credits)`,
505
+ );
506
+ });
507
+
508
+ const choice = await prompt("\nSelect a plan (1 or 2): ");
509
+ const selectedIndex = parseInt(choice, 10) - 1;
510
+
511
+ if (
512
+ isNaN(selectedIndex) ||
513
+ selectedIndex < 0 ||
514
+ selectedIndex >= plans.length
515
+ ) {
516
+ console.error("āŒ Invalid selection");
517
+ process.exit(1);
518
+ }
519
+
520
+ const selectedPlan = plans[selectedIndex];
521
+ if (!selectedPlan) {
522
+ console.error("āŒ Plan not found");
523
+ process.exit(1);
524
+ }
525
+
526
+ console.log(`\nšŸ”„ Initiating payment for ${selectedPlan.name}...`);
527
+
528
+ const paymentResult = await apiRequest("/api/payment/initiate", {
529
+ method: "POST",
530
+ body: { planId: selectedPlan.id },
531
+ token: credentials.token,
532
+ apiUrl: credentials.apiUrl,
533
+ });
534
+
535
+ if (!paymentResult.ok) {
536
+ console.error(
537
+ `āŒ Error: ${paymentResult.data["error"] ?? "Payment failed"}`,
538
+ );
539
+ process.exit(1);
540
+ }
541
+
542
+ const { paymentLink } = paymentResult.data as { paymentLink: string };
543
+
544
+ console.log("\nāœ… Payment link generated!");
545
+ console.log(
546
+ `\nšŸ”— Open this link to complete payment:\n ${paymentLink}`,
547
+ );
548
+ console.log("\nAfter payment, your credits will be automatically added.");
549
+ });
550
+
551
+ return program;
552
+ }