json-humanized 2.0.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.
- package/LICENSE +21 -0
- package/README.md +351 -0
- package/bin/cli.js +319 -0
- package/docs/ARCHITECTURE.md +139 -0
- package/docs/DEMO.html +461 -0
- package/docs/PUBLISHING.md +124 -0
- package/examples/api-response.json +42 -0
- package/examples/demo.js +50 -0
- package/examples/user-profile.json +36 -0
- package/index.d.ts +138 -0
- package/package.json +71 -0
- package/src/cache.js +172 -0
- package/src/config.js +259 -0
- package/src/diff.js +284 -0
- package/src/formatters/index.js +113 -0
- package/src/formatters/template.js +132 -0
- package/src/humanizer.js +307 -0
- package/src/index.js +157 -0
- package/src/parsers/index.js +119 -0
- package/src/strategies/ai.js +108 -0
- package/src/strategies/ollama.js +135 -0
- package/src/strategies/openai.js +82 -0
- package/src/watch.js +133 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 json-humanized contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# json-humanized
|
|
4
|
+
|
|
5
|
+
**Transform any JSON / YAML / TOML into natural human language**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/json-humanized)
|
|
8
|
+
[](https://github.com/AceAnomDev/json-humanized/actions)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](package.json)
|
|
11
|
+
[](https://www.npmjs.com/package/json-humanized)
|
|
12
|
+
|
|
13
|
+
**[Live Demo](https://aceanomdev.github.io/json-humanized)** · [Installation](#installation) · [Usage](#usage) · [API](#api) · [Config](#config-file)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ jh user.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
This JSON contains a structured object with 6 fields.
|
|
27
|
+
• Identifier: usr_8f3k2
|
|
28
|
+
• Name: "Alice Johnson"
|
|
29
|
+
• Email address: alice@example.com
|
|
30
|
+
• Age: 28 years old
|
|
31
|
+
• Password: *** (hidden for security)
|
|
32
|
+
• Balance: $4.3K
|
|
33
|
+
• Created: March 15, 2024 at 10:30 AM
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Works **100% offline** (no API key needed) — or plug in Claude AI / OpenAI / Ollama for smarter descriptions.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g json-humanized
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or use without installing:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx json-humanized data.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### CLI
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Basic — local engine (offline, instant)
|
|
60
|
+
jh data.json
|
|
61
|
+
|
|
62
|
+
# YAML and TOML also work
|
|
63
|
+
jh config.yaml
|
|
64
|
+
jh settings.toml
|
|
65
|
+
|
|
66
|
+
# AI-powered (Claude)
|
|
67
|
+
jh data.json --engine ai
|
|
68
|
+
|
|
69
|
+
# AI with OpenAI
|
|
70
|
+
jh data.json --engine ai --provider openai
|
|
71
|
+
|
|
72
|
+
# AI with local Ollama (no API key!)
|
|
73
|
+
jh data.json --engine ai --provider ollama
|
|
74
|
+
|
|
75
|
+
# Output formats
|
|
76
|
+
jh data.json --format markdown --output report.md
|
|
77
|
+
jh data.json --format story
|
|
78
|
+
jh data.json --format json
|
|
79
|
+
|
|
80
|
+
# Diff two files
|
|
81
|
+
jh v1.json --diff v2.json
|
|
82
|
+
jh v1.json --diff v2.json --format markdown
|
|
83
|
+
|
|
84
|
+
# Watch mode — re-runs on every save
|
|
85
|
+
jh data.json --watch
|
|
86
|
+
|
|
87
|
+
# Stdin
|
|
88
|
+
echo '{"name":"Alice","age":30}' | jh --stdin
|
|
89
|
+
|
|
90
|
+
# Custom Handlebars template
|
|
91
|
+
jh data.json --template ./report.hbs
|
|
92
|
+
|
|
93
|
+
# Limit JSON chars sent to AI (saves tokens)
|
|
94
|
+
jh huge.json --engine ai --max-chars 8000
|
|
95
|
+
|
|
96
|
+
# Cache management
|
|
97
|
+
jh --cache-stats
|
|
98
|
+
jh --cache-clear
|
|
99
|
+
|
|
100
|
+
# Init config/template files
|
|
101
|
+
jh --init-config # creates .jh.config.json
|
|
102
|
+
jh --init-template # creates template.hbs
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### All CLI flags
|
|
106
|
+
|
|
107
|
+
| Flag | Default | Description |
|
|
108
|
+
|------|---------|-------------|
|
|
109
|
+
| `--engine <engine>` | `local` | `local` or `ai` |
|
|
110
|
+
| `--provider <provider>` | `anthropic` | `anthropic`, `openai`, `ollama` |
|
|
111
|
+
| `--format <format>` | `plain` | `plain`, `markdown`, `story`, `json` |
|
|
112
|
+
| `--mode <mode>` | `structured` | `structured`, `prose`, `story` |
|
|
113
|
+
| `--lang <lang>` | `English` | Any language (AI only) |
|
|
114
|
+
| `--context <text>` | — | Context hint for AI |
|
|
115
|
+
| `--api-key <key>` | env var | API key override |
|
|
116
|
+
| `--max-chars <n>` | `12000` | Max chars sent to AI |
|
|
117
|
+
| `--output <file>` | stdout | Save output to file |
|
|
118
|
+
| `--template <file>` | — | Handlebars `.hbs` template |
|
|
119
|
+
| `--diff <fileB>` | — | Compare two files |
|
|
120
|
+
| `--watch` | — | Re-run on file changes |
|
|
121
|
+
| `--config <file>` | auto | Explicit config file path |
|
|
122
|
+
| `--cache` / `--no-cache` | `true` | Enable/disable AI caching |
|
|
123
|
+
| `--cache-clear` | — | Delete all cached responses |
|
|
124
|
+
| `--cache-stats` | — | Show cache info |
|
|
125
|
+
| `--init-config` | — | Generate sample config |
|
|
126
|
+
| `--init-template` | — | Generate sample template |
|
|
127
|
+
| `--stdin` | — | Read from stdin |
|
|
128
|
+
| `--silent` | — | No spinner, no banner |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## API
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
const { humanize, humanizeFile, humanizeString } = require('json-humanized');
|
|
136
|
+
|
|
137
|
+
// Parse a JS value
|
|
138
|
+
const text = await humanize({ name: 'Alice', age: 30 });
|
|
139
|
+
|
|
140
|
+
// From a file (JSON, YAML, or TOML)
|
|
141
|
+
const text = await humanizeFile('./users.yaml', { format: 'markdown' });
|
|
142
|
+
|
|
143
|
+
// From a raw string
|
|
144
|
+
const text = await humanizeString('{"key":"value"}');
|
|
145
|
+
|
|
146
|
+
// AI mode with caching
|
|
147
|
+
const text = await humanize(data, {
|
|
148
|
+
engine: 'ai',
|
|
149
|
+
aiProvider: 'anthropic', // or 'openai' | 'ollama'
|
|
150
|
+
format: 'plain',
|
|
151
|
+
lang: 'Russian',
|
|
152
|
+
cache: true,
|
|
153
|
+
cacheTTL: 3600,
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Diff
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
const { diff } = require('json-humanized');
|
|
161
|
+
|
|
162
|
+
// Compare two objects
|
|
163
|
+
const result = await diff.diff(before, after);
|
|
164
|
+
// "Found 3 differences: …"
|
|
165
|
+
|
|
166
|
+
// Compare two files
|
|
167
|
+
const result = await diff.diffFiles('v1.json', 'v2.json', { format: 'markdown' });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### TypeScript
|
|
171
|
+
|
|
172
|
+
Full types are included — no `@types/` package needed:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { humanize, humanizeFile, HumanizeOptions } from 'json-humanized';
|
|
176
|
+
|
|
177
|
+
const opts: HumanizeOptions = { engine: 'ai', aiProvider: 'ollama', format: 'markdown' };
|
|
178
|
+
const text = await humanizeFile('./data.json', opts);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Config file
|
|
184
|
+
|
|
185
|
+
Create `.jh.config.json` (or run `jh --init-config`) to set defaults:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"engine": "local",
|
|
190
|
+
"format": "plain",
|
|
191
|
+
"lang": "English",
|
|
192
|
+
"maxChars": 12000,
|
|
193
|
+
"cache": true,
|
|
194
|
+
"cacheTTL": 3600,
|
|
195
|
+
|
|
196
|
+
"fieldLabels": {
|
|
197
|
+
"user_id": "User ID",
|
|
198
|
+
"txn_ref": "Transaction reference",
|
|
199
|
+
"internal_*": null
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
"fieldTypes": {
|
|
203
|
+
"invoice_no": "id",
|
|
204
|
+
"balance": "money"
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
"hiddenFields": ["debug_*", "internal_hash"],
|
|
208
|
+
|
|
209
|
+
"aiProvider": "anthropic",
|
|
210
|
+
"ollamaUrl": "http://localhost:11434",
|
|
211
|
+
"ollamaModel": "llama3",
|
|
212
|
+
"openaiModel": "gpt-4o-mini"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Config is searched upward from the current directory (like ESLint / Prettier).
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Custom templates
|
|
221
|
+
|
|
222
|
+
Create a Handlebars template (or run `jh --init-template`):
|
|
223
|
+
|
|
224
|
+
```hbs
|
|
225
|
+
# {{filename}}
|
|
226
|
+
> Generated: {{timestamp}} · Engine: {{engine}}
|
|
227
|
+
|
|
228
|
+
{{humanized}}
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
*Type: {{stats.type}}, Keys: {{stats.keys}}*
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Use it with:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
jh data.json --template ./report.hbs
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Available template variables: `{{humanized}}`, `{{filename}}`, `{{engine}}`, `{{format}}`, `{{timestamp}}`, `{{stats.type}}`, `{{stats.keys}}`, `{{stats.items}}`
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## AI Providers
|
|
245
|
+
|
|
246
|
+
| Provider | Env Variable | Install |
|
|
247
|
+
|----------|-------------|---------|
|
|
248
|
+
| `anthropic` (default) | `ANTHROPIC_API_KEY` | `npm install @anthropic-ai/sdk` |
|
|
249
|
+
| `openai` | `OPENAI_API_KEY` | `npm install openai` |
|
|
250
|
+
| `ollama` | none needed | [Install Ollama](https://ollama.ai) |
|
|
251
|
+
|
|
252
|
+
Optional dependencies — install only what you use.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Caching
|
|
257
|
+
|
|
258
|
+
AI responses are cached by default in `~/.jh-cache/`.
|
|
259
|
+
|
|
260
|
+
- Same JSON + same options → returns cached result instantly
|
|
261
|
+
- Cache TTL: 1 hour (configurable)
|
|
262
|
+
- Override: `jh data.json --engine ai --no-cache`
|
|
263
|
+
- Clear all: `jh --cache-clear`
|
|
264
|
+
- Custom dir: `JH_CACHE_DIR=/tmp/my-cache jh data.json --engine ai`
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Diff
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
$ jh v1.json --diff v2.json
|
|
272
|
+
|
|
273
|
+
Found 3 differences:
|
|
274
|
+
|
|
275
|
+
➕ Added (1):
|
|
276
|
+
+ phone (phone): "555-0100"
|
|
277
|
+
|
|
278
|
+
✏ Changed (2):
|
|
279
|
+
~ name (name): "Alice" → "Alice Johnson"
|
|
280
|
+
~ balance (balance): $4.3K → $4.5K
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# AI-powered diff (more natural language)
|
|
285
|
+
$ jh v1.json --diff v2.json --engine ai --lang Russian
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Watch mode
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
$ jh data.json --watch
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Watches the file and re-runs humanization on every save. Supports local and AI engines.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Supported file types
|
|
301
|
+
|
|
302
|
+
| Extension | Notes |
|
|
303
|
+
|-----------|-------|
|
|
304
|
+
| `.json` | Always available |
|
|
305
|
+
| `.yaml`, `.yml` | Requires `npm install js-yaml` |
|
|
306
|
+
| `.toml` | Requires `npm install @iarna/toml` |
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Project structure
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
json-humanized/
|
|
314
|
+
├── bin/
|
|
315
|
+
│ └── cli.js # CLI entry point
|
|
316
|
+
├── src/
|
|
317
|
+
│ ├── index.js # Public API
|
|
318
|
+
│ ├── humanizer.js # Rule-based engine
|
|
319
|
+
│ ├── config.js # Config file loader
|
|
320
|
+
│ ├── cache.js # AI response cache
|
|
321
|
+
│ ├── diff.js # Diff engine
|
|
322
|
+
│ ├── watch.js # File watcher
|
|
323
|
+
│ ├── parsers/
|
|
324
|
+
│ │ └── index.js # JSON / YAML / TOML parser
|
|
325
|
+
│ ├── formatters/
|
|
326
|
+
│ │ ├── index.js # plain, markdown, story, json
|
|
327
|
+
│ │ └── template.js # Handlebars template renderer
|
|
328
|
+
│ └── strategies/
|
|
329
|
+
│ ├── ai.js # AI provider router
|
|
330
|
+
│ ├── openai.js # OpenAI provider
|
|
331
|
+
│ └── ollama.js # Ollama provider
|
|
332
|
+
├── docs/
|
|
333
|
+
│ ├── DEMO.html # Live browser demo
|
|
334
|
+
│ └── ARCHITECTURE.md
|
|
335
|
+
├── examples/
|
|
336
|
+
├── test/
|
|
337
|
+
├── index.d.ts # TypeScript types
|
|
338
|
+
└── .jh.config.json # (optional) project config
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Contributing
|
|
344
|
+
|
|
345
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Issues and PRs are welcome!
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
MIT © json-humanized contributors
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// json-humanized · CLI v2.0
|
|
6
|
+
// Usage: jh <file> [options]
|
|
7
|
+
// jh --diff a.json b.json
|
|
8
|
+
// jh --watch file.json
|
|
9
|
+
// echo '{"key":"val"}' | jh --stdin
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
const { program } = require('commander');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const ora = require('ora');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const { humanizeFile, humanizeString } = require('../src/index');
|
|
19
|
+
const { diff, diffFiles } = require('../src/diff');
|
|
20
|
+
const { watch } = require('../src/watch');
|
|
21
|
+
const { clearCache, cacheStats } = require('../src/cache');
|
|
22
|
+
const { generateExampleConfig } = require('../src/config');
|
|
23
|
+
const { generateExampleTemplate } = require('../src/formatters/template');
|
|
24
|
+
const pkg = require('../package.json');
|
|
25
|
+
|
|
26
|
+
// ─── banner ──────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function printBanner() {
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(chalk.bold.cyan(' ╔══════════════════════════════════╗'));
|
|
31
|
+
console.log(chalk.bold.cyan(' ║') + chalk.bold.white(' json-humanized v' + pkg.version + ' ') + chalk.bold.cyan('║'));
|
|
32
|
+
console.log(chalk.bold.cyan(' ║') + chalk.gray(' Turn JSON into human language ') + chalk.bold.cyan('║'));
|
|
33
|
+
console.log(chalk.bold.cyan(' ╚══════════════════════════════════╝'));
|
|
34
|
+
console.log('');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── CLI definition ──────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.name('json-humanized')
|
|
41
|
+
.alias('jh')
|
|
42
|
+
.version(pkg.version, '-v, --version', 'Show version')
|
|
43
|
+
.description(chalk.cyan('Transform any JSON/YAML/TOML into natural human language\n') +
|
|
44
|
+
chalk.gray(' Supports files, stdin, diff, watch, and AI-powered descriptions'))
|
|
45
|
+
|
|
46
|
+
.argument('[file]', 'JSON/YAML/TOML file to humanize (or use --stdin)')
|
|
47
|
+
|
|
48
|
+
// ── Input
|
|
49
|
+
.option('--stdin', 'Read from stdin instead of a file')
|
|
50
|
+
.option('--diff <fileB>', 'Compare <file> with <fileB> and describe differences')
|
|
51
|
+
|
|
52
|
+
// ── Engine & Provider
|
|
53
|
+
.option('-e, --engine <engine>', 'Engine: local (default) or ai', 'local')
|
|
54
|
+
.option('--provider <provider>', 'AI provider: anthropic, openai, ollama', 'anthropic')
|
|
55
|
+
|
|
56
|
+
// ── Output
|
|
57
|
+
.option('-f, --format <format>', 'Output format: plain, markdown, story, json', 'plain')
|
|
58
|
+
.option('-m, --mode <mode>', 'Description mode: structured, prose, story', 'structured')
|
|
59
|
+
.option('-o, --output <file>', 'Save output to a file')
|
|
60
|
+
.option('--template <file>', 'Handlebars .hbs template for custom output')
|
|
61
|
+
|
|
62
|
+
// ── AI options
|
|
63
|
+
.option('-l, --lang <lang>', 'Output language (AI mode only)', 'English')
|
|
64
|
+
.option('-c, --context <text>', 'Context hint for AI (e.g. "Stripe webhook")', '')
|
|
65
|
+
.option('-k, --api-key <key>', 'API key (overrides env variable)')
|
|
66
|
+
.option('--max-chars <n>', 'Max JSON chars sent to AI (default 12000)', '12000')
|
|
67
|
+
|
|
68
|
+
// ── Watch
|
|
69
|
+
.option('--watch', 'Watch file and re-humanize on every save')
|
|
70
|
+
|
|
71
|
+
// ── Config
|
|
72
|
+
.option('--config <file>', 'Path to .jh.config.json config file')
|
|
73
|
+
.option('--init-config', 'Create a sample .jh.config.json in current directory')
|
|
74
|
+
.option('--init-template', 'Create a sample template.hbs in current directory')
|
|
75
|
+
|
|
76
|
+
// ── Cache
|
|
77
|
+
.option('--no-cache', 'Bypass AI response cache for this run')
|
|
78
|
+
.option('--cache-clear', 'Clear all cached AI responses and exit')
|
|
79
|
+
.option('--cache-stats', 'Show cache statistics and exit')
|
|
80
|
+
|
|
81
|
+
// ── Misc
|
|
82
|
+
.option('--no-banner', 'Suppress the banner')
|
|
83
|
+
.option('--silent', 'No spinner or banner, just output')
|
|
84
|
+
|
|
85
|
+
.addHelpText('after', `
|
|
86
|
+
${chalk.bold('Examples:')}
|
|
87
|
+
${chalk.cyan('$')} jh data.json
|
|
88
|
+
${chalk.cyan('$')} jh data.yaml --format markdown --output report.md
|
|
89
|
+
${chalk.cyan('$')} jh users.json --engine ai --lang Spanish
|
|
90
|
+
${chalk.cyan('$')} jh a.json --diff b.json
|
|
91
|
+
${chalk.cyan('$')} jh config.json --watch
|
|
92
|
+
${chalk.cyan('$')} echo '{"name":"Alice","age":30}' | jh --stdin
|
|
93
|
+
${chalk.cyan('$')} jh payload.json --engine ai --provider openai
|
|
94
|
+
${chalk.cyan('$')} jh payload.json --engine ai --provider ollama
|
|
95
|
+
${chalk.cyan('$')} jh big.json --engine ai --max-chars 8000
|
|
96
|
+
${chalk.cyan('$')} jh data.json --template ./report.hbs
|
|
97
|
+
|
|
98
|
+
${chalk.bold('Engines:')}
|
|
99
|
+
${chalk.yellow('local')} — Fast, offline, rule-based. No API key needed.
|
|
100
|
+
${chalk.yellow('ai')} — AI-powered. See --provider for supported backends.
|
|
101
|
+
|
|
102
|
+
${chalk.bold('AI Providers:')}
|
|
103
|
+
${chalk.yellow('anthropic')} — Claude (default). Needs ANTHROPIC_API_KEY.
|
|
104
|
+
${chalk.yellow('openai')} — GPT models. Needs OPENAI_API_KEY.
|
|
105
|
+
${chalk.yellow('ollama')} — Local models (llama3, mistral…). No key needed.
|
|
106
|
+
|
|
107
|
+
${chalk.bold('Formats:')}
|
|
108
|
+
${chalk.yellow('plain')} — Human-readable text (default)
|
|
109
|
+
${chalk.yellow('markdown')} — Markdown report with headers and table
|
|
110
|
+
${chalk.yellow('story')} — Narrative storytelling style
|
|
111
|
+
${chalk.yellow('json')} — JSON output with metadata
|
|
112
|
+
|
|
113
|
+
${chalk.bold('Environment Variables:')}
|
|
114
|
+
ANTHROPIC_API_KEY — API key for Claude AI engine
|
|
115
|
+
OPENAI_API_KEY — API key for OpenAI engine
|
|
116
|
+
OLLAMA_URL — Ollama base URL (default: http://localhost:11434)
|
|
117
|
+
OLLAMA_MODEL — Ollama model name (default: llama3)
|
|
118
|
+
JH_CACHE_DIR — Custom cache directory (default: ~/.jh-cache)
|
|
119
|
+
`);
|
|
120
|
+
|
|
121
|
+
program.parse(process.argv);
|
|
122
|
+
|
|
123
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
async function main() {
|
|
126
|
+
const opts = program.opts();
|
|
127
|
+
const [file] = program.args;
|
|
128
|
+
const silent = opts.silent;
|
|
129
|
+
|
|
130
|
+
if (!silent && opts.banner !== false) printBanner();
|
|
131
|
+
|
|
132
|
+
// ── Cache management commands ────────────────────────────────────────────
|
|
133
|
+
if (opts.cacheStats) {
|
|
134
|
+
const s = cacheStats();
|
|
135
|
+
console.log(chalk.cyan(' Cache statistics:'));
|
|
136
|
+
console.log(` Directory : ${s.dir}`);
|
|
137
|
+
console.log(` Entries : ${s.entries}`);
|
|
138
|
+
console.log(` Size : ${(s.totalBytes / 1024).toFixed(1)} KB`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (opts.cacheClear) {
|
|
143
|
+
const n = clearCache();
|
|
144
|
+
console.log(chalk.green(` ✓ Cleared ${n} cached AI response(s)`));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Init commands ────────────────────────────────────────────────────────
|
|
149
|
+
if (opts.initConfig) {
|
|
150
|
+
const target = path.join(process.cwd(), '.jh.config.json');
|
|
151
|
+
fs.writeFileSync(target, generateExampleConfig(), 'utf8');
|
|
152
|
+
console.log(chalk.green(` ✓ Created ${target}`));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (opts.initTemplate) {
|
|
157
|
+
const target = path.join(process.cwd(), 'template.hbs');
|
|
158
|
+
generateExampleTemplate(target);
|
|
159
|
+
console.log(chalk.green(` ✓ Created ${target}`));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Diff mode ────────────────────────────────────────────────────────────
|
|
164
|
+
if (opts.diff) {
|
|
165
|
+
if (!file) {
|
|
166
|
+
console.error(chalk.red(' ✖ --diff requires a first file argument: jh a.json --diff b.json'));
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const spinner = !silent ? ora({ text: chalk.cyan('Comparing files…'), color: 'cyan' }).start() : null;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const result = await diffFiles(file, opts.diff, {
|
|
174
|
+
engine: opts.engine,
|
|
175
|
+
format: opts.format,
|
|
176
|
+
apiKey: opts.apiKey || process.env.ANTHROPIC_API_KEY,
|
|
177
|
+
lang: opts.lang,
|
|
178
|
+
});
|
|
179
|
+
spinner && spinner.succeed(chalk.green('Done!'));
|
|
180
|
+
outputResult(result, opts, silent);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
spinner && spinner.fail(chalk.red('Failed'));
|
|
183
|
+
exitError(err);
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Watch mode ───────────────────────────────────────────────────────────
|
|
189
|
+
if (opts.watch) {
|
|
190
|
+
if (!file) {
|
|
191
|
+
console.error(chalk.red(' ✖ --watch requires a file argument'));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const options = buildOptions(opts);
|
|
196
|
+
const { humanize } = require('../src/index');
|
|
197
|
+
|
|
198
|
+
watch(file, options, async (data, watchOpts) => {
|
|
199
|
+
return humanize(data, watchOpts);
|
|
200
|
+
});
|
|
201
|
+
return; // watch keeps running
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Standard humanize ────────────────────────────────────────────────────
|
|
205
|
+
if (!file && !opts.stdin) {
|
|
206
|
+
console.error(chalk.red(' ✖ Please provide a JSON/YAML/TOML file or use --stdin'));
|
|
207
|
+
console.error(chalk.gray(' Run `jh --help` for usage information'));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
validateOpts(opts);
|
|
212
|
+
|
|
213
|
+
const spinner = !silent ? ora({
|
|
214
|
+
text: opts.engine === 'ai'
|
|
215
|
+
? chalk.cyan(`Sending to ${opts.provider} AI…`)
|
|
216
|
+
: chalk.cyan('Analysing structure…'),
|
|
217
|
+
color: 'cyan',
|
|
218
|
+
}).start() : null;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const options = buildOptions(opts);
|
|
222
|
+
let result;
|
|
223
|
+
|
|
224
|
+
if (opts.stdin) {
|
|
225
|
+
const raw = await readStdin();
|
|
226
|
+
if (!raw.trim()) {
|
|
227
|
+
spinner && spinner.fail('No input received from stdin');
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
result = await humanizeString(raw, options);
|
|
231
|
+
} else {
|
|
232
|
+
if (!fs.existsSync(path.resolve(file))) {
|
|
233
|
+
spinner && spinner.fail(`File not found: ${file}`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
result = await humanizeFile(file, { ...options, filename: path.basename(file) });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
spinner && spinner.succeed(chalk.green('Done!'));
|
|
240
|
+
outputResult(result, opts, silent);
|
|
241
|
+
|
|
242
|
+
} catch (err) {
|
|
243
|
+
spinner && spinner.fail(chalk.red('Failed'));
|
|
244
|
+
exitError(err);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
function buildOptions(opts) {
|
|
251
|
+
return {
|
|
252
|
+
engine: opts.engine,
|
|
253
|
+
aiProvider: opts.provider,
|
|
254
|
+
format: opts.format,
|
|
255
|
+
mode: opts.mode,
|
|
256
|
+
lang: opts.lang,
|
|
257
|
+
context: opts.context,
|
|
258
|
+
apiKey: opts.apiKey || process.env.ANTHROPIC_API_KEY,
|
|
259
|
+
maxChars: parseInt(opts.maxChars, 10) || 12000,
|
|
260
|
+
template: opts.template,
|
|
261
|
+
cache: opts.cache !== false,
|
|
262
|
+
configPath: opts.config,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function outputResult(result, opts, silent) {
|
|
267
|
+
if (opts.output) {
|
|
268
|
+
fs.writeFileSync(path.resolve(opts.output), result, 'utf8');
|
|
269
|
+
if (!silent) console.log('\n' + chalk.green(` ✓ Saved to: ${chalk.bold(opts.output)}`));
|
|
270
|
+
} else {
|
|
271
|
+
console.log('\n' + result);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function validateOpts(opts) {
|
|
276
|
+
const validEngines = ['local', 'ai'];
|
|
277
|
+
const validFormats = ['plain', 'markdown', 'story', 'json'];
|
|
278
|
+
const validProviders = ['anthropic', 'openai', 'ollama'];
|
|
279
|
+
|
|
280
|
+
if (!validEngines.includes(opts.engine)) {
|
|
281
|
+
console.error(chalk.red(` ✖ Unknown engine: ${opts.engine}. Use: ${validEngines.join(', ')}`));
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
if (!validFormats.includes(opts.format)) {
|
|
285
|
+
console.error(chalk.red(` ✖ Unknown format: ${opts.format}. Use: ${validFormats.join(', ')}`));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
if (opts.engine === 'ai' && !validProviders.includes(opts.provider)) {
|
|
289
|
+
console.error(chalk.red(` ✖ Unknown provider: ${opts.provider}. Use: ${validProviders.join(', ')}`));
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const apiKey = opts.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
294
|
+
if (opts.engine === 'ai' && opts.provider === 'anthropic' && !apiKey) {
|
|
295
|
+
console.error(chalk.red(' ✖ Anthropic engine requires ANTHROPIC_API_KEY'));
|
|
296
|
+
console.error(chalk.yellow(' Set ANTHROPIC_API_KEY env variable, or use --api-key flag'));
|
|
297
|
+
console.error(chalk.gray(' Or switch to local engine: --engine local'));
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function exitError(err) {
|
|
303
|
+
console.error('\n' + chalk.red(' Error: ') + err.message);
|
|
304
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function readStdin() {
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
if (process.stdin.isTTY) return resolve('');
|
|
311
|
+
let data = '';
|
|
312
|
+
process.stdin.setEncoding('utf8');
|
|
313
|
+
process.stdin.on('data', chunk => { data += chunk; });
|
|
314
|
+
process.stdin.on('end', () => resolve(data));
|
|
315
|
+
process.stdin.on('error', reject);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
main();
|