mcpField
C
create-agent-tui

create-agent-tui

github.com/openrouterteamエージェントワークフロー309296 回インストール1 日前に更新
リポジトリ

create-agent-tui について

create-agent-tuiは、TypeScript製のエージェント向けターミナルUIを生成するスキルです。ゼロから開発する人を対象に、ツールやモジュール、表示スタイルを選べるプロジェクトを作成し、OpenRouterを利用します。

主な機能

  1. 01TypeScript製エージェントTUIの生成
  2. 02ツールとハーネスモジュールの選択
  3. 03セッション、承認、ログの設定
  4. 04入力、ツール表示、ローダーのカスタマイズ
  5. 05OpenRouterのモデル切り替えコマンド

活用シーン

  1. 01エージェントプロジェクトの新規作成
  2. 02既存エージェントへのツール追加
  3. 03セッション保存や承認フローの追加
  4. 04エージェントのターミナル操作の調整

MCP 対応

READMEではMCPを拡張ポイントとして挙げていますが、具体的な設定や機能は説明されていません。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/openrouterteam/skills --skill create-agent-tui

インストール後、Claude Code で/create-agent-tuiを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

metadata.json
README.md
references/
input-styles.md
loader.md
modules.md
server-entry-points.md
slash-commands.md
system-prompt.md
tool-display.md
tools.md
tui.md
sample/
.gitignore
package-lock.json
package.json
sample/src/
agent.ts
banner.ts
cli.ts
commands.ts
config.ts
loader.ts
renderer.ts
screenshot-demos.ts
session.ts
terminal-bg.ts
sample/src/tools/
custom.ts
file-edit.ts
file-read.ts
file-write.ts
glob.ts
grep.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_searchONエンジン、最大結果数、ドメインフィルタリング
日時openrouter:datetimeONタイムゾーン
画像生成openrouter:image_generationOFFモデル、品質、サイズ、形式

サーバーツールは、ユーザー定義ツールとともに tools 配列に追加します。クライアント側のコードは不要です。OpenRouter が実行します。

ユーザー定義ツール(クライアント側、src/tools/ に生成)
ツールデフォルト説明
ファイル読み取りONオフセットと上限を指定してファイルを読み取り、画像を検出します
ファイル書き込みONファイルを書き込み/作成し、ディレクトリを自動作成します
ファイル編集ON差分検証を伴う検索と置換
Glob/検索ONglob パターンでファイルを検出します
Grep/検索ON正規表現でコンテンツを検索します
ディレクトリ一覧ONディレクトリの内容を一覧表示します
Shell/BashONタイムアウトと出力取得を設定してコマンドを実行します
JS REPLOFF永続的な Node.js 環境
サブエージェントの起動OFF子エージェントにタスクを委任します
計画/ToDoOFF複数ステップのタスク進行状況を追跡します
ユーザー入力の要求OFF構造化された複数選択式の質問
ウェブ取得OFFウェブページを取得し、テキストを抽出します
画像表示OFFローカル画像を Base64 として読み取ります
カスタムツールテンプレートONドメイン固有のツール向けの空のひな形
ハーネスモジュール(アーキテクチャコンポーネント)
モジュールデフォルト説明
セッション永続化ONJSONL 形式の追記専用会話ログ
ASCII ロゴバナーOFF起動時にカスタム ASCII アートバナーを表示 — プロジェクト名を尋ねます
コンテキスト圧縮OFFコンテキストが長くなったときに古いメッセージを要約します
システムプロンプトの構成OFF静的および動的なコンテキストから指示を組み立てます
ツール権限/承認OFF危険なツールの実行をユーザー確認で制御します
構造化イベントログOFFツール呼び出し、API リクエスト、エラーのイベントを出力します
@ ファイル参照OFF@filename を使って、次のメッセージにファイルの内容を添付します
! シェルショートカットOFF!command でシェルを実行し、出力をコンテキストに挿入します
複数行入力OFFShift+Enter で複数行入力(raw ターミナルモードが必要)
スラッシュコマンド(ユーザー向けREPLコマンド)
コマンドデフォルト説明
/modelONOpenRouter API 経由でモデルを切り替えます
/newON新しい会話を開始します
/helpON利用可能なコマンドを一覧表示します
/compactOFFコンテキストの圧縮を手動で実行します
/sessionOFFセッションのメタデータとトークン使用量を表示します
/exportOFF会話を Markdown として保存します

スラッシュコマンドが有効な場合は、コマンドレジストリを含む src/commands.ts を生成します。仕様については references/slash-commands.md を参照してください。

ビジュアルのカスタマイズ(それぞれ単一選択として提示)

入力スタイル — プロンプトの表示方法です。references/input-styles.md を参照してください。

スタイルデフォルト説明
blockON› プロンプト付きの全幅背景ボックスで、ターミナルのテーマに合わせて変化します
bordered入力の上下に水平の ─ 線を表示します
plainエスケープシーケンスを使わない、シンプルな > readline プロンプトです
その他希望する内容をユーザーが説明し、カスタム入力スタイルを実装します

ツールの表示 — 実行中のツール呼び出しの表示方法です。references/tool-display.md を参照してください。

スタイルデフォルト説明
groupedON太字のアクションラベルとツリー形式の出力
emoji呼び出しごとに ⚡/✓ マーカー、引数、実行時間を表示します
minimal集約した1行の要約を表示します
hiddenツールの出力を表示しません
その他希望する内容をユーザーが説明し、カスタム表示を実装します

ローダーアニメーション — モデルの応答を待っている間に表示します。references/loader.md を参照してください。

スタイルデフォルト説明
spinnerONテキストの左側に点字ドットのスピナー(⠋⠙⠹…)を表示します
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、動的モデル)

コメント