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.
@@ -0,0 +1,124 @@
1
+ # Publishing to npm
2
+
3
+ Step-by-step guide for maintainers.
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ - You have an account on [npmjs.com](https://www.npmjs.com)
10
+ - You are logged in: `npm whoami` should print your username
11
+ - If not: `npm login`
12
+
13
+ ---
14
+
15
+ ## First publish
16
+
17
+ ### 1. Check the package name is available
18
+
19
+ ```bash
20
+ npm view json-humanized
21
+ # If it returns "npm error 404" → name is free ✓
22
+ # If it returns package info → name is taken, update "name" in package.json
23
+ ```
24
+
25
+ ### 2. Review what will be published
26
+
27
+ ```bash
28
+ npm pack --dry-run
29
+ ```
30
+
31
+ You should see only:
32
+ - `bin/cli.js`
33
+ - `src/**`
34
+ - `examples/*.json`, `examples/demo.js`
35
+ - `README.md`, `LICENSE`, `package.json`
36
+
37
+ NOT included (via `.npmignore`): `test/`, `.github/`, `docs/`, `node_modules/`
38
+
39
+ ### 3. Run tests one final time
40
+
41
+ ```bash
42
+ npm test
43
+ ```
44
+
45
+ ### 4. Publish
46
+
47
+ ```bash
48
+ npm publish
49
+ ```
50
+
51
+ That's it. Your package is live at:
52
+ `https://www.npmjs.com/package/json-humanized`
53
+
54
+ ---
55
+
56
+ ## Publishing an update
57
+
58
+ ### 1. Update CHANGELOG.md
59
+
60
+ Add an entry under a new version heading.
61
+
62
+ ### 2. Bump the version
63
+
64
+ ```bash
65
+ # Patch (1.0.0 → 1.0.1): bug fixes
66
+ npm version patch
67
+
68
+ # Minor (1.0.0 → 1.1.0): new features, backwards-compatible
69
+ npm version minor
70
+
71
+ # Major (1.0.0 → 2.0.0): breaking changes
72
+ npm version major
73
+ ```
74
+
75
+ This command automatically:
76
+ - Updates `version` in `package.json`
77
+ - Creates a git commit: `"1.0.1"`
78
+ - Creates a git tag: `"v1.0.1"`
79
+
80
+ ### 3. Push to GitHub
81
+
82
+ ```bash
83
+ git push && git push --tags
84
+ ```
85
+
86
+ ### 4. Publish to npm
87
+
88
+ ```bash
89
+ npm publish
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Publish with a tag (beta)
95
+
96
+ ```bash
97
+ npm version prerelease --preid=beta # → 1.0.1-beta.0
98
+ npm publish --tag beta
99
+ ```
100
+
101
+ Users install it with: `npm install json-humanized@beta`
102
+
103
+ ---
104
+
105
+ ## Unpublish (within 72 hours only)
106
+
107
+ ```bash
108
+ npm unpublish json-humanized@1.0.0
109
+ ```
110
+
111
+ After 72 hours npm does not allow unpublishing — deprecate instead:
112
+
113
+ ```bash
114
+ npm deprecate json-humanized@1.0.0 "Critical bug, use 1.0.1"
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Checking download stats
120
+
121
+ ```bash
122
+ npm info json-humanized
123
+ # or visit: https://www.npmjs.com/package/json-humanized
124
+ ```
@@ -0,0 +1,42 @@
1
+ {
2
+ "data": [
3
+ {
4
+ "id": "order_001",
5
+ "status": "delivered",
6
+ "created_at": "2024-06-01T10:00:00Z",
7
+ "total": 129.95,
8
+ "customer_name": "Bob Martinez",
9
+ "items": [
10
+ { "name": "Wireless Headphones", "qty": 1, "price": 89.99 },
11
+ { "name": "USB-C Cable", "qty": 2, "price": 19.99 }
12
+ ],
13
+ "shipping_address": {
14
+ "city": "Chicago",
15
+ "country": "US"
16
+ }
17
+ },
18
+ {
19
+ "id": "order_002",
20
+ "status": "processing",
21
+ "created_at": "2024-06-28T09:15:00Z",
22
+ "total": 249.00,
23
+ "customer_name": "Clara Kim",
24
+ "items": [
25
+ { "name": "Mechanical Keyboard", "qty": 1, "price": 249.00 }
26
+ ],
27
+ "shipping_address": {
28
+ "city": "Austin",
29
+ "country": "US"
30
+ }
31
+ }
32
+ ],
33
+ "meta": {
34
+ "total": 2,
35
+ "page": 1,
36
+ "per_page": 20
37
+ },
38
+ "links": {
39
+ "self": "https://api.example.com/orders?page=1",
40
+ "next": null
41
+ }
42
+ }
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ // json-humanized — Programmatic API demo
4
+
5
+ const { humanize, humanizeFile, humanizeString } = require('../src/index');
6
+
7
+ async function demo() {
8
+ console.log('═'.repeat(60));
9
+ console.log(' json-humanized · Programmatic API Demo');
10
+ console.log('═'.repeat(60));
11
+
12
+ // ── 1. Humanize inline data ─────────────────────────────────────
13
+ console.log('\n📌 Example 1: Simple object\n');
14
+ const result1 = await humanize({
15
+ name: 'Alice',
16
+ age: 30,
17
+ email: 'alice@example.com',
18
+ premium: true,
19
+ score: 9.2,
20
+ });
21
+ console.log(result1);
22
+
23
+ // ── 2. Humanize from file ───────────────────────────────────────
24
+ console.log('\n📌 Example 2: From file (user-profile.json)\n');
25
+ const result2 = await humanizeFile('./examples/user-profile.json', {
26
+ format: 'plain',
27
+ });
28
+ console.log(result2);
29
+
30
+ // ── 3. Markdown format ──────────────────────────────────────────
31
+ console.log('\n📌 Example 3: Markdown output\n');
32
+ const result3 = await humanizeString(
33
+ JSON.stringify({ errors: [{ code: 404, message: 'Not found', path: '/api/users/999' }] }),
34
+ { format: 'markdown' }
35
+ );
36
+ console.log(result3);
37
+
38
+ // ── 4. Story format ─────────────────────────────────────────────
39
+ console.log('\n📌 Example 4: Story format\n');
40
+ const result4 = await humanize(
41
+ { city: 'Paris', population: 2161000, country: 'France', latitude: 48.8566, longitude: 2.3522 },
42
+ { format: 'story' }
43
+ );
44
+ console.log(result4);
45
+ }
46
+
47
+ demo().catch(err => {
48
+ console.error('Demo failed:', err.message);
49
+ process.exit(1);
50
+ });
@@ -0,0 +1,36 @@
1
+ {
2
+ "user": {
3
+ "id": "usr_8f2a1c9d",
4
+ "name": "Alice Rosenberg",
5
+ "email": "alice@example.com",
6
+ "age": 29,
7
+ "phone": "+1-555-0147",
8
+ "premium": true,
9
+ "created_at": "2023-06-15T08:30:00Z",
10
+ "address": {
11
+ "city": "San Francisco",
12
+ "country": "United States",
13
+ "zip": "94105"
14
+ },
15
+ "preferences": {
16
+ "theme": "dark",
17
+ "notifications": true,
18
+ "language": "en"
19
+ },
20
+ "tags": ["designer", "beta-tester", "early-adopter"]
21
+ },
22
+ "subscription": {
23
+ "plan": "Pro",
24
+ "status": "active",
25
+ "price": 49.99,
26
+ "billing_cycle": "monthly",
27
+ "next_billing_date": "2024-07-15T00:00:00Z",
28
+ "features": ["unlimited_projects", "ai_assistant", "priority_support"]
29
+ },
30
+ "stats": {
31
+ "total_projects": 42,
32
+ "storage_used_gb": 3.7,
33
+ "last_login": "2024-06-28T14:22:00Z",
34
+ "rating": 4.8
35
+ }
36
+ }
package/index.d.ts ADDED
@@ -0,0 +1,138 @@
1
+ // Type definitions for json-humanized
2
+ // Project: https://github.com/AceAnomDev/json-humanized
3
+
4
+ export type Engine = 'local' | 'ai';
5
+ export type AIProvider = 'anthropic' | 'openai' | 'ollama';
6
+ export type Format = 'plain' | 'markdown' | 'story' | 'json';
7
+ export type Mode = 'structured' | 'prose' | 'story';
8
+
9
+ export interface HumanizeOptions {
10
+ /** Processing engine. Default: 'local' */
11
+ engine?: Engine;
12
+
13
+ /** AI provider (only used when engine = 'ai'). Default: 'anthropic' */
14
+ aiProvider?: AIProvider;
15
+
16
+ /** API key for Anthropic or OpenAI. Falls back to env variable. */
17
+ apiKey?: string;
18
+
19
+ /** Output format. Default: 'plain' */
20
+ format?: Format;
21
+
22
+ /** Description style. Default: 'structured' */
23
+ mode?: Mode;
24
+
25
+ /** Natural language for output (AI mode). Default: 'English' */
26
+ lang?: string;
27
+
28
+ /** Context hint for the AI (e.g. "This is a Stripe webhook"). */
29
+ context?: string;
30
+
31
+ /** Source filename shown in the output header. */
32
+ filename?: string;
33
+
34
+ /** Max chars of JSON sent to AI. Default: 12000 */
35
+ maxChars?: number;
36
+
37
+ /** Path to a Handlebars (.hbs) template file for custom output. */
38
+ template?: string;
39
+
40
+ /** Enable AI response caching. Default: true */
41
+ cache?: boolean;
42
+
43
+ /** Cache TTL in seconds. Default: 3600 */
44
+ cacheTTL?: number;
45
+
46
+ /** Explicit path to a .jh.config.json file. Auto-detected if omitted. */
47
+ configPath?: string;
48
+
49
+ /** Ollama base URL. Default: 'http://localhost:11434' */
50
+ ollamaUrl?: string;
51
+
52
+ /** Ollama model name. Default: 'llama3' */
53
+ ollamaModel?: string;
54
+
55
+ /** OpenAI model. Default: 'gpt-4o-mini' */
56
+ openaiModel?: string;
57
+ }
58
+
59
+ export interface DiffOptions {
60
+ /** 'local' for rule-based diff, 'ai' for AI-powered diff. Default: 'local' */
61
+ engine?: Engine;
62
+
63
+ /** Output format. Default: 'plain' */
64
+ format?: 'plain' | 'markdown' | 'json';
65
+
66
+ /** Natural language for AI diff output. Default: 'English' */
67
+ lang?: string;
68
+
69
+ /** Context hint for AI diff. */
70
+ context?: string;
71
+
72
+ /** API key for AI diff mode. */
73
+ apiKey?: string;
74
+ }
75
+
76
+ export interface CacheStats {
77
+ entries: number;
78
+ totalBytes: number;
79
+ dir: string;
80
+ }
81
+
82
+ export interface DiffModule {
83
+ diff(a: unknown, b: unknown, options?: DiffOptions): Promise<string>;
84
+ diffFiles(fileA: string, fileB: string, options?: DiffOptions): Promise<string>;
85
+ }
86
+
87
+ export interface CacheModule {
88
+ buildCacheKey(data: unknown, options?: object): string;
89
+ readCache(key: string, ttl?: number): string | null;
90
+ writeCache(key: string, result: string): void;
91
+ clearCache(): number;
92
+ cacheStats(): CacheStats;
93
+ withCache(data: unknown, options: object, fn: () => Promise<string>, enabled?: boolean): Promise<string>;
94
+ }
95
+
96
+ export interface ConfigModule {
97
+ loadConfig(configPath?: string): { config: object; configPath: string | null };
98
+ resolveLabel(key: string, fieldLabels?: Record<string, string | null>): string | null;
99
+ resolveType(key: string, fieldTypes?: Record<string, string>): string | null;
100
+ isHidden(key: string, hiddenFields?: string[]): boolean;
101
+ generateExampleConfig(): string;
102
+ }
103
+
104
+ /**
105
+ * Humanize a parsed JavaScript value (object, array, primitive).
106
+ *
107
+ * @example
108
+ * import { humanize } from 'json-humanized';
109
+ * const text = await humanize({ name: 'Alice', age: 30 });
110
+ */
111
+ export function humanize(data: unknown, options?: HumanizeOptions): Promise<string>;
112
+
113
+ /**
114
+ * Read a JSON/YAML/TOML file from disk and humanize it.
115
+ *
116
+ * @example
117
+ * import { humanizeFile } from 'json-humanized';
118
+ * const text = await humanizeFile('./users.json', { format: 'markdown' });
119
+ */
120
+ export function humanizeFile(filePath: string, options?: HumanizeOptions): Promise<string>;
121
+
122
+ /**
123
+ * Parse and humanize a raw JSON/YAML/TOML string.
124
+ *
125
+ * @example
126
+ * import { humanizeString } from 'json-humanized';
127
+ * const text = await humanizeString('{"key":"value"}');
128
+ */
129
+ export function humanizeString(rawString: string, options?: HumanizeOptions): Promise<string>;
130
+
131
+ /** Diff utilities */
132
+ export const diff: DiffModule;
133
+
134
+ /** Cache utilities */
135
+ export const cache: CacheModule;
136
+
137
+ /** Config utilities */
138
+ export const config: ConfigModule;
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "json-humanized",
3
+ "version": "2.0.0",
4
+ "description": "Transform any JSON/YAML/TOML into human-readable prose — powered by Claude AI, OpenAI, Ollama, or built-in rule-based engine",
5
+ "main": "src/index.js",
6
+ "types": "index.d.ts",
7
+ "bin": {
8
+ "json-humanized": "./bin/cli.js",
9
+ "jh": "./bin/cli.js"
10
+ },
11
+ "type": "commonjs",
12
+ "scripts": {
13
+ "test": "node test/index.test.js",
14
+ "example": "node examples/demo.js",
15
+ "lint": "echo 'No linter configured'",
16
+ "cache:clear": "node -e \"require('./src/cache').clearCache(); console.log('Cache cleared')\"",
17
+ "cache:stats": "node -e \"const s=require('./src/cache').cacheStats(); console.log(s)\""
18
+ },
19
+ "keywords": [
20
+ "json",
21
+ "yaml",
22
+ "toml",
23
+ "human-readable",
24
+ "nlp",
25
+ "cli",
26
+ "ai",
27
+ "claude",
28
+ "openai",
29
+ "ollama",
30
+ "text-generation",
31
+ "data-description",
32
+ "json-to-text",
33
+ "diff",
34
+ "watch",
35
+ "developer-tools"
36
+ ],
37
+ "author": "json-humanized contributors",
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "chalk": "4.1.2",
41
+ "commander": "11.1.0",
42
+ "ora": "5.4.1"
43
+ },
44
+ "optionalDependencies": {
45
+ "@anthropic-ai/sdk": "^0.20.0",
46
+ "openai": "^4.0.0",
47
+ "js-yaml": "^4.1.0",
48
+ "@iarna/toml": "^2.2.5",
49
+ "handlebars": "^4.7.8"
50
+ },
51
+ "engines": {
52
+ "node": ">=14.0.0"
53
+ },
54
+ "files": [
55
+ "bin/",
56
+ "src/",
57
+ "examples/",
58
+ "docs/",
59
+ "index.d.ts",
60
+ "README.md",
61
+ "LICENSE"
62
+ ],
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "https://github.com/AceAnomDev/json-humanized.git"
66
+ },
67
+ "bugs": {
68
+ "url": "https://github.com/AceAnomDev/json-humanized/issues"
69
+ },
70
+ "homepage": "https://github.com/AceAnomDev/json-humanized#readme"
71
+ }
package/src/cache.js ADDED
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * cache.js — file-based cache for AI responses.
5
+ *
6
+ * Saves API tokens by reusing results for identical (JSON + options) combos.
7
+ * Cache files are stored in ~/.jh-cache/ (or $JH_CACHE_DIR).
8
+ * Each entry expires after `ttl` seconds (default 3600 = 1 hour).
9
+ *
10
+ * No external dependencies — uses built-in crypto + fs.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const crypto = require('crypto');
16
+ const os = require('os');
17
+
18
+ // ─── cache directory ─────────────────────────────────────────────────────────
19
+
20
+ function getCacheDir() {
21
+ return process.env.JH_CACHE_DIR || path.join(os.homedir(), '.jh-cache');
22
+ }
23
+
24
+ function ensureCacheDir() {
25
+ const dir = getCacheDir();
26
+ if (!fs.existsSync(dir)) {
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ }
29
+ return dir;
30
+ }
31
+
32
+ // ─── cache key ───────────────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * Build a stable cache key from JSON content + relevant options.
36
+ * We hash the combination so filenames stay short and filesystem-safe.
37
+ */
38
+ function buildCacheKey(data, options = {}) {
39
+ const { engine, format, lang, context, aiProvider, mode } = options;
40
+
41
+ const payload = JSON.stringify({
42
+ data,
43
+ engine: engine || 'local',
44
+ format: format || 'plain',
45
+ lang: lang || 'English',
46
+ context: context || '',
47
+ aiProvider: aiProvider || 'anthropic',
48
+ mode: mode || 'structured',
49
+ });
50
+
51
+ return crypto.createHash('sha256').update(payload).digest('hex');
52
+ }
53
+
54
+ // ─── read / write ────────────────────────────────────────────────────────────
55
+
56
+ /**
57
+ * Try to read a cached result.
58
+ * Returns the cached string, or null if miss / expired.
59
+ *
60
+ * @param {string} key from buildCacheKey()
61
+ * @param {number} ttl seconds; 0 = never expire
62
+ * @returns {string|null}
63
+ */
64
+ function readCache(key, ttl = 3600) {
65
+ try {
66
+ const file = path.join(getCacheDir(), `${key}.json`);
67
+ if (!fs.existsSync(file)) return null;
68
+
69
+ const entry = JSON.parse(fs.readFileSync(file, 'utf8'));
70
+
71
+ if (ttl > 0) {
72
+ const ageSeconds = (Date.now() - entry.savedAt) / 1000;
73
+ if (ageSeconds > ttl) {
74
+ fs.unlinkSync(file); // clean up expired entry
75
+ return null;
76
+ }
77
+ }
78
+
79
+ return entry.result;
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Write a result to the cache.
87
+ *
88
+ * @param {string} key
89
+ * @param {string} result the humanized text to cache
90
+ */
91
+ function writeCache(key, result) {
92
+ try {
93
+ ensureCacheDir();
94
+ const file = path.join(getCacheDir(), `${key}.json`);
95
+ const entry = { savedAt: Date.now(), result };
96
+ fs.writeFileSync(file, JSON.stringify(entry), 'utf8');
97
+ } catch {
98
+ // Cache write failures are silent — the caller still gets a valid result
99
+ }
100
+ }
101
+
102
+ // ─── cache management ────────────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Delete all cache entries.
106
+ * @returns {number} number of files deleted
107
+ */
108
+ function clearCache() {
109
+ const dir = getCacheDir();
110
+ if (!fs.existsSync(dir)) return 0;
111
+
112
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
113
+ for (const f of files) {
114
+ try { fs.unlinkSync(path.join(dir, f)); } catch {}
115
+ }
116
+ return files.length;
117
+ }
118
+
119
+ /**
120
+ * Return cache statistics.
121
+ * @returns {{ entries: number, totalBytes: number, dir: string }}
122
+ */
123
+ function cacheStats() {
124
+ const dir = getCacheDir();
125
+ if (!fs.existsSync(dir)) return { entries: 0, totalBytes: 0, dir };
126
+
127
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
128
+ let totalBytes = 0;
129
+
130
+ for (const f of files) {
131
+ try {
132
+ totalBytes += fs.statSync(path.join(dir, f)).size;
133
+ } catch {}
134
+ }
135
+
136
+ return { entries: files.length, totalBytes, dir };
137
+ }
138
+
139
+ // ─── wrapped fetch helper ────────────────────────────────────────────────────
140
+
141
+ /**
142
+ * Call `fn` with caching: return cached result if available, otherwise
143
+ * call `fn`, cache its output, and return it.
144
+ *
145
+ * @param {*} data JSON data (used to build the cache key)
146
+ * @param {object} options humanize options (used to build the cache key)
147
+ * @param {Function} fn async () => string — the real API call
148
+ * @param {boolean} [enabled] pass false to bypass cache entirely
149
+ * @returns {Promise<string>}
150
+ */
151
+ async function withCache(data, options, fn, enabled = true) {
152
+ if (!enabled) return fn();
153
+
154
+ const ttl = options.cacheTTL != null ? options.cacheTTL : 3600;
155
+ const key = buildCacheKey(data, options);
156
+
157
+ const cached = readCache(key, ttl);
158
+ if (cached !== null) return cached;
159
+
160
+ const result = await fn();
161
+ writeCache(key, result);
162
+ return result;
163
+ }
164
+
165
+ module.exports = {
166
+ buildCacheKey,
167
+ readCache,
168
+ writeCache,
169
+ clearCache,
170
+ cacheStats,
171
+ withCache,
172
+ };