关于 stay-awake
面向使用 miserlymouse 的 macOS 操作流程技能,用于按时长或时钟时间保持机器唤醒,并安全替换已有定时断言。它会区分定时运行与包装任务,避免终止任务所持有的唤醒断言。
主要功能
- 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 中,因此请阅读该文档,而不要猜测格式。
默认时长为两小时
未指定时长的请求,例如单独说“保持唤醒”,表示 2h。
像启动其他请求一样启动它即可,因为默认值和其他时长一样,只是一个时长,不需要特殊处理。
miserlymouse 本身不提供此默认值,因为该工具要求指定时长且没有自己的默认值,所以默认值只存在于此处。
回复中不要提及默认值,因为那一行已经说明机器会保持唤醒多久。
请求是替换,而非叠加
“让这台机器再运行两个小时”表示机器应保持唤醒,直到从现在起两小时后。
这并不表示在现有计划的基础上再加两个小时。
因此,新的结束时间始终是发出请求的时刻加上所要求的时长,无论此前正在运行什么。
这既可以缩短时长,也可以延长时长;在一个八小时的断言正在生效时请求 20m,就会正确地将结束时间提前。
唯一不会缩短的是代表正在运行的任务持有的断言,下面会介绍这一点。
miserlymouse 运行的两种形式
此技能的一切都取决于能否区分这两种情况,因此请先分类,再采取行动。
定时运行只指定了一个时长,这正是此技能会启动和替换的运行类型。
包装运行还指定了要运行的命令,例如 miserlymouse 30m make build;此技能绝不会触碰这种运行。
监督进程是 argv 中包含 bin/miserlymouse 的进程:
pgrep -f bin/miserlymouse
询问每个监督进程的直接子进程,然后确认该子进程实际是什么:
pgrep -P 22088
ps -o comm= -p 22089
直接子进程名为 caffeinate,表示这是定时运行;该子进程持有的断言是此技能可以替换的对象。
直接子进程名为其他任何名称,表示这是包装运行,此时子进程本身就是任务,而断言是它下面的孙进程。
为什么不能触碰包装运行
caffeinate 会派生一个子进程来持有断言,然后在自身进程中执行任务,因此任务会保留监督进程启动它时分配的 PID,而断言则位于任务之下,而非与任务并列。
包装运行时,caffeinate 会完全忽略 -t,因此断言会持续到任务结束为止,没有可用于与请求进行比较的计划结束时间。
终止该断言不会停止任务,只会让任务失去保持机器唤醒的保障;随后任务仍会继续运行,而机器可能进入睡眠状态。
由于断言是孙进程,从监督进程执行 pgrep -P 无法找到它;这就是安全保障,必须始终如此。
绝不要在整个进程表中按名称搜索 caffeinate 并终止搜索结果,因为这样会直接绕过这项安全保障,影响正在运行的任务所持有的断言。
只会终止定时运行的断言
通过被分类为定时运行的监督进程执行 pgrep -P,以找到每个断言。
任何没有通过这种方式找到的 caffeinate,都属于包装运行或其他工具,不能由此技能终止。
回答还能保持唤醒多久
关于机器还能保持唤醒多久的问题,需要结合整体情况回答,而且整个过程不会启动或终止任何东西。
列出机器上的所有断言:
pgrep -l caffeinate
然后逐个查看:
ps -o pid=,ppid=,lstart=,command= -p 22089
沿着每个断言向上追溯父级,确认它是否属于 miserlymouse 监督进程:限时运行时直接属于它,包装运行时则通过作业属于它。
对于限时运行,结束时间是 lstart 的值加上 -t 指定的秒数,剩余时间则是该秒数减去 etime。
对于包装运行,说明该断言会持续与作业一样久,并指出作业名称,因为没有可提供的结束时间。
对于其他情况,将其称为其他工具的断言,到此为止。
将其中最晚的结束时间作为答案,按照“如何表述结束时间”一节的要求来措辞,并明确说明是否有作业或外部断言会让机器保持唤醒,超过限时运行的结束时间。
如果没有任何断言在维持唤醒状态,就说明机器想什么时候休眠都可以。
回复只能有一行
整条回复只能包含剩余时长和结束时间,不得有其他内容。
“距离现在还有 1h,结束时间是周二凌晨 1:30”是对延长唤醒时间的请求的完整回答。
不要提及监督进程、断言、PID,也不要说明正在执行流程的哪一步,因为这些步骤只是得出答案的过程,不属于答案本身。
不要在工具调用之间叙述,因为对分类和交接过程的连续解说,只是把同样的冗长内容分散到多条消息中。
不要解释说该请求替换了现有断言,而不是新增断言,因为工具始终如此运行,每次都这么说毫无意义。
不要报告不存在其他断言,因为这一行已经涵盖了整台机器的情况。
只有当有内容改变答案时,才需要另加一行;也就是说,包装运行或外部断言使机器保持唤醒的时间超过了所述结束时间,或者新断言未能启动、旧计划仍然有效。
如何表述结束时间
向用户报告的每个结束时间,都应使用亲切易懂的星期和时刻表述,而不是时间戳。
说“周六凌晨 12:40”,绝不要说“00:40:04”,也不要说“2026-08-29T00:40:04”。
时段标记写作 AM 或 PM,绝不要拼写成“早上”“晚上”或类似说法。
像“距离现在还有 30m,结束时间是周六 12:40 AM”这样,同时给出剩余时长和时刻,让答案无需计算就能直接使用。
时长应简洁紧凑地书写,数字与单位相连,例如 2h30m 或 30m,不要写成“30 分钟”或“两个半小时”。
这与 miserlymouse 接受的输入时长格式相同,因此可以将报告中的时长直接交给工具使用。
某个时长单位为零时,应省略该部分,不要补零;因此九十分钟写作 1h30m,一百二十分钟写作 2h。
时长应从报告时刻开始计算,而不是从断言启动时刻开始计算,因为经过几次工具调用后,这两个时刻会有所不同。
省略秒数,因为没人会把笔记本电脑的唤醒时间安排精确到秒。
如果结束时间不在当天,就写出星期几;深夜提出的请求通常属于这种情况。
--json --dry-run 打印的 ISO 8601 格式是需要转换的输入,不应原样交给用户。
启动或替换
这些步骤的顺序正是这项技能的关键。
必须先让新的断言生效,再让旧断言失效,否则机器会在间隙中短暂进入可休眠状态。
第一步,按上文对每个 supervisor 进行分类,并仅记录定时运行的断言 PID。
循环运行也要记录,但应作为需要报告的事项,而不是需要终止的对象。
第二步,在后台启动新的运行,使其在本次会话结束后仍继续运行:
nohup uv run --no-active --project /Users/mtm/pdev/taylormonacelli/miserlymouse miserlymouse for 2h > /dev/null 2>&1 &
第三步,再次列出 supervisor,找出新的 supervisor,并取出第一步中未出现的 PID。
通过比较 PID 集合来识别它,因为相同持续时间的两次运行无法通过 argv 区分。
第四步,确认新的 supervisor 已实际获取其断言:
pgrep -P <new supervisor PID> caffeinate
如果没有任何输出,请在新的调用中再次运行,最多等待约 10 秒,因为冷启动 uv 并非即时完成。
如果它始终没有出现,请停止并报告失败,保留旧断言不动。
在这种情况下,机器会按旧计划保持唤醒,这是安全的结果。
第五步,现在才终止第一步中记录的每个定时断言 PID:
kill 22089
第六步,确认新的 supervisor 仍在运行,并且除它之外没有其他定时 supervisor 存活。
第一步中记录的任何循环运行都应仍在运行;它存活是成功,而非失败。
按上文所述的口头形式报告新的结束时间;只要有任务持有自己的断言,也要说明这一点,因为此时机器会一直保持唤醒,直到两者中较晚的结束时间。
终止断言,绝不终止 supervisor
终止 uv 包装进程或 Python supervisor 会使 caffeinate 成为孤儿进程,之后它会在整个原定时段内持续保持机器唤醒,却无人监管。
改为终止 caffeinate 子进程即可彻底清理,因为 supervisor 会发现子进程退出并关闭自身,同时结束 uv 包装进程。
只需对断言 PID 执行一次 kill,即可移除整棵进程树。
截止到某个时刻
以时钟时间为基准的请求仍使用相同的六个步骤,只需修改命令末尾:
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 下,并在会话结束后继续运行。
向后台运行的 supervisor 发送 SIGINT 完全不起作用,因为后台运行的 shell 会将该信号设为忽略,Python 也会继承此设置,因此工具的 Ctrl-C 清理流程不会执行。
后台运行时进度条会自行关闭,因为 stderr 不是终端,所以无需使用 --no-progress。
pgrep -f bin/miserlymouse 只会匹配 Python supervisor,不会匹配 uv 包装进程;后者的 argv 中项目路径不含 bin 组件。
添加 --json --dry-run 会打印 ISO 8601 格式的确切结束时间,但不会实际运行;这样可以在确认之前说明最终计划。