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
56 changes: 41 additions & 15 deletions src/commands/code/auth-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,50 @@ const TOOL_NAMES = {
} as const;

/**
* Tool-specific login instructions — the single source of truth, also used by
* init.ts#buildNextSteps. Authentication is ALWAYS performed by the tool
* itself (OpenCode /connect, Pi /login) — never by writing the tool's private
* auth store from the CLI. OpenCode V2 moved its credential store and silently
* ignores the V1 auth.json we used to write; delegating login to the tool is
* the only version-proof contract, and gives each tool its own Keycloak
* session (no shared refresh tokens).
* Tool-specific login methods — the single source of truth for all login
* copy (inline notes AND init.ts#buildNextSteps). Authentication is ALWAYS
* performed by the tool itself (OpenCode /connect, Pi /login) — never by
* writing the tool's private auth store from the CLI. OpenCode V2 moved its
* credential store and silently ignores the V1 auth.json we used to write;
* delegating login to the tool is the only version-proof contract, and gives
* each tool its own Keycloak session (no shared refresh tokens).
*/
export const SUBSCRIPTION_LOGIN_INSTRUCTIONS = {
opencode: 'Run `opencode`, type `/connect` and choose "Login with Berget".',
pi: 'Run `pi`, type `/login` and choose "Use a subscription" → "Berget AI".',
export const TOOL_LOGIN_METHODS = {
opencode: {
apiKey: 'choose "Enter Berget API Key manually"',
command: '/connect',
subscription: 'choose "Login with Berget"',
},
pi: {
apiKey: 'paste the key',
command: '/login',
subscription: 'choose "Use a subscription" → "Berget AI"',
},
} as const;

export const API_KEY_LOGIN_INSTRUCTIONS = {
opencode:
'Run `opencode`, type `/connect` and choose "Enter Berget API Key manually",\nor set the BERGET_API_KEY environment variable.',
pi: 'Run `pi`, type `/login` and paste the key,\nor set the BERGET_API_KEY environment variable.',
} as const;
type Tool = keyof typeof TOOL_LOGIN_METHODS;

const loginInstruction = (tool: Tool, method: 'apiKey' | 'subscription'): string =>
`Run \`${tool}\`, type \`${TOOL_LOGIN_METHODS[tool].command}\` and ${TOOL_LOGIN_METHODS[tool][method]}`;

export const SUBSCRIPTION_LOGIN_INSTRUCTIONS: Record<Tool, string> = {
opencode: `${loginInstruction('opencode', 'subscription')}.`,
pi: `${loginInstruction('pi', 'subscription')}.`,
};

export const API_KEY_LOGIN_INSTRUCTIONS: Record<Tool, string> = {
opencode: `${loginInstruction('opencode', 'apiKey')},\nor set the BERGET_API_KEY environment variable.`,
pi: `${loginInstruction('pi', 'apiKey')},\nor set the BERGET_API_KEY environment variable.`,
};

/**
* The two login alternatives presented as a bullet list in the final
* next-steps panel.
*/
export const LOGIN_METHOD_BULLETS: Record<Tool, string> = {
opencode: ` • ${TOOL_LOGIN_METHODS.opencode.subscription} (subscription), or\n • ${TOOL_LOGIN_METHODS.opencode.apiKey} — or set BERGET_API_KEY`,
pi: ` • ${TOOL_LOGIN_METHODS.pi.subscription} (subscription), or\n • ${TOOL_LOGIN_METHODS.pi.apiKey} — or set BERGET_API_KEY`,
};

export async function configureAuth(
deps: Pick<AuthDeps, 'apiKeyService' | 'files' | 'homeDir' | 'prompter' | 'seatStatusService'>,
Expand Down
13 changes: 8 additions & 5 deletions src/commands/code/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { FsFileStore } from './adapters/fs-file-store.js';
import { createSeatStatusService } from './adapters/seat-status.js';
import { SpawnCommandRunner } from './adapters/spawn-command-runner.js';
import {
API_KEY_LOGIN_INSTRUCTIONS,
configureAuth,
ensureCliAuth,
SUBSCRIPTION_LOGIN_INSTRUCTIONS,
LOGIN_METHOD_BULLETS,
TOOL_LOGIN_METHODS,
} from './auth-sync.js';
import { CancelledError, CommandFailedError, FatalError, PrerequisiteError } from './errors.js';
import {
Expand Down Expand Up @@ -166,9 +166,12 @@ function buildNextSteps(
}

if (toolConfigured && !authenticated) {
return tool === 'opencode'
? `Next steps:\n\n1. ${SUBSCRIPTION_LOGIN_INSTRUCTIONS.opencode}\n ${API_KEY_LOGIN_INSTRUCTIONS.opencode}\n2. Select model: /models`
: `Next steps:\n\n1. Restart Pi or run /reload\n2. ${SUBSCRIPTION_LOGIN_INSTRUCTIONS.pi}\n ${API_KEY_LOGIN_INSTRUCTIONS.pi}\n3. Select model: /model`;
const steps = [
...(tool === 'pi' ? ['Restart Pi or run /reload'] : []),
`Run \`${tool}\` and type \`${TOOL_LOGIN_METHODS[tool].command}\`, then either:\n${LOGIN_METHOD_BULLETS[tool]}`,
`Select model: ${tool === 'opencode' ? '/models' : '/model'}`,
];
return `Next steps:\n\n${steps.map((step, index) => `${index + 1}. ${step}`).join('\n')}`;
}

if (!toolConfigured && authenticated) {
Expand Down
Loading