pgserve 0.1.1

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 (158) hide show
  1. package/.genie/AGENTS.md +13 -0
  2. package/.genie/agents/README.md +110 -0
  3. package/.genie/agents/analyze.md +176 -0
  4. package/.genie/agents/forge.md +290 -0
  5. package/.genie/agents/garbage-cleaner.md +324 -0
  6. package/.genie/agents/garbage-collector.md +596 -0
  7. package/.genie/agents/github-issue-gc.md +618 -0
  8. package/.genie/agents/review.md +380 -0
  9. package/.genie/agents/semantic-analyzer/find-duplicates.md +90 -0
  10. package/.genie/agents/semantic-analyzer/find-orphans.md +99 -0
  11. package/.genie/agents/semantic-analyzer.md +101 -0
  12. package/.genie/agents/update.md +182 -0
  13. package/.genie/agents/wish.md +357 -0
  14. package/.genie/code/AGENTS.md +692 -0
  15. package/.genie/code/agents/audit/risk.md +173 -0
  16. package/.genie/code/agents/audit/security.md +189 -0
  17. package/.genie/code/agents/audit.md +145 -0
  18. package/.genie/code/agents/challenge.md +230 -0
  19. package/.genie/code/agents/change-reviewer.md +295 -0
  20. package/.genie/code/agents/code-garbage-collector.md +425 -0
  21. package/.genie/code/agents/code-quality.md +410 -0
  22. package/.genie/code/agents/commit-suggester.md +255 -0
  23. package/.genie/code/agents/commit.md +124 -0
  24. package/.genie/code/agents/consensus.md +204 -0
  25. package/.genie/code/agents/daily-standup.md +722 -0
  26. package/.genie/code/agents/docgen.md +48 -0
  27. package/.genie/code/agents/explore.md +79 -0
  28. package/.genie/code/agents/fix.md +100 -0
  29. package/.genie/code/agents/git/commit-advisory.md +219 -0
  30. package/.genie/code/agents/git/workflows/issue.md +244 -0
  31. package/.genie/code/agents/git/workflows/pr.md +179 -0
  32. package/.genie/code/agents/git/workflows/release.md +460 -0
  33. package/.genie/code/agents/git/workflows/report.md +342 -0
  34. package/.genie/code/agents/git.md +432 -0
  35. package/.genie/code/agents/implementor.md +161 -0
  36. package/.genie/code/agents/install.md +515 -0
  37. package/.genie/code/agents/issue-creator.md +344 -0
  38. package/.genie/code/agents/polish.md +116 -0
  39. package/.genie/code/agents/qa.md +653 -0
  40. package/.genie/code/agents/refactor.md +294 -0
  41. package/.genie/code/agents/release.md +1129 -0
  42. package/.genie/code/agents/roadmap.md +885 -0
  43. package/.genie/code/agents/tests.md +557 -0
  44. package/.genie/code/agents/tracer.md +50 -0
  45. package/.genie/code/agents/update/upstream-update.md +85 -0
  46. package/.genie/code/agents/update/versions/generic-update.md +305 -0
  47. package/.genie/code/agents/vibe.md +1317 -0
  48. package/.genie/code/spells/agent-configuration.md +58 -0
  49. package/.genie/code/spells/automated-rc-publishing.md +106 -0
  50. package/.genie/code/spells/branch-tracker-guidance.md +28 -0
  51. package/.genie/code/spells/debug.md +320 -0
  52. package/.genie/code/spells/emoji-naming-convention.md +303 -0
  53. package/.genie/code/spells/evidence-storage.md +26 -0
  54. package/.genie/code/spells/file-naming-rules.md +35 -0
  55. package/.genie/code/spells/forge-code-blueprints.md +195 -0
  56. package/.genie/code/spells/genie-integration.md +153 -0
  57. package/.genie/code/spells/publishing-protocol.md +61 -0
  58. package/.genie/code/spells/team-consultation-protocol.md +284 -0
  59. package/.genie/code/spells/tool-requirements.md +20 -0
  60. package/.genie/code/spells/triad-maintenance-protocol.md +154 -0
  61. package/.genie/code/teams/tech-council/council.md +328 -0
  62. package/.genie/code/teams/tech-council/jt.md +352 -0
  63. package/.genie/code/teams/tech-council/nayr.md +305 -0
  64. package/.genie/code/teams/tech-council/oettam.md +375 -0
  65. package/.genie/neurons/README.md +193 -0
  66. package/.genie/neurons/forge.md +106 -0
  67. package/.genie/neurons/genie.md +63 -0
  68. package/.genie/neurons/review.md +106 -0
  69. package/.genie/neurons/wish.md +104 -0
  70. package/.genie/product/README.md +20 -0
  71. package/.genie/product/cli-automation.md +359 -0
  72. package/.genie/product/environment.md +60 -0
  73. package/.genie/product/mission.md +60 -0
  74. package/.genie/product/roadmap.md +44 -0
  75. package/.genie/product/tech-stack.md +34 -0
  76. package/.genie/product/templates/context-template.md +218 -0
  77. package/.genie/product/templates/qa-done-report-template.md +68 -0
  78. package/.genie/product/templates/review-report-template.md +89 -0
  79. package/.genie/product/templates/wish-template.md +120 -0
  80. package/.genie/scripts/helpers/analyze-commit.js +195 -0
  81. package/.genie/scripts/helpers/bullet-counter.js +194 -0
  82. package/.genie/scripts/helpers/bullet-find.js +289 -0
  83. package/.genie/scripts/helpers/bullet-id.js +244 -0
  84. package/.genie/scripts/helpers/check-secrets.js +237 -0
  85. package/.genie/scripts/helpers/count-tokens.js +200 -0
  86. package/.genie/scripts/helpers/create-frontmatter.js +456 -0
  87. package/.genie/scripts/helpers/detect-markers.js +293 -0
  88. package/.genie/scripts/helpers/detect-todos.js +267 -0
  89. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +135 -0
  90. package/.genie/scripts/helpers/embeddings.js +344 -0
  91. package/.genie/scripts/helpers/find-empty-sections.js +158 -0
  92. package/.genie/scripts/helpers/index.js +319 -0
  93. package/.genie/scripts/helpers/validate-frontmatter.js +578 -0
  94. package/.genie/scripts/helpers/validate-links.js +207 -0
  95. package/.genie/scripts/helpers/validate-paths.js +373 -0
  96. package/.genie/spells/README.md +9 -0
  97. package/.genie/spells/ace-protocol.md +118 -0
  98. package/.genie/spells/ask-one-at-a-time.md +175 -0
  99. package/.genie/spells/backup-analyzer.md +542 -0
  100. package/.genie/spells/blocker.md +12 -0
  101. package/.genie/spells/break-things-move-fast.md +56 -0
  102. package/.genie/spells/context-candidates.md +72 -0
  103. package/.genie/spells/context-critic.md +51 -0
  104. package/.genie/spells/defer-to-expertise.md +278 -0
  105. package/.genie/spells/delegate-dont-do.md +292 -0
  106. package/.genie/spells/error-investigation-protocol.md +328 -0
  107. package/.genie/spells/evidence-based-completion.md +273 -0
  108. package/.genie/spells/experiment.md +65 -0
  109. package/.genie/spells/file-creation-protocol.md +229 -0
  110. package/.genie/spells/forge-integration.md +281 -0
  111. package/.genie/spells/forge-orchestration.md +514 -0
  112. package/.genie/spells/gather-context.md +18 -0
  113. package/.genie/spells/global-health-check.md +34 -0
  114. package/.genie/spells/global-noop-roundtrip.md +25 -0
  115. package/.genie/spells/install-genie.md +1232 -0
  116. package/.genie/spells/install.md +82 -0
  117. package/.genie/spells/investigate-before-commit.md +112 -0
  118. package/.genie/spells/know-yourself.md +288 -0
  119. package/.genie/spells/learn.md +828 -0
  120. package/.genie/spells/mcp-diagnostic-protocol.md +246 -0
  121. package/.genie/spells/mcp-first.md +124 -0
  122. package/.genie/spells/multi-step-execution.md +67 -0
  123. package/.genie/spells/orchestration-boundary-protocol.md +256 -0
  124. package/.genie/spells/orchestrator-not-implementor.md +189 -0
  125. package/.genie/spells/prompt.md +746 -0
  126. package/.genie/spells/reflect.md +404 -0
  127. package/.genie/spells/routing-decision-matrix.md +368 -0
  128. package/.genie/spells/run-in-parallel.md +12 -0
  129. package/.genie/spells/session-state-updater-example.md +196 -0
  130. package/.genie/spells/session-state-updater.md +220 -0
  131. package/.genie/spells/track-long-running-tasks.md +133 -0
  132. package/.genie/spells/troubleshoot-infrastructure.md +176 -0
  133. package/.genie/spells/upgrade-genie.md +415 -0
  134. package/.genie/spells/url-presentation-protocol.md +301 -0
  135. package/.genie/spells/wish-initiation.md +158 -0
  136. package/.genie/spells/wish-issue-linkage.md +410 -0
  137. package/.genie/spells/wish-lifecycle.md +100 -0
  138. package/.genie/state/provider-status.json +3 -0
  139. package/.genie/state/version.json +16 -0
  140. package/AGENTS.md +422 -0
  141. package/CLAUDE.md +1 -0
  142. package/LICENSE +21 -0
  143. package/Makefile +235 -0
  144. package/README.md +323 -0
  145. package/bin/pglite-server.js +457 -0
  146. package/ecosystem.config.cjs +23 -0
  147. package/examples/multi-tenant-demo.js +104 -0
  148. package/package.json +47 -0
  149. package/src/detector.js +105 -0
  150. package/src/index.js +177 -0
  151. package/src/pool.js +320 -0
  152. package/src/ports.js +114 -0
  153. package/src/protocol.js +216 -0
  154. package/src/registry.js +134 -0
  155. package/src/router.js +289 -0
  156. package/src/server.js +265 -0
  157. package/tests/benchmarks/runner.js +489 -0
  158. package/tests/multi-tenant.test.js +201 -0
@@ -0,0 +1,237 @@
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();
@@ -0,0 +1,200 @@
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();