@shawnstack/quickforge 1.3.18 → 1.3.19
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 +10 -10
- package/bin/quickforge.mjs +258 -49
- package/dist/assets/anthropic-Bj3HAZgj.js +39 -0
- package/dist/assets/azure-openai-responses-IdZZrSrI.js +1 -0
- package/dist/assets/github-copilot-headers-CMb2BbzT.js +1 -0
- package/dist/assets/google-Brt_lS1J.js +1 -0
- package/dist/assets/{google-shared-XhYUKiGZ.js → google-shared-CLc4ziON.js} +3 -3
- package/dist/assets/google-vertex-B6HsoZ34.js +1 -0
- package/dist/assets/{index-Dm7aEWvT.js → index-D0CVLdX_.js} +525 -489
- package/dist/assets/index-D0W9hAl_.css +3 -0
- package/dist/assets/{mistral-DxhS4Wkn.js → mistral-CenXqwPz.js} +3 -3
- package/dist/assets/openai-codex-responses-D9ffGwbj.js +7 -0
- package/dist/assets/openai-completions-eWdeSGBG.js +5 -0
- package/dist/assets/openai-responses-Cavpmjeu.js +1 -0
- package/dist/assets/{openai-responses-shared-f_P3e1nz.js → openai-responses-shared-DF3ZGaUx.js} +5 -3
- package/dist/assets/transform-messages-CmnxG9RB.js +1 -0
- package/dist/index.html +2 -2
- package/node_modules/@anthropic-ai/sdk/CHANGELOG.md +34 -0
- package/node_modules/@anthropic-ai/sdk/bin/migration-config.json +185 -0
- package/node_modules/@anthropic-ai/sdk/package.json +1 -1
- package/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +4 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +4 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/files.js +5 -5
- package/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +5 -5
- package/node_modules/@anthropic-ai/sdk/resources/beta/index.js +11 -9
- package/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +1 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.js +11 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.mjs +5 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.js +130 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.mjs +126 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.js +145 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.mjs +140 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.js +81 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.mjs +77 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.js +6 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.mjs +3 -0
- package/node_modules/@anthropic-ai/sdk/tools/memory/node.js +12 -5
- package/node_modules/@anthropic-ai/sdk/tools/memory/node.mjs +12 -5
- package/node_modules/@anthropic-ai/sdk/version.js +1 -1
- package/node_modules/@anthropic-ai/sdk/version.mjs +1 -1
- package/node_modules/@aws-sdk/client-bedrock-runtime/package.json +5 -5
- package/node_modules/@aws-sdk/core/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
- package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
- package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
- package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
- package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
- package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
- package/node_modules/@aws-sdk/token-providers/package.json +3 -3
- package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
- package/node_modules/@mariozechner/pi-agent-core/README.md +14 -0
- package/node_modules/@mariozechner/pi-agent-core/dist/agent-loop.js +9 -0
- package/node_modules/@mariozechner/pi-agent-core/dist/agent.js +1 -1
- package/node_modules/@mariozechner/pi-agent-core/package.json +2 -2
- package/node_modules/@mariozechner/pi-ai/README.md +20 -31
- package/node_modules/@mariozechner/pi-ai/dist/env-api-keys.js +7 -0
- package/node_modules/@mariozechner/pi-ai/dist/index.js +2 -0
- package/node_modules/@mariozechner/pi-ai/dist/models.generated.js +2420 -1213
- package/node_modules/@mariozechner/pi-ai/dist/models.js +28 -20
- package/node_modules/@mariozechner/pi-ai/dist/providers/amazon-bedrock.js +11 -11
- package/node_modules/@mariozechner/pi-ai/dist/providers/anthropic.js +43 -26
- package/node_modules/@mariozechner/pi-ai/dist/providers/azure-openai-responses.js +12 -6
- package/node_modules/@mariozechner/pi-ai/dist/providers/cloudflare.js +10 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-shared.js +4 -13
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-vertex.js +4 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/google.js +4 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/mistral.js +8 -7
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-codex-responses.js +296 -41
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js +169 -153
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses-shared.js +14 -1
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses.js +22 -8
- package/node_modules/@mariozechner/pi-ai/dist/providers/register-builtins.js +0 -18
- package/node_modules/@mariozechner/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@mariozechner/pi-ai/dist/session-resources.js +22 -0
- package/node_modules/@mariozechner/pi-ai/dist/utils/diagnostics.js +25 -0
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/index.js +0 -10
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/openai-codex.js +25 -14
- package/node_modules/@mariozechner/pi-ai/dist/utils/overflow.js +14 -0
- package/node_modules/@mariozechner/pi-ai/package.json +2 -6
- package/package.json +3 -3
- package/server/agent-manager.mjs +279 -12
- package/server/auto-compaction.mjs +1 -2
- package/server/conversation-compaction.mjs +0 -5
- package/server/index.mjs +1 -0
- package/server/routes/static.mjs +1 -0
- package/server/routes/tools.mjs +3 -1
- package/server/session-utils.mjs +6 -1
- package/server/share-store.mjs +27 -4
- package/server/subagents.mjs +101 -0
- package/server/system-prompt.mjs +30 -1
- package/server/tools/definitions.mjs +18 -0
- package/server/tools/index.mjs +1013 -911
- package/dist/assets/anthropic-Ck2DxOfr.js +0 -39
- package/dist/assets/azure-openai-responses-DIoz5q4Z.js +0 -1
- package/dist/assets/github-copilot-headers-CrI0CIJ7.js +0 -1
- package/dist/assets/google-Dau-4ve_.js +0 -1
- package/dist/assets/google-gemini-cli-DttMmbGb.js +0 -2
- package/dist/assets/google-vertex-BeukMl44.js +0 -1
- package/dist/assets/index-DgJVElbv.css +0 -3
- package/dist/assets/openai-codex-responses-X3sTzNAa.js +0 -7
- package/dist/assets/openai-completions-CRB9Vm0w.js +0 -5
- package/dist/assets/openai-responses-DXluu3oi.js +0 -1
- package/dist/assets/transform-messages-CV4kCtBB.js +0 -1
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/LICENSE +0 -201
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/README.md +0 -62
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-cjs/index.js +0 -156
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/constants.js +0 -2
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromEnvSigningName.js +0 -16
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromSso.js +0 -80
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromStatic.js +0 -8
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js +0 -11
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js +0 -10
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/index.js +0 -4
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/nodeProvider.js +0 -5
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenExpiry.js +0 -7
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenKey.js +0 -7
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js +0 -8
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +0 -69
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-gemini-cli.js +0 -779
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-antigravity.js +0 -377
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-gemini-cli.js +0 -482
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 速构 QuickForge
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img alt="Version" src="https://img.shields.io/badge/version-1.3.
|
|
4
|
+
<img alt="Version" src="https://img.shields.io/badge/version-1.3.19-blue" />
|
|
5
5
|
<img alt="License" src="https://img.shields.io/badge/license-MIT-green" />
|
|
6
6
|
<img alt="Node" src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" />
|
|
7
7
|
<img alt="React" src="https://img.shields.io/badge/react-19-61DAFB?logo=react" />
|
|
@@ -65,7 +65,7 @@ QuickForge 的工具能力很直接,因此也需要谨慎使用:
|
|
|
65
65
|
#### 从 npm 安装
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
npm install -g @shawnstack/quickforge@1.3.
|
|
68
|
+
npm install -g @shawnstack/quickforge@1.3.19
|
|
69
69
|
qf
|
|
70
70
|
|
|
71
71
|
# CLI 工具
|
|
@@ -79,17 +79,17 @@ qf update
|
|
|
79
79
|
当前版本的离线包:
|
|
80
80
|
|
|
81
81
|
```text
|
|
82
|
-
package-offline/shawnstack-quickforge-1.3.
|
|
82
|
+
package-offline/shawnstack-quickforge-1.3.19.tgz
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
在安装了 Node.js 20+ 和 npm 的机器上执行:
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
npm install -g ./package-offline/shawnstack-quickforge-1.3.
|
|
88
|
+
npm install -g ./package-offline/shawnstack-quickforge-1.3.19.tgz
|
|
89
89
|
qf
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
该包由 `v1.3.
|
|
92
|
+
该包由 `v1.3.19` 标签生成,包含离线安装所需的运行时依赖。
|
|
93
93
|
|
|
94
94
|
### 本地开发
|
|
95
95
|
|
|
@@ -228,7 +228,7 @@ QuickForge intentionally exposes powerful local capabilities, so the boundaries
|
|
|
228
228
|
#### npm
|
|
229
229
|
|
|
230
230
|
```bash
|
|
231
|
-
npm install -g @shawnstack/quickforge@1.3.
|
|
231
|
+
npm install -g @shawnstack/quickforge@1.3.19
|
|
232
232
|
qf
|
|
233
233
|
|
|
234
234
|
# CLI utilities
|
|
@@ -239,20 +239,20 @@ qf update
|
|
|
239
239
|
|
|
240
240
|
#### Offline tarball
|
|
241
241
|
|
|
242
|
-
The offline release package for `v1.3.
|
|
242
|
+
The offline release package for `v1.3.19` is:
|
|
243
243
|
|
|
244
244
|
```text
|
|
245
|
-
package-offline/shawnstack-quickforge-1.3.
|
|
245
|
+
package-offline/shawnstack-quickforge-1.3.19.tgz
|
|
246
246
|
```
|
|
247
247
|
|
|
248
248
|
Install it on a machine with Node.js 20+ and npm:
|
|
249
249
|
|
|
250
250
|
```bash
|
|
251
|
-
npm install -g ./package-offline/shawnstack-quickforge-1.3.
|
|
251
|
+
npm install -g ./package-offline/shawnstack-quickforge-1.3.19.tgz
|
|
252
252
|
qf
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
The package was generated from tag `v1.3.
|
|
255
|
+
The package was generated from tag `v1.3.19` and includes bundled runtime dependencies for offline installation.
|
|
256
256
|
|
|
257
257
|
### Local development
|
|
258
258
|
|
package/bin/quickforge.mjs
CHANGED
|
@@ -266,33 +266,178 @@ function getLogFile() {
|
|
|
266
266
|
return path.join(getDataDir(), 'logs', `server-${date}.log`)
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
function sleep(ms) {
|
|
270
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getPort() {
|
|
274
|
+
return String(process.env.QUICKFORGE_PORT || '5176')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function getDisplayHost() {
|
|
278
|
+
const host = process.env.QUICKFORGE_HOST || '0.0.0.0'
|
|
279
|
+
return host === '0.0.0.0' ? '<LAN-IP>' : host
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getProbeHost() {
|
|
283
|
+
const host = process.env.QUICKFORGE_HOST || '127.0.0.1'
|
|
284
|
+
if (host === '0.0.0.0' || host === '::') return '127.0.0.1'
|
|
285
|
+
return host
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getHealthUrl() {
|
|
289
|
+
return `http://${getProbeHost()}:${getPort()}/api/health`
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function getServiceUrl() {
|
|
293
|
+
return `http://${getDisplayHost()}:${getPort()}`
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function formatHealth(health) {
|
|
297
|
+
if (!health) return 'unavailable'
|
|
298
|
+
const parts = []
|
|
299
|
+
if (health.pid) parts.push(`PID ${health.pid}`)
|
|
300
|
+
if (health.bootId) parts.push(`bootId ${health.bootId}`)
|
|
301
|
+
if (health.startedAt) parts.push(`started ${health.startedAt}`)
|
|
302
|
+
if (health.mode) parts.push(`mode ${health.mode}`)
|
|
303
|
+
return parts.join(', ') || 'available'
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function fetchHealth(timeoutMs = 800) {
|
|
307
|
+
const controller = new AbortController()
|
|
308
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs)
|
|
309
|
+
timeout.unref?.()
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch(getHealthUrl(), {
|
|
313
|
+
headers: { accept: 'application/json' },
|
|
314
|
+
signal: controller.signal,
|
|
315
|
+
})
|
|
316
|
+
if (!response.ok) return null
|
|
317
|
+
const payload = await response.json()
|
|
318
|
+
if (!payload || payload.ok !== true || !payload.pid) return null
|
|
319
|
+
return payload
|
|
320
|
+
} catch {
|
|
321
|
+
return null
|
|
322
|
+
} finally {
|
|
323
|
+
clearTimeout(timeout)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function waitForHealth({ expectedPid = null, previousBootId = null, requireChanged = false, timeoutMs = 15000 } = {}) {
|
|
328
|
+
const deadline = Date.now() + timeoutMs
|
|
329
|
+
while (Date.now() < deadline) {
|
|
330
|
+
const health = await fetchHealth()
|
|
331
|
+
if (health) {
|
|
332
|
+
const pidMatches = !expectedPid || Number(health.pid) === Number(expectedPid)
|
|
333
|
+
const bootChanged = !requireChanged || !previousBootId || health.bootId !== previousBootId
|
|
334
|
+
if (pidMatches && bootChanged) return health
|
|
335
|
+
}
|
|
336
|
+
await sleep(300)
|
|
337
|
+
}
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function waitForProcessExit(pid, timeoutMs = 10000) {
|
|
342
|
+
const deadline = Date.now() + timeoutMs
|
|
343
|
+
while (Date.now() < deadline) {
|
|
344
|
+
if (!isProcessRunning(pid)) return true
|
|
345
|
+
await sleep(250)
|
|
274
346
|
}
|
|
347
|
+
return !isProcessRunning(pid)
|
|
348
|
+
}
|
|
275
349
|
|
|
276
|
-
|
|
277
|
-
|
|
350
|
+
async function resolveRunningService() {
|
|
351
|
+
const pidFilePid = await readPid()
|
|
352
|
+
const pidFileAlive = pidFilePid ? isProcessRunning(pidFilePid) : false
|
|
353
|
+
if (pidFilePid && !pidFileAlive) {
|
|
354
|
+
console.log(`Found stale PID file (${pidFilePid}); cleaning it up.`)
|
|
278
355
|
await removePid()
|
|
279
|
-
return
|
|
280
356
|
}
|
|
281
357
|
|
|
282
|
-
|
|
358
|
+
const health = await fetchHealth()
|
|
359
|
+
if (health?.pid && isProcessRunning(Number(health.pid))) {
|
|
360
|
+
return {
|
|
361
|
+
pid: Number(health.pid),
|
|
362
|
+
source: 'health',
|
|
363
|
+
health,
|
|
364
|
+
pidFilePid,
|
|
365
|
+
pidFileAlive,
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (pidFileAlive) {
|
|
370
|
+
return {
|
|
371
|
+
pid: pidFilePid,
|
|
372
|
+
source: 'pid-file',
|
|
373
|
+
health: null,
|
|
374
|
+
pidFilePid,
|
|
375
|
+
pidFileAlive,
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
pid: null,
|
|
381
|
+
source: 'none',
|
|
382
|
+
health: null,
|
|
383
|
+
pidFilePid,
|
|
384
|
+
pidFileAlive,
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function terminateProcess(pid) {
|
|
389
|
+
if (!pid || !isProcessRunning(pid)) return true
|
|
390
|
+
|
|
283
391
|
try {
|
|
284
392
|
process.kill(pid, 'SIGTERM')
|
|
285
393
|
} catch {
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
394
|
+
// The process may have already exited.
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (await waitForProcessExit(pid, 10000)) return true
|
|
398
|
+
|
|
399
|
+
console.log(`PID ${pid} did not exit after SIGTERM; forcing stop...`)
|
|
400
|
+
try {
|
|
401
|
+
process.kill(pid, 'SIGKILL')
|
|
402
|
+
} catch {
|
|
403
|
+
// The process may have already exited.
|
|
292
404
|
}
|
|
293
405
|
|
|
406
|
+
return waitForProcessExit(pid, 5000)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function stopResolvedService(service) {
|
|
410
|
+
if (!service?.pid) {
|
|
411
|
+
console.log('QuickForge is not running.')
|
|
412
|
+
return false
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
console.log(`Stopping QuickForge (PID ${service.pid}, source: ${service.source})...`)
|
|
416
|
+
if (service.health) console.log(`Current service: ${formatHealth(service.health)}`)
|
|
417
|
+
if (service.pidFilePid && service.pidFilePid !== service.pid) {
|
|
418
|
+
console.log(`PID file points to ${service.pidFilePid}, but active service is PID ${service.pid}; using active service.`)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const stopped = await terminateProcess(service.pid)
|
|
294
422
|
await removePid()
|
|
295
|
-
|
|
423
|
+
|
|
424
|
+
if (!stopped) {
|
|
425
|
+
throw new Error(`Timed out stopping QuickForge PID ${service.pid}.`)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const remaining = await fetchHealth()
|
|
429
|
+
if (remaining?.pid) {
|
|
430
|
+
console.log(`Warning: /api/health still responds after stop: ${formatHealth(remaining)}`)
|
|
431
|
+
} else {
|
|
432
|
+
console.log('QuickForge stopped.')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return true
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function cmdStop() {
|
|
439
|
+
const service = await resolveRunningService()
|
|
440
|
+
await stopResolvedService(service)
|
|
296
441
|
}
|
|
297
442
|
|
|
298
443
|
function lanModeEnabled() {
|
|
@@ -309,23 +454,26 @@ function prepareEnvForCommand() {
|
|
|
309
454
|
return env
|
|
310
455
|
}
|
|
311
456
|
|
|
312
|
-
function
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (existingPid && isProcessRunning(existingPid)) {
|
|
322
|
-
console.log(`QuickForge is already running (PID ${existingPid}).`)
|
|
457
|
+
async function startService({ previousBootId = null } = {}) {
|
|
458
|
+
const existing = await resolveRunningService()
|
|
459
|
+
if (existing.pid) {
|
|
460
|
+
console.log(`QuickForge is already running (PID ${existing.pid}).`)
|
|
461
|
+
if (existing.health) console.log(`Current service: ${formatHealth(existing.health)}`)
|
|
462
|
+
if (existing.health?.pid && existing.pidFilePid !== existing.health.pid) {
|
|
463
|
+
await writePid(existing.health.pid)
|
|
464
|
+
console.log(`PID file updated: ${getPidFile()}`)
|
|
465
|
+
}
|
|
323
466
|
console.log('Use "quickforge stop" to stop it first, or "quickforge restart".')
|
|
324
|
-
return
|
|
467
|
+
return existing.health
|
|
325
468
|
}
|
|
326
469
|
|
|
327
|
-
|
|
328
|
-
|
|
470
|
+
const serviceUrl = getServiceUrl()
|
|
471
|
+
const healthUrl = getHealthUrl()
|
|
472
|
+
const dataDir = getDataDir()
|
|
473
|
+
const logFile = getLogFile()
|
|
474
|
+
|
|
475
|
+
console.log(`Starting QuickForge on ${serviceUrl}...`)
|
|
476
|
+
console.log(`Health check: ${healthUrl}`)
|
|
329
477
|
|
|
330
478
|
const child = spawn(process.execPath, [serverScript], {
|
|
331
479
|
detached: true,
|
|
@@ -334,13 +482,37 @@ async function cmdStart() {
|
|
|
334
482
|
env: prepareEnvForCommand(),
|
|
335
483
|
})
|
|
336
484
|
|
|
337
|
-
|
|
485
|
+
let exitInfo = null
|
|
486
|
+
child.once('exit', (code, signal) => {
|
|
487
|
+
exitInfo = { code, signal }
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
await new Promise((resolve, reject) => {
|
|
491
|
+
child.once('spawn', resolve)
|
|
492
|
+
child.once('error', reject)
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
console.log(`Spawned server process (PID ${child.pid}). Waiting for service readiness...`)
|
|
496
|
+
const health = await waitForHealth({ expectedPid: child.pid, previousBootId, requireChanged: Boolean(previousBootId) })
|
|
497
|
+
|
|
498
|
+
if (!health) {
|
|
499
|
+
const exitReason = exitInfo
|
|
500
|
+
? `process exited early (code ${exitInfo.code ?? 'null'}, signal ${exitInfo.signal ?? 'null'})`
|
|
501
|
+
: 'health check timed out'
|
|
502
|
+
|
|
503
|
+
if (!exitInfo && isProcessRunning(child.pid)) {
|
|
504
|
+
console.log(`Startup ${exitReason}; stopping spawned PID ${child.pid}...`)
|
|
505
|
+
await terminateProcess(child.pid)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
await removePid()
|
|
509
|
+
throw new Error(`QuickForge failed to start: ${exitReason}. Check log: ${logFile}`)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
await writePid(health.pid)
|
|
338
513
|
child.unref()
|
|
339
514
|
|
|
340
|
-
|
|
341
|
-
const dataDir = getDataDir()
|
|
342
|
-
const logFile = getLogFile()
|
|
343
|
-
console.log(`QuickForge started (PID ${child.pid}).`)
|
|
515
|
+
console.log(`QuickForge started and verified (${formatHealth(health)}).`)
|
|
344
516
|
console.log(`Open: ${serviceUrl}`)
|
|
345
517
|
console.log(`Data: ${dataDir}`)
|
|
346
518
|
console.log(`Config: ${path.join(dataDir, 'config', 'config.json')}`)
|
|
@@ -354,31 +526,68 @@ async function cmdStart() {
|
|
|
354
526
|
console.log(' quickforge restart Restart the background service')
|
|
355
527
|
console.log(' quickforge status Check if the service is running')
|
|
356
528
|
console.log(' quickforge logs Watch today\'s server log')
|
|
529
|
+
|
|
530
|
+
return health
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function cmdStart() {
|
|
534
|
+
await startService()
|
|
357
535
|
}
|
|
358
536
|
|
|
359
537
|
async function cmdRestart() {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
538
|
+
console.log('Restarting QuickForge...')
|
|
539
|
+
const before = await fetchHealth()
|
|
540
|
+
if (before) {
|
|
541
|
+
console.log(`Before restart: ${formatHealth(before)}`)
|
|
542
|
+
} else {
|
|
543
|
+
console.log('Before restart: no healthy service responded.')
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const service = await resolveRunningService()
|
|
547
|
+
if (service.pid) {
|
|
548
|
+
await stopResolvedService(service)
|
|
549
|
+
await sleep(500)
|
|
550
|
+
} else {
|
|
551
|
+
console.log('No running QuickForge service found; starting a new one.')
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const after = await startService({ previousBootId: before?.bootId || null })
|
|
555
|
+
if (!after) return
|
|
556
|
+
|
|
557
|
+
const pidChanged = !before?.pid || Number(after.pid) !== Number(before.pid)
|
|
558
|
+
const bootChanged = !before?.bootId || after.bootId !== before.bootId
|
|
559
|
+
|
|
560
|
+
if (pidChanged || bootChanged) {
|
|
561
|
+
console.log(`Restart verified: ${formatHealth(after)}`)
|
|
562
|
+
} else {
|
|
563
|
+
console.log('Warning: service responded after restart, but PID/bootId did not change.')
|
|
564
|
+
process.exitCode = 1
|
|
565
|
+
}
|
|
364
566
|
}
|
|
365
567
|
|
|
366
568
|
async function cmdStatus() {
|
|
367
|
-
const
|
|
368
|
-
if (
|
|
369
|
-
console.log(
|
|
569
|
+
const service = await resolveRunningService()
|
|
570
|
+
if (service.health) {
|
|
571
|
+
console.log(`QuickForge is running (${formatHealth(service.health)}).`)
|
|
572
|
+
console.log(`URL: ${getServiceUrl()}`)
|
|
573
|
+
console.log(`Health: ${getHealthUrl()}`)
|
|
574
|
+
console.log(`Log: ${getLogFile()}`)
|
|
575
|
+
if (service.pidFilePid !== service.health.pid) {
|
|
576
|
+
await writePid(service.health.pid)
|
|
577
|
+
console.log(`PID file repaired: ${getPidFile()}`)
|
|
578
|
+
}
|
|
579
|
+
console.log('Watch: quickforge logs')
|
|
370
580
|
return
|
|
371
581
|
}
|
|
372
582
|
|
|
373
|
-
if (
|
|
374
|
-
console.log(`QuickForge is running (PID ${pid}).`)
|
|
375
|
-
console.log(`
|
|
583
|
+
if (service.pid) {
|
|
584
|
+
console.log(`QuickForge process is running (PID ${service.pid}), but /api/health is not reachable.`)
|
|
585
|
+
console.log(`Health: ${getHealthUrl()}`)
|
|
376
586
|
console.log(`Log: ${getLogFile()}`)
|
|
377
|
-
|
|
378
|
-
} else {
|
|
379
|
-
console.log(`QuickForge PID ${pid} is stale (not running).`)
|
|
380
|
-
await removePid()
|
|
587
|
+
return
|
|
381
588
|
}
|
|
589
|
+
|
|
590
|
+
console.log('QuickForge is not running.')
|
|
382
591
|
}
|
|
383
592
|
|
|
384
593
|
async function cmdLogs() {
|