Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Adding a dependency has a licensing consequence worth knowing about before you r

## Releases

Publishing is automated. Every push to `main` publishes a prerelease to the `next` dist-tag, stamped as `<version>-<short-sha>`; pushing a `v*` tag verifies that `package.json` matches the tag and publishes to `latest`. Don't bump versions by hand in a pull request.
Publishing is automated. Every push to `main` publishes a prerelease to the `next` dist-tag, stamped as `<version>-<short-sha>`; pushing a `v*` tag verifies that `package.json` matches the tag and publishes to `latest`. A pull request bumps `version` in `package.json` itself, following semver: patch for fixes, minor for new commands or options, major for breaking changes.

## License

Expand Down
57 changes: 38 additions & 19 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kizenapps/cli",
"version": "1.16.0",
"version": "1.17.0",
"description": "CLI tool for running Kizen plugin apps locally",
"license": "GPL-3.0-only",
"repository": {
Expand Down
4 changes: 4 additions & 0 deletions shared/lib/validIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ export const VALID_ICONS = new Set([
'chevron-up',
'window-restore',
'forward',
'circle-check',
'arrow-progress',
'puzzle-piece',
'code',
]);

export const VALID_ICONS_LIST = [...VALID_ICONS].sort();
Expand Down
2 changes: 2 additions & 0 deletions src/__snapshots__/program.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Commands:
whoami [options] Show the user and business of the stored session
auth-header [options] Print an Authorization header for the stored session
as JSON (for a Claude Code MCP headersHelper)
admin [options] Run Kizen's admin CLI (kizen) with the signed-in
session
help [command] display help for command
"
`;
88 changes: 88 additions & 0 deletions src/commands/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { spawn } from 'node:child_process';
import { Command, type ParseOptionsResult } from 'commander';
import { ADMIN_PREFIX, runAdmin, type AdminDeps } from '../lib/kizenAdmin.js';
import { createDefaultKizenVenvDeps, ensureKizen } from '../lib/kizenVenv.js';
import { createOAuthStore } from '../lib/oauthStore.js';

const ADMIN = 'admin';

const INIT_HELP = `Usage: appbuilder admin init [--force]

Install kizen for appbuilder admin, or confirm it's installed. Needs Python 3.12 or newer.

Options:
--force remove kizen's install and install it again, to repair a damaged one; don't run it
while appbuilder admin is running
`;

/**
* `admin` takes its arguments raw: commander parses none of them, so kizen's own `--help`,
* `--version` and `--` reach it untouched. `runAdmin` reads the wrapper's options itself.
*/
class RawArgsCommand extends Command {
override parseOptions(argv: string[]): ParseOptionsResult {
return { operands: [...argv], unknown: [] };
}
}

export function createDefaultAdminDeps(command: Command): AdminDeps {
const writeStderr = (text: string): void => {
process.stderr.write(text);
};

const venvDeps = createDefaultKizenVenvDeps((line) => {
writeStderr(`${ADMIN_PREFIX}: ${line}\n`);
}, writeStderr);

// @types/node types `isTTY` as boolean, but it's undefined on a stream that isn't a TTY.
const stdin: { isTTY?: boolean } = process.stdin;

return {
store: createOAuthStore(),
ensureKizen: (options) => ensureKizen(venvDeps, options),
spawn,
signals: process,
interactive: stdin.isTTY === true,
processEnv: process.env,
execPath: process.execPath,
scriptPath: process.argv[1],
outputHelp: () => {
command.outputHelp();
},
outputInitHelp: () => {
process.stdout.write(INIT_HELP);
},
writeStderr,
setExitCode: (code) => {
process.exitCode = code;
},
};
}

export function adminCommand(program: Command): void {
const admin = new RawArgsCommand(ADMIN)
.copyInheritedSettings(program)
.description("Run Kizen's admin CLI (kizen) with the signed-in session")
.usage('[--env <env>] [--] <kizen args...>\n appbuilder admin init [--force]')
.option('--env <env>', 'environment to use (default: the only signed-in one)')
.allowExcessArguments()
.addHelpText(
'after',
`
Wrapper options end at the first other argument or at --; everything after goes to kizen.
"appbuilder admin init" installs kizen ahead of time; it's otherwise installed on first use.`,
)
.action(async () => {
await runAdmin(admin.args, createDefaultAdminDeps(admin));
});

program.addCommand(admin);

// Without positional options, the root parses its own options (`-V`/`--version`) and drops `--`
// anywhere in argv, subcommand arguments included. Hand everything after a leading `admin` to it
// untouched instead; every other command parses as before.
const parseOptions = program.parseOptions.bind(program);

program.parseOptions = (argv: string[]): ParseOptionsResult =>
argv[0] === ADMIN ? { operands: [ADMIN], unknown: argv.slice(1) } : parseOptions(argv);
}
7 changes: 7 additions & 0 deletions src/commands/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ const AUTH_COMMANDS: AuthCommandSpec[] = [
description:
'Print an Authorization header for the stored session as JSON (for a Claude Code MCP headersHelper)',
env: 'environment to use; must match CLAUDE_CODE_MCP_SERVER_URL when that is set (default: the environment matching it, else the only signed-in one)',
options: [
[
'--business <id>',
"with --user, use only this saved account, and fail if it isn't the environment's current one",
],
['--user <userId>', 'with --business, the user id of that account'],
],
run: runAuthHeader,
},
];
Expand Down
Loading
Loading