create-agent-tui について
create-agent-tuiは、TypeScript製のエージェント向けターミナルUIを生成するスキルです。ゼロから開発する人を対象に、ツールやモジュール、表示スタイルを選べるプロジェクトを作成し、OpenRouterを利用します。
主な機能
- 01TypeScript製エージェントTUIの生成
- 02ツールとハーネスモジュールの選択
- 03セッション、承認、ログの設定
- 04入力、ツール表示、ローダーのカスタマイズ
- 05OpenRouterのモデル切り替えコマンド
活用シーン
- 01エージェントプロジェクトの新規作成
- 02既存エージェントへのツール追加
- 03セッション保存や承認フローの追加
- 04エージェントのターミナル操作の調整
MCP 対応
READMEではMCPを拡張ポイントとして挙げていますが、具体的な設定や機能は説明されていません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ openrouterteam/ skills --skill create-agent-tuiインストール後、Claude Code で/create-agent-tuiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.jsonREADME.mdreferences/input-styles.mdloader.mdmodules.mdserver-entry-points.mdslash-commands.mdsystem-prompt.mdtool-display.mdtools.mdtui.mdsample/.gitignorepackage-lock.jsonpackage.jsonsample/src/agent.tsbanner.tscli.tscommands.tsconfig.tsloader.tsrenderer.tsscreenshot-demos.tssession.tsterminal-bg.tssample/src/tools/custom.tsfile-edit.tsfile-read.tsfile-write.tsglob.tsgrep.ts詳細
エージェント TUI の作成
OpenRouter を対象とした、完全なエージェント TUI を TypeScript で生成します。生成されるプロジェクトでは、内部ループ(モデル呼び出し、ツール実行、停止条件)に @openrouter/agent を使用し、カスタマイズ可能なターミナルインターフェース、設定、セッション管理、ツール定義、エントリーポイントを含む外側のシェルを提供します。
アーキテクチャは、3 つの本番環境向けエージェントシステムを参考にしています。
- pi-mono/coding-agent — 3 層の分離、JSONL セッション、プラグイン可能なツール操作
- Claude Code — ツールのメタデータ(読み取り専用、破壊的操作、承認)、システムプロンプトの構成
- Codex CLI — 階層化された設定、セッションキャッシュを備えた承認フロー、構造化ログ
前提条件
- Node.js 18+
- openrouter.ai/settings/keys から取得した
OPENROUTER_API_KEY - SDK の完全なリファレンスについては、
openrouter-typescript-sdkスキルを参照してください
判断ツリー
| ユーザーの要望 | 対応 |
|---|---|
| エージェントをゼロから新規作成する | 以下のチェックリストを提示 → 生成ワークフローに従う |
| 既存のハーネスにツールを追加する | references/tools.md を読み、ツールのチェックリストのみを提示する |
| ハーネスモジュールを追加する | references/modules.md を読み、モジュールを生成する |
| API サーバーのエントリーポイントを追加する | references/server-entry-points.md を読む |
インタラクティブなツールチェックリスト
複数選択式のチェックリストとして提示してください。ON と記された項目は、デフォルトで事前選択されています。
OpenRouter サーバーツール(サーバー側、実装不要)
| ツール | 型文字列 | デフォルト | 設定 |
|---|---|---|---|
| ウェブ検索 | openrouter:web_search | ON | エンジン、最大結果数、ドメインフィルタリング |
| 日時 | openrouter:datetime | ON | タイムゾーン |
| 画像生成 | openrouter:image_generation | OFF | モデル、品質、サイズ、形式 |
サーバーツールは、ユーザー定義ツールとともに tools 配列に追加します。クライアント側のコードは不要です。OpenRouter が実行します。
ユーザー定義ツール(クライアント側、src/tools/ に生成)
| ツール | デフォルト | 説明 |
|---|---|---|
| ファイル読み取り | ON | オフセットと上限を指定してファイルを読み取り、画像を検出します |
| ファイル書き込み | ON | ファイルを書き込み/作成し、ディレクトリを自動作成します |
| ファイル編集 | ON | 差分検証を伴う検索と置換 |
| Glob/検索 | ON | glob パターンでファイルを検出します |
| Grep/検索 | ON | 正規表現でコンテンツを検索します |
| ディレクトリ一覧 | ON | ディレクトリの内容を一覧表示します |
| Shell/Bash | ON | タイムアウトと出力取得を設定してコマンドを実行します |
| JS REPL | OFF | 永続的な Node.js 環境 |
| サブエージェントの起動 | OFF | 子エージェントにタスクを委任します |
| 計画/ToDo | OFF | 複数ステップのタスク進行状況を追跡します |
| ユーザー入力の要求 | OFF | 構造化された複数選択式の質問 |
| ウェブ取得 | OFF | ウェブページを取得し、テキストを抽出します |
| 画像表示 | OFF | ローカル画像を Base64 として読み取ります |
| カスタムツールテンプレート | ON | ドメイン固有のツール向けの空のひな形 |
ハーネスモジュール(アーキテクチャコンポーネント)
| モジュール | デフォルト | 説明 |
|---|---|---|
| セッション永続化 | ON | JSONL 形式の追記専用会話ログ |
| ASCII ロゴバナー | OFF | 起動時にカスタム ASCII アートバナーを表示 — プロジェクト名を尋ねます |
| コンテキスト圧縮 | OFF | コンテキストが長くなったときに古いメッセージを要約します |
| システムプロンプトの構成 | OFF | 静的および動的なコンテキストから指示を組み立てます |
| ツール権限/承認 | OFF | 危険なツールの実行をユーザー確認で制御します |
| 構造化イベントログ | OFF | ツール呼び出し、API リクエスト、エラーのイベントを出力します |
@ ファイル参照 | OFF | @filename を使って、次のメッセージにファイルの内容を添付します |
! シェルショートカット | OFF | !command でシェルを実行し、出力をコンテキストに挿入します |
| 複数行入力 | OFF | Shift+Enter で複数行入力(raw ターミナルモードが必要) |
スラッシュコマンド(ユーザー向けREPLコマンド)
| コマンド | デフォルト | 説明 |
|---|---|---|
/model | ON | OpenRouter API 経由でモデルを切り替えます |
/new | ON | 新しい会話を開始します |
/help | ON | 利用可能なコマンドを一覧表示します |
/compact | OFF | コンテキストの圧縮を手動で実行します |
/session | OFF | セッションのメタデータとトークン使用量を表示します |
/export | OFF | 会話を Markdown として保存します |
スラッシュコマンドが有効な場合は、コマンドレジストリを含む src/commands.ts を生成します。仕様については references/slash-commands.md を参照してください。
ビジュアルのカスタマイズ(それぞれ単一選択として提示)
入力スタイル — プロンプトの表示方法です。references/input-styles.md を参照してください。
| スタイル | デフォルト | 説明 |
|---|---|---|
block | ON | › プロンプト付きの全幅背景ボックスで、ターミナルのテーマに合わせて変化します |
bordered | 入力の上下に水平の ─ 線を表示します | |
plain | エスケープシーケンスを使わない、シンプルな > readline プロンプトです | |
| その他 | 希望する内容をユーザーが説明し、カスタム入力スタイルを実装します |
ツールの表示 — 実行中のツール呼び出しの表示方法です。references/tool-display.md を参照してください。
| スタイル | デフォルト | 説明 |
|---|---|---|
grouped | ON | 太字のアクションラベルとツリー形式の出力 |
emoji | 呼び出しごとに ⚡/✓ マーカー、引数、実行時間を表示します | |
minimal | 集約した1行の要約を表示します | |
hidden | ツールの出力を表示しません | |
| その他 | 希望する内容をユーザーが説明し、カスタム表示を実装します |
ローダーアニメーション — モデルの応答を待っている間に表示します。references/loader.md を参照してください。
| スタイル | デフォルト | 説明 |
|---|---|---|
spinner | ON | テキストの左側に点字ドットのスピナー(⠋⠙⠹…)を表示します |
gradient | ローダーのテキスト上にスクロールするカラーシマーを表示します | |
minimal | 末尾にドット(Working···)を表示します | |
| その他 | 希望する内容をユーザーが説明し、カスタムアニメーションを実装します |
また、ローダーのテキストも尋ねてください(デフォルト:"Working")。
生成ワークフロー
チェックリストの選択内容を受け取ったら、次のワークフローに従います。
- [ ] Generate package.json with dependencies
- [ ] Generate src/config.ts (add showBanner field if ASCII Logo Banner is ON)
- [ ] Generate src/tools/index.ts wiring selected tools + server tools
- [ ] Generate selected tool files in src/tools/ (see Tool Pattern below, specs in references/tools.md)
- [ ] Generate src/agent.ts (core runner)
- [ ] Generate selected harness modules (specs in references/modules.md)
- [ ] Generate src/terminal-bg.ts (adaptive input background — see references/tui.md)
- [ ] Generate input style functions in src/cli.ts (block/bordered/plain — see references/input-styles.md)
- [ ] Generate src/renderer.ts (tool display — see references/tool-display.md)
- [ ] Generate src/loader.ts (loader animation — see references/loader.md)
- [ ] If slash commands selected: generate src/commands.ts (see references/slash-commands.md)
- [ ] If ASCII Logo Banner is ON: generate src/banner.ts (see ASCII Logo Banner section below)
- [ ] Generate src/cli.ts entry point (or src/server.ts — see references/server-entry-points.md)
- [ ] Generate .env.example with OPENROUTER_API_KEY=
- [ ] Verify: run npx tsc --noEmit to check types
ツールのパターン
ユーザー定義のツールはすべて、@openrouter/agent/tool を使った次のパターンに従います。以下に完全な例を1つ示します。references/tools.md にある他のツールもすべて同じ形式です。
import { tool } from '@openrouter/agent/tool';
import { z } from 'zod';
import { readFile, stat } from 'fs/promises';
const DEFAULT_LINE_LIMIT = 2000;
const MAX_LINE_CHARS = 2000;
export const fileReadTool = tool({
name: 'file_read',
description:
'Read the contents of a file. Output is capped at 2000 lines by default (use offset/limit to paginate) and any line longer than 2000 characters is truncated. When the response is truncated, the hint field tells you how to continue.',
inputSchema: z.object({
path: z.string().describe('Absolute path to the file'),
offset: z.number().optional().describe('Start reading from this line (1-indexed)'),
limit: z.number().optional().describe(`Maximum lines to return (default ${DEFAULT_LINE_LIMIT})`),
}),
execute: async ({ path, offset, limit }) => {
try {
const content = await readFile(path, 'utf-8');
const lines = content.split('\n');
const start = offset ? offset - 1 : 0;
const end = Math.min(start + (limit ?? DEFAULT_LINE_LIMIT), lines.length);
let longLines = 0;
const slice = lines.slice(start, end).map((line) => {
if (line.length <= MAX_LINE_CHARS) return line;
longLines++;
return line.slice(0, MAX_LINE_CHARS) + `… [line truncated, ${line.length - MAX_LINE_CHARS} chars dropped]`;
});
const tailTruncated = end < lines.length;
const truncated = tailTruncated || longLines > 0;
const hintParts: string[] = [`Showing lines ${start + 1}-${end} of ${lines.length}.`];
if (tailTruncated) hintParts.push(`Use offset=${end + 1} to continue.`);
if (longLines > 0) hintParts.push(`${longLines} line(s) exceeded ${MAX_LINE_CHARS} chars and were per-line truncated; use grep to fetch content from those lines.`);
return {
content: slice.join('\n'),
totalLines: lines.length,
...(truncated && {
truncated: true,
...(tailTruncated && { nextOffset: end + 1 }),
hint: hintParts.join(' '),
}),
};
} catch (err: any) {
if (err.code === 'ENOENT') return { error: `File not found: ${path}` };
if (err.code === 'EACCES') return { error: `Permission denied: ${path}` };
return { error: err.message };
}
},
});
他のすべてのツールの仕様については、references/tools.md を参照してください。
コアファイル
これらのファイルは常に生成されます。エージェントはチェックリストの選択内容に応じて調整します。
package.json
プロジェクトを初期化し、依存関係の最新バージョンをインストールします。
npm init -y
npm pkg set type=module
npm pkg set scripts.start="tsx src/cli.ts"
npm pkg set scripts.dev="tsx watch src/cli.ts"
npm install @openrouter/agent glob zod
npm install -D tsx typescript @types/node
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
src/config.ts
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
export interface DisplayConfig {
toolDisplay: 'emoji' | 'grouped' | 'minimal' | 'hidden';
reasoning: boolean;
inputStyle: 'block' | 'bordered' | 'plain';
}
export interface AgentConfig {
apiKey: string;
model: string;
systemPrompt: string;
maxSteps: number;
maxCost: number;
sessionDir: string;
showBanner: boolean;
display: DisplayConfig;
slashCommands: boolean;
}
const DEFAULTS: AgentConfig = {
apiKey: '',
model: 'anthropic/claude-opus-4.7',
systemPrompt: [
'You are a coding assistant with access to tools for reading, writing, editing, and searching files, and running shell commands.',
'',
'Current working directory: {cwd}',
'',
'Guidelines:',
'- Use your tools proactively. Explore the codebase to find answers instead of asking the user.',
'- Keep working until the task is fully resolved before responding.',
'- Do not guess or make up information — use your tools to verify.',
'- Be concise and direct.',
'- Show file paths clearly when working with files.',
'- Prefer grep and glob tools over shell commands for file search.',
'- When editing code, make minimal targeted changes consistent with the existing style.',
].join('\n'),
maxSteps: 20,
maxCost: 1.0,
sessionDir: '.sessions',
showBanner: false,
display: { toolDisplay: 'grouped', reasoning: false, inputStyle: 'block' },
slashCommands: true,
};
export function loadConfig(overrides: Partial<AgentConfig> = {}): AgentConfig {
let config = { ...DEFAULTS };
const configPath = resolve('agent.config.json');
if (existsSync(configPath)) {
const file = JSON.parse(readFileSync(configPath, 'utf-8'));
if (file.display) {
config.display = { ...config.display, ...file.display };
}
config = { ...config, ...file, display: config.display };
}
if (process.env.OPENROUTER_API_KEY) config.apiKey = process.env.OPENROUTER_API_KEY;
if (process.env.AGENT_MODEL) config.model = process.env.AGENT_MODEL;
if (process.env.AGENT_MAX_STEPS) config.maxSteps = Number(process.env.AGENT_MAX_STEPS);
if (process.env.AGENT_MAX_COST) config.maxCost = Number(process.env.AGENT_MAX_COST);
if (overrides.display) {
config.display = { ...config.display, ...overrides.display };
}
config = { ...config, ...overrides, display: config.display };
if (!config.apiKey) throw new Error('OPENROUTER_API_KEY is required.');
return config;
}
src/tools/index.ts
チェックリストの選択内容に応じてインポートを調整します。この例には、デフォルトでONのツールがすべて含まれています。
import { serverTool } from '@openrouter/agent';
import { fileReadTool } from './file-read.js';
import { fileWriteTool } from './file-write.js';
import { fileEditTool } from './file-edit.js';
import { globTool } from './glob.js';
import { grepTool } from './grep.js';
import { listDirTool } from './list-dir.js';
import { shellTool } from './shell.js';
export const tools = [
// User-defined tools — executed client-side
fileReadTool,
fileWriteTool,
fileEditTool,
globTool,
grepTool,
listDirTool,
shellTool,
// Server tools — executed by OpenRouter, no client implementation needed
serverTool({ type: 'openrouter:web_search' }),
serverTool({ type: 'openrouter:datetime', parameters: { timezone: 'UTC' } }),
];
src/agent.ts
import { OpenRouter } from '@openrouter/agent';
import type { Item } from '@openrouter/agent';
import { stepCountIs, maxCost } from '@openrouter/agent/stop-conditions';
import type { AgentConfig } from './config.js';
import { tools } from './tools/index.js';
export type ChatMessage = { role: 'user' | 'assistant' | 'system'; content: string };
export type AgentEvent =
| { type: 'text'; delta: string }
| { type: 'tool_call'; name: string; callId: string; args: Record<string, unknown> }
| { type: 'tool_result'; name: string; callId: string; output: string }
| { type: 'reasoning'; delta: string };
export async function runAgent(
config: AgentConfig,
input: string | ChatMessage[],
options?: { onEvent?: (event: AgentEvent) => void; signal?: AbortSignal },
) {
const client = new OpenRouter({ apiKey: config.apiKey });
const result = client.callModel({
model: config.model,
instructions: config.systemPrompt.replace('{cwd}', process.cwd()),
input: input as string | Item[],
tools,
stopWhen: [stepCountIs(config.maxSteps), maxCost(config.maxCost)],
});
if (options?.onEvent) {
// Track text length PER message item by id. A multi-step agent emits
// multiple OutputMessage items over the course of a single run (one per
// assistant turn between tool calls), and each one grows from 0 to its
// final length. A single global cursor breaks on the second message:
// when its length is smaller than the cursor from the first, the slice
// cuts mid-string and drops the start of the new message's text.
const textByItem = new Map<string, number>();
const callNames = new Map<string, string>();
for await (const item of result.getItemsStream()) {
if (options?.signal?.aborted) break;
if (item.type === 'message') {
const text = item.content
?.filter((c): c is { type: 'output_text'; text: string } => 'text' in c)
.map((c) => c.text)
.join('') ?? '';
const prev = textByItem.get(item.id) ?? 0;
if (text.length > prev) {
options.onEvent({ type: 'text', delta: text.slice(prev) });
textByItem.set(item.id, text.length);
}
} else if (item.type === 'function_call') {
callNames.set(item.callId, item.name);
if (item.status === 'completed') {
const args = (() => { try { return item.arguments ? JSON.parse(item.arguments) : {}; } catch { return {}; } })();
options.onEvent({ type: 'tool_call', name: item.name, callId: item.callId, args });
}
} else if (item.type === 'function_call_output') {
const out = typeof item.output === 'string' ? item.output : JSON.stringify(item.output);
options.onEvent({
type: 'tool_result',
name: callNames.get(item.callId) ?? 'unknown',
callId: item.callId,
output: out.length > 200 ? out.slice(0, 200) + '…' : out,
});
} else if (item.type === 'reasoning') {
const text = item.summary?.map((s: { text: string }) => s.text).join('') ?? '';
if (text) options.onEvent({ type: 'reasoning', delta: text });
}
}
}
const response = await result.getResponse();
return { text: response.outputText ?? '', usage: response.usage, output: response.output };
}
export async function runAgentWithRetry(
config: AgentConfig,
input: string | ChatMessage[],
options?: { onEvent?: (event: AgentEvent) => void; signal?: AbortSignal; maxRetries?: number },
) {
for (let attempt = 0, max = options?.maxRetries ?? 3; attempt <= max; attempt++) {
try { return await runAgent(config, input, options); }
catch (err: any) {
const s = err?.status ?? err?.statusCode;
if (!(s === 429 || (s >= 500 && s < 600)) || attempt === max) throw err;
await new Promise((r) => setTimeout(r, Math.min(1000 * 2 ** attempt, 30000)));
}
}
throw new Error('Unreachable');
}
src/cli.ts
入力スタイルは block(背景ボックス)、bordered(水平線)、plain(シンプルなキャレット)の3種類に対応しています。styledReadLine()、borderedReadLine()、および getInput() ディスパッチャーの完全な実装については、references/input-styles.md を参照してください。
import { createInterface } from 'readline';
import { loadConfig } from './config.js';
import { runAgentWithRetry, type AgentEvent } from './agent.js';
import { detectBg } from './terminal-bg.js';
// import { styledReadLine, borderedReadLine } from ... — see references/input-styles.md
const DIM = '\x1b[2m';
const RESET = '\x1b[0m';
const BOLD = '\x1b[1m';
const CYAN = '\x1b[36m';
const GREEN = '\x1b[32m';
const YELLOW = '\x1b[33m';
const GRAY = '\x1b[90m';
function formatTokens(n: number): string {
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
}
function summarizeArgs(name: string, args: Record<string, unknown>): string {
const key = { shell: 'command', file_read: 'path', file_write: 'path',
file_edit: 'path', glob: 'pattern', grep: 'pattern', web_search: 'query',
}[name] ?? Object.keys(args)[0];
if (!key || !(key in args)) return '';
const val = String(args[key]);
return `${key}=${val.length > 40 ? val.slice(0, 40) + '…' : val}`;
}
async function main() {
const config = loadConfig();
const BG_INPUT = config.display.inputStyle === 'block' ? await detectBg() : '';
// Banner
const width = Math.min(process.stdout.columns || 60, 60);
const line = GRAY + '─'.repeat(width) + RESET;
console.log(`\n${line}`);
console.log(` ${BOLD}My Agent${RESET} ${DIM}v0.1.0${RESET}`);
console.log(` ${DIM}model${RESET} ${CYAN}${config.model}${RESET}`);
if (config.slashCommands) console.log(` ${DIM}/model to change${RESET}`);
console.log(`${line}\n`);
const rl = createInterface({ input: process.stdin, output: process.stdout, prompt: `${GREEN}>${RESET} ` });
async function getInput(): Promise<string> {
switch (config.display.inputStyle) {
case 'block': return styledReadLine(BG_INPUT);
case 'bordered': return borderedReadLine();
case 'plain':
default:
return new Promise((r) => { rl.prompt(); rl.once('line', r); });
}
}
while (true) {
const input = await getInput();
const trimmed = input.trim();
if (!trimmed) continue;
if (config.display.inputStyle !== 'plain') {
const cwd = process.cwd().replace(process.env.HOME ?? '', '~');
process.stdout.write(`\x1b[K ${DIM}${cwd}${RESET}\n`);
}
if (trimmed.toLowerCase() === 'exit') { process.exit(0); }
console.log();
let streaming = false, started = false;
const toolStart = new Map<string, number>();
const dots = ['·', '··', '···'];
let di = 0;
const spin = setInterval(() => {
if (!started) process.stdout.write(`\r${DIM}${dots[di++ % 3]}${RESET}`);
}, 300);
const handleEvent = (event: AgentEvent) => {
if (!started) { started = true; process.stdout.write('\r\x1b[K'); }
if (event.type === 'text') { streaming = true; process.stdout.write(event.delta); }
else if (event.type === 'tool_call') {
if (streaming) { process.stdout.write('\n'); streaming = false; }
toolStart.set(event.callId, Date.now());
const args = summarizeArgs(event.name, event.args);
console.log(` ${YELLOW}⚡${RESET} ${DIM}${event.name}${args ? ' ' + args : ''}${RESET}`);
} else if (event.type === 'tool_result') {
const ms = Date.now() - (toolStart.get(event.callId) ?? Date.now());
console.log(` ${GREEN}✓${RESET} ${DIM}${event.name} (${(ms / 1000).toFixed(1)}s)${RESET}`);
started = false;
}
};
try {
const result = await runAgentWithRetry(config, trimmed, { onEvent: handleEvent });
clearInterval(spin);
if (streaming) process.stdout.write(RESET);
const inT = result.usage?.inputTokens ?? 0;
const outT = result.usage?.outputTokens ?? 0;
console.log(`\n${GRAY} ${formatTokens(inT)} in · ${formatTokens(outT)} out${RESET}\n`);
} catch (err: any) {
clearInterval(spin);
if (streaming) process.stdout.write(RESET);
console.log(`\n${YELLOW} Error: ${err.message}${RESET}\n`);
}
}
}
main();
ASCII ロゴバナー
ASCII Logo Banner が選択された場合は、ユーザーにプロジェクト名を尋ね、その名前のASCIIアートを含む src/banner.ts を生成します。アートには █ 文字を使ったブロック体スタイルを使用します。バナーは60列のターミナルに収まるようにしてください。
src/banner.ts
ユーザーのプロジェクト名のASCIIアートを生成します。「ACME」というプロジェクト名の場合の例:
const RESET = '\x1b[0m';
const BOLD = '\x1b[1m';
const DIM = '\x1b[2m';
const CYAN = '\x1b[36m';
const LOGO = `
█████╗ ██████╗███╗ ███╗███████╗
██╔══██╗██╔════╝████╗ ████║██╔════╝
███████║██║ ██╔████╔██║█████╗
██╔══██║██║ ██║╚██╔╝██║██╔══╝
██║ ██║╚██████╗██║ ╚═╝ ██║███████╗
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝`;
export function printBanner(model: string): void {
console.log(CYAN + BOLD + LOGO + RESET);
console.log(` ${DIM}model ${RESET}${model}\n`);
}
ASCIIアートは、ユーザーの実際のプロジェクト名に合わせて調整します。60列に収まる、1語または2語の短い名前にしてください。
src/cli.ts に組み込む
showBanner が選択されている場合は、テキストバナーより前の main() の先頭に追加します。
import { printBanner } from './banner.js';
// In main(), replace the text banner with:
if (config.showBanner) {
printBanner(config.model);
} else {
// fall back to the text banner from the cli.ts template above
}
AgentConfig に showBanner: boolean を追加します(デフォルトは false)。agent.config.json または loadConfig({ showBanner: true }) で有効にします。
参考ファイル
コアファイル以外の内容については、以下を参照してください。
- references/tools.md — ユーザー定義ツールの仕様:file-read、file-write、file-edit、glob、grep、list-dir、shell、js-repl、sub-agent、plan、request-input、web-fetch、view-image、カスタムテンプレート
- references/modules.md — ハーネスモジュール:セッションの永続化、コンテキストの圧縮、システムプロンプトの構成、ツールの承認、構造化ログ
- references/tui.md — ターミナルのバックグラウンド検出、適応型入力背景
- references/tool-display.md — ツールの表示スタイル:絵文字、グループ化、最小限の表示;TuiRenderer クラス、ツールごとの色、フォーマッター
- references/input-styles.md — 入力スタイル:block(背景ボックス)、bordered(水平線)、plain(シンプルなキャレット)
- references/loader.md — ローダーのアニメーション:gradient(スクロールする光沢)、spinner(点字のドット)、minimal(末尾のドット)
- references/slash-commands.md — スラッシュコマンドレジストリ:/model、/new、/help、/compact、/session、/export
- references/system-prompt.md — デフォルトのシステムプロンプト、buildSystemPrompt()、カスタマイズガイド
- references/server-entry-points.md — SSE ストリーミングを備えた Express/Hono API サーバーのエントリーポイントと、拡張ポイント(MCP、WebSocket、動的モデル)