@simonfestl/husky-cli 1.10.0 → 1.12.0

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/README.md CHANGED
@@ -143,6 +143,76 @@ husky e2e list --task <id> # List artifacts
143
143
  husky e2e clean --older-than 7 # Clean old artifacts
144
144
  ```
145
145
 
146
+ ### PR Management (PR Agent)
147
+
148
+ ```bash
149
+ husky pr list # List open PRs
150
+ husky pr get <pr-number> # Get PR details
151
+ husky pr review <pr-number> # Start review
152
+ husky pr approve <pr-number> # Approve PR
153
+ husky pr request-changes <pr-number> --comment "..."
154
+ husky pr merge <pr-number> # Merge PR
155
+ husky pr close <pr-number> # Close PR
156
+ ```
157
+
158
+ ### Infrastructure (DevOps)
159
+
160
+ ```bash
161
+ husky infra status # Overall infra status
162
+ husky infra vms # List all VMs
163
+ husky infra services # Cloud Run services
164
+ husky infra logs <service> # Service logs
165
+ husky infra metrics # Resource metrics
166
+ ```
167
+
168
+ ### YouTube Summarization
169
+
170
+ ```bash
171
+ husky youtube <url> # Summarize video with Gemini AI
172
+ husky youtube <url> --remember # Also store in Second Brain
173
+ husky youtube <url> --json # JSON output
174
+ ```
175
+
176
+ ### Image Generation
177
+
178
+ ```bash
179
+ husky image "a futuristic city" # Generate image with Imagen 3
180
+ husky image "..." --output ./image.png # Save to file
181
+ husky image "..." --aspect 16:9 # Aspect ratio
182
+ ```
183
+
184
+ ### Mermaid Diagrams
185
+
186
+ ```bash
187
+ husky mermaid validate <file> # Validate Mermaid syntax
188
+ husky mermaid validate --stdin # Validate from stdin
189
+ ```
190
+
191
+ ### Service Accounts
192
+
193
+ ```bash
194
+ husky sa list # List service accounts
195
+ husky sa create <name> --role worker # Create service account
196
+ husky sa get <id> # Get details
197
+ husky sa delete <id> # Delete service account
198
+ ```
199
+
200
+ ### Agent Messaging
201
+
202
+ ```bash
203
+ husky agent-msg send <to> "message" # Send to another agent
204
+ husky agent-msg inbox # Check inbox
205
+ husky agent-msg read <id> # Read message
206
+ ```
207
+
208
+ ### Preview Deployments
209
+
210
+ ```bash
211
+ husky preview list # List PR previews
212
+ husky preview get <pr-number> # Get preview URL
213
+ husky preview logs <pr-number> # Preview logs
214
+ ```
215
+
146
216
  ### Business Strategy
147
217
 
148
218
  ```bash
@@ -249,6 +319,41 @@ husky config list
249
319
  husky config test
250
320
  ```
251
321
 
322
+ ### Authentication (Session Tokens)
323
+
324
+ Session tokens provide short-lived JWT authentication for agents. They are created using `HUSKY_API_KEY` and auto-refresh when expired.
325
+
326
+ ```bash
327
+ # Login (creates 1-hour session token)
328
+ husky auth login --agent supervisor
329
+ husky auth login --agent husky-worker-1
330
+
331
+ # Check session status
332
+ husky auth session
333
+ husky auth session --json
334
+
335
+ # Refresh token manually
336
+ husky auth refresh
337
+ husky auth refresh --agent supervisor
338
+
339
+ # Logout (clear session)
340
+ husky auth logout
341
+ ```
342
+
343
+ **VM Startup Pattern:**
344
+ ```bash
345
+ #!/bin/bash
346
+ VM_NAME=$(hostname)
347
+ husky auth login --agent "$VM_NAME"
348
+ # All subsequent commands use Bearer token
349
+ ```
350
+
351
+ **How it works:**
352
+ 1. `HUSKY_API_KEY` is used once to create a session token
353
+ 2. All subsequent API calls use `Authorization: Bearer <token>`
354
+ 3. Token auto-refreshes when expired or within 5 minutes of expiry
355
+ 4. Falls back to `x-api-key` if refresh fails
356
+
252
357
  ### Help & Documentation
253
358
 
254
359
  ```bash
@@ -328,6 +433,27 @@ husky --version
328
433
 
329
434
  ## Changelog
330
435
 
