gitdog 0.0.0-stage → 0.1.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 +76 -2
- package/dist/api-catalog.json +432 -0
- package/dist/api-keys.d.ts +17 -0
- package/dist/api-keys.js +137 -0
- package/dist/auth.d.ts +13 -0
- package/dist/auth.js +243 -0
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.js +8 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +31 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +109 -0
- package/dist/commands.d.ts +9 -0
- package/dist/commands.js +680 -0
- package/dist/projects.d.ts +55 -0
- package/dist/projects.js +355 -0
- package/dist/storage.d.ts +24 -0
- package/dist/storage.js +194 -0
- package/dist/ui.d.ts +19 -0
- package/dist/ui.js +181 -0
- package/package.json +54 -4
package/README.md
CHANGED
|
@@ -1,3 +1,77 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/hyodotdev/gitdog-cli/main/.github/assets/gitdog-logo.png" alt="Gitdog" width="112" height="112">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">Gitdog CLI</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Your GitHub assistant. Now in the terminal.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/gitdog"><img src="https://img.shields.io/npm/v/gitdog?logo=npm&color=CB3837" alt="npm version"></a>
|
|
13
|
+
<a href="https://github.com/hyodotdev/gitdog-cli/actions/workflows/ci.yml"><img src="https://github.com/hyodotdev/gitdog-cli/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
|
|
14
|
+
<a href="https://github.com/hyodotdev/gitdog-cli/actions/workflows/release.yml"><img src="https://github.com/hyodotdev/gitdog-cli/actions/workflows/release.yml/badge.svg" alt="npm release"></a>
|
|
15
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D22.14.0-3c873a?logo=nodedotjs&logoColor=white" alt="Node.js 22.14 or later"></a>
|
|
16
|
+
<a href="https://gitdog.dev"><img src="https://img.shields.io/badge/Gitdog-gitdog.dev-111" alt="Gitdog website"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://raw.githubusercontent.com/hyodotdev/gitdog-cli/main/.github/assets/terminal.svg?v=0.1.1" alt="Gitdog terminal: connect a project, configure automation and review pending actions" width="800">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
Connect repositories, configure Gitdog's issue and discussion assistants, review
|
|
24
|
+
AI actions, and automate project tasks from your terminal or CI.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
Requires **Node.js 22.14+**.
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install --global gitdog
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
gitdog login
|
|
38
|
+
gitdog github install
|
|
39
|
+
gitdog github recover
|
|
40
|
+
gitdog init owner/repo
|
|
41
|
+
gitdog config set requireApproval true
|
|
42
|
+
gitdog config push
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
GitHub App setup is only needed once. A project connects an existing GitHub
|
|
46
|
+
repository to Gitdog.
|
|
47
|
+
|
|
48
|
+
## Everyday commands
|
|
49
|
+
|
|
50
|
+
| Task | Command |
|
|
51
|
+
| ------------------------ | ---------------------------- |
|
|
52
|
+
| List projects | `gitdog projects list` |
|
|
53
|
+
| Read automation settings | `gitdog config get --remote` |
|
|
54
|
+
| Review pending actions | `gitdog tickets pending` |
|
|
55
|
+
| Check code indexing | `gitdog index status` |
|
|
56
|
+
| List team members | `gitdog team members` |
|
|
57
|
+
| Check usage | `gitdog usage summary` |
|
|
58
|
+
|
|
59
|
+
Use `gitdog --help` to explore commands. Add `--json` for scripts.
|
|
60
|
+
|
|
61
|
+
## Use in CI
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
gitdog keys create --name ci
|
|
65
|
+
gitdog --key-file /path/to/key.json projects list --json
|
|
66
|
+
gitdog keys revoke KEY_ID --yes
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`keys create` prints the private key file's path. Keys are project-scoped and
|
|
70
|
+
read-only by default; add `--write` for settings and ticket delete/restore. Keep key
|
|
71
|
+
files out of Git.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
[Command reference](https://github.com/hyodotdev/gitdog-cli/blob/main/docs/REFERENCE.md)
|
|
76
|
+
· [Gitdog](https://gitdog.dev)
|
|
77
|
+
· [Report an issue](https://github.com/hyodotdev/gitdog-cli/issues)
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "admin/mutation:deleteAiLog",
|
|
4
|
+
"kind": "mutation",
|
|
5
|
+
"args": "{\n logId: v.id(\"aiLogs\"),\n }"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"path": "admin/mutation:updateAiLogStatus",
|
|
9
|
+
"kind": "mutation",
|
|
10
|
+
"args": "{\n logId: v.id(\"aiLogs\"),\n status: v.union(\n v.literal(\"success\"),\n v.literal(\"failed\"),\n v.literal(\"pending_approval\"),\n v.literal(\"approved\"),\n v.literal(\"rejected\"),\n v.literal(\"request_changes\"),\n ),\n userFeedback: v.optional(v.string()),\n }"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "admin/paymentMutation:addTokens",
|
|
14
|
+
"kind": "mutation",
|
|
15
|
+
"args": "{\n subscriptionId: v.id(\"subscriptions\"),\n tokensToAdd: v.number(),\n operationId: v.string(),\n adminNote: v.optional(v.string()),\n }"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "admin/paymentMutation:cancelSubscription",
|
|
19
|
+
"kind": "mutation",
|
|
20
|
+
"args": "{\n subscriptionId: v.id(\"subscriptions\"),\n operationId: v.string(),\n adminNote: v.optional(v.string()),\n }"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "admin/paymentMutation:reactivateSubscription",
|
|
24
|
+
"kind": "mutation",
|
|
25
|
+
"args": "{\n subscriptionId: v.id(\"subscriptions\"),\n operationId: v.string(),\n adminNote: v.optional(v.string()),\n }"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "admin/paymentMutation:reconcileGeminiChatUsage",
|
|
29
|
+
"kind": "mutation",
|
|
30
|
+
"args": "{\n reservationKey: v.string(),\n evidenceSource: v.union(\n v.literal(\"google_api_response\"),\n v.literal(\"google_cloud_billing_export\"),\n v.literal(\"google_support_confirmation\"),\n v.literal(\"anthropic_api_response\"),\n v.literal(\"anthropic_usage_report\"),\n v.literal(\"anthropic_support_confirmation\"),\n v.literal(\"openai_api_response\"),\n v.literal(\"openai_usage_report\"),\n v.literal(\"openai_support_confirmation\"),\n),\n evidenceReference: v.string(),\n outcome: v.union(\n v.object({ kind: v.literal(\"known_not_billed\") }),\n v.object({\n kind: v.literal(\"known_billed\"),\n actualInputTokens: v.number(),\n actualOutputTokens: v.number(),\n providerRequestId: v.optional(v.string()),\n }),\n ),\n }"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "admin/paymentMutation:reconcileGeminiEmbeddingUsage",
|
|
34
|
+
"kind": "mutation",
|
|
35
|
+
"args": "{\n reservationKey: v.string(),\n evidenceSource: v.union(\n v.literal(\"google_api_response\"),\n v.literal(\"google_cloud_billing_export\"),\n v.literal(\"google_support_confirmation\"),\n v.literal(\"openai_api_response\"),\n v.literal(\"openai_billing_export\"),\n v.literal(\"openai_support_confirmation\"),\n),\n evidenceReference: v.string(),\n outcome: v.union(\n v.object({ kind: v.literal(\"known_not_billed\") }),\n v.object({\n kind: v.literal(\"known_billed\"),\n actualProviderTokens: v.number(),\n }),\n ),\n }"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"path": "admin/paymentMutation:refundTokens",
|
|
39
|
+
"kind": "mutation",
|
|
40
|
+
"args": "{\n subscriptionId: v.id(\"subscriptions\"),\n tokensToRefund: v.number(),\n adminNote: v.optional(v.string()),\n paymentLogId: v.id(\"paymentLogs\"),\n }"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "admin/paymentMutation:requeueSubscriptionInvoiceReview",
|
|
44
|
+
"kind": "mutation",
|
|
45
|
+
"args": "{\n manualReviewId: v.id(\"paymentSettlementManualReviews\"),\n evidenceReference: v.string(),\n confirmRetry: v.literal(true),\n }"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"path": "admin/paymentMutation:updatePaymentLog",
|
|
49
|
+
"kind": "mutation",
|
|
50
|
+
"args": "{\n paymentLogId: v.id(\"paymentLogs\"),\n status: v.optional(\n v.union(\n v.literal(\"success\"),\n v.literal(\"failed\"),\n v.literal(\"pending\"),\n v.literal(\"refunded\"),\n v.literal(\"canceled\"),\n ),\n ),\n adminNote: v.optional(v.string()),\n }"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"path": "admin/paymentQuery:getPaymentStats",
|
|
54
|
+
"kind": "query",
|
|
55
|
+
"args": "{\n year: v.optional(v.number()),\n month: v.optional(v.number()), // 0-11 (January = 0)\n }"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"path": "admin/paymentQuery:listOpenSettlementReviews",
|
|
59
|
+
"kind": "query",
|
|
60
|
+
"args": "{ limit: v.optional(v.number()) }"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"path": "admin/paymentQuery:listPaymentLogs",
|
|
64
|
+
"kind": "query",
|
|
65
|
+
"args": "{\n limit: v.optional(v.number()),\n cursor: v.optional(v.string()),\n statusFilter: v.optional(\n v.union(\n v.literal(\"success\"),\n v.literal(\"failed\"),\n v.literal(\"pending\"),\n v.literal(\"refunded\"),\n v.literal(\"canceled\"),\n ),\n ),\n eventTypeFilter: v.optional(v.string()),\n }"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"path": "admin/paymentQuery:listPaymentLogsByUser",
|
|
69
|
+
"kind": "query",
|
|
70
|
+
"args": "{\n year: v.optional(v.number()),\n month: v.optional(v.number()), // 0-11 (January = 0)\n }"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"path": "admin/paymentQuery:listSubscriptions",
|
|
74
|
+
"kind": "query",
|
|
75
|
+
"args": "{\n limit: v.optional(v.number()),\n }"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"path": "admin/query:getAiLogById",
|
|
79
|
+
"kind": "query",
|
|
80
|
+
"args": "{\n logId: v.id(\"aiLogs\"),\n }"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"path": "admin/query:getAiLogStats",
|
|
84
|
+
"kind": "query",
|
|
85
|
+
"args": "{}"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"path": "admin/query:isAdmin",
|
|
89
|
+
"kind": "query",
|
|
90
|
+
"args": "{}"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"path": "admin/query:listAiLogs",
|
|
94
|
+
"kind": "query",
|
|
95
|
+
"args": "{\n status: v.optional(\n v.union(\n v.literal(\"success\"),\n v.literal(\"failed\"),\n v.literal(\"pending_approval\"),\n v.literal(\"approved\"),\n v.literal(\"rejected\"),\n v.literal(\"request_changes\"),\n ),\n ),\n workflowType: v.optional(v.string()),\n limit: v.optional(v.number()),\n cursor: v.optional(v.string()),\n }"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"path": "admin/query:listAiLogsByIndexing",
|
|
99
|
+
"kind": "query",
|
|
100
|
+
"args": "{\n limit: v.optional(v.number()),\n cursor: v.optional(v.number()),\n }"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"path": "admin/query:listAiLogsByTicket",
|
|
104
|
+
"kind": "query",
|
|
105
|
+
"args": "{\n limit: v.optional(v.number()),\n cursor: v.optional(v.number()), // createdAt timestamp for cursor-based pagination\n }"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"path": "apiKeys/action:create",
|
|
109
|
+
"kind": "action",
|
|
110
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n name: v.string(),\n scopes: v.array(v.union(v.literal(\"read\"), v.literal(\"write\"))),\n expiresInDays: v.number(),\n }"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"path": "apiKeys/mutation:revoke",
|
|
114
|
+
"kind": "mutation",
|
|
115
|
+
"args": "{ keyId: v.id(\"projectApiKeys\") }"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"path": "apiKeys/query:list",
|
|
119
|
+
"kind": "query",
|
|
120
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n paginationOpts: paginationOptsValidator,\n }"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"path": "github/action:listUserRepositories",
|
|
124
|
+
"kind": "action",
|
|
125
|
+
"args": "{\n cursor: v.optional(v.string()),\n reconnectRepositoryId: v.optional(v.string()),\n }"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"path": "github/action:recoverOAuthInstallation",
|
|
129
|
+
"kind": "action",
|
|
130
|
+
"args": "{}"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"path": "github/action:retryRepositoryAccessReconciliation",
|
|
134
|
+
"kind": "action",
|
|
135
|
+
"args": "{ installationId: v.number() }"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"path": "github/action:saveInstallation",
|
|
139
|
+
"kind": "action",
|
|
140
|
+
"args": "{\n installationId: v.number(),\n }"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"path": "github/query:getInstallation",
|
|
144
|
+
"kind": "query",
|
|
145
|
+
"args": "{\n installationId: v.number(),\n }"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"path": "github/query:getUserInstallation",
|
|
149
|
+
"kind": "query",
|
|
150
|
+
"args": "{}"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"path": "github/query:getUserInstallations",
|
|
154
|
+
"kind": "query",
|
|
155
|
+
"args": "{}"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"path": "invitations/action:create",
|
|
159
|
+
"kind": "action",
|
|
160
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n inviteeGithubUsername: v.string(),\n role: v.literal(\"viewer\"),\n }"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"path": "invitations/mutation:accept",
|
|
164
|
+
"kind": "mutation",
|
|
165
|
+
"args": "{\n invitationId: v.id(\"invitations\"),\n }"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"path": "invitations/mutation:cancel",
|
|
169
|
+
"kind": "mutation",
|
|
170
|
+
"args": "{\n invitationId: v.id(\"invitations\"),\n }"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"path": "invitations/mutation:reject",
|
|
174
|
+
"kind": "mutation",
|
|
175
|
+
"args": "{\n invitationId: v.id(\"invitations\"),\n }"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"path": "invitations/query:getPendingForRepository",
|
|
179
|
+
"kind": "query",
|
|
180
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"path": "invitations/query:listForRepository",
|
|
184
|
+
"kind": "query",
|
|
185
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"path": "invitations/query:listPending",
|
|
189
|
+
"kind": "query",
|
|
190
|
+
"args": "{}"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"path": "notifications/mutation:markAllAsRead",
|
|
194
|
+
"kind": "mutation",
|
|
195
|
+
"args": "{}"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"path": "notifications/mutation:markAsRead",
|
|
199
|
+
"kind": "mutation",
|
|
200
|
+
"args": "{\n notificationId: v.id(\"notifications\"),\n }"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"path": "notifications/query:getUnreadCount",
|
|
204
|
+
"kind": "query",
|
|
205
|
+
"args": "{}"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"path": "notifications/query:list",
|
|
209
|
+
"kind": "query",
|
|
210
|
+
"args": "{\n limit: v.optional(v.number()),\n }"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"path": "notifications/query:listUnread",
|
|
214
|
+
"kind": "query",
|
|
215
|
+
"args": "{}"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"path": "repoIndexing/mutation:initializeIndexing",
|
|
219
|
+
"kind": "mutation",
|
|
220
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"path": "repoIndexing/query:getFileContent",
|
|
224
|
+
"kind": "query",
|
|
225
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n filePath: v.string(),\n }"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"path": "repoIndexing/query:getFileList",
|
|
229
|
+
"kind": "query",
|
|
230
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n limit: v.optional(v.number()),\n }"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"path": "repoIndexing/query:getIndexingLogs",
|
|
234
|
+
"kind": "query",
|
|
235
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n limit: v.optional(v.number()),\n }"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"path": "repoIndexing/query:getIndexingStatus",
|
|
239
|
+
"kind": "query",
|
|
240
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"path": "repoLearnings/query:countByRepository",
|
|
244
|
+
"kind": "query",
|
|
245
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"path": "repositories/action:createAndIndex",
|
|
249
|
+
"kind": "action",
|
|
250
|
+
"args": "{\n installationId: v.number(),\n githubRepositoryId: v.number(),\n reconnectRepositoryId: v.optional(v.string()),\n }"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"path": "repositories/action:syncRepository",
|
|
254
|
+
"kind": "action",
|
|
255
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"path": "repositories/mutation:remove",
|
|
259
|
+
"kind": "mutation",
|
|
260
|
+
"args": "{ repositoryId: v.id(\"repositories\") }"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"path": "repositories/mutation:toggleActive",
|
|
264
|
+
"kind": "mutation",
|
|
265
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n isActive: v.optional(v.boolean()),\n }"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"path": "repositories/mutation:updateSettings",
|
|
269
|
+
"kind": "mutation",
|
|
270
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n settings: v.object({\n issueAssistantEnabled: v.boolean(),\n autoFormatIssue: v.boolean(),\n autoAnswerQuestions: v.boolean(),\n tagMaintainersForCodeChanges: v.boolean(),\n issueResponseStyle: v.optional(v.string()),\n requireApproval: v.boolean(),\n discussionAssistantEnabled: v.boolean(),\n autoFormatDiscussion: v.boolean(),\n autoAnswerDiscussionQuestions: v.boolean(),\n tagMaintainersForDiscussionCodeChanges: v.boolean(),\n discussionResponseStyle: v.optional(v.string()),\n requireApprovalForDiscussions: v.boolean(),\n }),\n }"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"path": "repositories/query:get",
|
|
274
|
+
"kind": "query",
|
|
275
|
+
"args": "{\n owner: v.string(),\n repo: v.string(),\n }"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"path": "repositories/query:getById",
|
|
279
|
+
"kind": "query",
|
|
280
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"path": "repositories/query:getConnectedRepos",
|
|
284
|
+
"kind": "query",
|
|
285
|
+
"args": "{}"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"path": "repositories/query:getReconnectTarget",
|
|
289
|
+
"kind": "query",
|
|
290
|
+
"args": "{\n reconnectRepositoryId: v.string(),\n }"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"path": "repositories/query:list",
|
|
294
|
+
"kind": "query",
|
|
295
|
+
"args": "{}"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"path": "repositoryMembers/mutation:leave",
|
|
299
|
+
"kind": "mutation",
|
|
300
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"path": "repositoryMembers/mutation:remove",
|
|
304
|
+
"kind": "mutation",
|
|
305
|
+
"args": "{\n memberId: v.id(\"repositoryMembers\"),\n }"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"path": "repositoryMembers/query:getMyRole",
|
|
309
|
+
"kind": "query",
|
|
310
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"path": "repositoryMembers/query:list",
|
|
314
|
+
"kind": "query",
|
|
315
|
+
"args": "{\n repositoryId: v.id(\"repositories\"),\n }"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"path": "subscriptions/action:createCheckoutUrl",
|
|
319
|
+
"kind": "action",
|
|
320
|
+
"args": "{\n plan: v.literal(\"pro\"),\n }"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"path": "subscriptions/action:createTokenPackCheckoutUrl",
|
|
324
|
+
"kind": "action",
|
|
325
|
+
"args": "{\n packSize: v.union(v.literal(\"small\"), v.literal(\"large\")),\n }"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"path": "subscriptions/query:calculateOverage",
|
|
329
|
+
"kind": "query",
|
|
330
|
+
"args": "{}"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"path": "subscriptions/query:canPerformAction",
|
|
334
|
+
"kind": "query",
|
|
335
|
+
"args": "{}"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"path": "subscriptions/query:getCurrent",
|
|
339
|
+
"kind": "query",
|
|
340
|
+
"args": "{}"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"path": "subscriptions/query:getPricingInfo",
|
|
344
|
+
"kind": "query",
|
|
345
|
+
"args": "{}"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"path": "ticketActions/action:approve",
|
|
349
|
+
"kind": "action",
|
|
350
|
+
"args": "{\n actionId: v.id(\"ticketActions\"),\n }"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"path": "ticketActions/action:reject",
|
|
354
|
+
"kind": "action",
|
|
355
|
+
"args": "{\n actionId: v.id(\"ticketActions\"),\n }"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"path": "ticketActions/action:requestChanges",
|
|
359
|
+
"kind": "action",
|
|
360
|
+
"args": "{\n actionId: v.id(\"ticketActions\"),\n feedback: v.string(),\n }"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"path": "ticketActions/query:getById",
|
|
364
|
+
"kind": "query",
|
|
365
|
+
"args": "{\n id: v.id(\"ticketActions\"),\n }"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"path": "ticketActions/query:listByTicket",
|
|
369
|
+
"kind": "query",
|
|
370
|
+
"args": "{\n ticketId: v.id(\"tickets\"),\n }"
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"path": "tickets/mutation:restore",
|
|
374
|
+
"kind": "mutation",
|
|
375
|
+
"args": "{\n id: v.id(\"tickets\"),\n }"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"path": "tickets/mutation:softDelete",
|
|
379
|
+
"kind": "mutation",
|
|
380
|
+
"args": "{\n id: v.id(\"tickets\"),\n }"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"path": "tickets/query:getById",
|
|
384
|
+
"kind": "query",
|
|
385
|
+
"args": "{\n id: v.id(\"tickets\"),\n }"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"path": "tickets/query:list",
|
|
389
|
+
"kind": "query",
|
|
390
|
+
"args": "{\n githubRepo: v.optional(v.string()),\n status: v.optional(v.union(v.literal(\"open\"), v.literal(\"closed\"))),\n ticketType: v.optional(\n v.union(v.literal(\"issue\"), v.literal(\"discussion\")),\n ),\n search: v.optional(v.string()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n }"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"path": "tickets/query:listPendingApproval",
|
|
394
|
+
"kind": "query",
|
|
395
|
+
"args": "{\n githubRepo: v.optional(v.string()),\n ticketType: v.optional(\n v.union(v.literal(\"issue\"), v.literal(\"discussion\")),\n ),\n search: v.optional(v.string()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n }"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"path": "tokenUsage/query:getCurrentMonthUsage",
|
|
399
|
+
"kind": "query",
|
|
400
|
+
"args": "{}"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"path": "tokenUsage/query:getUsageByRepository",
|
|
404
|
+
"kind": "query",
|
|
405
|
+
"args": "{}"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"path": "users/action:ensureProfile",
|
|
409
|
+
"kind": "action",
|
|
410
|
+
"args": "{}"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"path": "users/mutation:attestLegalEligibility",
|
|
414
|
+
"kind": "mutation",
|
|
415
|
+
"args": "{\n minimumAgeConfirmed: v.literal(true),\n professionalUseConfirmed: v.literal(true),\n termsAccepted: v.literal(true),\n privacyPolicyAccepted: v.literal(true),\n displayedTermsVersion: v.string(),\n displayedPrivacyPolicyVersion: v.string(),\n }"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"path": "users/mutation:ensureProfileFromAuth",
|
|
419
|
+
"kind": "mutation",
|
|
420
|
+
"args": "{}"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"path": "users/query:getCurrentUser",
|
|
424
|
+
"kind": "query",
|
|
425
|
+
"args": "{}"
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
"path": "users/query:getProfile",
|
|
429
|
+
"kind": "query",
|
|
430
|
+
"args": "{ userId: v.id(\"users\") }"
|
|
431
|
+
}
|
|
432
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ProjectKey = {
|
|
2
|
+
version: 1;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
httpUrl: string;
|
|
6
|
+
keyId: string;
|
|
7
|
+
repositoryId: string;
|
|
8
|
+
name: string;
|
|
9
|
+
scopes: string[];
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function projectHttpUrl(apiUrl: string): string;
|
|
13
|
+
export declare function readProjectKey(path: string): Promise<ProjectKey>;
|
|
14
|
+
/** Refuse key paths that Git would track, so secrets are not committed. */
|
|
15
|
+
export declare function assertUntrackedKeyPath(path: string): Promise<void>;
|
|
16
|
+
export declare function saveProjectKey(path: string, key: ProjectKey): Promise<void>;
|
|
17
|
+
export declare function callWithProjectKey<T>(apiUrl: string, keyFile: string | undefined, rawKey: string | undefined, kind: string, path: string, args: unknown, timeoutMs: number): Promise<T>;
|
package/dist/api-keys.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { mkdir, open, readFile, realpath, rm } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { asRecord } from "./ui.js";
|
|
6
|
+
import { exists, validateUrl } from "./storage.js";
|
|
7
|
+
export function projectHttpUrl(apiUrl) {
|
|
8
|
+
return validateUrl(process.env.GITDOG_HTTP_URL ??
|
|
9
|
+
apiUrl.replace(/\.convex\.cloud$/, ".convex.site"), "Project HTTP URL");
|
|
10
|
+
}
|
|
11
|
+
export async function readProjectKey(path) {
|
|
12
|
+
let value;
|
|
13
|
+
try {
|
|
14
|
+
value = JSON.parse(await readFile(resolve(path), "utf8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error("Cannot read the API key file. Check its path, permissions and JSON syntax.");
|
|
18
|
+
}
|
|
19
|
+
const key = asRecord(value);
|
|
20
|
+
if (key?.version !== 1 ||
|
|
21
|
+
typeof key.apiKey !== "string" ||
|
|
22
|
+
!/^gdk_[a-f0-9]{64}$/.test(key.apiKey) ||
|
|
23
|
+
typeof key.apiUrl !== "string" ||
|
|
24
|
+
typeof key.httpUrl !== "string" ||
|
|
25
|
+
typeof key.keyId !== "string" ||
|
|
26
|
+
typeof key.repositoryId !== "string" ||
|
|
27
|
+
typeof key.name !== "string" ||
|
|
28
|
+
!Array.isArray(key.scopes) ||
|
|
29
|
+
!key.scopes.every((scope) => typeof scope === "string") ||
|
|
30
|
+
typeof key.expiresAt !== "number")
|
|
31
|
+
throw new Error("Invalid Gitdog project API key file.");
|
|
32
|
+
return {
|
|
33
|
+
version: 1,
|
|
34
|
+
apiKey: key.apiKey,
|
|
35
|
+
apiUrl: validateUrl(key.apiUrl, "Key API URL"),
|
|
36
|
+
httpUrl: validateUrl(key.httpUrl, "Key HTTP URL"),
|
|
37
|
+
keyId: key.keyId,
|
|
38
|
+
repositoryId: key.repositoryId,
|
|
39
|
+
name: key.name,
|
|
40
|
+
scopes: key.scopes,
|
|
41
|
+
expiresAt: key.expiresAt,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Refuse key paths that Git would track, so secrets are not committed. */
|
|
45
|
+
export async function assertUntrackedKeyPath(path) {
|
|
46
|
+
let directory = dirname(path);
|
|
47
|
+
while (!(await exists(directory))) {
|
|
48
|
+
const parent = dirname(directory);
|
|
49
|
+
if (parent === directory)
|
|
50
|
+
return;
|
|
51
|
+
directory = parent;
|
|
52
|
+
}
|
|
53
|
+
const canonicalDirectory = await realpath(directory);
|
|
54
|
+
const canonicalPath = join(canonicalDirectory, relative(directory, path));
|
|
55
|
+
try {
|
|
56
|
+
const { stdout } = await promisify(execFile)("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
57
|
+
cwd: canonicalDirectory,
|
|
58
|
+
env: { ...process.env, LC_ALL: "C" },
|
|
59
|
+
timeout: 5000,
|
|
60
|
+
});
|
|
61
|
+
if (stdout.trim() === "false")
|
|
62
|
+
return;
|
|
63
|
+
if (stdout.trim() !== "true")
|
|
64
|
+
throw new Error("Unexpected Git work tree result");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const gitError = error;
|
|
68
|
+
if (gitError.code === 128 &&
|
|
69
|
+
/fatal: not a git repository/i.test(gitError.stderr ?? ""))
|
|
70
|
+
return;
|
|
71
|
+
throw new Error("Could not verify the key path with Git. Fix Git configuration or choose the default private key location.");
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await promisify(execFile)("git", ["check-ignore", "-q", "--", canonicalPath], {
|
|
75
|
+
cwd: canonicalDirectory,
|
|
76
|
+
env: { ...process.env, LC_ALL: "C" },
|
|
77
|
+
timeout: 5000,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error.code === 1)
|
|
82
|
+
throw new Error("The key file would be inside a Git repository and not ignored. Choose a path outside the repository or add it to .gitignore.");
|
|
83
|
+
throw new Error("Could not verify Git ignore rules for the key path. No key was issued.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function saveProjectKey(path, key) {
|
|
87
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
88
|
+
const handle = await open(path, "wx", 0o600);
|
|
89
|
+
try {
|
|
90
|
+
await handle.writeFile(JSON.stringify(key, null, 2) + "\n");
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
await handle.close();
|
|
94
|
+
await rm(path, { force: true });
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
await handle.close();
|
|
98
|
+
}
|
|
99
|
+
export async function callWithProjectKey(apiUrl, keyFile, rawKey, kind, path, args, timeoutMs) {
|
|
100
|
+
const deadline = Date.now() + timeoutMs;
|
|
101
|
+
if (rawKey && keyFile)
|
|
102
|
+
throw new Error("Select GITDOG_API_KEY or a key file, not both.");
|
|
103
|
+
const credential = keyFile ? await readProjectKey(keyFile) : undefined;
|
|
104
|
+
if (credential && credential.apiUrl !== apiUrl)
|
|
105
|
+
throw new Error("This API key file belongs to another API URL. Select the matching --api-url.");
|
|
106
|
+
const key = credential?.apiKey ?? rawKey;
|
|
107
|
+
if (!key || !/^gdk_[a-f0-9]{64}$/.test(key))
|
|
108
|
+
throw new Error("Invalid Gitdog project API key.");
|
|
109
|
+
if (kind === "action")
|
|
110
|
+
throw new Error("This command requires an owner login. Project API keys cannot manage accounts, keys, billing or GitHub actions.");
|
|
111
|
+
const remaining = deadline - Date.now();
|
|
112
|
+
if (remaining <= 0)
|
|
113
|
+
throw new Error("Project API request timed out.");
|
|
114
|
+
const response = await fetch(`${credential?.httpUrl ?? projectHttpUrl(apiUrl)}/v1/cli`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
redirect: "error",
|
|
117
|
+
signal: AbortSignal.timeout(remaining),
|
|
118
|
+
headers: { "Content-Type": "application/json", "X-Gitdog-Api-Key": key },
|
|
119
|
+
body: JSON.stringify({ kind, path, args }),
|
|
120
|
+
});
|
|
121
|
+
let result;
|
|
122
|
+
try {
|
|
123
|
+
result = asRecord(await response.json());
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
throw new Error(`Project API returned HTTP ${response.status} without a JSON response.`);
|
|
127
|
+
}
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const error = asRecord(result?.error);
|
|
130
|
+
throw new Error(typeof error?.message === "string"
|
|
131
|
+
? error.message
|
|
132
|
+
: `Project API returned HTTP ${response.status}.`);
|
|
133
|
+
}
|
|
134
|
+
if (!result || !Object.hasOwn(result, "value"))
|
|
135
|
+
throw new Error("Invalid project API response.");
|
|
136
|
+
return result.value;
|
|
137
|
+
}
|