codeep 1.1.35 → 1.2.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 (36) hide show
  1. package/README.md +90 -4
  2. package/dist/api/index.js +64 -2
  3. package/dist/renderer/App.d.ts +5 -10
  4. package/dist/renderer/App.js +165 -314
  5. package/dist/renderer/components/Export.d.ts +22 -0
  6. package/dist/renderer/components/Export.js +64 -0
  7. package/dist/renderer/components/Help.js +5 -1
  8. package/dist/renderer/components/Logout.d.ts +29 -0
  9. package/dist/renderer/components/Logout.js +91 -0
  10. package/dist/renderer/components/Search.d.ts +30 -0
  11. package/dist/renderer/components/Search.js +83 -0
  12. package/dist/renderer/components/Settings.js +20 -0
  13. package/dist/renderer/components/Status.d.ts +6 -0
  14. package/dist/renderer/components/Status.js +20 -1
  15. package/dist/renderer/main.js +296 -156
  16. package/dist/utils/agent.d.ts +5 -0
  17. package/dist/utils/agent.js +238 -3
  18. package/dist/utils/agent.test.d.ts +1 -0
  19. package/dist/utils/agent.test.js +250 -0
  20. package/dist/utils/diffPreview.js +104 -35
  21. package/dist/utils/gitignore.d.ts +24 -0
  22. package/dist/utils/gitignore.js +161 -0
  23. package/dist/utils/gitignore.test.d.ts +1 -0
  24. package/dist/utils/gitignore.test.js +167 -0
  25. package/dist/utils/skills.d.ts +21 -0
  26. package/dist/utils/skills.js +51 -0
  27. package/dist/utils/smartContext.js +8 -0
  28. package/dist/utils/smartContext.test.d.ts +1 -0
  29. package/dist/utils/smartContext.test.js +382 -0
  30. package/dist/utils/tokenTracker.d.ts +52 -0
  31. package/dist/utils/tokenTracker.js +86 -0
  32. package/dist/utils/tools.d.ts +16 -0
  33. package/dist/utils/tools.js +146 -19
  34. package/dist/utils/tools.test.d.ts +1 -0
  35. package/dist/utils/tools.test.js +664 -0
  36. package/package.json +1 -1
@@ -15,12 +15,15 @@ import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, getModelsForCur
15
15
  import { isProjectDirectory, getProjectContext } from '../utils/project.js';
16
16
  import { getCurrentVersion } from '../utils/update.js';
17
17
  import { getProviderList } from '../config/providers.js';
18
+ import { getSessionStats } from '../utils/tokenTracker.js';
18
19
  // State
19
20
  let projectPath = process.cwd();
20
21
  let projectContext = null;
21
22
  let hasWriteAccess = false;
22
23
  let sessionId = getCurrentSessionId();
23
24
  let app;
25
+ // Added file context (/add, /drop)
26
+ const addedFiles = new Map();
24
27
  /**
25
28
  * Get current status
26
29
  */
