@hustle-together/api-dev-tools 3.11.1 → 3.12.2

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 (139) hide show
  1. package/.claude/agents/code-reviewer.md +170 -0
  2. package/.claude/agents/docs-generator.md +80 -0
  3. package/.claude/agents/implementation-reviewer.md +119 -0
  4. package/.claude/agents/parallel-researcher.md +52 -0
  5. package/.claude/agents/research-validator.md +116 -0
  6. package/.claude/agents/schema-generator.md +70 -0
  7. package/.claude/agents/test-writer.md +104 -0
  8. package/.claude/api-dev-state.json +305 -56
  9. package/.claude/commands/README.md +21 -10
  10. package/.claude/commands/add-command.md +8 -5
  11. package/.claude/commands/api-create.md +36 -25
  12. package/.claude/commands/api-env.md +1 -0
  13. package/.claude/commands/api-interview.md +32 -19
  14. package/.claude/commands/api-research.md +47 -21
  15. package/.claude/commands/api-status.md +21 -1
  16. package/.claude/commands/api-verify.md +14 -13
  17. package/.claude/commands/beepboop.md +4 -5
  18. package/.claude/commands/busycommit.md +2 -3
  19. package/.claude/commands/commit.md +2 -3
  20. package/.claude/commands/cycle.md +2 -7
  21. package/.claude/commands/gap.md +2 -3
  22. package/.claude/commands/green.md +2 -7
  23. package/.claude/commands/issue.md +3 -8
  24. package/.claude/commands/ntfy-setup.md +91 -0
  25. package/.claude/commands/ntfy-test.md +74 -0
  26. package/.claude/commands/plan.md +2 -3
  27. package/.claude/commands/pr.md +2 -3
  28. package/.claude/commands/publish.md +40 -0
  29. package/.claude/commands/red.md +2 -7
  30. package/.claude/commands/refactor.md +2 -7
  31. package/.claude/commands/spike.md +2 -7
  32. package/.claude/commands/summarize.md +2 -3
  33. package/.claude/commands/tdd.md +2 -7
  34. package/.claude/commands/worktree-add.md +208 -216
  35. package/.claude/commands/worktree-cleanup.md +172 -178
  36. package/.claude/settings.json +63 -12
  37. package/.claude/settings.local.json +2 -1
  38. package/.claude-plugin/marketplace.json +2 -11
  39. package/.skills/README.md +55 -53
  40. package/.skills/_shared/settings.json +1 -1
  41. package/.skills/add-command/SKILL.md +10 -5
  42. package/.skills/api-create/SKILL.md +146 -35
  43. package/.skills/api-env/SKILL.md +1 -0
  44. package/.skills/api-interview/SKILL.md +32 -19
  45. package/.skills/api-research/SKILL.md +47 -21
  46. package/.skills/api-status/SKILL.md +21 -1
  47. package/.skills/api-verify/SKILL.md +14 -13
  48. package/.skills/beepboop/SKILL.md +6 -5
  49. package/.skills/busycommit/SKILL.md +4 -3
  50. package/.skills/commit/SKILL.md +4 -3
  51. package/.skills/cycle/SKILL.md +4 -7
  52. package/.skills/gap/SKILL.md +4 -3
  53. package/.skills/green/SKILL.md +4 -7
  54. package/.skills/issue/SKILL.md +5 -8
  55. package/.skills/plan/SKILL.md +4 -3
  56. package/.skills/pr/SKILL.md +4 -3
  57. package/.skills/publish/SKILL.md +160 -0
  58. package/.skills/red/SKILL.md +4 -7
  59. package/.skills/refactor/SKILL.md +4 -7
  60. package/.skills/spike/SKILL.md +4 -7
  61. package/.skills/summarize/SKILL.md +4 -3
  62. package/.skills/tdd/SKILL.md +4 -7
  63. package/.skills/update-todos/SKILL.md +22 -0
  64. package/.skills/worktree-add/SKILL.md +210 -216
  65. package/.skills/worktree-cleanup/SKILL.md +183 -187
  66. package/CHANGELOG.md +97 -79
  67. package/README.md +161 -7142
  68. package/bin/cli.js +448 -805
  69. package/commands/README.md +66 -31
  70. package/commands/add-command.md +8 -5
  71. package/commands/beepboop.md +4 -5
  72. package/commands/busycommit.md +2 -3
  73. package/commands/commit.md +2 -3
  74. package/commands/cycle.md +2 -7
  75. package/commands/gap.md +2 -3
  76. package/commands/green.md +2 -7
  77. package/commands/hustle-api-continue.md +8 -5
  78. package/commands/hustle-api-create.md +70 -29
  79. package/commands/hustle-api-env.md +1 -0
  80. package/commands/hustle-api-interview.md +32 -19
  81. package/commands/hustle-api-research.md +47 -21
  82. package/commands/hustle-api-sessions.md +8 -7
  83. package/commands/hustle-api-status.md +21 -1
  84. package/commands/hustle-api-verify.md +14 -13
  85. package/commands/hustle-combine.md +488 -241
  86. package/commands/hustle-ui-create-page.md +113 -50
  87. package/commands/hustle-ui-create.md +179 -26
  88. package/commands/issue.md +3 -8
  89. package/commands/plan.md +2 -3
  90. package/commands/pr.md +2 -3
  91. package/commands/red.md +2 -7
  92. package/commands/refactor.md +2 -7
  93. package/commands/spike.md +2 -7
  94. package/commands/summarize.md +2 -3
  95. package/commands/tdd.md +2 -7
  96. package/commands/worktree-add.md +208 -216
  97. package/commands/worktree-cleanup.md +172 -178
  98. package/hooks/api-workflow-check.py +5 -3
  99. package/hooks/enforce-component-type-confirm.py +97 -0
  100. package/hooks/lib/__init__.py +1 -0
  101. package/hooks/lib/greptile.py +355 -0
  102. package/hooks/lib/ntfy.py +209 -0
  103. package/hooks/notify-input-needed.py +73 -0
  104. package/hooks/notify-phase-complete.py +90 -0
  105. package/hooks/run-code-review.py +246 -0
  106. package/hooks/track-token-usage.py +121 -0
  107. package/package.json +13 -3
  108. package/scripts/collect-test-results.ts +102 -77
  109. package/scripts/extract-parameters.ts +112 -70
  110. package/scripts/generate-test-manifest.ts +118 -77
  111. package/templates/.env.example +57 -0
  112. package/templates/BRAND_GUIDE.md +92 -52
  113. package/templates/CLAUDE-SECTION.md +40 -37
  114. package/templates/SPEC.json +186 -38
  115. package/templates/api-dev-state.json +33 -4
  116. package/templates/api-showcase/_components/APICard.tsx +22 -18
  117. package/templates/api-showcase/_components/APIModal.tsx +110 -64
  118. package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
  119. package/templates/api-showcase/_components/APITester.tsx +128 -67
  120. package/templates/api-showcase/page.tsx +4 -4
  121. package/templates/api-test/page.tsx +51 -30
  122. package/templates/api-test/test-structure/route.ts +43 -34
  123. package/templates/component/Component.stories.tsx +41 -39
  124. package/templates/component/Component.test.tsx +96 -78
  125. package/templates/component/Component.tsx +63 -52
  126. package/templates/component/Component.types.ts +10 -6
  127. package/templates/component/Component.visual.spec.ts +170 -0
  128. package/templates/component/index.ts +2 -2
  129. package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
  130. package/templates/dev-tools/page.tsx +4 -3
  131. package/templates/mcp-servers.json +30 -2
  132. package/templates/page/page.e2e.test.ts +56 -48
  133. package/templates/page/page.tsx +3 -3
  134. package/templates/shared/HeroHeader.tsx +16 -15
  135. package/templates/shared/index.ts +1 -1
  136. package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
  137. package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
  138. package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
  139. package/templates/ui-showcase/page.tsx +4 -4
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hook: PostToolUse
4
+ Purpose: Send NTFY notification when a phase completes
5
+
6
+ Triggers after state file is updated with phase completion.
7
+ Includes token usage in notification.
8
+
9
+ Version: 3.10.0
10
+ """
11
+ import json
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ # Add lib to path
16
+ sys.path.insert(0, str(Path(__file__).parent))
17
+ from lib.ntfy import send_phase_update
18
+
19
+
20
+ def main():
21
+ # Read hook input from stdin
22
+ try:
23
+ input_data = json.load(sys.stdin)
24
+ except json.JSONDecodeError:
25
+ sys.exit(0)
26
+
27
+ tool_name = input_data.get("tool_name", "")
28
+ tool_input = input_data.get("tool_input", {})
29
+
30
+ # Only trigger on Write/Edit to state file
31
+ if tool_name not in ["Write", "Edit"]:
32
+ sys.exit(0)
33
+
34
+ file_path = tool_input.get("file_path", "")
35
+ if "api-dev-state.json" not in file_path:
36
+ sys.exit(0)
37
+
38
+ # Read the updated state
39
+ cwd = Path.cwd()
40
+ state_file = cwd / ".claude" / "api-dev-state.json"
41
+
42
+ if not state_file.exists():
43
+ sys.exit(0)
44
+
45
+ try:
46
+ state = json.loads(state_file.read_text())
47
+ except (json.JSONDecodeError, IOError):
48
+ sys.exit(0)
49
+
50
+ # Check for recently completed phases
51
+ phases = state.get("phases", {})
52
+ workflow = state.get("workflow", "unknown")
53
+ element = state.get("element_name", state.get("endpoint", "unknown"))
54
+
55
+ phase_names = {
56
+ "disambiguation": "Disambiguation",
57
+ "scope": "Scope",
58
+ "research_initial": "Initial Research",
59
+ "interview": "Interview",
60
+ "research_deep": "Deep Research",
61
+ "schema_creation": "Schema Creation",
62
+ "environment_check": "Environment Check",
63
+ "tdd_red": "TDD Red",
64
+ "tdd_green": "TDD Green",
65
+ "verify": "Verification",
66
+ "tdd_refactor": "Refactor",
67
+ "documentation": "Documentation",
68
+ "completion": "Completion",
69
+ }
70
+
71
+ for phase_key, phase_data in phases.items():
72
+ if isinstance(phase_data, dict):
73
+ status = phase_data.get("status", "")
74
+ notified = phase_data.get("ntfy_notified", False)
75
+
76
+ if status == "complete" and not notified:
77
+ phase_name = phase_names.get(phase_key, phase_key.title())
78
+ send_phase_update(
79
+ phase_name=phase_name,
80
+ status="complete",
81
+ details=f"Element: {element}",
82
+ workflow=workflow
83
+ )
84
+ # Mark as notified (would need to update state, but we avoid writes in hooks)
85
+
86
+ sys.exit(0)
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Phase 11: AI Code Review Hook
4
+
5
+ Triggers Greptile AI code review after Phase 10 (Verify) and BEFORE Phase 12 (Refactor).
6
+ This ensures issues are caught early and can be fixed during the refactor phase,
7
+ rather than after PR creation when it's too late.
8
+
9
+ Hook Type: PostToolUse (triggers after tests pass in Phase 9/10)
10
+
11
+ Greptile API:
12
+ POST https://api.greptile.com/v2/query
13
+ - Analyzes code changes against entire codebase context
14
+ - Returns issues with file:line references
15
+ - Provides actionable fix suggestions
16
+
17
+ Environment Variables:
18
+ GREPTILE_API_KEY: Your Greptile API key (get from https://app.greptile.com)
19
+ GITHUB_TOKEN: GitHub Personal Access Token with repo access
20
+ CODE_REVIEW_ENABLED: Set to 'true' to enable (default: true)
21
+
22
+ Version: 1.1.0
23
+ """
24
+ import os
25
+ import sys
26
+ import json
27
+ import subprocess
28
+ from pathlib import Path
29
+
30
+ # Add lib directory to path for imports
31
+ HOOK_DIR = Path(__file__).parent
32
+ LIB_DIR = HOOK_DIR / "lib"
33
+ sys.path.insert(0, str(LIB_DIR))
34
+
35
+ try:
36
+ from greptile import (
37
+ is_configured,
38
+ review_changes,
39
+ get_review_summary,
40
+ format_review_for_display,
41
+ get_status
42
+ )
43
+ GREPTILE_AVAILABLE = True
44
+ except ImportError:
45
+ GREPTILE_AVAILABLE = False
46
+
47
+
48
+ def get_git_diff() -> tuple:
49
+ """Get the current git diff and changed files."""
50
+ try:
51
+ # Get list of changed files
52
+ files_result = subprocess.run(
53
+ ["git", "diff", "--name-only", "HEAD~1"],
54
+ capture_output=True,
55
+ text=True,
56
+ timeout=30
57
+ )
58
+ files_changed = files_result.stdout.strip().split("\n") if files_result.stdout else []
59
+
60
+ # Get full diff
61
+ diff_result = subprocess.run(
62
+ ["git", "diff", "HEAD~1"],
63
+ capture_output=True,
64
+ text=True,
65
+ timeout=30
66
+ )
67
+ diff_content = diff_result.stdout
68
+
69
+ return files_changed, diff_content
70
+ except (subprocess.TimeoutExpired, FileNotFoundError):
71
+ return [], ""
72
+
73
+
74
+ def get_repo_info() -> tuple:
75
+ """Get repository owner and name from git remote."""
76
+ try:
77
+ result = subprocess.run(
78
+ ["git", "remote", "get-url", "origin"],
79
+ capture_output=True,
80
+ text=True,
81
+ timeout=10
82
+ )
83
+ if result.returncode == 0:
84
+ url = result.stdout.strip()
85
+ # Parse GitHub URL (handles both HTTPS and SSH)
86
+ if "github.com" in url:
87
+ if url.startswith("git@"):
88
+ # SSH format: git@github.com:owner/repo.git
89
+ parts = url.split(":")[-1].replace(".git", "").split("/")
90
+ else:
91
+ # HTTPS format: https://github.com/owner/repo.git
92
+ parts = url.replace(".git", "").split("/")[-2:]
93
+
94
+ if len(parts) >= 2:
95
+ return parts[-2], parts[-1]
96
+ except (subprocess.TimeoutExpired, FileNotFoundError):
97
+ pass
98
+ return None, None
99
+
100
+
101
+ def load_state() -> dict:
102
+ """Load current workflow state."""
103
+ state_file = Path.cwd() / ".claude" / "api-dev-state.json"
104
+ if state_file.exists():
105
+ try:
106
+ return json.loads(state_file.read_text())
107
+ except (json.JSONDecodeError, IOError):
108
+ pass
109
+ return {}
110
+
111
+
112
+ def update_state_with_review(review_summary: dict):
113
+ """Update state file with code review results."""
114
+ state_file = Path.cwd() / ".claude" / "api-dev-state.json"
115
+ state = load_state()
116
+
117
+ # Add or update code_review phase
118
+ if "phases" not in state:
119
+ state["phases"] = {}
120
+
121
+ state["phases"]["code_review"] = {
122
+ "status": "complete",
123
+ "score": review_summary.get("score", 0),
124
+ "issues_found": review_summary.get("issue_count", 0),
125
+ "suggestions": review_summary.get("suggestion_count", 0),
126
+ "reviewed_at": __import__("datetime").datetime.now().isoformat()
127
+ }
128
+
129
+ try:
130
+ state_file.write_text(json.dumps(state, indent=2))
131
+ except IOError:
132
+ pass
133
+
134
+
135
+ def should_run_review(hook_input: dict) -> bool:
136
+ """Determine if code review should run based on hook context."""
137
+ # Check if code review is enabled
138
+ if os.environ.get("CODE_REVIEW_ENABLED", "true").lower() == "false":
139
+ return False
140
+
141
+ tool_name = hook_input.get("tool_name", "")
142
+
143
+ # Run after tests pass (Phase 9/10) - triggers before refactoring
144
+ if tool_name == "Bash":
145
+ tool_input = hook_input.get("tool_input", {})
146
+ command = tool_input.get("command", "")
147
+ tool_result = hook_input.get("tool_result", {})
148
+ stdout = tool_result.get("stdout", "")
149
+
150
+ # Check if tests just passed
151
+ if ("pnpm test" in command or "npm test" in command or "vitest" in command):
152
+ # Only run if tests passed (look for success indicators)
153
+ if "pass" in stdout.lower() or "āœ“" in stdout or "PASS" in stdout:
154
+ return True
155
+
156
+ # Also run if verify-after-green hook triggered
157
+ if "verify" in tool_name.lower():
158
+ return True
159
+
160
+ return False
161
+
162
+
163
+ def main():
164
+ """Main hook entry point."""
165
+ # Read hook input
166
+ try:
167
+ hook_input = json.loads(sys.stdin.read())
168
+ except json.JSONDecodeError:
169
+ hook_input = {}
170
+
171
+ # Check if we should run
172
+ if not should_run_review(hook_input):
173
+ # Pass through - no review needed
174
+ print(json.dumps({"continue": True}))
175
+ return
176
+
177
+ # Check if Greptile is available and configured
178
+ if not GREPTILE_AVAILABLE:
179
+ print(json.dumps({
180
+ "continue": True,
181
+ "message": "Greptile library not found - skipping code review"
182
+ }))
183
+ return
184
+
185
+ if not is_configured():
186
+ status = get_status()
187
+ print(json.dumps({
188
+ "continue": True,
189
+ "message": f"Phase 11 Code Review skipped: {status['message']}"
190
+ }))
191
+ return
192
+
193
+ # Get repository info
194
+ repo_owner, repo_name = get_repo_info()
195
+ if not repo_owner or not repo_name:
196
+ print(json.dumps({
197
+ "continue": True,
198
+ "message": "Could not determine repository info - skipping code review"
199
+ }))
200
+ return
201
+
202
+ # Get changes to review
203
+ files_changed, diff_content = get_git_diff()
204
+ if not diff_content:
205
+ print(json.dumps({
206
+ "continue": True,
207
+ "message": "No changes detected - skipping code review"
208
+ }))
209
+ return
210
+
211
+ # Run the review
212
+ result = review_changes(
213
+ repo_owner=repo_owner,
214
+ repo_name=repo_name,
215
+ files_changed=files_changed,
216
+ diff_content=diff_content
217
+ )
218
+
219
+ if not result:
220
+ print(json.dumps({
221
+ "continue": True,
222
+ "message": "Greptile review failed - check API credentials"
223
+ }))
224
+ return
225
+
226
+ # Parse and display results
227
+ summary = get_review_summary(result)
228
+ update_state_with_review(summary)
229
+
230
+ # Format for display
231
+ display_output = format_review_for_display(summary)
232
+
233
+ # Determine if we should block based on critical issues
234
+ has_critical = summary.get("score", 10) < 5
235
+
236
+ print(json.dumps({
237
+ "continue": not has_critical,
238
+ "message": display_output,
239
+ "review_score": summary.get("score", 0),
240
+ "issues_count": summary.get("issue_count", 0),
241
+ "action_required": has_critical
242
+ }))
243
+
244
+
245
+ if __name__ == "__main__":
246
+ main()
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hook: PostToolUse
4
+ Purpose: Track token usage per phase and display after phase completion
5
+
6
+ Logs token usage to state file and outputs summary after each phase.
7
+ Integrates with ccusage if available.
8
+
9
+ Version: 3.10.0
10
+ """
11
+ import json
12
+ import sys
13
+ import subprocess
14
+ from pathlib import Path
15
+ from datetime import datetime
16
+
17
+
18
+ def get_token_usage() -> dict:
19
+ """Get current token usage from ccusage."""
20
+ try:
21
+ result = subprocess.run(
22
+ ["ccusage", "--json"],
23
+ capture_output=True,
24
+ text=True,
25
+ timeout=5
26
+ )
27
+ if result.returncode == 0:
28
+ return json.loads(result.stdout)
29
+ except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError):
30
+ pass
31
+ return {}
32
+
33
+
34
+ def main():
35
+ # Read hook input from stdin
36
+ try:
37
+ input_data = json.load(sys.stdin)
38
+ except json.JSONDecodeError:
39
+ sys.exit(0)
40
+
41
+ tool_name = input_data.get("tool_name", "")
42
+ tool_input = input_data.get("tool_input", {})
43
+
44
+ # Only trigger on Write/Edit to state file
45
+ if tool_name not in ["Write", "Edit"]:
46
+ sys.exit(0)
47
+
48
+ file_path = tool_input.get("file_path", "")
49
+ if "api-dev-state.json" not in file_path:
50
+ sys.exit(0)
51
+
52
+ # Get current token usage
53
+ usage = get_token_usage()
54
+ if not usage:
55
+ sys.exit(0)
56
+
57
+ # Read state file
58
+ cwd = Path.cwd()
59
+ state_file = cwd / ".claude" / "api-dev-state.json"
60
+
61
+ if not state_file.exists():
62
+ sys.exit(0)
63
+
64
+ try:
65
+ state = json.loads(state_file.read_text())
66
+ except (json.JSONDecodeError, IOError):
67
+ sys.exit(0)
68
+
69
+ # Check for phase completion and log usage
70
+ phases = state.get("phases", {})
71
+ current_phase = None
72
+
73
+ for phase_key, phase_data in phases.items():
74
+ if isinstance(phase_data, dict):
75
+ status = phase_data.get("status", "")
76
+ if status == "complete":
77
+ current_phase = phase_key
78
+
79
+ if current_phase:
80
+ # Initialize token tracking in state if needed
81
+ if "token_usage" not in state:
82
+ state["token_usage"] = {
83
+ "by_phase": {},
84
+ "total_at_start": usage.get("total_tokens", 0),
85
+ "started_at": datetime.now().isoformat()
86
+ }
87
+
88
+ # Record phase completion tokens
89
+ state["token_usage"]["by_phase"][current_phase] = {
90
+ "total_tokens": usage.get("total_tokens", 0),
91
+ "total_cost": usage.get("total_cost", 0),
92
+ "timestamp": datetime.now().isoformat()
93
+ }
94
+
95
+ # Calculate phase delta if we have previous data
96
+ by_phase = state["token_usage"]["by_phase"]
97
+ phase_keys = list(by_phase.keys())
98
+
99
+ if len(phase_keys) >= 2:
100
+ prev_phase = phase_keys[-2]
101
+ prev_tokens = by_phase[prev_phase].get("total_tokens", 0)
102
+ current_tokens = usage.get("total_tokens", 0)
103
+ delta = current_tokens - prev_tokens
104
+
105
+ # Output phase token summary
106
+ print(f"\nšŸ“Š Phase '{current_phase}' Token Usage:", file=sys.stderr)
107
+ print(f" Phase tokens: {delta:,}", file=sys.stderr)
108
+ print(f" Total tokens: {current_tokens:,}", file=sys.stderr)
109
+ print(f" Total cost: ${usage.get('total_cost', 0):.2f}", file=sys.stderr)
110
+
111
+ # Update state file with token tracking
112
+ try:
113
+ state_file.write_text(json.dumps(state, indent=2))
114
+ except IOError:
115
+ pass
116
+
117
+ sys.exit(0)
118
+
119
+
120
+ if __name__ == "__main__":
121
+ main()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hustle-together/api-dev-tools",
3
- "version": "3.11.1",
4
- "description": "Interview-driven, research-first API development toolkit with 13-phase TDD workflow, enforcement hooks, and 23 Agent Skills for cross-platform AI agents",
3
+ "version": "3.12.2",
4
+ "description": "Interview-driven, research-first API development toolkit with 14-phase TDD workflow, enforcement hooks, and 23 Agent Skills for cross-platform AI agents",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
7
7
  "api-dev-tools": "./bin/cli.js"
@@ -20,7 +20,17 @@
20
20
  "CHANGELOG.md"
21
21
  ],
22
22
  "scripts": {
23
- "test": "node bin/cli.js --scope=project"
23
+ "test": "node bin/cli.js --scope=project",
24
+ "usage": "ccusage",
25
+ "format": "prettier --write .",
26
+ "lint": "eslint . --fix"
27
+ },
28
+ "devDependencies": {
29
+ "prettier": "^3.0.0",
30
+ "eslint": "^8.0.0"
31
+ },
32
+ "optionalDependencies": {
33
+ "ccusage": "^1.0.0"
24
34
  },
25
35
  "keywords": [
26
36
  "claude",