whitesmith 0.0.0 → 0.0.2

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 (71) hide show
  1. package/README.md +228 -0
  2. package/dist/auto-work.d.ts +11 -0
  3. package/dist/auto-work.d.ts.map +1 -0
  4. package/dist/auto-work.js +22 -0
  5. package/dist/auto-work.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +108 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/comment.d.ts +29 -0
  10. package/dist/comment.d.ts.map +1 -0
  11. package/dist/comment.js +390 -0
  12. package/dist/comment.js.map +1 -0
  13. package/dist/git.d.ts +12 -0
  14. package/dist/git.d.ts.map +1 -1
  15. package/dist/git.js +57 -14
  16. package/dist/git.js.map +1 -1
  17. package/dist/harnesses/agent-harness.d.ts +13 -0
  18. package/dist/harnesses/agent-harness.d.ts.map +1 -1
  19. package/dist/harnesses/index.d.ts +1 -1
  20. package/dist/harnesses/index.d.ts.map +1 -1
  21. package/dist/harnesses/pi.d.ts +7 -5
  22. package/dist/harnesses/pi.d.ts.map +1 -1
  23. package/dist/harnesses/pi.js +122 -9
  24. package/dist/harnesses/pi.js.map +1 -1
  25. package/dist/install-ci.d.ts +7 -0
  26. package/dist/install-ci.d.ts.map +1 -0
  27. package/dist/install-ci.js +760 -0
  28. package/dist/install-ci.js.map +1 -0
  29. package/dist/orchestrator.d.ts +24 -4
  30. package/dist/orchestrator.d.ts.map +1 -1
  31. package/dist/orchestrator.js +254 -63
  32. package/dist/orchestrator.js.map +1 -1
  33. package/dist/prompts.d.ts.map +1 -1
  34. package/dist/prompts.js +1 -0
  35. package/dist/prompts.js.map +1 -1
  36. package/dist/providers/github-ci.d.ts +16 -0
  37. package/dist/providers/github-ci.d.ts.map +1 -0
  38. package/dist/providers/github-ci.js +733 -0
  39. package/dist/providers/github-ci.js.map +1 -0
  40. package/dist/providers/github.d.ts +21 -0
  41. package/dist/providers/github.d.ts.map +1 -1
  42. package/dist/providers/github.js +88 -3
  43. package/dist/providers/github.js.map +1 -1
  44. package/dist/providers/index.d.ts +1 -0
  45. package/dist/providers/index.d.ts.map +1 -1
  46. package/dist/providers/issue-provider.d.ts +26 -0
  47. package/dist/providers/issue-provider.d.ts.map +1 -1
  48. package/dist/task-manager.d.ts +4 -0
  49. package/dist/task-manager.d.ts.map +1 -1
  50. package/dist/task-manager.js +6 -0
  51. package/dist/task-manager.js.map +1 -1
  52. package/dist/types.d.ts +13 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js +2 -0
  55. package/dist/types.js.map +1 -1
  56. package/package.json +3 -1
  57. package/src/auto-work.ts +26 -0
  58. package/src/cli.ts +123 -1
  59. package/src/comment.ts +531 -0
  60. package/src/git.ts +58 -12
  61. package/src/harnesses/agent-harness.ts +15 -0
  62. package/src/harnesses/index.ts +1 -1
  63. package/src/harnesses/pi.ts +146 -10
  64. package/src/orchestrator.ts +290 -72
  65. package/src/prompts.ts +1 -0
  66. package/src/providers/github-ci.ts +840 -0
  67. package/src/providers/github.ts +118 -5
  68. package/src/providers/index.ts +1 -0
  69. package/src/providers/issue-provider.ts +25 -1
  70. package/src/task-manager.ts +7 -0
  71. package/src/types.ts +11 -0
@@ -1,7 +1,25 @@
1
- import {exec} from 'node:child_process';
1
+ import {exec, execSync} from 'node:child_process';
2
2
  import * as fs from 'node:fs';
3
+ import {homedir} from 'node:os';
3
4
  import * as path from 'node:path';
