@jmylchreest/aide-plugin 0.0.57 → 0.0.59

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.
@@ -161,25 +161,44 @@ export function compareVersions(a: string, b: string): number {
161
161
  }
162
162
 
163
163
  /**
164
- * Get the download URL for the current platform
164
+ * Get the platform-specific binary name
165
165
  */
166
- export function getDownloadUrl(): string {
167
- const platform = process.platform; // 'darwin', 'linux', 'win32'
168
- const arch = process.arch; // 'x64', 'arm64'
169
-
166
+ export function getBinaryName(): string {
167
+ const platform = process.platform;
168
+ const arch = process.arch;
170
169
  const goos = platform === "win32" ? "windows" : platform;
171
170
  const goarch = arch === "x64" ? "amd64" : arch;
172
171
  const ext = platform === "win32" ? ".exe" : "";
172
+ return `aide-${goos}-${goarch}${ext}`;
173
+ }
173
174
 
174
- const binaryName = `aide-${goos}-${goarch}${ext}`;
175
-
175
+ /**
176
+ * Get download URLs for the current platform, in priority order.
177
+ * Returns the versioned URL first (exact match), then the latest release
178
+ * URL as a fallback. The fallback handles the race condition where the
179
+ * marketplace pulls the new plugin version before the release action
180
+ * has finished publishing binary artifacts.
181
+ */
182
+ export function getDownloadUrls(): string[] {
183
+ const binaryName = getBinaryName();
176
184
  const version = getPluginVersion();
185
+
177
186
  if (version) {
178
- return `https://github.com/jmylchreest/aide/releases/download/v${version}/${binaryName}`;
187
+ return [
188
+ `https://github.com/jmylchreest/aide/releases/download/v${version}/${binaryName}`,
189
+ `https://github.com/jmylchreest/aide/releases/latest/download/${binaryName}`,
190
+ ];
179
191
  }
180
192
 
181
- // Fallback to latest if version can't be determined
182
- return `https://github.com/jmylchreest/aide/releases/latest/download/${binaryName}`;
193
+ return [`https://github.com/jmylchreest/aide/releases/latest/download/${binaryName}`];
194
+ }
195
+
196
+ /**
197
+ * Get the download URL for the current platform (first priority URL).
198
+ * @deprecated Use getDownloadUrls() for fallback support.
199
+ */
200
+ export function getDownloadUrl(): string {
201
+ return getDownloadUrls()[0];
183
202
  }
184
203
 
185
204
  /**
@@ -250,11 +269,7 @@ export async function downloadAideBinary(
250
269
  }
251
270
  }
252
271
 
253
- const url = getDownloadUrl();
254
-
255
- if (!quiet) {
256
- log(`[aide] Downloading from: ${url}`);
257
- }
272
+ const urls = getDownloadUrls();
258
273
 
259
274
  try {
260
275
  // Create bin directory
@@ -262,13 +277,35 @@ export async function downloadAideBinary(
262
277
  mkdirSync(destDir, { recursive: true });
263
278
  }
264
279
 
265
- // Download using native fetch (follows redirects by default)
266
- const response = await fetch(url, {
267
- headers: { "User-Agent": "aide-plugin" },
268
- });
280
+ // Try each URL in priority order, falling back on 404/network errors.
281
+ // This handles the race where the marketplace pulls a new plugin version
282
+ // before the release action has finished publishing binaries.
283
+ let response: Response | null = null;
284
+ let usedUrl = urls[0];
269
285
 
270
- if (!response.ok) {
271
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
286
+ for (const url of urls) {
287
+ if (!quiet) {
288
+ log(`[aide] Downloading from: ${url}`);
289
+ }
290
+ const resp = await fetch(url, {
291
+ headers: { "User-Agent": "aide-plugin" },
292
+ });
293
+ if (resp.ok) {
294
+ response = resp;
295
+ usedUrl = url;
296
+ break;
297
+ }
298
+ if (resp.status === 404 && url !== urls[urls.length - 1]) {
299
+ if (!quiet) {
300
+ log(`[aide] Release not found (HTTP 404), trying fallback...`);
301
+ }
302
+ continue;
303
+ }
304
+ throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
305
+ }
306
+
307
+ if (!response || !response.ok) {
308
+ throw new Error("All download URLs failed");
272
309
  }
273
310
 
274
311
  if (!response.body) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shared utilities for Claude Code hooks.
2
+ * Shared utilities for Claude Code and Codex CLI hooks.
3
3
  *
4
4
  * readStdin() is the only unique implementation here. All other functions
5
5
  * are convenience wrappers around src/core/aide-client.ts that resolve the
@@ -41,6 +41,58 @@ export async function readStdin(): Promise<string> {
41
41
  return Buffer.concat(chunks).toString("utf-8");
42
42
  }
43
43
 
44
+ /**
45
+ * Normalize hook input JSON from different platforms (Claude Code, Codex CLI).
46
+ *
47
+ * Both platforms use command-type hooks with JSON stdin, but field names may
48
+ * differ between versions. This function maps known alternative names to the
49
+ * canonical snake_case format used by aide hook scripts.
50
+ *
51
+ * Returns the normalized JSON string (or the original if no changes needed).
52
+ */
53
+ export function normalizeHookInput(raw: string): string {
54
+ try {
55
+ const data = JSON.parse(raw) as Record<string, unknown>;
56
+
57
+ // Map known alternative field names → canonical snake_case
58
+ const aliases: Record<string, string> = {
59
+ hookEventName: "hook_event_name",
60
+ sessionId: "session_id",
61
+ toolName: "tool_name",
62
+ agentId: "agent_id",
63
+ agentName: "agent_name",
64
+ toolInput: "tool_input",
65
+ permissionMode: "permission_mode",
66
+ };
67
+
68
+ let changed = false;
69
+ for (const [alt, canonical] of Object.entries(aliases)) {
70
+ if (alt in data && !(canonical in data)) {
71
+ data[canonical] = data[alt];
72
+ delete data[alt];
73
+ changed = true;
74
+ }
75
+ }
76
+
77
+ return changed ? JSON.stringify(data) : raw;
78
+ } catch {
79
+ return raw;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Detect which AI assistant harness is running these hooks.
85
+ *
86
+ * - Codex CLI: hook dispatcher sets AIDE_PLATFORM=codex
87
+ * - Claude Code: sets CLAUDE_PLUGIN_ROOT
88
+ * - OpenCode uses a separate code path (src/opencode/hooks.ts), so hooks
89
+ * in src/hooks/ are only invoked by Claude Code or Codex.
90
+ */
91
+ export function detectPlatform(): "claude-code" | "codex" {
92
+ if (process.env.AIDE_PLATFORM === "codex") return "codex";
93
+ return "claude-code";
94
+ }
95
+
44
96
  /**
45
97
  * Get the plugin root directory from environment variables.
46
98
  */