@agentuity/coder 1.0.37

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 (92) hide show
  1. package/README.md +57 -0
  2. package/dist/chain-preview.d.ts +55 -0
  3. package/dist/chain-preview.d.ts.map +1 -0
  4. package/dist/chain-preview.js +472 -0
  5. package/dist/chain-preview.js.map +1 -0
  6. package/dist/client.d.ts +43 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +402 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/commands.d.ts +22 -0
  11. package/dist/commands.d.ts.map +1 -0
  12. package/dist/commands.js +99 -0
  13. package/dist/commands.js.map +1 -0
  14. package/dist/footer.d.ts +34 -0
  15. package/dist/footer.d.ts.map +1 -0
  16. package/dist/footer.js +249 -0
  17. package/dist/footer.js.map +1 -0
  18. package/dist/handlers.d.ts +24 -0
  19. package/dist/handlers.d.ts.map +1 -0
  20. package/dist/handlers.js +83 -0
  21. package/dist/handlers.js.map +1 -0
  22. package/dist/hub-overlay.d.ts +107 -0
  23. package/dist/hub-overlay.d.ts.map +1 -0
  24. package/dist/hub-overlay.js +1794 -0
  25. package/dist/hub-overlay.js.map +1 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +1585 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/output-viewer.d.ts +49 -0
  31. package/dist/output-viewer.d.ts.map +1 -0
  32. package/dist/output-viewer.js +389 -0
  33. package/dist/output-viewer.js.map +1 -0
  34. package/dist/overlay.d.ts +40 -0
  35. package/dist/overlay.d.ts.map +1 -0
  36. package/dist/overlay.js +225 -0
  37. package/dist/overlay.js.map +1 -0
  38. package/dist/protocol.d.ts +118 -0
  39. package/dist/protocol.d.ts.map +1 -0
  40. package/dist/protocol.js +3 -0
  41. package/dist/protocol.js.map +1 -0
  42. package/dist/remote-session.d.ts +113 -0
  43. package/dist/remote-session.d.ts.map +1 -0
  44. package/dist/remote-session.js +645 -0
  45. package/dist/remote-session.js.map +1 -0
  46. package/dist/remote-tui.d.ts +40 -0
  47. package/dist/remote-tui.d.ts.map +1 -0
  48. package/dist/remote-tui.js +606 -0
  49. package/dist/remote-tui.js.map +1 -0
  50. package/dist/renderers.d.ts +34 -0
  51. package/dist/renderers.d.ts.map +1 -0
  52. package/dist/renderers.js +669 -0
  53. package/dist/renderers.js.map +1 -0
  54. package/dist/review.d.ts +15 -0
  55. package/dist/review.d.ts.map +1 -0
  56. package/dist/review.js +154 -0
  57. package/dist/review.js.map +1 -0
  58. package/dist/titlebar.d.ts +3 -0
  59. package/dist/titlebar.d.ts.map +1 -0
  60. package/dist/titlebar.js +59 -0
  61. package/dist/titlebar.js.map +1 -0
  62. package/dist/todo/index.d.ts +3 -0
  63. package/dist/todo/index.d.ts.map +1 -0
  64. package/dist/todo/index.js +3 -0
  65. package/dist/todo/index.js.map +1 -0
  66. package/dist/todo/store.d.ts +6 -0
  67. package/dist/todo/store.d.ts.map +1 -0
  68. package/dist/todo/store.js +43 -0
  69. package/dist/todo/store.js.map +1 -0
  70. package/dist/todo/types.d.ts +13 -0
  71. package/dist/todo/types.d.ts.map +1 -0
  72. package/dist/todo/types.js +2 -0
  73. package/dist/todo/types.js.map +1 -0
  74. package/package.json +44 -0
  75. package/src/chain-preview.ts +621 -0
  76. package/src/client.ts +515 -0
  77. package/src/commands.ts +132 -0
  78. package/src/footer.ts +305 -0
  79. package/src/handlers.ts +113 -0
  80. package/src/hub-overlay.ts +2324 -0
  81. package/src/index.ts +1907 -0
  82. package/src/output-viewer.ts +480 -0
  83. package/src/overlay.ts +294 -0
  84. package/src/protocol.ts +157 -0
  85. package/src/remote-session.ts +800 -0
  86. package/src/remote-tui.ts +707 -0
  87. package/src/renderers.ts +740 -0
  88. package/src/review.ts +201 -0
  89. package/src/titlebar.ts +63 -0
  90. package/src/todo/index.ts +2 -0
  91. package/src/todo/store.ts +49 -0
  92. package/src/todo/types.ts +14 -0
