@simonfestl/husky-cli 0.9.6 → 1.0.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 +19 -13
- package/dist/commands/chat.d.ts +2 -0
- package/dist/commands/chat.js +162 -0
- package/dist/commands/completion.js +0 -9
- package/dist/commands/interactive/tasks.js +93 -9
- package/dist/commands/interactive.js +0 -5
- package/dist/commands/llm-context.d.ts +0 -4
- package/dist/commands/llm-context.js +9 -15
- package/dist/commands/task.js +101 -15
- package/dist/index.js +7 -5
- package/dist/lib/project-resolver.d.ts +26 -0
- package/dist/lib/project-resolver.js +111 -0
- package/package.json +1 -1
- package/dist/commands/interactive/jules-sessions.d.ts +0 -1
- package/dist/commands/interactive/jules-sessions.js +0 -460
- package/dist/commands/jules.d.ts +0 -2
- package/dist/commands/jules.js +0 -593
- package/dist/commands/services.d.ts +0 -2
- package/dist/commands/services.js +0 -381
package/dist/commands/task.js
CHANGED
|
@@ -6,6 +6,7 @@ import { paginateList, printPaginated } from "../lib/pagination.js";
|
|
|
6
6
|
import { ensureWorkerRegistered, generateSessionId, registerSession } from "../lib/worker.js";
|
|
7
7
|
import { WorktreeManager } from "../lib/worktree.js";
|
|
8
8
|
import { execSync } from "child_process";
|
|
9
|
+
import { resolveProject, fetchProjects, formatProjectList } from "../lib/project-resolver.js";
|
|
9
10
|
export const taskCommand = new Command("task")
|
|
10
11
|
.description("Manage tasks");
|
|
11
12
|
// Helper: Get task ID from --id flag or HUSKY_TASK_ID env var
|
|
@@ -221,7 +222,8 @@ taskCommand
|
|
|
221
222
|
taskCommand
|
|
222
223
|
.command("start <id>")
|
|
223
224
|
.description("Start working on a task")
|
|
224
|
-
.
|
|
225
|
+
.option("--no-worktree", "Skip worktree creation")
|
|
226
|
+
.action(async (id, options) => {
|
|
225
227
|
const config = getConfig();
|
|
226
228
|
if (!config.apiUrl) {
|
|
227
229
|
console.error("Error: API URL not configured.");
|
|
@@ -232,6 +234,15 @@ taskCommand
|
|
|
232
234
|
const workerId = await ensureWorkerRegistered(config.apiUrl, config.apiKey || "");
|
|
233
235
|
const sessionId = generateSessionId();
|
|
234
236
|
await registerSession(config.apiUrl, config.apiKey || "", workerId, sessionId);
|
|
237
|
+
// Create worktree for isolation (unless --no-worktree)
|
|
238
|
+
let worktreeInfo = null;
|
|
239
|
+
if (options.worktree !== false) {
|
|
240
|
+
worktreeInfo = createWorktreeForTask(id);
|
|
241
|
+
if (worktreeInfo) {
|
|
242
|
+
console.log(`✓ Created worktree: ${worktreeInfo.path}`);
|
|
243
|
+
console.log(` Branch: ${worktreeInfo.branch}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
235
246
|
const res = await fetch(`${config.apiUrl}/api/tasks/${id}/start`, {
|
|
236
247
|
method: "POST",
|
|
237
248
|
headers: {
|
|
@@ -242,6 +253,11 @@ taskCommand
|
|
|
242
253
|
agent: "claude-code",
|
|
243
254
|
workerId,
|
|
244
255
|
sessionId,
|
|
256
|
+
// Include worktree info if created
|
|
257
|
+
...(worktreeInfo ? {
|
|
258
|
+
worktreePath: worktreeInfo.path,
|
|
259
|
+
worktreeBranch: worktreeInfo.branch,
|
|
260
|
+
} : {}),
|
|
245
261
|
}),
|
|
246
262
|
});
|
|
247
263
|
if (!res.ok) {
|
|
@@ -251,6 +267,10 @@ taskCommand
|
|
|
251
267
|
console.log(`✓ Started: ${task.title}`);
|
|
252
268
|
console.log(` Worker: ${workerId}`);
|
|
253
269
|
console.log(` Session: ${sessionId}`);
|
|
270
|
+
// Show hint to cd into worktree
|
|
271
|
+
if (worktreeInfo) {
|
|
272
|
+
console.log(`\n💡 To work in isolation: cd ${worktreeInfo.path}`);
|
|
273
|
+
}
|
|
254
274
|
}
|
|
255
275
|
catch (error) {
|
|
256
276
|
console.error("Error starting task:", error);
|
|
@@ -262,6 +282,7 @@ taskCommand
|
|
|
262
282
|
.command("done <id>")
|
|
263
283
|
.description("Mark task as done")
|
|
264
284
|
.option("--pr <url>", "Link to PR")
|
|
285
|
+
.option("--skip-qa", "Skip QA review and mark as done directly")
|
|
265
286
|
.action(async (id, options) => {
|
|
266
287
|
const config = getConfig();
|
|
267
288
|
if (!config.apiUrl) {
|
|
@@ -275,25 +296,36 @@ taskCommand
|
|
|
275
296
|
"Content-Type": "application/json",
|
|
276
297
|
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
277
298
|
},
|
|
278
|
-
body: JSON.stringify({
|
|
299
|
+
body: JSON.stringify({
|
|
300
|
+
prUrl: options.pr,
|
|
301
|
+
skipQA: options.skipQa === true,
|
|
302
|
+
}),
|
|
279
303
|
});
|
|
280
304
|
if (!res.ok) {
|
|
281
305
|
throw new Error(`API error: ${res.status}`);
|
|
282
306
|
}
|
|
283
307
|
const task = await res.json();
|
|
284
|
-
|
|
308
|
+
if (task.qaTriggered) {
|
|
309
|
+
console.log(`✓ Task moved to review: ${task.title}`);
|
|
310
|
+
console.log(` QA pipeline triggered - awaiting verification`);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
console.log(`✓ Completed: ${task.title}`);
|
|
314
|
+
}
|
|
315
|
+
if (task.message) {
|
|
316
|
+
console.log(` ${task.message}`);
|
|
317
|
+
}
|
|
285
318
|
}
|
|
286
319
|
catch (error) {
|
|
287
320
|
console.error("Error completing task:", error);
|
|
288
321
|
process.exit(1);
|
|
289
322
|
}
|
|
290
323
|
});
|
|
291
|
-
// husky task create <title>
|
|
292
324
|
taskCommand
|
|
293
325
|
.command("create <title>")
|
|
294
326
|
.description("Create a new task")
|
|
295
327
|
.option("-d, --description <desc>", "Task description")
|
|
296
|
-
.option("--project <
|
|
328
|
+
.option("--project <project>", "Project name or ID")
|
|
297
329
|
.option("--path <path>", "Path in project")
|
|
298
330
|
.option("-p, --priority <priority>", "Priority (low, medium, high)", "medium")
|
|
299
331
|
.action(async (title, options) => {
|
|
@@ -302,6 +334,29 @@ taskCommand
|
|
|
302
334
|
console.error("Error: API URL not configured.");
|
|
303
335
|
process.exit(1);
|
|
304
336
|
}
|
|
337
|
+
let resolvedProjectId;
|
|
338
|
+
const resolverConfig = { apiUrl: config.apiUrl, apiKey: config.apiKey };
|
|
339
|
+
if (options.project) {
|
|
340
|
+
const resolved = await resolveProject(options.project, resolverConfig);
|
|
341
|
+
if (!resolved) {
|
|
342
|
+
const projects = await fetchProjects(resolverConfig);
|
|
343
|
+
console.error(`\n❌ Project "${options.project}" not found.\n`);
|
|
344
|
+
console.error(formatProjectList(projects));
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
if (resolved.resolvedBy === "name-match") {
|
|
348
|
+
console.log(`ℹ️ Resolved "${options.project}" → ${resolved.projectName} (${resolved.projectId})`);
|
|
349
|
+
}
|
|
350
|
+
if (resolved.resolvedBy === "fuzzy-match") {
|
|
351
|
+
const confirmed = await confirm(`Did you mean "${resolved.projectName}"? (${Math.round(resolved.confidence * 100)}% match)`);
|
|
352
|
+
if (!confirmed) {
|
|
353
|
+
console.log("Cancelled.");
|
|
354
|
+
process.exit(0);
|
|
355
|
+
}
|
|
356
|
+
console.log(`ℹ️ Using project: ${resolved.projectName} (${resolved.projectId})`);
|
|
357
|
+
}
|
|
358
|
+
resolvedProjectId = resolved.projectId;
|
|
359
|
+
}
|
|
305
360
|
try {
|
|
306
361
|
const res = await fetch(`${config.apiUrl}/api/tasks`, {
|
|
307
362
|
method: "POST",
|
|
@@ -312,13 +367,14 @@ taskCommand
|
|
|
312
367
|
body: JSON.stringify({
|
|
313
368
|
title,
|
|
314
369
|
description: options.description,
|
|
315
|
-
projectId:
|
|
370
|
+
projectId: resolvedProjectId,
|
|
316
371
|
linkedPath: options.path,
|
|
317
372
|
priority: options.priority,
|
|
318
373
|
}),
|
|
319
374
|
});
|
|
320
375
|
if (!res.ok) {
|
|
321
|
-
|
|
376
|
+
const errorData = await res.json().catch(() => ({}));
|
|
377
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
322
378
|
}
|
|
323
379
|
const task = await res.json();
|
|
324
380
|
console.log(`✓ Created: #${task.id} ${task.title}`);
|
|
@@ -328,7 +384,6 @@ taskCommand
|
|
|
328
384
|
process.exit(1);
|
|
329
385
|
}
|
|
330
386
|
});
|
|
331
|
-
// husky task update <id>
|
|
332
387
|
taskCommand
|
|
333
388
|
.command("update <id>")
|
|
334
389
|
.description("Update task properties")
|
|
@@ -337,12 +392,12 @@ taskCommand
|
|
|
337
392
|
.option("--status <status>", "New status (e.g., backlog, in_progress, review, done, or custom status)")
|
|
338
393
|
.option("--priority <priority>", "New priority (low, medium, high, urgent)")
|
|
339
394
|
.option("--assignee <assignee>", "New assignee (human, llm, unassigned)")
|
|
340
|
-
.option("--project <
|
|
395
|
+
.option("--project <project>", "Link to project (name or ID)")
|
|
341
396
|
.option("--no-worktree", "Skip automatic worktree creation when starting task")
|
|
342
397
|
.option("--json", "Output as JSON")
|
|
343
398
|
.action(async (id, options) => {
|
|
344
399
|
const config = ensureConfig();
|
|
345
|
-
|
|
400
|
+
const resolverConfig = { apiUrl: config.apiUrl, apiKey: config.apiKey };
|
|
346
401
|
const updates = {};
|
|
347
402
|
if (options.title)
|
|
348
403
|
updates.title = options.title;
|
|
@@ -354,8 +409,27 @@ taskCommand
|
|
|
354
409
|
updates.priority = options.priority;
|
|
355
410
|
if (options.assignee)
|
|
356
411
|
updates.assignee = options.assignee;
|
|
357
|
-
if (options.project)
|
|
358
|
-
|
|
412
|
+
if (options.project) {
|
|
413
|
+
const resolved = await resolveProject(options.project, resolverConfig);
|
|
414
|
+
if (!resolved) {
|
|
415
|
+
const projects = await fetchProjects(resolverConfig);
|
|
416
|
+
console.error(`\n❌ Project "${options.project}" not found.\n`);
|
|
417
|
+
console.error(formatProjectList(projects));
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
if (resolved.resolvedBy === "name-match") {
|
|
421
|
+
console.log(`ℹ️ Resolved "${options.project}" → ${resolved.projectName} (${resolved.projectId})`);
|
|
422
|
+
}
|
|
423
|
+
if (resolved.resolvedBy === "fuzzy-match") {
|
|
424
|
+
const confirmed = await confirm(`Did you mean "${resolved.projectName}"? (${Math.round(resolved.confidence * 100)}% match)`);
|
|
425
|
+
if (!confirmed) {
|
|
426
|
+
console.log("Cancelled.");
|
|
427
|
+
process.exit(0);
|
|
428
|
+
}
|
|
429
|
+
console.log(`ℹ️ Using project: ${resolved.projectName} (${resolved.projectId})`);
|
|
430
|
+
}
|
|
431
|
+
updates.projectId = resolved.projectId;
|
|
432
|
+
}
|
|
359
433
|
if (Object.keys(updates).length === 0) {
|
|
360
434
|
console.error("Error: No update options provided. Use --help for available options.");
|
|
361
435
|
process.exit(1);
|
|
@@ -547,11 +621,23 @@ taskCommand
|
|
|
547
621
|
});
|
|
548
622
|
// husky task message <id> "message" - post status message to task
|
|
549
623
|
taskCommand
|
|
550
|
-
.command("message
|
|
624
|
+
.command("message [id] [message]")
|
|
551
625
|
.description("Post a status message to a task")
|
|
552
|
-
.
|
|
626
|
+
.option("-m, --message <text>", "Message text (alternative to positional arg)")
|
|
627
|
+
.option("--id <taskId>", "Task ID (alternative to positional arg, or use HUSKY_TASK_ID)")
|
|
628
|
+
.action(async (idArg, messageArg, options) => {
|
|
553
629
|
const config = ensureConfig();
|
|
554
|
-
|
|
630
|
+
// Support both: `husky task message <id> <msg>` and `husky task message -m <msg> --id <id>`
|
|
631
|
+
const taskId = idArg || options.id || process.env.HUSKY_TASK_ID;
|
|
632
|
+
const message = messageArg || options.message;
|
|
633
|
+
if (!taskId) {
|
|
634
|
+
console.error("Error: Task ID required. Use positional arg, --id, or set HUSKY_TASK_ID");
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
if (!message) {
|
|
638
|
+
console.error("Error: Message required. Use positional arg or -m/--message");
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
555
641
|
try {
|
|
556
642
|
const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/status`, {
|
|
557
643
|
method: "POST",
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
+
import { createRequire } from "module";
|
|
3
4
|
import { taskCommand } from "./commands/task.js";
|
|
4
5
|
import { configCommand } from "./commands/config.js";
|
|
5
6
|
import { agentCommand } from "./commands/agent.js";
|
|
@@ -10,7 +11,6 @@ import { projectCommand } from "./commands/project.js";
|
|
|
10
11
|
import { ideaCommand } from "./commands/idea.js";
|
|
11
12
|
import { departmentCommand } from "./commands/department.js";
|
|
12
13
|
import { workflowCommand } from "./commands/workflow.js";
|
|
13
|
-
import { julesCommand } from "./commands/jules.js";
|
|
14
14
|
import { vmCommand } from "./commands/vm.js";
|
|
15
15
|
import { vmConfigCommand } from "./commands/vm-config.js";
|
|
16
16
|
import { processCommand } from "./commands/process.js";
|
|
@@ -20,15 +20,18 @@ import { completionCommand } from "./commands/completion.js";
|
|
|
20
20
|
import { worktreeCommand } from "./commands/worktree.js";
|
|
21
21
|
import { workerCommand } from "./commands/worker.js";
|
|
22
22
|
import { bizCommand } from "./commands/biz.js";
|
|
23
|
-
import { servicesCommand } from "./commands/services.js";
|
|
24
23
|
import { printLLMContext, llmCommand } from "./commands/llm-context.js";
|
|
25
24
|
import { runInteractiveMode } from "./commands/interactive.js";
|
|
26
25
|
import { serviceAccountCommand } from "./commands/service-account.js";
|
|
26
|
+
import { chatCommand } from "./commands/chat.js";
|
|
27
|
+
// Read version from package.json
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const packageJson = require("../package.json");
|
|
27
30
|
const program = new Command();
|
|
28
31
|
program
|
|
29
32
|
.name("husky")
|
|
30
33
|
.description("CLI for Huskyv0 Task Orchestration with Claude Agent")
|
|
31
|
-
.version(
|
|
34
|
+
.version(packageJson.version)
|
|
32
35
|
.option("--llm", "Output LLM reference documentation (markdown)");
|
|
33
36
|
program.addCommand(taskCommand);
|
|
34
37
|
program.addCommand(configCommand);
|
|
@@ -40,7 +43,6 @@ program.addCommand(projectCommand);
|
|
|
40
43
|
program.addCommand(ideaCommand);
|
|
41
44
|
program.addCommand(departmentCommand);
|
|
42
45
|
program.addCommand(workflowCommand);
|
|
43
|
-
program.addCommand(julesCommand);
|
|
44
46
|
program.addCommand(vmCommand);
|
|
45
47
|
program.addCommand(vmConfigCommand);
|
|
46
48
|
program.addCommand(processCommand);
|
|
@@ -50,8 +52,8 @@ program.addCommand(completionCommand);
|
|
|
50
52
|
program.addCommand(worktreeCommand);
|
|
51
53
|
program.addCommand(workerCommand);
|
|
52
54
|
program.addCommand(bizCommand);
|
|
53
|
-
program.addCommand(servicesCommand);
|
|
54
55
|
program.addCommand(serviceAccountCommand);
|
|
56
|
+
program.addCommand(chatCommand);
|
|
55
57
|
program.addCommand(llmCommand);
|
|
56
58
|
// Handle --llm flag specially
|
|
57
59
|
if (process.argv.includes("--llm")) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface Project {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ResolveResult {
|
|
6
|
+
projectId: string;
|
|
7
|
+
projectName: string;
|
|
8
|
+
resolvedBy: "exact-id" | "name-match" | "fuzzy-match";
|
|
9
|
+
confidence: number;
|
|
10
|
+
}
|
|
11
|
+
export interface FuzzyMatch {
|
|
12
|
+
project: Project;
|
|
13
|
+
score: number;
|
|
14
|
+
}
|
|
15
|
+
interface Config {
|
|
16
|
+
apiUrl: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function fetchProjects(config: Config): Promise<Project[]>;
|
|
20
|
+
export declare function resolveProject(input: string, config: Config, fuzzyThreshold?: number): Promise<ResolveResult | null>;
|
|
21
|
+
export declare function formatProjectList(projects: Project[]): string;
|
|
22
|
+
export declare function validateProjectId(projectId: string, config: Config): Promise<{
|
|
23
|
+
valid: boolean;
|
|
24
|
+
project?: Project;
|
|
25
|
+
}>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export async function fetchProjects(config) {
|
|
2
|
+
try {
|
|
3
|
+
const res = await fetch(`${config.apiUrl}/api/projects`, {
|
|
4
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
5
|
+
});
|
|
6
|
+
if (!res.ok)
|
|
7
|
+
return [];
|
|
8
|
+
return res.json();
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function calculateLevenshteinDistance(a, b) {
|
|
15
|
+
const matrix = [];
|
|
16
|
+
for (let i = 0; i <= b.length; i++) {
|
|
17
|
+
matrix[i] = [i];
|
|
18
|
+
}
|
|
19
|
+
for (let j = 0; j <= a.length; j++) {
|
|
20
|
+
matrix[0][j] = j;
|
|
21
|
+
}
|
|
22
|
+
for (let i = 1; i <= b.length; i++) {
|
|
23
|
+
for (let j = 1; j <= a.length; j++) {
|
|
24
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
25
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return matrix[b.length][a.length];
|
|
33
|
+
}
|
|
34
|
+
function similarityScore(a, b) {
|
|
35
|
+
const normalizedA = a.toLowerCase().trim();
|
|
36
|
+
const normalizedB = b.toLowerCase().trim();
|
|
37
|
+
if (normalizedA === normalizedB)
|
|
38
|
+
return 1;
|
|
39
|
+
const maxLen = Math.max(normalizedA.length, normalizedB.length);
|
|
40
|
+
if (maxLen === 0)
|
|
41
|
+
return 1;
|
|
42
|
+
const distance = calculateLevenshteinDistance(normalizedA, normalizedB);
|
|
43
|
+
return 1 - (distance / maxLen);
|
|
44
|
+
}
|
|
45
|
+
function findClosestMatch(input, projects) {
|
|
46
|
+
if (projects.length === 0)
|
|
47
|
+
return null;
|
|
48
|
+
let bestMatch = null;
|
|
49
|
+
for (const project of projects) {
|
|
50
|
+
const nameScore = similarityScore(input, project.name);
|
|
51
|
+
const idScore = similarityScore(input, project.id);
|
|
52
|
+
const score = Math.max(nameScore, idScore);
|
|
53
|
+
if (!bestMatch || score > bestMatch.score) {
|
|
54
|
+
bestMatch = { project, score };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return bestMatch;
|
|
58
|
+
}
|
|
59
|
+
export async function resolveProject(input, config, fuzzyThreshold = 0.6) {
|
|
60
|
+
const projects = await fetchProjects(config);
|
|
61
|
+
if (projects.length === 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const byId = projects.find((p) => p.id === input);
|
|
65
|
+
if (byId) {
|
|
66
|
+
return {
|
|
67
|
+
projectId: byId.id,
|
|
68
|
+
projectName: byId.name,
|
|
69
|
+
resolvedBy: "exact-id",
|
|
70
|
+
confidence: 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const byName = projects.find((p) => p.name.toLowerCase() === input.toLowerCase());
|
|
74
|
+
if (byName) {
|
|
75
|
+
return {
|
|
76
|
+
projectId: byName.id,
|
|
77
|
+
projectName: byName.name,
|
|
78
|
+
resolvedBy: "name-match",
|
|
79
|
+
confidence: 1,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const fuzzy = findClosestMatch(input, projects);
|
|
83
|
+
if (fuzzy && fuzzy.score >= fuzzyThreshold) {
|
|
84
|
+
return {
|
|
85
|
+
projectId: fuzzy.project.id,
|
|
86
|
+
projectName: fuzzy.project.name,
|
|
87
|
+
resolvedBy: "fuzzy-match",
|
|
88
|
+
confidence: fuzzy.score,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
export function formatProjectList(projects) {
|
|
94
|
+
if (projects.length === 0) {
|
|
95
|
+
return " No projects available.";
|
|
96
|
+
}
|
|
97
|
+
const lines = [];
|
|
98
|
+
lines.push(" Available projects:");
|
|
99
|
+
for (const p of projects) {
|
|
100
|
+
lines.push(` - ${p.name} (ID: ${p.id})`);
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
export async function validateProjectId(projectId, config) {
|
|
105
|
+
const projects = await fetchProjects(config);
|
|
106
|
+
const project = projects.find((p) => p.id === projectId);
|
|
107
|
+
return {
|
|
108
|
+
valid: !!project,
|
|
109
|
+
project,
|
|
110
|
+
};
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function julesSessionsMenu(): Promise<void>;
|