4
- import type {AgentHarness} from './agent-harness.js';
5
+ import type {AgentHarness, AgentHarnessConfig} from './agent-harness.js';
6
+
7
+ /** Subset of pi JSON event fields we care about */
8
+ interface PiEvent {
9
+ type: string;
10
+ toolName?: string;
11
+ args?: any;
12
+ result?: any;
13
+ isError?: boolean;
14
+ assistantMessageEvent?: {type: string; delta?: string};
15
+ reason?: string;
16
+ attempt?: number;
17
+ maxAttempts?: number;
18
+ delayMs?: number;
19
+ errorMessage?: string;
20
+ success?: boolean;
21
+ finalError?: string;
22
+ }
5
23
 
6
24
  /**
7
25
  * Agent harness for @mariozechner/pi-coding-agent.
@@ -10,12 +28,72 @@ import type {AgentHarness} from './agent-harness.js';
10
28
  */
11
29
  export class PiHarness implements AgentHarness {
12
30
  private cmd: string;
31
+ private provider: string;
32
+ private model: string;
13
33
 
14
- /**
15
- * @param cmd - Command to invoke pi (default: "pi")
16
- */
17
- constructor(cmd: string = 'pi') {
18
- this.cmd = cmd;
34
+ constructor(config: AgentHarnessConfig) {
35
+ this.cmd = config.cmd;
36
+ this.provider = config.provider;
37
+ this.model = config.model;
38
+ }
39
+
40
+ async validate(): Promise<void> {
41
+ // Check if the command exists
42
+ try {
43
+ execSync(`which ${this.cmd}`, {stdio: 'pipe'});
44
+ } catch {
45
+ throw new Error(
46
+ `Agent command '${this.cmd}' not found. ` +
47
+ `Make sure it is installed and available in PATH. ` +
48
+ `For pi-coding-agent: npm install -g @mariozechner/pi-coding-agent`,
49
+ );
50
+ }
51
+
52
+ // Check auth.json exists and has the expected provider
53
+ const homeDir = process.env.HOME || homedir();
54
+ const authJsonPath = path.join(homeDir, '.pi', 'agent', 'auth.json');
55
+ if (fs.existsSync(authJsonPath)) {
56
+ try {
57
+ const authData = JSON.parse(fs.readFileSync(authJsonPath, 'utf-8'));
58
+ const providers = Object.keys(authData);
59
+ console.log(`Auth file found at ${authJsonPath} with providers: ${providers.join(', ')}`);
60
+ if (!authData[this.provider]) {
61
+ console.warn(
62
+ `WARNING: Provider '${this.provider}' not found in auth.json (has: ${providers.join(', ')})`,
63
+ );
64
+ }
65
+ } catch (e: any) {
66
+ console.warn(`WARNING: Could not parse auth.json: ${e.message}`);
67
+ }
68
+ } else {
69
+ console.warn(`WARNING: No auth.json found at ${authJsonPath}`);
70
+ }
71
+
72
+ // Validate auth by making a minimal API call
73
+ try {
74
+ const result = execSync(
75
+ `${this.cmd} --print --no-tools --no-session --provider ${this.provider} --model ${this.model} "respond with OK"`,
76
+ {stdio: 'pipe', timeout: 30_000},
77
+ );
78
+ const output = result.toString().trim();
79
+ if (!output) {
80
+ throw new Error('Empty response');
81
+ }
82
+ console.log(`Auth check passed (response: ${output.slice(0, 20)})`);
83
+ } catch (error: any) {
84
+ const stderr = error.stderr?.toString() || '';
85
+ const stdout = error.stdout?.toString() || '';
86
+ const details =
87
+ [stderr, stdout].filter(Boolean).join('\n') || error.message || 'unknown error';
88
+ throw new Error(
89
+ `Agent auth validation failed. Ensure valid credentials are configured.\n` +
90
+ `Set ANTHROPIC_API_KEY or configure OAuth via ~/.pi/agent/auth.json\n` +
91
+ `Auth file path: ${authJsonPath}\n` +
92
+ `Auth file exists: ${fs.existsSync(authJsonPath)}\n` +
93
+ `HOME: ${homeDir}\n` +
94
+ `Details: ${details.slice(0, 800)}`,
95
+ );
96
+ }
19
97
  }
20
98
 
21
99
  async run(options: {
@@ -23,13 +101,13 @@ export class PiHarness implements AgentHarness {
23
101
  workDir: string;
24
102
  logFile?: string;
25
103
  }): Promise<{output: string; exitCode: number}> {
26
- // Write prompt to a temp file to avoid shell escaping issues
104
+ // Write prompt to a temp file and use @file syntax so pi reads contents
27
105
  const promptFile = path.join(options.workDir, '.whitesmith-prompt.md');
28
106
  fs.writeFileSync(promptFile, options.prompt, 'utf-8');
29
107
 
30
108
  try {
31
109
  const result = await this.exec(
32
- `${this.cmd} --prompt-file "${promptFile}" --yes`,
110
+ `${this.cmd} --print --mode json --no-session --provider ${this.provider} --model ${this.model} @"${promptFile}"`,
33
111
  options.workDir,
34
112
  options.logFile,
35
113
  );
@@ -44,6 +122,43 @@ export class PiHarness implements AgentHarness {
44
122
  }
45
123
  }
46
124
 
125
+ /** Format a pi JSON event as a human-readable log line (null = skip) */
126
+ private formatEvent(event: PiEvent): string | null {
127
+ switch (event.type) {
128
+ case 'agent_start':
129
+ return '\nšŸ¤– Agent started';
130
+ case 'agent_end':
131
+ return '\nšŸ Agent finished';
132
+ case 'turn_start':
133
+ return '\n--- turn ---';
134
+ case 'message_update': {
135
+ const evt = event.assistantMessageEvent;
136
+ if (evt?.type === 'text_delta' && evt.delta) return evt.delta;
137
+ if (evt?.type === 'thinking_delta' && evt.delta) return evt.delta;
138
+ return null;
139
+ }
140
+ case 'tool_execution_start': {
141
+ const argsStr = JSON.stringify(event.args);
142
+ const truncArgs = argsStr.length > 200 ? argsStr.slice(0, 200) + '…' : argsStr;
143
+ return `\nšŸ”§ ${event.toolName}(${truncArgs})`;
144
+ }
145
+ case 'tool_execution_end': {
146
+ const icon = event.isError ? 'āŒ' : 'āœ…';
147
+ const res = typeof event.result === 'string' ? event.result : JSON.stringify(event.result);
148
+ const truncRes = res.length > 500 ? res.slice(0, 500) + '…' : res;
149
+ return `${icon} ${event.toolName} → ${truncRes}`;
150
+ }
151
+ case 'compaction_start':
152
+ return `\nšŸ“¦ Compaction (${event.reason})`;
153
+ case 'auto_retry_start':
154
+ return `\nšŸ”„ Retry ${event.attempt}/${event.maxAttempts}: ${event.errorMessage}`;
155
+ case 'auto_retry_end':
156
+ return event.success ? 'šŸ”„ Retry succeeded' : `šŸ”„ Retry failed: ${event.finalError}`;
157
+ default:
158
+ return null;
159
+ }
160
+ }
161
+
47
162
  private exec(
48
163
  cmd: string,
49
164
  workDir: string,
@@ -56,15 +171,35 @@ export class PiHarness implements AgentHarness {
56
171
  timeout: 30 * 60 * 1000, // 30 minute timeout
57
172
  });
58
173
 
174
+ // Close stdin immediately so pi doesn't hang waiting for piped input
175
+ child.stdin?.end();
176
+
59
177
  let output = '';
178
+ let lineBuffer = '';
60
179
  const logStream = logFile
61
180
  ? fs.createWriteStream(path.resolve(workDir, logFile), {flags: 'a'})
62
181
  : null;
63
182
 
183
+ const processLine = (line: string) => {
184
+ if (!line.trim()) return;
185
+ try {
186
+ const event: PiEvent = JSON.parse(line);
187
+ const formatted = this.formatEvent(event);
188
+ if (formatted !== null) {
189
+ process.stdout.write(formatted);
190
+ }
191
+ } catch {
192
+ process.stdout.write(line + '\n');
193
+ }
194
+ };
195
+
64
196
  child.stdout?.on('data', (data: string) => {
65
197
  output += data;
66
- process.stdout.write(data);
67
198
  logStream?.write(data);
199
+ lineBuffer += data;
200
+ const lines = lineBuffer.split('\n');
201
+ lineBuffer = lines.pop() ?? '';
202
+ for (const line of lines) processLine(line);
68
203
  });
69
204
 
70
205
  child.stderr?.on('data', (data: string) => {
@@ -74,6 +209,7 @@ export class PiHarness implements AgentHarness {
74
209
  });
75
210
 
76
211
  child.on('close', (code) => {
212
+ if (lineBuffer.trim()) processLine(lineBuffer);
77
213
  logStream?.end();
78
214
  resolve({output, exitCode: code ?? 1});
79
215
  });