@hienlh/ppm 0.5.1 → 0.5.3
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/CHANGELOG.md +40 -0
- package/dist/web/assets/{api-client-ANLU-Irq.js → api-client-BxCvlogn.js} +1 -1
- package/dist/web/assets/chat-tab-AwRs7rWS.js +7 -0
- package/dist/web/assets/code-editor-BviTme00.js +1 -0
- package/dist/web/assets/diff-viewer-CCZM_VBl.js +4 -0
- package/dist/web/assets/git-graph-UCZZ6fX6.js +1 -0
- package/dist/web/assets/index-BxHR8fUA.css +2 -0
- package/dist/web/assets/index-yvVRZ65D.js +21 -0
- package/dist/web/assets/{input-D-F4ITU0.js → input-Bzyi1GeB.js} +1 -1
- package/dist/web/assets/{jsx-runtime-B4BJKQ1u.js → jsx-runtime-Bzk8w7Zh.js} +1 -1
- package/dist/web/assets/markdown-renderer-DzVh1Ft8.js +59 -0
- package/dist/web/assets/{rotate-ccw-BesidNnx.js → rotate-ccw-ZqeedZLA.js} +1 -1
- package/dist/web/assets/settings-store-DikslxSJ.js +1 -0
- package/dist/web/assets/settings-tab-C-AGuxll.js +1 -0
- package/dist/web/assets/tab-store-BNgVKR5w.js +1 -0
- package/dist/web/assets/terminal-tab-CnbdkUFt.js +36 -0
- package/dist/web/assets/{use-monaco-theme-CsNwoeyj.js → use-monaco-theme-BFv4d2_j.js} +2 -2
- package/dist/web/assets/{utils-bntUtdc7.js → utils-EM9hC5pN.js} +1 -1
- package/dist/web/index.html +8 -9
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/cli/commands/init.ts +2 -2
- package/src/cli/commands/status.ts +66 -1
- package/src/cli/commands/stop.ts +39 -2
- package/src/index.ts +4 -2
- package/src/providers/claude-agent-sdk.ts +30 -21
- package/src/server/helpers/resolve-project.ts +2 -2
- package/src/server/index.ts +2 -1
- package/src/server/routes/chat.ts +1 -0
- package/src/server/ws/chat.ts +4 -2
- package/src/services/claude-usage.service.ts +34 -0
- package/src/services/config.service.ts +11 -1
- package/src/types/api.ts +1 -2
- package/src/types/chat.ts +1 -2
- package/src/web/components/chat/attachment-chips.tsx +1 -1
- package/src/web/components/chat/chat-history-bar.tsx +7 -3
- package/src/web/components/chat/chat-tab.tsx +4 -2
- package/src/web/components/chat/message-input.tsx +13 -14
- package/src/web/components/chat/message-list.tsx +5 -4
- package/src/web/components/chat/tool-cards.tsx +3 -6
- package/src/web/components/editor/code-editor.tsx +2 -1
- package/src/web/components/editor/diff-viewer.tsx +43 -22
- package/src/web/components/explorer/file-tree.tsx +3 -3
- package/src/web/components/git/git-graph.tsx +2 -1
- package/src/web/components/git/git-status-panel.tsx +166 -89
- package/src/web/components/layout/command-palette.tsx +2 -1
- package/src/web/components/layout/mobile-drawer.tsx +2 -2
- package/src/web/components/layout/mobile-nav.tsx +1 -1
- package/src/web/components/layout/panel-layout.tsx +16 -16
- package/src/web/components/layout/split-drop-overlay.tsx +3 -3
- package/src/web/components/shared/markdown-renderer.tsx +16 -10
- package/src/web/hooks/use-chat.ts +10 -17
- package/src/web/hooks/use-terminal.ts +66 -23
- package/src/web/hooks/use-usage.ts +1 -14
- package/src/web/lib/utils.ts +5 -0
- package/src/web/stores/panel-store.ts +15 -14
- package/src/web/stores/panel-utils.ts +12 -10
- package/src/web/stores/settings-store.ts +1 -1
- package/dist/web/assets/chat-tab-d_HzPDhE.js +0 -7
- package/dist/web/assets/code-editor-DFAu3knd.js +0 -1
- package/dist/web/assets/diff-viewer-Bue0mOJY.js +0 -4
- package/dist/web/assets/git-graph-Cjq-lK5h.js +0 -1
- package/dist/web/assets/index-D_IIxtVN.js +0 -21
- package/dist/web/assets/index-DhsWierF.css +0 -2
- package/dist/web/assets/markdown-renderer-B9l76G5h.js +0 -59
- package/dist/web/assets/react-WvgCEYPV.js +0 -1
- package/dist/web/assets/settings-store-CGtTcr8r.js +0 -1
- package/dist/web/assets/settings-tab-BDPgdHPI.js +0 -1
- package/dist/web/assets/tab-store-Dq1kMOkJ.js +0 -1
- package/dist/web/assets/terminal-tab-BEOvTEai.js +0 -36
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e,o as t,r as n,t as r}from"./jsx-runtime-B4BJKQ1u.js";import{t as i}from"./input-D-F4ITU0.js";import{a,t as o}from"./tab-store-Dq1kMOkJ.js";import{t as s}from"./rotate-ccw-BesidNnx.js";import{i as c,t as l}from"./api-client-ANLU-Irq.js";import{E as u,I as d,S as f,T as p,a as m,c as h,d as g,f as _,i as ee,j as te,l as v,m as y,o as ne,p as b,s as re,u as x,w as ie,y as S}from"./index-D_IIxtVN.js";var ae=e(`cherry`,[[`path`,{d:`M2 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z`,key:`cvxqlc`}],[`path`,{d:`M12 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z`,key:`1ostrc`}],[`path`,{d:`M7 14c3.22-2.91 4.29-8.75 5-12 1.66 2.38 4.94 9 5 12`,key:`hqx58h`}],[`path`,{d:`M22 9c-4.29 0-7.14-2.33-10-7 5.71 0 10 4.67 10 7Z`,key:`eykp1o`}]]),C=e(`git-merge`,[[`circle`,{cx:`18`,cy:`18`,r:`3`,key:`1xkwt0`}],[`circle`,{cx:`6`,cy:`6`,r:`3`,key:`1lh9wr`}],[`path`,{d:`M6 21V9a9 9 0 0 0 9 9`,key:`7kw0sc`}]]),w=e(`tag`,[[`path`,{d:`M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z`,key:`vktsd0`}],[`circle`,{cx:`7.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`kqv944`}]]),T=t(n(),1),E=r(),D=[`#4fc3f7`,`#81c784`,`#ffb74d`,`#e57373`,`#ba68c8`,`#4dd0e1`,`#aed581`,`#ff8a65`,`#f06292`,`#7986cb`],O=32,k=20,oe=5;function A({metadata:e}){let t=e?.projectName,[n,r]=(0,T.useState)(null),[a,d]=(0,T.useState)(!0),[S,C]=(0,T.useState)(null),[A,j]=(0,T.useState)(!1),[M,N]=(0,T.useState)({type:null}),[P,F]=(0,T.useState)(``),[I,L]=(0,T.useState)(null),[R,z]=(0,T.useState)([]),[ce,B]=(0,T.useState)(!1),{openTab:V}=o(),H=(0,T.useCallback)(async()=>{if(t)try{d(!0),r(await l.get(`${c(t)}/git/graph?max=200`)),C(null)}catch(e){C(e instanceof Error?e.message:`Failed to fetch graph`)}finally{d(!1)}},[t]);(0,T.useEffect)(()=>{H();let e=setInterval(H,1e4);return()=>clearInterval(e)},[H]);let U=async(e,n)=>{if(t){j(!0);try{await l.post(`${c(t)}${e}`,n),await H()}catch(e){C(e instanceof Error?e.message:`Action failed`)}finally{j(!1)}}},W=e=>U(`/git/checkout`,{ref:e}),le=e=>U(`/git/cherry-pick`,{hash:e}),ue=e=>U(`/git/revert`,{hash:e}),de=e=>U(`/git/merge`,{source:e}),fe=e=>U(`/git/branch/delete`,{name:e}),pe=e=>U(`/git/push`,{branch:e}),G=async(e,t)=>{if(n?.branches.some(t=>t.name===e||t.name.endsWith(`/${e}`))){if(!window.confirm(`Branch "${e}" already exists.\nDelete it and recreate from this commit?`))return;await U(`/git/branch/delete`,{name:e})}await U(`/git/branch/create`,{name:e,from:t})},K=(e,t)=>U(`/git/tag`,{name:e,hash:t}),me=async e=>{if(t)try{let n=await l.get(`${c(t)}/git/pr-url?branch=${encodeURIComponent(e)}`);n.url&&window.open(n.url,`_blank`)}catch{}},q=e=>{navigator.clipboard.writeText(e)},he=async e=>{if(I?.hash===e.hash){L(null);return}L(e),B(!0);try{let n=e.parents[0]??``,r=n?`ref1=${encodeURIComponent(n)}&`:``,i=await l.get(`${c(t)}/git/diff-stat?${r}ref2=${encodeURIComponent(e.hash)}`);z(Array.isArray(i)?i:[])}catch(e){console.error(`diff-stat error:`,e),z([])}finally{B(!1)}},ge=e=>{let n=e.parents[0];V({type:`git-diff`,title:`Diff ${e.abbreviatedHash}`,closable:!0,metadata:{projectName:t,ref1:n??void 0,ref2:e.hash},projectId:t??null})},{laneMap:J,maxLane:_e}=(0,T.useMemo)(()=>{let e=new Map;if(!n)return{laneMap:e,maxLane:0};let t=0,r=new Map;for(let i of n.commits){let n=r.get(i.hash);n===void 0&&(n=t++),e.set(i.hash,n),r.delete(i.hash);for(let e=0;e<i.parents.length;e++){let a=i.parents[e];r.has(a)||r.set(a,e===0?n:t++)}}return{laneMap:e,maxLane:Math.max(t-1,0)}},[n]),Y=n?.branches.find(e=>e.current),ve=(0,T.useMemo)(()=>{let e=new Map;if(!n)return e;for(let t of n.branches){let n=e.get(t.commitHash)??[];n.push({name:t.name,type:`branch`}),e.set(t.commitHash,n)}for(let t of n.commits)for(let n of t.refs)if(n.startsWith(`tag: `)){let r=n.replace(`tag: `,``),i=e.get(t.hash)??[];i.push({name:r,type:`tag`}),e.set(t.hash,i)}return e},[n]),ye=(0,T.useMemo)(()=>{if(!n)return[];let e=[];for(let t=0;t<n.commits.length;t++){let r=n.commits[t],i=J.get(r.hash)??0,a=D[i%D.length];for(let o of r.parents){let s=n.commits.findIndex(e=>e.hash===o);if(s<0)continue;let c=J.get(o)??0,l=D[c%D.length],u=i*k+k/2,d=t*O+O/2,f=c*k+k/2,p=s*O+O/2,m,h=r.parents.indexOf(o)>0;if(u===f)m=`M ${u} ${d} L ${f} ${p}`;else if(h){let e=d+O;m=`M ${u} ${d} C ${u} ${e} ${f} ${d} ${f} ${e} L ${f} ${p}`}else{let e=p-O;m=`M ${u} ${d} L ${u} ${e} C ${u} ${p} ${f} ${e} ${f} ${p}`}let g=r.parents.indexOf(o)===0?a:l;e.push({d:m,color:g})}}return e},[n,J]);(_e+1)*k+k;let X=(n?.commits.length??0)*O,[Z,be]=(0,T.useState)((typeof window<`u`&&window.innerWidth<768?6:10)*k+k),Q=(0,T.useRef)(!1),$=(0,T.useCallback)(e=>{Q.current=!0;let t=Z,n=n=>{if(!Q.current)return;let r=`touches`in n?n.touches[0].clientX:n.clientX;be(Math.max(40,t+r-e))},r=()=>{Q.current=!1,window.removeEventListener(`mousemove`,n),window.removeEventListener(`mouseup`,r),window.removeEventListener(`touchmove`,n),window.removeEventListener(`touchend`,r)};window.addEventListener(`mousemove`,n),window.addEventListener(`mouseup`,r),window.addEventListener(`touchmove`,n,{passive:!1}),window.addEventListener(`touchend`,r)},[Z]),xe=(0,T.useCallback)(e=>{e.preventDefault(),$(e.clientX)},[$]),Se=(0,T.useCallback)(e=>{$(e.touches[0].clientX)},[$]);if(!t)return(0,E.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground text-sm`,children:`No project selected.`});if(a&&!n)return(0,E.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,E.jsx)(ie,{className:`size-5 animate-spin`}),(0,E.jsx)(`span`,{className:`text-sm`,children:`Loading git graph...`})]});if(S&&!n)return(0,E.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-2 text-destructive text-sm`,children:[(0,E.jsx)(`p`,{children:S}),(0,E.jsx)(v,{variant:`outline`,size:`sm`,onClick:H,children:`Retry`})]});function Ce(e){let t=new Date(e),n=new Date().getTime()-t.getTime(),r=Math.floor(n/6e4);if(r<1)return`just now`;if(r<60)return`${r}m ago`;let i=Math.floor(r/60);if(i<24)return`${i}h ago`;let a=Math.floor(i/24);if(a<30)return`${a}d ago`;let o=Math.floor(a/30);return o<12?`${o}mo ago`:`${Math.floor(o/12)}y ago`}return(0,E.jsxs)(`div`,{className:`flex flex-col h-full`,children:[(0,E.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-2 border-b`,children:[(0,E.jsxs)(`span`,{className:`text-sm font-medium`,children:[`Git Graph`,Y?` - ${Y.name}`:``]}),(0,E.jsx)(v,{variant:`ghost`,size:`icon-xs`,onClick:H,disabled:A,children:(0,E.jsx)(f,{className:a?`animate-spin`:``})})]}),S&&(0,E.jsx)(`div`,{className:`px-3 py-1.5 text-xs text-destructive bg-destructive/10`,children:S}),(0,E.jsx)(`div`,{className:`flex-1 overflow-y-auto overflow-x-auto md:overflow-x-hidden`,children:(0,E.jsxs)(`div`,{className:`flex min-w-max md:min-w-0`,style:{height:`${X}px`},children:[(0,E.jsxs)(`div`,{className:`sticky left-0 z-10 shrink-0 bg-background`,style:{width:`${Z}px`},children:[(0,E.jsxs)(`svg`,{width:Z,height:X,children:[ye.map((e,t)=>(0,E.jsx)(`path`,{d:e.d,stroke:e.color,strokeWidth:2,fill:`none`},t)),n?.commits.map((e,t)=>{let n=J.get(e.hash)??0,r=n*k+k/2,i=t*O+O/2,a=D[n%D.length];return(0,E.jsx)(`circle`,{cx:r,cy:i,r:oe,fill:a,stroke:`#0f1419`,strokeWidth:2},e.hash)})]}),(0,E.jsx)(`div`,{className:`absolute top-0 right-0 w-3 md:w-2 h-full cursor-col-resize hover:bg-primary/20 flex items-center justify-center bg-primary/10 md:bg-transparent`,onMouseDown:xe,onTouchStart:Se,children:(0,E.jsx)(p,{className:`size-3 text-muted-foreground md:opacity-0 md:hover:opacity-100`})})]}),(0,E.jsx)(`div`,{className:`flex-1 min-w-[400px]`,children:n?.commits.map((e,t)=>{let n=D[(J.get(e.hash)??0)%D.length],r=ve.get(e.hash)??[],i=r.filter(e=>e.type===`branch`),a=r.filter(e=>e.type===`tag`);return(0,E.jsxs)(x,{children:[(0,E.jsx)(y,{asChild:!0,children:(0,E.jsx)(`div`,{className:`flex items-center hover:bg-muted/50 cursor-pointer text-sm border-b border-border/30 ${I?.hash===e.hash?`bg-primary/10`:``}`,style:{height:`${O}px`},onClick:()=>he(e),children:(0,E.jsxs)(`div`,{className:`flex items-center gap-2 flex-1 min-w-0 px-2`,children:[(0,E.jsx)(`span`,{className:`font-mono text-xs text-muted-foreground w-14 shrink-0`,children:e.abbreviatedHash}),i.map(e=>(0,E.jsx)(se,{label:e,color:n,currentBranch:Y,onCheckout:W,onMerge:de,onPush:pe,onCreatePr:me,onDelete:fe},`branch-${e.name}`)),a.map(e=>(0,E.jsxs)(`span`,{className:`inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 bg-amber-500/20 text-amber-500 border border-amber-500/30`,children:[(0,E.jsx)(w,{className:`size-2.5`}),e.name]},`tag-${e.name}`)),(0,E.jsx)(`span`,{className:`flex-1 truncate`,children:e.subject}),(0,E.jsx)(`span`,{className:`text-xs text-muted-foreground shrink-0 hidden sm:inline`,children:e.authorName}),(0,E.jsx)(`span`,{className:`text-xs text-muted-foreground shrink-0 w-14 text-right`,children:Ce(e.authorDate)})]})})}),(0,E.jsxs)(g,{children:[(0,E.jsx)(_,{onClick:()=>W(e.hash),children:`Checkout`}),(0,E.jsxs)(_,{onClick:()=>{N({type:`branch`,hash:e.hash}),F(``)},children:[(0,E.jsx)(u,{className:`size-3`}),`Create Branch...`]}),(0,E.jsx)(b,{}),(0,E.jsxs)(_,{onClick:()=>le(e.hash),children:[(0,E.jsx)(ae,{className:`size-3`}),`Cherry Pick`]}),(0,E.jsxs)(_,{onClick:()=>ue(e.hash),children:[(0,E.jsx)(s,{className:`size-3`}),`Revert`]}),(0,E.jsxs)(_,{onClick:()=>{N({type:`tag`,hash:e.hash}),F(``)},children:[(0,E.jsx)(w,{className:`size-3`}),`Create Tag...`]}),(0,E.jsx)(b,{}),(0,E.jsx)(_,{onClick:()=>ge(e),children:`View Diff`}),(0,E.jsxs)(_,{onClick:()=>q(e.hash),children:[(0,E.jsx)(te,{className:`size-3`}),`Copy Hash`]})]})]},e.hash)})})]})}),I&&(0,E.jsxs)(`div`,{className:`border-t bg-muted/30 max-h-[40%] overflow-auto`,children:[(0,E.jsxs)(`div`,{className:`px-3 py-2 border-b flex items-center justify-between`,children:[(0,E.jsxs)(`span`,{className:`text-sm font-medium truncate`,children:[I.abbreviatedHash,` — `,I.subject]}),(0,E.jsx)(v,{variant:`ghost`,size:`icon-xs`,onClick:()=>L(null),children:`✕`})]}),(0,E.jsxs)(`div`,{className:`px-3 py-2 text-xs space-y-1`,children:[(0,E.jsxs)(`div`,{className:`flex gap-4`,children:[(0,E.jsx)(`span`,{className:`text-muted-foreground`,children:`Author`}),(0,E.jsxs)(`span`,{children:[I.authorName,` <`,I.authorEmail,`>`]})]}),(0,E.jsxs)(`div`,{className:`flex gap-4`,children:[(0,E.jsx)(`span`,{className:`text-muted-foreground`,children:`Date`}),(0,E.jsx)(`span`,{children:new Date(I.authorDate).toLocaleString()})]}),(0,E.jsxs)(`div`,{className:`flex gap-4`,children:[(0,E.jsx)(`span`,{className:`text-muted-foreground`,children:`Hash`}),(0,E.jsx)(`span`,{className:`font-mono cursor-pointer hover:text-primary`,onClick:()=>q(I.hash),children:I.hash})]}),I.parents.length>0&&(0,E.jsxs)(`div`,{className:`flex gap-4`,children:[(0,E.jsx)(`span`,{className:`text-muted-foreground`,children:`Parents`}),(0,E.jsx)(`span`,{className:`font-mono`,children:I.parents.map(e=>e.slice(0,7)).join(`, `)})]}),I.body&&(0,E.jsx)(`div`,{className:`mt-2 p-2 bg-background rounded text-xs whitespace-pre-wrap`,children:I.body})]}),(0,E.jsxs)(`div`,{className:`px-3 py-1 border-t`,children:[(0,E.jsx)(`div`,{className:`text-xs text-muted-foreground py-1`,children:ce?`Loading files...`:`${R.length} file${R.length===1?``:`s`} changed`}),R.map(e=>(0,E.jsxs)(`div`,{className:`flex items-center gap-2 py-0.5 text-xs hover:bg-muted/50 rounded px-1 cursor-pointer`,onClick:()=>V({type:`git-diff`,title:`Diff ${e.path.split(`/`).pop()}`,closable:!0,metadata:{projectName:t,ref1:I.parents[0]??void 0,ref2:I.hash,filePath:e.path},projectId:t??null}),children:[(0,E.jsx)(`span`,{className:`flex-1 truncate font-mono`,children:e.path}),e.additions>0&&(0,E.jsxs)(`span`,{className:`text-green-500`,children:[`+`,e.additions]}),e.deletions>0&&(0,E.jsxs)(`span`,{className:`text-red-500`,children:[`-`,e.deletions]})]},e.path))]})]}),(0,E.jsx)(ee,{open:M.type!==null,onOpenChange:e=>{e||N({type:null})},children:(0,E.jsxs)(m,{children:[(0,E.jsx)(re,{children:(0,E.jsx)(h,{children:M.type===`branch`?`Create Branch`:`Create Tag`})}),(0,E.jsx)(i,{placeholder:M.type===`branch`?`Branch name`:`Tag name`,value:P,onChange:e=>F(e.target.value),onKeyDown:e=>{e.key===`Enter`&&P.trim()&&(M.type===`branch`?G(P.trim(),M.hash):K(P.trim(),M.hash),N({type:null}))},autoFocus:!0}),(0,E.jsxs)(ne,{children:[(0,E.jsx)(v,{variant:`outline`,onClick:()=>N({type:null}),children:`Cancel`}),(0,E.jsx)(v,{disabled:!P.trim(),onClick:()=>{M.type===`branch`?G(P.trim(),M.hash):K(P.trim(),M.hash),N({type:null})},children:`Create`})]})]})})]})}function se({label:e,color:t,currentBranch:n,onCheckout:r,onMerge:i,onPush:o,onCreatePr:s,onDelete:c}){return(0,E.jsxs)(x,{children:[(0,E.jsx)(y,{asChild:!0,children:(0,E.jsxs)(`span`,{className:`inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 cursor-context-menu`,style:{backgroundColor:`${t}30`,color:t,border:`1px solid ${t}50`},children:[(0,E.jsx)(u,{className:`size-2.5`}),e.name]})}),(0,E.jsxs)(g,{children:[(0,E.jsx)(_,{onClick:()=>r(e.name),children:`Checkout`}),(0,E.jsxs)(_,{onClick:()=>i(e.name),disabled:e.name===n?.name,children:[(0,E.jsx)(C,{className:`size-3`}),`Merge into current`]}),(0,E.jsx)(b,{}),(0,E.jsxs)(_,{onClick:()=>o(e.name),children:[(0,E.jsx)(d,{className:`size-3`}),`Push`]}),(0,E.jsxs)(_,{onClick:()=>s(e.name),children:[(0,E.jsx)(a,{className:`size-3`}),`Create PR`]}),(0,E.jsx)(b,{}),(0,E.jsxs)(_,{variant:`destructive`,onClick:()=>c(e.name),disabled:e.name===n?.name,children:[(0,E.jsx)(S,{className:`size-3`}),`Delete`]})]})]})}export{A as GitGraph};
|