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.
- package/.env.example +1 -0
- package/INSTRUCTIONS.MD +70 -0
- package/dist/ai/engine.d.ts +24 -0
- package/dist/ai/engine.d.ts.map +1 -0
- package/dist/ai/engine.js +69 -0
- package/dist/ai/engine.js.map +1 -0
- package/dist/ai/prompts.d.ts +11 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +116 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +384 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/formatter/index.d.ts +13 -0
- package/dist/formatter/index.d.ts.map +1 -0
- package/dist/formatter/index.js +76 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/extractor.d.ts +23 -0
- package/dist/parser/extractor.d.ts.map +1 -0
- package/dist/parser/extractor.js +148 -0
- package/dist/parser/extractor.js.map +1 -0
- package/dist/scanner/index.d.ts +11 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +30 -0
- package/dist/scanner/index.js.map +1 -0
- package/package.json +31 -0
- package/server/.env.example +22 -0
- package/server/package-lock.json +4426 -0
- package/server/package.json +31 -0
- package/server/src/config/database.d.ts +18 -0
- package/server/src/config/database.d.ts.map +1 -0
- package/server/src/config/database.js +8 -0
- package/server/src/config/database.js.map +1 -0
- package/server/src/config/database.ts +31 -0
- package/server/src/index.d.ts +2 -0
- package/server/src/index.d.ts.map +1 -0
- package/server/src/index.js +33 -0
- package/server/src/index.js.map +1 -0
- package/server/src/index.ts +55 -0
- package/server/src/middleware/auth.d.ts +12 -0
- package/server/src/middleware/auth.d.ts.map +1 -0
- package/server/src/middleware/auth.js +35 -0
- package/server/src/middleware/auth.js.map +1 -0
- package/server/src/middleware/auth.ts +56 -0
- package/server/src/routes/auth.d.ts +3 -0
- package/server/src/routes/auth.d.ts.map +1 -0
- package/server/src/routes/auth.js +145 -0
- package/server/src/routes/auth.js.map +1 -0
- package/server/src/routes/auth.ts +185 -0
- package/server/src/routes/dashboard.ts +243 -0
- package/server/src/routes/generate.d.ts +3 -0
- package/server/src/routes/generate.d.ts.map +1 -0
- package/server/src/routes/generate.js +55 -0
- package/server/src/routes/generate.js.map +1 -0
- package/server/src/routes/generate.ts +75 -0
- package/server/src/routes/payment.ts +192 -0
- package/server/src/services/ai.d.ts +10 -0
- package/server/src/services/ai.d.ts.map +1 -0
- package/server/src/services/ai.js +75 -0
- package/server/src/services/ai.js.map +1 -0
- package/server/src/services/ai.ts +99 -0
- package/server/src/services/payment.ts +141 -0
- package/server/src/types/flutterwave.d.ts +60 -0
- package/server/supabase_payments.sql +35 -0
- package/server/supabase_schema.sql +90 -0
- package/server/tsconfig.json +17 -0
- package/server/vercel.json +15 -0
- package/server/verify_dashboard.ts +126 -0
- package/src/ai/engine.ts +103 -0
- package/src/ai/prompts.ts +123 -0
- package/src/cli/index.ts +552 -0
- package/src/formatter/index.ts +110 -0
- package/src/index.ts +11 -0
- package/src/parser/extractor.ts +211 -0
- package/src/scanner/index.ts +49 -0
- 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];
|
package/src/cli/index.ts
ADDED
|
@@ -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
|
+
}
|