claude-master-toolkit 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -20
- package/dist/cli.js +220 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# claude-master-toolkit
|
|
2
2
|
|
|
3
3
|
**Token-efficient CLI + metrics dashboard for [Claude Code](https://claude.ai/code).**
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ Opens a local dashboard at `http://localhost:3200` backed by SQLite. Reads your
|
|
|
15
15
|
|
|
16
16
|
## Why
|
|
17
17
|
|
|
18
|
-
Claude Code stores every session as JSONL under `~/.claude/projects/`. That's the source of truth for real token usage and cost — but there's no good way to see it. `
|
|
18
|
+
Claude Code stores every session as JSONL under `~/.claude/projects/`. That's the source of truth for real token usage and cost — but there's no good way to see it. `claude-master-toolkit` parses those files into SQLite and gives you:
|
|
19
19
|
|
|
20
20
|
- **Realized cost** per session, per project, per day — using the actual Claude 4.6 / 4.5 pricing table.
|
|
21
21
|
- **Context window telemetry** — know when you're about to hit the 200k wall before the warning.
|
|
@@ -27,7 +27,7 @@ Think of it as the missing `/cost` page for Claude Code.
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npm install -g
|
|
30
|
+
npm install -g claude-master-toolkit
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Requires Node 18+. The database auto-initializes on first run at `~/.claude/state/claude-master-toolkit/ctk.sqlite`.
|
|
@@ -37,9 +37,9 @@ Requires Node 18+. The database auto-initializes on first run at `~/.claude/stat
|
|
|
37
37
|
### Dashboard
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
ctk dashboard # http://localhost:3200
|
|
41
|
+
ctk dashboard -p 4000 # custom port
|
|
42
|
+
ctk dashboard --no-open # don't auto-open browser
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
The server watches `~/.claude/projects/` and syncs new session events in real time.
|
|
@@ -48,19 +48,19 @@ The server watches `~/.claude/projects/` and syncs new session events in real ti
|
|
|
48
48
|
|
|
49
49
|
All commands are designed to return **compact, Claude-friendly output** — ideal for the `Bash` tool in agent loops.
|
|
50
50
|
|
|
51
|
-
| Command
|
|
52
|
-
|
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
51
|
+
| Command | What it does |
|
|
52
|
+
| --------------------------- | --------------------------------------------------------------------------- |
|
|
53
|
+
| `ctk cost [--quiet]` | Realized cost of the current session (reads session JSONL, applies pricing) |
|
|
54
|
+
| `ctk context` | Current window usage, cumulative totals, cost, threshold advice |
|
|
55
|
+
| `ctk tokens [file]` | Rough token count for a file or stdin (`chars / 4`) |
|
|
56
|
+
| `ctk estimate <file>` | Faithful pre-flight count via Anthropic `count_tokens` API |
|
|
57
|
+
| `ctk slice <file> <symbol>` | Extract just one function / class / type from a file |
|
|
58
|
+
| `ctk find <query> [path]` | Ranked ripgrep, top 20 results |
|
|
59
|
+
| `ctk git-log [N]` | One-line log for last N commits |
|
|
60
|
+
| `ctk git-changed` | Files changed vs main branch with line counts |
|
|
61
|
+
| `ctk test-summary [cmd]` | Run a test command, print only pass/fail summary |
|
|
62
|
+
| `ctk model <phase>` | Model alias recommendation per SDD phase |
|
|
63
|
+
| `ctk dashboard` | Launch the local metrics dashboard |
|
|
64
64
|
|
|
65
65
|
Every command supports `--json` for machine-readable output.
|
|
66
66
|
|
|
@@ -80,7 +80,7 @@ Pricing table is hard-coded in `src/shared/pricing.ts` for the Claude 4.6 / 4.5
|
|
|
80
80
|
|
|
81
81
|
## Context window vs cumulative cost
|
|
82
82
|
|
|
83
|
-
These are **different metrics** and `
|
|
83
|
+
These are **different metrics** and `ctk` reports both:
|
|
84
84
|
|
|
85
85
|
- **Context window** = what Claude sees right now. Equals the last turn's `input + cache_read + output`. Not a running sum — summing double-counts cache reads.
|
|
86
86
|
- **Cumulative cost** = every turn's tokens weighted by per-model prices. This is what `/cost` shows.
|
package/dist/cli.js
CHANGED
|
@@ -679,8 +679,8 @@ function gitChangedCommand() {
|
|
|
679
679
|
|
|
680
680
|
// src/cli/commands/find.ts
|
|
681
681
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
682
|
-
function findCommand(query,
|
|
683
|
-
const searchPath =
|
|
682
|
+
function findCommand(query, path3) {
|
|
683
|
+
const searchPath = path3 ?? ".";
|
|
684
684
|
try {
|
|
685
685
|
const result = execFileSync2(
|
|
686
686
|
"rg",
|
|
@@ -1751,14 +1751,231 @@ async function dashboardCommand(options) {
|
|
|
1751
1751
|
}
|
|
1752
1752
|
}
|
|
1753
1753
|
|
|
1754
|
+
// src/cli/commands/install.ts
|
|
1755
|
+
import fs from "fs";
|
|
1756
|
+
import path from "path";
|
|
1757
|
+
import os from "os";
|
|
1758
|
+
import { execSync as execSync3 } from "child_process";
|
|
1759
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1760
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
1761
|
+
var __dirname2 = path.dirname(__filename);
|
|
1762
|
+
var PKG_ROOT = path.resolve(__dirname2, "..");
|
|
1763
|
+
var CLAUDE_DIST = path.join(PKG_ROOT, "claude-dist");
|
|
1764
|
+
var CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
1765
|
+
function say(msg) {
|
|
1766
|
+
console.log(" " + msg);
|
|
1767
|
+
}
|
|
1768
|
+
function head(msg) {
|
|
1769
|
+
console.log("\n== " + msg + " ==\n");
|
|
1770
|
+
}
|
|
1771
|
+
function die(msg) {
|
|
1772
|
+
console.error("\u2717 " + msg);
|
|
1773
|
+
process.exit(1);
|
|
1774
|
+
throw new Error(msg);
|
|
1775
|
+
}
|
|
1776
|
+
function linkForce(target, link, type) {
|
|
1777
|
+
fs.mkdirSync(path.dirname(link), { recursive: true });
|
|
1778
|
+
const stat = fs.lstatSync(link, { throwIfNoEntry: false });
|
|
1779
|
+
if (stat) fs.unlinkSync(link);
|
|
1780
|
+
fs.symlinkSync(target, link, type);
|
|
1781
|
+
}
|
|
1782
|
+
async function installClaudeCommand(opts) {
|
|
1783
|
+
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
1784
|
+
die("~/.claude not found \u2014 is Claude Code installed?");
|
|
1785
|
+
}
|
|
1786
|
+
if (!fs.existsSync(CLAUDE_DIST)) {
|
|
1787
|
+
die(`claude-dist not found at ${CLAUDE_DIST} \u2014 broken package?`);
|
|
1788
|
+
}
|
|
1789
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1790
|
+
const backupDir = path.join(CLAUDE_DIR, "backups", `cmt-${timestamp}`);
|
|
1791
|
+
head("1. Backup existing config");
|
|
1792
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
1793
|
+
const claudeMdPath = path.join(CLAUDE_DIR, "CLAUDE.md");
|
|
1794
|
+
if (fs.existsSync(claudeMdPath) && !fs.lstatSync(claudeMdPath).isSymbolicLink()) {
|
|
1795
|
+
fs.copyFileSync(claudeMdPath, path.join(backupDir, "CLAUDE.md"));
|
|
1796
|
+
say("backed up CLAUDE.md");
|
|
1797
|
+
}
|
|
1798
|
+
const settingsPath = path.join(CLAUDE_DIR, "settings.json");
|
|
1799
|
+
if (fs.existsSync(settingsPath)) {
|
|
1800
|
+
fs.copyFileSync(settingsPath, path.join(backupDir, "settings.json"));
|
|
1801
|
+
say("backed up settings.json");
|
|
1802
|
+
}
|
|
1803
|
+
fs.writeFileSync(path.join(backupDir, "pkg-root"), PKG_ROOT);
|
|
1804
|
+
head("2. Symlink CLAUDE.md + hooks");
|
|
1805
|
+
linkForce(path.join(CLAUDE_DIST, "CLAUDE.md"), claudeMdPath, "file");
|
|
1806
|
+
for (const hook of ["session-start.sh", "user-prompt-submit.sh"]) {
|
|
1807
|
+
const src = path.join(CLAUDE_DIST, "hooks", hook);
|
|
1808
|
+
try {
|
|
1809
|
+
fs.chmodSync(src, 493);
|
|
1810
|
+
} catch {
|
|
1811
|
+
}
|
|
1812
|
+
linkForce(src, path.join(CLAUDE_DIR, "hooks", hook), "file");
|
|
1813
|
+
}
|
|
1814
|
+
say("linked CLAUDE.md + hooks (2)");
|
|
1815
|
+
head("3. Symlink skills + agents");
|
|
1816
|
+
const skills = ["sdd-new", "sdd-ff", "sdd-continue", "engram-protocol", "delegate"];
|
|
1817
|
+
for (const skill of skills) {
|
|
1818
|
+
linkForce(
|
|
1819
|
+
path.join(CLAUDE_DIST, "skills", skill),
|
|
1820
|
+
path.join(CLAUDE_DIR, "skills", skill),
|
|
1821
|
+
"dir"
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
linkForce(
|
|
1825
|
+
path.join(CLAUDE_DIST, "agents", "sdd-orchestrator.md"),
|
|
1826
|
+
path.join(CLAUDE_DIR, "agents", "sdd-orchestrator.md"),
|
|
1827
|
+
"file"
|
|
1828
|
+
);
|
|
1829
|
+
say(`linked skills (${skills.length}) + agents (1)`);
|
|
1830
|
+
head("4. Merge settings.json");
|
|
1831
|
+
const patchPath = path.join(CLAUDE_DIST, "settings.patch.json");
|
|
1832
|
+
let base = {};
|
|
1833
|
+
if (fs.existsSync(settingsPath)) {
|
|
1834
|
+
base = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
1835
|
+
}
|
|
1836
|
+
const patch = JSON.parse(fs.readFileSync(patchPath, "utf-8"));
|
|
1837
|
+
const merged = { ...base, ...patch };
|
|
1838
|
+
fs.writeFileSync(settingsPath, JSON.stringify(merged, null, 2));
|
|
1839
|
+
say("settings merged");
|
|
1840
|
+
head("5. Model preference");
|
|
1841
|
+
const stateDir = path.join(CLAUDE_DIR, "state", "claude-master-toolkit");
|
|
1842
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
1843
|
+
const prefFile = path.join(stateDir, "model-preference");
|
|
1844
|
+
if (!fs.existsSync(prefFile)) {
|
|
1845
|
+
fs.writeFileSync(prefFile, "inherit\n");
|
|
1846
|
+
say("model preference \u2192 inherit");
|
|
1847
|
+
} else {
|
|
1848
|
+
say("model preference: " + fs.readFileSync(prefFile, "utf-8").trim());
|
|
1849
|
+
}
|
|
1850
|
+
head("6. Caveman plugin");
|
|
1851
|
+
if (opts.skipCaveman) {
|
|
1852
|
+
say("skipped (--skip-caveman)");
|
|
1853
|
+
} else {
|
|
1854
|
+
try {
|
|
1855
|
+
execSync3("command -v claude", { stdio: "ignore" });
|
|
1856
|
+
try {
|
|
1857
|
+
execSync3("claude plugin marketplace add JuliusBrussee/caveman", { stdio: "ignore" });
|
|
1858
|
+
execSync3("claude plugin install caveman@caveman", { stdio: "ignore" });
|
|
1859
|
+
say("caveman plugin configured");
|
|
1860
|
+
} catch {
|
|
1861
|
+
say("caveman install skipped (already installed or failed)");
|
|
1862
|
+
}
|
|
1863
|
+
} catch {
|
|
1864
|
+
say("caveman skipped (claude CLI not found)");
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
head("\u2713 Claude stack installed");
|
|
1868
|
+
console.log(`
|
|
1869
|
+
Package: ${PKG_ROOT}
|
|
1870
|
+
Backup: ${backupDir}
|
|
1871
|
+
Config: ~/.claude (symlinked)
|
|
1872
|
+
|
|
1873
|
+
To remove: ctk uninstall claude
|
|
1874
|
+
`);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// src/cli/commands/uninstall.ts
|
|
1878
|
+
import fs2 from "fs";
|
|
1879
|
+
import path2 from "path";
|
|
1880
|
+
import os2 from "os";
|
|
1881
|
+
import { execSync as execSync4 } from "child_process";
|
|
1882
|
+
var CLAUDE_DIR2 = path2.join(os2.homedir(), ".claude");
|
|
1883
|
+
function say2(msg) {
|
|
1884
|
+
console.log(" " + msg);
|
|
1885
|
+
}
|
|
1886
|
+
function head2(msg) {
|
|
1887
|
+
console.log("\n== " + msg + " ==\n");
|
|
1888
|
+
}
|
|
1889
|
+
function removeSymlink(p) {
|
|
1890
|
+
const stat = fs2.lstatSync(p, { throwIfNoEntry: false });
|
|
1891
|
+
if (stat && stat.isSymbolicLink()) {
|
|
1892
|
+
fs2.unlinkSync(p);
|
|
1893
|
+
return true;
|
|
1894
|
+
}
|
|
1895
|
+
return false;
|
|
1896
|
+
}
|
|
1897
|
+
async function uninstallClaudeCommand(opts) {
|
|
1898
|
+
head2("1. Remove symlinks");
|
|
1899
|
+
const links = [
|
|
1900
|
+
path2.join(CLAUDE_DIR2, "CLAUDE.md"),
|
|
1901
|
+
path2.join(CLAUDE_DIR2, "hooks", "session-start.sh"),
|
|
1902
|
+
path2.join(CLAUDE_DIR2, "hooks", "user-prompt-submit.sh"),
|
|
1903
|
+
path2.join(CLAUDE_DIR2, "skills", "sdd-new"),
|
|
1904
|
+
path2.join(CLAUDE_DIR2, "skills", "sdd-ff"),
|
|
1905
|
+
path2.join(CLAUDE_DIR2, "skills", "sdd-continue"),
|
|
1906
|
+
path2.join(CLAUDE_DIR2, "skills", "engram-protocol"),
|
|
1907
|
+
path2.join(CLAUDE_DIR2, "skills", "delegate"),
|
|
1908
|
+
path2.join(CLAUDE_DIR2, "agents", "sdd-orchestrator.md")
|
|
1909
|
+
];
|
|
1910
|
+
let removed = 0;
|
|
1911
|
+
for (const link of links) if (removeSymlink(link)) removed++;
|
|
1912
|
+
say2(`removed symlinks (${removed}/${links.length})`);
|
|
1913
|
+
head2("2. Clean state");
|
|
1914
|
+
const stateDir = path2.join(CLAUDE_DIR2, "state", "claude-master-toolkit");
|
|
1915
|
+
if (fs2.existsSync(stateDir)) {
|
|
1916
|
+
for (const f of ["ctk.sqlite", "ctk.sqlite-journal", "model-preference"]) {
|
|
1917
|
+
const p = path2.join(stateDir, f);
|
|
1918
|
+
if (fs2.existsSync(p)) fs2.unlinkSync(p);
|
|
1919
|
+
}
|
|
1920
|
+
say2("cleaned state dir");
|
|
1921
|
+
} else {
|
|
1922
|
+
say2("no state dir found");
|
|
1923
|
+
}
|
|
1924
|
+
head2("3. Restore latest backup");
|
|
1925
|
+
const backupsDir = path2.join(CLAUDE_DIR2, "backups");
|
|
1926
|
+
if (fs2.existsSync(backupsDir)) {
|
|
1927
|
+
const candidates = fs2.readdirSync(backupsDir).filter((n) => n.startsWith("cmt-")).sort();
|
|
1928
|
+
const latest = candidates[candidates.length - 1];
|
|
1929
|
+
if (latest) {
|
|
1930
|
+
const dir = path2.join(backupsDir, latest);
|
|
1931
|
+
for (const f of ["CLAUDE.md", "settings.json"]) {
|
|
1932
|
+
const src = path2.join(dir, f);
|
|
1933
|
+
if (fs2.existsSync(src)) {
|
|
1934
|
+
fs2.copyFileSync(src, path2.join(CLAUDE_DIR2, f));
|
|
1935
|
+
say2(`restored ${f}`);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
} else {
|
|
1939
|
+
say2("no backup found");
|
|
1940
|
+
}
|
|
1941
|
+
} else {
|
|
1942
|
+
say2("no backups dir");
|
|
1943
|
+
}
|
|
1944
|
+
head2("4. Caveman plugin");
|
|
1945
|
+
if (opts.removeCaveman) {
|
|
1946
|
+
try {
|
|
1947
|
+
execSync4("command -v claude", { stdio: "ignore" });
|
|
1948
|
+
try {
|
|
1949
|
+
execSync4("claude plugin uninstall caveman@caveman", { stdio: "ignore" });
|
|
1950
|
+
say2("caveman uninstalled");
|
|
1951
|
+
} catch {
|
|
1952
|
+
say2("caveman not installed or failed");
|
|
1953
|
+
}
|
|
1954
|
+
} catch {
|
|
1955
|
+
say2("claude CLI not found");
|
|
1956
|
+
}
|
|
1957
|
+
} else {
|
|
1958
|
+
say2("kept (use --remove-caveman to remove)");
|
|
1959
|
+
}
|
|
1960
|
+
head2("\u2713 Claude stack removed");
|
|
1961
|
+
console.log(`
|
|
1962
|
+
Backups: ${path2.join(CLAUDE_DIR2, "backups")}
|
|
1963
|
+
Reinstall: ctk install claude
|
|
1964
|
+
`);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1754
1967
|
// src/cli/index.ts
|
|
1755
|
-
var VERSION = "0.1.
|
|
1968
|
+
var VERSION = "0.1.4";
|
|
1756
1969
|
var program = new Command().name("ctk").description(
|
|
1757
1970
|
"Claude Master Toolkit \u2014 token-efficient CLI + metrics dashboard"
|
|
1758
1971
|
).version(VERSION).option("--json", "Output in JSON format (AI-friendly)").hook("preAction", (thisCommand) => {
|
|
1759
1972
|
const opts = thisCommand.opts();
|
|
1760
1973
|
if (opts["json"]) setJsonMode(true);
|
|
1761
1974
|
});
|
|
1975
|
+
var install = program.command("install").description("Install integration stacks (claude, cursor, ...)");
|
|
1976
|
+
install.command("claude").description("Install Claude Code stack into ~/.claude (symlinks + settings)").option("--skip-caveman", "Skip caveman plugin install").action(installClaudeCommand);
|
|
1977
|
+
var uninstall = program.command("uninstall").description("Uninstall integration stacks");
|
|
1978
|
+
uninstall.command("claude").description("Uninstall Claude Code stack from ~/.claude (restore backup)").option("--remove-caveman", "Also uninstall caveman plugin").action(uninstallClaudeCommand);
|
|
1762
1979
|
program.command("slice <file> <symbol>").description("Extract a symbol block from a file (function/class/type)").action(sliceCommand);
|
|
1763
1980
|
program.command("model <phase>").description("Print model alias for an SDD phase (respects user preference)").action(modelCommand);
|
|
1764
1981
|
program.command("model-pref [action] [value]").description("Get/set/clear model selection preference").action(modelPrefCommand);
|
package/package.json
CHANGED