scai 0.1.15 → 0.1.16

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
@@ -30,8 +30,6 @@ We believe your code — and your workflow — should stay **yours**. scai runs
30
30
  ✅ Backed by open-source models
31
31
  ✅ Designed for CLI-first developers
32
32
 
33
- ---
34
-
35
33
  ## 📦 Installation
36
34
 
37
35
  1. **Install [Ollama](https://ollama.com)**
@@ -59,8 +57,6 @@ This will:
59
57
  - Start the Ollama background server
60
58
  - Download required models (`llama3`, etc.)
61
59
 
62
- ---
63
-
64
60
  ## 🧪 Usage Examples
65
61
 
66
62
  ### 💬 Suggest a commit message
@@ -108,7 +104,6 @@ To pipe input for summarization:
108
104
  cat <file> | scai summ
109
105
  ```
110
106
 
111
-
112
107
  ---
113
108
 
114
109
  ### 📝 Update the changelog
@@ -121,6 +116,14 @@ Analyzes the current Git diff and updates (or creates) a `CHANGELOG.md` file wit
121
116
 
122
117
  ---
123
118
 
119
+ ### 📦 Module Pipeline (Advanced Usage)
120
+
121
+ For more advanced functionality, scai allows you to run custom pipelines with multiple modules to process code. You can specify multiple modules to be run, such as comment, summary, cleanup, and more.
122
+
123
+ ```bash
124
+ scai <file> --modules comment,summary,cleanup
125
+ ```
126
+
124
127
  ## 🔐 License & Fair Use
125
128
 
126
129
  **scai is free to use** for individuals, teams, and companies — including in commercial work.
@@ -137,5 +140,3 @@ But you may not:
137
140
  - ❌ Claim ownership of the tool
138
141
 
139
142
  See [LICENSE](./LICENSE) for full terms.
140
-
141
- ---
@@ -2,7 +2,7 @@
2
2
  import { execSync } from 'child_process';
3
3
  import fs from 'fs/promises';
4
4
  import path from 'path';
5
- import { runPromptPipeline } from '../pipeline/runPipeline.js';
5
+ import { runModulePipeline } from '../pipeline/runModulePipeline.js';
6
6
  import { changelogModule } from '../pipeline/modules/changeLogModule.js';
7
7
  export async function handleChangelogUpdate() {
8
8
  try {
@@ -14,7 +14,7 @@ export async function handleChangelogUpdate() {
14
14
  console.log("⚠️ No staged or unstaged changes to include in changelog.");
15
15
  return;
16
16
  }
17
- const result = await runPromptPipeline([changelogModule], { code: diff });
17
+ const result = await runModulePipeline([changelogModule], { code: diff });
18
18
  if (!result.code.trim()) {
19
19
  console.log("✅ No significant changes for changelog.");
20
20
  return;
@@ -0,0 +1,31 @@
1
+ import { readFileSync } from 'fs';
2
+ import { getModuleByName } from '../registry/moduleRegistry.js';
3
+ import { runModulePipeline } from '../pipeline/runModulePipeline.js';
4
+ export async function runModulePipelineFromCLI(file, options) {
5
+ if (!options.modules) {
6
+ console.error('❌ No modules specified. Use --modules or -m.');
7
+ process.exit(1);
8
+ }
9
+ const moduleNames = options.modules.split(',').map((m) => m.trim());
10
+ // Read file content
11
+ let fileContent = '';
12
+ try {
13
+ fileContent = readFileSync(file, 'utf-8');
14
+ }
15
+ catch (err) {
16
+ console.error(`❌ Could not read file: ${file}`);
17
+ process.exit(1);
18
+ }
19
+ const input = { code: fileContent, filepath: file };
20
+ // Retrieve modules from the registry
21
+ const resolvedModules = moduleNames.map((name) => {
22
+ const module = getModuleByName(name);
23
+ if (!module) {
24
+ console.error(`❌ Failed to load module: ${name}`);
25
+ process.exit(1);
26
+ }
27
+ return module;
28
+ });
29
+ // Run the module pipeline
30
+ await runModulePipeline(resolvedModules, input);
31
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import { runPromptPipeline } from '../pipeline/runPipeline.js';
3
+ import { runModulePipeline } from '../pipeline/runModulePipeline.js';
4
4
  import { addCommentsModule } from '../pipeline/modules/commentModule.js';
5
5
  import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
6
6
  export async function handleRefactor(filepath, options = {}) {
@@ -27,7 +27,7 @@ export async function handleRefactor(filepath, options = {}) {
27
27
  // Read source code
28
28
  const originalCode = await fs.readFile(filepath, 'utf-8');
29
29
  // Run through pipeline modules
30
- const refactored = await runPromptPipeline([addCommentsModule, cleanupModule], { code: originalCode });
30
+ const refactored = await runModulePipeline([addCommentsModule, cleanupModule], { code: originalCode });
31
31
  if (!refactored.code.trim())
32
32
  throw new Error('⚠️ Model returned empty result');
33
33
  // Save refactored output
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs/promises';
2
- import { summaryModule } from '../pipeline/modules/summaryModule.js';
3
2
  import readline from 'readline';
3
+ import { summaryModule } from '../pipeline/modules/summaryModule.js'; // Import summaryModule
4
+ import { summarizeCode } from '../utils/summarizer.js'; // Import summarizeCode
4
5
  export async function summarizeFile(filepath) {
5
6
  let code = '';
6
7
  if (filepath) {
@@ -12,7 +13,6 @@ export async function summarizeFile(filepath) {
12
13
  return;
13
14
  }
14
15
  }
15
- // ⬇️ Add this check right here
16
16
  else if (process.stdin.isTTY) {
17
17
  console.error('❌ No file provided and no piped input.\n👉 Usage: scai summ <file> or cat file | scai summ');
18
18
  return;
@@ -28,7 +28,11 @@ export async function summarizeFile(filepath) {
28
28
  }
29
29
  }
30
30
  if (code.trim()) {
31
- await summaryModule.run({ code });
31
+ // Call the summary module to get the raw summary
32
+ const summary = await summaryModule.run({ code });
33
+ // Pass the summary text to the utility function for formatting
34
+ const formattedSummary = summarizeCode(summary.code);
35
+ console.log(formattedSummary);
32
36
  }
33
37
  else {
34
38
  console.error('❌ No code provided to summarize.');
@@ -1,11 +1,11 @@
1
1
  import fs from 'fs/promises';
2
2
  import { generateTestsModule } from '../pipeline/modules/generateTestsModule.js';
3
3
  import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
4
- import { runPromptPipeline } from '../pipeline/runPipeline.js';
4
+ import { runModulePipeline } from '../pipeline/runModulePipeline.js';
5
5
  export async function generateTests(filepath) {
6
6
  try {
7
7
  const code = await fs.readFile(filepath, 'utf-8');
8
- const result = await runPromptPipeline([generateTestsModule, cleanupModule], { code, filepath });
8
+ const result = await runModulePipeline([generateTestsModule, cleanupModule], { code, filepath });
9
9
  console.log('✅ Test generated and cleaned up.');
10
10
  }
11
11
  catch (err) {
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
+ // Importing package.json for version
3
4
  import { createRequire } from 'module';
4
5
  const require = createRequire(import.meta.url);
5
6
  const { version } = require('../package.json');
7
+ // Importing commands
6
8
  import { checkEnv } from "./commands/EnvCmd.js";
7
9
  import { checkGit } from "./commands/GitCmd.js";
8
10
  import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
@@ -12,6 +14,7 @@ import { bootstrap } from './modelSetup.js';
12
14
  import { ModelConfig } from './config/ModelConfig.js';
13
15
  import { summarizeFile } from "./commands/SummaryCmd.js";
14
16
  import { handleChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
17
+ import { runModulePipelineFromCLI } from './commands/ModulePipelineCmd.js';
15
18
  // Create the CLI instance
16
19
  const cmd = new Command('scai')
17
20
  .version(version)
@@ -63,6 +66,16 @@ cmd
63
66
  .action(() => {
64
67
  ModelConfig.logCurrentConfig();
65
68
  });
69
+ cmd
70
+ .arguments('<file>')
71
+ .option('-m, --modules <modules>', 'Comma-separated list of modules to run (e.g., comments,cleanup,summary)')
72
+ .action((file, options) => {
73
+ if (!options.modules) {
74
+ console.error('❌ You must specify modules with -m or --modules');
75
+ process.exit(1);
76
+ }
77
+ runModulePipelineFromCLI(file, options);
78
+ });
66
79
  // ✅ Now that commands are defined, parse args
67
80
  cmd.parse(process.argv);
68
81
  // ✅ Apply global options after parsing
@@ -6,7 +6,7 @@ export const commitSuggesterModule = {
6
6
  async run({ code }) {
7
7
  const model = ModelConfig.getModel();
8
8
  const prompt = `
9
- Suggest 3 concise, conventional Git commit messages based on this diff.
9
+ Suggest ALWAYS 3 concise, conventional Git commit messages based on this diff.
10
10
  Use this format ONLY:
11
11
 
12
12
  1. feat: ...
@@ -1,4 +1,4 @@
1
- export async function runPromptPipeline(modules, input) {
1
+ export async function runModulePipeline(modules, input) {
2
2
  let current = input;
3
3
  // Add flag or condition for logging (optional)
4
4
  const isDebug = true;
@@ -0,0 +1,21 @@
1
+ import { addCommentsModule } from '../pipeline/modules/commentModule.js';
2
+ import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
3
+ import { summaryModule } from '../pipeline/modules/summaryModule.js';
4
+ import { generateTestsModule } from '../pipeline/modules/generateTestsModule.js';
5
+ import { commitSuggesterModule } from '../pipeline/modules/commitSuggesterModule.js';
6
+ import { changelogModule } from '../pipeline/modules/changeLogModule.js';
7
+ // Add more as needed...
8
+ const builtInModules = {
9
+ comments: addCommentsModule,
10
+ cleanup: cleanupModule,
11
+ summary: summaryModule,
12
+ tests: generateTestsModule,
13
+ suggest: commitSuggesterModule,
14
+ changelog: changelogModule,
15
+ };
16
+ export function getModuleByName(name) {
17
+ return builtInModules[name];
18
+ }
19
+ export function listAvailableModules() {
20
+ return Object.keys(builtInModules);
21
+ }
@@ -0,0 +1,10 @@
1
+ import { wrapText } from "./textWrapper.js";
2
+ // src/utils/summarizer.ts
3
+ export function summarizeCode(summaryText) {
4
+ // Get the terminal width (default to 80 if not available)
5
+ const terminalWidth = process.stdout.columns || 80;
6
+ // Use two-thirds of the terminal width for the wrapping
7
+ const twoThirdsTerminalWidth = Math.floor((terminalWidth * 2) / 3);
8
+ // Wrap the summary output to fit within two-thirds of the terminal width
9
+ return wrapText(summaryText, twoThirdsTerminalWidth);
10
+ }
@@ -0,0 +1,18 @@
1
+ export function wrapText(text, maxWidth) {
2
+ const words = text.split(' ');
3
+ let wrappedText = '';
4
+ let currentLine = '';
5
+ words.forEach(word => {
6
+ // Check if adding the word would exceed the max width
7
+ if ((currentLine + word).length > maxWidth) {
8
+ wrappedText += currentLine + '\n'; // Add the current line and start a new one
9
+ currentLine = word + ' '; // Start the new line with the current word
10
+ }
11
+ else {
12
+ currentLine += word + ' '; // Add the word to the current line
13
+ }
14
+ });
15
+ // Append the last line if any
16
+ wrappedText += currentLine;
17
+ return wrappedText;
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"