quackstack 1.0.10 → 1.0.12

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/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  QuackStack is an interactive CLI tool that indexes your codebase using local AI embeddings and lets you ask questions about it conversationally. Perfect for understanding unfamiliar code, onboarding to new projects, or giving your AI coding assistant persistent context.
6
6
 
7
+ ## 🎯 Quack in Action!
8
+ Check out the QuackStack Live demo [here](https://courageous-spaniel.clueso.site/share/4f5e6395-8ad8-4d18-8e81-f736a6581a25)!
7
9
  ## ✨ Features
8
10
 
9
11
  * 🚀 **Zero-config** - Just run `quack` in any project directory
@@ -272,4 +274,4 @@ MIT
272
274
 
273
275
  **Large Codebases**: First index might take a few minutes. After that, only changed files are re-indexed.
274
276
 
275
- **No Vendor Lock-in**: Unlike other tools, QuackStack works with Cursor, Windsurf, Cline, Continue, and Aider - choose your favorite!
277
+ **No Vendor Lock-in**: Unlike other tools, QuackStack works with Cursor, Windsurf, Cline, Continue, and Aider - choose your favorite!
package/dist/cli.cjs CHANGED
@@ -8,6 +8,7 @@ const commander_1 = require("commander");
8
8
  const chalk_animation_1 = __importDefault(require("chalk-animation"));
9
9
  const repl_js_1 = require("./repl.js");
10
10
  const context_generator_js_1 = require("./lib/context-generator.js");
11
+ const readme_js_1 = require("./commands/readme.js");
11
12
  const path_1 = __importDefault(require("path"));
12
13
  const program = new commander_1.Command();
13
14
  const PROJECT_NAME = path_1.default.basename(process.cwd());
@@ -18,12 +19,17 @@ program
18
19
  .option("-r, --reindex", "Force reindex the codebase")
19
20
  .option("-c, --context", "Generate context files for ALL AI coding tools (Cursor, Windsurf, Cline, Continue, Aider)")
20
21
  .option("-d, --docs", "Generate CODEBASE.md - universal documentation for any IDE/editor")
22
+ .option("--readme", "Generate README.md from your codebase")
21
23
  .option("--cursor", "[DEPRECATED] Use --context instead. Generates .cursorrules only")
22
24
  .option("-w, --watch", "Watch mode: auto-update context files on file changes")
23
25
  .action(async (options) => {
24
26
  const title = chalk_animation_1.default.rainbow("Welcome to QuackStack! 🐥\n");
25
27
  await new Promise(res => setTimeout(res, 1500));
26
28
  title.stop();
29
+ if (options.readme) {
30
+ await (0, readme_js_1.generateReadme)(PROJECT_NAME);
31
+ process.exit(0);
32
+ }
27
33
  if (options.context) {
28
34
  await (0, context_generator_js_1.generateContextFiles)(PROJECT_NAME);
29
35
  await (0, context_generator_js_1.updateGlobalContext)(PROJECT_NAME);
@@ -0,0 +1,101 @@
1
+ import { client } from "../lib/database.js";
2
+ import { getAIClient } from "../lib/ai-provider.js";
3
+ import fs from "fs/promises";
4
+ import path from "path";
5
+ import readline from "readline";
6
+ async function askQuestion(query) {
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+ return new Promise(resolve => {
12
+ rl.question(query, answer => {
13
+ rl.close();
14
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
15
+ });
16
+ });
17
+ }
18
+ export async function generateReadme(projectName, outputPath) {
19
+ const readmePath = outputPath || path.join(process.cwd(), "README.md");
20
+ const exists = await fs.access(readmePath).then(() => true).catch(() => false);
21
+ if (exists) {
22
+ const shouldOverwrite = await askQuestion("⚠️ README.md already exists. Overwrite? (y/n): ");
23
+ if (!shouldOverwrite) {
24
+ console.log("Cancelled.");
25
+ return;
26
+ }
27
+ }
28
+ const snippets = await client.codeSnippet.findMany({
29
+ where: { projectName },
30
+ });
31
+ const fileStructure = [...new Set(snippets.map(s => s.filePath))].sort();
32
+ const technologies = detectTechnologies(snippets);
33
+ const entryPoints = findEntryPoints(snippets);
34
+ const context = `
35
+ Project Structure:
36
+ ${fileStructure.map(f => `- ${f}`).join('\n')}
37
+
38
+ Technologies Detected:
39
+ ${technologies.join(', ')}
40
+
41
+ Entry Points:
42
+ ${entryPoints.map(e => `- ${e.filePath} (${e.functionName})`).join('\n')}
43
+
44
+ Sample Code Snippets:
45
+ ${snippets.slice(0, 10).map(s => `
46
+ File: ${s.filePath}
47
+ ${s.content.slice(0, 200)}...
48
+ `).join('\n---\n')}
49
+ `;
50
+ const aiClient = getAIClient();
51
+ const prompt = `
52
+ You are a technical documentation expert. Based on the following codebase context, generate a comprehensive README.md file.
53
+
54
+ Include:
55
+ - Project title and description
56
+ - Technologies used
57
+ - Installation instructions
58
+ - Usage examples
59
+ - Project structure overview
60
+ - Key features
61
+ - Contributing guidelines (basic)
62
+
63
+ Make it professional, clear, and actionable.
64
+
65
+ Context:
66
+ ${context}
67
+ `;
68
+ console.log("🦆 Generating README.md...");
69
+ const readme = await aiClient.generateAnswer(prompt, context);
70
+ await fs.writeFile(readmePath, readme, "utf-8");
71
+ console.log(`✅ README.md generated at ${readmePath}`);
72
+ }
73
+ function detectTechnologies(snippets) {
74
+ const techs = new Set();
75
+ snippets.forEach(s => {
76
+ const filePath = s.filePath.toLowerCase();
77
+ if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx'))
78
+ techs.add('React');
79
+ if (filePath.endsWith('.ts'))
80
+ techs.add('TypeScript');
81
+ if (filePath.endsWith('.py'))
82
+ techs.add('Python');
83
+ if (filePath.endsWith('.rs'))
84
+ techs.add('Rust');
85
+ if (filePath.includes('prisma'))
86
+ techs.add('Prisma');
87
+ if (s.content.includes('express'))
88
+ techs.add('Express');
89
+ if (s.content.includes('fastapi'))
90
+ techs.add('FastAPI');
91
+ if (s.content.includes('django'))
92
+ techs.add('Django');
93
+ });
94
+ return Array.from(techs);
95
+ }
96
+ function findEntryPoints(snippets) {
97
+ return snippets.filter(s => s.filePath.includes('main') ||
98
+ s.filePath.includes('index') ||
99
+ s.filePath.includes('app') ||
100
+ s.functionName?.toLowerCase().includes('main')).slice(0, 5);
101
+ }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { client } from "../lib/database.js";
3
3
  import { localEmbeddings } from "../lib/local-embeddings.js";
4
- import { aiClient } from "../lib/ai-provider.js";
4
+ import { getAIClient } from "../lib/ai-provider.js";
5
5
  export async function search(query, projectName) {
6
6
  const snippets = await client.codeSnippet.findMany({
7
7
  where: { projectName },
@@ -28,6 +28,7 @@ export async function search(query, projectName) {
28
28
  const context = uniqueResults
29
29
  .map((r, i) => `[${i + 1}] ${r.filePath}${r.functionName ? ` (${r.functionName})` : ""}\n${r.content}`)
30
30
  .join("\n\n---\n\n");
31
+ const aiClient = getAIClient();
31
32
  const answer = await aiClient.generateAnswer(query, context);
32
33
  return { answer, sources: uniqueResults };
33
34
  }
@@ -180,4 +180,10 @@ export class AIClient {
180
180
  return this.provider;
181
181
  }
182
182
  }
183
- export const aiClient = new AIClient();
183
+ let aiClientInstance = null;
184
+ export function getAIClient() {
185
+ if (!aiClientInstance) {
186
+ aiClientInstance = new AIClient();
187
+ }
188
+ return aiClientInstance;
189
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quackstack",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Your cracked unpaid intern for all things codebase related! AI-powered codebase search and Q&A.",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",