This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called vp. Vite+ is distinct from Vite, but it invokes Vite through vp dev and vp build.
vp is a global binary that handles the full development lifecycle. Run vp help to print a list of commands and vp <command> --help for information about a specific command.
- create - Create a new project from a template
- migrate - Migrate an existing project to Vite+
- config - Configure hooks and agent integration
- staged - Run linters on staged files
- install (
i) - Install dependencies - env - Manage Node.js versions
- dev - Run the development server
- check - Run format, lint, and TypeScript type checks
- lint - Lint code
- fmt - Format code
- test - Run tests
- run - Run monorepo tasks
- exec - Execute a command from local
node_modules/.bin - dlx - Execute a package binary without installing it as a dependency
- cache - Manage the task cache
- build - Build for production
- pack - Build libraries
- preview - Preview production build
Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the packageManager field in package.json or package manager-specific lockfiles.
- add - Add packages to dependencies
- remove (
rm,un,uninstall) - Remove packages from dependencies - update (
up) - Update packages to latest versions - dedupe - Deduplicate dependencies
- outdated - Check for outdated packages
- list (
ls) - List installed packages - why (
explain) - Show why a package is installed - info (
view,show) - View package information from the registry - link (
ln) / unlink - Manage local package links - pm - Forward a command to the package manager
- upgrade - Update
vpitself to the latest version
These commands map to their corresponding tools. For example, vp dev --port 3000 runs Vite's dev server and works the same as Vite. vp test runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using vp --version. This is useful when researching documentation, features, and bugs.
- Using the package manager directly: Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations.
- Always use Vite commands to run tools: Don't attempt to run
vp vitestorvp oxlint. They do not exist. Usevp testandvp lintinstead. - Running scripts: Vite+ built-in commands (
vp dev,vp build,vp test, etc.) always run the Vite+ built-in tool, not anypackage.jsonscript of the same name. To run a custom script that shares a name with a built-in command, usevp run <script>. For example, if you have a customdevscript that runs multiple services concurrently, run it withvp run dev, notvp dev(which always starts Vite's dev server). - Do not install Vitest, Oxlint, Oxfmt, or tsdown directly: Vite+ wraps these tools. They must not be installed directly. You cannot upgrade these tools by installing their latest versions. Always use Vite+ commands.
- Use Vite+ wrappers for one-off binaries: Use
vp dlxinstead of package-manager-specificdlx/npxcommands. - Import JavaScript modules from
vite-plus: Instead of importing fromviteorvitest, all modules should be imported from the project'svite-plusdependency. For example,import { defineConfig } from 'vite-plus';orimport { expect, test, vi } from 'vite-plus/test';. You must not installvitestto import test utilities. - Type-Aware Linting: There is no need to install
oxlint-tsgolint,vp lint --type-awareworks out of the box.
- Run
vp installafter pulling remote changes and before getting started. - Run
vp checkandvp testto validate changes.
This repository is a TypeScript CLI for managing Halo instances.
- Runtime: Node.js >= 22
- Language: TypeScript, ESM
- CLI entry:
src/cli.ts - Published binary:
halo - Local development entry:
tsx src/cli.ts - Build output:
dist/cli.mjs
The root CLI currently registers these business areas:
authpostsearchpluginthemeattachmentbackupmomentcommentnotification
This project no longer implements large nested command trees directly on the root cac instance.
Use this pattern instead:
- Register a placeholder root command in
src/cli.tsviaregisterXxxCommands(cli). - Implement a dedicated sub-CLI in
src/commands/xxx/index.tswith its owncac("halo xxx")instance. - Export
tryRunXxxCommand(args, runtime). - In
src/cli.ts, dispatch in order by callingtryRunXxxCommand(...)before the final root parse.
This pattern is important because it gives correct help output for:
halo xxxhalo xxx --help- nested subcommands like
halo comment reply
If a business area has a nested namespace, create another dedicated sub-CLI for that nested branch instead of trying to handle help manually in a single root command.
Within a command area, prefer colocating command-specific helpers with the command itself, for example:
src/commands/post/index.tssrc/commands/post/format.tssrc/commands/post/input.tssrc/commands/post/types.tssrc/commands/post/__test__/...
Do not move command-local types, input parsing, browser helpers, file helpers, or tests back into shared roots unless they are reused across multiple business areas.
Authentication and HTTP clients are centralized in src/utils/runtime.ts.
RuntimeContextis intentionally narrow: it resolves profiles and constructs clients.RuntimeContext.getClientsForOptions(...)returns:clients.axiosclients.consoleclients.core
- Shared auth/config models live in
src/shared/profile.ts - Auth supports:
- Basic Auth
- Bearer token
- Profile storage is handled by
src/utils/config-store.ts - Profile metadata is stored in
config.json, but actual basic/bearer credentials are stored in the system keyring via@napi-rs/keyring - URL normalization lives in
src/utils/url.ts - Package upload helpers live in
src/utils/package-file.ts - Config path defaults to:
$HALO_CLI_CONFIG_DIR/config.jsonif set- otherwise
$XDG_CONFIG_HOME/halo/config.json - otherwise
~/.config/halo/config.json
- login
- current
- profile list/current/get/use/delete/doctor
- supports multi-profile management
- deleting a profile must also remove its stored credentials from the system keyring
profile doctorvalidates configured profiles against available keyring credentials and should be kept useful for recovery workflows
- list/get/create/update/delete/open/import-json/export-json
- uses Halo UC post APIs
- draft content is persisted through content annotations
- create/update support taxonomy resolution and creation for categories/tags
import-json/export-jsonuse the same{ post, content }shape returned bypost get --json- importing checks
metadata.name; if the post already exists, it requires confirmation before updating unless--forceis used export-jsonwrites the same payload to./<post-name>.jsonby default;--output <path>overrides the destination
- public site search
- does not require authenticated console access when
--urlis provided
- list/get/enable/disable/install/uninstall/upgrade and related management flows
- includes App Store-aware upgrade logic
- list/get/current/install/upgrade/activate/reload/delete
- uses Halo theme console/core APIs
- includes App Store-aware upgrade logic similar to
plugin - local installation uses multipart upload to the theme install endpoint because the generated SDK install signature is not file-parameter-friendly
listmarks the currently activated theme in table output
- list/get/delete/upload/download
- upload/download include progress feedback in TTY mode
- list/get/create/download/delete
create --waitpolls until completion
- list/get/create/update/delete
- does not use
@halo-dev/api-clientfor business APIs - uses manual
axiosrequests against the moments plugin UC endpoints - this was necessary because the main Halo SDK does not expose moments business APIs
comment listcomment getcomment approvecomment deletecomment create-replycomment reply listcomment reply getcomment reply approvecomment reply delete
Comment approval behavior follows the Halo console frontend:
- approving a comment uses
core.content.comment.patchCommentwith JSON Patch - approving a reply uses
core.content.reply.patchReplywith JSON Patch - creating a reply uses
console.content.comment.createReply, which creates an already approved reply in console context
notification listnotification getnotification deletenotification mark-as-readnotification mark-as-read --all
Notification management uses the Halo user-space UC notification APIs exposed in @halo-dev/api-client.
Single-notification lookup is implemented by filtering the authenticated user's notification list by metadata.name.
Shared output helpers live in src/utils/output.ts.
- Prefer adding business-specific
printXxxList(...)andprintXxx(...)helpers in the owning command area, usuallysrc/commands/<module>/format.ts - Keep
src/utils/output.tslimited to generic JSON/detail rendering helpers such asprintJson(...)andprintDetailObject(...) - Table output is standardized via
cli-table3 - Time formatting uses
dayjs - Byte formatting uses
pretty-bytes - JSON output is controlled by
--json
When adding a new business area, keep raw object printing out of command files as much as possible and route business formatting through the command-local formatter first, falling back to src/utils/output.ts only for generic helpers.
- Co-locate command tests under
src/commands/<module>/__test__/ - Keep cross-cutting utility tests under
src/utils/__test__/ - Keep shared integration tests under
src/shared/**/__test__/ - When moving command logic into a command folder, move its tests with it rather than leaving them in a shared
src/commands/__test__/root
- Use
@inquirer/promptsfor interactive flows only when running in TTY mode - Use
CliErrorfor user-facing validation errors - Any dangerous operation must require an explicit confirmation step in TTY mode before execution. This applies not only to delete/uninstall flows, but also to other risky state-changing actions such as disable or similar mutating operations.
- Require
--forcefor dangerous operations in non-interactive mode so confirmation can be skipped explicitly and consistently. - Prefer consistent success/cancel/delete messaging with existing commands
- For long-running upload/download/polling flows, use
orawhen stdout is a TTY and--jsonis not enabled
For dependency management and general project tooling, prefer Vite+ as described above.
For this CLI specifically, the most useful validation commands are:
pnpm typecheckvp lintvp checkvp test
pnpm typecheck is currently the fastest way to catch TypeScript regressions during command development.
When implementing a new command area:
- Check whether
@halo-dev/api-clientalready exposes the needed console/core/public API. - If it does, prefer the SDK over manual HTTP.
- If it does not, inspect upstream Halo or plugin code under
current-repos/and implement with manualaxiosrequests. - Keep root command registration minimal and put real logic in a dedicated sub-CLI file under
src/commands/. - Add command-local helpers such as
format.ts,types.ts,input.ts, orfiles.tsunder the owning command directory when they are not shared across business areas. - Put command tests under
src/commands/<module>/__test__/. - Use
src/utils/output.tsonly for generic output helpers shared across multiple areas. - Use
src/shared/for true shared models or integrations, for examplesrc/shared/profile.tsorsrc/shared/integrations/app-store.ts. - Validate with typecheck and lint before finishing.
The workspace currently includes reference repos under current-repos/:
halo/plugin-app-store/plugin-moments/vscode-extension-halo/
These are used as implementation references for API behavior, frontend console behavior, and feature parity.