@sweny-ai/core 0.1.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.
Files changed (93) hide show
  1. package/dist/__tests__/claude.test.d.ts +1 -0
  2. package/dist/__tests__/claude.test.js +328 -0
  3. package/dist/__tests__/executor.test.d.ts +1 -0
  4. package/dist/__tests__/executor.test.js +296 -0
  5. package/dist/__tests__/integration/datadog.integration.test.d.ts +1 -0
  6. package/dist/__tests__/integration/datadog.integration.test.js +23 -0
  7. package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +1 -0
  8. package/dist/__tests__/integration/e2e-workflow.integration.test.js +75 -0
  9. package/dist/__tests__/integration/github.integration.test.d.ts +1 -0
  10. package/dist/__tests__/integration/github.integration.test.js +37 -0
  11. package/dist/__tests__/integration/harness.d.ts +24 -0
  12. package/dist/__tests__/integration/harness.js +34 -0
  13. package/dist/__tests__/integration/linear.integration.test.d.ts +1 -0
  14. package/dist/__tests__/integration/linear.integration.test.js +15 -0
  15. package/dist/__tests__/integration/sentry.integration.test.d.ts +1 -0
  16. package/dist/__tests__/integration/sentry.integration.test.js +20 -0
  17. package/dist/__tests__/integration/slack.integration.test.d.ts +1 -0
  18. package/dist/__tests__/integration/slack.integration.test.js +22 -0
  19. package/dist/__tests__/schema.test.d.ts +1 -0
  20. package/dist/__tests__/schema.test.js +239 -0
  21. package/dist/__tests__/skills-index.test.d.ts +1 -0
  22. package/dist/__tests__/skills-index.test.js +122 -0
  23. package/dist/__tests__/skills.test.d.ts +1 -0
  24. package/dist/__tests__/skills.test.js +296 -0
  25. package/dist/__tests__/studio.test.d.ts +1 -0
  26. package/dist/__tests__/studio.test.js +172 -0
  27. package/dist/__tests__/testing.test.d.ts +1 -0
  28. package/dist/__tests__/testing.test.js +224 -0
  29. package/dist/browser.d.ts +17 -0
  30. package/dist/browser.js +22 -0
  31. package/dist/claude.d.ts +48 -0
  32. package/dist/claude.js +293 -0
  33. package/dist/cli/check.d.ts +11 -0
  34. package/dist/cli/check.js +237 -0
  35. package/dist/cli/config-file.d.ts +12 -0
  36. package/dist/cli/config-file.js +208 -0
  37. package/dist/cli/config.d.ts +77 -0
  38. package/dist/cli/config.js +565 -0
  39. package/dist/cli/main.d.ts +10 -0
  40. package/dist/cli/main.js +744 -0
  41. package/dist/cli/output.d.ts +26 -0
  42. package/dist/cli/output.js +357 -0
  43. package/dist/cli/renderer.d.ts +33 -0
  44. package/dist/cli/renderer.js +423 -0
  45. package/dist/cli/renderer.test.d.ts +1 -0
  46. package/dist/cli/renderer.test.js +302 -0
  47. package/dist/cli/setup.d.ts +11 -0
  48. package/dist/cli/setup.js +310 -0
  49. package/dist/executor.d.ts +29 -0
  50. package/dist/executor.js +173 -0
  51. package/dist/executor.test.d.ts +1 -0
  52. package/dist/executor.test.js +314 -0
  53. package/dist/index.d.ts +37 -0
  54. package/dist/index.js +36 -0
  55. package/dist/mcp.d.ts +11 -0
  56. package/dist/mcp.js +183 -0
  57. package/dist/mcp.test.d.ts +1 -0
  58. package/dist/mcp.test.js +334 -0
  59. package/dist/schema.d.ts +318 -0
  60. package/dist/schema.js +207 -0
  61. package/dist/skills/betterstack.d.ts +7 -0
  62. package/dist/skills/betterstack.js +114 -0
  63. package/dist/skills/datadog.d.ts +7 -0
  64. package/dist/skills/datadog.js +107 -0
  65. package/dist/skills/github.d.ts +8 -0
  66. package/dist/skills/github.js +155 -0
  67. package/dist/skills/index.d.ts +68 -0
  68. package/dist/skills/index.js +134 -0
  69. package/dist/skills/linear.d.ts +7 -0
  70. package/dist/skills/linear.js +89 -0
  71. package/dist/skills/notification.d.ts +11 -0
  72. package/dist/skills/notification.js +142 -0
  73. package/dist/skills/sentry.d.ts +7 -0
  74. package/dist/skills/sentry.js +105 -0
  75. package/dist/skills/slack.d.ts +8 -0
  76. package/dist/skills/slack.js +115 -0
  77. package/dist/studio.d.ts +124 -0
  78. package/dist/studio.js +174 -0
  79. package/dist/testing.d.ts +88 -0
  80. package/dist/testing.js +253 -0
  81. package/dist/types.d.ts +144 -0
  82. package/dist/types.js +11 -0
  83. package/dist/workflow-builder.d.ts +45 -0
  84. package/dist/workflow-builder.js +120 -0
  85. package/dist/workflow-builder.test.d.ts +1 -0
  86. package/dist/workflow-builder.test.js +117 -0
  87. package/dist/workflows/implement.d.ts +11 -0
  88. package/dist/workflows/implement.js +83 -0
  89. package/dist/workflows/index.d.ts +2 -0
  90. package/dist/workflows/index.js +2 -0
  91. package/dist/workflows/triage.d.ts +18 -0
  92. package/dist/workflows/triage.js +108 -0
  93. package/package.json +83 -0
