@coderook/cli 0.9.0 → 0.10.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.
@@ -9,6 +9,20 @@ exports.mergeTrack = mergeTrack;
9
9
  exports.resolveMergeConflict = resolveMergeConflict;
10
10
  exports.applyMerge = applyMerge;
11
11
  exports.cancelMerge = cancelMerge;
12
+ exports.aiAccess = aiAccess;
13
+ exports.setAiAccess = setAiAccess;
14
+ exports.versions = versions;
15
+ exports.issues = issues;
16
+ exports.createIssue = createIssue;
17
+ exports.releases = releases;
18
+ exports.collaborators = collaborators;
19
+ exports.invite = invite;
20
+ exports.watch = watch;
21
+ exports.setWatch = setWatch;
22
+ exports.workflows = workflows;
23
+ exports.tokens = tokens;
24
+ exports.revokeToken = revokeToken;
25
+ exports.deleteProject = deleteProject;
12
26
  /** The small part of the API the command-line tool needs directly. */
13
27
  const identify_js_1 = require("../../desktop-app/src/main/identify.js");
14
28
  const config_js_1 = require("./config.js");
@@ -91,3 +105,161 @@ async function applyMerge(mergeTrackId) {
91
105
  async function cancelMerge(mergeTrackId) {
92
106
  await call(`/v1/merge-tracks/${mergeTrackId}/cancel`, { method: "POST" });
93
107
  }
108
+ /**
109
+ * Read one project's machine-access answers.
110
+ *
111
+ * Taken from the project record rather than a dedicated endpoint, because
112
+ * that is where they live — four columns on the repository, not a separate
113
+ * resource. Defaults to permissive, matching the service: a project that has
114
+ * never been touched allows everything.
115
+ */
116
+ async function aiAccess(repositoryId) {
117
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}`);
118
+ const project = (body.repository ?? body);
119
+ /*
120
+ Read from the nested object the service actually sends, not from flat
121
+ fields beside it. Writing uses `aiRead`; reading returns `ai.read`, and
122
+ binding to the write shape made every setting report as allowed — a
123
+ missing field is not false, so a project with reading switched off said
124
+ it was on. The asymmetry is the API's; the mistake was assuming it away
125
+ rather than looking.
126
+ */
127
+ const ai = (project.ai ?? {});
128
+ const allowed = (key) => ai[key] !== false;
129
+ return {
130
+ aiRead: allowed("read"),
131
+ aiDownload: allowed("download"),
132
+ aiContribute: allowed("contribute"),
133
+ aiRequest: allowed("request"),
134
+ };
135
+ }
136
+ /**
137
+ * Change one answer.
138
+ *
139
+ * One at a time, matching the site: each is a separate decision, and sending
140
+ * all four would make turning off downloads look like it needed a view about
141
+ * indexing as well.
142
+ */
143
+ async function setAiAccess(repositoryId, change) {
144
+ await call(`/v1/repositories/${encodeURIComponent(repositoryId)}`, {
145
+ method: "PATCH",
146
+ body: change,
147
+ });
148
+ }
149
+ /** Every saved version of a project, newest first. */
150
+ async function versions(repositoryId) {
151
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/versions`);
152
+ return (body.versions ?? []).map((row) => ({
153
+ id: String(row.id ?? ""),
154
+ sequence: Number(row.sequence ?? 0),
155
+ message: String(row.message ?? ""),
156
+ fileCount: Number(row.fileCount ?? 0),
157
+ storedSize: Number(row.storedSize ?? row.sourceSize ?? 0),
158
+ createdAt: String(row.createdAt ?? ""),
159
+ }));
160
+ }
161
+ async function issues(repositoryId) {
162
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/issues`);
163
+ return {
164
+ issues: (body.issues ?? []).map((row) => ({
165
+ number: Number(row.number ?? 0),
166
+ title: String(row.title ?? ""),
167
+ state: String(row.state ?? "open"),
168
+ labels: Array.isArray(row.labels) ? row.labels : [],
169
+ commentCount: Number(row.commentCount ?? 0),
170
+ createdAt: String(row.createdAt ?? ""),
171
+ })),
172
+ openCount: Number(body.openCount ?? 0),
173
+ closedCount: Number(body.closedCount ?? 0),
174
+ };
175
+ }
176
+ async function createIssue(repositoryId, input) {
177
+ /*
178
+ The number comes back at the top level, not nested under an `issue` key.
179
+ Reading the wrong shape reported every new issue as #0 — harmless in
180
+ itself, and exactly the kind of small wrongness that makes somebody stop
181
+ trusting the rest of the output.
182
+ */
183
+ const created = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/issues`, { method: "POST", body: { title: input.title, body: input.body ?? "" } });
184
+ return { number: Number(created.number ?? 0) };
185
+ }
186
+ async function releases(repositoryId) {
187
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/releases`);
188
+ return (body.releases ?? []).map((row) => ({
189
+ sequence: Number(row.sequence ?? 0),
190
+ name: String(row.name ?? ""),
191
+ notes: String(row.notes ?? ""),
192
+ releasedAt: String(row.releasedAt ?? ""),
193
+ releasedBy: String(row.releasedBy ?? ""),
194
+ assets: Array.isArray(row.assets) ? row.assets : [],
195
+ }));
196
+ }
197
+ async function collaborators(repositoryId) {
198
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/collaborators`);
199
+ return {
200
+ collaborators: (body.collaborators ?? []).map((row) => ({
201
+ displayName: String(row.displayName ?? ""),
202
+ email: String(row.email ?? ""),
203
+ role: String(row.role ?? "member"),
204
+ })),
205
+ invites: (body.invites ?? []).map((row) => ({
206
+ email: String(row.email ?? ""),
207
+ role: String(row.role ?? "member"),
208
+ expiresAt: String(row.expiresAt ?? row.expires_at ?? ""),
209
+ })),
210
+ };
211
+ }
212
+ async function invite(repositoryId, input) {
213
+ await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/invites`, {
214
+ method: "POST",
215
+ body: input,
216
+ });
217
+ }
218
+ async function watch(repositoryId) {
219
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/watch`);
220
+ return {
221
+ level: String(body.level ?? "versions"),
222
+ inApp: body.inApp !== false,
223
+ desktop: body.desktop === true,
224
+ emailDigest: body.emailDigest === true,
225
+ };
226
+ }
227
+ async function setWatch(repositoryId, change) {
228
+ await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/watch`, {
229
+ method: "PUT",
230
+ body: change,
231
+ });
232
+ }
233
+ async function workflows(repositoryId) {
234
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/workflows`);
235
+ return (body.workflows ?? []).map((row) => ({
236
+ name: String(row.name ?? ""),
237
+ trigger: String(row.trigger ?? ""),
238
+ command: String(row.command ?? ""),
239
+ runsOn: String(row.runsOn ?? "hosted"),
240
+ runs: Number(row.runs ?? 0),
241
+ passing: Number(row.passing ?? 0),
242
+ archivedAt: row.archivedAt ? String(row.archivedAt) : null,
243
+ }));
244
+ }
245
+ /** Personal access tokens on the account. */
246
+ async function tokens() {
247
+ const body = await call("/v1/account/tokens");
248
+ return (body.tokens ?? []).map((row) => ({
249
+ id: String(row.id ?? ""),
250
+ name: String(row.name ?? ""),
251
+ lastUsedAt: String(row.lastUsedAt ?? ""),
252
+ createdAt: String(row.createdAt ?? ""),
253
+ }));
254
+ }
255
+ async function revokeToken(tokenId) {
256
+ await call(`/v1/account/tokens/${encodeURIComponent(tokenId)}`, {
257
+ method: "DELETE",
258
+ });
259
+ }
260
+ /** Remove a project. The service keeps it for its retention window. */
261
+ async function deleteProject(repositoryId) {
262
+ await call(`/v1/repositories/${encodeURIComponent(repositoryId)}`, {
263
+ method: "DELETE",
264
+ });
265
+ }