diff --git a/README.md b/README.md index 2376092..2aa8679 100644 --- a/README.md +++ b/README.md @@ -124,15 +124,22 @@ Set up your API key and install the MCP server into your AI agents. one init ``` -Supports Claude Code, Claude Desktop, Cursor, Windsurf, Codex, and Kiro. Installs globally by default, or per-project with `-p` so your team can share configs (each person uses their own API key). +Supports Claude Code, Claude Desktop, Cursor, Windsurf, Codex, and Kiro. -If you've already set up, `one init` shows your current status and lets you update your key, install to more agents, or reconfigure. +**Global vs. project scope.** `one init` is interactive and asks where the setup should live: + +- **Global** (`~/.one/config.json`) — applies to every folder. Best when you only need one workspace / API key. +- **Project** (`~/.one/projects//config.json`) — scoped to the current project, stored under your home directory so secrets never land in git. Use this when different projects need different API keys, connections, or access control. + +When you run `one` in a project, it uses the project config if one exists and falls back to the global config otherwise. Use `one config path` to see which config is active and the full resolution order. + +If you've already set up, `one init` shows your current status for the active scope and lets you update your key, install to more agents, or reconfigure. | Flag | What it does | |------|-------------| | `-y` | Skip confirmations | -| `-g` | Install globally (default) | -| `-p` | Install for current project only | +| `-g` | Non-interactive: write the One config globally (`~/.one/config.json`) | +| `-p` | Non-interactive: write the One config for this project (`~/.one/projects//config.json`) | ### `one add ` diff --git a/package-lock.json b/package-lock.json index 6e996b0..d3ebbe9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@withone/cli", - "version": "1.28.0", + "version": "1.29.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@withone/cli", - "version": "1.28.0", + "version": "1.29.0", "dependencies": { "@clack/prompts": "^0.9.1", "commander": "^13.1.0", diff --git a/package.json b/package.json index a1d8e16..4c80dc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@withone/cli", - "version": "1.28.0", + "version": "1.29.0", "description": "CLI for managing One", "type": "module", "files": [ diff --git a/skills/one/SKILL.md b/skills/one/SKILL.md index 86bd6a8..6a1b445 100644 --- a/skills/one/SKILL.md +++ b/skills/one/SKILL.md @@ -22,6 +22,8 @@ description: | You have access to the One CLI which lets you interact with 250+ third-party platforms through their APIs. Always include the `--agent` flag right after `one` for structured JSON output. +If the user wants a separate API key / connections for a specific project (vs. their default), walk them through running `one init` from that project folder and picking the "project" scope — see `references/scoping.md`. + ## Core Workflow: search -> knowledge -> execute Always follow this sequence when the user wants to do something on a connected platform: diff --git a/skills/one/references/scoping.md b/skills/one/references/scoping.md new file mode 100644 index 0000000..bd43493 --- /dev/null +++ b/skills/one/references/scoping.md @@ -0,0 +1,35 @@ +# Project vs. global config + +The One CLI can be configured at two scopes: + +- **Global** — `~/.one/config.json`. Applies everywhere the user runs `one`. +- **Project** — `~/.one/projects//config.json`, where `` is the project root path with slashes replaced by dashes (e.g. `/Users/jane/acme` → `-Users-jane-acme`). Only applies when running `one` from inside that project folder. + +**Resolution order:** env vars → `.onerc` in cwd → project config → global config. Project config wins when present; otherwise the CLI falls back to the global config. + +## When to suggest project scope + +Suggest project scope when the user wants any of the following for a specific folder only, without changing their default setup: + +- A different One API key (e.g. sandbox workspace for a client project) +- A different set of connections / connection keys +- Different access control (permissions, scoped connections, knowledge-only mode) + +## How to set it up + +Do **not** hand-edit `.onerc` or config files. Walk the user through the interactive init: + +```bash +cd /path/to/the/project +one init +``` + +When `init` asks "Where should this setup live?", pick **"This project only"**. Init will write the config to `~/.one/projects//config.json` and everything else (skill install, MCP) stays untouched. + +To see which config is currently active and the full fallback chain: + +```bash +one --agent config path +``` + +To switch an existing project back to using the global config, delete its project config file — the CLI will automatically fall back to global on the next run. diff --git a/src/commands/init.ts b/src/commands/init.ts index c76413c..5e09ab4 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -4,7 +4,22 @@ import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { fileURLToPath } from 'node:url'; -import { writeConfig, readConfig, getConfigPath, getApiBase, getAccessControl } from '../lib/config.js'; +import { + writeConfig, + readConfig, + getConfigPath, + getApiBase, + getAccessControl, + resolveConfig, + readGlobalConfig, + readProjectConfig, + getGlobalConfigPath, + getProjectConfigPath, + getProjectRoot, + globalConfigExists, + projectConfigExists, + type ConfigScope, +} from '../lib/config.js'; import { getAllAgents, installMcpConfig, @@ -29,21 +44,127 @@ export async function initCommand(options: { yes?: boolean; global?: boolean; pr output.error('This command requires interactive input. Run without --agent.'); } - const existingConfig = readConfig(); - printBanner(); + // Determine which scope (global vs project) this init run should edit. + const scope = await chooseConfigScope(options); + if (scope === null) { + p.cancel('Setup cancelled.'); + return; + } + + // Read the config at the chosen scope directly — not the resolved one, + // which would fall back to global when editing an uninitialized project. + const existingConfig = scope === 'project' ? readProjectConfig() : readGlobalConfig(); + if (existingConfig) { - await handleExistingConfig(existingConfig.apiKey, options); + await handleExistingConfig(existingConfig.apiKey, scope, options); return; } - await freshSetup(options); + await freshSetup(scope, options); +} + +// ── Scope picker ───────────────────────────────────────────────────── +// +// Config resolution at read-time is: project → global. So when a user runs +// `one init` inside a project that already has a project config, we edit +// that. When they run it in a fresh project with a global config already +// set up, we default to creating a project config so they can have a +// dedicated setup without touching their global one. When nothing exists +// anywhere, we default to global so the single setup applies everywhere. + +async function chooseConfigScope( + options: { global?: boolean; project?: boolean }, +): Promise { + // Explicit flags skip the picker — used by scripts/agents. + if (options.global) return 'global'; + if (options.project) return 'project'; + + const resolved = resolveConfig(); + const hasGlobal = globalConfigExists(); + const hasProject = projectConfigExists(); + const projectRoot = resolved.projectRoot; + const projectName = path.basename(projectRoot); + const homeGlobal = tildify(getGlobalConfigPath()); + const homeProject = tildify(getProjectConfigPath(projectRoot)); + + // If a project config already exists for this cwd, go straight into + // editing it — but let the user flip to global editing from the menu. + if (hasProject) { + console.log(); + console.log(` ${pc.dim('Project:')} ${projectName} ${pc.dim(projectRoot)}`); + console.log(` ${pc.bold('Active config:')} ${pc.cyan('project')} ${pc.dim('· ' + homeProject)}`); + console.log(); + + if (hasGlobal) { + const which = await p.select({ + message: 'Which config do you want to edit?', + options: [ + { value: 'project', label: `This project (${projectName})`, hint: homeProject }, + { value: 'global', label: 'Global (all folders)', hint: homeGlobal }, + ], + initialValue: 'project', + }); + if (p.isCancel(which)) return null; + return which; + } + return 'project'; + } + + // No project config yet — explain the choice and pick a sensible default. + console.log(); + console.log(` ${pc.bold('Initializing One')}`); + console.log(` ${pc.dim('─'.repeat(42))}`); + console.log(` ${pc.dim('Project:')} ${projectName} ${pc.dim(projectRoot)}`); + console.log(` ${pc.dim('Global:')} ${hasGlobal ? pc.green('✓ configured') : pc.yellow('— not set up')} ${pc.dim(homeGlobal)}`); + console.log(` ${pc.dim('Project:')} ${pc.yellow('— not set up')} ${pc.dim(homeProject)}`); + console.log(); + + const defaultScope: ConfigScope = hasGlobal ? 'project' : 'global'; + const hint = hasGlobal + ? 'Your global config stays as-is. This folder gets its own setup.' + : 'No global config yet — this becomes your default for every folder.'; + p.note(hint, defaultScope === 'project' ? 'Recommended: project' : 'Recommended: global'); + + const which = await p.select({ + message: 'Where should this setup live?', + options: [ + { + value: 'project', + label: `This project only (${projectName})`, + hint: 'different API key / connections just for this folder', + }, + { + value: 'global', + label: 'Globally (all folders)', + hint: 'applies everywhere you run `one`', + }, + ], + initialValue: defaultScope, + }); + + if (p.isCancel(which)) return null; + return which; +} + +function tildify(filePath: string): string { + const home = os.homedir(); + return filePath.startsWith(home) ? '~' + filePath.slice(home.length) : filePath; +} + +function scopeLabel(scope: ConfigScope): string { + return scope === 'project' ? pc.cyan('[project]') : pc.magenta('[global]'); +} + +function scopedMessage(scope: ConfigScope, message: string): string { + return `${scopeLabel(scope)} ${message}`; } // ── Status display + action menu when config already exists ────────── async function handleExistingConfig( apiKey: string, + scope: ConfigScope, options: { yes?: boolean; global?: boolean; project?: boolean }, ): Promise { const statuses = getAgentStatuses(); @@ -51,13 +172,15 @@ async function handleExistingConfig( // Display current setup const masked = maskApiKey(apiKey); const skillInstalled = isSkillInstalled(); + const activeConfigPath = + scope === 'project' ? getProjectConfigPath() : getGlobalConfigPath(); console.log(); - console.log(` ${pc.bold('Current Setup')}`); + console.log(` ${pc.bold('Current Setup')} ${scopeLabel(scope)}`); console.log(` ${pc.dim('─'.repeat(42))}`); console.log(` ${pc.dim('API Key:')} ${masked}`); console.log(` ${pc.dim('Skill:')} ${skillInstalled ? pc.green('installed') : pc.yellow('not installed')}`); - console.log(` ${pc.dim('Config:')} ${getConfigPath()}`); + console.log(` ${pc.dim('Config:')} ${tildify(activeConfigPath)}`); // Show access control summary if non-default settings are configured const ac = getAccessControl(); @@ -111,7 +234,7 @@ async function handleExistingConfig( }); const action = await p.select({ - message: 'What would you like to do?', + message: scopedMessage(scope, 'What would you like to do?'), options: actionOptions, }); @@ -140,24 +263,24 @@ async function handleExistingConfig( p.outro('Done.'); break; case 'update-key': - await handleUpdateKey(statuses); + await handleUpdateKey(statuses, scope); break; case 'access-control': await configCommand(); break; case 'start-fresh': - await freshSetup({ yes: true }); + await freshSetup(scope, { yes: true }); break; } } // ── Action handlers ────────────────────────────────────────────────── -async function handleUpdateKey(statuses: AgentStatus[]): Promise { - p.note(`Get your API key at:\n${pc.cyan(getApiKeyUrl())}`, 'API Key'); +async function handleUpdateKey(statuses: AgentStatus[], scope: ConfigScope): Promise { + p.note(`Get your API key at:\n${pc.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`); const openBrowser = await p.confirm({ - message: 'Open browser to get API key?', + message: scopedMessage(scope, 'Open browser to get API key?'), initialValue: true, }); @@ -171,7 +294,7 @@ async function handleUpdateKey(statuses: AgentStatus[]): Promise { } const newKey = await p.text({ - message: 'Enter your new One API key:', + message: scopedMessage(scope, 'Enter your new One API key:'), placeholder: 'sk_live_...', validate: (value) => { if (!value) return 'API key is required'; @@ -216,14 +339,19 @@ async function handleUpdateKey(statuses: AgentStatus[]): Promise { } } - // Update config (preserve accessControl) - const config = readConfig(); - writeConfig({ - apiKey: newKey, - installedAgents: config?.installedAgents ?? [], - createdAt: config?.createdAt ?? new Date().toISOString(), - accessControl: config?.accessControl, - }); + // Update config (preserve accessControl) at the active scope. + const current = scope === 'project' ? readProjectConfig() : readGlobalConfig(); + writeConfig( + { + apiKey: newKey, + installedAgents: current?.installedAgents ?? [], + createdAt: current?.createdAt ?? new Date().toISOString(), + accessControl: current?.accessControl, + apiBase: current?.apiBase, + cacheTtl: current?.cacheTtl, + }, + scope, + ); if (reinstalled.length > 0) { p.log.success(`Updated MCP configs: ${reinstalled.join(', ')}`); @@ -665,12 +793,15 @@ async function promptAndInstallMcp( // ── First-run setup (no existing config) ───────────────────────────── -async function freshSetup(options: { yes?: boolean; global?: boolean; project?: boolean }): Promise { +async function freshSetup( + scope: ConfigScope, + options: { yes?: boolean; global?: boolean; project?: boolean }, +): Promise { // Step 1: Get API key - p.note(`Get your API key at:\n${pc.cyan(getApiKeyUrl())}`, 'API Key'); + p.note(`Get your API key at:\n${pc.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`); const openBrowser = await p.confirm({ - message: 'Open browser to get API key?', + message: scopedMessage(scope, 'Open browser to get API key?'), initialValue: true, }); @@ -684,7 +815,7 @@ async function freshSetup(options: { yes?: boolean; global?: boolean; project?: } const apiKey = await p.text({ - message: 'Enter your One API key:', + message: scopedMessage(scope, 'Enter your One API key:'), placeholder: 'sk_live_...', validate: (value) => { if (!value) return 'API key is required'; @@ -715,12 +846,15 @@ async function freshSetup(options: { yes?: boolean; global?: boolean; project?: spinner.stop('API key validated'); - // Save API key to config - writeConfig({ - apiKey, - installedAgents: [], - createdAt: new Date().toISOString(), - }); + // Save API key to config at the chosen scope + writeConfig( + { + apiKey, + installedAgents: [], + createdAt: new Date().toISOString(), + }, + scope, + ); // Step 2: Install skill await promptSkillInstall(); @@ -728,9 +862,18 @@ async function freshSetup(options: { yes?: boolean; global?: boolean; project?: // Step 3: Connect integrations await promptConnectIntegrations(apiKey); + const savedPath = + scope === 'project' ? getProjectConfigPath() : getGlobalConfigPath(); + + const resolutionHint = + scope === 'project' + ? `When you run ${pc.cyan('one')} from ${pc.bold(path.basename(getProjectRoot()))}, it uses this project config.\n` + + `From anywhere else, it falls back to your global config.` + : `This config applies to every folder unless a project config is set.`; + p.note( - `Config saved to: ${pc.dim(getConfigPath())}`, - 'Setup Complete' + `${scopeLabel(scope)} Config saved to:\n${pc.dim(tildify(savedPath))}\n\n${resolutionHint}`, + 'Setup Complete', ); printOnboardingPrompt(); diff --git a/src/index.ts b/src/index.ts index 5791dfa..0f64135 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,13 @@ import { createRequire } from 'module'; import { Command } from 'commander'; import { initCommand } from './commands/init.js'; import { configCommand } from './commands/config.js'; +import { + resolveConfig, + getGlobalConfigPath, + getProjectConfigPath, + globalConfigExists, + projectConfigExists, +} from './lib/config.js'; import { connectionAddCommand, connectionListCommand, connectionDeleteCommand } from './commands/connection.js'; import { platformsCommand } from './commands/platforms.js'; import { actionsSearchCommand, actionsKnowledgeCommand, actionsExecuteCommand } from './commands/actions.js'; @@ -131,10 +138,10 @@ program.hook('postAction', async () => { program .command('init') - .description('Set up One and install MCP to your AI agents') + .description('Set up One and install MCP to your AI agents (interactive: picks global or project scope)') .option('-y, --yes', 'Skip confirmations') - .option('-g, --global', 'Install MCP globally (available in all projects)') - .option('-p, --project', 'Install MCP for this project only (creates .mcp.json)') + .option('-g, --global', 'Write the One config globally (~/.one/config.json) — skips the scope picker') + .option('-p, --project', 'Write the One config for this project only (~/.one/projects//) — skips the scope picker') .action(async (options) => { await initCommand(options); }); @@ -147,6 +154,46 @@ const config = program await configCommand(); }); +config + .command('path') + .description('Show the active config path, scope, and the fallback chain (project → global)') + .action(() => { + const resolved = resolveConfig(); + const globalPath = getGlobalConfigPath(); + const projectPath = getProjectConfigPath(resolved.projectRoot); + const hasGlobal = globalConfigExists(); + const hasProject = projectConfigExists(resolved.projectRoot); + + if (isAgentMode()) { + outputJson({ + command: 'config path', + scope: resolved.scope, + path: resolved.path, + projectRoot: resolved.projectRoot, + projectSlug: resolved.projectSlug, + fallback: { + project: { path: projectPath, exists: hasProject }, + global: { path: globalPath, exists: hasGlobal }, + }, + }); + return; + } + + if (!resolved.scope) { + console.log('No One config found.'); + console.log(` project: ${projectPath} (not set up)`); + console.log(` global: ${globalPath} (not set up)`); + console.log("\nRun 'one init' to get started."); + return; + } + + console.log(`Active: ${resolved.scope}`); + console.log(`Path: ${resolved.path}`); + console.log(`\nResolution order (first match wins):`); + console.log(` 1. project ${projectPath} ${hasProject ? '✓' : '—'}`); + console.log(` 2. global ${globalPath} ${hasGlobal ? '✓' : '—'}`); + }); + const configSkills = config .command('skills') .description('Manage locally-installed skill files'); diff --git a/src/lib/config.ts b/src/lib/config.ts index 99c5a83..609c2a9 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -5,34 +5,168 @@ import type { Config, AccessControlSettings, PermissionLevel } from './types.js' const CONFIG_DIR = path.join(os.homedir(), '.one'); const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json'); +const PROJECTS_DIR = path.join(CONFIG_DIR, 'projects'); -export function getConfigPath(): string { - return CONFIG_FILE; +export type ConfigScope = 'project' | 'global'; + +export interface ResolvedConfig { + config: Config | null; + scope: ConfigScope | null; // null when no config exists anywhere + path: string; // path that was read (or would be read) + projectRoot: string; // detected project root for cwd + projectSlug: string; // slug used for project config dir } -export function configExists(): boolean { - return fs.existsSync(CONFIG_FILE); +// ── Project detection ──────────────────────────────────────────────── + +/** + * Walk up from cwd looking for a project marker (.git, package.json). + * Falls back to cwd if nothing is found. + */ +export function getProjectRoot(cwd: string = process.cwd()): string { + let dir = path.resolve(cwd); + const root = path.parse(dir).root; + while (dir !== root) { + if ( + fs.existsSync(path.join(dir, '.git')) || + fs.existsSync(path.join(dir, 'package.json')) + ) { + return dir; + } + dir = path.dirname(dir); + } + return path.resolve(cwd); } -export function readConfig(): Config | null { - if (!configExists()) { - return null; +/** + * Encode an absolute path into a slug, matching Claude Code's convention: + * replace path separators with '-'. e.g. + * /Users/moe/projects/acme → -Users-moe-projects-acme + */ +export function getProjectSlug(projectRoot: string = getProjectRoot()): string { + return projectRoot.replace(/[\\/]/g, '-'); +} + +export function getProjectConfigDir(projectRoot: string = getProjectRoot()): string { + return path.join(PROJECTS_DIR, getProjectSlug(projectRoot)); +} + +export function getProjectConfigPath(projectRoot: string = getProjectRoot()): string { + return path.join(getProjectConfigDir(projectRoot), 'config.json'); +} + +export function getGlobalConfigPath(): string { + return CONFIG_FILE; +} + +// ── Resolver ───────────────────────────────────────────────────────── + +/** + * Resolve which config to use for the current cwd. Project config wins when + * present; otherwise the global config. Callers that need scope-awareness + * should use this; convenience wrappers below preserve the legacy API. + */ +export function resolveConfig(): ResolvedConfig { + const projectRoot = getProjectRoot(); + const projectSlug = getProjectSlug(projectRoot); + const projectPath = getProjectConfigPath(projectRoot); + + if (fs.existsSync(projectPath)) { + const config = readConfigFile(projectPath); + if (config) { + return { config, scope: 'project', path: projectPath, projectRoot, projectSlug }; + } } + + if (fs.existsSync(CONFIG_FILE)) { + const config = readConfigFile(CONFIG_FILE); + if (config) { + return { config, scope: 'global', path: CONFIG_FILE, projectRoot, projectSlug }; + } + } + + return { config: null, scope: null, path: CONFIG_FILE, projectRoot, projectSlug }; +} + +function readConfigFile(filePath: string): Config | null { try { - const content = fs.readFileSync(CONFIG_FILE, 'utf-8'); + const content = fs.readFileSync(filePath, 'utf-8'); return JSON.parse(content) as Config; } catch { return null; } } -export function writeConfig(config: Config): void { +// ── Legacy API (preserves existing call sites) ─────────────────────── + +export function getConfigPath(): string { + return resolveConfig().path; +} + +export function configExists(): boolean { + return resolveConfig().config !== null; +} + +export function globalConfigExists(): boolean { + return fs.existsSync(CONFIG_FILE); +} + +export function projectConfigExists(projectRoot: string = getProjectRoot()): boolean { + return fs.existsSync(getProjectConfigPath(projectRoot)); +} + +export function getActiveScope(): ConfigScope | null { + return resolveConfig().scope; +} + +export function readConfig(): Config | null { + return resolveConfig().config; +} + +/** + * Read only the global config, regardless of project scope. Used by init + * when presenting scope choices or switching between them. + */ +export function readGlobalConfig(): Config | null { + if (!fs.existsSync(CONFIG_FILE)) return null; + return readConfigFile(CONFIG_FILE); +} + +/** + * Read only the project config for the current cwd, regardless of fallback. + */ +export function readProjectConfig(): Config | null { + const projectPath = getProjectConfigPath(); + if (!fs.existsSync(projectPath)) return null; + return readConfigFile(projectPath); +} + +/** + * Write config. When `scope` is omitted, writes to whichever scope is + * currently active (project if one exists for cwd, else global). This keeps + * callers like updateAccessControl/updateApiBase scope-preserving. + */ +export function writeConfig(config: Config, scope?: ConfigScope): void { + const targetScope: ConfigScope = scope ?? resolveConfig().scope ?? 'global'; + + if (targetScope === 'project') { + const dir = getProjectConfigDir(); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); + } + const filePath = getProjectConfigPath(); + fs.writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 0o600 }); + return; + } + if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { mode: 0o700 }); } fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 }); } +// ── .onerc override (unchanged behavior) ───────────────────────────── + function readOneRc(): Record { const rcPath = path.join(process.cwd(), '.onerc'); if (!fs.existsSync(rcPath)) return {}; @@ -56,7 +190,7 @@ function readOneRc(): Record { } export function getApiKey(): string | null { - // Priority: env var > .onerc > ~/.one/config.json + // Priority: env var > .onerc > project config > global config if (process.env.ONE_SECRET) return process.env.ONE_SECRET; const rc = readOneRc(); @@ -103,13 +237,6 @@ export function getAccessControl(): AccessControlSettings { const DEFAULT_API_BASE = 'https://api.withone.ai/v1'; export function getApiBase(): string { - // Priority: env var > .onerc > ~/.one/config.json > default - const envBase = process.env.ONE_API_BASE; - if (envBase) return `${envBase.replace(/\/+$/, '').replace(/\/v1$/, '')}/v1`; - - const rc = readOneRc(); - if (rc.ONE_API_BASE) return `${rc.ONE_API_BASE.replace(/\/+$/, '').replace(/\/v1$/, '')}/v1`; - const config = readConfig(); if (config?.apiBase) return `${config.apiBase}/v1`; return DEFAULT_API_BASE; diff --git a/src/lib/guide-content.ts b/src/lib/guide-content.ts index 1209dd5..5a014ae 100644 --- a/src/lib/guide-content.ts +++ b/src/lib/guide-content.ts @@ -5,7 +5,7 @@ export const GUIDE_OVERVIEW = `# One CLI — Agent Guide ## Setup -1. Run \`one init\` to configure your API key +1. Run \`one init\` to configure your API key (interactive — can be global or per-project) 2. Run \`one add \` to connect platforms via OAuth 3. Run \`one --agent connection list\` to verify connections