436
+ ### v1.12.0 (2026-01-12) - Session Token Authentication
437
+
438
+ **New Features:**
439
+ - `husky auth login --agent <name>` - Create session token from HUSKY_API_KEY
440
+ - `husky auth logout` - Clear session token
441
+ - `husky auth session` - Show session status (agent, role, expiry)
442
+ - `husky auth refresh` - Manually refresh token
443
+
444
+ **Improvements:**
445
+ - All API calls now use Bearer token authentication (auto-refresh)
446
+ - Token auto-refreshes when expired or within 5 minutes of expiry
447
+ - Falls back to x-api-key for backwards compatibility
448
+ - JWT_SECRET now required in production (fail-fast)
449
+
450
+ **Documentation:**
451
+ - Added missing command sections: pr, infra, youtube, image, mermaid, sa, agent-msg, preview
452
+ - Updated architecture docs with session token flow
453
+
454
+ **Code Quality:**
455
+ - Removed `as any` type suppression in sop.ts
456
+
331
457
  ### v1.7.0 (2026-01-11) - E2E Agent Production Ready
332
458
 
333
459
  **New Features:**
@@ -1,5 +1,5 @@
1
1
  import { Command } from "commander";
2
- import { getConfig } from "./config.js";
2
+ import { getConfig, setSessionConfig, clearSessionConfig, getSessionConfig } from "./config.js";
3
3
  import { getPermissions, clearPermissionsCache, getCacheStatus, hasPermission, canAccessKnowledgeBase } from "../lib/permissions-cache.js";
4
4
  const API_KEY_ROLES = [
5
5
  "admin", "supervisor", "worker", "reviewer", "support",
@@ -260,3 +260,178 @@ authCommand
260
260
  process.exit(1);
261
261
  }
262
262
  });
