GitHub に新しいリポを作って cd するまでに 3 コマンド叩く生活をやめた — ghnew を npm に公開するまで
はじめに:「新規プロジェクト始めるか」のたびに 3 コマンド
新しい個人プロジェクトを始めるたびに、自分はだいたいこれをやっている。
gh repo create alice/my-new-app --private --add-readme
gh repo clone alice/my-new-app
cd my-new-app
3 コマンド。毎回。ghq 派の自分はそこに ghq get ... も挟まるので体感 4 コマンド。
「これ 1 行にできないんかな」とずっと思いつつ、~/.zshrc に関数を書きかけては「ホスト切替もしたいし、SSH/HTTPS の選択も毎回違うし…」で結局放置していた。
そんなところに npx ghnew my-new-app 一発で全部終わる CLI を作って npm に公開した。
npx ghnew my-new-app
→ repo 作成 → ghq の管理下に clone → 最後に cd "/path/to/your/new/dir" を box で表示。c を押すとクリップボードに入る。
100 行未満の薄いラッパー。なのに作る過程で詰まったところが地味に多く、Unix の根本制約や 2026 年の npm 事情にぶつかったので学びをまとめておく。
できあがったやつ
$ ghnew my-new-app
┌ ghnew
│ creating private repo on github.com…
│ ✓ created alice/my-new-app
│ cloning via https…
│ ✓ cloned
└
╭─ next ─────────────────────────────────────────────────╮
│ │
│ cd "/Users/alice/projects/github.com/alice/my-new-app"│
│ │
╰────────────────────────────────────────────────────────╯
press c to copy · any other key to exit
c を押すと cd "/Users/.../my-new-app" がクリップボードに入る。末尾改行なし、ダブルクォート付きにしてあるので、ペーストして Enter で即 cd できる。
エージェントや CI 用の非対話モードもある:
ghnew --json --remote github.com/alice my-new-app
# → {"schemaVersion":1,"host":"github.com","login":"alice", ... ,"path":"/Users/.../my-new-app"}
設計判断 1: 『子プロセスは親シェルの cwd を変えられない』
最初に詰まったのはここ。
当初は cd まで完全自動化したくて、こんなシェル関数で包む案を考えていた。
ghnew() {
local path
path=$(npx ghnew "$@") || return $?
[[ -n $path ]] && cd "$path"
}
これで ghnew foo 一発で新ディレクトリに移動できる。ただしユーザーが各自 ~/.zshrc に source してもらう必要がある。
ここで Unix の根本ルールに改めてぶつかる:
外から起動された子プロセスは、親シェルの作業ディレクトリを絶対に変えられない。
これは npx だろうが Python スクリプトだろうが Go バイナリだろうが同じ。 OS レベルで決まっている話で、回避手段は基本「親シェル自身に動かしてもらう」=シェル関数しかない。
ところが本人 (この CLI を作ってもらっていた自分) が、
「あー、cd は手動でいいよ。コピペできるくらいで十分」
と言った瞬間に設計が一気にシンプルになった。zsh 関数いらない、source のセットアップ手順不要、ただの npm パッケージ単独で完結。
便利さの最後の 10% は、要件次第で切り捨てた方がいい時がある。結果的に「c を押すとクリップボードに入る」だけの素直な CLI になって、これで十分だった。
設計判断 2: uvx / npx / zsh 関数の三択
候補は 3 つあった。
| 方式 | 起動コスト | ユーザー側準備 |
|---|---|---|
uvx (Python) |
速い (Rust 製) |
uv 必須 |
npx (Node.js) |
普通 | Node が入ってる人が多い |
| zsh 関数 | ゼロ | 各自 dotfiles に書く・自動配布できない |
「Node はもう入ってる」「@inquirer/prompts の対話 UI が成熟」「シェル関数だと配布が辛い」あたりで npx 採用。uvx も悪くなかったが、起動が一拍速い以外のメリットが薄かった。
gh CLI の落とし穴: --hostname フラグは存在しない
これで結構な時間を溶かした。GitHub Enterprise (GHE) も対象にしたかったので「gh repo create --hostname git.example.com で host 切替できるはず」と思ったら、そんなフラグは存在しない。
cli/cli#11093 で「--hostname 追加してほしい」という要望が立っている状態。正解は GH_HOST 環境変数を子プロセスに注入。
spawnSync('gh', ['repo', 'create', `${user}/${name}`, '--private', '--add-readme'], {
env: { ...process.env, GH_HOST: account.host },
stdio: ['inherit', 2, 'inherit'],
});
ついでに gh auth status も近年は --json hosts を受け付ける (gh >= 2.40)。テキストパースを捨てて JSON 一本にできる。
const { hosts } = JSON.parse(
execFileSync('gh', ['auth', 'status', '--json', 'hosts'], { encoding: 'utf8' })
);
// → { "github.com": [{ login, gitProtocol: "https" }],
// "git.example.com": [{ login, gitProtocol: "ssh" }] }
しかも gitProtocol まで取れるので、ghq get に渡す URL を SSH / HTTPS で自動切替できる。
const cloneUrl = gitProtocol === 'ssh'
? `git@${host}:${login}/${name}.git`
: `https://${host}/${login}/${name}`;
GHE は SSH、GitHub.com は HTTPS、みたいな個別設定を持っていてもユーザーが意識せずに済むのが地味に嬉しい。
stdout / stderr の規律: > out.txt で out.txt が空になる設計
スクリプト用途のことを考えて、出力規律をきっちり決めた。
-
stdout = 機械可読出力のみ:
--jsonの 1 行 JSON、--quietの path -
stderr = 人間向け全部: 進捗ログ、cd の box、
c で copyの hint、エラー
これにより ghnew foo > out.txt で:
-
out.txtは空になる (pretty モードは stdout を使わない) - 端末側には box がちゃんと表示される
-
path=$(ghnew --quiet --remote github.com/alice foo)でパスだけが取れる
ここで罠が 2 つ。
const account = await select(
{ message: 'account:', choices },
{ output: process.stderr } // ← これがないと UI が消える
);
このあたりは将来の自分 (と AI エージェント) が壊さないよう、リポ同梱の CLAUDE.md に「不変条件」として明文化してある。
npm publish 周りの現代 (2026 年版)
久しぶりに npm に何か出して、想像以上に挙動が変わっていた。
審査は今もない
npm publish を叩いた瞬間にレジストリに反映される。App Store のような人間レビューは存在しない。
代わりに「72 時間以内なら自由に取り下げられる、それ以降は npm deprecate で塩漬けが基本」という事後対応モデル。同じ name@version は一度公開したら永久に再利用できない。
2FA が事実上必須になっていた
npm login --auth-type=web でブラウザ OAuth してログインまでは普通に通る。問題は publish 時。
$ npm publish
npm error code E_403
npm error 403 Forbidden - PUT https://registry.npmjs.org/ghnew - Two-factor
npm error authentication or granular access token with bypass 2fa enabled
npm error is required to publish packages.
npm のアカウント設定で「Require two-factor authentication and disallow tokens」を有効化しないと弾かれる。これを有効化すれば publish 時に毎回 OTP が求められて通る。
パスキー (WebAuthn) を選ぶと OTP は無くなる
2FA の方式に パスキー (WebAuthn) を選択できる。Touch ID や iCloud Keychain で認証する例のやつ。これを設定すると…
$ npm publish
npm error code EOTP
npm error This operation requires a one-time password.
npm error Open this URL in your browser to authenticate:
npm error https://www.npmjs.com/auth/cli/<random-token>
OTP コードは存在しない。代わりに表示される URL をブラウザで開く → パスキー認証 (Touch ID 等) → npm CLI 側が自動で再開して publish が完了する。
コード入力を排した代償として「ブラウザ往復が必須」になる、というトレードオフ。リモートマシンから npm publish --otp <code> で済ませる運用とは相性が悪いので、CI で publish するなら Trusted Publishing (OIDC) かトークン運用に切り替える必要がある。
Shai-Hulud worm を意識した最低限の対策
2025/09 に発生した npm の supply-chain 攻撃 Shai-Hulud は、開発者のトークンを phishing で奪取して同じメンテナの他パッケージへ自己複製感染するワーム。preinstall / postinstall script を踏み台にする。
flat name (ghnew) で個人アカウントから出す以上、せめてこれくらいは:
-
package.jsonのscriptsにはpreinstall/postinstallを絶対に置かない - README にも「
npm install --ignore-scriptsで問題なく動く」と明記 - CI で使う場合は Granular Access Token で scope を
ghnewのみに限定 - 将来的に Trusted Publishing (OIDC) へ移行
これらは CLAUDE.md (リポ同梱) で「禁止事項」として文章化しておいた。エージェントに修正させた時に postinstall 付きの形に書き換える事故を未然に防ぐ。
AI エージェント連携: .claude/skills/ghnew/SKILL.md を同梱
Claude Code 等のエージェントから安全に呼べるよう、リポに skill (SKILL.md) を同梱した。
エージェント向けのレシピをファイルに書く。
---
name: ghnew
description: Create a brand-new GitHub or GHE repo AND clone it locally...
when_to_use: |
Use when the user says "create a new repo", "新規リポ作って", ...
Do NOT use for cloning existing repos, forking, listing, deleting.
---
# Recommended call
npx -y ghnew@^0.2 --json --remote <host>/<login> <name>
# Visibility — default to private
Never pass --public unless the user explicitly says
"OSS" / "open source" / "オープンソース".
ポイントは「Do NOT use」の否定条件を明示すること。「create a new repo」だけだと既存 repo の操作 (clone / list / delete) にも誤発火するので、AND clone it locally のような追加条件で精度を上げる。
配布は skills CLI を使えばワンライナーで終わる。
npx skills add -y -g https://github.com/ryoshin0830/ghnew
repo を clone して、 SKILL.md を自動 discovery して ~/.claude/skills/ に登録してくれる。便利。
なお npm tarball には .claude/ も CLAUDE.md も含めていない (.npmignore で除外)。 skill ファイルを npm から配るとパッケージサイズが膨らむし、typosquat のリスク面が広がるので、配布は GitHub repo 経由に統一した。
3 並列でレビュー: opencode + codex + Claude general-purpose
実装が一通り終わった時点で、コードのレビューを性格が違う 3 エージェント並列でやらせた。
- Claude の
general-purposeagent -
opencode run "..."(OpenAI 系) -
codex e "..."(OpenAI codex CLI)
得意領域が地味に違うので、揃った観点もあれば独自の指摘も結構あった。
-
opencode:
--remoteの正規表現^([^/]+)\/(.+)$がスラッシュを許容して--remote github.com/alice/evilが account=alice/evilで通過する Critical バグを発見 -
codex:
--jsonモードで gh/ghq の stderr が先に出力されるから「stderr 全体を JSON として読めない」契約破れ - Claude: skill の trigger 文言が過剰反応するので否定条件 (Do NOT use) を入れろ
「3 観点で同じ指摘されたら確実、 1-2 個しか言わなかった指摘も検証する」というトリアージで動かすと、シングル AI レビューより取りこぼしが減る感覚があった。安いし速いのでオススメ。
詰まり: opencode / codex を Bash から呼ぶと無限 hang する
このレビューフローで最初詰まったのがここ。バックグラウンドで動かしたら output が一向に出てこない。
opencode run "..." 2>&1 | tail -200 &
# → 5 分待っても output file が 0 bytes
プロセスは alive、CPU は使ってない。原因を切り分けたら codex のログに:
Reading additional input from stdin...
argv で受け取ったプロンプトに加えて、stdin からも追加入力を読みに行く仕様だった。通常のターミナルなら TTY の Ctrl-D で EOF を送れるが、Bash 経由で起動すると stdin は閉じないパイプ → 永久に EOF を待つ。
</dev/null で stdin に即時 EOF を送ると解決する。
opencode run "..." </dev/null 2>&1
codex e "..." </dev/null 2>&1
シンプルな話なんだけど、ハマると気付きにくい。CLI を自動化で叩く時の典型的な罠。
おまけ: npm link と npm unlink の関係
開発中は npm link で global PATH に「シンボリックリンク」を作って、編集 → 即実行のループを回す。
cd ~/projects/.../ghnew
npm link
# どこからでも:
ghnew foo
# → ~/.../bin/ghnew → ../lib/node_modules/ghnew → ~/projects/.../ghnew
これはファイルコピーではない。シンボリックリンク。本体は手元のディレクトリそのもので、bin/ghnew.mjs を編集したら次の ghnew foo で即反映される。
publish 後に不要になったら:
npm unlink -g ghnew # ≒ npm uninstall -g ghnew
両者は実質同じコマンド。違いは「linked package を消すなら unlink」「registry インストール版を消すなら uninstall」という意図表現だけ。ローカルのプロジェクトディレクトリは無傷で残るので「unlink でソースコードが消えるんじゃ…?」みたいな心配は不要。
0.2.0 の機能まとめ
-
--json: 機械可読 1 行 JSON、schemaVersion: 1で将来の互換性判定を可能に -
--quiet: path のみ、script 用 -
--remote HOST/LOGIN:--host+--accountのショートハンド - 色つき box (Claude Code / opencode の流儀に近い)
-
cでcd "<path>"をクリップボードへ (pbcopy/wl-copy/xclip/ OSC 52 over SSH) -
NO_COLOR対応 (no-color.org 仕様準拠: env が set かつ非空のときのみ disable) - 終了コード
E_VALIDATION(1),E_AUTH(2),E_DEPS(127),E_INTERRUPTED(130) を明文化
まとめ — 学んだ 5 つ
-
「全部自動」が常に正解とは限らない。
cdまで自動化を諦めたら設計が一気にシンプルになった。便利さの最後の 10% は要件次第。 -
gh CLI も罠がある。
--hostnameは存在しない、gh auth status --json hostsの方がロバスト。 -
stdout/stderr 規律は文章化しておく。後から変更が入って壊れるから、
CLAUDE.mdに「不変条件」として残す。 - npm publish は 2026 では「パスキーでブラウザ往復」が普通。OTP コード入力という体験は消えつつある。
- AI レビューは並列で観点を散らすと取りこぼしが減る。性格が違う 3 エージェントに同じコードを見せるのは安いし速い。
ぜひ試してみてください。
npx ghnew my-new-app
Discussion