@@ -28,6 +31,7 @@ function getStatus() {
28
31
  const provider = getCurrentProvider();
29
32
  const providers = getProviderList();
30
33
  const providerInfo = providers.find(p => p.id === provider.id);
34
+ const stats = getSessionStats();
31
35
  return {
32
36
  version: getCurrentVersion(),
33
37
  provider: providerInfo?.name || 'Unknown',
@@ -37,14 +41,29 @@ function getStatus() {
37
41
  hasWriteAccess,
38
42
  sessionId,
39
43
  messageCount: 0, // Will be updated
44
+ tokenStats: {
45
+ totalTokens: stats.totalTokens,
46
+ promptTokens: stats.totalPromptTokens,
47
+ completionTokens: stats.totalCompletionTokens,
48
+ requestCount: stats.requestCount,
49
+ },
40
50
  };
41
51
  }
42
52
  // Agent state
43
53
  let isAgentRunning = false;
44
54
  let agentAbortController = null;
45
55
  /**
46
- * Handle chat submission
56
+ * Format added files as context to prepend to user messages
47
57
  */
58
+ function formatAddedFilesContext() {
59
+ if (addedFiles.size === 0)
60
+ return '';
61
+ const parts = ['[Attached files]'];
62
+ for (const [, file] of addedFiles) {
63
+ parts.push(`\nFile: ${file.relativePath}\n\`\`\`\n${file.content}\n\`\`\``);
64
+ }
65
+ return parts.join('\n') + '\n\n';
66
+ }
48
67
  async function handleSubmit(message) {
49
68
  // Check if we're waiting for interactive mode answers
50
69
  if (pendingInteractiveContext) {
@@ -112,7 +131,10 @@ async function handleSubmit(message) {
112
131
  app.startStreaming();
113
132
  // Get conversation history for context
114
133
  const history = app.getChatHistory();
115
- const response = await chat(message, history, (chunk) => {
134
+ // Prepend added file context if any
135
+ const fileContext = formatAddedFilesContext();
136
+ const enrichedMessage = fileContext ? fileContext + message : message;
137
+ const response = await chat(enrichedMessage, history, (chunk) => {
116
138
  app.addStreamChunk(chunk);
117
139
  }, undefined, projectContext, undefined);
118
140
  app.endStreaming();
@@ -290,7 +312,10 @@ async function executeAgentTask(task, dryRun = false) {
290
312
  // Store context in local variable for TypeScript narrowing
291
313
  const context = projectContext;
292
314
  try {
293
- const result = await runAgent(task, context, {
315
+ // Enrich task with added file context if any
316
+ const fileContext = formatAddedFilesContext();
317
+ const enrichedTask = fileContext ? fileContext + task : task;
318
+ const result = await runAgent(enrichedTask, context, {
294
319
  dryRun,
295
320
  onIteration: (iteration) => {
296
321
  app.updateAgentProgress(iteration);
@@ -312,37 +337,64 @@ async function executeAgentTask(task, dryRun = false) {
312
337
  // Update agent thinking
313
338
  const shortTarget = target.length > 50 ? '...' + target.slice(-47) : target;
314
339
  app.setAgentThinking(`${actionType}: ${shortTarget}`);
315
- // Set code preview and add chat message for write/edit operations
340
+ // Add chat message with diff preview for write/edit operations
316
341
  if (actionType === 'write' && tool.parameters.content) {
317
342
  const filePath = tool.parameters.path;
318
- const ext = filePath.split('.').pop() || '';
319
- app.addMessage({
320
- role: 'system',
321
- content: `**Write** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.content}\n\`\`\``,
322
- });
323
- app.setAgentCodePreview({
324
- path: filePath,
325
- actionType: 'write',
326
- content: tool.parameters.content,
327
- });
343
+ try {
344
+ const { createFileDiff, formatDiffForDisplay } = require('../utils/diffPreview');
345
+ const diff = createFileDiff(filePath, tool.parameters.content, context.root);
346
+ const diffText = formatDiffForDisplay(diff);
347
+ const additions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'add').length, 0);
348
+ const deletions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'remove').length, 0);
349
+ app.addMessage({
350
+ role: 'system',
351
+ content: `**${diff.type === 'create' ? 'Create' : 'Write'}** \`${filePath}\` (+${additions} -${deletions})\n\n\`\`\`diff\n${diffText}\n\`\`\``,
352
+ });
353
+ }
354
+ catch {
355
+ const ext = filePath.split('.').pop() || '';
356
+ app.addMessage({
357
+ role: 'system',
358
+ content: `**Write** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.content}\n\`\`\``,
359
+ });
360
+ }
328
361
  }
329
362
  else if (actionType === 'edit' && tool.parameters.new_text) {
330
363
  const filePath = tool.parameters.path;
331
- const ext = filePath.split('.').pop() || '';
364
+ try {
365
+ const { createEditDiff, formatDiffForDisplay } = require('../utils/diffPreview');
366
+ const diff = createEditDiff(filePath, tool.parameters.old_text, tool.parameters.new_text, context.root);
367
+ if (diff) {
368
+ const additions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'add').length, 0);
369
+ const deletions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'remove').length, 0);
370
+ app.addMessage({
371
+ role: 'system',
372
+ content: `**Edit** \`${filePath}\` (+${additions} -${deletions})\n\n\`\`\`diff\n${formatDiffForDisplay(diff)}\n\`\`\``,
373
+ });
374
+ }
375
+ else {
376
+ const ext = filePath.split('.').pop() || '';
377
+ app.addMessage({
378
+ role: 'system',
379
+ content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
380
+ });
381
+ }
382
+ }
383
+ catch {
384
+ const ext = filePath.split('.').pop() || '';
385
+ app.addMessage({
386
+ role: 'system',
387
+ content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
388
+ });
389
+ }
390
+ }
391
+ else if (actionType === 'delete') {
392
+ const filePath = tool.parameters.path;
332
393
  app.addMessage({
333
394
  role: 'system',
334
- content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
335
- });
336
- app.setAgentCodePreview({
337
- path: filePath,
338
- actionType: 'edit',
339
- content: tool.parameters.new_text,
340
- oldContent: tool.parameters.old_text,
395
+ content: `**Delete** \`${filePath}\``,
341
396
  });
342
397
  }
343
- else {
344
- app.setAgentCodePreview(null);
345
- }
346
398
  },
347
399
  onToolResult: (result, toolCall) => {
348
400
  const toolName = toolCall.tool.toLowerCase();
@@ -374,6 +426,34 @@ async function executeAgentTask(task, dryRun = false) {
374
426
  const summary = result.finalResponse || `Completed ${result.actions.length} actions in ${result.iterations} steps.`;
375
427
  app.addMessage({ role: 'assistant', content: summary });
376
428
  app.notify(`Agent completed: ${result.actions.length} actions`);
429
+ // Auto-commit if enabled and there were file changes
430
+ if (!dryRun && config.get('agentAutoCommit') && result.actions.length > 0) {
431
+ try {
432
+ const { autoCommitAgentChanges, createBranchAndCommit } = await import('../utils/git.js');
433
+ const useBranch = config.get('agentAutoCommitBranch');
434
+ if (useBranch) {
435
+ const commitResult = createBranchAndCommit(task, result.actions, context.root);
436
+ if (commitResult.success) {
437
+ app.addMessage({ role: 'system', content: `Auto-committed on branch \`${commitResult.branch}\` (${commitResult.hash?.slice(0, 7)})` });
438
+ }
439
+ else if (commitResult.error !== 'No changes detected by git') {
440
+ app.addMessage({ role: 'system', content: `Auto-commit failed: ${commitResult.error}` });
441
+ }
442
+ }
443
+ else {
444
+ const commitResult = autoCommitAgentChanges(task, result.actions, context.root);
445
+ if (commitResult.success) {
446
+ app.addMessage({ role: 'system', content: `Auto-committed: ${commitResult.hash?.slice(0, 7)}` });
447
+ }
448
+ else if (commitResult.error !== 'No changes detected by git') {
449
+ app.addMessage({ role: 'system', content: `Auto-commit failed: ${commitResult.error}` });
450
+ }
451
+ }
452
+ }
453
+ catch {
454
+ // Silently ignore commit errors
455
+ }
456
+ }
377
457
  }
378
458
  else if (result.aborted) {
379
459
  app.addMessage({ role: 'assistant', content: 'Agent stopped by user.' });
@@ -397,6 +477,91 @@ async function executeAgentTask(task, dryRun = false) {
397
477
  app.setAgentRunning(false);
398
478
  }
399
479
  }
480
+ /**
481
+ * Run a skill by name or shortcut with the given args.
482
+ * Wires the skill execution engine to App's UI.
483
+ */
484
+ async function runSkill(nameOrShortcut, args) {
485
+ const { findSkill, parseSkillArgs, executeSkill, trackSkillUsage } = await import('../utils/skills.js');
486
+ const skill = findSkill(nameOrShortcut);
487
+ if (!skill)
488
+ return false;
489
+ // Pre-flight checks
490
+ if (skill.requiresGit) {
491
+ const { getGitStatus } = await import('../utils/git.js');
492
+ if (!projectPath || !getGitStatus(projectPath).isRepo) {
493
+ app.notify('This skill requires a git repository');
494
+ return true;
495
+ }
496
+ }
497
+ if (skill.requiresWriteAccess && !hasWriteAccess) {
498
+ app.notify('This skill requires write access. Use /grant first.');
499
+ return true;
500
+ }
501
+ const params = parseSkillArgs(args.join(' '), skill);
502
+ app.addMessage({ role: 'user', content: `/${skill.name}${args.length ? ' ' + args.join(' ') : ''}` });
503
+ trackSkillUsage(skill.name);
504
+ const { execSync } = await import('child_process');
505
+ try {
506
+ const result = await executeSkill(skill, params, {
507
+ onCommand: async (cmd) => {
508
+ try {
509
+ const output = execSync(cmd, {
510
+ cwd: projectPath || process.cwd(),
511
+ encoding: 'utf-8',
512
+ timeout: 60000,
513
+ });
514
+ return output.trim();
515
+ }
516
+ catch (err) {
517
+ const error = err;
518
+ throw new Error(error.stderr || error.message || 'Command failed');
519
+ }
520
+ },
521
+ onPrompt: (prompt) => {
522
+ return new Promise((resolve, reject) => {
523
+ handleSubmit(prompt).then(() => {
524
+ // The AI response will be displayed in chat.
525
+ // We resolve with an empty string since the response is already shown.
526
+ resolve('');
527
+ }).catch(reject);
528
+ });
529
+ },
530
+ onAgent: (task) => {
531
+ return new Promise((resolve, reject) => {
532
+ if (!projectContext) {
533
+ reject(new Error('Agent requires project context'));
534
+ return;
535
+ }
536
+ runAgentTask(task).then(() => resolve('Agent completed')).catch(reject);
537
+ });
538
+ },
539
+ onConfirm: (message) => {
540
+ return new Promise((resolve) => {
541
+ app.showConfirm({
542
+ title: 'Confirm',
543
+ message: [message],
544
+ confirmLabel: 'Yes',
545
+ cancelLabel: 'No',
546
+ onConfirm: () => resolve(true),
547
+ onCancel: () => resolve(false),
548
+ });
549
+ });
550
+ },
551
+ onNotify: (message) => {
552
+ app.notify(message);
553
+ },
554
+ });
555
+ if (!result.success && result.output !== 'Cancelled by user') {
556
+ app.notify(`Skill failed: ${result.output}`);
557
+ }
558
+ }
559
+ catch (err) {
560
+ app.notify(`Skill error: ${err.message}`);
561
+ trackSkillUsage(skill.name, false);
562
+ }
563
+ return true;
564
+ }
400
565
  /**
401
566
  * Run a chain of commands sequentially
402
567
  */
@@ -560,28 +725,6 @@ function handleCommand(command, args) {
560
725
  });
561
726
  break;
562
727
  }
563
- case 'commit': {
564
- if (!projectContext) {
565
- app.notify('No project context');
566
- return;
567
- }
568
- import('../utils/git.js').then(({ getGitDiff, getGitStatus, suggestCommitMessage }) => {
569
- const status = getGitStatus(projectPath);
570
- if (!status.isRepo) {
571
- app.notify('Not a git repository');
572
- return;
573
- }
574
- const diff = getGitDiff(true, projectPath);
575
- if (!diff.success || !diff.diff) {
576
- app.notify('No staged changes. Use git add first.');
577
- return;
578
- }
579
- const suggestion = suggestCommitMessage(diff.diff);
580
- app.addMessage({ role: 'user', content: '/commit' });
581
- handleSubmit(`Generate a commit message for these staged changes. Suggestion: "${suggestion}"\n\nDiff:\n\`\`\`diff\n${diff.diff.slice(0, 2000)}\n\`\`\``);
582
- });
583
- break;
584
- }
585
728
  case 'undo': {
586
729
  import('../utils/agent.js').then(({ undoLastAction }) => {
587
730
  const result = undoLastAction();
@@ -980,6 +1123,81 @@ function handleCommand(command, args) {
980
1123
  });
981
1124
  break;
982
1125
  }
1126
+ // File context commands
1127
+ case 'add': {
1128
+ if (!args.length) {
1129
+ if (addedFiles.size === 0) {
1130
+ app.notify('Usage: /add <file-path> [file2] ... | No files added');
1131
+ }
1132
+ else {
1133
+ const fileList = Array.from(addedFiles.values()).map(f => f.relativePath).join(', ');
1134
+ app.notify(`Added files (${addedFiles.size}): ${fileList}`);
1135
+ }
1136
+ return;
1137
+ }
1138
+ const path = require('path');
1139
+ const fs = require('fs');
1140
+ const root = projectContext?.root || projectPath;
1141
+ let added = 0;
1142
+ const errors = [];
1143
+ for (const filePath of args) {
1144
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath);
1145
+ const relativePath = path.isAbsolute(filePath) ? path.relative(root, filePath) : filePath;
1146
+ try {
1147
+ const stat = fs.statSync(fullPath);
1148
+ if (!stat.isFile()) {
1149
+ errors.push(`${filePath}: not a file`);
1150
+ continue;
1151
+ }
1152
+ if (stat.size > 100000) {
1153
+ errors.push(`${filePath}: too large (${Math.round(stat.size / 1024)}KB, max 100KB)`);
1154
+ continue;
1155
+ }
1156
+ const content = fs.readFileSync(fullPath, 'utf-8');
1157
+ addedFiles.set(fullPath, { relativePath, content });
1158
+ added++;
1159
+ }
1160
+ catch {
1161
+ errors.push(`${filePath}: file not found`);
1162
+ }
1163
+ }
1164
+ if (added > 0) {
1165
+ app.notify(`Added ${added} file(s) to context (${addedFiles.size} total)`);
1166
+ }
1167
+ if (errors.length > 0) {
1168
+ app.notify(errors.join(', '));
1169
+ }
1170
+ break;
1171
+ }
1172
+ case 'drop': {
1173
+ if (!args.length) {
1174
+ if (addedFiles.size === 0) {
1175
+ app.notify('No files in context');
1176
+ }
1177
+ else {
1178
+ const count = addedFiles.size;
1179
+ addedFiles.clear();
1180
+ app.notify(`Dropped all ${count} file(s) from context`);
1181
+ }
1182
+ return;
1183
+ }
1184
+ const path = require('path');
1185
+ const root = projectContext?.root || projectPath;
1186
+ let dropped = 0;
1187
+ for (const filePath of args) {
1188
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath);
1189
+ if (addedFiles.delete(fullPath)) {
1190
+ dropped++;
1191
+ }
1192
+ }
1193
+ if (dropped > 0) {
1194
+ app.notify(`Dropped ${dropped} file(s) (${addedFiles.size} remaining)`);
1195
+ }
1196
+ else {
1197
+ app.notify('File not found in context. Use /add to see added files.');
1198
+ }
1199
+ break;
1200
+ }
983
1201
  // Agent history and changes
984
1202
  case 'history': {
985
1203
  import('../utils/agent.js').then(({ getAgentHistory }) => {
@@ -1121,120 +1339,37 @@ function handleCommand(command, args) {
1121
1339
  break;
1122
1340
  }
1123
1341
  // Skills shortcuts
1124
- case 'c': {
1125
- handleCommand('commit', []);
1126
- break;
1127
- }
1128
- case 't': {
1129
- if (!projectContext) {
1130
- app.notify('No project context');
1131
- return;
1132
- }
1133
- app.addMessage({ role: 'user', content: '/test' });
1134
- handleSubmit('Generate and run tests for the current project. Focus on untested code.');
1135
- break;
1136
- }
1137
- case 'd': {
1138
- if (!projectContext) {
1139
- app.notify('No project context');
1140
- return;
1141
- }
1142
- app.addMessage({ role: 'user', content: '/docs' });
1143
- handleSubmit('Add documentation to the code. Focus on functions and classes that lack proper documentation.');
1144
- break;
1145
- }
1146
- case 'r': {
1147
- if (!projectContext) {
1148
- app.notify('No project context');
1149
- return;
1150
- }
1151
- app.addMessage({ role: 'user', content: '/refactor' });
1152
- handleSubmit('Refactor the code to improve quality, readability, and maintainability.');
1153
- break;
1154
- }
1155
- case 'f': {
1156
- if (!projectContext) {
1157
- app.notify('No project context');
1158
- return;
1159
- }
1160
- app.addMessage({ role: 'user', content: '/fix' });
1161
- handleSubmit('Debug and fix any issues in the current code. Look for bugs, errors, and potential problems.');
1162
- break;
1163
- }
1164
- case 'e': {
1165
- if (!args.length) {
1166
- app.notify('Usage: /e <file or code to explain>');
1167
- return;
1168
- }
1169
- app.addMessage({ role: 'user', content: `/explain ${args.join(' ')}` });
1170
- handleSubmit(`Explain this code or concept: ${args.join(' ')}`);
1171
- break;
1172
- }
1173
- case 'o': {
1174
- if (!projectContext) {
1175
- app.notify('No project context');
1176
- return;
1177
- }
1178
- app.addMessage({ role: 'user', content: '/optimize' });
1179
- handleSubmit('Optimize the code for better performance. Focus on efficiency and speed improvements.');
1180
- break;
1181
- }
1182
- case 'b': {
1183
- if (!projectContext) {
1184
- app.notify('No project context');
1185
- return;
1186
- }
1187
- app.addMessage({ role: 'user', content: '/debug' });
1188
- handleSubmit('Help debug the current issue. Analyze the code and identify the root cause of problems.');
1189
- break;
1190
- }
1191
- case 'p': {
1192
- // Push shortcut
1193
- import('child_process').then(({ execSync }) => {
1194
- try {
1195
- execSync('git push', { cwd: projectPath, encoding: 'utf-8' });
1196
- app.notify('Pushed successfully');
1197
- }
1198
- catch (err) {
1199
- app.notify(`Push failed: ${err.message}`);
1200
- }
1201
- });
1202
- break;
1203
- }
1204
- // Full skill names
1342
+ // Skill shortcuts and full names — delegated to skill execution engine
1343
+ case 'c':
1344
+ case 'commit':
1345
+ case 't':
1205
1346
  case 'test':
1347
+ case 'd':
1206
1348
  case 'docs':
1349
+ case 'r':
1207
1350
  case 'refactor':
1351
+ case 'f':
1208
1352
  case 'fix':
1353
+ case 'e':
1209
1354
  case 'explain':
1355
+ case 'o':
1210
1356
  case 'optimize':
1211
- case 'debug': {
1212
- const skillMap = {
1213
- test: 't',
1214
- docs: 'd',
1215
- refactor: 'r',
1216
- fix: 'f',
1217
- explain: 'e',
1218
- optimize: 'o',
1219
- debug: 'b',
1220
- };
1221
- handleCommand(skillMap[command], args);
1222
- break;
1223
- }
1224
- case 'push': {
1225
- handleCommand('p', args);
1226
- break;
1227
- }
1228
- case 'pull': {
1229
- import('child_process').then(({ execSync }) => {
1230
- try {
1231
- execSync('git pull', { cwd: projectPath, encoding: 'utf-8' });
1232
- app.notify('Pulled successfully');
1233
- }
1234
- catch (err) {
1235
- app.notify(`Pull failed: ${err.message}`);
1236
- }
1237
- });
1357
+ case 'b':
1358
+ case 'debug':
1359
+ case 'p':
1360
+ case 'push':
1361
+ case 'pull':
1362
+ case 'amend':
1363
+ case 'pr':
1364
+ case 'changelog':
1365
+ case 'branch':
1366
+ case 'stash':
1367
+ case 'unstash':
1368
+ case 'build':
1369
+ case 'deploy':
1370
+ case 'release':
1371
+ case 'publish': {
1372
+ runSkill(command, args);
1238
1373
  break;
1239
1374
  }
1240
1375
  case 'skills': {
@@ -1336,7 +1471,12 @@ function handleCommand(command, args) {
1336
1471
  break;
1337
1472
  }
1338
1473
  default:
1339
- app.notify(`Unknown command: /${command}`);
1474
+ // Try to run as a skill (handles custom skills and any built-in not in the switch)
1475
+ runSkill(command, args).then(handled => {
1476
+ if (!handled) {
1477
+ app.notify(`Unknown command: /${command}`);
1478
+ }
1479
+ });
1340
1480
  }
1341
1481
  }
1342
1482
  /**
@@ -30,6 +30,11 @@ export interface AgentResult {
30
30
  error?: string;
31
31
  aborted?: boolean;
32
32
  }
33
+ /**
34
+ * Load project rules from .codeep/rules.md or CODEEP.md
35
+ * Returns the rules content formatted for system prompt, or empty string if no rules found
36
+ */
37
+ export declare function loadProjectRules(projectRoot: string): string;
33
38
  /**
34
39
  * Run the agent loop
35
40
  */