@tarcisiopgs/lisa 1.7.5 → 1.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -21
- package/dist/index.js +60 -9
- package/dist/{kanban-YP3TJJUT.js → kanban-5C3WZIKC.js} +28 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
<!-- Add a GIF or screen recording of the full pipeline here (fetch → implement → PR) -->
|
|
18
|
-
|
|
19
17
|
## Quickstart
|
|
20
18
|
|
|
21
19
|
```bash
|
|
@@ -57,9 +55,9 @@ Lisa follows a deterministic pipeline:
|
|
|
57
55
|
|
|
58
56
|
1. **Fetch** — Pulls the next issue from Linear, Trello, Plane, Shortcut, GitLab Issues, GitHub Issues, or Jira matching the configured label, team, and project. Issues are sorted by priority. Blocked issues are skipped.
|
|
59
57
|
2. **Activate** — Moves the issue to `in_progress` so your team knows it's being worked on.
|
|
60
|
-
3. **Implement** — Builds a structured prompt with full issue context and sends it to the AI agent. The agent works in a worktree or branch, implements the change, and commits.
|
|
61
|
-
4. **Validate** —
|
|
62
|
-
5. **PR** — Pushes the branch and creates a pull request referencing the original issue.
|
|
58
|
+
3. **Implement** — Builds a structured prompt with full issue context and sends it to the AI agent. The agent works in a worktree or branch, implements the change, runs tests, and commits.
|
|
59
|
+
4. **Validate** — If the agent's tests pass and pre-push hooks succeed, the branch is pushed. If hooks fail, Lisa re-invokes the agent with the error output and retries.
|
|
60
|
+
5. **PR** — Pushes the branch and creates a pull request referencing the original issue. The PR body includes a footer crediting the provider that resolved it.
|
|
63
61
|
6. **Update** — Moves the issue to the `done` status and removes the pickup label.
|
|
64
62
|
7. **Next** — Picks the next issue. When there are no more matching issues, Lisa stops.
|
|
65
63
|
|
|
@@ -75,18 +73,20 @@ Lisa follows a deterministic pipeline:
|
|
|
75
73
|
|
|
76
74
|
## Providers
|
|
77
75
|
|
|
78
|
-
| Provider |
|
|
79
|
-
|
|
80
|
-
| Claude Code | `claude` |
|
|
81
|
-
| Gemini CLI | `gemini` |
|
|
82
|
-
| OpenCode | `opencode` |
|
|
83
|
-
| GitHub Copilot CLI | `copilot` |
|
|
84
|
-
| Cursor Agent | `
|
|
85
|
-
| Goose | `goose` |
|
|
86
|
-
| Aider | `aider` |
|
|
76
|
+
| Provider | Key | Command |
|
|
77
|
+
|----------|-----|---------|
|
|
78
|
+
| Claude Code | `claude` | `claude` |
|
|
79
|
+
| Gemini CLI | `gemini` | `gemini` |
|
|
80
|
+
| OpenCode | `opencode` | `opencode` |
|
|
81
|
+
| GitHub Copilot CLI | `copilot` | `copilot` |
|
|
82
|
+
| Cursor Agent | `cursor` | `agent` / `cursor-agent` |
|
|
83
|
+
| Goose | `goose` | `goose` |
|
|
84
|
+
| Aider | `aider` | `aider` |
|
|
87
85
|
|
|
88
86
|
At least one provider must be installed and available in your PATH.
|
|
89
87
|
|
|
88
|
+
> **Cursor Free plan** — `lisa init` automatically detects Free accounts and restricts model selection to `auto` only. On paid plans, a curated list of top-tier models is shown (`composer-1.5`, `opus-4.6`, `sonnet-4.6`, `gpt-5.3-codex`, etc.).
|
|
89
|
+
|
|
90
90
|
### Fallback Chain
|
|
91
91
|
|
|
92
92
|
Configure multiple models — Lisa tries each in order. Transient errors (429, quota, timeout, network) trigger the next model; non-transient errors stop the chain.
|
|
@@ -108,7 +108,7 @@ npm install -g @tarcisiopgs/lisa
|
|
|
108
108
|
## Environment Variables
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
# Required (at least one)
|
|
111
|
+
# Required for PR creation (at least one)
|
|
112
112
|
export GITHUB_TOKEN="" # or have `gh` CLI authenticated
|
|
113
113
|
|
|
114
114
|
# Required when source = linear
|
|
@@ -150,16 +150,48 @@ export JIRA_API_TOKEN="" # Atlassian API token
|
|
|
150
150
|
| `lisa run --limit N` | Process up to N issues |
|
|
151
151
|
| `lisa run --dry-run` | Preview without executing |
|
|
152
152
|
| `lisa run --provider NAME` | Override AI provider |
|
|
153
|
-
| `lisa run --source NAME` | Override issue source
|
|
153
|
+
| `lisa run --source NAME` | Override issue source |
|
|
154
154
|
| `lisa run --label NAME` | Override label filter |
|
|
155
|
-
| `lisa run --github METHOD` | Override GitHub method (cli
|
|
155
|
+
| `lisa run --github METHOD` | Override GitHub method (`cli` or `token`) |
|
|
156
156
|
| `lisa run --json` | Output as JSON lines |
|
|
157
157
|
| `lisa run --quiet` | Suppress non-essential output |
|
|
158
|
-
| `lisa
|
|
159
|
-
| `lisa config
|
|
160
|
-
| `lisa config --
|
|
161
|
-
| `lisa
|
|
158
|
+
| `lisa init` | Create `.lisa/config.yaml` interactively |
|
|
159
|
+
| `lisa config` | Edit config interactively |
|
|
160
|
+
| `lisa config --show` | Print current config as JSON |
|
|
161
|
+
| `lisa config --set key=value` | Set a single config value |
|
|
162
162
|
| `lisa status` | Show session stats |
|
|
163
|
+
| `lisa issue get <id>` | Fetch full issue details as JSON (for use inside worktrees) |
|
|
164
|
+
| `lisa issue done <id> --pr-url <url>` | Complete issue, attach PR, update status, remove label |
|
|
165
|
+
|
|
166
|
+
## TUI
|
|
167
|
+
|
|
168
|
+
When running in an interactive terminal, `lisa run` renders a real-time Kanban board:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
┌──────────────────────────┐ ┌───────────────────────────┐ ┌───────────────────────────┐
|
|
172
|
+
│ ▶ BACKLOG [3] │ │ ▶ IN PROGRESS [1] │ │ ▶ IN REVIEW [2] │
|
|
173
|
+
│ │ │ │ │ │
|
|
174
|
+
│ ┌────────────────────┐ │ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │
|
|
175
|
+
│ │ ENG-42 │ │ │ │ ● ENG-38 │ │ │ │ ✓ ENG-35 │ │
|
|
176
|
+
│ │ Add dark mode │ │ │ │ Fix login redirect │ │ │ │ Update dependencies │ │
|
|
177
|
+
│ │ ready │ │ │ │ ~1 running │ │ │ │ PR created │ │
|
|
178
|
+
│ └────────────────────┘ │ │ └─────────────────────┘ │ │ └─────────────────────┘ │
|
|
179
|
+
└──────────────────────────┘ └───────────────────────────┘ └───────────────────────────┘
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Keyboard shortcuts
|
|
183
|
+
|
|
184
|
+
| Key | Action |
|
|
185
|
+
|-----|--------|
|
|
186
|
+
| `Tab` | Move to next column |
|
|
187
|
+
| `Shift+Tab` | Move to previous column |
|
|
188
|
+
| `↑` / `↓` | Navigate cards / scroll output |
|
|
189
|
+
| `Enter` | Open issue detail view (streams provider output) |
|
|
190
|
+
| `Esc` | Close detail view, return to board |
|
|
191
|
+
| `p` | Pause / resume — loop finishes the current issue then waits |
|
|
192
|
+
| `q` | Quit |
|
|
193
|
+
|
|
194
|
+
The sidebar legend updates contextually: board shortcuts when browsing the Kanban, scroll and back hints when viewing issue detail. The terminal tab title also updates in real time: it shows a spinner with the active issue ID while work is in progress, and a checkmark when done.
|
|
163
195
|
|
|
164
196
|
## Configuration
|
|
165
197
|
|
package/dist/index.js
CHANGED
|
@@ -179,6 +179,33 @@ async function isGhCliAvailable() {
|
|
|
179
179
|
return false;
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
+
var PROVIDER_DISPLAY_NAMES = {
|
|
183
|
+
claude: "Claude Code",
|
|
184
|
+
gemini: "Gemini CLI",
|
|
185
|
+
opencode: "OpenCode",
|
|
186
|
+
copilot: "GitHub Copilot CLI",
|
|
187
|
+
cursor: "Cursor Agent",
|
|
188
|
+
goose: "Goose",
|
|
189
|
+
aider: "Aider"
|
|
190
|
+
};
|
|
191
|
+
function formatProviderName(providerUsed) {
|
|
192
|
+
const providerKey = providerUsed.split("/")[0] ?? providerUsed;
|
|
193
|
+
return PROVIDER_DISPLAY_NAMES[providerKey] ?? providerKey;
|
|
194
|
+
}
|
|
195
|
+
async function appendPrAttribution(prUrl, providerUsed) {
|
|
196
|
+
try {
|
|
197
|
+
const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
|
|
198
|
+
const { body } = JSON.parse(bodyJson);
|
|
199
|
+
const providerName = formatProviderName(providerUsed);
|
|
200
|
+
const attribution = `
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
\u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
|
|
204
|
+
const newBody = (body ?? "") + attribution;
|
|
205
|
+
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
182
209
|
|
|
183
210
|
// src/git/worktree.ts
|
|
184
211
|
import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
@@ -3127,6 +3154,13 @@ function createSource(name) {
|
|
|
3127
3154
|
var activeCleanup = null;
|
|
3128
3155
|
var activeProviderPid = null;
|
|
3129
3156
|
var shuttingDown = false;
|
|
3157
|
+
var loopPaused = false;
|
|
3158
|
+
kanbanEmitter.on("loop:pause", () => {
|
|
3159
|
+
loopPaused = true;
|
|
3160
|
+
});
|
|
3161
|
+
kanbanEmitter.on("loop:resume", () => {
|
|
3162
|
+
loopPaused = false;
|
|
3163
|
+
});
|
|
3130
3164
|
function resolveModels(config2) {
|
|
3131
3165
|
if (!config2.models || config2.models.length === 0) {
|
|
3132
3166
|
return [{ provider: config2.provider }];
|
|
@@ -3293,6 +3327,7 @@ async function runLoop(config2, opts) {
|
|
|
3293
3327
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
3294
3328
|
const logFile = resolve5(config2.logs.dir, `session_${session}_${timestamp2}.log`);
|
|
3295
3329
|
divider(session);
|
|
3330
|
+
await waitIfPaused();
|
|
3296
3331
|
startSpinner("fetching issue...");
|
|
3297
3332
|
if (opts.issueId) {
|
|
3298
3333
|
log(`Fetching issue '${opts.issueId}' from ${config2.source}...`);
|
|
@@ -3602,6 +3637,7 @@ ${result.output}
|
|
|
3602
3637
|
}
|
|
3603
3638
|
const worktreePath = await findWorktreeForBranch(repoPath, manifest.branch ?? "");
|
|
3604
3639
|
ok(`PR created by provider: ${manifest.prUrl}`);
|
|
3640
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
3605
3641
|
if (worktreePath) await cleanupWorktree(repoPath, worktreePath);
|
|
3606
3642
|
ok(`Session ${session} complete for ${issue2.id}`);
|
|
3607
3643
|
return {
|
|
@@ -3708,6 +3744,7 @@ ${result.output}
|
|
|
3708
3744
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
3709
3745
|
}
|
|
3710
3746
|
ok(`PR created by provider: ${manifest.prUrl}`);
|
|
3747
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
3711
3748
|
await cleanupWorktree(repoPath, worktreePath);
|
|
3712
3749
|
ok(`Session ${session} complete for ${issue2.id}`);
|
|
3713
3750
|
return {
|
|
@@ -3896,6 +3933,7 @@ ${result.output}
|
|
|
3896
3933
|
return { ...failResult(result.providerUsed, result), branch: branchName };
|
|
3897
3934
|
}
|
|
3898
3935
|
await cleanupWorktree(repoPath, worktreePath);
|
|
3936
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
3899
3937
|
ok(`Step ${stepNum} complete: ${repoPath} \u2014 PR: ${manifest.prUrl}`);
|
|
3900
3938
|
return {
|
|
3901
3939
|
success: true,
|
|
@@ -3976,6 +4014,7 @@ ${result.output}
|
|
|
3976
4014
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
3977
4015
|
}
|
|
3978
4016
|
ok(`PR created by provider: ${manifest.prUrl}`);
|
|
4017
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
3979
4018
|
ok(`Session ${session} complete for ${issue2.id}`);
|
|
3980
4019
|
return {
|
|
3981
4020
|
success: true,
|
|
@@ -3995,6 +4034,11 @@ async function cleanupWorktree(repoRoot, worktreePath) {
|
|
|
3995
4034
|
function sleep(ms) {
|
|
3996
4035
|
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
3997
4036
|
}
|
|
4037
|
+
async function waitIfPaused() {
|
|
4038
|
+
while (loopPaused) {
|
|
4039
|
+
await sleep(500);
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
3998
4042
|
|
|
3999
4043
|
// src/cli.ts
|
|
4000
4044
|
function sleep2(ms) {
|
|
@@ -4051,7 +4095,7 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
4051
4095
|
if (isTUI) {
|
|
4052
4096
|
const { render } = await import("ink");
|
|
4053
4097
|
const { createElement } = await import("react");
|
|
4054
|
-
const { KanbanApp } = await import("./kanban-
|
|
4098
|
+
const { KanbanApp } = await import("./kanban-5C3WZIKC.js");
|
|
4055
4099
|
render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
|
|
4056
4100
|
}
|
|
4057
4101
|
await runLoop(merged, {
|
|
@@ -4247,15 +4291,21 @@ var issue = defineCommand({
|
|
|
4247
4291
|
meta: { name: "issue", description: "Issue tracker operations for use inside worktrees" },
|
|
4248
4292
|
subCommands: { get: issueGet, done: issueDone }
|
|
4249
4293
|
});
|
|
4250
|
-
var
|
|
4294
|
+
var CURSOR_PREFERRED_MODELS = [
|
|
4251
4295
|
"auto",
|
|
4252
4296
|
"composer-1.5",
|
|
4297
|
+
"composer-1",
|
|
4298
|
+
"gpt-5.3-codex",
|
|
4299
|
+
"gpt-5.2",
|
|
4300
|
+
"gpt-5.1-codex-max",
|
|
4253
4301
|
"opus-4.6-thinking",
|
|
4254
4302
|
"opus-4.6",
|
|
4303
|
+
"sonnet-4.6-thinking",
|
|
4255
4304
|
"sonnet-4.6",
|
|
4256
|
-
"
|
|
4257
|
-
"
|
|
4258
|
-
"
|
|
4305
|
+
"gemini-3.1-pro",
|
|
4306
|
+
"gemini-3-pro",
|
|
4307
|
+
"grok",
|
|
4308
|
+
"kimi-k2.5"
|
|
4259
4309
|
];
|
|
4260
4310
|
function fetchCursorModels() {
|
|
4261
4311
|
try {
|
|
@@ -4267,13 +4317,14 @@ function fetchCursorModels() {
|
|
|
4267
4317
|
return false;
|
|
4268
4318
|
}
|
|
4269
4319
|
});
|
|
4270
|
-
if (!bin) return
|
|
4320
|
+
if (!bin) return CURSOR_PREFERRED_MODELS;
|
|
4271
4321
|
const raw = execSync8(`${bin} --list-models`, { encoding: "utf-8", timeout: 1e4 });
|
|
4272
4322
|
const clean = raw.replace(/\x1b\[[0-9;]*[mGKHFA-Z]/g, "");
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4323
|
+
const all = clean.split("\n").map((l) => l.trim()).filter((l) => l.includes(" - ")).map((l) => (l.split(" - ")[0] ?? "").trim()).filter(Boolean);
|
|
4324
|
+
const filtered = CURSOR_PREFERRED_MODELS.filter((m) => all.includes(m));
|
|
4325
|
+
return filtered.length > 0 ? filtered : CURSOR_PREFERRED_MODELS;
|
|
4275
4326
|
} catch {
|
|
4276
|
-
return
|
|
4327
|
+
return CURSOR_PREFERRED_MODELS;
|
|
4277
4328
|
}
|
|
4278
4329
|
}
|
|
4279
4330
|
function fetchOpenCodeModels() {
|
|
@@ -337,11 +337,7 @@ function IssueDetail({ card, onBack }) {
|
|
|
337
337
|
] }) : visibleLines.map((line, i) => (
|
|
338
338
|
// biome-ignore lint/suspicious/noArrayIndexKey: log lines have no stable key
|
|
339
339
|
/* @__PURE__ */ jsx4(Text4, { color: "white", dimColor: true, children: line }, i)
|
|
340
|
-
)) })
|
|
341
|
-
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
|
|
342
|
-
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "[\u2191\u2193] scroll" }),
|
|
343
|
-
/* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: "[Esc] back to board" })
|
|
344
|
-
] })
|
|
340
|
+
)) })
|
|
345
341
|
]
|
|
346
342
|
}
|
|
347
343
|
);
|
|
@@ -352,14 +348,15 @@ import { existsSync } from "fs";
|
|
|
352
348
|
import { basename, join } from "path";
|
|
353
349
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
354
350
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
355
|
-
function Sidebar({ provider, source, cwd }) {
|
|
356
|
-
const dir = basename(cwd);
|
|
351
|
+
function Sidebar({ provider, source, cwd, activeView, paused = false }) {
|
|
352
|
+
const dir = basename(cwd).toUpperCase();
|
|
357
353
|
const cwdLabel = existsSync(join(cwd, ".git")) ? "REPOSITORY" : "WORKSPACE";
|
|
358
354
|
return /* @__PURE__ */ jsxs5(
|
|
359
355
|
Box5,
|
|
360
356
|
{
|
|
361
357
|
flexDirection: "column",
|
|
362
358
|
width: 28,
|
|
359
|
+
flexShrink: 0,
|
|
363
360
|
borderStyle: "single",
|
|
364
361
|
borderColor: "yellow",
|
|
365
362
|
paddingX: 1,
|
|
@@ -377,8 +374,8 @@ function Sidebar({ provider, source, cwd }) {
|
|
|
377
374
|
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D" })
|
|
378
375
|
] }),
|
|
379
376
|
/* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, children: [
|
|
380
|
-
/* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u25B6 " }),
|
|
381
|
-
/* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "RUNNING" })
|
|
377
|
+
/* @__PURE__ */ jsx5(Text5, { color: paused ? "yellow" : "green", children: paused ? "\u23F8 " : "\u25B6 " }),
|
|
378
|
+
/* @__PURE__ */ jsx5(Text5, { color: paused ? "yellow" : "green", bold: true, children: paused ? "PAUSED" : "RUNNING" })
|
|
382
379
|
] }),
|
|
383
380
|
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
384
381
|
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
@@ -404,11 +401,15 @@ function Sidebar({ provider, source, cwd }) {
|
|
|
404
401
|
] }),
|
|
405
402
|
/* @__PURE__ */ jsx5(Box5, { flexGrow: 1 }),
|
|
406
403
|
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
407
|
-
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
404
|
+
activeView === "board" ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
408
405
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[Tab] next column" }),
|
|
409
406
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u2191\u2193] navigate " }),
|
|
410
407
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u21B5] view detail " }),
|
|
408
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: paused ? "[p] resume " : "[p] pause " }),
|
|
411
409
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[q] quit " })
|
|
410
|
+
] }) : /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
411
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u2191\u2193] scroll " }),
|
|
412
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[Esc] back to board" })
|
|
412
413
|
] })
|
|
413
414
|
]
|
|
414
415
|
}
|
|
@@ -423,6 +424,7 @@ function KanbanApp({ config }) {
|
|
|
423
424
|
const [activeView, setActiveView] = useState3("board");
|
|
424
425
|
const [activeColIndex, setActiveColIndex] = useState3(0);
|
|
425
426
|
const [activeCardIndex, setActiveCardIndex] = useState3(0);
|
|
427
|
+
const [paused, setPaused] = useState3(false);
|
|
426
428
|
useEffect3(() => {
|
|
427
429
|
const onExit = () => exit();
|
|
428
430
|
kanbanEmitter.on("tui:exit", onExit);
|
|
@@ -454,6 +456,12 @@ function KanbanApp({ config }) {
|
|
|
454
456
|
if (key.escape) setActiveView("board");
|
|
455
457
|
return;
|
|
456
458
|
}
|
|
459
|
+
if (input === "p") {
|
|
460
|
+
const next = !paused;
|
|
461
|
+
setPaused(next);
|
|
462
|
+
kanbanEmitter.emit(next ? "loop:pause" : "loop:resume");
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
457
465
|
if (key.tab && !key.shift) {
|
|
458
466
|
const nextCol = (activeColIndex + 1) % 3;
|
|
459
467
|
setActiveColIndex(nextCol);
|
|
@@ -489,7 +497,16 @@ function KanbanApp({ config }) {
|
|
|
489
497
|
};
|
|
490
498
|
const selectedCard = activeView === "detail" ? columnCards[activeColIndex]?.[activeCardIndex] ?? null : null;
|
|
491
499
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", height: process.stdout.rows, children: [
|
|
492
|
-
/* @__PURE__ */ jsx6(
|
|
500
|
+
/* @__PURE__ */ jsx6(
|
|
501
|
+
Sidebar,
|
|
502
|
+
{
|
|
503
|
+
provider: config.provider,
|
|
504
|
+
source: config.source,
|
|
505
|
+
cwd: process.cwd(),
|
|
506
|
+
activeView,
|
|
507
|
+
paused
|
|
508
|
+
}
|
|
509
|
+
),
|
|
493
510
|
activeView === "board" || !selectedCard ? /* @__PURE__ */ jsx6(
|
|
494
511
|
Board,
|
|
495
512
|
{
|