Skip to content

Commit 993b031

Browse files
chore: handoff checkpoint on main
1 parent 71700c4 commit 993b031

11 files changed

Lines changed: 763 additions & 3 deletions

File tree

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ NPM_TOKEN=npm_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
99
# Get from https://github.com/settings/tokens
1010
GITHUB_TOKEN=ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1111

12+
# Greptile API Key (for AI code review via MCP)
13+
# Get from https://app.greptile.com/settings
14+
GREPTILE_API_KEY=
15+
1216
# Linear API Key (for Linear integration)
1317
LINEAR_API_KEY=lin_api_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1418

package-lock.json

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@
151151
"optionalDependencies": {
152152
"blessed": "^0.1.81",
153153
"blessed-contrib": "^4.11.0",
154-
"chokidar": "^5.0.0"
154+
"chokidar": "^5.0.0",
155+
"node-pty": "^1.1.0"
155156
}
156157
}

src/cli/claude-sm.ts

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
loadModelRouterConfig,
3131
type ModelProvider,
3232
} from '../core/models/model-router.js';
33+
import { launchWrapper } from '../features/sweep/pty-wrapper.js';
3334
import { FallbackMonitor } from '../core/models/fallback-monitor.js';
3435

3536
// __filename and __dirname are provided by esbuild banner for ESM compatibility
@@ -43,6 +44,8 @@ interface ClaudeSMConfig {
4344
defaultNotifyOnDone: boolean;
4445
defaultWhatsApp: boolean;
4546
defaultModelRouting: boolean;
47+
defaultSweep: boolean;
48+
defaultGreptile: boolean;
4649
}
4750

