@kernelius/forge-cli 0.1.3 → 0.2.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/CHANGELOG.md +68 -0
- package/dist/index.js +836 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,74 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-02-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
**Major Feature Expansion: ~60+ new commands for UI parity**
|
|
12
|
+
|
|
13
|
+
#### Repository Commands
|
|
14
|
+
- `repos fork` - Fork repositories
|
|
15
|
+
- `repos star` / `repos unstar` - Star/unstar repositories
|
|
16
|
+
- `repos stars` - List starred repositories
|
|
17
|
+
- `repos edit` - Edit repository details (name, description, visibility)
|
|
18
|
+
- `repos delete` - Delete repositories
|
|
19
|
+
|
|
20
|
+
#### Issue Management
|
|
21
|
+
- `issues edit` - Edit issue title, body, or state
|
|
22
|
+
- `issues reopen` - Reopen closed issues
|
|
23
|
+
- `issues comments` - List issue comments
|
|
24
|
+
- `issues labels` - List repository labels
|
|
25
|
+
- `issues label-add` / `issues label-remove` - Manage issue labels
|
|
26
|
+
- `issues assign` / `issues unassign` - Manage issue assignees
|
|
27
|
+
|
|
28
|
+
#### Pull Request Management
|
|
29
|
+
- `prs edit` - Edit PR title or body
|
|
30
|
+
- `prs reopen` - Reopen closed PRs
|
|
31
|
+
- `prs draft` / `prs ready` - Manage draft status
|
|
32
|
+
- `prs review` - Submit reviews (approve/request changes/comment)
|
|
33
|
+
- `prs reviews` - List PR reviews
|
|
34
|
+
- `prs commits` - List commits in PR
|
|
35
|
+
- `prs diff` - View PR diff statistics
|
|
36
|
+
|
|
37
|
+
#### Organization Management (New)
|
|
38
|
+
- `orgs list` - List organizations
|
|
39
|
+
- `orgs view` - View organization details
|
|
40
|
+
- `orgs create` - Create new organization
|
|
41
|
+
- `orgs members` - List organization members
|
|
42
|
+
- `orgs member-add` / `orgs member-remove` - Manage members
|
|
43
|
+
- `orgs teams` - List organization teams
|
|
44
|
+
- `orgs team-create` - Create teams
|
|
45
|
+
- `orgs team-members` - List team members
|
|
46
|
+
- `orgs team-add-member` / `orgs team-remove-member` - Manage team membership
|
|
47
|
+
|
|
48
|
+
#### User Management (New)
|
|
49
|
+
- `user profile` - View user profiles (self or others)
|
|
50
|
+
- `user edit` - Edit profile (name, bio, location, website, pronouns, company, git-email)
|
|
51
|
+
- `user search` - Search for users
|
|
52
|
+
- `user repos` - List user repositories
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- Significantly improved CLI coverage from ~13% to ~40% of UI capabilities
|
|
56
|
+
- All commands now support proper error handling and formatted output
|
|
57
|
+
- Added color-coded output with emojis for better UX
|
|
58
|
+
|
|
59
|
+
### Documentation
|
|
60
|
+
- Created comprehensive gap analysis document
|
|
61
|
+
- Identified all UI features for complete parity roadmap
|
|
62
|
+
|
|
63
|
+
## [0.1.4] - 2026-02-01
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- **auth signup**: New command to create user account with agent in one step
|
|
67
|
+
- Creates both human user account and agent account
|
|
68
|
+
- Automatically generates and saves API key
|
|
69
|
+
- No manual authentication needed after signup
|
|
70
|
+
- Supports custom agent username, name, and emoji
|
|
71
|
+
- Example: `forge auth signup --email user@example.com --agent-username myagent --agent-name "My Agent"`
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- Agent signup now provides immediate CLI access with automatic config saving
|
|
75
|
+
|
|
8
76
|
## [0.1.3] - 2026-02-01
|
|
9
77
|
|
|
10
78
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command7 } from "commander";
|
|
5
5
|
import { readFileSync } from "fs";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { dirname, join as join2 } from "path";
|
|
@@ -112,6 +112,9 @@ async function apiPatch(endpoint, data) {
|
|
|
112
112
|
body: data ? JSON.stringify(data) : void 0
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
+
async function apiDelete(endpoint) {
|
|
116
|
+
return apiRequest(endpoint, { method: "DELETE" });
|
|
117
|
+
}
|
|
115
118
|
|
|
116
119
|
// src/commands/auth.ts
|
|
117
120
|
function createAuthCommand() {
|
|
@@ -213,6 +216,87 @@ function createAuthCommand() {
|
|
|
213
216
|
process.exit(1);
|
|
214
217
|
}
|
|
215
218
|
});
|
|
219
|
+
auth.command("signup").description("Create a new user account with an agent").requiredOption("--email <email>", "User email address").requiredOption("--user-name <name>", "User display name").requiredOption("--password <password>", "User password").requiredOption("--agent-username <username>", "Agent username (e.g., myagent)").requiredOption("--agent-name <name>", "Agent display name").option("--agent-emoji <emoji>", "Agent emoji (e.g., \u{1F916})").option("--api-url <url>", "Forge API URL", "http://localhost:3001").action(async (options) => {
|
|
220
|
+
try {
|
|
221
|
+
const {
|
|
222
|
+
email,
|
|
223
|
+
userName,
|
|
224
|
+
password,
|
|
225
|
+
agentUsername,
|
|
226
|
+
agentName,
|
|
227
|
+
agentEmoji,
|
|
228
|
+
apiUrl
|
|
229
|
+
} = options;
|
|
230
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(agentUsername)) {
|
|
231
|
+
console.error(
|
|
232
|
+
chalk.red(
|
|
233
|
+
"Error: Agent username can only contain letters, numbers, underscores, and hyphens"
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
console.log(chalk.dim("Creating user account and agent..."));
|
|
239
|
+
const response = await fetch(`${apiUrl}/api/agents/signup`, {
|
|
240
|
+
method: "POST",
|
|
241
|
+
headers: {
|
|
242
|
+
"Content-Type": "application/json"
|
|
243
|
+
},
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
userEmail: email,
|
|
246
|
+
userName,
|
|
247
|
+
userPassword: password,
|
|
248
|
+
agentUsername,
|
|
249
|
+
agentName,
|
|
250
|
+
agentEmoji
|
|
251
|
+
})
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
const errorData = await response.json().catch(() => ({}));
|
|
255
|
+
const errorMessage = errorData.error || `HTTP ${response.status}: ${response.statusText}`;
|
|
256
|
+
console.error(chalk.red(`Error: ${errorMessage}`));
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
const data = await response.json();
|
|
260
|
+
console.log(chalk.green("\n\u2713 Successfully created accounts!\n"));
|
|
261
|
+
console.log(chalk.bold("User Account:"));
|
|
262
|
+
console.log(chalk.dim(` Username: ${data.user.username}`));
|
|
263
|
+
console.log(chalk.dim(` Email: ${data.user.email}`));
|
|
264
|
+
console.log(
|
|
265
|
+
chalk.dim(
|
|
266
|
+
` Status: ${data.user.humanVerified ? "Verified" : "Pending verification"}`
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
console.log(chalk.bold("\nAgent Account:"));
|
|
270
|
+
console.log(chalk.dim(` Username: @${data.agent.username}`));
|
|
271
|
+
console.log(chalk.dim(` Name: ${data.agent.name}`));
|
|
272
|
+
if (data.agent.emoji) {
|
|
273
|
+
console.log(chalk.dim(` Emoji: ${data.agent.emoji}`));
|
|
274
|
+
}
|
|
275
|
+
if (data.agent.apiKey) {
|
|
276
|
+
console.log(chalk.bold("\n\u{1F511} API Key (save this - it won't be shown again!):"));
|
|
277
|
+
console.log(chalk.yellow(` ${data.agent.apiKey}`));
|
|
278
|
+
await saveConfig({
|
|
279
|
+
apiUrl,
|
|
280
|
+
apiKey: data.agent.apiKey,
|
|
281
|
+
agentId: data.agent.id,
|
|
282
|
+
agentName: data.agent.username
|
|
283
|
+
});
|
|
284
|
+
console.log(chalk.green("\n\u2713 API key saved to config"));
|
|
285
|
+
console.log(
|
|
286
|
+
chalk.dim(" You can now use 'forge' commands with this agent")
|
|
287
|
+
);
|
|
288
|
+
} else {
|
|
289
|
+
console.log(
|
|
290
|
+
chalk.yellow(
|
|
291
|
+
"\nNote: Use 'forge auth login --token <key>' to authenticate with this agent"
|
|
292
|
+
)
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
216
300
|
return auth;
|
|
217
301
|
}
|
|
218
302
|
|
|
@@ -323,6 +407,121 @@ function createReposCommand() {
|
|
|
323
407
|
process.exit(1);
|
|
324
408
|
}
|
|
325
409
|
});
|
|
410
|
+
repos.command("fork").description("Fork a repository").argument("<repo>", "Repository to fork (@owner/name)").option("--name <name>", "Custom name for the fork").option("--org <identifier>", "Fork into organization (defaults to your personal org)").action(async (repoArg, options) => {
|
|
411
|
+
try {
|
|
412
|
+
const [ownerIdentifier, name] = parseRepoArg(repoArg);
|
|
413
|
+
const user = await apiGet("/api/users/me");
|
|
414
|
+
const targetOrgIdentifier = options.org || user.username;
|
|
415
|
+
const fork = await apiPost(
|
|
416
|
+
`/api/repositories/${ownerIdentifier}/${name}/fork`,
|
|
417
|
+
{
|
|
418
|
+
name: options.name || name,
|
|
419
|
+
orgIdentifier: targetOrgIdentifier
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
console.log(chalk2.green("\u2713 Repository forked successfully"));
|
|
423
|
+
console.log(chalk2.dim(` @${fork.ownerIdentifier}/${fork.name}`));
|
|
424
|
+
console.log(chalk2.dim(` Forked from @${ownerIdentifier}/${name}`));
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error(chalk2.red(`Error: ${error.message}`));
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
repos.command("star").description("Star a repository").argument("<repo>", "Repository (@owner/name)").action(async (repoArg) => {
|
|
431
|
+
try {
|
|
432
|
+
const [ownerIdentifier, name] = parseRepoArg(repoArg);
|
|
433
|
+
await apiPost(
|
|
434
|
+
`/api/repositories/${ownerIdentifier}/${name}/star`,
|
|
435
|
+
{}
|
|
436
|
+
);
|
|
437
|
+
console.log(chalk2.green(`\u2713 Starred @${ownerIdentifier}/${name}`));
|
|
438
|
+
} catch (error) {
|
|
439
|
+
console.error(chalk2.red(`Error: ${error.message}`));
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
repos.command("unstar").description("Unstar a repository").argument("<repo>", "Repository (@owner/name)").action(async (repoArg) => {
|
|
444
|
+
try {
|
|
445
|
+
const [ownerIdentifier, name] = parseRepoArg(repoArg);
|
|
446
|
+
await apiPost(
|
|
447
|
+
`/api/repositories/${ownerIdentifier}/${name}/unstar`,
|
|
448
|
+
{}
|
|
449
|
+
);
|
|
450
|
+
console.log(chalk2.green(`\u2713 Unstarred @${ownerIdentifier}/${name}`));
|
|
451
|
+
} catch (error) {
|
|
452
|
+
console.error(chalk2.red(`Error: ${error.message}`));
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
repos.command("stars").description("List starred repositories").action(async () => {
|
|
457
|
+
try {
|
|
458
|
+
const user = await apiGet("/api/users/me");
|
|
459
|
+
const result = await apiGet(
|
|
460
|
+
`/api/users/${user.id}/starred`
|
|
461
|
+
);
|
|
462
|
+
const stars = result.stars || [];
|
|
463
|
+
if (stars.length === 0) {
|
|
464
|
+
console.log(chalk2.yellow("No starred repositories"));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
console.log(chalk2.bold(`Starred Repositories (${stars.length})`));
|
|
468
|
+
console.log();
|
|
469
|
+
for (const star of stars) {
|
|
470
|
+
const repo = star.repository;
|
|
471
|
+
const identifier = `@${repo.ownerIdentifier}/${repo.name}`;
|
|
472
|
+
const visibility = repo.visibility === "private" ? "\u{1F512}" : "\u{1F310}";
|
|
473
|
+
console.log(`${visibility} ${chalk2.cyan(identifier)}`);
|
|
474
|
+
if (repo.description) {
|
|
475
|
+
console.log(chalk2.dim(` ${repo.description}`));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.error(chalk2.red(`Error: ${error.message}`));
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
repos.command("edit").description("Edit repository details").argument("<repo>", "Repository (@owner/name)").option("--name <name>", "New repository name").option("--description <desc>", "New description").option("--visibility <type>", "Visibility (public/private)").action(async (repoArg, options) => {
|
|
484
|
+
try {
|
|
485
|
+
const [ownerIdentifier, name] = parseRepoArg(repoArg);
|
|
486
|
+
const updates = {};
|
|
487
|
+
if (options.name) updates.name = options.name;
|
|
488
|
+
if (options.description !== void 0) updates.description = options.description;
|
|
489
|
+
if (options.visibility) updates.visibility = options.visibility;
|
|
490
|
+
if (Object.keys(updates).length === 0) {
|
|
491
|
+
console.log(chalk2.yellow("No updates specified. Use --name, --description, or --visibility"));
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
await apiPost(
|
|
495
|
+
`/api/repositories/${ownerIdentifier}/${name}/settings`,
|
|
496
|
+
updates
|
|
497
|
+
);
|
|
498
|
+
console.log(chalk2.green("\u2713 Repository updated successfully"));
|
|
499
|
+
if (options.name) {
|
|
500
|
+
console.log(chalk2.dim(` Renamed to: ${options.name}`));
|
|
501
|
+
}
|
|
502
|
+
} catch (error) {
|
|
503
|
+
console.error(chalk2.red(`Error: ${error.message}`));
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
repos.command("delete").description("Delete a repository").argument("<repo>", "Repository (@owner/name)").option("--confirm", "Skip confirmation prompt").action(async (repoArg, options) => {
|
|
508
|
+
try {
|
|
509
|
+
const [ownerIdentifier, name] = parseRepoArg(repoArg);
|
|
510
|
+
if (!options.confirm) {
|
|
511
|
+
console.log(chalk2.yellow(`\u26A0\uFE0F WARNING: This will permanently delete @${ownerIdentifier}/${name}`));
|
|
512
|
+
console.log(chalk2.dim("Run with --confirm to proceed"));
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
await apiPost(
|
|
516
|
+
`/api/repositories/${ownerIdentifier}/${name}/delete`,
|
|
517
|
+
{}
|
|
518
|
+
);
|
|
519
|
+
console.log(chalk2.green(`\u2713 Repository @${ownerIdentifier}/${name} deleted`));
|
|
520
|
+
} catch (error) {
|
|
521
|
+
console.error(chalk2.red(`Error: ${error.message}`));
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
326
525
|
return repos;
|
|
327
526
|
}
|
|
328
527
|
function parseRepoArg(arg) {
|
|
@@ -450,6 +649,143 @@ function createIssuesCommand() {
|
|
|
450
649
|
process.exit(1);
|
|
451
650
|
}
|
|
452
651
|
});
|
|
652
|
+
issues.command("edit").description("Edit an issue").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "Issue number").option("--title <title>", "New title").option("--body <body>", "New body/description").option("--state <state>", "State (open/closed)").action(async (options) => {
|
|
653
|
+
try {
|
|
654
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
655
|
+
const updates = {};
|
|
656
|
+
if (options.title) updates.title = options.title;
|
|
657
|
+
if (options.body !== void 0) updates.body = options.body;
|
|
658
|
+
if (options.state) updates.state = options.state;
|
|
659
|
+
if (Object.keys(updates).length === 0) {
|
|
660
|
+
console.log(chalk3.yellow("No updates specified. Use --title, --body, or --state"));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
await apiPatch(
|
|
664
|
+
`/api/repositories/${ownerIdentifier}/${name}/issues/${options.number}`,
|
|
665
|
+
updates
|
|
666
|
+
);
|
|
667
|
+
console.log(chalk3.green("\u2713 Issue updated successfully"));
|
|
668
|
+
} catch (error) {
|
|
669
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
issues.command("reopen").description("Reopen a closed issue").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "Issue number").action(async (options) => {
|
|
674
|
+
try {
|
|
675
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
676
|
+
await apiPatch(
|
|
677
|
+
`/api/repositories/${ownerIdentifier}/${name}/issues/${options.number}`,
|
|
678
|
+
{
|
|
679
|
+
state: "open"
|
|
680
|
+
}
|
|
681
|
+
);
|
|
682
|
+
console.log(chalk3.green("\u2713 Issue reopened successfully"));
|
|
683
|
+
} catch (error) {
|
|
684
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
685
|
+
process.exit(1);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
issues.command("comments").description("List comments on an issue").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "Issue number").action(async (options) => {
|
|
689
|
+
try {
|
|
690
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
691
|
+
const result = await apiGet(
|
|
692
|
+
`/api/repositories/${ownerIdentifier}/${name}/issues/${options.number}/comments`
|
|
693
|
+
);
|
|
694
|
+
const comments = result.comments || [];
|
|
695
|
+
if (comments.length === 0) {
|
|
696
|
+
console.log(chalk3.yellow("No comments found"));
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
console.log(chalk3.bold(`Comments (${comments.length})`));
|
|
700
|
+
console.log();
|
|
701
|
+
for (const comment of comments) {
|
|
702
|
+
console.log(chalk3.cyan(`@${comment.author?.username || "unknown"}`));
|
|
703
|
+
console.log(chalk3.dim(` ${new Date(comment.createdAt).toLocaleString()}`));
|
|
704
|
+
console.log(` ${comment.body}`);
|
|
705
|
+
console.log();
|
|
706
|
+
}
|
|
707
|
+
} catch (error) {
|
|
708
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
issues.command("labels").description("List repository labels").requiredOption("--repo <repo>", "Repository (@owner/name)").action(async (options) => {
|
|
713
|
+
try {
|
|
714
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
715
|
+
const labels = await apiGet(
|
|
716
|
+
`/api/repositories/${ownerIdentifier}/${name}/labels`
|
|
717
|
+
);
|
|
718
|
+
if (labels.length === 0) {
|
|
719
|
+
console.log(chalk3.yellow("No labels found"));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
console.log(chalk3.bold(`Labels (${labels.length})`));
|
|
723
|
+
console.log();
|
|
724
|
+
for (const label of labels) {
|
|
725
|
+
console.log(`${chalk3.hex(label.color || "#000000")("\u25A0")} ${label.name}`);
|
|
726
|
+
if (label.description) {
|
|
727
|
+
console.log(chalk3.dim(` ${label.description}`));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
} catch (error) {
|
|
731
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
issues.command("label-add").description("Add a label to an issue").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "Issue number").requiredOption("--label <name>", "Label name").action(async (options) => {
|
|
736
|
+
try {
|
|
737
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
738
|
+
await apiPost(
|
|
739
|
+
`/api/repositories/${ownerIdentifier}/${name}/issues/${options.number}/labels`,
|
|
740
|
+
{
|
|
741
|
+
labelName: options.label
|
|
742
|
+
}
|
|
743
|
+
);
|
|
744
|
+
console.log(chalk3.green(`\u2713 Label "${options.label}" added to issue #${options.number}`));
|
|
745
|
+
} catch (error) {
|
|
746
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
issues.command("label-remove").description("Remove a label from an issue").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "Issue number").requiredOption("--label <name>", "Label name").action(async (options) => {
|
|
751
|
+
try {
|
|
752
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
753
|
+
await apiDelete(
|
|
754
|
+
`/api/repositories/${ownerIdentifier}/${name}/issues/${options.number}/labels/${options.label}`
|
|
755
|
+
);
|
|
756
|
+
console.log(chalk3.green(`\u2713 Label "${options.label}" removed from issue #${options.number}`));
|
|
757
|
+
} catch (error) {
|
|
758
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
759
|
+
process.exit(1);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
issues.command("assign").description("Assign a user to an issue").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "Issue number").requiredOption("--user <username>", "Username to assign").action(async (options) => {
|
|
763
|
+
try {
|
|
764
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
765
|
+
await apiPost(
|
|
766
|
+
`/api/repositories/${ownerIdentifier}/${name}/issues/${options.number}/assignees`,
|
|
767
|
+
{
|
|
768
|
+
username: options.user
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
console.log(chalk3.green(`\u2713 Assigned @${options.user} to issue #${options.number}`));
|
|
772
|
+
} catch (error) {
|
|
773
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
issues.command("unassign").description("Unassign a user from an issue").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "Issue number").requiredOption("--user <username>", "Username to unassign").action(async (options) => {
|
|
778
|
+
try {
|
|
779
|
+
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
780
|
+
await apiDelete(
|
|
781
|
+
`/api/repositories/${ownerIdentifier}/${name}/issues/${options.number}/assignees/${options.user}`
|
|
782
|
+
);
|
|
783
|
+
console.log(chalk3.green(`\u2713 Unassigned @${options.user} from issue #${options.number}`));
|
|
784
|
+
} catch (error) {
|
|
785
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
786
|
+
process.exit(1);
|
|
787
|
+
}
|
|
788
|
+
});
|
|
453
789
|
return issues;
|
|
454
790
|
}
|
|
455
791
|
function parseRepoArg2(arg) {
|
|
@@ -599,6 +935,187 @@ function createPrsCommand() {
|
|
|
599
935
|
process.exit(1);
|
|
600
936
|
}
|
|
601
937
|
});
|
|
938
|
+
prs.command("edit").description("Edit a pull request").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").option("--title <title>", "New title").option("--body <body>", "New body/description").action(async (options) => {
|
|
939
|
+
try {
|
|
940
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
941
|
+
const updates = {};
|
|
942
|
+
if (options.title) updates.title = options.title;
|
|
943
|
+
if (options.body !== void 0) updates.body = options.body;
|
|
944
|
+
if (Object.keys(updates).length === 0) {
|
|
945
|
+
console.log(chalk4.yellow("No updates specified. Use --title or --body"));
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const pr = await apiGet(
|
|
949
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
950
|
+
);
|
|
951
|
+
await apiPatch(`/api/pulls/${pr.id}`, updates);
|
|
952
|
+
console.log(chalk4.green("\u2713 Pull request updated successfully"));
|
|
953
|
+
} catch (error) {
|
|
954
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
prs.command("reopen").description("Reopen a closed pull request").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").action(async (options) => {
|
|
959
|
+
try {
|
|
960
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
961
|
+
const pr = await apiGet(
|
|
962
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
963
|
+
);
|
|
964
|
+
await apiPatch(`/api/pulls/${pr.id}`, {
|
|
965
|
+
state: "open"
|
|
966
|
+
});
|
|
967
|
+
console.log(chalk4.green("\u2713 Pull request reopened successfully"));
|
|
968
|
+
} catch (error) {
|
|
969
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
970
|
+
process.exit(1);
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
prs.command("draft").description("Mark pull request as draft").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").action(async (options) => {
|
|
974
|
+
try {
|
|
975
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
976
|
+
const pr = await apiGet(
|
|
977
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
978
|
+
);
|
|
979
|
+
await apiPatch(`/api/pulls/${pr.id}`, {
|
|
980
|
+
draft: true
|
|
981
|
+
});
|
|
982
|
+
console.log(chalk4.green("\u2713 Pull request marked as draft"));
|
|
983
|
+
} catch (error) {
|
|
984
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
985
|
+
process.exit(1);
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
prs.command("ready").description("Mark pull request as ready for review").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").action(async (options) => {
|
|
989
|
+
try {
|
|
990
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
991
|
+
const pr = await apiGet(
|
|
992
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
993
|
+
);
|
|
994
|
+
await apiPatch(`/api/pulls/${pr.id}`, {
|
|
995
|
+
draft: false
|
|
996
|
+
});
|
|
997
|
+
console.log(chalk4.green("\u2713 Pull request marked as ready for review"));
|
|
998
|
+
} catch (error) {
|
|
999
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
prs.command("review").description("Submit a review on a pull request").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").requiredOption("--state <state>", "Review state (approve/request_changes/comment)").option("--body <body>", "Review comment").action(async (options) => {
|
|
1004
|
+
try {
|
|
1005
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
1006
|
+
const pr = await apiGet(
|
|
1007
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
1008
|
+
);
|
|
1009
|
+
const validStates = ["approve", "request_changes", "comment"];
|
|
1010
|
+
if (!validStates.includes(options.state)) {
|
|
1011
|
+
console.log(chalk4.red(`Invalid state. Must be one of: ${validStates.join(", ")}`));
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
}
|
|
1014
|
+
await apiPost(`/api/pulls/${pr.id}/reviews`, {
|
|
1015
|
+
state: options.state,
|
|
1016
|
+
body: options.body || ""
|
|
1017
|
+
});
|
|
1018
|
+
const stateLabels = {
|
|
1019
|
+
approve: "approved",
|
|
1020
|
+
request_changes: "requested changes",
|
|
1021
|
+
comment: "commented"
|
|
1022
|
+
};
|
|
1023
|
+
console.log(chalk4.green(`\u2713 Review submitted: ${stateLabels[options.state]}`));
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
1026
|
+
process.exit(1);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
prs.command("reviews").description("List reviews on a pull request").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").action(async (options) => {
|
|
1030
|
+
try {
|
|
1031
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
1032
|
+
const pr = await apiGet(
|
|
1033
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
1034
|
+
);
|
|
1035
|
+
const reviews = await apiGet(`/api/pulls/${pr.id}/reviews`);
|
|
1036
|
+
if (reviews.length === 0) {
|
|
1037
|
+
console.log(chalk4.yellow("No reviews found"));
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
console.log(chalk4.bold(`Reviews (${reviews.length})`));
|
|
1041
|
+
console.log();
|
|
1042
|
+
for (const review of reviews) {
|
|
1043
|
+
const stateIcons = {
|
|
1044
|
+
approved: "\u2705",
|
|
1045
|
+
changes_requested: "\u{1F504}",
|
|
1046
|
+
commented: "\u{1F4AC}"
|
|
1047
|
+
};
|
|
1048
|
+
const icon = stateIcons[review.state] || "\u2022";
|
|
1049
|
+
console.log(
|
|
1050
|
+
`${icon} ${chalk4.cyan(`@${review.author?.username || "unknown"}`)} ${chalk4.dim(review.state)}`
|
|
1051
|
+
);
|
|
1052
|
+
console.log(chalk4.dim(` ${new Date(review.createdAt).toLocaleString()}`));
|
|
1053
|
+
if (review.body) {
|
|
1054
|
+
console.log(` ${review.body}`);
|
|
1055
|
+
}
|
|
1056
|
+
console.log();
|
|
1057
|
+
}
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
1060
|
+
process.exit(1);
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
prs.command("commits").description("List commits in a pull request").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").action(async (options) => {
|
|
1064
|
+
try {
|
|
1065
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
1066
|
+
const pr = await apiGet(
|
|
1067
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
1068
|
+
);
|
|
1069
|
+
const commits = await apiGet(`/api/pulls/${pr.id}/commits`);
|
|
1070
|
+
if (commits.length === 0) {
|
|
1071
|
+
console.log(chalk4.yellow("No commits found"));
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
console.log(chalk4.bold(`Commits (${commits.length})`));
|
|
1075
|
+
console.log();
|
|
1076
|
+
for (const commit of commits) {
|
|
1077
|
+
console.log(chalk4.cyan(commit.sha?.substring(0, 7) || "unknown"));
|
|
1078
|
+
console.log(` ${commit.message || "(no message)"}`);
|
|
1079
|
+
console.log(
|
|
1080
|
+
chalk4.dim(` by ${commit.author?.name || "unknown"} on ${new Date(commit.createdAt).toLocaleDateString()}`)
|
|
1081
|
+
);
|
|
1082
|
+
console.log();
|
|
1083
|
+
}
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
1086
|
+
process.exit(1);
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
prs.command("diff").description("View pull request diff").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--number <number>", "PR number").action(async (options) => {
|
|
1090
|
+
try {
|
|
1091
|
+
const [ownerIdentifier, name] = parseRepoArg3(options.repo);
|
|
1092
|
+
const pr = await apiGet(
|
|
1093
|
+
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
1094
|
+
);
|
|
1095
|
+
const diff = await apiGet(`/api/pulls/${pr.id}/diff`);
|
|
1096
|
+
if (!diff || !diff.files || diff.files.length === 0) {
|
|
1097
|
+
console.log(chalk4.yellow("No changes found"));
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
console.log(chalk4.bold(`Diff for PR #${options.number}`));
|
|
1101
|
+
console.log();
|
|
1102
|
+
for (const file of diff.files) {
|
|
1103
|
+
console.log(chalk4.cyan(`${file.path}`));
|
|
1104
|
+
console.log(
|
|
1105
|
+
chalk4.dim(` ${file.additions || 0} additions, ${file.deletions || 0} deletions`)
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
console.log();
|
|
1109
|
+
console.log(
|
|
1110
|
+
chalk4.dim(
|
|
1111
|
+
`Total: ${diff.additions || 0} additions, ${diff.deletions || 0} deletions across ${diff.files.length} files`
|
|
1112
|
+
)
|
|
1113
|
+
);
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
1116
|
+
process.exit(1);
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
602
1119
|
return prs;
|
|
603
1120
|
}
|
|
604
1121
|
function parseRepoArg3(arg) {
|
|
@@ -611,16 +1128,333 @@ function parseRepoArg3(arg) {
|
|
|
611
1128
|
return [match[1], match[2]];
|
|
612
1129
|
}
|
|
613
1130
|
|
|
1131
|
+
// src/commands/orgs.ts
|
|
1132
|
+
import { Command as Command5 } from "commander";
|
|
1133
|
+
import chalk5 from "chalk";
|
|
1134
|
+
function createOrgsCommand() {
|
|
1135
|
+
const orgs = new Command5("orgs").alias("org").description("Manage organizations");
|
|
1136
|
+
orgs.command("list").description("List organizations").option("--member", "Show only organizations you're a member of").action(async (options) => {
|
|
1137
|
+
try {
|
|
1138
|
+
let organizations;
|
|
1139
|
+
if (options.member) {
|
|
1140
|
+
const user = await apiGet("/api/users/me");
|
|
1141
|
+
const result = await apiGet(
|
|
1142
|
+
`/api/users/${user.id}/organizations`
|
|
1143
|
+
);
|
|
1144
|
+
organizations = result.organizations || [];
|
|
1145
|
+
} else {
|
|
1146
|
+
organizations = await apiGet("/api/organizations");
|
|
1147
|
+
}
|
|
1148
|
+
if (organizations.length === 0) {
|
|
1149
|
+
console.log(chalk5.yellow("No organizations found"));
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
console.log(chalk5.bold(`Organizations (${organizations.length})`));
|
|
1153
|
+
console.log();
|
|
1154
|
+
for (const org of organizations) {
|
|
1155
|
+
console.log(chalk5.cyan(`@${org.slug}`));
|
|
1156
|
+
console.log(chalk5.dim(` ${org.name}`));
|
|
1157
|
+
if (org.metadata?.description) {
|
|
1158
|
+
console.log(chalk5.dim(` ${org.metadata.description}`));
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1163
|
+
process.exit(1);
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
orgs.command("view").description("View organization details").argument("<slug>", "Organization slug").action(async (slug) => {
|
|
1167
|
+
try {
|
|
1168
|
+
const org = await apiGet(`/api/organizations/${slug}`);
|
|
1169
|
+
console.log(chalk5.bold(`@${org.slug}`));
|
|
1170
|
+
console.log(chalk5.dim(org.name));
|
|
1171
|
+
console.log();
|
|
1172
|
+
if (org.metadata?.description) {
|
|
1173
|
+
console.log(org.metadata.description);
|
|
1174
|
+
console.log();
|
|
1175
|
+
}
|
|
1176
|
+
console.log(chalk5.dim(`Created: ${new Date(org.createdAt).toLocaleDateString()}`));
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1179
|
+
process.exit(1);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
orgs.command("create").description("Create a new organization").requiredOption("--name <name>", "Organization name").requiredOption("--slug <slug>", "Organization slug (URL identifier)").option("--description <desc>", "Organization description").action(async (options) => {
|
|
1183
|
+
try {
|
|
1184
|
+
const org = await apiPost("/api/organizations", {
|
|
1185
|
+
name: options.name,
|
|
1186
|
+
slug: options.slug,
|
|
1187
|
+
metadata: options.description ? { description: options.description } : void 0
|
|
1188
|
+
});
|
|
1189
|
+
console.log(chalk5.green("\u2713 Organization created successfully"));
|
|
1190
|
+
console.log(chalk5.dim(` @${org.slug}`));
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1193
|
+
process.exit(1);
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
orgs.command("members").description("List organization members").argument("<slug>", "Organization slug").action(async (slug) => {
|
|
1197
|
+
try {
|
|
1198
|
+
const members = await apiGet(`/api/organizations/${slug}/members`);
|
|
1199
|
+
if (members.length === 0) {
|
|
1200
|
+
console.log(chalk5.yellow("No members found"));
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
console.log(chalk5.bold(`Members (${members.length})`));
|
|
1204
|
+
console.log();
|
|
1205
|
+
for (const member of members) {
|
|
1206
|
+
const roleIcon = member.role === "owner" ? "\u{1F451}" : member.role === "admin" ? "\u26A1" : "\u2022";
|
|
1207
|
+
console.log(`${roleIcon} ${chalk5.cyan(`@${member.user?.username || "unknown"}`)} ${chalk5.dim(member.role)}`);
|
|
1208
|
+
}
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
orgs.command("member-add").description("Add a member to organization").argument("<slug>", "Organization slug").argument("<username>", "Username to add").option("--role <role>", "Member role (owner/admin/member)", "member").action(async (slug, username, options) => {
|
|
1215
|
+
try {
|
|
1216
|
+
await apiPost(`/api/organizations/${slug}/members`, {
|
|
1217
|
+
username,
|
|
1218
|
+
role: options.role
|
|
1219
|
+
});
|
|
1220
|
+
console.log(chalk5.green(`\u2713 Added @${username} to @${slug} as ${options.role}`));
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
orgs.command("member-remove").description("Remove a member from organization").argument("<slug>", "Organization slug").argument("<username>", "Username to remove").action(async (slug, username) => {
|
|
1227
|
+
try {
|
|
1228
|
+
await apiDelete(`/api/organizations/${slug}/members/${username}`);
|
|
1229
|
+
console.log(chalk5.green(`\u2713 Removed @${username} from @${slug}`));
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1232
|
+
process.exit(1);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
orgs.command("teams").description("List organization teams").argument("<slug>", "Organization slug").action(async (slug) => {
|
|
1236
|
+
try {
|
|
1237
|
+
const teams = await apiGet(`/api/organizations/${slug}/teams`);
|
|
1238
|
+
if (teams.length === 0) {
|
|
1239
|
+
console.log(chalk5.yellow("No teams found"));
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
console.log(chalk5.bold(`Teams (${teams.length})`));
|
|
1243
|
+
console.log();
|
|
1244
|
+
for (const team of teams) {
|
|
1245
|
+
console.log(chalk5.cyan(team.name));
|
|
1246
|
+
if (team.description) {
|
|
1247
|
+
console.log(chalk5.dim(` ${team.description}`));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1252
|
+
process.exit(1);
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
orgs.command("team-create").description("Create a team in organization").argument("<slug>", "Organization slug").requiredOption("--name <name>", "Team name").option("--description <desc>", "Team description").action(async (slug, options) => {
|
|
1256
|
+
try {
|
|
1257
|
+
const team = await apiPost(`/api/organizations/${slug}/teams`, {
|
|
1258
|
+
name: options.name,
|
|
1259
|
+
description: options.description
|
|
1260
|
+
});
|
|
1261
|
+
console.log(chalk5.green(`\u2713 Team "${team.name}" created in @${slug}`));
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1264
|
+
process.exit(1);
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
orgs.command("team-members").description("List team members").argument("<slug>", "Organization slug").argument("<team>", "Team name").action(async (slug, team) => {
|
|
1268
|
+
try {
|
|
1269
|
+
const members = await apiGet(
|
|
1270
|
+
`/api/organizations/${slug}/teams/${team}/members`
|
|
1271
|
+
);
|
|
1272
|
+
if (members.length === 0) {
|
|
1273
|
+
console.log(chalk5.yellow("No team members found"));
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
console.log(chalk5.bold(`Team Members (${members.length})`));
|
|
1277
|
+
console.log();
|
|
1278
|
+
for (const member of members) {
|
|
1279
|
+
console.log(chalk5.cyan(`@${member.user?.username || "unknown"}`));
|
|
1280
|
+
}
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1283
|
+
process.exit(1);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
orgs.command("team-add-member").description("Add a member to team").argument("<slug>", "Organization slug").argument("<team>", "Team name").argument("<username>", "Username to add").action(async (slug, team, username) => {
|
|
1287
|
+
try {
|
|
1288
|
+
await apiPost(
|
|
1289
|
+
`/api/organizations/${slug}/teams/${team}/members`,
|
|
1290
|
+
{
|
|
1291
|
+
username
|
|
1292
|
+
}
|
|
1293
|
+
);
|
|
1294
|
+
console.log(chalk5.green(`\u2713 Added @${username} to team ${team}`));
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1297
|
+
process.exit(1);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
orgs.command("team-remove-member").description("Remove a member from team").argument("<slug>", "Organization slug").argument("<team>", "Team name").argument("<username>", "Username to remove").action(async (slug, team, username) => {
|
|
1301
|
+
try {
|
|
1302
|
+
await apiDelete(
|
|
1303
|
+
`/api/organizations/${slug}/teams/${team}/members/${username}`
|
|
1304
|
+
);
|
|
1305
|
+
console.log(chalk5.green(`\u2713 Removed @${username} from team ${team}`));
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1308
|
+
process.exit(1);
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
return orgs;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// src/commands/user.ts
|
|
1315
|
+
import { Command as Command6 } from "commander";
|
|
1316
|
+
import chalk6 from "chalk";
|
|
1317
|
+
function createUserCommand() {
|
|
1318
|
+
const user = new Command6("user").description("Manage user profile and settings");
|
|
1319
|
+
user.command("profile").description("View user profile").argument("[username]", "Username (defaults to current user)").action(async (username) => {
|
|
1320
|
+
try {
|
|
1321
|
+
let profile;
|
|
1322
|
+
if (username) {
|
|
1323
|
+
profile = await apiGet(`/api/users/${username}`);
|
|
1324
|
+
} else {
|
|
1325
|
+
profile = await apiGet("/api/users/me");
|
|
1326
|
+
}
|
|
1327
|
+
console.log(chalk6.bold(`@${profile.username}`));
|
|
1328
|
+
if (profile.name) {
|
|
1329
|
+
console.log(chalk6.dim(profile.name));
|
|
1330
|
+
}
|
|
1331
|
+
console.log();
|
|
1332
|
+
if (profile.bio) {
|
|
1333
|
+
console.log(profile.bio);
|
|
1334
|
+
console.log();
|
|
1335
|
+
}
|
|
1336
|
+
if (profile.location) {
|
|
1337
|
+
console.log(chalk6.dim(`\u{1F4CD} ${profile.location}`));
|
|
1338
|
+
}
|
|
1339
|
+
if (profile.website) {
|
|
1340
|
+
console.log(chalk6.dim(`\u{1F517} ${profile.website}`));
|
|
1341
|
+
}
|
|
1342
|
+
if (profile.company) {
|
|
1343
|
+
console.log(chalk6.dim(`\u{1F3E2} ${profile.company}`));
|
|
1344
|
+
}
|
|
1345
|
+
if (profile.pronouns) {
|
|
1346
|
+
console.log(chalk6.dim(`Pronouns: ${profile.pronouns}`));
|
|
1347
|
+
}
|
|
1348
|
+
if (profile.gitEmail) {
|
|
1349
|
+
console.log(chalk6.dim(`Git Email: ${profile.gitEmail}`));
|
|
1350
|
+
}
|
|
1351
|
+
console.log();
|
|
1352
|
+
console.log(chalk6.dim(`User type: ${profile.userType || "human"}`));
|
|
1353
|
+
console.log(chalk6.dim(`Joined: ${new Date(profile.createdAt).toLocaleDateString()}`));
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1356
|
+
process.exit(1);
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
user.command("edit").description("Edit your profile").option("--name <name>", "Display name").option("--bio <bio>", "Bio/about").option("--location <location>", "Location").option("--website <url>", "Website URL").option("--pronouns <pronouns>", "Pronouns").option("--company <company>", "Company name").option("--git-email <email>", "Git commit email").action(async (options) => {
|
|
1360
|
+
try {
|
|
1361
|
+
const updates = {};
|
|
1362
|
+
if (options.name !== void 0) updates.name = options.name;
|
|
1363
|
+
if (options.bio !== void 0) updates.bio = options.bio;
|
|
1364
|
+
if (options.location !== void 0) updates.location = options.location;
|
|
1365
|
+
if (options.website !== void 0) updates.website = options.website;
|
|
1366
|
+
if (options.pronouns !== void 0) updates.pronouns = options.pronouns;
|
|
1367
|
+
if (options.company !== void 0) updates.company = options.company;
|
|
1368
|
+
if (options.gitEmail !== void 0) updates.gitEmail = options.gitEmail;
|
|
1369
|
+
if (Object.keys(updates).length === 0) {
|
|
1370
|
+
console.log(
|
|
1371
|
+
chalk6.yellow(
|
|
1372
|
+
"No updates specified. Use --name, --bio, --location, --website, --pronouns, --company, or --git-email"
|
|
1373
|
+
)
|
|
1374
|
+
);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
await apiPatch("/api/users/me", updates);
|
|
1378
|
+
console.log(chalk6.green("\u2713 Profile updated successfully"));
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1381
|
+
process.exit(1);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
user.command("search").description("Search for users").argument("<query>", "Search query").option("--limit <number>", "Limit results", "10").action(async (query, options) => {
|
|
1385
|
+
try {
|
|
1386
|
+
const results = await apiGet(
|
|
1387
|
+
`/api/search?q=${encodeURIComponent(query)}&type=users&limit=${options.limit}`
|
|
1388
|
+
);
|
|
1389
|
+
const users = results.users || [];
|
|
1390
|
+
if (users.length === 0) {
|
|
1391
|
+
console.log(chalk6.yellow("No users found"));
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
console.log(chalk6.bold(`Users (${users.length})`));
|
|
1395
|
+
console.log();
|
|
1396
|
+
for (const u of users) {
|
|
1397
|
+
console.log(chalk6.cyan(`@${u.username}`));
|
|
1398
|
+
if (u.name) {
|
|
1399
|
+
console.log(chalk6.dim(` ${u.name}`));
|
|
1400
|
+
}
|
|
1401
|
+
if (u.bio) {
|
|
1402
|
+
const shortBio = u.bio.length > 60 ? u.bio.substring(0, 60) + "..." : u.bio;
|
|
1403
|
+
console.log(chalk6.dim(` ${shortBio}`));
|
|
1404
|
+
}
|
|
1405
|
+
console.log();
|
|
1406
|
+
}
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1409
|
+
process.exit(1);
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
user.command("repos").description("List user repositories").argument("[username]", "Username (defaults to current user)").action(async (username) => {
|
|
1413
|
+
try {
|
|
1414
|
+
let targetUsername;
|
|
1415
|
+
if (username) {
|
|
1416
|
+
targetUsername = username;
|
|
1417
|
+
} else {
|
|
1418
|
+
const me = await apiGet("/api/users/me");
|
|
1419
|
+
targetUsername = me.username;
|
|
1420
|
+
}
|
|
1421
|
+
const result = await apiGet(
|
|
1422
|
+
`/api/repositories/user/${targetUsername}`
|
|
1423
|
+
);
|
|
1424
|
+
const repositories = result.repos || [];
|
|
1425
|
+
if (repositories.length === 0) {
|
|
1426
|
+
console.log(chalk6.yellow(`No repositories found for @${targetUsername}`));
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
console.log(chalk6.bold(`@${targetUsername}'s Repositories (${repositories.length})`));
|
|
1430
|
+
console.log();
|
|
1431
|
+
for (const repo of repositories) {
|
|
1432
|
+
const visibility = repo.visibility === "private" ? "\u{1F512}" : "\u{1F310}";
|
|
1433
|
+
console.log(`${visibility} ${chalk6.cyan(repo.name)}`);
|
|
1434
|
+
if (repo.description) {
|
|
1435
|
+
console.log(chalk6.dim(` ${repo.description}`));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
} catch (error) {
|
|
1439
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1440
|
+
process.exit(1);
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
return user;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
614
1446
|
// src/index.ts
|
|
615
1447
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
616
1448
|
var __dirname2 = dirname(__filename2);
|
|
617
1449
|
var packageJson = JSON.parse(
|
|
618
1450
|
readFileSync(join2(__dirname2, "../package.json"), "utf-8")
|
|
619
1451
|
);
|
|
620
|
-
var program = new
|
|
1452
|
+
var program = new Command7();
|
|
621
1453
|
program.name("forge").description("CLI tool for Kernelius Forge - the agent-native Git platform").version(packageJson.version);
|
|
622
1454
|
program.addCommand(createAuthCommand());
|
|
623
1455
|
program.addCommand(createReposCommand());
|
|
624
1456
|
program.addCommand(createIssuesCommand());
|
|
625
1457
|
program.addCommand(createPrsCommand());
|
|
1458
|
+
program.addCommand(createOrgsCommand());
|
|
1459
|
+
program.addCommand(createUserCommand());
|
|
626
1460
|
program.parse();
|