@scheduler-systems/gal-cli 0.1.13-beta.2-alpha.pr30 → 0.1.13-beta.2-alpha.pr74
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/dist/index.cjs +265 -50
- package/package.json +3 -1
- package/scripts/postinstall.cjs +128 -11
- package/scripts/preuninstall.cjs +149 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scheduler-systems/gal-cli",
|
|
3
|
-
"version": "0.1.13-beta.2-alpha.
|
|
3
|
+
"version": "0.1.13-beta.2-alpha.pr74",
|
|
4
4
|
"description": "GAL CLI - Command-line tool for managing AI agent configurations across your organization",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist/index.cjs",
|
|
14
14
|
"scripts/postinstall.cjs",
|
|
15
|
+
"scripts/preuninstall.cjs",
|
|
15
16
|
"README.md",
|
|
16
17
|
"LICENSE"
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
|
19
20
|
"postinstall": "node scripts/postinstall.cjs",
|
|
21
|
+
"preuninstall": "node scripts/preuninstall.cjs",
|
|
20
22
|
"dev": "tsx watch src/index.ts",
|
|
21
23
|
"build": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.cjs --format=cjs --define:__CLI_VERSION__=\\\"$(node -p \"require('./package.json').version\")\\\" --define:__DEFAULT_API_URL__=\\\"http://localhost:3000\\\" && chmod +x dist/index.cjs",
|
|
22
24
|
"build:publish": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --target=node18 --minify --outfile=dist/index.cjs --format=cjs --define:__CLI_VERSION__=\\\"$(node -p \"require('./package.json').version\")\\\" --define:__DEFAULT_API_URL__=\\\"${DEFAULT_API_URL:-https://gal-api-s3pvf4isfa-uc.a.run.app}\\\" && sed -i '' '1s/^#!.*$//' dist/index.cjs && printf '%s\\n' '#!/usr/bin/env node' | cat - dist/index.cjs > dist/temp.cjs && mv dist/temp.cjs dist/index.cjs && chmod +x dist/index.cjs",
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Automatically installs Claude Code integrations when GAL CLI is installed:
|
|
6
6
|
* 1. SessionStart hook → ~/.claude/hooks/gal-sync-reminder.js
|
|
7
7
|
* 2. Status line script → ~/.claude/status_lines/gal-sync-status.py
|
|
8
|
+
* 3. GAL CLI rules → ~/.claude/rules/gal-cli.md
|
|
8
9
|
*
|
|
9
10
|
* These are CLI-level configs (not org-specific).
|
|
10
11
|
* Org-specific configs are handled by `gal sync --pull`.
|
|
@@ -25,8 +26,33 @@ const cliPackageJson = require('../package.json');
|
|
|
25
26
|
const CLI_VERSION = cliPackageJson.version;
|
|
26
27
|
|
|
27
28
|
// Version markers for idempotency
|
|
28
|
-
const HOOK_VERSION = '2.
|
|
29
|
+
const HOOK_VERSION = '2.1.0'; // SessionStart version (2.1.0: Added telemetry tracking)
|
|
29
30
|
const STATUS_LINE_VERSION = '1.0.0';
|
|
31
|
+
const RULES_VERSION = '1.0.0';
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// GAL CLI Rules Content
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// This is injected into ~/.claude/rules/gal-cli.md
|
|
37
|
+
// Provides persistent GAL CLI awareness without running on every prompt
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
const GAL_CLI_RULES_CONTENT = `# GAL CLI
|
|
41
|
+
|
|
42
|
+
<!-- GAL_RULES_VERSION = "${RULES_VERSION}" -->
|
|
43
|
+
|
|
44
|
+
The \`gal\` CLI is available for managing org-approved AI agent configurations.
|
|
45
|
+
|
|
46
|
+
## Available Commands
|
|
47
|
+
- \`gal sync --pull\` - Download latest approved config from your organization
|
|
48
|
+
- \`gal auth login\` - Authenticate with GitHub
|
|
49
|
+
- \`gal --help\` - See all available commands
|
|
50
|
+
|
|
51
|
+
## Behavior Rules
|
|
52
|
+
- **Confirmation Required**: Always ask the user before running any \`gal\` command
|
|
53
|
+
- **Self-Discovery**: If unsure about syntax, run \`gal --help\` or \`gal <command> --help\` first
|
|
54
|
+
- **Sync Notifications**: When you see a GAL sync notification, ask: "Do you want me to sync gal now?"
|
|
55
|
+
`;
|
|
30
56
|
|
|
31
57
|
// =============================================================================
|
|
32
58
|
// SessionStart Hook Content
|
|
@@ -61,11 +87,54 @@ const GAL_DIR = '.gal';
|
|
|
61
87
|
const SYNC_STATE_FILE = 'sync-state.json';
|
|
62
88
|
const GAL_CONFIG_FILE = path.join(os.homedir(), '.gal', 'config.json');
|
|
63
89
|
|
|
64
|
-
function showMessage(message) {
|
|
90
|
+
function showMessage(message, status) {
|
|
91
|
+
// Queue telemetry event before showing message
|
|
92
|
+
queueTelemetryEvent(status);
|
|
65
93
|
console.log(JSON.stringify({ systemMessage: message }));
|
|
66
94
|
process.exit(0);
|
|
67
95
|
}
|
|
68
96
|
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Telemetry: Queue events for CLI to send on next run (GAL-114)
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
function queueTelemetryEvent(status) {
|
|
102
|
+
const pendingEventsPath = path.join(os.homedir(), '.gal', 'telemetry-pending-events.json');
|
|
103
|
+
const galDir = path.join(os.homedir(), '.gal');
|
|
104
|
+
|
|
105
|
+
let pending = [];
|
|
106
|
+
try {
|
|
107
|
+
if (fs.existsSync(pendingEventsPath)) {
|
|
108
|
+
pending = JSON.parse(fs.readFileSync(pendingEventsPath, 'utf-8'));
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
|
|
112
|
+
// Add session start hook event
|
|
113
|
+
pending.push({
|
|
114
|
+
id: require('crypto').randomUUID(),
|
|
115
|
+
eventType: 'hook_triggered',
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
payload: {
|
|
118
|
+
notificationType: 'session_start',
|
|
119
|
+
hookVersion: '${HOOK_VERSION}',
|
|
120
|
+
status: status,
|
|
121
|
+
cwd: process.cwd(),
|
|
122
|
+
platform: process.platform,
|
|
123
|
+
nodeVersion: process.version,
|
|
124
|
+
},
|
|
125
|
+
queuedAt: Date.now(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
if (!fs.existsSync(galDir)) {
|
|
130
|
+
fs.mkdirSync(galDir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
fs.writeFileSync(pendingEventsPath, JSON.stringify(pending), 'utf-8');
|
|
133
|
+
} catch {
|
|
134
|
+
// Ignore errors - telemetry is optional
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
69
138
|
// =============================================================================
|
|
70
139
|
// Self-cleaning: Remove hook if GAL CLI is uninstalled
|
|
71
140
|
// =============================================================================
|
|
@@ -81,11 +150,16 @@ function isGalInstalled() {
|
|
|
81
150
|
|
|
82
151
|
function selfClean() {
|
|
83
152
|
const hookPath = __filename;
|
|
84
|
-
const
|
|
153
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
154
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
155
|
+
const rulesPath = path.join(claudeDir, 'rules', 'gal-cli.md');
|
|
85
156
|
|
|
86
157
|
// Remove hook file
|
|
87
158
|
try { fs.unlinkSync(hookPath); } catch {}
|
|
88
159
|
|
|
160
|
+
// Remove rules file
|
|
161
|
+
try { fs.unlinkSync(rulesPath); } catch {}
|
|
162
|
+
|
|
89
163
|
// Remove hook entries from settings.json
|
|
90
164
|
try {
|
|
91
165
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -138,7 +212,7 @@ const galConfig = readGalConfig();
|
|
|
138
212
|
|
|
139
213
|
// Check 1: Not authenticated
|
|
140
214
|
if (!galConfig || !galConfig.authToken) {
|
|
141
|
-
showMessage("🔐 GAL: Authentication required.\\nRun: gal auth login");
|
|
215
|
+
showMessage("🔐 GAL: Authentication required.\\nRun: gal auth login", 'auth_required');
|
|
142
216
|
}
|
|
143
217
|
|
|
144
218
|
// Check 2: Token expired
|
|
@@ -146,7 +220,7 @@ const tokenPayload = decodeJwt(galConfig.authToken);
|
|
|
146
220
|
if (tokenPayload && tokenPayload.exp) {
|
|
147
221
|
const expiresAt = tokenPayload.exp * 1000;
|
|
148
222
|
if (Date.now() > expiresAt) {
|
|
149
|
-
showMessage("🔐 GAL: Session expired.\\nRun: gal auth login");
|
|
223
|
+
showMessage("🔐 GAL: Session expired.\\nRun: gal auth login", 'token_expired');
|
|
150
224
|
}
|
|
151
225
|
}
|
|
152
226
|
|
|
@@ -163,13 +237,13 @@ const state = readSyncState();
|
|
|
163
237
|
|
|
164
238
|
if (!state) {
|
|
165
239
|
const orgName = galConfig.defaultOrg || 'your organization';
|
|
166
|
-
showMessage(\`📥 GAL: Not synced with \${orgName}'s approved config.\\nRun: gal sync --pull
|
|
240
|
+
showMessage(\`📥 GAL: Not synced with \${orgName}'s approved config.\\nRun: gal sync --pull\`, 'not_synced');
|
|
167
241
|
}
|
|
168
242
|
|
|
169
243
|
// Check 4: Config outdated
|
|
170
244
|
if (state.lastSyncHash !== state.approvedConfigHash) {
|
|
171
245
|
const days = Math.floor((Date.now() - new Date(state.lastSyncTimestamp).getTime()) / (24 * 60 * 60 * 1000));
|
|
172
|
-
showMessage(\`⚠️ GAL: Config is \${days} day(s) behind \${state.organization}'s approved version.\\nRun: gal sync --pull
|
|
246
|
+
showMessage(\`⚠️ GAL: Config is \${days} day(s) behind \${state.organization}'s approved version.\\nRun: gal sync --pull\`, 'config_outdated');
|
|
173
247
|
}
|
|
174
248
|
|
|
175
249
|
// Check 5: Missing synced files
|
|
@@ -180,12 +254,12 @@ if (state.syncedFiles && state.syncedFiles.length > 0) {
|
|
|
180
254
|
});
|
|
181
255
|
|
|
182
256
|
if (missingFiles.length > 0) {
|
|
183
|
-
showMessage(\`⚠️ GAL: Missing synced file(s): \${missingFiles.join(', ')}.\\nRun: gal sync --pull
|
|
257
|
+
showMessage(\`⚠️ GAL: Missing synced file(s): \${missingFiles.join(', ')}.\\nRun: gal sync --pull\`, 'missing_files');
|
|
184
258
|
}
|
|
185
259
|
}
|
|
186
260
|
|
|
187
261
|
// All good - show synced status
|
|
188
|
-
showMessage(\`✅ GAL: Synced with \${state.organization}'s approved config (v\${state.version || 'latest'})
|
|
262
|
+
showMessage(\`✅ GAL: Synced with \${state.organization}'s approved config (v\${state.version || 'latest'})\`, 'synced');
|
|
189
263
|
`;
|
|
190
264
|
|
|
191
265
|
// =============================================================================
|
|
@@ -441,6 +515,48 @@ function installHook() {
|
|
|
441
515
|
}
|
|
442
516
|
}
|
|
443
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Install GAL CLI rules to ~/.claude/rules/
|
|
520
|
+
*
|
|
521
|
+
* Key behaviors:
|
|
522
|
+
* - Idempotent: Checks version before writing
|
|
523
|
+
* - Provides persistent GAL CLI awareness
|
|
524
|
+
* - No longer runs on every prompt (unlike hooks)
|
|
525
|
+
*/
|
|
526
|
+
function installRules() {
|
|
527
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
528
|
+
const rulesDir = path.join(claudeDir, 'rules');
|
|
529
|
+
const rulesPath = path.join(rulesDir, 'gal-cli.md');
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
// Create rules directory if needed
|
|
533
|
+
if (!fs.existsSync(rulesDir)) {
|
|
534
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Check if rules file already exists with current version
|
|
538
|
+
let needsUpdate = true;
|
|
539
|
+
if (fs.existsSync(rulesPath)) {
|
|
540
|
+
const existingContent = fs.readFileSync(rulesPath, 'utf-8');
|
|
541
|
+
const versionMatch = existingContent.match(/GAL_RULES_VERSION = "([^"]+)"/);
|
|
542
|
+
if (versionMatch && versionMatch[1] === RULES_VERSION) {
|
|
543
|
+
needsUpdate = false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Write the rules file if needed
|
|
548
|
+
if (needsUpdate) {
|
|
549
|
+
fs.writeFileSync(rulesPath, GAL_CLI_RULES_CONTENT, 'utf-8');
|
|
550
|
+
console.log('✓ GAL CLI rules installed');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return true;
|
|
554
|
+
} catch (error) {
|
|
555
|
+
// Silent fail - rules are optional enhancement
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
444
560
|
/**
|
|
445
561
|
* Queue a telemetry event for the next CLI run (GAL-114)
|
|
446
562
|
* Since postinstall is CommonJS and telemetry module is ESM,
|
|
@@ -558,14 +674,15 @@ function installStatusLine() {
|
|
|
558
674
|
|
|
559
675
|
function main() {
|
|
560
676
|
const hookInstalled = installHook();
|
|
677
|
+
const rulesInstalled = installRules();
|
|
561
678
|
const statusLineInstalled = installStatusLine();
|
|
562
679
|
|
|
563
680
|
// Queue telemetry event (GAL-114)
|
|
564
681
|
queueTelemetryEvent();
|
|
565
682
|
|
|
566
|
-
if (hookInstalled || statusLineInstalled) {
|
|
683
|
+
if (hookInstalled || rulesInstalled || statusLineInstalled) {
|
|
567
684
|
console.log('');
|
|
568
|
-
console.log('Restart Claude Code for changes to take effect.');
|
|
685
|
+
console.log('Restart Claude Code/Cursor for changes to take effect.');
|
|
569
686
|
console.log('');
|
|
570
687
|
console.log('Next steps:');
|
|
571
688
|
console.log(' 1. gal auth login - Authenticate with GitHub');
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GAL CLI Preuninstall Script
|
|
4
|
+
* Automatically cleans up GAL files when the CLI is uninstalled via npm
|
|
5
|
+
* This runs before the package is removed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remove GAL hook entries from settings.json without deleting the file
|
|
14
|
+
*/
|
|
15
|
+
function removeGalHookEntries(settingsPath) {
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(settingsPath)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
22
|
+
|
|
23
|
+
if (!settings.hooks?.UserPromptSubmit) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Filter out GAL hooks
|
|
28
|
+
const originalLength = settings.hooks.UserPromptSubmit.length;
|
|
29
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter((entry) => {
|
|
30
|
+
if (!entry.hooks) return true;
|
|
31
|
+
// Keep entry only if it has non-GAL hooks
|
|
32
|
+
entry.hooks = entry.hooks.filter((hook) =>
|
|
33
|
+
!hook.command?.includes('gal-') && !hook.command?.includes('/gal/')
|
|
34
|
+
);
|
|
35
|
+
return entry.hooks.length > 0;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Remove empty hooks array
|
|
39
|
+
if (settings.hooks.UserPromptSubmit.length === 0) {
|
|
40
|
+
delete settings.hooks.UserPromptSubmit;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Remove empty hooks object
|
|
44
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
45
|
+
delete settings.hooks;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (settings.hooks?.UserPromptSubmit?.length !== originalLength) {
|
|
49
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Clean up GAL-installed files from user's home directory
|
|
61
|
+
*/
|
|
62
|
+
function cleanupUserLevel() {
|
|
63
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
64
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
65
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
66
|
+
|
|
67
|
+
let removed = [];
|
|
68
|
+
|
|
69
|
+
// Remove GAL hook files
|
|
70
|
+
if (fs.existsSync(hooksDir)) {
|
|
71
|
+
try {
|
|
72
|
+
const files = fs.readdirSync(hooksDir);
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
if (file.startsWith('gal-')) {
|
|
75
|
+
const hookPath = path.join(hooksDir, file);
|
|
76
|
+
try {
|
|
77
|
+
fs.unlinkSync(hookPath);
|
|
78
|
+
removed.push(hookPath);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// Silent fail
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
// Silent fail
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Remove GAL hook entries from settings.json
|
|
90
|
+
if (removeGalHookEntries(settingsPath)) {
|
|
91
|
+
removed.push(`${settingsPath} (GAL hooks removed)`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return removed;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Clean up GAL config from user's home directory
|
|
99
|
+
*/
|
|
100
|
+
function cleanupGalConfig() {
|
|
101
|
+
const galConfigDir = path.join(os.homedir(), '.gal');
|
|
102
|
+
|
|
103
|
+
if (fs.existsSync(galConfigDir)) {
|
|
104
|
+
try {
|
|
105
|
+
fs.rmSync(galConfigDir, { recursive: true, force: true });
|
|
106
|
+
return [galConfigDir];
|
|
107
|
+
} catch (err) {
|
|
108
|
+
// Silent fail
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Main cleanup function
|
|
118
|
+
*/
|
|
119
|
+
function cleanup() {
|
|
120
|
+
console.log('\n═══════════════════════════════════════════════════');
|
|
121
|
+
console.log(' GAL CLI Uninstall Cleanup');
|
|
122
|
+
console.log('═══════════════════════════════════════════════════\n');
|
|
123
|
+
|
|
124
|
+
const userLevelFiles = cleanupUserLevel();
|
|
125
|
+
const galConfigFiles = cleanupGalConfig();
|
|
126
|
+
const allRemoved = [...userLevelFiles, ...galConfigFiles];
|
|
127
|
+
|
|
128
|
+
if (allRemoved.length > 0) {
|
|
129
|
+
console.log('✓ Cleaned up GAL files:');
|
|
130
|
+
for (const file of allRemoved) {
|
|
131
|
+
console.log(` - ${file}`);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
console.log('No GAL files found to clean up.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('\n═══════════════════════════════════════════════════');
|
|
138
|
+
console.log(' GAL CLI has been uninstalled');
|
|
139
|
+
console.log('═══════════════════════════════════════════════════\n');
|
|
140
|
+
console.log('To reinstall: npm install -g @scheduler-systems/gal-cli\n');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Run cleanup
|
|
144
|
+
try {
|
|
145
|
+
cleanup();
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Silent fail - don't prevent uninstall
|
|
148
|
+
console.error('GAL cleanup encountered an error, but uninstall will proceed.');
|
|
149
|
+
}
|