loopat 0.1.52 → 0.1.54
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/package.json +1 -1
- package/server/src/git-host.ts +1 -0
- package/server/src/github.ts +101 -2
- package/server/src/index.ts +88 -3
- package/server/src/loops.ts +29 -1
- package/server/src/paths.ts +2 -0
- package/server/src/podman.ts +5 -2
- package/server/src/session.ts +25 -0
- package/web/dist/assets/{CodeEditor-JV36Z3V5.js → CodeEditor-ROIg2ylA.js} +1 -1
- package/web/dist/assets/Editor-BN7Q5g4N.js +1 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/Markdown-DvFvJx3d.js +3 -0
- package/web/dist/assets/MilkdownEditor-DsqsnlSK.js +123 -0
- package/web/dist/assets/{Terminal-Vi3Ufhgi.js → Terminal-DQxchB0u.js} +2 -2
- package/web/dist/assets/arc-JSCNOeGh.js +1 -0
- package/web/dist/assets/architecture-7EHR7CIX-C6TANlSv.js +1 -0
- package/web/dist/assets/architectureDiagram-3BPJPVTR-BiL4yrd-.js +36 -0
- package/web/dist/assets/array-CwG8vNfn.js +1 -0
- package/web/dist/assets/blockDiagram-GPEHLZMM-BlIpPvY3.js +132 -0
- package/web/dist/assets/c4Diagram-AAUBKEIU-CzsNYAS-.js +10 -0
- package/web/dist/assets/channel-BadMOAUe.js +1 -0
- package/web/dist/assets/chunk-2J33WTMH-jzikTe5Z.js +1 -0
- package/web/dist/assets/chunk-3OPIFGDE-CPRLtc-k.js +62 -0
- package/web/dist/assets/chunk-4BX2VUAB-C4VZq7p9.js +1 -0
- package/web/dist/assets/chunk-4EGX6M5U-BgefWETD.js +1 -0
- package/web/dist/assets/chunk-55IACEB6-DUDG-guT.js +1 -0
- package/web/dist/assets/chunk-5DO6E6H7-CXaGVrrt.js +1 -0
- package/web/dist/assets/chunk-5ZQYHXKU-DAcci771.js +2 -0
- package/web/dist/assets/chunk-62oNxeRG.js +1 -0
- package/web/dist/assets/chunk-727SXJPM-DB5VAPjb.js +206 -0
- package/web/dist/assets/chunk-AQP2D5EJ-zKFdEFYM.js +231 -0
- package/web/dist/assets/chunk-BR22UD5L-D9r29Y8a.js +1 -0
- package/web/dist/assets/chunk-BSJP7CBP-DF8jhsUx.js +1 -0
- package/web/dist/assets/chunk-CSCIHK7Q-KZCheCnJ.js +125 -0
- package/web/dist/assets/chunk-FHYWG6QK-5gBLDG6p.js +1 -0
- package/web/dist/assets/chunk-FMBD7UC4-BruiH2nG.js +15 -0
- package/web/dist/assets/chunk-KSCS5N6A-Cc-HIfIs.js +10 -0
- package/web/dist/assets/chunk-L5ZTLDWV-4FtjsXMJ.js +1 -0
- package/web/dist/assets/chunk-MPE355IW-RWzERmdt.js +1 -0
- package/web/dist/assets/chunk-MZUSXYTE-Xjzy1goZ.js +1 -0
- package/web/dist/assets/chunk-N66VUXT2-7wnXYZTH.js +1 -0
- package/web/dist/assets/chunk-ND2GUHAM-CLZCc4eF.js +1 -0
- package/web/dist/assets/chunk-NNHCCRGN-DHWtWlDY.js +159 -0
- package/web/dist/assets/chunk-NZK2D7GU-OE2TAhC6.js +1 -0
- package/web/dist/assets/chunk-O5CBEL6O-Bg6sZtyT.js +70 -0
- package/web/dist/assets/chunk-PUPMXCY4-C1dr9z9P.js +1 -0
- package/web/dist/assets/chunk-QZHKN3VN-DxLqo3gI.js +1 -0
- package/web/dist/assets/chunk-UIBZB4QT-DBYf6fSv.js +1 -0
- package/web/dist/assets/chunk-WCWK7LTN-DpJdbsk3.js +1 -0
- package/web/dist/assets/classDiagram-4FO5ZUOK-DGIJJBY5.js +1 -0
- package/web/dist/assets/classDiagram-v2-Q7XG4LA2-BdsZBmZ_.js +1 -0
- package/web/dist/assets/cose-bilkent-S5V4N54A-CWucooZw.js +1 -0
- package/web/dist/assets/cytoscape.esm-fMM2C9dj.js +321 -0
- package/web/dist/assets/dagre-BM42HDAG-CZ5AtThh.js +4 -0
- package/web/dist/assets/dagre-DtjYhTY3.js +1 -0
- package/web/dist/assets/defaultLocale-Dda4OpKy.js +1 -0
- package/web/dist/assets/diagram-2AECGRRQ-DE80i5fc.js +43 -0
- package/web/dist/assets/diagram-5GNKFQAL-Egr34zuZ.js +10 -0
- package/web/dist/assets/diagram-KO2AKTUF-CvajPPQL.js +3 -0
- package/web/dist/assets/diagram-LMA3HP47-f9ekzEBK.js +24 -0
- package/web/dist/assets/diagram-OG6HWLK6-CJhcoTKU.js +24 -0
- package/web/dist/assets/dist-Qo4xPaJL.js +1 -0
- package/web/dist/assets/erDiagram-TEJ5UH35-CYNgf9or.js +85 -0
- package/web/dist/assets/eventmodeling-FCH6USID-CFdOE-LK.js +1 -0
- package/web/dist/assets/flowDiagram-I6XJVG4X-BXTQ7QRg.js +162 -0
- package/web/dist/assets/ganttDiagram-6RSMTGT7-9CPIXrRu.js +292 -0
- package/web/dist/assets/gitGraph-WXDBUCRP-CQyqjuxy.js +1 -0
- package/web/dist/assets/gitGraphDiagram-PVQCEYII-D8BksuAg.js +106 -0
- package/web/dist/assets/graphlib-CVvj2XkR.js +1 -0
- package/web/dist/assets/index-BbAx7IhR.js +452 -0
- package/web/dist/assets/index-COolIbl3.css +1 -0
- package/web/dist/assets/info-J43DQDTF-_TOGLt_U.js +1 -0
- package/web/dist/assets/infoDiagram-5YYISTIA-Be0OcURm.js +2 -0
- package/web/dist/assets/init-D6KNwrax.js +1 -0
- package/web/dist/assets/isObject-6BN_nwjc.js +1 -0
- package/web/dist/assets/ishikawaDiagram-YF4QCWOH-BFyPbPqD.js +70 -0
- package/web/dist/assets/journeyDiagram-JHISSGLW-DR1RevaB.js +139 -0
- package/web/dist/assets/jsx-runtime-ZP0XCtgh.js +1 -0
- package/web/dist/assets/kanban-definition-UN3LZRKU-C5BAKIQX.js +89 -0
- package/web/dist/assets/katex-SDxE4_FN.js +257 -0
- package/web/dist/assets/{lib-B8L80SIn.js → lib-B1ZjR0ui.js} +3 -3
- package/web/dist/assets/line-C1hQ6Yic.js +1 -0
- package/web/dist/assets/linear-BTFE1p_D.js +1 -0
- package/web/dist/assets/mermaid-parser.core-B4wi-4x8.js +4 -0
- package/web/dist/assets/mindmap-definition-RKZ34NQL-DqgCcW-r.js +96 -0
- package/web/dist/assets/now-CfACN9Ld.js +1 -0
- package/web/dist/assets/ordinal-SeL1F7Yj.js +1 -0
- package/web/dist/assets/packet-YPE3B663-CsilPsGk.js +1 -0
- package/web/dist/assets/path-DNPd7Py7.js +1 -0
- package/web/dist/assets/pie-LRSECV5Y-B-D_L-Uc.js +1 -0
- package/web/dist/assets/pieDiagram-4H26LBE5-BP9tC1HT.js +30 -0
- package/web/dist/assets/quadrantDiagram-W4KKPZXB-BBjjuCms.js +7 -0
- package/web/dist/assets/radar-GUYGQ44K-DHD2KQPQ.js +1 -0
- package/web/dist/assets/requirementDiagram-4Y6WPE33-B_O_cEpd.js +84 -0
- package/web/dist/assets/rough.esm-DEh6Frf9.js +1 -0
- package/web/dist/assets/sankeyDiagram-5OEKKPKP-Cv_l_I0A.js +40 -0
- package/web/dist/assets/sequenceDiagram-3UESZ5HK-DBBZS2WD.js +162 -0
- package/web/dist/assets/src-BTV4tisa.js +1 -0
- package/web/dist/assets/stateDiagram-AJRCARHV-BaH2s_og.js +1 -0
- package/web/dist/assets/stateDiagram-v2-BHNVJYJU-Ryor5k_i.js +1 -0
- package/web/dist/assets/timeline-definition-PNZ67QCA-BNvy_0DN.js +120 -0
- package/web/dist/assets/treeView-BLDUP644-C8olDR1G.js +1 -0
- package/web/dist/assets/treemap-LRROVOQU-BIYIGzet.js +1 -0
- package/web/dist/assets/vennDiagram-CIIHVFJN-D5RSWRYm.js +34 -0
- package/web/dist/assets/viz-BR7pXPfi.js +7215 -0
- package/web/dist/assets/wardley-L42UT6IY-CfnZRM3V.js +1 -0
- package/web/dist/assets/wardleyDiagram-YWT4CUSO-5PntQ1A_.js +78 -0
- package/web/dist/assets/xychartDiagram-2RQKCTM6-B_1m_ITe.js +7 -0
- package/web/dist/index.html +23 -4
- package/web/dist/manifest.json +18 -0
- package/web/dist/assets/Editor-9lLPQzvP.js +0 -1
- package/web/dist/assets/Markdown-D5ElKjTG.js +0 -5
- package/web/dist/assets/MilkdownEditor-t_CG4MJj.js +0 -123
- package/web/dist/assets/index-C1GIAoyn.js +0 -145
- package/web/dist/assets/index-D-WGDCY6.css +0 -1
- package/web/dist/assets/jsx-runtime-DAYmCNe8.js +0 -1
- /package/web/dist/assets/{w3c-keyname-DXh_HxYD.js → w3c-keyname-BoNxyEq0.js} +0 -0
package/package.json
CHANGED
package/server/src/git-host.ts
CHANGED
package/server/src/github.ts
CHANGED
|
@@ -48,6 +48,48 @@ function fail(op: string, r: { status: number; data: any }): never {
|
|
|
48
48
|
throw new Error(`github ${op} failed (${r.status})${msg ? `: ${msg}` : ""}`)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* OAuth Device Flow — login on localhost/cloud/any deployment without a per-user
|
|
53
|
+
* app or a callback URL. One public client_id (NOT a secret — like gh CLI) is
|
|
54
|
+
* baked in, overridable via LOOPAT_GITHUB_CLIENT_ID for self-hosters with their
|
|
55
|
+
* own app. Scope: `repo` only — list/create/clone/push private repos; git is
|
|
56
|
+
* over https-token so no ssh-key scope is needed. The token lands in the vault;
|
|
57
|
+
* from there onboarding is identical to a pasted PAT.
|
|
58
|
+
*/
|
|
59
|
+
export const GITHUB_DEVICE_CLIENT_ID = process.env.LOOPAT_GITHUB_CLIENT_ID || "Ov23lijopFG81cPZXsI2"
|
|
60
|
+
export const GITHUB_DEVICE_SCOPE = "repo"
|
|
61
|
+
|
|
62
|
+
/** Step 1: request a device code. Returns the code to show + how long to poll. */
|
|
63
|
+
export async function requestDeviceCode(): Promise<{
|
|
64
|
+
device_code: string; user_code: string; verification_uri: string; interval: number; expires_in: number
|
|
65
|
+
}> {
|
|
66
|
+
const res = await fetch("https://github.com/login/device/code", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({ client_id: GITHUB_DEVICE_CLIENT_ID, scope: GITHUB_DEVICE_SCOPE }),
|
|
70
|
+
})
|
|
71
|
+
const d = await res.json().catch(() => null)
|
|
72
|
+
if (!res.ok || !d?.device_code) throw new Error(`github device code request failed (${res.status})`)
|
|
73
|
+
return { device_code: d.device_code, user_code: d.user_code, verification_uri: d.verification_uri, interval: d.interval ?? 5, expires_in: d.expires_in ?? 900 }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Step 2: poll once for the token. `pending` until the user approves; `slow_down`
|
|
77
|
+
* asks us to back off; any other error is fatal. Returns the token on success. */
|
|
78
|
+
export async function pollDeviceToken(deviceCode: string): Promise<
|
|
79
|
+
{ status: "ok"; token: string } | { status: "pending" } | { status: "slow_down" } | { status: "error"; error: string }
|
|
80
|
+
> {
|
|
81
|
+
const res = await fetch("https://github.com/login/oauth/access_token", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
84
|
+
body: JSON.stringify({ client_id: GITHUB_DEVICE_CLIENT_ID, device_code: deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }),
|
|
85
|
+
})
|
|
86
|
+
const d = await res.json().catch(() => null)
|
|
87
|
+
if (d?.access_token) return { status: "ok", token: d.access_token }
|
|
88
|
+
if (d?.error === "authorization_pending") return { status: "pending" }
|
|
89
|
+
if (d?.error === "slow_down") return { status: "slow_down" }
|
|
90
|
+
return { status: "error", error: d?.error_description ?? d?.error ?? `poll failed (${res.status})` }
|
|
91
|
+
}
|
|
92
|
+
|
|
51
93
|
/** Capability 1 — authenticate: turn a token into the user's login. */
|
|
52
94
|
export async function getViewer(c: GithubClient): Promise<{ login: string; id: number }> {
|
|
53
95
|
const r = await gh(c, "GET", "/user")
|
|
@@ -133,13 +175,32 @@ export async function ensureCollaborator(
|
|
|
133
175
|
export const githubProvider: GitHostProvider = {
|
|
134
176
|
id: "github",
|
|
135
177
|
label: "GitHub",
|
|
136
|
-
|
|
178
|
+
// https-token: the device-flow OAuth token (scope `repo`) does both API and
|
|
179
|
+
// git over https://<login>:<token>@github.com/… — one credential for every
|
|
180
|
+
// repo, no per-repo deploy key. (OAuth-App tokens don't expire by default.)
|
|
181
|
+
gitAuthMode: "https-token",
|
|
182
|
+
// Onboarding step 1: no personal repo yet → device-flow login. loopat drives
|
|
183
|
+
// the device dance (start/poll) and, on token, provisions the repo. Once the
|
|
184
|
+
// repo is imported there's nothing left to gate on, so we're done.
|
|
185
|
+
async onboarding(ctx) {
|
|
186
|
+
if (!ctx.personalRepoImported) {
|
|
187
|
+
return {
|
|
188
|
+
done: false,
|
|
189
|
+
show: {
|
|
190
|
+
kind: "device",
|
|
191
|
+
title: "用 GitHub 登录",
|
|
192
|
+
description: "loopat 不存你的数据 —— 登录后会自动建一个私有个人仓库,你的 key / ssh / memory 都加密存在里面。",
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { done: true }
|
|
197
|
+
},
|
|
137
198
|
async authenticate(cred) {
|
|
138
199
|
return await getViewer(githubClient(cred.token, cred.baseUrl))
|
|
139
200
|
},
|
|
140
201
|
async ensureRepo(cred, name, opts) {
|
|
141
202
|
const r = await ensureUserRepo(githubClient(cred.token, cred.baseUrl), name, { private: opts?.private })
|
|
142
|
-
return { url: r.
|
|
203
|
+
return { url: r.httpUrl, created: r.created }
|
|
143
204
|
},
|
|
144
205
|
async registerDeployKey(cred, repo, title, pubkey, readOnly) {
|
|
145
206
|
await ensureDeployKey(githubClient(cred.token, cred.baseUrl), repo.owner, repo.name, title, pubkey, readOnly)
|
|
@@ -147,6 +208,12 @@ export const githubProvider: GitHostProvider = {
|
|
|
147
208
|
async registerUserKey(cred, title, pubkey) {
|
|
148
209
|
await ensureUserKey(githubClient(cred.token, cred.baseUrl), title, pubkey)
|
|
149
210
|
},
|
|
211
|
+
async listRepos(cred) {
|
|
212
|
+
const c = githubClient(cred.token, cred.baseUrl)
|
|
213
|
+
const r = await gh(c, "GET", "/user/repos?per_page=100&affiliation=owner&sort=updated")
|
|
214
|
+
const items = Array.isArray(r.data) ? r.data : []
|
|
215
|
+
return items.map((p: any) => ({ name: p.name, path: p.full_name }))
|
|
216
|
+
},
|
|
150
217
|
async grantAccess(cred, repo, login, level) {
|
|
151
218
|
await ensureCollaborator(
|
|
152
219
|
githubClient(cred.token, cred.baseUrl),
|
|
@@ -156,6 +223,38 @@ export const githubProvider: GitHostProvider = {
|
|
|
156
223
|
level === "write" ? "push" : "pull",
|
|
157
224
|
)
|
|
158
225
|
},
|
|
226
|
+
|
|
227
|
+
// Seed a freshly-created personal repo: one provider default (key goes in the
|
|
228
|
+
// vault later) and an ssh keypair (standard id_ed25519, git-crypt-encrypted)
|
|
229
|
+
// so loops can clone repos with zero config.
|
|
230
|
+
async seedDefaults(ctx) {
|
|
231
|
+
const fs = await import("node:fs/promises")
|
|
232
|
+
const path = await import("node:path")
|
|
233
|
+
const { execFile } = await import("node:child_process")
|
|
234
|
+
const { promisify } = await import("node:util")
|
|
235
|
+
const run = promisify(execFile)
|
|
236
|
+
|
|
237
|
+
const config = {
|
|
238
|
+
providers: {
|
|
239
|
+
default: "anthropic",
|
|
240
|
+
anthropic: {
|
|
241
|
+
model: "claude-opus-4-7",
|
|
242
|
+
baseUrl: "https://api.anthropic.com",
|
|
243
|
+
apiKey: "${ANTHROPIC_API_KEY}",
|
|
244
|
+
maxContextTokens: 200000,
|
|
245
|
+
models: [{ id: "claude-opus-4-7", enabled: true }],
|
|
246
|
+
enabled: true,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
await fs.mkdir(path.join(ctx.repoDir, ".loopat"), { recursive: true })
|
|
251
|
+
await fs.writeFile(path.join(ctx.repoDir, ".loopat", "config.json"), JSON.stringify(config, null, 2) + "\n")
|
|
252
|
+
|
|
253
|
+
const sshDir = path.join(ctx.vaultDir, "mounts", "home", ".ssh")
|
|
254
|
+
await fs.mkdir(sshDir, { recursive: true })
|
|
255
|
+
await run("ssh-keygen", ["-t", "ed25519", "-N", "", "-C", `loopat:${ctx.login}`, "-f", path.join(sshDir, "id_ed25519")])
|
|
256
|
+
await fs.writeFile(path.join(sshDir, "config"), "Host *\n StrictHostKeyChecking accept-new\n")
|
|
257
|
+
},
|
|
159
258
|
}
|
|
160
259
|
|
|
161
260
|
registerProvider(githubProvider)
|
package/server/src/index.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { Hono } from "hono"
|
|
|
2
2
|
import { cors } from "hono/cors"
|
|
3
3
|
import { createBunWebSocket } from "hono/bun"
|
|
4
4
|
import { existsSync } from "node:fs"
|
|
5
|
-
import { execFile, execFileSync } from "node:child_process"
|
|
5
|
+
import { execFile, execFileSync, spawn } from "node:child_process"
|
|
6
6
|
import { promisify } from "node:util"
|
|
7
|
-
import { listLoops, createLoop, getLoop, loopExists, patchLoopMeta, backfillAllMounts, ensureWorkspaceDirs, provisionUserPersonal, importPersonalFromRepo, setupPersonalViaProvider, listPersonalReposViaProvider, authenticateViaProvider, isPersonalFresh, ensureUiNotesWorktree, syncUiNotes, ffUpdateUiNotes, discardUiNotes, notesBehind, inspectPersonalDirty, syncPersonalToRemote, deletePersonalVault, pullPersonalFromRemote, pushPersonalToRemote, ensureContextMounts, effectiveDriver, isDriver, distillLoop, inspectRepoSync, pullRepoFromRemote, pushRepoToRemote, listVaultPublicKeys, userOnboarding, submitOnboarding } from "./loops"
|
|
8
|
-
import { getEphemeralHostPort, probePodman, stopAllWorkspaceContainers, ensureServeContainer, ensurePortProxyContainer, ensureSandboxImage } from "./podman"
|
|
7
|
+
import { listLoops, createLoop, getLoop, loopExists, patchLoopMeta, backfillAllMounts, ensureWorkspaceDirs, provisionUserPersonal, importPersonalFromRepo, setupPersonalViaProvider, listPersonalReposViaProvider, authenticateViaProvider, isPersonalFresh, ensureUiNotesWorktree, syncUiNotes, ffUpdateUiNotes, discardUiNotes, notesBehind, inspectPersonalDirty, syncPersonalToRemote, deletePersonalVault, pullPersonalFromRemote, pushPersonalToRemote, ensureContextMounts, effectiveDriver, isDriver, distillLoop, inspectRepoSync, pullRepoFromRemote, pushRepoToRemote, listVaultPublicKeys, userOnboarding, submitOnboarding, dismissOnboarding, deviceFlowStart, deviceFlowPoll } from "./loops"
|
|
8
|
+
import { getEphemeralHostPort, probePodman, stopAllWorkspaceContainers, ensureServeContainer, ensurePortProxyContainer, ensureSandboxImage, buildPodmanExecArgs, ensureContainer, containerName, V_LOOP_WORKDIR } from "./podman"
|
|
9
9
|
import { startMcpAuth, completeMcpAuth, probeOAuthSupport, evictOAuthProbe, parseBearerEnvName, mcpRequiredEnvs, parseTemplateVars, type OAuthSupport } from "./mcp-oauth"
|
|
10
10
|
import { DEFAULT_VAULT, loadVaultEnvs } from "./vaults"
|
|
11
11
|
import {
|
|
@@ -1295,6 +1295,33 @@ app.post("/api/onboarding/submit", requireAuth, async (c) => {
|
|
|
1295
1295
|
return c.json(r.next)
|
|
1296
1296
|
})
|
|
1297
1297
|
|
|
1298
|
+
// Dismiss the onboarding gate. When the user clicks "skip / don't show again",
|
|
1299
|
+
// write a marker so `userOnboarding` returns `done` without running any provider
|
|
1300
|
+
// logic. The gate stays cleared until the marker is removed.
|
|
1301
|
+
app.post("/api/onboarding/done", requireAuth, async (c) => {
|
|
1302
|
+
const userId = c.get("userId") as string
|
|
1303
|
+
await dismissOnboarding(userId)
|
|
1304
|
+
return c.json({ ok: true })
|
|
1305
|
+
})
|
|
1306
|
+
|
|
1307
|
+
// GitHub device-flow login — start: returns the user code + verification URL.
|
|
1308
|
+
app.post("/api/onboarding/device/start", requireAuth, async (c) => {
|
|
1309
|
+
try {
|
|
1310
|
+
return c.json(await deviceFlowStart())
|
|
1311
|
+
} catch (e: any) {
|
|
1312
|
+
return c.json({ error: e?.message ?? "device start failed" }, 502)
|
|
1313
|
+
}
|
|
1314
|
+
})
|
|
1315
|
+
|
|
1316
|
+
// Poll: pending until the user approves; on token, hands the token back so the
|
|
1317
|
+
// repo picker takes over. Frontend re-polls on `pending`/`slow_down`.
|
|
1318
|
+
app.post("/api/onboarding/device/poll", requireAuth, async (c) => {
|
|
1319
|
+
const body = await c.req.json().catch(() => ({}))
|
|
1320
|
+
const deviceCode = typeof body?.device_code === "string" ? body.device_code : ""
|
|
1321
|
+
if (!deviceCode) return c.json({ error: "device_code required" }, 400)
|
|
1322
|
+
return c.json(await deviceFlowPoll(deviceCode))
|
|
1323
|
+
})
|
|
1324
|
+
|
|
1298
1325
|
// Export the user's git-crypt key (base64). Behind a fresh password check
|
|
1299
1326
|
// to prevent walk-up attacks on an unattended browser. The key decrypts
|
|
1300
1327
|
// .loopat/vaults/** on any host that holds it, so we don't want a stolen
|
|
@@ -3130,6 +3157,8 @@ app.get(
|
|
|
3130
3157
|
session.clear(userId ?? "anon")
|
|
3131
3158
|
} else if (msg?.type === "interrupt") {
|
|
3132
3159
|
session.interrupt()
|
|
3160
|
+
} else if (msg?.type === "background_tasks") {
|
|
3161
|
+
session.backgroundTasks().catch(() => {})
|
|
3133
3162
|
} else if (msg?.type === "queue_clear") {
|
|
3134
3163
|
session.clearQueue()
|
|
3135
3164
|
} else if (msg?.type === "queue_remove") {
|
|
@@ -3197,6 +3226,62 @@ app.get(
|
|
|
3197
3226
|
}
|
|
3198
3227
|
} catch {}
|
|
3199
3228
|
}
|
|
3229
|
+
} else if (msg?.type === "shell" && typeof msg.command === "string" && typeof msg.id === "string") {
|
|
3230
|
+
// One-shot shell command in the sandbox (CC "!" bang mode).
|
|
3231
|
+
// Runs via podman exec with a 30s timeout; sends back stdout/stderr.
|
|
3232
|
+
const cmd = msg.command.trim()
|
|
3233
|
+
if (cmd.length > 4096) {
|
|
3234
|
+
try { ws.send(JSON.stringify({ type: "shell_result", id: msg.id, error: "command too long" })) } catch {}
|
|
3235
|
+
return
|
|
3236
|
+
}
|
|
3237
|
+
if (!meta || (userId && !isDriver(meta, userId))) {
|
|
3238
|
+
try { ws.send(JSON.stringify({ type: "shell_result", id: msg.id, error: "only the driver can run shell commands" })) } catch {}
|
|
3239
|
+
return
|
|
3240
|
+
}
|
|
3241
|
+
if (meta.archived) {
|
|
3242
|
+
try { ws.send(JSON.stringify({ type: "shell_result", id: msg.id, error: "loop is archived" })) } catch {}
|
|
3243
|
+
return
|
|
3244
|
+
}
|
|
3245
|
+
;(async () => {
|
|
3246
|
+
try {
|
|
3247
|
+
await ensureContainer({ loopId: id, createdBy: meta.createdBy })
|
|
3248
|
+
const execArgs = ["exec", "--workdir", V_LOOP_WORKDIR(id), containerName(id), "bash", "-c", cmd]
|
|
3249
|
+
const child = spawn("podman", execArgs, { stdio: ["ignore", "pipe", "pipe"] })
|
|
3250
|
+
const chunks: Buffer[] = []
|
|
3251
|
+
const errChunks: Buffer[] = []
|
|
3252
|
+
child.stdout.on("data", (d: Buffer) => chunks.push(d))
|
|
3253
|
+
child.stderr.on("data", (d: Buffer) => errChunks.push(d))
|
|
3254
|
+
const timedOut = await new Promise<boolean>((resolve) => {
|
|
3255
|
+
const t = setTimeout(() => { child.kill(); resolve(true) }, 30_000)
|
|
3256
|
+
child.on("close", () => { clearTimeout(t); resolve(false) })
|
|
3257
|
+
})
|
|
3258
|
+
const out = Buffer.concat(chunks).toString("utf8").replace(/\r\n/g, "\n")
|
|
3259
|
+
const err = Buffer.concat(errChunks).toString("utf8").replace(/\r\n/g, "\n")
|
|
3260
|
+
const now = Date.now()
|
|
3261
|
+
if (timedOut) {
|
|
3262
|
+
try { ws.send(JSON.stringify({ type: "shell_result", id: msg.id, _cmd: cmd, stdout: out, stderr: err, exitCode: null, error: "timed out (30s)" })) } catch {}
|
|
3263
|
+
} else {
|
|
3264
|
+
const exitCode = child.exitCode ?? 1
|
|
3265
|
+
try { ws.send(JSON.stringify({ type: "shell_result", id: msg.id, _cmd: cmd, stdout: out, stderr: err, exitCode })) } catch {}
|
|
3266
|
+
// Inject into session history so shell output survives refresh
|
|
3267
|
+
// and replays for new subscribers. Use same shape as WS messages.
|
|
3268
|
+
const outText = [out, err].filter(Boolean).join("\n").trim() || "bash completed with no output"
|
|
3269
|
+
const suffix = exitCode !== 0 ? "\n[exit " + exitCode + "]" : ""
|
|
3270
|
+
session.injectHistory({
|
|
3271
|
+
type: "user",
|
|
3272
|
+
message: { role: "user", content: [{ type: "text", text: "$ " + cmd }] },
|
|
3273
|
+
uuid: msg.id,
|
|
3274
|
+
})
|
|
3275
|
+
session.injectHistory({
|
|
3276
|
+
type: "assistant",
|
|
3277
|
+
message: { role: "assistant", content: [{ type: "text", text: "```bash\n" + outText + suffix + "\n```" }] },
|
|
3278
|
+
uuid: "sh_" + msg.id,
|
|
3279
|
+
})
|
|
3280
|
+
}
|
|
3281
|
+
} catch (e: any) {
|
|
3282
|
+
try { ws.send(JSON.stringify({ type: "shell_result", id: msg.id, stdout: "", stderr: "", exitCode: 1, error: e?.message ?? String(e) })) } catch {}
|
|
3283
|
+
}
|
|
3284
|
+
})()
|
|
3200
3285
|
}
|
|
3201
3286
|
} catch (e) {
|
|
3202
3287
|
console.error("ws message parse error", e)
|
package/server/src/loops.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { join, dirname } from "node:path"
|
|
1
2
|
import { chmod, copyFile, mkdir, mkdtemp, readdir, readFile, rename, writeFile, stat, symlink, lstat, rm } from "node:fs/promises"
|
|
2
3
|
import { randomUUID } from "node:crypto"
|
|
3
4
|
import { execFile } from "node:child_process"
|
|
4
5
|
import { promisify } from "node:util"
|
|
5
6
|
import { existsSync, chmodSync } from "node:fs"
|
|
6
|
-
import { join } from "node:path"
|
|
7
7
|
import { tmpdir } from "node:os"
|
|
8
8
|
import {
|
|
9
9
|
loopsDir,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
workspaceMemoryDir,
|
|
37
37
|
hostDeployKeyPath,
|
|
38
38
|
personalGitCryptKeyPath,
|
|
39
|
+
onboardingDismissedPath,
|
|
39
40
|
loopHistoryPath,
|
|
40
41
|
loopChatHistoryPath,
|
|
41
42
|
loopKindClaudePath,
|
|
@@ -719,6 +720,8 @@ export type OnboardingStatus = {
|
|
|
719
720
|
/** Resolve the active provider's current onboarding view (done, or next form).
|
|
720
721
|
* The provider owns the whole flow; loopat just feeds it context. */
|
|
721
722
|
async function onboardingView(userId: string, vault: string): Promise<OnboardingStatus> {
|
|
723
|
+
// User dismissed the onboarding gate → skip all provider checks.
|
|
724
|
+
if (existsSync(onboardingDismissedPath(userId))) return { gated: false, done: true }
|
|
722
725
|
const provider = await resolveProvider()
|
|
723
726
|
if (!provider?.onboarding) return { gated: false, done: true }
|
|
724
727
|
const personalRepoImported = !(await isPersonalFresh(userId))
|
|
@@ -740,6 +743,14 @@ export async function userOnboarding(userId: string, vault: string = "default"):
|
|
|
740
743
|
return onboardingView(userId, vault)
|
|
741
744
|
}
|
|
742
745
|
|
|
746
|
+
/** Dismiss the onboarding gate — write a marker that `onboardingView` checks
|
|
747
|
+
* before running any provider logic. Idempotent; the gate stays cleared. */
|
|
748
|
+
export async function dismissOnboarding(userId: string): Promise<void> {
|
|
749
|
+
const p = onboardingDismissedPath(userId)
|
|
750
|
+
try { await mkdir(dirname(p), { recursive: true }) } catch {}
|
|
751
|
+
await writeFile(p, `${new Date().toISOString()}\n`)
|
|
752
|
+
}
|
|
753
|
+
|
|
743
754
|
/**
|
|
744
755
|
* Run one onboarding form submission. Fetches the provider's CURRENT form (to
|
|
745
756
|
* learn each field's action), then executes the submitted values:
|
|
@@ -793,6 +804,23 @@ export async function submitOnboarding(
|
|
|
793
804
|
return { ok: true, next: await onboardingView(userId, vault) }
|
|
794
805
|
}
|
|
795
806
|
|
|
807
|
+
/** Start a GitHub device-flow login: returns the code to show + poll interval. */
|
|
808
|
+
export async function deviceFlowStart(): Promise<{ device_code: string; user_code: string; verification_uri: string; interval: number }> {
|
|
809
|
+
const { requestDeviceCode } = await import("./github")
|
|
810
|
+
const d = await requestDeviceCode()
|
|
811
|
+
return { device_code: d.device_code, user_code: d.user_code, verification_uri: d.verification_uri, interval: d.interval }
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/** Poll once. While the user hasn't approved, report pending. On approval, hand
|
|
815
|
+
* the token back so the picker (existing repo + git-crypt flow) takes over — we
|
|
816
|
+
* do NOT auto-create a repo. */
|
|
817
|
+
export async function deviceFlowPoll(
|
|
818
|
+
deviceCode: string,
|
|
819
|
+
): Promise<{ status: "pending" | "slow_down" } | { status: "error"; error: string } | { status: "ok"; token: string }> {
|
|
820
|
+
const { pollDeviceToken } = await import("./github")
|
|
821
|
+
return pollDeviceToken(deviceCode)
|
|
822
|
+
}
|
|
823
|
+
|
|
796
824
|
/**
|
|
797
825
|
* Detect whether `personal/<user>/` is "fresh" — i.e. only has the
|
|
798
826
|
* scaffolding we put there (`.git`, `memory/`). If yes, it's safe to wipe +
|
package/server/src/paths.ts
CHANGED
|
@@ -160,6 +160,8 @@ export const hostSecretsDir = (user: string) => join(LOOPAT_HOME, "host-secrets"
|
|
|
160
160
|
export const hostDeployKeyPath = (user: string) => join(hostSecretsDir(user), "deploy-key")
|
|
161
161
|
export const hostDeployKeyPubPath = (user: string) => join(hostSecretsDir(user), "deploy-key.pub")
|
|
162
162
|
export const personalGitCryptKeyPath = (user: string) => join(hostSecretsDir(user), "git-crypt.key")
|
|
163
|
+
/** Marker file — when present the onboarding gate is dismissed even if incomplete. */
|
|
164
|
+
export const onboardingDismissedPath = (user: string) => join(hostSecretsDir(user), "onboarding-dismissed")
|
|
163
165
|
export const personalTokenUsagePath = (user: string) => join(personalLoopatDir(user), "token-usage.json")
|
|
164
166
|
export const workspaceSecretsDir = () => join(workspaceDir(), "secrets")
|
|
165
167
|
|
package/server/src/podman.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* SDK and PTY are `podman exec` siblings of this.
|
|
28
28
|
*
|
|
29
29
|
* Two mount-authority tiers (same model as bwrap):
|
|
30
|
-
* - operator: ~/.
|
|
30
|
+
* - operator: ~/.loopat/config.json `mounts` (any host path)
|
|
31
31
|
* - member: convention-based via `vaults/<v>/mounts/home/<rel>/...` → $HOME/<rel>/...
|
|
32
32
|
* - admin: no mount capability
|
|
33
33
|
*
|
|
@@ -213,7 +213,7 @@ export async function buildVolumeMounts(opts: ContainerOptions): Promise<VolumeM
|
|
|
213
213
|
const mounts: VolumeMount[] = []
|
|
214
214
|
|
|
215
215
|
// /tmp: shared with host (for socat / mktemp / IPC sockets). Same as today.
|
|
216
|
-
mounts.push({ src: "/tmp", dst: "/tmp" })
|
|
216
|
+
// mounts.push({ src: "/tmp", dst: "/tmp" })
|
|
217
217
|
|
|
218
218
|
// $HOME: per-loop upper layer, persistent across container restarts. We
|
|
219
219
|
// place it under /loopat/home/<user> instead of host's actual homedir so
|
|
@@ -394,6 +394,9 @@ export async function buildPodmanCreateArgs(opts: ContainerOptions): Promise<str
|
|
|
394
394
|
|
|
395
395
|
const args: string[] = [
|
|
396
396
|
"--name", containerName(opts.loopId),
|
|
397
|
+
// Self-heal a stale name collision (orphan with our name from a crash) so a
|
|
398
|
+
// recreate doesn't loop on "name already in use" → 137; we own the name.
|
|
399
|
+
"--replace",
|
|
397
400
|
"--label", `${LABEL_LOOP}=${opts.loopId}`,
|
|
398
401
|
"--label", `${LABEL_WORKSPACE}=${WORKSPACE}`,
|
|
399
402
|
// --userns=keep-id:uid=2000,gid=2000 maps whatever uid is running
|
package/server/src/session.ts
CHANGED
|
@@ -536,6 +536,12 @@ class LoopSession {
|
|
|
536
536
|
// Required by SDK when using permissionMode: "bypassPermissions"
|
|
537
537
|
...(this.currentPermissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
|
|
538
538
|
systemPrompt: { type: "preset", preset: "claude_code", append: loopatAppend },
|
|
539
|
+
// Stream full subagent conversations (text + thinking) and AI-generated
|
|
540
|
+
// progress summaries so the inline AgentRenderer card shows real-time
|
|
541
|
+
// activity instead of just a spinner. AgentRenderer + childMessagesByAgentId
|
|
542
|
+
// on the FE side are already wired to consume these.
|
|
543
|
+
forwardSubagentText: true,
|
|
544
|
+
agentProgressSummaries: true,
|
|
539
545
|
mcpServers,
|
|
540
546
|
// External marketplace plugins (enabledPlugins in settings.json) are
|
|
541
547
|
// resolved natively by the inner SDK now — ~/.claude/plugins/ is
|
|
@@ -1172,6 +1178,13 @@ class LoopSession {
|
|
|
1172
1178
|
console.log(`[loop:${this.id.slice(0, 8)}] detach → viewers=${this.subscribers.size}`)
|
|
1173
1179
|
}
|
|
1174
1180
|
|
|
1181
|
+
/** Push an arbitrary message into history + persist (no broadcast — caller
|
|
1182
|
+
* sends the result via another channel). Used by shell (!) commands. */
|
|
1183
|
+
injectHistory(msg: any) {
|
|
1184
|
+
this.history.push(msg)
|
|
1185
|
+
this.persist(msg)
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1175
1188
|
async sendUserText(text: string, permissionMode?: SdkPermissionMode) {
|
|
1176
1189
|
updateLoopStatus(this.id, `User: ${text.slice(0, 50)}${text.length > 50 ? "..." : ""}`)
|
|
1177
1190
|
if (this.generating || this.messageQueue.length > 0 || this.queueProcessing) {
|
|
@@ -1313,6 +1326,18 @@ class LoopSession {
|
|
|
1313
1326
|
if (this.q) await this.q.interrupt().catch(() => {})
|
|
1314
1327
|
}
|
|
1315
1328
|
|
|
1329
|
+
/** Background in-flight foreground tasks (Bash commands + subagents) so the
|
|
1330
|
+
* turn finalizes and the queue can drain. Returns false if there was
|
|
1331
|
+
* nothing to background — useful for telling the caller whether the next
|
|
1332
|
+
* user message will actually go through immediately. */
|
|
1333
|
+
async backgroundTasks(): Promise<boolean> {
|
|
1334
|
+
if (!this.q) return false
|
|
1335
|
+
return this.q.backgroundTasks().catch((e: any) => {
|
|
1336
|
+
console.warn(`[sdk:${this.id.slice(0, 8)}] backgroundTasks failed: ${e?.message ?? e}`)
|
|
1337
|
+
return false
|
|
1338
|
+
})
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1316
1341
|
getQueueLength(): number {
|
|
1317
1342
|
return this.messageQueue.length
|
|
1318
1343
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{i as e}from"./chunk-62oNxeRG.js";import{n as t,t as n}from"./jsx-runtime-ZP0XCtgh.js";import{n as r,r as i,t as a}from"./w3c-keyname-BoNxyEq0.js";var o=e(t(),1),s=[],c=[];(()=>{let e=`lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o`.split(`,`).map(e=>e?parseInt(e,36):1);for(let t=0,n=0;t<e.length;t++)(t%2?c:s).push(n+=e[t])})();function l(e){if(e<768)return!1;for(let t=0,n=s.length;;){let r=t+n>>1;if(e<s[r])n=r;else if(e>=c[r])t=r+1;else return!0;if(t==n)return!1}}function u(e){return e>=127462&&e<=127487}var d=8205;function f(e,t,n=!0,r=!0){return(n?p:m)(e,t,r)}function p(e,t,n){if(t==e.length)return t;t&&g(e.charCodeAt(t))&&_(e.charCodeAt(t-1))&&t--;let r=h(e,t);for(t+=v(r);t<e.length;){let i=h(e,t);if(r==d||i==d||n&&l(i))t+=v(i),r=i;else if(u(i)){let n=0,r=t-2;for(;r>=0&&u(h(e,r));)n++,r-=2;if(n%2==0)break;t+=2}else break}return t}function m(e,t,n){for(;t>0;){let r=p(e,t-2,n);if(r<t)return r;t--}return 0}function h(e,t){let n=e.charCodeAt(t);if(!_(n)||t+1==e.length)return n;let r=e.charCodeAt(t+1);return g(r)?(n-55296<<10)+(r-56320)+65536:n}function g(e){return e>=56320&&e<57344}function _(e){return e>=55296&&e<56320}function v(e){return e<65536?1:2}var y=class e{lineAt(e){if(e<0||e>this.length)throw RangeError(`Invalid position ${e} in document of length ${this.length}`);return this.lineInner(e,!1,1,0)}line(e){if(e<1||e>this.lines)throw RangeError(`Invalid line number ${e} in ${this.lines}-line document`);return this.lineInner(e,!0,1,0)}replace(e,t,n){[e,t]=w(this,e,t);let r=[];return this.decompose(0,e,r,2),n.length&&n.decompose(0,n.length,r,3),this.decompose(t,this.length,r,1),x.from(r,this.length-(t-e)+n.length)}append(e){return this.replace(this.length,this.length,e)}slice(e,t=this.length){[e,t]=w(this,e,t);let n=[];return this.decompose(e,t,n,0),x.from(n,t-e)}eq(e){if(e==this)return!0;if(e.length!=this.length||e.lines!=this.lines)return!1;let t=this.scanIdentical(e,1),n=this.length-this.scanIdentical(e,-1),r=new C(this),i=new C(e);for(let e=t,a=t;;){if(r.next(e),i.next(e),e=0,r.lineBreak!=i.lineBreak||r.done!=i.done||r.value!=i.value)return!1;if(a+=r.value.length,r.done||a>=n)return!0}}iter(e=1){return new C(this,e)}iterRange(e,t=this.length){return new ne(this,e,t)}iterLines(e,t){let n;if(e==null)n=this.iter();else{t??=this.lines+1;let r=this.line(e).from;n=this.iterRange(r,Math.max(r,t==this.lines+1?this.length:t<=1?0:this.line(t-1).to))}return new re(n)}toString(){return this.sliceString(0)}toJSON(){let e=[];return this.flatten(e),e}constructor(){}static of(t){if(t.length==0)throw RangeError(`A document must have at least one line`);return t.length==1&&!t[0]?e.empty:t.length<=32?new b(t):x.from(b.split(t,[]))}},b=class e extends y{constructor(e,t=ee(e)){super(),this.text=e,this.length=t}get lines(){return this.text.length}get children(){return null}lineInner(e,t,n,r){for(let i=0;;i++){let a=this.text[i],o=r+a.length;if((t?n:o)>=e)return new ie(r,o,n,a);r=o+1,n++}}decompose(t,n,r,i){let a=t<=0&&n>=this.length?this:new e(S(this.text,t,n),Math.min(n,this.length)-Math.max(0,t));if(i&1){let t=r.pop(),n=te(a.text,t.text.slice(),0,a.length);if(n.length<=32)r.push(new e(n,t.length+a.length));else{let t=n.length>>1;r.push(new e(n.slice(0,t)),new e(n.slice(t)))}}else r.push(a)}replace(t,n,r){if(!(r instanceof e))return super.replace(t,n,r);[t,n]=w(this,t,n);let i=te(this.text,te(r.text,S(this.text,0,t)),n),a=this.length+r.length-(n-t);return i.length<=32?new e(i,a):x.from(e.split(i,[]),a)}sliceString(e,t=this.length,n=`
|
|
2
2
|
`){[e,t]=w(this,e,t);let r=``;for(let i=0,a=0;i<=t&&a<this.text.length;a++){let o=this.text[a],s=i+o.length;i>e&&a&&(r+=n),e<s&&t>i&&(r+=o.slice(Math.max(0,e-i),t-i)),i=s+1}return r}flatten(e){for(let t of this.text)e.push(t)}scanIdentical(){return 0}static split(t,n){let r=[],i=-1;for(let a of t)r.push(a),i+=a.length+1,r.length==32&&(n.push(new e(r,i)),r=[],i=-1);return i>-1&&n.push(new e(r,i)),n}},x=class e extends y{constructor(e,t){super(),this.children=e,this.length=t,this.lines=0;for(let t of e)this.lines+=t.lines}lineInner(e,t,n,r){for(let i=0;;i++){let a=this.children[i],o=r+a.length,s=n+a.lines-1;if((t?s:o)>=e)return a.lineInner(e,t,n,r);r=o+1,n=s+1}}decompose(e,t,n,r){for(let i=0,a=0;a<=t&&i<this.children.length;i++){let o=this.children[i],s=a+o.length;if(e<=s&&t>=a){let i=r&(a<=e|(s>=t?2:0));a>=e&&s<=t&&!i?n.push(o):o.decompose(e-a,t-a,n,i)}a=s+1}}replace(t,n,r){if([t,n]=w(this,t,n),r.lines<this.lines)for(let i=0,a=0;i<this.children.length;i++){let o=this.children[i],s=a+o.length;if(t>=a&&n<=s){let c=o.replace(t-a,n-a,r),l=this.lines-o.lines+c.lines;if(c.lines<l>>4&&c.lines>l>>6){let a=this.children.slice();return a[i]=c,new e(a,this.length-(n-t)+r.length)}return super.replace(a,s,c)}a=s+1}return super.replace(t,n,r)}sliceString(e,t=this.length,n=`
|
|
3
3
|
`){[e,t]=w(this,e,t);let r=``;for(let i=0,a=0;i<this.children.length&&a<=t;i++){let o=this.children[i],s=a+o.length;a>e&&i&&(r+=n),e<s&&t>a&&(r+=o.sliceString(e-a,t-a,n)),a=s+1}return r}flatten(e){for(let t of this.children)t.flatten(e)}scanIdentical(t,n){if(!(t instanceof e))return 0;let r=0,[i,a,o,s]=n>0?[0,0,this.children.length,t.children.length]:[this.children.length-1,t.children.length-1,-1,-1];for(;;i+=n,a+=n){if(i==o||a==s)return r;let e=this.children[i],c=t.children[a];if(e!=c)return r+e.scanIdentical(c,n);r+=e.length+1}}static from(t,n=t.reduce((e,t)=>e+t.length+1,-1)){let r=0;for(let e of t)r+=e.lines;if(r<32){let e=[];for(let n of t)n.flatten(e);return new b(e,n)}let i=Math.max(32,r>>5),a=i<<1,o=i>>1,s=[],c=0,l=-1,u=[];function d(t){let n;if(t.lines>a&&t instanceof e)for(let e of t.children)d(e);else t.lines>o&&(c>o||!c)?(f(),s.push(t)):t instanceof b&&c&&(n=u[u.length-1])instanceof b&&t.lines+n.lines<=32?(c+=t.lines,l+=t.length+1,u[u.length-1]=new b(n.text.concat(t.text),n.length+1+t.length)):(c+t.lines>i&&f(),c+=t.lines,l+=t.length+1,u.push(t))}function f(){c!=0&&(s.push(u.length==1?u[0]:e.from(u,l)),l=-1,c=u.length=0)}for(let e of t)d(e);return f(),s.length==1?s[0]:new e(s,n)}};y.empty=new b([``],0);function ee(e){let t=-1;for(let n of e)t+=n.length+1;return t}function te(e,t,n=0,r=1e9){for(let i=0,a=0,o=!0;a<e.length&&i<=r;a++){let s=e[a],c=i+s.length;c>=n&&(c>r&&(s=s.slice(0,r-i)),i<n&&(s=s.slice(n-i)),o?(t[t.length-1]+=s,o=!1):t.push(s)),i=c+1}return t}function S(e,t,n){return te(e,[``],t,n)}var C=class{constructor(e,t=1){this.dir=t,this.done=!1,this.lineBreak=!1,this.value=``,this.nodes=[e],this.offsets=[t>0?1:(e instanceof b?e.text.length:e.children.length)<<1]}nextInner(e,t){for(this.done=this.lineBreak=!1;;){let n=this.nodes.length-1,r=this.nodes[n],i=this.offsets[n],a=i>>1,o=r instanceof b?r.text.length:r.children.length;if(a==(t>0?o:0)){if(n==0)return this.done=!0,this.value=``,this;t>0&&this.offsets[n-1]++,this.nodes.pop(),this.offsets.pop()}else if((i&1)==(t>0?0:1)){if(this.offsets[n]+=t,e==0)return this.lineBreak=!0,this.value=`
|
|
4
4
|
`,this;e--}else if(r instanceof b){let i=r.text[a+(t<0?-1:0)];if(this.offsets[n]+=t,i.length>Math.max(0,e))return this.value=e==0?i:t>0?i.slice(e):i.slice(0,i.length-e),this;e-=i.length}else{let i=r.children[a+(t<0?-1:0)];e>i.length?(e-=i.length,this.offsets[n]+=t):(t<0&&this.offsets[n]--,this.nodes.push(i),this.offsets.push(t>0?1:(i instanceof b?i.text.length:i.children.length)<<1))}}}next(e=0){return e<0&&(this.nextInner(-e,-this.dir),e=this.value.length),this.nextInner(e,this.dir)}},ne=class{constructor(e,t,n){this.value=``,this.done=!1,this.cursor=new C(e,t>n?-1:1),this.pos=t>n?e.length:0,this.from=Math.min(t,n),this.to=Math.max(t,n)}nextInner(e,t){if(t<0?this.pos<=this.from:this.pos>=this.to)return this.value=``,this.done=!0,this;e+=Math.max(0,t<0?this.pos-this.to:this.from-this.pos);let n=t<0?this.pos-this.from:this.to-this.pos;e>n&&(e=n),n-=e;let{value:r}=this.cursor.next(e);return this.pos+=(r.length+e)*t,this.value=r.length<=n?r:t<0?r.slice(r.length-n):r.slice(0,n),this.done=!this.value,this}next(e=0){return e<0?e=Math.max(e,this.from-this.pos):e>0&&(e=Math.min(e,this.to-this.pos)),this.nextInner(e,this.cursor.dir)}get lineBreak(){return this.cursor.lineBreak&&this.value!=``}},re=class{constructor(e){this.inner=e,this.afterBreak=!0,this.value=``,this.done=!1}next(e=0){let{done:t,lineBreak:n,value:r}=this.inner.next(e);return t&&this.afterBreak?(this.value=``,this.afterBreak=!1):t?(this.done=!0,this.value=``):n?this.afterBreak?this.value=``:(this.afterBreak=!0,this.next()):(this.value=r,this.afterBreak=!1),this}get lineBreak(){return!1}};typeof Symbol<`u`&&(y.prototype[Symbol.iterator]=function(){return this.iter()},C.prototype[Symbol.iterator]=ne.prototype[Symbol.iterator]=re.prototype[Symbol.iterator]=function(){return this});var ie=class{constructor(e,t,n,r){this.from=e,this.to=t,this.number=n,this.text=r}get length(){return this.to-this.from}};function w(e,t,n){return t=Math.max(0,Math.min(e.length,t)),[t,Math.max(t,Math.min(e.length,n))]}function T(e,t,n=!0,r=!0){return f(e,t,n,r)}function ae(e){return e>=56320&&e<57344}function oe(e){return e>=55296&&e<56320}function se(e,t){let n=e.charCodeAt(t);if(!oe(n)||t+1==e.length)return n;let r=e.charCodeAt(t+1);return ae(r)?(n-55296<<10)+(r-56320)+65536:n}function ce(e){return e<=65535?String.fromCharCode(e):(e-=65536,String.fromCharCode((e>>10)+55296,(e&1023)+56320))}function le(e){return e<65536?1:2}var ue=/\r\n?|\n/,de=(function(e){return e[e.Simple=0]=`Simple`,e[e.TrackDel=1]=`TrackDel`,e[e.TrackBefore=2]=`TrackBefore`,e[e.TrackAfter=3]=`TrackAfter`,e})(de||={}),fe=class e{constructor(e){this.sections=e}get length(){let e=0;for(let t=0;t<this.sections.length;t+=2)e+=this.sections[t];return e}get newLength(){let e=0;for(let t=0;t<this.sections.length;t+=2){let n=this.sections[t+1];e+=n<0?this.sections[t]:n}return e}get empty(){return this.sections.length==0||this.sections.length==2&&this.sections[1]<0}iterGaps(e){for(let t=0,n=0,r=0;t<this.sections.length;){let i=this.sections[t++],a=this.sections[t++];a<0?(e(n,r,i),r+=i):r+=a,n+=i}}iterChangedRanges(e,t=!1){ge(this,e,t)}get invertedDesc(){let t=[];for(let e=0;e<this.sections.length;){let n=this.sections[e++],r=this.sections[e++];r<0?t.push(n,r):t.push(r,n)}return new e(t)}composeDesc(e){return this.empty?e:e.empty?this:ve(this,e)}mapDesc(e,t=!1){return e.empty?this:_e(this,e,t)}mapPos(e,t=-1,n=de.Simple){let r=0,i=0;for(let a=0;a<this.sections.length;){let o=this.sections[a++],s=this.sections[a++],c=r+o;if(s<0){if(c>e)return i+(e-r);i+=o}else{if(n!=de.Simple&&c>=e&&(n==de.TrackDel&&r<e&&c>e||n==de.TrackBefore&&r<e||n==de.TrackAfter&&c>e))return null;if(c>e||c==e&&t<0&&!o)return e==r||t<0?i:i+s;i+=s}r=c}if(e>r)throw RangeError(`Position ${e} is out of range for changeset of length ${r}`);return i}touchesRange(e,t=e){for(let n=0,r=0;n<this.sections.length&&r<=t;){let i=this.sections[n++],a=this.sections[n++],o=r+i;if(a>=0&&r<=t&&o>=e)return r<e&&o>t?`cover`:!0;r=o}return!1}toString(){let e=``;for(let t=0;t<this.sections.length;){let n=this.sections[t++],r=this.sections[t++];e+=(e?` `:``)+n+(r>=0?`:`+r:``)}return e}toJSON(){return this.sections}static fromJSON(t){if(!Array.isArray(t)||t.length%2||t.some(e=>typeof e!=`number`))throw RangeError(`Invalid JSON representation of ChangeDesc`);return new e(t)}static create(t){return new e(t)}},pe=class e extends fe{constructor(e,t){super(e),this.inserted=t}apply(e){if(this.length!=e.length)throw RangeError(`Applying change set to a document with the wrong length`);return ge(this,(t,n,r,i,a)=>e=e.replace(r,r+(n-t),a),!1),e}mapDesc(e,t=!1){return _e(this,e,t,!0)}invert(t){let n=this.sections.slice(),r=[];for(let e=0,i=0;e<n.length;e+=2){let a=n[e],o=n[e+1];if(o>=0){n[e]=o,n[e+1]=a;let s=e>>1;for(;r.length<s;)r.push(y.empty);r.push(a?t.slice(i,i+a):y.empty)}i+=a}return new e(n,r)}compose(e){return this.empty?e:e.empty?this:ve(this,e,!0)}map(e,t=!1){return e.empty?this:_e(this,e,t,!0)}iterChanges(e,t=!1){ge(this,e,t)}get desc(){return fe.create(this.sections)}filter(t){let n=[],r=[],i=[],a=new ye(this);done:for(let e=0,o=0;;){let s=e==t.length?1e9:t[e++];for(;o<s||o==s&&a.len==0;){if(a.done)break done;let e=Math.min(a.len,s-o);me(i,e,-1);let t=a.ins==-1?-1:a.off==0?a.ins:0;me(n,e,t),t>0&&he(r,n,a.text),a.forward(e),o+=e}let c=t[e++];for(;o<c;){if(a.done)break done;let e=Math.min(a.len,c-o);me(n,e,-1),me(i,e,a.ins==-1?-1:a.off==0?a.ins:0),a.forward(e),o+=e}}return{changes:new e(n,r),filtered:fe.create(i)}}toJSON(){let e=[];for(let t=0;t<this.sections.length;t+=2){let n=this.sections[t],r=this.sections[t+1];r<0?e.push(n):r==0?e.push([n]):e.push([n].concat(this.inserted[t>>1].toJSON()))}return e}static of(t,n,r){let i=[],a=[],o=0,s=null;function c(t=!1){if(!t&&!i.length)return;o<n&&me(i,n-o,-1);let r=new e(i,a);s=s?s.compose(r.map(s)):r,i=[],a=[],o=0}function l(t){if(Array.isArray(t))for(let e of t)l(e);else if(t instanceof e){if(t.length!=n)throw RangeError(`Mismatched change set length (got ${t.length}, expected ${n})`);c(),s=s?s.compose(t.map(s)):t}else{let{from:e,to:s=e,insert:l}=t;if(e>s||e<0||s>n)throw RangeError(`Invalid change range ${e} to ${s} (in doc of length ${n})`);let u=l?typeof l==`string`?y.of(l.split(r||ue)):l:y.empty,d=u.length;if(e==s&&d==0)return;e<o&&c(),e>o&&me(i,e-o,-1),me(i,s-e,d),he(a,i,u),o=s}}return l(t),c(!s),s}static empty(t){return new e(t?[t,-1]:[],[])}static fromJSON(t){if(!Array.isArray(t))throw RangeError(`Invalid JSON representation of ChangeSet`);let n=[],r=[];for(let e=0;e<t.length;e++){let i=t[e];if(typeof i==`number`)n.push(i,-1);else if(!Array.isArray(i)||typeof i[0]!=`number`||i.some((e,t)=>t&&typeof e!=`string`))throw RangeError(`Invalid JSON representation of ChangeSet`);else if(i.length==1)n.push(i[0],0);else{for(;r.length<e;)r.push(y.empty);r[e]=y.of(i.slice(1)),n.push(i[0],r[e].length)}}return new e(n,r)}static createSet(t,n){return new e(t,n)}};function me(e,t,n,r=!1){if(t==0&&n<=0)return;let i=e.length-2;i>=0&&n<=0&&n==e[i+1]?e[i]+=t:i>=0&&t==0&&e[i]==0?e[i+1]+=n:r?(e[i]+=t,e[i+1]+=n):e.push(t,n)}function he(e,t,n){if(n.length==0)return;let r=t.length-2>>1;if(r<e.length)e[e.length-1]=e[e.length-1].append(n);else{for(;e.length<r;)e.push(y.empty);e.push(n)}}function ge(e,t,n){let r=e.inserted;for(let i=0,a=0,o=0;o<e.sections.length;){let s=e.sections[o++],c=e.sections[o++];if(c<0)i+=s,a+=s;else{let l=i,u=a,d=y.empty;for(;l+=s,u+=c,c&&r&&(d=d.append(r[o-2>>1])),!(n||o==e.sections.length||e.sections[o+1]<0);)s=e.sections[o++],c=e.sections[o++];t(i,l,a,u,d),i=l,a=u}}}function _e(e,t,n,r=!1){let i=[],a=r?[]:null,o=new ye(e),s=new ye(t);for(let e=-1;;)if(o.done&&s.len||s.done&&o.len)throw Error(`Mismatched change set lengths`);else if(o.ins==-1&&s.ins==-1){let e=Math.min(o.len,s.len);me(i,e,-1),o.forward(e),s.forward(e)}else if(s.ins>=0&&(o.ins<0||e==o.i||o.off==0&&(s.len<o.len||s.len==o.len&&!n))){let t=s.len;for(me(i,s.ins,-1);t;){let n=Math.min(o.len,t);o.ins>=0&&e<o.i&&o.len<=n&&(me(i,0,o.ins),a&&he(a,i,o.text),e=o.i),o.forward(n),t-=n}s.next()}else if(o.ins>=0){let t=0,n=o.len;for(;n;)if(s.ins==-1){let e=Math.min(n,s.len);t+=e,n-=e,s.forward(e)}else if(s.ins==0&&s.len<n)n-=s.len,s.next();else break;me(i,t,e<o.i?o.ins:0),a&&e<o.i&&he(a,i,o.text),e=o.i,o.forward(o.len-n)}else if(o.done&&s.done)return a?pe.createSet(i,a):fe.create(i);else throw Error(`Mismatched change set lengths`)}function ve(e,t,n=!1){let r=[],i=n?[]:null,a=new ye(e),o=new ye(t);for(let e=!1;;)if(a.done&&o.done)return i?pe.createSet(r,i):fe.create(r);else if(a.ins==0)me(r,a.len,0,e),a.next();else if(o.len==0&&!o.done)me(r,0,o.ins,e),i&&he(i,r,o.text),o.next();else if(a.done||o.done)throw Error(`Mismatched change set lengths`);else{let t=Math.min(a.len2,o.len),n=r.length;if(a.ins==-1){let n=o.ins==-1?-1:o.off?0:o.ins;me(r,t,n,e),i&&n&&he(i,r,o.text)}else o.ins==-1?(me(r,a.off?0:a.len,t,e),i&&he(i,r,a.textBit(t))):(me(r,a.off?0:a.len,o.off?0:o.ins,e),i&&!o.off&&he(i,r,o.text));e=(a.ins>t||o.ins>=0&&o.len>t)&&(e||r.length>n),a.forward2(t),o.forward(t)}}var ye=class{constructor(e){this.set=e,this.i=0,this.next()}next(){let{sections:e}=this.set;this.i<e.length?(this.len=e[this.i++],this.ins=e[this.i++]):(this.len=0,this.ins=-2),this.off=0}get done(){return this.ins==-2}get len2(){return this.ins<0?this.len:this.ins}get text(){let{inserted:e}=this.set,t=this.i-2>>1;return t>=e.length?y.empty:e[t]}textBit(e){let{inserted:t}=this.set,n=this.i-2>>1;return n>=t.length&&!e?y.empty:t[n].slice(this.off,e==null?void 0:this.off+e)}forward(e){e==this.len?this.next():(this.len-=e,this.off+=e)}forward2(e){this.ins==-1?this.forward(e):e==this.ins?this.next():(this.ins-=e,this.off+=e)}},be=class e{constructor(e,t,n){this.from=e,this.to=t,this.flags=n}get anchor(){return this.flags&32?this.to:this.from}get head(){return this.flags&32?this.from:this.to}get empty(){return this.from==this.to}get assoc(){return this.flags&8?-1:this.flags&16?1:0}get bidiLevel(){let e=this.flags&7;return e==7?null:e}get goalColumn(){let e=this.flags>>6;return e==16777215?void 0:e}map(t,n=-1){let r,i;return this.empty?r=i=t.mapPos(this.from,n):(r=t.mapPos(this.from,1),i=t.mapPos(this.to,-1)),r==this.from&&i==this.to?this:new e(r,i,this.flags)}extend(e,t=e,n=0){if(e<=this.anchor&&t>=this.anchor)return E.range(e,t,void 0,void 0,n);let r=Math.abs(e-this.anchor)>Math.abs(t-this.anchor)?e:t;return E.range(this.anchor,r,void 0,void 0,n)}eq(e,t=!1){return this.anchor==e.anchor&&this.head==e.head&&this.goalColumn==e.goalColumn&&(!t||!this.empty||this.assoc==e.assoc)}toJSON(){return{anchor:this.anchor,head:this.head}}static fromJSON(e){if(!e||typeof e.anchor!=`number`||typeof e.head!=`number`)throw RangeError(`Invalid JSON representation for SelectionRange`);return E.range(e.anchor,e.head)}static create(t,n,r){return new e(t,n,r)}},E=class e{constructor(e,t){this.ranges=e,this.mainIndex=t}map(t,n=-1){return t.empty?this:e.create(this.ranges.map(e=>e.map(t,n)),this.mainIndex)}eq(e,t=!1){if(this.ranges.length!=e.ranges.length||this.mainIndex!=e.mainIndex)return!1;for(let n=0;n<this.ranges.length;n++)if(!this.ranges[n].eq(e.ranges[n],t))return!1;return!0}get main(){return this.ranges[this.mainIndex]}asSingle(){return this.ranges.length==1?this:new e([this.main],0)}addRange(t,n=!0){return e.create([t].concat(this.ranges),n?0:this.mainIndex+1)}replaceRange(t,n=this.mainIndex){let r=this.ranges.slice();return r[n]=t,e.create(r,this.mainIndex)}toJSON(){return{ranges:this.ranges.map(e=>e.toJSON()),main:this.mainIndex}}static fromJSON(t){if(!t||!Array.isArray(t.ranges)||typeof t.main!=`number`||t.main>=t.ranges.length)throw RangeError(`Invalid JSON representation for EditorSelection`);return new e(t.ranges.map(e=>be.fromJSON(e)),t.main)}static single(t,n=t){return new e([e.range(t,n)],0)}static create(t,n=0){if(t.length==0)throw RangeError(`A selection needs at least one range`);for(let r=0,i=0;i<t.length;i++){let a=t[i];if(a.empty?a.from<=r:a.from<r)return e.normalized(t.slice(),n);r=a.to}return new e(t,n)}static cursor(e,t=0,n,r){return be.create(e,e,(t==0?0:t<0?8:16)|(n==null?7:Math.min(6,n))|(r??16777215)<<6)}static range(e,t,n,r,i){let a=(n??16777215)<<6|(r==null?7:Math.min(6,r));return!i&&e!=t&&(i=t<e?1:-1),t<e?be.create(t,e,48|a):be.create(e,t,(i?i<0?8:16:0)|a)}static normalized(t,n=0){let r=t[n];t.sort((e,t)=>e.from-t.from),n=t.indexOf(r);for(let r=1;r<t.length;r++){let i=t[r],a=t[r-1];if(i.empty?i.from<=a.to:i.from<a.to){let o=a.from,s=Math.max(i.to,a.to);r<=n&&n--,t.splice(--r,2,i.anchor>i.head?e.range(s,o):e.range(o,s))}}return new e(t,n)}};function xe(e,t){for(let n of e.ranges)if(n.to>t)throw RangeError(`Selection points outside of document`)}var Se=0,D=class e{constructor(e,t,n,r,i){this.combine=e,this.compareInput=t,this.compare=n,this.isStatic=r,this.id=Se++,this.default=e([]),this.extensions=typeof i==`function`?i(this):i}get reader(){return this}static define(t={}){return new e(t.combine||(e=>e),t.compareInput||((e,t)=>e===t),t.compare||(t.combine?(e,t)=>e===t:Ce),!!t.static,t.enables)}of(e){return new we([],this,0,e)}compute(e,t){if(this.isStatic)throw Error(`Can't compute a static facet`);return new we(e,this,1,t)}computeN(e,t){if(this.isStatic)throw Error(`Can't compute a static facet`);return new we(e,this,2,t)}from(e,t){return t||=e=>e,this.compute([e],n=>t(n.field(e)))}};function Ce(e,t){return e==t||e.length==t.length&&e.every((e,n)=>e===t[n])}var we=class{constructor(e,t,n,r){this.dependencies=e,this.facet=t,this.type=n,this.value=r,this.id=Se++}dynamicSlot(e){let t=this.value,n=this.facet.compareInput,r=this.id,i=e[r]>>1,a=this.type==2,o=!1,s=!1,c=[];for(let t of this.dependencies)t==`doc`?o=!0:t==`selection`?s=!0:(e[t.id]??1)&1||c.push(e[t.id]);return{create(e){return e.values[i]=t(e),1},update(e,r){if(o&&r.docChanged||s&&(r.docChanged||r.selection)||Ee(e,c)){let r=t(e);if(a?!Te(r,e.values[i],n):!n(r,e.values[i]))return e.values[i]=r,1}return 0},reconfigure:(e,o)=>{let s,c=o.config.address[r];if(c!=null){let r=ze(o,c);if(this.dependencies.every(t=>t instanceof D?o.facet(t)===e.facet(t):t instanceof ke?o.field(t,!1)==e.field(t,!1):!0)||(a?Te(s=t(e),r,n):n(s=t(e),r)))return e.values[i]=r,0}else s=t(e);return e.values[i]=s,1}}}};function Te(e,t,n){if(e.length!=t.length)return!1;for(let r=0;r<e.length;r++)if(!n(e[r],t[r]))return!1;return!0}function Ee(e,t){let n=!1;for(let r of t)Re(e,r)&1&&(n=!0);return n}function De(e,t,n){let r=n.map(t=>e[t.id]),i=n.map(e=>e.type),a=r.filter(e=>!(e&1)),o=e[t.id]>>1;function s(e){let n=[];for(let t=0;t<r.length;t++){let a=ze(e,r[t]);if(i[t]==2)for(let e of a)n.push(e);else n.push(a)}return t.combine(n)}return{create(e){for(let t of r)Re(e,t);return e.values[o]=s(e),1},update(e,n){if(!Ee(e,a))return 0;let r=s(e);return t.compare(r,e.values[o])?0:(e.values[o]=r,1)},reconfigure(e,i){let a=Ee(e,r),c=i.config.facets[t.id],l=i.facet(t);if(c&&!a&&Ce(n,c))return e.values[o]=l,0;let u=s(e);return t.compare(u,l)?(e.values[o]=l,0):(e.values[o]=u,1)}}}var Oe=D.define({static:!0}),ke=class e{constructor(e,t,n,r,i){this.id=e,this.createF=t,this.updateF=n,this.compareF=r,this.spec=i,this.provides=void 0}static define(t){let n=new e(Se++,t.create,t.update,t.compare||((e,t)=>e===t),t);return t.provide&&(n.provides=t.provide(n)),n}create(e){return(e.facet(Oe).find(e=>e.field==this)?.create||this.createF)(e)}slot(e){let t=e[this.id]>>1;return{create:e=>(e.values[t]=this.create(e),1),update:(e,n)=>{let r=e.values[t],i=this.updateF(r,n);return this.compareF(r,i)?0:(e.values[t]=i,1)},reconfigure:(e,n)=>{let r=e.facet(Oe),i=n.facet(Oe),a;return(a=r.find(e=>e.field==this))&&a!=i.find(e=>e.field==this)?(e.values[t]=a.create(e),1):n.config.address[this.id]==null?(e.values[t]=this.create(e),1):(e.values[t]=n.field(this),0)}}}init(e){return[this,Oe.of({field:this,create:e})]}get extension(){return this}},Ae={lowest:4,low:3,default:2,high:1,highest:0};function je(e){return t=>new Ne(t,e)}var Me={highest:je(Ae.highest),high:je(Ae.high),default:je(Ae.default),low:je(Ae.low),lowest:je(Ae.lowest)},Ne=class{constructor(e,t){this.inner=e,this.prec=t}},Pe=class e{of(e){return new Fe(this,e)}reconfigure(t){return e.reconfigure.of({compartment:this,extension:t})}get(e){return e.config.compartments.get(this)}},Fe=class{constructor(e,t){this.compartment=e,this.inner=t}},Ie=class e{constructor(e,t,n,r,i,a){for(this.base=e,this.compartments=t,this.dynamicSlots=n,this.address=r,this.staticValues=i,this.facets=a,this.statusTemplate=[];this.statusTemplate.length<n.length;)this.statusTemplate.push(0)}staticFacet(e){let t=this.address[e.id];return t==null?e.default:this.staticValues[t>>1]}static resolve(t,n,r){let i=[],a=Object.create(null),o=new Map;for(let e of Le(t,n,o))e instanceof ke?i.push(e):(a[e.facet.id]||(a[e.facet.id]=[])).push(e);let s=Object.create(null),c=[],l=[];for(let e of i)s[e.id]=l.length<<1,l.push(t=>e.slot(t));let u=r?.config.facets;for(let e in a){let t=a[e],n=t[0].facet,i=u&&u[e]||[];if(t.every(e=>e.type==0))if(s[n.id]=c.length<<1|1,Ce(i,t))c.push(r.facet(n));else{let e=n.combine(t.map(e=>e.value));c.push(r&&n.compare(e,r.facet(n))?r.facet(n):e)}else{for(let e of t)e.type==0?(s[e.id]=c.length<<1|1,c.push(e.value)):(s[e.id]=l.length<<1,l.push(t=>e.dynamicSlot(t)));s[n.id]=l.length<<1,l.push(e=>De(e,n,t))}}return new e(t,o,l.map(e=>e(s)),s,c,a)}};function Le(e,t,n){let r=[[],[],[],[],[]],i=new Map;function a(e,o){let s=i.get(e);if(s!=null){if(s<=o)return;let t=r[s].indexOf(e);t>-1&&r[s].splice(t,1),e instanceof Fe&&n.delete(e.compartment)}if(i.set(e,o),Array.isArray(e))for(let t of e)a(t,o);else if(e instanceof Fe){if(n.has(e.compartment))throw RangeError(`Duplicate use of compartment in extensions`);let r=t.get(e.compartment)||e.inner;n.set(e.compartment,r),a(r,o)}else if(e instanceof Ne)a(e.inner,e.prec);else if(e instanceof ke)r[o].push(e),e.provides&&a(e.provides,o);else if(e instanceof we)r[o].push(e),e.facet.extensions&&a(e.facet.extensions,Ae.default);else{let t=e.extension;if(!t)throw Error(`Unrecognized extension value in extension set (${e}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`);a(t,o)}}return a(e,Ae.default),r.reduce((e,t)=>e.concat(t))}function Re(e,t){if(t&1)return 2;let n=t>>1,r=e.status[n];if(r==4)throw Error(`Cyclic dependency between fields and/or facets`);if(r&2)return r;e.status[n]=4;let i=e.computeSlot(e,e.config.dynamicSlots[n]);return e.status[n]=2|i}function ze(e,t){return t&1?e.config.staticValues[t>>1]:e.values[t>>1]}var Be=D.define(),Ve=D.define({combine:e=>e.some(e=>e),static:!0}),He=D.define({combine:e=>e.length?e[0]:void 0,static:!0}),Ue=D.define(),We=D.define(),Ge=D.define(),Ke=D.define({combine:e=>e.length?e[0]:!1}),qe=class{constructor(e,t){this.type=e,this.value=t}static define(){return new Je}},Je=class{of(e){return new qe(this,e)}},Ye=class{constructor(e){this.map=e}of(e){return new O(this,e)}},O=class e{constructor(e,t){this.type=e,this.value=t}map(t){let n=this.type.map(this.value,t);return n===void 0?void 0:n==this.value?this:new e(this.type,n)}is(e){return this.type==e}static define(e={}){return new Ye(e.map||(e=>e))}static mapEffects(e,t){if(!e.length)return e;let n=[];for(let r of e){let e=r.map(t);e&&n.push(e)}return n}};O.reconfigure=O.define(),O.appendConfig=O.define();var Xe=class e{constructor(t,n,r,i,a,o){this.startState=t,this.changes=n,this.selection=r,this.effects=i,this.annotations=a,this.scrollIntoView=o,this._doc=null,this._state=null,r&&xe(r,n.newLength),a.some(t=>t.type==e.time)||(this.annotations=a.concat(e.time.of(Date.now())))}static create(t,n,r,i,a,o){return new e(t,n,r,i,a,o)}get newDoc(){return this._doc||=this.changes.apply(this.startState.doc)}get newSelection(){return this.selection||this.startState.selection.map(this.changes)}get state(){return this._state||this.startState.applyTransaction(this),this._state}annotation(e){for(let t of this.annotations)if(t.type==e)return t.value}get docChanged(){return!this.changes.empty}get reconfigured(){return this.startState.config!=this.state.config}isUserEvent(t){let n=this.annotation(e.userEvent);return!!(n&&(n==t||n.length>t.length&&n.slice(0,t.length)==t&&n[t.length]==`.`))}};Xe.time=qe.define(),Xe.userEvent=qe.define(),Xe.addToHistory=qe.define(),Xe.remote=qe.define();function Ze(e,t){let n=[];for(let r=0,i=0;;){let a,o;if(r<e.length&&(i==t.length||t[i]>=e[r]))a=e[r++],o=e[r++];else if(i<t.length)a=t[i++],o=t[i++];else return n;!n.length||n[n.length-1]<a?n.push(a,o):n[n.length-1]<o&&(n[n.length-1]=o)}}function Qe(e,t,n){let r,i,a;return n?(r=t.changes,i=pe.empty(t.changes.length),a=e.changes.compose(t.changes)):(r=t.changes.map(e.changes),i=e.changes.mapDesc(t.changes,!0),a=e.changes.compose(r)),{changes:a,selection:t.selection?t.selection.map(i):e.selection?.map(r),effects:O.mapEffects(e.effects,r).concat(O.mapEffects(t.effects,i)),annotations:e.annotations.length?e.annotations.concat(t.annotations):t.annotations,scrollIntoView:e.scrollIntoView||t.scrollIntoView}}function $e(e,t,n){let r=t.selection,i=it(t.annotations);return t.userEvent&&(i=i.concat(Xe.userEvent.of(t.userEvent))),{changes:t.changes instanceof pe?t.changes:pe.of(t.changes||[],n,e.facet(He)),selection:r&&(r instanceof E?r:E.single(r.anchor,r.head)),effects:it(t.effects),annotations:i,scrollIntoView:!!t.scrollIntoView}}function et(e,t,n){let r=$e(e,t.length?t[0]:{},e.doc.length);t.length&&t[0].filter===!1&&(n=!1);for(let i=1;i<t.length;i++){t[i].filter===!1&&(n=!1);let a=!!t[i].sequential;r=Qe(r,$e(e,t[i],a?r.changes.newLength:e.doc.length),a)}let i=Xe.create(e,r.changes,r.selection,r.effects,r.annotations,r.scrollIntoView);return nt(n?tt(i):i)}function tt(e){let t=e.startState,n=!0;for(let r of t.facet(Ue)){let t=r(e);if(t===!1){n=!1;break}Array.isArray(t)&&(n=n===!0?t:Ze(n,t))}if(n!==!0){let r,i;if(n===!1)i=e.changes.invertedDesc,r=pe.empty(t.doc.length);else{let t=e.changes.filter(n);r=t.changes,i=t.filtered.mapDesc(t.changes).invertedDesc}e=Xe.create(t,r,e.selection&&e.selection.map(i),O.mapEffects(e.effects,i),e.annotations,e.scrollIntoView)}let r=t.facet(We);for(let n=r.length-1;n>=0;n--){let i=r[n](e);e=i instanceof Xe?i:Array.isArray(i)&&i.length==1&&i[0]instanceof Xe?i[0]:et(t,it(i),!1)}return e}function nt(e){let t=e.startState,n=t.facet(Ge),r=e;for(let i=n.length-1;i>=0;i--){let a=n[i](e);a&&Object.keys(a).length&&(r=Qe(r,$e(t,a,e.changes.newLength),!0))}return r==e?e:Xe.create(t,e.changes,e.selection,r.effects,r.annotations,r.scrollIntoView)}var rt=[];function it(e){return e==null?rt:Array.isArray(e)?e:[e]}var k=(function(e){return e[e.Word=0]=`Word`,e[e.Space=1]=`Space`,e[e.Other=2]=`Other`,e})(k||={}),at=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/,ot;try{ot=RegExp(`[\\p{Alphabetic}\\p{Number}_]`,`u`)}catch{}function st(e){if(ot)return ot.test(e);for(let t=0;t<e.length;t++){let n=e[t];if(/\w/.test(n)||n>``&&(n.toUpperCase()!=n.toLowerCase()||at.test(n)))return!0}return!1}function ct(e){return t=>{if(!/\S/.test(t))return k.Space;if(st(t))return k.Word;for(let n=0;n<e.length;n++)if(t.indexOf(e[n])>-1)return k.Word;return k.Other}}var A=class e{constructor(e,t,n,r,i,a){this.config=e,this.doc=t,this.selection=n,this.values=r,this.status=e.statusTemplate.slice(),this.computeSlot=i,a&&(a._state=this);for(let e=0;e<this.config.dynamicSlots.length;e++)Re(this,e<<1);this.computeSlot=null}field(e,t=!0){let n=this.config.address[e.id];if(n==null){if(t)throw RangeError(`Field is not present in this state`);return}return Re(this,n),ze(this,n)}update(...e){return et(this,e,!0)}applyTransaction(t){let n=this.config,{base:r,compartments:i}=n;for(let e of t.effects)e.is(Pe.reconfigure)?(n&&=(i=new Map,n.compartments.forEach((e,t)=>i.set(t,e)),null),i.set(e.value.compartment,e.value.extension)):e.is(O.reconfigure)?(n=null,r=e.value):e.is(O.appendConfig)&&(n=null,r=it(r).concat(e.value));let a;n?a=t.startState.values.slice():(n=Ie.resolve(r,i,this),a=new e(n,this.doc,this.selection,n.dynamicSlots.map(()=>null),(e,t)=>t.reconfigure(e,this),null).values);let o=t.startState.facet(Ve)?t.newSelection:t.newSelection.asSingle();new e(n,t.newDoc,o,a,(e,n)=>n.update(e,t),t)}replaceSelection(e){return typeof e==`string`&&(e=this.toText(e)),this.changeByRange(t=>({changes:{from:t.from,to:t.to,insert:e},range:E.cursor(t.from+e.length)}))}changeByRange(e){let t=this.selection,n=e(t.ranges[0]),r=this.changes(n.changes),i=[n.range],a=it(n.effects);for(let n=1;n<t.ranges.length;n++){let o=e(t.ranges[n]),s=this.changes(o.changes),c=s.map(r);for(let e=0;e<n;e++)i[e]=i[e].map(c);let l=r.mapDesc(s,!0);i.push(o.range.map(l)),r=r.compose(c),a=O.mapEffects(a,c).concat(O.mapEffects(it(o.effects),l))}return{changes:r,selection:E.create(i,t.mainIndex),effects:a}}changes(t=[]){return t instanceof pe?t:pe.of(t,this.doc.length,this.facet(e.lineSeparator))}toText(t){return y.of(t.split(this.facet(e.lineSeparator)||ue))}sliceDoc(e=0,t=this.doc.length){return this.doc.sliceString(e,t,this.lineBreak)}facet(e){let t=this.config.address[e.id];return t==null?e.default:(Re(this,t),ze(this,t))}toJSON(e){let t={doc:this.sliceDoc(),selection:this.selection.toJSON()};if(e)for(let n in e){let r=e[n];r instanceof ke&&this.config.address[r.id]!=null&&(t[n]=r.spec.toJSON(this.field(e[n]),this))}return t}static fromJSON(t,n={},r){if(!t||typeof t.doc!=`string`)throw RangeError(`Invalid JSON representation for EditorState`);let i=[];if(r){for(let e in r)if(Object.prototype.hasOwnProperty.call(t,e)){let n=r[e],a=t[e];i.push(n.init(e=>n.spec.fromJSON(a,e)))}}return e.create({doc:t.doc,selection:E.fromJSON(t.selection),extensions:n.extensions?i.concat([n.extensions]):i})}static create(t={}){let n=Ie.resolve(t.extensions||[],new Map),r=t.doc instanceof y?t.doc:y.of((t.doc||``).split(n.staticFacet(e.lineSeparator)||ue)),i=t.selection?t.selection instanceof E?t.selection:E.single(t.selection.anchor,t.selection.head):E.single(0);return xe(i,r.length),n.staticFacet(Ve)||(i=i.asSingle()),new e(n,r,i,n.dynamicSlots.map(()=>null),(e,t)=>t.create(e),null)}get tabSize(){return this.facet(e.tabSize)}get lineBreak(){return this.facet(e.lineSeparator)||`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i as e}from"./chunk-62oNxeRG.js";import{n as t,t as n}from"./jsx-runtime-ZP0XCtgh.js";import{d as r,f as i,u as a}from"./index-BbAx7IhR.js";import{CodeEditor as o}from"./CodeEditor-ROIg2ylA.js";var s=e(t(),1),c=n();function l(){try{if(localStorage.getItem(`loopat:editor:wordWrap`)===`0`)return!1}catch{}return!0}function u({loopId:e,path:t,onSelectionChange:n}){let[u,d]=(0,s.useState)(``),[f,p]=(0,s.useState)(``),[m,h]=(0,s.useState)(!1),[g,_]=(0,s.useState)(!1),[v,y]=(0,s.useState)(l);(0,s.useEffect)(()=>{if(!t){d(``),p(``);return}h(!0),a(e,t).then(e=>{let t=e?.content??``;d(t),p(t)}).finally(()=>h(!1))},[e,t]);let b=t&&f!==u,x=async()=>{if(!(!t||g)){_(!0);try{await r(e,t,f)&&d(f)}finally{_(!1)}}};return t?(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(`div`,{className:`flex-1 min-h-0 relative`,onKeyDown:e=>{(e.metaKey||e.ctrlKey)&&e.key===`s`&&(e.preventDefault(),x())},children:m?(0,c.jsx)(`div`,{className:`h-full w-full flex items-center justify-center text-[12px] text-gray-400`,children:`loading…`}):(0,c.jsx)(o,{path:t,value:f,onChange:p,wordWrap:v,onSelectionChange:n})}),(0,c.jsxs)(`div`,{className:`border-t border-gray-200 px-3 py-1.5 text-[11px] text-gray-500 flex items-center gap-3`,children:[(0,c.jsx)(`span`,{className:`truncate`,children:t}),b&&(0,c.jsx)(`button`,{onClick:x,className:`text-orange-600 hover:underline`,title:`ctrl/⌘+S`,children:g?`saving…`:`unsaved · save`}),(0,c.jsx)(`span`,{className:`flex-1`}),(0,c.jsx)(`button`,{onClick:()=>{let e=!v;y(e);try{localStorage.setItem(`loopat:editor:wordWrap`,e?`1`:`0`)}catch{}},className:`flex items-center gap-1 hover:text-gray-700 transition-colors ${v?`text-gray-500`:`text-gray-300`}`,title:v?`word wrap: on`:`word wrap: off`,children:(0,c.jsx)(i,{size:13})}),(0,c.jsx)(`span`,{children:`utf-8 · LF`})]})]}):(0,c.jsx)(`div`,{className:`flex-1 min-h-0 flex items-center justify-center text-[13px] text-gray-500 px-8 text-center`,children:`没打开文件 · 在 ▤ workdir 里点一个`})}export{u as Editor};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|