@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scheduler-systems/gal-cli",
3
- "version": "0.1.13-beta.2-alpha.pr30",
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",
@@ -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.0.0'; // SessionStart version (was 1.x for UserPromptSubmit)
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 settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
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
+ }