4851
interface ClaudeConfig {
@@ -65,6 +68,8 @@ interface ClaudeConfig {
6568
useModelRouting: boolean;
6669
forceProvider?: ModelProvider;
6770
useThinkingMode: boolean;
71+
useSweep: boolean;
72+
useGreptile: boolean;
6873
}
6974

7075
const DEFAULT_SM_CONFIG: ClaudeSMConfig = {
@@ -76,6 +81,8 @@ const DEFAULT_SM_CONFIG: ClaudeSMConfig = {
7681
defaultNotifyOnDone: true,
7782
defaultWhatsApp: false,
7883
defaultModelRouting: false,
84+
defaultSweep: true,
85+
defaultGreptile: true,
7986
};
8087

8188
function getConfigPath(): string {
@@ -129,6 +136,8 @@ class ClaudeSM {
129136
sessionStartTime: Date.now(),
130137
useModelRouting: this.smConfig.defaultModelRouting,
131138
useThinkingMode: false,
139+
useSweep: this.smConfig.defaultSweep,
140+
useGreptile: this.smConfig.defaultGreptile,
132141
};
133142

134143
this.stackmemoryPath = this.findStackMemory();
@@ -213,6 +222,60 @@ class ClaudeSM {
213222
return null;
214223
}
215224

225+
private ensureGreptileMcp(): void {
226+
const apiKey = process.env['GREPTILE_API_KEY'];
227+
const ghToken = process.env['GITHUB_TOKEN'];
228+
229+
if (!apiKey) {
230+
console.log(
231+
chalk.gray(' Greptile: disabled (set GREPTILE_API_KEY in .env)')
232+
);
233+
return;
234+
}
235+
236+
// Check if already registered
237+
try {
238+
const result = execSync('claude mcp list 2>/dev/null', {
239+
encoding: 'utf-8',
240+
});
241+
if (result.includes('greptile')) {
242+
console.log(chalk.gray(' Greptile: MCP server registered'));
243+
return;
244+
}
245+
} catch {
246+
// claude mcp list not available, try to add anyway
247+
}
248+
249+
// Register Greptile MCP server via HTTP transport
250+
try {
251+
const cmd = [
252+
'claude mcp add',
253+
'--transport http',
254+
'greptile',
255+
'https://api.greptile.com/mcp',
256+
`--header "Authorization: Bearer ${apiKey}"`,
257+
];
258+
if (ghToken) {
259+
cmd.push(`--header "X-GitHub-Token: ${ghToken}"`);
260+
}
261+
execSync(cmd.join(' '), { stdio: 'ignore' });
262+
console.log(chalk.cyan(' Greptile: MCP server registered'));
263+
} catch {
264+
// Fallback: register via stdio transport with npx
265+
try {
266+
const envArgs = [`GREPTILE_API_KEY=${apiKey}`];
267+
if (ghToken) envArgs.push(`GITHUB_TOKEN=${ghToken}`);
268+
execSync(
269+
`claude mcp add greptile -- env ${envArgs.join(' ')} npx greptile-mcp-server`,
270+
{ stdio: 'ignore' }
271+
);
272+
console.log(chalk.cyan(' Greptile: MCP server registered (stdio)'));
273+
} catch {
274+
console.log(chalk.gray(' Greptile: failed to register MCP server'));
275+
}
276+
}
277+
}
278+
216279
private setupWorktree(): string | null {
217280
if (!this.config.useWorktree || !this.isGitRepo()) {
218281
return null;
@@ -674,6 +737,18 @@ class ClaudeSM {
674737
case '--no-model-routing':
675738
this.config.useModelRouting = false;
676739
break;
740+
case '--sweep':
741+
this.config.useSweep = true;
742+
break;
743+
case '--no-sweep':
744+
this.config.useSweep = false;
745+
break;
746+
case '--greptile':
747+
this.config.useGreptile = true;
748+
break;
749+
case '--no-greptile':
750+
this.config.useGreptile = false;
751+
break;
677752
default:
678753
claudeArgs.push(arg);
679754
}
@@ -856,6 +931,11 @@ class ClaudeSM {
856931
}
857932
}
858933

934+
// Ensure Greptile MCP server is registered if enabled
935+
if (this.config.useGreptile) {
936+
this.ensureGreptileMcp();
937+
}
938+
859939
// Start WhatsApp services if enabled
860940
if (this.config.useWhatsApp) {
861941
console.log(
@@ -865,6 +945,31 @@ class ClaudeSM {
865945
}
866946

867947
console.log();
948+
949+
// Launch via Sweep PTY wrapper if enabled
950+
if (this.config.useSweep) {
951+
const claudeBin = this.resolveClaudeBin();
952+
if (!claudeBin) {
953+
console.error(chalk.red('Claude CLI not found.'));
954+
process.exit(1);
955+
return;
956+
}
957+
console.log(
958+
chalk.cyan('[Sweep] Launching Claude with prediction bar...')
959+
);
960+
console.log(chalk.gray('─'.repeat(42)));
961+
try {
962+
await launchWrapper({
963+
claudeBin,
964+
claudeArgs,
965+
});
966+
} catch (error) {
967+
console.error(chalk.red((error as Error).message));
968+
process.exit(1);
969+
}
970+
return;
971+
}
972+
868973
console.log(chalk.gray('Starting Claude...'));
869974
console.log(chalk.gray('─'.repeat(42)));
870975

@@ -1041,6 +1146,12 @@ configCmd
10411146
console.log(
10421147
` defaultModelRouting: ${config.defaultModelRouting ? chalk.green('true') : chalk.gray('false')}`
10431148
);
1149+
console.log(
1150+
` defaultSweep: ${config.defaultSweep ? chalk.green('true') : chalk.gray('false')}`
1151+
);
1152+
console.log(
1153+
` defaultGreptile: ${config.defaultGreptile ? chalk.green('true') : chalk.gray('false')}`
1154+
);
10441155
console.log(chalk.gray(`\nConfig: ${getConfigPath()}`));
10451156
});
10461157

@@ -1062,14 +1173,16 @@ configCmd
10621173
whatsapp: 'defaultWhatsApp',
10631174
'model-routing': 'defaultModelRouting',
10641175
modelrouting: 'defaultModelRouting',
1176+
sweep: 'defaultSweep',
1177+
greptile: 'defaultGreptile',
10651178
};
10661179

10671180
const configKey = keyMap[key];
10681181
if (!configKey) {
10691182
console.log(chalk.red(`Unknown key: ${key}`));
10701183
console.log(
10711184
chalk.gray(
1072-
'Valid keys: worktree, sandbox, chrome, tracing, remote, notify-done, whatsapp'
1185+
'Valid keys: worktree, sandbox, chrome, tracing, remote, notify-done, whatsapp, sweep, greptile'
10731186
)
10741187
);
10751188
process.exit(1);
@@ -1185,6 +1298,31 @@ configCmd
11851298
console.log(chalk.green('Model routing disabled by default'));
11861299
});
11871300

1301+
configCmd
1302+
.command('greptile-on')
1303+
.description(
1304+
'Enable Greptile AI code review by default (requires GREPTILE_API_KEY)'
1305+
)
1306+
.action(() => {
1307+
const config = loadSMConfig();
1308+
config.defaultGreptile = true;
1309+
saveSMConfig(config);
1310+
console.log(chalk.green('Greptile enabled by default'));
1311+
if (!process.env['GREPTILE_API_KEY']) {
1312+
console.log(chalk.gray('Set GREPTILE_API_KEY in .env to activate'));
1313+
}
1314+
});
1315+
1316+
configCmd
1317+
.command('greptile-off')
1318+
.description('Disable Greptile AI code review by default')
1319+
.action(() => {
1320+
const config = loadSMConfig();
1321+
config.defaultGreptile = false;
1322+
saveSMConfig(config);
1323+
console.log(chalk.green('Greptile disabled by default'));
1324+
});
1325+
11881326
// Main command (default action when no subcommand)
11891327
program
11901328
.option('-w, --worktree', 'Create isolated worktree for this instance')
@@ -1215,6 +1353,10 @@ program
12151353
.option('--ollama', 'Force Ollama provider for this session')
12161354
.option('--model-routing', 'Enable model routing')
12171355
.option('--no-model-routing', 'Disable model routing')
1356+
.option('--sweep', 'Enable Sweep next-edit predictions (PTY wrapper)')
1357+
.option('--no-sweep', 'Disable Sweep predictions')
1358+
.option('--greptile', 'Enable Greptile AI code review (MCP server)')
1359+
.option('--no-greptile', 'Disable Greptile integration')
12181360
.helpOption('-h, --help', 'Display help')
12191361
.allowUnknownOption(true)
12201362
.action(async (_options) => {

src/cli/commands/sweep.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { join } from 'path';
1111
import {
1212
createServerManager,
1313
createPredictionClient,
14+
launchWrapper,
1415
SweepServerConfig,
1516
DEFAULT_SERVER_CONFIG,
1617
} from '../../features/sweep/index.js';
@@ -285,6 +286,25 @@ Predictions are triggered after file edits via Claude Code hooks.
285286
}
286287
});
287288

289+
// Wrap command
290+
cmd
291+
.command('wrap')
292+
.description('Launch Claude Code with Sweep prediction status bar')
293+
.option('--claude-bin <path>', 'Path to claude binary')
294+
.allowUnknownOption(true)
295+
.action(async (options, command) => {
296+
try {
297+
const claudeArgs = command.args || [];
298+
await launchWrapper({
299+
claudeBin: options.claudeBin,
300+
claudeArgs,
301+
});
302+
} catch (error) {
303+
console.error(chalk.red((error as Error).message));
304+
process.exit(1);
305+
}
306+
});
307+
288308
return cmd;
289309
}
290310

