@gizai/cli 2.0.1 → 2.1.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/LICENSE +21 -0
- package/README.md +9 -178
- package/bin/giz.js +77 -0
- package/bin/install.sh +261 -0
- package/package.json +16 -46
- package/cli/codex-relay.mjs +0 -46
- package/cli/computer-identity.mjs +0 -76
- package/cli/engine.mjs +0 -58
- package/cli/giz.mjs +0 -1112
- package/cli/mcp-stdio-client.mjs +0 -81
- package/cli/media-capture.mjs +0 -321
- package/cli/update.mjs +0 -150
- package/mcp.json +0 -6
- package/plugin.json +0 -10
package/cli/giz.mjs
DELETED
|
@@ -1,1112 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { computerIdentity, managedRevision } from './computer-identity.mjs'
|
|
3
|
-
import { checkForUpdate, explicitUpdate, installSource, maybeNotify } from './update.mjs'
|
|
4
|
-
import { createHash, randomBytes } from 'node:crypto'
|
|
5
|
-
import { openAsBlob, statSync } from 'node:fs'
|
|
6
|
-
import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'
|
|
7
|
-
import { createServer } from 'node:http'
|
|
8
|
-
import { once } from 'node:events'
|
|
9
|
-
import { createRequire } from 'node:module'
|
|
10
|
-
import os from 'node:os'
|
|
11
|
-
import path from 'node:path'
|
|
12
|
-
import { spawn, spawnSync } from 'node:child_process'
|
|
13
|
-
import { fileURLToPath } from 'node:url'
|
|
14
|
-
import { lock } from 'proper-lockfile'
|
|
15
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
16
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
17
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
18
|
-
|
|
19
|
-
const { version: CLI_VERSION } = createRequire(import.meta.url)('../package.json')
|
|
20
|
-
const DEFAULT_URL = 'https://www.giz.ai'
|
|
21
|
-
const CLIENT_ID = 'giz-cli'
|
|
22
|
-
const SCOPES = ['giz.read', 'giz.inference', 'giz.write', 'giz.execute', 'giz.destructive', 'offline_access']
|
|
23
|
-
const BOOLEAN_OPTIONS = new Set(['help', 'json', 'no-open', 'version', 'device'])
|
|
24
|
-
const configPath = path.resolve(process.env.GIZ_CONFIG ||
|
|
25
|
-
path.join(os.homedir(), '.config', 'giz', 'credentials.json'))
|
|
26
|
-
|
|
27
|
-
class CliError extends Error {
|
|
28
|
-
constructor(message, hint, status) {
|
|
29
|
-
super(message)
|
|
30
|
-
this.hint = hint
|
|
31
|
-
this.status = status
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function parse(argv) {
|
|
36
|
-
const positionals = []
|
|
37
|
-
const options = {}
|
|
38
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
39
|
-
const value = argv[index]
|
|
40
|
-
if (value === '--') {
|
|
41
|
-
positionals.push(...argv.slice(index + 1))
|
|
42
|
-
break
|
|
43
|
-
}
|
|
44
|
-
if (!value.startsWith('--')) {
|
|
45
|
-
positionals.push(value)
|
|
46
|
-
continue
|
|
47
|
-
}
|
|
48
|
-
const [rawName, inline] = value.slice(2).split(/=(.*)/s, 2)
|
|
49
|
-
const next = argv[index + 1]
|
|
50
|
-
const parsed = inline !== undefined
|
|
51
|
-
? inline
|
|
52
|
-
: BOOLEAN_OPTIONS.has(rawName) || !next || next.startsWith('--')
|
|
53
|
-
? true
|
|
54
|
-
: next
|
|
55
|
-
if (rawName === 'upload') {
|
|
56
|
-
options.upload = [...(Array.isArray(options.upload) ? options.upload : []), parsed]
|
|
57
|
-
} else {
|
|
58
|
-
options[rawName] = parsed
|
|
59
|
-
}
|
|
60
|
-
if (inline === undefined && parsed !== true) index += 1
|
|
61
|
-
}
|
|
62
|
-
return { positionals, options }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function readConfig() {
|
|
66
|
-
try {
|
|
67
|
-
return JSON.parse(await readFile(configPath, 'utf8'))
|
|
68
|
-
} catch (error) {
|
|
69
|
-
if (error?.code === 'ENOENT') return undefined
|
|
70
|
-
throw new CliError('Could not read Giz credentials.', `Remove or repair ${configPath}.`)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function updateConfig(update) {
|
|
75
|
-
await mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 })
|
|
76
|
-
const release = await lock(configPath, { realpath: false, retries: { retries: 120, minTimeout: 100, maxTimeout: 500 } })
|
|
77
|
-
const temporary = `${configPath}.${randomBytes(8).toString('hex')}`
|
|
78
|
-
try {
|
|
79
|
-
const config = await update(await readConfig())
|
|
80
|
-
if (config) {
|
|
81
|
-
await writeFile(temporary, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600, flag: 'wx' })
|
|
82
|
-
await rename(temporary, configPath)
|
|
83
|
-
} else {
|
|
84
|
-
await unlink(configPath).catch((error) => { if (error.code !== 'ENOENT') throw error })
|
|
85
|
-
}
|
|
86
|
-
return config
|
|
87
|
-
} finally {
|
|
88
|
-
try { await unlink(temporary).catch((error) => { if (error.code !== 'ENOENT') throw error }) }
|
|
89
|
-
finally { await release() }
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function discovery(baseUrl) {
|
|
94
|
-
const response = await fetch(new URL('/.well-known/oauth-authorization-server', baseUrl), { signal: AbortSignal.timeout(30_000) })
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
throw new CliError(
|
|
97
|
-
`OAuth discovery failed: HTTP ${response.status}.`,
|
|
98
|
-
`Check ${new URL('/.well-known/oauth-authorization-server', baseUrl)}.`
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
return await response.json()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function exchangeToken(baseUrl, metadata, grant, config = {}) {
|
|
105
|
-
const body = new URLSearchParams({
|
|
106
|
-
client_id: CLIENT_ID,
|
|
107
|
-
resource: new URL('/api/mcp', baseUrl).href,
|
|
108
|
-
...grant
|
|
109
|
-
})
|
|
110
|
-
const response = await fetch(metadata.token_endpoint, {
|
|
111
|
-
method: 'POST',
|
|
112
|
-
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
113
|
-
body,
|
|
114
|
-
signal: AbortSignal.timeout(30_000)
|
|
115
|
-
})
|
|
116
|
-
const payload = await response.json()
|
|
117
|
-
if (!response.ok) {
|
|
118
|
-
const error = new CliError(payload.error_description || payload.error || 'OAuth token exchange failed.', 'Run: giz auth login')
|
|
119
|
-
error.oauthError = payload.error
|
|
120
|
-
throw error
|
|
121
|
-
}
|
|
122
|
-
return {
|
|
123
|
-
...config,
|
|
124
|
-
baseUrl,
|
|
125
|
-
...payload,
|
|
126
|
-
refresh_token: payload.refresh_token || config.refresh_token,
|
|
127
|
-
expires_at: Date.now() + Number(payload.expires_in || 3600) * 1000
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function credentials(baseUrl, rejectedToken) {
|
|
132
|
-
if (process.env.GIZ_TOKEN) return { access_token: process.env.GIZ_TOKEN, baseUrl }
|
|
133
|
-
const config = await readConfig()
|
|
134
|
-
if (!config?.access_token) {
|
|
135
|
-
const computer = await computerIdentity(rejectedToken)
|
|
136
|
-
if (!computer) throw new CliError('Not authenticated.', 'Run: giz auth login')
|
|
137
|
-
if (computer.baseUrl !== baseUrl) throw new CliError('A Computer identity cannot be used with another server.', 'Run giz auth login with your user account.')
|
|
138
|
-
return computer
|
|
139
|
-
}
|
|
140
|
-
if (String(config.baseUrl).replace(/\/+$/, '') !== baseUrl) {
|
|
141
|
-
throw new CliError(
|
|
142
|
-
`Credentials belong to ${config.baseUrl}.`,
|
|
143
|
-
`Run: giz auth login --base-url ${baseUrl}`
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
const expired = (value) => value.access_token === rejectedToken || Number(value.expires_at || 0) <= Date.now() + 60_000
|
|
147
|
-
if (expired(config)) {
|
|
148
|
-
return await updateConfig(async (config) => {
|
|
149
|
-
if (config?.baseUrl !== baseUrl) throw new CliError('Giz credentials changed.', 'Run: giz auth login')
|
|
150
|
-
if (expired(config)) {
|
|
151
|
-
if (!config.refresh_token) throw new CliError('Giz session expired.', 'Run: giz auth login')
|
|
152
|
-
return await exchangeToken(baseUrl, await discovery(baseUrl), {
|
|
153
|
-
grant_type: 'refresh_token', refresh_token: config.refresh_token
|
|
154
|
-
}, config)
|
|
155
|
-
}
|
|
156
|
-
return config
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
return config
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function authenticatedFetch(baseUrl, url, init = {}) {
|
|
163
|
-
const headers = new Headers(init.headers)
|
|
164
|
-
let auth = await credentials(baseUrl)
|
|
165
|
-
const signal = AbortSignal.timeout(25 * 60_000)
|
|
166
|
-
const send = () => {
|
|
167
|
-
headers.set('authorization', `Bearer ${auth.access_token}`)
|
|
168
|
-
// The server resolves membership and roles; the CLI only names the organization, as the web's selector does.
|
|
169
|
-
if (process.env.GIZ_ORGANIZATION) headers.set('x-giz-organization', process.env.GIZ_ORGANIZATION)
|
|
170
|
-
return fetch(url, { ...init, headers, signal: init.signal ? AbortSignal.any([init.signal, signal]) : signal })
|
|
171
|
-
}
|
|
172
|
-
let response = await send()
|
|
173
|
-
if (response.status === 401 && !process.env.GIZ_TOKEN) {
|
|
174
|
-
await response.body?.cancel()
|
|
175
|
-
auth = await credentials(baseUrl, auth.access_token)
|
|
176
|
-
response = await send()
|
|
177
|
-
}
|
|
178
|
-
return response
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function jsonResponse(response, baseUrl) {
|
|
182
|
-
const text = await response.text()
|
|
183
|
-
let payload
|
|
184
|
-
try { payload = JSON.parse(text) } catch {
|
|
185
|
-
if (response.ok) throw new CliError('Giz API returned invalid JSON.', `Request: ${new URL(response.url).pathname}`)
|
|
186
|
-
payload = { message: text.slice(0, 500) }
|
|
187
|
-
}
|
|
188
|
-
if (!response.ok) {
|
|
189
|
-
const message = payload?.message || payload?.error?.message || `Giz API returned HTTP ${response.status}.`
|
|
190
|
-
// Older servers have only the legacy message; classify that exact contract without hiding other 404s.
|
|
191
|
-
if (response.status === 404 && (payload?.code === 'EXTERNAL_TOOL_UNAVAILABLE' ||
|
|
192
|
-
(new URL(response.url).pathname.startsWith('/api/tools/') && /^Unknown Giz tool: /.test(message)))) {
|
|
193
|
-
const compatibility = payload?.code === 'EXTERNAL_TOOL_UNAVAILABLE'
|
|
194
|
-
? message : 'The server version is unknown (older server).'
|
|
195
|
-
throw new CliError(
|
|
196
|
-
`This server does not support the requested command. CLI ${CLI_VERSION}. ${compatibility}`,
|
|
197
|
-
`Check the server address (${baseUrl}) and its supported version; choose a supporting server with --base-url.`,
|
|
198
|
-
response.status
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
throw new CliError(
|
|
202
|
-
message,
|
|
203
|
-
response.status === 403 && /scope/i.test(message)
|
|
204
|
-
? (process.env.GIZ_TOKEN ? 'Provide a GIZ_TOKEN with the required scope.' : `Run: giz auth login --base-url ${baseUrl}`)
|
|
205
|
-
: `Request: ${new URL(response.url).pathname}`,
|
|
206
|
-
response.status
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
return payload
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function api(baseUrl, pathname, data, options = {}) {
|
|
213
|
-
const response = await authenticatedFetch(baseUrl, new URL(pathname, baseUrl), {
|
|
214
|
-
method: options.method || (data === undefined ? 'GET' : 'POST'),
|
|
215
|
-
headers: { accept: 'application/json', ...(data === undefined ? {} : { 'content-type': 'application/json' }) },
|
|
216
|
-
...(data === undefined ? {} : { body: JSON.stringify(data) })
|
|
217
|
-
})
|
|
218
|
-
const payload = await jsonResponse(response, baseUrl)
|
|
219
|
-
if (pathname === '/api/external/me' && payload.minimumCliVersion) {
|
|
220
|
-
const current = CLI_VERSION.split('.').map(Number)
|
|
221
|
-
const minimum = String(payload.minimumCliVersion).split('.').map(Number)
|
|
222
|
-
const differing = minimum.findIndex((part, index) => part !== current[index])
|
|
223
|
-
if (differing >= 0 && current[differing] < minimum[differing]) {
|
|
224
|
-
process.stderr.write(`Warning: Giz CLI ${CLI_VERSION} is below minimum ${payload.minimumCliVersion}; update @gizai/cli.\n`)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return payload
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function uploadMedia(baseUrl, localFile) {
|
|
231
|
-
const filePath = path.resolve(String(localFile))
|
|
232
|
-
// openAsBlob reports a missing or unreadable path only as "Unable to open file as blob".
|
|
233
|
-
let isFile = false
|
|
234
|
-
try { isFile = statSync(filePath).isFile() } catch {}
|
|
235
|
-
if (!isFile) throw new CliError(`Cannot read the file to upload: ${filePath}`, 'Check the path; it is resolved from the current directory.')
|
|
236
|
-
const body = new FormData()
|
|
237
|
-
body.append('file', await openAsBlob(filePath), path.basename(filePath))
|
|
238
|
-
const response = await authenticatedFetch(baseUrl, new URL('/api/storage/media', baseUrl), {
|
|
239
|
-
method: 'POST', headers: { accept: 'application/json' }, body
|
|
240
|
-
})
|
|
241
|
-
const payload = await jsonResponse(response, baseUrl)
|
|
242
|
-
const url = String(payload?.url || '').trim()
|
|
243
|
-
const drivePath = String(payload?.path || '').trim()
|
|
244
|
-
if (!url || !drivePath) throw new CliError('Giz upload returned an incomplete file reference.', 'Try the upload again.')
|
|
245
|
-
return { ...payload, url: new URL(url, baseUrl).href }
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async function login(baseUrl, options) {
|
|
249
|
-
const metadata = await discovery(baseUrl)
|
|
250
|
-
if (options.device === true) return await deviceLogin(baseUrl, metadata)
|
|
251
|
-
const verifier = randomBytes(48).toString('base64url')
|
|
252
|
-
const challenge = createHash('sha256').update(verifier).digest('base64url')
|
|
253
|
-
const state = randomBytes(24).toString('base64url')
|
|
254
|
-
const callback = await waitForCallback(state)
|
|
255
|
-
const authorization = new URL(metadata.authorization_endpoint)
|
|
256
|
-
for (const [name, value] of Object.entries({
|
|
257
|
-
response_type: 'code',
|
|
258
|
-
client_id: CLIENT_ID,
|
|
259
|
-
redirect_uri: callback.url,
|
|
260
|
-
scope: SCOPES.join(' '),
|
|
261
|
-
resource: new URL('/api/mcp', baseUrl).href,
|
|
262
|
-
code_challenge: challenge,
|
|
263
|
-
code_challenge_method: 'S256',
|
|
264
|
-
prompt: 'consent',
|
|
265
|
-
state
|
|
266
|
-
})) authorization.searchParams.set(name, value)
|
|
267
|
-
process.stderr.write(`Opening Giz sign-in:\n${authorization}\n`)
|
|
268
|
-
if (options.open !== 'false' && options['no-open'] !== true) openBrowser(authorization.href)
|
|
269
|
-
const code = await callback.code
|
|
270
|
-
await updateConfig(() => exchangeToken(baseUrl, metadata, {
|
|
271
|
-
grant_type: 'authorization_code', redirect_uri: callback.url, code, code_verifier: verifier
|
|
272
|
-
}))
|
|
273
|
-
return await api(baseUrl, '/api/external/me')
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async function deviceLogin(baseUrl, metadata) {
|
|
277
|
-
if (!metadata.device_authorization_endpoint) throw new CliError('OAuth device authorization is not supported.')
|
|
278
|
-
const response = await fetch(metadata.device_authorization_endpoint, {
|
|
279
|
-
method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
280
|
-
body: new URLSearchParams({ client_id: CLIENT_ID, scope: SCOPES.join(' '), resource: new URL('/api/mcp', baseUrl).href }),
|
|
281
|
-
signal: AbortSignal.timeout(30_000)
|
|
282
|
-
})
|
|
283
|
-
const device = await response.json()
|
|
284
|
-
if (!response.ok) throw new CliError(device.error_description || device.error || 'OAuth device authorization failed.')
|
|
285
|
-
if (!device.device_code || !device.user_code || !device.verification_uri || !(Number(device.expires_in) > 0)) {
|
|
286
|
-
throw new CliError('OAuth device authorization returned an incomplete response.')
|
|
287
|
-
}
|
|
288
|
-
process.stderr.write(`Giz sign-in: ${device.verification_uri_complete || device.verification_uri}\nCode: ${device.user_code}\n`)
|
|
289
|
-
let interval = Number(device.interval ?? 5) * 1000
|
|
290
|
-
if (!Number.isFinite(interval) || interval <= 0) throw new CliError('OAuth device authorization returned an invalid interval.')
|
|
291
|
-
const deadline = Date.now() + Number(device.expires_in) * 1000
|
|
292
|
-
while (Date.now() < deadline) {
|
|
293
|
-
const remaining = deadline - Date.now()
|
|
294
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(interval, remaining)))
|
|
295
|
-
if (interval >= remaining || Date.now() >= deadline) break
|
|
296
|
-
try {
|
|
297
|
-
await updateConfig(() => exchangeToken(baseUrl, metadata, {
|
|
298
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: device.device_code
|
|
299
|
-
}))
|
|
300
|
-
} catch (error) {
|
|
301
|
-
if (error.oauthError === 'slow_down') interval += 5000
|
|
302
|
-
else if (error.name === 'TimeoutError') interval *= 2
|
|
303
|
-
else if (error.oauthError !== 'authorization_pending') throw error
|
|
304
|
-
continue
|
|
305
|
-
}
|
|
306
|
-
return await api(baseUrl, '/api/external/me')
|
|
307
|
-
}
|
|
308
|
-
throw new CliError('expired_token: Giz device code expired.', 'Run: giz auth login --device')
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async function waitForCallback(expectedState) {
|
|
312
|
-
const server = createServer()
|
|
313
|
-
server.listen(0, '127.0.0.1')
|
|
314
|
-
await once(server, 'listening')
|
|
315
|
-
const callbackUrl = `http://127.0.0.1:${server.address().port}/callback`
|
|
316
|
-
const code = new Promise((resolve, reject) => {
|
|
317
|
-
const finish = (error, value) => {
|
|
318
|
-
clearTimeout(timer)
|
|
319
|
-
server.close()
|
|
320
|
-
error ? reject(error) : resolve(value)
|
|
321
|
-
}
|
|
322
|
-
const timer = setTimeout(() => finish(new CliError('Giz sign-in timed out.', 'Run: giz auth login')), 5 * 60 * 1000)
|
|
323
|
-
server.on('request', (request, response) => {
|
|
324
|
-
const url = new URL(request.url || '/', callbackUrl)
|
|
325
|
-
if (url.pathname !== '/callback') {
|
|
326
|
-
response.writeHead(404).end('Not found')
|
|
327
|
-
return
|
|
328
|
-
}
|
|
329
|
-
if (url.searchParams.get('state') !== expectedState) {
|
|
330
|
-
response.writeHead(400).end('This sign-in link has expired. Use the link from your current giz auth login.')
|
|
331
|
-
return
|
|
332
|
-
}
|
|
333
|
-
const code = url.searchParams.get('code')
|
|
334
|
-
const error = url.searchParams.get('error') || !code
|
|
335
|
-
? new CliError(url.searchParams.get('error_description') || url.searchParams.get('error') || 'OAuth callback did not include a code.', 'Run: giz auth login')
|
|
336
|
-
: undefined
|
|
337
|
-
response.setHeader('content-type', 'text/html; charset=utf-8')
|
|
338
|
-
response.setHeader('connection', 'close')
|
|
339
|
-
response.end(error
|
|
340
|
-
? '<h1>Giz connection failed</h1><p>Return to the terminal for details.</p>'
|
|
341
|
-
: '<h1>Giz connected</h1><p>You can close this window and return to the terminal.</p>')
|
|
342
|
-
finish(error, code)
|
|
343
|
-
})
|
|
344
|
-
server.on('error', finish)
|
|
345
|
-
})
|
|
346
|
-
return { url: callbackUrl, code }
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function openBrowser(url) {
|
|
350
|
-
// Not `cmd /c start`: cmd splits the URL at its first `&`, so Windows sign-in lost client_id and every later parameter.
|
|
351
|
-
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'rundll32' : 'xdg-open'
|
|
352
|
-
const args = process.platform === 'win32' ? ['url.dll,FileProtocolHandler', url] : [url]
|
|
353
|
-
const child = spawn(command, args, { detached: true, stdio: 'ignore' })
|
|
354
|
-
child.on('error', () => {})
|
|
355
|
-
child.unref()
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// --input @FILE and --input - (stdin) carry JSON too large for one process argument (Linux caps one at 128 KiB).
|
|
359
|
-
async function inputText(value) {
|
|
360
|
-
if (value === '-') {
|
|
361
|
-
let text = ''
|
|
362
|
-
for await (const chunk of process.stdin) text += chunk
|
|
363
|
-
return text
|
|
364
|
-
}
|
|
365
|
-
if (typeof value === 'string' && value.startsWith('@')) {
|
|
366
|
-
try {
|
|
367
|
-
return await readFile(value.slice(1), 'utf8')
|
|
368
|
-
} catch {
|
|
369
|
-
throw new CliError(`Could not read ${value.slice(1)}.`, 'Pass --input @PATH to an existing JSON file.')
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return value
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function jsonInput(value) {
|
|
376
|
-
if (value === undefined || value === true) return {}
|
|
377
|
-
try {
|
|
378
|
-
const parsed = JSON.parse(value)
|
|
379
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error()
|
|
380
|
-
return parsed
|
|
381
|
-
} catch {
|
|
382
|
-
throw new CliError('--input must be one JSON object.', 'Example: --input \'{"prompt":"A quiet ocean"}\'')
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function required(value, name, example) {
|
|
387
|
-
if (value) return value
|
|
388
|
-
throw new CliError(`${name} is required.`, example)
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
async function applyUploads(baseUrl, input, values = []) {
|
|
392
|
-
for (const value of values.filter((item) => item !== undefined)) {
|
|
393
|
-
const separator = String(value).indexOf('=')
|
|
394
|
-
if (separator < 1 || separator === String(value).length - 1) {
|
|
395
|
-
throw new CliError(
|
|
396
|
-
'--upload must be FIELD=LOCAL_PATH.',
|
|
397
|
-
'Example: --upload image=./first-frame.png'
|
|
398
|
-
)
|
|
399
|
-
}
|
|
400
|
-
const field = String(value).slice(0, separator).trim()
|
|
401
|
-
const localFile = String(value).slice(separator + 1).trim()
|
|
402
|
-
const stored = await uploadMedia(baseUrl, localFile)
|
|
403
|
-
const current = input[field]
|
|
404
|
-
input[field] = current === undefined ? stored.url : Array.isArray(current) ? [...current, stored.url] : [current, stored.url]
|
|
405
|
-
}
|
|
406
|
-
return input
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Mutations use the user's reviewed App revision; fetching it silently would bypass conflict protection.
|
|
410
|
-
function appRevision(options) {
|
|
411
|
-
const raw = options['expected-revision']
|
|
412
|
-
if (raw === undefined || raw === '') {
|
|
413
|
-
throw new CliError('A revision is required.', 'Pass the revision from giz app list as --expected-revision.')
|
|
414
|
-
}
|
|
415
|
-
const revision = Number(raw)
|
|
416
|
-
if (typeof raw !== 'string' || !/^\d+$/.test(raw) || !Number.isSafeInteger(revision) || revision < 1 || revision > 9_007_199_254_740_990) {
|
|
417
|
-
throw new CliError('The revision must be a safe integer of 1 or more.', 'Check the current revision with giz app list.')
|
|
418
|
-
}
|
|
419
|
-
return revision
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async function invoke(baseUrl, tool, input) {
|
|
423
|
-
return await api(baseUrl, `/api/tools/${encodeURIComponent(tool)}`, input)
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function mcpTransport(baseUrl) {
|
|
427
|
-
return new StreamableHTTPClientTransport(new URL('/api/mcp', baseUrl), {
|
|
428
|
-
fetch: (url, init) => authenticatedFetch(baseUrl, url, init)
|
|
429
|
-
})
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async function serveMcp(baseUrl) {
|
|
433
|
-
const remote = mcpTransport(baseUrl)
|
|
434
|
-
const stdio = new StdioServerTransport()
|
|
435
|
-
try {
|
|
436
|
-
await remote.start()
|
|
437
|
-
await new Promise((resolve, reject) => {
|
|
438
|
-
remote.onerror = stdio.onerror = reject
|
|
439
|
-
remote.onmessage = (message) => {
|
|
440
|
-
if (message.result?.protocolVersion) remote.setProtocolVersion(message.result.protocolVersion)
|
|
441
|
-
stdio.send(message).catch(reject)
|
|
442
|
-
}
|
|
443
|
-
stdio.onmessage = (message) => { remote.send(message).catch(reject) }
|
|
444
|
-
process.stdin.once('end', resolve)
|
|
445
|
-
stdio.start().catch(reject)
|
|
446
|
-
})
|
|
447
|
-
} finally {
|
|
448
|
-
await remote.close()
|
|
449
|
-
await stdio.close()
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
async function runCommand(baseUrl, positionals, options) {
|
|
454
|
-
const [command, subcommand, ...rest] = positionals
|
|
455
|
-
if (command === '__update-check') {
|
|
456
|
-
await checkForUpdate({ file: options.cache, url: options.registry })
|
|
457
|
-
return undefined
|
|
458
|
-
}
|
|
459
|
-
if (command === 'update' && options.help === true) return { kind: 'help', value: help('update') }
|
|
460
|
-
if (command === 'update' && !subcommand) {
|
|
461
|
-
const result = await explicitUpdate({ version: CLI_VERSION, source: installSource() })
|
|
462
|
-
return { kind: 'message', value: result.message }
|
|
463
|
-
}
|
|
464
|
-
if (options.version === true) {
|
|
465
|
-
const revision = await managedRevision()
|
|
466
|
-
return { kind: 'message', value: CLI_VERSION + (revision ? ` (region ${revision.trim()})` : '') }
|
|
467
|
-
}
|
|
468
|
-
if (!command || command === 'help' || options.help === true) {
|
|
469
|
-
// A script that asks for help on a command that does not exist must fail, not read the overview as success.
|
|
470
|
-
if (command && command !== 'help' && !help().split('\n').some((line) => line.trim().split(' ')[1] === command && line.startsWith(' giz '))) {
|
|
471
|
-
throw new CliError(`Unknown command "${command}".`, 'Run: giz --help')
|
|
472
|
-
}
|
|
473
|
-
return { kind: 'help', value: help(command) }
|
|
474
|
-
}
|
|
475
|
-
if (command === 'engine' && subcommand === 'run') {
|
|
476
|
-
if (rest[0] !== 'claude') throw new CliError('Only Claude Code is an engine connection.')
|
|
477
|
-
await api(baseUrl, '/api/external/engine/claude/authorization')
|
|
478
|
-
const { runEngine } = await import('./engine.mjs')
|
|
479
|
-
const result = await runEngine(rest[0], required(rest[1], 'INSTRUCTION', 'Run: giz engine run claude "instruction"'), options.dir,
|
|
480
|
-
event => process.stdout.write(JSON.stringify(event) + '\n'))
|
|
481
|
-
if (result.status !== 'completed') process.exitCode = 1
|
|
482
|
-
return { kind: 'result', value: result }
|
|
483
|
-
}
|
|
484
|
-
if (command === 'auth' && subcommand === 'login') {
|
|
485
|
-
return { kind: 'auth', value: await login(baseUrl, options) }
|
|
486
|
-
}
|
|
487
|
-
if (command === 'auth' && subcommand === 'status') {
|
|
488
|
-
return { kind: 'auth', value: await api(baseUrl, '/api/external/me') }
|
|
489
|
-
}
|
|
490
|
-
if (command === 'auth' && subcommand === 'logout') {
|
|
491
|
-
await updateConfig(async (config) => {
|
|
492
|
-
if (config?.refresh_token) {
|
|
493
|
-
const metadata = await discovery(config.baseUrl)
|
|
494
|
-
const response = await fetch(metadata.revocation_endpoint, {
|
|
495
|
-
method: 'POST', signal: AbortSignal.timeout(30_000),
|
|
496
|
-
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
497
|
-
body: new URLSearchParams({ token: config.refresh_token, token_type_hint: 'refresh_token', client_id: CLIENT_ID })
|
|
498
|
-
})
|
|
499
|
-
if (!response.ok) throw new CliError(`Giz sign-out failed: HTTP ${response.status}.`, 'Run: giz auth logout')
|
|
500
|
-
}
|
|
501
|
-
})
|
|
502
|
-
return { kind: 'message', value: 'Signed out of Giz on this device.' }
|
|
503
|
-
}
|
|
504
|
-
if (command === 'api-key' && subcommand === 'list') {
|
|
505
|
-
return { kind: 'result', value: await api(baseUrl, '/api/external/api-keys') }
|
|
506
|
-
}
|
|
507
|
-
if (command === 'api-key' && subcommand === 'create') {
|
|
508
|
-
const name = required(rest.join(' ') || options.name, 'NAME', 'Run: giz api-key create "My app"')
|
|
509
|
-
const scopes = options.scopes
|
|
510
|
-
? String(options.scopes).split(',').map((scope) => scope.trim()).filter(Boolean)
|
|
511
|
-
: undefined
|
|
512
|
-
return {
|
|
513
|
-
kind: 'result',
|
|
514
|
-
value: await api(baseUrl, '/api/external/api-keys', {
|
|
515
|
-
name,
|
|
516
|
-
...(scopes ? { scopes } : {}),
|
|
517
|
-
...(options.expires ? { expiresAt: options.expires } : {})
|
|
518
|
-
})
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (command === 'api-key' && subcommand === 'revoke') {
|
|
522
|
-
const id = required(rest[0], 'KEY', 'Run: giz api-key revoke key_...')
|
|
523
|
-
return {
|
|
524
|
-
kind: 'result',
|
|
525
|
-
value: await api(baseUrl, `/api/external/api-keys/${encodeURIComponent(id)}`, undefined, { method: 'DELETE' })
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (command === 'credits') {
|
|
530
|
-
// Batch scripts read the balance before paid work instead of learning it from a refusal (MCP-TOOL-SCHEMA-LIMITS).
|
|
531
|
-
if (subcommand) throw new CliError(`Unknown command "credits ${subcommand}".`, 'Run: giz credits')
|
|
532
|
-
return { kind: 'result', value: await invoke(baseUrl, 'account.credits', {}) }
|
|
533
|
-
}
|
|
534
|
-
if (command === 'model' && subcommand === 'list') {
|
|
535
|
-
return {
|
|
536
|
-
kind: 'models',
|
|
537
|
-
value: await invoke(baseUrl, 'model.list', {
|
|
538
|
-
...(options.type ? { type: options.type } : {}),
|
|
539
|
-
...(options.search ? { search: options.search } : {})
|
|
540
|
-
})
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
if (command === 'model' && subcommand === 'get') {
|
|
544
|
-
const model = required(rest[0] || options.model, 'MODEL', 'Run: giz model get MODEL')
|
|
545
|
-
return { kind: 'model', value: await invoke(baseUrl, 'model.get', { model }) }
|
|
546
|
-
}
|
|
547
|
-
if (command === 'model' && subcommand === 'run') {
|
|
548
|
-
const model = required(rest[0] || options.model, 'MODEL', 'Run: giz model run MODEL --input JSON')
|
|
549
|
-
const input = await applyUploads(baseUrl, jsonInput(options.input), options.upload)
|
|
550
|
-
return { kind: 'job', value: await invoke(baseUrl, 'model.run', { model, input }) }
|
|
551
|
-
}
|
|
552
|
-
if (command === 'models') {
|
|
553
|
-
throw new CliError('Unknown command "models".', 'Did you mean: giz model list')
|
|
554
|
-
}
|
|
555
|
-
if (command === 'chat') {
|
|
556
|
-
const model = required(options.model || subcommand, 'MODEL', 'Run: giz chat MODEL "your prompt"')
|
|
557
|
-
const input = jsonInput(options.input)
|
|
558
|
-
const prompt = rest.join(' ').trim()
|
|
559
|
-
const messages = input.messages || (prompt ? [{ role: 'user', content: prompt }] : undefined)
|
|
560
|
-
required(messages, 'MESSAGES', 'Pass a prompt or --input \'{"messages":[...]}\'')
|
|
561
|
-
return {
|
|
562
|
-
kind: 'result',
|
|
563
|
-
value: await api(baseUrl, '/v1/chat/completions', {
|
|
564
|
-
...input,
|
|
565
|
-
model,
|
|
566
|
-
messages,
|
|
567
|
-
stream: false
|
|
568
|
-
})
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (command === 'generate' && ['image', 'video', 'audio', 'music', 'talking-head'].includes(subcommand)) {
|
|
573
|
-
const tool = {
|
|
574
|
-
image: 'generateImage',
|
|
575
|
-
video: 'generateVideo',
|
|
576
|
-
audio: 'generateAudio',
|
|
577
|
-
music: 'generateMusic',
|
|
578
|
-
'talking-head': 'generateTalkingHead'
|
|
579
|
-
}[subcommand]
|
|
580
|
-
const uploads = [
|
|
581
|
-
...(Array.isArray(options.upload) ? options.upload : []),
|
|
582
|
-
...(options.image ? [`references=${options.image}`] : []),
|
|
583
|
-
...(options.audio ? [`audio=${options.audio}`] : [])
|
|
584
|
-
]
|
|
585
|
-
const input = await applyUploads(baseUrl, {
|
|
586
|
-
...jsonInput(options.input),
|
|
587
|
-
...(rest.length ? { prompt: rest.join(' ') } : {}),
|
|
588
|
-
...(options.model ? { model: options.model } : {})
|
|
589
|
-
}, uploads)
|
|
590
|
-
return { kind: 'job', value: await invoke(baseUrl, tool, input) }
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (command === 'job' && subcommand === 'list') {
|
|
594
|
-
return { kind: 'jobs', value: await invoke(baseUrl, 'job.list', options.limit ? { limit: Number(options.limit) } : {}) }
|
|
595
|
-
}
|
|
596
|
-
if (command === 'job' && subcommand === 'get') {
|
|
597
|
-
const job = required(rest[0], 'JOB', 'Run: giz job get JOB')
|
|
598
|
-
return { kind: 'job', value: await invoke(baseUrl, 'job.get', { job }) }
|
|
599
|
-
}
|
|
600
|
-
if (command === 'job' && subcommand === 'cancel') {
|
|
601
|
-
const job = required(rest[0], 'JOB', 'Run: giz job cancel JOB')
|
|
602
|
-
return { kind: 'job', value: await invoke(baseUrl, 'job.cancel', { job }) }
|
|
603
|
-
}
|
|
604
|
-
if (command === 'job' && subcommand === 'wait') {
|
|
605
|
-
const id = required(rest[0], 'JOB', 'Run: giz job wait JOB')
|
|
606
|
-
while (true) {
|
|
607
|
-
const job = await invoke(baseUrl, 'job.get', { job: id })
|
|
608
|
-
if (['completed', 'failed', 'cancelled'].includes(job.status)) return { kind: 'job', value: job, exitCode: job.status === 'completed' ? 0 : 1 }
|
|
609
|
-
if (process.stderr.isTTY) process.stderr.write(`\r${job.id} ${job.status} ${job.message || ''}`.slice(0, 120))
|
|
610
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (command === 'drive') {
|
|
615
|
-
const mappings = {
|
|
616
|
-
list: () => ['drive.list', { path: rest[0] || '' }],
|
|
617
|
-
search: () => ['drive.search', { query: required(rest.join(' '), 'QUERY', 'Run: giz drive search QUERY') }],
|
|
618
|
-
read: () => ['drive.readFile', { path: required(rest[0], 'PATH', 'Run: giz drive read PATH') }],
|
|
619
|
-
mkdir: () => ['drive.makeDirectory', { path: required(rest[0], 'PATH', 'Run: giz drive mkdir PATH') }],
|
|
620
|
-
move: () => ['drive.movePath', {
|
|
621
|
-
from: required(rest[0], 'FROM', 'Run: giz drive move FROM TO'),
|
|
622
|
-
to: required(rest[1], 'TO', 'Run: giz drive move FROM TO')
|
|
623
|
-
}],
|
|
624
|
-
delete: () => ['drive.deletePath', { path: required(rest[0], 'PATH', 'Run: giz drive delete PATH') }]
|
|
625
|
-
}
|
|
626
|
-
if (subcommand === 'write') {
|
|
627
|
-
const drivePath = required(rest[0], 'PATH', 'Run: giz drive write PATH --content TEXT')
|
|
628
|
-
let content = options.content
|
|
629
|
-
let encoding = 'utf8'
|
|
630
|
-
if (options.file) {
|
|
631
|
-
const buffer = await readFile(String(options.file))
|
|
632
|
-
content = buffer.toString('base64')
|
|
633
|
-
encoding = 'base64'
|
|
634
|
-
}
|
|
635
|
-
required(content, 'CONTENT', 'Use --content TEXT or --file LOCAL_PATH.')
|
|
636
|
-
return {
|
|
637
|
-
kind: 'result',
|
|
638
|
-
value: await invoke(baseUrl, 'drive.writeFile', {
|
|
639
|
-
path: drivePath,
|
|
640
|
-
content: String(content),
|
|
641
|
-
encoding,
|
|
642
|
-
...(options.computer ? { computer: options.computer } : {})
|
|
643
|
-
})
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
const mapping = mappings[subcommand === 'ls' ? 'list' : subcommand]
|
|
647
|
-
if (!mapping) throw new CliError(`Unknown drive command "${subcommand || ''}".`, 'Run: giz drive --help')
|
|
648
|
-
const [tool, input] = mapping()
|
|
649
|
-
return {
|
|
650
|
-
kind: 'result',
|
|
651
|
-
value: await invoke(baseUrl, tool, { ...input, ...(options.computer ? { computer: options.computer } : {}) })
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (command === 'table' && subcommand === 'list') {
|
|
656
|
-
return { kind: 'result', value: await invoke(baseUrl, 'workspace.objects', {}) }
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if (command === 'table' && subcommand === 'get') {
|
|
660
|
-
const object = required(rest[0], 'TABLE', 'Run: giz table get task')
|
|
661
|
-
return { kind: 'result', value: await invoke(baseUrl, 'workspace.objects', { object }) }
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
if (command === 'table' && subcommand === 'action') {
|
|
665
|
-
const view = required(rest[0], 'TABLE', 'Run: giz table action TABLE ACTION [--id ID] --input JSON')
|
|
666
|
-
const action = required(rest[1], 'ACTION', 'Run: giz table get TABLE to see its actions')
|
|
667
|
-
// Namespaced table tools use their canonical external contract and table ownership checks.
|
|
668
|
-
if (action.startsWith('table.')) {
|
|
669
|
-
if (options.id !== undefined) throw new CliError('Table tools do not take --id.', 'Put row IDs in --input.')
|
|
670
|
-
return { kind: 'result', value: await invoke(baseUrl, action, { ...jsonInput(options.input), table: view }) }
|
|
671
|
-
}
|
|
672
|
-
const id = options.id === undefined ? undefined : String(options.id).includes(',') ? String(options.id).split(',') : String(options.id)
|
|
673
|
-
return {
|
|
674
|
-
kind: 'result',
|
|
675
|
-
value: await invoke(baseUrl, 'workspace.executeAction', { view, action, ...(id === undefined ? {} : { dataId: id }), data: jsonInput(options.input) })
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (command === 'table' && ['read', 'write'].includes(subcommand)) {
|
|
680
|
-
const table = required(rest[0], 'TABLE', `Run: giz table ${subcommand} TABLE_PATH --input JSON`)
|
|
681
|
-
return {
|
|
682
|
-
kind: 'result',
|
|
683
|
-
value: await invoke(baseUrl, subcommand === 'read' ? 'table.readRows' : 'table.writeRows', {
|
|
684
|
-
...jsonInput(options.input),
|
|
685
|
-
table,
|
|
686
|
-
...(options.computer ? { computer: options.computer } : {})
|
|
687
|
-
})
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
if (command === 'upload') {
|
|
692
|
-
const localFile = required(subcommand, 'LOCAL_PATH', 'Run: giz upload ./reference.png')
|
|
693
|
-
return { kind: 'result', value: await uploadMedia(baseUrl, localFile) }
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if (command === 'computer') {
|
|
697
|
-
if (subcommand === 'list') return { kind: 'computers', value: await invoke(baseUrl, 'computer.list', {}) }
|
|
698
|
-
const computer = options.computer || (subcommand === 'exec' ? undefined : rest[0])
|
|
699
|
-
if (subcommand === 'get') return { kind: 'result', value: await invoke(baseUrl, 'computer.get', computer ? { computer } : {}) }
|
|
700
|
-
if (subcommand === 'start' || subcommand === 'stop') {
|
|
701
|
-
return {
|
|
702
|
-
kind: 'result',
|
|
703
|
-
value: await invoke(baseUrl, `computer.${subcommand}`, computer ? { computer } : {})
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
if (subcommand === 'exec') {
|
|
707
|
-
if (!rest.length) throw new CliError('COMMAND is required.', 'Run: giz computer exec -- ls -la')
|
|
708
|
-
const stdin = options['stdin-file'] ? await readFile(String(options['stdin-file']), 'utf8') : options.stdin
|
|
709
|
-
const value = await invoke(baseUrl, 'computer.exec', {
|
|
710
|
-
command: rest,
|
|
711
|
-
...(stdin !== undefined ? { stdin: String(stdin) } : {}),
|
|
712
|
-
...(options['deadline-ms'] !== undefined ? { deadlineMs: Number(options['deadline-ms']) } : {}),
|
|
713
|
-
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
714
|
-
...(computer ? { computer } : {})
|
|
715
|
-
})
|
|
716
|
-
return { kind: 'result', value, exitCode: value.exitCode }
|
|
717
|
-
}
|
|
718
|
-
throw new CliError(`Unknown computer command "${subcommand || ''}".`, 'Run: giz computer --help')
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
if (command === 'app') {
|
|
722
|
-
const computer = options.computer ? { computer: options.computer } : {}
|
|
723
|
-
if (subcommand === 'list') {
|
|
724
|
-
return { kind: 'result', value: await invoke(baseUrl, 'app.list', computer) }
|
|
725
|
-
}
|
|
726
|
-
if (subcommand === 'save' || subcommand === 'create') {
|
|
727
|
-
const input = jsonInput(options.input)
|
|
728
|
-
return { kind: 'result', value: await invoke(baseUrl, 'app.save', { ...input, ...computer }) }
|
|
729
|
-
}
|
|
730
|
-
if (subcommand === 'run') {
|
|
731
|
-
const id = required(rest[0], 'APP_ID', 'Run: giz app run APP_ID --expected-revision REVISION')
|
|
732
|
-
return { kind: 'result', value: await invoke(baseUrl, 'app.run', { ...computer, id, expectedRevision: appRevision(options) }) }
|
|
733
|
-
}
|
|
734
|
-
if (subcommand === 'publish') {
|
|
735
|
-
const id = required(rest[0], 'APP_ID', 'Run: giz app publish APP_ID private|workspace|public')
|
|
736
|
-
const visibility = required(rest[1] || options.visibility, 'VISIBILITY', 'Run: giz app publish APP_ID private|workspace|public')
|
|
737
|
-
return { kind: 'result', value: await invoke(baseUrl, 'app.publish', { ...computer, id, visibility, expectedRevision: appRevision(options) }) }
|
|
738
|
-
}
|
|
739
|
-
if (subcommand === 'open') {
|
|
740
|
-
const id = required(rest[0], 'APP_ID', 'Run: giz app open APP_ID')
|
|
741
|
-
return { kind: 'result', value: await invoke(baseUrl, 'app.open', { ...computer, id }) }
|
|
742
|
-
}
|
|
743
|
-
if (subcommand === 'delete') {
|
|
744
|
-
const id = required(rest[0], 'APP_ID', 'Run: giz app delete APP_ID')
|
|
745
|
-
return { kind: 'result', value: await invoke(baseUrl, 'app.delete', { ...computer, id, expectedRevision: appRevision(options) }) }
|
|
746
|
-
}
|
|
747
|
-
throw new CliError(`Unknown app command "${subcommand || ''}".`, 'Run: giz app --help')
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
if (command === 'connection') {
|
|
751
|
-
if (subcommand === 'list') {
|
|
752
|
-
return {
|
|
753
|
-
kind: 'result',
|
|
754
|
-
value: await invoke(baseUrl, 'capability.status', {
|
|
755
|
-
...(options.computer ? { computer: options.computer } : {})
|
|
756
|
-
})
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
throw new CliError(`Unknown connection command "${subcommand || ''}".`, 'Run: giz connection --help')
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
if (command === 'plugin') {
|
|
763
|
-
const computer = options.computer ? { computer: options.computer } : {}
|
|
764
|
-
if (subcommand === 'list' || subcommand === 'search') {
|
|
765
|
-
const query = subcommand === 'search'
|
|
766
|
-
? required(rest.join(' '), 'QUERY', 'Run: giz plugin search QUERY [--origin catalog|skill|mcp]')
|
|
767
|
-
: undefined
|
|
768
|
-
return {
|
|
769
|
-
kind: 'result',
|
|
770
|
-
value: await invoke(baseUrl, 'plugin.catalog', {
|
|
771
|
-
...(query ? { query } : { origin: 'installed' }),
|
|
772
|
-
...(query && options.origin ? { origin: options.origin } : {}),
|
|
773
|
-
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
774
|
-
...computer
|
|
775
|
-
})
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
if (subcommand === 'inspect') {
|
|
779
|
-
const drivePath = required(rest[0], 'PATH', 'Run: giz plugin inspect DRIVE_PATH')
|
|
780
|
-
return { kind: 'result', value: await invoke(baseUrl, 'plugin.catalog', { path: drivePath, ...computer }) }
|
|
781
|
-
}
|
|
782
|
-
if (subcommand === 'install') {
|
|
783
|
-
const source = options.path
|
|
784
|
-
? { path: options.path }
|
|
785
|
-
: { id: required(rest[0], 'ID', 'Run: giz plugin install ID --query QUERY, or giz plugin install --path DRIVE_PATH') }
|
|
786
|
-
return {
|
|
787
|
-
kind: 'result',
|
|
788
|
-
value: await invoke(baseUrl, 'plugin.install', {
|
|
789
|
-
...source,
|
|
790
|
-
...(options.query ? { query: options.query } : {}),
|
|
791
|
-
...computer
|
|
792
|
-
})
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
if (['update', 'enable', 'disable', 'remove'].includes(subcommand)) {
|
|
796
|
-
const name = required(rest[0], 'NAME', `Run: giz plugin ${subcommand} NAME`)
|
|
797
|
-
return { kind: 'result', value: await invoke(baseUrl, 'plugin.manage', { operation: subcommand, name, ...computer }) }
|
|
798
|
-
}
|
|
799
|
-
if (subcommand === 'publish') {
|
|
800
|
-
const drivePath = required(rest[0], 'PATH', 'Run: giz plugin publish DRIVE_PATH --visibility organization|public')
|
|
801
|
-
const visibility = required(options.visibility, 'VISIBILITY', 'Add --visibility organization|public.')
|
|
802
|
-
return {
|
|
803
|
-
kind: 'result',
|
|
804
|
-
value: await invoke(baseUrl, 'plugin.manage', {
|
|
805
|
-
operation: 'publish',
|
|
806
|
-
path: drivePath,
|
|
807
|
-
visibility,
|
|
808
|
-
...(options.organization ? { organizationId: options.organization } : {}),
|
|
809
|
-
...computer
|
|
810
|
-
})
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
if (subcommand === 'unpublish') {
|
|
814
|
-
const releaseId = required(rest[0], 'RELEASE', 'Run: giz plugin unpublish RELEASE')
|
|
815
|
-
return { kind: 'result', value: await invoke(baseUrl, 'plugin.manage', { operation: 'unpublish', releaseId }) }
|
|
816
|
-
}
|
|
817
|
-
throw new CliError(`Unknown plugin command "${subcommand || ''}".`, 'Run: giz plugin --help')
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
if (command === 'agent') {
|
|
821
|
-
if (subcommand === 'start') {
|
|
822
|
-
return {
|
|
823
|
-
kind: 'result',
|
|
824
|
-
value: await invoke(baseUrl, 'agent.start', {
|
|
825
|
-
...jsonInput(options.input),
|
|
826
|
-
...(options.computer ? { computer: options.computer } : {})
|
|
827
|
-
})
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
if (subcommand === 'status' || subcommand === 'cancel') {
|
|
831
|
-
const turnId = required(rest[0], 'TURN', `Run: giz agent ${subcommand} TURN`)
|
|
832
|
-
return {
|
|
833
|
-
kind: 'result',
|
|
834
|
-
value: await invoke(baseUrl, `agent.${subcommand}`, {
|
|
835
|
-
turnId,
|
|
836
|
-
...(subcommand === 'status' && options.wait ? { wait: true } : {})
|
|
837
|
-
})
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
throw new CliError(`Unknown agent command "${subcommand || ''}".`, 'Run: giz agent --help')
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (command === 'invoke') {
|
|
844
|
-
const tool = required(subcommand, 'TOOL', 'Run: giz invoke TOOL --input JSON')
|
|
845
|
-
return { kind: 'result', value: await invoke(baseUrl, tool, jsonInput(options.input)) }
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
if (command === 'mcp' && subcommand === 'url') {
|
|
849
|
-
return { kind: 'message', value: new URL('/api/mcp', baseUrl).href }
|
|
850
|
-
}
|
|
851
|
-
if (command === 'mcp' && subcommand === 'serve') {
|
|
852
|
-
await serveMcp(baseUrl)
|
|
853
|
-
return
|
|
854
|
-
}
|
|
855
|
-
if (command === 'mcp' && subcommand === 'check') {
|
|
856
|
-
const client = new Client({ name: 'giz-cli', version: CLI_VERSION })
|
|
857
|
-
try {
|
|
858
|
-
await client.connect(mcpTransport(baseUrl))
|
|
859
|
-
const { tools } = await client.listTools()
|
|
860
|
-
if (!tools.length) throw new CliError('Giz MCP returned zero tools.', 'Check your Giz connection and scopes.')
|
|
861
|
-
return { kind: 'result', value: { server: client.getServerVersion(), tools: tools.map((tool) => tool.name) } }
|
|
862
|
-
} finally { await client.close() }
|
|
863
|
-
}
|
|
864
|
-
if (command === 'mcp' && subcommand === 'add') {
|
|
865
|
-
const client = required(rest[0], 'CLIENT', 'Run: giz mcp add codex|claude')
|
|
866
|
-
if (!['codex', 'claude'].includes(client)) throw new CliError(`Unsupported MCP client "${client}".`, 'Supported clients: codex, claude')
|
|
867
|
-
const transport = options.transport || 'stdio'
|
|
868
|
-
if (!['stdio', 'http'].includes(transport)) throw new CliError('MCP transport must be stdio or http.')
|
|
869
|
-
const endpoint = new URL('/api/mcp', baseUrl).href
|
|
870
|
-
const target = transport === 'stdio'
|
|
871
|
-
? ['--env', `GIZ_CONFIG=${configPath}`, '--', process.execPath, fileURLToPath(import.meta.url), 'mcp', 'serve', '--base-url', baseUrl]
|
|
872
|
-
: client === 'codex' ? ['--url', endpoint, '--oauth-resource', endpoint] : [endpoint]
|
|
873
|
-
runExternal(client, ['mcp', 'add', ...(client === 'claude' ? ['--scope', 'user', '--transport', transport] : []), 'giz', ...target],
|
|
874
|
-
`Install ${client === 'codex' ? 'Codex: npm install -g @openai/codex' : 'Claude Code: npm install -g @anthropic-ai/claude-code'}`, transport === 'http')
|
|
875
|
-
return { kind: 'message', value: `Giz was added to ${client} (${transport}). ${transport === 'stdio'
|
|
876
|
-
? 'Run giz auth login, then giz mcp check --json before starting a headless session.'
|
|
877
|
-
: client === 'claude' ? 'Open Claude Code and run /mcp to sign in.' : 'Complete the browser approval opened by Codex.'}` }
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
if (command === 'skill' && subcommand === 'install') {
|
|
881
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'skills')
|
|
882
|
-
runExternal('npx', ['--yes', 'skills', 'add', root, '--skill', '*', '--agent', 'universal', '--copy', '-y'], 'Install Node.js 20 or newer first.')
|
|
883
|
-
return { kind: 'message', value: 'Giz Skill installed for available AI agents.' }
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
throw new CliError(`Unknown command "${[command, subcommand].filter(Boolean).join(' ')}".`, 'Run: giz --help')
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
function runExternal(command, args, hint, interactive = false) {
|
|
890
|
-
const result = spawnSync(command, args, { encoding: 'utf8', stdio: interactive ? ['inherit', 2, 2] : ['inherit', 'pipe', 'pipe'] })
|
|
891
|
-
if (result.error?.code === 'ENOENT') throw new CliError(`${command} is not installed.`, hint)
|
|
892
|
-
if (result.error || result.status !== 0) throw new CliError(
|
|
893
|
-
`${command} failed (${result.error?.code || result.signal || result.status}): ${(result.stderr || result.stdout || result.error?.message || '').trim()}`
|
|
894
|
-
)
|
|
895
|
-
if (result.stderr || result.stdout) process.stderr.write(result.stderr + result.stdout)
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
function print(kind, value, json, output = process.stdout) {
|
|
899
|
-
if (json) {
|
|
900
|
-
output.write(`${JSON.stringify(value, null, 2)}\n`)
|
|
901
|
-
return
|
|
902
|
-
}
|
|
903
|
-
if (kind === 'help' || kind === 'message') {
|
|
904
|
-
output.write(`${value}\n`)
|
|
905
|
-
return
|
|
906
|
-
}
|
|
907
|
-
if (kind === 'auth' && value.identity?.type === 'computer') {
|
|
908
|
-
output.write(`Signed in as a Computer identity (owner ${value.email || value.name})\n`)
|
|
909
|
-
return
|
|
910
|
-
}
|
|
911
|
-
if (kind === 'models') {
|
|
912
|
-
const rows = value.models || []
|
|
913
|
-
for (const model of rows) output.write(`${model.id}\t${model.type}\t${model.provider || '-'}\t${model.label}\n`)
|
|
914
|
-
output.write(`\n${rows.length} models\n`)
|
|
915
|
-
return
|
|
916
|
-
}
|
|
917
|
-
if (kind === 'jobs') {
|
|
918
|
-
for (const job of value.jobs || []) output.write(`${job.id}\t${job.status}\t${job.tool}\t${job.createdAt}\n`)
|
|
919
|
-
return
|
|
920
|
-
}
|
|
921
|
-
if (kind === 'computers') {
|
|
922
|
-
for (const computer of value.computers || []) {
|
|
923
|
-
output.write(`${computer.id}\t${computer.status || '-'}\t${computer.isDefault ? 'default' : '-'}\t${computer.name}\n`)
|
|
924
|
-
}
|
|
925
|
-
return
|
|
926
|
-
}
|
|
927
|
-
if (kind === 'job') {
|
|
928
|
-
output.write(`${value.id} ${value.status}\n`)
|
|
929
|
-
if (value.message) output.write(`${value.message}\n`)
|
|
930
|
-
if (value.error) output.write(`Error: ${value.error}\n`)
|
|
931
|
-
if (value.result) output.write(`${JSON.stringify(value.result, null, 2)}\n`)
|
|
932
|
-
if (!['completed', 'failed', 'cancelled'].includes(value.status)) {
|
|
933
|
-
output.write(`Run: giz job wait ${value.id}\n`)
|
|
934
|
-
}
|
|
935
|
-
return
|
|
936
|
-
}
|
|
937
|
-
output.write(`${JSON.stringify(value, null, 2)}\n`)
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
function help(command) {
|
|
941
|
-
if (command === 'engine') return `Usage
|
|
942
|
-
giz engine run claude "instruction" [--dir path]
|
|
943
|
-
|
|
944
|
-
Runs the official Claude Code in an isolated worktree. Connect Claude Code in Giz first.
|
|
945
|
-
Progress and the final diff, files and summary are returned. Interrupting stops the engine.`
|
|
946
|
-
if (command === 'api-key') return `Usage
|
|
947
|
-
giz api-key list
|
|
948
|
-
giz api-key create "My app" [--scopes giz.inference,giz.read] [--expires ISO_TIMESTAMP]
|
|
949
|
-
giz api-key revoke KEY_ID
|
|
950
|
-
|
|
951
|
-
The secret is shown only when the key is created.`
|
|
952
|
-
if (command === 'credits') return `Usage
|
|
953
|
-
giz credits
|
|
954
|
-
|
|
955
|
-
Prints your remaining credit (purchased and plan), when plan credit resets, and this month's spend and charges.
|
|
956
|
-
In a workspace it prints the workspace's monthly budget. Read it before paid batch work.`
|
|
957
|
-
if (command === 'chat') return `Usage
|
|
958
|
-
giz chat MODEL "your prompt"
|
|
959
|
-
giz chat MODEL --input '{"messages":[{"role":"user","content":"Hello"}]}'`
|
|
960
|
-
if (command === 'model') return `Usage
|
|
961
|
-
giz model list [--type TYPE] [--search TEXT]
|
|
962
|
-
giz model get MODEL
|
|
963
|
-
giz model run MODEL --input JSON [--upload FIELD=LOCAL_PATH]
|
|
964
|
-
|
|
965
|
-
Run model get first. Its live input schema is the source of truth. Repeat --upload for multiple media inputs.
|
|
966
|
-
model run returns a job immediately. Retrieve the result with giz job wait JOB_ID, or poll with giz job get JOB_ID.
|
|
967
|
-
For speech-to-text, use model run with the audio field returned by model get. Callback/webhook delivery is not supported.`
|
|
968
|
-
if (command === 'generate') return `Usage
|
|
969
|
-
giz generate image|video|music|talking-head "PROMPT" [--model MODEL]
|
|
970
|
-
giz generate audio --input '{"purpose":"speech","script":"Hello"}' [--model MODEL]
|
|
971
|
-
giz generate audio --input '{"purpose":"sound","prompt":"Rain","duration":5}'
|
|
972
|
-
|
|
973
|
-
Options
|
|
974
|
-
--image LOCAL_PATH Upload a visual reference
|
|
975
|
-
--audio LOCAL_PATH Upload speech audio
|
|
976
|
-
--upload FIELD=PATH Upload into an exact tool field
|
|
977
|
-
--input JSON Input matching the tool schema (@FILE reads a file, - reads stdin)
|
|
978
|
-
|
|
979
|
-
For model-specific audio options, inspect giz model get MODEL and pass supported fields in settings (for example settings.speed), not at the top level.
|
|
980
|
-
Generation returns a job immediately; use giz job wait JOB_ID for its result.`
|
|
981
|
-
if (command === 'drive') return `Usage
|
|
982
|
-
giz drive ls [PATH] (list alias)
|
|
983
|
-
giz drive search QUERY
|
|
984
|
-
giz drive read PATH
|
|
985
|
-
giz drive write PATH --content TEXT
|
|
986
|
-
giz drive write PATH --file LOCAL_PATH
|
|
987
|
-
giz drive mkdir PATH
|
|
988
|
-
giz drive move FROM TO
|
|
989
|
-
giz drive delete PATH`
|
|
990
|
-
if (command === 'table') return `Usage
|
|
991
|
-
giz table list
|
|
992
|
-
giz table get TABLE
|
|
993
|
-
giz table read TABLE [--input JSON] [--computer COMPUTER]
|
|
994
|
-
giz table write TABLE --input JSON [--computer COMPUTER]
|
|
995
|
-
giz table action TABLE ACTION [--id ID[,ID...]] --input JSON
|
|
996
|
-
|
|
997
|
-
TABLE is a table's Drive path (for example Sales/Customers.view.ts) or a business object name from giz table list
|
|
998
|
-
(for example task, issue, requirement). giz table get shows its fields, the row changes it accepts (writes) and its
|
|
999
|
-
actions. Namespaced table actions (such as table.updateSchema) use the table tool contract; an object without a write changes only through its actions. Use --org to work in an organization.
|
|
1000
|
-
read input: {"select":["name"],"filters":[{"field":"status","operator":"eq","value":"open"}],"limit":50}
|
|
1001
|
-
write input: {"create":[{"name":"Acme"}],"update":[{"id":"ROW_ID","status":"done"}],"remove":["ROW_ID"]}
|
|
1002
|
-
One write is one change people can undo in the table. At most 200 rows per write.`
|
|
1003
|
-
if (command === 'computer') return `Usage
|
|
1004
|
-
giz computer list
|
|
1005
|
-
giz computer get|start|stop [COMPUTER]
|
|
1006
|
-
giz computer exec [--computer COMPUTER] [--cwd PATH] [--deadline-ms MS] [--stdin TEXT|--stdin-file PATH] -- COMMAND [ARGS...]`
|
|
1007
|
-
if (command === 'app') return `Usage
|
|
1008
|
-
giz app list [--computer COMPUTER]
|
|
1009
|
-
giz app create|save [--computer COMPUTER] --input JSON
|
|
1010
|
-
giz app run APP_ID --expected-revision REVISION [--computer COMPUTER]
|
|
1011
|
-
giz app publish APP_ID private|workspace|public --expected-revision REVISION [--computer COMPUTER]
|
|
1012
|
-
giz app open APP_ID [--computer COMPUTER]
|
|
1013
|
-
giz app delete APP_ID --expected-revision REVISION [--computer COMPUTER]`
|
|
1014
|
-
if (command === 'plugin') return `Usage
|
|
1015
|
-
giz plugin list [--computer COMPUTER]
|
|
1016
|
-
giz plugin search QUERY [--origin catalog|skill|mcp] [--limit N]
|
|
1017
|
-
giz plugin inspect DRIVE_PATH
|
|
1018
|
-
giz plugin install ID --query QUERY [--computer COMPUTER]
|
|
1019
|
-
giz plugin install --path DRIVE_PATH [--computer COMPUTER]
|
|
1020
|
-
giz plugin update|enable|disable|remove NAME [--computer COMPUTER]
|
|
1021
|
-
giz plugin publish DRIVE_PATH --visibility organization|public [--organization ORG]
|
|
1022
|
-
giz plugin unpublish RELEASE
|
|
1023
|
-
|
|
1024
|
-
A plugin (Agent Plugins 1.0) bundles skills, MCP servers and Agents; it is what you install, turn off and share.`
|
|
1025
|
-
if (command === 'connection') return `Usage
|
|
1026
|
-
giz connection list [--computer COMPUTER]`
|
|
1027
|
-
if (command === 'agent') return `Usage
|
|
1028
|
-
giz agent start [--computer COMPUTER] --input JSON
|
|
1029
|
-
giz agent status TURN [--wait]
|
|
1030
|
-
giz agent cancel TURN`
|
|
1031
|
-
if (command === 'mcp') return `Usage
|
|
1032
|
-
giz mcp url
|
|
1033
|
-
giz mcp serve
|
|
1034
|
-
giz mcp check --json
|
|
1035
|
-
giz mcp add codex|claude [--transport stdio|http]
|
|
1036
|
-
|
|
1037
|
-
stdio (default) shares giz auth login and refreshes tokens automatically.
|
|
1038
|
-
http uses the client's own OAuth login. Install the chosen client first.`
|
|
1039
|
-
if (command === 'update') return `Usage\n giz update\n\nUpdates @gizai/cli with its owning package manager.`
|
|
1040
|
-
if (command === 'auth') return `Usage
|
|
1041
|
-
giz auth login [--device]
|
|
1042
|
-
giz auth status
|
|
1043
|
-
giz auth logout`
|
|
1044
|
-
return `Giz CLI — AI models, Drive, and Computer from any terminal
|
|
1045
|
-
|
|
1046
|
-
Usage
|
|
1047
|
-
giz auth login [--device]
|
|
1048
|
-
giz update
|
|
1049
|
-
giz api-key list|create|revoke
|
|
1050
|
-
giz chat MODEL "your prompt"
|
|
1051
|
-
giz model list [--type TYPE] [--search TEXT]
|
|
1052
|
-
giz model get MODEL
|
|
1053
|
-
giz model run MODEL --input JSON [--upload FIELD=LOCAL_PATH]
|
|
1054
|
-
giz generate image|video|music|talking-head "PROMPT" [--model MODEL]
|
|
1055
|
-
giz generate audio --input JSON [--model MODEL]
|
|
1056
|
-
giz job list|get|wait|cancel [JOB]
|
|
1057
|
-
giz credits
|
|
1058
|
-
giz drive ls|list|search|read|write|mkdir|move|delete
|
|
1059
|
-
giz table list|get|read|write|action
|
|
1060
|
-
giz upload LOCAL_PATH
|
|
1061
|
-
giz computer list|get|start|stop|exec
|
|
1062
|
-
giz app list|create|save|run|publish|open|delete
|
|
1063
|
-
giz plugin list|search|inspect|install|update|enable|disable|remove|publish|unpublish
|
|
1064
|
-
giz connection list
|
|
1065
|
-
giz agent start|status|cancel
|
|
1066
|
-
giz engine run claude "instruction" [--dir path]
|
|
1067
|
-
giz mcp url|serve|check|add
|
|
1068
|
-
giz skill install
|
|
1069
|
-
giz invoke TOOL --input JSON
|
|
1070
|
-
|
|
1071
|
-
Use giz COMMAND --help for parameters and examples.
|
|
1072
|
-
|
|
1073
|
-
Global options
|
|
1074
|
-
--json Machine-readable JSON output
|
|
1075
|
-
--base-url URL Another Giz deployment
|
|
1076
|
-
--org ORGANIZATION Organization id to work in (or GIZ_ORGANIZATION)
|
|
1077
|
-
--no-open Print the login URL without opening a browser
|
|
1078
|
-
|
|
1079
|
-
Docs: https://www.giz.ai/mcp/`
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
async function main() {
|
|
1083
|
-
const { positionals, options } = parse(process.argv.slice(2))
|
|
1084
|
-
if (positionals[0] === '__update-check') {
|
|
1085
|
-
await runCommand('', positionals, options)
|
|
1086
|
-
return
|
|
1087
|
-
}
|
|
1088
|
-
const source = installSource()
|
|
1089
|
-
const saved = await readConfig()
|
|
1090
|
-
const updateMode = ['notify', 'off'].includes(saved?.updates?.mode) ? saved.updates.mode : 'notify'
|
|
1091
|
-
void maybeNotify({ version: CLI_VERSION, source, mode: updateMode, env: { ...process.env, GIZ_JSON: options.json === true ? '1' : process.env.GIZ_JSON } }).catch(() => {})
|
|
1092
|
-
const requiresAuth = positionals.length && !options.help && !options.version &&
|
|
1093
|
-
!(positionals[0] === 'auth' && ['login', 'logout'].includes(positionals[1])) &&
|
|
1094
|
-
!['update', '__update-check'].includes(positionals[0])
|
|
1095
|
-
const computer = requiresAuth && !process.env.GIZ_TOKEN && !saved?.access_token ? await computerIdentity() : undefined
|
|
1096
|
-
const baseUrl = String(options['base-url'] || process.env.GIZ_URL || saved?.baseUrl || computer?.baseUrl || DEFAULT_URL).replace(/\/+$/, '')
|
|
1097
|
-
if (typeof options.org === 'string') process.env.GIZ_ORGANIZATION = options.org
|
|
1098
|
-
options.input = await inputText(options.input)
|
|
1099
|
-
const output = await runCommand(baseUrl, positionals, options)
|
|
1100
|
-
if (output) {
|
|
1101
|
-
print(output.kind, output.value, options.json === true)
|
|
1102
|
-
process.exitCode = output.exitCode ?? (output.value?.isError ? 1 : 0)
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
main().catch((error) => {
|
|
1107
|
-
const { options } = parse(process.argv.slice(2))
|
|
1108
|
-
const json = options.json === true
|
|
1109
|
-
const failure = { message: String(error?.message || error), hint: error?.hint, status: error?.status }
|
|
1110
|
-
print('message', json ? { error: failure } : `Error: ${failure.message}${failure.hint ? `\nHint: ${failure.hint}` : ''}`, json, process.stderr)
|
|
1111
|
-
process.exitCode = 1
|
|
1112
|
-
})
|