263
+ authCommand
264
+ .command("login")
265
+ .description("Create a session token for this agent")
266
+ .requiredOption("--agent <name>", "Agent name (must be registered in Firestore)")
267
+ .option("--json", "Output as JSON")
268
+ .action(async (options) => {
269
+ try {
270
+ const config = getConfig();
271
+ if (!config.apiUrl || !config.apiKey) {
272
+ console.error("API not configured. Run: husky config set api-url <url> && husky config set api-key <key>");
273
+ process.exit(1);
274
+ }
275
+ const url = new URL("/api/auth/session", config.apiUrl);
276
+ const res = await fetch(url.toString(), {
277
+ method: "POST",
278
+ headers: {
279
+ "x-api-key": config.apiKey,
280
+ "Content-Type": "application/json",
281
+ },
282
+ body: JSON.stringify({ agent: options.agent }),
283
+ });
284
+ if (!res.ok) {
285
+ const error = await res.json().catch(() => ({ error: res.statusText }));
286
+ if (res.status === 404) {
287
+ console.error(`Agent '${options.agent}' not found. Register the agent first.`);
288
+ }
289
+ else {
290
+ console.error(`Login failed: ${error.message || error.error || `HTTP ${res.status}`}`);
291
+ }
292
+ process.exit(1);
293
+ }
294
+ const session = await res.json();
295
+ setSessionConfig(session);
296
+ if (options.json) {
297
+ console.log(JSON.stringify({
298
+ success: true,
299
+ agent: session.agent,
300
+ role: session.role,
301
+ expiresAt: session.expiresAt,
302
+ }, null, 2));
303
+ return;
304
+ }
305
+ const expiresAt = new Date(session.expiresAt);
306
+ console.log("\n✅ Session created");
307
+ console.log("─".repeat(40));
308
+ console.log(`Agent: ${session.agent}`);
309
+ console.log(`Role: ${session.role}`);
310
+ console.log(`Expires: ${expiresAt.toLocaleString()}`);
311
+ console.log("");
312
+ console.log("All API calls will now use this session token.");
313
+ }
314
+ catch (error) {
315
+ console.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
316
+ process.exit(1);
317
+ }
318
+ });
319
+ authCommand
320
+ .command("logout")
321
+ .description("Clear the current session token")
322
+ .option("--json", "Output as JSON")
323
+ .action(async (options) => {
324
+ const session = getSessionConfig();
325
+ if (!session) {
326
+ if (options.json) {
327
+ console.log(JSON.stringify({ success: false, message: "No active session" }));
328
+ }
329
+ else {
330
+ console.log("No active session to clear.");
331
+ }
332
+ return;
333
+ }
334
+ clearSessionConfig();
335
+ if (options.json) {
336
+ console.log(JSON.stringify({ success: true, agent: session.agent }));
337
+ return;
338
+ }
339
+ console.log(`✅ Session cleared for agent '${session.agent}'`);
340
+ });
341
+ authCommand
342
+ .command("session")
343
+ .description("Show current session status")
344
+ .option("--json", "Output as JSON")
345
+ .action(async (options) => {
346
+ const session = getSessionConfig();
347
+ if (!session || !session.token) {
348
+ if (options.json) {
349
+ console.log(JSON.stringify({ active: false }));
350
+ }
351
+ else {
352
+ console.log("No active session. Run: husky auth login --agent <name>");
353
+ }
354
+ return;
355
+ }
356
+ const expiresAt = session.expiresAt ? new Date(session.expiresAt) : null;
357
+ const now = new Date();
358
+ const isExpired = expiresAt ? expiresAt < now : true;
359
+ const expiresInMs = expiresAt ? expiresAt.getTime() - now.getTime() : 0;
360
+ const expiresInMinutes = Math.max(0, Math.floor(expiresInMs / 60000));
361
+ if (options.json) {
362
+ console.log(JSON.stringify({
363
+ active: !isExpired,
364
+ agent: session.agent,
365
+ role: session.role,
366
+ expiresAt: session.expiresAt,
367
+ expired: isExpired,
368
+ expiresInMinutes,
369
+ }, null, 2));
370
+ return;
371
+ }
372
+ console.log("\n🔐 Session Status");
373
+ console.log("─".repeat(40));
374
+ console.log(`Agent: ${session.agent || "(unknown)"}`);
375
+ console.log(`Role: ${session.role || "(unknown)"}`);
376
+ if (isExpired) {
377
+ console.log(`Status: 🔴 EXPIRED`);
378
+ console.log(`Expired: ${expiresAt?.toLocaleString() || "(unknown)"}`);
379
+ console.log("");
380
+ console.log("Run: husky auth refresh --agent <name>");
381
+ }
382
+ else {
383
+ console.log(`Status: 🟢 ACTIVE`);
384
+ console.log(`Expires: ${expiresAt?.toLocaleString()} (${expiresInMinutes} minutes)`);
385
+ }
386
+ });
387
+ authCommand
388
+ .command("refresh")
389
+ .description("Refresh the session token")
390
+ .option("--agent <name>", "Agent name (uses current session agent if not specified)")
391
+ .option("--json", "Output as JSON")
392
+ .action(async (options) => {
393
+ try {
394
+ const config = getConfig();
395
+ if (!config.apiUrl || !config.apiKey) {
396
+ console.error("API not configured. Run: husky config set api-url <url> && husky config set api-key <key>");
397
+ process.exit(1);
398
+ }
399
+ const currentSession = getSessionConfig();
400
+ const agentName = options.agent || currentSession?.agent;
401
+ if (!agentName) {
402
+ console.error("No agent specified and no active session. Use: husky auth refresh --agent <name>");
403
+ process.exit(1);
404
+ }
405
+ const url = new URL("/api/auth/session", config.apiUrl);
406
+ const res = await fetch(url.toString(), {
407
+ method: "POST",
408
+ headers: {
409
+ "x-api-key": config.apiKey,
410
+ "Content-Type": "application/json",
411
+ },
412
+ body: JSON.stringify({ agent: agentName }),
413
+ });
414
+ if (!res.ok) {
415
+ const error = await res.json().catch(() => ({ error: res.statusText }));
416
+ console.error(`Refresh failed: ${error.message || error.error || `HTTP ${res.status}`}`);
417
+ process.exit(1);
418
+ }
419
+ const session = await res.json();
420
+ setSessionConfig(session);
421
+ if (options.json) {
422
+ console.log(JSON.stringify({
423
+ success: true,
424
+ agent: session.agent,
425
+ role: session.role,
426
+ expiresAt: session.expiresAt,
427
+ }, null, 2));
428
+ return;
429
+ }
430
+ const expiresAt = new Date(session.expiresAt);
431
+ console.log(`✅ Session refreshed for '${session.agent}' (expires: ${expiresAt.toLocaleString()})`);
432
+ }
433
+ catch (error) {
434
+ console.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
435
+ process.exit(1);
436
+ }
437
+ });
@@ -3,6 +3,10 @@ type AgentRole = "supervisor" | "worker" | "reviewer" | "e2e_agent" | "pr_agent"
3
3
  interface Config {
4
4
  apiUrl?: string;
5
5
  apiKey?: string;
6
+ sessionToken?: string;
7
+ sessionExpiresAt?: string;
8
+ sessionAgent?: string;
9
+ sessionRole?: string;
6
10
  workerId?: string;
7
11
  workerName?: string;
8
12
  role?: AgentRole;
@@ -28,8 +32,12 @@ interface Config {
28
32
  nocodbApiToken?: string;
29
33
  nocodbBaseUrl?: string;
30
34
  nocodbWorkspaceId?: string;
35
+ skuterzoneUsername?: string;
36
+ skuterzonePassword?: string;
37
+ skuterzoneBaseUrl?: string;
31
38
  }
32
39
  export declare function getConfig(): Config;
40
+ export declare function saveConfig(config: Config): void;
33
41
  /**
34
42
  * Fetch role and permissions from /api/auth/whoami
35
43
  * Caches the result in config for 1 hour
@@ -52,5 +60,18 @@ export declare function getRole(): AgentRole | undefined;
52
60
  export declare function clearRoleCache(): void;
53
61
  export declare function setConfig(key: "apiUrl" | "apiKey" | "workerId" | "workerName", value: string): void;
54
62
  export declare function setGotessConfig(token: string, bookId: string): void;
63
+ export declare function setSessionConfig(session: {
64
+ token: string;
65
+ expiresAt: string;
66
+ agent: string;
67
+ role: string;
68
+ }): void;
69
+ export declare function clearSessionConfig(): void;
70
+ export declare function getSessionConfig(): {
71
+ token?: string;
72
+ expiresAt?: string;
73
+ agent?: string;
74
+ role?: string;
75
+ } | null;
55
76
  export declare const configCommand: Command;
56
77
  export {};
@@ -41,7 +41,7 @@ export function getConfig() {
41
41
  return {};
42
42
  }
43
43
  }
44
- function saveConfig(config) {
44
+ export function saveConfig(config) {
45
45
  if (!existsSync(CONFIG_DIR)) {
46
46
  mkdirSync(CONFIG_DIR, { recursive: true });
47
47
  }
@@ -127,6 +127,33 @@ export function setGotessConfig(token, bookId) {
127
127
  config.gotessBookId = bookId;
128
128
  saveConfig(config);
129
129
  }
130
+ export function setSessionConfig(session) {
131
+ const config = getConfig();
132
+ config.sessionToken = session.token;
133
+ config.sessionExpiresAt = session.expiresAt;
134
+ config.sessionAgent = session.agent;
135
+ config.sessionRole = session.role;
136
+ saveConfig(config);
137
+ }
138
+ export function clearSessionConfig() {
139
+ const config = getConfig();
140
+ delete config.sessionToken;
141
+ delete config.sessionExpiresAt;
142
+ delete config.sessionAgent;
143
+ delete config.sessionRole;
144
+ saveConfig(config);
145
+ }
146
+ export function getSessionConfig() {
147
+ const config = getConfig();
148
+ if (!config.sessionToken)
149
+ return null;
150
+ return {
151
+ token: config.sessionToken,
152
+ expiresAt: config.sessionExpiresAt,
153
+ agent: config.sessionAgent,
154
+ role: config.sessionRole,
155
+ };
156
+ }
130
157
  export const configCommand = new Command("config")
131
158
  .description("Manage CLI configuration");
132
159
  // husky config set <key> <value>
@@ -166,6 +193,10 @@ configCommand
166
193
  "nocodb-api-token": "nocodbApiToken",
167
194
  "nocodb-base-url": "nocodbBaseUrl",
168
195
  "nocodb-workspace-id": "nocodbWorkspaceId",
196
+ // Skuterzone
197
+ "skuterzone-username": "skuterzoneUsername",
198
+ "skuterzone-password": "skuterzonePassword",
199
+ "skuterzone-base-url": "skuterzoneBaseUrl",
169
200
  };
170
201
  const configKey = keyMappings[key];
171
202
  if (!configKey) {
@@ -180,6 +211,7 @@ configCommand
180
211
  console.log(" Gotess: gotess-token, gotess-book-id");
181
212
  console.log(" Gemini: gemini-api-key");
182
213
  console.log(" NocoDB: nocodb-api-token, nocodb-base-url, nocodb-workspace-id");
214
+ console.log(" Skuterzone: skuterzone-username, skuterzone-password, skuterzone-base-url");
183
215
  console.log(" Brain: agent-type");
184
216
  console.error("\n💡 For configuration help: husky explain config");
185
217
  process.exit(1);
@@ -201,7 +233,7 @@ configCommand
201
233
  config[configKey] = value;
202
234
  saveConfig(config);
203
235
  // Mask sensitive values in output
204
- const sensitiveKeys = ["api-key", "billbee-api-key", "billbee-password", "zendesk-api-token", "seatable-api-token", "gotess-token", "gemini-api-key", "nocodb-api-token"];
236
+ const sensitiveKeys = ["api-key", "billbee-api-key", "billbee-password", "zendesk-api-token", "seatable-api-token", "gotess-token", "gemini-api-key", "nocodb-api-token", "skuterzone-username", "skuterzone-password"];
205
237
  const displayValue = sensitiveKeys.includes(key) ? "***" : value;
206
238
  console.log(`✓ Set ${key} = ${displayValue}`);
207
239
  });
@@ -829,12 +829,14 @@ taskCommand
829
829
  throw new Error(`API error: ${res.status}`);
830
830
  }
831
831
  const data = await res.json();
832
- if (data.status === "approved") {
832
+ if (data.approved === true && data.pending === false) {
833
833
  console.log("✓ Plan approved!");
834
834
  process.exit(0);
835
835
  }
836
- else if (data.status === "rejected") {
836
+ else if (data.approved === false && data.rejected === true) {
837
837
  console.log("✗ Plan rejected");
838
+ if (data.reason)
839
+ console.log(`Reason: ${data.reason}`);
838
840
  process.exit(1);
839
841
  }
840
842
  // Still pending, wait and poll again
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ import { e2eCommand } from "./commands/e2e.js";
34
34
  import { prCommand } from "./commands/pr.js";
35
35
  import { youtubeCommand } from "./commands/youtube.js";
36
36
  import { imageCommand } from "./commands/image.js";
37
+ import { authCommand } from "./commands/auth.js";
37
38
  // Read version from package.json
38
39
  const require = createRequire(import.meta.url);
39
40
  const packageJson = require("../package.json");
@@ -75,6 +76,7 @@ program.addCommand(e2eCommand);
75
76
  program.addCommand(prCommand);
76
77
  program.addCommand(youtubeCommand);
77
78
  program.addCommand(imageCommand);
79
+ program.addCommand(authCommand);
78
80
  // Handle --llm flag specially
79
81
  if (process.argv.includes("--llm")) {
80
82
  printLLMContext();
@@ -0,0 +1,13 @@
1
+ export interface ApiRequestOptions {
2
+ method?: string;
3
+ body?: unknown;
4
+ skipAuth?: boolean;
5
+ }
6
+ export declare function apiRequest<T>(path: string, options?: ApiRequestOptions): Promise<T>;
7
+ export declare function getApiClient(): {
8
+ get: <T>(path: string) => Promise<T>;
9
+ post: <T>(path: string, body?: unknown) => Promise<T>;
10
+ put: <T>(path: string, body?: unknown) => Promise<T>;
11
+ patch: <T>(path: string, body?: unknown) => Promise<T>;
12
+ delete: <T>(path: string) => Promise<T>;
13
+ };
@@ -0,0 +1,117 @@
1
+ import { getConfig, getSessionConfig, setSessionConfig, clearSessionConfig } from "../commands/config.js";
2
+ const REFRESH_THRESHOLD_MS = 5 * 60 * 1000;
3
+ let refreshInProgress = null;
4
+ function isSessionExpired(expiresAt) {
5
+ if (!expiresAt)
6
+ return true;
7
+ return new Date(expiresAt).getTime() < Date.now();
8
+ }
9
+ function isSessionExpiringSoon(expiresAt) {
10
+ if (!expiresAt)
11
+ return true;
12
+ return new Date(expiresAt).getTime() - Date.now() < REFRESH_THRESHOLD_MS;
13
+ }
14
+ async function doRefresh(agentName) {
15
+ const config = getConfig();
16
+ if (!config.apiUrl || !config.apiKey)
17
+ return null;
18
+ try {
19
+ const url = new URL("/api/auth/session", config.apiUrl);
20
+ const res = await fetch(url.toString(), {
21
+ method: "POST",
22
+ headers: {
23
+ "x-api-key": config.apiKey,
24
+ "Content-Type": "application/json",
25
+ },
26
+ body: JSON.stringify({ agent: agentName }),
27
+ });
28
+ if (!res.ok)
29
+ return null;
30
+ const session = await res.json();
31
+ setSessionConfig(session);
32
+ return session;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ async function refreshSession(agentName) {
39
+ if (refreshInProgress) {
40
+ return refreshInProgress;
41
+ }
42
+ refreshInProgress = doRefresh(agentName).finally(() => {
43
+ refreshInProgress = null;
44
+ });
45
+ return refreshInProgress;
46
+ }
47
+ async function doFetch(url, method, authHeader, body) {
48
+ return fetch(url.toString(), {
49
+ method,
50
+ headers: {
51
+ ...authHeader,
52
+ "Content-Type": "application/json",
53
+ },
54
+ body: body ? JSON.stringify(body) : undefined,
55
+ });
56
+ }
57
+ async function getAuthHeader(session, apiKey) {
58
+ if (session?.token && session.expiresAt) {
59
+ if (isSessionExpired(session.expiresAt)) {
60
+ if (session.agent) {
61
+ const newSession = await refreshSession(session.agent);
62
+ if (newSession) {
63
+ return { "Authorization": `Bearer ${newSession.token}` };
64
+ }
65
+ }
66
+ clearSessionConfig();
67
+ if (apiKey) {
68
+ return { "x-api-key": apiKey };
69
+ }
70
+ throw new Error("Session expired and no API key available for refresh");
71
+ }
72
+ if (isSessionExpiringSoon(session.expiresAt) && session.agent) {
73
+ refreshSession(session.agent).catch(() => { });
74
+ }
75
+ return { "Authorization": `Bearer ${session.token}` };
76
+ }
77
+ if (apiKey) {
78
+ return { "x-api-key": apiKey };
79
+ }
80
+ throw new Error("No authentication configured. Run: husky auth login --agent <name> or husky config set api-key <key>");
81
+ }
82
+ export async function apiRequest(path, options = {}) {
83
+ const config = getConfig();
84
+ if (!config.apiUrl) {
85
+ throw new Error("API URL not configured. Run: husky config set api-url <url>");
86
+ }
87
+ const session = getSessionConfig();
88
+ const method = options.method || "GET";
89
+ const url = new URL(path, config.apiUrl);
90
+ const authHeader = options.skipAuth
91
+ ? {}
92
+ : await getAuthHeader(session, config.apiKey);
93
+ const res = await doFetch(url, method, authHeader, options.body);
94
+ if (!res.ok) {
95
+ if (res.status === 401 && session?.token && session.agent) {
96
+ const newSession = await refreshSession(session.agent);
97
+ if (newSession) {
98
+ const retryRes = await doFetch(url, method, { "Authorization": `Bearer ${newSession.token}` }, options.body);
99
+ if (retryRes.ok) {
100
+ return retryRes.json();
101
+ }
102
+ }
103
+ }
104
+ const error = await res.json().catch(() => ({ error: res.statusText }));
105
+ throw new Error(error.message || error.error || `HTTP ${res.status}`);
106
+ }
107
+ return res.json();
108
+ }
109
+ export function getApiClient() {
110
+ return {
111
+ get: (path) => apiRequest(path),
112
+ post: (path, body) => apiRequest(path, { method: "POST", body }),
113
+ put: (path, body) => apiRequest(path, { method: "PUT", body }),
114
+ patch: (path, body) => apiRequest(path, { method: "PATCH", body }),
115
+ delete: (path) => apiRequest(path, { method: "DELETE" }),
116
+ };
117
+ }
@@ -282,21 +282,8 @@ export class SOPService {
282
282
  };
283
283
  }
284
284
  async scrollPoints(collection, options) {
285
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
286
- const qdrantAny = this.qdrant;
287
285
  try {
288
- const response = await qdrantAny.request(`/collections/${collection}/points/scroll`, {
289
- method: 'POST',
290
- body: JSON.stringify({
291
- filter: options.filter,
292
- limit: options.limit || 50,
293
- with_payload: options.with_payload ?? true,
294
- }),
295
- });
296
- return response.result.points.map((p) => ({
297
- id: p.id,
298
- payload: p.payload || {},
299
- }));
286
+ return await this.qdrant.scroll(collection, options);
300
287
  }
301
288
  catch (error) {
302
289
  console.error('Scroll failed:', error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "@inquirer/prompts": "^8.1.0",
26
26
  "commander": "^12.1.0",
27
27
  "firebase-admin": "^13.6.0",
28
+ "playwright": "^1.57.0",
28
29
  "sharp": "^0.34.5",
29
30
  "youtube-transcript": "^1.2.1",
30
31
  "zod": "^4.3.5"