Skip to content
Closed
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
36 changes: 36 additions & 0 deletions .github/workflows/community-mods.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Community mods
on:
pull_request:
paths:
- 'mods/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'biome.json'
- '.github/workflows/community-mods.yml'
push:
branches: [main]
paths:
- 'mods/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'biome.json'
- '.github/workflows/community-mods.yml'
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm test
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm build
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
coverage/
35 changes: 35 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.15/schema.json",
"files": {
"includes": [
"mods/community/**",
"package.json",
"tsconfig.json",
"mods/registry.json"
]
},
"assist": {
"enabled": true
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 4,
"lineWidth": 80
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"trailingCommas": "all",
"arrowParentheses": "asNeeded",
"bracketSpacing": false,
"semicolons": "always"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
4 changes: 4 additions & 0 deletions mods/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ against [`registry.schema.json`](./registry.schema.json).
`official` and the top-level `hidden` list are maintainer-only — leave them out of your PR.

Keep the array valid JSON (no trailing commas) and one mod per PR where you can, so review stays quick.

## Included community mods

[Ten useful mods](./community/README.md) live in this repository. Install the bundle once with `cmd mods add CommandCodeAI/command-code -g`, then enable or disable its individual mods by name. The ten catalog rows share this source; they are not separate npm packages.
58 changes: 58 additions & 0 deletions mods/community/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Community mods

Ten small, dependency-free mods for Command Code 1.81.0 or newer. The repository manifest loads the TypeScript sources directly; no build or npm publishing is needed to install.

```sh
cmd mods add CommandCodeAI/command-code -g
cmd mods list
```

**This installs all ten mods as one bundle.** Each catalog entry uses that same install command. Disable any individual mod by its name, then restart Command Code (or use `/reload`):

```sh
cmd mods disable cmd-mod-context-meter -g
cmd mods enable cmd-mod-context-meter -g
```

| Mod | Commands | What it does |
| --- | --- | --- |
| `cmd-mod-review` | `/review-changes [base-ref]` | Supplies real Git context for a code review with severity, locations, and failure scenarios. |
| `cmd-mod-standup` | `/standup [YYYY-MM-DD]` | Drafts an update from up to 50 recent commits and working changes. Defaults to the past 24 hours. |
| `cmd-mod-pr-draft` | `/pr-draft [base-ref]` | Drafts a PR title/body with verified changes and honest testing status. |
| `cmd-mod-release-notes` | `/release-notes [base-ref]` | Drafts user-facing notes and distinguishes uncommitted changes. |
| `cmd-mod-test-plan` | `/test-plan [base-ref]` | Plans concrete regression cases, expected outcomes, and relevant test commands. |
| `cmd-mod-git-summary` | `/git-summary [base-ref]` | Reports status, commit/staged/unstaged stats, and recent commits without a model call. |
| `cmd-mod-context-meter` | `/context-meter` | Shows measured context usage in a supported footer; suggests `/compact` at 80%. Never compacts automatically. |
| `cmd-mod-run-stats` | `/run-stats` | Reports timing and successful/failed tool counts for the current or latest run. Resets on a new session. |
| `cmd-mod-bookmarks` | `/bookmark <label>`, `/bookmarks`, `/jump-bookmark <label-or-id>` | Labels a session point and navigates back to it. Duplicate labels require an exact ID. Navigation changes the conversation branch; it does not revert filesystem changes. |
| `cmd-mod-focus` | `/focus <goal>`, `/focus status`, `/focus off` | Persists a scoped reminder in the session and restores it on resume. Later explicit instructions take priority. |

Git base defaults to the first available `origin/main`, `main`, `origin/master`, `master`, or `HEAD`. Specify a tag/commit/branch for release notes or an already-merged PR; a default of `HEAD` includes only working changes. Committed stats compare the merge base to HEAD. Git snapshots include filenames, commit subjects, and change counts; the agent inspects actual content for review and drafting. Each command output is capped at 6,000 characters and marked when truncated. Untracked contents are not included. `/standup` considers all local branches, so review attribution before sharing an update.

The first five commands submit a model turn. Git summary and session utilities do not. Draft commands do not publish, push, or send updates. A focus reminder guides the agent; it is not an enforcement boundary. Focus persists only when durable sessions are available. Bookmarks require a recorded session point. Run counts exclude denied/blocked tools and count completed/errored tool events across the current run.

## Develop and verify

From the repository root:

```sh
pnpm install --frozen-lockfile
pnpm test
pnpm lint
pnpm typecheck
pnpm build
```

To test the bundle before merging, install a local checkout:

```sh
cmd mods add ./path/to/command-code
```

Or load a single mod without installing:

```sh
cmd --mod ./mods/community/cmd-mod-git-summary.ts
```

`api.ts` is a structural, type-only subset of the host API; it ships no private package or runtime dependency. The Command Code source repo also verifies all factories against the real ModApi and loads this manifest through the real jiti loader. Built ESM files in `dist/` are optional and are not tracked. The tests cover command errors and reference validation, output limits, headless context usage, run lifecycle, session labels, and focus resume/reset behavior.
95 changes: 95 additions & 0 deletions mods/community/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Structural subset of Command Code's ModApi. Type-only: no runtime dependencies.
// Command Code's community-mods contract tests check this against the actual API.
export interface Disposable {
dispose(): void;
}
export interface CommandResult {
readonly message?: string;
readonly prompt?: string;
}
export interface ExecResult {
readonly stdout: string;
readonly stderr: string;
readonly code: number;
}
export interface ExecParams {
readonly command: string;
readonly args?: readonly string[];
readonly cwd?: string;
}
export type ModExec = (params: ExecParams) => Promise<ExecResult>;
export interface CommandContext {
readonly args: string;
readonly cwd: string;
readonly exec: ModExec;
}
export interface ModCommand {
readonly name: string;
readonly description: string;
readonly argumentHint?: string;
readonly handler: (
ctx: CommandContext,
) => CommandResult | Promise<CommandResult>;
}
export interface ModEvent {
readonly type: string;
}
export interface SessionEntry<T> {
readonly data?: T;
}
export interface SessionStore {
appendCustomEntry<T = unknown>(params: {
readonly customType: string;
readonly data?: T;
}): string;
getCustomEntries<T = unknown>(params: {
readonly customType: string;
}): readonly SessionEntry<T>[];
}
export interface SessionNode {
readonly id: string;
readonly label?: string;
readonly children: readonly SessionNode[];
}
export interface ContextUsage {
readonly tokens: number | null;
readonly contextWindow: number;
readonly percent: number | null;
}
export interface ModHooks {
readonly appendSystemPrompt?: () => string | undefined;
readonly onSessionStart?: () => void;
}
export interface ModApi {
readonly ui: {
readonly capabilities: {readonly status: boolean};
setStatus(text: string | null): Disposable;
notify(text: string, level?: 'info' | 'warning' | 'error'): void;
};
readonly session: SessionStore | undefined;
readonly sessions: {
leafId(): string | null;
tree(): readonly SessionNode[];
setLabel(params: {
readonly targetId: string;
readonly label: string | undefined;
}): void;
navigateTree(params: {readonly targetId: string}): Promise<void>;
};
addCommand(command: ModCommand): Disposable;
hooks(hooks: ModHooks): Disposable;
on(
event:
| 'session_start'
| 'run_start'
| 'run_end'
| 'run_error'
| 'tool_completed'
| 'tool_errored'
| 'model_request_end'
| 'compaction_done',
handler: (event: ModEvent) => void,
): Disposable;
getContextUsage(): ContextUsage | undefined;
isIdle(): boolean;
}
77 changes: 77 additions & 0 deletions mods/community/cmd-mod-bookmarks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type {ModApi, SessionNode} from './api.js';
import {hasControlCharacters} from './text.js';
export function labelledNodes(
nodes: readonly SessionNode[],
): readonly SessionNode[] {
return nodes.flatMap(node => [
...(node.label ? [node] : []),
...labelledNodes(node.children),
]);
}
export default function register(cmd: ModApi): void {
cmd.addCommand({
name: 'bookmark',
description: 'Label the current session point for returning later',
argumentHint: '<label>',
handler: ({args}) => {
const label = args.trim();
if (!label || label.length > 80 || hasControlCharacters(label))
return {
message:
'Use /bookmark <label> (1–80 characters, single line).',
};
if (!cmd.isIdle())
return {
message:
'Wait until the current run finishes before bookmarking.',
};
const targetId = cmd.sessions.leafId();
if (!targetId)
return {
message:
'No session point available yet. Run a prompt first.',
};
cmd.sessions.setLabel({targetId, label});
return {message: `Bookmarked: ${label}`};
},
});
cmd.addCommand({
name: 'bookmarks',
description: 'List labelled points in the active session',
handler: () => {
const nodes = labelledNodes(cmd.sessions.tree());
return {
message: nodes.length
? nodes.map(node => `${node.label} — ${node.id}`).join('\n')
: 'No bookmarks in this session.',
};
},
});
cmd.addCommand({
name: 'jump-bookmark',
description: 'Return to a labelled session point by exact label or ID',
argumentHint: '<label-or-id>',
handler: async ({args}) => {
if (!cmd.isIdle())
return {
message:
'Wait until the current run finishes before navigating.',
};
const query = args.trim();
if (!query) return {message: 'Use /jump-bookmark <label-or-id>.'};
const matches = labelledNodes(cmd.sessions.tree()).filter(
node => node.id === query || node.label === query,
);
if (matches.length !== 1)
return {
message: matches.length
? 'That label is ambiguous; use the ID from /bookmarks.'
: 'Bookmark not found. Use /bookmarks.',
};
const node = matches[0];
if (!node) return {message: 'Bookmark not found.'};
await cmd.sessions.navigateTree({targetId: node.id});
return {message: `Returned to: ${node.label}`};
},
});
}
23 changes: 23 additions & 0 deletions mods/community/cmd-mod-context-meter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type {ModApi} from './api.js';
export function contextText(cmd: ModApi): string {
const usage = cmd.getContextUsage();
if (!usage || usage.tokens === null || usage.percent === null)
return 'Context: not measured yet';
const percent = Math.round(usage.percent);
const hint = percent >= 80 ? ' — consider /compact before a new task' : '';
return `Context: ${usage.tokens.toLocaleString('en-US')} / ${usage.contextWindow.toLocaleString('en-US')} tokens (${percent}%)${hint}`;
}
export default function register(cmd: ModApi): void {
const refresh = (): void => {
if (cmd.ui.capabilities.status) cmd.ui.setStatus(contextText(cmd));
};
cmd.on('session_start', refresh);
cmd.on('model_request_end', refresh);
cmd.on('compaction_done', refresh);
cmd.on('run_end', refresh);
cmd.addCommand({
name: 'context-meter',
description: 'Show the measured context budget and a compaction hint',
handler: () => ({message: contextText(cmd)}),
});
}
Loading
Loading