{"id":13455756,"url":"https://github.com/privatenumber/tasuku","last_synced_at":"2026-03-04T07:10:41.430Z","repository":{"id":40558829,"uuid":"373819704","full_name":"privatenumber/tasuku","owner":"privatenumber","description":"✅ タスク — The minimal task visualizer for Node.js","archived":false,"fork":false,"pushed_at":"2026-02-27T20:59:31.000Z","size":2737,"stargazers_count":2179,"open_issues_count":18,"forks_count":34,"subscribers_count":9,"default_branch":"master","last_synced_at":"2026-02-28T00:51:09.278Z","etag":null,"topics":["cli","list","runner","task"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/privatenumber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"privatenumber"}},"created_at":"2021-06-04T11:29:16.000Z","updated_at":"2026-02-25T03:09:59.000Z","dependencies_parsed_at":"2025-02-28T00:00:29.185Z","dependency_job_id":"53f060d7-550e-42f8-bf52-b94080419aaa","html_url":"https://github.com/privatenumber/tasuku","commit_stats":{"total_commits":58,"total_committers":2,"mean_commits":29.0,"dds":0.2068965517241379,"last_synced_commit":"d833be1da764c45c8ce8ab63f742b2ae6b72c97c"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/privatenumber/tasuku","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Ftasuku","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Ftasuku/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Ftasuku/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Ftasuku/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/privatenumber","download_url":"https://codeload.github.com/privatenumber/tasuku/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Ftasuku/sbom","scorecard":{"id":745547,"data":{"date":"2025-08-11","repo":{"name":"github.com/privatenumber/tasuku","commit":"80e24570106071968e26c5d44b78800d8d2eeafd"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/25 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/package-size-report.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/package-size-report.yml/develop?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/package-size-report.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/package-size-report.yml/develop?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/release.yml/develop?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/release.yml/develop?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/release.yml/develop?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/test.yml/develop?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/test.yml/develop?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/privatenumber/tasuku/test.yml/develop?enable=pin","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   3 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/package-size-report.yml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'develop'","Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-xffm-g5w8-qvg7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-67mh-4wv8-2f99","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T18:35:57.032Z","repository_id":40558829,"created_at":"2025-08-22T18:35:57.032Z","updated_at":"2025-08-22T18:35:57.032Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30075425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T05:31:57.858Z","status":"ssl_error","status_checked_at":"2026-03-04T05:31:38.462Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","list","runner","task"],"created_at":"2024-07-31T08:01:10.623Z","updated_at":"2026-03-04T07:10:41.380Z","avatar_url":"https://github.com/privatenumber.png","language":"TypeScript","funding_links":["https://github.com/sponsors/privatenumber","https://github.com/sponsors/privatenumber)!"],"categories":["TypeScript","cli","Packages"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\".github/tasuku.svg\"\u003e\n    \u003cbr\u003e\n    \u003ci\u003eThe minimal task runner for Node.js\u003c/i\u003e\n\u003c/p\u003e\n\n### Features\n- Task list with dynamic states\n- Parallel \u0026 nestable tasks\n- Unopinionated\n- Type-safe\n\n→ [Try it out online](https://stackblitz.com/edit/tasuku-demo?file=index.js\u0026devtoolsheight=50\u0026view=editor)\n\n\u003csub\u003eFound this package useful? Show your support \u0026 appreciation by [sponsoring](https://github.com/sponsors/privatenumber)! ❤️\u003c/sub\u003e\n\n## Install\n```sh\nnpm i tasuku\n```\n\n## About\nタスク (Tasuku) is a minimal task runner for Node.js. You can use it to label any task/function so that its loading, success, and error states are rendered in the terminal.\n\nFor example, here's a simple script that copies a file from path A to B.\n\n```ts\nimport { copyFile } from 'node:fs/promises'\nimport task from 'tasuku'\n\ntask('Copying file from path A to B', async ({ setTitle }) =\u003e {\n    await copyFile('/path/A', '/path/B')\n\n    setTitle('Successfully copied file from path A to B!')\n})\n```\n\nRunning the script will look like this in the terminal:\n\n\u003cimg src=\".github/media/basic.gif\"\u003e\n\n## Usage\n### Task list\nCall `task(taskTitle, taskFunction)` to start a task and display it in a task list in the terminal.\n\n```ts\nimport task from 'tasuku'\n\ntask('Task 1', async () =\u003e {\n    await someAsyncTask()\n})\n\ntask('Task 2', async () =\u003e {\n    await someAsyncTask()\n})\n\ntask('Task 3', async () =\u003e {\n    await someAsyncTask()\n})\n```\n\n\u003cimg src=\".github/media/task-list.gif\"\u003e\n\n#### Task states\n- **◽️ Pending** The task is queued and has not started\n- **🔅 Loading** The task is running\n- **⚠️ Warning** The task completed with a warning\n- **❌ Error** The task exited with an error\n- **✅ Success** The task completed without error\n\n\u003cimg src=\".github/media/task-states.png\"\u003e\n\n### Unopinionated\nYou can call `task()` from anywhere. There are no requirements. It is designed to be as unopinionated as possible not to interfere with your code.\n\nThe tasks will be displayed in the terminal in a consolidated list.\n\nYou can change the title of the task by calling `setTitle()`.\n```ts\nimport task from 'tasuku'\n\ntask('Task 1', async () =\u003e {\n    await someAsyncTask()\n})\n\n// ...\n\nsomeOtherCode()\n\n// ...\n\ntask('Task 2', async ({ setTitle }) =\u003e {\n    await someAsyncTask()\n\n    setTitle('Task 2 complete')\n})\n```\n\n\u003cimg src=\".github/media/set-title.gif\"\u003e\n\n### Task return values\nThe return value of a task will be stored in the output `.result` property.\n\nIf using TypeScript, the type of `.result` will be inferred from the task function.\n\n```ts\nconst myTask = await task('Task 2', async () =\u003e {\n    await someAsyncTask()\n\n    return 'Success'\n})\n\nconsole.log(myTask.result) // 'Success'\n```\n\n### Nesting tasks\nTasks can be nested indefinitely. Nested tasks will be stacked hierarchically in the task list.\n```ts\nawait task('Do task', async ({ task }) =\u003e {\n    await someAsyncTask()\n\n    await task('Do another task', async ({ task }) =\u003e {\n        await someAsyncTask()\n\n        await task('And another', async () =\u003e {\n            await someAsyncTask()\n        })\n    })\n})\n```\n\n\u003cimg src=\".github/media/nested.gif\"\u003e\n\n### Collapsing nested tasks\nCall `.clear()` on the returned task API to collapse the nested task.\n```ts\nawait task('Do task', async ({ task }) =\u003e {\n    await someAsyncTask()\n\n    const nestedTask = await task('Do another task', async ({ task }) =\u003e {\n        await someAsyncTask()\n    })\n\n    nestedTask.clear()\n})\n```\n\n\u003cimg src=\".github/media/collapse.gif\"\u003e\n\n### Grouped tasks\nTasks can be grouped with `task.group()`. Pass in a function that returns an array of tasks to run them sequentially.\n\nThis is useful for displaying a queue of tasks that have yet to run.\n\n```ts\nconst groupedTasks = await task.group(task =\u003e [\n    task('Task 1', async () =\u003e {\n        await someAsyncTask()\n\n        return 'one'\n    }),\n\n    task('Waiting for Task 1', async ({ setTitle }) =\u003e {\n        setTitle('Task 2 running...')\n\n        await someAsyncTask()\n\n        setTitle('Task 2 complete')\n\n        return 'two'\n    })\n\n    // ...\n])\n\nconsole.log(groupedTasks) // [{ result: 'one' }, { result: 'two' }]\n```\n\n\u003cimg src=\".github/media/grouped.gif\"\u003e\n\n### Running tasks in parallel\nYou can run tasks in parallel by passing in `{ concurrency: n }` as the second argument in `task.group()`.\n\n```ts\nconst api = await task.group(task =\u003e [\n    task(\n        'Task 1',\n        async () =\u003e await someAsyncTask()\n    ),\n\n    task(\n        'Task 2',\n        async () =\u003e await someAsyncTask()\n    )\n\n    // ...\n], {\n    concurrency: 2 // Number of tasks to run at a time\n})\n\napi.clear() // Clear output\n```\n\n\u003cimg src=\".github/media/parallel.gif\"\u003e\n\nAlternatively, you can also use the native `Promise.all()` if you prefer. The advantage of using `task.group()` is that you can limit concurrency, displays queued tasks as pending, and it returns an API to easily clear the results.\n\n```ts\n// No API\nawait Promise.all([\n    task(\n        'Task 1',\n        async () =\u003e await someAsyncTask()\n    ),\n\n    task(\n        'Task 2',\n        async () =\u003e await someAsyncTask()\n    )\n\n    // ...\n])\n```\n\n## API\n\n### task(taskTitle, taskFunction, options?)\n\nReturns a Promise that resolves with object:\n```ts\ntype TaskAPI = {\n    // Result from taskFunction\n    result: unknown\n\n    // State of the task\n    state: 'error' | 'warning' | 'success'\n\n    // Invoke to clear the results from the terminal\n    clear: () =\u003e void\n}\n```\n\n#### taskTitle\nType: `string`\n\nRequired: true\n\nThe name of the task displayed.\n\n#### taskFunction\nType:\n```ts\ntype TaskFunction = (taskInnerApi: {\n    task: createTask\n    setTitle(title: string): void\n    setStatus(status?: string): void\n    setOutput(output: string | { message: string }): void\n    setWarning(warning: Error | string): void\n    setError(error: Error | string): void\n    startTime(): void\n    stopTime(): number\n}) =\u003e Promise\u003cunknown\u003e\n```\n\nRequired: true\n\nThe task function. The return value will be stored in the `.result` property of the `task()` output object.\n\n\n#### task\nA task function to use for nesting.\n\n#### setTitle()\nCall with a string to change the task title.\n\n#### setStatus()\nCall with a string to set the status of the task.\n\n#### setOutput()\nCall with a string to set the output of the task.\n\n\u003cimg src=\".github/media/task-output.png\"\u003e\n\n#### setWarning()\nCall with a string or Error instance to put the task in a warning state.\n\n#### setError()\nCall with a string or Error instance to put the task in an error state. Tasks automatically go into an error state when it catches an error in the task.\n\n\u003cimg src=\".github/media/set-error.png\"\u003e\n\n#### startTime()\nStart or restart the elapsed time counter. Calling again resets to 0. Time is displayed after the status: `⠋ Task [status] (3s)`\n\n#### stopTime()\nStop the elapsed time counter and return the elapsed milliseconds. The displayed time freezes at the stopped value. Useful for profiling task phases.\n\n```ts\nawait task('Multi-phase', async ({ startTime, stopTime, setStatus }) =\u003e {\n    startTime()\n    await phase1()\n    const phase1Time = stopTime()\n\n    setStatus('phase 2')\n    startTime()\n    await phase2()\n    const phase2Time = stopTime()\n\n    console.log(`Phase 1: ${phase1Time}ms, Phase 2: ${phase2Time}ms`)\n})\n```\n\n#### options\nType: `{ showTime?: boolean }`\n\nOptional task options.\n\n##### showTime\nWhen `true`, automatically starts the elapsed time counter when the task begins. Equivalent to calling `startTime()` at the start of the task function.\n\n```ts\nawait task('Building', async () =\u003e {\n    await build()\n}, { showTime: true })\n// Output: ✔ Building (3s)\n```\n\n\u003cimg src=\".github/media/elapsed-time.gif\"\u003e\n\nTime display:\n- Format: `(Xs)` for under a minute, `(Xm Ys)` for under an hour, `(Xh Ym)` for longer\n- Not shown if elapsed \u003c 1 second\n- Freezes at final value when task completes\n\n\n### task.group(createTaskFunctions, options)\nReturns a Promise that resolves with object:\n```ts\n// The results from the taskFunctions\ntype TaskGroupAPI = {\n    // Result from taskFunction\n    result: unknown\n\n    // State of the task\n    state: 'error' | 'warning' | 'success'\n\n    // Invoke to clear the task result\n    clear: () =\u003e void\n}[] \u0026 {\n\n    // Invoke to clear ALL results\n    clear: () =\u003e void\n}\n```\n\n#### createTaskFunctions\nType: `(task) =\u003e Task[]`\n\nRequired: true\n\nA function that returns all the tasks you want to group in an array.\n\n#### options\n\nDirectly passed into [`p-map`](https://github.com/sindresorhus/p-map).\n\n##### concurrency\nType: `number` (Integer)\n\nDefault: `1`\n\nNumber of tasks to run at a time.\n\n##### stopOnError\nType: `boolean`\n\nDefault: `true`\n\nWhen set to `false`, instead of stopping when a task fails, it will wait for all the tasks to finish and then reject with an aggregated error containing all the errors from the rejected promises.\n\n##### maxVisible\n\n\u003cp align=\"center\"\u003e\u003cimg src=\".github/media/max-visible.gif\" width=\"600\"\u003e\u003c/p\u003e\n\nType: `number | ((terminalHeight: number) =\u003e number)`\n\nDefault: Responsive to terminal height (rows - 2, minimum 5)\n\nMaximum number of lines to display in the task list. When there are more task lines than this limit, remaining tasks are hidden with a state breakdown (e.g., \"(+ 3 loading, 5 queued, 4 completed)\"). Active tasks are always prioritized over pending and completed ones. This accounts for nested subtasks which add extra lines.\n\nCan be a fixed number or a function called on each render for responsive limits.\n\nBy default, the limit is automatically lifted when all tasks complete and `.clear()` is called, revealing the full list.\n\n```ts\n// Fixed limit\nawait task.group(task =\u003e [...tasks], {\n    concurrency: 5,\n    maxVisible: 10\n})\n\n// Responsive limit (terminal height passed as parameter)\nawait task.group(task =\u003e [...tasks], {\n    concurrency: 5,\n    maxVisible: height =\u003e height - 5\n})\n```\n\n## FAQ\n\n### What does \"Tasuku\" mean?\n_Tasuku_  or タスク is the phonetic Japanese pronounciation of the word \"task\".\n\n\n### Why did you make this?\n\nI built _Tasuku_ as a lightweight task runner for scripts and CLI tools. It's designed to show task progress clearly without forcing a rigid structure on how you write your code.\n\nBig thanks to [listr](https://github.com/SamVerschueren/listr) and [listr2](https://github.com/cenk1cenk2/listr2), which inspired both the visuals and the idea—I've relied on them for years. But over time, I found their declarative approach too restrictive for my workflow, so I created something simpler and more flexible.\n\n_Tasuku_ uses its own minimal ANSI-based renderer for terminal output, giving you smooth `console.log()` integration with zero runtime dependencies. The rendering model was originally inspired by [ink](https://github.com/vadimdemedes/ink)'s approach to terminal UIs.\n\n### Doesn't the usage of nested `task` functions violate ESLint's [no-shadow](https://eslint.org/docs/rules/no-shadow)?\nYes, but it should be fine as you don't need access to other `task` functions aside from the immediate one.\n\nPut `task` in the allow list:\n- `\"no-shadow\": [\"error\", { \"allow\": [\"task\"] }]`\n- `\"@typescript-eslint/no-shadow\": [\"error\", { \"allow\": [\"task\"] }]`\n\n\n## Sponsors\n\u003cp align=\"center\"\u003e\n\t\u003ca href=\"https://github.com/sponsors/privatenumber\"\u003e\n\t\t\u003cimg src=\"https://cdn.jsdelivr.net/gh/privatenumber/sponsors/sponsorkit/sponsors.svg\"\u003e\n\t\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprivatenumber%2Ftasuku","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprivatenumber%2Ftasuku","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprivatenumber%2Ftasuku/lists"}