Skip to main content
The src/commands/ module contains wit’s 75+ CLI commands. Commands are implemented using Commander.js for argument parsing and Ink for terminal UI.

Overview

Key Files

FilePurpose
cli.tsCLI entry point, command registration
commands/index.tsCommand exports
commands/smart-status.tsThe zero command (wit with no args)
commands/command-help.tsHelp system

Command Categories

Porcelain (User-Facing)

Standard Git commands with improvements:
CommandFileDescription
initinit.tsInitialize repository
addadd.tsStage files
commitcommit.tsCreate commit
statusstatus.tsShow status
loglog.tsShow history
branchbranch.tsBranch operations
checkoutcheckout.tsCheckout branches/files
switchswitch.tsSwitch branches (with auto-stash)
mergemerge.tsMerge branches
diffdiff.tsShow differences
restorerestore.tsRestore files

Quality of Life

Commands that make Git easier:
CommandFileDescription
amendamend.tsAmend last commit
wipwip.tsQuick WIP commit
uncommituncommit.tsUndo last commit (keep changes)
undoundo.tsJournal-based undo
cleanupcleanup.tsClean merged branches
fixupfixup.tsCreate fixup commits
snapshotsnapshot.tsQuick snapshots
blameblame.tsAnnotate file history
statsstats.tsRepository statistics

History Rewriting

CommandFileDescription
cherry-pickcherry-pick.tsApply commits
rebaserebase.tsRebase branches
revertrevert.tsRevert commits
resetreset.tsReset HEAD
bisectbisect.tsBinary search for bugs

Remote Operations

CommandFileDescription
cloneclone.tsClone repository
fetchfetch.tsFetch from remote
pullpull.tsPull changes
pushpush.tsPush changes
remoteremote.tsManage remotes

Plumbing (Low-Level)

Internal commands for scripting:
CommandFileDescription
cat-filecat-file.tsShow object contents
hash-objecthash-object.tsCompute hash
ls-filesls-files.tsList tracked files
ls-treels-tree.tsList tree contents
rev-parserev-parse.tsParse revisions
update-refupdate-ref.tsUpdate references
symbolic-refsymbolic-ref.tsManage symbolic refs
for-each-reffor-each-ref.tsIterate refs
show-refshow-ref.tsList references
fsckfsck.tsVerify repository
gcgc.tsGarbage collection
reflogreflog.tsReference log

Platform Commands

wit-specific platform features:
CommandFileDescription
serveserve.tsStart wit server
prpr.tsPull request management
issueissue.tsIssue tracking
reviewreview.tsCode review
stackstack.tsStacked diffs
cyclecycle.tsSprint management
projectproject.tsProject management
inboxinbox.tsNotification inbox
collaboratorcollaborator.tsTeam management
tokentoken.tsAccess tokens
merge-queuemerge-queue.tsMerge queue
cici.tsCI/CD operations
dashboarddashboard.tsProject dashboard
wrappedwrapped.tsActivity insights
journaljournal.tsDocumentation

AI Commands

CommandFileDescription
aiai.tsAI assistant (commit, review, explain)
agentagent.tsInteractive AI agent
planplan.tsAI planning
searchsearch.tsSemantic search

Advanced

CommandFileDescription
stashstash.tsStash changes
tagtag.tsTag management
cleanclean.tsRemove untracked files
showshow.tsShow objects
worktreeworktree.tsWorktree management
submodulesubmodule.tsSubmodule operations
scopescope.tsMonorepo scopes
githubgithub.tsGitHub integration
github-importgithub-import.tsImport from GitHub

Command Implementation

Basic Structure

// src/commands/example.ts
import { Repository } from '../core/repository';

interface ExampleOptions {
  verbose?: boolean;
  force?: boolean;
}

export async function exampleCommand(
  args: string[],
  options: ExampleOptions
): Promise<void> {
  // Find repository
  const repo = await Repository.find(process.cwd());

  // Implement command logic
  // ...

  // Output results
  console.log('Done');
}

With Terminal UI

// src/commands/status.ts
import { Repository } from '../core/repository';
import { renderStatus } from '../ui/status';

