247-cli 0.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.
Files changed (179) hide show
  1. package/README.md +112 -0
  2. package/agent/dist/config.d.ts +29 -0
  3. package/agent/dist/config.d.ts.map +1 -0
  4. package/agent/dist/config.js +56 -0
  5. package/agent/dist/config.js.map +1 -0
  6. package/agent/dist/db/environments.d.ts +60 -0
  7. package/agent/dist/db/environments.d.ts.map +1 -0
  8. package/agent/dist/db/environments.js +235 -0
  9. package/agent/dist/db/environments.js.map +1 -0
  10. package/agent/dist/db/history.d.ts +37 -0
  11. package/agent/dist/db/history.d.ts.map +1 -0
  12. package/agent/dist/db/history.js +98 -0
  13. package/agent/dist/db/history.js.map +1 -0
  14. package/agent/dist/db/index.d.ts +37 -0
  15. package/agent/dist/db/index.d.ts.map +1 -0
  16. package/agent/dist/db/index.js +225 -0
  17. package/agent/dist/db/index.js.map +1 -0
  18. package/agent/dist/db/schema.d.ts +70 -0
  19. package/agent/dist/db/schema.d.ts.map +1 -0
  20. package/agent/dist/db/schema.js +79 -0
  21. package/agent/dist/db/schema.js.map +1 -0
  22. package/agent/dist/db/sessions.d.ts +75 -0
  23. package/agent/dist/db/sessions.d.ts.map +1 -0
  24. package/agent/dist/db/sessions.js +244 -0
  25. package/agent/dist/db/sessions.js.map +1 -0
  26. package/agent/dist/editor.d.ts +18 -0
  27. package/agent/dist/editor.d.ts.map +1 -0
  28. package/agent/dist/editor.js +222 -0
  29. package/agent/dist/editor.js.map +1 -0
  30. package/agent/dist/environments.d.ts +59 -0
  31. package/agent/dist/environments.d.ts.map +1 -0
  32. package/agent/dist/environments.js +229 -0
  33. package/agent/dist/environments.js.map +1 -0
  34. package/agent/dist/git.d.ts +39 -0
  35. package/agent/dist/git.d.ts.map +1 -0
  36. package/agent/dist/git.js +436 -0
  37. package/agent/dist/git.js.map +1 -0
  38. package/agent/dist/index.d.ts +2 -0
  39. package/agent/dist/index.d.ts.map +1 -0
  40. package/agent/dist/index.js +18 -0
  41. package/agent/dist/index.js.map +1 -0
  42. package/agent/dist/logger.d.ts +15 -0
  43. package/agent/dist/logger.d.ts.map +1 -0
  44. package/agent/dist/logger.js +44 -0
  45. package/agent/dist/logger.js.map +1 -0
  46. package/agent/dist/routes/editor.d.ts +9 -0
  47. package/agent/dist/routes/editor.d.ts.map +1 -0
  48. package/agent/dist/routes/editor.js +63 -0
  49. package/agent/dist/routes/editor.js.map +1 -0
  50. package/agent/dist/routes/environments.d.ts +6 -0
  51. package/agent/dist/routes/environments.d.ts.map +1 -0
  52. package/agent/dist/routes/environments.js +94 -0
  53. package/agent/dist/routes/environments.js.map +1 -0
  54. package/agent/dist/routes/files.d.ts +6 -0
  55. package/agent/dist/routes/files.d.ts.map +1 -0
  56. package/agent/dist/routes/files.js +84 -0
  57. package/agent/dist/routes/files.js.map +1 -0
  58. package/agent/dist/routes/hooks.d.ts +6 -0
  59. package/agent/dist/routes/hooks.d.ts.map +1 -0
  60. package/agent/dist/routes/hooks.js +80 -0
  61. package/agent/dist/routes/hooks.js.map +1 -0
  62. package/agent/dist/routes/index.d.ts +10 -0
  63. package/agent/dist/routes/index.d.ts.map +1 -0
  64. package/agent/dist/routes/index.js +10 -0
  65. package/agent/dist/routes/index.js.map +1 -0
  66. package/agent/dist/routes/projects.d.ts +6 -0
  67. package/agent/dist/routes/projects.d.ts.map +1 -0
  68. package/agent/dist/routes/projects.js +69 -0
  69. package/agent/dist/routes/projects.js.map +1 -0
  70. package/agent/dist/routes/sessions.d.ts +6 -0
  71. package/agent/dist/routes/sessions.d.ts.map +1 -0
  72. package/agent/dist/routes/sessions.js +194 -0
  73. package/agent/dist/routes/sessions.js.map +1 -0
  74. package/agent/dist/server.d.ts +6 -0
  75. package/agent/dist/server.d.ts.map +1 -0
  76. package/agent/dist/server.js +129 -0
  77. package/agent/dist/server.js.map +1 -0
  78. package/agent/dist/status.d.ts +42 -0
  79. package/agent/dist/status.d.ts.map +1 -0
  80. package/agent/dist/status.js +134 -0
  81. package/agent/dist/status.js.map +1 -0
  82. package/agent/dist/terminal.d.ts +15 -0
  83. package/agent/dist/terminal.d.ts.map +1 -0
  84. package/agent/dist/terminal.js +135 -0
  85. package/agent/dist/terminal.js.map +1 -0
  86. package/agent/dist/websocket-handlers.d.ts +13 -0
  87. package/agent/dist/websocket-handlers.d.ts.map +1 -0
  88. package/agent/dist/websocket-handlers.js +266 -0
  89. package/agent/dist/websocket-handlers.js.map +1 -0
  90. package/agent/node_modules/247-shared/dist/index.d.ts +2 -0
  91. package/agent/node_modules/247-shared/dist/index.d.ts.map +1 -0
  92. package/agent/node_modules/247-shared/dist/index.js +2 -0
  93. package/agent/node_modules/247-shared/dist/index.js.map +1 -0
  94. package/agent/node_modules/247-shared/dist/types/index.d.ts +215 -0
  95. package/agent/node_modules/247-shared/dist/types/index.d.ts.map +1 -0
  96. package/agent/node_modules/247-shared/dist/types/index.js +48 -0
  97. package/agent/node_modules/247-shared/dist/types/index.js.map +1 -0
  98. package/agent/node_modules/247-shared/package.json +29 -0
  99. package/dist/commands/doctor.d.ts +3 -0
  100. package/dist/commands/doctor.d.ts.map +1 -0
  101. package/dist/commands/doctor.js +279 -0
  102. package/dist/commands/doctor.js.map +1 -0
  103. package/dist/commands/hooks.d.ts +3 -0
  104. package/dist/commands/hooks.d.ts.map +1 -0
  105. package/dist/commands/hooks.js +127 -0
  106. package/dist/commands/hooks.js.map +1 -0
  107. package/dist/commands/init.d.ts +3 -0
  108. package/dist/commands/init.d.ts.map +1 -0
  109. package/dist/commands/init.js +130 -0
  110. package/dist/commands/init.js.map +1 -0
  111. package/dist/commands/logs.d.ts +3 -0
  112. package/dist/commands/logs.d.ts.map +1 -0
  113. package/dist/commands/logs.js +38 -0
  114. package/dist/commands/logs.js.map +1 -0
  115. package/dist/commands/profile.d.ts +3 -0
  116. package/dist/commands/profile.d.ts.map +1 -0
  117. package/dist/commands/profile.js +156 -0
  118. package/dist/commands/profile.js.map +1 -0
  119. package/dist/commands/service.d.ts +3 -0
  120. package/dist/commands/service.d.ts.map +1 -0
  121. package/dist/commands/service.js +235 -0
  122. package/dist/commands/service.js.map +1 -0
  123. package/dist/commands/start.d.ts +3 -0
  124. package/dist/commands/start.d.ts.map +1 -0
  125. package/dist/commands/start.js +120 -0
  126. package/dist/commands/start.js.map +1 -0
  127. package/dist/commands/status.d.ts +3 -0
  128. package/dist/commands/status.d.ts.map +1 -0
  129. package/dist/commands/status.js +62 -0
  130. package/dist/commands/status.js.map +1 -0
  131. package/dist/commands/stop.d.ts +3 -0
  132. package/dist/commands/stop.d.ts.map +1 -0
  133. package/dist/commands/stop.js +23 -0
  134. package/dist/commands/stop.js.map +1 -0
  135. package/dist/commands/update.d.ts +3 -0
  136. package/dist/commands/update.d.ts.map +1 -0
  137. package/dist/commands/update.js +121 -0
  138. package/dist/commands/update.js.map +1 -0
  139. package/dist/hooks/installer.d.ts +36 -0
  140. package/dist/hooks/installer.d.ts.map +1 -0
  141. package/dist/hooks/installer.js +175 -0
  142. package/dist/hooks/installer.js.map +1 -0
  143. package/dist/index.d.ts +5 -0
  144. package/dist/index.d.ts.map +1 -0
  145. package/dist/index.js +43 -0
  146. package/dist/index.js.map +1 -0
  147. package/dist/lib/config.d.ts +71 -0
  148. package/dist/lib/config.d.ts.map +1 -0
  149. package/dist/lib/config.js +161 -0
  150. package/dist/lib/config.js.map +1 -0
  151. package/dist/lib/paths.d.ts +34 -0
  152. package/dist/lib/paths.d.ts.map +1 -0
  153. package/dist/lib/paths.js +76 -0
  154. package/dist/lib/paths.js.map +1 -0
  155. package/dist/lib/prerequisites.d.ts +36 -0
  156. package/dist/lib/prerequisites.d.ts.map +1 -0
  157. package/dist/lib/prerequisites.js +181 -0
  158. package/dist/lib/prerequisites.js.map +1 -0
  159. package/dist/lib/process.d.ts +40 -0
  160. package/dist/lib/process.d.ts.map +1 -0
  161. package/dist/lib/process.js +192 -0
  162. package/dist/lib/process.js.map +1 -0
  163. package/dist/service/index.d.ts +44 -0
  164. package/dist/service/index.d.ts.map +1 -0
  165. package/dist/service/index.js +18 -0
  166. package/dist/service/index.js.map +1 -0
  167. package/dist/service/launchd.d.ts +18 -0
  168. package/dist/service/launchd.d.ts.map +1 -0
  169. package/dist/service/launchd.js +208 -0
  170. package/dist/service/launchd.js.map +1 -0
  171. package/dist/service/systemd.d.ts +18 -0
  172. package/dist/service/systemd.d.ts.map +1 -0
  173. package/dist/service/systemd.js +196 -0
  174. package/dist/service/systemd.js.map +1 -0
  175. package/hooks/.claude-plugin/plugin.json +5 -0
  176. package/hooks/hooks/hooks.json +66 -0
  177. package/hooks/scripts/check-duplication.sh +91 -0
  178. package/hooks/scripts/notify-status.sh +89 -0
  179. package/package.json +77 -0
