@gizai/cli 1.0.1 → 1.2.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 +133 -4
- package/cli/codex-relay.mjs +46 -0
- package/cli/computer-identity.mjs +76 -0
- package/cli/engine.mjs +58 -0
- package/cli/giz.mjs +602 -249
- package/package.json +10 -6
- package/skills/giz/SKILL.md +68 -4
- package/skills/giz/references/operations.md +9 -6
- package/skills/giz/references/slides.md +95 -0
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Giz CLI
|
|
2
2
|
|
|
3
|
-
Connect once in the browser, then use
|
|
3
|
+
Connect once in the browser, then use live Giz text and media models, Drive, and Computer from a terminal or AI agent.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install -g @gizai/cli
|
|
7
7
|
giz auth login
|
|
8
|
+
giz chat gpt-5-6-luna "Say hello in Korean"
|
|
8
9
|
giz generate image "A paper observatory at blue hour"
|
|
9
10
|
```
|
|
10
11
|
|
|
@@ -18,20 +19,148 @@ giz model run ltx2-distilled \
|
|
|
18
19
|
--upload image=./first-frame.png
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
MCP for Claude Code and Codex (install the chosen client first):
|
|
22
23
|
|
|
23
24
|
```bash
|
|
25
|
+
giz auth login
|
|
24
26
|
giz mcp add codex
|
|
25
27
|
giz mcp add claude
|
|
28
|
+
giz mcp check --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
These commands register `giz mcp serve` over stdio. It forwards the server's live
|
|
32
|
+
MCP protocol and shares the CLI's rotating OAuth credentials. Tokens are not
|
|
33
|
+
copied into Claude or Codex configuration. A custom `GIZ_CONFIG` path is preserved.
|
|
34
|
+
|
|
35
|
+
For clients with their own OAuth session, use `giz mcp add claude --transport http`
|
|
36
|
+
and sign in through `/mcp`, or `giz mcp add codex --transport http`. The remote
|
|
37
|
+
endpoint also supports OAuth discovery and scoped API keys. A manually copied
|
|
38
|
+
OAuth access token expires after one hour; updating CLI credentials does not
|
|
39
|
+
update a static client header.
|
|
40
|
+
|
|
41
|
+
For a headless Claude Code check, after login and registration:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude -p 'Use model_list to list one Giz image model.' --allowedTools mcp__giz__model_list
|
|
26
45
|
```
|
|
27
46
|
|
|
47
|
+
`giz mcp check --json` checks the MCP handshake and lists actual tool names without
|
|
48
|
+
running a model. Claude's `--verbose --output-format stream-json` also shows MCP
|
|
49
|
+
connection state. If registration reports an existing `giz` entry, remove that
|
|
50
|
+
entry with the client's `mcp remove` command before adding the desired transport.
|
|
51
|
+
The exact client failure is returned on stderr.
|
|
52
|
+
|
|
28
53
|
Agent Skill:
|
|
29
54
|
|
|
30
55
|
```bash
|
|
31
56
|
giz skill install
|
|
32
57
|
```
|
|
33
58
|
|
|
34
|
-
The CLI stores rotating OAuth credentials
|
|
35
|
-
|
|
59
|
+
The CLI stores rotating OAuth credentials as unencrypted JSON in
|
|
60
|
+
`~/.config/giz/credentials.json` with mode `0600` (owner access only), using atomic
|
|
61
|
+
writes and serialized sign-in, refresh, and sign-out. Failed token revocation is
|
|
62
|
+
reported before local credentials are removed. It never stores a Giz password.
|
|
63
|
+
The sign-in callback binds a fresh loopback port; use the URL printed by the
|
|
64
|
+
current login. Progress goes to stderr, leaving `--json` stdout machine-readable.
|
|
65
|
+
|
|
66
|
+
After updating from 1.0.1, run `giz auth login` again to consent to `giz.inference`.
|
|
67
|
+
Refreshing an old grant cannot add this scope. `giz auth status --json` shows
|
|
68
|
+
granted scopes; a missing scope error names the required permission.
|
|
69
|
+
|
|
70
|
+
`giz computer exec --deadline-ms 120000 -- COMMAND ARGS...` uses the bounded
|
|
71
|
+
Computer command API (2 minutes by default, up to 15 minutes). The CLI preserves
|
|
72
|
+
the remote command's exit code; `giz job wait` exits with status 1 for failed or
|
|
73
|
+
cancelled jobs. JSON results remain available on stdout. Free Computers have a runtime limit; check the current
|
|
74
|
+
Computer plan before starting long workflows. MCP authentication does not extend
|
|
75
|
+
that runtime. Media jobs run independently and are tracked with `giz job get`.
|
|
76
|
+
|
|
77
|
+
Create a scoped API key with `giz api-key create "My app"`; its secret is shown once. Set `GIZ_TOKEN` only for ephemeral automation. Run `giz --help` for chat, models, Drive, Computer, job, upload, and raw tool commands.
|
|
36
78
|
|
|
37
79
|
Documentation: <https://www.giz.ai/mcp/>
|
|
80
|
+
|
|
81
|
+
### Managed Giz Computers
|
|
82
|
+
|
|
83
|
+
Giz Computers receive the same CLI source through the Region release overlay, at
|
|
84
|
+
`/opt/giz/managed/apps/giz-cli`, with `/opt/giz/managed/bin/giz` first on PATH.
|
|
85
|
+
No npm installation or browser login is needed inside a managed Computer:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
giz auth status
|
|
89
|
+
giz drive ls
|
|
90
|
+
giz model list
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The Region owns `/opt/giz/identity/identity.sock` and exchanges the Computer identity for a
|
|
94
|
+
15-minute access token. For this release only, when the new socket path is absent,
|
|
95
|
+
the CLI reads `/run/giz/identity.sock` to support an overlay arriving before its
|
|
96
|
+
gateway update. No environment variable can override the socket path. The CLI
|
|
97
|
+
caches it only in memory; it never saves a refresh
|
|
98
|
+
token or API key for this identity. `giz auth status` displays
|
|
99
|
+
`컴퓨터 신원으로 로그인됨 (소유자 <email>)`. A saved `giz auth login` takes priority;
|
|
100
|
+
`giz auth logout` removes that user login and returns to the Computer identity.
|
|
101
|
+
Shared Computers can access only their own Drive, model discovery/inference, and
|
|
102
|
+
jobs started by that Computer. API key, billing, organization administration,
|
|
103
|
+
destructive operations and external writes require a separate user authorization.
|
|
104
|
+
The managed `giz --version` includes the Region source revision; npm installations
|
|
105
|
+
continue to report the package version. Existing Computers receive overlay updates
|
|
106
|
+
without recreation. The Region's normal release activation attaches identity sockets
|
|
107
|
+
for existing instances, and socket replacement remains visible through the directory mount.
|
|
108
|
+
|
|
109
|
+
App 변경은 `giz app list`에서 확인한 `revision`을 명시적으로 전달합니다.
|
|
110
|
+
`run`, `publish`, `delete`에는 `--expected-revision REVISION`이 필요합니다.
|
|
111
|
+
`create`/`save`의 `--input`에는 `id`와 `expectedRevision`을 넣습니다(새 앱은 0).
|
|
112
|
+
목록을 다시 읽어 개정 번호를 자동 대체하지 않으므로, 검토 뒤 다른 변경이 생기면 서버가 충돌을 거부합니다.
|
|
113
|
+
|
|
114
|
+
아래 표는 CLI 디스패처와 도구 item에서 생성합니다. 갱신 명령:
|
|
115
|
+
`GIZ_UPDATE_CLI_SURFACE=1 pnpm --filter giz-app-giz test --runInBand --runTestsByPath server/external/cli-surface.spec.ts`.
|
|
116
|
+
일반 CI 실행은 문서를 쓰지 않고 표의 차이를 실패로 보고합니다.
|
|
117
|
+
|
|
118
|
+
<!-- cli-surface:start -->
|
|
119
|
+
| CLI 명령 | 도구 ID | MCP 이름 | 필수 입력 | scope | Agent 발견 |
|
|
120
|
+
| --- | --- | --- | --- | --- | --- |
|
|
121
|
+
| model list | model.list | model_list | - | giz.read | model.list |
|
|
122
|
+
| model get | model.get | model_get | model | giz.read | model.get |
|
|
123
|
+
| model run | model.run | model_run | model, input | giz.inference | model.run |
|
|
124
|
+
| generate image | generateImage | generateImage | prompt | giz.write | generateImage |
|
|
125
|
+
| generate video | generateVideo | generateVideo | prompt | giz.write | generateVideo |
|
|
126
|
+
| generate music | generateMusic | generateMusic | prompt | giz.write | generateMusic |
|
|
127
|
+
| generate talking-head | generateTalkingHead | generateTalkingHead | prompt, references | giz.write | generateTalkingHead |
|
|
128
|
+
| generate audio | generateAudio | generateAudio | purpose | giz.write | generateAudio |
|
|
129
|
+
| job list | job.list | job_list | - | giz.read | server job |
|
|
130
|
+
| job get | job.get | job_get | job | giz.read | server job |
|
|
131
|
+
| job wait | job.get | job_get | job | giz.read | server job |
|
|
132
|
+
| job cancel | job.cancel | job_cancel | job | giz.destructive | server job |
|
|
133
|
+
| drive ls | drive.list | drive_list | - | giz.read | drive.list |
|
|
134
|
+
| drive list | drive.list | drive_list | - | giz.read | drive.list |
|
|
135
|
+
| drive search | drive.search | drive_search | query | giz.read | drive.search |
|
|
136
|
+
| drive read | drive.readFile | drive_readFile | path | giz.read | drive.readFile |
|
|
137
|
+
| drive write | drive.writeFile | drive_writeFile | path, content | giz.write | drive.writeFile |
|
|
138
|
+
| drive mkdir | drive.makeDirectory | drive_makeDirectory | path | giz.write | drive.makeDirectory |
|
|
139
|
+
| drive move | drive.movePath | drive_movePath | from, to | giz.write | drive.movePath |
|
|
140
|
+
| drive delete | drive.deletePath | drive_deletePath | path | giz.destructive | drive.deletePath |
|
|
141
|
+
| table list | workspace.objects | workspace_objects | - | giz.read | workspace.objects |
|
|
142
|
+
| table get | workspace.objects | workspace_objects | - | giz.read | workspace.objects |
|
|
143
|
+
| table read | table.readRows | table_readRows | table | giz.read | table.readRows |
|
|
144
|
+
| table write | table.writeRows | table_writeRows | table | giz.write | table.writeRows |
|
|
145
|
+
| table action | workspace.executeAction | workspace_executeAction | view, action | giz.execute | workspace.executeAction |
|
|
146
|
+
| computer list | computer.list | computer_list | - | giz.read | computer.list |
|
|
147
|
+
| computer get | computer.get | computer_get | - | giz.read | external only |
|
|
148
|
+
| computer start | computer.start | computer_start | - | giz.execute | external only |
|
|
149
|
+
| computer stop | computer.stop | computer_stop | - | giz.destructive | external only |
|
|
150
|
+
| computer exec | computer.exec | giz_computer_exec | - | giz.execute | computer.exec |
|
|
151
|
+
| app list | app.list | app_list | - | giz.read | app.list |
|
|
152
|
+
| app create | app.save | app_save | id, expectedRevision | giz.execute | app.save |
|
|
153
|
+
| app save | app.save | app_save | id, expectedRevision | giz.execute | app.save |
|
|
154
|
+
| app run | app.run | app_run | id, expectedRevision | giz.execute | app.run |
|
|
155
|
+
| app publish | app.publish | app_publish | id, expectedRevision, visibility | giz.write | app.publish |
|
|
156
|
+
| app open | app.open | app_open | id | giz.read | app.open |
|
|
157
|
+
| app delete | app.delete | app_delete | id, expectedRevision | giz.destructive | app.delete |
|
|
158
|
+
| capability list | capability.status | capability_status | - | giz.read | capability.status |
|
|
159
|
+
| capability search | capability.catalog | capability_catalog | kind, query | giz.read | capability.catalog |
|
|
160
|
+
| capability install | capability.install | capability_install | id, kind, query | giz.execute | capability.install |
|
|
161
|
+
| capability update | capability.manage | capability_manage | id, operation | giz.destructive | capability.manage |
|
|
162
|
+
| capability remove | capability.manage | capability_manage | id, operation | giz.destructive | capability.manage |
|
|
163
|
+
| agent start | agent.start | agent_start | agentId, prompt | giz.execute | external only |
|
|
164
|
+
| agent status | agent.status | agent_status | turnId | giz.read | agent.status |
|
|
165
|
+
| agent cancel | agent.cancel | agent_cancel | turnId | giz.execute | agent.cancel |
|
|
166
|
+
<!-- cli-surface:end -->
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Runs ONLY inside the user's Computer. stdout is the authenticated gateway wire,
|
|
2
|
+
// never credentials or provider error bodies. No listener or separate credential store.
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { homedir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { execFileSync } from 'node:child_process'
|
|
7
|
+
|
|
8
|
+
const emit = value => process.stdout.write(JSON.stringify(value) + '\n')
|
|
9
|
+
try {
|
|
10
|
+
const auth = JSON.parse(await readFile(join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'auth.json'), 'utf8'))
|
|
11
|
+
const token = auth.tokens?.access_token
|
|
12
|
+
const account = auth.tokens?.account_id
|
|
13
|
+
if (auth.OPENAI_API_KEY || !token || !account) {
|
|
14
|
+
emit({ status: 401 }); process.exitCode = 1
|
|
15
|
+
} else {
|
|
16
|
+
const headers = { authorization: `Bearer ${token}`, 'chatgpt-account-id': account, 'content-type': 'application/json', originator: 'codex_cli_rs' }
|
|
17
|
+
const mode = process.argv[2]
|
|
18
|
+
if (mode === 'models') {
|
|
19
|
+
const version = execFileSync('codex', ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split(' ').at(-1)
|
|
20
|
+
const response = await fetch(`https://chatgpt.com/backend-api/codex/models?client_version=${encodeURIComponent(version)}`, { headers, signal: AbortSignal.timeout(30000) })
|
|
21
|
+
if (!response.ok) emit({ status: response.status })
|
|
22
|
+
else {
|
|
23
|
+
const payload = await response.json()
|
|
24
|
+
emit({ status: 200, models: (payload.models || []).filter(model => model.visibility !== 'hide').map(model => ({ id: model.slug, label: model.display_name || model.slug })).filter(model => typeof model.id === 'string' && model.id.length <= 256) })
|
|
25
|
+
}
|
|
26
|
+
} else if (mode === 'responses') {
|
|
27
|
+
let input = ''
|
|
28
|
+
for await (const chunk of process.stdin) input += chunk
|
|
29
|
+
const body = JSON.parse(input)
|
|
30
|
+
body.store = false
|
|
31
|
+
body.stream = true
|
|
32
|
+
body.instructions ??= ''
|
|
33
|
+
delete body.max_output_tokens
|
|
34
|
+
delete body.temperature
|
|
35
|
+
delete body.top_p
|
|
36
|
+
const response = await fetch('https://chatgpt.com/backend-api/codex/responses', { method: 'POST', headers, body: JSON.stringify(body), signal: AbortSignal.timeout(300000) })
|
|
37
|
+
emit({ status: response.status })
|
|
38
|
+
if (response.ok) {
|
|
39
|
+
for await (const chunk of response.body) emit({ chunk: Buffer.from(chunk).toString('base64') })
|
|
40
|
+
emit({ done: true })
|
|
41
|
+
}
|
|
42
|
+
} else emit({ status: 400 })
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
emit({ status: 503 }); process.exitCode = 1
|
|
46
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { lstat, readFile } from 'node:fs/promises'
|
|
2
|
+
import { request } from 'node:http'
|
|
3
|
+
|
|
4
|
+
export const IDENTITY_SOCKET = '/opt/giz/identity/identity.sock'
|
|
5
|
+
|
|
6
|
+
// One-release bridge: the managed CLI overlay can arrive before the Region gateway migration.
|
|
7
|
+
export async function resolveIdentitySocket() {
|
|
8
|
+
try {
|
|
9
|
+
await lstat(IDENTITY_SOCKET)
|
|
10
|
+
return IDENTITY_SOCKET
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error.code !== 'ENOENT') throw error
|
|
13
|
+
return '/run/giz/identity.sock'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let cached
|
|
17
|
+
let pending
|
|
18
|
+
|
|
19
|
+
// The release overlay owns this stamp; outside npm installations do not have it.
|
|
20
|
+
export async function managedRevision() {
|
|
21
|
+
return readFile(new URL('../REVISION', import.meta.url), 'utf8').catch(error => {
|
|
22
|
+
if (error.code !== 'ENOENT') throw error
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The CLI never accepts a socket path from options, environment, or request data.
|
|
27
|
+
export async function computerIdentity(rejectedToken) {
|
|
28
|
+
if (cached?.access_token !== rejectedToken && cached?.expires_at > Date.now() + 60_000) return cached
|
|
29
|
+
pending ??= resolveIdentitySocket().then(socketPath => new Promise((resolve, reject) => {
|
|
30
|
+
const req = request({ socketPath, path: '/token', method: 'POST',
|
|
31
|
+
headers: { 'content-length': '0' } }, res => {
|
|
32
|
+
let body = ''
|
|
33
|
+
res.setEncoding('utf8')
|
|
34
|
+
res.on('data', chunk => {
|
|
35
|
+
body += chunk
|
|
36
|
+
if (body.length > 16_384) req.destroy(new Error('컴퓨터 신원 응답이 너무 큽니다.'))
|
|
37
|
+
})
|
|
38
|
+
res.on('error', reject)
|
|
39
|
+
res.on('end', () => {
|
|
40
|
+
try {
|
|
41
|
+
const value = JSON.parse(body)
|
|
42
|
+
if (res.statusCode !== 200) throw new Error(value.error_description || value.message || '컴퓨터 신원을 사용할 수 없습니다.')
|
|
43
|
+
const url = new URL(value.baseUrl)
|
|
44
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password ||
|
|
45
|
+
url.pathname !== '/' || url.search || url.hash || !value.access_token ||
|
|
46
|
+
value.token_type !== 'Bearer' || Number(value.expires_in) <= 0 || Number(value.expires_in) > 900) {
|
|
47
|
+
throw new Error('컴퓨터 신원 응답이 올바르지 않습니다.')
|
|
48
|
+
}
|
|
49
|
+
cached = { access_token: value.access_token, baseUrl: url.origin,
|
|
50
|
+
expires_at: Date.now() + Number(value.expires_in) * 1000 }
|
|
51
|
+
resolve(cached)
|
|
52
|
+
} catch (error) { reject(error) }
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
req.on('error', async error => {
|
|
56
|
+
if (error.code !== 'ENOENT') return reject(error)
|
|
57
|
+
try {
|
|
58
|
+
await lstat(socketPath)
|
|
59
|
+
reject(new Error('컴퓨터 신원 서비스에 연결할 수 없습니다. 잠시 후 다시 시도해 주세요.'))
|
|
60
|
+
} catch (lookupError) {
|
|
61
|
+
if (lookupError.code !== 'ENOENT') return reject(lookupError)
|
|
62
|
+
try {
|
|
63
|
+
if (await managedRevision() !== undefined) {
|
|
64
|
+
reject(new Error('컴퓨터 신원을 확인할 수 없습니다. 잠시 후 다시 시도하거나 소유자에게 문의해 주세요.'))
|
|
65
|
+
} else resolve(undefined)
|
|
66
|
+
} catch (error) { reject(error) }
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
req.setTimeout(10_000, () => req.destroy(new Error('컴퓨터 신원 응답 시간이 초과되었습니다.')))
|
|
70
|
+
req.end()
|
|
71
|
+
})).catch(error => {
|
|
72
|
+
error.hint = '컴퓨터 상태를 확인하고 다시 시도해 주세요. 차단되었다면 소유자 또는 조직 관리자에게 문의해 주세요.'
|
|
73
|
+
throw error
|
|
74
|
+
}).finally(() => { pending = undefined })
|
|
75
|
+
return pending
|
|
76
|
+
}
|
package/cli/engine.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn, execFileSync } from 'node:child_process'
|
|
2
|
+
import { mkdir } from 'node:fs/promises'
|
|
3
|
+
import { randomUUID } from 'node:crypto'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
/** Local Computer execution: official binary, existing permissions, isolated reviewable worktree. */
|
|
7
|
+
export async function runEngine(engine, instruction, directory = process.cwd(), progress = () => {}) {
|
|
8
|
+
if (engine !== 'claude' || typeof instruction !== 'string' || !instruction.trim()) throw new Error('Run: giz engine run claude "instruction" [--dir path]')
|
|
9
|
+
const git = (args, cwd = directory) => execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 8 * 1024 * 1024 })
|
|
10
|
+
const env = { ...process.env }
|
|
11
|
+
for (const key of ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_VERTEX', 'CLAUDE_CODE_USE_FOUNDRY']) delete env[key]
|
|
12
|
+
const status = spawn('claude', ['auth', 'status'], { env, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
13
|
+
let login = ''
|
|
14
|
+
status.stdout.on('data', chunk => { login += chunk })
|
|
15
|
+
await new Promise((resolve, reject) => { status.once('error', reject); status.once('close', code => code === 0 ? resolve() : reject(new Error('Log in to Claude Code in Connections.'))) })
|
|
16
|
+
const account = JSON.parse(login)
|
|
17
|
+
if (!account.loggedIn || account.authMethod !== 'claude.ai') throw new Error('A Claude subscription login is required.')
|
|
18
|
+
const root = git(['rev-parse', '--show-toplevel']).trim()
|
|
19
|
+
const common = path.resolve(root, git(['rev-parse', '--git-common-dir']).trim())
|
|
20
|
+
const worktree = path.join(common, 'giz-engine-worktrees', randomUUID())
|
|
21
|
+
await mkdir(path.dirname(worktree), { recursive: true })
|
|
22
|
+
git(['worktree', 'add', '--detach', worktree, 'HEAD'])
|
|
23
|
+
progress({ status: 'running', engine, worktree })
|
|
24
|
+
const child = spawn('claude', ['-p', instruction, '--output-format', 'stream-json', '--verbose', '--permission-mode', 'acceptEdits'],
|
|
25
|
+
{ cwd: worktree, env, detached: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
26
|
+
let output = '', summary = '', stopped = false
|
|
27
|
+
const stop = () => { stopped = true; try { process.kill(-child.pid, 'SIGTERM') } catch {} }
|
|
28
|
+
process.once('SIGTERM', stop); process.once('SIGINT', stop)
|
|
29
|
+
child.stdout.on('data', chunk => {
|
|
30
|
+
output += chunk
|
|
31
|
+
let newline
|
|
32
|
+
while ((newline = output.indexOf('\n')) >= 0) {
|
|
33
|
+
const line = output.slice(0, newline); output = output.slice(newline + 1)
|
|
34
|
+
try {
|
|
35
|
+
const event = JSON.parse(line)
|
|
36
|
+
if (event.type === 'result') summary = String(event.result || '')
|
|
37
|
+
// Do not pass raw tool inputs, prompts or authorization data into the task card.
|
|
38
|
+
progress({ status: 'running', event: event.type, engine, worktree })
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
child.stderr.resume()
|
|
43
|
+
let exitCode
|
|
44
|
+
try {
|
|
45
|
+
exitCode = await new Promise((resolve, reject) => { child.once('error', reject); child.once('close', code => resolve(code)) })
|
|
46
|
+
} finally { process.removeListener('SIGTERM', stop); process.removeListener('SIGINT', stop) }
|
|
47
|
+
const tracked = git(['diff', '--name-only', 'HEAD'], worktree).trim().split('\n').filter(Boolean)
|
|
48
|
+
const created = git(['ls-files', '--others', '--exclude-standard'], worktree).trim().split('\n').filter(Boolean)
|
|
49
|
+
let diff = git(['diff', '--binary', 'HEAD'], worktree)
|
|
50
|
+
for (const file of created) {
|
|
51
|
+
try { diff += git(['diff', '--no-index', '--binary', '--', '/dev/null', file], worktree) }
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error.status !== 1) throw error
|
|
54
|
+
diff += String(error.stdout)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { status: stopped ? 'canceled' : exitCode === 0 ? 'completed' : 'failed', engine, exitCode, worktree, files: [...new Set([...tracked, ...created])], diff, summary }
|
|
58
|
+
}
|