export async function statusCommand(options: StatusOptions): Promise<void> {
  const repo = await Repository.find(process.cwd());

  const status = await repo.status();

  if (options.json) {
    console.log(JSON.stringify(status, null, 2));
  } else {
    renderStatus(status);
  }
}

With API Client

// src/commands/pr.ts
import { getApiClient } from '../api';

export async function prListCommand(options: PrListOptions): Promise<void> {
  const api = getApiClient();

  const prs = await api.pullRequest.list.query({
    owner: options.owner,
    repo: options.repo,
    state: options.state,
  });

  for (const pr of prs) {
    console.log(`#${pr.number} ${pr.title}`);
  }
}

With AI Agent

// src/commands/agent.ts
import { getTsgitAgent } from '../ai';
import { renderAgentChat } from '../ui/agent-panel';

export async function agentCommand(): Promise<void> {
  const agent = getTsgitAgent();

  // Interactive chat loop
  renderAgentChat(agent);
}

CLI Entry Point

// src/cli.ts
import { Command } from 'commander';

const program = new Command();

program
  .name('wit')
  .description('Git that understands your code')
  .version('2.0.0');

// Zero command (just 'wit')
program
  .action(smartStatus);

// Git commands
program
  .command('init')
  .description('Initialize a new repository')
  .option('--hash <algorithm>', 'Hash algorithm (sha1 or sha256)')
  .action(initCommand);

program
  .command('add <files...>')
  .description('Stage files for commit')
  .option('-A, --all', 'Stage all changes')
  .action(addCommand);

program
  .command('commit')
  .description('Create a commit')
  .option('-m, --message <message>', 'Commit message')
  .option('-a, --all', 'Stage all modified files')
  .action(commitCommand);

// AI commands
program
  .command('ai <subcommand>')
  .description('AI-powered features')
  .action(aiCommand);

// Platform commands
program
  .command('pr <subcommand>')
  .description('Pull request management')
  .action(prCommand);

// Parse and execute
program.parse(process.argv);

The Zero Command

Running wit with no arguments shows intelligent status:
// src/commands/smart-status.ts
export async function smartStatus(): Promise<void> {
  const repo = await Repository.find(process.cwd());

  // Get status
  const status = await repo.status();
  const branch = repo.refs.getCurrentBranch();

  // Analyze context
  const context = await analyzeContext(repo);

  // Render smart status
  console.log(`
  wit · ${repo.name}
  You're working on: ${context.description}

  ${formatStatus(status)}

  ──────────────────────────────────────────────────

  ${suggestNextActions(status, context)}
`);
}
Output:
  wit · my-project
  You're working on: feature: user authentication

  ● Ready to commit (3 files)
    API: auth.ts, middleware.ts
    Tests: auth.test.ts

  ──────────────────────────────────────────────────

  wit commit     · commit staged changes
  wit ai commit  · commit with AI-generated message

Subcommand Patterns

Subcommands with Commander

// src/commands/pr.ts
const pr = program.command('pr').description('Pull request management');

pr.command('list')
  .description('List pull requests')
  .option('--state <state>', 'Filter by state')
  .action(prListCommand);

pr.command('create')
  .description('Create a pull request')
  .option('-t, --title <title>', 'PR title')
  .option('-b, --body <body>', 'PR body')
  .action(prCreateCommand);

pr.command('view <number>')
  .description('View pull request details')
  .action(prViewCommand);

pr.command('merge <number>')
  .description('Merge a pull request')
  .option('--squash', 'Squash commits')
  .option('--rebase', 'Rebase commits')
  .action(prMergeCommand);

AI Subcommands

// src/commands/ai.ts
const ai = program.command('ai').description('AI-powered features');

ai.command('commit')
  .description('Generate commit message with AI')
  .action(aiCommitCommand);

ai.command('review')
  .description('AI code review')
  .action(aiReviewCommand);

ai.command('explain')
  .description('Explain changes')
  .action(aiExplainCommand);

ai.command('resolve')
  .description('AI conflict resolution')
  .action(aiResolveCommand);

Terminal UI Components

wit uses Ink for rich terminal interfaces:

Status Display

// src/ui/status.ts
import { render, Box, Text } from 'ink';

function StatusView({ status }: { status: Status }) {
  return (
    <Box flexDirection="column">
      <Text color="green">Staged:</Text>
      {status.staged.map((file) => (
        <Text key={file}> + {file}</Text>
      ))}

      <Text color="yellow">Modified:</Text>
      {status.unstaged.map((file) => (
        <Text key={file}> M {file}</Text>
      ))}
    </Box>
  );
}

export function renderStatus(status: Status) {
  render(<StatusView status={status} />);
}

Interactive Selection

// src/ui/select.ts
import { render, useInput, useState } from 'ink';

function SelectPrompt({ items, onSelect }) {
  const [selected, setSelected] = useState(0);

  useInput((input, key) => {
    if (key.upArrow) setSelected((s) => Math.max(0, s - 1));
    if (key.downArrow) setSelected((s) => Math.min(items.length - 1, s + 1));
    if (key.return) onSelect(items[selected]);
  });

  return (
    <Box flexDirection="column">
      {items.map((item, i) => (
        <Text key={i} color={i === selected ? 'cyan' : undefined}>
          {i === selected ? '❯' : ' '} {item}
        </Text>
      ))}
    </Box>
  );
}

Progress Indicators

// src/ui/progress.ts
import { render, Box, Text } from 'ink';
import Spinner from 'ink-spinner';

function ProgressView({ message, progress }) {
  return (
    <Box>
      <Text color="cyan">
        <Spinner type="dots" />
      </Text>
      <Text> {message}</Text>
      {progress && <Text> ({progress}%)</Text>}
    </Box>
  );
}

Error Handling

Commands provide helpful error messages:
// src/commands/checkout.ts
export async function checkoutCommand(ref: string): Promise<void> {
  try {
    const repo = await Repository.find(process.cwd());
    await repo.checkout(ref);
  } catch (error) {
    if (error.code === 'REF_NOT_FOUND') {
      console.error(`Branch '${ref}' not found.`);
      console.error('');
      console.error('Did you mean one of these?');

      const suggestions = await repo.refs.findSimilar(ref);
      for (const s of suggestions) {
        console.error(`  ${s}`);
      }

      console.error('');
      console.error('To create the branch:');
      console.error(`  wit checkout -b ${ref}`);

      process.exit(1);
    }
    throw error;
  }
}

JSON Output

Most commands support --json for scripting:
export async function statusCommand(options: { json?: boolean }): Promise<void> {
  const repo = await Repository.find(process.cwd());
  const status = await repo.status();

  if (options.json) {
    console.log(JSON.stringify(status, null, 2));
    return;
  }

  // Human-readable output
  renderStatus(status);
}

Extension Points

Adding a New Command

  1. Create handler in src/commands/:
// src/commands/my-command.ts
export async function myCommand(args: string[], options: MyOptions): Promise<void> {
  // Implementation
}
  1. Register in CLI:
// src/cli.ts
program
  .command('my-command <args...>')
  .description('Does something')
  .option('-v, --verbose', 'Verbose output')
  .action(myCommand);
  1. Export from index:
// src/commands/index.ts
export { myCommand } from './my-command';

Adding Subcommands

const myGroup = program.command('my-group').description('My command group');

myGroup.command('sub1').action(sub1Command);
myGroup.command('sub2').action(sub2Command);

Custom UI Components

// src/ui/my-component.tsx
import { Box, Text } from 'ink';

export function MyComponent({ data }: { data: MyData }) {
  return (
    <Box>
      <Text>{data.message}</Text>
    </Box>
  );
}

Testing Commands

// src/commands/__tests__/status.test.ts
import { statusCommand } from '../status';

describe('status command', () => {
  it('shows clean status', async () => {
    const output = await captureOutput(() => statusCommand({}));
    expect(output).toContain('nothing to commit');
  });

  it('shows staged files', async () => {
    await stageFile('test.txt');
    const output = await captureOutput(() => statusCommand({}));
    expect(output).toContain('test.txt');
  });
});