mcpField
S
stay-awake

stay-awake

github.com/gkwa生产力047 次安装今天更新
代码仓库

关于 stay-awake

面向使用 miserlymouse 的 macOS 操作流程技能,用于按时长或时钟时间保持机器唤醒,并安全替换已有定时断言。它会区分定时运行与包装任务,避免终止任务所持有的唤醒断言。

主要功能

  1. 01按时长或时钟时间启动唤醒
  2. 02替换已有定时断言
  3. 03区分定时运行与包装任务
  4. 04仅终止定时运行的断言
  5. 05汇总机器上的唤醒时限

适用场景

  1. 01延长或缩短机器保持唤醒的时间
  2. 02将唤醒计划改为指定结束时间
  3. 03检查任务是否会让机器持续唤醒
  4. 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 格式的确切结束时间,但不会实际运行;这样可以在确认之前说明最终计划。

评论

支持 Markdown