pgserve 2.1.2 → 2.2.0

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 (227) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +31 -6
  5. package/bin/postgres-server.js +80 -7
  6. package/console/README.md +131 -0
  7. package/console/api.js +173 -0
  8. package/console/app.jsx +483 -0
  9. package/console/colors_and_type.css +227 -0
  10. package/console/components.jsx +167 -0
  11. package/console/console.css +1666 -0
  12. package/console/data.jsx +350 -0
  13. package/console/index.html +31 -0
  14. package/console/screens/databases.jsx +5 -0
  15. package/console/screens/health.jsx +5 -0
  16. package/console/screens/ingress.jsx +5 -0
  17. package/console/screens/optimizer.jsx +5 -0
  18. package/console/screens/rlm-sim.jsx +5 -0
  19. package/console/screens/rlm-trace.jsx +5 -0
  20. package/console/screens/security.jsx +5 -0
  21. package/console/screens/settings.jsx +611 -0
  22. package/console/screens/sql.jsx +5 -0
  23. package/console/screens/sync.jsx +5 -0
  24. package/console/screens/tables.jsx +5 -0
  25. package/console/tweaks-panel.jsx +425 -0
  26. package/package.json +11 -1
  27. package/src/cli-config.cjs +310 -0
  28. package/src/cli-install.cjs +98 -11
  29. package/src/cli-restart.cjs +228 -0
  30. package/src/cli-ui.cjs +580 -0
  31. package/src/cluster.js +43 -38
  32. package/src/postgres.js +141 -19
  33. package/src/settings-loader.cjs +235 -0
  34. package/src/settings-migrate.cjs +212 -0
  35. package/src/settings-pg-args.cjs +146 -0
  36. package/src/settings-schema.cjs +422 -0
  37. package/src/settings-validator.cjs +416 -0
  38. package/src/settings-writer.cjs +288 -0
  39. package/.claude/context/windows-debug.md +0 -119
  40. package/.genie/AGENTS.md +0 -15
  41. package/.genie/agents/README.md +0 -110
  42. package/.genie/agents/analyze.md +0 -176
  43. package/.genie/agents/forge.md +0 -290
  44. package/.genie/agents/garbage-cleaner.md +0 -324
  45. package/.genie/agents/garbage-collector.md +0 -596
  46. package/.genie/agents/github-issue-gc.md +0 -618
  47. package/.genie/agents/review.md +0 -380
  48. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  49. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  50. package/.genie/agents/semantic-analyzer.md +0 -101
  51. package/.genie/agents/update.md +0 -182
  52. package/.genie/agents/wish.md +0 -357
  53. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  54. package/.genie/code/AGENTS.md +0 -694
  55. package/.genie/code/agents/audit/risk.md +0 -173
  56. package/.genie/code/agents/audit/security.md +0 -189
  57. package/.genie/code/agents/audit.md +0 -145
  58. package/.genie/code/agents/challenge.md +0 -230
  59. package/.genie/code/agents/change-reviewer.md +0 -295
  60. package/.genie/code/agents/code-garbage-collector.md +0 -425
  61. package/.genie/code/agents/code-quality.md +0 -410
  62. package/.genie/code/agents/commit-suggester.md +0 -255
  63. package/.genie/code/agents/commit.md +0 -124
  64. package/.genie/code/agents/consensus.md +0 -204
  65. package/.genie/code/agents/daily-standup.md +0 -722
  66. package/.genie/code/agents/docgen.md +0 -48
  67. package/.genie/code/agents/explore.md +0 -79
  68. package/.genie/code/agents/fix.md +0 -100
  69. package/.genie/code/agents/git/commit-advisory.md +0 -219
  70. package/.genie/code/agents/git/workflows/issue.md +0 -244
  71. package/.genie/code/agents/git/workflows/pr.md +0 -179
  72. package/.genie/code/agents/git/workflows/release.md +0 -460
  73. package/.genie/code/agents/git/workflows/report.md +0 -342
  74. package/.genie/code/agents/git.md +0 -432
  75. package/.genie/code/agents/implementor.md +0 -161
  76. package/.genie/code/agents/install.md +0 -515
  77. package/.genie/code/agents/issue-creator.md +0 -344
  78. package/.genie/code/agents/polish.md +0 -116
  79. package/.genie/code/agents/qa.md +0 -653
  80. package/.genie/code/agents/refactor.md +0 -294
  81. package/.genie/code/agents/release.md +0 -1129
  82. package/.genie/code/agents/roadmap.md +0 -885
  83. package/.genie/code/agents/tests.md +0 -557
  84. package/.genie/code/agents/tracer.md +0 -50
  85. package/.genie/code/agents/update/upstream-update.md +0 -85
  86. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  87. package/.genie/code/agents/vibe.md +0 -1317
  88. package/.genie/code/spells/agent-configuration.md +0 -58
  89. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  90. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  91. package/.genie/code/spells/debug.md +0 -320
  92. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  93. package/.genie/code/spells/evidence-storage.md +0 -26
  94. package/.genie/code/spells/file-naming-rules.md +0 -35
  95. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  96. package/.genie/code/spells/genie-integration.md +0 -153
  97. package/.genie/code/spells/publishing-protocol.md +0 -61
  98. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  99. package/.genie/code/spells/tool-requirements.md +0 -20
  100. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  101. package/.genie/code/teams/tech-council/council.md +0 -328
  102. package/.genie/code/teams/tech-council/jt.md +0 -352
  103. package/.genie/code/teams/tech-council/nayr.md +0 -305
  104. package/.genie/code/teams/tech-council/oettam.md +0 -375
  105. package/.genie/neurons/README.md +0 -193
  106. package/.genie/neurons/forge.md +0 -106
  107. package/.genie/neurons/genie.md +0 -63
  108. package/.genie/neurons/review.md +0 -106
  109. package/.genie/neurons/wish.md +0 -104
  110. package/.genie/product/README.md +0 -20
  111. package/.genie/product/cli-automation.md +0 -359
  112. package/.genie/product/environment.md +0 -60
  113. package/.genie/product/mission.md +0 -60
  114. package/.genie/product/roadmap.md +0 -44
  115. package/.genie/product/tech-stack.md +0 -34
  116. package/.genie/product/templates/context-template.md +0 -218
  117. package/.genie/product/templates/qa-done-report-template.md +0 -68
  118. package/.genie/product/templates/review-report-template.md +0 -89
  119. package/.genie/product/templates/wish-template.md +0 -120
  120. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  121. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  122. package/.genie/scripts/helpers/bullet-find.js +0 -289
  123. package/.genie/scripts/helpers/bullet-id.js +0 -244
  124. package/.genie/scripts/helpers/check-secrets.js +0 -237
  125. package/.genie/scripts/helpers/count-tokens.js +0 -200
  126. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  127. package/.genie/scripts/helpers/detect-markers.js +0 -293
  128. package/.genie/scripts/helpers/detect-todos.js +0 -267
  129. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  130. package/.genie/scripts/helpers/embeddings.js +0 -344
  131. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  132. package/.genie/scripts/helpers/index.js +0 -319
  133. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  134. package/.genie/scripts/helpers/validate-links.js +0 -207
  135. package/.genie/scripts/helpers/validate-paths.js +0 -373
  136. package/.genie/spells/README.md +0 -9
  137. package/.genie/spells/ace-protocol.md +0 -118
  138. package/.genie/spells/ask-one-at-a-time.md +0 -175
  139. package/.genie/spells/backup-analyzer.md +0 -542
  140. package/.genie/spells/blocker.md +0 -12
  141. package/.genie/spells/break-things-move-fast.md +0 -56
  142. package/.genie/spells/context-candidates.md +0 -72
  143. package/.genie/spells/context-critic.md +0 -51
  144. package/.genie/spells/defer-to-expertise.md +0 -278
  145. package/.genie/spells/delegate-dont-do.md +0 -292
  146. package/.genie/spells/error-investigation-protocol.md +0 -328
  147. package/.genie/spells/evidence-based-completion.md +0 -273
  148. package/.genie/spells/experiment.md +0 -65
  149. package/.genie/spells/file-creation-protocol.md +0 -229
  150. package/.genie/spells/forge-integration.md +0 -281
  151. package/.genie/spells/forge-orchestration.md +0 -514
  152. package/.genie/spells/gather-context.md +0 -18
  153. package/.genie/spells/global-health-check.md +0 -34
  154. package/.genie/spells/global-noop-roundtrip.md +0 -25
  155. package/.genie/spells/install-genie.md +0 -1232
  156. package/.genie/spells/install.md +0 -82
  157. package/.genie/spells/investigate-before-commit.md +0 -112
  158. package/.genie/spells/know-yourself.md +0 -288
  159. package/.genie/spells/learn.md +0 -828
  160. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  161. package/.genie/spells/mcp-first.md +0 -124
  162. package/.genie/spells/multi-step-execution.md +0 -67
  163. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  164. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  165. package/.genie/spells/prompt.md +0 -746
  166. package/.genie/spells/reflect.md +0 -404
  167. package/.genie/spells/routing-decision-matrix.md +0 -368
  168. package/.genie/spells/run-in-parallel.md +0 -12
  169. package/.genie/spells/session-state-updater-example.md +0 -196
  170. package/.genie/spells/session-state-updater.md +0 -220
  171. package/.genie/spells/track-long-running-tasks.md +0 -133
  172. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  173. package/.genie/spells/upgrade-genie.md +0 -415
  174. package/.genie/spells/url-presentation-protocol.md +0 -301
  175. package/.genie/spells/wish-initiation.md +0 -158
  176. package/.genie/spells/wish-issue-linkage.md +0 -410
  177. package/.genie/spells/wish-lifecycle.md +0 -100
  178. package/.genie/state/provider-status.json +0 -3
  179. package/.genie/state/version.json +0 -16
  180. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  181. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  182. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  183. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  184. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  185. package/.gitguardian.yaml +0 -29
  186. package/.gitguardianignore +0 -16
  187. package/.github/workflows/ci.yml +0 -122
  188. package/.github/workflows/release.yml +0 -289
  189. package/.github/workflows/version.yml +0 -228
  190. package/.husky/pre-commit +0 -2
  191. package/AGENTS.md +0 -433
  192. package/CLAUDE.md +0 -1
  193. package/Makefile +0 -285
  194. package/assets/icon.ico +0 -0
  195. package/bun.lock +0 -435
  196. package/bunfig.toml +0 -28
  197. package/ecosystem.config.cjs +0 -23
  198. package/eslint.config.js +0 -63
  199. package/examples/multi-tenant-demo.js +0 -104
  200. package/install.sh +0 -123
  201. package/knip.json +0 -9
  202. package/scripts/test-bun-self-heal.sh +0 -163
  203. package/scripts/test-npx.sh +0 -60
  204. package/tests/audit.test.js +0 -189
  205. package/tests/backpressure.test.js +0 -167
  206. package/tests/benchmarks/runner.js +0 -1197
  207. package/tests/benchmarks/vector-generator.js +0 -368
  208. package/tests/cli-install.test.js +0 -322
  209. package/tests/control-db.test.js +0 -285
  210. package/tests/daemon-control.test.js +0 -171
  211. package/tests/daemon-fingerprint-integration.test.js +0 -111
  212. package/tests/daemon-pr24-regression.test.js +0 -198
  213. package/tests/fingerprint.test.js +0 -263
  214. package/tests/fixtures/240-orphan-seed.sql +0 -30
  215. package/tests/multi-tenant.test.js +0 -374
  216. package/tests/orphan-cleanup.test.js +0 -390
  217. package/tests/pg-version-regex.test.js +0 -129
  218. package/tests/quick-bench.js +0 -135
  219. package/tests/router-handshake-retry.test.js +0 -119
  220. package/tests/router-handshake-watchdog.test.js +0 -110
  221. package/tests/sdk.test.js +0 -71
  222. package/tests/stale-postmaster-pid.test.js +0 -85
  223. package/tests/stress-test.js +0 -439
  224. package/tests/sync-perf-test.js +0 -150
  225. package/tests/tcp-listen.test.js +0 -368
  226. package/tests/tenancy.test.js +0 -403
  227. package/tests/wrapper-supervision.test.js +0 -107
