epiq 1.16.2 → 1.17.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/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- process.env.NODE_ENV||="production";await import("./Index-WQDRC3CF.js");
2
+ import{a as o}from"./chunk-SID42K4V.js";o();process.env.NODE_ENV||="production";await import("./Index-RF3FTG55.js");
package/dist/mcp.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- process.env.EPIQ_MCP="true";process.env.NODE_ENV||="production";console.log=console.error;console.info=console.error;console.debug=console.error;console.warn=console.error;var{applyActorNameArgument:r}=await import("./actor-env-F6R6NBSD.js"),{isFail:s}=await import("./result-types-RM3ASLV3.js"),{parseMcpArgs:t}=await import("./args-6LT6KUMQ.js"),e=t(process.argv.slice(2));e.kind==="error"&&(console.error(e.message),process.exit(1));e.kind==="print"&&(process.stdout.write(e.text),process.exit(0));if(e.kind==="name"){let o=r(e.name,"The name argument");s(o)&&(console.error(o.message),process.exit(1))}var{setVirtualNodesEnabled:n}=await import("./virtual-nodes-W5WJQXFR.js");n(!1);var{startMcpServer:a}=await import("./server-JWNOKRFL.js");await a();
2
+ import{a as r}from"./chunk-SID42K4V.js";r();process.env.EPIQ_MCP="true";process.env.NODE_ENV||="production";console.log=console.error;console.info=console.error;console.debug=console.error;console.warn=console.error;var{applyActorNameArgument:s}=await import("./actor-env-F6R6NBSD.js"),{isFail:t}=await import("./result-types-RM3ASLV3.js"),{parseMcpArgs:n}=await import("./args-2F7WNQYO.js"),e=n(process.argv.slice(2));e.kind==="error"&&(console.error(e.message),process.exit(1));e.kind==="print"&&(process.stdout.write(e.text),process.exit(0));if(e.kind==="name"){let o=s(e.name,"The name argument");t(o)&&(console.error(o.message),process.exit(1))}var{setVirtualNodesEnabled:i}=await import("./virtual-nodes-DSAMVKWE.js");i(!1);var{startMcpServer:a}=await import("./server-NKAY4NNC.js");await a();
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{$ as Te,B as ne,C as se,D as re,E as ae,F as le,G as ce,H as pe,J as de,K as me,L as ue,M as ge,N as he,O as fe,P as we,Q as be,R as _e,S as ye,U as Re,X as Ie,Y as ke,Z as qe,_ as Se,aa as ve,b as A,ba as Ee,ca as je,da as xe,ea as Ae,fa as Ne,ga as Ce,ha as Le,i as F,ia as Pe,ja as Me,k as z,ka as Oe,l as X,la as Ge,m as W,ma as Ue,n as Y,o as J,oa as De,p as Q,pa as $e,q as V,qa as He,r as Z,s as ee,sa as Ke,t as te,u as oe,x as ie}from"./chunk-J3S3W27J.js";import{Sa as I,b as M,ca as $,gc as K,h as O,ja as H,l as G,m as U,x as D,xc as B,yb as k}from"./chunk-5WUPZIRV.js";import{a as P}from"./chunk-WUCQWBQE.js";import{b as p,c,d as l}from"./chunk-5RIQL7P4.js";import{McpServer as gt}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ht}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as ft}from"@modelcontextprotocol/sdk/types.js";import{z as e}from"zod";var ct=t=>{let o=new Map,n=[t.nodes,t.contributors,t.tags,t.goals,t.comments,t.attachments];for(let s of n)for(let r of Object.keys(s??{})){let a=I(r),d=o.get(a);d?d.includes(r)||d.push(r):o.set(a,[r])}return{byRef:o,goals:new Set(Object.keys(t.goals??{}))}},Be=()=>{let t=B();return l(t)?null:ct(t.value)},Fe="goal",ze=t=>/(Id|Ids)$/.test(t)||t==="sibling"||t==="idOrRef"||t===Fe,S=t=>typeof t=="string"&&t.length===7,T=t=>Array.isArray(t)?t.some(T):!t||typeof t!="object"?!1:Object.entries(t).some(([o,n])=>ze(o)?S(n)||Array.isArray(n)&&n.some(S):T(n)),Xe=(t,o,n=!1)=>{let s=(t.byRef.get(o.toUpperCase())??[]).filter(r=>!n||t.goals.has(r));return s.length>1?p(`Ref ${o} is ambiguous: use one of ${s.join(", ")}`):c("Resolved ref",s[0]??o)},We=(t,o)=>{let n=[];for(let s of t){let r=o(s);if(l(r))return r;n.push(r.value)}return c("Expanded",n)},Ye=(t,o)=>S(o)?Xe(t,o):Array.isArray(o)?We(o,n=>Ye(t,n)):c("Kept",o),N=(t,o)=>{if(Array.isArray(o))return We(o,s=>N(t,s));if(!o||typeof o!="object")return c("Kept",o);let n={};for(let[s,r]of Object.entries(o)){let a=s===Fe&&S(r)?Xe(t,r,!0):ze(s)?Ye(t,r):N(t,r);if(l(a))return a;n[s]=a.value}return c("Expanded",n)},Je=t=>{let o=Be();return o?N(o,t):c("No state to resolve against",t)},q=(t,o)=>{if(typeof o=="string"){let s=t.byRef.get(I(o));return s?.length===1&&s[0]===o?I(o):o}if(Array.isArray(o))return o.map(s=>q(t,s));if(!o||typeof o!="object")return o;let n={};for(let[s,r]of Object.entries(o))n[q(t,s)]=q(t,r);return typeof n.id=="string"&&n.id===n.ref&&delete n.ref,n},Qe=t=>{let o=Be();return o?q(o,t):t};import{ulid as pt}from"ulid";var Ve=[{name:"userName",question:"the name they want to appear under on the board"},{name:"preferredEditor",question:'the command that opens a file for editing, e.g. "vim", "nano" or "code --wait"'},{name:"autoSync",question:"whether the TUI and GUI should sync with the git remote on their own (true or false)"}],Ze=t=>t.trim().toLowerCase(),et=async(t={})=>{let o=G();if(l(o))return p(o.message);let n=o.value,s=t.userName?.trim(),r=(process.env[P]??"").trim();if(s&&r&&Ze(s)===Ze(r))return p(`userName "${s}" is this agent's own identity. Pass the user's name, the one they want the TUI and GUI to write as.`);let a={userName:s||void 0,preferredEditor:t.preferredEditor?.trim()||void 0,autoSync:t.autoSync},d={userName:n.userName?.trim()||void 0,preferredEditor:n.preferredEditor?.trim()||void 0,autoSync:typeof n.autoSync=="boolean"?n.autoSync:void 0};for(let m of Ve){let g=a[m.name],b=d[m.name];if(!(g===void 0||b===void 0||g===b))return p(`This machine is already set up with ${m.name} ${JSON.stringify(b)}; epiq_project_init does not change it. Call again without ${m.name}, or let the user change it in the TUI with :config.`)}let u={};if(a.userName&&!d.userName&&(u.userName=a.userName),a.preferredEditor&&!d.preferredEditor&&(u.preferredEditor=a.preferredEditor),a.autoSync!==void 0&&d.autoSync===void 0&&(u.autoSync=a.autoSync),(u.userName||d.userName)&&!n.userId&&(u.userId=pt()),Object.keys(u).length>0){let m=U(u);if(l(m))return p(m.message)}let E={...n,...u},j=Ve.filter(m=>{let g=E[m.name];return typeof g=="boolean"?!1:!g?.trim()});if(j.length>0){let m=j.map(R=>`${R.name} (${R.question})`).join("; "),g=j.some(R=>R.name==="userName")?await F(t.repoRoot??process.cwd()):null,b=g?` This repository's git user.name is ${JSON.stringify(g)} \u2014 offer it as the default for userName, and use it only if the user agrees.`:"";return p(`Setup incomplete. Ask the user for: ${m}.${b} Then call epiq_project_init again with the answers; what was given so far is saved.`)}let _={userId:E.userId??"",userName:E.userName??""};if(!_.userId||!_.userName)return p("Missing Epiq user id");let x=await K({cwd:t.repoRoot??process.cwd(),user:_});if(l(x))return x;let{defaultEvents:It,...y}=x.value,L=$({root:y.repoRoot});l(L)&&M.info(L.message);let at=y.warnings.length===0?"Project initialized":`Project initialized, with warnings: ${y.warnings.join(" ")}`;return c(at,{...y,user:_})};import v from"node:fs";import tt from"node:path";var h=".claude/skills/epiq/SKILL.md",C="---\nname: epiq\ndescription: Workflow for working an epiq issue board through the epiq MCP \u2014 take an identity, sync on demand, keep tickets small and their status, tags and comments current.\n---\n\n# Epiq board workflow\n\n- **Use the `epiq_*` MCP tools** for every board read and write \u2014 never the CLI, hand edits, or the state branch directly.\n- **Never touch the state branch** \u2014 no checkout, no edits to its files by hand or by script. The event log is the system of record; a manual edit can corrupt history for good.\n- Move tickets along the board with correct status\n- **The MCP never syncs on its own:** call `epiq_sync` to pull or publish.\n- **Take an identity before your first write** with `epiq_actor_assume`, e.g. `claude/peter`. Reuse one from `epiq_contributor_list` \u2014 names are permanent. If other agents may be running, ask which name is free, rather than just picking one.\n- **One MCP server per session** \u2014 the name binds to the process; a restart forgets it. Every session needs a server process of its own, so agents and the human stay apart.\n- **Keep tickets reasonably scoped**; put a small follow-up on its existing ticket instead of a new one.\n- **Read a ticket's comments before starting.**\n- **Board and lane descriptions describe team conventions** (`boardDescription`, `laneDescription` in responses): follow them unless they conflict with your user's instructions.\n- **Picking up a ticket:** assign yourself and move it to the in-progress lane. Ask before taking one assigned to someone else.\n- **Keep status, tags and description current**; reuse existing tags.\n- **Goals group tickets that serve one outcome, on any board.** Put a ticket in one with `epiq_issue_goal_set` (`goal`: its name, created if new, or null to take it out). A ticket has at most one goal; `epiq_goal_list` shows every goal and its progress. A ticket counts as done for its goal once closed, or in a lane named Done or marked done (`epiq_swimlane_rule_set`). A goal closes once all its tickets are closed, and reopens with any of them.\n- **Start bigger work with a goal.** Before a feature or larger task, create a goal (`epiq_goal_create`), describe its intent and expectations in a few lines, not its implementation (`epiq_goal_description_edit`), then break the work into tickets in that goal.\n- **Comment** on deviations, blockers, and the outcome when done.\n- **Prompt user for closing tickets and big decisions**\n- **Park decisions that are the user's to make.** A design fork, a change with blast radius beyond its ticket, anything you'd want a second opinion on \u2014 file a ticket tagged `human-input-needed` naming the options and the trade-offs, then carry on with whatever doesn't depend on the answer.\n- **Link commits** with an `Epiq-Ref: <ref>` line in the commit body. The MCP names every ticket by its 7-character ref; use that. Several tickets go on one line, comma-separated. A subject starting with the ref also links, but puts the ref in everyone's history.\n- *If you decide to take a new approach*, add a `fork` tag, and document the fork in a comment, also, update the description to reflect the new plan.\n- **Attachments are permanent** in every clone (max 500 KB): use with moderation, make sure images are cropped and compressed. An oversized image is permanent, for everybody.\n- **Be concise, use clear language.** Titles, descriptions, and comments should be scannable, easy to understand, not essays.\n- **When a ticket is done, Document the outcome in a comment starting with \"Solution:\"**. Use that exact prefix as the comment's opening line.\n- **The git user is the sole author of every commit you write.** Commit under the repo's configured user.name / user.email and nothing else: no Co-Authored-By: line, no Claude-Session: line, no \u{1F916} Generated with footer, and never an --author override naming Claude or a tool.\n- On rebase or merge, make sure Epiq refs survive. Dropping them from commit messages breaks commit linking.\n";var ot=async(t={})=>{let o=await O(t.repoRoot??process.cwd());if(l(o))return p(o.message);let n=tt.join(o.value,h);if(v.existsSync(n)){if(v.readFileSync(n,"utf8")===C)return c("Skill already installed and up to date",{path:n,written:!1});if(!t.force)return p(`${h} already exists and differs from the bundled skill. Pass force:true to overwrite it.`)}try{v.mkdirSync(tt.dirname(n),{recursive:!0}),v.writeFileSync(n,C,"utf8")}catch(s){return p(s instanceof Error?`Failed to write ${h}: ${s.message}`:`Failed to write ${h}`)}return c(`Installed skill at ${h}`,{path:n,written:!0})};var w=t=>({isError:l(t),content:[{type:"text",text:JSON.stringify(l(t)?{message:t.message}:{message:t.message,value:Qe(t.value)})}]}),wt=async t=>{if(!T(t))return c("No refs",t);let o=await z(t.repoRoot);return l(o)?c("Unbooted",t):Je(t)},i=t=>o=>A(async()=>{let n=await wt(o);return w(l(n)?n:await t(n.value))}),bt="A board's or lane's description (boardDescription, laneDescription) holds the team's conventions for it; follow them unless they conflict with your user's instructions. Replies name nodes by their 7-character ref, and every id input takes one.",_t=t=>({...t,tools:t.tools.map(({inputSchema:{$schema:o,...n},...s})=>({...s,inputSchema:n}))}),yt=t=>{let o=t.server,n=o.setRequestHandler.bind(o);o.setRequestHandler=((s,r)=>n(s,s===ft?async(a,d)=>_t(await r(a,d)):r))},Rt=()=>{let t=new gt({name:"epiq",version:"0.2.30"},{instructions:bt});return yt(t),t.registerTool("epiq_state_get",{description:"Get the full current Epiq state, including nodes and event log",inputSchema:e.object({repoRoot:e.string().optional()})},i(ie)),t.registerTool("epiq_issue_list",{description:"List Epiq issues: id, title, swimlane title and id, goal, tag and assignee names. Narrow with boardId, swimlaneId, tag, assignee or query (matched against title and description). brief: false adds descriptions and the rest; read one ticket with epiq_issue_get instead.",inputSchema:e.object({repoRoot:e.string().optional(),includeClosed:e.boolean().optional(),boardId:e.string().optional(),swimlaneId:e.string().optional(),tag:e.string().optional(),assignee:e.string().optional(),query:e.string().optional(),brief:e.boolean().optional()})},i(o=>re({...o,brief:o.brief??!0}))),t.registerTool("epiq_issue_get",{description:"Get one Epiq issue by ref or id, with its comments in log order: the latest three whole, earlier ones by their first three lines (olderComments says so); allComments returns every one whole. When you know which issue you want, prefer it to listing the board or epiq_state_get.",inputSchema:e.object({idOrRef:e.string().min(1),allComments:e.boolean().optional(),repoRoot:e.string().optional()})},i(se)),t.registerTool("epiq_issue_stats",{description:"Code stats for one issue, from the commits whose subject carries its ref: how big the change is, how it is spread, and how much rewrote its own work. Reads git only, so it is safe while other work is in flight. No such commits reads as zero size, which is not the same as no work: a commit only links if its subject opens with the ref.",inputSchema:e.object({idOrRef:e.string().min(1),repoRoot:e.string().optional()})},async o=>w(await Re(o))),t.registerTool("epiq_board_list",{description:"List Epiq boards",inputSchema:e.object({repoRoot:e.string().optional()})},i(ue)),t.registerTool("epiq_board_create",{description:"Create an Epiq board. It comes out without swimlanes; add them with epiq_swimlane_create. Refused while time travelling.",inputSchema:e.object({repoRoot:e.string().optional(),title:e.string().min(1).max(300)})},i(ge)),t.registerTool("epiq_swimlane_list",{description:"List Epiq swimlanes",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().optional()})},i(fe)),t.registerTool("epiq_swimlane_create",{description:"Create an Epiq swimlane on a board",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().min(1),title:e.string().min(1).max(300)})},i(we)),t.registerTool("epiq_swimlane_title_edit",{description:"Edit an Epiq swimlane title",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),title:e.string().min(1).max(300)})},i(be)),t.registerTool("epiq_board_title_edit",{description:"Edit an Epiq board title. Refused while time travelling.",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().min(1),title:e.string().min(1).max(300)})},i(he)),t.registerTool("epiq_swimlane_description_edit",{description:"Edit a swimlane's markdown description: the conventions for work in that lane. Empty clears it.",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),description:e.string().max(k("swimlane"))})},i(W)),t.registerTool("epiq_swimlane_rule_set",{description:"Turn a swimlane rule on or off. The one rule is `done`: an open ticket in a lane with it counts as done for its goal. A lane named Done always counts.",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),rule:e.enum(H),on:e.boolean()})},i(te)),t.registerTool("epiq_board_description_edit",{description:"Edit a board's markdown description: the conventions for work on that board. Empty clears it. Refused while time travelling.",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().min(1),description:e.string().max(k("board"))})},i(X)),t.registerTool("epiq_swimlane_move",{description:"Move/reorder an Epiq swimlane, optionally to another board",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),boardId:e.string().min(1),position:e.discriminatedUnion("at",[e.object({at:e.literal("start")}),e.object({at:e.literal("end")}),e.object({at:e.literal("before"),sibling:e.string().min(1)}),e.object({at:e.literal("after"),sibling:e.string().min(1)})]).optional()})},i(_e)),t.registerTool("epiq_swimlane_delete",{description:"Delete (archive) an Epiq swimlane. This also archives every issue contained in it.",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1)})},i(ye)),t.registerTool("epiq_issue_create",{description:"Create an Epiq issue in a swimlane: parentId is the swimlane id (epiq_swimlane_list), never a board id. Optionally set description, tags, and assignees atomically in the same call instead of separate follow-up edits.",inputSchema:e.object({title:e.string().min(1).max(300),parentId:e.string().min(1),repoRoot:e.string().optional(),description:e.string().max(2e4).optional(),tagNames:e.array(e.string().max(60)).max(20).optional(),assigneeNames:e.array(e.string().max(80)).max(20).optional()})},i(ae)),t.registerTool("epiq_issue_description_edit",{description:"Edit the markdown description of an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),description:e.string().max(2e4),repoRoot:e.string().optional()})},i(de)),t.registerTool("epiq_issue_title_edit",{description:"Edit the title of an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),title:e.string().min(1).max(300),repoRoot:e.string().optional()})},i(me)),t.registerTool("epiq_issue_tag_add",{description:"Add a tag to an Epiq issue, or to many at once with issueIds, creating the tag if it does not exist",inputSchema:e.object({issueId:e.string().min(1).optional(),issueIds:e.array(e.string().min(1)).min(1).max(200).optional(),tagName:e.string().min(1).max(60),repoRoot:e.string().optional()})},i(ke)),t.registerTool("epiq_issue_goal_set",{description:"Put an Epiq issue in a goal (a group of tickets serving one outcome, on any board), by the goal's name or id, creating the goal if there is none of that name; null takes it out. One goal per ticket. epiq_goal_list lists every goal with its progress.",inputSchema:e.object({issueId:e.string().min(1),goal:e.string().min(1).nullable(),repoRoot:e.string().optional()})},i(Q)),t.registerTool("epiq_goal_list",{description:"List the goals in the project: each a group of tickets serving one outcome, with done/total (done is closed, or in a done lane: see epiq_swimlane_rule_set), closed once all its tickets are, and the boards its tickets are on. Open goals first. Put a ticket in one with epiq_issue_goal_set; epiq_goal_stats gives its code stats.",inputSchema:e.object({repoRoot:e.string().optional()})},i(J)),t.registerTool("epiq_goal_stats",{description:"Code stats for a goal, by its name or id: epiq_issue_stats over every commit of every ticket in it, each commit counted once. Its `ref` field holds the goal id, not a ticket ref.",inputSchema:e.object({goal:e.string().min(1),repoRoot:e.string().optional()})},i(Ie)),t.registerTool("epiq_goal_create",{description:"Create a goal with no tickets yet. To create one and put a ticket in it at once, use epiq_issue_goal_set with the new name.",inputSchema:e.object({name:e.string().min(1),repoRoot:e.string().optional()})},i(V)),t.registerTool("epiq_goal_rename",{description:"Rename a goal; its colour and tickets stay. Ids are in epiq_goal_list.",inputSchema:e.object({goalId:e.string().min(1),name:e.string().min(1),repoRoot:e.string().optional()})},i(Z)),t.registerTool("epiq_goal_description_edit",{description:"Edit a goal's markdown description: what it is for and when it is done. Empty clears it. epiq_goal_list returns it.",inputSchema:e.object({goal:e.string().min(1),description:e.string().max(k("goal")),repoRoot:e.string().optional()})},i(Y)),t.registerTool("epiq_goal_remove",{description:"Remove a goal; its tickets stay where they are, in no goal. Ids are in epiq_goal_list.",inputSchema:e.object({goalId:e.string().min(1),repoRoot:e.string().optional()})},i(ee)),t.registerTool("epiq_issue_tag_remove",{description:"Remove a tag from an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),tagId:e.string().min(1),repoRoot:e.string().optional()})},i(Te)),t.registerTool("epiq_contributor_list",{description:"List people who can be assigned: everyone who has authored an event (optionally scoped to one board) unioned with the contributor registry. Use these ids with epiq_issue_assignee_add rather than assigning by name.",inputSchema:e.object({boardId:e.string().min(1).optional(),repoRoot:e.string().optional()})},i(Ae)),t.registerTool("epiq_contributor_remove",{description:"Remove an external contributor from the assignee suggestion lists. Their id and every reference to it survive, so existing assignments stay intact and the event log is untouched. Refused for anyone who has authored events, since their name is in the log regardless.",inputSchema:e.object({contributorId:e.string().min(1),repoRoot:e.string().optional()})},i(je)),t.registerTool("epiq_tag_remove",{description:"Delete a tag from the whole workspace. It disappears from every ticket, picker and filter, but its id and history stay in the event log, and the name is free for a new tag.",inputSchema:e.object({tagId:e.string().min(1),repoRoot:e.string().optional()})},i(qe)),t.registerTool("epiq_tag_restore",{description:"Put back a tag deleted with epiq_tag_remove, on every ticket that still carried it.",inputSchema:e.object({tagId:e.string().min(1),repoRoot:e.string().optional()})},i(Se)),t.registerTool("epiq_contributor_restore",{description:"Put a contributor removed with epiq_contributor_remove back into the suggestion lists, under the name they were created with.",inputSchema:e.object({contributorId:e.string().min(1),repoRoot:e.string().optional()})},i(xe)),t.registerTool("epiq_issue_assignee_add",{description:"Assign a contributor to an Epiq issue: self: true for yourself, else preferably assigneeId (epiq_contributor_list; fails if unknown). assigneeName matches an existing contributor by name; with createUnlinked it adds someone new as an external assignee.",inputSchema:e.object({issueId:e.string().min(1),assigneeId:e.string().min(1).optional(),self:e.boolean().optional(),assigneeName:e.string().min(1).max(80).optional(),createUnlinked:e.boolean().optional(),repoRoot:e.string().optional()})},i(Ee)),t.registerTool("epiq_issue_assignee_remove",{description:"Remove an assignee from an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),assigneeId:e.string().min(1),repoRoot:e.string().optional()})},i(Ne)),t.registerTool("epiq_issue_comment_add",{description:"Add a comment to an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),body:e.string().min(1).max(4e3),repoRoot:e.string().optional()})},i(Ce)),t.registerTool("epiq_issue_attachment_add",{description:"Attach an image file to an Epiq issue. Returns `markdown` \u2014 paste it into a comment or description body to render the image inline.",inputSchema:e.object({issueId:e.string().min(1),filePath:e.string().min(1),name:e.string().min(1).max(200).optional(),repoRoot:e.string().optional()})},i(oe)),t.registerTool("epiq_issue_comment_delete",{description:"Delete a comment from an Epiq issue",inputSchema:e.object({commentId:e.string().min(1),repoRoot:e.string().optional()})},i(Le)),t.registerTool("epiq_issue_comment_edit",{description:"Replace the body of one of your own comments on an Epiq issue",inputSchema:e.object({commentId:e.string().min(1),body:e.string().min(1).max(4e3),repoRoot:e.string().optional()})},i(Pe)),t.registerTool("epiq_issue_reopen",{description:"Reopen a closed Epiq issue, restoring it to its previous swimlane",inputSchema:e.object({issueId:e.string().min(1),repoRoot:e.string().optional()})},i(ce)),t.registerTool("epiq_issue_close",{description:"Close an Epiq issue, or many at once with issueIds \u2014 one call, an outcome per ticket",inputSchema:e.object({issueId:e.string().min(1).optional(),issueIds:e.array(e.string().min(1)).min(1).max(200).optional(),repoRoot:e.string().optional()})},i(le)),t.registerTool("epiq_issue_move",{description:"Move an Epiq issue to another swimlane (parentId is the swimlane id), or many at once with issueIds \u2014 one call, an outcome per ticket",inputSchema:e.object({issueId:e.string().min(1).optional(),issueIds:e.array(e.string().min(1)).min(1).max(200).optional(),parentId:e.string().min(1),position:e.discriminatedUnion("at",[e.object({at:e.literal("start")}),e.object({at:e.literal("end")}),e.object({at:e.literal("before"),sibling:e.string().min(1)}),e.object({at:e.literal("after"),sibling:e.string().min(1)})]).optional(),repoRoot:e.string().optional()})},i(pe)),t.registerTool("epiq_actor_assume",{description:'Take a board identity for this session, so your writes are attributed to you, not the user: the name you announced: the model provider, a slash, then a short human first name, e.g. "claude/peter". Takes effect on the next write. A name the server was launched with wins; re-assuming is harmless. Reuse an existing agent identity (epiq_contributor_list) before inventing one: a name is permanent once it has authored anything.',inputSchema:e.object({name:e.string().min(1).max(80),repoRoot:e.string().optional()})},i(ve)),t.registerTool("epiq_contributor_email_link",{description:"Bind a git author address to a contributor, so their commits and board activity read as one person. Defaults to you; pass contributorId for a colleague who does not use epiq. The repository's configured git address links itself on your first write, so this is for older addresses and other people. An address two contributors claim resolves to neither.",inputSchema:e.object({email:e.string().min(1).max(254),contributorId:e.string().min(1).optional(),repoRoot:e.string().optional()})},i(Me)),t.registerTool("epiq_contributor_email_unlink",{description:"Retract a link between a git address and a contributor; only its writer or the contributor it names may. The address stops resolving; the event stays in the log.",inputSchema:e.object({email:e.string().min(1).max(254),contributorId:e.string().min(1).optional(),repoRoot:e.string().optional()})},i(Oe)),t.registerTool("epiq_contributor_email_suggest",{description:"Unclaimed addresses in this repository's history that look like yours; until linked, their commits show a raw git name. Show the list and link only what the user confirms: each link is permanent and replicates to every clone.",inputSchema:e.object({repoRoot:e.string().optional()})},i(Ue)),t.registerTool("epiq_contributor_email_list",{description:"Every git address bound to a contributor, and who holds it. One with several claimants is flagged contested and resolves to none.",inputSchema:e.object({repoRoot:e.string().optional()})},i(Ge)),t.registerTool("epiq_project_init",{description:'Set up a new Epiq board in the git repository at repoRoot (default: the current directory), which must have no uncommitted changes. The first time on a machine, ask the user for userName (how they appear on the board: theirs, never your agent identity), preferredEditor (e.g. "vim" or "code --wait") and autoSync (whether the TUI and GUI sync on their own); without them it fails naming what is missing, keeping what was given. Answers the machine already has stay. Commits the state branch and .epiq/project.json and tries to push both; a failed push is a warning.',inputSchema:e.object({repoRoot:e.string().optional(),userName:e.string().min(1).max(80).optional(),preferredEditor:e.string().min(1).max(200).optional(),autoSync:e.boolean().optional()})},i(et)),t.registerTool("epiq_skill_install",{description:"Write the Epiq workflow skill to .claude/skills/epiq/SKILL.md in the repository, so agents there follow the board rules. In a new project, run it after epiq_project_init, which refuses the uncommitted file; on an existing board, any time. A differing file is overwritten only with force: true.",inputSchema:e.object({repoRoot:e.string().optional(),force:e.boolean().optional()})},i(ot)),t.registerTool("epiq_sync",{description:"Pull and push Epiq state with the git remote, then fetch the repos connected with epiq_repo_connect. Other tools never sync: call this to see or publish remote changes.",inputSchema:e.object({repoRoot:e.string().optional()})},async o=>w(await Ke(await A(()=>ne(o)),o))),t.registerTool("epiq_repo_list",{description:"List the repos connected to the board, whose commits count as its own: each one's clone status, size on disk, last fetch error and whether it is past the board's limit (and so not fetched), plus their total on this machine.",inputSchema:e.object({repoRoot:e.string().optional()})},async o=>w(await De(o))),t.registerTool("epiq_repo_connect",{description:"Connect another repo to the board by URL (https://, ssh://, git:// or user@host:path; never a local path), so its commits prefixed with a ticket's ref link to it; or change a connected repo's options, which applies at once. Board-wide: every contributor's epiq downloads it on their next sync. The first call clones to staging and returns a preview (size, host, default branch, latest commit, commits naming this board's tickets \u2014 none suggests the wrong repo), writing nothing; show it to the user, and only once they agree call again with confirm: true and the same options. branches: default or all. since: how much history to clone, board (default), all, or YYYY-MM-DD. label: its name on the board, the URL's basename unless set; empty resets it. Uses your git credentials: ssh-agent or a credential helper must already work.",inputSchema:e.object({repoRoot:e.string().optional(),url:e.string().min(1).max(D),branches:e.enum(["default","all"]).optional(),since:e.string().max(20).optional(),label:e.string().max(24).optional(),confirm:e.boolean().optional()})},async o=>w(await $e(o))),t.registerTool("epiq_repo_disconnect",{description:"Disconnect a repo by its label (when no other repo shares it), id or URL, and delete this machine's clone of it.",inputSchema:e.object({repoRoot:e.string().optional(),repo:e.string().min(1).max(2e3)})},async o=>w(await He(o))),t},mo=async()=>{let t=Rt(),o=new ht;await t.connect(o)};export{bt as MCP_INSTRUCTIONS,Rt as createMcpServer,w as resultJson,mo as startMcpServer};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Fc as a,Gc as b,Hc as c,Ic as d,Jc as e,Kc as f}from"./chunk-GOROXQFX.js";import"./chunk-WUCQWBQE.js";import"./chunk-5RIQL7P4.js";export{e as areVirtualNodesEnabled,a as createOrUpdateVirtualField,b as createOrUpdateVirtualFieldList,c as materializeTicketVirtualNodes,f as materializeVirtualNodes,d as setVirtualNodesEnabled};
2
+ import{Ic as a,Jc as b,Kc as c,Lc as d,Mc as e,Nc as f}from"./chunk-5WUPZIRV.js";import"./chunk-WUCQWBQE.js";import"./chunk-5RIQL7P4.js";export{e as areVirtualNodesEnabled,a as createOrUpdateVirtualField,b as createOrUpdateVirtualFieldList,c as materializeTicketVirtualNodes,f as materializeVirtualNodes,d as setVirtualNodesEnabled};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epiq",
3
- "version": "1.16.2",
3
+ "version": "1.17.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "EPIQ - ergonomic, distributed CLI-first issue tracker TUI ready for the agentic era",
@@ -23,14 +23,14 @@
23
23
  "epiq-mcp": "dist/mcp.js"
24
24
  },
25
25
  "engines": {
26
- "node": ">=18"
26
+ "node": ">=22"
27
27
  },
28
28
  "scripts": {
29
29
  "build": "tsc --noEmit",
30
30
  "build:gui:types": "tsc --noEmit -p source/gui/client/tsconfig.json",
31
31
  "build:gui": "npm run build:gui:types && node source/scripts/build-gui.mjs",
32
32
  "build:gui:dev": "EPIQ_GUI_DEV_BUILD=1 npm run build:gui",
33
- "build:npm": "node source/scripts/write-version.mjs && node source/scripts/write-skill.mjs && rm -rf ./dist && npm run build:gui && esbuild index=source/cli.ts mcp=source/mcp/index.ts --bundle --splitting --packages=external --platform=node --format=esm --target=node18 --minify --outdir=dist --banner:js='#!/usr/bin/env node'",
33
+ "build:npm": "node source/scripts/write-version.mjs && node source/scripts/write-skill.mjs && rm -rf ./dist && npm run build:gui && esbuild index=source/cli.ts mcp=source/mcp/index.ts --bundle --splitting --packages=external --platform=node --format=esm --target=node22 --minify --outdir=dist --banner:js='#!/usr/bin/env node'",
34
34
  "build:sea:bundle": "node source/scripts/write-version.mjs && node source/scripts/write-skill.mjs && node source/scripts/build-sea.mjs",
35
35
  "build:sea": "node source/scripts/build-sea.mjs",
36
36
  "build:publish": "npm run build:npm",
@@ -73,10 +73,10 @@
73
73
  "@modelcontextprotocol/sdk": "^1.32.1",
74
74
  "@pierre/diffs": "^1.3.6",
75
75
  "@xterm/headless": "^6.0.0",
76
- "ink": "^6.8.0",
76
+ "ink": "^8.0.0",
77
77
  "meow": "^14.1.0",
78
- "react": "^19.2.4",
79
- "react-dom": "^19.2.6",
78
+ "react": "^19.3.1",
79
+ "react-dom": "^19.3.1",
80
80
  "react-markdown": "^10.1.0",
81
81
  "react-router-dom": "^7.18.4",
82
82
  "remark-breaks": "^4.0.0",
package/readme.md CHANGED
@@ -1,79 +1,45 @@
1
- # Epiq
1
+ # Epiq - issue tracking as code
2
2
 
3
- Epiq is a self-hosted issue tracker that allows you to review workflows in real time or after the fact via replay. It persists state as an immutable event log, versioned and synchronized via Git.
3
+ Epiq is a self-hosted issue tracker persisting state as an immutable event log in your repository, versioned and synchronized via Git.
4
4
 
5
- Most issue trackers live outside your workflow. Instead of a centralized, managed service, Epiq offers issue tracking as an integrated part of the development environment offering workflow replay and inline code-diffs, tackling the review bottleneck with time-travel, filtering, and a tight code integration.
5
+ Epiq offers issue tracking as an integrated part of the development environment with workflow replay, inline code-diffs, filtering, and a tight code integration.
6
6
 
7
- **[Site →](https://ljtn.github.io/epiq/)**
8
-
9
- > All state is kept local, Git-backed, and versioned.
7
+ **[Docs, blog, etc. →](https://ljtn.github.io/epiq/)**
10
8
 
11
9
  ![The Epiq GUI: the event log, the board and a ticket's code diff, under the time travel timeline](https://raw.githubusercontent.com/ljtn/epiq/main/source/assets/time-travel.jpeg)
12
10
 
13
11
  ## Audit the workflow
14
12
 
15
- Agents now run whole sprints unattended. Because state is an event log, you can replay the board to find out what moved when, who moved it, and what changed along the way.
13
+ Because state is an event log, you can replay the board to find out what happened when, who made the changes, and what what lead up to it.
16
14
 
17
15
  ## Code, linked to tickets
18
16
 
19
- Link a commit to a ticket with an `Epiq-Ref:` line in its body, naming the ticket's ref:
17
+ Commits are linked to a ticket with an `Epiq-Ref:` line in its body, naming the ticket's ref:
20
18
 
21
19
  ```
22
20
  git commit -m "fix the parser" -m "Epiq-Ref: 1YRTG8T"
23
21
  ```
24
-
25
- One line can name several tickets (`Epiq-Ref: 1YRTG8T, 4KQ2M7D`), it can sit anywhere in the body, and its case does not matter. Prefixing the subject with the ref works too:
26
-
27
- ```
28
- git commit -m "1YRTG8T fix the parser"
29
- ```
30
-
31
- Linking makes a commit show up in the ticket's Code tab, with its message. You can comment on selected lines, and also file a new ticket straight from the selection. The MCP exposes the ref as a `ref` property agents can refer to.
32
-
33
22
  ![A ticket's Code tab, showing the diff of a linked commit](https://raw.githubusercontent.com/ljtn/epiq/main/source/assets/code-diff.jpeg)
34
23
 
35
- How you merge decides whether the link survives:
36
-
37
- - **Epiq-Ref lines** survive a squash merge that keeps the squashed messages in its body, as git's and GitHub's default messages do, so every ticket they name stays linked. A rebase or merge commit keeps them as they are.
38
- - **Subject prefixes** need the commits to land as they are: rebase-merge them. Squashing _within_ one ticket's commits is fine as long as the result keeps the prefix; do not squash commits carrying different prefixes into one.
39
-
40
24
  ### Commits from other repos
41
25
 
42
- A board can read commits from other repositories too — handy when the board lives in a repo of its own. Connect one from the GUI (the Repos section of the settings panel behind the gear, or **Connect repo** in the palette), the TUI (`:config repoConnect <url>`, `:config repoDisconnect <name>`) or the MCP (`epiq_repo_connect`, `epiq_repo_disconnect`).
43
-
44
- Connecting is a board-wide action: every contributor's epiq downloads the repo on their next sync. So it takes two steps. The first clones the repo into a staging area and shows what it is and what it costs — its size, host, default branch and latest commit, and how many of its commits name tickets on this board (none suggests the wrong repo) — and writes nothing. Only a confirm connects it: the TUI takes the same line again with `confirm`, the MCP a second call with `confirm: true`. Options of a connected repo change at once. `branches` is `default` (the default) or `all`; `since` is how much history to clone: `board` (since the board was created, the default), `all`, or a date; `label` is what the repo is called on the board — its URL's basename unless set, which is how a fork and its upstream are told apart.
45
-
46
- The list is part of the board: connecting, changing and disconnecting are board events, so they sync like any other change and never conflict — nothing to commit. Epiq keeps its own read-only bare clone of each repo under `~/.epiq-global/repos/`, fetched on sync with your usual git credentials (ssh-agent or a credential helper; a passphrase prompt cannot be answered) — never on startup. A commit shows once it is pushed and the board has synced.
47
-
48
- A board reads at most 20 repos; one connected past that is listed but not fetched until one ahead of it is disconnected. Each clone may take at most 2 GB of disk on your machine; `:config repoCloneMaxMb <MB>` changes that, in your own config. A fetch that would pass it is stopped and undone, and the repo says so. The Repos section shows what each clone takes on this machine, and the total. Autosync fetches each at most once a minute; a sync you ask for always fetches. Widening `since` fetches the older history, narrowing it keeps what is already cloned. Disconnecting a repo deletes its clone, on every machine as it syncs; worktrees of one board share a clone. A repo is something every contributor can fetch, so a local path is refused. A connected repo that is itself an epiq board lends its commits, never its board's event log.
49
-
50
- Since anyone on the board can connect a repo, URLs are read with care: only `https`, `http`, `ssh`, `git` and `user@host:path` URLs are accepted, never one carrying a password or a user before an http host, and git runs in the clones with none of your hooks, attribute drivers or command-running transports.
51
-
52
- ## Goals
53
-
54
- A goal groups the tickets that serve one outcome, on any board. A ticket can be in one goal. Goals are not tickets: a board shows the goals its tickets are in as a strip above the lanes, each pill underlined as far as the goal's tickets are done (closed, or in a done lane), on every board. A lane named Done counts, and so does any lane ticked "Tickets here count as done" in its panel.
55
-
56
- - **GUI:** pick a goal in a ticket's Goal dropdown, or type a new name there to create one; from the keyboard, **Put in a goal** and **Take out of its goal** in the palette (⌘K, then `goal`). Click a pill to show only that goal's tickets and open its panel: a description of what it's for, progress and every ticket in it on any board, a Stats tab measuring the code of all of them together, and a Code tab listing every commit on them. **Manage goals** (the strip, the dropdown or the palette) adds, renames and removes them.
57
- - **TUI:** `:goal <name>` puts the selected ticket in that goal, creating it if needed; `:ungoal` takes it out; `:filter goal <name>` shows only its tickets. A ticket in a goal carries `◆` in the goal's colour. `:rule done` and `:unrule done` mark the selected lane done or not; a done lane shows `✓` after its count. Renaming and removing a goal is done in the GUI or the MCP, as with tags.
58
- - **MCP:** `epiq_issue_goal_set` with a goal's name (created if needed) or `null`; `epiq_goal_list` lists every goal with its progress and boards; `epiq_goal_stats` gives a goal's code stats; `epiq_goal_create`, `epiq_goal_rename`, `epiq_goal_description_edit`, `epiq_goal_remove`; `epiq_goal_list` carries each goal's description. `epiq_board_list` lists the goals each board shows. `epiq_swimlane_rule_set` marks a lane done; `epiq_swimlane_list` says which lanes count.
59
-
60
- A board shows a goal once one of its tickets is on it. Removing a goal leaves its tickets where they are, in no goal.
26
+ A board can read commits from other repositories too. Connecting is a board-wide action: every contributor's epiq downloads the repo on their next sync.
61
27
 
62
28
  ## Terminal + Browser
63
29
 
64
- Epiq originated from the command line and offers a first-class terminal experience, but also features a browser interface powered by the same Git-backed event engine.
30
+ Epiq originated from the command line and offers a first-class terminal experience, but also features a browser interface.
65
31
 
66
32
  ![The Epiq terminal UI: the same board rendered in a terminal, with the command palette along the bottom](https://raw.githubusercontent.com/ljtn/epiq/main/source/assets/tui.jpeg)
67
33
 
68
- ## How it is different
34
+ ## What Epiq brings
69
35
 
70
36
  Fundamental design choices gives a system with:
71
37
 
72
- - **Workflow replay**, - inspect what happened while you were away, as if it happens now
38
+ - **Repo-native** — issues live where the code lives, simplifying sandboxed setups
39
+ - **Workflow replay**, - inspect what happened while you were away
73
40
  - **Simple setup** — no accounts, SaaS, or external services required
74
- - **Repo-native** — your issues can live where your code lives
75
41
  - **Offline-friendly** — works anywhere, with eventual consistency
76
- - **Fast** — local first, and eventual consistency makes Epiq edits instant
42
+ - **Fast** — local first edits are instant
77
43
  - **Portable** — runs on your local machine, on a remote Linux server or your grandma’s connected toaster
78
44
  - **Command driven** — scriptable and automation-friendly, ready for the agentic era
79
45
 
@@ -265,6 +231,5 @@ Epiq follows a **local-first** model:
265
231
 
266
232
  ---
267
233
 
268
- [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-Epiq-pink?logo=github)](https://github.com/sponsors/ljtn)
269
234
 
270
- 🫡 Never leave your editor!
235
+ [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-Epiq-pink?logo=github)](https://github.com/sponsors/ljtn)
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env node
2
- import{a as F}from"./chunk-MTEK6CI6.js";import{$ as vn,A as ee,B as Et,D as on,E as rn,F as nn,G as sn,H as an,I as ln,J as mn,K as cn,M as dn,N as un,P as pn,Q as fn,R as gn,S as yn,T as io,U as Ct,V as hn,W as bn,Y as xn,Z as wn,a as Dr,b as pe,ba as Sn,c as ie,ca as In,d as Or,e as Ur,ea as Tn,f as Gr,fa as En,g as $r,ga as Pt,h as Fr,ha as At,i as Hr,ia as Cn,j as kr,ja as Pn,ka as An,l as Wr,la as so,m as Vr,ma as ao,n as Kr,na as Nn,oa as Ae,p as Yr,pa as lo,q as qr,qa as Mn,r as Jr,ra as Rn,s as Xr,t as zr,ta as Bn,u as Qr,ua as Ln,v as Zr,va as jn,w as en,y as tn,z as Tt}from"./chunk-5E42KQM3.js";import{$ as zo,$b as vt,Ab as ne,Ac as Lr,Bb as dr,Bc as jr,Cb as ur,Cc as q,Da as Qo,Db as b,E as ro,Eb as pr,Fb as ht,G as ko,Ib as Ce,Ic as _r,Ka as $e,Kb as fr,L as Wo,La as Zo,Lb as ue,P as Vo,Pb as bt,Q as Ko,Qb as xt,Ra as de,T as ct,Ta as Ee,U as Te,Ub as gr,V as Yo,Vb as yr,Wb as hr,X as H,Xb as br,Y as qo,Yb as wt,Z as Jo,Zb as xr,_ as Xo,_b as wr,a as _o,aa as dt,ab as er,ac as vr,b as st,ba as Ge,bb as tr,bc as Sr,c as at,cb as or,cc as Ir,da as ut,db as gt,ea as pt,fc as Tr,g as lt,gb as yt,gc as Er,hb as rr,hc as Cr,ia as ft,ic as Pr,j as Do,k as oe,l as Oo,mc as Ar,n as Ie,nc as no,o as re,oc as Nr,p as Uo,pc as St,q as Ue,qb as nr,qc as It,r as m,rb as ir,rc as Mr,s as Go,sb as sr,sc as Rr,t as $o,tb as ar,tc as Q,u as Fo,ub as lr,uc as Pe,v as Ho,w as oo,wc as O,x as mt,xc as Fe,yb as mr,zb as cr,zc as Br}from"./chunk-GOROXQFX.js";import{f as it,g as jo}from"./chunk-WUCQWBQE.js";import{b as G,c as T,d as h,e as $}from"./chunk-5RIQL7P4.js";import z from"chalk";import{render as Dl}from"ink";import Ol from"meow";import Ul from"react";import gs from"node:os";import ys from"node:path";import He from"chalk";var Nt=["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557","\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551","\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u2584\u2584 \u2588\u2588\u2551","\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D","\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2580\u2580\u255D "];var Mt=" ",On=He.hex(m.secondary2),_n=He.hex(m.gray),hs=()=>{let t=Nt[0].length+Nt.length*2;return Nt.map((o,n)=>Array.from(o).map((s,u)=>{let[r,f,y]=$o((u+n*2)/t);return He.rgb(r,f,y)(s)}).join(""))},bs=e=>{let t=gs.homedir();return e===t||e.startsWith(t+ys.sep)?"~"+e.slice(t.length):e},Dn=(e,t)=>`${Mt}${He.hex(m.accent)("\u279C")} ${On(e.padEnd(9))}${t}`,mo=({url:e,repoRoot:t,version:o,reused:n})=>["",...hs().map(s=>Mt+s),"",`${Mt}${On("gui")} ${_n(`\xB7 v${o}`)}`,"",Dn("Local",He.hex(m.accent).bold(e)),Dn("Project",bs(t)),"",Mt+_n(n?"already running for this project \xB7 opened in your browser":"press ctrl+c to stop"),""].join(`
3
- `);import{spawn as xs}from"node:child_process";import ke from"node:process";var Un=(e=ke.platform,t=ke.env)=>e==="darwin"||e==="win32"||!!(t.DISPLAY||t.WAYLAND_DISPLAY||t.BROWSER),Gn=e=>{let t=ke.platform==="darwin"?"open":ke.platform==="win32"?"cmd":"xdg-open",o=ke.platform==="win32"?["/c","start","",e]:[e];try{let n=xs(t,o,{detached:!0,stdio:"ignore"});n.on("error",s=>{logger.info(`[gui] could not open a browser: ${s.message}`)}),n.unref()}catch(n){logger.info(`[gui] could not open a browser: ${String(n)}`)}};import{readFile as hi,stat as ca}from"node:fs/promises";import{createHash as da}from"node:crypto";import ua from"node:http";import xe from"node:path";import{fileURLToPath as pa}from"node:url";import ws from"node:fs";import $n from"node:path";var We=Number(process.env.EPIQ_GUI_PORT)||3710,co="/api/instance",uo="epiq",Rt=e=>{try{return ws.realpathSync($n.resolve(e))}catch{return $n.resolve(e)}},vs=e=>{let t=e;return typeof t=="object"&&t!==null&&t.app===uo&&typeof t.repoRoot=="string"&&typeof t.version=="string"&&typeof t.pid=="number"},Fn=async(e,t=500)=>{let o=new AbortController,n=setTimeout(()=>o.abort(),t);try{let s=await fetch(`http://127.0.0.1:${e}${co}`,{signal:o.signal});if(!s.ok)return null;let u=await s.json();return vs(u)?u:null}catch{return null}finally{clearTimeout(n)}};var Ss=6e4,Hn=e=>{let t,o=!1,n=!1,s=0,u=()=>{let g=Oo();return h(g)?(st.error(g.message),null):g.value},r=()=>{let g=u();if(!g)return!1;let l=Ie();return h(l)?!1:!!g.autoSync&&Ar(l.value)===null},f=()=>{let g=_o(u()?.autoSyncDebounceMs);return Math.max(0,g-(Date.now()-s))},y=async()=>{if(!(o||n)&&r()){n=!0,s=Date.now();try{await pe(async()=>{if(ie().mode!=="live")return!1;let l=await Et({repoRoot:e.project.repoRoot});return await e.publisher.publishIfLogMoved(),!h(l)})&&e.onReposFetch?.(Ss)}finally{n=!1,p()}}};function p(){o||!r()||n||t||(t=setTimeout(()=>{t=void 0,y()},f()))}return p(),{queueSync:p,reschedule:()=>{t&&(clearTimeout(t),t=void 0),p()},dispose:()=>{o=!0,t&&clearTimeout(t)}}};var Bt=(e,t)=>{let o,n=!1,s=async()=>{let u=Date.now();try{await e()}catch(r){logger.error("[watch] check threw",r)}n||(o=setTimeout(s,Math.max(t,Date.now()-u)))};return o=setTimeout(s,t),()=>{n=!0,o&&clearTimeout(o)}};var Is=/^(```|~~~)[^\n]*\n[\s\S]*?\n\1[ \t]*$/gm,Ts=/^(?:```|~~~)[\s\S]*$/m,Es=/^[ \t]*\|?[ \t]*:?-{2,}:?[ \t]*(?:\|[ \t]*:?-{2,}:?[ \t]*)*\|?[ \t]*$/gm,Cs=/^[ \t]*\|.*$/gm,Ps=/!\[[^\]]*\]\([^)]*\)/g,As=/\[([^\]]*)\]\([^)]*\)/g,Ns=/^[ \t]*(?:[#>]+[ \t]*|[-*+][ \t]+|\d+[.)][ \t]+)/gm,Ms=/^[ \t]*(?:-{3,}|\*{3,}|_{3,})[ \t]*$/gm,Rs=/[*`~]/g,Bs=/(?<![0-9A-Za-z])_|_(?![0-9A-Za-z])/g,Ls=e=>Xo(e.replace(Is," ").replace(Ts," ").replace(Ps," ").replace(As,"$1").replace(Ms," ").replace(Es," ").replace(Cs,t=>t.replaceAll("|"," ")).replace(Ns,"").replace(Rs,"").replace(Bs,"")),js=.5,kn=(e,t)=>{if(t<=0)return"";let o=Ls(e);if(o.length<=t)return o;let n="\u2026",s=t-n.length;if(s<=0)return n.slice(0,t);let u=o.slice(0,s),r=u.lastIndexOf(" ");return`${(r>=s*js?u.slice(0,r):u).trimEnd()}${n}`};var _s=e=>({...e,boards:(e.boards??[]).map(t=>({...t,swimlanes:(t.swimlanes??[]).map(o=>({...o,issues:(o.issues??[]).map(n=>({...n,description:""}))}))})),commentsByIssueId:Object.fromEntries(Object.entries(e.commentsByIssueId??{}).filter(([,t])=>t.length>0).map(([t,o])=>[t,o.map(n=>({...n,body:""}))])),attachmentsByIssueId:Object.fromEntries(Object.entries(e.attachmentsByIssueId??{}).filter(([,t])=>t.length>0))}),be=e=>h(e)?e:T(e.message,_s(e.value)),Ds=(e,t)=>(e.boards??[]).flatMap(o=>o.swimlanes??[]).flatMap(o=>o.issues??[]).find(o=>o.id===t),Wn=(e,t)=>{let o=Ds(e,t);return{issueId:t,description:o?.description??"",comments:e.commentsByIssueId?.[t]??[]}},Os=280,Vn=(e,t)=>({issueId:e,excerpt:kn(t,Os)});var Us=1e3,Kn=e=>{let t=oe(e.repoRoot);if(h(t))return null;let o=lt({repoRoot:t.value});return h(o)?null:o.value},Yn=e=>{let t=()=>{let r=Kn(e.project);return r===null?null:$e(r)},o=()=>{let r=or(e.project.repoRoot);return h(r)?null:JSON.stringify([r.value.live,r.value.overLimit])},n=o(),s=t();return{publishIfLogMoved:async()=>{let r=Kn(e.project);if(r===null||await gt(r))return;let f=$e(r);if(f===s)return;s=f;let y=be(await ee({repoRoot:e.project.repoRoot}));if(h(y))return;ue({type:"state",payload:y});let p=o();p!==null&&p!==n&&(n=p,ue({type:"repos:changed"}))}}},qn=(e,t=Us)=>Bt(()=>pe(async()=>{ie().mode==="live"&&await e.publishIfLogMoved()}),t);var Gs=new Set(["127.0.0.1","[::1]","localhost","epiq.localhost"]),po=(e,t)=>{if(!e)return!1;if(e==="null")return!0;let o;try{o=new URL(e)}catch{return!0}return o.protocol!=="http:"||o.port!==String(t)?!0:!Gs.has(o.hostname)},Jn=(e,t)=>e.method==="GET"||e.method==="HEAD"?null:po(e.origin,t)?{status:403,message:"Cross-origin request"}:e.method==="POST"&&!(e.contentType??"").toLowerCase().startsWith("application/json")?{status:415,message:"Expected content-type: application/json"}:null;import{WebSocketServer as sa}from"ws";var Fs=new RegExp(`^[0-9A-Z]{${7}}$`),Hs=e=>{let t=e.indexOf(" ");if(t===-1)return null;let o=e.slice(0,t).toUpperCase();return Fs.test(o)?o:null},ks=e=>{let t={};for(let o of e){let n=Hs(o.subject),s=new Set([...n?[n]:[],...o.refs??[]]);for(let u of s)(t[u]??=[]).push({time:o.time,insertions:o.insertions,deletions:o.deletions})}return t},Xn=async(e={})=>{let t=await yt({repoRoot:e.repoRoot});return h(t)?t:T("Collected commits by ref",ks(t.value))};var Ws="Filed here",zn=(e,t)=>e.set(t,(e.get(t)??0)+1),Qn=(e,t,o)=>[...e.entries()].map(([n,s])=>({laneId:n,title:n===null?Ws:o(n),count:s,share:t===0?0:s/t})).sort((n,s)=>s.count-n.count||n.title.localeCompare(s.title)),Zn=(e,t,o)=>{let n=new Map,s=new Map,u=0,r=0;for(let f of t)for(let[y,p]of f.entries()){if(p!==e)continue;u++,zn(n,f[y-1]??null);let g=f[y+1];g!==void 0&&(r++,zn(s,g))}return{arrived:u,departed:r,arrivesFrom:Qn(n,u,o),movesOnTo:Qn(s,r,o)}};var Ve=1440*60*1e3,Vs=({laneIds:e,journeys:t,now:o,days:n})=>{let s=o-(n-1)*Ve,u=new Map(e.map(r=>[r,Array.from({length:n},()=>[])]));for(let r of t)for(let[f,y]of r.entries()){let p=u.get(y.laneId);if(!p)continue;let g=r[f+1]?.enteredAt,l=Math.max(0,Math.ceil((y.enteredAt-s)/Ve)),d=g===void 0?n-1:Math.min(n-1,Math.ceil((g-s)/Ve)-1);for(let a=l;a<=d;a++)p[a].push(s+a*Ve-y.enteredAt)}return Object.fromEntries([...u].map(([r,f])=>[r,f.map((y,p)=>({t:s+p*Ve,median:Ln(y.sort((g,l)=>g-l)),count:y.length}))]))},ei=({laneId:e,journeys:t,now:o,days:n})=>Vs({laneIds:[e],journeys:t,now:o,days:n})[e];var Ks=40,Ys=30,qs=()=>({tickets:0,commits:0,insertions:0,deletions:0}),Js=async(e,t)=>{let o=qs();for(let n of t.slice(0,Ks)){let s=await Ct({repoRoot:e,idOrRef:de(n)});h(s)||s.value.shape.commits===0||(o.tickets++,o.commits+=s.value.shape.commits,o.insertions+=s.value.shape.insertions,o.deletions+=s.value.shape.deletions)}return o},ti=async e=>{let t=kr();if(h(t))return t;let{nodes:o}=t.value;if(!o[e.swimlaneId])return G("Swimlane not found");let s=Object.values(o).filter(ct),u=s.map(y=>Dr(y.log??[],ro(y.id))),r=Zn(e.swimlaneId,u.map(y=>y.map(p=>p.laneId)),y=>o[y]?.title??"Elsewhere"),f=s.filter(y=>y.parentNodeId===e.swimlaneId).map(y=>y.id);return T("Derived swimlane stats",{swimlaneId:e.swimlaneId,createdAt:ro(e.swimlaneId),...r,stayTrend:ei({laneId:e.swimlaneId,journeys:u,now:Date.now(),days:Ys}),code:await Js(e.repoRoot,f)})};var oi=new Set(["sync","email:link","email:unlink","repo:connect","repo:disconnect","issues:create","board:create","board:edit:title","swimlane:create","swimlane:edit:title","board:edit:description","swimlane:edit:description","swimlane:rule:set","goal:edit:description","swimlane:delete","swimlane:move","issues:move","issue:close","issue:reopen","issue:edit:title","issue:edit:description","issue:tag:add","issue:tag:remove","issue:goal:set","goal:create","goal:rename","goal:remove","contributor:remove","tag:remove","issue:assignee:add","issue:assignee:remove","issue:comment:add","issue:comment:delete"]);var ri=e=>{let t=Ge({exclude:e});return h(t)?[]:t.value.map(o=>({projectId:o.projectId,name:dt(o.root),root:o.root,lastOpenedAt:o.lastOpenedAt}))},ni=e=>{let t=Ge();if(h(t))return G(t.message);let o=t.value.find(n=>n.root===e);return o?T("Resolved recent project",o.root):G(`Not a recent project: ${e}`)};import{z as c}from"zod";var v=c.string().min(1),ii=c.discriminatedUnion("at",[c.object({at:c.literal("start")}),c.object({at:c.literal("end")}),c.object({at:c.literal("before"),sibling:v}),c.object({at:c.literal("after"),sibling:v})]),ae=c.number().finite(),w=(e,t)=>c.object({type:c.literal(e),payload:t}),le=e=>c.object({type:c.literal(e)}),Xs=c.discriminatedUnion("type",[le("state:get"),le("issues:list"),le("sync"),le("time-travel:live"),le("diff-stats:get"),w("project:open",c.object({root:c.string().min(1)})),w("issues:create",c.object({title:c.string(),parentId:v,description:c.string().optional(),tagNames:c.array(c.string()).optional()})),w("board:create",c.object({title:c.string()})),w("board:edit:title",c.object({boardId:v,title:c.string()})),w("swimlane:create",c.object({title:c.string(),boardId:v})),w("swimlane:edit:title",c.object({swimlaneId:v,title:c.string()})),w("board:edit:description",c.object({boardId:v,description:c.string()})),w("swimlane:edit:description",c.object({swimlaneId:v,description:c.string()})),w("swimlane:rule:set",c.object({swimlaneId:v,rule:c.string().min(1).max(64),on:c.boolean()})),w("goal:edit:description",c.object({goalId:v,description:c.string()})),w("swimlane:delete",c.object({swimlaneId:v})),w("swimlane:move",c.object({swimlaneId:v,boardId:v,position:ii.optional()})),w("issues:move",c.object({issueId:v,parentId:v,position:ii.optional()})),w("issue:edit:title",c.object({issueId:v,title:c.string()})),w("issue:edit:description",c.object({issueId:v,description:c.string()})),w("issue:tag:add",c.object({issueId:v,tagName:c.string()})),w("issue:goal:set",c.object({issueId:v,goal:v.nullable()})),w("goal:create",c.object({name:c.string(),issueId:v.optional()})),w("goal:rename",c.object({goalId:v,name:c.string()})),w("goal:remove",c.object({goalId:v})),w("issue:tag:remove",c.object({issueId:v,tagId:v})),w("contributor:remove",c.object({contributorId:v})),w("tag:remove",c.object({tagId:v})),c.object({type:c.literal("contributors:get"),payload:c.object({boardId:v.optional()}).optional()}),w("issue:assignee:add",c.object({issueId:v,assigneeId:v.optional(),assigneeName:c.string().optional(),createUnlinked:c.boolean().optional()})),w("issue:assignee:remove",c.object({issueId:v,assigneeId:v})),w("issue:close",c.object({issueId:v})),w("issue:reopen",c.object({issueId:v})),w("issue:comment:add",c.object({issueId:v,body:c.string()})),w("issue:comment:delete",c.object({issueId:v,commentId:v})),w("issue:comment:edit",c.object({issueId:v,commentId:v,body:c.string()})),w("issue:get",c.object({issueId:v})),w("issue:preview:get",c.object({issueId:v})),c.object({type:c.literal("timeline:get"),payload:c.object({start:ae.optional(),end:ae.optional(),boardId:v.optional(),requestId:c.number().finite().optional()}).optional()}),c.object({type:c.literal("commits:get"),payload:c.object({start:ae.optional(),end:ae.optional(),requestId:c.number().finite().optional()}).optional()}),w("time-travel:scrub",c.object({targetTime:ae})),w("time-travel:checkout-event",c.object({eventId:c.string().min(1)})),w("commit:inspect",c.object({sha:c.string(),repo:c.string().max(80).optional()})),le("repos:get"),w("repo:preview",c.object({url:c.string().min(1).max(mt),branches:c.enum(["default","all"]).optional(),since:c.string().max(20).optional(),label:c.string().max(24).optional()})),w("repo:preview:cancel",c.union([c.object({repo:c.string().min(1).max(80)}),c.object({url:c.string().min(1).max(mt)})])),w("repo:connect",c.object({url:c.string().min(1).max(mt),branches:c.enum(["default","all"]).optional(),since:c.string().max(20).optional(),label:c.string().max(24).optional()})),w("repo:disconnect",c.object({repo:c.string().min(1).max(2e3)})),c.object({type:c.literal("emails:get")}),w("email:link",c.object({email:c.string().min(1).max(254),contributorId:c.string().min(1).optional()})),w("email:unlink",c.object({email:c.string().min(1).max(254),contributorId:c.string().min(1).optional()})),w("commit:diff:get",c.object({sha:c.string(),repo:c.string().max(80).optional()})),w("issue:commits:get",c.object({issueId:v})),w("issue:squashed-diff:get",c.object({issueId:v,asOfTime:ae.optional()})),w("issue:stats:get",c.object({issueId:v,asOfTime:ae.optional()})),w("swimlane:stats:get",c.object({swimlaneId:v})),w("goal:stats:get",c.object({goalId:v,key:c.string().max(200),asOfTime:ae.optional()})),w("goal:commits:get",c.object({goalId:v,key:c.string().max(200),asOfTime:ae.optional()})),w("board:overview:get",c.object({boardId:v})),le("me:stats:get"),le("settings:get"),le("version:get"),w("settings:set",c.object({autoSync:c.boolean().optional(),autoSyncIntervalMs:c.number().finite().optional()}))]);var si=e=>{let t=Xs.safeParse(e);return t.success?{ok:!0,message:t.data}:{ok:!1,error:`Invalid message: ${t.error.issues.map(n=>`${n.path.join(".")||"<root>"}: ${n.message}`).join(", ")}`}};var zs="https://registry.npmjs.org/epiq/latest",Qs="https://api.github.com/repos/ljtn/epiq/releases?per_page=30",Zs="https://raw.githubusercontent.com/ljtn/epiq/main/install.sh";var ai=new Map,fo=new Map;var yo=/^v?\d+\.\d+\.\d+$/,go=e=>e.replace(/^v/,""),Ne=(e,t)=>{let o=go(e).split(".").map(Number),n=go(t).split(".").map(Number);for(let s=0;s<Math.max(o.length,n.length);s++){let u=o[s]??0,r=n[s]??0;if(u>r)return 1;if(u<r)return-1}return 0},ea=()=>process.env.IS_CI==="true"||process.env.EPIQ_NO_UPDATE_CHECK==="1",ta=async e=>{try{let t=await fetch(e,{signal:AbortSignal.timeout(5e3),headers:{accept:"application/json"}});return t.ok?await t.json():null}catch{return null}},ci=(e,t=()=>!0)=>{let o=ai.get(e);if(o&&Date.now()-o.at<o.ttl)return Promise.resolve(o.body);let n=fo.get(e);if(n)return n;let s=ta(e).then(u=>(ai.set(e,{at:Date.now(),ttl:u!==null&&t(u)?864e5:36e5,body:u}),fo.delete(e),u));return fo.set(e,s),s},ho=async e=>{if(ea())return{current:e,latest:null,updateAvailable:!1};let t=await ci(zs),o=typeof t?.version=="string"&&yo.test(t.version)?t.version:null;return{current:e,latest:o,updateAvailable:o?Ne(o,e)>0:!1}},oa=e=>{let t=[],o=null;for(let n of e.replace(/\r/g,"").split(`
4
- `)){let s=n.trimEnd();if(s.trim())if(/^[-*] /.test(s.trimStart())&&!/^\s{2,}/.test(s))o||(o={title:null,items:[]},t.push(o)),o.items.push(s.trimStart().slice(2).trim());else if(/^\s+/.test(s)&&o?.items.length)o.items[o.items.length-1]+=` ${s.trim()}`;else{if(yo.test(s.trim())&&t.length===0)continue;o={title:s.replace(/^#+\s*/,"").trim(),items:[]},t.push(o)}}return t.filter(n=>n.items.length>0)},ra=e=>{let t=e;return typeof t?.tag_name!="string"||!yo.test(t.tag_name)||t.draft||t.prerelease?null:{version:go(t.tag_name),url:typeof t.html_url=="string"?t.html_url:"",publishedAt:typeof t.published_at=="string"&&!Number.isNaN(Date.parse(t.published_at))?t.published_at:null,sections:typeof t.body=="string"?oa(t.body):[]}},li=e=>Array.isArray(e)?e.map(ra).filter(t=>!!t):[],na=async e=>li(await ci(Qs,t=>li(t).some(o=>Ne(o.version,e)===0))),ia=(e,t,o)=>e.filter(n=>Ne(n.version,t)>0&&Ne(n.version,o)<=0).sort((n,s)=>Ne(s.version,n.version)),mi={script:`curl -fsSL ${Zs} | sh`,npm:"npm install --global epiq@latest"},di=async e=>{let t=await ho(e);if(!t.updateAvailable||!t.latest)return{...t,releases:[],currentPublishedAt:null,commands:mi};let o=await na(t.latest);return{...t,releases:ia(o,e,t.latest),currentPublishedAt:o.find(n=>Ne(n.version,e)===0)?.publishedAt??null,commands:mi}};var aa=1024*1024,bo=()=>{ue({type:"state",payload:be(Tt())})},ui=()=>{ue({type:"repos:changed"})},xo=async(e,t)=>{la(await Bn({repoRoot:e,minIntervalMs:t}))},la=e=>{h(e)||(e.value.some(t=>t.changed)&&ui(),e.value.some(t=>t.changed||!t.ok)&&ue({type:"repos:fetched"}))},S=(e,t)=>{e.send(JSON.stringify(t))},Ke=async(e,t)=>{let o=be(await ee({repoRoot:t}));return h(o)&&o.message===Do?S(e,{type:"state:unavailable",payload:{message:o.message,repoRoot:t,recentProjects:ri(t)}}):S(e,{type:"state",payload:o})},ma=async(e,t)=>{let o=()=>S(e,{type:"state",payload:be(Tt())});if(ie().mode!=="live")return o();let n=await ee({repoRoot:t});return ie().mode!=="live"?o():S(e,{type:"state",payload:be(n)})},E=async(e,t,o,n,s)=>{if(S(e,{type:n,payload:s}),h(s))return S(e,{type:"failed",payload:s.message});o(),ma(e,t)},pi=(e,t,{onStateChanged:o,onSyncSettingsChanged:n,getPort:s})=>{new sa({server:e,path:"/ws",maxPayload:aa,verifyClient:r=>!po(r.origin,s())}).on("connection",r=>{fr(r),r.on("message",async f=>{let y=async l=>{let{type:d}=l,a=t.repoRoot;if(d==="project:open"){let i=ni(l.payload.root);return S(r,{type:"project:open:result",payload:i}),h(i)?void 0:(t.repoRoot=i.value,Ke(r,i.value))}if(d==="timeline:get"){let{requestId:i,...x}=l.payload??{};return S(r,{type:"timeline",requestId:i,payload:await Or({repoRoot:a,...x})})}if(d==="issue:get"){let i=l.payload?.issueId,x=Tt(),L=ln(i);return S(r,{type:"issue",payload:h(x)?x:T("Issue detail",{...Wn(x.value,i),history:h(L)?[]:L.value})})}if(d==="issue:preview:get"){let{issueId:i}=l.payload,x=tn(i);return S(r,{type:"issue:preview:result",payload:{issueId:i,result:h(x)?x:T("Issue preview",Vn(i,x.value))}})}if(d==="commits:get"){let{requestId:i,...x}=l.payload??{};return S(r,{type:"commits",requestId:i,payload:io(await yt({repoRoot:a,...x}))})}if(d==="emails:get")return S(r,{type:"emails",payload:await so({repoRoot:a}),suggestions:await ao({repoRoot:a})});if(d==="email:link"||d==="email:unlink"){let x=await(d==="email:link"?Pn:An)({repoRoot:a,...l.payload});return S(r,{type:"emails",payload:await so({repoRoot:a}),suggestions:await ao({repoRoot:a}),lastAction:h(x)?{ok:!1,message:x.message}:{ok:!0,message:x.message}}),E(r,a,o,`${d}:result`,x)}if(d==="me:stats:get")return S(r,{type:"me:stats",payload:await Nn({repoRoot:a})});if(d==="version:get")return S(r,{type:"version",payload:await di(F)});if(d==="settings:get")return S(r,{type:"settings",payload:no()});if(d==="settings:set"){let i=Nr({enabled:l.payload.autoSync,intervalMs:l.payload.autoSyncIntervalMs});return h(i)||n(),S(r,{type:"settings",payload:h(i)?no():i,lastAction:h(i)?{ok:!1,message:i.message}:{ok:!0,message:i.message}})}if(d==="commit:inspect")return S(r,{type:"commit:inspect:result",payload:await lr({repoRoot:a,sha:l.payload.sha,repo:l.payload.repo})});if(d==="commit:diff:get"){let{sha:i,repo:x}=l.payload;return S(r,{type:"commit:diff:result",payload:{sha:i,repo:x,result:await sr({repoRoot:a,sha:i,repo:x})}})}if(d==="repos:get"){let i=await nr(a);return S(r,{type:"repos",payload:await Ae({repoRoot:a}),own:i})}if(d==="repo:preview"){let i=await lo({repoRoot:a,...l.payload,previewOnly:!0});return S(r,{type:"repos",for:d,url:l.payload.url.trim(),payload:h(i)?await Ae({repoRoot:a}):i,preview:h(i)?null:i.value.preview??null,lastAction:{ok:!h(i),message:i.message}})}if(d==="repo:preview:cancel")return Rn(l.payload),S(r,{type:"repos",for:d,payload:await Ae({repoRoot:a}),preview:null});if(d==="repo:connect"||d==="repo:disconnect"){let i=d==="repo:connect"?await lo({repoRoot:a,...l.payload,confirm:!0}):await Mn({repoRoot:a,...l.payload});return S(r,{type:"repos",for:d,payload:h(i)?await Ae({repoRoot:a}):i,preview:null,lastAction:{ok:!h(i),message:i.message}}),h(i)?S(r,{type:`${d}:result`,payload:i}):(!h(i)&&i.message!==er&&!i.message.startsWith(tr)&&ui(),E(r,a,o,`${d}:result`,i))}if(d==="issue:commits:get"){let{issueId:i}=l.payload;return S(r,{type:"issue:commits:result",payload:{issueId:i,result:await ir(a,io(await rr({repoRoot:a,ref:de(i)})))}})}if(d==="issue:squashed-diff:get"){let{issueId:i,asOfTime:x}=l.payload;return S(r,{type:"issue:squashed-diff:result",payload:{issueId:i,asOfTime:x??null,result:await ar({repoRoot:a,ref:de(i),end:x})}})}if(d==="issue:stats:get"){let{issueId:i,asOfTime:x}=l.payload;return S(r,{type:"issue:stats:result",payload:{issueId:i,asOfTime:x??null,result:await Ct({repoRoot:a,idOrRef:de(i),end:x})}})}if(d==="diff-stats:get")return S(r,{type:"diff-stats",payload:await Xn({repoRoot:a})});if(d==="swimlane:stats:get"){let{swimlaneId:i}=l.payload;return S(r,{type:"swimlane:stats:result",payload:{swimlaneId:i,result:await ti({repoRoot:a,swimlaneId:i})}})}if(d==="goal:stats:get"){let{goalId:i,key:x,asOfTime:L}=l.payload;return S(r,{type:"goal:stats:result",payload:{goalId:i,key:x,result:await hn({repoRoot:a,goal:i,end:L})}})}if(d==="goal:commits:get"){let{goalId:i,key:x,asOfTime:L}=l.payload;return S(r,{type:"goal:commits:result",payload:{goalId:i,key:x,result:await bn({repoRoot:a,goal:i,end:L})}})}if(d==="board:overview:get"){let{boardId:i}=l.payload;return S(r,{type:"board:overview:result",payload:{boardId:i,result:jn({boardId:i})}})}if(d==="time-travel:scrub"){let i=await Ur({repoRoot:a,targetTime:l.payload.targetTime});return S(r,{type:"time-travel:result",payload:i}),h(i)?void 0:bo()}if(d==="time-travel:checkout-event"){let i=await Gr({repoRoot:a,eventId:l.payload.eventId});return S(r,{type:"time-travel:result",payload:i}),h(i)?void 0:bo()}if(d==="time-travel:live"){let i=await $r({repoRoot:a});return S(r,{type:"time-travel:result",payload:i}),h(i)?void 0:bo()}if(d==="state:get")return Ke(r,a);if(d==="sync"){let i=await Et({repoRoot:a});return S(r,{type:"sync:result",payload:i}),o(),h(i)||xo(a),Ke(r,a)}if(d==="issue:comment:add"){let i=await Pt({repoRoot:a,...l.payload});return E(r,a,o,"issue:comment:add:result",i)}if(d==="issue:comment:edit"){let i=await Cn({repoRoot:a,...l.payload});return E(r,a,o,"issue:comment:edit:result",i)}if(d==="issue:comment:delete"){let i=await At({repoRoot:a,...l.payload});return S(r,{type:"issue:comment:delete:result",payload:i}),h(i)?S(r,{type:"failed",payload:i.message}):(o(),Ke(r,a))}if(d==="issue:edit:description"){let i=await mn({repoRoot:a,...l.payload});return E(r,a,o,"issue:edit:description:result",i)}if(d==="issue:edit:title"){let i=await cn({repoRoot:a,...l.payload});return E(r,a,o,"issue:edit:title:result",i)}if(d==="issue:tag:add"){let i=await xn({repoRoot:a,...l.payload});return E(r,a,o,"issue:tag:add:result",i)}if(d==="issue:goal:set")return E(r,a,o,"issue:goal:set:result",await Yr({repoRoot:a,...l.payload}));if(d==="goal:create")return E(r,a,o,"goal:create:result",await qr({repoRoot:a,...l.payload}));if(d==="goal:rename")return E(r,a,o,"goal:rename:result",await Jr({repoRoot:a,...l.payload}));if(d==="goal:remove")return E(r,a,o,"goal:remove:result",await Xr({repoRoot:a,...l.payload}));if(d==="issue:tag:remove"){let i=await vn({repoRoot:a,...l.payload});return E(r,a,o,"issue:tag:remove:result",i)}if(d==="contributor:remove"){let i=await In({repoRoot:a,...l.payload});return E(r,a,o,"contributor:remove:result",i)}if(d==="tag:remove"){let i=await wn({repoRoot:a,...l.payload});return E(r,a,o,"tag:remove:result",i)}if(d==="contributors:get")return S(r,{type:"contributors",boardId:l.payload?.boardId,payload:await Tn({repoRoot:a,...l.payload})});if(d==="issue:assignee:add"){let i=await Sn({repoRoot:a,...l.payload});return E(r,a,o,"issue:assignee:add:result",i)}if(d==="issue:assignee:remove"){let i=await En({repoRoot:a,...l.payload});return E(r,a,o,"issue:assignee:remove:result",i)}if(d==="issues:list")return S(r,{type:"issues",payload:await on({repoRoot:a})});if(d==="issues:create"){let i=await rn({...l.payload,repoRoot:a});return S(r,{type:"issues:create:result",payload:i}),h(i)?ue({type:"failed",payload:i.message}):(o(),Ke(r,a))}if(d==="board:create"){let i=await dn({...l.payload,repoRoot:a});return E(r,a,o,"board:create:result",i)}if(d==="board:edit:title"){let i=await un({...l.payload,repoRoot:a});return E(r,a,o,"board:edit:title:result",i)}if(d==="swimlane:create"){let i=await pn({...l.payload,repoRoot:a});return E(r,a,o,"swimlane:create:result",i)}if(d==="swimlane:edit:title"){let i=await fn({...l.payload,repoRoot:a});return E(r,a,o,"swimlane:edit:title:result",i)}if(d==="board:edit:description"){let i=await Wr({...l.payload,repoRoot:a});return E(r,a,o,"board:edit:description:result",i)}if(d==="swimlane:edit:description"){let i=await Vr({...l.payload,repoRoot:a});return E(r,a,o,"swimlane:edit:description:result",i)}if(d==="swimlane:rule:set"){let i=await zr({...l.payload,repoRoot:a});return E(r,a,o,"swimlane:rule:set:result",i)}if(d==="goal:edit:description"){let i=await Kr({repoRoot:a,goal:l.payload.goalId,description:l.payload.description});return E(r,a,o,"goal:edit:description:result",i)}if(d==="swimlane:delete"){let i=await yn({...l.payload,repoRoot:a});return E(r,a,o,"swimlane:delete:result",i)}if(d==="swimlane:move"){if(!l.payload.position)return S(r,{type:"error",message:"Missing move position"});let i=await gn({...l.payload,repoRoot:a});return E(r,a,o,"swimlane:move:result",i)}if(d==="issues:move"){if(!l.payload.position)return S(r,{type:"error",message:"Missing move position"});let i=await an({...l.payload,repoRoot:a});return E(r,a,o,"issues:move:result",i)}if(d==="issue:close"){let i=await nn({repoRoot:a,issueId:l.payload.issueId});return E(r,a,o,"issue:close:result",i)}if(d==="issue:reopen"){let i=await sn({repoRoot:a,issueId:l.payload.issueId});return E(r,a,o,"issue:reopen:result",i)}return S(r,{type:"error",message:"Unknown message type"})},p,g=async l=>{if(p!=="repo:connect"&&p!=="repo:disconnect"&&p!=="repo:preview")return S(r,{type:"error",message:l});S(r,{type:"repos",for:p,payload:await Ae({repoRoot:t.repoRoot}),preview:null,lastAction:{ok:!1,message:l}}),p!=="repo:preview"&&S(r,{type:`${p}:result`,payload:G(l)})};try{let l=JSON.parse(f.toString());p=l?.type;let d=si(l);if(!d.ok)return await g(d.error);let a=d.message;return oi.has(a.type)?await pe(async()=>ie().mode!=="live"?S(r,{type:"failed",payload:"Read-only while viewing history"}):y(a)):await y(a)}catch(l){return g(l instanceof Error?l.message:String(l))}})})};var fa=xe.dirname(pa(import.meta.url)),ga=process.env.IS_LOCAL==="true"?xe.resolve(process.cwd(),"dist/gui"):xe.join(fa,"gui"),Lt,ya=async()=>(Lt===void 0&&(Lt=await import("node:sea").catch(()=>null)),Lt?.isSea()?Lt:null),R=(e,t,o)=>{e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(o))},ha=e=>R(e,409,{isError:!0,message:"Read-only while viewing history"}),jt=(e,t)=>pe(async()=>ie().mode!=="live"?ha(e):t()),fi=async(e,t=1024*1024)=>new Promise((o,n)=>{let s=[],u=0;e.on("data",r=>{if(u+=r.length,u>t){e.destroy(),n(new Error("Request body too large"));return}s.push(r)}),e.on("end",()=>{let r=Buffer.concat(s).toString("utf8");try{o(r?JSON.parse(r):{})}catch{n(new Error("Invalid JSON body"))}}),e.on("error",n)}),ba=2*1024*1024,xa={png:"image/png",jpg:"image/jpeg",gif:"image/gif",webp:"image/webp"},wa=e=>e.endsWith(".html")?"text/html; charset=utf-8":e.endsWith(".js")?"text/javascript; charset=utf-8":e.endsWith(".css")?"text/css; charset=utf-8":e.endsWith(".json")?"application/json; charset=utf-8":e.endsWith(".ico")?"image/x-icon":e.endsWith(".woff2")?"font/woff2":"application/octet-stream",gi=new Map,va=e=>`"${da("sha1").update(e).digest("base64url")}"`,Sa=async e=>{try{let{size:t,mtimeMs:o}=await ca(e);return`${t}:${o}`}catch{return null}},Ia=e=>{try{return decodeURIComponent(e)}catch{return null}},yi=async(e,t,o)=>{let n=e==="/"?"/index.html":Ia(e);if(n===null)return R(o,404,{isError:!0,message:"Not found"});let s=n.replace(/^\/+/,""),u=xe.resolve(ga),r=xe.resolve(u,s),f=xe.relative(u,r);if(f.startsWith("..")||xe.isAbsolute(f))return R(o,403,{isError:!0,message:"Forbidden"});let y=await ya(),p=y?"sea":await Sa(r),g=p===null?void 0:gi.get(f),l=g&&g.stamp===p?g.etag:null;if(l&&t.headers["if-none-match"]===l)return o.writeHead(304,{etag:l,"cache-control":"no-cache"}),o.end();try{let d=y?Buffer.from(y.getAsset(`gui/${f}`)):await hi(r),a=l??va(d);return p!==null&&gi.set(f,{stamp:p,etag:a}),o.writeHead(200,{"content-type":wa(r),etag:a,"cache-control":"no-cache"}),o.end(d)}catch{return R(o,404,{isError:!0,message:`Not found: ${s}`})}},Ta=async(e,t=We)=>new Promise((o,n)=>{let s=u=>{let r=y=>{if(e.off("listening",f),y.code==="EADDRINUSE"&&u===t){e.removeListener("error",r),s(0);return}n(y)},f=()=>{e.off("error",r);let y=e.address();if(!y||typeof y=="string"){n(new Error("Unable to resolve address"));return}o(y.port)};e.once("error",r),e.once("listening",f),e.listen(u,"127.0.0.1")};s(t)}),bi=async e=>{_r(!1);let t=0,o={repoRoot:e.repoRoot},n=ua.createServer((p,g)=>{s(p,g).catch(l=>(logger.error(`[gui] ${p.method} ${p.url}: ${l instanceof Error?l.message:String(l)}`),g.headersSent?g.end():R(g,500,{isError:!0,message:"Internal error"})))});async function s(p,g){let l=new URL(p.url??"/","http://127.0.0.1"),d=Jn({method:p.method,origin:p.headers.origin,contentType:p.headers["content-type"]},t);if(d)return R(g,d.status,{isError:!0,message:d.message});if(p.method==="GET"&&l.pathname===co)return R(g,200,{app:uo,repoRoot:Rt(o.repoRoot),version:F,pid:process.pid});if(l.pathname==="/api/state")return R(g,200,await ee({repoRoot:o.repoRoot}));if(p.method==="POST"&&l.pathname==="/api/comments")try{let a=await fi(p);if(!a.issueId||!a.body?.trim())return R(g,400,{isError:!0,message:"Missing issueId or body"});let i=a.issueId,x=a.body;return await jt(g,async()=>{let L=await Pt({repoRoot:o.repoRoot,issueId:i,body:x});return h(L)?R(g,400,{isError:!0,message:L.message}):R(g,200,await ee({repoRoot:o.repoRoot}))})}catch(a){return R(g,400,{isError:!0,message:a instanceof Error?a.message:"Unable to add comment"})}if(p.method==="POST"&&l.pathname==="/api/attachments")try{let a=await fi(p,ba);if(!a.issueId||!a.dataBase64)return R(g,400,{isError:!0,message:"Missing issueId or dataBase64"});let i=a.issueId,x=a.dataBase64,L=a.name??"";return await jt(g,async()=>{let D=await Qr({repoRoot:o.repoRoot,issueId:i,name:L,dataBase64:x});return h(D)?R(g,400,{isError:!0,message:D.message}):R(g,200,{...await ee({repoRoot:o.repoRoot}),attachment:D.value})})}catch(a){return R(g,400,{isError:!0,message:a instanceof Error?a.message:"Unable to add attachment"})}if(p.method==="DELETE"&&l.pathname.startsWith("/api/attachments/")){let a=decodeURIComponent(l.pathname.replace("/api/attachments/",""));return a?jt(g,async()=>{let i=await Zr({repoRoot:o.repoRoot,attachmentId:a});return h(i)?R(g,400,{isError:!0,message:i.message}):R(g,200,await ee({repoRoot:o.repoRoot}))}):R(g,400,{isError:!0,message:"Missing attachment id"})}if(p.method==="GET"&&l.pathname.startsWith("/media/")){let a=decodeURIComponent(l.pathname.replace("/media/","")),i=await en({repoRoot:o.repoRoot,fileName:a});if(h(i))return R(g,404,{isError:!0,message:i.message});try{let x=await hi(i.value.filePath);return g.writeHead(200,{"content-type":xa[i.value.ext]??"application/octet-stream","cache-control":"public, max-age=31536000, immutable"}),g.end(x)}catch{return R(g,404,{isError:!0,message:"Attachment blob not found"})}}if(p.method==="DELETE"&&l.pathname.startsWith("/api/comments/")){let a=decodeURIComponent(l.pathname.replace("/api/comments/",""));return a?jt(g,async()=>{let i=await At({repoRoot:o.repoRoot,commentId:a});return h(i)?R(g,400,{isError:!0,message:i.message}):R(g,200,await ee({repoRoot:o.repoRoot}))}):R(g,400,{isError:!0,message:"Missing comment id"})}return l.pathname.startsWith("/board/")||l.pathname==="/log"?yi("/",p,g):yi(l.pathname,p,g)}try{t=await Ta(n)}catch(p){return G(p instanceof Error?`Unable to start GUI server: ${p.message}`:`Unable to start GUI server: ${String(p)}`)}let u=Yn({project:o}),r=Hn({project:o,publisher:u,onReposFetch:p=>xo(o.repoRoot,p)}),f=qn(u);pi(n,o,{onStateChanged:()=>r.queueSync(),onSyncSettingsChanged:()=>r.reschedule(),getPort:()=>t}),n.on("close",()=>{r.dispose(),f()});let y=n.address();return!y||typeof y=="string"?(n.close(),G("Unable to resolve GUI server address")):T("Started GUI server",{url:`http://epiq.localhost:${y.port}${e.boardId?`/board/${e.boardId}`:""}`,server:n})};var xi=async e=>{let t=f=>{e.open!==!1&&Un()&&Gn(f)},o=it({});if(h(o))return G(o.message);let n=Ie();$(n)&&Ue(n.value);let s=await Fn(We);if(s&&s.repoRoot===Rt(e.repoRoot)){let f=`http://127.0.0.1:${We}`;return console.log(mo({url:f,repoRoot:e.repoRoot,version:F,reused:!0})),t(f),T("Reused running GUI",{url:f})}let u=await bi({repoRoot:e.repoRoot,boardId:""});if(h(u))return u;let{url:r}=u.value;return console.log(mo({url:r,repoRoot:e.repoRoot,version:F,reused:!1})),t(r),T("Started GUI",{url:r})};import{Box as ce}from"ink";import M from"react";import{Box as wo,Text as Ye}from"ink";import te,{useEffect as Ea,useMemo as Ca}from"react";import{ulid as Pa}from"ulid";var wi=({text:e,match:t,color:o,highlightColor:n=m.secondary,dimColor:s=!1})=>{if(!t)return te.createElement(Ye,{color:o,dimColor:s},e);let u=e.toLowerCase(),r=t.toLowerCase(),f=u.indexOf(r);return f===-1?te.createElement(Ye,{color:o,dimColor:s},e):te.createElement(Ye,{color:o,dimColor:s},e.slice(0,f),te.createElement(Ye,{backgroundColor:n,bold:!0},e.slice(f,f+t.length)),e.slice(f+t.length))},vi=Pa(),Aa=e=>`${e}`,Na=e=>{let{pendingNavTarget:t}=Sr();if(!t)return[];let{breadCrumb:o,contextNode:n,selectedNode:s}=t,u=new Set(Qo(ft.NONE,{breadCrumb:o,contextNode:n,readOnly:Q().readOnly,selectedNode:s}));return[...new Set(Object.values(ft))].filter(r=>r!=="move").filter(r=>r.length>0).filter(r=>r!==ft.PALETTE).map(r=>{let f=hr(r),y=Mr.find(p=>p.intent===f);return{command:r,description:y?.description??`[${r}] Run command`,isAvailable:u.has(r)}}).filter(r=>r.command.toLowerCase().startsWith(e)||e.length>=2&&r.description.toLowerCase().includes(e)).sort((r,f)=>r.isAvailable!==f.isAvailable?r.isAvailable?-1:1:r.command.localeCompare(f.command))},Ma=e=>Ee.text({id:vi,parentNodeId:e,rank:"000000",name:"Command Palette",readonly:!0,isVirtual:!0}),Ra=(e,t,o)=>Ee.text({id:Aa(e.command),parentNodeId:o,rank:String(t).padStart(6,"0"),name:e.command,disabled:!e.isAvailable,readonly:!0,isVirtual:!0}),vo=[],Ba=e=>{Si();let t=q.createNode(Ma(Q().rootNodeId));h(t)||!Te(t.value)||(vo=[t.value,...e.map((o,n)=>Ra(o,n,vi)).map(q.createNode).filter($).map(({value:o})=>o).filter(Te)],Ce.navigate({contextNode:t.value,selectedIndex:0}))},Si=()=>{let e=vo.map(t=>t.id);vo=[];for(let t of e)q.deleteNode(t)};function Ii({width:e,height:t}){let n=ur().value.trim().toLowerCase(),s=Ca(()=>Na(n),[n]);Ea(()=>(Ba(s),()=>{Si()}),[s]);let u=Q().selectedIndex;return te.createElement(wo,{flexDirection:"column",width:e,height:t-2},te.createElement(wt,{height:t-4,itemHeight:3,selectedIndex:u},s.map((r,f)=>{let y=f===u,p=r.isAvailable?m.accent:m.secondary2,g=r.isAvailable?"":"[unavailable] ";return te.createElement(wo,{key:r.command,flexDirection:"column",paddingX:1,borderLeft:!1,borderBottom:!1,borderRight:!1,borderColor:m.secondary,borderStyle:"single"},te.createElement(Ye,{color:p,dimColor:!r.isAvailable},y?"\u276F ":" ",te.createElement(wi,{text:zo(r.command),match:n,color:p,dimColor:!r.isAvailable})),te.createElement(wo,{paddingLeft:2},te.createElement(wi,{text:g+r.description,match:n.length>=2?n:"",color:y&&r.isAvailable?m.primary:m.secondary2,dimColor:!r.isAvailable})))})))}import{Box as Ya}from"ink";import Ot from"react";import Je from"chalk";import{Box as qe,Text as Pi}from"ink";import we,{useEffect as Ai,useMemo as La,useState as Ni}from"react";var Me=["\u2803","\u2806","\u2830","\u2818"],Ti=120,Ei=({isPalette:e,isPending:t,frame:o})=>{if(t){let n=(o%Me.length+Me.length)%Me.length;return Me[n]}return e?"?":":"},Ci=({infoMessage:e,isPending:t})=>t&&e.includes(cr)?"":e;var Mi=Je.hex(m.secondary),ja=Je.inverse,_a=Je.inverse.hex(m.secondary2),Da={hint:"",hints:[],overlap:0,remainder:""},Bi=({value:e,command:t})=>{if(!t)return null;let o=e.trimStart(),s=e.length-o.length;return e.slice(s,s+t.length)!==t?null:{start:s,end:s+t.length}},Oa=({command:e,modifier:t})=>{if(!t)return null;if(e==="tag"||e==="assign"){let o=Go(t);return{bg:n=>Je.bgHex(o).black(n),bgCursor:n=>Je.bgHex(o).inverse.black(n)}}return oo(t)},Ua=({value:e,command:t,modifier:o})=>{if(!t||!o)return null;let n=Bi({value:e,command:t});if(!n)return null;let u=e.slice(n.end).match(/^\s*/)?.[0].length??0,r=n.end+u;return e.slice(r,r+o.length)!==o?null:{start:r,end:r+o.length}},Ri=()=>({value:ne.value,cursorPosition:ne.cursorPosition,commandIsPending:ne.commandIsPending,autoCompletion:ne.commandMeta.autoCompletion??Da,infoMessage:ne.commandMeta.infoMessage,validationStatus:ne.commandMeta.validity,command:ne.commandMeta.command,inputString:ne.commandMeta.inputString,modifier:ne.commandMeta.modifier}),Ga=(e,t)=>e.value===t.value&&e.cursorPosition===t.cursorPosition&&e.commandIsPending===t.commandIsPending&&e.infoMessage===t.infoMessage&&e.autoCompletion.hint===t.autoCompletion.hint&&e.autoCompletion.overlap===t.autoCompletion.overlap&&e.autoCompletion.remainder===t.autoCompletion.remainder&&e.validationStatus===t.validationStatus&&e.command===t.command&&e.modifier===t.modifier&&e.inputString===t.inputString,Li=({width:e,mode:t})=>{let[o,n]=Ni(Ri());Ai(()=>{let B=()=>{let C=Ri();n(I=>Ga(I,C)?I:C)},P=dr(B);return B(),()=>{P()}},[]);let{value:s,cursorPosition:u,infoMessage:r,commandIsPending:f,autoCompletion:y,command:p,modifier:g,validationStatus:l}=o,d=f&&l===mr.Invalid,a=Ci({infoMessage:r,isPending:f}),[i,x]=Ni(0);Ai(()=>{if(!f){x(0);return}let B=setInterval(()=>{x(P=>(P+1)%Me.length)},Ti);return()=>{clearInterval(B)}},[f]);let L=Ei({isPalette:t===H.PALETTE,isPending:f,frame:i}),D=La(()=>{let B=Math.max(0,Math.min(u,s.length)),P=Bi({value:s,command:p}),C=Ua({value:s,command:p,modifier:g}),I=p?oo(p):null,V=Oa({command:p,modifier:g}),Se=(K,Y)=>{let eo=P&&Y>=P.start&&Y<P.end,to=C&&Y>=C.start&&Y<C.end;return eo&&I?I.fg(K):to&&V?V.bg(K):K},De=(K,Y)=>{let eo=P&&Y>=P.start&&Y<P.end,to=C&&Y>=C.start&&Y<C.end;return eo&&I?I.fgCursor(K):to&&V?V.bgCursor(K):ja(K)},he=s.slice(0,B),Z=s[B]??" ",Oe=B<s.length?s.slice(B+1):"",nt=Array.from(he).map((K,Y)=>Se(K,Y)).join(""),Bo=De(Z,B),Lo=Array.from(Oe).map((K,Y)=>Se(K,B+1+Y)).join("");if(y.hint){let K=y.hint[y.overlap]??Z;Bo=_a(K),Lo=Mi(y.remainder.slice(1)+Oe)}return nt+Bo+Lo},[s,u,y,p,g]);return we.createElement(qe,{flexDirection:"column",justifyContent:"flex-start"},we.createElement(qe,{flexDirection:"column",paddingX:1,borderColor:m.secondary,borderStyle:"round",width:e},we.createElement(qe,{height:1,flexWrap:"nowrap"},we.createElement(qe,{flexShrink:0},we.createElement(Pi,{wrap:"truncate-start"},Mi(L),D)),a&&we.createElement(qe,{flexShrink:1,minWidth:0},we.createElement(Pi,{wrap:"truncate",color:d?m.red:m.secondary2},` ${a} `)))))};import{Box as $a,Text as Xe}from"ink";import Re from"react";var ji=" ",Fa=({keys:e,label:t})=>e.length+1+t.length,Ha=(e,t)=>{let o=[],n=0;for(let s of e){let r=(o.length>0?ji.length:0)+Fa(s);if(n+r>t-4)break;o.push(s),n+=r}return o},_i=({width:e,availableHints:t})=>{let o=Ha(t,e);return Re.createElement($a,{width:e,borderStyle:"round",borderColor:m.secondary,paddingX:1},o.length?Re.createElement(Xe,{wrap:"truncate"},o.map(({keys:s,label:u},r)=>Re.createElement(Xe,{key:`${s}-${u}`},r>0?ji:"",Re.createElement(Xe,{color:m.accent},s),Re.createElement(Xe,{color:m.secondary2}," ",u)))):Re.createElement(Xe,{color:m.secondary2},"\xA0"))};import{Box as ka,Text as _t}from"ink";import ze,{useEffect as Wa,useRef as Be,useState as Va}from"react";var Ka=40,Dt=.3,So="\u2500",Di=1,Oi=16,Ui=({width:e})=>{let{replay:t}=O(),o=t!==null,n=t?Math.min(1,Math.max(0,t.progress)):0,u=Math.max(4,e-6),r=u*Di,[f,y]=Va(0),p=Be(n);p.current=n;let g=Be(0),l=Be(0),d=Be(0),a=Be(0),i=Be(0);if(Wa(()=>{if(!o){g.current=0,d.current=0,a.current=0,i.current=0,y(0);return}g.current=p.current,l.current=Date.now(),d.current=p.current;let P=setInterval(()=>{let C=Date.now(),I=p.current;if(I!==d.current){let Z=I-d.current,Oe=C-l.current;if(Z<0)a.current=0,i.current=0;else if(Z>0&&Oe>0){let nt=Z/Oe;a.current=a.current===0?nt:a.current*(1-Dt)+nt*Dt,i.current=i.current===0?Z:i.current*(1-Dt)+Z*Dt}g.current=I,l.current=C,d.current=I}let V=C-l.current,Se=Math.min(i.current,a.current*V),De=Math.min(1,g.current+Se),he=Math.round(De*r);y(Z=>Z===he?Z:he)},Ka);return()=>clearInterval(P)},[o,r]),!t)return null;let x=P=>{let C=u>1?P/(u-1):0,I=Math.round(C*(Oi-1))/(Oi-1);return Ho(I)},L=Math.min(u,Math.floor(f/Di)),D=[];for(let P=0;P<L;P++){let C=x(P),I=D[D.length-1];I&&I.color===C?I.text+=So:D.push({text:So,color:C})}let B=So.repeat(Math.max(0,u-L));return ze.createElement(ka,{width:e,borderStyle:"round",borderColor:m.secondary,paddingX:1,columnGap:1},ze.createElement(_t,{color:m.secondary2},"\u25B6"),ze.createElement(_t,null,D.map((P,C)=>ze.createElement(_t,{key:C,color:P.color},P.text)),ze.createElement(_t,{color:m.secondary,dimColor:!0},B)))};var Qe=({width:e,mode:t,availableHints:o})=>{let{timeMode:n}=O(),s=t===H.COMMAND_LINE||t===H.PALETTE;return Ot.createElement(Ya,null,s?Ot.createElement(Li,{width:e,mode:t}):n==="replay"?Ot.createElement(Ui,{width:e}):Ot.createElement(_i,{width:e,mode:t,availableHints:o}))};import{Box as fe,Text as Le}from"ink";import k,{useEffect as qa,useMemo as Ja}from"react";import{ulid as Gi}from"ulid";var Gt=Gi(),Xa=()=>[...new Set([...Q().availableActions].map(e=>e.description).filter(e=>e!==void 0))].map(Rr).filter(e=>e!==null).sort(({keys:e},{keys:t})=>e.length===1&&t.length!==1?-1:e.length!==1&&t.length===1?1:e.localeCompare(t,void 0,{sensitivity:"base"})).map(({keys:e,label:t})=>({keys:e,action:t})),za=e=>Ee.text({id:Gt,parentNodeId:e,rank:"000000",name:"Help",isVirtual:!0,readonly:!0}),Qa=(e,t,o)=>Ee.text({id:Gi(),parentNodeId:o,rank:String(t).padStart(6,"0"),name:e.keys,readonly:!0,isVirtual:!0}),Ut=[],$i=()=>{let e=Ut.filter(o=>o.id!==Gt).map(o=>o.id),t=Ut.filter(o=>o.id===Gt).map(o=>o.id);Ut=[];for(let o of[...e,...t])q.deleteNode(o)},Za=e=>{$i();let t=q.createNode(za(Q().rootNodeId));if(h(t)||!Te(t.value))return logger.error("Could not attach help nodes");Ut=[t.value,...e.map((o,n)=>Qa(o,n,Gt)).map(q.createNode).filter($).map(({value:o})=>o).filter(Te)],Ce.navigate({contextNode:t.value,selectedIndex:0})},Fi=({width:e,height:t})=>{let o=Ja(()=>Xa(),[]);qa(()=>(Za(o),()=>{$i()}),[o]);let n=Q().selectedIndex;return k.createElement(fe,{flexDirection:"column",marginTop:3,borderColor:m.secondary,borderStyle:"round",width:e,height:t-2},k.createElement(fe,{paddingX:1,justifyContent:"center"},k.createElement(Le,{color:m.accent},"Help"),k.createElement(Le,{dimColor:!0}," \u2014 navigate with arrows/hjkl, press q to close")),k.createElement(fe,{paddingX:1,marginTop:1},k.createElement(fe,{width:20,justifyContent:"flex-end"},k.createElement(Le,{color:m.secondary},"Key(s)")),k.createElement(fe,{flexGrow:1,width:40,paddingLeft:2},k.createElement(Le,{color:m.secondary},"Action"))),k.createElement(wt,{height:t-7,itemHeight:1,selectedIndex:n},o.map(({keys:s,action:u},r)=>{let f=r===n;return k.createElement(fe,{paddingX:1,key:`${s}-${u}-${r}`,flexDirection:"row"},k.createElement(fe,{width:20,justifyContent:"flex-end"},k.createElement(Le,{color:m.accent},s)),k.createElement(fe,{flexGrow:1,width:40,paddingLeft:2},k.createElement(Le,{color:f?m.primary:m.secondary2},u)))})))};import{Box as je,Text as W}from"ink";import A,{useEffect as el,useState as Io}from"react";var $t=({index:e,email:t,note:o,noteColor:n})=>A.createElement(je,null,A.createElement(W,{color:m.accent},e===void 0?" ":` ${e}.`),A.createElement(W,null," ",t),o&&A.createElement(W,{color:n??m.secondary2},` ${o}`)),Hi=({height:e})=>{let[t,o]=Io([]),[n,s]=Io([]),[u,r]=Io(null),{userName:f,gitEmail:y,gitName:p,userId:g}=re();return el(()=>{let l=!0;return(async()=>{let a=Pe(),i=oe(process.cwd());if(h(a)||h(i)||!g)return;let x=a.value.emailLinks,L=Ko(x,g);o(L.map(C=>({email:C,others:Vo(x,C).filter(I=>I!==g).map(I=>a.value.contributors[I]?.name??I)})));let D=at(i.value),B=await St({repoRoot:i.value,stateBranch:h(D)?void 0:D.value,names:[f,p],links:x});if(!l)return;if(h(B)){r(B.message);return}let P=It(B.value);ut(P.map(C=>C.email)),s(P)})(),()=>{l=!1}},[g,f,p,y]),A.createElement(je,{height:e-4,flexDirection:"column",paddingTop:1,paddingLeft:2,borderStyle:"round",borderColor:m.secondary,rowGap:1},A.createElement(je,null,A.createElement(W,{color:m.accent,bold:!0},f??"Not configured"),A.createElement(W,{color:m.secondary2}," on this board")),u&&A.createElement(W,{color:m.red},u),A.createElement(je,{flexDirection:"column"},A.createElement(W,{color:m.secondary2},"CLAIMED"),t.length===0?A.createElement($t,{email:"Nothing claimed \u2014 your commits show the name git signed them with.",note:""}):t.map(l=>A.createElement($t,{key:l.email,email:l.email,note:l.others.length>0?`CONTESTED: also ${l.others.join(", ")}, resolves to nobody`:l.email===y?"your git identity here":void 0,noteColor:l.others.length>0?m.yellow:void 0}))),A.createElement(je,{flexDirection:"column"},A.createElement(W,{color:m.secondary2},"UNCLAIMED IN THIS HISTORY"),n.length===0?A.createElement($t,{email:"Nothing left to claim here."}):n.map(l=>A.createElement($t,{key:l.email,email:l.email,note:`${l.commits} commit${l.commits===1?"":"s"} as ${l.names.join(", ")||"unknown"}`}))),A.createElement(je,{flexDirection:"column"},A.createElement(W,{color:m.secondary2},A.createElement(W,{color:m.accent},"[enter]")," writes a claim for the first into the command line, to confirm or change."),A.createElement(W,{color:m.secondary2},A.createElement(W,{color:m.accent},":config emails")," claims by address,",A.createElement(W,{color:m.accent}," :config emails-unclaim "),"gives one back."),A.createElement(W,{color:m.secondary2},A.createElement(W,{color:m.accent},"[esc]")," to close")))};import tl from"chalk";import{Box as ve,Text as U}from"ink";import N from"react";var ol=8,rl=()=>{let e=Ge({exclude:process.cwd()});return h(e)?[]:e.value.slice(0,ol)},ki=({width:e,height:t})=>{let o=rl();return N.createElement(ve,{height:t-4,flexDirection:"column",width:e,paddingTop:1,paddingLeft:2,borderStyle:"round",borderColor:m.secondary,rowGap:1},N.createElement(U,{color:m.accent,bold:!0},"Initialize project"),N.createElement(U,null,`This folder is not an ${tl.hex(m.accent)("epiq")} project yet.`),N.createElement(U,{color:m.primary},"To start tracking issues here, we need to initialize a new",N.createElement(U,{color:m.primary,backgroundColor:m.secondary}," .epiq/project.json "),"file in this repository."),N.createElement(ve,{marginTop:1,flexDirection:"column"},N.createElement(ve,null,N.createElement(U,{color:m.accent}," "),N.createElement(U,{color:m.primary},"Type "),N.createElement(U,{backgroundColor:m.secondary}," :init "))),N.createElement(ve,{marginTop:1},N.createElement(U,{color:m.secondary2},"This will create, commit and push "),N.createElement(U,{color:m.primary,backgroundColor:m.secondary}," .epiq/project.json ")),o.length>0&&N.createElement(ve,{marginTop:1,flexDirection:"column"},N.createElement(U,{color:m.accent,bold:!0},"Or open a recent project"),o.map((n,s)=>N.createElement(ve,{key:n.projectId},N.createElement(U,{color:m.accent},` ${s+1} `),N.createElement(U,{color:m.primary},dt(n.root)),N.createElement(U,{color:m.secondary2},` ${n.root}`))),N.createElement(ve,{marginTop:1},N.createElement(U,{color:m.accent}," "),N.createElement(U,{color:m.primary},"Type "),N.createElement(U,{backgroundColor:m.secondary}," :open 1 "),N.createElement(U,{color:m.secondary2}," or "),N.createElement(U,{backgroundColor:m.secondary}," :open <path> "))))};import{Box as Ht,Text as X}from"ink";import j from"react";import{Box as Ft,Text as me}from"ink";import J,{useEffect as nl,useState as Wi}from"react";var Vi=()=>{let[e,t]=Wi(null),[o,n]=Wi(null);return nl(()=>{let s=!0;return(async()=>{let r=Pe(),f=oe(process.cwd());if(h(r)||h(f))return;let y=at(f.value),{userName:p,gitName:g}=re(),l=await St({repoRoot:f.value,stateBranch:h(y)?void 0:y.value,names:[p,g],links:r.value.emailLinks});if(!s)return;if(h(l)){n(l.message);return}let d=It(l.value);ut(d.map(a=>a.email)),t(d)})(),()=>{s=!1}},[]),o?J.createElement(Ft,{marginTop:1},J.createElement(me,{color:m.red},o)):e===null?null:e.length===0?J.createElement(Ft,{marginTop:1},J.createElement(me,{color:m.secondary2},"No unclaimed addresses in this history \u2014 type",J.createElement(me,{color:m.accent}," :config emails none "),"to move on.")):J.createElement(Ft,{flexDirection:"column",marginTop:1},e.map(s=>J.createElement(me,{key:s.email},J.createElement(me,{color:m.accent}," \xB7 "),J.createElement(me,null,s.email),J.createElement(me,{color:m.secondary2},` ${s.commits} commit${s.commits===1?"":"s"} as ${s.names.join(", ")||"unknown"}`))),J.createElement(Ft,{marginTop:1},J.createElement(me,{color:m.secondary2},"Claim yours by address, or",J.createElement(me,{color:m.accent}," none "),"to skip.")))};var il=({isDone:e,command:t,value:o,verb:n="Type",hint:s})=>j.createElement(Ht,null,j.createElement(X,{color:m.accent,dimColor:e},e?" \u2714 ":" "),j.createElement(X,{dimColor:e},n," "),j.createElement(X,{backgroundColor:m.secondary,dimColor:e}," "+t+" "),o!==void 0&&j.createElement(j.Fragment,null,j.createElement(X,{dimColor:e}," "),j.createElement(X,{color:m.secondary,dimColor:e},"<"+o+">")),s),sl=(e,t)=>e.length===0?void 0:Jo(e.join(", "),Math.max(12,t-34)),al=e=>{if(typeof e=="string")return e;if(typeof e=="boolean")return e?"on":"off"};function To({width:e,height:t}){let{isSetPreferredEditor:o,isSetUserName:n,userName:s,preferredEditor:u,autoSync:r,isSetAutoSync:f,isSetEmails:y,claimedEmails:p,emailSetupDeclined:g}=pt(),l=[{key:"username",done:n,command:":config username",value:s,message:"First, choose your username."},{key:"editor",done:o,command:":config editor",value:u,message:"Nice. Now pick your editor."},{key:"autosync",done:f,command:":config autosync",value:r,message:"Almost there. Configure auto sync."},{key:"emails",done:y,command:":config emails",value:g?"declined":sl(p,e),message:"Last one. Claim the git addresses whose commits are yours, so the board calls them by your name."}],d=l.findIndex(i=>!i.done),a=d===-1?void 0:l[d];return j.createElement(Ht,{height:t-4,flexDirection:"column",width:e,paddingTop:1,paddingLeft:2,borderStyle:"round",borderColor:m.secondary,rowGap:1},j.createElement(X,{color:m.accent,bold:!0},"Welcome! \u{1F539}"),j.createElement(X,null,"Let's get you started - it's a breeze."),j.createElement(X,{color:m.secondary2},"Remember - you can always search available commands by typing:",j.createElement(X,{color:m.accent}," ? ")),a&&j.createElement(X,{color:m.secondary2},a.message),j.createElement(Ht,{flexDirection:"column"},l.map((i,x)=>{if(!(d===-1||x<=d))return null;let D=x===d,B=D&&d===0;return j.createElement(Ht,{key:i.key,flexDirection:"column",marginBottom:1},j.createElement(il,{isDone:i.done,command:B?":config us":i.command,value:i.done?al(i.value):void 0,verb:D&&!B?"Waiting:":"Type",hint:B?j.createElement(X,{color:m.secondary2}," then ",j.createElement(X,{color:m.accent},"tab")," to auto-complete ",j.createElement(X,{backgroundColor:m.secondary},` ${i.command.split(" ").pop()} `)):void 0}),i.key==="emails"&&!i.done&&j.createElement(Vi,null))})))}import Yt from"chalk";import{Box as tt,Text as _e}from"ink";import _ from"react";import{Box as kt,Text as Ki}from"ink";import ge from"react";import ll from"chalk";var ml=e=>(q.getTag(e)?.name??"").length+3,cl=e=>{let t=q.getContributor(e);return(t?t.name:"").length+3},Yi=({width:e})=>{let{viewMode:t}=re(),o=O(),{breadCrumb:n,contextNode:s,selectedIndex:u,renderedChildrenIndex:r}=o,f=r[s.id]?.[u],y=Lr(f?.id??s.id,"TICKET"),p=$(y)?y.value:void 0,g=(p?.props.tags??[]).filter(I=>q.getTag(I)),l=p?.props.assignees??[],d=p?.parentNodeId?!jr(s.id,p.parentNodeId)&&t==="dense":!1,a=n.map((I,V)=>{let Se=V===n.length-1,De=ht(I.id),he=Se?De?.[u]?.title:void 0;return`${V?" / ":""}${I.title??""}${he?` \u25B8 ${he}`:""}`}).join(""),i=p?q.getCommentsByIssue(p.id):[],x=i.length?String(i.length).length+11:0,L=d?g.reduce((I,V)=>I+ml(V),0)+l.reduce((I,V)=>I+cl(V),0)+x:0,D=f?.id!=null&&!(t==="wide"&&ct(f)),B=Math.max(0,e-L),P=a.substring(0,B)+(D?ll.hex(m.secondary2).dim(` ${de(f.id)}`):""),C=d?[...g.map(I=>ge.createElement(kt,{key:`tag-${I}`,paddingLeft:1},ge.createElement(wr,{id:I}))),...l.map(I=>ge.createElement(kt,{key:`assignee-${I}`,paddingLeft:1},ge.createElement(xr,{id:I}))),i.length?ge.createElement(kt,{key:"comments",paddingLeft:1},ge.createElement(Ki,{color:m.accent},"[",i.length," comment",i.length===1?"":"s","]")):null]:[];return ge.createElement(kt,{overflow:"hidden",justifyContent:"flex-start",alignItems:"flex-start"},ge.createElement(Ki,{color:m.secondary2},P),C)};import{Box as dl,Text as qi}from"ink";import Wt from"react";var ul=({filters:e})=>Wt.createElement(dl,{columnGap:1},Wt.createElement(qi,{color:"red"},"APPLIED FILTERS:"),e.map(({value:t,target:o,operator:n})=>Wt.createElement(qi,{backgroundColor:"black",color:"white"},` ${o}${n}"${t}" `))),Ji=Wt.memo(ul);import{Box as pl,Text as Vt}from"ink";import Ze from"react";function Xi(){let{eventLog:e,unappliedEvents:t}=O(),o=e.at(-1)?.id,n=ko(o??""),s=$(n)?vt(n.value):"INVALID DATE";return Ze.createElement(pl,{paddingLeft:1},Ze.createElement(Vt,{backgroundColor:m.accent,color:m.secondary}," Readonly "),Ze.createElement(Vt,{color:m.accent}," "+t.length+" edits ago at "+s+"."),Ze.createElement(Vt,{color:m.accent}," Resume with "),Ze.createElement(Vt,{backgroundColor:m.accent,color:m.secondary}," ",":peek now"," "))}import{Box as fl,Text as Eo}from"ink";import Kt from"react";function zi(){let{replay:e}=O();if(!e)return null;let{appliedCount:t,totalCount:o,currentTime:n}=e;return Kt.createElement(fl,{paddingRight:2,columnGap:1,justifyContent:"flex-end"},Kt.createElement(Eo,{backgroundColor:m.accent,color:m.secondary}," \u25B6 REPLAY "),Kt.createElement(Eo,{color:m.secondary2},vt(new Date(n))),Kt.createElement(Eo,{color:m.accent},`${t}/${o} edits`))}import Co from"chalk";import{Box as gl,Text as Qi}from"ink";import Po,{useEffect as yl,useState as hl}from"react";var bl=["#4c567a","#9d7cd8","#7aa2f7","#7dcfff","#9d7cd8"],xl=150,Zi=12,wl=.36;function es({syncStatus:e,autoSync:t}){let[o,n]=hl(0);yl(()=>{if(e.status!=="syncing"){n(0);return}let f=0,y=setInterval(()=>{f+=1;let p=(Math.sin(f*wl)+1)/2,g=Math.round(p*(Zi-1));n(l=>l===g?l:g)},xl);return()=>clearInterval(y)},[e.status]);let s={synced:" ",failed:"---",offline:"off",syncing:"...",pending:"idle"},r={synced:m.secondary2,failed:m.yellow,offline:m.secondary2,pending:m.secondary2,syncing:Fo(bl,o/(Zi-1))??""}[e.status];return Po.createElement(gl,null,Po.createElement(Qi,null,Co.hex(m.secondary2).dim(t?"Sync (auto): ":"Sync: ")),Po.createElement(Qi,null,Co.hex(r)("\u25CF")+Co.hex(m.secondary2).dim(` ${s[e.status].padEnd(4)} `)))}import et from"chalk";import{Text as ts}from"ink";import os,{useEffect as vl,useState as Sl}from"react";var Il=(e,t)=>{let o=e.split("."),n=t.split("."),s=n.findIndex((u,r)=>u!=o[r]);return n.map((u,r)=>r>=s?et.hex(m.accent).dim(u):et.hex(m.secondary).dim(u)).join(et.hex(m.secondary2).dim("."))};function rs(){let[e,t]=Sl({current:F,latest:null,updateAvailable:!1});return vl(()=>{let o=!0;return ho(F).then(n=>{o&&t(n)}),()=>{o=!1}},[]),!e.updateAvailable||!e.latest?os.createElement(ts,{dimColor:!0,color:m.secondary2},F+" "):os.createElement(ts,null,et.hex(m.secondary2).dim(F),et.hex(m.accent).dim(" \u2197 "),Il(F,e.latest))}function ot({filters:e,hideBreadCrumb:t=!1}){let{timeMode:o,syncStatus:n,mode:s,replay:u}=O(),{userName:r,preferredEditor:f,autoSync:y}=re(),p=64,g=process.stdout.columns-p-8,l=t||o==="replay",d=u?qo(u.currentLabel,Math.max(0,g)):"";return _.createElement(tt,{justifyContent:"space-between",flexDirection:"row","max-width":process.stdout.columns-40,overflow:"hidden"},o==="replay"?_.createElement(tt,{paddingLeft:1},_.createElement(_e,{color:m.secondary2},d)):l?_.createElement(_e,null," "):s===H.PALETTE?_.createElement(tt,null,_.createElement(_e,{color:m.accent},"Command Palette"),_.createElement(_e,{dimColor:!0,color:m.secondary2}," ","- search, select and press enter")):_.createElement(tt,{paddingLeft:1},e.length>0?_.createElement(Ji,{filters:e}):_.createElement(Yi,{width:g})),o==="live"?_.createElement(tt,{columnGap:1,paddingRight:2,"max-width":p,overflow:"hidden",justifyContent:"flex-end"},_.createElement(es,{autoSync:!!y,syncStatus:n}),_.createElement(_e,null,Yt.hex(m.secondary2).dim("Mode: ")+Yt.hex(m.secondary2).dim(s.padEnd(8," "))," "),_.createElement(ns,{icon:"@",value:r}),_.createElement(ns,{icon:"\u276F",value:f}),_.createElement(rs,null)):"",o==="peek"?_.createElement(Xi,null):"",o==="replay"?_.createElement(zi,null):"")}function ns({icon:e,value:t}){return _.createElement(_e,null,Yt.dim(e)+Yt.hex(m.accent)(` ${t??"-"} `))}import{Box as Tl,Text as El}from"ink";import rt from"react";import{Box as qt,Text as Ao}from"ink";import ye from"react";function No(){let e=O(),t=e.nodes[e.rootNodeId],s=process.stdout.rows-1-3,u=process.stdout.columns||120,r=t?.id?Fe(t?.id):[];return ye.createElement(qt,{flexDirection:"column",height:s,padding:1,borderStyle:"round",borderColor:m.secondary,width:u},ye.createElement(qt,{padding:1,paddingTop:0,paddingBottom:0},ye.createElement(Ao,null,"Select a board:")),ye.createElement(qt,{padding:1,flexDirection:"column"},r.map((f,y)=>{let p=e.contextNode.context==="WORKSPACE"&&e.selectedIndex===y,l=Fe(f.id).flatMap(({id:d})=>ht(d)).length;return ye.createElement(qt,{key:f.id??y},ye.createElement(Ao,{color:p?m.accent:m.secondary2},ye.createElement(br,{isSelected:p})),ye.createElement(Ao,{color:p?m.accent:m.secondary2},f.title," (",l," issues)",f.readonly?" \u{1F512}":""))})))}var Cl=({contextNode:e,selectedIndex:t,breadCrumb:o,mode:n,viewMode:s,height:u,width:r})=>{let{renderedChildrenIndex:f}=O(),y=o.find(({context:d})=>d==="BOARD"),l=u-1-3;return rt.createElement(Tl,{flexDirection:"row"},e.context==="WORKSPACE"?rt.createElement(No,null):y?rt.createElement(vr,{height:l,width:r,swimlanes:(f[y.id]??[]).filter(d=>d!==void 0&&Yo(d)),contextNode:e,selectedIndex:t,breadCrumb:o,viewMode:s,mode:n}):rt.createElement(El,null))},is=rt.memo(Cl);import{useEffect as Pl}from"react";var Al=e=>{Pl(()=>{if(!e)return;let t,o=!1,n=()=>{if(o)return;let{autoSyncIntervalMs:s,userName:u,preferredEditor:r}=re();r&&u&&s&&(t=setTimeout(()=>{if(o)return;let{readOnly:f,mode:y}=Q();y==="default"&&!f&&yr(),n()},s))};return n(),()=>{o=!0,t&&clearTimeout(t)}},[e])},ss=({autoSync:e})=>(Al(e),null);import{useEffect as Nl}from"react";var as=()=>{let e=oe(process.cwd());return h(e)?T("No project here",null):lt({repoRoot:e.value})},ls=async()=>{let e=as();if(h(e))return G(e.message);if(e.value===null)return T("No project here",null);let t=e.value,o=await gt(t);if(!Br())return T("State not initialized",null);let n=Pe();if(h(n))return T("State not initialized",null);let{mode:s,readOnly:u,timeMode:r,syncStatus:f,selectedNode:y,contextNode:p}=n.value;if(Tr())return T("Project loading",null);let g=as();return h(g)||g.value!==t?T("Project changed",null):s!==H.DEFAULT||u||r!=="live"?T("Skipped while not on the live board",null):y?.isVirtual||p?.isVirtual?T("Skipped on a virtual node",null):f?.status==="syncing"?T("Sync in progress",null):o?T("Worktree held by a sync",null):$e(t)===Zo(t)?T("Log unchanged",null):(st.debug("[watch] event log moved, reloading state"),gr(t))};var Ml=1e3,ms=()=>(Nl(()=>Bt(ls,Ml),[]),null);function Mo({width:e,height:t}){let o=O(),{autoSync:n,viewMode:s}=Uo(),u=o.filters;if(o.mode===H.HELP)return M.createElement(ce,{flexDirection:"column"},M.createElement(Fi,{width:e,height:t}));if(o.mode===H.IDENTITY)return M.createElement(ce,{flexDirection:"column"},M.createElement(Hi,{width:e,height:t}));if(o.mode===H.PALETTE)return M.createElement(ce,{flexDirection:"column"},M.createElement(ot,{filters:u}),M.createElement(Ii,{width:e,height:t-2}),M.createElement(Qe,{width:e,mode:o.mode,availableHints:o.availableHints}));let{isSetupDone:r}=pt(),f=!r,y=r&&!o.hasProjectDefinition;if(f)return M.createElement(ce,{flexDirection:"column"},M.createElement(ce,{flexDirection:"column"},M.createElement(ot,{hideBreadCrumb:!0,filters:u}),M.createElement(To,{height:t,width:e})),M.createElement(Qe,{width:e,mode:o.mode,availableHints:[]}));if(y)return M.createElement(ce,{flexDirection:"column"},M.createElement(ce,{flexDirection:"column"},M.createElement(ot,{hideBreadCrumb:!0,filters:u}),M.createElement(ki,{height:t,width:e})),M.createElement(Qe,{width:e,mode:o.mode,availableHints:[]}));let p=Wo(o.breadCrumb??[],"BOARD");if($(p)){let g=p.value.id,l=Fe(g).length,d=3,a=Math.floor(e/d),i=Math.floor(e/Math.max(l,1));e=Math.min(i,a)*Math.max(l,d)}return M.createElement(ce,{flexDirection:"column"},M.createElement(ss,{autoSync:n}),M.createElement(ms,null),M.createElement(ce,{flexDirection:"column"},M.createElement(ot,{filters:u}),M.createElement(is,{width:e,height:t,contextNode:o.contextNode,selectedIndex:o.selectedIndex,breadCrumb:o.breadCrumb,viewMode:s??"dense",mode:o.mode})),M.createElement(Qe,{width:e,mode:o.mode,availableHints:o.availableHints}))}import Rl from"readline";var Jt,Xt,se,Bl=new Set([b.MoveCursorLeft,b.EraseInputWord,b.MoveCursorLeftOfWord,b.MoveCursorRight,b.MoveCursorRightOfWord,b.NavToPreviousContainer,b.NavToNextContainer,b.NavPreviousItem,b.NavNextItem,b.NavPreviousItemJump,b.NavNextItemJump,b.SetViewDense,b.SetViewWide,b.Confirm,b.Exit,b.Yank,b.InitCommandLine,b.InitCommandPalette,b.ExitCommandLine,b.CaptureInput,b.EraseInput,b.EraseInputWord,b.HideHelp,b.ViewHelp,b.SetViewDense,b.SetViewWide,b.GetLastCommandFromHistory,b.GetNextCommandFromHistory,b.AutoCompleteCommand]),Ll=new Set([b.NavPreviousItem,b.NavNextItem,b.NavPreviousItemJump,b.NavNextItemJump,b.NavToPreviousContainer,b.NavToNextContainer,b.Confirm,b.Exit,b.InitCommandPalette,b.InitMove,b.MoveNextItem,b.MovePreviousItem,b.MoveNextItemJump,b.MovePreviousItemJump,b.MoveToNextContainer,b.MoveToPreviousContainer,b.Delete,b.AddItem,b.EditTitle,b.EditDescription,b.ConfirmMove,b.Edit,b.ViewHelp,b.Yank]),cs=async e=>{if(e.ctrl&&e.name==="c")return Ce.exit();let{actionIndex:t,mode:o,readOnly:n,timeMode:s}=Q(),u=pr(e,o);if(!u||n&&!Bl.has(u)||s==="replay"&&o===H.DEFAULT&&Ll.has(u))return;let r=t[o]?.[u];if(r?.action)try{await r.action(r,e)}catch(f){logger.error(f)}},jl=()=>async function(t,o){o.name!=="escape"&&(se&&(clearTimeout(se),se=void 0),await cs(o))},_l=()=>async function(t){if(t.length===1&&t[0]===27){se&&clearTimeout(se),se=setTimeout(()=>{cs({name:"escape",ctrl:!1,meta:!1,shift:!1,sequence:"\x1B"}),se=void 0},25);return}se&&(clearTimeout(se),se=void 0)};function ds(){Jt&&process.stdin.removeListener("keypress",Jt),Xt&&process.stdin.removeListener("data",Xt),Jt=jl(),Xt=_l(),Rl.emitKeypressEvents(process.stdin),process.stdin.isTTY&&process.stdin.setRawMode(!0),process.stdin.on("data",Xt),process.stdin.on("keypress",Jt)}async function us(){try{let e=it({});if(h(e))return bt(0,e.message);let t=Ie();$(t)&&Ue(t.value);let o=oe(process.cwd());Ue({gitEmail:await Fr(process.cwd()),gitName:await Hr(process.cwd())});let n=$(o)?await Er(o.value):Pr();if(h(n))return n;let s=Ro();return h(s)?bt(6,s.message):(ds(),$(o)&&Cr(o.value),T("Booted Epiq",void 0))}catch(e){return bt(0,xt(e))}}Ir();var Gl=`${z.bold("Epiq CLI")}
5
-
6
- ${z.dim("Boot in directory:")}
7
- ${z.cyan("$ epiq")}
8
-
9
- ${z.dim("Launch GUI:")}
10
- ${z.cyan("$ epiq gui")}
11
- ${z.cyan("$ epiq gui --no-open")} ${z.dim("no browser")}
12
-
13
- ${z.dim("Act as somebody other than the configured user:")}
14
- ${z.cyan("$ epiq --as claude")}
15
- `,Zt=Ol(Gl,{importMeta:import.meta,version:F,flags:{init:{type:"boolean",default:!1},as:{type:"string"},open:{type:"boolean",default:!0}}}),zt=Zt.input[0];zt&&zt!=="gui"&&(console.error(z.red(`Unknown command: ${zt}`)),process.exit(1));if(Zt.flags.as!==void 0){let e=jo(Zt.flags.as,"--as");h(e)&&(console.error(z.red(e.message)),process.exit(1))}var ps=process.stdout.columns||120,fs=process.stdout.rows||20,Qt=null,$l=e=>{try{return Qt?(Qt.rerender(e),T("Rerendered app",void 0)):(Qt=Dl(e),T("Rendered app",void 0))}catch(t){return G(`Unable to render app: ${xt(t)}`)}},Ro=()=>$l(Ul.createElement(Mo,{width:ps,height:fs}));process.stdout.on("resize",()=>{if(ps=process.stdout.columns||120,fs=process.stdout.rows||20,!Qt)return;let e=Ro();h(e)&&logger.info(`[boot:resize] ${e.message}`)});if(zt==="gui"){let e=await xi({repoRoot:process.cwd(),open:Zt.flags.open});h(e)&&(console.error(z.red(`Failed to start Epiq GUI:
16
- ${e.message}`)),process.exitCode=1)}else{console.clear();let e=await us();h(e)&&(logger.info(e.message),console.error(z.red(`Failed to boot Epiq:
17
- ${e.message}`)),process.exitCode=1)}export{Ro as renderApp};