247-cli 0.8.3 → 1.0.1
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/agent/dist/db/index.d.ts.map +1 -1
- package/agent/dist/db/index.js +41 -2
- package/agent/dist/db/index.js.map +1 -1
- package/agent/dist/db/schema.d.ts +12 -2
- package/agent/dist/db/schema.d.ts.map +1 -1
- package/agent/dist/db/schema.js +8 -2
- package/agent/dist/db/schema.js.map +1 -1
- package/agent/dist/db/sessions.d.ts +5 -0
- package/agent/dist/db/sessions.d.ts.map +1 -1
- package/agent/dist/db/sessions.js +21 -3
- package/agent/dist/db/sessions.js.map +1 -1
- package/agent/dist/heartbeat-monitor.d.ts +23 -0
- package/agent/dist/heartbeat-monitor.d.ts.map +1 -0
- package/agent/dist/heartbeat-monitor.js +102 -0
- package/agent/dist/heartbeat-monitor.js.map +1 -0
- package/agent/dist/routes/heartbeat.d.ts +10 -0
- package/agent/dist/routes/heartbeat.d.ts.map +1 -0
- package/agent/dist/routes/heartbeat.js +123 -0
- package/agent/dist/routes/heartbeat.js.map +1 -0
- package/agent/dist/routes/index.d.ts +1 -1
- package/agent/dist/routes/index.d.ts.map +1 -1
- package/agent/dist/routes/index.js +1 -1
- package/agent/dist/routes/index.js.map +1 -1
- package/agent/dist/routes/sessions.d.ts.map +1 -1
- package/agent/dist/routes/sessions.js +30 -1
- package/agent/dist/routes/sessions.js.map +1 -1
- package/agent/dist/server.d.ts.map +1 -1
- package/agent/dist/server.js +10 -2
- package/agent/dist/server.js.map +1 -1
- package/agent/dist/setup-statusline.d.ts +10 -0
- package/agent/dist/setup-statusline.d.ts.map +1 -0
- package/agent/dist/setup-statusline.js +94 -0
- package/agent/dist/setup-statusline.js.map +1 -0
- package/agent/dist/status.d.ts +6 -0
- package/agent/dist/status.d.ts.map +1 -1
- package/agent/dist/status.js.map +1 -1
- package/agent/dist/terminal.d.ts.map +1 -1
- package/agent/dist/terminal.js +8 -2
- package/agent/dist/terminal.js.map +1 -1
- package/agent/dist/websocket-handlers.d.ts.map +1 -1
- package/agent/dist/websocket-handlers.js +19 -2
- package/agent/dist/websocket-handlers.js.map +1 -1
- package/agent/node_modules/247-shared/dist/types/index.d.ts +6 -1
- package/agent/node_modules/247-shared/dist/types/index.d.ts.map +1 -1
- package/agent/node_modules/247-shared/dist/types/index.js.map +1 -1
- package/agent/node_modules/247-shared/package.json +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +8 -11
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/hooks.d.ts.map +1 -1
- package/dist/commands/hooks.js +37 -89
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +0 -21
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +0 -18
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +6 -9
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +0 -10
- package/dist/commands/update.js.map +1 -1
- package/dist/hooks/installer.d.ts +4 -21
- package/dist/hooks/installer.d.ts.map +1 -1
- package/dist/hooks/installer.js +6 -129
- package/dist/hooks/installer.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/paths.d.ts +1 -3
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +1 -5
- package/dist/lib/paths.js.map +1 -1
- package/package.json +1 -1
- package/agent/dist/routes/hooks.d.ts +0 -6
- package/agent/dist/routes/hooks.d.ts.map +0 -1
- package/agent/dist/routes/hooks.js +0 -80
- package/agent/dist/routes/hooks.js.map +0 -1
- package/hooks/.claude-plugin/plugin.json +0 -5
- package/hooks/hooks/hooks.json +0 -66
- package/hooks/scripts/check-duplication.sh +0 -91
- package/hooks/scripts/notify-status.sh +0 -89
package/dist/hooks/installer.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { join
|
|
1
|
+
import { existsSync, lstatSync, unlinkSync, rmSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
3
|
import { getAgentPaths } from '../lib/paths.js';
|
|
4
4
|
/**
|
|
5
|
-
* Get hooks installation status
|
|
5
|
+
* Get old hooks installation status.
|
|
6
|
+
* Used for detecting legacy hooks that need cleanup.
|
|
6
7
|
*/
|
|
7
8
|
export function getHooksStatus() {
|
|
8
9
|
const paths = getAgentPaths();
|
|
@@ -18,123 +19,15 @@ export function getHooksStatus() {
|
|
|
18
19
|
// Not a symlink
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
let sourceVersion;
|
|
22
|
-
let installedVersion;
|
|
23
|
-
// Read source version
|
|
24
|
-
const sourcePluginJson = join(paths.hooksSource, '.claude-plugin', 'plugin.json');
|
|
25
|
-
if (existsSync(sourcePluginJson)) {
|
|
26
|
-
try {
|
|
27
|
-
const data = JSON.parse(readFileSync(sourcePluginJson, 'utf-8'));
|
|
28
|
-
sourceVersion = data.version;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
// Ignore parse errors
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Read installed version
|
|
35
|
-
if (installed) {
|
|
36
|
-
try {
|
|
37
|
-
const data = JSON.parse(readFileSync(pluginJsonPath, 'utf-8'));
|
|
38
|
-
installedVersion = data.version;
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// Ignore parse errors
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
22
|
return {
|
|
45
23
|
installed,
|
|
46
24
|
path: dest,
|
|
47
25
|
isSymlink,
|
|
48
|
-
sourceVersion,
|
|
49
|
-
installedVersion,
|
|
50
|
-
needsUpdate: installed && sourceVersion !== installedVersion,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Install hooks to ~/.claude-plugins/247-hooks/
|
|
55
|
-
* In dev mode: creates a symlink
|
|
56
|
-
* In prod mode: copies files
|
|
57
|
-
*/
|
|
58
|
-
export function installHooks(options = {}) {
|
|
59
|
-
const paths = getAgentPaths();
|
|
60
|
-
const source = paths.hooksSource;
|
|
61
|
-
const dest = paths.hooksDestination;
|
|
62
|
-
// Verify source exists
|
|
63
|
-
if (!existsSync(source)) {
|
|
64
|
-
return {
|
|
65
|
-
success: false,
|
|
66
|
-
installed: false,
|
|
67
|
-
updated: false,
|
|
68
|
-
path: dest,
|
|
69
|
-
error: `Hooks source not found at ${source}`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
// Create parent directory if needed
|
|
73
|
-
const parentDir = dirname(dest);
|
|
74
|
-
if (!existsSync(parentDir)) {
|
|
75
|
-
mkdirSync(parentDir, { recursive: true });
|
|
76
|
-
}
|
|
77
|
-
const status = getHooksStatus();
|
|
78
|
-
const alreadyInstalled = status.installed;
|
|
79
|
-
// Skip if already up to date (unless force)
|
|
80
|
-
if (alreadyInstalled && !options.force && !status.needsUpdate) {
|
|
81
|
-
return {
|
|
82
|
-
success: true,
|
|
83
|
-
installed: false,
|
|
84
|
-
updated: false,
|
|
85
|
-
path: dest,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
// Remove existing if updating or force
|
|
89
|
-
if (existsSync(dest)) {
|
|
90
|
-
try {
|
|
91
|
-
if (status.isSymlink) {
|
|
92
|
-
unlinkSync(dest);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
rmSync(dest, { recursive: true, force: true });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
return {
|
|
100
|
-
success: false,
|
|
101
|
-
installed: false,
|
|
102
|
-
updated: false,
|
|
103
|
-
path: dest,
|
|
104
|
-
error: `Failed to remove existing: ${err.message}`,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Determine install method
|
|
109
|
-
const useSymlink = options.useSymlink ?? paths.isDev;
|
|
110
|
-
try {
|
|
111
|
-
if (useSymlink) {
|
|
112
|
-
// Symlink for dev - changes to hooks are immediately reflected
|
|
113
|
-
symlinkSync(source, dest, 'dir');
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
// Copy for prod - stable installation
|
|
117
|
-
copyDirectoryRecursive(source, dest);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch (err) {
|
|
121
|
-
return {
|
|
122
|
-
success: false,
|
|
123
|
-
installed: false,
|
|
124
|
-
updated: false,
|
|
125
|
-
path: dest,
|
|
126
|
-
error: `Failed to install: ${err.message}`,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
success: true,
|
|
131
|
-
installed: !alreadyInstalled,
|
|
132
|
-
updated: alreadyInstalled,
|
|
133
|
-
path: dest,
|
|
134
26
|
};
|
|
135
27
|
}
|
|
136
28
|
/**
|
|
137
|
-
* Uninstall hooks from ~/.claude-plugins/
|
|
29
|
+
* Uninstall old hooks from ~/.claude-plugins/247-hooks/
|
|
30
|
+
* Used for cleaning up the deprecated plugin-based hooks system.
|
|
138
31
|
*/
|
|
139
32
|
export function uninstallHooks() {
|
|
140
33
|
const paths = getAgentPaths();
|
|
@@ -156,20 +49,4 @@ export function uninstallHooks() {
|
|
|
156
49
|
return { success: false, error: err.message };
|
|
157
50
|
}
|
|
158
51
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Recursively copy a directory
|
|
161
|
-
*/
|
|
162
|
-
function copyDirectoryRecursive(source, dest) {
|
|
163
|
-
mkdirSync(dest, { recursive: true });
|
|
164
|
-
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
165
|
-
const srcPath = join(source, entry.name);
|
|
166
|
-
const destPath = join(dest, entry.name);
|
|
167
|
-
if (entry.isDirectory()) {
|
|
168
|
-
copyDirectoryRecursive(srcPath, destPath);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
copyFileSync(srcPath, destPath);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
52
|
//# sourceMappingURL=installer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/hooks/installer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/hooks/installer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQhD;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,gBAAgB,CAAC;IACpC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC;IAEnE,MAAM,SAAS,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAC7C,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC;YACH,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB;QAClB,CAAC;IACH,CAAC;IAED,OAAO;QACL,SAAS;QACT,IAAI,EAAE,IAAI;QACV,SAAS;KACV,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,gBAAgB,CAAC;IAEpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,sBAAsB;IAClD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,UAAU,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;AACH,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ const program = new Command();
|
|
|
15
15
|
program
|
|
16
16
|
.name('247')
|
|
17
17
|
.description('247 - Access Claude Code from anywhere 24/7\nby The Vibe Company')
|
|
18
|
-
.version('0.
|
|
18
|
+
.version('1.0.1')
|
|
19
19
|
.option('-P, --profile <name>', 'Use a specific profile (dev, prod, etc.)');
|
|
20
20
|
// Add commands
|
|
21
21
|
program.addCommand(initCommand);
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -9,9 +9,7 @@ export interface AgentPaths {
|
|
|
9
9
|
cliRoot: string;
|
|
10
10
|
/** Where the agent server code is located */
|
|
11
11
|
agentRoot: string;
|
|
12
|
-
/** Where
|
|
13
|
-
hooksSource: string;
|
|
14
|
-
/** Where hooks should be installed for Claude Code */
|
|
12
|
+
/** Where old hooks were installed (for cleanup) */
|
|
15
13
|
hooksDestination: string;
|
|
16
14
|
/** Configuration directory (~/.247/) */
|
|
17
15
|
configDir: string;
|
package/dist/lib/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAEhB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAElB,
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAEhB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAElB,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAC;IAEzB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAElB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IAEnB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAEhB,oBAAoB;IACpB,MAAM,EAAE,MAAM,CAAC;IAEf,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAC;IAEhB,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IAEjB,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAC;CAChB;AAID,wBAAgB,aAAa,IAAI,UAAU,CAkD1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
|
package/dist/lib/paths.js
CHANGED
|
@@ -22,16 +22,13 @@ export function getAgentPaths() {
|
|
|
22
22
|
const monorepoRoot = resolve(cliRoot, '..', '..');
|
|
23
23
|
const isDev = existsSync(join(monorepoRoot, 'pnpm-workspace.yaml'));
|
|
24
24
|
let agentRoot;
|
|
25
|
-
let hooksSource;
|
|
26
25
|
if (isDev) {
|
|
27
|
-
// Development: agent
|
|
26
|
+
// Development: agent is in the monorepo
|
|
28
27
|
agentRoot = resolve(monorepoRoot, 'apps', 'agent');
|
|
29
|
-
hooksSource = resolve(monorepoRoot, 'packages', 'hooks');
|
|
30
28
|
}
|
|
31
29
|
else {
|
|
32
30
|
// Production: agent code is bundled with CLI
|
|
33
31
|
agentRoot = join(cliRoot, 'agent');
|
|
34
|
-
hooksSource = join(cliRoot, 'hooks');
|
|
35
32
|
}
|
|
36
33
|
// Use testable home directory (allows override via AGENT_247_HOME)
|
|
37
34
|
const home = getTestableHomedir();
|
|
@@ -49,7 +46,6 @@ export function getAgentPaths() {
|
|
|
49
46
|
cachedPaths = {
|
|
50
47
|
cliRoot,
|
|
51
48
|
agentRoot,
|
|
52
|
-
hooksSource,
|
|
53
49
|
hooksDestination: join(home, '.claude-plugins', '247-hooks'),
|
|
54
50
|
configDir,
|
|
55
51
|
configPath: join(configDir, 'config.json'),
|
package/dist/lib/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAEvC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,EAAE,CAAC;AACjD,CAAC;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAEvC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,EAAE,CAAC;AACjD,CAAC;AAkCD,IAAI,WAAW,GAAsB,IAAI,CAAC;AAE1C,MAAM,UAAU,aAAa;IAC3B,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAEpC,mEAAmE;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAE/C,oEAAoE;IACpE,uEAAuE;IACvE,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAEpE,IAAI,SAAiB,CAAC;IAEtB,IAAI,KAAK,EAAE,CAAC;QACV,wCAAwC;QACxC,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACN,6CAA6C;QAC7C,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,mEAAmE;IACnE,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAElC,0BAA0B;IAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAErC,mCAAmC;IACnC,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAC;IACtB,IAAI,MAAc,CAAC;IACnB,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;QACpB,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IACpD,CAAC;IAED,WAAW,GAAG;QACZ,OAAO;QACP,SAAS;QACT,gBAAgB,EAAE,IAAI,CAAC,IAAI,EAAE,iBAAiB,EAAE,WAAW,CAAC;QAC5D,SAAS;QACT,UAAU,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC;QAC1C,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC;QAChC,MAAM;QACN,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC;QACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK;KACN,CAAC;IAEF,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAE9B,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE5D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,WAAW,GAAG,IAAI,CAAC;AACrB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/routes/hooks.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAMjC,wBAAgB,iBAAiB,IAAI,MAAM,CA0F1C"}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hooks API routes: receive status updates from Claude Code hooks.
|
|
3
|
-
*/
|
|
4
|
-
import { Router } from 'express';
|
|
5
|
-
import { tmuxSessionStatus, broadcastStatusUpdate } from '../status.js';
|
|
6
|
-
import * as sessionsDb from '../db/sessions.js';
|
|
7
|
-
import { getEnvironmentMetadata, getSessionEnvironment } from '../db/environments.js';
|
|
8
|
-
export function createHooksRoutes() {
|
|
9
|
-
const router = Router();
|
|
10
|
-
// Receive status updates from Claude Code hooks
|
|
11
|
-
router.post('/status', (req, res) => {
|
|
12
|
-
const { event, status, attention_reason, session_id, tmux_session, project, timestamp } = req.body;
|
|
13
|
-
if (!event) {
|
|
14
|
-
return res.status(400).json({ error: 'Missing event' });
|
|
15
|
-
}
|
|
16
|
-
const validStatuses = ['init', 'working', 'needs_attention', 'idle'];
|
|
17
|
-
const receivedStatus = validStatuses.includes(status) ? status : 'working';
|
|
18
|
-
const validReasons = [
|
|
19
|
-
'permission',
|
|
20
|
-
'input',
|
|
21
|
-
'plan_approval',
|
|
22
|
-
'task_complete',
|
|
23
|
-
];
|
|
24
|
-
const receivedReason = attention_reason && validReasons.includes(attention_reason) ? attention_reason : undefined;
|
|
25
|
-
const now = Date.now();
|
|
26
|
-
if (tmux_session) {
|
|
27
|
-
const existing = tmuxSessionStatus.get(tmux_session);
|
|
28
|
-
const statusChanged = !existing || existing.status !== receivedStatus;
|
|
29
|
-
const hookData = {
|
|
30
|
-
status: receivedStatus,
|
|
31
|
-
attentionReason: receivedReason,
|
|
32
|
-
lastEvent: event,
|
|
33
|
-
lastActivity: timestamp || now,
|
|
34
|
-
lastStatusChange: statusChanged ? now : existing.lastStatusChange,
|
|
35
|
-
project,
|
|
36
|
-
};
|
|
37
|
-
const sessionProject = tmux_session.split('--')[0] || project;
|
|
38
|
-
const dbSession = sessionsDb.upsertSession(tmux_session, {
|
|
39
|
-
project: sessionProject,
|
|
40
|
-
status: receivedStatus,
|
|
41
|
-
attentionReason: receivedReason,
|
|
42
|
-
lastEvent: event,
|
|
43
|
-
lastActivity: timestamp || now,
|
|
44
|
-
lastStatusChange: statusChanged ? now : (existing?.lastStatusChange ?? now),
|
|
45
|
-
environmentId: getSessionEnvironment(tmux_session),
|
|
46
|
-
});
|
|
47
|
-
tmuxSessionStatus.set(tmux_session, hookData);
|
|
48
|
-
const envId = getSessionEnvironment(tmux_session);
|
|
49
|
-
const envMeta = envId ? getEnvironmentMetadata(envId) : undefined;
|
|
50
|
-
broadcastStatusUpdate({
|
|
51
|
-
name: tmux_session,
|
|
52
|
-
project: sessionProject || project,
|
|
53
|
-
status: hookData.status,
|
|
54
|
-
attentionReason: hookData.attentionReason,
|
|
55
|
-
statusSource: 'hook',
|
|
56
|
-
lastEvent: hookData.lastEvent,
|
|
57
|
-
lastStatusChange: hookData.lastStatusChange,
|
|
58
|
-
createdAt: dbSession.created_at,
|
|
59
|
-
lastActivity: undefined,
|
|
60
|
-
environmentId: envId,
|
|
61
|
-
environment: envMeta
|
|
62
|
-
? {
|
|
63
|
-
id: envMeta.id,
|
|
64
|
-
name: envMeta.name,
|
|
65
|
-
provider: envMeta.provider,
|
|
66
|
-
icon: envMeta.icon,
|
|
67
|
-
isDefault: envMeta.isDefault,
|
|
68
|
-
}
|
|
69
|
-
: undefined,
|
|
70
|
-
});
|
|
71
|
-
console.log(`[Hook] ${tmux_session}: ${event} → ${receivedStatus}${receivedReason ? ` (${receivedReason})` : ''}`);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
console.warn(`[Hook] WARNING: Missing tmux_session for ${event} (session_id=${session_id}, project=${project})`);
|
|
75
|
-
}
|
|
76
|
-
res.json({ received: true });
|
|
77
|
-
});
|
|
78
|
-
return router;
|
|
79
|
-
}
|
|
80
|
-
//# sourceMappingURL=hooks.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/routes/hooks.ts"],"names":[],"mappings":"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"}
|
package/hooks/hooks/hooks.json
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
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
|
|
@@ -1,89 +0,0 @@
|
|
|
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
|