Skip to content

Commit bf725df

Browse files
committed
feat: add ten useful community mods and catalog listings
1 parent 6722c51 commit bf725df

24 files changed

Lines changed: 2886 additions & 1 deletion
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Community mods
2+
on:
3+
pull_request:
4+
paths:
5+
- 'mods/**'
6+
- 'package.json'
7+
- 'pnpm-lock.yaml'
8+
- 'tsconfig.json'
9+
- 'biome.json'
10+
- '.github/workflows/community-mods.yml'
11+
push:
12+
branches: [main]
13+
paths:
14+
- 'mods/**'
15+
- 'package.json'
16+
- 'pnpm-lock.yaml'
17+
- 'tsconfig.json'
18+
- 'biome.json'
19+
- '.github/workflows/community-mods.yml'
20+
permissions:
21+
contents: read
22+
jobs:
23+
verify:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: pnpm/action-setup@v4
28+
- uses: actions/setup-node@v4
29+
with:
30+
node-version: '22'
31+
cache: pnpm
32+
- run: pnpm install --frozen-lockfile --ignore-scripts
33+
- run: pnpm test
34+
- run: pnpm lint
35+
- run: pnpm typecheck
36+
- run: pnpm build

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
dist/
3+
coverage/

‎biome.json‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.3.15/schema.json",
3+
"files": {
4+
"includes": [
5+
"mods/community/**",
6+
"package.json",
7+
"tsconfig.json",
8+
"mods/registry.json"
9+
]
10+
},
11+
"assist": {
12+
"enabled": true
13+
},
14+
"formatter": {
15+
"enabled": true,
16+
"indentStyle": "tab",
17+
"indentWidth": 4,
18+
"lineWidth": 80
19+
},
20+
"javascript": {
21+
"formatter": {
22+
"quoteStyle": "single",
23+
"trailingCommas": "all",
24+
"arrowParentheses": "asNeeded",
25+
"bracketSpacing": false,
26+
"semicolons": "always"
27+
}
28+
},
29+
"linter": {
30+
"enabled": true,
31+
"rules": {
32+
"recommended": true
33+
}
34+
}
35+
}

‎mods/README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ against [`registry.schema.json`](./registry.schema.json).
4444
`official` and the top-level `hidden` list are maintainer-only — leave them out of your PR.
4545

