codeep 1.2.1 → 1.2.3

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.
@@ -96,6 +96,11 @@ export class Input {
96
96
  event.key = 'mouse';
97
97
  return event;
98
98
  }
99
+ // Enter (also handle \r\n sent by some terminals)
100
+ if (data === '\r' || data === '\n' || data === '\r\n') {
101
+ event.key = 'enter';
102
+ return event;
103
+ }
99
104
  // Detect paste: multiple printable characters at once (not escape sequences)
100
105
  if (data.length > 1 && !data.startsWith('\x1b')) {
101
106
  // Check if it's all printable characters (paste event)
@@ -179,11 +184,6 @@ export class Input {
179
184
  event.ctrl = true;
180
185
  return event;
181
186
  }
182
- // Enter
183
- if (data === '\r' || data === '\n') {
184
- event.key = 'enter';
185
- return event;
186
- }
187
187
  // Backspace
188
188
  if (data === '\x7f' || data === '\b') {
189
189
  event.key = 'backspace';
@@ -501,50 +501,51 @@ async function runSkill(nameOrShortcut, args) {
501
501
  const params = parseSkillArgs(args.join(' '), skill);
502
502
  app.addMessage({ role: 'user', content: `/${skill.name}${args.length ? ' ' + args.join(' ') : ''}` });
503
503
  trackSkillUsage(skill.name);
504
- const { execSync } = await import('child_process');
504
+ const { spawnSync } = await import('child_process');
505
505
  try {
506
506
  const result = await executeSkill(skill, params, {
507
507
  onCommand: async (cmd) => {
508
- try {
509
- const output = execSync(cmd, {
510
- cwd: projectPath || process.cwd(),
511
- encoding: 'utf-8',
512
- timeout: 60000,
513
- stdio: ['pipe', 'pipe', 'pipe'],
514
- });
515
- const result = output.trim();
516
- if (result) {
517
- app.addMessage({ role: 'system', content: `\`${cmd}\`\n\`\`\`\n${result}\n\`\`\`` });
508
+ // Use spawnSync via shell for reliable stdout+stderr capture
509
+ const proc = spawnSync(cmd, {
510
+ cwd: projectPath || process.cwd(),
511
+ encoding: 'utf-8',
512
+ timeout: 60000,
513
+ shell: true,
514
+ stdio: ['pipe', 'pipe', 'pipe'],
515
+ });
516
+ const stdout = (proc.stdout || '').trim();
517
+ const stderr = (proc.stderr || '').trim();
518
+ const output = stdout || stderr || '';
519
+ if (proc.status === 0) {
520
+ if (output) {
521
+ app.addMessage({ role: 'system', content: `\`${cmd}\`\n\`\`\`\n${output}\n\`\`\`` });
518
522
  }
519
- return result;
523
+ return output;
520
524
  }
521
- catch (err) {
522
- const error = err;
523
- const stderr = (error.stderr || '').trim();
524
- const stdout = (error.stdout || '').trim();
525
- // Git commands output progress/info to stderr even on success
526
- if (error.status === 0 || stderr.includes('up-to-date') || stderr.includes('up to date') || stderr.includes('Already up to date')) {
527
- const result = stdout || stderr;
528
- if (result) {
529
- app.addMessage({ role: 'system', content: `\`${cmd}\`\n\`\`\`\n${result}\n\`\`\`` });
530
- }
531
- return result;
532
- }
533
- const errOutput = stderr || stdout;
534
- if (errOutput) {
535
- app.addMessage({ role: 'system', content: `\`${cmd}\` failed:\n\`\`\`\n${errOutput}\n\`\`\`` });
536
- }
537
- throw new Error(errOutput || error.message || 'Command failed');
525
+ // Non-zero exit
526
+ if (output) {
527
+ app.addMessage({ role: 'system', content: `\`${cmd}\` failed:\n\`\`\`\n${output}\n\`\`\`` });
538
528
  }
529
+ throw new Error(output || `Command exited with code ${proc.status}`);
539
530
  },
540
- onPrompt: (prompt) => {
541
- return new Promise((resolve, reject) => {
542
- handleSubmit(prompt).then(() => {
543
- // The AI response will be displayed in chat.
544
- // We resolve with an empty string since the response is already shown.
545
- resolve('');
546
- }).catch(reject);
547
- });
531
+ onPrompt: async (prompt) => {
532
+ try {
533
+ app.addMessage({ role: 'user', content: prompt });
534
+ app.startStreaming();
535
+ const history = app.getChatHistory();
536
+ const response = await chat(prompt, history, (chunk) => {
537
+ app.addStreamChunk(chunk);
538
+ }, undefined, projectContext, undefined);
539
+ app.endStreaming();
540
+ // Return the AI response text for use in subsequent steps
541
+ const lastMsg = app.getMessages();
542
+ const assistantMsg = lastMsg[lastMsg.length - 1];
543
+ return (assistantMsg?.role === 'assistant' ? assistantMsg.content : response || '').trim();
544
+ }
545
+ catch (err) {
546
+ app.endStreaming();
547
+ throw err;
548
+ }
548
549
  },
549
550
  onAgent: (task) => {
550
551
  return new Promise((resolve, reject) => {
@@ -20,9 +20,10 @@ const BUILT_IN_SKILLS = [
20
20
  { name: 'message', description: 'Optional commit message (skips AI generation)', required: false },
21
21
  ],
22
22
  steps: [
23
- { type: 'prompt', content: 'Analyze the git diff and generate a conventional commit message following this format: type(scope): description. Types: feat, fix, docs, style, refactor, test, chore. Be concise.' },
23
+ { type: 'command', content: 'git diff --cached --stat || git diff --stat' },
24
+ { type: 'prompt', content: 'Based on this git diff, generate ONLY a conventional commit message (no explanation, no markdown). Format: type(scope): description. Types: feat, fix, docs, style, refactor, test, chore. Be concise. One line only.\n\n${_prev}' },
24
25
  { type: 'confirm', content: 'Commit with this message?' },
25
- { type: 'command', content: 'git add -A && git commit -m "${message}"' },
26
+ { type: 'command', content: 'git add -A && git commit -m "${_prev}"' },
26
27
  { type: 'notify', content: 'Changes committed successfully!' },
27
28
  ],
28
29
  },
@@ -707,6 +708,23 @@ export function parseSkillArgs(args, skill) {
707
708
  }
708
709
  return result;
709
710
  }
711
+ /**
712
+ * Sanitize text for safe use inside shell commands.
713
+ * Strips markdown formatting and escapes double quotes.
714
+ */
715
+ function sanitizeForShell(text) {
716
+ return text
717
+ // Strip markdown code blocks
718
+ .replace(/```[\s\S]*?```/g, '')
719
+ // Strip inline backticks
720
+ .replace(/`([^`]*)`/g, '$1')
721
+ // Strip bold/italic markers
722
+ .replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1')
723
+ // Take only the first non-empty line (commit messages should be one line)
724
+ .split('\n').map(l => l.trim()).filter(Boolean)[0] || text.trim()
725
+ // Escape double quotes for shell safety
726
+ .replace(/"/g, '\\"');
727
+ }
710
728
  /**
711
729
  * Interpolate parameters into skill step content
712
730
  */
@@ -778,7 +796,9 @@ export async function executeSkill(skill, params, callbacks) {
778
796
  let lastOutput = '';
779
797
  for (const step of skill.steps) {
780
798
  // Interpolate params and ${_prev} into step content
781
- const allParams = { ...params, _prev: lastOutput };
799
+ // For command steps, sanitize _prev for safe shell usage
800
+ const sanitizedPrev = step.type === 'command' ? sanitizeForShell(lastOutput) : lastOutput;
801
+ const allParams = { ...params, _prev: sanitizedPrev };
782
802
  const content = interpolateParams(step.content, allParams);
783
803
  try {
784
804
  let result = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",