orizu 0.4.0 → 0.5.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/README.md +22 -13
- package/dist/artifact-archive.js +152 -0
- package/dist/connectors-cli.js +80 -0
- package/dist/credentials.js +4 -1
- package/dist/daytona-slice-rehearsal.js +255 -0
- package/dist/daytona-slice.js +441 -0
- package/dist/egress-allowlist-cli.js +132 -0
- package/dist/egress-policy.js +159 -0
- package/dist/git-credential.js +131 -0
- package/dist/github-setup.js +226 -0
- package/dist/help.js +276 -21
- package/dist/hosted-bootstrap-rehearsal.js +235 -0
- package/dist/hosted-bootstrap.js +365 -0
- package/dist/hosted-broker-server.js +220 -0
- package/dist/hosted-git-http-server.js +136 -0
- package/dist/hosted-harness-claude.js +493 -0
- package/dist/hosted-harness-opencode.js +729 -0
- package/dist/hosted-harness.js +60 -0
- package/dist/hosted-loop.js +477 -0
- package/dist/hosted-run-event-sink.js +499 -0
- package/dist/hosted-runtime-assets.js +216 -0
- package/dist/hosted-session-cli.js +642 -0
- package/dist/index.js +945 -258
- package/dist/kill-switch-cli.js +89 -0
- package/dist/manifests-cli.js +143 -0
- package/dist/preview-runtime.js +4 -1
- package/dist/sandbox-provider.js +238 -0
- package/dist/secret-redaction.js +112 -0
- package/dist/session-timeline.js +103 -0
- package/dist/setup-prompt.js +4 -2
- package/dist/skill-installer.js +9 -1
- package/dist/smoke.js +250 -0
- package/dist/task-assignment-manifest.js +96 -0
- package/dist/vercel-sandbox-provider.js +260 -0
- package/dist/workbench-cli.js +493 -0
- package/dist/workspace-sync.js +924 -0
- package/dist/workspace.js +868 -57
- package/package.json +4 -1
- package/src/artifact-archive.ts +182 -0
- package/src/connectors-cli.ts +115 -0
- package/src/credentials.ts +6 -2
- package/src/daytona-slice-rehearsal.ts +320 -0
- package/src/daytona-slice.ts +598 -0
- package/src/egress-allowlist-cli.ts +147 -0
- package/src/egress-policy.ts +188 -0
- package/src/git-credential.ts +155 -0
- package/src/github-setup.ts +366 -0
- package/src/help.ts +277 -21
- package/src/hosted-bootstrap-rehearsal.ts +312 -0
- package/src/hosted-bootstrap.ts +571 -0
- package/src/hosted-broker-server.ts +278 -0
- package/src/hosted-git-http-server.ts +164 -0
- package/src/hosted-harness-claude.ts +621 -0
- package/src/hosted-harness-opencode.ts +823 -0
- package/src/hosted-harness.ts +124 -0
- package/src/hosted-loop.ts +680 -0
- package/src/hosted-run-event-sink.ts +632 -0
- package/src/hosted-runtime-assets.ts +272 -0
- package/src/hosted-session-cli.ts +844 -0
- package/src/index.ts +1102 -317
- package/src/kill-switch-cli.ts +113 -0
- package/src/manifests-cli.ts +168 -0
- package/src/preview-runtime.ts +14 -9
- package/src/sandbox-provider.ts +440 -0
- package/src/secret-redaction.ts +124 -0
- package/src/session-timeline.ts +152 -0
- package/src/setup-prompt.ts +4 -2
- package/src/skill-installer.ts +13 -1
- package/src/smoke.ts +342 -0
- package/src/task-assignment-manifest.ts +164 -0
- package/src/vercel-sandbox-provider.ts +388 -0
- package/src/workbench-cli.ts +701 -0
- package/src/workspace-sync.ts +1148 -0
- package/src/workspace.ts +1051 -64
- package/vendor/skills/orizu-cli/SKILL.md +8 -4
- package/vendor/skills/orizu-cli/references/cli-reference.md +12 -4
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub link + hosted-repo attach for `orizu setup` (ALI-971 / WS-C + WS-D).
|
|
3
|
+
*
|
|
4
|
+
* Pure logic + injected HTTP/git/io so index.ts stays thin and the flows are
|
|
5
|
+
* unit-testable without a real browser, server, or git. Two responsibilities:
|
|
6
|
+
*
|
|
7
|
+
* 1. `runGithubLink` — the REQUIRED team-setup link step: POST the link route,
|
|
8
|
+
* open/print the App-install URL, then poll until the org is connected
|
|
9
|
+
* (with a timeout + resume guidance).
|
|
10
|
+
* 2. `runHostedAttach` — clone the provisioned workbench repo and wire the
|
|
11
|
+
* repo-local git credential helper so future pull/push flow through the
|
|
12
|
+
* Orizu broker. Never stores a long-lived credential: the clone uses a
|
|
13
|
+
* one-shot brokered token embedded only in the transient clone URL, which
|
|
14
|
+
* is immediately rewritten to a clean origin.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { spawnSync } from 'child_process'
|
|
18
|
+
import { mkdirSync } from 'fs'
|
|
19
|
+
import { join } from 'path'
|
|
20
|
+
|
|
21
|
+
import { authedFetch } from './http.js'
|
|
22
|
+
|
|
23
|
+
export type SetupFetcher = (path: string, init?: RequestInit) => Promise<Response>
|
|
24
|
+
|
|
25
|
+
export interface GithubLinkIo {
|
|
26
|
+
print: (line: string) => void
|
|
27
|
+
fetcher?: SetupFetcher
|
|
28
|
+
openUrl?: (url: string) => void
|
|
29
|
+
sleep?: (ms: number) => Promise<void>
|
|
30
|
+
now?: () => number
|
|
31
|
+
pollIntervalMs?: number
|
|
32
|
+
pollTimeoutMs?: number
|
|
33
|
+
/**
|
|
34
|
+
* Second-factor confirmation of the bound org (confused-deputy defense). The
|
|
35
|
+
* callback binds the installation but parks it in `pending_confirmation`; this
|
|
36
|
+
* asks the human to confirm the org they were shown before it becomes active.
|
|
37
|
+
*/
|
|
38
|
+
confirm?: (orgLogin: string | null) => Promise<boolean>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface GithubLinkResult {
|
|
42
|
+
status: 'active'
|
|
43
|
+
orgLogin: string | null
|
|
44
|
+
alreadyLinked: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const DEFAULT_POLL_INTERVAL_MS = 2000
|
|
48
|
+
const DEFAULT_POLL_TIMEOUT_MS = 180000
|
|
49
|
+
|
|
50
|
+
function defaultSleep(ms: number): Promise<void> {
|
|
51
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function readError(response: Response): Promise<string> {
|
|
55
|
+
try {
|
|
56
|
+
const body = (await response.json()) as { error?: string }
|
|
57
|
+
if (body.error) return body.error
|
|
58
|
+
} catch {
|
|
59
|
+
// fall through to status text
|
|
60
|
+
}
|
|
61
|
+
return `status ${response.status}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Begin (or resume) the required GitHub link for a team and block until the
|
|
66
|
+
* customer org is connected. Throws with resume guidance on timeout.
|
|
67
|
+
*/
|
|
68
|
+
export async function runGithubLink(teamSlug: string, io: GithubLinkIo): Promise<GithubLinkResult> {
|
|
69
|
+
const fetcher = io.fetcher ?? authedFetch
|
|
70
|
+
|
|
71
|
+
const start = await fetcher('/api/cli/github/link', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
body: JSON.stringify({ teamSlug }),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (start.status === 409) {
|
|
78
|
+
io.print(' GitHub org already connected for this team.')
|
|
79
|
+
return { status: 'active', orgLogin: null, alreadyLinked: true }
|
|
80
|
+
}
|
|
81
|
+
if (!start.ok) {
|
|
82
|
+
throw new Error(`Failed to start GitHub link: ${await readError(start)}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { installUrl, state } = (await start.json()) as { installUrl: string; state: string }
|
|
86
|
+
|
|
87
|
+
io.print(' Connect your team GitHub org by installing the Orizu GitHub App:')
|
|
88
|
+
io.print(` ${installUrl}`)
|
|
89
|
+
if (io.openUrl) {
|
|
90
|
+
io.openUrl(installUrl)
|
|
91
|
+
io.print(' Opened the install page in your browser. Waiting for you to finish...')
|
|
92
|
+
} else {
|
|
93
|
+
io.print(' Open the URL above, install the App, then return here. Waiting...')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const now = io.now ?? (() => Date.now())
|
|
97
|
+
const sleep = io.sleep ?? defaultSleep
|
|
98
|
+
const interval = io.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS
|
|
99
|
+
const timeout = io.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS
|
|
100
|
+
const deadline = now() + timeout
|
|
101
|
+
|
|
102
|
+
for (;;) {
|
|
103
|
+
const poll = await fetcher(`/api/cli/github/link?state=${encodeURIComponent(state)}`)
|
|
104
|
+
if (poll.ok) {
|
|
105
|
+
const data = (await poll.json()) as { status: string; orgLogin?: string | null }
|
|
106
|
+
if (data.status === 'active') {
|
|
107
|
+
io.print(` GitHub org connected${data.orgLogin ? ` (${data.orgLogin})` : ''}.`)
|
|
108
|
+
return { status: 'active', orgLogin: data.orgLogin ?? null, alreadyLinked: false }
|
|
109
|
+
}
|
|
110
|
+
if (data.status === 'pending_confirmation') {
|
|
111
|
+
return await confirmLink(teamSlug, data.orgLogin ?? null, io, fetcher)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (now() >= deadline) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
'Timed out waiting for the GitHub App install to complete. ' +
|
|
117
|
+
`Resume with \`orizu github link --team ${teamSlug}\` after finishing the install.`
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
await sleep(interval)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The install callback bound an org but left the row `pending_confirmation`.
|
|
126
|
+
* Confirm the org with the human (second factor), then complete via the
|
|
127
|
+
* authenticated confirm endpoint — this is the only path to `active`.
|
|
128
|
+
*/
|
|
129
|
+
async function confirmLink(
|
|
130
|
+
teamSlug: string,
|
|
131
|
+
orgLogin: string | null,
|
|
132
|
+
io: GithubLinkIo,
|
|
133
|
+
fetcher: SetupFetcher
|
|
134
|
+
): Promise<GithubLinkResult> {
|
|
135
|
+
io.print(` GitHub reported installation on org: ${orgLogin ?? '(unknown)'}`)
|
|
136
|
+
if (!io.confirm) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Org ${orgLogin ?? '(unknown)'} is bound but needs confirmation. ` +
|
|
139
|
+
`Run \`orizu github link --team ${teamSlug}\` interactively to confirm.`
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
const approved = await io.confirm(orgLogin)
|
|
143
|
+
if (!approved) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Did not confirm org ${orgLogin ?? '(unknown)'}. ` +
|
|
146
|
+
`Re-run \`orizu github link --team ${teamSlug}\` to link the correct org.`
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
const resp = await fetcher('/api/cli/github/link/confirm', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: { 'Content-Type': 'application/json' },
|
|
152
|
+
body: JSON.stringify({ teamSlug, orgLogin }),
|
|
153
|
+
})
|
|
154
|
+
if (!resp.ok) {
|
|
155
|
+
throw new Error(`Failed to confirm GitHub link: ${await readError(resp)}`)
|
|
156
|
+
}
|
|
157
|
+
const confirmed = (await resp.json()) as { orgLogin?: string | null }
|
|
158
|
+
io.print(` GitHub org connected (${confirmed.orgLogin ?? orgLogin ?? 'linked'}).`)
|
|
159
|
+
return { status: 'active', orgLogin: confirmed.orgLogin ?? orgLogin, alreadyLinked: false }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// -- Hosted attach ----------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
export type SetupMode = 'local' | 'attach' | 'provision-then-attach'
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Decide how `orizu setup` should materialize the workspace. `--local` (or the
|
|
168
|
+
* absence of a linked org) keeps Phase 1 scaffolding; a linked org with a
|
|
169
|
+
* provisioned repo attaches by clone; a linked org without a repo provisions
|
|
170
|
+
* first, then attaches.
|
|
171
|
+
*/
|
|
172
|
+
export function decideSetupMode(input: {
|
|
173
|
+
local: boolean
|
|
174
|
+
activeInstallation: boolean
|
|
175
|
+
repoFullName: string | null
|
|
176
|
+
}): SetupMode {
|
|
177
|
+
if (input.local || !input.activeInstallation) return 'local'
|
|
178
|
+
return input.repoFullName ? 'attach' : 'provision-then-attach'
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface GitRunResult {
|
|
182
|
+
status: number
|
|
183
|
+
stderr?: string
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface GitRunOpts {
|
|
187
|
+
cwd?: string
|
|
188
|
+
env?: Record<string, string>
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type GitRunner = (args: string[], opts?: GitRunOpts) => GitRunResult
|
|
192
|
+
|
|
193
|
+
export interface HostedAttachIo {
|
|
194
|
+
print: (line: string) => void
|
|
195
|
+
fetcher?: SetupFetcher
|
|
196
|
+
git: GitRunner
|
|
197
|
+
/** Overridable for tests; defaults to creating `.orizu/` on disk. */
|
|
198
|
+
ensureOrizuDir?: (repoDir: string) => void
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface HostedAttachOptions {
|
|
202
|
+
workspaceId: string
|
|
203
|
+
repoFullName: string | null
|
|
204
|
+
targetDir: string
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface HostedAttachResult {
|
|
208
|
+
repoFullName: string
|
|
209
|
+
provisioned: boolean
|
|
210
|
+
targetDir: string
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function gitOrThrow(git: GitRunner, args: string[], opts?: GitRunOpts) {
|
|
214
|
+
const result = git(args, opts)
|
|
215
|
+
if (result.status !== 0) {
|
|
216
|
+
throw new Error(`git ${args[0]} failed: ${result.stderr || `exit ${result.status}`}`)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const CREDENTIAL_HELPER = '!orizu git-credential'
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Provision-if-needed, then clone with the Orizu credential helper already
|
|
224
|
+
* configured so the brokered token NEVER touches argv, the clone URL, or
|
|
225
|
+
* .git/config. The helper resolves the workspace during clone via the
|
|
226
|
+
* `ORIZU_WORKSPACE_ID` env var (the working tree — and orizu.team.json — does
|
|
227
|
+
* not exist yet), then mints on demand. Repo-local config persists the helper
|
|
228
|
+
* for subsequent pull/push, which resolve the workspace from cwd.
|
|
229
|
+
*/
|
|
230
|
+
export async function runHostedAttach(
|
|
231
|
+
options: HostedAttachOptions,
|
|
232
|
+
io: HostedAttachIo
|
|
233
|
+
): Promise<HostedAttachResult> {
|
|
234
|
+
const fetcher = io.fetcher ?? authedFetch
|
|
235
|
+
let repoFullName = options.repoFullName
|
|
236
|
+
let provisioned = false
|
|
237
|
+
|
|
238
|
+
if (!repoFullName) {
|
|
239
|
+
io.print(' Provisioning the hosted workbench repo...')
|
|
240
|
+
const response = await fetcher(
|
|
241
|
+
`/api/cli/workspaces/${encodeURIComponent(options.workspaceId)}/provision-repo`,
|
|
242
|
+
{
|
|
243
|
+
method: 'POST',
|
|
244
|
+
headers: { 'Content-Type': 'application/json' },
|
|
245
|
+
body: JSON.stringify({}),
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
throw new Error(`Failed to provision workbench repo: ${await readError(response)}`)
|
|
250
|
+
}
|
|
251
|
+
const data = (await response.json()) as { repo: string }
|
|
252
|
+
repoFullName = data.repo
|
|
253
|
+
provisioned = true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const cleanUrl = `https://github.com/${repoFullName}.git`
|
|
257
|
+
|
|
258
|
+
io.print(` Cloning ${repoFullName}...`)
|
|
259
|
+
// Clone with the helper inline + the workspace id in the env so the token is
|
|
260
|
+
// brokered on demand and never materializes in the URL or on disk.
|
|
261
|
+
gitOrThrow(
|
|
262
|
+
io.git,
|
|
263
|
+
[
|
|
264
|
+
'-c',
|
|
265
|
+
`credential.helper=${CREDENTIAL_HELPER}`,
|
|
266
|
+
'-c',
|
|
267
|
+
'credential.useHttpPath=true',
|
|
268
|
+
'clone',
|
|
269
|
+
cleanUrl,
|
|
270
|
+
options.targetDir,
|
|
271
|
+
],
|
|
272
|
+
{ env: { ORIZU_WORKSPACE_ID: options.workspaceId } }
|
|
273
|
+
)
|
|
274
|
+
// Persist the helper repo-locally for future pull/push (cwd-resolved).
|
|
275
|
+
gitOrThrow(io.git, ['config', 'credential.helper', CREDENTIAL_HELPER], { cwd: options.targetDir })
|
|
276
|
+
gitOrThrow(io.git, ['config', 'credential.useHttpPath', 'true'], { cwd: options.targetDir })
|
|
277
|
+
|
|
278
|
+
const ensureOrizuDir = io.ensureOrizuDir ?? ((repoDir: string) => mkdirSync(join(repoDir, '.orizu'), { recursive: true }))
|
|
279
|
+
ensureOrizuDir(options.targetDir)
|
|
280
|
+
|
|
281
|
+
io.print(' Attached. Configured the Orizu credential helper for git.')
|
|
282
|
+
return { repoFullName, provisioned, targetDir: options.targetDir }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// -- Interactive setup dispatch (ALI-996 / WS-C crumb) ----------------------
|
|
286
|
+
|
|
287
|
+
function defaultGitRunner(args: string[], opts?: { cwd?: string }): GitRunResult {
|
|
288
|
+
// Strip repo-context env (set by git when running inside hooks) so the
|
|
289
|
+
// child git is scoped strictly to cwd/args — see daytona-slice-rehearsal.ts.
|
|
290
|
+
const env: NodeJS.ProcessEnv = { ...process.env }
|
|
291
|
+
delete env.GIT_DIR
|
|
292
|
+
delete env.GIT_WORK_TREE
|
|
293
|
+
delete env.GIT_INDEX_FILE
|
|
294
|
+
const result = spawnSync('git', args, { cwd: opts?.cwd, encoding: 'utf8', env })
|
|
295
|
+
return { status: result.status ?? 1, stderr: result.stderr }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface InteractiveHostedSetupInput {
|
|
299
|
+
teamSlug: string
|
|
300
|
+
targetDir: string
|
|
301
|
+
/** The `--local` flag: forces Phase-1 scaffolding regardless of linkage. */
|
|
302
|
+
local: boolean
|
|
303
|
+
/** True once the required github-link step has connected an active org. */
|
|
304
|
+
activeInstallation: boolean
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export interface InteractiveHostedSetupIo {
|
|
308
|
+
print: (line: string) => void
|
|
309
|
+
fetcher?: SetupFetcher
|
|
310
|
+
git?: GitRunner
|
|
311
|
+
ensureOrizuDir?: (repoDir: string) => void
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface InteractiveHostedSetupResult {
|
|
315
|
+
mode: SetupMode
|
|
316
|
+
/** True when the hosted repo was cloned; false means the caller scaffolds locally. */
|
|
317
|
+
attached: boolean
|
|
318
|
+
repoFullName: string | null
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Bridge the required github-link step to the hosted attach. Create-or-attaches
|
|
323
|
+
* the server workspace (whose response now surfaces repoProvider/repoFullName —
|
|
324
|
+
* ALI-972), then routes through the already-tested `decideSetupMode`:
|
|
325
|
+
* - `local` → returns `attached: false`; the caller scaffolds.
|
|
326
|
+
* - `attach` / `provision-then-attach` → clones (provisioning first if needed)
|
|
327
|
+
* via `runHostedAttach` and returns `attached: true`.
|
|
328
|
+
*/
|
|
329
|
+
export async function runInteractiveHostedSetup(
|
|
330
|
+
input: InteractiveHostedSetupInput,
|
|
331
|
+
io: InteractiveHostedSetupIo
|
|
332
|
+
): Promise<InteractiveHostedSetupResult> {
|
|
333
|
+
const fetcher = io.fetcher ?? authedFetch
|
|
334
|
+
|
|
335
|
+
const response = await fetcher('/api/cli/workspaces', {
|
|
336
|
+
method: 'POST',
|
|
337
|
+
headers: { 'Content-Type': 'application/json' },
|
|
338
|
+
body: JSON.stringify({ teamSlug: input.teamSlug, name: input.teamSlug, slug: input.teamSlug }),
|
|
339
|
+
})
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
throw new Error(`Failed to attach workspace: ${await readError(response)}`)
|
|
342
|
+
}
|
|
343
|
+
const data = (await response.json()) as {
|
|
344
|
+
workspace?: { id?: string; repoFullName?: string | null }
|
|
345
|
+
}
|
|
346
|
+
const workspace = data.workspace
|
|
347
|
+
if (!workspace?.id) {
|
|
348
|
+
throw new Error('Workspace attach response did not include an id')
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const repoFullName = workspace.repoFullName ?? null
|
|
352
|
+
const mode = decideSetupMode({
|
|
353
|
+
local: input.local,
|
|
354
|
+
activeInstallation: input.activeInstallation,
|
|
355
|
+
repoFullName,
|
|
356
|
+
})
|
|
357
|
+
if (mode === 'local') {
|
|
358
|
+
return { mode, attached: false, repoFullName: null }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const result = await runHostedAttach(
|
|
362
|
+
{ workspaceId: workspace.id, repoFullName, targetDir: input.targetDir },
|
|
363
|
+
{ print: io.print, fetcher, git: io.git ?? defaultGitRunner, ensureOrizuDir: io.ensureOrizuDir }
|
|
364
|
+
)
|
|
365
|
+
return { mode, attached: true, repoFullName: result.repoFullName }
|
|
366
|
+
}
|