@tarcisiopgs/lisa 1.7.3 → 1.7.5

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.
@@ -1,5 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/output/terminal.ts
4
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5
+ var SPINNER_INTERVAL_MS = 80;
6
+ var spinnerTimer = null;
7
+ var spinnerFrame = 0;
8
+ function isTTY() {
9
+ return process.stdout.isTTY === true;
10
+ }
11
+ function writeOSC(title) {
12
+ process.stdout.write(`\x1B]0;${title}\x07`);
13
+ }
14
+ function setTitle(title) {
15
+ if (!isTTY()) return;
16
+ writeOSC(title);
17
+ }
18
+ function startSpinner(message) {
19
+ if (!isTTY()) return;
20
+ stopSpinner();
21
+ spinnerFrame = 0;
22
+ writeOSC(`${SPINNER_FRAMES[0]} Lisa \u2014 ${message}`);
23
+ spinnerTimer = setInterval(() => {
24
+ spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
25
+ writeOSC(`${SPINNER_FRAMES[spinnerFrame]} Lisa \u2014 ${message}`);
26
+ }, SPINNER_INTERVAL_MS);
27
+ }
28
+ function stopSpinner(message) {
29
+ if (spinnerTimer) {
30
+ clearInterval(spinnerTimer);
31
+ spinnerTimer = null;
32
+ }
33
+ if (!isTTY()) return;
34
+ if (message) {
35
+ writeOSC(message);
36
+ }
37
+ }
38
+ function notify() {
39
+ if (!isTTY()) return;
40
+ process.stdout.write("\x07");
41
+ }
42
+ function resetTitle() {
43
+ if (!isTTY()) return;
44
+ writeOSC("");
45
+ }
46
+
3
47
  // src/ui/state.ts
4
48
  import { EventEmitter } from "events";
5
49
  import { useEffect, useState } from "react";
@@ -68,6 +112,11 @@ function useKanbanState() {
68
112
  }
69
113
 
70
114
  export {
115
+ setTitle,
116
+ startSpinner,
117
+ stopSpinner,
118
+ notify,
119
+ resetTitle,
71
120
  kanbanEmitter,
72
121
  useKanbanState
73
122
  };
package/dist/index.js CHANGED
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- kanbanEmitter
4
- } from "./chunk-MUBWKMRZ.js";
3
+ kanbanEmitter,
4
+ notify,
5
+ resetTitle,
6
+ setTitle,
7
+ startSpinner,
8
+ stopSpinner
9
+ } from "./chunk-YZKNBQN6.js";
5
10
 
6
11
  // src/cli.ts
7
12
  import { execSync as execSync8 } from "child_process";
@@ -346,52 +351,6 @@ function banner() {
346
351
  `));
347
352
  }
348
353
 
349
- // src/output/terminal.ts
350
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
351
- var SPINNER_INTERVAL_MS = 80;
352
- var spinnerTimer = null;
353
- var spinnerFrame = 0;
354
- function isTTY() {
355
- return process.stdout.isTTY === true;
356
- }
357
- function writeOSC(title) {
358
- process.stdout.write(`\x1B]0;${title}\x07`);
359
- }
360
- function setTitle(title) {
361
- if (!isTTY()) return;
362
- writeOSC(title);
363
- }
364
- function startSpinner(message) {
365
- if (!isTTY()) return;
366
- if (getOutputMode() === "tui") return;
367
- stopSpinner();
368
- spinnerFrame = 0;
369
- writeOSC(`${SPINNER_FRAMES[0]} Lisa \u2014 ${message}`);
370
- spinnerTimer = setInterval(() => {
371
- spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
372
- writeOSC(`${SPINNER_FRAMES[spinnerFrame]} Lisa \u2014 ${message}`);
373
- }, SPINNER_INTERVAL_MS);
374
- }
375
- function stopSpinner(message) {
376
- if (spinnerTimer) {
377
- clearInterval(spinnerTimer);
378
- spinnerTimer = null;
379
- }
380
- if (!isTTY()) return;
381
- if (getOutputMode() === "tui") return;
382
- if (message) {
383
- writeOSC(message);
384
- }
385
- }
386
- function notify() {
387
- if (!isTTY()) return;
388
- process.stdout.write("\x07");
389
- }
390
- function resetTitle() {
391
- if (!isTTY()) return;
392
- writeOSC("");
393
- }
394
-
395
354
  // src/prompt.ts
396
355
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
397
356
  import { join as join2, resolve as resolve3 } from "path";
@@ -4092,7 +4051,7 @@ Add them to your ${shell} and run: source ${shell}`));
4092
4051
  if (isTUI) {
4093
4052
  const { render } = await import("ink");
4094
4053
  const { createElement } = await import("react");
4095
- const { KanbanApp } = await import("./kanban-KPIDX2SU.js");
4054
+ const { KanbanApp } = await import("./kanban-YP3TJJUT.js");
4096
4055
  render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
4097
4056
  }
