-
Create the project
Install Node.js 24.0 or newer first.
The command passes
shipas the project name. The creator makes a new directory with that name and copies the TypeScript template into it. It will not overwrite an existing directory. If you use an agent to create it automatically, follow the agent setup guide.npm init func@latest shipTerminalnpm init func@latest shipnpm init func@latest shipyarn create func shippnpm create func shipbun create func ship -
Install and inspect the generated CLI
Enter the new directory, install dependencies, and run its help handler.
cd ship npm install npm run dev -- --helpTerminalcd ship npm install npm run dev -- --helpcd ship npm install npm run dev -- --helpcd ship yarn install yarn dev --helpcd ship pnpm install pnpm dev --helpcd ship bun install bun run dev -- --help
Understand the generated project
In the default template, the src directory contains all application code and tests contains the test cases. A dist directory will also appear after you run build.
In most cases, only the contents of dist are ultimately published.
.
|-- src
| |-- app.module.ts root module
| |-- config.ts package and runtime settings
| |-- commands
| | |-- error.command.ts global input-error output
| | |-- greet.command.ts named command example
| | |-- major.command.ts empty input, help, and version
| | |-- missing.command.ts unknown-command response
| | +-- index.ts command list
| |-- services
| | +-- project.service.ts injectable service example
| +-- index.ts executable entry
|-- tests executable behavior tests
|-- package.json
|-- tsconfig.json
+-- README.mdThe executable entry calls run(AppModule). The root module gathers the command and service classes that func is allowed to use:
import { FuncModule } from 'func'
import { commands } from './commands'
import { services } from './services'
@FuncModule({
commands,
services,
})
export class AppModule {}Commands describe the user-facing CLI. Services hold reusable work that should not be tied to parsing or printing. Tests spawn the generated executable so dispatch, input parsing, and output are checked together.
Run commands during development
The template exposes funcgo through ordinary npm scripts:
{
"scripts": {
"dev": "funcgo dev --",
"build": "funcgo build"
}
}In the npm tab, npm run dev -- <arguments> follows npm’s argument-passthrough convention. The first -- tells npm to stop reading its own options and append everything that follows to the script. The script’s trailing -- then tells funcgo dev that those tokens belong to your CLI rather than funcgo. You do not need to parse or remove either delimiter in application code.
npm run dev -- greet
npm run dev -- greet --name Ada
npm run dev -- greet shout --name Adanpm run dev -- greet
npm run dev -- greet --name Ada
npm run dev -- greet shout --name Ada yarn dev greet
yarn dev greet --name Ada
yarn dev greet shout --name Ada pnpm dev greet
pnpm dev greet --name Ada
pnpm dev greet shout --name Ada bun run dev -- greet
bun run dev -- greet --name Ada
bun run dev -- greet shout --name Ada Each invocation executes the TypeScript entry once. This is the fastest way to test a command while editing because no production bundle is required. Use the switcher in the terminal header for the equivalent commands from other package managers.
Add your first command
Create a class with a stable command name and one default handler. The class name is only for TypeScript; users type the name from @Command .
import { Command, Handler } from 'func'
@Command({
name: 'status',
description: 'Print service status',
})
export class StatusCommand {
@Handler()
run() {
console.log('All systems operational')
}
}Export it from the command list so AppModule can register it, then run npm run dev -- status.
import { ErrorHandler } from './error.command'
import { Major } from './major.command'
import { Missing } from './missing.command'
import { StatusCommand } from './status.command'
export const commands = [Major, StatusCommand, Missing, ErrorHandler]See Commands before adding aliases or several actions, and Field Options before accepting flags or values.
Optional: Map the command globally
This step is not required to use funcgo. Create a global link only if you want to type the final command name directly during development, such as ship; otherwise, keep using npm run dev -- <command>.
During development, your package manager can map the current package into its global command directory. Build first because package.json#bin points to the generated dist/bin.js, then create the link from the project root:
npm run build
npm link
# Use the key from package.json#bin
ship --helpnpm run build
npm link
# Use the key from package.json#bin
ship --help yarn build
yarn global add file:.
# Use the key from package.json#bin
ship --help pnpm build
pnpm add --global .
# Use the key from package.json#bin
ship --help bun run build
bun link --global
# Use the key from package.json#bin
ship --help The command name here is ship, so you can run ship --help above. The mapping uses this project’s build output. Rebuild the bundle after source changes and rerun the mapping command when needed.
To remove the mapping, use the corresponding command for the active package manager:
npm uninstall --global shipnpm uninstall --global ship yarn global remove ship pnpm remove --global ship bun unlink Rebuild while files change
funcgo build --watch performs an initial build, watches src/**/*.ts by default, and rebuilds after matching changes. Keep it running in one terminal and invoke the globally linked command in another. Press Ctrl+C to stop watching.
npm run build -- --watch
# Watch additional files or custom globs
npm run build -- --watch --watch-path 'src/**/*.ts' --watch-path config.jsonnpm run build -- --watch
# Watch additional files or custom globs
npm run build -- --watch --watch-path 'src/**/*.ts' --watch-path config.json yarn build --watch
# Watch additional files or custom globs
yarn build --watch --watch-path 'src/**/*.ts' --watch-path config.json pnpm build --watch
# Watch additional files or custom globs
pnpm build --watch --watch-path 'src/**/*.ts' --watch-path config.json bun run build -- --watch
# Watch additional files or custom globs
bun run build -- --watch --watch-path 'src/**/*.ts' --watch-path config.json Each --watch-path can be a file, directory, or positive glob. Use it for inputs outside src, such as a JSON configuration file. Generated output, node_modules, and .git are ignored.
Troubleshoot the first run
The shell cannot find the global command
Confirm that the build produced dist/bin.js, run npm link from the package root, and invoke the key from package.json#bin rather than the package name.
func reports an unknown command
Export the command class from the registered command list. Creating the file and adding @Command() does not register the class by itself.
npm consumes an option intended for the CLI
Keep the passthrough separator: use npm run dev -- status --json. The tokens after -- belong to your CLI.
Bundle and prepare to publish
The build script bundles the configured TypeScript entry into func.outDir (the template uses dist) and creates an executable bin.js. The package’s bin field exposes that file under the command name users will install.
npm run build
npm pack --dry-runnpm run build
npm pack --dry-run yarn build
yarn pack --dry-run pnpm build
pnpm pack --dry-run bun run build
bun pm pack --dry-run The dry-run pack command in the terminal shows which files would be published without publishing them. Confirm that the bundle, package metadata, README, and license are present, then run npm publish to publish the package to npm. For custom entry, output, external dependency, and watch settings, see Tooling.