@sohqureshi/tokenwise 1.0.7 → 1.0.10

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/README.md CHANGED
@@ -147,9 +147,25 @@ toNatural({
147
147
  }
148
148
  }
149
149
  });
150
- // policy: holder name Carlos Rivera, policy number HLT-2048, claim: status under review, requested amount 64000.
150
+ // Carlos Rivera has policy HLT-2048. The claim is under review for 64,000.
151
151
  ```
152
152
 
153
+ `toNatural()` uses semantic templates when the field relationships are
154
+ unambiguous (for example, a policy holder, policy number, and claim). Unknown
155
+ JSON shapes use a conservative key-value fallback so the formatter does not
156
+ invent relationships or facts. For dynamic entity-shaped objects, it can
157
+ recognize common subject fields such as `name`, `title`, `label`, `displayName`,
158
+ `entityName`, `fullName`, `userName`, `customerName`, `ownerName`,
159
+ `accountName`, `companyName`, `organizationName`, `teamName`, `projectName`,
160
+ `productName`, `serviceName`, `resourceName`, `deviceName`, `patientName`,
161
+ `taskName`, and `eventName`. These can be paired with state fields such as
162
+ `status`, `state`, `condition`, `stage`, `role`, `type`, `category`, `priority`,
163
+ `phase`, `mode`, `availability`, `outcome`, `result`, `health`, `progress`,
164
+ `visibility`, `access`, `membership`, `sentiment`, and `severity`.
165
+ Matching is case-insensitive and also supports keys that contain these
166
+ concepts, such as `patientName`, `orderStatus`, `currentPhase`, or
167
+ `incidentSeverity`.
168
+
153
169
  ### `toTOON()`
154
170
 
155
171
  Converts JSON into a compact TOON-like text format. Arrays of objects become table-style rows.
@@ -210,10 +226,37 @@ LLMs charge and reason over tokens. Sending raw JSON often includes repeated key
210
226
  node demo.js
211
227
  ```
212
228
 
213
- Use `--analyze` to compare serialized input and output with TokenWise's
214
- four-characters-per-token heuristic. For model-accurate counts, pass
215
- `exact: true` and a `model` name to `estimateTokens()` or `analyze()`.
216
- This is still only as exact as the tokenizer implementation you use.
229
+ Use `--analyze` to compare serialized input and output with the model's
230
+ `tiktoken` encoding. `estimateTokens()` and `analyze()` use exact
231
+ model-aware counts by default; pass `exact: false` to opt into the
232
+ four-characters-per-token heuristic. Use `model` to select the tokenizer
233
+ used by `tiktoken`.
234
+
235
+ ---
236
+
237
+ ## Release Notes
238
+
239
+ ### v1.0.10 — 2026-09-13
240
+
241
+ - Add conservative contains-based semantic matching for dynamic subject and state keys, including fields such as `patientName`, `orderStatus`, and `incidentSeverity`.
242
+ - Expand `toNatural()` regression coverage for dynamic entities, nested objects, arrays, metadata filtering, and alternate subject fields.
243
+ - Update the browser demo to use the v1.0.10 CDN package.
244
+
245
+ ### v1.0.8 — 2026-09-06
246
+
247
+ - Expose exact tokenizer metadata when available (model + encoding), and fall back to a clear, model-aware estimator in browser demos.
248
+ - Demo updated to show selected model and expected encoding when the exact tokenizer (tiktoken) is not available in-browser.
249
+ - Updated analyze() to surface tokenizer encoding in analysis output so the demo shows "Estimator: model=gpt-4 expected_encoding=cl100k_base" even when using the heuristic fallback.
250
+ - Misc: build artifacts updated.
251
+
252
+ ### v1.0.9 — 2026-09-13
253
+
254
+ - Use `tiktoken` model-aware token counts by default in `estimateTokens()` and `analyze()`.
255
+ - Keep the four-characters-per-token estimate available with `exact: false`.
256
+ - Correctly identify `o200k_base` for modern OpenAI models such as `gpt-4o`, `gpt-4.1`, `o1`, and `o3`.
257
+ - Update the browser demo to use the v1.0.9 CDN package.
258
+ - Make demo analysis actionable by showing signed token and character changes, the selected transformation, and an explanation of whether the result is cheaper or larger.
259
+ - Rename the demo's input-only Analyze view to Inspect, showing input size and structure without applying a transformation.
217
260
 
218
261
  ---
219
262
 
