gsd-init 1.0.5 → 1.0.7
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/bin/gsd-init.js
CHANGED
|
@@ -8,11 +8,9 @@ if (major < 16) {
|
|
|
8
8
|
}
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
|
-
const os = require('os');
|
|
12
11
|
const readline = require('readline');
|
|
13
12
|
|
|
14
13
|
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
15
|
-
const OBS_ROOT = process.env.GSD_INIT_OBS_ROOT || path.join(os.homedir(), '.claude', 'gsd-observer');
|
|
16
14
|
|
|
17
15
|
const TEMPLATE_FILES = [
|
|
18
16
|
{ src: 'agents/gsd-observer.md', dst: 'agents/gsd-observer.md' },
|
|
@@ -22,6 +20,7 @@ const TEMPLATE_FILES = [
|
|
|
22
20
|
{ src: 'scripts/wake-observer.sh', dst: 'scripts/wake-observer.sh' },
|
|
23
21
|
{ src: 'scripts/notify-worker.sh', dst: 'scripts/notify-worker.sh' },
|
|
24
22
|
{ src: 'scripts/teardown.sh', dst: 'scripts/teardown.sh' },
|
|
23
|
+
{ src: 'scripts/listen.sh', dst: 'scripts/listen.sh' },
|
|
25
24
|
{ src: 'scripts/start.sh', dst: 'scripts/start.sh' },
|
|
26
25
|
{ src: 'scripts/verify.sh', dst: 'scripts/verify.sh' },
|
|
27
26
|
{ src: 'schema/event.json', dst: 'schema/event.json' },
|
|
@@ -35,14 +34,21 @@ const SH_FILES = [
|
|
|
35
34
|
'scripts/wake-observer.sh',
|
|
36
35
|
'scripts/notify-worker.sh',
|
|
37
36
|
'scripts/teardown.sh',
|
|
37
|
+
'scripts/listen.sh',
|
|
38
38
|
'scripts/start.sh',
|
|
39
39
|
'scripts/verify.sh',
|
|
40
40
|
];
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
function gsdRoot(projDir) {
|
|
43
|
+
return process.env.GSD_INIT_OBS_ROOT || path.join(projDir, '.gsd');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeGsdEntry(projDir) {
|
|
47
|
+
return {
|
|
48
|
+
matcher: '',
|
|
49
|
+
hooks: [{ type: 'command', command: path.join(gsdRoot(projDir), 'hooks', 'gsd-stop-hook.sh') }]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
46
52
|
|
|
47
53
|
function getSettingsLabel(settingsPath) {
|
|
48
54
|
if (!fs.existsSync(settingsPath)) return '[create]';
|
|
@@ -57,28 +63,24 @@ function getSettingsLabel(settingsPath) {
|
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
function planInstall(tmplDir, obsRoot, projDir) {
|
|
60
|
-
var homeDir = os.homedir();
|
|
61
66
|
var ops = [];
|
|
62
67
|
for (var i = 0; i < TEMPLATE_FILES.length; i++) {
|
|
63
68
|
var f = TEMPLATE_FILES[i];
|
|
64
69
|
var dest = path.join(obsRoot, f.dst);
|
|
65
70
|
var label = fs.existsSync(dest) ? '[overwrite]' : '[create]';
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
ops.push({ dest: dest, label: label, relative: relDisplay });
|
|
71
|
+
// Display relative to projDir
|
|
72
|
+
var rel = path.relative(projDir, dest);
|
|
73
|
+
ops.push({ dest: dest, label: label, relative: rel });
|
|
70
74
|
}
|
|
71
75
|
var settingsPath = path.join(projDir, '.claude', 'settings.json');
|
|
72
76
|
ops.push({ dest: settingsPath, label: getSettingsLabel(settingsPath), relative: '.claude/settings.json' });
|
|
73
77
|
return ops;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
// Stubs — implemented in later tasks
|
|
77
80
|
function formatDryRun(ops) {
|
|
78
81
|
var lines = ['gsd-init — GSD Observer/Worker setup', '', 'Files to install:'];
|
|
79
82
|
for (var i = 0; i < ops.length; i++) {
|
|
80
83
|
var op = ops[i];
|
|
81
|
-
// Pad label to 14 chars for alignment: '[create]' is 8, '[overwrite]' is 11, '[merge]' is 7, '[skip]' is 6
|
|
82
84
|
var padded = op.label + ' '.repeat(Math.max(1, 14 - op.label.length));
|
|
83
85
|
lines.push(' ' + padded + op.relative);
|
|
84
86
|
}
|
|
@@ -96,7 +98,8 @@ function copyTemplates(tmplDir, obsRoot) {
|
|
|
96
98
|
}
|
|
97
99
|
function chmodScripts(obsRoot) {
|
|
98
100
|
for (var i = 0; i < SH_FILES.length; i++) {
|
|
99
|
-
fs.chmodSync(path.join(obsRoot, SH_FILES[i]), 0o755);
|
|
101
|
+
try { fs.chmodSync(path.join(obsRoot, SH_FILES[i]), 0o755); }
|
|
102
|
+
catch (e) { /* ignore */ }
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
function mergeSettings(projDir, entry) {
|
|
@@ -132,18 +135,18 @@ function prompt(question) {
|
|
|
132
135
|
});
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
function printSummary() {
|
|
138
|
+
function printSummary(projDir) {
|
|
139
|
+
var gsdScripts = path.join(gsdRoot(projDir), 'scripts');
|
|
136
140
|
console.log([
|
|
137
141
|
'',
|
|
138
142
|
'Done! Next steps:',
|
|
139
|
-
'
|
|
143
|
+
' ' + path.join(gsdScripts, 'start.sh') + ' <project-name>',
|
|
140
144
|
'',
|
|
141
|
-
' Creates tmux sessions
|
|
142
|
-
' project-dir defaults to current directory if omitted.',
|
|
145
|
+
' Creates tmux sessions gsd-observer-<project-name> and gsd-worker-<project-name>.',
|
|
143
146
|
' Opens Terminal windows attached to each session.',
|
|
144
147
|
'',
|
|
145
|
-
' Observer agent
|
|
146
|
-
' Verify setup:
|
|
148
|
+
' Observer agent: ' + path.join(gsdRoot(projDir), 'agents', 'gsd-observer.md'),
|
|
149
|
+
' Verify setup: ' + path.join(gsdScripts, 'verify.sh'),
|
|
147
150
|
].join('\n'));
|
|
148
151
|
}
|
|
149
152
|
|
|
@@ -151,8 +154,10 @@ async function run() {
|
|
|
151
154
|
var args = process.argv.slice(2);
|
|
152
155
|
var skipPrompt = args.indexOf('--yes') !== -1 || args.indexOf('-y') !== -1;
|
|
153
156
|
var projDir = process.env.GSD_INIT_PROJ_DIR || process.cwd();
|
|
157
|
+
var obsRoot = gsdRoot(projDir);
|
|
158
|
+
var gsdEntry = makeGsdEntry(projDir);
|
|
154
159
|
|
|
155
|
-
var ops = planInstall(TEMPLATES_DIR,
|
|
160
|
+
var ops = planInstall(TEMPLATES_DIR, obsRoot, projDir);
|
|
156
161
|
|
|
157
162
|
// Abort on malformed settings.json early
|
|
158
163
|
var errOp = null;
|
|
@@ -176,10 +181,10 @@ async function run() {
|
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
183
|
|
|
179
|
-
// Step 1: create
|
|
184
|
+
// Step 1: create .gsd/ subdirs
|
|
180
185
|
var subdirs = ['agents', 'hooks', 'scripts', 'schema'];
|
|
181
186
|
for (var j = 0; j < subdirs.length; j++) {
|
|
182
|
-
var subdir = path.join(
|
|
187
|
+
var subdir = path.join(obsRoot, subdirs[j]);
|
|
183
188
|
try { mkdirpSync(subdir); }
|
|
184
189
|
catch (e) { console.error('Error creating ' + subdir + ': ' + e.message); process.exit(1); }
|
|
185
190
|
}
|
|
@@ -190,18 +195,18 @@ async function run() {
|
|
|
190
195
|
catch (e) { console.error('Error creating ' + dotClaudeDir + ': ' + e.message); process.exit(1); }
|
|
191
196
|
|
|
192
197
|
// Step 3: copy templates
|
|
193
|
-
try { copyTemplates(TEMPLATES_DIR,
|
|
194
|
-
catch (e) { console.error('Error copying templates to ' +
|
|
198
|
+
try { copyTemplates(TEMPLATES_DIR, obsRoot); }
|
|
199
|
+
catch (e) { console.error('Error copying templates to ' + obsRoot + ': ' + e.message); process.exit(1); }
|
|
195
200
|
|
|
196
201
|
// Step 4: chmod .sh files
|
|
197
|
-
try { chmodScripts(
|
|
198
|
-
catch (e) { console.error('Error setting permissions in ' +
|
|
202
|
+
try { chmodScripts(obsRoot); }
|
|
203
|
+
catch (e) { console.error('Error setting permissions in ' + obsRoot + ': ' + e.message); process.exit(1); }
|
|
199
204
|
|
|
200
205
|
// Step 5: merge settings.json
|
|
201
|
-
try { mergeSettings(projDir,
|
|
206
|
+
try { mergeSettings(projDir, gsdEntry); }
|
|
202
207
|
catch (e) { console.error(e.message); process.exit(1); }
|
|
203
208
|
|
|
204
|
-
printSummary();
|
|
209
|
+
printSummary(projDir);
|
|
205
210
|
}
|
|
206
211
|
|
|
207
212
|
if (require.main === module) {
|
|
@@ -211,5 +216,5 @@ if (require.main === module) {
|
|
|
211
216
|
module.exports = {
|
|
212
217
|
planInstall, getSettingsLabel, formatDryRun, mkdirpSync,
|
|
213
218
|
copyTemplates, chmodScripts, mergeSettings, printSummary,
|
|
214
|
-
TEMPLATE_FILES, SH_FILES,
|
|
219
|
+
TEMPLATE_FILES, SH_FILES, makeGsdEntry, gsdRoot
|
|
215
220
|
};
|
package/package.json
CHANGED
|
@@ -43,9 +43,9 @@ When you receive an instruction to read an event file and respond:
|
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
6. Run the notify script:
|
|
46
|
+
6. Run the notify script (path is provided in the wake instruction):
|
|
47
47
|
```bash
|
|
48
|
-
|
|
48
|
+
<project_dir>/.gsd/scripts/notify-worker.sh <event_id>
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
**Rules:**
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# GSD Listener — runs in observer session, polls for new events and wakes Observer Claude.
|
|
3
|
+
OBSERVER_SESSION="${GSD_OBSERVER_SESSION:-gsd-observer}"
|
|
4
|
+
OBSERVER_PANE="${GSD_OBSERVER_PANE:-0.0}"
|
|
5
|
+
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
POLL_INTERVAL=2
|
|
7
|
+
|
|
8
|
+
log() { echo "[gsd-listener] $*"; }
|
|
9
|
+
|
|
10
|
+
log "Listening for GSD events... (session: ${OBSERVER_SESSION}, pane: ${OBSERVER_PANE})"
|
|
11
|
+
|
|
12
|
+
declare -A seen
|
|
13
|
+
|
|
14
|
+
while true; do
|
|
15
|
+
for event_file in /tmp/gsd-event-*.json; do
|
|
16
|
+
[ -f "$event_file" ] || continue
|
|
17
|
+
event_id="${event_file#/tmp/gsd-event-}"
|
|
18
|
+
event_id="${event_id%.json}"
|
|
19
|
+
|
|
20
|
+
# Skip already seen or already responded
|
|
21
|
+
[ -n "${seen[$event_id]+_}" ] && continue
|
|
22
|
+
if [ -f "/tmp/gsd-response-${event_id}.json" ]; then
|
|
23
|
+
seen[$event_id]=1
|
|
24
|
+
continue
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
log "New event: $event_id — waking Observer Claude"
|
|
28
|
+
seen[$event_id]=1
|
|
29
|
+
"$SCRIPTS_DIR/wake-observer.sh" "$event_id" "$OBSERVER_SESSION" "$OBSERVER_PANE" || \
|
|
30
|
+
log "WARNING: wake-observer.sh failed for $event_id"
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
sleep "$POLL_INTERVAL"
|
|
34
|
+
done
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
SESSION="${GSD_OBSERVER_SESSION:-gsd-observer}"
|
|
4
|
+
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
|
|
4
6
|
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
|
5
7
|
tmux new-session -d -s "$SESSION"
|
|
8
|
+
|
|
9
|
+
# Pane 0.0 — Observer Claude
|
|
6
10
|
tmux send-keys -t "${SESSION}:0.0" "claude --allowedTools 'Bash,Read,Write,Glob,Grep'" Enter
|
|
11
|
+
|
|
12
|
+
# Pane 0.1 — Listener daemon (split horizontally, small strip at bottom)
|
|
13
|
+
tmux split-window -t "${SESSION}:0" -v -l 6
|
|
14
|
+
tmux send-keys -t "${SESSION}:0.1" "GSD_OBSERVER_SESSION=${SESSION} ${SCRIPTS_DIR}/listen.sh" Enter
|
|
15
|
+
|
|
16
|
+
# Focus Claude pane
|
|
17
|
+
tmux select-pane -t "${SESSION}:0.0"
|
|
18
|
+
|
|
7
19
|
echo "[gsd-observer] Waiting for Observer Claude to start..."
|
|
8
20
|
for i in $(seq 1 30); do
|
|
9
21
|
pane=$(tmux capture-pane -pt "${SESSION}:0.0" 2>/dev/null || echo "")
|
|
@@ -22,7 +22,7 @@ fi
|
|
|
22
22
|
echo "[wake-observer] Waiting for Observer pane to be ready..." >&2
|
|
23
23
|
ready=0
|
|
24
24
|
for i in $(seq 1 15); do
|
|
25
|
-
pane_content=$(tmux capture-pane -pt "$full_target"
|
|
25
|
+
pane_content=$(tmux capture-pane -pt "$full_target" 2>/dev/null || echo "")
|
|
26
26
|
if echo "$pane_content" | grep -qE '❯|>|\$|✓|claude'; then
|
|
27
27
|
ready=1
|
|
28
28
|
break
|
|
@@ -36,7 +36,7 @@ fi
|
|
|
36
36
|
|
|
37
37
|
# Inject task into Observer pane
|
|
38
38
|
tmux send-keys -t "$full_target" \
|
|
39
|
-
"Read ${event_path} and respond as GSD Observer. Write response to /tmp/gsd-response-${event_id}.json then run
|
|
39
|
+
"Read ${event_path} and respond as GSD Observer. Write response to /tmp/gsd-response-${event_id}.json then run ${SCRIPTS_DIR}/notify-worker.sh ${event_id}" \
|
|
40
40
|
Enter
|
|
41
41
|
|
|
42
42
|
echo "[wake-observer] Observer woken for event ${event_id}" >&2
|