@@ -0,0 +1,26 @@
1
+ import type { CliConfig } from "./config.js";
2
+ import type { CheckResult } from "./check.js";
3
+ import type { NodeResult } from "../types.js";
4
+ export declare const c: {
5
+ brand: import("chalk").ChalkInstance;
6
+ brandDim: import("chalk").ChalkInstance;
7
+ learn: import("chalk").ChalkInstance;
8
+ act: import("chalk").ChalkInstance;
9
+ report: import("chalk").ChalkInstance;
10
+ ok: import("chalk").ChalkInstance;
11
+ fail: import("chalk").ChalkInstance;
12
+ subtle: import("chalk").ChalkInstance;
13
+ link: import("chalk").ChalkInstance;
14
+ };
15
+ export declare function phaseColor(phase: string): (s: string) => string;
16
+ export declare function stripAnsi(str: string): string;
17
+ export declare function formatBanner(config: CliConfig, version: string): string;
18
+ export declare function formatPhaseHeader(phase: string): string;
19
+ export declare function getStepDetails(name: string, data?: Record<string, unknown>): string[];
20
+ export declare function formatStepLine(icon: string, counter: string, name: string, elapsed: string, reason?: string): string;
21
+ export declare function formatDagResultHuman(results: Map<string, NodeResult>, durationMs: number, config?: CliConfig): string;
22
+ export declare function formatValidationErrors(errors: string[]): string;
23
+ export declare function extractCredentialHint(err: unknown): string | null;
24
+ export declare function formatCrashError(error: unknown): string;
25
+ export declare function formatCheckResults(results: CheckResult[]): string;
26
+ export declare function formatResultJson(results: Map<string, NodeResult>): string;
@@ -0,0 +1,357 @@
1
+ import chalk from "chalk";
2
+ // ── Color palette ───────────────────────────────────────────────
3
+ export const c = {
4
+ brand: chalk.hex("#FF6B2B"),
5
+ brandDim: chalk.hex("#CC5522"),
6
+ learn: chalk.hex("#60A5FA"),
7
+ act: chalk.hex("#F59E0B"),
8
+ report: chalk.hex("#A78BFA"),
9
+ ok: chalk.hex("#34D399"),
10
+ fail: chalk.hex("#F87171"),
11
+ subtle: chalk.hex("#6B7280"),
12
+ link: chalk.hex("#60A5FA").underline,
13
+ };
14
+ export function phaseColor(phase) {
15
+ return phase === "learn" ? c.learn : phase === "act" ? c.act : phase === "report" ? c.report : chalk.white;
16
+ }
17
+ // ── Utilities ───────────────────────────────────────────────────
18
+ export function stripAnsi(str) {
19
+ return str.replace(/\x1B\[[0-9;]*m/g, "");
20
+ }
21
+ function visLen(str) {
22
+ return stripAnsi(str).length;
23
+ }
24
+ // ── Box drawing ─────────────────────────────────────────────────
25
+ const BOX_WIDTH = 50;
26
+ function padLine(line, width) {
27
+ const pad = Math.max(0, width - visLen(line));
28
+ return line + " ".repeat(pad);
29
+ }
30
+ function boxTop() {
31
+ return c.brandDim(" \u256D" + "\u2500".repeat(BOX_WIDTH) + "\u256E");
32
+ }
33
+ function boxBottom() {
34
+ return c.brandDim(" \u2570" + "\u2500".repeat(BOX_WIDTH) + "\u256F");
35
+ }
36
+ function boxDivider() {
37
+ return c.brandDim(" \u251C" + "\u2500".repeat(BOX_WIDTH) + "\u2524");
38
+ }
39
+ function boxEmpty() {
40
+ return c.brandDim(" \u2502") + " ".repeat(BOX_WIDTH) + c.brandDim("\u2502");
41
+ }
42
+ function wrapText(text, maxWidth) {
43
+ const words = stripAnsi(text).split(" ");
44
+ const lines = [];
45
+ let current = "";
46
+ for (const word of words) {
47
+ if (current.length === 0) {
48
+ current = word;
49
+ }
50
+ else if (current.length + 1 + word.length <= maxWidth) {
51
+ current += " " + word;
52
+ }
53
+ else {
54
+ lines.push(current);
55
+ current = word;
56
+ }
57
+ }
58
+ if (current.length > 0)
59
+ lines.push(current);
60
+ return lines;
61
+ }
62
+ function boxLine(content) {
63
+ const maxInner = BOX_WIDTH - 4;
64
+ const visible = visLen(content);
65
+ if (visible <= maxInner) {
66
+ const inner = padLine(content, maxInner);
67
+ return c.brandDim(" \u2502") + " " + inner + " " + c.brandDim("\u2502");
68
+ }
69
+ // Wrap long lines
70
+ const wrapped = wrapText(content, maxInner);
71
+ return wrapped
72
+ .map((line) => {
73
+ const padded = padLine(line, maxInner);
74
+ return c.brandDim(" \u2502") + " " + padded + " " + c.brandDim("\u2502");
75
+ })
76
+ .join("\n");
77
+ }
78
+ function boxSection(lines) {
79
+ return [boxEmpty(), ...lines.map(boxLine), boxEmpty()];
80
+ }
81
+ // ── Banner ──────────────────────────────────────────────────────
82
+ export function formatBanner(config, version) {
83
+ const title = `${c.brand("\u25B2")} ${chalk.bold(c.brand("SWEny"))} ${chalk.bold("Triage")}`;
84
+ const ver = c.subtle(`v${version}`);
85
+ const titlePad = BOX_WIDTH - 4 - visLen(title) - visLen(ver);
86
+ const mode = config.dryRun ? chalk.hex("#F59E0B")("dry run") : c.ok("live");
87
+ const header = [title + " ".repeat(Math.max(1, titlePad)) + ver];
88
+ const rows = [
89
+ `${c.subtle("Repository")}${" ".repeat(6)}${chalk.white(config.repository)}`,
90
+ `${c.subtle("Agent")}${" ".repeat(11)}${chalk.white(config.codingAgentProvider)}`,
91
+ `${c.subtle("Observability")}${" ".repeat(3)}${chalk.white(config.observabilityProvider)}`,
92
+ `${c.subtle("Issue tracker")}${" ".repeat(3)}${chalk.white(config.issueTrackerProvider)}`,
93
+ `${c.subtle("Time range")}${" ".repeat(6)}${chalk.white(config.timeRange)}`,
94
+ `${c.subtle("Mode")}${" ".repeat(12)}${mode}`,
95
+ ];
96
+ return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(rows), boxBottom()].join("\n");
97
+ }
98
+ // ── Phase header ────────────────────────────────────────────────
99
+ // TODO: The old engine had WorkflowPhase; in the new DAG model phases are not
100
+ // a first-class concept. This helper is kept for backward compat if needed.
101
+ export function formatPhaseHeader(phase) {
102
+ const color = phaseColor(phase);
103
+ const label = phase.charAt(0).toUpperCase() + phase.slice(1);
104
+ const ruleLen = Math.max(0, 44 - label.length - 5);
105
+ return `\n ${c.subtle("\u2504\u2504\u2504")} ${color(label)} ${c.subtle("\u2504".repeat(ruleLen))}`;
106
+ }
107
+ // ── Step details ────────────────────────────────────────────────
108
+ export function getStepDetails(name, data) {
109
+ if (!data)
110
+ return [];
111
+ const details = [];
112
+ switch (name) {
113
+ case "build-context": {
114
+ const content = data.knownIssuesContent;
115
+ if (content) {
116
+ const count = (content.match(/^- \*\*/gm) || []).length;
117
+ if (count > 0)
118
+ details.push(`${count} known issues loaded`);
119
+ }
120
+ break;
121
+ }
122
+ case "investigate": {
123
+ const found = data.issuesFound ? "Issues found" : "No issues found";
124
+ const rec = data.recommendation;
125
+ if (rec)
126
+ details.push(`${found}, recommending: ${rec}`);
127
+ else
128
+ details.push(found);
129
+ if (data.targetRepo)
130
+ details.push(`Target: ${data.targetRepo}`);
131
+ break;
132
+ }
133
+ case "novelty-gate": {
134
+ const action = data.action;
135
+ if (action === "dry-run")
136
+ details.push("Dry run — analysis only");
137
+ else if (action === "skip")
138
+ details.push("No novel issues found");
139
+ else if (action === "+1")
140
+ details.push(`+1 on existing ${data.issueIdentifier || "issue"}`);
141
+ else if (action === "implement")
142
+ details.push("Proceeding with implementation");
143
+ break;
144
+ }
145
+ case "create-issue": {
146
+ const id = data.issueIdentifier;
147
+ const title = data.issueTitle;
148
+ if (id && title)
149
+ details.push(`${id}: ${title}`);
150
+ const url = data.issueUrl;
151
+ if (url)
152
+ details.push(url);
153
+ break;
154
+ }
155
+ case "cross-repo-check": {
156
+ if (data.dispatched)
157
+ details.push(`Dispatched to ${data.targetRepo}`);
158
+ break;
159
+ }
160
+ case "implement-fix": {
161
+ if (data.branchName)
162
+ details.push(`Branch: ${data.branchName}`);
163
+ if (data.hasCodeChanges)
164
+ details.push("Code changes committed");
165
+ break;
166
+ }
167
+ case "create-pr": {
168
+ const prUrl = data.prUrl;
169
+ const prNum = data.prNumber;
170
+ if (prUrl)
171
+ details.push(`PR #${prNum ?? ""}: ${prUrl}`);
172
+ const linked = data.issueIdentifier;
173
+ if (linked)
174
+ details.push(`Linked to ${linked}`);
175
+ break;
176
+ }
177
+ }
178
+ return details;
179
+ }
180
+ // ── Format step line ────────────────────────────────────────────
181
+ export function formatStepLine(icon, counter, name, elapsed, reason) {
182
+ const label = `${counter} ${name}`;
183
+ const pad = Math.max(1, 40 - visLen(label));
184
+ const suffix = reason ? c.subtle(` \u2014 ${reason}`) : "";
185
+ return ` ${icon} ${c.subtle(counter)} ${name}${" ".repeat(pad)}${c.subtle(elapsed)}${suffix}`;
186
+ }
187
+ // ── DAG result summary ──────────────────────────────────────────
188
+ // TODO: These formatters are adapted from the old engine WorkflowResult.
189
+ // They inspect node result data by name, which may need updating once
190
+ // the DAG node IDs settle.
191
+ export function formatDagResultHuman(results, durationMs, config) {
192
+ const duration = formatDuration(durationMs);
193
+ // Check for any failures
194
+ for (const [nodeId, result] of results) {
195
+ if (result.status === "failed") {
196
+ return formatDagFailureResult(nodeId, result, duration);
197
+ }
198
+ }
199
+ // Check for PR creation
200
+ const createPrResult = results.get("create_pr");
201
+ if (createPrResult?.data?.prUrl) {
202
+ return formatDagSuccessResult(results, duration);
203
+ }
204
+ // No action / skip
205
+ return formatDagNoActionResult(results, duration, config);
206
+ }
207
+ function formatDagSuccessResult(results, duration) {
208
+ const title = `${c.ok("\u2713")} ${chalk.bold("Triage Complete")}`;
209
+ const titlePad = BOX_WIDTH - 4 - visLen(title) - visLen(duration);
210
+ const header = [title + " ".repeat(Math.max(1, titlePad)) + c.subtle(duration)];
211
+ const body = [];
212
+ const issueData = results.get("create_issue")?.data;
213
+ const prData = results.get("create_pr")?.data;
214
+ if (issueData?.issueIdentifier) {
215
+ body.push(`${c.subtle("Issue")}${" ".repeat(5)}${chalk.bold(String(issueData.issueIdentifier))}`);
216
+ if (issueData.issueTitle)
217
+ body.push(`${" ".repeat(10)}${String(issueData.issueTitle)}`);
218
+ if (issueData.issueUrl)
219
+ body.push(`${" ".repeat(10)}${c.link(String(issueData.issueUrl))}`);
220
+ body.push("");
221
+ }
222
+ if (prData?.prUrl) {
223
+ body.push(`${c.subtle("PR")}${" ".repeat(8)}${chalk.bold("#" + String(prData.prNumber ?? ""))}`);
224
+ body.push(`${" ".repeat(10)}${c.link(String(prData.prUrl))}`);
225
+ body.push("");
226
+ }
227
+ return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(body), boxBottom(), ""].join("\n");
228
+ }
229
+ function formatDagFailureResult(nodeId, result, duration) {
230
+ const title = `${c.fail("\u2717")} ${chalk.bold("Workflow Failed")}`;
231
+ const titlePad = BOX_WIDTH - 4 - visLen(title) - visLen(duration);
232
+ const header = [title + " ".repeat(Math.max(1, titlePad)) + c.subtle(duration)];
233
+ const body = [];
234
+ body.push(`Failed at: ${chalk.bold(nodeId)}`);
235
+ if (result.data?.error) {
236
+ body.push("");
237
+ body.push(String(result.data.error));
238
+ }
239
+ return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(body), boxBottom(), ""].join("\n");
240
+ }
241
+ function formatDagNoActionResult(results, duration, config) {
242
+ const title = `${c.subtle("\u2212")} ${chalk.bold("No Action Needed")}`;
243
+ const titlePad = BOX_WIDTH - 4 - visLen(title) - visLen(duration);
244
+ const header = [title + " ".repeat(Math.max(1, titlePad)) + c.subtle(duration)];
245
+ const body = [];
246
+ const investigateData = results.get("investigate")?.data;
247
+ if (investigateData?.is_duplicate) {
248
+ body.push("Issue identified as a duplicate of an existing ticket.");
249
+ if (investigateData.duplicate_of) {
250
+ body.push(`${" ".repeat(2)}${c.link(String(investigateData.duplicate_of))}`);
251
+ }
252
+ }
253
+ else {
254
+ const rec = investigateData?.recommendation;
255
+ if (rec)
256
+ body.push(`Recommendation: ${String(rec)}`);
257
+ else
258
+ body.push("No actionable issues detected.");
259
+ }
260
+ // Suggest widening the search when using narrow defaults
261
+ const hints = [];
262
+ if (!config?.timeRange || config.timeRange === "24h") {
263
+ hints.push("--time-range 7d");
264
+ }
265
+ if (hints.length > 0) {
266
+ body.push("");
267
+ body.push(c.subtle(`Tip: try ${hints.join(" or ")} to widen the search`));
268
+ }
269
+ return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(body), boxBottom(), ""].join("\n");
270
+ }
271
+ // ── Validation errors ───────────────────────────────────────────
272
+ export function formatValidationErrors(errors) {
273
+ const title = `${c.fail("\u2717")} ${chalk.bold("Configuration Error")}`;
274
+ const header = [title];
275
+ const body = [];
276
+ for (let i = 0; i < errors.length; i++) {
277
+ if (i > 0)
278
+ body.push("");
279
+ body.push(`${c.subtle(`${i + 1}.`)} ${errors[i]}`);
280
+ }
281
+ return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(body), boxBottom()].join("\n");
282
+ }
283
+ // ── Credential hint extraction ───────────────────────────────────
284
+ export function extractCredentialHint(err) {
285
+ const msg = err instanceof Error ? err.message : String(err);
286
+ if (/401|unauthorized|authentication/i.test(msg) && /anthropic/i.test(msg)) {
287
+ return "Check your ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN — get a key at https://console.anthropic.com";
288
+ }
289
+ if (/401|403|unauthorized/i.test(msg) && /datadog/i.test(msg)) {
290
+ return "Check your DD_API_KEY and DD_APP_KEY — find them at https://app.datadoghq.com/organization-settings/api-keys";
291
+ }
292
+ if (/401|403|unauthorized/i.test(msg) && /linear/i.test(msg)) {
293
+ return "Check your LINEAR_API_KEY — find it at https://linear.app/settings/api";
294
+ }
295
+ if (/401|403|unauthorized/i.test(msg) && /github/i.test(msg)) {
296
+ return "Check your GITHUB_TOKEN — create a Personal Access Token at https://github.com/settings/tokens";
297
+ }
298
+ if (/ENOTFOUND|ETIMEDOUT|network/i.test(msg)) {
299
+ return "Network error — check your internet connection and provider endpoint URL.";
300
+ }
301
+ return null;
302
+ }
303
+ // ── Crash error ─────────────────────────────────────────────────
304
+ export function formatCrashError(error) {
305
+ const msg = error instanceof Error ? error.message : "Unknown error";
306
+ const title = `${c.fail("\u2717")} ${chalk.bold("Unexpected Error")}`;
307
+ const header = [title];
308
+ const hint = extractCredentialHint(error);
309
+ const body = [
310
+ msg,
311
+ ...(hint ? ["", `${c.subtle("Hint:")} ${hint}`] : []),
312
+ "",
313
+ c.subtle("If this persists, please open an issue:"),
314
+ c.link("https://github.com/swenyai/sweny/issues"),
315
+ ];
316
+ return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(body), boxBottom(), ""].join("\n");
317
+ }
318
+ // ── Check results ────────────────────────────────────────────────
319
+ export function formatCheckResults(results) {
320
+ const title = `${chalk.bold("Provider Connectivity Check")}`;
321
+ const header = [title];
322
+ const body = results.map((r) => {
323
+ const icon = r.status === "ok" ? c.ok("\u2713") : r.status === "fail" ? c.fail("\u2717") : c.subtle("\u2212");
324
+ const name = chalk.white(r.name);
325
+ const detail = r.status === "ok" ? c.subtle(r.detail) : r.status === "fail" ? chalk.red(r.detail) : c.subtle(r.detail);
326
+ return `${icon} ${name}\n ${detail}`;
327
+ });
328
+ const hasFailure = results.some((r) => r.status === "fail");
329
+ const summary = hasFailure
330
+ ? c.fail("One or more checks failed — fix the issues above before running sweny triage.")
331
+ : results.every((r) => r.status === "skip")
332
+ ? c.subtle("All providers set to file mode — no network checks performed.")
333
+ : c.ok("All checks passed.");
334
+ return [
335
+ "",
336
+ boxTop(),
337
+ ...boxSection(header),
338
+ boxDivider(),
339
+ ...boxSection(body),
340
+ boxDivider(),
341
+ ...boxSection([summary]),
342
+ boxBottom(),
343
+ "",
344
+ ].join("\n");
345
+ }
346
+ // ── JSON output ─────────────────────────────────────────────────
347
+ export function formatResultJson(results) {
348
+ return JSON.stringify(Object.fromEntries(results), null, 2);
349
+ }
350
+ // ── Helpers ─────────────────────────────────────────────────────
351
+ function formatDuration(ms) {
352
+ const s = Math.round(ms / 1000);
353
+ if (s < 60)
354
+ return `${s}s`;
355
+ const m = Math.floor(s / 60);
356
+ return `${m}m ${s % 60}s`;
357
+ }
@@ -0,0 +1,33 @@
1
+ import type { Workflow, ExecutionEvent } from "../types.js";
2
+ export type NodeState = "pending" | "running" | "completed" | "failed";
3
+ /** Strip ANSI escape codes for accurate visible-width calculations. */
4
+ export declare function stripAnsi(str: string): string;
5
+ export interface DagRendererOptions {
6
+ /** If true, call render() automatically on every update(). Default: false. */
7
+ animate?: boolean;
8
+ /** Stream to write to when animate=true or render() is called. Default: process.stderr. */
9
+ stream?: NodeJS.WriteStream;
10
+ }
11
+ export declare class DagRenderer {
12
+ private readonly workflow;
13
+ private readonly options;
14
+ private readonly nodeStates;
15
+ private readonly topoOrder;
16
+ /** Number of lines written in the last render pass (for cursor repositioning). */
17
+ private lastLineCount;
18
+ constructor(workflow: Workflow, options?: DagRendererOptions);
19
+ /** Update renderer state from an execution event. Re-renders if animate=true. */
20
+ update(event: ExecutionEvent): void;
21
+ /** Returns the current state of a node. Returns "pending" for unknown nodes. */
22
+ getNodeState(nodeId: string): NodeState;
23
+ /** Returns the number of tool calls for a node. Returns 0 for unknown nodes. */
24
+ getToolCallCount(nodeId: string): number;
25
+ /** Renders the DAG to a string (no side effects). */
26
+ renderToString(): string;
27
+ /**
28
+ * Writes the current DAG render to the stream.
29
+ * When animate=true, uses cursor manipulation to update in place.
30
+ */
31
+ render(): void;
32
+ private getOrCreate;
33
+ }