RetainProcess の構成を作る AI へのガイド
このガイドは、RetainProcess を初めて扱う AI アシスタント向けです。利用者の代わりに、RetainProcess に登録する「構成」を書くための書式と規則をまとめています。これは RetainProcess 1.0 の仕様です(出典: https://foo-tec.com/retainprocess/)。
1. RetainProcess とは
- macOS のメニューバーに常駐するアプリ。開発で使う、長く動き続けるコマンド(開発サーバー、ワーカー、ローカル LLM など)を「構成」として登録し、ワンクリックで起動(Run)・停止(Stop)する。
- 動かしている間、CPU・メモリ(プロセスグループの合算)と stdout / stderr のログを表示する。ログはファイルにも保存し、古い世代を指定フォルダへ退避する。
- 停止はプロセスグループ単位: SIGTERM を送り、猶予秒数を過ぎてもグループが残っていれば SIGKILL。
- RetainProcess を終了しても、動かしているコマンドは止まらない。
- CLI
retain-processがあるが、CLI では構成を追加・編集できない。構成の追加は「取り込み(Import)」か、スタックのファイル(retainprocess.yml)で行う。
2. 出力の選び方(JSON か YAML か)
| 状況 | 出すもの | 利用者の反映方法 |
|---|---|---|
| 単独のコマンドを 1 つ以上登録したい | 構成の JSON(配列) | ポップアップの ⤓ → Import from Clipboard(または Import from File…) |
| 1 つのプロジェクトの複数のサービスを、依存の順に起動したい(DB → API → フロントなど) | retainprocess.yml | プロジェクトのディレクトリに保存し、そこで retain-process up |
しないこと: ~/Library/Application Support/RetainProcess/config.json を直接編集させない。アプリは起動時にしか読まず、動いているアプリが次に保存したときに上書きされて消える。
3. 利用者に確かめること
次の項目が会話から分からなければ、推測せずに質問する。プロジェクトのフォルダを示されたら、先に IDE の起動構成を読む(4 章)と、多くはそこで分かる。
- 動かすコマンドそのもの(ターミナルで打っているとおりに)
- どのディレクトリで動かすか(絶対パスか
~/から) - 必要な環境変数、または読む
.envファイル - 落ちたときに自動で起動し直したいか(
no/on-failure/always) - 複数のサービスに依存関係があるか。あるなら、準備完了をポートか HTTP で判定できるか
- (任意)案件ごとにまとめる名前(ワークスペース)
4. IDE の起動構成から作る(IntelliJ・VS Code・Cursor など)
利用者がプロジェクトのフォルダを示したら、そこにある IDE の起動構成(Run Configuration / launch.json / tasks.json)を読み、RetainProcess の構成に変換できる。コマンド・引数・作業ディレクトリ・環境変数を聞き直さずに済む。
- フォルダを読める AI(Claude Code、Cursor のエージェント、Codex など)なら、フォルダのパスだけで始められる。
- ファイルを読めないチャットの AI なら、下の表のファイルの中身を貼り付けてもらう。
- RetainProcess 自体に IDE の構成を読む機能は無い。変換するのは AI で、結果は通常どおり取り込み用の JSON(または
retainprocess.yml)として出す。
探す場所
| IDE | ファイル | 備考 |
|---|---|---|
| JetBrains(IntelliJ IDEA・WebStorm・PyCharm・RustRover・GoLand など) | .run/*.run.xml.idea/runConfigurations/*.xml.idea/workspace.xml の <component name="RunManager"> | 1 つの <configuration> が 1 つの起動構成。name と type 属性を見る |
| VS Code・Cursor(Cursor は VS Code と同じファイル) | .vscode/launch.json.vscode/tasks.json*.code-workspace の launch / tasks | JSON にコメントや末尾のカンマがあってよい(JSONC) |
| (IDE 以外の手がかり) | package.json の scripts、Procfile、Makefile、docker-compose.yml | IDE の構成が無いときの候補 |
選び方
- 変換するのは動き続けるものだけ: 開発サーバー、API、ワーカー、ウォッチ(
dev・serve・watch・startなど)。 - すぐ終わるもの(ビルド、テスト、lint、マイグレーション、コード生成)、デバッガのアタッチ(
"request": "attach")、GUI アプリやシミュレータの起動(Xcode・AppleRunConfiguration・open)は変換しない。 - 見つけた起動構成の一覧と、変換する / しないの判断を先に利用者に見せ、確認を取ってから出力する。
nameは元の起動構成の名前を使う。workspaceにプロジェクトのフォルダ名を入れると、まとめて絞り込める。- 依存関係がある複数の起動構成(VS Code の
compounds、tasks のdependsOn、「API を起動してからフロントを起動する」などの手順)は、retainprocess.ymlのスタックにするのも提案する。
変数の置き換え
| 元の書き方 | 置き換え | 備考 |
|---|---|---|
$PROJECT_DIR$、${workspaceFolder} | プロジェクトのフォルダのパス(~/… の形) | JetBrains の file://$PROJECT_DIR$ は file:// を外す |
$USER_HOME$、${userHome} | ~ | args の中では ~ が展開されないので、そこだけは絶対パスにする |
$MODULE_DIR$、$MODULE_WORKING_DIR$ | そのモジュールのフォルダ | 分からなければ利用者に聞く |
${env:NAME} | 書かない(ログインシェルの環境から引き継がれる) | ログインシェルに無い変数なら利用者に値を聞く |
${file}、${input:…}、${command:…} | 変換しない | 開いているファイルや入力に依存する構成は対象外として伝える |
JetBrains の起動構成の読み方(type ごと)
| type | 変換 | 読む項目 |
|---|---|---|
ShConfigurationType(シェルスクリプト) | EXECUTE_SCRIPT_FILE が true: executable_path = INTERPRETER_PATH、args = INTERPRETER_OPTIONS + SCRIPT_PATH + SCRIPT_OPTIONSfalse: executable_path = /bin/zsh、args = -lc 'exec <SCRIPT_TEXT>' | SCRIPT_WORKING_DIRECTORY → working_dir、<envs> → env_text |
js.build_tools.npm(npm / pnpm / yarn) | executable_path = <package-manager> の名前(無ければ npm)、args = <command> + <scripts> + <arguments>(例 run dev) | <package-json> のフォルダ → working_dir、<envs> |
CargoCommandRunConfiguration | executable_path = cargo、args = command の値(例 run --bin api) | workingDirectory、<envs> |
MAKEFILE_TARGET_RUN_CONFIGURATION | executable_path = make、args = target + arguments | workingDirectory。ビルドだけのターゲットは変換しない |
| Python・Node.js・Go の実行構成 | インタプリタ(Python は SDK のパス、例 .venv/bin/python)+ スクリプトまたは -m モジュール + 引数 | 作業ディレクトリ・環境変数・引数の項目。Python には PYTHONUNBUFFERED=1 を足す |
| Java の Application・Spring Boot | クラスパスが IDE 任せなので直接は変換できない。./gradlew bootRun や ./mvnw spring-boot:run などの代わりを提案し、利用者に確かめる | Gradle / Maven の構成があればそのタスク名 |
VS Code・Cursor の読み方
- launch.json(
"request": "launch"のもの):node/pwa-node:runtimeExecutable(無ければnode)+runtimeArgs+program+args。runtimeExecutableがnpmならnpm run-script devのようになるpython/debugpy:pythonの指定(無ければ.venv/bin/pythonを探す)+-m moduleまたはprogram+argsgo:go run <program>+argslldb/cppdbg:program+args(ビルド済みの実行ファイル。preLaunchTaskのビルドは含めない)- 共通:
cwd→working_dir、env→env_text、envFile→env_files。デバッガ専用の設定(console・stopOnEntry・sourceMapsなど)は捨てる
- tasks.json:
"isBackground": trueのものや、名前・コマンドがdev/watch/serveのものが候補。"type": "process"はcommandを実行ファイル、argsを引数に。"type": "shell"はシェルの構文を含みうるので/bin/zsh -lc '…'に。options.cwd/options.envも読む
例
JetBrains の .idea/runConfigurations/dev.xml:
<configuration default="false" name="dev" type="js.build_tools.npm"> <package-json value="$PROJECT_DIR$/package.json" /> <command value="run" /> <scripts><script value="dev" /></scripts> <package-manager value="/opt/homebrew/bin/pnpm" /> <envs><env name="PORT" value="3000" /></envs> </configuration>
VS Code の .vscode/launch.json:
{
"configurations": [
{
"name": "API",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": ["app.main:app", "--reload"],
"cwd": "${workspaceFolder}/api",
"envFile": "${workspaceFolder}/api/.env"
}
]
}
プロジェクトのフォルダが ~/src/shop なら、変換結果は次のとおり。
[
{
"name": "dev",
"executable_path": "pnpm",
"args": "run dev",
"working_dir": "~/src/shop",
"env_text": "PORT=3000\n",
"workspace": "shop"
},
{
"name": "API",
"executable_path": ".venv/bin/python",
"args": "-m uvicorn app.main:app --reload",
"working_dir": "~/src/shop/api",
"env_text": "PYTHONUNBUFFERED=1\n",
"env_files": [".env"],
"workspace": "shop"
}
]
補足: pnpm は名前で書けば、ログインシェルの PATH で見つかる(元の構成の /opt/homebrew/bin/pnpm のような絶対パスでもよい)。Python の仮想環境の場所(.venv)が構成に書かれていなければ、フォルダの中を確かめるか利用者に聞く。
5. 構成の JSON の書式
取り込みは次の 3 つの形を受け付ける。形 1(構成の配列)を使うこと。
- 構成の配列
[ {…}, {…} ](推奨) - 構成 1 件のオブジェクト
{ "name": …, … } config.json全体({"version": 1, "configs": [ … ]}。settingsは取り込まれない)
すべてのフィールドに既定値があり、省略してよい。既定値と同じなら書かないほうが読みやすい。id は書かない(取り込み時に必ず新しく採番される)。
| キー | 型 | 既定値 | 意味・規則 |
|---|---|---|---|
name | 文字列 | "" | 表示名。空なら Untitled。重複してよい。CLI では名前(大文字小文字を区別しない)で指定するので、短く一意にするとよい |
executable_path | 文字列 | "" | 必須。実行ファイル。コマンド名(npm)・絶対パス・相対パス(作業ディレクトリ基準)。先頭の ~/ は展開。コマンド名はログインシェルの PATH で探す |
args | 文字列 | "" | 引数を 1 本の文字列で。空白区切り、"…" / '…' でくくれる、\ でエスケープ。シェルを通さない(下の 6 章) |
working_dir | 文字列 | "" | 作業ディレクトリ。空ならホーム。先頭の ~/ は展開。存在しなければ起動に失敗する |
env_text | 文字列 | "" | 環境変数。KEY=VALUE を \n で区切る。# で始まる行と空行は無視。KEY は [A-Za-z_][A-Za-z0-9_]*。値の引用符は外されない(A="x" は値が "x" になる)。$VAR は展開しない |
env_files | 文字列の配列 | [] | 読む .env ファイル。~ を展開、相対なら作業ディレクトリ基準。Run のたびに読む。書式は KEY=VALUE、export 可、"…" / '…' の引用符は外す、${VAR} は展開しない |
workspace | 文字列 | "" | まとめて表示を絞り込むための名前。空 = どこにも属さない |
restart | 文字列 | "no" | 自動再起動。"no" / "on-failure"(異常終了時)/ "always"(正常終了でも)。間隔は 1 秒から倍々で最大 30 秒、60 秒未満の終了が 10 回続くと諦める |
tty | 真偽値 | false | 疑似端末で動かす。出力がバッファされず、色も出る。stdout と stderr は 1 本にまとまる。入力は送れない |
stop_grace_secs | 整数 | 5 | Stop で SIGTERM を送ってから SIGKILL までの秒数。0〜300 |
log_enabled | 真偽値 | true | ログをファイル(~/Library/Logs/RetainProcess/<id>/current.log)に保存するか |
log_max_size_mb | 整数 | 5 | 1 ファイルの上限(MB)。1〜1024 |
log_max_files | 整数 | 3 | 残す世代数。0〜50。あふれた分は退避先へ移す(退避先が無ければ削除) |
log_file_per_run | 真偽値 | true | Run のたびに新しいログファイルで始める |
archive_dir | 文字列 / null | null | あふれたログの退避先(絶対パス)。通常は書かない(利用者がアプリのダイアログで選ぶ。未設定ならアプリ全体の退避先を使う) |
全項目を書いた例(実際の構成)
RetainProcess で使っている Ollama の構成を、全項目のまま載せる。省略した項目は表の既定値になるので、普段はここまで書かなくてよい。
{
"id": "8e01a5a9-2f9c-4b4f-a977-5b90bfa5e6dd",
"name": "Ollama",
"executable_path": "ollama",
"args": "serve",
"working_dir": "",
"env_text": "# OLLAMA_HOST=127.0.0.1:11434\n# OLLAMA_MODELS=~/.ollama/models\n",
"env_files": [],
"workspace": "AI",
"stop_grace_secs": 10,
"log_enabled": true,
"log_max_size_mb": 5,
"log_max_files": 3,
"log_file_per_run": false,
"archive_dir": null,
"restart": "no",
"tty": false
}
idは取り込み時に必ず採番し直されるので、書いてあっても使われない(新しく作るときは書かない)。working_dirが空なのでホームで動く。env_textの 2 行は#で始まるコメントで、使うときに外す。archive_dirはnull(未設定)。アプリ全体の退避先(Log Archive Home)があればそこのOllamaフォルダへ、無ければあふれたログは削除される。空文字列""ではなくnullにするか、項目ごと省く。
環境変数は次の順に重ね、後のものが勝つ: ログインシェルの環境 → env_files(書いた順)→ env_text。子プロセスには RETAIN_PROCESS=1 も渡る。
6. コマンドの起動のされ方(つまずきやすい点)
- シェルを通さない。
argsは分割して直接execveに渡す。パイプ|、&&、リダイレクト>、$VARの展開、グロブ*、argsの中の~はどれも効かない。必要ならexecutable_pathを/bin/zsh、argsを-lc 'cd sub && exec npm run dev'のようにする(最後のコマンドはexecで置き換えると、停止と監視が素直になる)。 - 前景で動き続けるコマンドにする。 自分でバックグラウンドに回るもの(
docker compose up -d、&、nohup、--daemon、brew services start)は、すぐ終わったように見えて管理できない。-dなどを外した前景版を使う。 - 入力は送れない。 パスワードの入力や確認(
y/n)を待つコマンドは止まったままになる。sudoは使わない。 - PATH はログインシェルの環境(
$SHELL -licで取得)。nvm・pyenv・Homebrew などで入れたコマンドは、ターミナルと同じように名前で見つかる。仮想環境の Python はexecutable_pathに.venv/bin/pythonのような作業ディレクトリからのパスを書くと確実。 - 出力のバッファ。 パイプにつながると出力をためて遅れて出すツールがある(Python など)。
PYTHONUNBUFFERED=1を足すか、"tty": trueにする。 - 色。 端末でないと色を出さないツールが多い。色が欲しければ
"tty": true、またはFORCE_COLOR=1/CLICOLOR_FORCE=1。 - 停止に時間がかかるもの(DB、
docker compose up、JVM など)はstop_grace_secsを 15〜30 に増やす。 - ポートの衝突。 同じポートを使うものが既に動いていると、Run は失敗する。
- 引き取り。 同じ実行ファイル・同じ引数のプロセスが RetainProcess の外で既に動いていると、それを引き取って監視・停止の対象にする(その場合、出力は表示できない)。
- 秘密。
env_textの値は設定ファイルに平文で保存される。トークンなどは.envファイルに置き、env_filesで参照するほうがよい。
7. JSON の例
次の配列をそのまま取り込める(1 件だけでも配列にする)。
[
{
"name": "Blog Dev",
"executable_path": "npm",
"args": "run dev -- --port 4321",
"working_dir": "~/src/blog",
"env_text": "NODE_ENV=development\nFORCE_COLOR=1\n",
"workspace": "Blog"
},
{
"name": "API (uvicorn)",
"executable_path": ".venv/bin/python",
"args": "-m uvicorn app.main:app --reload --port 8000",
"working_dir": "~/src/shop/api",
"env_text": "PYTHONUNBUFFERED=1\n",
"env_files": [".env"],
"restart": "on-failure"
},
{
"name": "Ollama",
"executable_path": "ollama",
"args": "serve",
"env_text": "OLLAMA_HOST=127.0.0.1:11434\n",
"stop_grace_secs": 10
},
{
"name": "Docker Stack",
"executable_path": "docker",
"args": "compose up",
"working_dir": "~/src/shop",
"stop_grace_secs": 30
},
{
"name": "Worker (shell)",
"executable_path": "/bin/zsh",
"args": "-lc 'source ./env.sh && exec bundle exec sidekiq'",
"working_dir": "~/src/shop/backend",
"tty": true,
"restart": "always"
}
]
補足: docker compose up の場合、CPU・メモリに出るのは docker の CLI の分だけで、コンテナ自体(Docker の VM の中)は含まれない。
8. スタック(retainprocess.yml)の書式
docker compose のファイルの部分集合。プロジェクトのディレクトリに retainprocess.yml(.yaml も可)として置く。ファイルが正で、アプリ側では編集しない(変えたらファイルを直す)。
トップレベル
name: スタック名(省略時はディレクトリ名)services: サービス名をキーにしたマップ。サービス名は[a-zA-Z0-9][a-zA-Z0-9_.-]*
サービスのキー(これ以外は読み飛ばされ、警告が出る)
| キー | 書き方 |
|---|---|
command | 必須。文字列(JSON の args と同じ分割)か配列。先頭の語が実行ファイル。シェルは通さない |
working_dir | このファイルのディレクトリからの相対パス、または絶対パス。省略時はファイルのディレクトリ |
environment | マップ(KEY: value)か配列(- KEY=value) |
env_file | 文字列か配列。相対パスは このファイルのディレクトリ基準(working_dir 基準ではない) |
depends_on | 配列(= 起動したら次へ)、またはマップで condition: service_started / service_healthy(準備完了を待つ) |
healthcheck | port: 5432(そのポートで待ち受けたら準備完了。any なら何かのポート)か http: http://localhost:8080/health(2xx で準備完了。ホストは localhost / 127.0.0.1 / [::1] だけ、リダイレクトは追わない)。interval(既定 1s)、timeout(準備完了を待つ上限、既定 60s) |
stop_grace_period | 既定 5s |
restart | no / on-failure / always(unless-stopped は always と同じ) |
tty | true / false |
x-retainprocess | RetainProcess 独自の設定: log_enabled / log_max_size_mb / log_max_files / log_file_per_run / archive_dir / workspace(意味は JSON と同じ) |
- 時間は
500ms/10s/1m30s/2h、または数だけ(秒)。 image・ports・volumes・build・profilesなどは使えない(読み飛ばされる)。コンテナが要るものは、docker compose upを 1 つのサービスのcommandにする。${VAR}は展開しない。:や#を含む値、数字だけの値(ポート番号など)をcommandの配列やenvironmentに書くときは"…"でくくる。- 依存の循環、存在しないサービスへの依存、空の
commandはエラーになり、そのスタックは操作できない。
例
name: shop
services:
db:
command: postgres -D ./data -p 5432
working_dir: backend
healthcheck:
port: 5432
timeout: 1m30s
stop_grace_period: 15s
api:
command: [.venv/bin/python, -m, uvicorn, "app.main:app", --port, "8000"]
working_dir: api
env_file: api/.env
environment:
PYTHONUNBUFFERED: "1"
depends_on:
db:
condition: service_healthy
healthcheck:
http: http://localhost:8000/health
restart: on-failure
web:
command: npm run dev
working_dir: web
tty: true
depends_on: [api]
x-retainprocess:
workspace: Shop
Up は依存の順に起動し(service_healthy なら準備完了を待つ)、Down は逆順に止める。
9. Mac に最初から入っているもので試す例
利用者が「まず試したい」「何を登録すればよいか分からない」ときは、インストール不要の macOS 標準のコマンドで例を作る。どれも前景で動き続け、Stop で止まる。
| やりたいこと | コマンド | 見どころ |
|---|---|---|
| Mac をスリープさせない(Run 中だけ) | /usr/bin/caffeinate -d -i | Run でスリープ防止、Stop で元に戻る。トグルのように使える |
| アプリのログを流し見る | /usr/bin/log stream --style compact --predicate 'process == "Finder"' | Logs タブと検索の練習。出力が多いのでログの世代交代も確かめられる |
| ネットワークの疎通を見張る | /sbin/ping -i 10 www.apple.com | 10 秒ごとに 1 行。途切れたら Logs で分かる |
| メモリの状況を記録する | /usr/bin/vm_stat 60 | 1 分ごとの記録がログファイルに残る |
| ポートで待ち受ける | /usr/bin/nc -lk 127.0.0.1 9000 | ヘッダーに :9000 が出る。別のターミナルで echo hello | nc 127.0.0.1 9000 とすると Logs に出る |
| 定期的に何かする(シェルのループ) | /bin/zsh -c 'while true; do date; sleep 60; done' | シェルの構文が要るときの書き方の例 |
JSON の例
[
{
"name": "Keep Awake",
"executable_path": "/usr/bin/caffeinate",
"args": "-d -i",
"log_enabled": false
},
{
"name": "Finder Log",
"executable_path": "/usr/bin/log",
"args": "stream --style compact --predicate 'process == \"Finder\"'",
"log_max_size_mb": 2,
"log_max_files": 2
},
{
"name": "Ping Watch",
"executable_path": "/sbin/ping",
"args": "-i 10 www.apple.com"
},
{
"name": "Memory Stats",
"executable_path": "/usr/bin/vm_stat",
"args": "60",
"workspace": "Monitor"
},
{
"name": "Port 9000",
"executable_path": "/usr/bin/nc",
"args": "-lk 127.0.0.1 9000"
},
{
"name": "Clock",
"executable_path": "/bin/zsh",
"args": "-c 'while true; do date; sleep 60; done'"
}
]
スタックの例(依存と準備完了を試す)
listener がポート 9000 で待ち受けてから、sender が 5 秒ごとに時刻を送る。retain-process up で順に起動し、listener の Logs に時刻が流れる。
name: mac-demo
services:
listener:
command: /usr/bin/nc -lk 127.0.0.1 9000
healthcheck:
port: 9000
sender:
command: [/bin/zsh, -c, "while true; do date | /usr/bin/nc -w 1 127.0.0.1 9000; sleep 5; done"]
depends_on:
listener:
condition: service_healthy
restart: on-failure
できないこと: Safari・ミュージック・メモなどの GUI アプリ(open -a …)は管理できない(open はすぐ終わり、監視も停止もできない)。sudo が要るもの(powermetrics、fs_usage など)も使えない。python3 / ruby / git は Command Line Tools が無い Mac ではインストールを促すダイアログが出るだけなので、「標準のコマンド」の例には使わない。
log は zsh の組み込みコマンドと名前が重なる。RetainProcess はシェルを通さないので log でも /usr/bin/log が動くが、例では絶対パスで書くと誤解が無い。
10. 出す前のチェックリスト
- JSON は構成の配列で、構文として正しい(末尾のカンマなし、
env_textの改行は\n)。 - 各構成に
nameとexecutable_pathがある。idは書いていない。 argsにパイプ・&&・$VAR・~が無い(あれば/bin/zsh -lc '…'に)。- 前景で動き続けるコマンドになっている(
-dや&が無い)。入力を待たない。sudoが無い。 env_textの値を引用符でくくっていない。秘密はenv_files側にある。- 既定値と同じフィールドを省いている。
archive_dirを勝手に決めていない。 - YAML なら、使えないキー(
imageなど)を使っていない。依存が循環していない。 - 推測で埋めた値(パス、ポート)があれば、その旨を利用者に伝えている。
11. 利用者に伝える反映のしかた
JSON の場合
- JSON をコピーする。
- メニューバーの RetainProcess のアイコンをクリックし、ポップアップの左ペインの
⤓→Import from Clipboard。ファイルに保存したならImport from File…。 - 構成は一覧の末尾に追加される(既存の構成は変わらない。同じものを 2 回取り込むと 2 件になる)。自動では起動しないので、選んで
▶ Run。 - 直すときは Configuration タブで編集する(自動で保存される。動いている間の変更は次の Run から効く)。
YAML の場合
retainprocess.ymlをプロジェクトのディレクトリに保存する。- そのディレクトリで
retain-process up(初回はスタックの登録も行う)。状態はretain-process ps、停止はretain-process down。 - ファイルを直したら、もう一度
retain-process up(または、ポップアップのスタックの表示でReload)。
CLI の retain-process は、アプリが起動していないと終了コード 2 で「RetainProcess is not running」と返す。その場合は open -a RetainProcess で起動してもらう。