@yorz/cli 0.9.3 → 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 +802 -326
- package/dist/gui/assets/{abnfDiagram-VRR7QNED-BXdYbq3y.js → abnfDiagram-VRR7QNED-ChpNHKmG.js} +1 -1
- package/dist/{gui-mobile/assets/arc-BinP16HO.js → gui/assets/arc-BSWQE9li.js} +1 -1
- package/dist/{gui-mobile/assets/architectureDiagram-ZJ3FMSHR-DPaYnoe2.js → gui/assets/architectureDiagram-ZJ3FMSHR-BGI0Ue9c.js} +1 -1
- package/dist/{gui-mobile/assets/blockDiagram-677ZJIJ3-CcdgoWjg.js → gui/assets/blockDiagram-677ZJIJ3-DzJ0su4P.js} +1 -1
- package/dist/gui/assets/{c4Diagram-LMCZKHZV-BQRLrFmd.js → c4Diagram-LMCZKHZV-BijbkJk0.js} +1 -1
- package/dist/gui/assets/channel-S36nQ2lf.js +1 -0
- package/dist/gui/assets/{chunk-2Q5K7J3B-Bb01F4JY.js → chunk-2Q5K7J3B-Ds8H4SMx.js} +1 -1
- package/dist/gui/assets/{chunk-32BRIVSS-D_fmYp0w.js → chunk-32BRIVSS-CaodSuYb.js} +1 -1
- package/dist/gui/assets/{chunk-5VM5RSS4-CSVRyWUl.js → chunk-5VM5RSS4-Dk7weLtE.js} +1 -1
- package/dist/gui/assets/{chunk-EX3LRPZG-0tMlNePS.js → chunk-EX3LRPZG-Dc4TtD4O.js} +1 -1
- package/dist/gui/assets/{chunk-JWPE2WC7-BjMvMllJ.js → chunk-JWPE2WC7-BXjLPdIx.js} +1 -1
- package/dist/gui/assets/{chunk-MOJQB5TN-kCAEgJRp.js → chunk-MOJQB5TN-Ct0R-MTA.js} +1 -1
- package/dist/gui/assets/{chunk-RYQCIY6F-Bux4XgPK.js → chunk-RYQCIY6F-BkMMhSkU.js} +1 -1
- package/dist/gui/assets/{chunk-V7JOEXUC-BP87KF_P.js → chunk-V7JOEXUC-CA_5lNDX.js} +1 -1
- package/dist/gui/assets/{chunk-VR4S4FIN-DTiqIx-p.js → chunk-VR4S4FIN-BP0qSlug.js} +1 -1
- package/dist/gui/assets/{chunk-XXDRQBXY-B7k8EFM_.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--rhAXZui.js → gui/assets/cose-bilkent-JH36ORCC-DH_K5WAp.js} +1 -1
- package/dist/gui/assets/{cynefin-VYW2F7L2-DzMBSujh.js → cynefin-VYW2F7L2-BBIjNWwX.js} +1 -1
- package/dist/{gui-mobile/assets/cynefinDiagram-TSTJHNR4-CQ3yGfYY.js → gui/assets/cynefinDiagram-TSTJHNR4-BRRBlTKf.js} +1 -1
- package/dist/{gui-mobile/assets/dagre-VKFMJZFB-BbgmZx8f.js → gui/assets/dagre-VKFMJZFB-CiENng6J.js} +1 -1
- package/dist/gui/assets/{diagram-FQU43EPY-CMCJAQGZ.js → diagram-FQU43EPY-BpAJWtF0.js} +1 -1
- package/dist/{gui-mobile/assets/diagram-G47NLZAW-CqXfBP6i.js → gui/assets/diagram-G47NLZAW-VQzk2Zt3.js} +1 -1
- package/dist/gui/assets/{diagram-NH7WQ7WH-C4WG9hEr.js → diagram-NH7WQ7WH-BETlyFIV.js} +1 -1
- package/dist/{gui-mobile/assets/diagram-OA4YK3LP-DF9IybfH.js → gui/assets/diagram-OA4YK3LP-D7kweveS.js} +1 -1
- package/dist/gui/assets/{diagram-WEI45ONY-DE9_2qZ8.js → diagram-WEI45ONY-4xazks6b.js} +1 -1
- package/dist/gui/assets/{ebnfDiagram-CCIWWBDH-BL7vwncJ.js → ebnfDiagram-CCIWWBDH-x6c2P9Gq.js} +1 -1
- package/dist/{gui-mobile/assets/erDiagram-Q63AITRT-DWpkkPmJ.js → gui/assets/erDiagram-Q63AITRT-Dwu_NmfY.js} +1 -1
- package/dist/gui/assets/{flowDiagram-23GEKE2U-DcgdUDC_.js → flowDiagram-23GEKE2U-mlV2EfUY.js} +1 -1
- package/dist/{gui-mobile/assets/ganttDiagram-NO4QXBWP-B3b1yqDR.js → gui/assets/ganttDiagram-NO4QXBWP-sRHt6-K7.js} +1 -1
- package/dist/gui/assets/{gitGraphDiagram-IHSO6WYX-Bh16bq4A.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-CKRUUIby.js → infoDiagram-FWYZ7A6U-B4Uf4vez.js} +1 -1
- package/dist/gui/assets/{ishikawaDiagram-FXEZZL3T-CEMvAMgC.js → ishikawaDiagram-FXEZZL3T-BF78753F.js} +1 -1
- package/dist/{gui-mobile/assets/journeyDiagram-5HDEW3XC-C0pTHZiP.js → gui/assets/journeyDiagram-5HDEW3XC-BO6V3zi-.js} +1 -1
- package/dist/gui/assets/{kanban-definition-HUTT4EX6-D2JIKO0d.js → kanban-definition-HUTT4EX6-C6Abp-Z0.js} +1 -1
- package/dist/gui/assets/{linear-1sIzYuR6.js → linear-4_sZdUed.js} +1 -1
- package/dist/gui/assets/{mermaid.core-_0eTmAI-.js → mermaid.core-BoPHxIg3.js} +4 -4
- package/dist/gui/assets/{mindmap-definition-LN4V7U3C-Pci6rYHR.js → mindmap-definition-LN4V7U3C-EcN1F0cO.js} +1 -1
- package/dist/gui/assets/{pegDiagram-2B236MQR-q2F8rPpc.js → pegDiagram-2B236MQR-giOW1xob.js} +1 -1
- package/dist/gui/assets/{pieDiagram-ENE6RG2P-Bxs2H0I9.js → pieDiagram-ENE6RG2P-D9cIRAxf.js} +1 -1
- package/dist/{gui-mobile/assets/quadrantDiagram-ABIIQ3AL-C0mOJrSQ.js → gui/assets/quadrantDiagram-ABIIQ3AL-1jwzcF1N.js} +1 -1
- package/dist/gui/assets/{railroadDiagram-RFXS5EU6-DLkA9HFf.js → railroadDiagram-RFXS5EU6-kp1xeMyA.js} +1 -1
- package/dist/gui/assets/{requirementDiagram-TGXJPOKE-DmqV2XMZ.js → requirementDiagram-TGXJPOKE-yspWGQMw.js} +1 -1
- package/dist/{gui-mobile/assets/sankeyDiagram-HTMAVEWB-C5rsz_9V.js → gui/assets/sankeyDiagram-HTMAVEWB-Ylm3QxdV.js} +1 -1
- package/dist/{gui-mobile/assets/sequenceDiagram-DBY2YBRQ-CJPJqG5C.js → gui/assets/sequenceDiagram-DBY2YBRQ-CJJnMk2d.js} +1 -1
- package/dist/gui/assets/{sizeCapture-X5ZJPWSS-dcpaI63h.js → sizeCapture-X5ZJPWSS-F3cALp3d.js} +1 -1
- package/dist/{gui-mobile/assets/stateDiagram-2N3HPSRC-CcuZbZ-q.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-C3tNR3sN.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-DJxPM33l.js → gui/assets/timeline-definition-FHXFAJF6-Bl_hCpye.js} +1 -1
- package/dist/gui/assets/{vennDiagram-L72KCM5P-CgwpgHPj.js → vennDiagram-L72KCM5P-CvGLve2y.js} +1 -1
- package/dist/gui/assets/{wardleyDiagram-EHGQE667-Cnc-fgIC.js → wardleyDiagram-EHGQE667-DLRGPeZX.js} +1 -1
- package/dist/{gui-mobile/assets/xychartDiagram-FW5EYKEG-X6HGdnuS.js → gui/assets/xychartDiagram-FW5EYKEG-bB6p1K59.js} +1 -1
- package/dist/gui/index.html +2 -2
- package/dist/gui-mobile/assets/{abnfDiagram-VRR7QNED-3aNc_9tC.js → abnfDiagram-VRR7QNED-Cii3YIsO.js} +1 -1
- package/dist/{gui/assets/arc-BDaL1Z0T.js → gui-mobile/assets/arc-D5zxWTc6.js} +1 -1
- package/dist/{gui/assets/architectureDiagram-ZJ3FMSHR-DBV2eI0R.js → gui-mobile/assets/architectureDiagram-ZJ3FMSHR-BUrFMtmN.js} +1 -1
- package/dist/{gui/assets/blockDiagram-677ZJIJ3-BukUw8ke.js → gui-mobile/assets/blockDiagram-677ZJIJ3-CLXT0p5K.js} +1 -1
- package/dist/gui-mobile/assets/{c4Diagram-LMCZKHZV-Dt-ebUaz.js → c4Diagram-LMCZKHZV-XZDjR7Pf.js} +1 -1
- package/dist/gui-mobile/assets/channel-DAACRh1k.js +1 -0
- package/dist/gui-mobile/assets/{chunk-2Q5K7J3B-Dkn1JJmn.js → chunk-2Q5K7J3B-C0SSdrgO.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-32BRIVSS-Cou5wk8N.js → chunk-32BRIVSS-B-l29SGQ.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-5VM5RSS4-DE1VWYBj.js → chunk-5VM5RSS4-2Oc13ID8.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-EX3LRPZG-DIJfdh0r.js → chunk-EX3LRPZG-tRiKUhHg.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-JWPE2WC7-DMTNZnRa.js → chunk-JWPE2WC7-CWxLnmGK.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-MOJQB5TN-C37wZo1y.js → chunk-MOJQB5TN-CRo9axdS.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-RYQCIY6F-B00F7z4X.js → chunk-RYQCIY6F-BWk2tC2V.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-V7JOEXUC-BBhJsm6x.js → chunk-V7JOEXUC-CAf_TQ8c.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-VR4S4FIN-BaRfY0Qz.js → chunk-VR4S4FIN-Djvb-G48.js} +1 -1
- package/dist/gui-mobile/assets/{chunk-XXDRQBXY-DLP_jCaJ.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-BaKMo0vy.js → gui-mobile/assets/cose-bilkent-JH36ORCC-DQd0ag_q.js} +1 -1
- package/dist/gui-mobile/assets/{cynefin-VYW2F7L2-DP4VQrg7.js → cynefin-VYW2F7L2-O3JIjwWq.js} +1 -1
- package/dist/{gui/assets/cynefinDiagram-TSTJHNR4-BeUiEs05.js → gui-mobile/assets/cynefinDiagram-TSTJHNR4-Ca9ChAoZ.js} +1 -1
- package/dist/{gui/assets/dagre-VKFMJZFB-Ds02vw0z.js → gui-mobile/assets/dagre-VKFMJZFB-Dz84jzyH.js} +1 -1
- package/dist/gui-mobile/assets/{diagram-FQU43EPY-B3ayJYFS.js → diagram-FQU43EPY-Ddl2aZsD.js} +1 -1
- package/dist/{gui/assets/diagram-G47NLZAW-BsBRMo6j.js → gui-mobile/assets/diagram-G47NLZAW-BUUkAM1L.js} +1 -1
- package/dist/gui-mobile/assets/{diagram-NH7WQ7WH-DPg20-Zs.js → diagram-NH7WQ7WH-Bhp3-t7h.js} +1 -1
- package/dist/{gui/assets/diagram-OA4YK3LP-BA2cjwAc.js → gui-mobile/assets/diagram-OA4YK3LP-Dz2X8j8T.js} +1 -1
- package/dist/gui-mobile/assets/{diagram-WEI45ONY-BSAcECq5.js → diagram-WEI45ONY-m28jBgF4.js} +1 -1
- package/dist/gui-mobile/assets/{ebnfDiagram-CCIWWBDH-BhJETAAk.js → ebnfDiagram-CCIWWBDH-Dh-_tj-T.js} +1 -1
- package/dist/{gui/assets/erDiagram-Q63AITRT-rCrtODep.js → gui-mobile/assets/erDiagram-Q63AITRT-C-3poaMf.js} +1 -1
- package/dist/gui-mobile/assets/{flowDiagram-23GEKE2U-CHAjzU36.js → flowDiagram-23GEKE2U-BopsgXAH.js} +1 -1
- package/dist/{gui/assets/ganttDiagram-NO4QXBWP-BsWsRaQX.js → gui-mobile/assets/ganttDiagram-NO4QXBWP-Bn7GJbzm.js} +1 -1
- package/dist/gui-mobile/assets/{gitGraphDiagram-IHSO6WYX-BqRTGYY0.js → gitGraphDiagram-IHSO6WYX-DfLUxTzH.js} +1 -1
- package/dist/gui-mobile/assets/{index-BwPHOQF7.js → index--agUQ6GR.js} +16 -16
- package/dist/gui-mobile/assets/index-CNDPS9z2.css +1 -0
- package/dist/gui-mobile/assets/{infoDiagram-FWYZ7A6U-4PN9nRxs.js → infoDiagram-FWYZ7A6U-nrBaoqsE.js} +1 -1
- package/dist/gui-mobile/assets/{ishikawaDiagram-FXEZZL3T-wzGQWfQz.js → ishikawaDiagram-FXEZZL3T-DEVq4aJ1.js} +1 -1
- package/dist/{gui/assets/journeyDiagram-5HDEW3XC-hPSE5KSG.js → gui-mobile/assets/journeyDiagram-5HDEW3XC-CA-AnD2i.js} +1 -1
- package/dist/gui-mobile/assets/{kanban-definition-HUTT4EX6-D_XwIFzm.js → kanban-definition-HUTT4EX6-BtUD4217.js} +1 -1
- package/dist/gui-mobile/assets/{linear-DHPIiHoC.js → linear-Cs1nDCen.js} +1 -1
- package/dist/gui-mobile/assets/{mermaid.core-DWa29NaI.js → mermaid.core-CjlnyTvi.js} +4 -4
- package/dist/gui-mobile/assets/{mindmap-definition-LN4V7U3C-BsAz9DiI.js → mindmap-definition-LN4V7U3C-DpHq0ntz.js} +1 -1
- package/dist/gui-mobile/assets/{pegDiagram-2B236MQR-k9xbtGjh.js → pegDiagram-2B236MQR-UB1lVTuX.js} +1 -1
- package/dist/gui-mobile/assets/{pieDiagram-ENE6RG2P-vrMiQFQU.js → pieDiagram-ENE6RG2P-7qkPFOFY.js} +1 -1
- package/dist/{gui/assets/quadrantDiagram-ABIIQ3AL-jhiPHiLV.js → gui-mobile/assets/quadrantDiagram-ABIIQ3AL-DPM04b8X.js} +1 -1
- package/dist/gui-mobile/assets/{railroadDiagram-RFXS5EU6-BuXb1GwD.js → railroadDiagram-RFXS5EU6-BY7DXXsK.js} +1 -1
- package/dist/gui-mobile/assets/{requirementDiagram-TGXJPOKE-zuyAg5UY.js → requirementDiagram-TGXJPOKE-RJuz8ezU.js} +1 -1
- package/dist/{gui/assets/sankeyDiagram-HTMAVEWB-DKKUsQKC.js → gui-mobile/assets/sankeyDiagram-HTMAVEWB-DIHqskb-.js} +1 -1
- package/dist/{gui/assets/sequenceDiagram-DBY2YBRQ-DFX-fI6J.js → gui-mobile/assets/sequenceDiagram-DBY2YBRQ-B91bF6fy.js} +1 -1
- package/dist/gui-mobile/assets/{sizeCapture-X5ZJPWSS-Dfmt1n7j.js → sizeCapture-X5ZJPWSS-Bb8LgAbm.js} +1 -1
- package/dist/{gui/assets/stateDiagram-2N3HPSRC-DOBn8whp.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-CrvE3E_z.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-Cg8Rxg6S.js → gui-mobile/assets/timeline-definition-FHXFAJF6-fXhjYYU8.js} +1 -1
- package/dist/gui-mobile/assets/{vennDiagram-L72KCM5P-C95GuO2U.js → vennDiagram-L72KCM5P-BxTJzmPG.js} +1 -1
- package/dist/gui-mobile/assets/{wardleyDiagram-EHGQE667-ULJzIJRO.js → wardleyDiagram-EHGQE667-CiAx3NHL.js} +1 -1
- package/dist/{gui/assets/xychartDiagram-FW5EYKEG-Do9QbE6u.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-CmM-rnSu.js +0 -1
- package/dist/gui/assets/classDiagram-OUVF2IWQ-foBbbuIG.js +0 -1
- package/dist/gui/assets/classDiagram-v2-EOCWNBFH-foBbbuIG.js +0 -1
- package/dist/gui/assets/index-e5JFp5J1.css +0 -1
- package/dist/gui/assets/index-oIjkNbg4.js +0 -34
- package/dist/gui/assets/stateDiagram-v2-6OUMAXLB-CSydFa0j.js +0 -1
- package/dist/gui/assets/swimlanesDiagram-G3AALYLV-CqM-5nJs.js +0 -8
- package/dist/gui-mobile/assets/channel-BwGfICk5.js +0 -1
- package/dist/gui-mobile/assets/classDiagram-OUVF2IWQ-BC5JEZFR.js +0 -1
- package/dist/gui-mobile/assets/classDiagram-v2-EOCWNBFH-BC5JEZFR.js +0 -1
- package/dist/gui-mobile/assets/index-BABBu2VR.css +0 -1
- package/dist/gui-mobile/assets/stateDiagram-v2-6OUMAXLB-D1oP_d1q.js +0 -1
- package/dist/gui-mobile/assets/swimlanesDiagram-G3AALYLV-auRnO-sL.js +0 -8
package/dist/cli/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
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
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -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,6 +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" };
|
|
2381
|
+
if (kind === "pi") return { kind: "pi" };
|
|
2355
2382
|
return { kind: "claude" };
|
|
2356
2383
|
}
|
|
2357
2384
|
function normalizeSpecsDir(value) {
|
|
@@ -2367,7 +2394,7 @@ async function loadCustomInstructions(projectPath) {
|
|
|
2367
2394
|
]);
|
|
2368
2395
|
return mergeCustomInstructions(project, global);
|
|
2369
2396
|
}
|
|
2370
|
-
const KINDS = ["claude", "codex", "opencode"];
|
|
2397
|
+
const KINDS = ["claude", "codex", "opencode", "pi"];
|
|
2371
2398
|
const DRAFT_ID_RE = /^[a-zA-Z0-9-]{1,64}$/;
|
|
2372
2399
|
function buildChatPrompt(prompt, draftId, attachments) {
|
|
2373
2400
|
if (attachments.length === 0) return prompt;
|
|
@@ -3421,6 +3448,39 @@ function createEventsRoutes(resolveProject, registry, projectsBus, systemNotific
|
|
|
3421
3448
|
});
|
|
3422
3449
|
return app;
|
|
3423
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
|
+
}
|
|
3424
3484
|
function createProjectRoutes(registry, worktreeManager) {
|
|
3425
3485
|
const app = new Hono();
|
|
3426
3486
|
app.get("/projects", async (c) => {
|
|
@@ -3434,7 +3494,8 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3434
3494
|
} catch {
|
|
3435
3495
|
return c.json({ error: "invalid JSON body" }, 400);
|
|
3436
3496
|
}
|
|
3437
|
-
const
|
|
3497
|
+
const raw = body;
|
|
3498
|
+
const path = raw?.path;
|
|
3438
3499
|
if (typeof path !== "string" || !path.trim()) {
|
|
3439
3500
|
return c.json({ error: "path required" }, 400);
|
|
3440
3501
|
}
|
|
@@ -3442,7 +3503,7 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3442
3503
|
if (!isAbsolute(trimmed)) {
|
|
3443
3504
|
return c.json({ error: "path must be absolute" }, 400);
|
|
3444
3505
|
}
|
|
3445
|
-
const normalized =
|
|
3506
|
+
const normalized = normalizeAbsPath(trimmed);
|
|
3446
3507
|
if (!existsSync(normalized)) {
|
|
3447
3508
|
return c.json({ error: `path does not exist: ${normalized}` }, 400);
|
|
3448
3509
|
}
|
|
@@ -3451,7 +3512,7 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3451
3512
|
return c.json({ error: `path is not a directory: ${normalized}` }, 400);
|
|
3452
3513
|
}
|
|
3453
3514
|
try {
|
|
3454
|
-
const result = await registry.
|
|
3515
|
+
const result = await registry.addWithGit(normalized, { gitInit: raw?.gitInit === true });
|
|
3455
3516
|
return c.json(
|
|
3456
3517
|
{
|
|
3457
3518
|
id: result.entry.id,
|
|
@@ -3462,6 +3523,9 @@ function createProjectRoutes(registry, worktreeManager) {
|
|
|
3462
3523
|
result.created ? 201 : 200
|
|
3463
3524
|
);
|
|
3464
3525
|
} catch (err) {
|
|
3526
|
+
if (err instanceof NeedGitInitError) {
|
|
3527
|
+
return c.json({ error: err.message, needGitInit: true, path: err.path }, 409);
|
|
3528
|
+
}
|
|
3465
3529
|
return c.json({ error: err.message }, 400);
|
|
3466
3530
|
}
|
|
3467
3531
|
});
|
|
@@ -3589,7 +3653,8 @@ function parseAgent(value) {
|
|
|
3589
3653
|
if (kind === "claude") return { value: { kind: "claude" } };
|
|
3590
3654
|
if (kind === "opencode") return { value: { kind: "opencode" } };
|
|
3591
3655
|
if (kind === "codex") return { value: { kind: "codex" } };
|
|
3592
|
-
|
|
3656
|
+
if (kind === "pi") return { value: { kind: "pi" } };
|
|
3657
|
+
return { error: "agent.kind must be inherit | claude | opencode | codex | pi" };
|
|
3593
3658
|
}
|
|
3594
3659
|
function parseSpecsDir(value) {
|
|
3595
3660
|
const fallback = defaultProjectConfig().specsDir;
|
|
@@ -3794,8 +3859,8 @@ function parseBody(value) {
|
|
|
3794
3859
|
}
|
|
3795
3860
|
const agentObj = agent;
|
|
3796
3861
|
const defaultKind = agentObj.defaultKind;
|
|
3797
|
-
if (defaultKind !== "claude" && defaultKind !== "opencode" && defaultKind !== "codex") {
|
|
3798
|
-
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" };
|
|
3799
3864
|
}
|
|
3800
3865
|
const notifications = obj.notifications;
|
|
3801
3866
|
if (!notifications || typeof notifications !== "object") {
|
|
@@ -4212,6 +4277,172 @@ function createProjectFilesRoutes(resolveProject) {
|
|
|
4212
4277
|
});
|
|
4213
4278
|
return app;
|
|
4214
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
|
+
}
|
|
4215
4446
|
class CommandRunStore {
|
|
4216
4447
|
file;
|
|
4217
4448
|
cache = null;
|
|
@@ -5758,6 +5989,7 @@ function createApp(opts) {
|
|
|
5758
5989
|
api.route("/", createSpecDraftsRoutes(resolveProject));
|
|
5759
5990
|
api.route("/", createWorktreeRoutes(opts.registry, worktreeManager));
|
|
5760
5991
|
api.route("/", createProjectFilesRoutes(resolveProject));
|
|
5992
|
+
api.route("/", createFsRoutes());
|
|
5761
5993
|
api.route("/", createCommandsRoutes(resolveProject));
|
|
5762
5994
|
api.route(
|
|
5763
5995
|
"/",
|
|
@@ -6342,13 +6574,57 @@ class SpecWatcher {
|
|
|
6342
6574
|
return parts[0];
|
|
6343
6575
|
}
|
|
6344
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
|
+
}
|
|
6345
6621
|
function blocksFrom(message) {
|
|
6346
6622
|
if (!message || typeof message !== "object") return [];
|
|
6347
6623
|
const content = message.content;
|
|
6348
6624
|
if (typeof content === "string") return content ? [{ type: "text", text: content }] : [];
|
|
6349
6625
|
return Array.isArray(content) ? content : [];
|
|
6350
6626
|
}
|
|
6351
|
-
function toolResultText(content) {
|
|
6627
|
+
function toolResultText$1(content) {
|
|
6352
6628
|
if (typeof content === "string") return content;
|
|
6353
6629
|
if (Array.isArray(content)) {
|
|
6354
6630
|
let acc = "";
|
|
@@ -6394,10 +6670,10 @@ function usageWindows(usage) {
|
|
|
6394
6670
|
}
|
|
6395
6671
|
return out;
|
|
6396
6672
|
}
|
|
6397
|
-
function num$
|
|
6673
|
+
function num$2(value) {
|
|
6398
6674
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
6399
6675
|
}
|
|
6400
|
-
function isRecord(value) {
|
|
6676
|
+
function isRecord$1(value) {
|
|
6401
6677
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6402
6678
|
}
|
|
6403
6679
|
function withCost(usage, costUsd) {
|
|
@@ -6405,11 +6681,11 @@ function withCost(usage, costUsd) {
|
|
|
6405
6681
|
return { ...usage ?? {}, costUsd };
|
|
6406
6682
|
}
|
|
6407
6683
|
function primaryModel(modelUsage) {
|
|
6408
|
-
if (!isRecord(modelUsage)) return void 0;
|
|
6684
|
+
if (!isRecord$1(modelUsage)) return void 0;
|
|
6409
6685
|
let best;
|
|
6410
6686
|
let bestOutput = -1;
|
|
6411
6687
|
for (const [model, raw] of Object.entries(modelUsage)) {
|
|
6412
|
-
const output = isRecord(raw) ? num$
|
|
6688
|
+
const output = isRecord$1(raw) ? num$2(raw.outputTokens) ?? 0 : 0;
|
|
6413
6689
|
if (output > bestOutput) {
|
|
6414
6690
|
bestOutput = output;
|
|
6415
6691
|
best = model;
|
|
@@ -6417,38 +6693,8 @@ function primaryModel(modelUsage) {
|
|
|
6417
6693
|
}
|
|
6418
6694
|
return best;
|
|
6419
6695
|
}
|
|
6420
|
-
const SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
6421
|
-
function isSpecWrite(name2, input) {
|
|
6422
|
-
if (typeof name2 !== "string" || !SPEC_WRITE_TOOLS.has(name2)) return false;
|
|
6423
|
-
const path = isRecord(input) ? input.file_path : void 0;
|
|
6424
|
-
return typeof path === "string" && path.endsWith("spec.md");
|
|
6425
|
-
}
|
|
6426
6696
|
function usageOf(message) {
|
|
6427
|
-
return isRecord(message) ? message.usage : void 0;
|
|
6428
|
-
}
|
|
6429
|
-
class PhaseAccumulator {
|
|
6430
|
-
constructor(startedAt) {
|
|
6431
|
-
this.startedAt = startedAt;
|
|
6432
|
-
}
|
|
6433
|
-
usage = {};
|
|
6434
|
-
requests = 0;
|
|
6435
|
-
/** Count one API response, whether or not it reported usage. */
|
|
6436
|
-
add(raw) {
|
|
6437
|
-
this.requests += 1;
|
|
6438
|
-
const snapshot = normalizeUsage("claude", raw);
|
|
6439
|
-
if (!snapshot) return;
|
|
6440
|
-
for (const [key, value] of Object.entries(snapshot)) {
|
|
6441
|
-
const field = key;
|
|
6442
|
-
this.usage[field] = (this.usage[field] ?? 0) + value;
|
|
6443
|
-
}
|
|
6444
|
-
}
|
|
6445
|
-
snapshot() {
|
|
6446
|
-
return {
|
|
6447
|
-
usage: { ...this.usage },
|
|
6448
|
-
requests: this.requests,
|
|
6449
|
-
durationMs: Date.now() - this.startedAt
|
|
6450
|
-
};
|
|
6451
|
-
}
|
|
6697
|
+
return isRecord$1(message) ? message.usage : void 0;
|
|
6452
6698
|
}
|
|
6453
6699
|
class ClaudeSession {
|
|
6454
6700
|
constructor(id, cwd, isNew) {
|
|
@@ -6473,7 +6719,7 @@ class ClaudeSession {
|
|
|
6473
6719
|
};
|
|
6474
6720
|
if (this.isNew && !this.started) options.sessionId = this.id;
|
|
6475
6721
|
else options.resume = this.id;
|
|
6476
|
-
const phase = new PhaseAccumulator(Date.now());
|
|
6722
|
+
const phase = new PhaseAccumulator("claude", Date.now());
|
|
6477
6723
|
let planPhase;
|
|
6478
6724
|
const withPhases = (base) => ({
|
|
6479
6725
|
...base ?? {},
|
|
@@ -6498,26 +6744,26 @@ class ClaudeSession {
|
|
|
6498
6744
|
if (b.type === "text" && typeof b.text === "string") {
|
|
6499
6745
|
yield { type: "text", delta: b.text };
|
|
6500
6746
|
} else if (b.type === "tool_use") {
|
|
6501
|
-
if (!planPhase && isSpecWrite(b.name, b.input)) planPhase = phase.snapshot();
|
|
6747
|
+
if (!planPhase && isSpecWrite("claude", b.name, b.input)) planPhase = phase.snapshot();
|
|
6502
6748
|
yield { type: "tool-use", name: String(b.name ?? "?"), input: b.input ?? {} };
|
|
6503
6749
|
}
|
|
6504
6750
|
}
|
|
6505
6751
|
} else if (m.type === "user") {
|
|
6506
6752
|
for (const b of blocksFrom(m.message)) {
|
|
6507
6753
|
if (b.type === "tool_result") {
|
|
6508
|
-
yield { type: "tool-result", text: toolResultText(b.content) };
|
|
6754
|
+
yield { type: "tool-result", text: toolResultText$1(b.content) };
|
|
6509
6755
|
}
|
|
6510
6756
|
}
|
|
6511
6757
|
} else if (m.type === "result") {
|
|
6512
6758
|
usage = m.usage;
|
|
6513
6759
|
metrics = {
|
|
6514
|
-
usage: withCost(normalizeUsage("claude", m.usage), num$
|
|
6515
|
-
modelUsage: isRecord(m.modelUsage) ? m.modelUsage : void 0,
|
|
6516
|
-
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),
|
|
6517
6763
|
stopReason: typeof m.stop_reason === "string" ? m.stop_reason : void 0,
|
|
6518
6764
|
model: primaryModel(m.modelUsage),
|
|
6519
|
-
durationMs: num$
|
|
6520
|
-
apiDurationMs: num$
|
|
6765
|
+
durationMs: num$2(m.duration_ms),
|
|
6766
|
+
apiDurationMs: num$2(m.duration_api_ms)
|
|
6521
6767
|
};
|
|
6522
6768
|
} else if (m.type === "system" && m.subtype === "compact_boundary") {
|
|
6523
6769
|
const meta = m.compact_metadata ?? {};
|
|
@@ -6525,9 +6771,9 @@ class ClaudeSession {
|
|
|
6525
6771
|
type: "compact",
|
|
6526
6772
|
metrics: {
|
|
6527
6773
|
trigger: meta.trigger === "manual" || meta.trigger === "auto" ? meta.trigger : void 0,
|
|
6528
|
-
preTokens: num$
|
|
6529
|
-
postTokens: num$
|
|
6530
|
-
durationMs: num$
|
|
6774
|
+
preTokens: num$2(meta.pre_tokens),
|
|
6775
|
+
postTokens: num$2(meta.post_tokens),
|
|
6776
|
+
durationMs: num$2(meta.duration_ms)
|
|
6531
6777
|
}
|
|
6532
6778
|
};
|
|
6533
6779
|
} else if (m.type === "system" && m.subtype === "session_state_changed" && m.state === "idle") {
|
|
@@ -6581,7 +6827,7 @@ class ClaudeAdapter {
|
|
|
6581
6827
|
} else if (b.type === "tool_use") {
|
|
6582
6828
|
parts.push({ type: "tool-use", name: String(b.name ?? "?"), input: b.input ?? {} });
|
|
6583
6829
|
} else if (b.type === "tool_result") {
|
|
6584
|
-
parts.push({ type: "tool-result", text: toolResultText(b.content) });
|
|
6830
|
+
parts.push({ type: "tool-result", text: toolResultText$1(b.content) });
|
|
6585
6831
|
}
|
|
6586
6832
|
}
|
|
6587
6833
|
if (parts.length) out.push({ role: entry.type, parts });
|
|
@@ -6679,10 +6925,10 @@ function parseCodexSessionIndex(text) {
|
|
|
6679
6925
|
}
|
|
6680
6926
|
return out;
|
|
6681
6927
|
}
|
|
6682
|
-
function asRecord
|
|
6928
|
+
function asRecord(value) {
|
|
6683
6929
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6684
6930
|
}
|
|
6685
|
-
function numberFrom
|
|
6931
|
+
function numberFrom(value) {
|
|
6686
6932
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6687
6933
|
if (typeof value === "string" && value.trim()) {
|
|
6688
6934
|
const n = Number(value);
|
|
@@ -6690,33 +6936,33 @@ function numberFrom$1(value) {
|
|
|
6690
6936
|
}
|
|
6691
6937
|
return null;
|
|
6692
6938
|
}
|
|
6693
|
-
function resetFrom
|
|
6939
|
+
function resetFrom(value) {
|
|
6694
6940
|
if (typeof value === "string" && value.trim()) {
|
|
6695
6941
|
const n2 = Number(value);
|
|
6696
6942
|
if (Number.isFinite(n2)) return new Date(n2 * 1e3).toISOString();
|
|
6697
6943
|
const d = new Date(value);
|
|
6698
6944
|
return Number.isFinite(d.getTime()) ? d.toISOString() : null;
|
|
6699
6945
|
}
|
|
6700
|
-
const n = numberFrom
|
|
6946
|
+
const n = numberFrom(value);
|
|
6701
6947
|
return n == null ? null : new Date(n * 1e3).toISOString();
|
|
6702
6948
|
}
|
|
6703
6949
|
function codexWindow(key, label, value) {
|
|
6704
|
-
const rec = asRecord
|
|
6950
|
+
const rec = asRecord(value);
|
|
6705
6951
|
if (!rec) return null;
|
|
6706
|
-
const utilization = numberFrom
|
|
6952
|
+
const utilization = numberFrom(rec.used_percent) ?? numberFrom(rec.usedPercent) ?? numberFrom(rec.utilization) ?? numberFrom(rec.percent_used);
|
|
6707
6953
|
if (utilization == null) return null;
|
|
6708
6954
|
return {
|
|
6709
6955
|
key,
|
|
6710
6956
|
label,
|
|
6711
6957
|
utilization,
|
|
6712
|
-
resetsAt: resetFrom
|
|
6958
|
+
resetsAt: resetFrom(rec.resets_at ?? rec.reset_at ?? rec.resetsAt ?? rec.resetAt)
|
|
6713
6959
|
};
|
|
6714
6960
|
}
|
|
6715
6961
|
function parseCodexUsageResponse(raw) {
|
|
6716
|
-
const root = asRecord
|
|
6962
|
+
const root = asRecord(raw);
|
|
6717
6963
|
if (!root) return null;
|
|
6718
|
-
const rateLimit = asRecord
|
|
6719
|
-
const rateLimits = asRecord
|
|
6964
|
+
const rateLimit = asRecord(root.rate_limit);
|
|
6965
|
+
const rateLimits = asRecord(root.rate_limits);
|
|
6720
6966
|
const windows = [];
|
|
6721
6967
|
const primary = codexWindow(
|
|
6722
6968
|
"primary",
|
|
@@ -6733,7 +6979,7 @@ function parseCodexUsageResponse(raw) {
|
|
|
6733
6979
|
const additional = root.additional_rate_limits ?? root.additionalRateLimits ?? rateLimit?.additional;
|
|
6734
6980
|
if (Array.isArray(additional)) {
|
|
6735
6981
|
for (const [idx, item] of additional.entries()) {
|
|
6736
|
-
const rec = asRecord
|
|
6982
|
+
const rec = asRecord(item);
|
|
6737
6983
|
const label = String(rec?.label ?? rec?.name ?? rec?.limit_name ?? `Additional ${idx + 1}`);
|
|
6738
6984
|
const win = codexWindow(`additional:${idx}`, label, item);
|
|
6739
6985
|
if (win) windows.push(win);
|
|
@@ -6762,8 +7008,8 @@ function parseCodexTokenCountSnapshot(text) {
|
|
|
6762
7008
|
} catch {
|
|
6763
7009
|
continue;
|
|
6764
7010
|
}
|
|
6765
|
-
const rec = asRecord
|
|
6766
|
-
const payload = asRecord
|
|
7011
|
+
const rec = asRecord(obj);
|
|
7012
|
+
const payload = asRecord(rec?.payload);
|
|
6767
7013
|
if (payload?.type !== "token_count") continue;
|
|
6768
7014
|
const parsed = parseCodexUsageResponse({ rate_limits: payload.rate_limits });
|
|
6769
7015
|
if (!parsed) continue;
|
|
@@ -6940,7 +7186,7 @@ class CodexAdapter {
|
|
|
6940
7186
|
} catch {
|
|
6941
7187
|
return null;
|
|
6942
7188
|
}
|
|
6943
|
-
const tokens = asRecord
|
|
7189
|
+
const tokens = asRecord(asRecord(raw)?.tokens);
|
|
6944
7190
|
const accessToken = typeof tokens?.access_token === "string" ? tokens.access_token : "";
|
|
6945
7191
|
const accountId = typeof tokens?.account_id === "string" ? tokens.account_id : "";
|
|
6946
7192
|
return accessToken && accountId ? { accessToken, accountId } : null;
|
|
@@ -7039,136 +7285,6 @@ class CodexAdapter {
|
|
|
7039
7285
|
return null;
|
|
7040
7286
|
}
|
|
7041
7287
|
}
|
|
7042
|
-
const OPENCODE_QUOTA_INSTALL = "npx @slkiser/opencode-quota@latest init";
|
|
7043
|
-
const OPENCODE_QUOTA_PLUGIN_BIN = join(
|
|
7044
|
-
homedir(),
|
|
7045
|
-
".cache",
|
|
7046
|
-
"opencode",
|
|
7047
|
-
"packages",
|
|
7048
|
-
"@slkiser",
|
|
7049
|
-
"opencode-quota@latest",
|
|
7050
|
-
"node_modules",
|
|
7051
|
-
"@slkiser",
|
|
7052
|
-
"opencode-quota",
|
|
7053
|
-
"dist",
|
|
7054
|
-
"bin",
|
|
7055
|
-
"opencode-quota.js"
|
|
7056
|
-
);
|
|
7057
|
-
function asRecord(value) {
|
|
7058
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7059
|
-
}
|
|
7060
|
-
function numberFrom(value) {
|
|
7061
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
7062
|
-
if (typeof value === "string" && value.trim()) {
|
|
7063
|
-
const n = Number(value);
|
|
7064
|
-
if (Number.isFinite(n)) return n;
|
|
7065
|
-
}
|
|
7066
|
-
return null;
|
|
7067
|
-
}
|
|
7068
|
-
function resetFrom(value) {
|
|
7069
|
-
if (typeof value === "string" && value.trim()) {
|
|
7070
|
-
const n2 = Number(value);
|
|
7071
|
-
if (Number.isFinite(n2)) return new Date(n2 * 1e3).toISOString();
|
|
7072
|
-
const d = new Date(value);
|
|
7073
|
-
return Number.isFinite(d.getTime()) ? d.toISOString() : null;
|
|
7074
|
-
}
|
|
7075
|
-
const n = numberFrom(value);
|
|
7076
|
-
return n == null ? null : new Date(n * 1e3).toISOString();
|
|
7077
|
-
}
|
|
7078
|
-
function usageWindowFromRecord(rec, idx) {
|
|
7079
|
-
const label = String(rec.label ?? rec.name ?? rec.title ?? rec.provider ?? `Window ${idx + 1}`);
|
|
7080
|
-
const used = numberFrom(rec.usedPercent) ?? numberFrom(rec.used_percent) ?? numberFrom(rec.percent) ?? numberFrom(rec.utilization);
|
|
7081
|
-
const remaining = numberFrom(rec.remainingPercent) ?? numberFrom(rec.remaining_percent) ?? numberFrom(rec.percentRemaining);
|
|
7082
|
-
const utilization = used ?? (remaining == null ? null : 100 - remaining);
|
|
7083
|
-
if (utilization == null) return null;
|
|
7084
|
-
return {
|
|
7085
|
-
key: String(rec.key ?? rec.id ?? label),
|
|
7086
|
-
label,
|
|
7087
|
-
utilization,
|
|
7088
|
-
resetsAt: resetFrom(rec.resetsAt ?? rec.resets_at ?? rec.resetAt ?? rec.reset_at ?? rec.reset)
|
|
7089
|
-
};
|
|
7090
|
-
}
|
|
7091
|
-
function collectOpencodeQuotaWindows(value, out, seen) {
|
|
7092
|
-
if (!value || seen.has(value)) return;
|
|
7093
|
-
seen.add(value);
|
|
7094
|
-
if (Array.isArray(value)) {
|
|
7095
|
-
for (const item of value) collectOpencodeQuotaWindows(item, out, seen);
|
|
7096
|
-
return;
|
|
7097
|
-
}
|
|
7098
|
-
const rec = asRecord(value);
|
|
7099
|
-
if (!rec) return;
|
|
7100
|
-
const win = usageWindowFromRecord(rec, out.length);
|
|
7101
|
-
if (win) out.push(win);
|
|
7102
|
-
for (const key of ["windows", "quotas", "items", "limits", "entries"]) {
|
|
7103
|
-
if (key in rec) collectOpencodeQuotaWindows(rec[key], out, seen);
|
|
7104
|
-
}
|
|
7105
|
-
for (const key of ["providers", "data"]) {
|
|
7106
|
-
const child = asRecord(rec[key]);
|
|
7107
|
-
if (child) {
|
|
7108
|
-
for (const value2 of Object.values(child)) collectOpencodeQuotaWindows(value2, out, seen);
|
|
7109
|
-
} else if (key in rec) {
|
|
7110
|
-
collectOpencodeQuotaWindows(rec[key], out, seen);
|
|
7111
|
-
}
|
|
7112
|
-
}
|
|
7113
|
-
}
|
|
7114
|
-
function parseOpencodeQuotaOutput(text) {
|
|
7115
|
-
let raw;
|
|
7116
|
-
try {
|
|
7117
|
-
raw = JSON.parse(text);
|
|
7118
|
-
} catch {
|
|
7119
|
-
return parseOpencodeQuotaText(text);
|
|
7120
|
-
}
|
|
7121
|
-
const windows = [];
|
|
7122
|
-
collectOpencodeQuotaWindows(raw, windows, /* @__PURE__ */ new Set());
|
|
7123
|
-
if (windows.length === 0) return null;
|
|
7124
|
-
return {
|
|
7125
|
-
kind: "opencode",
|
|
7126
|
-
status: "available",
|
|
7127
|
-
checkedAt: Date.now(),
|
|
7128
|
-
source: "external-cli",
|
|
7129
|
-
rateLimitsAvailable: true,
|
|
7130
|
-
windows
|
|
7131
|
-
};
|
|
7132
|
-
}
|
|
7133
|
-
function parseOpencodeQuotaText(text) {
|
|
7134
|
-
const windows = [];
|
|
7135
|
-
let provider = "";
|
|
7136
|
-
for (const line of text.split("\n")) {
|
|
7137
|
-
const providerMatch = line.match(/^\s*→\s*\[(.+?)]/);
|
|
7138
|
-
if (providerMatch) {
|
|
7139
|
-
provider = providerMatch[1].trim();
|
|
7140
|
-
continue;
|
|
7141
|
-
}
|
|
7142
|
-
const quotaMatch = line.match(/^\s*(.+?quota)\s+.*?(\d+(?:\.\d+)?)%\s+left/i);
|
|
7143
|
-
if (!quotaMatch) continue;
|
|
7144
|
-
const label = [provider, quotaMatch[1].trim()].filter(Boolean).join(": ");
|
|
7145
|
-
const remaining = Number(quotaMatch[2]);
|
|
7146
|
-
windows.push({
|
|
7147
|
-
key: label || `quota:${windows.length}`,
|
|
7148
|
-
label: label || `Quota ${windows.length + 1}`,
|
|
7149
|
-
utilization: Math.max(0, Math.min(100, 100 - remaining)),
|
|
7150
|
-
resetsAt: null
|
|
7151
|
-
});
|
|
7152
|
-
}
|
|
7153
|
-
if (windows.length === 0) return null;
|
|
7154
|
-
return {
|
|
7155
|
-
kind: "opencode",
|
|
7156
|
-
status: "available",
|
|
7157
|
-
checkedAt: Date.now(),
|
|
7158
|
-
source: "external-cli",
|
|
7159
|
-
rateLimitsAvailable: true,
|
|
7160
|
-
windows
|
|
7161
|
-
};
|
|
7162
|
-
}
|
|
7163
|
-
async function opencodeQuotaCommands() {
|
|
7164
|
-
const commands = [{ cmd: "opencode-quota", args: ["show", "--json"] }];
|
|
7165
|
-
try {
|
|
7166
|
-
await access(OPENCODE_QUOTA_PLUGIN_BIN);
|
|
7167
|
-
commands.push({ cmd: process.execPath, args: [OPENCODE_QUOTA_PLUGIN_BIN, "show", "--json"] });
|
|
7168
|
-
} catch {
|
|
7169
|
-
}
|
|
7170
|
-
return commands;
|
|
7171
|
-
}
|
|
7172
7288
|
function textParts(parts) {
|
|
7173
7289
|
const out = [];
|
|
7174
7290
|
for (const p of parts) {
|
|
@@ -7300,62 +7416,404 @@ class OpenCodeAdapter {
|
|
|
7300
7416
|
}
|
|
7301
7417
|
return out;
|
|
7302
7418
|
}
|
|
7419
|
+
/**
|
|
7420
|
+
* `usageStatus: false` —— opencode 自身没有配额查询接口,早先那条路径是去调
|
|
7421
|
+
* 第三方 opencode-quota 插件;实测装了也拿不到可用数据,只会在空态里留下一行
|
|
7422
|
+
* 「去装插件」的噪音。与其留一个必然失败的探测(每次还要 spawn 一个 10s 超时
|
|
7423
|
+
* 的子进程),不如明确声明不支持:前端据此什么都不显示。
|
|
7424
|
+
*/
|
|
7303
7425
|
capabilities() {
|
|
7304
|
-
return { listSessions: true, getMessages: true, usageStatus:
|
|
7426
|
+
return { listSessions: true, getMessages: true, usageStatus: false };
|
|
7305
7427
|
}
|
|
7306
|
-
async
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
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)
|
|
7324
7642
|
};
|
|
7325
|
-
|
|
7326
|
-
lastError = err2;
|
|
7327
|
-
if (err2.code === "ENOENT") continue;
|
|
7328
|
-
break;
|
|
7643
|
+
return;
|
|
7329
7644
|
}
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
source: "external-cli",
|
|
7339
|
-
installCommand: OPENCODE_QUOTA_INSTALL,
|
|
7340
|
-
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()
|
|
7341
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;
|
|
7342
7660
|
}
|
|
7343
|
-
|
|
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),
|
|
7344
7735
|
kind: this.kind,
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
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 };
|
|
7350
7790
|
}
|
|
7351
7791
|
async dispose() {
|
|
7352
|
-
|
|
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;
|
|
7353
7804
|
try {
|
|
7354
|
-
|
|
7355
|
-
server.close();
|
|
7805
|
+
infos = await SessionManager$1.list(this.cwd, this.opts.sessionDir);
|
|
7356
7806
|
} catch {
|
|
7807
|
+
return [];
|
|
7357
7808
|
}
|
|
7358
|
-
|
|
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;
|
|
7359
7817
|
}
|
|
7360
7818
|
}
|
|
7361
7819
|
function createAdapter(kind, cwd) {
|
|
@@ -7364,6 +7822,8 @@ function createAdapter(kind, cwd) {
|
|
|
7364
7822
|
return new CodexAdapter(cwd);
|
|
7365
7823
|
case "opencode":
|
|
7366
7824
|
return new OpenCodeAdapter(cwd);
|
|
7825
|
+
case "pi":
|
|
7826
|
+
return new PiAdapter(cwd);
|
|
7367
7827
|
case "claude":
|
|
7368
7828
|
default:
|
|
7369
7829
|
return new ClaudeAdapter(cwd);
|
|
@@ -7505,7 +7965,7 @@ class SessionManager {
|
|
|
7505
7965
|
const indexed = await this.store.list();
|
|
7506
7966
|
const byId = new Map(indexed.map((s) => [s.id, s]));
|
|
7507
7967
|
const nativeIds = /* @__PURE__ */ new Set();
|
|
7508
|
-
for (const kind of ["claude", "codex", "opencode"]) {
|
|
7968
|
+
for (const kind of ["claude", "codex", "opencode", "pi"]) {
|
|
7509
7969
|
const adapter = this.adapters.get(kind);
|
|
7510
7970
|
if (!adapter.capabilities().listSessions) continue;
|
|
7511
7971
|
try {
|
|
@@ -7944,6 +8404,22 @@ class ProjectRegistry {
|
|
|
7944
8404
|
const normalized = await prepareProjectDir(absPath);
|
|
7945
8405
|
return await addProject(normalized, this.globalConfigPath);
|
|
7946
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
|
+
}
|
|
7947
8423
|
async remove(id) {
|
|
7948
8424
|
await this.release(id).catch(() => {
|
|
7949
8425
|
});
|
|
@@ -8054,7 +8530,9 @@ class ProjectRegistry {
|
|
|
8054
8530
|
}
|
|
8055
8531
|
function resolveProjectAgentKind(projectAgent, globalDefault) {
|
|
8056
8532
|
if (projectAgent.kind === "inherit") return globalDefault;
|
|
8057
|
-
if (projectAgent.kind === "codex" || projectAgent.kind === "opencode"
|
|
8533
|
+
if (projectAgent.kind === "codex" || projectAgent.kind === "opencode" || projectAgent.kind === "pi") {
|
|
8534
|
+
return projectAgent.kind;
|
|
8535
|
+
}
|
|
8058
8536
|
return "claude";
|
|
8059
8537
|
}
|
|
8060
8538
|
function basename(p) {
|
|
@@ -8970,35 +9448,33 @@ async function defaultPrompt(question) {
|
|
|
8970
9448
|
}
|
|
8971
9449
|
}
|
|
8972
9450
|
async function runAdd(opts) {
|
|
8973
|
-
const
|
|
8974
|
-
const
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
gitInitialized = true;
|
|
8988
|
-
} 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) {
|
|
8989
9465
|
throw new AddGitAbortedError(
|
|
8990
|
-
`yorz add:
|
|
9466
|
+
`yorz add: target directory is not a git repository; pass --yes to auto-run git init in non-interactive mode`
|
|
8991
9467
|
);
|
|
8992
9468
|
}
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
)
|
|
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
|
+
}
|
|
8997
9475
|
}
|
|
9476
|
+
return await addProjectWithGit({ ...base, gitInit: true });
|
|
8998
9477
|
}
|
|
8999
|
-
const gitignore = await ensureTmpIgnored(abs);
|
|
9000
|
-
const { entry, created } = await addProject(abs, opts.globalConfigPath);
|
|
9001
|
-
return { entry, created, gitInitialized, gitignore };
|
|
9002
9478
|
}
|
|
9003
9479
|
const md = new MarkdownIt("commonmark", { html: false, linkify: false });
|
|
9004
9480
|
function buildContext(raw, kind, filePath) {
|