stay-awake について
miserlymouseを使い、指定時間または時刻までマシンをスリープさせないための運用スキルです。時間指定の実行とコマンドを包むジョブを区別し、ジョブが保持するアサーションを誤って終了しないようにします。
主な機能
- 01時間または時刻を指定した起動
- 02既存の時間指定アサーションの置換
- 03時間指定実行とラップジョブの識別
- 04時間指定実行のアサーションのみ終了
- 05マシン全体の最も遅い終了時刻を報告
活用シーン
- 01マシンの稼働時間を延長または短縮
- 02時刻を指定した新しい終了期限の設定
- 03ジョブがスリープを妨げているか確認
- 04マシンが次にスリープ可能な時刻を報告
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ gkwa/ volcanicviper --skill stay-awakeインストール後、Claude Code で/stay-awakeを入力して読み込まれたか確認します。
詳細
スリープを防ぐ
miserlymouse は、人が読みやすい期間にわたって caffeinate のアサーションを保持します。
/Users/mtm/pdev/taylormonacelli/miserlymouse にあり、そのチェックアウトから実行されます。
uv run --no-active --project /Users/mtm/pdev/taylormonacelli/miserlymouse miserlymouse for 2h
期間と時刻の文法は /Users/mtm/pdev/taylormonacelli/miserlymouse/README.md に記載されています。形式を推測せず、そちらを読んでください。
デフォルトの期間は2時間です
期間を指定しないリクエスト、たとえば「スリープしないで」のみのリクエストは、2h を意味します。
デフォルトは他の期間と同じように扱われ、特別な処理はないため、他のリクエストと同じ方法で開始してください。
miserlymouse は期間を必須としており、独自のデフォルト値を持たないため、このデフォルト値はここだけにあり、他にはありません。
返信ではデフォルトについて触れないでください。機械がどれくらいスリープしないかは、すでに1行で示されています。
リクエストは置き換えであり、追加ではありません
「このマシンをあと2時間動かし続けて」は、今から2時間後までマシンをスリープさせないという意味です。
すでに予定されている時間に2時間を加えるという意味ではありません。
そのため、新しい終了時刻は、すでに実行中のものに関係なく、常にリクエストの時点に指定された期間を加えた時刻になります。
延長だけでなく短縮もできます。たとえば、8時間のアサーションが有効な間に 20m を指定すると、終了時刻が正しく早まります。
ただし、実行中のジョブに代わって保持されているアサーションだけは短縮されません。この点については後述します。
miserlymouse の実行形態は2種類です
このスキルでは両者を見分けることがすべての基礎となるため、操作する前に分類してください。
時間指定実行では期間だけが指定されており、このスキルが開始や置き換えを行うのはこの種の実行です。
ラップ実行では、miserlymouse 30m make build のように実行するコマンドも指定されており、このスキルは一切操作しません。
スーパーバイザープロセスとは、argv に bin/miserlymouse が含まれるプロセスです。
pgrep -f bin/miserlymouse
各スーパーバイザーに直接の子プロセスを尋ね、次にその子プロセスが実際に何であるかを確認してください。
pgrep -P 22088
ps -o comm= -p 22089
直接の子プロセスの名前が caffeinate なら時間指定実行であり、その子プロセスがこのスキルによる置き換えの対象となるアサーションです。
直接の子プロセスの名前がそれ以外ならラップ実行です。この場合、子プロセス自体がジョブであり、その下の孫プロセスがアサーションです。
ラップ実行を操作してはいけない理由
caffeinate はアサーションを保持する子プロセスをフォークした後、自身のプロセス上でジョブを exec します。そのため、ジョブはスーパーバイザーが起動した PID を引き継ぎ、アサーションはジョブの横ではなく、その下に置かれます。
ジョブがラップされると、caffeinate は -t を完全に無視するため、アサーションはジョブとまったく同じ期間だけ持続し、リクエストと比較できる予定終了時刻はありません。
そのアサーションを終了してもジョブは停止しません。マシンをスリープさせない仕組みをジョブから取り除くだけであり、その後もジョブはマシンがスリープできる状態で実行を続けます。
アサーションは孫プロセスであるため、スーパーバイザーから pgrep -P を実行しても到達できません。これが安全策であり、この状態を維持しなければなりません。
プロセステーブル全体から名前で caffeinate を検索し、見つかったプロセスを終了してはいけません。そうすると、この安全策を直接すり抜け、実行中のジョブのアサーションに手を出すことになります。
終了するのは時間指定実行のアサーションだけです
時間指定実行と分類されたスーパーバイザーから pgrep -P を実行して、各アサーションに到達してください。
そのような探索で見つからない caffeinate は、ラップ実行か別のツールに属しています。このスキルが終了させる対象ではありません。
どれくらい長くなるかに答える
マシンがどれくらい長くスリープせずにいるかという質問には、全体の状況を確認して答えます。このとき、何も開始したり終了したりしません。
マシン上のすべてのアサーションを一覧表示します。
pgrep -l caffeinate
次に、それぞれについて順に確認します。
ps -o pid=,ppid=,lstart=,command= -p 22089
それぞれを親までたどり、タイマー実行では直接、ラッピング実行ではジョブを介して、miserlymouse のスーパーバイザーに属しているかどうかを確認します。
タイマー実行の場合、終了時刻は lstart の値に -t に指定された秒数を加えた時刻で、残り時間はその秒数から etime を引いた値です。
ラッピング実行の場合、アサーションはジョブが続く限り有効だと伝え、終了時刻は示せないため、ジョブの名前を伝えます。
それ以外の場合は、別のツールのアサーションとして示し、それ以上は述べません。
それらのうち最も遅い終了時刻を回答として報告し、「終了時刻の伝え方」の節に従った表現にします。また、ジョブまたは外部のアサーションがタイマー実行の終了後もマシンをスリープさせない場合は、そのことをはっきり伝えます。
アサーションが何もなければ、マシンはいつでも自由にスリープできると伝えます。
返信は1行
返信全体は、期間と終了時刻だけにします。それ以外は何も含めません。
「今から1時間後、火曜日の午前1時30分」は、スリープせずにいる時間を延長してほしいという依頼への完全な回答です。
スーパーバイザー、アサーション、PID、または手順のどの段階かについては何も述べません。手順は回答にたどり着くためのものであり、回答の一部ではないためです。
ツール呼び出しの間に説明を挟まないでください。分類や引き継ぎについて実況すると、その冗長さを複数のメッセージに分けて伝えることになるだけです。
既存のアサーションを追加ではなく置き換えたことは説明しないでください。これはツールの通常の動作であり、毎回伝えても何も学べないためです。
ほかのアサーションがないことも報告しません。1行の回答でマシン全体の状態がすでに示されるためです。
2行目が必要なのは、回答の内容を変える場合に限ります。つまり、ラッピング実行または外部のアサーションによってマシンが指定された終了時刻を超えてスリープせずにいる場合、あるいは新しいアサーションの開始に失敗し、以前のスケジュールがそのまま有効な場合です。
終了時刻の伝え方
ユーザーに伝える終了時刻はすべて、タイムスタンプではなく、わかりやすい曜日と時刻の表現にします。
「土曜日の午前12時40分」とし、「00:40:04」や「2026-08-29T00:40:04」とはしません。
午前・午後は AM または PM と表記し、「朝」「夜」などの表現にはしません。
「今から30分後、土曜日の午前12時40分」のように、残り時間を時刻とともに示します。これにより、回答をそのまま使えます。
残り時間は、時間と分を単位に詰めて簡潔に表記します。2h30m や 30m のようにし、「30分」や「2時間半」とは書きません。
これは miserlymouse が入力として受け付けるものと同じ期間の書式なので、レポートから読み取った期間をそのままツールに渡せます。
ゼロの単位は付けずに省略します。90分は 1h30m、120分は 2h です。
この期間は、アサーションの開始時刻ではなく、レポートを行う時点から測ります。ツール呼び出しを数回行うと、両者の時刻は異なるためです。
数秒単位でノートパソコンをスケジュールする人はいないため、秒は省略します。
終了時刻が当日を過ぎる場合は、曜日を含めます。深夜の依頼では、通常このようになります。
--json --dry-run が出力する ISO 8601 形式は、この表記に変換するための入力であり、そのままユーザーに渡すものではありません。
開始または置き換え
これらの手順の順序が、このスキルの要点です。
古いアサーションを終了する前に新しいアサーションを有効にしないと、その間にマシンが一時的にスリープ可能な状態になります。
手順 1、前述のとおり、すべてのスーパーバイザーを分類し、時間指定実行のアサーション PID のみを記録します。
ラッピング実行も記録しますが、終了させるものではなく、報告するものとして扱います。
手順 2、新しいものをデタッチして起動し、このセッションが終わった後も動作するようにします。
nohup uv run --no-active --project /Users/mtm/pdev/taylormonacelli/miserlymouse miserlymouse for 2h > /dev/null 2>&1 &
手順 3、再度スーパーバイザーを一覧表示し、手順 1 にはなかった PID を特定して、新しいスーパーバイザーを見つけます。
同じ時間の実行は argv では区別できないため、PID セットの差分によって特定します。
手順 4、新しいスーパーバイザーが実際にアサーションを取得したことを確認します。
pgrep -P <new supervisor PID> caffeinate
何も表示されない場合は、新しい呼び出しで再実行し、合計で約 10 秒を上限に確認します。コールドスタート時の uv の起動は即時ではないためです。
一度も表示されない場合は、停止して失敗を報告し、古いアサーションには手を付けません。
その場合、マシンは古いスケジュールのまま起動状態を保つため、安全な結果となります。
手順 5、そしてここで初めて、手順 1 で記録した各時間指定アサーション PID を終了します。
kill 22089
手順 6、新しいスーパーバイザーが存在し、それ以外の時間指定スーパーバイザーが残っていないことを確認します。
手順 1 で記録したラッピング実行は残っていることが想定されており、それが残っているのは失敗ではなく成功です。
前述の読み上げ形式で新しい終了時刻を報告し、ジョブが独自のアサーションを維持している場合はその旨も付け加えます。その場合、マシンは両者のうち遅いほうの終了時刻まで起動状態を保つためです。
スーパーバイザーではなく、アサーションを終了する
uv ラッパー、または Python スーパーバイザーを終了すると、caffeinate が孤立し、スーパーバイザーに監視されないまま、元の時間が経過するまでマシンを起動状態に保ちます。
代わりに caffeinate の子プロセスを終了すると、スーパーバイザーが子プロセスの終了を検知して自身をシャットダウンし、それに伴って uv ラッパーも終了するため、完全に停止できます。
アサーション PID に対して kill を 1 回実行すると、プロセスツリー全体が削除されます。
時刻を指定する場合
時計の時刻に基づくリクエストでも、同じ 6 つの手順を使い、コマンドの末尾だけを変更します。
nohup uv run --no-active --project /Users/mtm/pdev/taylormonacelli/miserlymouse miserlymouse until 5pm > /dev/null 2>&1 &
miserlymouse は時刻を次に訪れる時刻として解決するため、時間に変換せず、そのまま時刻を渡します。
終了前の警告
アサーションの終了が近づくと、miserlymouse はユーザーから求められなくてもスマートフォンに ntfy の警告を送信するため、このスキルが実行するコマンドにそのためのフラグを含める必要はありません。
オフセットとトピックは /Users/mtm/pdev/taylormonacelli/miserlymouse/README.md に記載されているため、内容を言い直さず、そちらを参照してください。
警告は、ターミナルを見ていない人に確実に届くことを目的としているため、--no-notify は決して渡さないでください。
置き換えを行うと、古いアサーションに紐づいた古い警告スケジュールも終了します。適用されなくなった終了時刻の警告は、警告がないよりも悪いため、これは正しい動作です。
新しい終了時刻はいつもどおり報告し、ユーザーから尋ねられない限り警告には触れないでください。すでに登録済みの購読以外に、ユーザーが行うことはないためです。
ラッピング実行では、カウントダウン対象となる終了時刻が設定されていないため、何も送信されません。これも、時間指定実行の代わりには決してならない理由のひとつです。
参考
nohup と末尾の & はどちらも必須で、プロセスは PID 1 に付け替えられ、セッション終了後も動作し続けます。
デタッチされたスーパーバイザーに SIGINT を送っても何も起こりません。バックグラウンド化するシェルがそのシグナルを無視するように設定し、Python もそれを継承するため、ツールの Ctrl-C による終了処理は実行されません。
デタッチ状態では stderr がターミナルではないため、プログレスバーは自動的に非表示になります。したがって、--no-progress は不要です。
pgrep -f bin/miserlymouse は Python スーパーバイザーのみを対象とし、プロジェクトパスの argv に bin コンポーネントを含まない uv ラッパーには一致しません。
--json --dry-run を追加すると、正確な終了時刻を ISO 8601 形式で表示し、何も実行しません。これにより、実行を確定する前に結果のスケジュールを提示できます。