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.
Files changed (3) hide show
  1. package/README.md +20 -20
  2. package/dist/cli.js +220 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # workctl
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. `workctl` parses those files into SQLite and gives you:
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 workctl
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
- workctl dashboard # http://localhost:3200
41
- workctl dashboard -p 4000 # custom port
42
- workctl dashboard --no-open # don't auto-open browser
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 | What it does |
52
- | ------------------------------- | --------------------------------------------------------------------------- |
53
- | `workctl cost [--quiet]` | Realized cost of the current session (reads session JSONL, applies pricing) |
54
- | `workctl context` | Current window usage, cumulative totals, cost, threshold advice |
55
- | `workctl tokens [file]` | Rough token count for a file or stdin (`chars / 4`) |
56
- | `workctl estimate <file>` | Faithful pre-flight count via Anthropic `count_tokens` API |
57
- | `workctl slice <file> <symbol>` | Extract just one function / class / type from a file |
58
- | `workctl find <query> [path]` | Ranked ripgrep, top 20 results |
59
- | `workctl git-log [N]` | One-line log for last N commits |
60
- | `workctl git-changed` | Files changed vs main branch with line counts |
61
- | `workctl test-summary [cmd]` | Run a test command, print only pass/fail summary |
62
- | `workctl model <phase>` | Model alias recommendation per SDD phase |
63
- | `workctl dashboard` | Launch the local metrics dashboard |
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 `workctl` reports both:
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, path) {
683
- const searchPath = path ?? ".";
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.2";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-master-toolkit",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Token-efficient CLI + metrics dashboard for Claude Code. Measure real cost, cut context bloat, and keep sessions cheap.",
5
5
  "type": "module",
6
6
  "bin": {