@@ -1,237 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Check Secrets Helper
5
- *
6
- * Fast, deterministic secret detection using regex patterns.
7
- * No LLM needed - pure pattern matching.
8
- *
9
- * Usage:
10
- * genie helper check-secrets [files...]
11
- * genie helper check-secrets --staged (check git staged files)
12
- *
13
- * Exit codes:
14
- * 0 - No secrets found
15
- * 1 - Secrets detected
16
- */
17
-
18
- const { execSync } = require('child_process');
19
- const fs = require('fs');
20
- const path = require('path');
21
-
22
- // Secret patterns (regex)
23
- const SECRET_PATTERNS = [
24
- // API Keys
25
- { pattern: /['"]?[A-Z_]{3,}API[_-]?KEY['"]?\s*[:=]\s*['"][^'"]{20,}['"]/, name: 'API Key', severity: 'critical' },
26
- { pattern: /AKIA[0-9A-Z]{16}/, name: 'AWS Access Key', severity: 'critical' },
27
-
28
- // Private Keys
29
- { pattern: /-----BEGIN (RSA |DSA |EC )?PRIVATE KEY-----/, name: 'Private Key', severity: 'critical' },
30
- { pattern: /-----BEGIN OPENSSH PRIVATE KEY-----/, name: 'SSH Private Key', severity: 'critical' },
31
-
32
- // Tokens
33
- { pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/, name: 'GitHub Token', severity: 'critical' },
34
- { pattern: /glpat-[A-Za-z0-9_-]{20,}/, name: 'GitLab Token', severity: 'critical' },
35
- { pattern: /sk-[A-Za-z0-9]{20,}/, name: 'OpenAI API Key', severity: 'critical' },
36
- { pattern: /AIza[0-9A-Za-z_-]{35}/, name: 'Google API Key', severity: 'high' },
37
-
38
- // Credentials
39
- { pattern: /['"]?password['"]?\s*[:=]\s*['"][^'"]{8,}['"]/, name: 'Password in Code', severity: 'high' },
40
- { pattern: /['"]?secret['"]?\s*[:=]\s*['"][^'"]{8,}['"]/, name: 'Secret in Code', severity: 'high' },
41
- { pattern: /['"]?token['"]?\s*[:=]\s*['"][^'"]{16,}['"]/, name: 'Token in Code', severity: 'medium' },
42
-
43
- // Database URLs
44
- { pattern: /postgresql:\/\/[^:]+:[^@]+@/, name: 'PostgreSQL Connection String', severity: 'high' },
45
- { pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@/, name: 'MongoDB Connection String', severity: 'high' },
46
-
47
- // Cryptocurrency
48
- { pattern: /0x[a-fA-F0-9]{40}/, name: 'Ethereum Address', severity: 'medium' },
49
- { pattern: /[13][a-km-zA-HJ-NP-Z1-9]{25,34}/, name: 'Bitcoin Address', severity: 'medium' },
50
- ];
51
-
52
- // Files to always skip (even if staged)
53
- const SKIP_FILES = [
54
- '.genie/scripts/helpers/check-secrets.js', // This file (contains patterns)
55
- 'package-lock.json',
56
- 'pnpm-lock.yaml',
57
- 'yarn.lock',
58
- '.git/',
59
- 'node_modules/',
60
- '.genie/state/',
61
- '.genie/backups/',
62
- ];
63
-
64
- // Load .secretsignore file if it exists
65
- function loadSecretsIgnore() {
66
- const ignorePath = path.join(process.cwd(), '.genie', '.secretsignore');
67
- if (!fs.existsSync(ignorePath)) {
68
- return [];
69
- }
70
-
71
- try {
72
- const content = fs.readFileSync(ignorePath, 'utf8');
73
- return content
74
- .split('\n')
75
- .map(line => line.trim())
76
- .filter(line => line && !line.startsWith('#'))
77
- .map(line => {
78
- // Parse "file:line" format
79
- const parts = line.split(':');
80
- return {
81
- file: parts[0],
82
- line: parts[1] ? parseInt(parts[1]) : null
83
- };
84
- });
85
- } catch (e) {
86
- return [];
87
- }
88
- }
89
-
90
- function shouldSkipFile(file) {
91
- return SKIP_FILES.some(skip => file.includes(skip));
92
- }
93
-
94
- function isIgnoredFinding(finding, ignoreList) {
95
- return ignoreList.some(ignore => {
96
- const fileMatches = finding.file === ignore.file || finding.file.endsWith(ignore.file);
97
- const lineMatches = ignore.line === null || ignore.line === finding.line;
98
- return fileMatches && lineMatches;
99
- });
100
- }
101
-
102
- function getStagedFiles() {
103
- try {
104
- const files = execSync('git diff --cached --name-only --diff-filter=ACM', {
105
- encoding: 'utf8',
106
- stdio: ['pipe', 'pipe', 'pipe']
107
- }).trim().split('\n').filter(Boolean);
108
-
109
- return files.filter(f => !shouldSkipFile(f) && fs.existsSync(f));
110
- } catch (e) {
111
- return [];
112
- }
113
- }
114
-
115
- function checkFile(filePath) {
116
- const findings = [];
117
-
118
- try {
119
- const content = fs.readFileSync(filePath, 'utf8');
120
- const lines = content.split('\n');
121
-
122
- lines.forEach((line, lineNum) => {
123
- SECRET_PATTERNS.forEach(({ pattern, name, severity }) => {
124
- if (pattern.test(line)) {
125
- findings.push({
126
- file: filePath,
127
- line: lineNum + 1,
128
- pattern: name,
129
- severity,
130
- snippet: line.trim().substring(0, 80)
131
- });
132
- }
133
- });
134
- });
135
- } catch (e) {
136
- // Skip files that can't be read (binary, etc)
137
- }
138
-
139
- return findings;
140
- }
141
-
142
- function formatFindings(findings) {
143
- if (findings.length === 0) {
144
- return '✅ No secrets detected';
145
- }
146
-
147
- const critical = findings.filter(f => f.severity === 'critical');
148
- const high = findings.filter(f => f.severity === 'high');
149
- const medium = findings.filter(f => f.severity === 'medium');
150
-
151
- let report = [];
152
- report.push('');
153
- report.push('❌ SECRETS DETECTED IN STAGED FILES');
154
- report.push('━'.repeat(60));
155
- report.push('');
156
-
157
- if (critical.length > 0) {
158
- report.push('🔴 CRITICAL (blocks commit):');
159
- critical.forEach(f => {
160
- report.push(` ${f.file}:${f.line}`);
161
- report.push(` Pattern: ${f.pattern}`);
162
- report.push(` Snippet: ${f.snippet}`);
163
- report.push('');
164
- });
165
- }
166
-
167
- if (high.length > 0) {
168
- report.push('🟠 HIGH (blocks commit):');
169
- high.forEach(f => {
170
- report.push(` ${f.file}:${f.line}`);
171
- report.push(` Pattern: ${f.pattern}`);
172
- report.push('');
173
- });
174
- }
175
-
176
- if (medium.length > 0) {
177
- report.push('🟡 MEDIUM (warning):');
178
- medium.forEach(f => {
179
- report.push(` ${f.file}:${f.line} - ${f.pattern}`);
180
- });
181
- report.push('');
182
- }
183
-
184
- report.push('━'.repeat(60));
185
- report.push('');
186
- report.push('🔧 How to fix:');
187
- report.push(' 1. Remove secrets from code');
188
- report.push(' 2. Use environment variables instead (.env files)');
189
- report.push(' 3. Add .env* to .gitignore');
190
- report.push(' 4. If false positive, add to .genie/.secretsignore');
191
- report.push('');
192
-
193
- return report.join('\n');
194
- }
195
-
196
- function main() {
197
- const args = process.argv.slice(2);
198
-
199
- let files = [];
200
-
201
- if (args.includes('--staged') || args.length === 0) {
202
- // Default: check staged files
203
- files = getStagedFiles();
204
- } else {
205
- // Check specific files
206
- files = args.filter(f => fs.existsSync(f) && !shouldSkipFile(f));
207
- }
208
-
209
- if (files.length === 0) {
210
- console.log('✅ No files to check');
211
- process.exit(0);
212
- }
213
-
214
- // Load ignore list
215
- const ignoreList = loadSecretsIgnore();
216
-
217
- let allFindings = [];
218
- files.forEach(file => {
219
- const findings = checkFile(file);
220
- allFindings = allFindings.concat(findings);
221
- });
222
-
223
- // Filter out ignored findings
224
- const filteredFindings = allFindings.filter(f => !isIgnoredFinding(f, ignoreList));
225
-
226
- console.log(formatFindings(filteredFindings));
227
-
228
- // Block on critical or high severity
229
- const blocking = filteredFindings.filter(f => f.severity === 'critical' || f.severity === 'high');
230
- if (blocking.length > 0) {
231
- process.exit(1);
232
- }
233
-
234
- process.exit(0);
235
- }
236
-
237
- main();
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Token Counter Helper
5
- *
6
- * Count tokens in files or text using tiktoken (cl100k_base encoding).
7
- * This is the ONLY way to count tokens in the Genie framework.
8
- *
9
- * Usage:
10
- * node count-tokens.js <file-path> # Output: just token count number
11
- * node count-tokens.js <file-path> --json # Output: JSON with metadata
12
- * echo "some text" | node count-tokens.js # Output: just token count
13
- * node count-tokens.js --before=file --after=file # Compare (always JSON)
14
- *
15
- * Output (default):
16
- * Plain number: 1234
17
- *
18
- * Output (--json):
19
- * JSON: { tokens: N, lines: N, bytes: N, encoding: "cl100k_base" }
20
- */
21
-
22
- const fs = require('fs');
23
- const path = require('path');
24
-
25
- /**
26
- * Count tokens using tiktoken
27
- */
28
- function countTokens(text) {
29
- try {
30
- const { getEncoding } = require('js-tiktoken');
31
- const encName = process.env.TOKEN_ENCODING || 'cl100k_base';
32
- const encoder = getEncoding(encName);
33
- const tokens = encoder.encode(text).length;
34
- try { encoder.free && encoder.free(); } catch {}
35
- return { tokens, encoding: encName, method: 'tiktoken' };
36
- } catch (err) {
37
- // Fallback to word count approximation (should not happen if tiktoken installed)
38
- console.error(`Warning: tiktoken failed, using word count fallback: ${err.message}`);
39
- const approx = (text || '').trim().split(/\s+/).filter(Boolean).length;
40
- return { tokens: approx, encoding: 'approx-words', method: 'fallback' };
41
- }
42
- }
43
-
44
- /**
45
- * Get file stats (lines, bytes)
46
- */
47
- function getStats(text) {
48
- const lines = (text.match(/\n/g) || []).length + 1;
49
- const bytes = Buffer.byteLength(text, 'utf8');
50
- return { lines, bytes };
51
- }
52
-
53
- /**
54
- * Count tokens in file
55
- */
56
- function countFile(filePath) {
57
- try {
58
- const content = fs.readFileSync(filePath, 'utf-8');
59
- const { tokens, encoding, method } = countTokens(content);
60
- const { lines, bytes } = getStats(content);
61
-
62
- return {
63
- file: path.basename(filePath),
64
- path: filePath,
65
- tokens,
66
- lines,
67
- bytes,
68
- encoding,
69
- method,
70
- };
71
- } catch (err) {
72
- return {
73
- error: err.message,
74
- file: filePath,
75
- };
76
- }
77
- }
78
-
79
- /**
80
- * Compare before/after token counts
81
- */
82
- function compareFiles(beforePath, afterPath) {
83
- const before = countFile(beforePath);
84
- const after = countFile(afterPath);
85
-
86
- if (before.error || after.error) {
87
- return { error: 'Failed to read files' };
88
- }
89
-
90
- const diff = after.tokens - before.tokens;
91
- const percentChange = ((diff / before.tokens) * 100).toFixed(1);
92
- const saved = diff < 0;
93
-
94
- return {
95
- before: {
96
- file: before.file,
97
- tokens: before.tokens,
98
- lines: before.lines,
99
- bytes: before.bytes,
100
- },
101
- after: {
102
- file: after.file,
103
- tokens: after.tokens,
104
- lines: after.lines,
105
- bytes: after.bytes,
106
- },
107
- diff: {
108
- tokens: diff,
109
- percent: percentChange,
110
- saved: saved,
111
- message: saved
112
- ? `Saved ${Math.abs(diff)} tokens (${Math.abs(percentChange)}% reduction)`
113
- : `Added ${diff} tokens (${percentChange}% increase)`,
114
- },
115
- encoding: before.encoding,
116
- };
117
- }
118
-
119
- /**
120
- * Main
121
- */
122
- function main() {
123
- const args = process.argv.slice(2);
124
- const jsonMode = args.includes('--json');
125
- const fileArgs = args.filter(a => !a.startsWith('--'));
126
-
127
- // Check for comparison mode (--before=file --after=file)
128
- const beforeArg = args.find(a => a.startsWith('--before='));
129
- const afterArg = args.find(a => a.startsWith('--after='));
130
-
131
- if (beforeArg && afterArg) {
132
- const beforePath = beforeArg.split('=')[1];
133
- const afterPath = afterArg.split('=')[1];
134
- const result = compareFiles(beforePath, afterPath);
135
- console.log(JSON.stringify(result, null, 2));
136
- process.exit(result.error ? 1 : 0);
137
- }
138
-
139
- // Single file mode
140
- if (fileArgs.length > 0) {
141
- const filePath = fileArgs[0];
142
- if (!fs.existsSync(filePath)) {
143
- console.error(jsonMode
144
- ? JSON.stringify({ error: `File not found: ${filePath}` })
145
- : `Error: File not found: ${filePath}`);
146
- process.exit(1);
147
- }
148
- const result = countFile(filePath);
149
- if (result.error) {
150
- console.error(jsonMode ? JSON.stringify(result) : `Error: ${result.error}`);
151
- process.exit(1);
152
- }
153
- console.log(jsonMode ? JSON.stringify(result, null, 2) : result.tokens.toString());
154
- process.exit(0);
155
- }
156
-
157
- // Stdin mode
158
- if (!process.stdin.isTTY) {
159
- let input = '';
160
- process.stdin.setEncoding('utf-8');
161
- process.stdin.on('data', chunk => input += chunk);
162
- process.stdin.on('end', () => {
163
- const { tokens, encoding, method } = countTokens(input);
164
- const { lines, bytes } = getStats(input);
165
- if (jsonMode) {
166
- console.log(JSON.stringify({
167
- tokens,
168
- lines,
169
- bytes,
170
- encoding,
171
- method,
172
- }, null, 2));
173
- } else {
174
- console.log(tokens.toString());
175
- }
176
- });
177
- return;
178
- }
179
-
180
- // No input, show usage
181
- console.error(`
182
- Usage:
183
- node count-tokens.js <file-path> # Output: just token count (number)
184
- node count-tokens.js <file-path> --json # Output: JSON with metadata
185
- echo "text" | node count-tokens.js # Output: just token count
186
- node count-tokens.js --before=old.md --after=new.md # Compare files (always JSON)
187
-
188
- Output (default): Plain number (e.g., "1234")
189
- Output (--json): JSON with token count, lines, bytes, encoding
190
-
191
- Examples:
192
- node count-tokens.js README.md # 1234
193
- node count-tokens.js README.md --json # { "tokens": 1234, ... }
194
- node count-tokens.js --before=before.md --after=after.md
195
- cat myfile.md | node count-tokens.js
196
- `);
197
- process.exit(1);
198
- }
199
-
200
- main();