@@ -13,12 +13,12 @@ import {
13
13
  import {
14
14
  estimateTokensWithMeta,
15
15
  serializeForTokenEstimate
16
- } from "./chunk-U433WUUT.js";
16
+ } from "./chunk-L3HY6AWL.js";
17
17
 
18
18
  // src/core/analyze.ts
19
19
  function analyze(input, options = {}) {
20
20
  const {
21
- exact = false,
21
+ exact = true,
22
22
  model = "gpt-4o-mini",
23
23
  fallbackToHeuristic = true
24
24
  } = options;
@@ -0,0 +1,241 @@
1
+ // src/core/natural.ts
2
+ function toNatural(data, depth = 0) {
3
+ if (data === null || data === void 0) return "nothing";
4
+ if (typeof data === "string") return data;
5
+ if (typeof data === "number") return String(data);
6
+ if (typeof data === "boolean") return data ? "yes" : "no";
7
+ if (Array.isArray(data)) {
8
+ if (data.length === 0) return "empty list";
9
+ if (data.every(isPlainObject)) {
10
+ return data.map((item, index) => `${index + 1}. ${stripTrailingPeriod(toNatural(item, depth + 1))}.`).join(" ");
11
+ }
12
+ const items = data.map((item) => toNatural(item, depth + 1));
13
+ return joinNaturalList(items);
14
+ }
15
+ if (typeof data === "object") {
16
+ return buildContextualStory(data, depth);
17
+ }
18
+ return String(data);
19
+ }
20
+ function buildContextualStory(obj, depth = 0) {
21
+ const semanticStory = buildSemanticStory(obj);
22
+ if (semanticStory) return semanticStory;
23
+ const nestedEntity = Object.values(obj).find((value) => {
24
+ return isPlainObject(value) && buildSemanticStory(value) !== null;
25
+ });
26
+ if (nestedEntity) {
27
+ const nestedStory = buildSemanticStory(nestedEntity);
28
+ if (nestedStory) return nestedStory;
29
+ }
30
+ let name = obj.name || obj.userName || obj.user;
31
+ if (typeof name === "object" && name !== null && name.name) {
32
+ name = name.name;
33
+ }
34
+ const entries = Object.entries(obj).filter(([key]) => {
35
+ return !["id", "timestamp", "apiKey"].includes(key);
36
+ });
37
+ if (entries.length === 0) return "";
38
+ let story = name && typeof name === "string" ? `User ${name}` : "";
39
+ const clauses = entries.map(([key, value]) => {
40
+ if (key === "user" && name && typeof value === "object" && value !== null && !Array.isArray(value)) {
41
+ const userProps = Object.entries(value).filter(([k]) => !["id", "name", "timestamp", "apiKey"].includes(k)).map(([k, v]) => formatPropertyClause(k, v, depth + 1)).filter((c) => c !== null && c !== "").join(", ");
42
+ return userProps ? `(${userProps})` : null;
43
+ }
44
+ return formatPropertyClause(key, value, depth);
45
+ }).filter((c) => c !== null && c !== "");
46
+ if (clauses.length === 0) return story;
47
+ if (story) {
48
+ story += " " + clauses.join(", ");
49
+ } else {
50
+ story = clauses.join(", ");
51
+ }
52
+ return story + ".";
53
+ }
54
+ function buildSemanticStory(obj) {
55
+ if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
56
+ const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
57
+ const details = [
58
+ typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
59
+ typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
60
+ ].filter((value) => value !== null);
61
+ const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
62
+ if (isPlainObject(obj.claim)) {
63
+ const claim = describeClaim(obj.claim);
64
+ if (claim) sentences.push(claim);
65
+ }
66
+ if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
67
+ sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
68
+ }
69
+ return sentences.join(" ");
70
+ }
71
+ const subject = findSubject(obj);
72
+ const stateEntry = findSemanticEntry(obj, [
73
+ "status",
74
+ "state",
75
+ "condition",
76
+ "stage",
77
+ "role",
78
+ "type",
79
+ "category",
80
+ "classification",
81
+ "priority",
82
+ "phase",
83
+ "mode",
84
+ "availability",
85
+ "outcome",
86
+ "result",
87
+ "health",
88
+ "progress",
89
+ "visibility",
90
+ "access",
91
+ "membership",
92
+ "sentiment",
93
+ "severity"
94
+ ]);
95
+ if (subject && stateEntry && typeof stateEntry[1] === "string") {
96
+ const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
97
+ return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
98
+ }
99
+ return null;
100
+ }
101
+ function findSubject(obj) {
102
+ const subjectKeys = [
103
+ "name",
104
+ "title",
105
+ "label",
106
+ "displayName",
107
+ "entityName",
108
+ "fullName",
109
+ "userName",
110
+ "username",
111
+ "personName",
112
+ "customerName",
113
+ "clientName",
114
+ "ownerName",
115
+ "accountName",
116
+ "companyName",
117
+ "organizationName",
118
+ "teamName",
119
+ "departmentName",
120
+ "projectName",
121
+ "productName",
122
+ "serviceName",
123
+ "resourceName",
124
+ "fileName",
125
+ "deviceName",
126
+ "hostName",
127
+ "applicationName",
128
+ "appName",
129
+ "taskName",
130
+ "eventName",
131
+ "itemName",
132
+ "orderName",
133
+ "patientName",
134
+ "employeeName"
135
+ ];
136
+ const entries = Object.entries(obj);
137
+ for (const key of subjectKeys) {
138
+ if (typeof obj[key] === "string" && obj[key].trim()) {
139
+ return { key, value: obj[key].trim() };
140
+ }
141
+ }
142
+ const nameEntry = entries.find(([key, value]) => {
143
+ return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
144
+ });
145
+ if (nameEntry) {
146
+ return { key: nameEntry[0], value: nameEntry[1].trim() };
147
+ }
148
+ return null;
149
+ }
150
+ function findSemanticEntry(obj, keys) {
151
+ const entries = Object.entries(obj);
152
+ const exactEntry = entries.find(([key]) => keys.includes(key));
153
+ if (exactEntry) return exactEntry;
154
+ return entries.find(([key]) => {
155
+ const normalizedKey = key.toLowerCase();
156
+ return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
157
+ }) ?? null;
158
+ }
159
+ function shouldDescribeSemantically(key, value) {
160
+ return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
161
+ }
162
+ function formatSemanticValue(value) {
163
+ if (typeof value === "number") return formatNumber(value);
164
+ if (typeof value === "boolean") return value ? "enabled" : "disabled";
165
+ return String(value);
166
+ }
167
+ function describeClaim(claim) {
168
+ const parts = [];
169
+ if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
170
+ if (typeof claim.requestedAmount === "number") {
171
+ parts.push(`for ${formatNumber(claim.requestedAmount)}`);
172
+ }
173
+ if (typeof claim.claimNumber === "string") {
174
+ parts.push(`(reference ${claim.claimNumber})`);
175
+ }
176
+ return parts.length ? `${parts.join(" ")}.` : null;
177
+ }
178
+ function formatNumber(value) {
179
+ return new Intl.NumberFormat("en-US").format(value);
180
+ }
181
+ function formatPropertyClause(key, value, depth) {
182
+ const naturalKey = camelToWords(key);
183
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
184
+ if (["internal", "metadata", "debug"].includes(key)) {
185
+ return null;
186
+ }
187
+ const nested = Object.entries(value).filter(([k]) => !["id", "timestamp", "apiKey", "createdAt", "updatedAt"].includes(k)).map(([k, v]) => {
188
+ if (typeof v === "object" && v !== null) {
189
+ return formatPropertyClause(k, v, depth + 1);
190
+ }
191
+ const propValue = toNatural(v, depth + 1);
192
+ if (propValue === "yes" || propValue === "no") {
193
+ return `${camelToWords(k)} ${propValue === "yes" ? "enabled" : "disabled"}`;
194
+ }
195
+ return `${camelToWords(k)} ${propValue}`;
196
+ }).filter((c) => c && c.trim()).join(", ");
197
+ if (!nested) return null;
198
+ return `${naturalKey}: ${nested}`;
199
+ }
200
+ if (Array.isArray(value)) {
201
+ if (value.length === 0) return null;
202
+ if (["skills", "hobbies", "interests", "tags", "languages", "items"].includes(key)) {
203
+ const items2 = value.map((item) => {
204
+ const str = String(item);
205
+ return str.charAt(0).toUpperCase() + str.slice(1);
206
+ });
207
+ return `Having ${joinNaturalList(items2)}`;
208
+ }
209
+ const items = joinNaturalList(value.map((item) => toNatural(item, depth + 1)));
210
+ return `${naturalKey}: ${items}`;
211
+ }
212
+ const naturalValue = toNatural(value, depth + 1);
213
+ if (naturalValue === "yes") return `${naturalKey} enabled`;
214
+ if (naturalValue === "no") return `${naturalKey} disabled`;
215
+ if (key === "theme") return `prefers the ${naturalValue} ${key}`;
216
+ if (key === "age") return `age: ${naturalValue}`;
217
+ if (key === "email") return `email: ${naturalValue}`;
218
+ if (key === "city") return `address: ${naturalValue}`;
219
+ if (key === "debug" && naturalValue === "yes") return `debug enabled`;
220
+ return `${naturalKey}: ${naturalValue}`;
221
+ }
222
+ function camelToWords(str) {
223
+ return str.replace(/([A-Z])/g, " $1").toLowerCase().trim();
224
+ }
225
+ function isPlainObject(value) {
226
+ return typeof value === "object" && value !== null && !Array.isArray(value);
227
+ }
228
+ function joinNaturalList(items) {
229
+ if (items.length === 0) return "";
230
+ if (items.length === 1) return items[0];
231
+ if (items.length === 2) return `${items[0]} and ${items[1]}`;
232
+ const lastItem = items[items.length - 1];
233
+ return `${items.slice(0, -1).join(", ")} and ${lastItem}`;
234
+ }
235
+ function stripTrailingPeriod(value) {
236
+ return value.replace(/\.+$/, "");
237
+ }
238
+
239
+ export {
240
+ toNatural
241
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  analyze
3
- } from "./chunk-IOMOVRC7.js";
3
+ } from "./chunk-2TDVL6BJ.js";
4
4
  import {
5
5
  toTOON
6
6
  } from "./chunk-6VRLDRJK.js";
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-L7BC62MT.js";
13
13
  import {
14
14
  toNatural
15
- } from "./chunk-D4CFTFM3.js";
15
+ } from "./chunk-BN3L7TUV.js";
16
16
  import {
17
17
  prune
18
18
  } from "./chunk-ZD536GZF.js";