4098
4057
  await runLoop(merged, {
@@ -4141,15 +4100,14 @@ var init = defineCommand({
4141
4100
  process.exit(1);
4142
4101
  }
4143
4102
  if (configExists()) {
4144
- const overwrite = await clack.confirm({
4145
- message: "A config already exists at .lisa/config.yaml. Overwrite it?"
4146
- });
4147
- if (clack.isCancel(overwrite) || !overwrite) {
4148
- log("Cancelled.");
4149
- return;
4150
- }
4103
+ const existing = loadConfig();
4104
+ clack.log.info(
4105
+ `Existing config found \u2014 current values will be pre-filled. Edit what you need, keep the rest.`
4106
+ );
4107
+ await runConfigWizard(existing);
4108
+ } else {
4109
+ await runConfigWizard();
4151
4110
  }
4152
- await runConfigWizard();
4153
4111
  }
4154
4112
  });
4155
4113
  var status = defineCommand({
@@ -4289,20 +4247,62 @@ var issue = defineCommand({
4289
4247
  meta: { name: "issue", description: "Issue tracker operations for use inside worktrees" },
4290
4248
  subCommands: { get: issueGet, done: issueDone }
4291
4249
  });
4292
- var CURSOR_MODELS = [
4250
+ var CURSOR_MODELS_FALLBACK = [
4293
4251
  "auto",
4294
4252
  "composer-1.5",
4295
- "composer-1",
4296
- "gpt-5.3-codex",
4297
- "gpt-5.3-codex-low",
4298
- "gpt-5.3-codex-high",
4299
- "gpt-5.3-codex-xhigh",
4300
- "gpt-5.3-codex-fast",
4253
+ "opus-4.6-thinking",
4254
+ "opus-4.6",
4301
4255
  "sonnet-4.6",
4302
- "sonnet-4.6-thinking",
4303
- "sonnet-4.5",
4304
- "sonnet-4.5-thinking"
4256
+ "gpt-5.3-codex",
4257
+ "gpt-5.2",
4258
+ "gemini-3.1-pro"
4305
4259
  ];
4260
+ function fetchCursorModels() {
4261
+ try {
4262
+ const bin = ["agent", "cursor-agent"].find((b) => {
4263
+ try {
4264
+ execSync8(`${b} --version`, { stdio: "ignore" });
4265
+ return true;
4266
+ } catch {
4267
+ return false;
4268
+ }
4269
+ });
4270
+ if (!bin) return CURSOR_MODELS_FALLBACK;
4271
+ const raw = execSync8(`${bin} --list-models`, { encoding: "utf-8", timeout: 1e4 });
4272
+ const clean = raw.replace(/\x1b\[[0-9;]*[mGKHFA-Z]/g, "");
4273
+ const models = clean.split("\n").map((l) => l.trim()).filter((l) => l.includes(" - ")).map((l) => (l.split(" - ")[0] ?? "").trim()).filter(Boolean);
4274
+ return models.length > 0 ? models : CURSOR_MODELS_FALLBACK;
4275
+ } catch {
4276
+ return CURSOR_MODELS_FALLBACK;
4277
+ }
4278
+ }
4279
+ function fetchOpenCodeModels() {
4280
+ try {
4281
+ const raw = execSync8("opencode models", { encoding: "utf-8", timeout: 1e4 });
4282
+ const hasAnthropic = Boolean(process.env.ANTHROPIC_API_KEY);
4283
+ const hasGoogle = Boolean(
4284
+ process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY
4285
+ );
4286
+ const hasOpenAI = Boolean(process.env.OPENAI_API_KEY);
4287
+ const hasCopilot = Boolean(process.env.GITHUB_COPILOT_API_KEY || process.env.GITHUB_TOKEN);
4288
+ const hasGroq = Boolean(process.env.GROQ_API_KEY);
4289
+ const hasMistral = Boolean(process.env.MISTRAL_API_KEY);
4290
+ const hasDeepSeek = Boolean(process.env.DEEPSEEK_API_KEY);
4291
+ return raw.split("\n").map((l) => l.trim()).filter((m) => {
4292
+ if (/^opencode\//.test(m)) return true;
4293
+ if (/^anthropic\/claude-(opus|sonnet|haiku)-4-\d+$/.test(m)) return hasAnthropic;
4294
+ if (/^google\/gemini-2\.5-(pro|flash|flash-lite)$/.test(m)) return hasGoogle;
4295
+ if (/^openai\//.test(m)) return hasOpenAI;
4296
+ if (/^github-copilot\//.test(m)) return hasCopilot;
4297
+ if (/^groq\//.test(m)) return hasGroq;
4298
+ if (/^mistral\//.test(m)) return hasMistral;
4299
+ if (/^deepseek\//.test(m)) return hasDeepSeek;
4300
+ return false;
4301
+ });
4302
+ } catch {
4303
+ return [];
4304
+ }
4305
+ }
4306
4306
  var main = defineCommand({
4307
4307
  meta: {
4308
4308
  name: "lisa",
@@ -4311,8 +4311,10 @@ var main = defineCommand({
4311
4311
  },
4312
4312
  subCommands: { run, config, init, status, issue }
4313
4313
  });
4314
- async function runConfigWizard() {
4315
- clack.intro(pc2.cyan(" lisa \u266A autonomous issue resolver "));
4314
+ async function runConfigWizard(existing) {
4315
+ clack.intro(
4316
+ pc2.cyan(existing ? " lisa \u266A editing config " : " lisa \u266A autonomous issue resolver ")
4317
+ );
4316
4318
  const providerLabels = {
4317
4319
  claude: "Claude Code",
4318
4320
  gemini: "Gemini CLI",
@@ -4323,8 +4325,13 @@ async function runConfigWizard() {
4323
4325
  aider: "Aider"
4324
4326
  };
4325
4327
  const providerModels = {
4326
- claude: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
4327
- gemini: ["gemini-2.5-pro", "gemini-2.0-flash", "gemini-1.5-pro"]
4328
+ claude: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5", "claude-sonnet-4-5"],
4329
+ gemini: ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"],
4330
+ // opencode: populated dynamically below (fetchOpenCodeModels)
4331
+ copilot: ["claude-opus-4.6", "claude-sonnet-4.6", "claude-haiku-4.5", "gpt-5.2"],
4332
+ goose: ["claude-sonnet-4-5", "claude-opus-4-5", "claude-haiku-4-5"],
4333
+ aider: ["claude-opus-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"]
4334
+ // cursor: populated dynamically below (fetchCursorModels)
4328
4335
  };
4329
4336
  const allProviders = await getAllProvidersWithAvailability();
4330
4337
  const available = allProviders.filter((r) => r.available).map((r) => r.provider);
@@ -4343,12 +4350,13 @@ async function runConfigWizard() {
4343
4350
  return process.exit(1);
4344
4351
  }
4345
4352
  let providerName;
4346
- if (available.length === 1 && available[0]) {
4353
+ if (available.length === 1 && available[0] && !existing) {
4347
4354
  providerName = available[0].name;
4348
4355
  clack.log.info(`Auto-detected ${pc2.bold(providerLabels[providerName])} as your AI provider.`);
4349
4356
  } else {
4350
4357
  const selected = await clack.select({
4351
4358
  message: "Which AI provider should resolve your issues?",
4359
+ initialValue: existing?.provider,
4352
4360
  options: allProviders.map(({ provider, available: isAvailable }) => ({
4353
4361
  value: provider.name,
4354
4362
  label: providerLabels[provider.name],
@@ -4367,12 +4375,22 @@ async function runConfigWizard() {
4367
4375
  availableModels = ["auto"];
4368
4376
  clack.log.info("Cursor Free plan detected \u2014 only the 'auto' model is available.");
4369
4377
  } else {
4370
- availableModels = CURSOR_MODELS;
4378
+ availableModels = fetchCursorModels();
4371
4379
  }
4380
+ } else if (providerName === "opencode") {
4381
+ const dynamic = fetchOpenCodeModels();
4382
+ availableModels = dynamic.length > 0 ? dynamic : [
4383
+ "anthropic/claude-opus-4-6",
4384
+ "anthropic/claude-sonnet-4-6",
4385
+ "anthropic/claude-haiku-4-5",
4386
+ "google/gemini-2.5-pro",
4387
+ "google/gemini-2.5-flash"
4388
+ ];
4372
4389
  }
4373
4390
  if (availableModels && availableModels.length > 0) {
4374
4391
  const modelSelection = await clack.multiselect({
4375
4392
  message: "Which models should Lisa use? Select in order \u2014 first = primary, rest = fallbacks",
4393
+ initialValues: existing?.models?.filter((m) => availableModels.includes(m)) ?? [],
4376
4394
  options: availableModels.map((m) => ({
4377
4395
  value: m,
4378
4396
  label: m
@@ -4384,6 +4402,7 @@ async function runConfigWizard() {
4384
4402
  }
4385
4403
  const source = await clack.select({
4386
4404
  message: "Where do your issues come from?",
4405
+ initialValue: existing?.source,
4387
4406
  options: [
4388
4407
  { value: "linear", label: "Linear", apiHint: "GraphQL API", envVars: ["LINEAR_API_KEY"] },
4389
4408
  {
@@ -4445,13 +4464,14 @@ Then reload: ${pc2.cyan(`source ${shell}`)}`
4445
4464
  const githubMethod = await detectGitHubMethod();
4446
4465
  const teamAnswer = await clack.text({
4447
4466
  message: source === "linear" ? "What is your Linear team name?" : source === "trello" ? "What is your Trello board name?" : source === "jira" ? "What is your Jira project key?" : "What is your team or project name?",
4467
+ initialValue: existing?.source_config.team ?? "",
4448
4468
  placeholder: source === "linear" ? "e.g. Engineering" : void 0
4449
4469
  });
4450
4470
  if (clack.isCancel(teamAnswer)) return process.exit(0);
4451
4471
  const team = teamAnswer;
4452
4472
  const labelAnswer = await clack.text({
4453
4473
  message: "Which label marks issues as ready for the agent to pick up?",
4454
- initialValue: "ready",
4474
+ initialValue: existing?.source_config.label ?? "ready",
4455
4475
  placeholder: "e.g. ready, ai, lisa"
4456
4476
  });
4457
4477
  if (clack.isCancel(labelAnswer)) return process.exit(0);
@@ -4463,52 +4483,54 @@ Then reload: ${pc2.cyan(`source ${shell}`)}`
4463
4483
  if (source === "trello") {
4464
4484
  const pickFromAnswer = await clack.text({
4465
4485
  message: "Pick up cards from which list?",
4466
- initialValue: "Backlog"
4486
+ initialValue: existing?.source_config.pick_from ?? "Backlog"
4467
4487
  });
4468
4488
  if (clack.isCancel(pickFromAnswer)) return process.exit(0);
4469
4489
  pickFrom = pickFromAnswer;
4470
4490
  project = pickFrom;
4471
4491
  const inProgressAnswer = await clack.text({
4472
4492
  message: "Move the card to which list while the agent is working?",
4473
- initialValue: "In Progress"
4493
+ initialValue: existing?.source_config.in_progress ?? "In Progress"
4474
4494
  });
4475
4495
  if (clack.isCancel(inProgressAnswer)) return process.exit(0);
4476
4496
  inProgress = inProgressAnswer;
4477
4497
  const doneAnswer = await clack.text({
4478
4498
  message: "Move the card to which list after the PR is created?",
4479
- initialValue: "Code Review"
4499
+ initialValue: existing?.source_config.done ?? "Code Review"
4480
4500
  });
4481
4501
  if (clack.isCancel(doneAnswer)) return process.exit(0);
4482
4502
  done = doneAnswer;
4483
4503
  } else {
4484
4504
  const projectAnswer = await clack.text({
4485
4505
  message: source === "linear" ? "Which Linear project should Lisa work on? (leave empty for all team issues)" : "Which project should Lisa work on?",
4506
+ initialValue: existing?.source_config.project ?? "",
4486
4507
  placeholder: source === "linear" ? "e.g. Q1 Roadmap (optional)" : void 0
4487
4508
  });
4488
4509
  if (clack.isCancel(projectAnswer)) return process.exit(0);
4489
4510
  project = projectAnswer;
4490
4511
  const pickFromAnswer = await clack.text({
4491
4512
  message: "Pick up issues in which status?",
4492
- initialValue: "Backlog",
4513
+ initialValue: existing?.source_config.pick_from ?? "Backlog",
4493
4514
  placeholder: "e.g. Backlog, Todo"
4494
4515
  });
4495
4516
  if (clack.isCancel(pickFromAnswer)) return process.exit(0);
4496
4517
  pickFrom = pickFromAnswer;
4497
4518
  const inProgressAnswer = await clack.text({
4498
4519
  message: "Move to which status while the agent is working?",
4499
- initialValue: "In Progress"
4520
+ initialValue: existing?.source_config.in_progress ?? "In Progress"
4500
4521
  });
4501
4522
  if (clack.isCancel(inProgressAnswer)) return process.exit(0);
4502
4523
  inProgress = inProgressAnswer;
4503
4524
  const doneAnswer = await clack.text({
4504
4525
  message: "Move to which status after the PR is created?",
4505
- initialValue: "In Review"
4526
+ initialValue: existing?.source_config.done ?? "In Review"
4506
4527
  });
4507
4528
  if (clack.isCancel(doneAnswer)) return process.exit(0);
4508
4529
  done = doneAnswer;
4509
4530
  }
4510
4531
  const workflowAnswer = await clack.select({
4511
4532
  message: "How should Lisa check out code for each issue?",
4533
+ initialValue: existing?.workflow,
4512
4534
  options: [
4513
4535
  {
4514
4536
  value: "worktree",
@@ -4528,7 +4550,7 @@ Then reload: ${pc2.cyan(`source ${shell}`)}`
4528
4550
  let baseBranch = "main";
4529
4551
  const cwd = process.cwd();
4530
4552
  if (repos.length === 0) {
4531
- const detected = detectDefaultBranch(cwd);
4553
+ const detected = existing?.base_branch ?? detectDefaultBranch(cwd);
4532
4554
  const branchAnswer = await clack.text({
4533
4555
  message: "What is the base branch to branch off from?",
4534
4556
  initialValue: detected
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  kanbanEmitter,
4
+ resetTitle,
5
+ startSpinner,
6
+ stopSpinner,
4
7
  useKanbanState
5
- } from "./chunk-MUBWKMRZ.js";
8
+ } from "./chunk-YZKNBQN6.js";
6
9
 
7
10
  // src/ui/kanban.tsx
8
11
  import { Box as Box6, useApp, useInput as useInput2 } from "ink";
@@ -109,6 +112,7 @@ function Column({ label, cards, isFocused = false, activeCardIndex = 0 }) {
109
112
  {
110
113
  flexDirection: "column",
111
114
  flexGrow: 1,
115
+ flexBasis: 0,
112
116
  borderStyle: "single",
113
117
  borderColor,
114
118
  paddingX: 1,
@@ -344,11 +348,13 @@ function IssueDetail({ card, onBack }) {
344
348
  }
345
349
 
346
350
  // src/ui/sidebar.tsx
347
- import { basename } from "path";
351
+ import { existsSync } from "fs";
352
+ import { basename, join } from "path";
348
353
  import { Box as Box5, Text as Text5 } from "ink";
349
354
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
350
355
  function Sidebar({ provider, source, cwd }) {
351
356
  const dir = basename(cwd);
357
+ const cwdLabel = existsSync(join(cwd, ".git")) ? "REPOSITORY" : "WORKSPACE";
352
358
  return /* @__PURE__ */ jsxs5(
353
359
  Box5,
354
360
  {
@@ -390,7 +396,7 @@ function Sidebar({ provider, source, cwd }) {
390
396
  ] })
391
397
  ] }),
392
398
  /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
393
- /* @__PURE__ */ jsx5(Text5, { color: "white", dimColor: true, children: "WORKSPACE" }),
399
+ /* @__PURE__ */ jsx5(Text5, { color: "white", dimColor: true, children: cwdLabel }),
394
400
  /* @__PURE__ */ jsxs5(Box5, { children: [
395
401
  /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u25B8 " }),
396
402
  /* @__PURE__ */ jsx5(Text5, { color: "white", bold: true, children: dir.length > 18 ? `${dir.slice(0, 15)}\u2026` : dir })
@@ -426,6 +432,17 @@ function KanbanApp({ config }) {
426
432
  }, [exit]);
427
433
  const backlog = cards.filter((c) => c.column === "backlog");
428
434
  const inProgress = cards.filter((c) => c.column === "in_progress");
435
+ useEffect3(() => {
436
+ if (workComplete) {
437
+ stopSpinner("Lisa \u2014 done \u2713");
438
+ } else if (inProgress.length > 0) {
439
+ const ids = inProgress.map((c) => c.id).join(", ");
440
+ startSpinner(ids);
441
+ } else {
442
+ stopSpinner("Lisa \u266A");
443
+ }
444
+ return () => resetTitle();
445
+ }, [inProgress, workComplete]);
429
446
  const done = cards.filter((c) => c.column === "done");
430
447
  const columnCards = [backlog, inProgress, done];
431
448
  useInput2((input, key) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tarcisiopgs/lisa",
3
- "version": "1.7.3",
4
- "description": "Deterministic autonomous issue resolver — structured AI agent loop for Linear/Trello",
3
+ "version": "1.7.5",
4
+ "description": "Autonomous issue resolver",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {