@simonfestl/husky-cli 1.30.1 → 1.31.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.
@@ -6,6 +6,29 @@ const API_KEY_ROLES = [
6
6
  "admin", "supervisor", "worker", "reviewer", "support",
7
7
  "purchasing", "ops", "e2e_agent", "pr_agent"
8
8
  ];
9
+ /**
10
+ * Fetch VM Identity Token from GCP Metadata Server
11
+ * This token is cryptographically signed by Google and proves the caller is running on a specific GCP VM.
12
+ * Returns null if not running on GCP or metadata server is unavailable.
13
+ *
14
+ * Note: format=full is required to include google.compute_engine claims (instance_name, project_id, etc.)
15
+ */
16
+ async function getVMIdentityToken(audience = "husky-api") {
17
+ try {
18
+ const url = `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=${audience}&format=full`;
19
+ const res = await fetch(url, {
20
+ headers: { "Metadata-Flavor": "Google" },
21
+ signal: AbortSignal.timeout(3000), // 3 second timeout
22
+ });
23
+ if (res.ok) {
24
+ return (await res.text()).trim();
25
+ }
26
+ }
27
+ catch {
28
+ // Not on GCP or metadata server unavailable - this is expected for local development
29
+ }
30
+ return null;
31
+ }
9
32
  export const authCommand = new Command("auth")
10
33
  .description("Manage API keys and authentication");
11
34
  authCommand
@@ -250,7 +273,8 @@ authCommand
250
273
  authCommand
251
274
  .command("login")
252
275
  .description("Create a session token for this agent")
253
- .requiredOption("--agent <name>", "Agent name (must be registered in Firestore)")
276
+ .requiredOption("--agent <name>", "Agent name or alias (must be registered in Firestore)")
277
+ .option("--force", "Clear existing session before creating new one")
254
278
  .option("--json", "Output as JSON")
255
279
  .action(async (options) => {
256
280
  try {
@@ -259,14 +283,26 @@ authCommand
259
283
  console.error("API not configured. Run: husky config set api-url <url> && husky config set api-key <key>");
260
284
  process.exit(1);
261
285
  }
286
+ // Clear existing session if --force flag is set
287
+ if (options.force) {
288
+ clearSessionConfig();
289
+ clearPermissionsCache();
290
+ }
291
+ // Try to get VM Identity Token from GCP Metadata Server
292
+ // This will be null on local development machines
293
+ const vmIdentityToken = await getVMIdentityToken();
262
294
  const url = new URL("/api/auth/session", config.apiUrl);
295
+ const requestBody = { agent: options.agent };
296
+ if (vmIdentityToken) {
297
+ requestBody.vmIdentityToken = vmIdentityToken;
298
+ }
263
299
  const res = await fetch(url.toString(), {
264
300
  method: "POST",
265
301
  headers: {
266
302
  "x-api-key": config.apiKey,
267
303
  "Content-Type": "application/json",
268
304
  },
269
- body: JSON.stringify({ agent: options.agent }),
305
+ body: JSON.stringify(requestBody),
270
306
  });
271
307
  if (!res.ok) {
272
308
  const error = await res.json().catch(() => ({ error: res.statusText }));
@@ -401,14 +437,20 @@ authCommand
401
437
  console.error("No agent specified and no active session. Use: husky auth refresh --agent <name>");
402
438
  process.exit(1);
403
439
  }
440
+ // Try to get VM Identity Token from GCP Metadata Server
441
+ const vmIdentityToken = await getVMIdentityToken();
404
442
  const url = new URL("/api/auth/session", config.apiUrl);
443
+ const requestBody = { agent: agentName };
444
+ if (vmIdentityToken) {
445
+ requestBody.vmIdentityToken = vmIdentityToken;
446
+ }
405
447
  const res = await fetch(url.toString(), {
406
448
  method: "POST",
407
449
  headers: {
408
450
  "x-api-key": config.apiKey,
409
451
  "Content-Type": "application/json",
410
452
  },
411
- body: JSON.stringify({ agent: agentName }),
453
+ body: JSON.stringify(requestBody),
412
454
  });
413
455
  if (!res.ok) {
414
456
  const error = await res.json().catch(() => ({ error: res.statusText }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "1.30.1",
3
+ "version": "1.31.1",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {