@codizelabs/agent-rewake 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +170 -2
- package/dist/agent-rewake.js +15797 -0
- package/dist/hosts/claude-code/.claude-plugin/marketplace.json +12 -0
- package/dist/hosts/claude-code/.claude-plugin/plugin.json +17 -0
- package/dist/hosts/claude-code/hooks/hooks.json +1 -0
- package/dist/hosts/claude-code/hooks/logic.js +382 -0
- package/dist/hosts/claude-code/hooks/register.js +727 -0
- package/package.json +80 -4
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-rewake",
|
|
3
|
+
"owner": { "name": "Codize Labs", "url": "https://github.com/codizelabs" },
|
|
4
|
+
"description": "Agent Rewake's plugin for Claude Code.",
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "rewake",
|
|
8
|
+
"source": "./",
|
|
9
|
+
"description": "Continues this session when your usage limit resets, and sends messages you schedule into it."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rewake",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Continues this session when your usage limit resets, and sends messages you schedule into it.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Codize Labs",
|
|
7
|
+
"url": "https://github.com/codizelabs"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://codizelabs.github.io/agent-rewake/",
|
|
10
|
+
"repository": "https://github.com/codizelabs/agent-rewake",
|
|
11
|
+
"license": "Apache-2.0",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"usage-limit",
|
|
14
|
+
"resume",
|
|
15
|
+
"schedule"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "modules": ["./register.js"] }
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// Pure helpers for Agent Rewake's Claude Code mod: no mods API calls, so the mod's tests and
|
|
2
|
+
// Rewake's own tests can import them. Kept in step with Rewake's core rules (src/core/resume.ts,
|
|
3
|
+
// src/core/time.ts); a test in Rewake's repository fails if they drift apart.
|
|
4
|
+
|
|
5
|
+
export const MINUTE = 60_000;
|
|
6
|
+
export const HOUR = 60 * MINUTE;
|
|
7
|
+
/** Wait this long past the reset, so the first request isn't refused at the boundary. */
|
|
8
|
+
export const AFTER_RESET_MS = 60_000;
|
|
9
|
+
/** Past this, the machine probably slept through the reset: offer the message, don't send it. */
|
|
10
|
+
export const STALE_MS = 30 * MINUTE;
|
|
11
|
+
/** How far ahead a continue keeps the Mac awake: covers a 5-hour limit (Rewake's WAKE_HORIZON_MS). */
|
|
12
|
+
export const WAKE_HORIZON_MS = 6 * HOUR;
|
|
13
|
+
/** Claude Code's own wait gives up on resets further away than this. */
|
|
14
|
+
export const NATIVE_HORIZON_MS = 24 * HOUR;
|
|
15
|
+
/** A reset further away than this is always asked about, even with "always" (rule 2). */
|
|
16
|
+
export const FAR_RESET_MS = 24 * HOUR;
|
|
17
|
+
/** A limit with no reset time after this long is not a usage limit Rewake can wait out. */
|
|
18
|
+
export const WAITING_EXPIRES_MS = 10 * MINUTE;
|
|
19
|
+
/** Times Rewake waits for a later window that is still used up, before it stops. */
|
|
20
|
+
export const MAX_REARMS = 4;
|
|
21
|
+
/** The limit came back this soon after a continue: count it as a re-hit. */
|
|
22
|
+
export const REHIT_WINDOW_MS = 15 * MINUTE;
|
|
23
|
+
/** Re-hits in a row before Rewake stops continuing this session (Claude Code's own cap is 2). */
|
|
24
|
+
export const MAX_REHITS = 2;
|
|
25
|
+
/**
|
|
26
|
+
* Sent as the person's message, so it says who sent it (Rewake's AUTO_LABEL, src/addon.ts):
|
|
27
|
+
* automation never speaks as the person (zed-launch A-5).
|
|
28
|
+
*/
|
|
29
|
+
export const CONTINUE_TEXT =
|
|
30
|
+
"[Sent automatically by Agent Rewake after the usage limit reset] Continue from where you left off.";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Spending caps are billing, never waited for, except a cap that resets within a day: a gateway's
|
|
34
|
+
* daily cap ("spend limit reached (daily; resets 00:00 UTC)") comes back on its own.
|
|
35
|
+
*/
|
|
36
|
+
const BILLING_KINDS = new Set(["spend_limit"]);
|
|
37
|
+
const isBilling = (w, now) => {
|
|
38
|
+
if (!BILLING_KINDS.has(w.kind)) return false;
|
|
39
|
+
const t = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
|
|
40
|
+
return !(Number.isFinite(t) && t > now && t - now <= FAR_RESET_MS);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* When the used-up windows reset, in ms since the epoch, or undefined when none is used up now.
|
|
45
|
+
* `rateLimits` is `$.session.usage().rateLimits` or `session.measure`'s `e.rateLimits`:
|
|
46
|
+
* `{ kind, percentUsed, resetsAt? }[]`, `resetsAt` an ISO 8601 string. The figures are those of
|
|
47
|
+
* the last API response, so a window whose reset has passed is ignored even if it still reads 100.
|
|
48
|
+
* A spending cap (`spend_limit`) is billing and never counts, unless it resets within a day.
|
|
49
|
+
*/
|
|
50
|
+
export function blockedUntil(rateLimits, now) {
|
|
51
|
+
let latest;
|
|
52
|
+
for (const w of rateLimits ?? []) {
|
|
53
|
+
if (isBilling(w, now)) continue;
|
|
54
|
+
if (!(w.percentUsed >= 100) || typeof w.resetsAt !== "string") continue;
|
|
55
|
+
const t = Date.parse(w.resetsAt);
|
|
56
|
+
if (!Number.isFinite(t) || t <= now) continue;
|
|
57
|
+
if (latest === undefined || t > latest) latest = t;
|
|
58
|
+
}
|
|
59
|
+
return latest;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The kind of the window that resets last among the used-up ones ("five_hour", "seven_day"). */
|
|
63
|
+
export function blockingKind(rateLimits, now) {
|
|
64
|
+
const until = blockedUntil(rateLimits, now);
|
|
65
|
+
return (rateLimits ?? []).find(
|
|
66
|
+
(w) => !isBilling(w, now) && w.resetsAt !== undefined && Date.parse(w.resetsAt) === until,
|
|
67
|
+
)?.kind;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Claude Code's own panels, by `CLAUDE_CODE_ENTRYPOINT`: the VS Code extension (also in Cursor and
|
|
72
|
+
* other VS Code builds) and the desktop app. They run Claude Code through the Agent SDK, so the
|
|
73
|
+
* session isn't "interactive" and has no surface, but a person is at the prompt.
|
|
74
|
+
*/
|
|
75
|
+
const PANELS = { "claude-vscode": "vscode", "claude-desktop": "desktop" };
|
|
76
|
+
|
|
77
|
+
/** Whether a person is at this session's prompt: the terminal, or one of Claude Code's panels. */
|
|
78
|
+
export function personAtPrompt({ isInteractive, entrypoint }) {
|
|
79
|
+
return (
|
|
80
|
+
isInteractive === true || (typeof entrypoint === "string" && Object.hasOwn(PANELS, entrypoint))
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The session's surface, or the panel's when the Agent SDK reports none. */
|
|
85
|
+
export function surfaceOf({ surface, entrypoint }) {
|
|
86
|
+
return (
|
|
87
|
+
surface ??
|
|
88
|
+
(typeof entrypoint === "string" && Object.hasOwn(PANELS, entrypoint)
|
|
89
|
+
? PANELS[entrypoint]
|
|
90
|
+
: null)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Whether Claude Code's own "Continue automatically at usage limit" will most likely continue
|
|
96
|
+
* this limit by itself: an interactive terminal session, the setting not turned off (absent means
|
|
97
|
+
* on), and a reset within 24 hours. The Desktop app's own checkbox can't be read, so Desktop
|
|
98
|
+
* never counts as native here: Rewake asks there.
|
|
99
|
+
*/
|
|
100
|
+
export function nativeLikely({ isInteractive, surface, setting, resetAt, now }) {
|
|
101
|
+
return (
|
|
102
|
+
isInteractive === true &&
|
|
103
|
+
surface === "terminal" &&
|
|
104
|
+
setting !== false &&
|
|
105
|
+
resetAt !== undefined &&
|
|
106
|
+
resetAt - now <= NATIVE_HORIZON_MS
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Whether to ask before arming: always, unless the person chose "always" and it's within a day.
|
|
112
|
+
* The Desktop app always asks: its own "Auto-continue when limits reset" checkbox can't be read,
|
|
113
|
+
* and both continuing would send twice.
|
|
114
|
+
*/
|
|
115
|
+
export function mustAsk({ autoContinue, fireAt, now, surface }) {
|
|
116
|
+
return autoContinue !== "always" || fireAt - now > FAR_RESET_MS || surface === "desktop";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whether a limit record has outlived its use and should be forgotten, so it no longer holds up
|
|
121
|
+
* scheduled messages or later limits: a limit that never got a reset time, Claude Code's own wait
|
|
122
|
+
* whose time is long past, or an offer left unanswered for a day after its reset.
|
|
123
|
+
*/
|
|
124
|
+
export function expired(ep, now) {
|
|
125
|
+
if (!ep) return false;
|
|
126
|
+
if (ep.state === "waiting") return now - ep.createdAt > WAITING_EXPIRES_MS;
|
|
127
|
+
if (ep.state === "native") return ep.fireAt === undefined || now - ep.fireAt > STALE_MS;
|
|
128
|
+
if (ep.state === "offered") return ep.fireAt === undefined || now - ep.fireAt > FAR_RESET_MS;
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** "3:05 PM" or "15:05", in the person's clock ("12h" by default, as in Rewake's settings). */
|
|
133
|
+
function clockTime(d, clock) {
|
|
134
|
+
const h = d.getHours();
|
|
135
|
+
const m = String(d.getMinutes()).padStart(2, "0");
|
|
136
|
+
if (clock === "24h") return `${String(h).padStart(2, "0")}:${m}`;
|
|
137
|
+
return `${h % 12 === 0 ? 12 : h % 12}:${m} ${h < 12 ? "AM" : "PM"}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const startOfDay = (ms) => {
|
|
141
|
+
const d = new Date(ms);
|
|
142
|
+
d.setHours(0, 0, 0, 0);
|
|
143
|
+
return d.getTime();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* "3:05 PM today", "9:00 AM tomorrow (Thursday)", "Friday at 9:00 AM", "Monday 12 October at
|
|
148
|
+
* 9:00 AM": Rewake's own wording for times (src/core/time.ts formatWhen), in the host's time zone.
|
|
149
|
+
*/
|
|
150
|
+
export function when(ms, now, clock = "12h") {
|
|
151
|
+
const time = clockTime(new Date(ms), clock);
|
|
152
|
+
const dayDiff = Math.round((startOfDay(ms) - startOfDay(now)) / 86_400_000);
|
|
153
|
+
const weekday = new Intl.DateTimeFormat("en-US", { weekday: "long" }).format(ms);
|
|
154
|
+
if (dayDiff === 0) return `${time} today`;
|
|
155
|
+
if (dayDiff === 1) return `${time} tomorrow (${weekday})`;
|
|
156
|
+
if (dayDiff > 1 && dayDiff < 7) return `${weekday} at ${time}`;
|
|
157
|
+
const date = new Intl.DateTimeFormat("en-US", { day: "numeric", month: "long" }).format(ms);
|
|
158
|
+
return `${weekday} ${date} at ${time}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Session ids become file names in Rewake's state folder: accept only the shapes Claude Code uses. */
|
|
162
|
+
export function safeId(id) {
|
|
163
|
+
return typeof id === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(id);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---- `/rewake`: Rewake's one command, the same everywhere (src/core/command.ts and time.ts).
|
|
167
|
+
// A copy, because this mod runs without Rewake's code; test/command.test.ts compares the two.
|
|
168
|
+
|
|
169
|
+
/** Schedules are one-off and at most 30 days ahead (src/core/time.ts). */
|
|
170
|
+
const MAX_AHEAD_MS = 30 * 24 * HOUR;
|
|
171
|
+
|
|
172
|
+
/** Hours and minutes from "09:00", "9:30pm", "9pm", "21:05". */
|
|
173
|
+
function clockOf(text) {
|
|
174
|
+
const m = /^(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/.exec(text);
|
|
175
|
+
if (!m) return undefined;
|
|
176
|
+
let h = Number(m[1]);
|
|
177
|
+
const min = m[2] === undefined ? 0 : Number(m[2]);
|
|
178
|
+
const suffix = m[3];
|
|
179
|
+
if (m[2] === undefined && suffix === undefined) return undefined;
|
|
180
|
+
if (min > 59) return undefined;
|
|
181
|
+
if (suffix) {
|
|
182
|
+
if (h < 1 || h > 12) return undefined;
|
|
183
|
+
if (suffix === "am" && h === 12) h = 0;
|
|
184
|
+
if (suffix === "pm" && h !== 12) h += 12;
|
|
185
|
+
} else if (h > 23) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
return { h, m: min };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function atLocal(now, dayOffset, h, m) {
|
|
192
|
+
const d = new Date(now);
|
|
193
|
+
d.setDate(d.getDate() + dayOffset);
|
|
194
|
+
d.setHours(h, m, 0, 0);
|
|
195
|
+
return d.getTime();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* "in 90m", "in 1h30m", "in 2d", "9pm", "21:05", "tomorrow 9:00", "2026-10-06 09:00": the same
|
|
200
|
+
* times as Rewake's parseWhen, with the same answers. Returns { ok, at } or { ok: false, error }.
|
|
201
|
+
*/
|
|
202
|
+
export function parseWhen(input, now) {
|
|
203
|
+
const text = input.trim().toLowerCase();
|
|
204
|
+
const fail = (error) => ({ ok: false, error });
|
|
205
|
+
if (text === "") return fail("Give a time, for example 09:00, tomorrow 09:00 or in 3h.");
|
|
206
|
+
let result;
|
|
207
|
+
const rel = /^in\s+((?:\d+\s*[dhm]\s*)+)$/.exec(text);
|
|
208
|
+
if (rel?.[1]) {
|
|
209
|
+
let ms = 0;
|
|
210
|
+
for (const part of rel[1].matchAll(/(\d+)\s*([dhm])/g)) {
|
|
211
|
+
const unit = part[2];
|
|
212
|
+
ms += Number(part[1]) * (unit === "d" ? 86_400_000 : unit === "h" ? HOUR : MINUTE);
|
|
213
|
+
}
|
|
214
|
+
result = ms > 0 ? now + ms : Number.NaN;
|
|
215
|
+
}
|
|
216
|
+
if (result === undefined) {
|
|
217
|
+
const day = /^(today|tomorrow)\s+(.+)$/.exec(text);
|
|
218
|
+
if (day?.[2]) {
|
|
219
|
+
const c = clockOf(day[2]);
|
|
220
|
+
result = c ? atLocal(now, day[1] === "tomorrow" ? 1 : 0, c.h, c.m) : Number.NaN;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (result === undefined) {
|
|
224
|
+
const c = clockOf(text);
|
|
225
|
+
if (c) {
|
|
226
|
+
const today = atLocal(now, 0, c.h, c.m);
|
|
227
|
+
result = today > now ? today : atLocal(now, 1, c.h, c.m);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (result === undefined && /^\d{4}-\d{2}-\d{2}[t ]\d{2}:\d{2}/i.test(input.trim()))
|
|
231
|
+
result = new Date(input.trim().replace(" ", "T")).getTime();
|
|
232
|
+
if (result === undefined)
|
|
233
|
+
return fail(
|
|
234
|
+
`"${input.trim()}" isn't a time Rewake understands. Try 09:00, tomorrow 09:00 or in 3h.`,
|
|
235
|
+
);
|
|
236
|
+
if (Number.isNaN(result)) return fail(`"${input.trim()}" isn't a valid date or time.`);
|
|
237
|
+
if (result <= now) return fail("That time has already passed.");
|
|
238
|
+
if (result - now > MAX_AHEAD_MS) return fail("Schedules can be at most 30 days ahead.");
|
|
239
|
+
return { ok: true, at: result };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* "<when> <message>", trying the longest time phrase first; a leading "at" is allowed. Returns
|
|
244
|
+
* { ok, at, text } (text may be empty: a time alone) or { ok: false, error }.
|
|
245
|
+
*/
|
|
246
|
+
export function splitWhen(args, now) {
|
|
247
|
+
let words = args.split(/\s+/).filter(Boolean);
|
|
248
|
+
if (words[0]?.toLowerCase() === "at" && words.length > 1) words = words.slice(1);
|
|
249
|
+
if (words[0]?.toLowerCase() === "in") {
|
|
250
|
+
let i = 1;
|
|
251
|
+
while (i < words.length && /^\d+[dhm](\d+[dhm])*$/i.test(words[i] ?? "")) i++;
|
|
252
|
+
const r = parseWhen(words.slice(0, i).join(" "), now);
|
|
253
|
+
return r.ok ? { ok: true, at: r.at, text: words.slice(i).join(" ") } : r;
|
|
254
|
+
}
|
|
255
|
+
let error = "Start with a time, for example: /rewake in 1h Run the tests.";
|
|
256
|
+
for (const take of [2, 1]) {
|
|
257
|
+
if (words.length < take) continue;
|
|
258
|
+
const r = parseWhen(words.slice(0, take).join(" "), now);
|
|
259
|
+
if (r.ok) return { ok: true, at: r.at, text: words.slice(take).join(" ") };
|
|
260
|
+
if (take === 1) error = r.error;
|
|
261
|
+
}
|
|
262
|
+
return { ok: false, error };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** `/rewake <args>` as one of the command's forms (src/core/command.ts parseRewake). */
|
|
266
|
+
export function parseCommand(args) {
|
|
267
|
+
const s = (args ?? "").trim();
|
|
268
|
+
if (s === "") return { kind: "home" };
|
|
269
|
+
const [first = "", ...words] = s.split(/\s+/);
|
|
270
|
+
const sub = first.toLowerCase();
|
|
271
|
+
const rest = words.join(" ");
|
|
272
|
+
switch (sub) {
|
|
273
|
+
case "help":
|
|
274
|
+
case "?":
|
|
275
|
+
return { kind: "help" };
|
|
276
|
+
case "list":
|
|
277
|
+
return { kind: "list" };
|
|
278
|
+
case "continue":
|
|
279
|
+
return rest ? { kind: "continue", when: rest } : { kind: "continue" };
|
|
280
|
+
case "cancel":
|
|
281
|
+
case "rm":
|
|
282
|
+
case "delete": {
|
|
283
|
+
const which = words[0]?.toLowerCase();
|
|
284
|
+
return which ? { kind: "cancel", which } : { kind: "cancel" };
|
|
285
|
+
}
|
|
286
|
+
case "clear":
|
|
287
|
+
return { kind: "cancel", which: "all" };
|
|
288
|
+
case "auto": {
|
|
289
|
+
const w = words[0]?.toLowerCase();
|
|
290
|
+
return w === "on"
|
|
291
|
+
? { kind: "auto", on: true }
|
|
292
|
+
: w === "off"
|
|
293
|
+
? { kind: "auto", on: false }
|
|
294
|
+
: { kind: "auto" };
|
|
295
|
+
}
|
|
296
|
+
case "ask":
|
|
297
|
+
return { kind: "auto", on: false };
|
|
298
|
+
case "stop":
|
|
299
|
+
return { kind: "stop" };
|
|
300
|
+
case "resume":
|
|
301
|
+
if (words.length === 0) return { kind: "continue" };
|
|
302
|
+
return { kind: "item", action: "resume", n: words[0] ?? "", rest: words.slice(1).join(" ") };
|
|
303
|
+
case "now":
|
|
304
|
+
case "pause":
|
|
305
|
+
case "move":
|
|
306
|
+
case "edit": {
|
|
307
|
+
const [n, ...more] = words;
|
|
308
|
+
return { kind: "item", action: sub, ...(n !== undefined && { n }), rest: more.join(" ") };
|
|
309
|
+
}
|
|
310
|
+
case "prompt":
|
|
311
|
+
return { kind: "prompt", text: rest };
|
|
312
|
+
case "page":
|
|
313
|
+
return { kind: "page" };
|
|
314
|
+
case "every":
|
|
315
|
+
case "cron":
|
|
316
|
+
return { kind: "repeat", sub, words };
|
|
317
|
+
default:
|
|
318
|
+
return { kind: "at", args: s };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** What `/rewake` can do in Claude Code (src/core/command.ts CLAUDE_CODE_PLACE). */
|
|
323
|
+
export const FEATURES = new Set(["messages", "cancelOne", "auto"]);
|
|
324
|
+
|
|
325
|
+
/** The feature a command needs beyond continuing and listing (src/core/command.ts featureOf). */
|
|
326
|
+
export function featureOf(c) {
|
|
327
|
+
switch (c.kind) {
|
|
328
|
+
case "repeat":
|
|
329
|
+
return "repeat";
|
|
330
|
+
case "cancel":
|
|
331
|
+
return c.which === undefined ? undefined : "cancelOne";
|
|
332
|
+
case "auto":
|
|
333
|
+
return "auto";
|
|
334
|
+
case "stop":
|
|
335
|
+
return "stop";
|
|
336
|
+
case "item":
|
|
337
|
+
return "items";
|
|
338
|
+
case "prompt":
|
|
339
|
+
return "prompt";
|
|
340
|
+
case "page":
|
|
341
|
+
return "page";
|
|
342
|
+
default:
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const FEATURE_WORDS = {
|
|
348
|
+
messages: "schedule messages",
|
|
349
|
+
repeat: "repeat messages",
|
|
350
|
+
cancelOne: "delete scheduled messages",
|
|
351
|
+
auto: "turn automatic continue on or off",
|
|
352
|
+
stop: "stop a scheduled reply",
|
|
353
|
+
items: "change a scheduled message",
|
|
354
|
+
prompt: "change the resume message",
|
|
355
|
+
page: "open the overview page",
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/** The one line for something Claude Code can't do. */
|
|
359
|
+
export function notHere(feature) {
|
|
360
|
+
return `Rewake can't ${FEATURE_WORDS[feature]} in Claude Code. Type /rewake help to see what it can do.`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** The help text (src/core/command.ts rewakeHelp(CLAUDE_CODE_PLACE)). */
|
|
364
|
+
export function helpText() {
|
|
365
|
+
const rows = [
|
|
366
|
+
["/rewake", "at a usage limit: continue after the reset"],
|
|
367
|
+
["/rewake 3:30pm", "continue after the usage limit at that time"],
|
|
368
|
+
["/rewake in 1h Run the tests", "send a message later (9pm, tomorrow 9:00, in 90m)"],
|
|
369
|
+
["/rewake list", "what's scheduled here"],
|
|
370
|
+
["/rewake cancel", "cancel the continue after the usage limit"],
|
|
371
|
+
["/rewake cancel N | all", "delete scheduled message N, or all of them"],
|
|
372
|
+
["/rewake auto on|off", "continue after usage limits without asking, or ask each time"],
|
|
373
|
+
];
|
|
374
|
+
const width = Math.max(...rows.map(([l]) => l.length));
|
|
375
|
+
return [
|
|
376
|
+
"Rewake in Claude Code:",
|
|
377
|
+
"",
|
|
378
|
+
...rows.map(([l, r]) => `${l.padEnd(width)} ${r}`),
|
|
379
|
+
"",
|
|
380
|
+
"Times: 9:00, 9pm, tomorrow 9:00, in 90m, in 3h, 2026-10-06 09:00 (your local time).",
|
|
381
|
+
].join("\n");
|
|
382
|
+
}
|