unsnooze 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -1
- package/bin/unsnooze.js +5 -0
- package/package.json +1 -1
- package/src/cli.js +35 -5
- package/src/resumer.js +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.0 — 2026-07-11
|
|
4
|
+
|
|
5
|
+
- **Per-session wake messages**: `unsnooze message <id|--all> "<text>"` sets a
|
|
6
|
+
custom resume message for specific tracked sessions (`--clear` reverts).
|
|
7
|
+
Precedence: per-session → per-agent (`resumeMessages.<id>`) → global
|
|
8
|
+
`resumeMessage`. Applies on both wake paths — typed into a live pane, or
|
|
9
|
+
carried in argv for `codex resume` — and sessions with a custom message
|
|
10
|
+
show a `msg: "…"` marker in `unsnooze status`.
|
|
11
|
+
|
|
3
12
|
## 1.2.0 — 2026-07-10
|
|
4
13
|
|
|
5
14
|
### GUI surfaces: VS Code extension, desktop apps
|
package/README.md
CHANGED
|
@@ -128,6 +128,7 @@ claude / codex / grok # normal usage — wrapped automatically
|
|
|
128
128
|
unsnooze status # tracked sessions + reset countdowns
|
|
129
129
|
unsnooze resume-now [id|--all] # don't wait for the reset time
|
|
130
130
|
unsnooze cancel [id|--all] # stop tracking a session
|
|
131
|
+
unsnooze message <id> "text" # per-session wake message (--clear to reset)
|
|
131
132
|
unsnooze config list # settings (see below)
|
|
132
133
|
unsnooze config set <k> <v> # e.g. autoResume off
|
|
133
134
|
unsnooze logs [-f] # what unsnooze has been doing
|
|
@@ -149,7 +150,7 @@ unsnooze help # full command list (also -h / --help)
|
|
|
149
150
|
| `menuAutoAnswer` | `true` | May unsnooze answer Claude's limit menu (send keys in your pane)? Off = watch-only. |
|
|
150
151
|
| `notifications` | `true` | Desktop notification on limit detected / session resumed / gave up. |
|
|
151
152
|
| `guiWatch` | `true` | May the daemon watch session files for GUI-surface stops (VS Code extension, desktop apps)? Needs the daemon running (`unsnooze install --daemon`). |
|
|
152
|
-
| `resumeMessage` | *"Continue where you left off…"* | The message sent to wake a session. |
|
|
153
|
+
| `resumeMessage` | *"Continue where you left off…"* | The message sent to wake a session. Override it for a single session with `unsnooze message <id> "…"` — visible in `unsnooze status`. |
|
|
153
154
|
| `resumeMessages.claude` / `.codex` / `.grok` | `""` | Per-agent override of `resumeMessage`. Empty = use the global message; clear one with `unsnooze config set resumeMessages.claude ""`. |
|
|
154
155
|
| `agents.claude` / `agents.codex` / `agents.grok` | `true` / `true` / `false` | Which CLIs are guarded. |
|
|
155
156
|
|
package/bin/unsnooze.js
CHANGED
|
@@ -30,6 +30,10 @@ async function main() {
|
|
|
30
30
|
const { cmdReport } = await import('../src/report.js');
|
|
31
31
|
return cmdReport(rest);
|
|
32
32
|
}
|
|
33
|
+
case 'message': {
|
|
34
|
+
const { cmdMessage } = await import('../src/cli.js');
|
|
35
|
+
return cmdMessage(rest);
|
|
36
|
+
}
|
|
33
37
|
case 'config': {
|
|
34
38
|
const { cmdConfig } = await import('../src/cli.js');
|
|
35
39
|
return cmdConfig(rest);
|
|
@@ -83,6 +87,7 @@ Usage:
|
|
|
83
87
|
unsnooze status list tracked sessions + reset countdowns
|
|
84
88
|
unsnooze resume-now [id|--all] resume stopped session(s) immediately
|
|
85
89
|
unsnooze cancel [id|--all] stop tracking session(s)
|
|
90
|
+
unsnooze message <id|--all> <t> set a per-session wake message (--clear to reset)
|
|
86
91
|
unsnooze logs [-f] show (or follow) the unsnooze log
|
|
87
92
|
unsnooze daemon persistent watcher for GUI sessions (VS Code
|
|
88
93
|
extension, desktop apps) — no tmux needed to
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -30,16 +30,19 @@ export function cmdStatus() {
|
|
|
30
30
|
const id = s.sessionId ? s.sessionId.slice(0, 8) : '(no id)';
|
|
31
31
|
const reset = s.resetAt ? `${new Date(s.resetAt).toLocaleString()} (${fmtCountdown(s.resetAt - now)})` : '?';
|
|
32
32
|
const origin = s.origin ?? (s.pane ? 'cli' : '?');
|
|
33
|
+
const msg = s.resumeMessage
|
|
34
|
+
? ` · msg: "${s.resumeMessage.length > 44 ? s.resumeMessage.slice(0, 44) + '…' : s.resumeMessage}"`
|
|
35
|
+
: '';
|
|
33
36
|
console.log(` [${s.status.toUpperCase().padEnd(9)}] ${id} ${(s.agent || 'claude').padEnd(6)} ${s.limitType?.padEnd(7) ?? 'unknown'} ${s.cwd}`);
|
|
34
|
-
console.log(` pane ${s.pane ?? '-'} · via ${origin} · resets ${reset} · attempts ${s.attempts ?? 0}/${MAX_RESUME_ATTEMPTS}${s.lastError ? ` · last error: ${s.lastError}` : ''}`);
|
|
37
|
+
console.log(` pane ${s.pane ?? '-'} · via ${origin} · resets ${reset} · attempts ${s.attempts ?? 0}/${MAX_RESUME_ATTEMPTS}${s.lastError ? ` · last error: ${s.lastError}` : ''}${msg}`);
|
|
35
38
|
}
|
|
36
39
|
return 0;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
function selectKeys(state, idOrAll) {
|
|
40
|
-
const
|
|
41
|
-
if (idOrAll === '--all' || idOrAll === undefined) return
|
|
42
|
-
const match =
|
|
42
|
+
function selectKeys(state, idOrAll, statuses = ['stopped']) {
|
|
43
|
+
const candidates = Object.values(state.sessions).filter(s => statuses.includes(s.status));
|
|
44
|
+
if (idOrAll === '--all' || idOrAll === undefined) return candidates.map(s => s.key);
|
|
45
|
+
const match = candidates.filter(s => s.key.startsWith(idOrAll) || (s.sessionId || '').startsWith(idOrAll));
|
|
43
46
|
return match.map(s => s.key);
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -82,6 +85,33 @@ export function cmdLogs(follow) {
|
|
|
82
85
|
return 0;
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
// `unsnooze message <id|--all> <text...>` — set (or --clear) the wake message
|
|
89
|
+
// for specific sessions; the resumer prefers it over the global setting.
|
|
90
|
+
export function cmdMessage(rest) {
|
|
91
|
+
const [idOrAll, ...textParts] = rest;
|
|
92
|
+
const clear = textParts[0] === '--clear';
|
|
93
|
+
const text = textParts.join(' ').trim();
|
|
94
|
+
if (!idOrAll || (!clear && !text)) {
|
|
95
|
+
console.error('unsnooze message <id|--all> <text...> (or --clear to revert to the default)');
|
|
96
|
+
return 2;
|
|
97
|
+
}
|
|
98
|
+
const state = readState();
|
|
99
|
+
// stopped OR resuming: editing the message before a retry is legitimate.
|
|
100
|
+
const keys = selectKeys(state, idOrAll, ['stopped', 'resuming']);
|
|
101
|
+
if (keys.length === 0) { console.log('unsnooze: no matching active sessions.'); return 1; }
|
|
102
|
+
updateState(s => {
|
|
103
|
+
for (const key of keys) {
|
|
104
|
+
if (!s.sessions[key]) continue;
|
|
105
|
+
if (clear) delete s.sessions[key].resumeMessage;
|
|
106
|
+
else s.sessions[key].resumeMessage = text;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
console.log(clear
|
|
110
|
+
? `unsnooze: cleared custom message on ${keys.length} session(s) (global default applies).`
|
|
111
|
+
: `unsnooze: ${keys.length} session(s) will wake with: "${text}"`);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
// `unsnooze config list | get <key> | set <key> <value>`
|
|
86
116
|
export function cmdConfig(rest) {
|
|
87
117
|
const [action, key, ...valueParts] = rest;
|
package/src/resumer.js
CHANGED
|
@@ -72,8 +72,10 @@ function selfCommand() {
|
|
|
72
72
|
export async function dispatchOne(rec, { tmux = realTmux, resumeMessage, selfCmd = selfCommand() } = {}) {
|
|
73
73
|
const key = rec.key;
|
|
74
74
|
const agent = getAgent(rec.agent);
|
|
75
|
-
//
|
|
76
|
-
|
|
75
|
+
// Wake-message precedence: per-session (`unsnooze message <id> "..."`) →
|
|
76
|
+
// explicit option → per-agent (`resumeMessages.<id>`) → global. Applies to
|
|
77
|
+
// both the live-pane sendText and the argv reopen path.
|
|
78
|
+
resumeMessage = rec.resumeMessage ?? resumeMessage ?? resolveResumeMessage(agent.id);
|
|
77
79
|
|
|
78
80
|
// Live-pane path: only if the pane still exists AND the agent CLI is its
|
|
79
81
|
// foreground command (pane ids get recycled — never inject into a random
|