tasktui 1.0.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.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # tasktui
2
+
3
+ Run tasks in a multiplexed terminal with dependency management.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ $ npm install --save-dev task-tui
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```
14
+ $ task-tui --help
15
+
16
+ Usage
17
+ $ tasktui [--config <PATH> | --help]
18
+
19
+ Options
20
+ --config Path to config file (default: ./tasktui.config.json)
21
+ ```
package/dist/app.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare const ui: import("./ui.js").UIComponents;
2
+ export declare function loadAndProcessConfig(configPath?: string): void;
3
+ export default ui;
package/dist/app.js ADDED
@@ -0,0 +1,71 @@
1
+ import { render, showError } from './renderer.js';
2
+ import { createState, getAllTasksInOrder } from './state.js';
3
+ import { cleanup, ensureDependencies, spawnTask } from './tasks.js';
4
+ import { createUI } from './ui.js';
5
+ import { ensureError, loadConfig } from './utils.js';
6
+ const ui = createUI();
7
+ const state = createState();
8
+ function handleMove(steps) {
9
+ const allTasks = getAllTasksInOrder(state);
10
+ if (allTasks.length === 0)
11
+ return;
12
+ const selectedIndex = allTasks.indexOf(state.selectedTask);
13
+ const newIndex = (selectedIndex + steps + allTasks.length) % allTasks.length;
14
+ const newTask = allTasks[newIndex];
15
+ if (newTask) {
16
+ state.selectedTask = newTask;
17
+ render(ui, state);
18
+ }
19
+ }
20
+ export function loadAndProcessConfig(configPath) {
21
+ try {
22
+ const config = loadConfig(configPath);
23
+ state.config = config;
24
+ const tasks = config?.tasks ?? {};
25
+ if (!Object.keys(tasks).length && state.init) {
26
+ cleanup(state);
27
+ process.exit(0);
28
+ }
29
+ state.init = true;
30
+ state.tasks = tasks;
31
+ // Process tasks
32
+ for (const [name, task] of Object.entries(tasks)) {
33
+ if (state.spawnedTasks.has(name))
34
+ continue;
35
+ state.spawnedTasks.add(name);
36
+ if (task.dependsOn.length) {
37
+ const allDepsExist = ensureDependencies(name, task.dependsOn, state);
38
+ if (!allDepsExist)
39
+ continue;
40
+ state.queue.push({
41
+ name,
42
+ task,
43
+ remainingDeps: task.dependsOn,
44
+ });
45
+ continue;
46
+ }
47
+ spawnTask(name, task, state, () => render(ui, state));
48
+ }
49
+ render(ui, state);
50
+ }
51
+ catch (e) {
52
+ const error = ensureError(e);
53
+ showError(error.message, ui, state);
54
+ }
55
+ }
56
+ // Key bindings
57
+ ui.screen.key(['up', 'k'], () => {
58
+ handleMove(-1);
59
+ });
60
+ ui.screen.key(['down', 'j'], () => {
61
+ handleMove(1);
62
+ });
63
+ ui.screen.key(['C-c', 'q'], () => {
64
+ cleanup(state);
65
+ process.exit(0);
66
+ });
67
+ // Handle resize
68
+ ui.screen.on('resize', () => {
69
+ render(ui, state);
70
+ });
71
+ export default ui;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import meow from 'meow';
3
+ import ui, { loadAndProcessConfig } from './app.js';
4
+ const cli = meow(`
5
+ Usage
6
+ $ tasktui [--config <PATH> | --help]
7
+
8
+ Options
9
+ --config Path to config file (default: ./tasktui.config.json)
10
+ `, {
11
+ importMeta: import.meta,
12
+ flags: {
13
+ config: {
14
+ type: 'string',
15
+ },
16
+ help: {},
17
+ },
18
+ });
19
+ loadAndProcessConfig(cli.flags.config);
20
+ ui.screen.render();
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { Task } from '../lib/types.js';
3
+ export default function TaskWindow({ tasks, selected, }: {
4
+ tasks: Record<string, Task>;
5
+ selected: string;
6
+ }): React.JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { Box, Text } from 'ink';
2
+ import childProcess from 'node:child_process';
3
+ import React, { useEffect, useState } from 'react';
4
+ import stripAnsi from 'strip-ansi';
5
+ export default function TaskWindow({ tasks, selected, }) {
6
+ const [buffers, setBuffers] = useState({});
7
+ useEffect(() => {
8
+ for (const [name, task] of Object.entries(tasks)) {
9
+ const subProcess = childProcess.spawn('sh', ['-c', task.command]);
10
+ subProcess.stdout.on('data', (newOutput) => {
11
+ const text = stripAnsi(newOutput.toString('utf8')).trim();
12
+ setBuffers(prev => {
13
+ return { ...prev, [name]: text };
14
+ });
15
+ });
16
+ }
17
+ }, [tasks]);
18
+ return (React.createElement(Box, { flexDirection: "column" },
19
+ React.createElement(Text, { dimColor: true }, selected),
20
+ React.createElement(Text, null, buffers[selected] ?? '')));
21
+ }
@@ -0,0 +1,2 @@
1
+ declare const CONFIG_PATH = "./tasktui.config.json";
2
+ export { CONFIG_PATH };
@@ -0,0 +1,2 @@
1
+ const CONFIG_PATH = './tasktui.config.json';
2
+ export { CONFIG_PATH };
@@ -0,0 +1,2 @@
1
+ declare const CONFIG_PATH = "./task-tui.config.json";
2
+ export { CONFIG_PATH };
@@ -0,0 +1,2 @@
1
+ const CONFIG_PATH = './task-tui.config.json';
2
+ export { CONFIG_PATH };
@@ -0,0 +1,15 @@
1
+ import z from 'zod';
2
+ export declare const TaskSchema: z.ZodObject<{
3
+ command: z.ZodString;
4
+ dependsOn: z.ZodDefault<z.ZodArray<z.ZodString>>;
5
+ cwd: z.ZodDefault<z.ZodString>;
6
+ }, z.z.core.$strip>;
7
+ export type Task = z.infer<typeof TaskSchema>;
8
+ export declare const TasksConfigSchema: z.ZodObject<{
9
+ tasks: z.ZodRecord<z.ZodString, z.ZodObject<{
10
+ command: z.ZodString;
11
+ dependsOn: z.ZodDefault<z.ZodArray<z.ZodString>>;
12
+ cwd: z.ZodDefault<z.ZodString>;
13
+ }, z.z.core.$strip>>;
14
+ }, z.z.core.$strip>;
15
+ export type TasksConfig = z.infer<typeof TasksConfigSchema>;
@@ -0,0 +1,9 @@
1
+ import z from 'zod';
2
+ export const TaskSchema = z.object({
3
+ command: z.string(),
4
+ dependsOn: z.string().array().default([]),
5
+ cwd: z.string().default(process.cwd()),
6
+ });
7
+ export const TasksConfigSchema = z.object({
8
+ tasks: z.record(z.string(), TaskSchema),
9
+ });
@@ -0,0 +1,5 @@
1
+ import z from 'zod';
2
+ import { TasksConfig } from './types.js';
3
+ export default function formatZodError(error: z.ZodError): string;
4
+ export declare function loadConfig(configPath?: string): TasksConfig;
5
+ export declare function ensureError(error: unknown): Error;
@@ -0,0 +1,44 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import z from 'zod';
4
+ import { CONFIG_PATH } from './constants.js';
5
+ import { TasksConfigSchema } from './types.js';
6
+ function getConfigPath(cliOption) {
7
+ if (cliOption)
8
+ return cliOption;
9
+ return CONFIG_PATH;
10
+ }
11
+ export default function formatZodError(error) {
12
+ if (error.issues.length === 0) {
13
+ return 'Unknown Zod error';
14
+ }
15
+ const issue = error.issues[0];
16
+ if (!issue)
17
+ return error.message;
18
+ const path = issue.path.join('.');
19
+ return `${issue.message} at '${path}' [code: ${issue.code}]`;
20
+ }
21
+ export function loadConfig(configPath) {
22
+ try {
23
+ const relativePath = path.join(process.cwd(), getConfigPath(configPath));
24
+ const raw = fs.readFileSync(relativePath, 'utf-8');
25
+ const parsed = JSON.parse(raw);
26
+ return z.parse(TasksConfigSchema, parsed);
27
+ }
28
+ catch (e) {
29
+ const error = ensureError(e);
30
+ throw error;
31
+ }
32
+ }
33
+ export function ensureError(error) {
34
+ if (error instanceof z.ZodError)
35
+ return new Error(formatZodError(error));
36
+ if (error instanceof Error)
37
+ return error;
38
+ let stringified = '[Unable to stringify the thrown value]';
39
+ try {
40
+ stringified = JSON.stringify(error);
41
+ }
42
+ catch { }
43
+ return new Error(stringified);
44
+ }
@@ -0,0 +1,4 @@
1
+ import { AppState } from './state.js';
2
+ import { UIComponents } from './ui.js';
3
+ export declare function showError(message: string, ui: UIComponents, state: AppState): void;
4
+ export declare function render(ui: UIComponents, state: AppState): void;
@@ -0,0 +1,58 @@
1
+ import { getOrderedTasks } from './state.js';
2
+ export function showError(message, ui, state) {
3
+ state.error = message;
4
+ ui.errorBox.setContent(`{red-fg}Error: ${message}{/}`);
5
+ ui.errorBox.show();
6
+ ui.screen.render();
7
+ }
8
+ export function render(ui, state) {
9
+ const { running, queued, completed } = getOrderedTasks(state);
10
+ const lines = [];
11
+ // Running section
12
+ if (running.length > 0) {
13
+ lines.push(`{gray-fg} Running (${running.length}){/}`);
14
+ for (const name of running) {
15
+ const isSelected = name === state.selectedTask;
16
+ const color = isSelected ? 'yellow-fg' : 'white-fg';
17
+ lines.push(`{${color}}${name}{/}`);
18
+ }
19
+ lines.push('');
20
+ }
21
+ // Queued section
22
+ if (queued.length > 0) {
23
+ lines.push(`{gray-fg} Queued (${queued.length}){/}`);
24
+ for (const queueItem of queued) {
25
+ const isSelected = queueItem.name === state.selectedTask;
26
+ const color = isSelected ? 'yellow-fg' : 'gray-fg';
27
+ lines.push(`{${color}}${queueItem.name}{/}`);
28
+ }
29
+ lines.push('');
30
+ }
31
+ // Completed section
32
+ if (completed.length > 0) {
33
+ lines.push(`{gray-fg} Completed (${completed.length}){/}`);
34
+ for (const name of completed) {
35
+ const buffer = state.buffers[name];
36
+ const isSelected = name === state.selectedTask;
37
+ let statusSymbol = '✓';
38
+ let color = 'gray-fg';
39
+ if (buffer?.errored) {
40
+ statusSymbol = '✗';
41
+ color = 'red-fg';
42
+ }
43
+ if (isSelected) {
44
+ color = 'yellow-fg';
45
+ }
46
+ lines.push(`{${color}}${name}{/} ${statusSymbol}`);
47
+ }
48
+ }
49
+ ui.taskList.setContent(lines.join('\n'));
50
+ // Update output pane
51
+ if (state.selectedTask) {
52
+ ui.taskNameBox.setContent(`{gray-fg}${state.selectedTask}{/}`);
53
+ const output = state.buffers[state.selectedTask]?.text ?? '';
54
+ ui.taskOutputBox.setContent(output);
55
+ ui.taskOutputBox.setScrollPerc(100); // Auto-scroll to bottom
56
+ }
57
+ ui.screen.render();
58
+ }
@@ -0,0 +1,31 @@
1
+ import childProcess from 'node:child_process';
2
+ import { Task, TasksConfig } from './types.js';
3
+ export interface TaskBuffer {
4
+ running: boolean;
5
+ errored: boolean;
6
+ text: string;
7
+ }
8
+ export interface QueueItem {
9
+ name: string;
10
+ task: Task;
11
+ remainingDeps: string[];
12
+ }
13
+ export interface AppState {
14
+ init: boolean;
15
+ spawnedTasks: Set<string>;
16
+ childProcesses: Map<string, childProcess.ChildProcess>;
17
+ taskOrder: string[];
18
+ config?: TasksConfig;
19
+ tasks: Record<string, Task>;
20
+ error: string | null;
21
+ selectedTask: string;
22
+ buffers: Record<string, TaskBuffer>;
23
+ queue: QueueItem[];
24
+ }
25
+ export declare function createState(): AppState;
26
+ export declare function getOrderedTasks(state: AppState): {
27
+ running: string[];
28
+ queued: QueueItem[];
29
+ completed: string[];
30
+ };
31
+ export declare function getAllTasksInOrder(state: AppState): string[];
package/dist/state.js ADDED
@@ -0,0 +1,24 @@
1
+ export function createState() {
2
+ return {
3
+ init: false,
4
+ spawnedTasks: new Set(),
5
+ childProcesses: new Map(),
6
+ taskOrder: [],
7
+ config: undefined,
8
+ tasks: {},
9
+ error: null,
10
+ selectedTask: '',
11
+ buffers: {},
12
+ queue: [],
13
+ };
14
+ }
15
+ export function getOrderedTasks(state) {
16
+ const running = state.taskOrder.filter((name) => state.buffers[name]?.running);
17
+ const queued = state.queue;
18
+ const completed = state.taskOrder.filter((name) => state.buffers[name] && !state.buffers[name].running);
19
+ return { running, queued, completed };
20
+ }
21
+ export function getAllTasksInOrder(state) {
22
+ const { running, queued, completed } = getOrderedTasks(state);
23
+ return [...running, ...queued.map((q) => q.name), ...completed];
24
+ }
@@ -0,0 +1,5 @@
1
+ import { AppState } from './state.js';
2
+ import { Task } from './types.js';
3
+ export declare function ensureDependencies(task: string, deps: string[], state: AppState): boolean;
4
+ export declare function spawnTask(name: string, task: Task, state: AppState, onUpdate: () => void): void;
5
+ export declare function cleanup(state: AppState): void;
package/dist/tasks.js ADDED
@@ -0,0 +1,92 @@
1
+ import childProcess from 'node:child_process';
2
+ import ui from './app.js';
3
+ import { showError } from './renderer.js';
4
+ import { ensureError } from './utils.js';
5
+ export function ensureDependencies(task, deps, state) {
6
+ let allDepsExist = true;
7
+ for (const dep of deps) {
8
+ if (!Object.keys(state.tasks).includes(dep)) {
9
+ state.buffers[task] = {
10
+ running: false,
11
+ text: `Cannot depend on ${dep} as it does not exist`,
12
+ errored: true,
13
+ };
14
+ state.taskOrder.push(task);
15
+ allDepsExist = false;
16
+ break;
17
+ }
18
+ }
19
+ return allDepsExist;
20
+ }
21
+ export function spawnTask(name, task, state, onUpdate) {
22
+ console.log(task.command);
23
+ const subProcess = childProcess.spawn('sh', ['-c', task.command], {
24
+ cwd: task.cwd,
25
+ env: {
26
+ ...process.env,
27
+ FORCE_COLOR: '1',
28
+ CLICOLOR_FORCE: '1',
29
+ },
30
+ });
31
+ state.childProcesses.set(name, subProcess);
32
+ subProcess.on('spawn', () => {
33
+ if (Object.keys(state.buffers).length === 0) {
34
+ state.selectedTask = name;
35
+ }
36
+ state.buffers[name] = { running: true, text: '', errored: false };
37
+ state.taskOrder.push(name);
38
+ onUpdate();
39
+ });
40
+ const handleOutput = (newOutput) => {
41
+ const text = newOutput.toString('utf8');
42
+ const currentText = state.buffers[name]?.text ?? '';
43
+ state.buffers[name] = {
44
+ running: true,
45
+ text: currentText + text,
46
+ errored: false,
47
+ };
48
+ if (state.selectedTask === name) {
49
+ onUpdate();
50
+ }
51
+ };
52
+ subProcess.stdout.on('data', handleOutput);
53
+ subProcess.stderr.on('data', handleOutput);
54
+ subProcess.on('close', (code) => {
55
+ const currentText = state.buffers[name]?.text ?? '';
56
+ state.buffers[name] = {
57
+ running: false,
58
+ text: currentText,
59
+ errored: code !== null && code !== 0,
60
+ };
61
+ state.childProcesses.delete(name);
62
+ // Check queue for dependent tasks
63
+ checkQueue(state, onUpdate);
64
+ onUpdate();
65
+ });
66
+ subProcess.on('error', (e) => {
67
+ const error = ensureError(e);
68
+ showError(error.message, ui, state);
69
+ });
70
+ }
71
+ function checkQueue(state, onUpdate) {
72
+ const next = [];
73
+ for (const queueItem of state.queue) {
74
+ const remaining = queueItem.remainingDeps.filter((dep) => {
75
+ const buffer = state.buffers[dep];
76
+ return buffer?.running || buffer?.errored || !buffer;
77
+ });
78
+ if (remaining.length === 0) {
79
+ spawnTask(queueItem.name, queueItem.task, state, onUpdate);
80
+ continue;
81
+ }
82
+ next.push({ ...queueItem, remainingDeps: remaining });
83
+ }
84
+ state.queue = next;
85
+ }
86
+ export function cleanup(state) {
87
+ for (const [_, proc] of state.childProcesses.entries()) {
88
+ if (!proc.killed) {
89
+ proc.kill('SIGTERM');
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,15 @@
1
+ import z from 'zod';
2
+ export declare const TaskSchema: z.ZodObject<{
3
+ command: z.ZodString;
4
+ dependsOn: z.ZodDefault<z.ZodArray<z.ZodString>>;
5
+ cwd: z.ZodDefault<z.ZodString>;
6
+ }, z.z.core.$strip>;
7
+ export type Task = z.infer<typeof TaskSchema>;
8
+ export declare const TasksConfigSchema: z.ZodObject<{
9
+ tasks: z.ZodRecord<z.ZodString, z.ZodObject<{
10
+ command: z.ZodString;
11
+ dependsOn: z.ZodDefault<z.ZodArray<z.ZodString>>;
12
+ cwd: z.ZodDefault<z.ZodString>;
13
+ }, z.z.core.$strip>>;
14
+ }, z.z.core.$strip>;
15
+ export type TasksConfig = z.infer<typeof TasksConfigSchema>;
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ import z from 'zod';
2
+ export const TaskSchema = z.object({
3
+ command: z.string(),
4
+ dependsOn: z.string().array().default([]),
5
+ cwd: z.string().default(process.cwd()),
6
+ });
7
+ export const TasksConfigSchema = z.object({
8
+ tasks: z.record(z.string(), TaskSchema),
9
+ });
package/dist/ui.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import blessed from 'blessed';
2
+ export interface UIComponents {
3
+ screen: blessed.Widgets.Screen;
4
+ sidebar: blessed.Widgets.BoxElement;
5
+ taskList: blessed.Widgets.BoxElement;
6
+ queueContainer: blessed.Widgets.BoxElement;
7
+ taskNameBox: blessed.Widgets.BoxElement;
8
+ taskOutputBox: blessed.Widgets.Log;
9
+ errorBox: blessed.Widgets.BoxElement;
10
+ }
11
+ export declare function createUI(): UIComponents;
package/dist/ui.js ADDED
@@ -0,0 +1,121 @@
1
+ import blessed from 'blessed';
2
+ export function createUI() {
3
+ // Create screen
4
+ const screen = blessed.screen({
5
+ smartCSR: true,
6
+ title: 'Task Runner',
7
+ fullUnicode: true,
8
+ });
9
+ // Create sidebar container
10
+ const sidebar = blessed.box({
11
+ parent: screen,
12
+ left: 0,
13
+ top: 0,
14
+ width: 25,
15
+ height: '100%',
16
+ });
17
+ // Add a vertical line separator
18
+ blessed.line({
19
+ parent: screen,
20
+ orientation: 'vertical',
21
+ left: 24,
22
+ top: 0,
23
+ height: '100%',
24
+ style: {
25
+ fg: 'white',
26
+ },
27
+ });
28
+ // Tasks list
29
+ const taskList = blessed.box({
30
+ parent: sidebar,
31
+ width: '100%',
32
+ height: 'shrink',
33
+ tags: true,
34
+ });
35
+ // Queue section
36
+ const queueContainer = blessed.box({
37
+ parent: sidebar,
38
+ top: 'center',
39
+ width: '100%',
40
+ height: 'shrink',
41
+ tags: true,
42
+ });
43
+ // Help text at bottom
44
+ blessed.box({
45
+ parent: sidebar,
46
+ bottom: 0,
47
+ width: '100%',
48
+ height: 1,
49
+ content: '{gray-fg}↑↓ - Navigate{/}',
50
+ tags: true,
51
+ });
52
+ // Output pane container
53
+ const outputPane = blessed.box({
54
+ parent: screen,
55
+ left: 25,
56
+ top: 0,
57
+ width: '100%-25',
58
+ height: '100%',
59
+ });
60
+ // Task name header
61
+ const taskNameBox = blessed.box({
62
+ parent: outputPane,
63
+ top: 0,
64
+ width: '100%',
65
+ height: 1,
66
+ content: '',
67
+ tags: true,
68
+ style: {
69
+ fg: 'gray',
70
+ },
71
+ });
72
+ // Task output (scrollable log)
73
+ const taskOutputBox = blessed.log({
74
+ parent: outputPane,
75
+ top: 1,
76
+ width: '100%',
77
+ height: '100%-1',
78
+ scrollable: true,
79
+ alwaysScroll: true,
80
+ scrollbar: {
81
+ ch: '█',
82
+ style: {
83
+ fg: 'white',
84
+ },
85
+ },
86
+ keys: true,
87
+ vi: true,
88
+ mouse: true,
89
+ tags: true,
90
+ });
91
+ // Error display
92
+ const errorBox = blessed.box({
93
+ parent: screen,
94
+ top: 'center',
95
+ left: 'center',
96
+ width: '80%',
97
+ height: 'shrink',
98
+ border: {
99
+ type: 'line',
100
+ },
101
+ style: {
102
+ fg: 'red',
103
+ border: {
104
+ fg: 'red',
105
+ },
106
+ },
107
+ tags: true,
108
+ hidden: true,
109
+ });
110
+ // Focus on output box for scrolling
111
+ taskOutputBox.focus();
112
+ return {
113
+ screen,
114
+ sidebar,
115
+ taskList,
116
+ queueContainer,
117
+ taskNameBox,
118
+ taskOutputBox,
119
+ errorBox,
120
+ };
121
+ }
@@ -0,0 +1,5 @@
1
+ import z from 'zod';
2
+ import { TasksConfig } from './types.js';
3
+ export default function formatZodError(error: z.ZodError): string;
4
+ export declare function loadConfig(configPath?: string): TasksConfig;
5
+ export declare function ensureError(error: unknown): Error;
package/dist/utils.js ADDED
@@ -0,0 +1,44 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import z from 'zod';
4
+ import { CONFIG_PATH } from './constants.js';
5
+ import { TasksConfigSchema } from './types.js';
6
+ function getConfigPath(cliOption) {
7
+ if (cliOption)
8
+ return cliOption;
9
+ return CONFIG_PATH;
10
+ }
11
+ export default function formatZodError(error) {
12
+ if (error.issues.length === 0) {
13
+ return 'Unknown Zod error';
14
+ }
15
+ const issue = error.issues[0];
16
+ if (!issue)
17
+ return error.message;
18
+ const path = issue.path.join('.');
19
+ return `${issue.message} at '${path}' [code: ${issue.code}]`;
20
+ }
21
+ export function loadConfig(configPath) {
22
+ try {
23
+ const relativePath = path.join(process.cwd(), getConfigPath(configPath));
24
+ const raw = fs.readFileSync(relativePath, 'utf-8');
25
+ const parsed = JSON.parse(raw);
26
+ return z.parse(TasksConfigSchema, parsed);
27
+ }
28
+ catch (e) {
29
+ const error = ensureError(e);
30
+ throw error;
31
+ }
32
+ }
33
+ export function ensureError(error) {
34
+ if (error instanceof z.ZodError)
35
+ return new Error(formatZodError(error));
36
+ if (error instanceof Error)
37
+ return error;
38
+ let stringified = '[Unable to stringify the thrown value]';
39
+ try {
40
+ stringified = JSON.stringify(error);
41
+ }
42
+ catch { }
43
+ return new Error(stringified);
44
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "tasktui",
3
+ "description": "Run tasks in a multiplexed terminal with dependency management.",
4
+ "version": "1.0.0",
5
+ "license": "MIT",
6
+ "bin": "dist/cli.js",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=16"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "blessed": "^0.1.81",
20
+ "meow": "^11.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@sindresorhus/tsconfig": "^3.0.1",
24
+ "@types/blessed": "^0.1.25",
25
+ "@vdemedes/prettier-config": "^2.0.1",
26
+ "prettier": "^2.8.7",
27
+ "ts-node": "^10.9.1",
28
+ "typescript": "^5.0.3",
29
+ "zod": "^4.1.12"
30
+ }
31
+ }