@kernelius/forge-cli 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +89 -0
- package/dist/index.js +254 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ Config is stored at `~/.config/forge/config.json`:
|
|
|
193
193
|
|
|
194
194
|
```json
|
|
195
195
|
{
|
|
196
|
-
"apiUrl": "https://forge.
|
|
196
|
+
"apiUrl": "https://forge-api.kernelius.com",
|
|
197
197
|
"apiKey": "forge_agent_xxx...",
|
|
198
198
|
"agentId": "user-id",
|
|
199
199
|
"agentName": "username"
|
package/SKILL.md
CHANGED
|
@@ -221,6 +221,95 @@ forge templates view patient-record
|
|
|
221
221
|
forge repos create --name my-patient-repo --template patient-record --visibility private
|
|
222
222
|
```
|
|
223
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
|
+
|
|
224
313
|
## Important Notes
|
|
225
314
|
|
|
226
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";
|
|
@@ -1800,13 +1800,264 @@ function createUserCommand() {
|
|
|
1800
1800
|
return user;
|
|
1801
1801
|
}
|
|
1802
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
|
+
|
|
1803
2054
|
// src/index.ts
|
|
1804
2055
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
1805
2056
|
var __dirname2 = dirname(__filename2);
|
|
1806
2057
|
var packageJson = JSON.parse(
|
|
1807
2058
|
readFileSync(join2(__dirname2, "../package.json"), "utf-8")
|
|
1808
2059
|
);
|
|
1809
|
-
var program = new
|
|
2060
|
+
var program = new Command9();
|
|
1810
2061
|
program.name("forge").description("CLI tool for Kernelius Forge - the agent-native Git platform").version(packageJson.version);
|
|
1811
2062
|
program.addCommand(createAuthCommand());
|
|
1812
2063
|
program.addCommand(createReposCommand());
|
|
@@ -1815,4 +2066,5 @@ program.addCommand(createPrsCommand());
|
|
|
1815
2066
|
program.addCommand(createOrgsCommand());
|
|
1816
2067
|
program.addCommand(createUserCommand());
|
|
1817
2068
|
program.addCommand(createTemplatesCommand());
|
|
2069
|
+
program.addCommand(createWebhooksCommand());
|
|
1818
2070
|
program.parse();
|