@kernelius/forge-cli 0.3.3 → 0.4.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/SKILL.md +163 -1
- package/dist/index.js +272 -14
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -40,7 +40,18 @@ Before using any forge commands, ensure the user is authenticated:
|
|
|
40
40
|
forge auth whoami
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
If not authenticated, the user
|
|
43
|
+
If not authenticated, the user can either:
|
|
44
|
+
|
|
45
|
+
**Option 1: Create a new account (signup)**
|
|
46
|
+
```bash
|
|
47
|
+
forge auth signup \
|
|
48
|
+
--username johndoe \
|
|
49
|
+
--email john@example.com \
|
|
50
|
+
--name "John Doe" \
|
|
51
|
+
--password secret
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Option 2: Login with existing API key**
|
|
44
55
|
1. Get an agent API key from Forge at `/settings/agents`
|
|
45
56
|
2. Login with: `forge auth login --token forge_agent_xxx...`
|
|
46
57
|
|
|
@@ -106,6 +117,24 @@ forge issues close --repo @owner/repo --number 42
|
|
|
106
117
|
forge issues comment --repo @owner/repo --number 42 --body "This is fixed now"
|
|
107
118
|
```
|
|
108
119
|
|
|
120
|
+
**List comments on an issue:**
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
forge issues comments --repo @owner/repo --number 42
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Reopen an issue:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
forge issues reopen --repo @owner/repo --number 42
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Edit an issue:**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
forge issues edit --repo @owner/repo --number 42 --title "New title" --body "Updated description"
|
|
136
|
+
```
|
|
137
|
+
|
|
109
138
|
## Pull Requests
|
|
110
139
|
|
|
111
140
|
**List pull requests:**
|
|
@@ -148,6 +177,139 @@ forge prs close --repo @owner/repo --number 10
|
|
|
148
177
|
forge prs comment --repo @owner/repo --number 10 --body "Looks good to me!"
|
|
149
178
|
```
|
|
150
179
|
|
|
180
|
+
**Submit a review:**
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Approve a PR
|
|
184
|
+
forge prs review --repo @owner/repo --number 10 --state approve --body "LGTM!"
|
|
185
|
+
|
|
186
|
+
# Request changes
|
|
187
|
+
forge prs review --repo @owner/repo --number 10 --state request_changes --body "Please fix X"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**View PR diff:**
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
forge prs diff --repo @owner/repo --number 10
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**List commits in a PR:**
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
forge prs commits --repo @owner/repo --number 10
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Templates
|
|
203
|
+
|
|
204
|
+
**List available templates:**
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
forge templates list
|
|
208
|
+
# Filter by organization type:
|
|
209
|
+
forge templates list --org-type healthcare
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**View template details:**
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
forge templates view patient-record
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Create repository from template:**
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
forge repos create --name my-patient-repo --template patient-record --visibility private
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Webhooks
|
|
225
|
+
|
|
226
|
+
Webhooks allow external systems (like OpenClaw) to receive notifications when events occur in Forge repositories.
|
|
227
|
+
|
|
228
|
+
**List webhooks:**
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
forge webhooks list --repo @owner/repo
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Create a webhook:**
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
forge webhooks create --repo @owner/repo \
|
|
238
|
+
--url "https://your-server.com/hooks/forge" \
|
|
239
|
+
--events "issue.created,issue.commented,pr.created,pr.merged" \
|
|
240
|
+
--name "My Integration"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Test a webhook:**
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
forge webhooks test --repo @owner/repo --id <webhook-id>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**View recent deliveries:**
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
forge webhooks deliveries --repo @owner/repo --id <webhook-id>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**List available events:**
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
forge webhooks events
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Update a webhook:**
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
forge webhooks update --repo @owner/repo --id <webhook-id> --events "issue.created,pr.created" --active
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Delete a webhook:**
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
forge webhooks delete --repo @owner/repo --id <webhook-id>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Regenerate secret:**
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
forge webhooks regenerate-secret --repo @owner/repo --id <webhook-id>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Webhook Events
|
|
280
|
+
|
|
281
|
+
| Event | Description |
|
|
282
|
+
|-------|-------------|
|
|
283
|
+
| `issue.created` | New issue opened |
|
|
284
|
+
| `issue.updated` | Issue title/body changed |
|
|
285
|
+
| `issue.closed` | Issue closed |
|
|
286
|
+
| `issue.reopened` | Issue reopened |
|
|
287
|
+
| `issue.commented` | Comment added to issue |
|
|
288
|
+
| `pr.created` | Pull request opened |
|
|
289
|
+
| `pr.updated` | PR title/body changed |
|
|
290
|
+
| `pr.merged` | Pull request merged |
|
|
291
|
+
| `pr.closed` | PR closed without merging |
|
|
292
|
+
| `pr.review_requested` | Review requested on PR |
|
|
293
|
+
| `pr.reviewed` | Review submitted |
|
|
294
|
+
| `pr.commented` | Comment on PR |
|
|
295
|
+
| `push` | Commits pushed |
|
|
296
|
+
|
|
297
|
+
### Verifying Webhook Signatures
|
|
298
|
+
|
|
299
|
+
Forge signs webhook payloads with HMAC-SHA256. Verify using the `X-Forge-Signature` header:
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
const crypto = require('crypto');
|
|
303
|
+
|
|
304
|
+
function verifySignature(payload, signature, secret) {
|
|
305
|
+
const expected = 'sha256=' + crypto
|
|
306
|
+
.createHmac('sha256', secret)
|
|
307
|
+
.update(JSON.stringify(payload))
|
|
308
|
+
.digest('hex');
|
|
309
|
+
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
151
313
|
## Important Notes
|
|
152
314
|
|
|
153
315
|
- **Always specify `--repo @owner/repo`** when working with issues or PRs
|
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 Command9 } from "commander";
|
|
5
5
|
import { readFileSync } from "fs";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { dirname, join as join2 } from "path";
|
|
@@ -1049,9 +1049,10 @@ function createIssuesCommand() {
|
|
|
1049
1049
|
issues.command("labels").description("List repository labels").requiredOption("--repo <repo>", "Repository (@owner/name)").action(async (options) => {
|
|
1050
1050
|
try {
|
|
1051
1051
|
const [ownerIdentifier, name] = parseRepoArg2(options.repo);
|
|
1052
|
-
const
|
|
1052
|
+
const result = await apiGet(
|
|
1053
1053
|
`/api/repositories/${ownerIdentifier}/${name}/labels`
|
|
1054
1054
|
);
|
|
1055
|
+
const labels = result.labels || [];
|
|
1055
1056
|
if (labels.length === 0) {
|
|
1056
1057
|
console.log(chalk4.yellow("No labels found"));
|
|
1057
1058
|
return;
|
|
@@ -1369,7 +1370,8 @@ function createPrsCommand() {
|
|
|
1369
1370
|
const pr = await apiGet(
|
|
1370
1371
|
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
1371
1372
|
);
|
|
1372
|
-
const
|
|
1373
|
+
const result = await apiGet(`/api/pulls/${pr.id}/reviews`);
|
|
1374
|
+
const reviews = result.reviews || [];
|
|
1373
1375
|
if (reviews.length === 0) {
|
|
1374
1376
|
console.log(chalk5.yellow("No reviews found"));
|
|
1375
1377
|
return;
|
|
@@ -1403,7 +1405,8 @@ function createPrsCommand() {
|
|
|
1403
1405
|
const pr = await apiGet(
|
|
1404
1406
|
`/api/repositories/${ownerIdentifier}/${name}/pulls/${options.number}`
|
|
1405
1407
|
);
|
|
1406
|
-
const
|
|
1408
|
+
const result = await apiGet(`/api/pulls/${pr.id}/commits`);
|
|
1409
|
+
const commits = result.commits || [];
|
|
1407
1410
|
if (commits.length === 0) {
|
|
1408
1411
|
console.log(chalk5.yellow("No commits found"));
|
|
1409
1412
|
return;
|
|
@@ -1546,7 +1549,8 @@ function createOrgsCommand() {
|
|
|
1546
1549
|
});
|
|
1547
1550
|
orgs.command("members").description("List organization members").argument("<slug>", "Organization slug").action(async (slug) => {
|
|
1548
1551
|
try {
|
|
1549
|
-
const
|
|
1552
|
+
const result = await apiGet(`/api/orgs/${slug}/members`);
|
|
1553
|
+
const members = result.members || [];
|
|
1550
1554
|
if (members.length === 0) {
|
|
1551
1555
|
console.log(chalk6.yellow("No members found"));
|
|
1552
1556
|
return;
|
|
@@ -1564,7 +1568,7 @@ function createOrgsCommand() {
|
|
|
1564
1568
|
});
|
|
1565
1569
|
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) => {
|
|
1566
1570
|
try {
|
|
1567
|
-
await apiPost(`/api/
|
|
1571
|
+
await apiPost(`/api/orgs/${slug}/members`, {
|
|
1568
1572
|
username,
|
|
1569
1573
|
role: options.role
|
|
1570
1574
|
});
|
|
@@ -1576,7 +1580,7 @@ function createOrgsCommand() {
|
|
|
1576
1580
|
});
|
|
1577
1581
|
orgs.command("member-remove").description("Remove a member from organization").argument("<slug>", "Organization slug").argument("<username>", "Username to remove").action(async (slug, username) => {
|
|
1578
1582
|
try {
|
|
1579
|
-
await apiDelete(`/api/
|
|
1583
|
+
await apiDelete(`/api/orgs/${slug}/members/${username}`);
|
|
1580
1584
|
console.log(chalk6.green(`\u2713 Removed @${username} from @${slug}`));
|
|
1581
1585
|
} catch (error) {
|
|
1582
1586
|
console.error(chalk6.red(`Error: ${error.message}`));
|
|
@@ -1585,7 +1589,8 @@ function createOrgsCommand() {
|
|
|
1585
1589
|
});
|
|
1586
1590
|
orgs.command("teams").description("List organization teams").argument("<slug>", "Organization slug").action(async (slug) => {
|
|
1587
1591
|
try {
|
|
1588
|
-
const
|
|
1592
|
+
const result = await apiGet(`/api/orgs/${slug}/teams`);
|
|
1593
|
+
const teams = result.teams || [];
|
|
1589
1594
|
if (teams.length === 0) {
|
|
1590
1595
|
console.log(chalk6.yellow("No teams found"));
|
|
1591
1596
|
return;
|
|
@@ -1605,7 +1610,7 @@ function createOrgsCommand() {
|
|
|
1605
1610
|
});
|
|
1606
1611
|
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) => {
|
|
1607
1612
|
try {
|
|
1608
|
-
const team = await apiPost(`/api/
|
|
1613
|
+
const team = await apiPost(`/api/orgs/${slug}/teams`, {
|
|
1609
1614
|
name: options.name,
|
|
1610
1615
|
description: options.description
|
|
1611
1616
|
});
|
|
@@ -1617,9 +1622,10 @@ function createOrgsCommand() {
|
|
|
1617
1622
|
});
|
|
1618
1623
|
orgs.command("team-members").description("List team members").argument("<slug>", "Organization slug").argument("<team>", "Team name").action(async (slug, team) => {
|
|
1619
1624
|
try {
|
|
1620
|
-
const
|
|
1621
|
-
`/api/
|
|
1625
|
+
const result = await apiGet(
|
|
1626
|
+
`/api/orgs/${slug}/teams/${team}/members`
|
|
1622
1627
|
);
|
|
1628
|
+
const members = result.members || [];
|
|
1623
1629
|
if (members.length === 0) {
|
|
1624
1630
|
console.log(chalk6.yellow("No team members found"));
|
|
1625
1631
|
return;
|
|
@@ -1637,7 +1643,7 @@ function createOrgsCommand() {
|
|
|
1637
1643
|
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) => {
|
|
1638
1644
|
try {
|
|
1639
1645
|
await apiPost(
|
|
1640
|
-
`/api/
|
|
1646
|
+
`/api/orgs/${slug}/teams/${team}/members`,
|
|
1641
1647
|
{
|
|
1642
1648
|
username
|
|
1643
1649
|
}
|
|
@@ -1651,7 +1657,7 @@ function createOrgsCommand() {
|
|
|
1651
1657
|
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) => {
|
|
1652
1658
|
try {
|
|
1653
1659
|
await apiDelete(
|
|
1654
|
-
`/api/
|
|
1660
|
+
`/api/orgs/${slug}/teams/${team}/members/${username}`
|
|
1655
1661
|
);
|
|
1656
1662
|
console.log(chalk6.green(`\u2713 Removed @${username} from team ${team}`));
|
|
1657
1663
|
} catch (error) {
|
|
@@ -1794,13 +1800,264 @@ function createUserCommand() {
|
|
|
1794
1800
|
return user;
|
|
1795
1801
|
}
|
|
1796
1802
|
|
|
1803
|
+
// src/commands/webhooks.ts
|
|
1804
|
+
import { Command as Command8 } from "commander";
|
|
1805
|
+
import chalk8 from "chalk";
|
|
1806
|
+
function createWebhooksCommand() {
|
|
1807
|
+
const webhooks = new Command8("webhooks").alias("webhook").description("Manage repository webhooks");
|
|
1808
|
+
webhooks.command("list").description("List webhooks for a repository").requiredOption("--repo <repo>", "Repository (@owner/name)").action(async (options) => {
|
|
1809
|
+
try {
|
|
1810
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
1811
|
+
const result = await apiGet(
|
|
1812
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks`
|
|
1813
|
+
);
|
|
1814
|
+
const webhookList = result.webhooks || [];
|
|
1815
|
+
if (webhookList.length === 0) {
|
|
1816
|
+
console.log(chalk8.yellow("No webhooks found"));
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
console.log(chalk8.bold(`Webhooks (${webhookList.length})`));
|
|
1820
|
+
console.log();
|
|
1821
|
+
for (const webhook of webhookList) {
|
|
1822
|
+
const statusIcon = webhook.active ? "\u{1F7E2}" : "\u26AA";
|
|
1823
|
+
console.log(`${statusIcon} ${chalk8.cyan(webhook.name || webhook.id)}`);
|
|
1824
|
+
console.log(chalk8.dim(` URL: ${webhook.url}`));
|
|
1825
|
+
console.log(chalk8.dim(` Events: ${(webhook.events || []).join(", ")}`));
|
|
1826
|
+
if (webhook.lastTriggeredAt) {
|
|
1827
|
+
console.log(chalk8.dim(` Last triggered: ${new Date(webhook.lastTriggeredAt).toLocaleString()}`));
|
|
1828
|
+
}
|
|
1829
|
+
if (webhook.failureCount > 0) {
|
|
1830
|
+
console.log(chalk8.yellow(` Failures: ${webhook.failureCount}`));
|
|
1831
|
+
}
|
|
1832
|
+
console.log();
|
|
1833
|
+
}
|
|
1834
|
+
} catch (error) {
|
|
1835
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
1836
|
+
process.exit(1);
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
webhooks.command("view").description("View webhook details").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--id <id>", "Webhook ID").action(async (options) => {
|
|
1840
|
+
try {
|
|
1841
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
1842
|
+
const webhook = await apiGet(
|
|
1843
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks/${options.id}`
|
|
1844
|
+
);
|
|
1845
|
+
const statusIcon = webhook.active ? "\u{1F7E2} Active" : "\u26AA Inactive";
|
|
1846
|
+
console.log(chalk8.bold(webhook.name || `Webhook ${webhook.id}`));
|
|
1847
|
+
console.log(chalk8.dim(`Status: ${statusIcon}`));
|
|
1848
|
+
console.log();
|
|
1849
|
+
console.log(`URL: ${chalk8.cyan(webhook.url)}`);
|
|
1850
|
+
console.log(`Secret: ${chalk8.dim(webhook.secret)}`);
|
|
1851
|
+
console.log();
|
|
1852
|
+
console.log(chalk8.bold("Events:"));
|
|
1853
|
+
for (const event of webhook.events || []) {
|
|
1854
|
+
console.log(` \u2022 ${event}`);
|
|
1855
|
+
}
|
|
1856
|
+
console.log();
|
|
1857
|
+
if (webhook.description) {
|
|
1858
|
+
console.log(`Description: ${webhook.description}`);
|
|
1859
|
+
}
|
|
1860
|
+
console.log(chalk8.dim(`Created: ${new Date(webhook.createdAt).toLocaleString()}`));
|
|
1861
|
+
if (webhook.lastTriggeredAt) {
|
|
1862
|
+
console.log(chalk8.dim(`Last triggered: ${new Date(webhook.lastTriggeredAt).toLocaleString()}`));
|
|
1863
|
+
}
|
|
1864
|
+
if (webhook.lastSuccessAt) {
|
|
1865
|
+
console.log(chalk8.green(`Last success: ${new Date(webhook.lastSuccessAt).toLocaleString()}`));
|
|
1866
|
+
}
|
|
1867
|
+
if (webhook.lastFailureAt) {
|
|
1868
|
+
console.log(chalk8.yellow(`Last failure: ${new Date(webhook.lastFailureAt).toLocaleString()}`));
|
|
1869
|
+
}
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
1872
|
+
process.exit(1);
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
webhooks.command("create").description("Create a new webhook").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--url <url>", "Webhook URL to receive events").requiredOption("--events <events>", "Comma-separated list of events to listen for").option("--name <name>", "Friendly name for the webhook").option("--description <desc>", "Description of the webhook").option("--inactive", "Create webhook as inactive").action(async (options) => {
|
|
1876
|
+
try {
|
|
1877
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
1878
|
+
const events = options.events.split(",").map((e) => e.trim());
|
|
1879
|
+
const webhook = await apiPost(
|
|
1880
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks`,
|
|
1881
|
+
{
|
|
1882
|
+
url: options.url,
|
|
1883
|
+
events,
|
|
1884
|
+
webhookName: options.name,
|
|
1885
|
+
description: options.description,
|
|
1886
|
+
active: !options.inactive
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
console.log(chalk8.green("\u2713 Webhook created successfully"));
|
|
1890
|
+
console.log();
|
|
1891
|
+
console.log(`ID: ${chalk8.cyan(webhook.id)}`);
|
|
1892
|
+
console.log(`URL: ${webhook.url}`);
|
|
1893
|
+
console.log(`Events: ${events.join(", ")}`);
|
|
1894
|
+
console.log();
|
|
1895
|
+
console.log(chalk8.bold.yellow("\u26A0\uFE0F Save this secret - it will only be shown once:"));
|
|
1896
|
+
console.log(chalk8.cyan(webhook.secretFull));
|
|
1897
|
+
console.log();
|
|
1898
|
+
console.log(chalk8.dim("Use this secret to verify webhook signatures (X-Forge-Signature header)"));
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
1901
|
+
process.exit(1);
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
webhooks.command("update").description("Update a webhook").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--id <id>", "Webhook ID").option("--url <url>", "New URL").option("--events <events>", "New comma-separated list of events").option("--name <name>", "New name").option("--description <desc>", "New description").option("--active", "Enable webhook").option("--inactive", "Disable webhook").action(async (options) => {
|
|
1905
|
+
try {
|
|
1906
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
1907
|
+
const updates = {};
|
|
1908
|
+
if (options.url) updates.url = options.url;
|
|
1909
|
+
if (options.events) updates.events = options.events.split(",").map((e) => e.trim());
|
|
1910
|
+
if (options.name !== void 0) updates.name = options.name;
|
|
1911
|
+
if (options.description !== void 0) updates.description = options.description;
|
|
1912
|
+
if (options.active) updates.active = true;
|
|
1913
|
+
if (options.inactive) updates.active = false;
|
|
1914
|
+
if (Object.keys(updates).length === 0) {
|
|
1915
|
+
console.log(chalk8.yellow("No updates specified"));
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
await apiPatch(
|
|
1919
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks/${options.id}`,
|
|
1920
|
+
updates
|
|
1921
|
+
);
|
|
1922
|
+
console.log(chalk8.green("\u2713 Webhook updated successfully"));
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
1925
|
+
process.exit(1);
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
webhooks.command("delete").description("Delete a webhook").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--id <id>", "Webhook ID").action(async (options) => {
|
|
1929
|
+
try {
|
|
1930
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
1931
|
+
await apiDelete(
|
|
1932
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks/${options.id}`
|
|
1933
|
+
);
|
|
1934
|
+
console.log(chalk8.green("\u2713 Webhook deleted successfully"));
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
1937
|
+
process.exit(1);
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
webhooks.command("test").description("Send a test ping to a webhook").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--id <id>", "Webhook ID").action(async (options) => {
|
|
1941
|
+
try {
|
|
1942
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
1943
|
+
console.log(chalk8.dim("Sending test ping..."));
|
|
1944
|
+
const result = await apiPost(
|
|
1945
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks/${options.id}/test`,
|
|
1946
|
+
{}
|
|
1947
|
+
);
|
|
1948
|
+
if (result.success) {
|
|
1949
|
+
console.log(chalk8.green("\u2713 Webhook test successful"));
|
|
1950
|
+
console.log(chalk8.dim(` Status: ${result.delivery.statusCode}`));
|
|
1951
|
+
console.log(chalk8.dim(` Duration: ${result.delivery.duration}ms`));
|
|
1952
|
+
} else {
|
|
1953
|
+
console.log(chalk8.red("\u2717 Webhook test failed"));
|
|
1954
|
+
if (result.delivery.statusCode) {
|
|
1955
|
+
console.log(chalk8.dim(` Status: ${result.delivery.statusCode}`));
|
|
1956
|
+
}
|
|
1957
|
+
if (result.delivery.error) {
|
|
1958
|
+
console.log(chalk8.dim(` Error: ${result.delivery.error}`));
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
1963
|
+
process.exit(1);
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
webhooks.command("deliveries").description("List recent webhook deliveries").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--id <id>", "Webhook ID").option("--limit <n>", "Number of deliveries to show", "20").action(async (options) => {
|
|
1967
|
+
try {
|
|
1968
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
1969
|
+
const result = await apiGet(
|
|
1970
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks/${options.id}/deliveries?limit=${options.limit}`
|
|
1971
|
+
);
|
|
1972
|
+
const deliveries = result.deliveries || [];
|
|
1973
|
+
if (deliveries.length === 0) {
|
|
1974
|
+
console.log(chalk8.yellow("No deliveries found"));
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
console.log(chalk8.bold(`Recent Deliveries (${deliveries.length})`));
|
|
1978
|
+
console.log();
|
|
1979
|
+
for (const delivery of deliveries) {
|
|
1980
|
+
const statusIcon = delivery.success ? "\u2713" : "\u2717";
|
|
1981
|
+
const statusColor = delivery.success ? chalk8.green : chalk8.red;
|
|
1982
|
+
console.log(statusColor(`${statusIcon} ${delivery.event}`));
|
|
1983
|
+
console.log(chalk8.dim(` ${new Date(delivery.deliveredAt).toLocaleString()}`));
|
|
1984
|
+
if (delivery.statusCode) {
|
|
1985
|
+
console.log(chalk8.dim(` Status: ${delivery.statusCode} \xB7 ${delivery.duration}ms`));
|
|
1986
|
+
}
|
|
1987
|
+
if (delivery.error) {
|
|
1988
|
+
console.log(chalk8.yellow(` Error: ${delivery.error}`));
|
|
1989
|
+
}
|
|
1990
|
+
console.log();
|
|
1991
|
+
}
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
1994
|
+
process.exit(1);
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
webhooks.command("regenerate-secret").description("Regenerate webhook secret").requiredOption("--repo <repo>", "Repository (@owner/name)").requiredOption("--id <id>", "Webhook ID").action(async (options) => {
|
|
1998
|
+
try {
|
|
1999
|
+
const [ownerIdentifier, name] = parseRepoArg4(options.repo);
|
|
2000
|
+
const result = await apiPost(
|
|
2001
|
+
`/api/repositories/${ownerIdentifier}/${name}/webhooks/${options.id}/regenerate-secret`,
|
|
2002
|
+
{}
|
|
2003
|
+
);
|
|
2004
|
+
console.log(chalk8.green("\u2713 Secret regenerated successfully"));
|
|
2005
|
+
console.log();
|
|
2006
|
+
console.log(chalk8.bold.yellow("\u26A0\uFE0F Save this new secret - it will only be shown once:"));
|
|
2007
|
+
console.log(chalk8.cyan(result.secretFull));
|
|
2008
|
+
console.log();
|
|
2009
|
+
console.log(chalk8.dim("Update your webhook receiver to use this new secret"));
|
|
2010
|
+
} catch (error) {
|
|
2011
|
+
console.error(chalk8.red(`Error: ${error.message}`));
|
|
2012
|
+
process.exit(1);
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
webhooks.command("events").description("List available webhook event types").action(() => {
|
|
2016
|
+
console.log(chalk8.bold("Available Webhook Events"));
|
|
2017
|
+
console.log();
|
|
2018
|
+
const events = [
|
|
2019
|
+
{ name: "issue.created", desc: "New issue opened" },
|
|
2020
|
+
{ name: "issue.updated", desc: "Issue title or body changed" },
|
|
2021
|
+
{ name: "issue.closed", desc: "Issue closed" },
|
|
2022
|
+
{ name: "issue.reopened", desc: "Issue reopened" },
|
|
2023
|
+
{ name: "issue.commented", desc: "New comment on issue" },
|
|
2024
|
+
{ name: "pr.created", desc: "Pull request opened" },
|
|
2025
|
+
{ name: "pr.updated", desc: "PR title/body changed" },
|
|
2026
|
+
{ name: "pr.merged", desc: "PR merged" },
|
|
2027
|
+
{ name: "pr.closed", desc: "PR closed without merging" },
|
|
2028
|
+
{ name: "pr.review_requested", desc: "Review requested on PR" },
|
|
2029
|
+
{ name: "pr.reviewed", desc: "PR review submitted" },
|
|
2030
|
+
{ name: "pr.commented", desc: "New comment on PR" },
|
|
2031
|
+
{ name: "push", desc: "Commits pushed to branch" },
|
|
2032
|
+
{ name: "repo.created", desc: "Repository created" },
|
|
2033
|
+
{ name: "repo.deleted", desc: "Repository deleted" }
|
|
2034
|
+
];
|
|
2035
|
+
for (const event of events) {
|
|
2036
|
+
console.log(` ${chalk8.cyan(event.name.padEnd(22))} ${chalk8.dim(event.desc)}`);
|
|
2037
|
+
}
|
|
2038
|
+
console.log();
|
|
2039
|
+
console.log(chalk8.dim("Use comma-separated list with --events flag:"));
|
|
2040
|
+
console.log(chalk8.dim(" forge webhooks create --events issue.created,pr.created ..."));
|
|
2041
|
+
});
|
|
2042
|
+
return webhooks;
|
|
2043
|
+
}
|
|
2044
|
+
function parseRepoArg4(arg) {
|
|
2045
|
+
const match = arg.match(/^@?([^/]+)\/(.+)$/);
|
|
2046
|
+
if (!match) {
|
|
2047
|
+
throw new Error(
|
|
2048
|
+
`Invalid repository format: ${arg}. Expected format: @owner/name or owner/name`
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
return [match[1], match[2]];
|
|
2052
|
+
}
|
|
2053
|
+
|
|
1797
2054
|
// src/index.ts
|
|
1798
2055
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
1799
2056
|
var __dirname2 = dirname(__filename2);
|
|
1800
2057
|
var packageJson = JSON.parse(
|
|
1801
2058
|
readFileSync(join2(__dirname2, "../package.json"), "utf-8")
|
|
1802
2059
|
);
|
|
1803
|
-
var program = new
|
|
2060
|
+
var program = new Command9();
|
|
1804
2061
|
program.name("forge").description("CLI tool for Kernelius Forge - the agent-native Git platform").version(packageJson.version);
|
|
1805
2062
|
program.addCommand(createAuthCommand());
|
|
1806
2063
|
program.addCommand(createReposCommand());
|
|
@@ -1809,4 +2066,5 @@ program.addCommand(createPrsCommand());
|
|
|
1809
2066
|
program.addCommand(createOrgsCommand());
|
|
1810
2067
|
program.addCommand(createUserCommand());
|
|
1811
2068
|
program.addCommand(createTemplatesCommand());
|
|
2069
|
+
program.addCommand(createWebhooksCommand());
|
|
1812
2070
|
program.parse();
|