unsnooze 1.0.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/src/wizard.js ADDED
@@ -0,0 +1,109 @@
1
+ // `unsnooze setup` — first-run interactive wizard (also invoked by a bare
2
+ // `unsnooze install` in a TTY with no saved config). Detects installed agent
3
+ // CLIs, asks for toggles, writes config.json, then wires wrappers + hooks.
4
+
5
+ import { execFileSync } from 'node:child_process';
6
+ import * as p from '@clack/prompts';
7
+ import { listAgents } from './agents/index.js';
8
+ import { isCommunityGrokCli } from './agents/grok.js';
9
+ import { DEFAULTS, writeConfig } from './settings.js';
10
+
11
+ export function detectInstalledAgents({ which = defaultWhich } = {}) {
12
+ return listAgents().map(agent => ({ agent, installed: which(agent.bin) }));
13
+ }
14
+
15
+ function defaultWhich(bin) {
16
+ try {
17
+ execFileSync(process.platform === 'win32' ? 'where' : 'which', [bin], { stdio: 'pipe' });
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ export async function runWizard() {
25
+ p.intro('unsnooze — wake every limit-stopped AI coding session automatically');
26
+
27
+ const { tmuxAvailable } = await import('./tmux.js');
28
+ if (!tmuxAvailable()) {
29
+ p.log.warn(process.platform === 'win32'
30
+ ? 'tmux not found — unsnooze does not support native Windows.\nRun it inside WSL (https://learn.microsoft.com/windows/wsl/install), where tmux and the wrappers work normally.'
31
+ : 'tmux not found — unsnooze needs tmux to watch and revive sessions.\nInstall it first (brew install tmux / apt install tmux), then re-run `unsnooze setup`.');
32
+ }
33
+
34
+ const detected = detectInstalledAgents();
35
+ const options = detected.map(({ agent, installed }) => ({
36
+ value: agent.id,
37
+ label: agent.name + (agent.experimental ? ' (experimental)' : ''),
38
+ hint: installed ? 'detected' : 'not found in PATH',
39
+ }));
40
+
41
+ const agents = await p.multiselect({
42
+ message: 'Which CLIs should unsnooze guard?',
43
+ options,
44
+ initialValues: detected.filter(d => d.installed && !d.agent.experimental).map(d => d.agent.id),
45
+ required: true,
46
+ });
47
+ if (p.isCancel(agents)) return cancelled();
48
+
49
+ if (agents.includes('grok') && isCommunityGrokCli()) {
50
+ p.log.warn('Your `grok` looks like the community superagent-ai/grok-cli, not xAI\'s Grok Build.\n'
51
+ + 'unsnooze targets Grok Build; detection may not work on the community CLI.');
52
+ }
53
+
54
+ const autoResume = await p.confirm({
55
+ message: 'Auto-resume sessions when the limit resets? (off = track only, resume manually)',
56
+ initialValue: DEFAULTS.autoResume,
57
+ });
58
+ if (p.isCancel(autoResume)) return cancelled();
59
+
60
+ const menuAutoAnswer = await p.confirm({
61
+ message: 'Allowed to answer Claude\'s limit menu for you? (always picks "Stop and wait", never "Upgrade")',
62
+ initialValue: DEFAULTS.menuAutoAnswer,
63
+ });
64
+ if (p.isCancel(menuAutoAnswer)) return cancelled();
65
+
66
+ const notifications = await p.confirm({
67
+ message: 'Desktop notifications when limits hit and sessions resume?',
68
+ initialValue: DEFAULTS.notifications,
69
+ });
70
+ if (p.isCancel(notifications)) return cancelled();
71
+
72
+ const customizeMsg = await p.confirm({
73
+ message: 'Customize the message sent to resume a session?',
74
+ initialValue: false,
75
+ });
76
+ if (p.isCancel(customizeMsg)) return cancelled();
77
+
78
+ let resumeMessage = DEFAULTS.resumeMessage;
79
+ if (customizeMsg) {
80
+ const msg = await p.text({
81
+ message: 'Resume message:',
82
+ initialValue: DEFAULTS.resumeMessage,
83
+ validate: v => (v.trim() ? undefined : 'message cannot be empty'),
84
+ });
85
+ if (p.isCancel(msg)) return cancelled();
86
+ resumeMessage = msg;
87
+ }
88
+
89
+ writeConfig({
90
+ autoResume, menuAutoAnswer, notifications, resumeMessage,
91
+ agents: Object.fromEntries(listAgents().map(a => [a.id, agents.includes(a.id)])),
92
+ });
93
+
94
+ const s = p.spinner();
95
+ s.start('Installing shell wrappers and hooks');
96
+ const { cmdInstall } = await import('./install.js');
97
+ const code = cmdInstall(['--yes']);
98
+ s.stop(code === 0 ? 'Wrappers and hooks installed' : 'Install hit a problem — see output above');
99
+
100
+ p.outro(code === 0
101
+ ? 'Done. Reload your shell (`exec $SHELL`) and use claude/codex/grok as usual.\nCheck `unsnooze status` anytime; change settings with `unsnooze config`.'
102
+ : 'Setup incomplete — fix the issue above and re-run `unsnooze setup`.');
103
+ return code;
104
+ }
105
+
106
+ function cancelled() {
107
+ p.cancel('Setup cancelled — nothing was changed. Re-run anytime with `unsnooze setup`.');
108
+ return 1;
109
+ }