chatgpt-to-markdown 1.7.1 → 1.8.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/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # ChatGPT to Markdown
2
2
 
3
- Convert ChatGPT conversation JSON files to Markdown format with ease.
4
-
5
3
  [![npm version](https://badge.fury.io/js/chatgpt-to-markdown.svg)](https://badge.fury.io/js/chatgpt-to-markdown)
4
+ [![license](https://img.shields.io/npm/l/smartart.svg)](https://github.com/sanand0/smartart/blob/main/LICENSE)
5
+
6
+ Convert ChatGPT conversation JSON files to Markdown format with ease.
6
7
 
7
8
  ## Usage
8
9
 
@@ -18,29 +19,7 @@ npx chatgpt-to-markdown path/to/your/conversations.json
18
19
 
19
20
  This will generate one Markdown file for each chat same directory as the conversations JSON file. The file name will be the chat title, with invalid filename characters replaced by spaces.
20
21
 
21
- ## Thinking Time Analysis
22
-
23
- Analyze thinking time statistics from your ChatGPT conversations:
24
-
25
- ```bash
26
- npx -p chatgpt-to-markdown thinktime path/to/your/conversations.json
27
- ```
28
-
29
- This will analyze all conversations and show statistics about thinking/reasoning time, including:
30
-
31
- - Total conversations with thinking
32
- - Total thinking time and blocks
33
- - Distribution of thinking times
34
- - Top conversations by thinking time
35
- - Top individual thinking blocks
36
-
37
- Optional flag:
38
-
39
- - `--save-details`: Save detailed results to `thinking_analysis_results_node.json`
40
-
41
- ## Example
42
-
43
- Here's an example of the Markdown output for a chat with the title `Medium-Style Table CSS`:
22
+ For example, the Markdown output for a chat with the title `Medium-Style Table CSS` might look like this:
44
23
 
45
24
  ````markdown
46
25
  # Medium-Style Table CSS
@@ -67,6 +46,26 @@ table {
67
46
  ```
68
47
  ````
69
48
 
49
+ ### Thinking Time Analysis
50
+
51
+ Analyze thinking time statistics from your ChatGPT conversations:
52
+
53
+ ```bash
54
+ npx -p chatgpt-to-markdown thinktime path/to/your/conversations.json
55
+ ```
56
+
57
+ This will analyze all conversations and show statistics about thinking/reasoning time, including:
58
+
59
+ - Total conversations with thinking
60
+ - Total thinking time and blocks
61
+ - Distribution of thinking times
62
+ - Top conversations by thinking time
63
+ - Top individual thinking blocks
64
+
65
+ Optional flag:
66
+
67
+ - `--save-details`: Save detailed results to `thinking_analysis_results_node.json`
68
+
70
69
  ## Installation
71
70
 
72
71
  ```bash
@@ -91,50 +90,40 @@ const options = {
91
90
  chatgptToMarkdown(json, sourceDir);
92
91
  ```
93
92
 
94
- ## Options
93
+ ### Options
95
94
 
96
95
  - `dateFormat`: A function that takes a `Date` object and returns a string. Defaults to a string format like "1 Jan 2023 11:30 PM".
97
96
 
98
- ## Contributing
99
-
100
- - Create a new branch for your feature or bugfix.
101
- - Make your changes.
102
-
103
- # Contributing
97
+ ## Development
104
98
 
105
- - Fork the repository on GitHub and clone the fork to your machine.
106
- - Run `npm install` to install dependencies
107
- - Edit [`index.js`](index.js) or [`cli.js`](cli.js), documenting your changes
108
- - Push your changes back to your fork on GitHub and submit a pull request to the main repository.
99
+ ```bash
100
+ git clone https://github.com/sanand0/chatgpt-to-markdown.git
101
+ cd chatgpt-to-markdown
109
102
 
110
- # Release
103
+ npm install
104
+ npm run lint && npm run build && npm test
111
105
 
112
- ```shell
113
- npm version minor
114
106
  npm publish
115
- git push --follow-tags
107
+ git commit . -m"$COMMIT_MSG"; git tag $VERSION; git push --follow-tags
116
108
  ```
117
109
 
118
110
  ## Release notes
119
111
 
120
- - 1.7.1: 29 Jun 2025. Add thinktime analysis tool as npx executable. Analyze thinking/reasoning time statistics from ChatGPT conversations.
121
- - 1.6.0: 18 Jul 2025. Handle `thoughts`, `reasoning_recap`, `sonic_webpage`. Include projects
122
- - 1.5.5: 02 Nov 2024. Add conversation link. Use conversation ID as fallback title if title is empty.
123
- - 1.5.4: 02 Nov 2024. Skip `user_editable_context` to avoid polluting Markdown with custom instructions
124
- - 1.5.3: 05 Aug 2024. Show text from multimodal prompts
125
- - 1.5.2: 05 Aug 2024. Show tether_browsing_display summary
126
- - 1.5.1: 22 Mar 2024. Handle unicode filenames
127
- - 1.5.0: 28 Nov 2023. Handle `tether_browsing_display`, `tether_quote` and `system_error`
128
- - 1.4.0: 29 Oct 2023. Handle multi-modal text from Dall-E
129
- - 1.3.0: 29 Sep 2023. Set create and update dates from chat
130
- - 1.2.0: 28 Sep 2023. Added test cases
131
- - 1.1.0: 26 Sep 2023. Add date format option
132
- - 1.0.0: 26 Sep 2023. Initial release
112
+ - [1.8.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.8.0): 30 Jul 2025. Standardized package.json & README.md
113
+ - [1.7.1](https://npmjs.com/package/chatgpt-to-markdown/v/1.7.1): 29 Jun 2025. Add thinktime analysis tool as npx executable. Analyze thinking/reasoning time statistics from ChatGPT conversations
114
+ - [1.6.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.6.0): 18 Jun 2025. Handle `thoughts`, `reasoning_recap`, `sonic_webpage`. Include projects
115
+ - [1.5.5](https://npmjs.com/package/chatgpt-to-markdown/v/1.5.5): 2 Nov 2024. Add conversation link. Use conversation ID as fallback title if title is empty
116
+ - [1.5.4](https://npmjs.com/package/chatgpt-to-markdown/v/1.5.4): 2 Nov 2024. Skip `user_editable_context` to avoid polluting Markdown with custom instructions
117
+ - [1.5.3](https://npmjs.com/package/chatgpt-to-markdown/v/1.5.3): 5 Aug 2024. Show text from multimodal prompts
118
+ - [1.5.2](https://npmjs.com/package/chatgpt-to-markdown/v/1.5.2): 5 Aug 2024. Show tether_browsing_display summary
119
+ - [1.5.1](https://npmjs.com/package/chatgpt-to-markdown/v/1.5.1): 22 Mar 2024. Handle unicode filenames
120
+ - [1.5.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.5.0): 28 Nov 2023. Handle `tether_browsing_display`, `tether_quote` and `system_error`
121
+ - [1.4.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.4.0): 29 Oct 2023. Handle multi-modal text from Dall-E
122
+ - [1.3.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.3.0): 29 Sept 2023. Set create and update dates from chat
123
+ - [1.2.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.2.0): 28 Sept 2023. Added test cases
124
+ - [1.1.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.1.0): 26 Sept 2023. Add date format option
125
+ - [1.0.0](https://npmjs.com/package/chatgpt-to-markdown/v/1.0.0): 26 Sept 2023. Initial release
133
126
 
134
127
  ## License
135
128
 
136
- MIT
137
-
138
- ## Support
139
-
140
- If you encounter any problems or have suggestions, please [open an issue](https://github.com/sanand0/chatgpt-to-markdown/issues) or submit a pull request.
129
+ [MIT](LICENSE)
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import path from "path";
3
+ import { promises as fs } from "fs";
4
+ import chatgptToMarkdown from "./chatgpt-to-markdown.js";
5
+
6
+ async function run() {
7
+ const filePath = process.argv[2] || "conversations.json";
8
+ let json;
9
+ try {
10
+ const data = await fs.readFile(filePath, "utf8");
11
+ json = JSON.parse(data);
12
+ } catch {
13
+ console.error(`Error: File '${filePath}' must be a JSON file`);
14
+ console.error("Usage: node chatgpt-to-markdown-cli.js [conversations.json]");
15
+ process.exit(1);
16
+ }
17
+
18
+ const sourceDir = path.dirname(filePath);
19
+ await chatgptToMarkdown(json, sourceDir);
20
+ }
21
+
22
+ run();
@@ -8,7 +8,7 @@ import path from "path";
8
8
  */
9
9
  function sanitizeFileName(title) {
10
10
  return title
11
- .replace(/[<>:"\/\\|?*\n]/g, " ")
11
+ .replace(/[<>:"/\\|?*\n]/g, " ")
12
12
  .replace(/[^\w\s]/gi, " ")
13
13
  .replace(/\s+/g, " ")
14
14
  .trim();
package/package.json CHANGED
@@ -1,31 +1,43 @@
1
1
  {
2
2
  "name": "chatgpt-to-markdown",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Convert ChatGPT exported conversations.json to Markdown",
5
- "main": "index.js",
5
+ "homepage": "https://github.com/sanand0/chatgpt-to-markdown",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/sanand0/chatgpt-to-markdown.git"
9
+ },
10
+ "license": "MIT",
11
+ "author": "Anand S <root.node@gmail.com>",
6
12
  "type": "module",
13
+ "module": "chatgpt-to-markdown.js",
14
+ "prettier": {
15
+ "printWidth": 120
16
+ },
7
17
  "files": [
8
- "cli.js",
9
- "index.js",
18
+ "LICENSE",
19
+ "README.md",
20
+ "chatgpt-to-markdown.js",
10
21
  "thinktime.js",
11
- "README.md"
22
+ "chatgpt-to-markdown-cli.js",
23
+ "thinktime-cli.js"
12
24
  ],
25
+ "exports": "./chatgpt-to-markdown.js",
13
26
  "bin": {
14
- "chatgpt-to-markdown": "cli.js",
15
- "thinktime": "thinktime.js"
27
+ "chatgpt-to-markdown": "chatgpt-to-markdown-cli.js",
28
+ "thinktime": "thinktime-cli.js"
16
29
  },
17
30
  "scripts": {
18
- "prepublishOnly": "npx -y prettier@3.5 --write *.js *.md package.json && npm test",
19
- "test": "npx -y vitest@3 run --globals"
31
+ "lint:oxlint": "npx -y oxlint --fix",
32
+ "lint:js-md": "npx -y prettier@3.5 --print-width 120 --write '**/*.js' '!**/*.min.js' '!dist/**' '**/*.md'",
33
+ "lint:html": "npx -y js-beautify@1 '**/*.html' --type html --replace --indent-size 2 --max-preserve-newlines 1 --end-with-newline",
34
+ "lint": "npm run lint:oxlint && npm run lint:js-md && npm run lint:html",
35
+ "test": "npx -y vitest@3 run --globals",
36
+ "prepublishOnly": "npm run lint && npm test"
20
37
  },
21
38
  "keywords": [
22
39
  "chatgpt",
23
40
  "markdown",
24
41
  "converter"
25
- ],
26
- "author": "S Anand <root.node@gmail.com>",
27
- "license": "MIT",
28
- "prettier": {
29
- "printWidth": 120
30
- }
42
+ ]
31
43
  }
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import { promises as fs } from "fs";
3
+ import { analyzeAllConversations } from "./thinktime.js";
4
+
5
+ function printResults(results) {
6
+ /**
7
+ * Print analysis results in a readable format.
8
+ */
9
+ console.log("\n" + "=".repeat(60));
10
+ console.log("THINKING TIME ANALYSIS RESULTS");
11
+ console.log("=".repeat(60));
12
+
13
+ const totalConvs = results.totalConversations;
14
+ const thinkingConvs = results.conversationsWithThinking;
15
+
16
+ console.log(`Total conversations: ${totalConvs.toLocaleString()}`);
17
+ console.log(
18
+ `Conversations with thinking: ${thinkingConvs.toLocaleString()} (${((thinkingConvs / totalConvs) * 100).toFixed(1)}%)`,
19
+ );
20
+ console.log(`Conversations without thinking: ${(totalConvs - thinkingConvs).toLocaleString()}`);
21
+
22
+ if (thinkingConvs > 0) {
23
+ console.log(`\nTHINKING TIME STATISTICS:`);
24
+ console.log(
25
+ `Total thinking time: ${results.totalThinkingTime.toFixed(1)} seconds (${(results.totalThinkingTime / 60).toFixed(1)} minutes)`,
26
+ );
27
+ console.log(`Total thinking blocks: ${results.totalThinkingBlocks.toLocaleString()}`);
28
+ console.log(
29
+ `Average thinking time per conversation: ${(results.totalThinkingTime / thinkingConvs).toFixed(1)} seconds`,
30
+ );
31
+ console.log(
32
+ `Average thinking blocks per conversation: ${(results.totalThinkingBlocks / thinkingConvs).toFixed(1)}`,
33
+ );
34
+
35
+ console.log(`\nTHINKING TIME DISTRIBUTION:`);
36
+ for (const [category, count] of Object.entries(results.thinkingDistribution)) {
37
+ const percentage = ((count / thinkingConvs) * 100).toFixed(1);
38
+ console.log(` ${category}: ${count.toLocaleString()} conversations (${percentage}%)`);
39
+ }
40
+
41
+ console.log(`\nTOP 10 CONVERSATIONS BY THINKING TIME:`);
42
+ const sortedConvs = results.conversationDetails.sort((a, b) => b.thinkingTimeSec - a.thinkingTimeSec).slice(0, 10);
43
+
44
+ sortedConvs.forEach((conv, i) => {
45
+ const title = conv.title.length > 50 ? conv.title.substring(0, 50) + "..." : conv.title;
46
+ console.log(
47
+ ` ${(i + 1).toString().padStart(2)}. ${conv.thinkingTimeSec.toFixed(1).padStart(6)}s (${conv.thinkingBlocks} blocks) - ${title}`,
48
+ );
49
+ });
50
+
51
+ console.log(`\nTOP 10 INDIVIDUAL THINKING BLOCKS:`);
52
+ const sortedBlocks = results.allThinkingBlocks.sort((a, b) => b.durationSec - a.durationSec).slice(0, 10);
53
+
54
+ sortedBlocks.forEach((block, i) => {
55
+ const title =
56
+ block.conversationTitle.length > 30
57
+ ? block.conversationTitle.substring(0, 30) + "..."
58
+ : block.conversationTitle;
59
+ const userMsg =
60
+ block.precedingUserMessage.length > 80
61
+ ? block.precedingUserMessage.substring(0, 80) + "..."
62
+ : block.precedingUserMessage;
63
+ console.log(` ${(i + 1).toString().padStart(2)}. ${block.durationSec.toFixed(1).padStart(6)}s - ${title}`);
64
+ console.log(` User: ${userMsg}`);
65
+ console.log();
66
+ });
67
+ }
68
+ }
69
+
70
+ async function main() {
71
+ const filename = process.argv[2] || "conversations.json";
72
+
73
+ try {
74
+ const startTime = process.hrtime.bigint();
75
+ const results = await analyzeAllConversations(filename);
76
+ const endTime = process.hrtime.bigint();
77
+
78
+ printResults(results);
79
+
80
+ // Print execution time
81
+ const executionTimeMs = Number(endTime - startTime) / 1000000;
82
+ console.log(`\nExecution time: ${(executionTimeMs / 1000).toFixed(2)} seconds`);
83
+
84
+ // Optionally save detailed results to JSON
85
+ if (process.argv[3] === "--save-details") {
86
+ const outputFile = "thinking_analysis_results_node.json";
87
+ await fs.writeFile(outputFile, JSON.stringify(results, null, 2));
88
+ console.log(`\nDetailed results saved to ${outputFile}`);
89
+ }
90
+ } catch (error) {
91
+ if (error.code === "ENOENT") {
92
+ console.error(`Error: File '${filename}' not found`);
93
+ console.error("Usage: node thinktime-cli.js [conversations.json] [--save-details]");
94
+ process.exit(1);
95
+ } else if (error instanceof SyntaxError) {
96
+ console.error(`Error: Invalid JSON in file '${filename}': ${error.message}`);
97
+ process.exit(1);
98
+ } else {
99
+ console.error(`Error: ${error.message}`);
100
+ process.exit(1);
101
+ }
102
+ }
103
+ }
104
+
105
+ main();
package/thinktime.js CHANGED
@@ -58,7 +58,7 @@ function findPrecedingUserMessage(mapping, targetNodeId) {
58
58
  return "No preceding user message found";
59
59
  }
60
60
 
61
- function analyzeConversationThinking(conversation) {
61
+ export function analyzeConversationThinking(conversation) {
62
62
  /**
63
63
  * Analyze a single conversation for thinking time.
64
64
  *
@@ -104,7 +104,7 @@ function analyzeConversationThinking(conversation) {
104
104
  return { totalThinkingTime, thinkingBlocks, thinkingDetails };
105
105
  }
106
106
 
107
- async function analyzeAllConversations(filename) {
107
+ export async function analyzeAllConversations(filename) {
108
108
  /**
109
109
  * Analyze all conversations in the JSON file.
110
110
  *
@@ -175,105 +175,3 @@ async function analyzeAllConversations(filename) {
175
175
 
176
176
  return results;
177
177
  }
178
-
179
- function printResults(results) {
180
- /**
181
- * Print analysis results in a readable format.
182
- */
183
- console.log("\n" + "=".repeat(60));
184
- console.log("THINKING TIME ANALYSIS RESULTS");
185
- console.log("=".repeat(60));
186
-
187
- const totalConvs = results.totalConversations;
188
- const thinkingConvs = results.conversationsWithThinking;
189
-
190
- console.log(`Total conversations: ${totalConvs.toLocaleString()}`);
191
- console.log(
192
- `Conversations with thinking: ${thinkingConvs.toLocaleString()} (${((thinkingConvs / totalConvs) * 100).toFixed(1)}%)`,
193
- );
194
- console.log(`Conversations without thinking: ${(totalConvs - thinkingConvs).toLocaleString()}`);
195
-
196
- if (thinkingConvs > 0) {
197
- console.log(`\nTHINKING TIME STATISTICS:`);
198
- console.log(
199
- `Total thinking time: ${results.totalThinkingTime.toFixed(1)} seconds (${(results.totalThinkingTime / 60).toFixed(1)} minutes)`,
200
- );
201
- console.log(`Total thinking blocks: ${results.totalThinkingBlocks.toLocaleString()}`);
202
- console.log(
203
- `Average thinking time per conversation: ${(results.totalThinkingTime / thinkingConvs).toFixed(1)} seconds`,
204
- );
205
- console.log(
206
- `Average thinking blocks per conversation: ${(results.totalThinkingBlocks / thinkingConvs).toFixed(1)}`,
207
- );
208
-
209
- console.log(`\nTHINKING TIME DISTRIBUTION:`);
210
- for (const [category, count] of Object.entries(results.thinkingDistribution)) {
211
- const percentage = ((count / thinkingConvs) * 100).toFixed(1);
212
- console.log(` ${category}: ${count.toLocaleString()} conversations (${percentage}%)`);
213
- }
214
-
215
- console.log(`\nTOP 10 CONVERSATIONS BY THINKING TIME:`);
216
- const sortedConvs = results.conversationDetails.sort((a, b) => b.thinkingTimeSec - a.thinkingTimeSec).slice(0, 10);
217
-
218
- sortedConvs.forEach((conv, i) => {
219
- const title = conv.title.length > 50 ? conv.title.substring(0, 50) + "..." : conv.title;
220
- console.log(
221
- ` ${(i + 1).toString().padStart(2)}. ${conv.thinkingTimeSec.toFixed(1).padStart(6)}s (${conv.thinkingBlocks} blocks) - ${title}`,
222
- );
223
- });
224
-
225
- console.log(`\nTOP 10 INDIVIDUAL THINKING BLOCKS:`);
226
- const sortedBlocks = results.allThinkingBlocks.sort((a, b) => b.durationSec - a.durationSec).slice(0, 10);
227
-
228
- sortedBlocks.forEach((block, i) => {
229
- const title =
230
- block.conversationTitle.length > 30
231
- ? block.conversationTitle.substring(0, 30) + "..."
232
- : block.conversationTitle;
233
- const userMsg =
234
- block.precedingUserMessage.length > 80
235
- ? block.precedingUserMessage.substring(0, 80) + "..."
236
- : block.precedingUserMessage;
237
- console.log(` ${(i + 1).toString().padStart(2)}. ${block.durationSec.toFixed(1).padStart(6)}s - ${title}`);
238
- console.log(` User: ${userMsg}`);
239
- console.log();
240
- });
241
- }
242
- }
243
-
244
- async function main() {
245
- const filename = process.argv[2] || "conversations.json";
246
-
247
- try {
248
- const startTime = process.hrtime.bigint();
249
- const results = await analyzeAllConversations(filename);
250
- const endTime = process.hrtime.bigint();
251
-
252
- printResults(results);
253
-
254
- // Print execution time
255
- const executionTimeMs = Number(endTime - startTime) / 1000000;
256
- console.log(`\nExecution time: ${(executionTimeMs / 1000).toFixed(2)} seconds`);
257
-
258
- // Optionally save detailed results to JSON
259
- if (process.argv[3] === "--save-details") {
260
- const outputFile = "thinking_analysis_results_node.json";
261
- await fs.writeFile(outputFile, JSON.stringify(results, null, 2));
262
- console.log(`\nDetailed results saved to ${outputFile}`);
263
- }
264
- } catch (error) {
265
- if (error.code === "ENOENT") {
266
- console.error(`Error: File '${filename}' not found`);
267
- console.error("Usage: node thinking_time.js [conversations.json] [--save-details]");
268
- process.exit(1);
269
- } else if (error instanceof SyntaxError) {
270
- console.error(`Error: Invalid JSON in file '${filename}': ${error.message}`);
271
- process.exit(1);
272
- } else {
273
- console.error(`Error: ${error.message}`);
274
- process.exit(1);
275
- }
276
- }
277
- }
278
-
279
- main();
package/cli.js DELETED
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
- import path from "path";
3
- import { promises as fs } from "fs";
4
- import chatgptToMarkdown from "./index.js";
5
-
6
- async function run() {
7
- const filePath = process.argv[2];
8
- if (!filePath) {
9
- console.error("Please provide a file path as a command line argument.");
10
- process.exit(1);
11
- }
12
-
13
- const data = await fs.readFile(filePath, "utf8");
14
- const json = JSON.parse(data);
15
- const sourceDir = path.dirname(filePath);
16
- await chatgptToMarkdown(json, sourceDir);
17
- }
18
-
19
- run();