src/core/config/feature-flags.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface FeatureFlags {
1616
aiSummaries: boolean;
1717
skills: boolean;
1818
ralph: boolean;
19+
greptile: boolean;
1920
}
2021

2122
/**
@@ -68,6 +69,12 @@ export function isFeatureEnabled(feature: keyof FeatureFlags): boolean {
6869
// Ralph enabled by default in development (unless explicitly disabled)
6970
// For npm package users, must be explicitly enabled
7071
return process.env['STACKMEMORY_RALPH'] !== 'false';
72+
case 'greptile':
73+
// Greptile enabled when API key is available
74+
return (
75+
process.env['STACKMEMORY_GREPTILE'] !== 'false' &&
76+
!!process.env['GREPTILE_API_KEY']
77+
);
7178
default:
7279
return false;
7380
}
@@ -85,6 +92,7 @@ export function getFeatureFlags(): FeatureFlags {
8592
aiSummaries: isFeatureEnabled('aiSummaries'),
8693
skills: isFeatureEnabled('skills'),
8794
ralph: isFeatureEnabled('ralph'),
95+
greptile: isFeatureEnabled('greptile'),
8896
};
8997
}
9098

@@ -115,5 +123,8 @@ export function logFeatureStatus(): void {
115123
console.log(
116124
` Ralph: ${flags.ralph ? 'enabled' : 'disabled (set STACKMEMORY_RALPH=true)'}`
117125
);
126+
console.log(
127+
` Greptile: ${flags.greptile ? 'enabled' : 'disabled (no GREPTILE_API_KEY)'}`
128+
);
118129
}
119130
}

src/features/sweep/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,11 @@ export * from './types.js';
99
export * from './prompt-builder.js';
1010
export * from './prediction-client.js';
1111
export * from './sweep-server-manager.js';
12+
export * from './state-watcher.js';
13+
export * from './status-bar.js';
14+
export * from './tab-interceptor.js';
15+
export {
16+
PtyWrapper,
17+
launchWrapper,
18+
type PtyWrapperConfig,
19+
} from './pty-wrapper.js';

0 commit comments

Comments
 (0)