/keep-awake opens a visible Windows Terminal tab that keeps the laptop awake (12 hours by default); closing the tab ends it.

A Claude Code mod for Windows. /keep-awake opens a visible Windows Terminal tab named "Keep-awake" that stops the laptop from sleeping (and the screen from turning off) for 12 hours. Closing the tab ends it at once.
/keep-awake: start, 12 hours/keep-awake 8: start for another number of hours (0.05 to 24)/keep-awake status: on or off, and when it ends/keep-awake stop: end it (same as closing the tab)While it runs, the dim line under the prompt ends with "· awake until <day time>". Only one runs at a time. The tab keeps going if Claude Code is closed. Requires Windows Terminal (wt.exe) and PowerShell 7 (pwsh).
At the prompt of a Claude Code terminal session:
/plugin install keep-awake --marketplace keyhearingaids/claude-keep-awake
Answer y to add the marketplace, then press Enter for the user scope (every project).
From a shell:
claude plugin install keep-awake --marketplace keyhearingaids/claude-keep-awake
In the desktop app, where /plugin does not run: click + next to the prompt box, then Plugins, Add marketplace with keyhearingaids/claude-keep-awake, then Add plugin.
scripts/launch.ps1 opens scripts/keep-awake.ps1 in a new Windows Terminal tab. That script calls SetThreadExecutionState in a loop and writes its process id and end time to %LOCALAPPDATA%\claude-keep-awake\status.json, which the mod reads to show status. Windows drops the keep-awake request as soon as the tab's PowerShell process exits.
MIT. See LICENSE.
hooks/register.ts 153 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Awake } from '../types'
5
6const DEFAULT_HOURS = 12
7// 0.05 h is 3 minutes: short enough to test with.
8const MIN_HOURS = 0.05
9const MAX_HOURS = 24
10// Named in full: the engine starts commands with a trimmed PATH, which the
11// new Terminal tab inherits.
12const PWSH = 'C:\\Program Files\\PowerShell\\7\\pwsh.exe'
13
14const awake = atom({ plugin: 'keep-awake', key: 'awake' } as const, null as Awake | null)
15
16async function stateFile($: EngineInterface): Promise<string> {
17 const base = (await $.env.get('LOCALAPPDATA')) ?? (await $.env.get('TEMP')) ?? '.'
18
19 return `${base}\\claude-keep-awake\\status.json`
20}
21
22// The running keep-awake, or null: the status file the script wrote, its end
23// still ahead, and its process still alive (closing the tab kills it without
24// the script getting to delete the file).
25async function check($: EngineInterface): Promise<Awake | null> {
26 const path = await stateFile($)
27 if (!(await $.fs.exists(path))) {
28 return null
29 }
30
31 let found: Awake
32 try {
33 found = JSON.parse((await $.fs.read(path)).replace(/^/, '')) as Awake
34 } catch {
35 return null
36 }
37 if (!Number.isInteger(found.pid) || found.endsAt <= (await $.clock.now())) {
38 return null
39 }
40
41 const { stdout } = await $.process.run(
42 ['tasklist', '/FI', `PID eq ${found.pid}`, '/FO', 'CSV', '/NH'],
43 { timeoutMs: 10000 },
44 )
45
46 return stdout.includes(`"${found.pid}"`) ? found : null
47}
48
49async function refresh($: EngineInterface): Promise<Awake | null> {
50 const now = await check($).catch(() => null)
51 await update($, awake, () => now)
52
53 return now
54}
55
56async function start($: EngineInterface, hours: number): Promise<string> {
57 const running = await refresh($)
58 if (running) {
59 return `Keep-awake is already running, until ${running.label}. Close its tab (or /keep-awake stop) first to start a new one.`
60 }
61
62 // A command has 10 s in all; stop waiting for the tab well before that and
63 // let the minute timer catch a slower start.
64 const deadline = (await $.clock.now()) + 7500
65 const launcher = `${$.plugin.root}\\scripts\\launch.ps1`
66 const path = await stateFile($)
67 try {
68 const { exitCode, stderr } = await $.process.run(
69 [
70 PWSH, '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
71 '-File', launcher, '-Hours', String(hours), '-StateFile', path,
72 ],
73 { timeoutMs: 6000 },
74 )
75 if (exitCode !== 0 && stderr.trim()) {
76 return `Could not open Windows Terminal: ${stderr.trim()}`
77 }
78 } catch (error) {
79 return `Could not open Windows Terminal: ${String(error)}`
80 }
81
82 // The tab writes its status file once PowerShell is up.
83 while ((await $.clock.now()) < deadline) {
84 await $.clock.sleep(500)
85 const now = await refresh($)
86 if (now) {
87 return `Keep-awake started in a new Terminal tab, until ${now.label}. Close that tab to end it.`
88 }
89 }
90
91 return 'Opening the Keep-awake tab in Windows Terminal. It has not reported in yet; run /keep-awake status in a moment to confirm.'
92}
93
94async function stop($: EngineInterface): Promise<string> {
95 const running = await refresh($)
96 if (!running) {
97 return 'Keep-awake is not running.'
98 }
99
100 await $.process.run(['taskkill', '/PID', String(running.pid), '/F'], { timeoutMs: 10000 })
101 await refresh($)
102
103 return 'Keep-awake stopped. The laptop can sleep again.'
104}
105
106export const register: Register = on => {
107 on('session.start', async ($, e, next) => {
108 await $.command.register({
109 name: 'keep-awake',
110 description: 'Keep the laptop awake in a visible Terminal tab (12 hours unless you give a number of hours)',
111 argumentHint: '[hours | status | stop]',
112 })
113 void refresh($)
114 $.clock.every(60000, () => {
115 void refresh($)
116 })
117
118 return next(e)
119 })
120
121 on('command.run', { command: 'keep-awake' }, async ($, e) => {
122 const arg = e.args.trim().toLowerCase()
123
124 if (arg === 'stop') {
125 return { text: await stop($) }
126 }
127 if (arg === 'status') {
128 const now = await refresh($)
129
130 return { text: now ? `Keep-awake is on, until ${now.label}.` : 'Keep-awake is off.' }
131 }
132
133 const hours = arg === '' ? DEFAULT_HOURS : Number(arg.replace(/h(ours?)?$/, ''))
134 if (!Number.isFinite(hours) || hours < MIN_HOURS || hours > MAX_HOURS) {
135 return {
136 text: `Usage: /keep-awake (12 hours), /keep-awake <hours> (${MIN_HOURS} to ${MAX_HOURS}), /keep-awake status, /keep-awake stop.`,
137 }
138 }
139
140 return { text: await start($, hours) }
141 })
142
143 // The dim line under the prompt: add the end time while it runs.
144 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
145 const now = await read($, awake)
146 if (!now) {
147 return next(e)
148 }
149
150 return next({ ...e, props: { ...e.props, tail: ` · awake until ${now.label}` } })
151 })
152}
153types/index.d.ts 10 lines1// What the keep-awake script writes on start: its process id, when it ends
2// (Unix ms) and that time as the tab shows it ("Sat 2:14 AM").
3export type Awake = { pid: number; endsAt: number; label: string }
4
5declare module 'claude-code' {
6 interface PluginState {
7 'keep-awake': { awake: Awake | null }
8 }
9}
10