@yorz/cli 0.9.2 → 0.10.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 +27 -44
- package/dist/cli/index.js +857 -375
- package/dist/cli/serve-launcher.cjs +56 -0
- package/dist/gui/assets/{abnfDiagram-VRR7QNED-CvFCe3r_.js → abnfDiagram-VRR7QNED-ChpNHKmG.js} +1 -1
- package/dist/{gui-mobile/assets/arc-BBWQwcOX.js → gui/assets/arc-BSWQE9li.js} +1 -1
- package/dist/{gui-mobile/assets/architectureDiagram-ZJ3FMSHR-BdDouhRk.js → gui/assets/architectureDiagram-ZJ3FMSHR-BGI0Ue9c.js} +1 -1
- package/dist/{gui-mobile/assets/blockDiagram-677ZJIJ3-CyfQcEAp.js → gui/assets/blockDiagram-677ZJIJ3-DzJ0su4P.js} +1 -1
- package/dist/gui/assets/{c4Diagram-LMCZKHZV-Cgt15wDO.js → c4Diagram-LMCZKHZV-BijbkJk0.js} +1 -1
- package/dist/gui/assets/channel-S36nQ2lf.js +1 -0
- package/dist/gui/assets/{chunk-2Q5K7J3B-ByMpzo_7.js → chunk-2Q5K7J3B-Ds8H4SMx.js} +1 -1
- package/dist/gui/assets/{chunk-32BRIVSS-LHwTDCaD.js → chunk-32BRIVSS-CaodSuYb.js} +1 -1
- package/dist/gui/assets/{chunk-5VM5RSS4-CXrS6z-4.js → chunk-5VM5RSS4-Dk7weLtE.js} +1 -1
- package/dist/gui/assets/{chunk-EX3LRPZG-Bw7a0zH3.js → chunk-EX3LRPZG-Dc4TtD4O.js} +1 -1
- package/dist/gui/assets/{chunk-JWPE2WC7-wFaiogOx.js → chunk-JWPE2WC7-BXjLPdIx.js} +1 -1
- package/dist/gui/assets/{chunk-MOJQB5TN-CC4tXc_1.js → chunk-MOJQB5TN-Ct0R-MTA.js} +1 -1
- package/dist/gui/assets/{chunk-RYQCIY6F-BzMRf9sh.js → chunk-RYQCIY6F-BkMMhSkU.js} +1 -1
- package/dist/gui/assets/{chunk-V7JOEXUC-DXF9bAuV.js → chunk-V7JOEXUC-CA_5lNDX.js} +1 -1
- package/dist/gui/assets/{chunk-VR4S4FIN-DlTRKb1a.js → chunk-VR4S4FIN-BP0qSlug.js} +1 -1
- package/dist/gui/assets/{chunk-XXDRQBXY-kVTuzkcL.js → chunk-XXDRQBXY-ChJ7HYsh.js} +1 -1
- package/dist/gui/assets/classDiagram-OUVF2IWQ-DfrKIwL2.js +1 -0
- package/dist/gui/assets/classDiagram-v2-EOCWNBFH-DfrKIwL2.js +1 -0
- package/dist/{gui-mobile/assets/cose-bilkent-JH36ORCC-k5DcB7wy.js → gui/assets/cose-bilkent-JH36ORCC-DH_K5WAp.js} +1 -1
- package/dist/gui/assets/{cynefin-VYW2F7L2-BKxPBlKv.js → cynefin-VYW2F7L2-BBIjNWwX.js} +1 -1
- package/dist/{gui-mobile/assets/cynefinDiagram-TSTJHNR4-CfH5z8jt.js → gui/assets/cynefinDiagram-TSTJHNR4-BRRBlTKf.js} +1 -1
- package/dist/{gui-mobile/assets/dagre-VKFMJZFB-WliPW3Tx.js → gui/assets/dagre-VKFMJZFB-CiENng6J.js} +1 -1
- package/dist/gui/assets/{diagram-FQU43EPY-DSPwTKxt.js → diagram-FQU43EPY-BpAJWtF0.js} +1 -1
- package/dist/{gui-mobile/assets/diagram-G47NLZAW-CJFcYT7j.js → gui/assets/diagram-G47NLZAW-VQzk2Zt3.js} +1 -1
- package/dist/gui/assets/{diagram-NH7WQ7WH-Dz_3pmyJ.js → diagram-NH7WQ7WH-BETlyFIV.js} +1 -1
- package/dist/{gui-mobile/assets/diagram-OA4YK3LP-c_vARYuP.js → gui/assets/diagram-OA4YK3LP-D7kweveS.js} +1 -1
- package/dist/gui/assets/{diagram-WEI45ONY-Dq-7pg1u.js → diagram-WEI45ONY-4xazks6b.js} +1 -1
- package/dist/gui/assets/{ebnfDiagram-CCIWWBDH-DqkXLemr.js → ebnfDiagram-CCIWWBDH-x6c2P9Gq.js} +1 -1
- package/dist/{gui-mobile/assets/erDiagram-Q63AITRT-BosAgKfh.js → gui/assets/erDiagram-Q63AITRT-Dwu_NmfY.js} +1 -1
- package/dist/gui/assets/{flowDiagram-23GEKE2U-BItXJm0z.js → flowDiagram-23GEKE2U-mlV2EfUY.js} +1 -1
- package/dist/{gui-mobile/assets/ganttDiagram-NO4QXBWP-CJGUZTqX.js → gui/assets/ganttDiagram-NO4QXBWP-sRHt6-K7.js} +1 -1
- package/dist/gui/assets/{gitGraphDiagram-IHSO6WYX-DOTmWDXD.js → gitGraphDiagram-IHSO6WYX-DAmAE7m_.js} +1 -1
- package/dist/gui/assets/index-C3kFbt2F.js +34 -0
- package/dist/gui/assets/index-CF8GzEat.css +1 -0
- package/dist/gui/assets/{infoDiagram-FWYZ7A6U-CH94-RWl.js → infoDiagram-FWYZ7A6U-B4Uf4vez.js} +1 -1
- package/dist/gui/assets/{ishikawaDiagram-FXEZZL3T-DnPsPjhE.js → ishikawaDiagram-FXEZZL3T-BF78753F.js} +1 -1
- package/dist/{gui-mobile/assets/journeyDiagram-5HDEW3XC-B4ejke91.js → gui/assets/journeyDiagram-5HDEW3XC-BO6V3zi-.js} +1 -1
- package/dist/gui/assets/{kanban-definition-HUTT4EX6-CnEFBR_5.js → kanban-definition-HUTT4EX6-C6Abp-Z0.js} +1 -1
- package/dist/gui/assets/{linear-C76lB0Op.js → linear-4_sZdUed.js} +1 -1
- package/dist/gui/assets/{mermaid.core-rkuoyu1Y.js → mermaid.core-BoPHxIg3.js} +4 -4
- package/dist/gui/assets/{mindmap-definition-LN4V7U3C-Cp4m5M8t.js → mindmap-definition-LN4V7U3C-EcN1F0cO.js} +1 -1
- package/dist/gui/assets/{pegDiagram-2B236MQR-OpqvRJRn.js → pegDiagram-2B236MQR-giOW1xob.js} +1 -1
- package/dist/gui/assets/{pieDiagram-ENE6RG2P-CA2NTDmg.js → pieDiagram-ENE6RG2P-D9cIRAxf.js} +1 -1
- package/dist/{gui-mobile/assets/quadrantDiagram-ABIIQ3AL-CpOioe7x.js → gui/assets/quadrantDiagram-ABIIQ3AL-1jwzcF1N.js} +1 -1
- package/dist/gui/assets/{railroadDiagram-RFXS5EU6-D8sNxc-D.js → railroadDiagram-RFXS5EU6-kp1xeMyA.js} +1 -1
- package/dist/gui/assets/{requirementDiagram-TGXJPOKE-B_BAewjP.js → requirementDiagram-TGXJPOKE-yspWGQMw.js} +1 -1
- package/dist/{gui-mobile/assets/sankeyDiagram-HTMAVEWB-6gqAXG6C.js → gui/assets/sankeyDiagram-HTMAVEWB-Ylm3QxdV.js} +1 -1
- package/dist/{gui-mobile/assets/sequenceDiagram-DBY2YBRQ-D-7-OUF6.js → gui/assets/sequenceDiagram-DBY2YBRQ-CJJnMk2d.js} +1 -1
- package/dist/gui/assets/{sizeCapture-X5ZJPWSS-uA8UtQtp.js → sizeCapture-X5ZJPWSS-F3cALp3d.js} +1 -1
- package/dist/{gui-mobile/assets/stateDiagram-2N3HPSRC-CBem3dqE.js → gui/assets/stateDiagram-2N3HPSRC-D7aZ2O-l.js} +1 -1
- package/dist/gui/assets/stateDiagram-v2-6OUMAXLB-D8vtVO10.js +1 -0
- package/dist/{gui-mobile/assets/swimlanes-5IMT3BWC-CTxpjyu6.js → gui/assets/swimlanes-5IMT3BWC-UrlubmXy.js} +2 -2
- package/dist/gui/assets/swimlanesDiagram-G3AALYLV-BAhohxIt.js +8 -0
- package/dist/{gui-mobile/assets/timeline-definition-FHXFAJF6-sxn38dUp.js → gui/assets/timeline-definition-FHXFAJF6-Bl_hCpye.js} +1 -1
- package/dist/gui/assets/{vennDiagram-L72KCM5P-B29R19dw.js → vennDiagram-L72KCM5P-CvGLve2y.js} +1 -1
- package/dist/gui/assets/{wardleyDiagram-EHGQE667-B5eqpyg8.js → wardleyDiagram-EHGQE667-DLRGPeZX.js} +1 -1
- package/dist/{gui-mobile/assets/xychartDiagram-FW5EYKEG-AmEku7SD.js → gui/assets/xychartDiagram-FW5EYKEG-bB6p1K59.js} +1 -1
- package/dist/gui/index.html +2 -2
- package/dist/gui-mobile/assets/{abnfDiagram-VRR7QNED-dO2fe8On.js → abnfDiagram-VRR7QNED-Cii3YIsO.js} +1 -1
- package/dist/{gui/assets/arc-BR65Sp8N.js → gui-mobile/assets/arc-D5zxWTc6.js} +1 -1
- package/dist/{gui/assets/architectureDiagram-ZJ3FMSHR-AiVGAt8P.js → gui-mobile/assets/architectureDiagram-ZJ3FMSHR-BUrFMtmN.js} +1 -1
- package/dist/{gui/assets/blockDiagram-677ZJIJ3-HuFROz1l.js → gui-mobile/assets/blockDiagram-677ZJIJ3-CLXT0p5K.js} +1 -1
- package/dist/gui-mobile/assets/{c4Diagram-LMCZKHZV-BDwCd8AJ.js → c4Diagram-LMCZKHZV-XZDjR7Pf.js} +1 -1
- package/dist/gui-mobile/assets/channel-DAACRh1k.js +1 -0
- package/dist/gui-mobile/assets/{chunk-2Q5K7J3B-C21jFDT7.js → chunk-2Q5K7J3B-C0SSdrgO.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-32BRIVSS-DYcgMwLC.js → chunk-32BRIVSS-B-l29SGQ.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-5VM5RSS4-DHUChIkE.js → chunk-5VM5RSS4-2Oc13ID8.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-EX3LRPZG-BsiOO0cg.js → chunk-EX3LRPZG-tRiKUhHg.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-JWPE2WC7-DwRFcp8M.js → chunk-JWPE2WC7-CWxLnmGK.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-MOJQB5TN-CZAIa8xj.js → chunk-MOJQB5TN-CRo9axdS.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-RYQCIY6F-CjiSByKd.js → chunk-RYQCIY6F-BWk2tC2V.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-V7JOEXUC-C5X5gobg.js → chunk-V7JOEXUC-CAf_TQ8c.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-VR4S4FIN-BG4Ho3oM.js → chunk-VR4S4FIN-Djvb-G48.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-XXDRQBXY-Cy55QGn1.js → chunk-XXDRQBXY-DcdCewNm.js} +1 -1
- package/dist/gui-mobile/assets/classDiagram-OUVF2IWQ-k67r25Hl.js +1 -0
- package/dist/gui-mobile/assets/classDiagram-v2-EOCWNBFH-k67r25Hl.js +1 -0
- package/dist/{gui/assets/cose-bilkent-JH36ORCC-BAqcUvRT.js → gui-mobile/assets/cose-bilkent-JH36ORCC-DQd0ag_q.js} +1 -1
- package/dist/gui-mobile/assets/{cynefin-VYW2F7L2-D0zs5vNv.js → cynefin-VYW2F7L2-O3JIjwWq.js} +1 -1
- package/dist/{gui/assets/cynefinDiagram-TSTJHNR4-B30nmSKx.js → gui-mobile/assets/cynefinDiagram-TSTJHNR4-Ca9ChAoZ.js} +1 -1
- package/dist/{gui/assets/dagre-VKFMJZFB-B_6xJi_I.js → gui-mobile/assets/dagre-VKFMJZFB-Dz84jzyH.js} +1 -1
- package/dist/gui-mobile/assets/{diagram-FQU43EPY-CdurYY3r.js → diagram-FQU43EPY-Ddl2aZsD.js} +1 -1
- package/dist/{gui/assets/diagram-G47NLZAW-CE6kku8E.js → gui-mobile/assets/diagram-G47NLZAW-BUUkAM1L.js} +1 -1
- package/dist/gui-mobile/assets/{diagram-NH7WQ7WH-Covs7EOO.js → diagram-NH7WQ7WH-Bhp3-t7h.js} +1 -1
- package/dist/{gui/assets/diagram-OA4YK3LP-9K6njZVx.js → gui-mobile/assets/diagram-OA4YK3LP-Dz2X8j8T.js} +1 -1
- package/dist/gui-mobile/assets/{diagram-WEI45ONY-B8DLNiMy.js → diagram-WEI45ONY-m28jBgF4.js} +1 -1
- package/dist/gui-mobile/assets/{ebnfDiagram-CCIWWBDH-dOuQRK-3.js → ebnfDiagram-CCIWWBDH-Dh-_tj-T.js} +1 -1
- package/dist/{gui/assets/erDiagram-Q63AITRT-DwCKeJjN.js → gui-mobile/assets/erDiagram-Q63AITRT-C-3poaMf.js} +1 -1
- package/dist/gui-mobile/assets/{flowDiagram-23GEKE2U-HWFThTdv.js → flowDiagram-23GEKE2U-BopsgXAH.js} +1 -1
- package/dist/{gui/assets/ganttDiagram-NO4QXBWP-B7F8aBqd.js → gui-mobile/assets/ganttDiagram-NO4QXBWP-Bn7GJbzm.js} +1 -1
- package/dist/gui-mobile/assets/{gitGraphDiagram-IHSO6WYX-D1j9Ff_z.js → gitGraphDiagram-IHSO6WYX-DfLUxTzH.js} +1 -1
- package/dist/gui-mobile/assets/{index-BWC60m5U.js → index--agUQ6GR.js} +16 -16
- package/dist/gui-mobile/assets/index-CNDPS9z2.css +1 -0
- package/dist/gui-mobile/assets/{infoDiagram-FWYZ7A6U-5Gq_R5ny.js → infoDiagram-FWYZ7A6U-nrBaoqsE.js} +1 -1
- package/dist/gui-mobile/assets/{ishikawaDiagram-FXEZZL3T-DZAckb0K.js → ishikawaDiagram-FXEZZL3T-DEVq4aJ1.js} +1 -1
- package/dist/{gui/assets/journeyDiagram-5HDEW3XC-IxNPj9DL.js → gui-mobile/assets/journeyDiagram-5HDEW3XC-CA-AnD2i.js} +1 -1
- package/dist/gui-mobile/assets/{kanban-definition-HUTT4EX6-CKIlESkz.js → kanban-definition-HUTT4EX6-BtUD4217.js} +1 -1
- package/dist/gui-mobile/assets/{linear-DjghM5B6.js → linear-Cs1nDCen.js} +1 -1
- package/dist/gui-mobile/assets/{mermaid.core-Cery4OJZ.js → mermaid.core-CjlnyTvi.js} +4 -4
- package/dist/gui-mobile/assets/{mindmap-definition-LN4V7U3C-Bb9PmQcE.js → mindmap-definition-LN4V7U3C-DpHq0ntz.js} +1 -1
- package/dist/gui-mobile/assets/{pegDiagram-2B236MQR-Dk6yxZPA.js → pegDiagram-2B236MQR-UB1lVTuX.js} +1 -1
- package/dist/gui-mobile/assets/{pieDiagram-ENE6RG2P-9qPsJCKZ.js → pieDiagram-ENE6RG2P-7qkPFOFY.js} +1 -1
- package/dist/{gui/assets/quadrantDiagram-ABIIQ3AL-1wgAkd5_.js → gui-mobile/assets/quadrantDiagram-ABIIQ3AL-DPM04b8X.js} +1 -1
- package/dist/gui-mobile/assets/{railroadDiagram-RFXS5EU6-CYnrq0_H.js → railroadDiagram-RFXS5EU6-BY7DXXsK.js} +1 -1
- package/dist/gui-mobile/assets/{requirementDiagram-TGXJPOKE-Dan1m7mP.js → requirementDiagram-TGXJPOKE-RJuz8ezU.js} +1 -1
- package/dist/{gui/assets/sankeyDiagram-HTMAVEWB-D5yRszAY.js → gui-mobile/assets/sankeyDiagram-HTMAVEWB-DIHqskb-.js} +1 -1
- package/dist/{gui/assets/sequenceDiagram-DBY2YBRQ-xLRttR6a.js → gui-mobile/assets/sequenceDiagram-DBY2YBRQ-B91bF6fy.js} +1 -1
- package/dist/gui-mobile/assets/{sizeCapture-X5ZJPWSS-Cf4atOsc.js → sizeCapture-X5ZJPWSS-Bb8LgAbm.js} +1 -1
- package/dist/{gui/assets/stateDiagram-2N3HPSRC-JLfWMf-y.js → gui-mobile/assets/stateDiagram-2N3HPSRC-ef4n7bGq.js} +1 -1
- package/dist/gui-mobile/assets/stateDiagram-v2-6OUMAXLB-D5KNiKni.js +1 -0
- package/dist/{gui/assets/swimlanes-5IMT3BWC-DzNlggdg.js → gui-mobile/assets/swimlanes-5IMT3BWC-ZdNv29bw.js} +2 -2
- package/dist/gui-mobile/assets/swimlanesDiagram-G3AALYLV-CTHOZVwF.js +8 -0
- package/dist/{gui/assets/timeline-definition-FHXFAJF6-DisxBENp.js → gui-mobile/assets/timeline-definition-FHXFAJF6-fXhjYYU8.js} +1 -1
- package/dist/gui-mobile/assets/{vennDiagram-L72KCM5P-BI_Xsfbx.js → vennDiagram-L72KCM5P-BxTJzmPG.js} +1 -1
- package/dist/gui-mobile/assets/{wardleyDiagram-EHGQE667-1DzhpDmv.js → wardleyDiagram-EHGQE667-CiAx3NHL.js} +1 -1
- package/dist/{gui/assets/xychartDiagram-FW5EYKEG-BFhk2kj2.js → gui-mobile/assets/xychartDiagram-FW5EYKEG-OM8YLeYP.js} +1 -1
- package/dist/gui-mobile/index.html +2 -2
- package/dist/gui-mobile/sw.js +1 -1
- package/package.json +3 -2
- package/dist/gui/assets/channel-DWjIfFiO.js +0 -1
- package/dist/gui/assets/classDiagram-OUVF2IWQ-I21k2As-.js +0 -1
- package/dist/gui/assets/classDiagram-v2-EOCWNBFH-I21k2As-.js +0 -1
- package/dist/gui/assets/index-CkGO8Fpq.js +0 -34
- package/dist/gui/assets/index-e5JFp5J1.css +0 -1
- package/dist/gui/assets/stateDiagram-v2-6OUMAXLB-m9lGgZDF.js +0 -1
- package/dist/gui/assets/swimlanesDiagram-G3AALYLV-67oDFDZ3.js +0 -8
- package/dist/gui-mobile/assets/channel-BH91jO0g.js +0 -1
- package/dist/gui-mobile/assets/classDiagram-OUVF2IWQ-DI_HWJF7.js +0 -1
- package/dist/gui-mobile/assets/classDiagram-v2-EOCWNBFH-DI_HWJF7.js +0 -1
- package/dist/gui-mobile/assets/index-BABBu2VR.css +0 -1
- package/dist/gui-mobile/assets/stateDiagram-v2-6OUMAXLB-BQUCaoqO.js +0 -1
- package/dist/gui-mobile/assets/swimlanesDiagram-G3AALYLV-B6OThFgb.js +0 -8
package/dist/cli/index.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
import { homedir, platform } from "node:os";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import { stat, mkdir, writeFile, rename, readFile, rm, access, cp, readdir, appendFile, copyFile } from "node:fs/promises";
|
|
5
|
-
import { join, isAbsolute, resolve, basename as basename$1, dirname, extname, relative, sep, normalize } from "node:path";
|
|
5
|
+
import { posix, win32, join, isAbsolute, resolve, basename as basename$1, dirname, extname, relative, sep, normalize } from "node:path";
|
|
6
6
|
import { createHash, randomUUID, randomBytes } from "node:crypto";
|
|
7
|
-
import { existsSync, openSync, closeSync, statSync, readSync, mkdirSync, watch, createReadStream } from "node:fs";
|
|
7
|
+
import { existsSync, constants, openSync, closeSync, statSync, readSync, mkdirSync, watch, createReadStream } from "node:fs";
|
|
8
8
|
import { execFile, spawn, execFileSync } from "node:child_process";
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
10
11
|
import { serve } from "@hono/node-server";
|
|
11
12
|
import { isIP } from "node:net";
|
|
12
13
|
import { Hono } from "hono";
|
|
13
14
|
import { streamSSE } from "hono/streaming";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
15
15
|
import trash from "trash";
|
|
16
16
|
import matter from "gray-matter";
|
|
17
17
|
import chokidar from "chokidar";
|
|
@@ -20,14 +20,15 @@ import { listSessions, getSessionMessages, query } from "@anthropic-ai/claude-ag
|
|
|
20
20
|
import { createInterface } from "node:readline";
|
|
21
21
|
import { Codex } from "@openai/codex-sdk";
|
|
22
22
|
import { createOpencode } from "@opencode-ai/sdk";
|
|
23
|
+
import { ModelRuntime, SessionManager as SessionManager$1, createAgentSession } from "@earendil-works/pi-coding-agent";
|
|
23
24
|
import { createInterface as createInterface$1 } from "node:readline/promises";
|
|
24
25
|
import MarkdownIt from "markdown-it";
|
|
25
26
|
const name = "@yorz/cli";
|
|
26
|
-
const version = "0.
|
|
27
|
+
const version = "0.10.1";
|
|
27
28
|
const type = "module";
|
|
28
29
|
const description = "";
|
|
29
30
|
const bin = { "yorz": "./dist/cli/index.js" };
|
|
30
|
-
const engines = { "node": ">=
|
|
31
|
+
const engines = { "node": ">=22.19.0" };
|
|
31
32
|
const files = ["dist", "README.md"];
|
|
32
33
|
const keywords = ["ai", "agent", "spec-driven", "decision-os", "claude-code", "opencode", "workflow", "cli"];
|
|
33
34
|
const license = "LGPL-3.0-or-later";
|
|
@@ -35,7 +36,7 @@ const repository = { "type": "git", "url": "git+https://github.com/hughfenghen/Y
|
|
|
35
36
|
const homepage = "https://github.com/hughfenghen/YorZ#readme";
|
|
36
37
|
const publishConfig = { "access": "public" };
|
|
37
38
|
const scripts = { "build": "pnpm run build:cli && pnpm run build:gui && pnpm run build:gui-mobile", "build:cli": "vite build", "build:gui": "vite build --config vite.gui.config.ts", "build:gui-mobile": "vite build --config vite.gui-mobile.config.ts", "dev:cli": "pnpm run build:cli && node dist/cli/index.js serve --port 7424 --foreground", "dev:gui": "vite --config vite.gui.config.ts", "dev:gui-mobile": "vite --config vite.gui-mobile.config.ts", "gen:mobile-icons": "node scripts/gen-mobile-icons.mjs", "typecheck": "tsc -b", "test": "vitest run", "test:agent": "node scripts/test-agent.mjs", "test:e2e": "pnpm run build && playwright test", "format": 'prettier --write "src/**/*.{ts,tsx,md,css}"', "prepare": "node scripts/setup-git-hooks.mjs", "prepublishOnly": "pnpm run build" };
|
|
38
|
-
const dependencies = { "@anthropic-ai/claude-agent-sdk": "^0.3.207", "@hono/node-server": "^2.0.4", "@openai/codex-sdk": "^0.144.1", "@opencode-ai/sdk": "^1.17.18", "chokidar": "^5.0.0", "commander": "^12.1.0", "gray-matter": "^4.0.3", "hono": "^4.12.25", "jsdom": "^25.0.1", "markdown-it": "^14.2.0", "mermaid": "^11.0.0", "morphdom": "^2.7.8", "parse-diff": "^0.12.0", "timeago.js": "^4.0.2", "trash": "^10.1.1" };
|
|
39
|
+
const dependencies = { "@anthropic-ai/claude-agent-sdk": "^0.3.207", "@earendil-works/pi-coding-agent": "^0.85.1", "@hono/node-server": "^2.0.4", "@openai/codex-sdk": "^0.144.1", "@opencode-ai/sdk": "^1.17.18", "chokidar": "^5.0.0", "commander": "^12.1.0", "gray-matter": "^4.0.3", "hono": "^4.12.25", "jsdom": "^25.0.1", "markdown-it": "^14.2.0", "mermaid": "^11.0.0", "morphdom": "^2.7.8", "parse-diff": "^0.12.0", "timeago.js": "^4.0.2", "trash": "^10.1.1" };
|
|
39
40
|
const devDependencies = { "@fontsource/jetbrains-mono": "^5.3.0", "@kobalte/core": "^0.13.12", "@playwright/test": "^1.61.0", "@solidjs/router": "^0.16.1", "@types/jsdom": "^21.1.7", "@types/markdown-it": "^14.1.2", "@types/node": "^22.10.0", "autoprefixer": "^10.5.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "concurrently": "^9.1.0", "highlight.js": "^11.11.1", "i18next": "^26.3.6", "i18next-browser-languagedetector": "^8.2.1", "lucide-solid": "^1.24.0", "markdown-it-task-lists": "^2.1.1", "postcss": "^8.5.16", "prettier": "^3.4.0", "solid-js": "^1.9.13", "tailwind-merge": "^3.6.0", "tailwindcss": "^3.4.19", "tailwindcss-animate": "^1.0.7", "typescript": "^5.7.0", "vite": "^6.0.0", "vite-plugin-pwa": "^1.3.0", "vite-plugin-solid": "^2.11.12", "vitest": "^2.1.0", "workbox-window": "^7.4.1" };
|
|
40
41
|
const pkg = {
|
|
41
42
|
name,
|
|
@@ -141,6 +142,20 @@ function mergeCustomInstructions(project, global) {
|
|
|
141
142
|
const names = new Set(project.map((item) => item.name));
|
|
142
143
|
return [...project, ...global.filter((item) => !names.has(item.name))];
|
|
143
144
|
}
|
|
145
|
+
function normalizeAbsPath(input, platform2 = process.platform, cwd = process.cwd()) {
|
|
146
|
+
const trimmed = input.trim();
|
|
147
|
+
if (platform2 !== "win32") return posix.resolve(cwd, trimmed);
|
|
148
|
+
const resolved = win32.resolve(cwd, trimmed);
|
|
149
|
+
return upperDriveLetter(resolved);
|
|
150
|
+
}
|
|
151
|
+
function samePath(a, b, platform2 = process.platform) {
|
|
152
|
+
if (platform2 !== "win32") return a === b;
|
|
153
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
154
|
+
}
|
|
155
|
+
function upperDriveLetter(p) {
|
|
156
|
+
if (/^[a-z]:/.test(p)) return p[0].toUpperCase() + p.slice(1);
|
|
157
|
+
return p;
|
|
158
|
+
}
|
|
144
159
|
const CURRENT_VERSION$1 = 1;
|
|
145
160
|
const DEFAULT_GLOBAL_AGENT = { defaultKind: "claude" };
|
|
146
161
|
const DEFAULT_NOTIFICATIONS = {
|
|
@@ -264,7 +279,9 @@ function normalizeAgent$1(value) {
|
|
|
264
279
|
if (!value || typeof value !== "object") return { defaultKind: DEFAULT_GLOBAL_AGENT.defaultKind };
|
|
265
280
|
const obj = value;
|
|
266
281
|
const defaultKind = obj.defaultKind;
|
|
267
|
-
if (defaultKind === "opencode" || defaultKind === "codex"
|
|
282
|
+
if (defaultKind === "opencode" || defaultKind === "codex" || defaultKind === "pi") {
|
|
283
|
+
return { defaultKind };
|
|
284
|
+
}
|
|
268
285
|
return { defaultKind: "claude" };
|
|
269
286
|
}
|
|
270
287
|
function normalizeNotifications(value) {
|
|
@@ -391,9 +408,9 @@ function generateProjectId(absPath, depth = 0) {
|
|
|
391
408
|
function slugify(text) {
|
|
392
409
|
return text.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "");
|
|
393
410
|
}
|
|
394
|
-
async function addProject(absPath, filePath, now = () => /* @__PURE__ */ new Date()) {
|
|
411
|
+
async function addProject(absPath, filePath, now = () => /* @__PURE__ */ new Date(), platform2 = process.platform) {
|
|
395
412
|
const config = await loadGlobalConfig(filePath);
|
|
396
|
-
const existing = config.projects.find((p) => p.path
|
|
413
|
+
const existing = config.projects.find((p) => samePath(p.path, absPath, platform2));
|
|
397
414
|
if (existing) return { entry: existing, created: false };
|
|
398
415
|
const entry = {
|
|
399
416
|
id: generateProjectId(absPath),
|
|
@@ -507,13 +524,10 @@ async function isGitRepo(cwd) {
|
|
|
507
524
|
return false;
|
|
508
525
|
}
|
|
509
526
|
}
|
|
510
|
-
async function runGitInit(cwd) {
|
|
527
|
+
async function runGitInit(cwd, opts = {}) {
|
|
528
|
+
const stdio = opts.stdio ?? "inherit";
|
|
511
529
|
await new Promise((resolve2, reject) => {
|
|
512
|
-
const child = spawn(
|
|
513
|
-
"git",
|
|
514
|
-
["init"],
|
|
515
|
-
withHiddenWindowsConsole({ cwd, stdio: "inherit" })
|
|
516
|
-
);
|
|
530
|
+
const child = spawn("git", ["init"], withHiddenWindowsConsole({ cwd, stdio }));
|
|
517
531
|
child.on("error", reject);
|
|
518
532
|
child.on("exit", (code) => {
|
|
519
533
|
if (code === 0) resolve2();
|
|
@@ -521,6 +535,34 @@ async function runGitInit(cwd) {
|
|
|
521
535
|
});
|
|
522
536
|
});
|
|
523
537
|
}
|
|
538
|
+
async function ensureTmpIgnored(cwd) {
|
|
539
|
+
if (!await isGitRepo(cwd)) return null;
|
|
540
|
+
const giPath = join(cwd, ".gitignore");
|
|
541
|
+
let existing = "";
|
|
542
|
+
try {
|
|
543
|
+
existing = await readFile(giPath, "utf8");
|
|
544
|
+
} catch {
|
|
545
|
+
existing = "";
|
|
546
|
+
}
|
|
547
|
+
if (hasIgnoreEntry(existing, ".yorz/tmp")) {
|
|
548
|
+
return { updated: false, path: giPath };
|
|
549
|
+
}
|
|
550
|
+
const needsNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
551
|
+
const next = `${existing}${needsNewline ? "\n" : ""}.yorz/tmp
|
|
552
|
+
`;
|
|
553
|
+
await writeFile(giPath, next, "utf8");
|
|
554
|
+
return { updated: true, path: giPath };
|
|
555
|
+
}
|
|
556
|
+
function hasIgnoreEntry(content, target) {
|
|
557
|
+
const normalized = target.replace(/\/$/, "");
|
|
558
|
+
for (const rawLine of content.split("\n")) {
|
|
559
|
+
const line = rawLine.trim();
|
|
560
|
+
if (!line || line.startsWith("#")) continue;
|
|
561
|
+
const cleaned = line.replace(/\/$/, "").replace(/^\//, "");
|
|
562
|
+
if (cleaned === normalized) return true;
|
|
563
|
+
}
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
524
566
|
const SKILL_DIR_NAME = "yorz-spec";
|
|
525
567
|
const SKILL_DIR_NAMES = ["yorz-spec", "yorz-debug", "yorz-git-ops"];
|
|
526
568
|
const SKILL_FILES = /* @__PURE__ */ Object.assign({
|
|
@@ -705,34 +747,6 @@ async function dirExists(path) {
|
|
|
705
747
|
return false;
|
|
706
748
|
}
|
|
707
749
|
}
|
|
708
|
-
function hasIgnoreEntry(content, target) {
|
|
709
|
-
const normalized = target.replace(/\/$/, "");
|
|
710
|
-
for (const rawLine of content.split("\n")) {
|
|
711
|
-
const line = rawLine.trim();
|
|
712
|
-
if (!line || line.startsWith("#")) continue;
|
|
713
|
-
const cleaned = line.replace(/\/$/, "").replace(/^\//, "");
|
|
714
|
-
if (cleaned === normalized) return true;
|
|
715
|
-
}
|
|
716
|
-
return false;
|
|
717
|
-
}
|
|
718
|
-
async function ensureTmpIgnored(cwd) {
|
|
719
|
-
if (!await isGitRepo(cwd)) return null;
|
|
720
|
-
const giPath = join(cwd, ".gitignore");
|
|
721
|
-
let existing = "";
|
|
722
|
-
try {
|
|
723
|
-
existing = await readFile(giPath, "utf8");
|
|
724
|
-
} catch {
|
|
725
|
-
existing = "";
|
|
726
|
-
}
|
|
727
|
-
if (hasIgnoreEntry(existing, ".yorz/tmp")) {
|
|
728
|
-
return { updated: false, path: giPath };
|
|
729
|
-
}
|
|
730
|
-
const needsNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
731
|
-
const next = `${existing}${needsNewline ? "\n" : ""}.yorz/tmp
|
|
732
|
-
`;
|
|
733
|
-
await writeFile(giPath, next, "utf8");
|
|
734
|
-
return { updated: true, path: giPath };
|
|
735
|
-
}
|
|
736
750
|
async function uninstall(opts = {}) {
|
|
737
751
|
const baseDir = opts.skillsDir ?? resolveGlobalSkillsDir();
|
|
738
752
|
const results = [];
|
|
@@ -1479,39 +1493,50 @@ function findProjectRoot(startDir) {
|
|
|
1479
1493
|
function normalizeUsage(kind, raw) {
|
|
1480
1494
|
if (!raw || typeof raw !== "object") return void 0;
|
|
1481
1495
|
const obj = raw;
|
|
1482
|
-
const snapshot = kind === "codex" ? fromCodex(obj) : kind === "opencode" ? fromOpenCode(obj) : fromClaude(obj);
|
|
1496
|
+
const snapshot = kind === "codex" ? fromCodex(obj) : kind === "opencode" ? fromOpenCode(obj) : kind === "pi" ? fromPi(obj) : fromClaude(obj);
|
|
1483
1497
|
return isEmpty(snapshot) ? void 0 : snapshot;
|
|
1484
1498
|
}
|
|
1485
1499
|
function fromClaude(u) {
|
|
1486
1500
|
return compact({
|
|
1487
|
-
inputTokens: num$
|
|
1488
|
-
outputTokens: num$
|
|
1489
|
-
cacheReadTokens: num$
|
|
1490
|
-
cacheCreateTokens: num$
|
|
1501
|
+
inputTokens: num$3(u.input_tokens),
|
|
1502
|
+
outputTokens: num$3(u.output_tokens),
|
|
1503
|
+
cacheReadTokens: num$3(u.cache_read_input_tokens),
|
|
1504
|
+
cacheCreateTokens: num$3(u.cache_creation_input_tokens)
|
|
1491
1505
|
});
|
|
1492
1506
|
}
|
|
1493
1507
|
function fromCodex(u) {
|
|
1494
|
-
const total = num$
|
|
1495
|
-
const cached = num$
|
|
1508
|
+
const total = num$3(u.input_tokens);
|
|
1509
|
+
const cached = num$3(u.cached_input_tokens);
|
|
1496
1510
|
const uncached = total !== void 0 && cached !== void 0 ? Math.max(total - cached, 0) : total;
|
|
1497
1511
|
return compact({
|
|
1498
1512
|
inputTokens: uncached,
|
|
1499
1513
|
cacheReadTokens: cached,
|
|
1500
|
-
outputTokens: num$
|
|
1501
|
-
reasoningTokens: num$
|
|
1514
|
+
outputTokens: num$3(u.output_tokens),
|
|
1515
|
+
reasoningTokens: num$3(u.reasoning_output_tokens)
|
|
1502
1516
|
});
|
|
1503
1517
|
}
|
|
1504
1518
|
function fromOpenCode(u) {
|
|
1505
1519
|
const cache = u.cache && typeof u.cache === "object" ? u.cache : {};
|
|
1506
1520
|
return compact({
|
|
1507
|
-
inputTokens: num$
|
|
1508
|
-
outputTokens: num$
|
|
1509
|
-
reasoningTokens: num$
|
|
1510
|
-
cacheReadTokens: num$
|
|
1511
|
-
cacheCreateTokens: num$
|
|
1521
|
+
inputTokens: num$3(u.input),
|
|
1522
|
+
outputTokens: num$3(u.output),
|
|
1523
|
+
reasoningTokens: num$3(u.reasoning),
|
|
1524
|
+
cacheReadTokens: num$3(cache.read),
|
|
1525
|
+
cacheCreateTokens: num$3(cache.write)
|
|
1512
1526
|
});
|
|
1513
1527
|
}
|
|
1514
|
-
function
|
|
1528
|
+
function fromPi(u) {
|
|
1529
|
+
const cost = u.cost && typeof u.cost === "object" ? u.cost : {};
|
|
1530
|
+
return compact({
|
|
1531
|
+
inputTokens: num$3(u.input),
|
|
1532
|
+
outputTokens: num$3(u.output),
|
|
1533
|
+
cacheReadTokens: num$3(u.cacheRead),
|
|
1534
|
+
cacheCreateTokens: num$3(u.cacheWrite),
|
|
1535
|
+
reasoningTokens: num$3(u.reasoning),
|
|
1536
|
+
costUsd: num$3(cost.total)
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
function num$3(value) {
|
|
1515
1540
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1516
1541
|
}
|
|
1517
1542
|
function compact(snapshot) {
|
|
@@ -2344,6 +2369,7 @@ function normalizeAgent(value) {
|
|
|
2344
2369
|
if (typeof value === "string") {
|
|
2345
2370
|
if (value === "opencode") return { kind: "opencode" };
|
|
2346
2371
|
if (value === "codex") return { kind: "codex" };
|
|
2372
|
+
if (value === "pi") return { kind: "pi" };
|
|
2347
2373
|
return { kind: "claude" };
|
|
2348
2374
|
}
|
|
2349
2375
|
if (!value || typeof value !== "object") return { kind: "claude" };
|
|
@@ -2352,12 +2378,7 @@ function normalizeAgent(value) {
|
|
|
2352
2378
|
if (kind === "inherit") return { kind: "inherit" };
|
|
2353
2379
|
if (kind === "opencode") return { kind: "opencode" };
|
|
2354
2380
|
if (kind === "codex") return { kind: "codex" };
|
|
2355
|
-
if (kind === "
|
|
2356
|
-
const cmd = typeof obj.cmd === "string" ? obj.cmd.trim() : "";
|
|
2357
|
-
if (!cmd) return { kind: "claude" };
|
|
2358
|
-
const args = Array.isArray(obj.args) ? obj.args.filter((a) => typeof a === "string") : [];
|
|
2359
|
-
return { kind: "custom", cmd, args };
|
|
2360
|
-
}
|
|
2381
|
+
if (kind === "pi") return { kind: "pi" };
|
|
2361
2382
|
return { kind: "claude" };
|
|
2362
2383
|
}
|
|
2363
2384
|
function normalizeSpecsDir(value) {
|
|
@@ -2373,7 +2394,7 @@ async function loadCustomInstructions(projectPath) {
|
|
|
2373
2394
|
]);
|
|
2374
2395
|
return mergeCustomInstructions(project, global);
|
|
2375
2396
|
}
|
|
2376
|
-
const KINDS = ["claude", "codex", "opencode"];
|
|
2397
|
+
const KINDS = ["claude", "codex", "opencode", "pi"];
|
|
2377
2398
|
const DRAFT_ID_RE = /^[a-zA-Z0-9-]{1,64}$/;
|
|
2378
2399
|
function buildChatPrompt(prompt, draftId, attachments) {
|
|
2379
2400
|
if (attachments.length === 0) return prompt;
|
|
@@ -3427,6 +3448,39 @@ function createEventsRoutes(resolveProject, registry, projectsBus, systemNotific
|
|
|
3427
3448
|
});
|
|
3428
3449
|
return app;
|
|
3429
3450
|
}
|
|
3451
|
+
class NeedGitInitError extends Error {
|
|
3452
|
+
path;
|
|
3453
|
+
constructor(absPath) {
|
|
3454
|
+
super(`target directory is not a git repository: ${absPath}`);
|
|
3455
|
+
this.name = "NeedGitInitError";
|
|
3456
|
+
this.path = absPath;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
async function addProjectWithGit(opts) {
|
|
3460
|
+
const platform2 = opts.platform ?? process.platform;
|
|
3461
|
+
if (typeof opts.path !== "string" || !opts.path.trim()) {
|
|
3462
|
+
throw new Error("path required");
|
|
3463
|
+
}
|
|
3464
|
+
const abs = normalizeAbsPath(opts.path, platform2, opts.cwd);
|
|
3465
|
+
if (!existsSync(abs)) {
|
|
3466
|
+
throw new Error(`path does not exist: ${abs}`);
|
|
3467
|
+
}
|
|
3468
|
+
const stats = await stat(abs);
|
|
3469
|
+
if (!stats.isDirectory()) {
|
|
3470
|
+
throw new Error(`path is not a directory: ${abs}`);
|
|
3471
|
+
}
|
|
3472
|
+
let gitInitialized = false;
|
|
3473
|
+
if (!await isGitRepo(abs)) {
|
|
3474
|
+
if (!opts.gitInit) throw new NeedGitInitError(abs);
|
|
3475
|
+
const init = opts.runGitInit ?? ((cwd) => runGitInit(cwd, { stdio: "ignore" }));
|
|
3476
|
+
await init(abs);
|
|
3477
|
+
gitInitialized = true;
|
|
3478
|
+
}
|
|
3479
|
+
await prepareProjectDir(abs, opts.cwd);
|
|
3480
|
+
const gitignore = await ensureTmpIgnored(abs);
|
|
3481
|
+
const { entry, created } = await addProject(abs, opts.globalConfigPath, void 0, platform2);
|
|
3482
|
+
return { entry, created, gitInitialized, gitignore };
|
|
3483
|
+
}
|
|
3430
3484
|
function createProjectRoutes(registry, worktreeManager) {
|
|
3431
3485
|
const app = new Hono();
|
|
3432
3486
|
app.get("/projects", async (c) => {
|
|
@@ -3440,7 +3494,8 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3440
3494
|
} catch {
|
|
3441
3495
|
return c.json({ error: "invalid JSON body" }, 400);
|
|
3442
3496
|
}
|
|
3443
|
-
const
|
|
3497
|
+
const raw = body;
|
|
3498
|
+
const path = raw?.path;
|
|
3444
3499
|
if (typeof path !== "string" || !path.trim()) {
|
|
3445
3500
|
return c.json({ error: "path required" }, 400);
|
|
3446
3501
|
}
|
|
@@ -3448,7 +3503,7 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3448
3503
|
if (!isAbsolute(trimmed)) {
|
|
3449
3504
|
return c.json({ error: "path must be absolute" }, 400);
|
|
3450
3505
|
}
|
|
3451
|
-
const normalized =
|
|
3506
|
+
const normalized = normalizeAbsPath(trimmed);
|
|
3452
3507
|
if (!existsSync(normalized)) {
|
|
3453
3508
|
return c.json({ error: `path does not exist: ${normalized}` }, 400);
|
|
3454
3509
|
}
|
|
@@ -3457,7 +3512,7 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3457
3512
|
return c.json({ error: `path is not a directory: ${normalized}` }, 400);
|
|
3458
3513
|
}
|
|
3459
3514
|
try {
|
|
3460
|
-
const result = await registry.
|
|
3515
|
+
const result = await registry.addWithGit(normalized, { gitInit: raw?.gitInit === true });
|
|
3461
3516
|
return c.json(
|
|
3462
3517
|
{
|
|
3463
3518
|
id: result.entry.id,
|
|
@@ -3468,6 +3523,9 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3468
3523
|
result.created ? 201 : 200
|
|
3469
3524
|
);
|
|
3470
3525
|
} catch (err) {
|
|
3526
|
+
if (err instanceof NeedGitInitError) {
|
|
3527
|
+
return c.json({ error: err.message, needGitInit: true, path: err.path }, 409);
|
|
3528
|
+
}
|
|
3471
3529
|
return c.json({ error: err.message }, 400);
|
|
3472
3530
|
}
|
|
3473
3531
|
});
|
|
@@ -3595,27 +3653,8 @@ function parseAgent(value) {
|
|
|
3595
3653
|
if (kind === "claude") return { value: { kind: "claude" } };
|
|
3596
3654
|
if (kind === "opencode") return { value: { kind: "opencode" } };
|
|
3597
3655
|
if (kind === "codex") return { value: { kind: "codex" } };
|
|
3598
|
-
if (kind === "
|
|
3599
|
-
|
|
3600
|
-
if (typeof cmd !== "string" || !cmd.trim()) {
|
|
3601
|
-
return { error: "agent.cmd required when kind=custom" };
|
|
3602
|
-
}
|
|
3603
|
-
if (cmd.length > 500) return { error: "agent.cmd too long (max 500)" };
|
|
3604
|
-
const argsRaw = obj.args;
|
|
3605
|
-
let args = [];
|
|
3606
|
-
if (argsRaw !== void 0) {
|
|
3607
|
-
if (!Array.isArray(argsRaw)) return { error: "agent.args must be an array of strings" };
|
|
3608
|
-
args = [];
|
|
3609
|
-
for (const a of argsRaw) {
|
|
3610
|
-
if (typeof a !== "string") return { error: "agent.args entries must be strings" };
|
|
3611
|
-
if (a.length > 500) return { error: "agent.args entry too long (max 500)" };
|
|
3612
|
-
args.push(a);
|
|
3613
|
-
}
|
|
3614
|
-
if (args.length > 64) return { error: "agent.args too many entries (max 64)" };
|
|
3615
|
-
}
|
|
3616
|
-
return { value: { kind: "custom", cmd: cmd.trim(), args } };
|
|
3617
|
-
}
|
|
3618
|
-
return { error: "agent.kind must be inherit | claude | opencode | codex | custom" };
|
|
3656
|
+
if (kind === "pi") return { value: { kind: "pi" } };
|
|
3657
|
+
return { error: "agent.kind must be inherit | claude | opencode | codex | pi" };
|
|
3619
3658
|
}
|
|
3620
3659
|
function parseSpecsDir(value) {
|
|
3621
3660
|
const fallback = defaultProjectConfig().specsDir;
|
|
@@ -3820,8 +3859,8 @@ function parseBody(value) {
|
|
|
3820
3859
|
}
|
|
3821
3860
|
const agentObj = agent;
|
|
3822
3861
|
const defaultKind = agentObj.defaultKind;
|
|
3823
|
-
if (defaultKind !== "claude" && defaultKind !== "opencode" && defaultKind !== "codex") {
|
|
3824
|
-
return { error: "agent.defaultKind must be claude | opencode | codex" };
|
|
3862
|
+
if (defaultKind !== "claude" && defaultKind !== "opencode" && defaultKind !== "codex" && defaultKind !== "pi") {
|
|
3863
|
+
return { error: "agent.defaultKind must be claude | opencode | codex | pi" };
|
|
3825
3864
|
}
|
|
3826
3865
|
const notifications = obj.notifications;
|
|
3827
3866
|
if (!notifications || typeof notifications !== "object") {
|
|
@@ -4238,6 +4277,172 @@ function createProjectFilesRoutes(resolveProject) {
|
|
|
4238
4277
|
});
|
|
4239
4278
|
return app;
|
|
4240
4279
|
}
|
|
4280
|
+
const MAX_ENTRIES = 1e3;
|
|
4281
|
+
const WIN_DRIVE_ROOT = "";
|
|
4282
|
+
function createFsRoutes(deps = {}) {
|
|
4283
|
+
const platform2 = deps.platform ?? process.platform;
|
|
4284
|
+
const homedir$1 = deps.homedir ?? homedir;
|
|
4285
|
+
const isWin = platform2 === "win32";
|
|
4286
|
+
const p = isWin ? win32 : posix;
|
|
4287
|
+
const app = new Hono();
|
|
4288
|
+
app.get("/fs/list", async (c) => {
|
|
4289
|
+
const rawPath = c.req.query("path");
|
|
4290
|
+
const showHidden = c.req.query("showHidden") === "1";
|
|
4291
|
+
const home = normalizeAbsPath(homedir$1(), platform2);
|
|
4292
|
+
const blank = rawPath === void 0 || rawPath.trim() === "";
|
|
4293
|
+
if (isWin && blank) {
|
|
4294
|
+
const drives = await listWindowsDrives();
|
|
4295
|
+
return c.json({
|
|
4296
|
+
path: WIN_DRIVE_ROOT,
|
|
4297
|
+
parent: null,
|
|
4298
|
+
sep: p.sep,
|
|
4299
|
+
home,
|
|
4300
|
+
entries: drives,
|
|
4301
|
+
truncated: false
|
|
4302
|
+
});
|
|
4303
|
+
}
|
|
4304
|
+
const target = blank ? home : rawPath;
|
|
4305
|
+
if (!p.isAbsolute(target.trim())) {
|
|
4306
|
+
return c.json({ error: "path must be absolute" }, 400);
|
|
4307
|
+
}
|
|
4308
|
+
const dir = normalizeAbsPath(target, platform2);
|
|
4309
|
+
let stats;
|
|
4310
|
+
try {
|
|
4311
|
+
stats = await stat(dir);
|
|
4312
|
+
} catch (err) {
|
|
4313
|
+
return c.json({ error: describeFsError(err, dir) }, 400);
|
|
4314
|
+
}
|
|
4315
|
+
if (!stats.isDirectory()) {
|
|
4316
|
+
return c.json({ error: `path is not a directory: ${dir}` }, 400);
|
|
4317
|
+
}
|
|
4318
|
+
let rawEntries;
|
|
4319
|
+
try {
|
|
4320
|
+
rawEntries = await readdir(dir, { withFileTypes: true });
|
|
4321
|
+
} catch (err) {
|
|
4322
|
+
return c.json({ error: describeFsError(err, dir) }, 400);
|
|
4323
|
+
}
|
|
4324
|
+
const dirs = [];
|
|
4325
|
+
for (const entry of rawEntries) {
|
|
4326
|
+
if (!showHidden && entry.name.startsWith(".")) continue;
|
|
4327
|
+
const full = p.join(dir, entry.name);
|
|
4328
|
+
if (entry.isDirectory()) {
|
|
4329
|
+
dirs.push({ name: entry.name, path: full });
|
|
4330
|
+
continue;
|
|
4331
|
+
}
|
|
4332
|
+
if (!entry.isSymbolicLink()) continue;
|
|
4333
|
+
try {
|
|
4334
|
+
const s = await stat(full);
|
|
4335
|
+
if (s.isDirectory()) dirs.push({ name: entry.name, path: full });
|
|
4336
|
+
} catch {
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
dirs.sort((a, b) => a.name.localeCompare(b.name));
|
|
4340
|
+
const truncated = dirs.length > MAX_ENTRIES;
|
|
4341
|
+
return c.json({
|
|
4342
|
+
path: dir,
|
|
4343
|
+
parent: resolveParent(dir, platform2),
|
|
4344
|
+
sep: p.sep,
|
|
4345
|
+
home,
|
|
4346
|
+
entries: truncated ? dirs.slice(0, MAX_ENTRIES) : dirs,
|
|
4347
|
+
truncated
|
|
4348
|
+
});
|
|
4349
|
+
});
|
|
4350
|
+
app.post("/fs/mkdir", async (c) => {
|
|
4351
|
+
let body;
|
|
4352
|
+
try {
|
|
4353
|
+
body = await c.req.json();
|
|
4354
|
+
} catch {
|
|
4355
|
+
return c.json({ error: "invalid json body" }, 400);
|
|
4356
|
+
}
|
|
4357
|
+
const rawParent = typeof body.parent === "string" ? body.parent : "";
|
|
4358
|
+
const rawName = typeof body.name === "string" ? body.name : "";
|
|
4359
|
+
if (!rawParent.trim() || !p.isAbsolute(rawParent.trim())) {
|
|
4360
|
+
return c.json({ error: "parent must be an absolute directory path" }, 400);
|
|
4361
|
+
}
|
|
4362
|
+
const nameError = validateDirName(rawName, platform2);
|
|
4363
|
+
if (nameError) return c.json({ error: nameError }, 400);
|
|
4364
|
+
const parent = normalizeAbsPath(rawParent, platform2);
|
|
4365
|
+
try {
|
|
4366
|
+
const stats = await stat(parent);
|
|
4367
|
+
if (!stats.isDirectory()) {
|
|
4368
|
+
return c.json({ error: `path is not a directory: ${parent}` }, 400);
|
|
4369
|
+
}
|
|
4370
|
+
} catch (err) {
|
|
4371
|
+
return c.json({ error: describeFsError(err, parent) }, 400);
|
|
4372
|
+
}
|
|
4373
|
+
const target = p.join(parent, rawName.trim());
|
|
4374
|
+
try {
|
|
4375
|
+
await mkdir(target);
|
|
4376
|
+
} catch (err) {
|
|
4377
|
+
const code = err?.code;
|
|
4378
|
+
if (code === "EEXIST") return c.json({ error: `already exists: ${target}` }, 409);
|
|
4379
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
4380
|
+
return c.json({ error: `permission denied: ${target}` }, 403);
|
|
4381
|
+
}
|
|
4382
|
+
return c.json({ error: describeFsError(err, target) }, 400);
|
|
4383
|
+
}
|
|
4384
|
+
return c.json({ path: normalizeAbsPath(target, platform2) }, 201);
|
|
4385
|
+
});
|
|
4386
|
+
return app;
|
|
4387
|
+
}
|
|
4388
|
+
const WIN_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|$)/i;
|
|
4389
|
+
function validateDirName(name2, platform2) {
|
|
4390
|
+
const value = name2.trim();
|
|
4391
|
+
if (!value) return "name must not be empty";
|
|
4392
|
+
if (value.length > 255) return "name is too long";
|
|
4393
|
+
if (value === "." || value === "..") return 'name must not be "." or ".."';
|
|
4394
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
4395
|
+
return "name must not contain a path separator";
|
|
4396
|
+
}
|
|
4397
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) return "name must not contain control characters";
|
|
4398
|
+
if (platform2 === "win32") {
|
|
4399
|
+
if (/[<>:"|?*]/.test(value)) return 'name must not contain <>:"|?* on Windows';
|
|
4400
|
+
if (WIN_RESERVED_NAME.test(value)) return `"${value}" is a reserved name on Windows`;
|
|
4401
|
+
if (/[. ]$/.test(value)) return "name must not end with a dot or space on Windows";
|
|
4402
|
+
}
|
|
4403
|
+
return null;
|
|
4404
|
+
}
|
|
4405
|
+
function resolveParent(dir, platform2) {
|
|
4406
|
+
if (platform2 !== "win32") {
|
|
4407
|
+
const parent2 = posix.dirname(dir);
|
|
4408
|
+
return parent2 === dir ? null : parent2;
|
|
4409
|
+
}
|
|
4410
|
+
const root = win32.parse(dir).root;
|
|
4411
|
+
if (dir === root) {
|
|
4412
|
+
return isUncRoot(root) ? null : WIN_DRIVE_ROOT;
|
|
4413
|
+
}
|
|
4414
|
+
const parent = win32.dirname(dir);
|
|
4415
|
+
return parent === dir ? null : parent;
|
|
4416
|
+
}
|
|
4417
|
+
function isUncRoot(root) {
|
|
4418
|
+
return root.startsWith("\\\\");
|
|
4419
|
+
}
|
|
4420
|
+
async function listWindowsDrives() {
|
|
4421
|
+
const letters = [];
|
|
4422
|
+
for (let code = "C".charCodeAt(0); code <= "Z".charCodeAt(0); code++) {
|
|
4423
|
+
letters.push(String.fromCharCode(code));
|
|
4424
|
+
}
|
|
4425
|
+
const checked = await Promise.all(
|
|
4426
|
+
letters.map(async (letter) => {
|
|
4427
|
+
const root = `${letter}:\\`;
|
|
4428
|
+
try {
|
|
4429
|
+
await access(root, constants.R_OK);
|
|
4430
|
+
return { name: `${letter}:`, path: root };
|
|
4431
|
+
} catch {
|
|
4432
|
+
return null;
|
|
4433
|
+
}
|
|
4434
|
+
})
|
|
4435
|
+
);
|
|
4436
|
+
return checked.filter((d) => d !== null);
|
|
4437
|
+
}
|
|
4438
|
+
function describeFsError(err, dir) {
|
|
4439
|
+
const code = err?.code;
|
|
4440
|
+
if (code === "ENOENT") return `path does not exist: ${dir}`;
|
|
4441
|
+
if (code === "ENAMETOOLONG") return `path is too long: ${dir}`;
|
|
4442
|
+
if (code === "EACCES" || code === "EPERM") return `permission denied: ${dir}`;
|
|
4443
|
+
if (code === "ENOTDIR") return `path is not a directory: ${dir}`;
|
|
4444
|
+
return `cannot read directory: ${dir}`;
|
|
4445
|
+
}
|
|
4241
4446
|
class CommandRunStore {
|
|
4242
4447
|
file;
|
|
4243
4448
|
cache = null;
|
|
@@ -5784,6 +5989,7 @@ function createApp(opts) {
|
|
|
5784
5989
|
api.route("/", createSpecDraftsRoutes(resolveProject));
|
|
5785
5990
|
api.route("/", createWorktreeRoutes(opts.registry, worktreeManager));
|
|
5786
5991
|
api.route("/", createProjectFilesRoutes(resolveProject));
|
|
5992
|
+
api.route("/", createFsRoutes());
|
|
5787
5993
|
api.route("/", createCommandsRoutes(resolveProject));
|
|
5788
5994
|
api.route(
|
|
5789
5995
|
"/",
|
|
@@ -6368,13 +6574,57 @@ class SpecWatcher {
|
|
|
6368
6574
|
return parts[0];
|
|
6369
6575
|
}
|
|
6370
6576
|
}
|
|
6577
|
+
function isRecord$2(value) {
|
|
6578
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6579
|
+
}
|
|
6580
|
+
const SPEC_WRITE_TOOLS = {
|
|
6581
|
+
claude: /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]),
|
|
6582
|
+
codex: /* @__PURE__ */ new Set(),
|
|
6583
|
+
opencode: /* @__PURE__ */ new Set(["write", "edit", "patch"]),
|
|
6584
|
+
pi: /* @__PURE__ */ new Set(["write", "edit"])
|
|
6585
|
+
};
|
|
6586
|
+
const PATH_KEYS = ["file_path", "filePath", "path"];
|
|
6587
|
+
function isSpecWrite(kind, name2, input) {
|
|
6588
|
+
if (typeof name2 !== "string" || !SPEC_WRITE_TOOLS[kind].has(name2)) return false;
|
|
6589
|
+
if (!isRecord$2(input)) return false;
|
|
6590
|
+
for (const key of PATH_KEYS) {
|
|
6591
|
+
const path = input[key];
|
|
6592
|
+
if (typeof path === "string" && path.endsWith("spec.md")) return true;
|
|
6593
|
+
}
|
|
6594
|
+
return false;
|
|
6595
|
+
}
|
|
6596
|
+
class PhaseAccumulator {
|
|
6597
|
+
constructor(kind, startedAt) {
|
|
6598
|
+
this.kind = kind;
|
|
6599
|
+
this.startedAt = startedAt;
|
|
6600
|
+
}
|
|
6601
|
+
usage = {};
|
|
6602
|
+
requests = 0;
|
|
6603
|
+
/** Count one API response, whether or not it reported usage. */
|
|
6604
|
+
add(raw) {
|
|
6605
|
+
this.requests += 1;
|
|
6606
|
+
const snapshot = normalizeUsage(this.kind, raw);
|
|
6607
|
+
if (!snapshot) return;
|
|
6608
|
+
for (const [key, value] of Object.entries(snapshot)) {
|
|
6609
|
+
const field = key;
|
|
6610
|
+
this.usage[field] = (this.usage[field] ?? 0) + value;
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
snapshot() {
|
|
6614
|
+
return {
|
|
6615
|
+
usage: { ...this.usage },
|
|
6616
|
+
requests: this.requests,
|
|
6617
|
+
durationMs: Date.now() - this.startedAt
|
|
6618
|
+
};
|
|
6619
|
+
}
|
|
6620
|
+
}
|
|
6371
6621
|
function blocksFrom(message) {
|
|
6372
6622
|
if (!message || typeof message !== "object") return [];
|
|
6373
6623
|
const content = message.content;
|
|
6374
6624
|
if (typeof content === "string") return content ? [{ type: "text", text: content }] : [];
|
|
6375
6625
|
return Array.isArray(content) ? content : [];
|
|
6376
6626
|
}
|
|
6377
|
-
function toolResultText(content) {
|
|
6627
|
+
function toolResultText$1(content) {
|
|
6378
6628
|
if (typeof content === "string") return content;
|
|
6379
6629
|
if (Array.isArray(content)) {
|
|
6380
6630
|
let acc = "";
|
|
@@ -6420,10 +6670,10 @@ function usageWindows(usage) {
|
|
|
6420
6670
|
}
|
|
6421
6671
|
return out;
|
|
6422
6672
|
}
|
|
6423
|
-
function num$
|
|
6673
|
+
function num$2(value) {
|
|
6424
6674
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
6425
6675
|
}
|
|
6426
|
-
function isRecord(value) {
|
|
6676
|
+
function isRecord$1(value) {
|
|
6427
6677
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6428
6678
|
}
|
|
6429
6679
|
function withCost(usage, costUsd) {
|
|
@@ -6431,11 +6681,11 @@ function withCost(usage, costUsd) {
|
|
|
6431
6681
|
return { ...usage ?? {}, costUsd };
|
|
6432
6682
|
}
|
|
6433
6683
|
function primaryModel(modelUsage) {
|
|
6434
|
-
if (!isRecord(modelUsage)) return void 0;
|
|
6684
|
+
if (!isRecord$1(modelUsage)) return void 0;
|
|
6435
6685
|
let best;
|
|
6436
6686
|
let bestOutput = -1;
|
|
6437
6687
|
for (const [model, raw] of Object.entries(modelUsage)) {
|
|
6438
|
-
const output = isRecord(raw) ? num$
|
|
6688
|
+
const output = isRecord$1(raw) ? num$2(raw.outputTokens) ?? 0 : 0;
|
|
6439
6689
|
if (output > bestOutput) {
|
|
6440
6690
|
bestOutput = output;
|
|
6441
6691
|
best = model;
|
|
@@ -6443,38 +6693,8 @@ function primaryModel(modelUsage) {
|
|
|
6443
6693
|
}
|
|
6444
6694
|
return best;
|
|
6445
6695
|
}
|
|
6446
|
-
const SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
6447
|
-
function isSpecWrite(name2, input) {
|
|
6448
|
-
if (typeof name2 !== "string" || !SPEC_WRITE_TOOLS.has(name2)) return false;
|
|
6449
|
-
const path = isRecord(input) ? input.file_path : void 0;
|
|
6450
|
-
return typeof path === "string" && path.endsWith("spec.md");
|
|
6451
|
-
}
|
|
6452
6696
|
function usageOf(message) {
|
|
6453
|
-
return isRecord(message) ? message.usage : void 0;
|
|
6454
|
-
}
|
|
6455
|
-
class PhaseAccumulator {
|
|
6456
|
-
constructor(startedAt) {
|
|
6457
|
-
this.startedAt = startedAt;
|
|
6458
|
-
}
|
|
6459
|
-
usage = {};
|
|
6460
|
-
requests = 0;
|
|
6461
|
-
/** Count one API response, whether or not it reported usage. */
|
|
6462
|
-
add(raw) {
|
|
6463
|
-
this.requests += 1;
|
|
6464
|
-
const snapshot = normalizeUsage("claude", raw);
|
|
6465
|
-
if (!snapshot) return;
|
|
6466
|
-
for (const [key, value] of Object.entries(snapshot)) {
|
|
6467
|
-
const field = key;
|
|
6468
|
-
this.usage[field] = (this.usage[field] ?? 0) + value;
|
|
6469
|
-
}
|
|
6470
|
-
}
|
|
6471
|
-
snapshot() {
|
|
6472
|
-
return {
|
|
6473
|
-
usage: { ...this.usage },
|
|
6474
|
-
requests: this.requests,
|
|
6475
|
-
durationMs: Date.now() - this.startedAt
|
|
6476
|
-
};
|
|
6477
|
-
}
|
|
6697
|
+
return isRecord$1(message) ? message.usage : void 0;
|
|
6478
6698
|
}
|
|
6479
6699
|
class ClaudeSession {
|
|
6480
6700
|
constructor(id, cwd, isNew) {
|
|
@@ -6499,7 +6719,7 @@ class ClaudeSession {
|
|
|
6499
6719
|
};
|
|
6500
6720
|
if (this.isNew && !this.started) options.sessionId = this.id;
|
|
6501
6721
|
else options.resume = this.id;
|
|
6502
|
-
const phase = new PhaseAccumulator(Date.now());
|
|
6722
|
+
const phase = new PhaseAccumulator("claude", Date.now());
|
|
6503
6723
|
let planPhase;
|
|
6504
6724
|
const withPhases = (base) => ({
|
|
6505
6725
|
...base ?? {},
|
|
@@ -6524,26 +6744,26 @@ class ClaudeSession {
|
|
|
6524
6744
|
if (b.type === "text" && typeof b.text === "string") {
|
|
6525
6745
|
yield { type: "text", delta: b.text };
|
|
6526
6746
|
} else if (b.type === "tool_use") {
|
|
6527
|
-
if (!planPhase && isSpecWrite(b.name, b.input)) planPhase = phase.snapshot();
|
|
6747
|
+
if (!planPhase && isSpecWrite("claude", b.name, b.input)) planPhase = phase.snapshot();
|
|
6528
6748
|
yield { type: "tool-use", name: String(b.name ?? "?"), input: b.input ?? {} };
|
|
6529
6749
|
}
|
|
6530
6750
|
}
|
|
6531
6751
|
} else if (m.type === "user") {
|
|
6532
6752
|
for (const b of blocksFrom(m.message)) {
|
|
6533
6753
|
if (b.type === "tool_result") {
|
|
6534
|
-
yield { type: "tool-result", text: toolResultText(b.content) };
|
|
6754
|
+
yield { type: "tool-result", text: toolResultText$1(b.content) };
|
|
6535
6755
|
}
|
|
6536
6756
|
}
|
|
6537
6757
|
} else if (m.type === "result") {
|
|
6538
6758
|
usage = m.usage;
|
|
6539
6759
|
metrics = {
|
|
6540
|
-
usage: withCost(normalizeUsage("claude", m.usage), num$
|
|
6541
|
-
modelUsage: isRecord(m.modelUsage) ? m.modelUsage : void 0,
|
|
6542
|
-
numTurns: num$
|
|
6760
|
+
usage: withCost(normalizeUsage("claude", m.usage), num$2(m.total_cost_usd)),
|
|
6761
|
+
modelUsage: isRecord$1(m.modelUsage) ? m.modelUsage : void 0,
|
|
6762
|
+
numTurns: num$2(m.num_turns),
|
|
6543
6763
|
stopReason: typeof m.stop_reason === "string" ? m.stop_reason : void 0,
|
|
6544
6764
|
model: primaryModel(m.modelUsage),
|
|
6545
|
-
durationMs: num$
|
|
6546
|
-
apiDurationMs: num$
|
|
6765
|
+
durationMs: num$2(m.duration_ms),
|
|
6766
|
+
apiDurationMs: num$2(m.duration_api_ms)
|
|
6547
6767
|
};
|
|
6548
6768
|
} else if (m.type === "system" && m.subtype === "compact_boundary") {
|
|
6549
6769
|
const meta = m.compact_metadata ?? {};
|
|
@@ -6551,9 +6771,9 @@ class ClaudeSession {
|
|
|
6551
6771
|
type: "compact",
|
|
6552
6772
|
metrics: {
|
|
6553
6773
|
trigger: meta.trigger === "manual" || meta.trigger === "auto" ? meta.trigger : void 0,
|
|
6554
|
-
preTokens: num$
|
|
6555
|
-
postTokens: num$
|
|
6556
|
-
durationMs: num$
|
|
6774
|
+
preTokens: num$2(meta.pre_tokens),
|
|
6775
|
+
postTokens: num$2(meta.post_tokens),
|
|
6776
|
+
durationMs: num$2(meta.duration_ms)
|
|
6557
6777
|
}
|
|
6558
6778
|
};
|
|
6559
6779
|
} else if (m.type === "system" && m.subtype === "session_state_changed" && m.state === "idle") {
|
|
@@ -6607,7 +6827,7 @@ class ClaudeAdapter {
|
|
|
6607
6827
|
} else if (b.type === "tool_use") {
|
|
6608
6828
|
parts.push({ type: "tool-use", name: String(b.name ?? "?"), input: b.input ?? {} });
|
|
6609
6829
|
} else if (b.type === "tool_result") {
|
|
6610
|
-
parts.push({ type: "tool-result", text: toolResultText(b.content) });
|
|
6830
|
+
parts.push({ type: "tool-result", text: toolResultText$1(b.content) });
|
|
6611
6831
|
}
|
|
6612
6832
|
}
|
|
6613
6833
|
if (parts.length) out.push({ role: entry.type, parts });
|
|
@@ -6705,10 +6925,10 @@ function parseCodexSessionIndex(text) {
|
|
|
6705
6925
|
}
|
|
6706
6926
|
return out;
|
|
6707
6927
|
}
|
|
6708
|
-
function asRecord
|
|
6928
|
+
function asRecord(value) {
|
|
6709
6929
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6710
6930
|
}
|
|
6711
|
-
function numberFrom
|
|
6931
|
+
function numberFrom(value) {
|
|
6712
6932
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6713
6933
|
if (typeof value === "string" && value.trim()) {
|
|
6714
6934
|
const n = Number(value);
|
|
@@ -6716,33 +6936,33 @@ function numberFrom$1(value) {
|
|
|
6716
6936
|
}
|
|
6717
6937
|
return null;
|
|
6718
6938
|
}
|
|
6719
|
-
function resetFrom
|
|
6939
|
+
function resetFrom(value) {
|
|
6720
6940
|
if (typeof value === "string" && value.trim()) {
|
|
6721
6941
|
const n2 = Number(value);
|
|
6722
6942
|
if (Number.isFinite(n2)) return new Date(n2 * 1e3).toISOString();
|
|
6723
6943
|
const d = new Date(value);
|
|
6724
6944
|
return Number.isFinite(d.getTime()) ? d.toISOString() : null;
|
|
6725
6945
|
}
|
|
6726
|
-
const n = numberFrom
|
|
6946
|
+
const n = numberFrom(value);
|
|
6727
6947
|
return n == null ? null : new Date(n * 1e3).toISOString();
|
|
6728
6948
|
}
|
|
6729
6949
|
function codexWindow(key, label, value) {
|
|
6730
|
-
const rec = asRecord
|
|
6950
|
+
const rec = asRecord(value);
|
|
6731
6951
|
if (!rec) return null;
|
|
6732
|
-
const utilization = numberFrom
|
|
6952
|
+
const utilization = numberFrom(rec.used_percent) ?? numberFrom(rec.usedPercent) ?? numberFrom(rec.utilization) ?? numberFrom(rec.percent_used);
|
|
6733
6953
|
if (utilization == null) return null;
|
|
6734
6954
|
return {
|
|
6735
6955
|
key,
|
|
6736
6956
|
label,
|
|
6737
6957
|
utilization,
|
|
6738
|
-
resetsAt: resetFrom
|
|
6958
|
+
resetsAt: resetFrom(rec.resets_at ?? rec.reset_at ?? rec.resetsAt ?? rec.resetAt)
|
|
6739
6959
|
};
|
|
6740
6960
|
}
|
|
6741
6961
|
function parseCodexUsageResponse(raw) {
|
|
6742
|
-
const root = asRecord
|
|
6962
|
+
const root = asRecord(raw);
|
|
6743
6963
|
if (!root) return null;
|
|
6744
|
-
const rateLimit = asRecord
|
|
6745
|
-
const rateLimits = asRecord
|
|
6964
|
+
const rateLimit = asRecord(root.rate_limit);
|
|
6965
|
+
const rateLimits = asRecord(root.rate_limits);
|
|
6746
6966
|
const windows = [];
|
|
6747
6967
|
const primary = codexWindow(
|
|
6748
6968
|
"primary",
|
|
@@ -6759,7 +6979,7 @@ function parseCodexUsageResponse(raw) {
|
|
|
6759
6979
|
const additional = root.additional_rate_limits ?? root.additionalRateLimits ?? rateLimit?.additional;
|
|
6760
6980
|
if (Array.isArray(additional)) {
|
|
6761
6981
|
for (const [idx, item] of additional.entries()) {
|
|
6762
|
-
const rec = asRecord
|
|
6982
|
+
const rec = asRecord(item);
|
|
6763
6983
|
const label = String(rec?.label ?? rec?.name ?? rec?.limit_name ?? `Additional ${idx + 1}`);
|
|
6764
6984
|
const win = codexWindow(`additional:${idx}`, label, item);
|
|
6765
6985
|
if (win) windows.push(win);
|
|
@@ -6788,8 +7008,8 @@ function parseCodexTokenCountSnapshot(text) {
|
|
|
6788
7008
|
} catch {
|
|
6789
7009
|
continue;
|
|
6790
7010
|
}
|
|
6791
|
-
const rec = asRecord
|
|
6792
|
-
const payload = asRecord
|
|
7011
|
+
const rec = asRecord(obj);
|
|
7012
|
+
const payload = asRecord(rec?.payload);
|
|
6793
7013
|
if (payload?.type !== "token_count") continue;
|
|
6794
7014
|
const parsed = parseCodexUsageResponse({ rate_limits: payload.rate_limits });
|
|
6795
7015
|
if (!parsed) continue;
|
|
@@ -6966,7 +7186,7 @@ class CodexAdapter {
|
|
|
6966
7186
|
} catch {
|
|
6967
7187
|
return null;
|
|
6968
7188
|
}
|
|
6969
|
-
const tokens = asRecord
|
|
7189
|
+
const tokens = asRecord(asRecord(raw)?.tokens);
|
|
6970
7190
|
const accessToken = typeof tokens?.access_token === "string" ? tokens.access_token : "";
|
|
6971
7191
|
const accountId = typeof tokens?.account_id === "string" ? tokens.account_id : "";
|
|
6972
7192
|
return accessToken && accountId ? { accessToken, accountId } : null;
|
|
@@ -7065,136 +7285,6 @@ class CodexAdapter {
|
|
|
7065
7285
|
return null;
|
|
7066
7286
|
}
|
|
7067
7287
|
}
|
|
7068
|
-
const OPENCODE_QUOTA_INSTALL = "npx @slkiser/opencode-quota@latest init";
|
|
7069
|
-
const OPENCODE_QUOTA_PLUGIN_BIN = join(
|
|
7070
|
-
homedir(),
|
|
7071
|
-
".cache",
|
|
7072
|
-
"opencode",
|
|
7073
|
-
"packages",
|
|
7074
|
-
"@slkiser",
|
|
7075
|
-
"opencode-quota@latest",
|
|
7076
|
-
"node_modules",
|
|
7077
|
-
"@slkiser",
|
|
7078
|
-
"opencode-quota",
|
|
7079
|
-
"dist",
|
|
7080
|
-
"bin",
|
|
7081
|
-
"opencode-quota.js"
|
|
7082
|
-
);
|
|
7083
|
-
function asRecord(value) {
|
|
7084
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7085
|
-
}
|
|
7086
|
-
function numberFrom(value) {
|
|
7087
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
7088
|
-
if (typeof value === "string" && value.trim()) {
|
|
7089
|
-
const n = Number(value);
|
|
7090
|
-
if (Number.isFinite(n)) return n;
|
|
7091
|
-
}
|
|
7092
|
-
return null;
|
|
7093
|
-
}
|
|
7094
|
-
function resetFrom(value) {
|
|
7095
|
-
if (typeof value === "string" && value.trim()) {
|
|
7096
|
-
const n2 = Number(value);
|
|
7097
|
-
if (Number.isFinite(n2)) return new Date(n2 * 1e3).toISOString();
|
|
7098
|
-
const d = new Date(value);
|
|
7099
|
-
return Number.isFinite(d.getTime()) ? d.toISOString() : null;
|
|
7100
|
-
}
|
|
7101
|
-
const n = numberFrom(value);
|
|
7102
|
-
return n == null ? null : new Date(n * 1e3).toISOString();
|
|
7103
|
-
}
|
|
7104
|
-
function usageWindowFromRecord(rec, idx) {
|
|
7105
|
-
const label = String(rec.label ?? rec.name ?? rec.title ?? rec.provider ?? `Window ${idx + 1}`);
|
|
7106
|
-
const used = numberFrom(rec.usedPercent) ?? numberFrom(rec.used_percent) ?? numberFrom(rec.percent) ?? numberFrom(rec.utilization);
|
|
7107
|
-
const remaining = numberFrom(rec.remainingPercent) ?? numberFrom(rec.remaining_percent) ?? numberFrom(rec.percentRemaining);
|
|
7108
|
-
const utilization = used ?? (remaining == null ? null : 100 - remaining);
|
|
7109
|
-
if (utilization == null) return null;
|
|
7110
|
-
return {
|
|
7111
|
-
key: String(rec.key ?? rec.id ?? label),
|
|
7112
|
-
label,
|
|
7113
|
-
utilization,
|
|
7114
|
-
resetsAt: resetFrom(rec.resetsAt ?? rec.resets_at ?? rec.resetAt ?? rec.reset_at ?? rec.reset)
|
|
7115
|
-
};
|
|
7116
|
-
}
|
|
7117
|
-
function collectOpencodeQuotaWindows(value, out, seen) {
|
|
7118
|
-
if (!value || seen.has(value)) return;
|
|
7119
|
-
seen.add(value);
|
|
7120
|
-
if (Array.isArray(value)) {
|
|
7121
|
-
for (const item of value) collectOpencodeQuotaWindows(item, out, seen);
|
|
7122
|
-
return;
|
|
7123
|
-
}
|
|
7124
|
-
const rec = asRecord(value);
|
|
7125
|
-
if (!rec) return;
|
|
7126
|
-
const win = usageWindowFromRecord(rec, out.length);
|
|
7127
|
-
if (win) out.push(win);
|
|
7128
|
-
for (const key of ["windows", "quotas", "items", "limits", "entries"]) {
|
|
7129
|
-
if (key in rec) collectOpencodeQuotaWindows(rec[key], out, seen);
|
|
7130
|
-
}
|
|
7131
|
-
for (const key of ["providers", "data"]) {
|
|
7132
|
-
const child = asRecord(rec[key]);
|
|
7133
|
-
if (child) {
|
|
7134
|
-
for (const value2 of Object.values(child)) collectOpencodeQuotaWindows(value2, out, seen);
|
|
7135
|
-
} else if (key in rec) {
|
|
7136
|
-
collectOpencodeQuotaWindows(rec[key], out, seen);
|
|
7137
|
-
}
|
|
7138
|
-
}
|
|
7139
|
-
}
|
|
7140
|
-
function parseOpencodeQuotaOutput(text) {
|
|
7141
|
-
let raw;
|
|
7142
|
-
try {
|
|
7143
|
-
raw = JSON.parse(text);
|
|
7144
|
-
} catch {
|
|
7145
|
-
return parseOpencodeQuotaText(text);
|
|
7146
|
-
}
|
|
7147
|
-
const windows = [];
|
|
7148
|
-
collectOpencodeQuotaWindows(raw, windows, /* @__PURE__ */ new Set());
|
|
7149
|
-
if (windows.length === 0) return null;
|
|
7150
|
-
return {
|
|
7151
|
-
kind: "opencode",
|
|
7152
|
-
status: "available",
|
|
7153
|
-
checkedAt: Date.now(),
|
|
7154
|
-
source: "external-cli",
|
|
7155
|
-
rateLimitsAvailable: true,
|
|
7156
|
-
windows
|
|
7157
|
-
};
|
|
7158
|
-
}
|
|
7159
|
-
function parseOpencodeQuotaText(text) {
|
|
7160
|
-
const windows = [];
|
|
7161
|
-
let provider = "";
|
|
7162
|
-
for (const line of text.split("\n")) {
|
|
7163
|
-
const providerMatch = line.match(/^\s*→\s*\[(.+?)]/);
|
|
7164
|
-
if (providerMatch) {
|
|
7165
|
-
provider = providerMatch[1].trim();
|
|
7166
|
-
continue;
|
|
7167
|
-
}
|
|
7168
|
-
const quotaMatch = line.match(/^\s*(.+?quota)\s+.*?(\d+(?:\.\d+)?)%\s+left/i);
|
|
7169
|
-
if (!quotaMatch) continue;
|
|
7170
|
-
const label = [provider, quotaMatch[1].trim()].filter(Boolean).join(": ");
|
|
7171
|
-
const remaining = Number(quotaMatch[2]);
|
|
7172
|
-
windows.push({
|
|
7173
|
-
key: label || `quota:${windows.length}`,
|
|
7174
|
-
label: label || `Quota ${windows.length + 1}`,
|
|
7175
|
-
utilization: Math.max(0, Math.min(100, 100 - remaining)),
|
|
7176
|
-
resetsAt: null
|
|
7177
|
-
});
|
|
7178
|
-
}
|
|
7179
|
-
if (windows.length === 0) return null;
|
|
7180
|
-
return {
|
|
7181
|
-
kind: "opencode",
|
|
7182
|
-
status: "available",
|
|
7183
|
-
checkedAt: Date.now(),
|
|
7184
|
-
source: "external-cli",
|
|
7185
|
-
rateLimitsAvailable: true,
|
|
7186
|
-
windows
|
|
7187
|
-
};
|
|
7188
|
-
}
|
|
7189
|
-
async function opencodeQuotaCommands() {
|
|
7190
|
-
const commands = [{ cmd: "opencode-quota", args: ["show", "--json"] }];
|
|
7191
|
-
try {
|
|
7192
|
-
await access(OPENCODE_QUOTA_PLUGIN_BIN);
|
|
7193
|
-
commands.push({ cmd: process.execPath, args: [OPENCODE_QUOTA_PLUGIN_BIN, "show", "--json"] });
|
|
7194
|
-
} catch {
|
|
7195
|
-
}
|
|
7196
|
-
return commands;
|
|
7197
|
-
}
|
|
7198
7288
|
function textParts(parts) {
|
|
7199
7289
|
const out = [];
|
|
7200
7290
|
for (const p of parts) {
|
|
@@ -7265,7 +7355,19 @@ class OpenCodeAdapter {
|
|
|
7265
7355
|
booting = null;
|
|
7266
7356
|
async ensure() {
|
|
7267
7357
|
if (!this.booting) {
|
|
7268
|
-
this.booting = createOpencode({
|
|
7358
|
+
this.booting = createOpencode({
|
|
7359
|
+
hostname: "127.0.0.1",
|
|
7360
|
+
port: 0,
|
|
7361
|
+
config: {
|
|
7362
|
+
permission: {
|
|
7363
|
+
edit: "allow",
|
|
7364
|
+
bash: "allow",
|
|
7365
|
+
webfetch: "allow",
|
|
7366
|
+
doom_loop: "allow",
|
|
7367
|
+
external_directory: "allow"
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
}).catch((err) => {
|
|
7269
7371
|
this.booting = null;
|
|
7270
7372
|
throw err;
|
|
7271
7373
|
});
|
|
@@ -7314,62 +7416,404 @@ class OpenCodeAdapter {
|
|
|
7314
7416
|
}
|
|
7315
7417
|
return out;
|
|
7316
7418
|
}
|
|
7419
|
+
/**
|
|
7420
|
+
* `usageStatus: false` —— opencode 自身没有配额查询接口,早先那条路径是去调
|
|
7421
|
+
* 第三方 opencode-quota 插件;实测装了也拿不到可用数据,只会在空态里留下一行
|
|
7422
|
+
* 「去装插件」的噪音。与其留一个必然失败的探测(每次还要 spawn 一个 10s 超时
|
|
7423
|
+
* 的子进程),不如明确声明不支持:前端据此什么都不显示。
|
|
7424
|
+
*/
|
|
7317
7425
|
capabilities() {
|
|
7318
|
-
return { listSessions: true, getMessages: true, usageStatus:
|
|
7426
|
+
return { listSessions: true, getMessages: true, usageStatus: false };
|
|
7319
7427
|
}
|
|
7320
|
-
async
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7428
|
+
async dispose() {
|
|
7429
|
+
if (!this.booting) return;
|
|
7430
|
+
try {
|
|
7431
|
+
const { server } = await this.booting;
|
|
7432
|
+
server.close();
|
|
7433
|
+
} catch {
|
|
7434
|
+
}
|
|
7435
|
+
this.booting = null;
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
function isRecord(value) {
|
|
7439
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7440
|
+
}
|
|
7441
|
+
function num$1(value) {
|
|
7442
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7443
|
+
}
|
|
7444
|
+
function contentText(content) {
|
|
7445
|
+
if (typeof content === "string") return content;
|
|
7446
|
+
if (!Array.isArray(content)) return "";
|
|
7447
|
+
let acc = "";
|
|
7448
|
+
for (const c of content) {
|
|
7449
|
+
if (isRecord(c) && c.type === "text" && typeof c.text === "string") acc += c.text;
|
|
7450
|
+
}
|
|
7451
|
+
return acc;
|
|
7452
|
+
}
|
|
7453
|
+
function toolResultText(result) {
|
|
7454
|
+
if (typeof result === "string") return result;
|
|
7455
|
+
if (Array.isArray(result)) return contentText(result);
|
|
7456
|
+
if (isRecord(result)) {
|
|
7457
|
+
const inner = contentText(result.content);
|
|
7458
|
+
if (inner) return inner;
|
|
7459
|
+
if (typeof result.output === "string") return result.output;
|
|
7460
|
+
}
|
|
7461
|
+
return result == null ? "" : JSON.stringify(result);
|
|
7462
|
+
}
|
|
7463
|
+
function addUsage(acc, raw) {
|
|
7464
|
+
if (!isRecord(raw)) return;
|
|
7465
|
+
for (const key of [
|
|
7466
|
+
"input",
|
|
7467
|
+
"output",
|
|
7468
|
+
"cacheRead",
|
|
7469
|
+
"cacheWrite",
|
|
7470
|
+
"cacheWrite1h",
|
|
7471
|
+
"reasoning",
|
|
7472
|
+
"totalTokens"
|
|
7473
|
+
]) {
|
|
7474
|
+
const value = num$1(raw[key]);
|
|
7475
|
+
if (value === void 0) continue;
|
|
7476
|
+
acc[key] = (acc[key] ?? 0) + value;
|
|
7477
|
+
}
|
|
7478
|
+
const cost = raw.cost;
|
|
7479
|
+
if (!isRecord(cost)) return;
|
|
7480
|
+
const target = acc.cost ??= {};
|
|
7481
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
|
|
7482
|
+
const value = num$1(cost[key]);
|
|
7483
|
+
if (value === void 0) continue;
|
|
7484
|
+
target[key] = (target[key] ?? 0) + value;
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
7487
|
+
function hasUsage(usage) {
|
|
7488
|
+
return Object.keys(usage).length > 0;
|
|
7489
|
+
}
|
|
7490
|
+
class EventQueue {
|
|
7491
|
+
items = [];
|
|
7492
|
+
wake = null;
|
|
7493
|
+
closed = false;
|
|
7494
|
+
push(item) {
|
|
7495
|
+
this.items.push(item);
|
|
7496
|
+
this.notify();
|
|
7497
|
+
}
|
|
7498
|
+
/** Stop after the already-queued items drain — never discards them. */
|
|
7499
|
+
close() {
|
|
7500
|
+
this.closed = true;
|
|
7501
|
+
this.notify();
|
|
7502
|
+
}
|
|
7503
|
+
notify() {
|
|
7504
|
+
const resume = this.wake;
|
|
7505
|
+
this.wake = null;
|
|
7506
|
+
resume?.();
|
|
7507
|
+
}
|
|
7508
|
+
async *drain() {
|
|
7509
|
+
for (; ; ) {
|
|
7510
|
+
while (this.items.length) yield this.items.shift();
|
|
7511
|
+
if (this.closed) return;
|
|
7512
|
+
await new Promise((resolve2) => {
|
|
7513
|
+
this.wake = resolve2;
|
|
7514
|
+
});
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
}
|
|
7518
|
+
class PiSession {
|
|
7519
|
+
constructor(id, cwd, isNew, adapter) {
|
|
7520
|
+
this.id = id;
|
|
7521
|
+
this.cwd = cwd;
|
|
7522
|
+
this.isNew = isNew;
|
|
7523
|
+
this.adapter = adapter;
|
|
7524
|
+
}
|
|
7525
|
+
started = false;
|
|
7526
|
+
live = null;
|
|
7527
|
+
aborted = false;
|
|
7528
|
+
/**
|
|
7529
|
+
* One Pi session per turn.
|
|
7530
|
+
*
|
|
7531
|
+
* `AgentSession` has no release hook, so a long-lived `PiAgentSession` would
|
|
7532
|
+
* leak its listeners and extension runtime for as long as the chat exists.
|
|
7533
|
+
* The expensive part — model catalog and auth — is hoisted into the adapter's
|
|
7534
|
+
* shared `ModelRuntime`, leaving per-turn setup cheap.
|
|
7535
|
+
*/
|
|
7536
|
+
async *send(prompt, opts) {
|
|
7537
|
+
this.aborted = false;
|
|
7538
|
+
if (opts?.signal) {
|
|
7539
|
+
if (opts.signal.aborted) return;
|
|
7540
|
+
opts.signal.addEventListener("abort", () => this.abort(), { once: true });
|
|
7541
|
+
}
|
|
7542
|
+
const phase = new PhaseAccumulator("pi", Date.now());
|
|
7543
|
+
let planPhase;
|
|
7544
|
+
const usage = {};
|
|
7545
|
+
let model;
|
|
7546
|
+
let stopReason;
|
|
7547
|
+
let turns = 0;
|
|
7548
|
+
let compactionStartedAt = 0;
|
|
7549
|
+
const startedAt = Date.now();
|
|
7550
|
+
let session;
|
|
7551
|
+
let resumeError;
|
|
7552
|
+
try {
|
|
7553
|
+
const resolved = await this.openPiSession();
|
|
7554
|
+
session = resolved.session;
|
|
7555
|
+
resumeError = resolved.resumeError;
|
|
7556
|
+
} catch (err) {
|
|
7557
|
+
yield { type: "error", message: err instanceof Error ? err.message : String(err) };
|
|
7558
|
+
return;
|
|
7559
|
+
}
|
|
7560
|
+
this.live = session;
|
|
7561
|
+
if (resumeError) yield { type: "error", message: resumeError };
|
|
7562
|
+
if (!this.started || session.sessionId !== this.id) {
|
|
7563
|
+
this.started = true;
|
|
7564
|
+
this.id = session.sessionId;
|
|
7565
|
+
yield { type: "session-started", sessionId: this.id };
|
|
7566
|
+
}
|
|
7567
|
+
const queue = new EventQueue();
|
|
7568
|
+
const unsubscribe = session.subscribe((event) => queue.push(event));
|
|
7569
|
+
let promptError;
|
|
7570
|
+
const running = session.prompt(prompt, { expandPromptTemplates: false }).catch((err) => {
|
|
7571
|
+
promptError = err;
|
|
7572
|
+
}).finally(() => queue.close());
|
|
7573
|
+
try {
|
|
7574
|
+
for await (const event of queue.drain()) {
|
|
7575
|
+
switch (event.type) {
|
|
7576
|
+
case "message_update": {
|
|
7577
|
+
const delta = event.assistantMessageEvent;
|
|
7578
|
+
if (delta.type === "text_delta" && delta.delta) {
|
|
7579
|
+
yield { type: "text", delta: delta.delta };
|
|
7580
|
+
}
|
|
7581
|
+
break;
|
|
7582
|
+
}
|
|
7583
|
+
case "message_end": {
|
|
7584
|
+
const message = event.message;
|
|
7585
|
+
if (isRecord(message) && message.role === "assistant") {
|
|
7586
|
+
phase.add(message.usage);
|
|
7587
|
+
addUsage(usage, message.usage);
|
|
7588
|
+
if (typeof message.model === "string") model = message.model;
|
|
7589
|
+
if (typeof message.stopReason === "string") stopReason = message.stopReason;
|
|
7590
|
+
if (message.stopReason === "error") {
|
|
7591
|
+
const detail = typeof message.errorMessage === "string" && message.errorMessage ? message.errorMessage : "pi assistant turn failed";
|
|
7592
|
+
yield { type: "error", message: detail };
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
break;
|
|
7596
|
+
}
|
|
7597
|
+
case "turn_end":
|
|
7598
|
+
turns += 1;
|
|
7599
|
+
break;
|
|
7600
|
+
case "tool_execution_start": {
|
|
7601
|
+
if (!planPhase && isSpecWrite("pi", event.toolName, event.args)) {
|
|
7602
|
+
planPhase = phase.snapshot();
|
|
7603
|
+
}
|
|
7604
|
+
yield { type: "tool-use", name: event.toolName, input: event.args ?? {} };
|
|
7605
|
+
break;
|
|
7606
|
+
}
|
|
7607
|
+
case "tool_execution_end": {
|
|
7608
|
+
const text = toolResultText(event.result);
|
|
7609
|
+
if (text) yield { type: "tool-result", text };
|
|
7610
|
+
break;
|
|
7611
|
+
}
|
|
7612
|
+
case "compaction_start":
|
|
7613
|
+
compactionStartedAt = Date.now();
|
|
7614
|
+
break;
|
|
7615
|
+
case "compaction_end": {
|
|
7616
|
+
if (event.aborted) break;
|
|
7617
|
+
if (event.errorMessage) {
|
|
7618
|
+
yield { type: "error", message: event.errorMessage };
|
|
7619
|
+
break;
|
|
7620
|
+
}
|
|
7621
|
+
yield {
|
|
7622
|
+
type: "compact",
|
|
7623
|
+
metrics: {
|
|
7624
|
+
trigger: event.reason === "manual" ? "manual" : "auto",
|
|
7625
|
+
preTokens: num$1(event.result?.tokensBefore),
|
|
7626
|
+
postTokens: num$1(event.result?.estimatedTokensAfter),
|
|
7627
|
+
durationMs: compactionStartedAt ? Date.now() - compactionStartedAt : void 0
|
|
7628
|
+
}
|
|
7629
|
+
};
|
|
7630
|
+
break;
|
|
7631
|
+
}
|
|
7632
|
+
default:
|
|
7633
|
+
break;
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
await running;
|
|
7637
|
+
if (this.aborted) return;
|
|
7638
|
+
if (promptError) {
|
|
7639
|
+
yield {
|
|
7640
|
+
type: "error",
|
|
7641
|
+
message: promptError instanceof Error ? promptError.message : String(promptError)
|
|
7338
7642
|
};
|
|
7339
|
-
|
|
7340
|
-
lastError = err2;
|
|
7341
|
-
if (err2.code === "ENOENT") continue;
|
|
7342
|
-
break;
|
|
7643
|
+
return;
|
|
7343
7644
|
}
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
source: "external-cli",
|
|
7353
|
-
installCommand: OPENCODE_QUOTA_INSTALL,
|
|
7354
|
-
message: "opencode-quota is not installed"
|
|
7645
|
+
const metrics = {
|
|
7646
|
+
usage: normalizeUsage("pi", usage),
|
|
7647
|
+
numTurns: turns || void 0,
|
|
7648
|
+
stopReason,
|
|
7649
|
+
model,
|
|
7650
|
+
durationMs: Date.now() - startedAt,
|
|
7651
|
+
planPhase,
|
|
7652
|
+
observedTotal: phase.snapshot()
|
|
7355
7653
|
};
|
|
7654
|
+
yield { type: "turn-completed", usage: hasUsage(usage) ? usage : void 0, metrics };
|
|
7655
|
+
} finally {
|
|
7656
|
+
unsubscribe();
|
|
7657
|
+
session.dispose();
|
|
7658
|
+
this.live = null;
|
|
7659
|
+
this.started = true;
|
|
7356
7660
|
}
|
|
7357
|
-
|
|
7661
|
+
}
|
|
7662
|
+
abort() {
|
|
7663
|
+
this.aborted = true;
|
|
7664
|
+
void this.live?.abort();
|
|
7665
|
+
}
|
|
7666
|
+
/** Build (or reopen) the Pi session backing this turn. */
|
|
7667
|
+
async openPiSession() {
|
|
7668
|
+
let resumeError;
|
|
7669
|
+
let manager;
|
|
7670
|
+
if (this.isNew && !this.started) {
|
|
7671
|
+
manager = SessionManager$1.create(this.cwd, this.adapter.sessionDirOption, { id: this.id });
|
|
7672
|
+
} else {
|
|
7673
|
+
const path = await this.adapter.resolveSessionPath(this.id);
|
|
7674
|
+
if (path) {
|
|
7675
|
+
manager = SessionManager$1.open(path, this.adapter.sessionDirOption, this.cwd);
|
|
7676
|
+
} else {
|
|
7677
|
+
resumeError = `pi session ${this.id} was not found on disk; started a new session instead`;
|
|
7678
|
+
manager = SessionManager$1.create(this.cwd, this.adapter.sessionDirOption);
|
|
7679
|
+
}
|
|
7680
|
+
}
|
|
7681
|
+
const { session } = await createAgentSession({
|
|
7682
|
+
cwd: this.cwd,
|
|
7683
|
+
agentDir: this.adapter.agentDirOption,
|
|
7684
|
+
modelRuntime: await this.adapter.ensureRuntime(),
|
|
7685
|
+
sessionManager: manager
|
|
7686
|
+
});
|
|
7687
|
+
return { session, resumeError };
|
|
7688
|
+
}
|
|
7689
|
+
}
|
|
7690
|
+
class PiAdapter {
|
|
7691
|
+
constructor(cwd, opts = {}) {
|
|
7692
|
+
this.cwd = cwd;
|
|
7693
|
+
this.opts = opts;
|
|
7694
|
+
}
|
|
7695
|
+
kind = "pi";
|
|
7696
|
+
runtime = null;
|
|
7697
|
+
/** `session id → JSONL path`, refreshed from disk on a miss. */
|
|
7698
|
+
pathIndex = /* @__PURE__ */ new Map();
|
|
7699
|
+
get agentDirOption() {
|
|
7700
|
+
return this.opts.agentDir;
|
|
7701
|
+
}
|
|
7702
|
+
get sessionDirOption() {
|
|
7703
|
+
return this.opts.sessionDir;
|
|
7704
|
+
}
|
|
7705
|
+
/**
|
|
7706
|
+
* Lazily build one `ModelRuntime` for every session this adapter serves.
|
|
7707
|
+
*
|
|
7708
|
+
* Creating it reads auth and the model catalog — the dominant cost of
|
|
7709
|
+
* `createAgentSession()`. On failure the memo is cleared so the next turn
|
|
7710
|
+
* retries instead of replaying a cached rejection forever.
|
|
7711
|
+
*/
|
|
7712
|
+
async ensureRuntime() {
|
|
7713
|
+
if (!this.runtime) {
|
|
7714
|
+
const agentDir = this.opts.agentDir;
|
|
7715
|
+
this.runtime = ModelRuntime.create(
|
|
7716
|
+
agentDir ? { authPath: join(agentDir, "auth.json"), modelsPath: join(agentDir, "models.json") } : void 0
|
|
7717
|
+
).catch((err) => {
|
|
7718
|
+
this.runtime = null;
|
|
7719
|
+
throw err;
|
|
7720
|
+
});
|
|
7721
|
+
}
|
|
7722
|
+
return this.runtime;
|
|
7723
|
+
}
|
|
7724
|
+
async createSession() {
|
|
7725
|
+
return new PiSession(randomUUID(), this.cwd, true, this);
|
|
7726
|
+
}
|
|
7727
|
+
async resumeSession(id) {
|
|
7728
|
+
return new PiSession(id, this.cwd, false, this);
|
|
7729
|
+
}
|
|
7730
|
+
async listSessions() {
|
|
7731
|
+
const infos = await this.listPiSessions();
|
|
7732
|
+
return infos.map((info) => ({
|
|
7733
|
+
id: info.id,
|
|
7734
|
+
title: this.titleOf(info),
|
|
7358
7735
|
kind: this.kind,
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7736
|
+
createdAt: info.created.getTime(),
|
|
7737
|
+
updatedAt: info.modified.getTime()
|
|
7738
|
+
})).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
7739
|
+
}
|
|
7740
|
+
async getMessages(id) {
|
|
7741
|
+
const path = await this.resolveSessionPath(id);
|
|
7742
|
+
if (!path) return [];
|
|
7743
|
+
let entries;
|
|
7744
|
+
try {
|
|
7745
|
+
entries = SessionManager$1.open(path, this.opts.sessionDir, this.cwd).getEntries();
|
|
7746
|
+
} catch {
|
|
7747
|
+
return [];
|
|
7748
|
+
}
|
|
7749
|
+
const out = [];
|
|
7750
|
+
for (const entry of entries) {
|
|
7751
|
+
if (entry.type !== "message") continue;
|
|
7752
|
+
const message = entry.message;
|
|
7753
|
+
if (!isRecord(message)) continue;
|
|
7754
|
+
const ts = num$1(message.timestamp);
|
|
7755
|
+
if (message.role === "user") {
|
|
7756
|
+
const text = contentText(message.content);
|
|
7757
|
+
if (text) out.push({ role: "user", parts: [{ type: "text", text }], ts });
|
|
7758
|
+
} else if (message.role === "assistant") {
|
|
7759
|
+
const parts = [];
|
|
7760
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
7761
|
+
for (const block of content) {
|
|
7762
|
+
if (!isRecord(block)) continue;
|
|
7763
|
+
if (block.type === "text" && typeof block.text === "string" && block.text) {
|
|
7764
|
+
parts.push({ type: "text", text: block.text });
|
|
7765
|
+
} else if (block.type === "toolCall") {
|
|
7766
|
+
parts.push({
|
|
7767
|
+
type: "tool-use",
|
|
7768
|
+
name: String(block.name ?? "?"),
|
|
7769
|
+
input: block.arguments ?? {}
|
|
7770
|
+
});
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7773
|
+
if (parts.length) out.push({ role: "assistant", parts, ts });
|
|
7774
|
+
} else if (message.role === "toolResult") {
|
|
7775
|
+
const text = toolResultText(message.content);
|
|
7776
|
+
if (!text) continue;
|
|
7777
|
+
const last = out[out.length - 1];
|
|
7778
|
+
if (last?.role === "assistant") last.parts.push({ type: "tool-result", text });
|
|
7779
|
+
}
|
|
7780
|
+
}
|
|
7781
|
+
return out;
|
|
7782
|
+
}
|
|
7783
|
+
/**
|
|
7784
|
+
* `usageStatus: false` — Pi is BYO-provider: `ModelRuntime` knows whether auth
|
|
7785
|
+
* is configured but has no quota window to report. Declaring it unsupported
|
|
7786
|
+
* lets the GUI render nothing, which beats a probe that must always fail.
|
|
7787
|
+
*/
|
|
7788
|
+
capabilities() {
|
|
7789
|
+
return { listSessions: true, getMessages: true, usageStatus: false };
|
|
7364
7790
|
}
|
|
7365
7791
|
async dispose() {
|
|
7366
|
-
|
|
7792
|
+
this.runtime = null;
|
|
7793
|
+
this.pathIndex.clear();
|
|
7794
|
+
}
|
|
7795
|
+
/** Resolve a session id to its JSONL path, refreshing the index on a miss. */
|
|
7796
|
+
async resolveSessionPath(id) {
|
|
7797
|
+
const cached = this.pathIndex.get(id);
|
|
7798
|
+
if (cached) return cached;
|
|
7799
|
+
await this.listPiSessions();
|
|
7800
|
+
return this.pathIndex.get(id) ?? null;
|
|
7801
|
+
}
|
|
7802
|
+
async listPiSessions() {
|
|
7803
|
+
let infos;
|
|
7367
7804
|
try {
|
|
7368
|
-
|
|
7369
|
-
server.close();
|
|
7805
|
+
infos = await SessionManager$1.list(this.cwd, this.opts.sessionDir);
|
|
7370
7806
|
} catch {
|
|
7807
|
+
return [];
|
|
7371
7808
|
}
|
|
7372
|
-
|
|
7809
|
+
const mine = infos.filter((info) => !info.cwd || info.cwd === this.cwd);
|
|
7810
|
+
for (const info of mine) this.pathIndex.set(info.id, info.path);
|
|
7811
|
+
return mine;
|
|
7812
|
+
}
|
|
7813
|
+
titleOf(info) {
|
|
7814
|
+
if (info.name?.trim()) return info.name.trim();
|
|
7815
|
+
const summarized = info.firstMessage ? summarizeCodexPromptForTitle(info.firstMessage) : "";
|
|
7816
|
+
return summarized || info.id;
|
|
7373
7817
|
}
|
|
7374
7818
|
}
|
|
7375
7819
|
function createAdapter(kind, cwd) {
|
|
@@ -7378,6 +7822,8 @@ function createAdapter(kind, cwd) {
|
|
|
7378
7822
|
return new CodexAdapter(cwd);
|
|
7379
7823
|
case "opencode":
|
|
7380
7824
|
return new OpenCodeAdapter(cwd);
|
|
7825
|
+
case "pi":
|
|
7826
|
+
return new PiAdapter(cwd);
|
|
7381
7827
|
case "claude":
|
|
7382
7828
|
default:
|
|
7383
7829
|
return new ClaudeAdapter(cwd);
|
|
@@ -7519,7 +7965,7 @@ class SessionManager {
|
|
|
7519
7965
|
const indexed = await this.store.list();
|
|
7520
7966
|
const byId = new Map(indexed.map((s) => [s.id, s]));
|
|
7521
7967
|
const nativeIds = /* @__PURE__ */ new Set();
|
|
7522
|
-
for (const kind of ["claude", "codex", "opencode"]) {
|
|
7968
|
+
for (const kind of ["claude", "codex", "opencode", "pi"]) {
|
|
7523
7969
|
const adapter = this.adapters.get(kind);
|
|
7524
7970
|
if (!adapter.capabilities().listSessions) continue;
|
|
7525
7971
|
try {
|
|
@@ -7958,6 +8404,22 @@ class ProjectRegistry {
|
|
|
7958
8404
|
const normalized = await prepareProjectDir(absPath);
|
|
7959
8405
|
return await addProject(normalized, this.globalConfigPath);
|
|
7960
8406
|
}
|
|
8407
|
+
/**
|
|
8408
|
+
* 与 CLI `yorz add` 语义对齐的添加:含 git 仓库检查 / `git init` / `.gitignore` 写入。
|
|
8409
|
+
*
|
|
8410
|
+
* @param absPath 项目目录绝对路径(调用方需先归一化)。
|
|
8411
|
+
* @param opts `gitInit` 为 true 时授权对非 git 目录自动执行 `git init`。
|
|
8412
|
+
* @returns 注册结果。
|
|
8413
|
+
* @throws {NeedGitInitError} 目标非 git 仓库且未授权 `git init`;此时无任何副作用。
|
|
8414
|
+
*/
|
|
8415
|
+
async addWithGit(absPath, opts = {}) {
|
|
8416
|
+
if (!isAbsolute(absPath)) throw new Error(`path must be absolute: ${absPath}`);
|
|
8417
|
+
return await addProjectWithGit({
|
|
8418
|
+
path: absPath,
|
|
8419
|
+
gitInit: opts.gitInit === true,
|
|
8420
|
+
...this.globalConfigPath !== void 0 ? { globalConfigPath: this.globalConfigPath } : {}
|
|
8421
|
+
});
|
|
8422
|
+
}
|
|
7961
8423
|
async remove(id) {
|
|
7962
8424
|
await this.release(id).catch(() => {
|
|
7963
8425
|
});
|
|
@@ -8068,7 +8530,9 @@ class ProjectRegistry {
|
|
|
8068
8530
|
}
|
|
8069
8531
|
function resolveProjectAgentKind(projectAgent, globalDefault) {
|
|
8070
8532
|
if (projectAgent.kind === "inherit") return globalDefault;
|
|
8071
|
-
if (projectAgent.kind === "codex" || projectAgent.kind === "opencode"
|
|
8533
|
+
if (projectAgent.kind === "codex" || projectAgent.kind === "opencode" || projectAgent.kind === "pi") {
|
|
8534
|
+
return projectAgent.kind;
|
|
8535
|
+
}
|
|
8072
8536
|
return "claude";
|
|
8073
8537
|
}
|
|
8074
8538
|
function basename(p) {
|
|
@@ -8524,17 +8988,14 @@ function startBackgroundServe(opts) {
|
|
|
8524
8988
|
const entry = process.argv[1];
|
|
8525
8989
|
if (!entry) throw new Error("Cannot resolve CLI entrypoint for background service");
|
|
8526
8990
|
const stdio = backgroundStdio();
|
|
8527
|
-
const child =
|
|
8528
|
-
detached: true,
|
|
8529
|
-
stdio: stdio.stdio
|
|
8530
|
-
});
|
|
8531
|
-
child.unref();
|
|
8991
|
+
const child = spawnBackgroundWorker([entry, "serve", ...backgroundArgs(opts)], stdio.stdio);
|
|
8532
8992
|
log().info("background service spawned", {
|
|
8533
8993
|
pid: child.pid,
|
|
8994
|
+
viaLauncher: process.platform === "win32",
|
|
8534
8995
|
stdioFile: stdio.path,
|
|
8535
8996
|
logFile: getLogger().filePath
|
|
8536
8997
|
});
|
|
8537
|
-
const runtime = await waitForRuntime(
|
|
8998
|
+
const runtime = await waitForRuntime(void 0, START_WAIT_MS);
|
|
8538
8999
|
if (!runtime) {
|
|
8539
9000
|
log().warn("timed out waiting for runtime.json", {
|
|
8540
9001
|
pid: child.pid,
|
|
@@ -8543,12 +9004,13 @@ function startBackgroundServe(opts) {
|
|
|
8543
9004
|
}
|
|
8544
9005
|
const port = runtime?.port ?? opts.port ?? DEFAULT_SERVE_PORT;
|
|
8545
9006
|
const url = runtime?.url ?? `http://localhost:${port}/`;
|
|
8546
|
-
|
|
9007
|
+
const servicePid = runtime?.pid ?? child.pid;
|
|
9008
|
+
console.log(`YorZ Service started in background (pid=${servicePid ?? "unknown"}).`);
|
|
8547
9009
|
console.log(`Open ${url}${runtime ? "" : ` (or the next free port if ${port} is busy).`}`);
|
|
8548
9010
|
console.log(`Stop with: yorz serve stop`);
|
|
8549
9011
|
return {
|
|
8550
9012
|
background: true,
|
|
8551
|
-
pid:
|
|
9013
|
+
pid: servicePid,
|
|
8552
9014
|
url,
|
|
8553
9015
|
port
|
|
8554
9016
|
};
|
|
@@ -8575,6 +9037,26 @@ function restartWorkerArgs(opts) {
|
|
|
8575
9037
|
args.push("--skip-skill-check");
|
|
8576
9038
|
return args;
|
|
8577
9039
|
}
|
|
9040
|
+
function resolveServeLauncherPath() {
|
|
9041
|
+
return join(dirname(fileURLToPath(import.meta.url)), "serve-launcher.cjs");
|
|
9042
|
+
}
|
|
9043
|
+
function backgroundWorkerSpawnArgv(launcherPath, execPath, workerArgv) {
|
|
9044
|
+
return [launcherPath, "--", execPath, ...workerArgv];
|
|
9045
|
+
}
|
|
9046
|
+
function spawnBackgroundWorker(workerArgv, stdio) {
|
|
9047
|
+
if (process.platform === "win32") {
|
|
9048
|
+
const launcher = spawnWithoutWindow(
|
|
9049
|
+
process.execPath,
|
|
9050
|
+
backgroundWorkerSpawnArgv(resolveServeLauncherPath(), process.execPath, workerArgv),
|
|
9051
|
+
{ detached: true, stdio }
|
|
9052
|
+
);
|
|
9053
|
+
launcher.unref();
|
|
9054
|
+
return launcher;
|
|
9055
|
+
}
|
|
9056
|
+
const child = spawnWithoutWindow(process.execPath, workerArgv, { detached: true, stdio });
|
|
9057
|
+
child.unref();
|
|
9058
|
+
return child;
|
|
9059
|
+
}
|
|
8578
9060
|
async function runRestartServe(opts = {}) {
|
|
8579
9061
|
silenceConsoleMirror();
|
|
8580
9062
|
if (opts.worker) {
|
|
@@ -8585,12 +9067,8 @@ async function runRestartServe(opts = {}) {
|
|
|
8585
9067
|
}
|
|
8586
9068
|
const entry = process.argv[1];
|
|
8587
9069
|
if (!entry) throw new Error("Cannot resolve CLI entrypoint for restart");
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
stdio: "ignore"
|
|
8591
|
-
});
|
|
8592
|
-
child.unref();
|
|
8593
|
-
console.log(`YorZ Service restart scheduled (pid=${child.pid ?? "unknown"}).`);
|
|
9070
|
+
spawnBackgroundWorker([entry, ...restartWorkerArgs(opts)], "ignore");
|
|
9071
|
+
console.log(`YorZ Service restart scheduled.`);
|
|
8594
9072
|
}
|
|
8595
9073
|
async function ensureSkillsInstalledWithLog(cwd) {
|
|
8596
9074
|
const results = await ensureSkillsInstalled({ cwd });
|
|
@@ -8762,7 +9240,7 @@ async function waitForRuntime(pid, timeoutMs) {
|
|
|
8762
9240
|
const until = Date.now() + timeoutMs;
|
|
8763
9241
|
while (Date.now() < until) {
|
|
8764
9242
|
const live = await readLiveProcesses();
|
|
8765
|
-
const match = live.find((p) => pid === void 0
|
|
9243
|
+
const match = live.find((p) => pid === void 0);
|
|
8766
9244
|
if (match) return match;
|
|
8767
9245
|
await sleep(100);
|
|
8768
9246
|
}
|
|
@@ -8923,12 +9401,18 @@ async function readWindowsProcessSnapshot(pid) {
|
|
|
8923
9401
|
"if ($null -eq $p) { exit 1 }",
|
|
8924
9402
|
"$p | Select-Object CommandLine,CreationDate | ConvertTo-Json -Compress"
|
|
8925
9403
|
].join("; ");
|
|
8926
|
-
const { stdout } = await execFileAsync(
|
|
8927
|
-
"
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
9404
|
+
const { stdout } = await execFileAsync(
|
|
9405
|
+
"powershell.exe",
|
|
9406
|
+
[
|
|
9407
|
+
"-NoProfile",
|
|
9408
|
+
"-NonInteractive",
|
|
9409
|
+
"-Command",
|
|
9410
|
+
script
|
|
9411
|
+
],
|
|
9412
|
+
// 后台 Service 进程(detached,无控制台)内执行时,缺 windowsHide 会弹出
|
|
9413
|
+
// 可见的 PowerShell 窗口;前台终端调用则继承控制台,本就无影响。
|
|
9414
|
+
{ windowsHide: true }
|
|
9415
|
+
);
|
|
8932
9416
|
const parsed = JSON.parse(stdout.trim());
|
|
8933
9417
|
const commandLine = typeof parsed.CommandLine === "string" ? parsed.CommandLine : "";
|
|
8934
9418
|
const processStartedAt = typeof parsed.CreationDate === "string" ? parsed.CreationDate : void 0;
|
|
@@ -8964,35 +9448,33 @@ async function defaultPrompt(question) {
|
|
|
8964
9448
|
}
|
|
8965
9449
|
}
|
|
8966
9450
|
async function runAdd(opts) {
|
|
8967
|
-
const
|
|
8968
|
-
const
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
gitInitialized = true;
|
|
8982
|
-
} else {
|
|
9451
|
+
const gitInit = opts.runGitInit ?? ((cwd) => runGitInit(cwd));
|
|
9452
|
+
const base = {
|
|
9453
|
+
path: opts.path,
|
|
9454
|
+
...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
|
|
9455
|
+
...opts.globalConfigPath !== void 0 ? { globalConfigPath: opts.globalConfigPath } : {},
|
|
9456
|
+
runGitInit: gitInit
|
|
9457
|
+
};
|
|
9458
|
+
try {
|
|
9459
|
+
return await addProjectWithGit(base);
|
|
9460
|
+
} catch (err) {
|
|
9461
|
+
if (!(err instanceof NeedGitInitError)) throw err;
|
|
9462
|
+
const isTTY = opts.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
9463
|
+
if (!opts.yes) {
|
|
9464
|
+
if (!isTTY) {
|
|
8983
9465
|
throw new AddGitAbortedError(
|
|
8984
|
-
`yorz add:
|
|
9466
|
+
`yorz add: target directory is not a git repository; pass --yes to auto-run git init in non-interactive mode`
|
|
8985
9467
|
);
|
|
8986
9468
|
}
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
)
|
|
9469
|
+
const ask = opts.prompt ?? defaultPrompt;
|
|
9470
|
+
const raw = await ask(`yorz add: ${err.path} 未 git init,是否自动执行 \`git init\`? [y/N] `);
|
|
9471
|
+
const answer = raw.trim().toLowerCase();
|
|
9472
|
+
if (answer !== "y" && answer !== "yes") {
|
|
9473
|
+
throw new AddGitAbortedError(`yorz add: aborted — target directory is not a git repository`);
|
|
9474
|
+
}
|
|
8991
9475
|
}
|
|
9476
|
+
return await addProjectWithGit({ ...base, gitInit: true });
|
|
8992
9477
|
}
|
|
8993
|
-
const gitignore = await ensureTmpIgnored(abs);
|
|
8994
|
-
const { entry, created } = await addProject(abs, opts.globalConfigPath);
|
|
8995
|
-
return { entry, created, gitInitialized, gitignore };
|
|
8996
9478
|
}
|
|
8997
9479
|
const md = new MarkdownIt("commonmark", { html: false, linkify: false });
|
|
8998
9480
|
function buildContext(raw, kind, filePath) {
|