Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Remove Agent Selection from Pi in the Init Command

## Goal

Stop prompting users to select and configure an agent during `berget code init` when they choose Pi. The Pi init flow should only install the provider and set `defaultProvider = 'berget'`.

## Background

Pi uses a single system prompt (`SYSTEM.md`), so the init wizard currently asks:

1. "Set up an agent for Pi?"
2. Choose an agent from a list
3. Write/replace `SYSTEM.md`

This adds friction. Pi users can configure agents manually; the init command should focus on auth + provider setup only.

OpenCode will keep its multi-agent selection flow (agents are stored as separate `.md` files).

## Files to Change

### 1. `src/commands/code/init.ts`

- Remove `initPiAgent` from the import from `./pi.js`
- In `configureTool()`, remove the `await initPiAgent({ cwd, files, homeDir, prompter, scope })` call

### 2. `src/commands/code/pi.ts`

- Delete the entire `initPiAgent` function
- Remove unused imports: `getAllAgents`, `toPiPrompt`
- `getPiAgentDir` is still used for auth/settings paths → **keep**

### 3. `src/agents/index.ts`

- Remove the `toPiPrompt` export (only used by `initPiAgent`)

### 4. `src/commands/code/__tests__/init.test.ts`

Update Pi-related tests to no longer include agent-selection prompts or assert `SYSTEM.md` creation.

| Action | Test(s) |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Remove agent prompts** | `sets up pi project with fresh install`, `preserves existing Pi settings when setting defaultProvider`, `creates api key for pi when no seat`, `login failure shows manual auth instructions` |
| **Delete** | `skips agent selection for pi project` (rename or repurpose), `sets up agent for pi project`, `sets up agent for pi globally`, `overwrites pi SYSTEM.md when content differs`, `throws CancelledError when user cancels at agent write confirmation (pi)` |

## Verification

- [ ] `npm run test:run` passes
- [ ] `npm run typecheck` passes
- [ ] `npm run lint` passes
4 changes: 0 additions & 4 deletions src/agents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,4 @@ export function toMarkdown(agent: Agent): string {
return `${frontmatter}---\n\n${systemPrompt}`;
}

export function toPiPrompt(agent: Agent): string {
return agent.systemPrompt;
}

export { type Agent, type AgentConfig } from './types.js';
114 changes: 5 additions & 109 deletions src/commands/code/__tests__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,7 @@ describe('runInit', () => {
commands: new FakeCommandRunner()
.handle('pi --version', 'mocked') // For checkInstalled
.handle('pi install', ''), // For actual install
prompter: new FakePrompter([
select('pi'),
select('project'),
confirm(true, 'Set up an agent for Pi?'),
select('fullstack'), // Agent selection
confirm(true, 'Create'),
]),
prompter: new FakePrompter([select('pi'), select('project')]),
});

await runInit(deps);
Expand All @@ -148,26 +142,15 @@ describe('runInit', () => {
expect(installCall?.args).toContain('npm:@bergetai/pi-provider');
});

it('skips agent selection for pi project', async () => {
it('completes pi init without agent prompts', async () => {
const deps = makeDeps({
commands: new FakeCommandRunner()
.handle('pi --version', 'mocked') // For checkInstalled
.handle('pi install', ''), // For actual install
prompter: new FakePrompter([
select('pi'),
select('project'),
confirm(false, 'Set up an agent for Pi?'),
]),
prompter: new FakePrompter([select('pi'), select('project')]),
});

await runInit(deps);

const files = deps.files as FakeFileStore;
const written = files.getWrittenFiles();
// Should not create any agent files
for (const path of written.keys()) {
expect(path).not.toContain('SYSTEM.md');
}
await expect(runInit(deps)).resolves.not.toThrow();
});
});

Expand Down Expand Up @@ -331,21 +314,6 @@ describe('runInit', () => {

await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
});

it('throws CancelledError when user cancels at agent write confirmation (pi)', async () => {
const deps = makeDeps({
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
prompter: new FakePrompter([
select('pi'),
select('project'),
confirm(true, 'Set up an agent for Pi?'),
select('fullstack'),
confirm(false, /Create|Overwrite/),
]),
});

await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
});
});