@@ -0,0 +1,196 @@
1
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import { getAgentPaths } from '../lib/paths.js';
7
+ import { checkTmux } from '../lib/prerequisites.js';
8
+ const execAsync = promisify(exec);
9
+ const SERVICE_NAME = '247-agent';
10
+ export class SystemdService {
11
+ platform = 'linux';
12
+ serviceName = SERVICE_NAME;
13
+ get unitPath() {
14
+ return join(homedir(), '.config', 'systemd', 'user', `${SERVICE_NAME}.service`);
15
+ }
16
+ async status() {
17
+ const installed = existsSync(this.unitPath);
18
+ let running = false;
19
+ let enabled = false;
20
+ let pid;
21
+ if (installed) {
22
+ try {
23
+ const { stdout: statusOutput } = await execAsync(`systemctl --user is-active ${SERVICE_NAME} 2>/dev/null || true`);
24
+ running = statusOutput.trim() === 'active';
25
+ const { stdout: enabledOutput } = await execAsync(`systemctl --user is-enabled ${SERVICE_NAME} 2>/dev/null || true`);
26
+ enabled = enabledOutput.trim() === 'enabled';
27
+ if (running) {
28
+ const { stdout: pidOutput } = await execAsync(`systemctl --user show ${SERVICE_NAME} --property=MainPID --value 2>/dev/null || true`);
29
+ const parsedPid = parseInt(pidOutput.trim(), 10);
30
+ if (!isNaN(parsedPid) && parsedPid > 0) {
31
+ pid = parsedPid;
32
+ }
33
+ }
34
+ }
35
+ catch {
36
+ // Service not available
37
+ }
38
+ }
39
+ return {
40
+ installed,
41
+ running,
42
+ enabled,
43
+ pid,
44
+ configPath: installed ? this.unitPath : undefined,
45
+ };
46
+ }
47
+ async install(options = {}) {
48
+ const paths = getAgentPaths();
49
+ // Verify tmux is installed
50
+ const tmuxCheck = checkTmux();
51
+ if (tmuxCheck.status === 'error') {
52
+ return {
53
+ success: false,
54
+ error: 'tmux is not installed. Please install it first: sudo apt install tmux',
55
+ };
56
+ }
57
+ // Create systemd user directory
58
+ const systemdUserDir = join(homedir(), '.config', 'systemd', 'user');
59
+ if (!existsSync(systemdUserDir)) {
60
+ mkdirSync(systemdUserDir, { recursive: true });
61
+ }
62
+ // Create log directory
63
+ const logDir = join(homedir(), '.local', 'log', '247-agent');
64
+ if (!existsSync(logDir)) {
65
+ mkdirSync(logDir, { recursive: true });
66
+ }
67
+ // Determine entry point
68
+ const entryPoint = paths.isDev
69
+ ? join(paths.agentRoot, 'src', 'index.ts')
70
+ : join(paths.agentRoot, 'dist', 'index.js');
71
+ // Generate unit content
72
+ const unitContent = this.generateUnit({
73
+ description: '247 Agent - The Vibe Company',
74
+ nodePath: paths.nodePath,
75
+ agentScript: entryPoint,
76
+ workingDirectory: paths.agentRoot,
77
+ isDev: paths.isDev,
78
+ configPath: paths.configPath,
79
+ dataDir: paths.dataDir,
80
+ });
81
+ writeFileSync(this.unitPath, unitContent, 'utf-8');
82
+ // Reload systemd
83
+ try {
84
+ await execAsync('systemctl --user daemon-reload');
85
+ }
86
+ catch (err) {
87
+ return { success: false, error: `Failed to reload systemd: ${err.message}` };
88
+ }
89
+ // Enable at boot if requested
90
+ if (options.enableAtBoot ?? true) {
91
+ try {
92
+ await execAsync(`systemctl --user enable ${SERVICE_NAME}`);
93
+ }
94
+ catch (err) {
95
+ return { success: false, error: `Failed to enable service: ${err.message}` };
96
+ }
97
+ }
98
+ // Start now if requested
99
+ if (options.startNow) {
100
+ const startResult = await this.start();
101
+ if (!startResult.success) {
102
+ return { ...startResult, configPath: this.unitPath };
103
+ }
104
+ }
105
+ return { success: true, configPath: this.unitPath };
106
+ }
107
+ async uninstall() {
108
+ const status = await this.status();
109
+ // Stop and disable first
110
+ if (status.running) {
111
+ await this.stop();
112
+ }
113
+ if (status.enabled) {
114
+ try {
115
+ await execAsync(`systemctl --user disable ${SERVICE_NAME}`);
116
+ }
117
+ catch {
118
+ // Ignore disable errors
119
+ }
120
+ }
121
+ // Remove unit file
122
+ if (existsSync(this.unitPath)) {
123
+ try {
124
+ unlinkSync(this.unitPath);
125
+ await execAsync('systemctl --user daemon-reload');
126
+ }
127
+ catch (err) {
128
+ return { success: false, error: `Failed to remove unit file: ${err.message}` };
129
+ }
130
+ }
131
+ return { success: true };
132
+ }
133
+ async start() {
134
+ try {
135
+ await execAsync(`systemctl --user start ${SERVICE_NAME}`);
136
+ return { success: true };
137
+ }
138
+ catch (err) {
139
+ return { success: false, error: err.message };
140
+ }
141
+ }
142
+ async stop() {
143
+ try {
144
+ await execAsync(`systemctl --user stop ${SERVICE_NAME}`);
145
+ return { success: true };
146
+ }
147
+ catch (err) {
148
+ return { success: false, error: err.message };
149
+ }
150
+ }
151
+ async restart() {
152
+ try {
153
+ await execAsync(`systemctl --user restart ${SERVICE_NAME}`);
154
+ return { success: true };
155
+ }
156
+ catch (err) {
157
+ return { success: false, error: err.message };
158
+ }
159
+ }
160
+ getLogPaths() {
161
+ return {
162
+ stdout: `journalctl --user -u ${SERVICE_NAME} -o cat`,
163
+ stderr: `journalctl --user -u ${SERVICE_NAME} -p err -o cat`,
164
+ };
165
+ }
166
+ generateUnit(options) {
167
+ let execStart;
168
+ if (options.isDev) {
169
+ execStart = `/usr/bin/env npx tsx ${options.agentScript}`;
170
+ }
171
+ else {
172
+ execStart = `${options.nodePath} ${options.agentScript}`;
173
+ }
174
+ return `[Unit]
175
+ Description=${options.description}
176
+ After=network.target
177
+
178
+ [Service]
179
+ Type=simple
180
+ ExecStart=${execStart}
181
+ WorkingDirectory=${options.workingDirectory}
182
+ Restart=on-failure
183
+ RestartSec=5
184
+ StandardOutput=journal
185
+ StandardError=journal
186
+
187
+ Environment="AGENT_247_CONFIG=${options.configPath}"
188
+ Environment="AGENT_247_DATA=${options.dataDir}"
189
+ Environment="PATH=/usr/local/bin:/usr/bin:/bin"
190
+
191
+ [Install]
192
+ WantedBy=default.target
193
+ `;
194
+ }
195
+ }
196
+ //# sourceMappingURL=systemd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"systemd.js","sourceRoot":"","sources":["../../src/service/systemd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "247-status",
3
+ "version": "1.0.0",
4
+ "description": "Status tracking for 247 dashboard"
5
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "description": "Simplified status tracking hooks for 247",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/notify-status.sh",
10
+ "timeout": 5000
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "PermissionRequest": [
16
+ {
17
+ "matcher": "*",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/notify-status.sh",
22
+ "timeout": 5000
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "Stop": [
28
+ {
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/notify-status.sh",
33
+ "timeout": 5000
34
+ },
35
+ {
36
+ "type": "command",
37
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/check-duplication.sh",
38
+ "timeout": 30000
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ "Notification": [
44
+ {
45
+ "hooks": [
46
+ {
47
+ "type": "command",
48
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/notify-status.sh",
49
+ "timeout": 5000
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ "SessionEnd": [
55
+ {
56
+ "hooks": [
57
+ {
58
+ "type": "command",
59
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/notify-status.sh",
60
+ "timeout": 5000
61
+ }
62
+ ]
63
+ }
64
+ ]
65
+ }
66
+ }
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+ # Code duplication check hook for Claude Code
3
+ # Runs jscpd and outputs a report after each task
4
+
5
+ # Don't fail on errors - we never want to block Claude
6
+ set +e
7
+
8
+ # Read stdin for hook input (contains cwd)
9
+ INPUT=$(cat)
10
+ CWD=$(echo "$INPUT" | jq -r '.cwd // ""' 2>/dev/null || echo "")
11
+
12
+ # If no cwd from hook, use current directory
13
+ if [ -z "$CWD" ]; then
14
+ CWD=$(pwd)
15
+ fi
16
+
17
+ # Find the project root (look for .jscpd.json or package.json)
18
+ find_project_root() {
19
+ local dir="$1"
20
+ while [ "$dir" != "/" ]; do
21
+ if [ -f "$dir/.jscpd.json" ] || [ -f "$dir/package.json" ]; then
22
+ echo "$dir"
23
+ return 0
24
+ fi
25
+ dir=$(dirname "$dir")
26
+ done
27
+ return 1
28
+ }
29
+
30
+ PROJECT_ROOT=$(find_project_root "$CWD")
31
+
32
+ # If no project root found, skip silently
33
+ if [ -z "$PROJECT_ROOT" ]; then
34
+ exit 0
35
+ fi
36
+
37
+ # Check if jscpd config exists
38
+ if [ ! -f "$PROJECT_ROOT/.jscpd.json" ]; then
39
+ exit 0
40
+ fi
41
+
42
+ # Check if npx is available
43
+ if ! command -v npx &> /dev/null; then
44
+ exit 0
45
+ fi
46
+
47
+ # Create temp directory for report
48
+ REPORT_DIR=$(mktemp -d)
49
+ trap "rm -rf $REPORT_DIR" EXIT
50
+
51
+ # Run jscpd with JSON output
52
+ cd "$PROJECT_ROOT"
53
+ npx jscpd --reporters json --output "$REPORT_DIR" > /dev/null 2>&1 || true
54
+
55
+ REPORT_FILE="$REPORT_DIR/jscpd-report.json"
56
+
57
+ # Check if report was generated
58
+ if [ ! -f "$REPORT_FILE" ]; then
59
+ exit 0
60
+ fi
61
+
62
+ # Parse the JSON report
63
+ DUPLICATES=$(jq '.statistics.clones // 0' "$REPORT_FILE" 2>/dev/null || echo "0")
64
+ PERCENTAGE=$(jq -r '.statistics.percentage // "0"' "$REPORT_FILE" 2>/dev/null || echo "0")
65
+ TOTAL_LINES=$(jq '.statistics.total.lines // 0' "$REPORT_FILE" 2>/dev/null || echo "0")
66
+
67
+ # Only show report if there are duplicates
68
+ if [ "$DUPLICATES" -gt 0 ]; then
69
+ echo ""
70
+ echo "=========================================="
71
+ echo " CODE DUPLICATION REPORT"
72
+ echo "=========================================="
73
+ echo ""
74
+ echo " Duplicate blocks: $DUPLICATES"
75
+ echo " Duplication: ${PERCENTAGE}% of $TOTAL_LINES lines"
76
+ echo ""
77
+ echo " Duplicated locations:"
78
+ echo " ---------------------"
79
+
80
+ # Extract and format duplicate locations
81
+ jq -r '.duplicates[] | " [\(.firstFile.name):\(.firstFile.startLoc.line)-\(.firstFile.endLoc.line)]\n [\(.secondFile.name):\(.secondFile.startLoc.line)-\(.secondFile.endLoc.line)]\n Lines: \(.lines) | Tokens: \(.tokens)\n"' "$REPORT_FILE" 2>/dev/null || true
82
+
83
+ echo ""
84
+ echo " TIP: Refactor with:"
85
+ echo " 'Refactor the duplicated code above to follow DRY'"
86
+ echo ""
87
+ echo "=========================================="
88
+ echo ""
89
+ fi
90
+
91
+ exit 0
@@ -0,0 +1,89 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Read stdin for hook input
5
+ INPUT=$(cat)
6
+
7
+ # Extract event details from JSON
8
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"' 2>/dev/null || echo "unknown")
9
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "")
10
+ NOTIFICATION_TYPE=$(echo "$INPUT" | jq -r '.notification_type // ""' 2>/dev/null || echo "")
11
+ STOP_REASON=$(echo "$INPUT" | jq -r '.stop_reason // ""' 2>/dev/null || echo "")
12
+ CWD=$(echo "$INPUT" | jq -r '.cwd // ""' 2>/dev/null || echo "")
13
+
14
+ # Detect tmux session name
15
+ # Priority 1: CLAUDE_TMUX_SESSION env var (set by agent - most reliable)
16
+ TMUX_SESSION="${CLAUDE_TMUX_SESSION:-}"
17
+
18
+ # Priority 2: If not set, try tmux display-message (only if in tmux context)
19
+ if [ -z "$TMUX_SESSION" ] && [ -n "${TMUX:-}" ]; then
20
+ TMUX_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null || echo "")
21
+ fi
22
+
23
+ # If still no session detected, log warning but continue
24
+ if [ -z "$TMUX_SESSION" ]; then
25
+ echo "[WARN] Could not detect tmux session for hook $EVENT" >&2
26
+ fi
27
+
28
+ # Map events to simplified status model
29
+ # Status: working | needs_attention | idle
30
+ # AttentionReason: permission | input | plan_approval | task_complete
31
+ STATUS="working"
32
+ ATTENTION_REASON=""
33
+
34
+ case "$EVENT" in
35
+ "SessionStart")
36
+ STATUS="working"
37
+ ;;
38
+ "PermissionRequest")
39
+ STATUS="needs_attention"
40
+ ATTENTION_REASON="permission"
41
+ ;;
42
+ "Stop")
43
+ STATUS="needs_attention"
44
+ # end_turn means Claude is waiting for user input
45
+ # Otherwise, task is complete
46
+ if [ "$STOP_REASON" = "end_turn" ]; then
47
+ ATTENTION_REASON="input"
48
+ else
49
+ ATTENTION_REASON="task_complete"
50
+ fi
51
+ ;;
52
+ "Notification")
53
+ # idle_prompt notification means Claude is waiting for user input
54
+ if [ "$NOTIFICATION_TYPE" = "idle_prompt" ]; then
55
+ STATUS="needs_attention"
56
+ ATTENTION_REASON="input"
57
+ fi
58
+ ;;
59
+ "SessionEnd")
60
+ STATUS="idle"
61
+ ;;
62
+ *)
63
+ # Unknown event, default to working
64
+ STATUS="working"
65
+ ;;
66
+ esac
67
+
68
+ # Extract project name from cwd (last component of path)
69
+ PROJECT=$(basename "$CWD" 2>/dev/null || echo "")
70
+
71
+ # Get current timestamp in milliseconds
72
+ TIMESTAMP=$(($(date +%s) * 1000))
73
+
74
+ # Send status update to local agent
75
+ # Note: Use || true to ensure we never block Claude execution
76
+ curl -s -X POST "http://localhost:4678/api/hooks/status" \
77
+ -H "Content-Type: application/json" \
78
+ -d "{
79
+ \"event\": \"$EVENT\",
80
+ \"status\": \"$STATUS\",
81
+ \"attention_reason\": \"$ATTENTION_REASON\",
82
+ \"session_id\": \"$SESSION_ID\",
83
+ \"tmux_session\": \"$TMUX_SESSION\",
84
+ \"project\": \"$PROJECT\",
85
+ \"timestamp\": $TIMESTAMP
86
+ }" > /dev/null 2>&1 || true
87
+
88
+ # Exit successfully (never block Claude)
89
+ exit 0
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "247-cli",
3
+ "version": "0.3.0",
4
+ "description": "247 - Access Claude Code from anywhere 24/7",
5
+ "keywords": [
6
+ "claude",
7
+ "terminal",
8
+ "remote",
9
+ "ai",
10
+ "cli",
11
+ "247",
12
+ "vibecompany"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "The Vibe Company",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/The-Vibe-Company/247",
19
+ "directory": "packages/cli"
20
+ },
21
+ "homepage": "https://247.thevibecompany.co",
22
+ "bugs": {
23
+ "url": "https://github.com/The-Vibe-Company/247/issues"
24
+ },
25
+ "type": "module",
26
+ "engines": {
27
+ "node": ">=22.0.0"
28
+ },
29
+ "os": [
30
+ "darwin",
31
+ "linux"
32
+ ],
33
+ "cpu": [
34
+ "x64",
35
+ "arm64"
36
+ ],
37
+ "bin": {
38
+ "247": "./dist/index.js"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "hooks",
43
+ "agent",
44
+ "README.md"
45
+ ],
46
+ "scripts": {
47
+ "dev": "tsx src/index.ts",
48
+ "build": "tsc",
49
+ "bundle": "bash scripts/bundle.sh",
50
+ "typecheck": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "test:coverage": "vitest run --coverage",
54
+ "prepublishOnly": "pnpm build && pnpm bundle"
55
+ },
56
+ "dependencies": {
57
+ "commander": "^12.0.0",
58
+ "chalk": "^5.3.0",
59
+ "ora": "^8.0.0",
60
+ "enquirer": "^2.4.0",
61
+ "fs-extra": "^11.2.0",
62
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
63
+ "better-sqlite3": "^12.5.0",
64
+ "express": "^4.21.0",
65
+ "ws": "^8.18.0",
66
+ "cors": "^2.8.5",
67
+ "http-proxy": "^1.18.1"
68
+ },
69
+ "devDependencies": {
70
+ "@types/better-sqlite3": "^7.6.13",
71
+ "@types/fs-extra": "^11.0.4",
72
+ "@types/node": "^22.10.5",
73
+ "tsx": "^4.19.2",
74
+ "typescript": "^5.7.2",
75
+ "vitest": "^4.0.16"
76
+ }
77
+ }