@@ -0,0 +1,69 @@
1
+ // src/core/token.ts
2
+ import { createRequire } from "module";
3
+ var require2 = createRequire(import.meta.url);
4
+ function serializeForTokenEstimate(value) {
5
+ if (typeof value === "string") return value;
6
+ const serialized = JSON.stringify(value);
7
+ return serialized ?? "";
8
+ }
9
+ function estimateTokensHeuristic(value) {
10
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
11
+ }
12
+ function expectedEncodingForModel(model) {
13
+ const normalizedModel = model.toLowerCase();
14
+ if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
15
+ return "o200k_base";
16
+ }
17
+ if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
18
+ return "r50k_base";
19
+ }
20
+ return "cl100k_base";
21
+ }
22
+ function estimateTokens(value, options = {}) {
23
+ return estimateTokensWithMeta(value, options).count;
24
+ }
25
+ function estimateTokensWithMeta(value, options = {}) {
26
+ const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
27
+ const heuristic = estimateTokensHeuristic(value);
28
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
29
+ if (!exact) {
30
+ return { count: heuristic, estimator: heuristicEstimator };
31
+ }
32
+ try {
33
+ const tiktoken = require2("tiktoken");
34
+ const encoder = tiktoken.encoding_for_model(model);
35
+ const count = encoder.encode(serializeForTokenEstimate(value)).length;
36
+ let encodingName = encoder.name;
37
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
38
+ try {
39
+ encodingName = tiktoken.model_to_encoding(model);
40
+ } catch {
41
+ encodingName = void 0;
42
+ }
43
+ }
44
+ encodingName ?? (encodingName = expectedEncodingForModel(model));
45
+ encoder.free?.();
46
+ return {
47
+ count,
48
+ estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
49
+ };
50
+ } catch {
51
+ const expectedEncoding = expectedEncodingForModel(model);
52
+ if (!fallbackToHeuristic) {
53
+ return {
54
+ count: heuristic,
55
+ estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
56
+ };
57
+ }
58
+ return {
59
+ count: heuristic,
60
+ estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
61
+ };
62
+ }
63
+ }
64
+
65
+ export {
66
+ serializeForTokenEstimate,
67
+ estimateTokens,
68
+ estimateTokensWithMeta
69
+ };
package/dist/cli.cjs CHANGED
@@ -153,8 +153,18 @@ function serializeForTokenEstimate(value) {
153
153
  function estimateTokensHeuristic(value) {
154
154
  return Math.ceil(serializeForTokenEstimate(value).length / 4);
155
155
  }
156
+ function expectedEncodingForModel(model) {
157
+ const normalizedModel = model.toLowerCase();
158
+ if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
159
+ return "o200k_base";
160
+ }
161
+ if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
162
+ return "r50k_base";
163
+ }
164
+ return "cl100k_base";
165
+ }
156
166
  function estimateTokensWithMeta(value, options = {}) {
157
- const { exact = false, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
167
+ const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
158
168
  const heuristic = estimateTokensHeuristic(value);
159
169
  const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
160
170
  if (!exact) {
@@ -163,36 +173,40 @@ function estimateTokensWithMeta(value, options = {}) {
163
173
  try {
164
174
  const tiktoken = require2("tiktoken");
165
175
  const encoder = tiktoken.encoding_for_model(model);
166
- const text = serializeForTokenEstimate(value);
167
- const count = encoder.encode(text).length;
168
- let encodingName = null;
169
- if (encoder.name) encodingName = encoder.name;
176
+ const count = encoder.encode(serializeForTokenEstimate(value)).length;
177
+ let encodingName = encoder.name;
170
178
  if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
171
179
  try {
172
180
  encodingName = tiktoken.model_to_encoding(model);
173
- } catch (e) {
174
- encodingName = null;
181
+ } catch {
182
+ encodingName = void 0;
175
183
  }
176
184
  }
177
- if (!encodingName) {
178
- const m = String(model || "").toLowerCase();
179
- if (m.includes("davinci") || m.startsWith("text-")) encodingName = "r50k_base";
180
- else encodingName = "cl100k_base";
181
- }
182
- const estimator = `exact tokenizer: model=${model} encoding=${encodingName}`;
183
- return { count, estimator };
184
- } catch (e) {
185
+ encodingName ?? (encodingName = expectedEncodingForModel(model));
186
+ encoder.free?.();
187
+ return {
188
+ count,
189
+ estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
190
+ };
191
+ } catch {
192
+ const expectedEncoding = expectedEncodingForModel(model);
185
193
  if (!fallbackToHeuristic) {
186
- return { count: heuristic, estimator: "exact requested but tokenizer unavailable" };
194
+ return {
195
+ count: heuristic,
196
+ estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
197
+ };
187
198
  }
188
- return { count: heuristic, estimator: heuristicEstimator };
199
+ return {
200
+ count: heuristic,
201
+ estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
202
+ };
189
203
  }
190
204
  }
191
205
 
192
206
  // src/core/analyze.ts
193
207
  function analyze(input, options = {}) {
194
208
  const {
195
- exact = false,
209
+ exact = true,
196
210
  model = "gpt-4o-mini",
197
211
  fallbackToHeuristic = true
198
212
  } = options;
@@ -271,6 +285,15 @@ function toNatural(data, depth = 0) {
271
285
  return String(data);
272
286
  }
273
287
  function buildContextualStory(obj, depth = 0) {
288
+ const semanticStory = buildSemanticStory(obj);
289
+ if (semanticStory) return semanticStory;
290
+ const nestedEntity = Object.values(obj).find((value) => {
291
+ return isPlainObject2(value) && buildSemanticStory(value) !== null;
292
+ });
293
+ if (nestedEntity) {
294
+ const nestedStory = buildSemanticStory(nestedEntity);
295
+ if (nestedStory) return nestedStory;
296
+ }
274
297
  let name = obj.name || obj.userName || obj.user;
275
298
  if (typeof name === "object" && name !== null && name.name) {
276
299
  name = name.name;
@@ -295,6 +318,133 @@ function buildContextualStory(obj, depth = 0) {
295
318
  }
296
319
  return story + ".";
297
320
  }
321
+ function buildSemanticStory(obj) {
322
+ if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
323
+ const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
324
+ const details = [
325
+ typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
326
+ typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
327
+ ].filter((value) => value !== null);
328
+ const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
329
+ if (isPlainObject2(obj.claim)) {
330
+ const claim = describeClaim(obj.claim);
331
+ if (claim) sentences.push(claim);
332
+ }
333
+ if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
334
+ sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
335
+ }
336
+ return sentences.join(" ");
337
+ }
338
+ const subject = findSubject(obj);
339
+ const stateEntry = findSemanticEntry(obj, [
340
+ "status",
341
+ "state",
342
+ "condition",
343
+ "stage",
344
+ "role",
345
+ "type",
346
+ "category",
347
+ "classification",
348
+ "priority",
349
+ "phase",
350
+ "mode",
351
+ "availability",
352
+ "outcome",
353
+ "result",
354
+ "health",
355
+ "progress",
356
+ "visibility",
357
+ "access",
358
+ "membership",
359
+ "sentiment",
360
+ "severity"
361
+ ]);
362
+ if (subject && stateEntry && typeof stateEntry[1] === "string") {
363
+ const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
364
+ return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
365
+ }
366
+ return null;
367
+ }
368
+ function findSubject(obj) {
369
+ const subjectKeys = [
370
+ "name",
371
+ "title",
372
+ "label",
373
+ "displayName",
374
+ "entityName",
375
+ "fullName",
376
+ "userName",
377
+ "username",
378
+ "personName",
379
+ "customerName",
380
+ "clientName",
381
+ "ownerName",
382
+ "accountName",
383
+ "companyName",
384
+ "organizationName",
385
+ "teamName",
386
+ "departmentName",
387
+ "projectName",
388
+ "productName",
389
+ "serviceName",
390
+ "resourceName",
391
+ "fileName",
392
+ "deviceName",
393
+ "hostName",
394
+ "applicationName",
395
+ "appName",
396
+ "taskName",
397
+ "eventName",
398
+ "itemName",
399
+ "orderName",
400
+ "patientName",
401
+ "employeeName"
402
+ ];
403
+ const entries = Object.entries(obj);
404
+ for (const key of subjectKeys) {
405
+ if (typeof obj[key] === "string" && obj[key].trim()) {
406
+ return { key, value: obj[key].trim() };
407
+ }
408
+ }
409
+ const nameEntry = entries.find(([key, value]) => {
410
+ return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
411
+ });
412
+ if (nameEntry) {
413
+ return { key: nameEntry[0], value: nameEntry[1].trim() };
414
+ }
415
+ return null;
416
+ }
417
+ function findSemanticEntry(obj, keys) {
418
+ const entries = Object.entries(obj);
419
+ const exactEntry = entries.find(([key]) => keys.includes(key));
420
+ if (exactEntry) return exactEntry;
421
+ return entries.find(([key]) => {
422
+ const normalizedKey = key.toLowerCase();
423
+ return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
424
+ }) ?? null;
425
+ }
426
+ function shouldDescribeSemantically(key, value) {
427
+ return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
428
+ }
429
+ function formatSemanticValue(value) {
430
+ if (typeof value === "number") return formatNumber(value);
431
+ if (typeof value === "boolean") return value ? "enabled" : "disabled";
432
+ return String(value);
433
+ }
434
+ function describeClaim(claim) {
435
+ const parts = [];
436
+ if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
437
+ if (typeof claim.requestedAmount === "number") {
438
+ parts.push(`for ${formatNumber(claim.requestedAmount)}`);
439
+ }
440
+ if (typeof claim.claimNumber === "string") {
441
+ parts.push(`(reference ${claim.claimNumber})`);
442
+ }
443
+ return parts.length ? `${parts.join(" ")}.` : null;
444
+ }
445
+ function formatNumber(value) {
446
+ return new Intl.NumberFormat("en-US").format(value);
447
+ }
298
448
  function formatPropertyClause(key, value, depth) {
299
449
  const naturalKey = camelToWords(key);
300
450
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
@@ -395,7 +545,7 @@ Usage:
395
545
  Options:
396
546
  --toon Convert JSON to TOON format
397
547
  --compact Convert JSON to compact format
398
- --analyze Show token analysis (heuristic estimate)
548
+ --analyze Show token analysis (tiktoken estimate)
399
549
  `);
400
550
  process.exit(0);
401
551
  }
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AIChain
4
- } from "./chunk-P6KXISNT.js";
5
- import "./chunk-IOMOVRC7.js";
4
+ } from "./chunk-FBJ3DHS6.js";
5
+ import "./chunk-2TDVL6BJ.js";
6
6
  import "./chunk-6VRLDRJK.js";
7
7
  import "./chunk-XE36GLJP.js";
8
8
  import "./chunk-L7BC62MT.js";
9
- import "./chunk-D4CFTFM3.js";
9
+ import "./chunk-BN3L7TUV.js";
10
10
  import "./chunk-ZD536GZF.js";
11
- import "./chunk-U433WUUT.js";
11
+ import "./chunk-L3HY6AWL.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import fs from "fs";
@@ -22,7 +22,7 @@ Usage:
22
22
  Options:
23
23
  --toon Convert JSON to TOON format
24
24
  --compact Convert JSON to compact format
25
- --analyze Show token analysis (heuristic estimate)
25
+ --analyze Show token analysis (tiktoken estimate)
26
26
  `);
27
27
  process.exit(0);
28
28
  }