describe('file operations', () => {
Expand Down Expand Up @@ -440,13 +408,7 @@ describe('runInit', () => {
it('preserves existing Pi settings when setting defaultProvider', async () => {
const deps = makeDeps({
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
prompter: new FakePrompter([
select('pi'),
select('project'),
confirm(true, 'Set up an agent for Pi?'),
select('fullstack'),
confirm(true, 'Create'),
]),
prompter: new FakePrompter([select('pi'), select('project')]),
});

const files = deps.files as FakeFileStore;
Expand Down Expand Up @@ -581,9 +543,6 @@ describe('runInit', () => {
select('pi'),
select('project'),
confirm(true), // API key creation prompt
confirm(true, 'Set up an agent for Pi?'),
select('fullstack'),
confirm(true, 'Create'),
]),
});

Expand Down Expand Up @@ -683,44 +642,6 @@ describe('runInit', () => {
expect(written.has('/home/user/.config/opencode/agents/fullstack.md')).toBe(true);
});

it('sets up agent for pi project', async () => {
const deps = makeDeps({
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
prompter: new FakePrompter([
select('pi'),
select('project'),
confirm(true, 'Set up an agent for Pi?'),
select('fullstack'),
confirm(true, 'Create'),
]),
});

await runInit(deps);

const files = deps.files as FakeFileStore;
const written = files.getWrittenFiles();
expect(written.has('/home/user/project/.pi/SYSTEM.md')).toBe(true);
});

it('sets up agent for pi globally', async () => {
const deps = makeDeps({
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
prompter: new FakePrompter([
select('pi'),
select('global'),
confirm(true, 'Set up an agent for Pi?'),
select('backend'),
confirm(true, 'Create'),
]),
});

await runInit(deps);

const files = deps.files as FakeFileStore;
const written = files.getWrittenFiles();
expect(written.has('/home/user/.pi/agent/SYSTEM.md')).toBe(true);
});

it('skips writing identical opencode agent files', async () => {
const deps = makeDeps({
prompter: new FakePrompter([
Expand Down Expand Up @@ -764,31 +685,6 @@ describe('runInit', () => {
firstFrontend,
);
});

it('overwrites pi SYSTEM.md when content differs', async () => {
const files = new FakeFileStore();
files.seed('/home/user/project/.pi/SYSTEM.md', 'old agent content');

const deps = makeDeps({
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
files,
prompter: new FakePrompter([
select('pi'),
select('project'),
confirm(true, 'Set up an agent for Pi?'),
select('fullstack'),
confirm(true, 'SYSTEM.md already exists'),
]),
});

await runInit(deps);

const written = files.getWrittenFiles();
const content = written.get('/home/user/project/.pi/SYSTEM.md');
expect(content).not.toBe('old agent content');
// Pi doesn't use front matter, so check for system prompt content
expect(content).toContain('Fullstack Agent');
});
});

describe('nextSteps branching', () => {
Expand Down
3 changes: 1 addition & 2 deletions src/commands/code/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
initOpenCode,
initOpenCodeAgents,
} from './opencode.js';
import { getPiLabel, getPiState, initPi, initPiAgent } from './pi.js';
import { getPiLabel, getPiState, initPi } from './pi.js';
import { checkTool, promptForMissingTool } from './tool-check.js';

export interface InitCommandResult {
Expand Down Expand Up @@ -207,7 +207,6 @@ async function configureTool(
await initOpenCodeAgents({ cwd, files, homeDir, prompter, scope });
} else {
await initPi({ commands, cwd, files, homeDir, prompter, scope });
await initPiAgent({ cwd, files, homeDir, prompter, scope });
}
}

Expand Down
74 changes: 2 additions & 72 deletions src/commands/code/pi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import type { CommandRunner } from './ports/command-runner.js';
import type { FileStore } from './ports/file-store.js';
import type { Prompter } from './ports/prompter.js';

import { getAllAgents, toPiPrompt } from '../../agents/index.js';
import { CancelledError, CommandFailedError } from './errors.js';
import { CommandFailedError } from './errors.js';
import { readJsonMaybe, writeJsonFile } from './utils.js';
import { getPiAgentDir, getPiSettingsPath } from './xdg-paths.js';
import { getPiSettingsPath } from './xdg-paths.js';

const PI_PROVIDER = 'npm:@bergetai/pi-provider';
const PI_PROVIDER_NAME = '@bergetai/pi-provider';
Expand Down Expand Up @@ -92,75 +91,6 @@ export async function initPi(deps: InitPiDeps): Promise<void> {
}
}

export async function initPiAgent(deps: {
cwd: string;
files: FileStore;
homeDir: string;
prompter: Prompter;
scope: 'global' | 'project';
}): Promise<boolean> {
const { cwd, files, homeDir, prompter, scope } = deps;

const agents = getAllAgents().filter((a) => a.config.mode === 'primary');

if (agents.length === 0) {
return false;
}

const systemPath =
scope === 'project'
? path.join(cwd, '.pi', 'SYSTEM.md')
: path.join(getPiAgentDir(homeDir), 'SYSTEM.md');

prompter.note('Pi uses a single system prompt.', 'Agent Setup');

const shouldInitAgent = await prompter.confirm({
initialValue: false,
message: 'Set up an agent for Pi?',
});

if (!shouldInitAgent) return false;

const selectedAgentName = await prompter.select({
message: 'Choose an agent:',
options: agents.map((agent) => ({
hint: agent.config.description,
label: agent.config.name,
value: agent.config.name,
})),
});

const agent = agents.find((a) => a.config.name === selectedAgentName);
if (!agent) return false;

const systemExists = await files.exists(systemPath);
const confirmMsg = systemExists
? `SYSTEM.md already exists. Replace with ${agent.config.name}?`
: 'Create agent configuration?';

const shouldWrite = await prompter.confirm({
initialValue: true,
message: confirmMsg,
});

if (!shouldWrite) {
throw new CancelledError();
}

const s = prompter.spinner();
s.start('Writing agent configuration...');
try {
const systemDir = scope === 'project' ? path.join(cwd, '.pi') : getPiAgentDir(homeDir);
await files.mkdir(systemDir);
await files.writeFile(systemPath, toPiPrompt(agent));
s.stop(`Wrote agent configuration to ${systemPath}`);
} catch (error) {
s.stop('Failed to write agent configuration.');
throw error;
}
return true;
}

function hasPiProviderInSettings(settings: unknown): boolean {
if (!settings || typeof settings !== 'object') return false;
const packages = (settings as Record<string, unknown>).packages;
Expand Down
Loading