-
Notifications
You must be signed in to change notification settings - Fork 0
Project Mode
By default, apijack stores all configuration globally at ~/.<cliName>/. Project mode lets you scope config, routines, and extensions to a specific project directory — checked into git alongside your code. This makes it easy to share routines with teammates, run consistent setups in CI/CD, and add project-specific commands or authentication.
| Global Mode | Project Mode | |
|---|---|---|
| Config | ~/.<cliName>/config.json |
.apijack/config.json |
| Routines | ~/.<cliName>/routines/ |
.apijack/routines/ |
| Generated files | ~/.<cliName>/generated/ |
configurable via generatedDir
|
| Activated by | default |
.apijack.json in project root |
Project mode is activated automatically when apijack finds a .apijack.json file by walking up from the current working directory. Global mode is the fallback when no such file exists.
Create a .apijack.json file in your project root. The minimum required config just needs to be valid JSON:
{}A more typical setup:
{
"name": "my-api",
"specUrl": "https://api.example.com/v3/api-docs",
"generatedDir": "src/generated"
}All fields in .apijack.json are optional:
| Field | Type | Description |
|---|---|---|
name |
string |
Name of the CLI / project. When the shared apijack binary runs in project mode, this overrides the programName shown in --help and setup hints. |
description |
string |
Description shown in --help output when running the shared apijack binary in project mode. Falls back to the framework default if omitted. |
version |
string |
Version shown in --version output when running the shared apijack binary in project mode. Falls back to the installed @apijack/core version if omitted. |
specUrl |
string |
URL to the OpenAPI spec used by generate
|
generatedDir |
string |
Path where generated files are written (default: ~/.<cliName>/generated/) |
auth |
string |
Auth strategy identifier (used to select from registered strategies) |
allowedCidrs |
string[] |
CIDR blocks permitted to call the API (for network-restricted environments) |
Example with all fields:
{
"name": "my-api",
"description": "My API CLI",
"version": "1.0.0",
"specUrl": "https://api.example.com/v3/api-docs",
"generatedDir": "src/generated",
"auth": "bearer",
"allowedCidrs": ["10.0.0.0/8", "192.168.1.0/24"]
}When project mode is active, environment credentials are stored in .apijack/config.json inside your project directory rather than in your home directory. This file contains secrets, so add .apijack/ to .gitignore:
# .gitignore
.apijack/
The .apijack/ directory is also where extension files live (see below). Commit the extension files but not the config file.
Place routine YAML files in .apijack/routines/. See Writing Routines for the full routine format.
my-project/
.apijack.json
.apijack/
config.json
routines/
deploy.yaml
seed-database.yaml
apijack loads three kinds of project-local extensions from the .apijack/ directory at startup.
Export an AuthStrategy as the default export to override the auth strategy passed to createCli. Useful when your project uses session-based auth, OAuth, or any mechanism not covered by the built-in strategies.
// .apijack/auth.ts
import type { AuthStrategy } from "@apijack/core";
const myAuth: AuthStrategy = {
async getHeaders() {
const token = await fetchTokenFromVault();
return { Authorization: `Bearer ${token}` };
},
};
export default myAuth;Each .ts file in .apijack/commands/ can export a CommandRegistrar function as its default export. A registrar receives the root Commander program and a CliContext, and can attach any subcommands.
The command name defaults to the filename (without .ts) unless the module also exports a name string.
// .apijack/commands/deploy.ts
import type { CommandRegistrar } from "@apijack/core";
export const name = "deploy";
const registrar: CommandRegistrar = (program, ctx) => {
program
.command("deploy")
.description("Deploy the current build")
.action(async () => {
// custom logic using ctx.client, ctx.config, etc.
});
};
export default registrar;By default, ctx.session is lazily resolved — it stays null until a generated API command triggers resolution. Custom commands that read ctx.session directly need to opt in so the framework resolves the session before the action runs.
// .apijack/commands/whoami.ts
import type { CommandRegistrar, AuthedCliContext } from "@apijack/core";
export const name = "whoami";
export const requiresAuth = true;
const registrar: CommandRegistrar<true> = (program, ctx: AuthedCliContext) => {
program.command("whoami").action(() => {
// ctx.session is guaranteed non-null here
console.log(ctx.session.headers);
});
};
export default registrar;The CommandRegistrar<true> generic narrows ctx to AuthedCliContext (non-null session). Runtime enforcement is independent of the type annotation — setting export const requiresAuth = true resolves the session regardless of how the registrar is typed.
Preview modes (--dry-run, -o curl, -o routine-step, --help) skip the auth hook so command discovery stays offline. -o curl-with-creds still resolves, since the output includes credentials.
To flip the default for every custom command and dispatcher in the project, create .apijack/settings.json:
{
"customCommands": {
"defaults": {
"requiresAuth": true
}
}
}A module-level export const requiresAuth = false still wins over this default.
If a custom command mutates ctx.session (e.g. after rotating a token), call ctx.saveSession() to write the new value to disk. Setting ctx.session = null and calling ctx.saveSession() deletes the on-disk session file — useful for logout flows.
// .apijack/commands/logout.ts
export const name = "logout";
export const requiresAuth = true;
export default (program, ctx) => {
program.command("logout").action(async () => {
ctx.session = null;
await ctx.saveSession();
});
};ctx.resolveSession() is also available for on-demand resolution from code paths that didn't set requiresAuth.
Each .ts file in .apijack/dispatchers/ can export a DispatcherHandler as its default export. Dispatchers intercept command execution by name, letting you override or wrap the default behavior for specific operations.
The dispatcher is keyed by the filename (without .ts) unless the module exports a name string.
// .apijack/dispatchers/resources-create.ts
import type { DispatcherHandler } from "@apijack/core";
export const name = "resources-create";
const handler: DispatcherHandler = async (args, positionalArgs, ctx) => {
// Pre-processing
console.log("Creating resource with args:", args);
// Delegate to the default dispatcher or implement directly
return ctx.dispatch("resources-create", args, positionalArgs);
};
export default handler;Dispatchers use the same requiresAuth opt-in as custom commands. Set export const requiresAuth = true (or rely on .apijack/settings.json) to have the framework resolve ctx.session before the handler runs, and type the handler as DispatcherHandler<true> to narrow ctx to AuthedCliContext.
Each .ts file in .apijack/resolvers/ can export a CustomResolver as its default export. Custom resolvers extend the set of $_*(...) functions available inside routines — useful when you need a project-specific data source (a secrets vault, an internal service, a computed test fixture) that the built-in resolvers don't cover.
The resolver is keyed by the filename (without .ts) unless the module exports a name. The name must start with an underscore — routine references always use $_name syntax.
// .apijack/resolvers/_vault.ts
import type { CustomResolver } from "@apijack/core";
export const name = "_vault";
const vault: CustomResolver = async (argsStr, helpers) => {
// argsStr is the raw "(...)" contents. Use helpers.resolve to interpolate
// any $ references the caller passed in.
const key = helpers?.resolve(argsStr ?? "") ?? "";
return await fetchSecret(key);
};
export default vault;Used from a routine:
variables:
api_token: "$_vault(petstore/prod/api-token)"The helper receives:
-
argsStr— the raw string between(and)in the call (may be empty) -
helpers.resolve(value)— a function that resolves$-references the caller may have passed in, using the current step's variable scope
Names that collide with a registered built-in resolver are rejected at load time with a warning. Files that fail to import are silently skipped — same as the other extension directories.
Files in any extension directory that fail to import are silently skipped.
Team projects — Commit .apijack/routines/ so everyone on the team has access to the same automation workflows without manual setup.
CI/CD pipelines — Check in .apijack.json and .apijack/routines/ so your pipeline can run <cli> routine run deploy with no additional configuration.
Project-specific commands — Use .apijack/commands/ to add commands that only make sense in this project's context without modifying the shared CLI package.
Project-specific auth — Use .apijack/auth.ts when different projects authenticate differently (e.g., one uses an API key, another uses short-lived tokens from a vault).
Essentials
Using a CLI
Authoring Routines
- Writing Routines
- Variables
- Output Capture
- Conditions & Assertions
- Loops
- Error Handling
- Sub-Routines & Meta-Commands
- Routine Testing
Building a CLI
- Building a CLI
- Authentication Strategies
- Session Auth
- Project Mode
- MCP Server Integration
- Code Generation Internals
Reference