4646
Keep the array valid JSON (no trailing commas) and one mod per PR where you can, so review stays quick.
47+
48+
## Included community mods
49+
50+
[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.

‎mods/community/README.md‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Community mods
2+
3+
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.
4+
5+
```sh
6+
cmd mods add CommandCodeAI/command-code -g
7+
cmd mods list
8+
```
9+
10+
**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`):
11+
12+
```sh
13+
cmd mods disable cmd-mod-context-meter -g
14+
cmd mods enable cmd-mod-context-meter -g
15+
```
16+
17+
| Mod | Commands | What it does |
18+
| --- | --- | --- |
19+
| `cmd-mod-review` | `/review-changes [base-ref]` | Supplies real Git context for a code review with severity, locations, and failure scenarios. |
20+
| `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. |
21+
| `cmd-mod-pr-draft` | `/pr-draft [base-ref]` | Drafts a PR title/body with verified changes and honest testing status. |
22+
| `cmd-mod-release-notes` | `/release-notes [base-ref]` | Drafts user-facing notes and distinguishes uncommitted changes. |
23+
| `cmd-mod-test-plan` | `/test-plan [base-ref]` | Plans concrete regression cases, expected outcomes, and relevant test commands. |
24+
| `cmd-mod-git-summary` | `/git-summary [base-ref]` | Reports status, commit/staged/unstaged stats, and recent commits without a model call. |
25+
| `cmd-mod-context-meter` | `/context-meter` | Shows measured context usage in a supported footer; suggests `/compact` at 80%. Never compacts automatically. |
26+
| `cmd-mod-run-stats` | `/run-stats` | Reports timing and successful/failed tool counts for the current or latest run. Resets on a new session. |
27+
| `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. |
28+
| `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. |
29+
30+
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.
31+
32+
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.
33+
34+
## Develop and verify
35+
36+
From the repository root:
37+
38+
```sh
39+
pnpm install --frozen-lockfile
40+
pnpm test
41+
pnpm lint
42+
pnpm typecheck
43+
pnpm build
44+
```
45+
46+
To test the bundle before merging, install a local checkout:
47+
48+
```sh
49+
cmd mods add ./path/to/command-code
50+
```
51+
52+
Or load a single mod without installing:
53+
54+
```sh
55+
cmd --mod ./mods/community/cmd-mod-git-summary.ts
56+
```
57+
58+
`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.

‎mods/community/api.ts‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Structural subset of Command Code's ModApi. Type-only: no runtime dependencies.
2+
// Command Code's community-mods contract tests check this against the actual API.
3+
export interface Disposable {
4+
dispose(): void;
5+
}
6+
export interface CommandResult {
7+
readonly message?: string;
8+
readonly prompt?: string;
9+
}
10+
export interface ExecResult {
11+
readonly stdout: string;
12+
readonly stderr: string;
13+
readonly code: number;
14+
}
15+
export interface ExecParams {
16+
readonly command: string;
17+
readonly args?: readonly string[];
18+
readonly cwd?: string;
19+
}
20+
export type ModExec = (params: ExecParams) => Promise<ExecResult>;
21+
export interface CommandContext {
22+
readonly args: string;
23+
readonly cwd: string;
24+
readonly exec: ModExec;
25+
}
26+
export interface ModCommand {
27+
readonly name: string;
28+
readonly description: string;
29+
readonly argumentHint?: string;
30+
readonly handler: (
31+
ctx: CommandContext,
32+
) => CommandResult | Promise<CommandResult>;
33+
}
34+
export interface ModEvent {
35+
readonly type: string;
36+
}
37+
export interface SessionEntry<T> {
38+
readonly data?: T;
39+
}
40+
export interface SessionStore {
41+
appendCustomEntry<T = unknown>(params: {
42+
readonly customType: string;
43+
readonly data?: T;
44+
}): string;
45+
getCustomEntries<T = unknown>(params: {
46+
readonly customType: string;
47+
}): readonly SessionEntry<T>[];
48+
}
49+
export interface SessionNode {
50+
readonly id: string;
51+
readonly label?: string;
52+
readonly children: readonly SessionNode[];
53+
}
54+
export interface ContextUsage {
55+
readonly tokens: number | null;
56+
readonly contextWindow: number;
57+
readonly percent: number | null;
58+
}
59+
export interface ModHooks {
60+
readonly appendSystemPrompt?: () => string | undefined;
61+
readonly onSessionStart?: () => void;
62+
}
63+
export interface ModApi {
64+
readonly ui: {
65+
readonly capabilities: {readonly status: boolean};
66+
setStatus(text: string | null): Disposable;
67+
notify(text: string, level?: 'info' | 'warning' | 'error'): void;
68+
};
69+
readonly session: SessionStore | undefined;
70+
readonly sessions: {
71+
leafId(): string | null;
72+
tree(): readonly SessionNode[];
73+
setLabel(params: {
74+
readonly targetId: string;
75+
readonly label: string | undefined;
76+
}): void;
77+
navigateTree(params: {readonly targetId: string}): Promise<void>;
78+
};
79+
addCommand(command: ModCommand): Disposable;
80+
hooks(hooks: ModHooks): Disposable;
81+
on(
82+
event:
83+
| 'session_start'
84+
| 'run_start'
85+
| 'run_end'
86+
| 'run_error'
87+
| 'tool_completed'
88+
| 'tool_errored'
89+
| 'model_request_end'
90+
| 'compaction_done',
91+
handler: (event: ModEvent) => void,
92+
): Disposable;
93+
getContextUsage(): ContextUsage | undefined;
94+
isIdle(): boolean;
95+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type {ModApi, SessionNode} from './api.js';
2+
import {hasControlCharacters} from './text.js';
3+
export function labelledNodes(
4+
nodes: readonly SessionNode[],
5+
): readonly SessionNode[] {
6+
return nodes.flatMap(node => [
7+
...(node.label ? [node] : []),
8+
...labelledNodes(node.children),
9+
]);
10+
}
11+
export default function register(cmd: ModApi): void {
12+
cmd.addCommand({
13+
name: 'bookmark',
14+
description: 'Label the current session point for returning later',
15+
argumentHint: '<label>',
16+
handler: ({args}) => {
17+
const label = args.trim();
18+
if (!label || label.length > 80 || hasControlCharacters(label))
19+
return {
20+
message:
21+
'Use /bookmark <label> (1–80 characters, single line).',
22+
};
23+
if (!cmd.isIdle())
24+
return {
25+
message:
26+
'Wait until the current run finishes before bookmarking.',
27+
};
28+
const targetId = cmd.sessions.leafId();
29+
if (!targetId)
30+
return {
31+
message:
32+
'No session point available yet. Run a prompt first.',
33+
};
34+
cmd.sessions.setLabel({targetId, label});
35+
return {message: `Bookmarked: ${label}`};
36+
},
37+
});
38+
cmd.addCommand({
39+
name: 'bookmarks',
40+
description: 'List labelled points in the active session',
41+
handler: () => {
42+
const nodes = labelledNodes(cmd.sessions.tree());
43+
return {
44+
message: nodes.length
45+
? nodes.map(node => `${node.label} — ${node.id}`).join('\n')
46+
: 'No bookmarks in this session.',
47+
};
48+
},
49+
});
50+
cmd.addCommand({
51+
name: 'jump-bookmark',
52+
description: 'Return to a labelled session point by exact label or ID',
53+
argumentHint: '<label-or-id>',
54+
handler: async ({args}) => {
55+
if (!cmd.isIdle())
56+
return {
57+
message:
58+
'Wait until the current run finishes before navigating.',
59+
};
60+
const query = args.trim();
61+
if (!query) return {message: 'Use /jump-bookmark <label-or-id>.'};
62+
const matches = labelledNodes(cmd.sessions.tree()).filter(
63+
node => node.id === query || node.label === query,
64+
);
65+
if (matches.length !== 1)
66+
return {
67+
message: matches.length
68+
? 'That label is ambiguous; use the ID from /bookmarks.'
69+
: 'Bookmark not found. Use /bookmarks.',
70+
};
71+
const node = matches[0];
72+
if (!node) return {message: 'Bookmark not found.'};
73+
await cmd.sessions.navigateTree({targetId: node.id});
74+
return {message: `Returned to: ${node.label}`};
75+
},
76+
});
77+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type {ModApi} from './api.js';
2+
export function contextText(cmd: ModApi): string {
3+
const usage = cmd.getContextUsage();
4+
if (!usage || usage.tokens === null || usage.percent === null)
5+
return 'Context: not measured yet';
6+
const percent = Math.round(usage.percent);
7+
const hint = percent >= 80 ? ' — consider /compact before a new task' : '';
8+
return `Context: ${usage.tokens.toLocaleString('en-US')} / ${usage.contextWindow.toLocaleString('en-US')} tokens (${percent}%)${hint}`;
9+
}
10+
export default function register(cmd: ModApi): void {
11+
const refresh = (): void => {
12+
if (cmd.ui.capabilities.status) cmd.ui.setStatus(contextText(cmd));
13+
};
14+
cmd.on('session_start', refresh);
15+
cmd.on('model_request_end', refresh);
16+
cmd.on('compaction_done', refresh);
17+
cmd.on('run_end', refresh);
18+
cmd.addCommand({
19+
name: 'context-meter',
20+
description: 'Show the measured context budget and a compaction hint',
21+
handler: () => ({message: contextText(cmd)}),
22+
});
23+
}

0 commit comments

Comments
 (0)