@kernelius/forge-cli 0.1.4 → 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 +55 -0
- package/dist/index.js +755 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,61 @@ 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
|
+
|
|
8
63
|
## [0.1.4] - 2026-02-01
|
|
9
64
|
|
|
10
65
|
### Added
|
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() {
|
|
@@ -404,6 +407,121 @@ function createReposCommand() {
|
|
|
404
407
|
process.exit(1);
|
|
405
408
|
}
|
|
406
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
|
+
});
|
|
407
525
|
return repos;
|
|
408
526
|
}
|
|
409
527
|
function parseRepoArg(arg) {
|
|
@@ -531,6 +649,143 @@ function createIssuesCommand() {
|
|
|
531
649
|
process.exit(1);
|
|
532
650
|
}
|
|
533
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
|
+
});
|
|
534
789
|
return issues;
|
|
535
790
|
}
|
|
536
791
|
function parseRepoArg2(arg) {
|
|
@@ -680,6 +935,187 @@ function createPrsCommand() {
|
|
|
680
935
|
process.exit(1);
|
|
681
936
|
}
|
|
682
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
|
+
});
|
|
683
1119
|
return prs;
|
|
684
1120
|
}
|
|
685
1121
|
function parseRepoArg3(arg) {
|
|
@@ -692,16 +1128,333 @@ function parseRepoArg3(arg) {
|
|
|
692
1128
|
return [match[1], match[2]];
|
|
693
1129
|
}
|
|
694
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
|
+
|
|
695
1446
|
// src/index.ts
|
|
696
1447
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
697
1448
|
var __dirname2 = dirname(__filename2);
|
|
698
1449
|
var packageJson = JSON.parse(
|
|
699
1450
|
readFileSync(join2(__dirname2, "../package.json"), "utf-8")
|
|
700
1451
|
);
|
|
701
|
-
var program = new
|
|
1452
|
+
var program = new Command7();
|
|
702
1453
|
program.name("forge").description("CLI tool for Kernelius Forge - the agent-native Git platform").version(packageJson.version);
|
|
703
1454
|
program.addCommand(createAuthCommand());
|
|
704
1455
|
program.addCommand(createReposCommand());
|
|
705
1456
|
program.addCommand(createIssuesCommand());
|
|
706
1457
|
program.addCommand(createPrsCommand());
|
|
1458
|
+
program.addCommand(createOrgsCommand());
|
|
1459
|
+
program.addCommand(createUserCommand());
|
|
707
1460
|
program.parse();
|