package/src/review.ts ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Interactive code review command.
3
+ *
4
+ * Provides `/review` with 4 modes:
5
+ * 1. PR-style review against a base branch
6
+ * 2. Review uncommitted changes (staged + unstaged)
7
+ * 3. Review a specific commit
8
+ * 4. Custom review instructions (no diff)
9
+ *
10
+ * Gathers diff/instructions, parses stats, and sends
11
+ * a formatted review prompt to the @reviewer agent.
12
+ */
13
+ import type { ExtensionAPI, ExtensionCommandContext } from '@mariozechner/pi-coding-agent';
14
+ import { execFileSync } from 'node:child_process';
15
+
16
+ // ── Noise Filters ──
17
+ // Files excluded from diff stats (still included in the diff itself for context)
18
+ const EXCLUDED_PATTERNS = [
19
+ /\.(lock|lockb)$/, // lock files
20
+ /\.min\.(js|css)$/, // minified
21
+ /\.(png|jpg|jpeg|gif|svg|ico|webp)$/, // images
22
+ /\.(woff|woff2|ttf|eot)$/, // fonts
23
+ /node_modules\//, // dependencies
24
+ /\.map$/, // source maps
25
+ ];
26
+
27
+ const MAX_DIFF_SIZE = 100_000; // 100KB max for context window
28
+
29
+ // ── Types ──
30
+
31
+ interface DiffStats {
32
+ files: number;
33
+ added: number;
34
+ removed: number;
35
+ excludedFiles: string[];
36
+ }
37
+
38
+ // ── Git Helpers ──
39
+
40
+ function execGit(args: string[]): string {
41
+ try {
42
+ return execFileSync('git', args, {
43
+ encoding: 'utf-8',
44
+ maxBuffer: 5 * 1024 * 1024,
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ });
47
+ } catch (err: unknown) {
48
+ const stderr =
49
+ err && typeof err === 'object' && 'stderr' in err ? String((err as any).stderr) : '';
50
+ const msg = err instanceof Error ? err.message : String(err);
51
+ if (process.env['AGENTUITY_DEBUG']) {
52
+ console.error(`[agentuity-coder] git ${args.join(' ')} failed: ${stderr || msg}`);
53
+ }
54
+ return '';
55
+ }
56
+ }
57
+
58
+ function execGitDiff(diffArgs: string[]): string {
59
+ return execGit(['diff', ...diffArgs]);
60
+ }
61
+
62
+ function execGitShow(hash: string): string {
63
+ return execGit(['show', hash]);
64
+ }
65
+
66
+ // ── Diff Parsing ──
67
+
68
+ function isExcluded(filePath: string): boolean {
69
+ return EXCLUDED_PATTERNS.some((pattern) => pattern.test(filePath));
70
+ }
71
+
72
+ function parseDiffStats(diff: string): DiffStats {
73
+ if (!diff.trim()) {
74
+ return { files: 0, added: 0, removed: 0, excludedFiles: [] };
75
+ }
76
+
77
+ // Split on "diff --git" boundaries
78
+ const fileDiffs = diff.split(/^diff --git /m).filter(Boolean);
79
+
80
+ let files = 0;
81
+ let added = 0;
82
+ let removed = 0;
83
+ const excludedFiles: string[] = [];
84
+
85
+ for (const fileDiff of fileDiffs) {
86
+ // Extract file path from "a/path b/path" header
87
+ const headerMatch = fileDiff.match(/^a\/(.+?) b\//);
88
+ if (!headerMatch) continue;
89
+ const filePath = headerMatch[1] ?? '';
90
+
91
+ if (isExcluded(filePath)) {
92
+ excludedFiles.push(filePath);
93
+ continue;
94
+ }
95
+
96
+ files++;
97
+
98
+ // Count added/removed lines (lines starting with + or - but not headers)
99
+ const lines = fileDiff.split('\n');
100
+ for (const line of lines) {
101
+ if (line.startsWith('+') && !line.startsWith('+++')) {
102
+ added++;
103
+ } else if (line.startsWith('-') && !line.startsWith('---')) {
104
+ removed++;
105
+ }
106
+ }
107
+ }
108
+
109
+ return { files, added, removed, excludedFiles };
110
+ }
111
+
112
+ // ── Review Prompt Builder ──
113
+
114
+ function buildReviewPrompt(context: string, diff: string, stats: DiffStats): string {
115
+ const header = `## Code Review Request\n\n${context}\n\n**Stats:** ${stats.files} files changed, +${stats.added}/-${stats.removed} lines`;
116
+
117
+ const excludedNote =
118
+ stats.excludedFiles.length > 0
119
+ ? `\n\n_Excluded from stats: ${stats.excludedFiles.length} noise files (lock files, images, etc.)_`
120
+ : '';
121
+
122
+ // Truncate diff if too large
123
+ let diffContent = diff;
124
+ if (diff.length > MAX_DIFF_SIZE) {
125
+ diffContent = diff.slice(0, MAX_DIFF_SIZE) + '\n\n[Diff truncated - too large for review]';
126
+ }
127
+
128
+ return `${header}${excludedNote}\n\n\`\`\`diff\n${diffContent}\n\`\`\`\n\nPlease review these changes for:\n1. Correctness and potential bugs\n2. Security concerns\n3. Code quality and best practices\n4. Performance implications`;
129
+ }
130
+
131
+ // ── Main Handler ──
132
+
133
+ export async function handleReview(
134
+ _args: string,
135
+ ctx: ExtensionCommandContext,
136
+ pi: ExtensionAPI
137
+ ): Promise<void> {
138
+ // Non-interactive fallback
139
+ if (!ctx.hasUI) {
140
+ pi.sendUserMessage('@reviewer Review recent code changes', { deliverAs: 'followUp' });
141
+ return;
142
+ }
143
+
144
+ // Show mode selector
145
+ const mode = await ctx.ui.select('Review Mode', [
146
+ 'Review against a base branch (PR Style)',
147
+ 'Review uncommitted changes',
148
+ 'Review a specific commit',
149
+ 'Custom review instructions',
150
+ ]);
151
+
152
+ if (!mode) return;
153
+
154
+ let diff = '';
155
+ let context = '';
156
+
157
+ if (mode.includes('base branch')) {
158
+ const branch = await ctx.ui.input('Base branch', 'main');
159
+ if (!branch) return;
160
+ diff = execGitDiff([`${branch}...HEAD`]);
161
+ if (!diff.trim()) {
162
+ ctx.ui.notify(`No changes found between ${branch} and HEAD`, 'warning');
163
+ return;
164
+ }
165
+ context = `Reviewing changes from ${branch} to HEAD`;
166
+ } else if (mode.includes('uncommitted')) {
167
+ const staged = execGitDiff(['--cached']);
168
+ const unstaged = execGitDiff([]);
169
+ diff = staged + '\n' + unstaged;
170
+ if (!diff.trim()) {
171
+ ctx.ui.notify('No uncommitted changes found', 'warning');
172
+ return;
173
+ }
174
+ context = 'Reviewing uncommitted changes (staged + unstaged)';
175
+ } else if (mode.includes('specific commit')) {
176
+ const hash = await ctx.ui.input('Commit hash', '');
177
+ if (!hash) return;
178
+ diff = execGitShow(hash);
179
+ if (!diff.trim()) {
180
+ ctx.ui.notify(`No diff found for commit ${hash}`, 'warning');
181
+ return;
182
+ }
183
+ context = `Reviewing commit ${hash}`;
184
+ } else {
185
+ // Custom instructions — no diff
186
+ const instructions = await ctx.ui.input('Review instructions', '');
187
+ if (!instructions) return;
188
+ pi.sendUserMessage(`@reviewer ${instructions}`, { deliverAs: 'followUp' });
189
+ return;
190
+ }
191
+
192
+ // Parse diff stats
193
+ const stats = parseDiffStats(diff);
194
+
195
+ // Show summary notification
196
+ ctx.ui.notify(`${context}: ${stats.files} files, +${stats.added}/-${stats.removed}`, 'info');
197
+
198
+ // Compose and send review message
199
+ const reviewPrompt = buildReviewPrompt(context, diff, stats);
200
+ pi.sendUserMessage(`@reviewer ${reviewPrompt}`, { deliverAs: 'followUp' });
201
+ }
@@ -0,0 +1,63 @@
1
+ import type { ExtensionAPI, ExtensionContext } from '@mariozechner/pi-coding-agent';
2
+ import path from 'node:path';
3
+
4
+ const SPINNER_FRAMES = [
5
+ '\u280B',
6
+ '\u2819',
7
+ '\u2839',
8
+ '\u2838',
9
+ '\u283C',
10
+ '\u2834',
11
+ '\u2826',
12
+ '\u2827',
13
+ '\u2807',
14
+ '\u280F',
15
+ ];
16
+
17
+ let timer: ReturnType<typeof setInterval> | null = null;
18
+ let frameIndex = 0;
19
+
20
+ function getBaseTitle(pi: ExtensionAPI): string {
21
+ const cwd = path.basename(process.cwd());
22
+ // getSessionName() may not be in published types yet — use optional chaining
23
+ const session = (pi as any).getSessionName?.() as string | undefined;
24
+ return session ? `\u2A3A Agentuity - ${session} - ${cwd}` : `\u2A3A Agentuity - ${cwd}`;
25
+ }
26
+
27
+ export function setupTitlebar(pi: ExtensionAPI): void {
28
+ // Set initial title on session start
29
+ pi.on('session_start', async (_event, ctx) => {
30
+ if (!ctx.hasUI) return;
31
+ ctx.ui.setTitle(getBaseTitle(pi));
32
+ });
33
+
34
+ // Spinner during agent work
35
+ pi.on('agent_start', async (_event, ctx) => {
36
+ if (!ctx.hasUI) return;
37
+ stopSpinner(ctx, pi);
38
+ timer = setInterval(() => {
39
+ const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length];
40
+ ctx.ui.setTitle(`${frame} ${getBaseTitle(pi)}`);
41
+ frameIndex++;
42
+ }, 80);
43
+ });
44
+
45
+ pi.on('agent_end', async (_event, ctx) => {
46
+ if (!ctx.hasUI) return;
47
+ stopSpinner(ctx, pi);
48
+ });
49
+
50
+ pi.on('session_shutdown', async (_event, ctx) => {
51
+ if (!ctx.hasUI) return;
52
+ stopSpinner(ctx, pi);
53
+ });
54
+ }
55
+
56
+ function stopSpinner(ctx: ExtensionContext, pi: ExtensionAPI): void {
57
+ if (timer) {
58
+ clearInterval(timer);
59
+ timer = null;
60
+ }
61
+ frameIndex = 0;
62
+ ctx.ui.setTitle(getBaseTitle(pi));
63
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types.ts';
2
+ export * from './store.ts';
@@ -0,0 +1,49 @@
1
+ import type { Todo, TodoError, TodoErrorCode } from './types.ts';
2
+
3
+ const store = new Map<string, Todo>();
4
+
5
+ function makeTodoError(code: TodoErrorCode, message: string, field?: 'id' | 'text'): TodoError {
6
+ const error: TodoError = { code, message };
7
+ if (field !== undefined) {
8
+ error.field = field;
9
+ }
10
+ return error;
11
+ }
12
+
13
+ export function createTodo(text: string): Todo {
14
+ const trimmed = text.trim();
15
+ if (!trimmed) {
16
+ throw makeTodoError('VALIDATION_ERROR', 'text must not be empty', 'text');
17
+ }
18
+ const todo: Todo = {
19
+ id: crypto.randomUUID(),
20
+ text: trimmed,
21
+ completed: false,
22
+ createdAt: Date.now(),
23
+ };
24
+ store.set(todo.id, todo);
25
+ return todo;
26
+ }
27
+
28
+ export function listTodos(): Todo[] {
29
+ return Array.from(store.values()).sort((a, b) => b.createdAt - a.createdAt);
30
+ }
31
+
32
+ export function completeTodo(id: string): Todo {
33
+ const todo = store.get(id);
34
+ if (!todo) {
35
+ throw makeTodoError('NOT_FOUND', `todo with id '${id}' not found`, 'id');
36
+ }
37
+ const updated: Todo = { ...todo, completed: true };
38
+ store.set(id, updated);
39
+ return updated;
40
+ }
41
+
42
+ export function deleteTodo(id: string): Todo {
43
+ const todo = store.get(id);
44
+ if (!todo) {
45
+ throw makeTodoError('NOT_FOUND', `todo with id '${id}' not found`, 'id');
46
+ }
47
+ store.delete(id);
48
+ return todo;
49
+ }
@@ -0,0 +1,14 @@
1
+ export interface Todo {
2
+ id: string;
3
+ text: string;
4
+ completed: boolean;
5
+ createdAt: number;
6
+ }
7
+
8
+ export type TodoErrorCode = 'VALIDATION_ERROR' | 'NOT_FOUND';
9
+
10
+ export interface TodoError {
11
+ code: TodoErrorCode;
12
+ message: string;
13
+ field?: 'id' | 'text';
14
+ }