@tarcisiopgs/lisa 1.36.0 → 1.37.0

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.
@@ -32,16 +32,10 @@ import {
32
32
  resolveModels,
33
33
  runValidationCommands,
34
34
  runWithFallback
35
- } from "./chunk-6VIN5PMW.js";
35
+ } from "./chunk-XXVTKBC5.js";
36
36
  import {
37
- divider,
38
- error,
39
- initLogFile,
40
- kanbanEmitter,
41
- log,
42
- ok,
43
- warn
44
- } from "./chunk-V44FTYWZ.js";
37
+ kanbanEmitter
38
+ } from "./chunk-VS6R5KBO.js";
45
39
  import {
46
40
  appendRawEntry,
47
41
  migrateGuardrails
@@ -62,8 +56,17 @@ import {
62
56
  stopSpinner
63
57
  } from "./chunk-72CYGBT4.js";
64
58
  import {
59
+ divider,
60
+ error,
61
+ initLogFile,
62
+ log,
63
+ ok,
64
+ warn
65
+ } from "./chunk-HPWL5JRW.js";
66
+ import {
67
+ LisaError,
65
68
  formatError
66
- } from "./chunk-7JT7DTSS.js";
69
+ } from "./chunk-CTMZ666K.js";
67
70
  import {
68
71
  fetchPrFeedback,
69
72
  formatPrFeedbackEntry
@@ -111,7 +114,7 @@ var configSchema = object({
111
114
  base_branch: string().optional(),
112
115
  workspace: string().optional()
113
116
  }).passthrough();
114
- var ConfigValidationError = class extends Error {
117
+ var ConfigValidationError = class extends LisaError {
115
118
  constructor(message) {
116
119
  super(message);
117
120
  this.name = "ConfigValidationError";
@@ -857,13 +860,13 @@ function killProviderForIssue(issueId) {
857
860
  }, 5e3);
858
861
  }
859
862
  function setupEventListeners() {
860
- kanbanEmitter.on("loop:pause", () => {
863
+ const onPause = () => {
861
864
  _loopPaused = true;
862
- });
863
- kanbanEmitter.on("loop:resume", () => {
865
+ };
866
+ const onResume = () => {
864
867
  _loopPaused = false;
865
- });
866
- kanbanEmitter.on("loop:pause-provider", (issueId) => {
868
+ };
869
+ const onPauseProvider = (issueId) => {
867
870
  if (issueId) {
868
871
  const pid = activeProviderPids.get(issueId);
869
872
  if (pid) {
@@ -883,8 +886,8 @@ function setupEventListeners() {
883
886
  }
884
887
  }
885
888
  kanbanEmitter.emit("provider:paused", issueId);
886
- });
887
- kanbanEmitter.on("loop:resume-provider", (issueId) => {
889
+ };
890
+ const onResumeProvider = (issueId) => {
888
891
  if (issueId) {
889
892
  const pid = activeProviderPids.get(issueId);
890
893
  if (pid && providerPausedSet.has(issueId)) {
@@ -907,8 +910,8 @@ function setupEventListeners() {
907
910
  providerPausedSet.clear();
908
911
  }
909
912
  kanbanEmitter.emit("provider:resumed", issueId);
910
- });
911
- kanbanEmitter.on("loop:kill", (issueId) => {
913
+ };
914
+ const onKill = (issueId) => {
912
915
  if (issueId) {
913
916
  userKilledSet.add(issueId);
914
917
  killProviderForIssue(issueId);
@@ -919,8 +922,8 @@ function setupEventListeners() {
919
922
  killProviderForIssue(firstId);
920
923
  }
921
924
  }
922
- });
923
- kanbanEmitter.on("loop:skip", (issueId) => {
925
+ };
926
+ const onSkip = (issueId) => {
924
927
  if (issueId) {
925
928
  userSkippedSet.add(issueId);
926
929
  killProviderForIssue(issueId);
@@ -931,15 +934,33 @@ function setupEventListeners() {
931
934
  killProviderForIssue(firstId);
932
935
  }
933
936
  }
934
- });
935
- kanbanEmitter.on("loop:run", () => {
937
+ };
938
+ const onRun = () => {
936
939
  resolveIdle();
937
- });
938
- kanbanEmitter.on("loop:quit", () => {
940
+ };
941
+ const onQuit = () => {
939
942
  _userQuitFromWatchPrompt = true;
940
943
  setShuttingDown(true);
941
944
  resolveIdle();
942
- });
945
+ };
946
+ kanbanEmitter.on("loop:pause", onPause);
947
+ kanbanEmitter.on("loop:resume", onResume);
948
+ kanbanEmitter.on("loop:pause-provider", onPauseProvider);
949
+ kanbanEmitter.on("loop:resume-provider", onResumeProvider);
950
+ kanbanEmitter.on("loop:kill", onKill);
951
+ kanbanEmitter.on("loop:skip", onSkip);
952
+ kanbanEmitter.on("loop:run", onRun);
953
+ kanbanEmitter.on("loop:quit", onQuit);
954
+ return () => {
955
+ kanbanEmitter.off("loop:pause", onPause);
956
+ kanbanEmitter.off("loop:resume", onResume);
957
+ kanbanEmitter.off("loop:pause-provider", onPauseProvider);
958
+ kanbanEmitter.off("loop:resume-provider", onResumeProvider);
959
+ kanbanEmitter.off("loop:kill", onKill);
960
+ kanbanEmitter.off("loop:skip", onSkip);
961
+ kanbanEmitter.off("loop:run", onRun);
962
+ kanbanEmitter.off("loop:quit", onQuit);
963
+ };
943
964
  }
944
965
 
945
966
  // src/session/reconciliation.ts
@@ -1803,7 +1824,14 @@ var WORKTREES_DIR = ".worktrees";
1803
1824
  function generateBranchName(issueId, title) {
1804
1825
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").substring(0, 40).replace(/^-|-$/g, "");
1805
1826
  const safeId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1806
- return `feat/${safeId}-${slug}`;
1827
+ let branch = `feat/${safeId}-${slug}`;
1828
+ branch = branch.replace(/\.\./g, "");
1829
+ branch = branch.replace(/@\{/g, "");
1830
+ branch = branch.replace(/^[./]+/, "").replace(/[./]+$/, "");
1831
+ if (!branch || branch === "feat/" || branch === "feat") {
1832
+ branch = `feat/${safeId}-${Date.now()}`;
1833
+ }
1834
+ return branch;
1807
1835
  }
1808
1836
  async function cleanupOrphanedWorktree(repoRoot, branchName) {
1809
1837
  const { stdout: branchList } = await execa2("git", ["branch", "--list", branchName], {
@@ -2083,28 +2111,11 @@ async function monitorCi(branch, config, issue, models, cwd, logFile, workspace,
2083
2111
  // src/session/hooks.ts
2084
2112
  import { spawn as spawn2 } from "child_process";
2085
2113
  var DEFAULT_HOOK_TIMEOUT = 6e4;
2086
- var SENSITIVE_ENV_PATTERNS = [
2087
- /^GITHUB_TOKEN$/,
2088
- /^GH_TOKEN$/,
2089
- /^GITLAB_TOKEN$/,
2090
- /^BITBUCKET_.*(TOKEN|PASSWORD|SECRET)/i,
2091
- /^LINEAR_API_KEY$/,
2092
- /^TRELLO_(API_KEY|TOKEN)$/,
2093
- /^PLANE_API_TOKEN$/,
2094
- /^SHORTCUT_API_TOKEN$/,
2095
- /^JIRA_(API_TOKEN|TOKEN)$/,
2096
- /^AWS_(SECRET|SESSION).*KEY/i,
2097
- /^ANTHROPIC_API_KEY$/,
2098
- /^OPENAI_API_KEY$/,
2099
- /^GOOGLE_API_KEY$/,
2100
- /^GEMINI_API_KEY$/,
2101
- /^NPM_TOKEN$/,
2102
- /^PYPI_TOKEN$/
2103
- ];
2114
+ var SAFE_ENV_PATTERN = /^(PATH|HOME|USER|LANG|LANGUAGE|LC_.+|SHELL|TERM|TERM_PROGRAM|PWD|OLDPWD|TMPDIR|TMP|TEMP|EDITOR|VISUAL|CI|LISA_.+|NODE_ENV|NODE_PATH|NPM_.+|PNPM_.+|YARN_.+|XDG_.+|COLORTERM|FORCE_COLOR|NO_COLOR|COLUMNS|LINES|HOSTNAME|LOGNAME|SHLVL)$/;
2104
2115
  function sanitizeEnv(env) {
2105
2116
  const result = {};
2106
2117
  for (const [key, value] of Object.entries(env)) {
2107
- if (value !== void 0 && !SENSITIVE_ENV_PATTERNS.some((p) => p.test(key))) {
2118
+ if (value !== void 0 && SAFE_ENV_PATTERN.test(key)) {
2108
2119
  result[key] = value;
2109
2120
  }
2110
2121
  }
@@ -3433,6 +3444,8 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
3433
3444
  let consecutiveExhaustions = 0;
3434
3445
  const MAX_CONSECUTIVE_EXHAUSTIONS = 3;
3435
3446
  const slotPool = Array.from({ length: concurrency }, (_, i) => i);
3447
+ let watchStartTime = null;
3448
+ const watchTimeout = config.loop.watch_timeout ?? 0;
3436
3449
  const processIssue = async (issue, session, slotIndex) => {
3437
3450
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
3438
3451
  const logFile = resolve7(getLogsDir(workspace), `session_${session}_${timestamp}.log`);
@@ -3461,8 +3474,12 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
3461
3474
  } catch (err) {
3462
3475
  error(`Unhandled error in session for ${issue.id}: ${formatError(err)}`);
3463
3476
  await revertIssueStatus(issue, source, config);
3464
- activeCleanups.delete(issue.id);
3477
+ userKilledSet.delete(issue.id);
3478
+ userSkippedSet.delete(issue.id);
3479
+ providerPausedSet.delete(issue.id);
3465
3480
  activeProviderPids.delete(issue.id);
3481
+ activeCleanups.delete(issue.id);
3482
+ claimedIssueIds.delete(issue.id);
3466
3483
  if (config.bell !== false) notify(2);
3467
3484
  return;
3468
3485
  }
@@ -3525,10 +3542,20 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
3525
3542
  }
3526
3543
  if (issue && claimedIssueIds.has(issue.id)) {
3527
3544
  log(`Issue ${issue.id} already claimed by another worker. Skipping.`);
3545
+ await sleep(Math.max(config.loop.cooldown * 1e3, 2e3));
3528
3546
  break;
3529
3547
  }
3530
3548
  if (!issue) {
3531
3549
  if (opts.watch) {
3550
+ if (watchStartTime === null) watchStartTime = Date.now();
3551
+ if (watchTimeout > 0) {
3552
+ const elapsed = (Date.now() - watchStartTime) / 1e3;
3553
+ if (elapsed >= watchTimeout) {
3554
+ ok(`Watch mode timeout reached (${watchTimeout}s). Stopping.`);
3555
+ noMoreIssues = true;
3556
+ break;
3557
+ }
3558
+ }
3532
3559
  if (activeWorkers.size === 0) {
3533
3560
  if (completedCount > 0) {
3534
3561
  ok(`All issues resolved. Prompting user to continue watching...`);
@@ -3566,6 +3593,7 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
3566
3593
  break;
3567
3594
  }
3568
3595
  kanbanEmitter.emit("work:resumed");
3596
+ watchStartTime = null;
3569
3597
  sessionCounter = tentativeSession;
3570
3598
  const session = sessionCounter;
3571
3599
  claimedIssueIds.add(issue.id);
@@ -3929,6 +3957,8 @@ async function runSequentialLoop(config, source, models, workspace, opts) {
3929
3957
  const MAX_CONSECUTIVE_FETCH_ERRORS = 3;
3930
3958
  let consecutiveExhaustions = 0;
3931
3959
  const MAX_CONSECUTIVE_EXHAUSTIONS = 3;
3960
+ let watchStartTime = null;
3961
+ const watchTimeout = config.loop.watch_timeout ?? 0;
3932
3962
  while (true) {
3933
3963
  session++;
3934
3964
  if (opts.limit > 0 && session > opts.limit) {
@@ -3990,6 +4020,14 @@ async function runSequentialLoop(config, source, models, workspace, opts) {
3990
4020
  break;
3991
4021
  }
3992
4022
  if (opts.watch) {
4023
+ if (watchStartTime === null) watchStartTime = Date.now();
4024
+ if (watchTimeout > 0) {
4025
+ const elapsed = (Date.now() - watchStartTime) / 1e3;
4026
+ if (elapsed >= watchTimeout) {
4027
+ ok(`Watch mode timeout reached (${watchTimeout}s). Stopping.`);
4028
+ break;
4029
+ }
4030
+ }
3993
4031
  if (completedCount > 0) {
3994
4032
  ok(`All issues resolved. Prompting user to continue watching...`);
3995
4033
  kanbanEmitter.emit("work:watch-prompt");
@@ -4029,6 +4067,7 @@ async function runSequentialLoop(config, source, models, workspace, opts) {
4029
4067
  continue;
4030
4068
  }
4031
4069
  kanbanEmitter.emit("work:resumed");
4070
+ watchStartTime = null;
4032
4071
  ok(`Picked up: ${issue.id} \u2014 ${issue.title}`);
4033
4072
  setTitle(`Lisa \u2014 ${issue.id}`);
4034
4073
  const cachedPrUrls = loadPrUrls(workspace, issue.id);
@@ -4251,7 +4290,7 @@ async function runDemoLoop() {
4251
4290
  }
4252
4291
 
4253
4292
  // src/loop/index.ts
4254
- setupEventListeners();
4293
+ var cleanupEventListeners = setupEventListeners();
4255
4294
  async function runLoop(config, opts) {
4256
4295
  const source = createSource(config.source);
4257
4296
  const models = resolveModels(config);
@@ -4323,5 +4362,6 @@ export {
4323
4362
  checkoutBaseBranches,
4324
4363
  listSessionRecords,
4325
4364
  runDemoLoop,
4365
+ cleanupEventListeners,
4326
4366
  runLoop
4327
4367
  };
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/errors.ts
4
+ function formatError(err) {
5
+ if (err instanceof Error) {
6
+ const cause = err.cause ? ` (caused by: ${formatError(err.cause)})` : "";
7
+ return `${err.message}${cause}`;
8
+ }
9
+ return String(err);
10
+ }
11
+ var LisaError = class extends Error {
12
+ constructor(message, options) {
13
+ super(message, options);
14
+ this.name = "LisaError";
15
+ }
16
+ };
17
+ var SourceError = class extends LisaError {
18
+ constructor(message, source, statusCode, options) {
19
+ super(message, options);
20
+ this.source = source;
21
+ this.statusCode = statusCode;
22
+ this.name = "SourceError";
23
+ }
24
+ };
25
+
26
+ export {
27
+ formatError,
28
+ LisaError,
29
+ SourceError
30
+ };
@@ -2,9 +2,12 @@
2
2
  import {
3
3
  isGhCliAvailable
4
4
  } from "./chunk-YMV4CBQE.js";
5
+ import {
6
+ verbose
7
+ } from "./chunk-HPWL5JRW.js";
5
8
  import {
6
9
  formatError
7
- } from "./chunk-7JT7DTSS.js";
10
+ } from "./chunk-CTMZ666K.js";
8
11
 
9
12
  // src/cli/detection.ts
10
13
  import { execSync } from "child_process";
@@ -17,7 +20,8 @@ function getVersion() {
17
20
  const pkgPath = resolvePath(new URL(".", import.meta.url).pathname, "../package.json");
18
21
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
19
22
  return pkg.version;
20
- } catch {
23
+ } catch (err) {
24
+ verbose(`Failed to read package.json version: ${formatError(err)}`);
21
25
  return "0.0.0";
22
26
  }
23
27
  }
@@ -32,7 +36,8 @@ async function isCursorFreePlan() {
32
36
  try {
33
37
  execSync(`${b} --version`, { stdio: "ignore" });
34
38
  return true;
35
- } catch {
39
+ } catch (err) {
40
+ verbose(`Cursor binary "${b}" not found: ${formatError(err)}`);
36
41
  return false;
37
42
  }
38
43
  });
@@ -49,7 +54,8 @@ async function isCursorFreePlan() {
49
54
  } finally {
50
55
  try {
51
56
  rmSync(tmpDir, { recursive: true, force: true });
52
- } catch {
57
+ } catch (err) {
58
+ verbose(`Failed to clean up temp dir ${tmpDir}: ${formatError(err)}`);
53
59
  }
54
60
  }
55
61
  }
@@ -75,7 +81,8 @@ function fetchCursorModels() {
75
81
  try {
76
82
  execSync(`${b} --version`, { stdio: "ignore" });
77
83
  return true;
78
- } catch {
84
+ } catch (err) {
85
+ verbose(`Cursor binary "${b}" not available: ${formatError(err)}`);
79
86
  return false;
80
87
  }
81
88
  });
@@ -85,7 +92,8 @@ function fetchCursorModels() {
85
92
  const all = clean.split("\n").map((l) => l.trim()).filter((l) => l.includes(" - ")).map((l) => (l.split(" - ")[0] ?? "").trim()).filter(Boolean);
86
93
  const filtered = CURSOR_PREFERRED_MODELS.filter((m) => all.includes(m));
87
94
  return filtered.length > 0 ? filtered : CURSOR_PREFERRED_MODELS;
88
- } catch {
95
+ } catch (err) {
96
+ verbose(`Failed to fetch Cursor models: ${formatError(err)}`);
89
97
  return CURSOR_PREFERRED_MODELS;
90
98
  }
91
99
  }
@@ -98,7 +106,8 @@ function fetchOpenCodeModels() {
98
106
  const raw = execSync("opencode models", { encoding: "utf-8", timeout: 1e4 });
99
107
  const clean = raw.replace(/\x1b\[[0-9;]*[mGKHFA-Z]/g, "");
100
108
  return clean.split("\n").map((l) => l.trim()).filter((m) => /^[a-z0-9][\w.-]*\/.+/i.test(m));
101
- } catch {
109
+ } catch (err) {
110
+ verbose(`Failed to fetch OpenCode models: ${formatError(err)}`);
102
111
  return [];
103
112
  }
104
113
  }
@@ -120,7 +129,8 @@ async function detectPlatform() {
120
129
  const platformLabel = detectedPlatform === "cli" || detectedPlatform === "token" ? "GitHub" : detectedPlatform === "gitlab" ? "GitLab" : "Bitbucket";
121
130
  clack.log.info(`Detected ${platformLabel} remote`);
122
131
  }
123
- } catch {
132
+ } catch (err) {
133
+ verbose(`Platform detection from git remote failed: ${formatError(err)}`);
124
134
  }
125
135
  const initialValue = detectedPlatform ?? "cli";
126
136
  const selected = await clack.select({
@@ -210,7 +220,8 @@ function detectDefaultBranch(repoPath) {
210
220
  encoding: "utf-8"
211
221
  }).trim();
212
222
  return ref.replace("origin/", "");
213
- } catch {
223
+ } catch (err) {
224
+ verbose(`Failed to detect default branch, falling back to "main": ${formatError(err)}`);
214
225
  return "main";
215
226
  }
216
227
  }
@@ -219,7 +230,8 @@ function getGitRepoName(repoPath) {
219
230
  const url = execSync("git remote get-url origin", { cwd: repoPath, encoding: "utf-8" }).trim();
220
231
  const match = url.match(/\/([^/]+?)(?:\.git)?$/) ?? url.match(/:([^/]+?)(?:\.git)?$/);
221
232
  return match?.[1] ?? null;
222
- } catch {
233
+ } catch (err) {
234
+ verbose(`Failed to get git repo name for ${repoPath}: ${formatError(err)}`);
223
235
  return null;
224
236
  }
225
237
  }
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/output/logger.ts
4
+ import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
5
+ import { dirname } from "path";
6
+ import pc from "picocolors";
7
+ var logFilePath = null;
8
+ var outputMode = "default";
9
+ var logLevel = "default";
10
+ function setOutputMode(mode) {
11
+ outputMode = mode;
12
+ }
13
+ function getOutputMode() {
14
+ return outputMode;
15
+ }
16
+ function setLogLevel(level) {
17
+ logLevel = level;
18
+ }
19
+ function shouldPrintToConsole() {
20
+ return outputMode !== "tui" && logLevel !== "quiet";
21
+ }
22
+ function initLogFile(path) {
23
+ const dir = dirname(path);
24
+ if (!existsSync(dir)) {
25
+ mkdirSync(dir, { recursive: true });
26
+ }
27
+ writeFileSync(path, `[${timestamp()}] Log started
28
+ `);
29
+ logFilePath = path;
30
+ }
31
+ function timestamp() {
32
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
33
+ }
34
+ function writeToFile(level, message) {
35
+ if (logFilePath) {
36
+ appendFileSync(logFilePath, `[${timestamp()}] [${level}] ${message}
37
+ `);
38
+ }
39
+ }
40
+ function log(message) {
41
+ if (shouldPrintToConsole()) {
42
+ console.error(`${pc.cyan("[lisa]")} ${pc.dim(timestamp())} ${message}`);
43
+ }
44
+ writeToFile("info", message);
45
+ }
46
+ function warn(message) {
47
+ if (shouldPrintToConsole()) {
48
+ console.error(`${pc.yellow("[lisa]")} ${pc.dim(timestamp())} ${message}`);
49
+ }
50
+ writeToFile("warn", message);
51
+ }
52
+ function error(message) {
53
+ if (shouldPrintToConsole()) {
54
+ console.error(`${pc.red("[lisa]")} ${pc.dim(timestamp())} ${message}`);
55
+ }
56
+ writeToFile("error", message);
57
+ }
58
+ function ok(message) {
59
+ if (shouldPrintToConsole()) {
60
+ console.error(`${pc.green("[lisa]")} ${pc.dim(timestamp())} ${message}`);
61
+ }
62
+ writeToFile("ok", message);
63
+ }
64
+ function verbose(message) {
65
+ if (logLevel !== "verbose") return;
66
+ if (shouldPrintToConsole()) {
67
+ console.error(`${pc.dim("[lisa]")} ${pc.dim(timestamp())} ${pc.dim(message)}`);
68
+ }
69
+ writeToFile("verbose", message);
70
+ }
71
+ function divider(session) {
72
+ log(`${"\u2501".repeat(3)} Session ${session} ${"\u2501".repeat(3)}`);
73
+ }
74
+ function banner() {
75
+ if (outputMode !== "default" || logLevel === "quiet") return;
76
+ const title = " lisa \u266A autonomous issue resolver ";
77
+ const border = "\u2500".repeat(title.length);
78
+ console.error(pc.yellow(`
79
+ \u250C${border}\u2510`));
80
+ console.error(pc.yellow(` \u2502`) + pc.bold(pc.white(title)) + pc.yellow("\u2502"));
81
+ console.error(pc.yellow(` \u2514${border}\u2518
82
+ `));
83
+ }
84
+ function updateNotice(update) {
85
+ if (outputMode !== "default" || logLevel === "quiet") return;
86
+ const msg = `Update available ${pc.dim(update.currentVersion)} \u2192 ${pc.green(pc.bold(update.latestVersion))}`;
87
+ const cmd = `Run ${pc.cyan("npm i -g @tarcisiopgs/lisa")} to update`;
88
+ const lines = [msg, cmd];
89
+ const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
90
+ const maxLen = Math.max(...lines.map((l) => strip(l).length));
91
+ const pad = (s) => s + " ".repeat(maxLen - strip(s).length);
92
+ console.error(pc.yellow(` \u250C${"\u2500".repeat(maxLen + 2)}\u2510`));
93
+ for (const line of lines) {
94
+ console.error(pc.yellow(" \u2502 ") + pad(line) + pc.yellow(" \u2502"));
95
+ }
96
+ console.error(pc.yellow(` \u2514${"\u2500".repeat(maxLen + 2)}\u2518
97
+ `));
98
+ }
99
+
100
+ export {
101
+ setOutputMode,
102
+ getOutputMode,
103
+ setLogLevel,
104
+ initLogFile,
105
+ log,
106
+ warn,
107
+ error,
108
+ ok,
109
+ verbose,
110
+ divider,
111
+ banner,
112
+ updateNotice
113
+ };
@@ -5,17 +5,22 @@ import {
5
5
  resolveModels,
6
6
  runWithFallback,
7
7
  saveLineage
8
- } from "./chunk-6VIN5PMW.js";
8
+ } from "./chunk-XXVTKBC5.js";
9
+ import {
10
+ normalizeLabels
11
+ } from "./chunk-VS6R5KBO.js";
9
12
  import {
10
13
  error,
11
14
  log,
12
- normalizeLabels,
13
15
  ok,
14
16
  warn
15
- } from "./chunk-V44FTYWZ.js";
17
+ } from "./chunk-HPWL5JRW.js";
18
+ import {
19
+ LisaError
20
+ } from "./chunk-CTMZ666K.js";
16
21
 
17
22
  // src/cli/error.ts
18
- var CliError = class extends Error {
23
+ var CliError = class extends LisaError {
19
24
  exitCode;
20
25
  constructor(message, exitCode = 1) {
21
26
  super(message);