pinokiod 7.3.1 → 7.3.3

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.
Files changed (122) hide show
  1. package/kernel/api/github/index.js +444 -0
  2. package/kernel/api/index.js +199 -11
  3. package/kernel/api/process/index.js +124 -44
  4. package/kernel/api/shell_run_template.js +273 -0
  5. package/kernel/api/uri/index.js +51 -0
  6. package/kernel/bin/git.js +9 -10
  7. package/kernel/bin/huggingface.js +1 -1
  8. package/kernel/bin/zip.js +9 -1
  9. package/kernel/connect/providers/github/README.md +5 -4
  10. package/kernel/environment.js +195 -92
  11. package/kernel/git.js +98 -19
  12. package/kernel/gitconfig_template +7 -0
  13. package/kernel/gpu/amd.js +72 -0
  14. package/kernel/gpu/apple.js +8 -0
  15. package/kernel/gpu/common.js +12 -0
  16. package/kernel/gpu/intel.js +47 -0
  17. package/kernel/gpu/nvidia.js +8 -0
  18. package/kernel/index.js +11 -1
  19. package/kernel/managed_skills.js +871 -0
  20. package/kernel/plugin.js +6 -58
  21. package/kernel/plugin_sources.js +316 -0
  22. package/kernel/resource_usage/gpu.js +349 -0
  23. package/kernel/resource_usage/index.js +322 -0
  24. package/kernel/resource_usage/macos_footprint.js +197 -0
  25. package/kernel/resource_usage/preferences.js +92 -0
  26. package/kernel/resource_usage/process_tree.js +303 -0
  27. package/kernel/scripts/git/create +4 -4
  28. package/kernel/scripts/git/fork +7 -8
  29. package/kernel/shell.js +23 -2
  30. package/kernel/shells.js +41 -0
  31. package/kernel/sysinfo.js +62 -9
  32. package/kernel/util.js +60 -0
  33. package/package.json +1 -1
  34. package/server/index.js +984 -156
  35. package/server/lib/app_log_report.js +543 -0
  36. package/server/lib/content_validation.js +55 -33
  37. package/server/lib/launcher_instruction_bootstrap.js +4 -96
  38. package/server/lib/terminal_session_helpers.js +0 -3
  39. package/server/public/common.js +77 -31
  40. package/server/public/create-launcher.js +4 -32
  41. package/server/public/logs.js +1428 -0
  42. package/server/public/nav.js +7 -0
  43. package/server/public/plugin-detail.js +93 -10
  44. package/server/public/privacy_filter_worker.js +391 -0
  45. package/server/public/style.css +1104 -154
  46. package/server/public/task-launcher.js +8 -29
  47. package/server/public/universal-launcher.css +8 -6
  48. package/server/public/universal-launcher.js +3 -27
  49. package/server/routes/apps.js +195 -1
  50. package/server/views/app.ejs +3041 -717
  51. package/server/views/autolaunch.ejs +917 -0
  52. package/server/views/bootstrap.ejs +7 -1
  53. package/server/views/d.ejs +408 -65
  54. package/server/views/editor.ejs +85 -19
  55. package/server/views/index.ejs +661 -111
  56. package/server/views/init/index.ejs +1 -1
  57. package/server/views/install.ejs +1 -1
  58. package/server/views/logs.ejs +164 -86
  59. package/server/views/net.ejs +7 -1
  60. package/server/views/partials/d_terminal_column.ejs +2 -2
  61. package/server/views/partials/d_terminal_options.ejs +0 -8
  62. package/server/views/partials/fs_status.ejs +47 -0
  63. package/server/views/partials/home_action_modal.ejs +86 -0
  64. package/server/views/partials/home_run_menu.ejs +87 -0
  65. package/server/views/partials/main_sidebar.ejs +2 -0
  66. package/server/views/partials/menu.ejs +1 -1
  67. package/server/views/plugin_detail.ejs +19 -4
  68. package/server/views/plugins.ejs +201 -3
  69. package/server/views/pre.ejs +1 -1
  70. package/server/views/pro.ejs +1 -1
  71. package/server/views/shell.ejs +40 -18
  72. package/server/views/skills.ejs +506 -0
  73. package/server/views/terminal.ejs +45 -19
  74. package/spec/INSTRUCTION_SYNC.md +20 -10
  75. package/system/plugin/antigravity-cli/antigravity.png +0 -0
  76. package/system/plugin/antigravity-cli/common.js +155 -0
  77. package/system/plugin/antigravity-cli/install.js +272 -0
  78. package/system/plugin/antigravity-cli/pinokio.js +13 -0
  79. package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
  80. package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
  81. package/system/plugin/claude/claude.png +0 -0
  82. package/system/plugin/claude/pinokio.js +47 -0
  83. package/system/plugin/claude-auto/claude.png +0 -0
  84. package/system/plugin/claude-auto/pinokio.js +58 -0
  85. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  86. package/system/plugin/claude-desktop/pinokio.js +23 -0
  87. package/system/plugin/codex/openai.webp +0 -0
  88. package/system/plugin/codex/pinokio.js +42 -0
  89. package/system/plugin/codex-auto/openai.webp +0 -0
  90. package/system/plugin/codex-auto/pinokio.js +49 -0
  91. package/system/plugin/codex-desktop/icon.png +0 -0
  92. package/system/plugin/codex-desktop/pinokio.js +23 -0
  93. package/system/plugin/crush/crush.png +0 -0
  94. package/system/plugin/crush/pinokio.js +15 -0
  95. package/system/plugin/cursor/cursor.jpeg +0 -0
  96. package/system/plugin/cursor/pinokio.js +23 -0
  97. package/system/plugin/qwen/pinokio.js +34 -0
  98. package/system/plugin/qwen/qwen.png +0 -0
  99. package/system/plugin/vscode/pinokio.js +20 -0
  100. package/system/plugin/vscode/vscode.png +0 -0
  101. package/system/plugin/windsurf/pinokio.js +23 -0
  102. package/system/plugin/windsurf/windsurf.png +0 -0
  103. package/test/antigravity-cli-plugin.test.js +185 -0
  104. package/test/app-api.test.js +239 -0
  105. package/test/app-log-report.test.js +67 -0
  106. package/test/environment-cache-preflight.test.js +98 -0
  107. package/test/git-bin.test.js +59 -0
  108. package/test/git-defaults.test.js +97 -0
  109. package/test/github-api.test.js +158 -0
  110. package/test/github-connection.test.js +117 -0
  111. package/test/huggingface-bin.test.js +25 -0
  112. package/test/managed-skills.test.js +351 -0
  113. package/test/plugin-action-functions.test.js +337 -0
  114. package/test/plugin-dev-iframe.test.js +17 -0
  115. package/test/plugin-sources.test.js +203 -0
  116. package/test/privacy-filter-worker-heuristics.test.js +69 -0
  117. package/test/process-wait.test.js +169 -0
  118. package/test/script-api.test.js +97 -0
  119. package/test/shell-api.test.js +134 -0
  120. package/test/shell-run-template.test.js +209 -0
  121. package/test/storage-api.test.js +137 -0
  122. package/test/uri-api.test.js +100 -0
@@ -0,0 +1,444 @@
1
+ const path = require('path')
2
+ const { execFile, spawn } = require('child_process')
3
+ const fetch = require('cross-fetch')
4
+
5
+ const NON_INTERACTIVE_GIT_ENV = {
6
+ GIT_TERMINAL_PROMPT: "0",
7
+ GIT_ASKPASS: "",
8
+ SSH_ASKPASS: "",
9
+ GCM_INTERACTIVE: "never"
10
+ }
11
+
12
+ class Github {
13
+ static parseCredentialOutput(stdout) {
14
+ const credential = {}
15
+ for (const line of String(stdout || "").split(/\r?\n/)) {
16
+ const index = line.indexOf("=")
17
+ if (index <= 0) continue
18
+ credential[line.slice(0, index)] = line.slice(index + 1)
19
+ }
20
+ return credential
21
+ }
22
+
23
+ static parseGithubRemote(remoteUrl) {
24
+ const raw = typeof remoteUrl === "string" ? remoteUrl.trim() : ""
25
+ if (!raw) return null
26
+
27
+ const fromPath = (value) => {
28
+ const segments = String(value || "")
29
+ .replace(/^\/+/, "")
30
+ .replace(/\.git$/i, "")
31
+ .split("/")
32
+ .filter(Boolean)
33
+ if (segments.length < 2) return null
34
+ return {
35
+ owner: segments[0],
36
+ repo: segments[1],
37
+ fullName: `${segments[0]}/${segments[1]}`
38
+ }
39
+ }
40
+
41
+ if (/^https?:\/\//i.test(raw)) {
42
+ try {
43
+ const parsed = new URL(raw)
44
+ if (parsed.hostname.toLowerCase() !== "github.com") return null
45
+ return fromPath(parsed.pathname)
46
+ } catch (_) {
47
+ return null
48
+ }
49
+ }
50
+
51
+ let match = raw.match(/^git@github\.com:(.+)$/i)
52
+ if (match) return fromPath(match[1])
53
+
54
+ match = raw.match(/^ssh:\/\/git@github\.com\/(.+)$/i)
55
+ if (match) return fromPath(match[1])
56
+
57
+ return null
58
+ }
59
+
60
+ static validateOwner(owner) {
61
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(owner)) {
62
+ throw new Error(`Invalid GitHub owner: ${owner}`)
63
+ }
64
+ }
65
+
66
+ static validateRepoName(name) {
67
+ if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name === "..") {
68
+ throw new Error(`Invalid GitHub repository name: ${name}`)
69
+ }
70
+ }
71
+
72
+ static cleanParam(value) {
73
+ const raw = String(value == null ? "" : value).trim()
74
+ if (!raw || raw === "undefined" || raw === "null" || /^{{.+}}$/.test(raw)) {
75
+ return ""
76
+ }
77
+ return raw
78
+ }
79
+
80
+ static parseRepoName(name, cwd) {
81
+ const fallback = cwd ? path.basename(path.resolve(cwd)) : ""
82
+ const raw = Github.cleanParam(name) || fallback
83
+ const normalized = raw
84
+ .replace(/^https?:\/\/github\.com\//i, "")
85
+ .replace(/\.git$/i, "")
86
+ .replace(/^\/+|\/+$/g, "")
87
+
88
+ if (!normalized) {
89
+ throw new Error("Repository name is required.")
90
+ }
91
+
92
+ const parts = normalized.split("/").filter(Boolean)
93
+ if (parts.length > 2) {
94
+ throw new Error(`Invalid GitHub repository name: ${raw}`)
95
+ }
96
+
97
+ const repo = parts.length === 2 ? parts[1] : parts[0]
98
+ const owner = parts.length === 2 ? parts[0] : null
99
+
100
+ if (owner) Github.validateOwner(owner)
101
+ Github.validateRepoName(repo)
102
+
103
+ return { owner, repo }
104
+ }
105
+
106
+ static normalizeVisibility(visibility) {
107
+ const value = (Github.cleanParam(visibility) || "public").toLowerCase()
108
+ if (!["public", "private", "internal"].includes(value)) {
109
+ throw new Error(`Invalid GitHub repository visibility: ${visibility}`)
110
+ }
111
+ return value
112
+ }
113
+
114
+ static buildCreateRepoRequest({ owner, repo, authenticatedUser, visibility }) {
115
+ const login = authenticatedUser && authenticatedUser.login ? String(authenticatedUser.login) : ""
116
+ const body = { name: repo }
117
+
118
+ if (visibility === "internal") {
119
+ if (!owner || owner.toLowerCase() === login.toLowerCase()) {
120
+ throw new Error("Internal repositories require an organization owner, for example org/repo.")
121
+ }
122
+ body.visibility = "internal"
123
+ } else {
124
+ body.private = visibility === "private"
125
+ }
126
+
127
+ if (owner && owner.toLowerCase() !== login.toLowerCase()) {
128
+ return {
129
+ path: `/orgs/${encodeURIComponent(owner)}/repos`,
130
+ body
131
+ }
132
+ }
133
+
134
+ return {
135
+ path: "/user/repos",
136
+ body
137
+ }
138
+ }
139
+
140
+ static repoEquals(a, b) {
141
+ return Boolean(
142
+ a && b
143
+ && String(a.owner).toLowerCase() === String(b.owner).toLowerCase()
144
+ && String(a.repo).toLowerCase() === String(b.repo).toLowerCase()
145
+ )
146
+ }
147
+
148
+ static repoFromApi(repo) {
149
+ if (!repo || typeof repo !== "object") return null
150
+ const owner = repo.owner && repo.owner.login ? String(repo.owner.login) : ""
151
+ const name = repo.name ? String(repo.name) : ""
152
+ if (owner && name) {
153
+ return { owner, repo: name, fullName: `${owner}/${name}` }
154
+ }
155
+ if (repo.full_name) {
156
+ const parts = String(repo.full_name).split("/")
157
+ if (parts.length >= 2) {
158
+ return { owner: parts[0], repo: parts[1], fullName: `${parts[0]}/${parts[1]}` }
159
+ }
160
+ }
161
+ return Github.parseGithubRemote(repo.clone_url || repo.html_url || "")
162
+ }
163
+
164
+ params(req) {
165
+ return req && req.params ? req.params : {}
166
+ }
167
+
168
+ cwd(req, kernel) {
169
+ const params = this.params(req)
170
+ return path.resolve(params.cwd || params.path || req.cwd || (kernel && kernel.homedir) || process.cwd())
171
+ }
172
+
173
+ gitEnv(kernel, { nonInteractive = true } = {}) {
174
+ const env = kernel && kernel.envs ? { ...kernel.envs } : { ...process.env }
175
+ if (kernel && kernel.homedir && !env.GIT_CONFIG_GLOBAL) {
176
+ env.GIT_CONFIG_GLOBAL = path.resolve(kernel.homedir, "gitconfig")
177
+ }
178
+ return nonInteractive ? { ...env, ...NON_INTERACTIVE_GIT_ENV } : env
179
+ }
180
+
181
+ execGit(args, { cwd, kernel, input, timeout = 30000, allowFailure = false } = {}) {
182
+ return new Promise((resolve, reject) => {
183
+ const child = execFile(
184
+ "git",
185
+ args,
186
+ {
187
+ cwd,
188
+ env: this.gitEnv(kernel),
189
+ timeout,
190
+ maxBuffer: 1024 * 1024,
191
+ windowsHide: true
192
+ },
193
+ (error, stdout, stderr) => {
194
+ if (error && !allowFailure) {
195
+ error.stderr = stderr
196
+ reject(error)
197
+ return
198
+ }
199
+ resolve({ stdout: stdout || "", stderr: stderr || "", error })
200
+ }
201
+ )
202
+ if (typeof input === "string") {
203
+ child.stdin.end(input)
204
+ }
205
+ })
206
+ }
207
+
208
+ runGit(args, { cwd, kernel, ondata }) {
209
+ return new Promise((resolve, reject) => {
210
+ if (ondata) ondata({ raw: `git ${args.join(" ")}\r\n` })
211
+
212
+ const child = spawn("git", args, {
213
+ cwd,
214
+ env: this.gitEnv(kernel),
215
+ windowsHide: true
216
+ })
217
+ let stdout = ""
218
+ let stderr = ""
219
+
220
+ child.stdout.on("data", (chunk) => {
221
+ const raw = chunk.toString()
222
+ stdout += raw
223
+ if (ondata) ondata({ raw })
224
+ })
225
+ child.stderr.on("data", (chunk) => {
226
+ const raw = chunk.toString()
227
+ stderr += raw
228
+ if (ondata) ondata({ raw })
229
+ })
230
+ child.on("error", reject)
231
+ child.on("close", (code) => {
232
+ if (code === 0) {
233
+ resolve({ stdout, stderr })
234
+ } else {
235
+ const error = new Error(`git ${args[0]} failed with exit code ${code}`)
236
+ error.stdout = stdout
237
+ error.stderr = stderr
238
+ reject(error)
239
+ }
240
+ })
241
+ })
242
+ }
243
+
244
+ async getCredential(req, kernel) {
245
+ if (kernel && kernel.git && typeof kernel.git.ensureDefaults === "function") {
246
+ await kernel.git.ensureDefaults()
247
+ }
248
+
249
+ const result = await this.execGit(["credential", "fill"], {
250
+ cwd: (kernel && kernel.homedir) || this.cwd(req, kernel),
251
+ kernel,
252
+ input: "protocol=https\nhost=github.com\n\n",
253
+ timeout: 30000
254
+ })
255
+
256
+ const credential = Github.parseCredentialOutput(result.stdout)
257
+ if (!credential.password) {
258
+ throw new Error("GitHub is not connected. Open /github and connect GitHub first.")
259
+ }
260
+ return credential
261
+ }
262
+
263
+ async githubRequest(token, method, apiPath, body) {
264
+ const headers = {
265
+ "Accept": "application/vnd.github+json",
266
+ "Authorization": `Bearer ${token}`,
267
+ "User-Agent": "Pinokio",
268
+ "X-GitHub-Api-Version": "2022-11-28"
269
+ }
270
+ const options = { method, headers }
271
+ if (body) {
272
+ headers["Content-Type"] = "application/json"
273
+ options.body = JSON.stringify(body)
274
+ }
275
+
276
+ const response = await fetch(`https://api.github.com${apiPath}`, options)
277
+ const text = await response.text()
278
+ let data = null
279
+ if (text) {
280
+ try {
281
+ data = JSON.parse(text)
282
+ } catch (_) {
283
+ data = { message: text }
284
+ }
285
+ }
286
+
287
+ if (!response.ok) {
288
+ const message = data && data.message ? data.message : response.statusText
289
+ const details = data && Array.isArray(data.errors) && data.errors.length > 0
290
+ ? ` ${data.errors.map((error) => error.message || error.code || "").filter(Boolean).join(" ")}`
291
+ : ""
292
+ throw new Error(`GitHub request failed (${response.status}): ${message}${details}`)
293
+ }
294
+
295
+ return data
296
+ }
297
+
298
+ async getAuthenticatedUser(token) {
299
+ return this.githubRequest(token, "GET", "/user")
300
+ }
301
+
302
+ async remoteUrl(cwd, name, kernel) {
303
+ const result = await this.execGit(["remote", "get-url", name], {
304
+ cwd,
305
+ kernel,
306
+ allowFailure: true,
307
+ timeout: 10000
308
+ })
309
+ if (result.error) return ""
310
+ return result.stdout.trim()
311
+ }
312
+
313
+ async githubRemote(cwd, kernel) {
314
+ for (const name of ["upstream", "origin"]) {
315
+ const url = await this.remoteUrl(cwd, name, kernel)
316
+ const parsed = Github.parseGithubRemote(url)
317
+ if (parsed) return { ...parsed, remote: name, url }
318
+ }
319
+ throw new Error("No GitHub remote found. Add an origin or upstream remote first.")
320
+ }
321
+
322
+ async remoteExists(cwd, name, kernel) {
323
+ return Boolean(await this.remoteUrl(cwd, name, kernel))
324
+ }
325
+
326
+ async configureForkRemote({ cwd, kernel, ondata, source, forkRepo }) {
327
+ const forkRemote = Github.repoFromApi(forkRepo)
328
+ if (!forkRemote) {
329
+ throw new Error("GitHub fork response did not include a repository owner and name.")
330
+ }
331
+ const originUrl = await this.remoteUrl(cwd, "origin", kernel)
332
+ const origin = Github.parseGithubRemote(originUrl)
333
+ const cloneUrl = forkRepo.clone_url
334
+
335
+ if (!originUrl) {
336
+ if (ondata) ondata({ raw: `Adding origin remote for ${forkRepo.full_name}\r\n` })
337
+ await this.runGit(["remote", "add", "origin", cloneUrl], { cwd, kernel, ondata })
338
+ return
339
+ }
340
+
341
+ if (Github.repoEquals(origin, forkRemote)) {
342
+ if (ondata) ondata({ raw: `origin already points at ${forkRepo.full_name}\r\n` })
343
+ return
344
+ }
345
+
346
+ const remoteName = forkRemote.owner || "fork"
347
+ const existingRemoteUrl = await this.remoteUrl(cwd, remoteName, kernel)
348
+ if (existingRemoteUrl) {
349
+ const existingRemote = Github.parseGithubRemote(existingRemoteUrl)
350
+ if (Github.repoEquals(existingRemote, forkRemote)) {
351
+ if (ondata) ondata({ raw: `${remoteName} already points at ${forkRepo.full_name}\r\n` })
352
+ return
353
+ }
354
+ if (remoteName !== "fork" && !(await this.remoteExists(cwd, "fork", kernel))) {
355
+ if (ondata) ondata({ raw: `Adding fork remote for ${forkRepo.full_name}\r\n` })
356
+ await this.runGit(["remote", "add", "fork", cloneUrl], { cwd, kernel, ondata })
357
+ return
358
+ }
359
+ if (ondata) ondata({ raw: `${remoteName} remote already exists; leaving it unchanged\r\n` })
360
+ return
361
+ }
362
+
363
+ if (ondata) ondata({ raw: `Adding ${remoteName} remote for ${forkRepo.full_name}\r\n` })
364
+ await this.runGit(["remote", "add", remoteName, cloneUrl], { cwd, kernel, ondata })
365
+ }
366
+
367
+ async create(req, ondata, kernel) {
368
+ const params = this.params(req)
369
+ const cwd = this.cwd(req, kernel)
370
+ const credential = await this.getCredential(req, kernel)
371
+ const user = await this.getAuthenticatedUser(credential.password)
372
+ const visibility = Github.normalizeVisibility(params.visibility)
373
+ const repoName = Github.parseRepoName(params.name, cwd)
374
+ const targetRepo = {
375
+ owner: repoName.owner || String(user.login || ""),
376
+ repo: repoName.repo,
377
+ fullName: `${repoName.owner || String(user.login || "")}/${repoName.repo}`
378
+ }
379
+ const originUrl = await this.remoteUrl(cwd, "origin", kernel)
380
+ if (originUrl) {
381
+ const origin = Github.parseGithubRemote(originUrl)
382
+ if (!Github.repoEquals(origin, targetRepo)) {
383
+ throw new Error(`origin already points at ${originUrl}; refusing to push ${targetRepo.fullName}. Remove or update origin first.`)
384
+ }
385
+ }
386
+ const request = Github.buildCreateRepoRequest({
387
+ owner: repoName.owner,
388
+ repo: repoName.repo,
389
+ authenticatedUser: user,
390
+ visibility
391
+ })
392
+
393
+ if (ondata) ondata({ raw: `Creating GitHub repository ${repoName.owner ? `${repoName.owner}/` : ""}${repoName.repo}\r\n` })
394
+ const repo = await this.githubRequest(credential.password, "POST", request.path, request.body)
395
+
396
+ if (!originUrl) {
397
+ if (ondata) ondata({ raw: `Adding origin remote ${repo.clone_url}\r\n` })
398
+ await this.runGit(["remote", "add", "origin", repo.clone_url], { cwd, kernel, ondata })
399
+ } else if (ondata) {
400
+ ondata({ raw: `origin already exists: ${originUrl}\r\n` })
401
+ }
402
+
403
+ if (ondata) ondata({ raw: "Pushing current branch to origin\r\n" })
404
+ await this.runGit(["push", "-u", "origin", "HEAD"], { cwd, kernel, ondata })
405
+ if (ondata) ondata({ raw: `Created ${repo.html_url}\r\n` })
406
+
407
+ return repo
408
+ }
409
+
410
+ async fork(req, ondata, kernel) {
411
+ const params = this.params(req)
412
+ const cwd = this.cwd(req, kernel)
413
+ const credential = await this.getCredential(req, kernel)
414
+ const source = await this.githubRemote(cwd, kernel)
415
+ const body = {}
416
+
417
+ const requestedName = Github.cleanParam(params.name)
418
+ if (requestedName) {
419
+ const name = requestedName
420
+ Github.validateRepoName(name)
421
+ body.name = name
422
+ }
423
+ const requestedOrg = Github.cleanParam(params.org)
424
+ if (requestedOrg) {
425
+ const org = requestedOrg
426
+ Github.validateOwner(org)
427
+ body.organization = org
428
+ }
429
+
430
+ if (ondata) ondata({ raw: `Forking ${source.fullName}\r\n` })
431
+ const forkRepo = await this.githubRequest(
432
+ credential.password,
433
+ "POST",
434
+ `/repos/${encodeURIComponent(source.owner)}/${encodeURIComponent(source.repo)}/forks`,
435
+ body
436
+ )
437
+
438
+ await this.configureForkRemote({ cwd, kernel, ondata, source, forkRepo })
439
+ if (ondata) ondata({ raw: `Forked ${forkRepo.html_url}\r\n` })
440
+ return forkRepo
441
+ }
442
+ }
443
+
444
+ module.exports = Github