@gonzui/claude-task-manager
Version:
Task management extension for Claude Code with archiving and history
277 lines (256 loc) • 12.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomCommandGenerator = exports.SESSION_START_HOOK_ENTRY = exports.STATUS_LINE_ENTRY = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
/**
* Claude Code integration entries shared by `init --hooks` (merged into
* .claude/settings.json) and the plugin package (hooks/hooks.json), so both
* distribution channels stay in sync.
*/
exports.STATUS_LINE_ENTRY = {
type: 'command',
command: 'claude-task status --short',
padding: 1
};
exports.SESSION_START_HOOK_ENTRY = {
type: 'command',
command: 'claude-task status'
};
class CustomCommandGenerator {
constructor(workingDir, i18n) {
this.workingDir = workingDir;
this.i18n = i18n;
}
async createClaudeCustomCommand() {
const claudeDir = path.join(this.workingDir, '.claude');
const claudeCommandsDir = path.join(claudeDir, 'commands');
try {
if (await fs.pathExists(claudeDir)) {
await fs.ensureDir(claudeCommandsDir);
const taskCommandPath = path.join(claudeCommandsDir, 'task.md');
const taskCommandContent = this.generateCustomCommandContent();
await fs.writeFile(taskCommandPath, taskCommandContent);
console.log(this.i18n.t('commands.init.customCommand'));
}
}
catch (error) {
console.warn('Could not create Claude custom command:', error);
}
}
/**
* Create a Claude Code Skill (.claude/skills/task/SKILL.md) so newer Claude
* Code versions can discover task management automatically. Created only when
* a .claude directory already exists, mirroring the slash command behavior.
*/
async createClaudeSkill() {
const claudeDir = path.join(this.workingDir, '.claude');
const skillDir = path.join(claudeDir, 'skills', 'task');
try {
if (await fs.pathExists(claudeDir)) {
await fs.ensureDir(skillDir);
const skillPath = path.join(skillDir, 'SKILL.md');
await fs.writeFile(skillPath, this.generateSkillContent());
console.log(this.i18n.t('commands.init.skill'));
}
}
catch (error) {
console.warn('Could not create Claude skill:', error);
}
}
/**
* Register the claude-task MCP server in the project's .mcp.json so Claude
* Code can use schema-bound task tools. Created only when a .claude
* directory already exists, mirroring the command/skill behavior. An
* existing "claude-task" entry is left untouched so user edits survive
* re-running init.
*/
async createMcpConfig() {
const claudeDir = path.join(this.workingDir, '.claude');
const mcpConfigPath = path.join(this.workingDir, '.mcp.json');
try {
if (!(await fs.pathExists(claudeDir))) {
return;
}
let config = {};
if (await fs.pathExists(mcpConfigPath)) {
config = await fs.readJson(mcpConfigPath);
}
if (config.mcpServers && config.mcpServers['claude-task']) {
return;
}
config.mcpServers = {
...config.mcpServers,
'claude-task': {
command: 'claude-task-mcp',
args: []
}
};
await fs.writeJson(mcpConfigPath, config, { spaces: 2 });
console.log(this.i18n.t('commands.init.mcpConfig'));
}
catch (error) {
console.warn('Could not update .mcp.json:', error);
}
}
/**
* Wire claude-task into Claude Code's ambient integration points by merging
* a `statusLine` entry and a `SessionStart` hook into
* `.claude/settings.json`. Only invoked behind an explicit flag
* (`claude-task init --hooks`); the user asked for it, so the .claude
* directory is created if missing. Existing user settings are preserved:
* a present `statusLine` is left untouched, and the hook is only appended
* when no claude-task SessionStart hook exists yet.
*/
async createHooksConfig() {
const claudeDir = path.join(this.workingDir, '.claude');
const settingsPath = path.join(claudeDir, 'settings.json');
try {
await fs.ensureDir(claudeDir);
let settings = {};
if (await fs.pathExists(settingsPath)) {
settings = await fs.readJson(settingsPath);
}
if (!settings.statusLine) {
settings.statusLine = { ...exports.STATUS_LINE_ENTRY };
}
settings.hooks = settings.hooks || {};
const sessionStart = settings.hooks['SessionStart'] || [];
const alreadyHooked = sessionStart.some((entry) => typeof entry.command === 'string' && entry.command.includes('claude-task'));
if (!alreadyHooked) {
sessionStart.push({ ...exports.SESSION_START_HOOK_ENTRY });
settings.hooks['SessionStart'] = sessionStart;
}
await fs.writeJson(settingsPath, settings, { spaces: 2 });
console.log(this.i18n.t('commands.init.hooksConfig'));
}
catch (error) {
console.warn('Could not update .claude/settings.json:', error);
}
}
generateSkillContent() {
return `---
name: task
description: Manage development tasks with the claude-task CLI. Use when the user wants to create, run, track, split, complete, or archive tasks, or asks about the current task / task progress. Tasks live in task.md.
metadata:
source: claude-task-manager
---
This skill drives the \`claude-task\` CLI, which manages a \`task.md\` file and an
\`archive/\` history. Run the commands below with the Bash tool and report the
output back to the user.
- The user wants to start, track, or finish a unit of work.
- The user asks "what's the current task?", "how far along am I?", or similar.
- The user mentions task.md, subtasks, or task history.
| Intent | Command |
| --- | --- |
| Create a task | \`claude-task new "<title>" [--priority high|medium|low] [--tags a,b]\` |
| Create a task alongside the current one | \`claude-task new "<title>" --name <name>\` |
| List all tasks | \`claude-task list\` |
| Switch to another task | \`claude-task switch <name>\` (\`--create\` to start it) |
| Show current task / counts | \`claude-task status\` |
| Show subtask progress bar | \`claude-task progress\` |
| Complete subtask(s) | \`claude-task done <n> [<n> ...]\` (use \`--undo\` to uncheck) |
| Split a task into subtasks (AI) | \`claude-task split [-c <count>]\` |
| Show history | \`claude-task history [--limit <n>]\` |
| Archive current task | \`claude-task archive\` |
## Executing a task
To actually do the work described in the current task, read \`@task.md\` and carry
out the steps yourself. \`claude-task run\` exists, but inside a Claude Code session
prefer reading \`@task.md\` directly and editing the relevant files. As you finish
each subtask, mark it complete with \`claude-task done <n>\`.
## task.md vs your in-session todo list
\`task.md\` is the **persistent, cross-session source of truth**; your in-session
todo list is an **ephemeral working mirror** of it. To keep them from conflicting:
- When you start working on the current task, run \`claude-task progress\` and
seed your todo list from the unchecked subtasks (keep their numbering).
- When you finish a subtask, persist it first with \`claude-task done <n>\`, then
check off the mirrored todo. Never update only the in-session list.
- Add or reword subtasks by editing the checkboxes in \`task.md\` (or via
\`claude-task split\`), not only in the in-session list.
- If the two ever disagree, \`task.md\` wins — re-seed your todo list from
\`claude-task progress\`.
## Notes
- Quote titles that contain spaces.
- Subtask numbers in \`done\` match the order shown by \`claude-task progress\`.
- One task is active at a time (\`task.md\`), but several named tasks can coexist:
\`new --name <name>\` starts one without archiving, \`switch <name>\` changes the
active one (state is saved on both sides), \`list\` shows them all.
- \`new\` without \`--name\` keeps the classic behavior: it archives the current
task and replaces it, reusing the active name.
`;
}
generateCustomCommandContent() {
return `---
description: Manage tasks with the claude-task CLI
argument-hint: <new|list|switch|status|progress|done|split|history|archive|run> [options]
allowed-tools: Bash(claude-task:*), Read, Edit
---
The user ran: \`/task $ARGUMENTS\`
- \`/task new "<title>" [--priority high|medium|low] [--tags a,b]\` — create a task
(archives the current one; add \`--name <name>\` to create it alongside instead)
- \`/task list\` — list all tasks with their progress
- \`/task switch <name>\` — make another task active (\`--create\` to start it)
- \`/task status\` — show the current task and counts
- \`/task progress\` — show the subtask progress bar
- \`/task done <numbers...>\` — mark subtasks done (\`--undo\` to uncheck)
- \`/task split [--count n]\` — break the task into subtasks (uses AI)
- \`/task history [--limit n]\` — show task history
- \`/task archive\` — archive the current task
- \`/task run\` — execute the current task
## How to handle this invocation
Pass arguments through **verbatim** — never rewrite, reorder, or drop flags.
- **run** (or no argument): do NOT shell out to \`claude-task run\`. Instead read
the current \`task.md\`, seed your in-session todo list from its unchecked
checkboxes, and carry out the work yourself. As you finish each subtask,
persist it with \`claude-task done <n>\` first, then check off the mirrored
todo. \`task.md\` is the source of truth — if it and your todo list disagree,
re-seed from \`claude-task progress\`.
- **split**: run \`claude-task split $ARGUMENTS\`. It calls Claude in the
background to generate subtasks, so it may take a moment — this is expected.
- **everything else** (new / list / switch / status / progress / done / history /
archive): run
\`claude-task $ARGUMENTS\` with the Bash tool and report the output to the user.
Quote titles that contain spaces. Subtask numbers for \`done\` match the order
shown by \`/task progress\`.`;
}
}
exports.CustomCommandGenerator = CustomCommandGenerator;
//# sourceMappingURL=CustomCommandGenerator.js.map