@basaltkit/cli 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Machize Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # @basaltkit/cli
2
+
3
+ Terminal command framework (the `basalt` command) for Basalt applications: define your own commands, run them against the already-booted application, and inspect HTTP routes and scheduled tasks. You need it when you want to give your application its own "command line" — for example `basalt routes` or `basalt db:seed`.
4
+
5
+ ## What this module solves
6
+
7
+ A **CLI** (Command Line Interface) is a way to interact with a program by typing commands into a terminal, instead of clicking buttons. Almost every server application needs administrative tasks that don't make sense as web pages: listing registered routes, running migrations, seeding the database, and so on.
8
+
9
+ The problem is that these tasks usually need the application "alive": with the database connected, plugins registered, and configuration loaded. Writing a standalone script for each task forces you to repeat all that bootstrapping manually.
10
+
11
+ `@basaltkit/cli` solves this: you describe each command with `defineCommand`, register the commands with the `commandsPlugin` plugin, and `runCli` handles the rest — boots the application, parses the terminal arguments, runs the right command, and shuts everything down at the end. It also ships with two built-in commands (`routes` and `schedule:list`) and utilities for testing commands without printing anything to the screen.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @basaltkit/cli
17
+ ```
18
+
19
+ > Note: the package depends on `@basaltkit/core` (the heart of the framework, where `createApp` and the dependency container live). If you created the project with `create-basalt --cli`, both are already installed.
20
+
21
+ ## Get started in 5 minutes
22
+
23
+ 1. Create a `bin/basalt.ts` file at the root of the project — this will be the entry point for the `basalt` command:
24
+
25
+ ```typescript
26
+ #!/usr/bin/env node
27
+ import { runCli } from '@basaltkit/cli'
28
+ import { buildApp } from '../src/app.js'
29
+
30
+ const app = buildApp({ logLevel: 'silent' })
31
+ process.exit(await runCli({ app }))
32
+ ```
33
+
34
+ 2. Define one of your own commands, for example in `src/commands/greet.ts`:
35
+
36
+ ```typescript
37
+ import { defineCommand } from '@basaltkit/cli'
38
+
39
+ export const greetCommand = defineCommand({
40
+ name: 'greet',
41
+ description: 'Greets someone',
42
+ handle({ args, io }) {
43
+ io.log(`Hello, ${args[0] ?? 'world'}!`)
44
+ },
45
+ })
46
+ ```
47
+
48
+ 3. Register the command in the application (in `src/app.ts`), inside the plugin list:
49
+
50
+ ```typescript
51
+ import { createApp } from '@basaltkit/core'
52
+ import { commandsPlugin } from '@basaltkit/cli'
53
+ import { greetCommand } from './commands/greet.js'
54
+
55
+ export function buildApp() {
56
+ return createApp({
57
+ plugins: [commandsPlugin([greetCommand])],
58
+ })
59
+ }
60
+ ```
61
+
62
+ 4. Add a shortcut in `package.json`:
63
+
64
+ ```json
65
+ {
66
+ "scripts": {
67
+ "basalt": "tsx bin/basalt.ts"
68
+ }
69
+ }
70
+ ```
71
+
72
+ 5. Run it in the terminal:
73
+
74
+ ```bash
75
+ pnpm basalt list # lists all available commands
76
+ pnpm basalt greet Maria # prints: Hello, Maria!
77
+ ```
78
+
79
+ ## Usage guide
80
+
81
+ ### Built-in commands
82
+
83
+ Without registering anything, `runCli` always provides:
84
+
85
+ | Command | What it does |
86
+ | --- | --- |
87
+ | `basalt list` (or `basalt` with no arguments) | Lists all available commands, with their descriptions |
88
+ | `basalt routes` | Lists the HTTP routes registered by the application (read from the `http:routes` metadata bucket, populated by HTTP adapters such as `@basaltkit/fastify`) |
89
+ | `basalt schedule:list` | Lists scheduled tasks and their cron expressions (read from the `schedule:entries` bucket, populated by `@basaltkit/scheduler`) |
90
+
91
+ If you also install `@basaltkit/generator`, you gain the `make:*` commands (see the "How it connects to other modules" section).
92
+
93
+ ### Defining a command with arguments and flags
94
+
95
+ **Positional arguments** are the loose words after the command name; **flags** are options in the format `--name` or `--name=value`. The parser is simple and predictable:
96
+
97
+ - `--fresh` → `flags.fresh === true` (boolean)
98
+ - `--step=2` → `flags.step === '2'` (string — convert it to a number yourself if you need to)
99
+
100
+ ```typescript
101
+ import { defineCommand } from '@basaltkit/cli'
102
+
103
+ export const migrateCommand = defineCommand({
104
+ name: 'tenant:migrate',
105
+ description: 'Migrates a tenant database',
106
+ async handle({ args, flags, container, io }) {
107
+ const tenantId = args[0]
108
+ if (!tenantId) {
109
+ io.error('Usage: basalt tenant:migrate <tenantId> [--fresh] [--step=N]')
110
+ return 1 // exit code ≠ 0 signals an error to the terminal
111
+ }
112
+ const fresh = flags['fresh'] === true
113
+ const step = typeof flags['step'] === 'string' ? Number(flags['step']) : undefined
114
+
115
+ io.log(`Migrating ${tenantId} (fresh=${fresh}, step=${step ?? 'all'})…`)
116
+ // use container.get(TOKEN) to get application services
117
+ return 0
118
+ },
119
+ })
120
+ ```
121
+
122
+ Naming convention: `noun:verb`, for example `tenant:migrate`, `db:seed`.
123
+
124
+ ### Testing commands without printing to the screen
125
+
126
+ `memoryIo()` captures everything the command would print, so you can assert on it in tests (a real example from the package's test suite):
127
+
128
+ ```typescript
129
+ import { describe, expect, it } from 'vitest'
130
+ import { createApp } from '@basaltkit/core'
131
+ import { commandsPlugin, defineCommand, memoryIo, runCli } from '@basaltkit/cli'
132
+
133
+ it('runs the greet command', async () => {
134
+ const io = memoryIo()
135
+ const app = createApp({
136
+ plugins: [
137
+ commandsPlugin([
138
+ defineCommand({
139
+ name: 'greet',
140
+ handle: ({ args, io }) => io.log(`Hello, ${args[0]}!`),
141
+ }),
142
+ ]),
143
+ ],
144
+ })
145
+
146
+ const code = await runCli({ app, argv: ['greet', 'world', '--loud'], io })
147
+ expect(code).toBe(0)
148
+ expect(io.lines).toEqual(['Hello, world!'])
149
+ })
150
+ ```
151
+
152
+ ## API reference
153
+
154
+ Everything the package exports from `@basaltkit/cli`:
155
+
156
+ ### `defineCommand(command)`
157
+
158
+ A typed identity function — returns the definition as-is, it just ensures the object has the right shape.
159
+
160
+ `CommandDefinition`:
161
+
162
+ | Field | Type | Required? | Default | Description |
163
+ | --- | --- | --- | --- | --- |
164
+ | `name` | `string` | Yes | — | Command name; convention `noun:verb` |
165
+ | `description` | `string` | No | — | Description shown in `basalt list` |
166
+ | `handle` | `(context) => void \| number \| Promise<void \| number>` | Yes | — | The function that runs; returns the exit code (omit = 0) |
167
+
168
+ `CommandContext` (the object received by `handle`):
169
+
170
+ | Field | Type | Description |
171
+ | --- | --- | --- |
172
+ | `app` | `BasaltApp` | The application (already booted) |
173
+ | `container` | `Container` | The dependency container — use `container.get(TOKEN)` to get services |
174
+ | `io` | `CommandIo` | Write surface (`log`, `error`, `table`) |
175
+ | `args` | `string[]` | Positional arguments after the command name |
176
+ | `flags` | `Record<string, string \| boolean>` | Flags: `--key=value` → string, `--flag` → `true` |
177
+
178
+ ### `runCli(options): Promise<number>`
179
+
180
+ Boots the application (if it's still in the `created` phase), resolves the command (built-in + registered in the `commands` metadata bucket), runs it, and shuts down the application at the end (`app.shutdown()`, even on error). Returns the exit code instead of calling `process.exit` — the caller decides what to do with it.
181
+
182
+ `RunCliOptions`:
183
+
184
+ | Field | Type | Required? | Default | Description |
185
+ | --- | --- | --- | --- | --- |
186
+ | `app` | `BasaltApp` | Yes | — | The application created with `createApp` |
187
+ | `argv` | `string[]` | No | `process.argv.slice(2)` | Arguments to parse |
188
+ | `io` | `CommandIo` | No | `consoleIo()` | Write surface — swap for `memoryIo()` in tests |
189
+
190
+ Behavior: no command (or `list`) shows the command table and returns `0`; an unknown command prints an error and returns `1`; otherwise it returns the command's own code (`?? 0`).
191
+
192
+ ### `commandsPlugin(commands: CommandDefinition[])`
193
+
194
+ A Basalt plugin that registers the commands in the `'commands'` metadata bucket, where `runCli` fetches them from. Pass it in the `plugins` list of `createApp`.
195
+
196
+ ### `parseArgv(argv: string[]): ParsedArgv`
197
+
198
+ A minimal argument parser: the first "loose" word is the command; `--key=value` and `--flag` become flags. Returns `{ command: string | undefined, args: string[], flags: Record<string, string | boolean> }`.
199
+
200
+ ### `consoleIo(): CommandIo`
201
+
202
+ A `CommandIo` implementation that writes to the console (`console.log` / `console.error`; `table` renders via `renderTable`). It's the default for `runCli`.
203
+
204
+ ### `memoryIo(): CommandIo & { lines: string[]; errors: string[] }`
205
+
206
+ An in-memory implementation for tests: accumulates messages in `lines` and `errors` instead of printing them.
207
+
208
+ ### `renderTable(rows: Record<string, unknown>[]): string`
209
+
210
+ Renders the rows as an aligned text table, with no external dependencies. Returns `'(empty)'` for an empty list. `undefined`/`null` cells are left blank.
211
+
212
+ ### `builtinCommands(): CommandDefinition[]`
213
+
214
+ Returns `[routesCommand, scheduleListCommand]`. *(Advanced — `runCli` already includes them automatically.)*
215
+
216
+ ### `routesCommand` / `scheduleListCommand`
217
+
218
+ The definitions of the built-in `routes` and `schedule:list` commands. *(Advanced — useful only if you want to run them directly or compose your own list.)*
219
+
220
+ ### Exported types
221
+
222
+ | Type | Description |
223
+ | --- | --- |
224
+ | `CommandDefinition`, `CommandContext`, `CommandIo` | Described above |
225
+ | `RunCliOptions`, `ParsedArgv` | Described above |
226
+ | `RouteMetadata` | `{ method: string; url: string; [key: string]: unknown }` — entries from the `http:routes` bucket |
227
+ | `ScheduleMetadata` | `{ name: string; cron: string; timezone: string }` — entries from the `schedule:entries` bucket |
228
+
229
+ ## Common errors and solutions (FAQ)
230
+
231
+ **`Unknown command "x". Run "basalt list" to see what is available.`**
232
+ The command isn't registered. Confirm you passed it inside `commandsPlugin([...])` and that plugin is in the `plugins` list of `createApp`. Run `basalt list` to see what's available.
233
+
234
+ **My command runs but the `--step 2` flag doesn't work.**
235
+ The parser only recognizes the equals-sign form: `--step=2`. Written with a space, `2` is treated as a positional argument (it shows up in `args`).
236
+
237
+ **`basalt routes` says "No routes registered."**
238
+ The command reads the `http:routes` metadata bucket, which is populated by the HTTP adapter (for example `fastifyPlugin`). Make sure the adapter is registered on the same application you pass to `runCli`.
239
+
240
+ **The application "hangs" after the command finishes.**
241
+ `runCli` always calls `app.shutdown()` at the end. If something stays alive, it's probably a resource left open outside the application's lifecycle (a `setInterval` you created, for example) — close it in the command itself.
242
+
243
+ **I want a custom error exit code.**
244
+ Return a number from `handle` (for example `return 3`). `runCli` propagates it; the example `bin/basalt.ts` passes it to `process.exit`.
245
+
246
+ ## How it connects to other modules
247
+
248
+ - **`@basaltkit/core`** — direct dependency: `runCli` receives a `BasaltApp` from `createApp`, and commands access the `Container` and the metadata buckets (`ensureMetadata`).
249
+ - **`@basaltkit/generator`** — provides `generatorCommands()`, a list of `make:*` commands (code generators) ready to pass to `commandsPlugin`. That's how `basalt make:resource` shows up.
250
+ - **`@basaltkit/fastify`** — writes routes into the `http:routes` bucket, which the built-in `routes` command reads.
251
+ - **`@basaltkit/scheduler`** — writes tasks into the `schedule:entries` bucket, which `schedule:list` reads.
252
+ - **`create-basalt`** — with the `--cli` flag, the project generator creates `bin/basalt.ts`, the `pnpm basalt` script, and registers `commandsPlugin(generatorCommands())` for you.
@@ -0,0 +1,85 @@
1
+ import * as _basaltkit_core from '@basaltkit/core';
2
+ import { BasaltApp, Container } from '@basaltkit/core';
3
+
4
+ /** Output surface — swappable in tests to capture what a command prints. */
5
+ interface CommandIo {
6
+ log(message: string): void;
7
+ error(message: string): void;
8
+ table(rows: Record<string, unknown>[]): void;
9
+ /** Ask a yes/no question. Resolves true only on an explicit yes. */
10
+ confirm(question: string): Promise<boolean>;
11
+ }
12
+ interface CommandContext {
13
+ app: BasaltApp;
14
+ container: Container;
15
+ io: CommandIo;
16
+ /** Positional arguments after the command name. */
17
+ args: string[];
18
+ /** Parsed flags: `--key=value` → string, `--flag` → true. */
19
+ flags: Record<string, string | boolean>;
20
+ }
21
+ interface CommandDefinition {
22
+ /** Command name, by convention `noun:verb` (e.g. `tenant:migrate`). */
23
+ name: string;
24
+ description?: string;
25
+ handle(context: CommandContext): void | number | Promise<void | number>;
26
+ }
27
+ declare function defineCommand(command: CommandDefinition): CommandDefinition;
28
+
29
+ interface RunCliOptions {
30
+ app: BasaltApp;
31
+ /** Defaults to process.argv.slice(2). */
32
+ argv?: string[];
33
+ /** Defaults to console output — swap in tests. */
34
+ io?: CommandIo;
35
+ }
36
+ /**
37
+ * Boots the app, resolves the command (built-ins + everything registered in
38
+ * the 'commands' metadata bucket), runs it and shuts the app down.
39
+ * Returns the process exit code instead of calling process.exit — the bin
40
+ * wrapper decides what to do with it.
41
+ */
42
+ declare function runCli(options: RunCliOptions): Promise<number>;
43
+
44
+ /** Registers app-defined commands into the 'commands' metadata bucket. */
45
+ declare function commandsPlugin(commands: CommandDefinition[]): _basaltkit_core.BasaltPlugin<unknown>;
46
+
47
+ /** Renders rows as an aligned text table (no external dependencies). */
48
+ declare function renderTable(rows: Record<string, unknown>[]): string;
49
+ declare function consoleIo(): CommandIo;
50
+ /**
51
+ * In-memory IO for tests. `answers` feeds `confirm()` in order (defaults to yes
52
+ * once the queue is drained), so a test can drive an interactive command.
53
+ */
54
+ declare function memoryIo(options?: {
55
+ answers?: boolean[];
56
+ }): CommandIo & {
57
+ lines: string[];
58
+ errors: string[];
59
+ };
60
+
61
+ interface ParsedArgv {
62
+ command: string | undefined;
63
+ args: string[];
64
+ flags: Record<string, string | boolean>;
65
+ }
66
+ /** Minimal argv parser: first bare word is the command; `--key=value` and `--flag` become flags. */
67
+ declare function parseArgv(argv: string[]): ParsedArgv;
68
+
69
+ /** Route entries written by HTTP adapters into the 'http:routes' bucket. */
70
+ interface RouteMetadata {
71
+ method: string;
72
+ url: string;
73
+ [key: string]: unknown;
74
+ }
75
+ /** Schedule entries written by the scheduler into the 'schedule:entries' bucket. */
76
+ interface ScheduleMetadata {
77
+ name: string;
78
+ cron: string;
79
+ timezone: string;
80
+ }
81
+ declare const routesCommand: CommandDefinition;
82
+ declare const scheduleListCommand: CommandDefinition;
83
+ declare function builtinCommands(): CommandDefinition[];
84
+
85
+ export { type CommandContext, type CommandDefinition, type CommandIo, type ParsedArgv, type RouteMetadata, type RunCliOptions, type ScheduleMetadata, builtinCommands, commandsPlugin, consoleIo, defineCommand, memoryIo, parseArgv, renderTable, routesCommand, runCli, scheduleListCommand };
package/dist/index.js ADDED
@@ -0,0 +1,155 @@
1
+ // src/command.ts
2
+ function defineCommand(command) {
3
+ return command;
4
+ }
5
+
6
+ // src/runner.ts
7
+ import { ensureMetadata } from "@basaltkit/core";
8
+
9
+ // src/builtins.ts
10
+ import { METADATA } from "@basaltkit/core";
11
+ var routesCommand = defineCommand({
12
+ name: "routes",
13
+ description: "List the HTTP routes registered by the app",
14
+ handle({ container, io }) {
15
+ const routes = container.has(METADATA) ? container.get(METADATA).get("http:routes") : [];
16
+ if (routes.length === 0) {
17
+ io.log("No routes registered.");
18
+ return;
19
+ }
20
+ io.table(routes.map(({ method, url }) => ({ method, url })));
21
+ }
22
+ });
23
+ var scheduleListCommand = defineCommand({
24
+ name: "schedule:list",
25
+ description: "List scheduled tasks and their cron expressions",
26
+ handle({ container, io }) {
27
+ const entries = container.has(METADATA) ? container.get(METADATA).get("schedule:entries") : [];
28
+ if (entries.length === 0) {
29
+ io.log("No scheduled tasks.");
30
+ return;
31
+ }
32
+ io.table(entries.map(({ name, cron, timezone }) => ({ name, cron, timezone })));
33
+ }
34
+ });
35
+ function builtinCommands() {
36
+ return [routesCommand, scheduleListCommand];
37
+ }
38
+
39
+ // src/io.ts
40
+ import { createInterface } from "readline/promises";
41
+ function renderTable(rows) {
42
+ if (rows.length === 0) return "(empty)";
43
+ const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))];
44
+ const cell = (value) => value === void 0 || value === null ? "" : String(value);
45
+ const widths = columns.map(
46
+ (column) => Math.max(column.length, ...rows.map((row) => cell(row[column]).length))
47
+ );
48
+ const line = (values) => values.map((value, index) => value.padEnd(widths[index])).join(" ");
49
+ return [
50
+ line(columns),
51
+ line(widths.map((width) => "-".repeat(width))),
52
+ ...rows.map((row) => line(columns.map((column) => cell(row[column]))))
53
+ ].join("\n");
54
+ }
55
+ function consoleIo() {
56
+ return {
57
+ log: (message) => console.log(message),
58
+ error: (message) => console.error(message),
59
+ table: (rows) => console.log(renderTable(rows)),
60
+ confirm: async (question) => {
61
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
62
+ try {
63
+ const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
64
+ return answer === "y" || answer === "yes";
65
+ } finally {
66
+ rl.close();
67
+ }
68
+ }
69
+ };
70
+ }
71
+ function memoryIo(options = {}) {
72
+ const lines = [];
73
+ const errors = [];
74
+ const answers = [...options.answers ?? []];
75
+ return {
76
+ lines,
77
+ errors,
78
+ log: (message) => void lines.push(message),
79
+ error: (message) => void errors.push(message),
80
+ table: (rows) => void lines.push(renderTable(rows)),
81
+ confirm: async () => answers.shift() ?? true
82
+ };
83
+ }
84
+
85
+ // src/parse.ts
86
+ function parseArgv(argv) {
87
+ let command;
88
+ const args = [];
89
+ const flags = {};
90
+ for (const token of argv) {
91
+ if (token.startsWith("--")) {
92
+ const body = token.slice(2);
93
+ const eq = body.indexOf("=");
94
+ if (eq === -1) flags[body] = true;
95
+ else flags[body.slice(0, eq)] = body.slice(eq + 1);
96
+ } else if (command === void 0) {
97
+ command = token;
98
+ } else {
99
+ args.push(token);
100
+ }
101
+ }
102
+ return { command, args, flags };
103
+ }
104
+
105
+ // src/runner.ts
106
+ async function runCli(options) {
107
+ const { app } = options;
108
+ const io = options.io ?? consoleIo();
109
+ const { command, args, flags } = parseArgv(options.argv ?? process.argv.slice(2));
110
+ if (app.phase === "created") await app.boot();
111
+ try {
112
+ const registered = ensureMetadata(app.container).get("commands");
113
+ const commands = [...builtinCommands(), ...registered];
114
+ if (command === void 0 || command === "list") {
115
+ io.log("Available commands:");
116
+ io.table(
117
+ commands.map(({ name, description }) => ({ command: name, description: description ?? "" }))
118
+ );
119
+ return 0;
120
+ }
121
+ const found = commands.find((candidate) => candidate.name === command);
122
+ if (!found) {
123
+ io.error(`Unknown command "${command}". Run "basalt list" to see what is available.`);
124
+ return 1;
125
+ }
126
+ const code = await found.handle({ app, container: app.container, io, args, flags });
127
+ return code ?? 0;
128
+ } finally {
129
+ await app.shutdown();
130
+ }
131
+ }
132
+
133
+ // src/plugin.ts
134
+ import { definePlugin, ensureMetadata as ensureMetadata2 } from "@basaltkit/core";
135
+ function commandsPlugin(commands) {
136
+ return definePlugin({
137
+ name: "basalt:commands",
138
+ register({ container }) {
139
+ const metadata = ensureMetadata2(container);
140
+ for (const command of commands) metadata.add("commands", command);
141
+ }
142
+ });
143
+ }
144
+ export {
145
+ builtinCommands,
146
+ commandsPlugin,
147
+ consoleIo,
148
+ defineCommand,
149
+ memoryIo,
150
+ parseArgv,
151
+ renderTable,
152
+ routesCommand,
153
+ runCli,
154
+ scheduleListCommand
155
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@basaltkit/cli",
3
+ "version": "1.0.0",
4
+ "description": "The 'basalt' command framework for Basalt apps: define commands, run them against a booted app, and inspect routes and schedules.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "dependencies": {
17
+ "@basaltkit/core": "^1.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.15.0",
21
+ "tsup": "^8.4.0",
22
+ "typescript": "^5.8.0",
23
+ "vitest": "^3.1.0",
24
+ "@basaltkit/tsconfig": "^0.24.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/Zebedeu/basalt.git",
32
+ "directory": "packages/cli"
33
+ },
34
+ "homepage": "https://github.com/Zebedeu/basalt/tree/main/packages/cli#readme",
35
+ "bugs": "https://github.com/Zebedeu/basalt/issues",
36
+ "keywords": [
37
+ "basalt",
38
+ "typescript",
39
+ "cli",
40
+ "commands"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsup src/index.ts --format esm --dts --clean",
44
+ "test": "vitest run",
45
+ "typecheck": "tsc --noEmit"
46
+ }
47
+ }