@junhoyeo/prompthistory 0.1.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 +215 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +342 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Junho Yeo
|
|
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,215 @@
|
|
|
1
|
+
# prompthistory
|
|
2
|
+
|
|
3
|
+
> CLI tool to search and navigate your OpenCode prompt history
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/prompthistory)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- ๐ **Fuzzy search** through all your prompts
|
|
11
|
+
- ๐ **Filter by project** - find prompts from specific projects
|
|
12
|
+
- ๐
**Date range filtering** - search within time ranges
|
|
13
|
+
- ๐จ **Beautiful output** - formatted tables with colors
|
|
14
|
+
- โก **Fast** - streaming parser handles large history files efficiently
|
|
15
|
+
- ๐งน **Smart filtering** - automatically excludes slash commands, optional deduplication
|
|
16
|
+
- ๐ **Clipboard support** - copy any prompt with `--copy`
|
|
17
|
+
- ๐ฑ๏ธ **Interactive mode** - arrow key navigation with `--interactive`
|
|
18
|
+
- ๐ค **Export** - save results as JSON, CSV, or plain text
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Using npm
|
|
24
|
+
npm install -g prompthistory
|
|
25
|
+
|
|
26
|
+
# Using pnpm
|
|
27
|
+
pnpm add -g prompthistory
|
|
28
|
+
|
|
29
|
+
# Using bunx (no install needed)
|
|
30
|
+
bunx prompthistory
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### List recent prompts
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
prompthistory list --limit 10
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Search by keyword
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
prompthistory search "bug fix"
|
|
45
|
+
prompthistory search "api"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Filter by project
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
prompthistory search "refactor" --project tokscale
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Filter by date
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
prompthistory search "commit" --from 2026-01-01 --to 2026-01-13
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Show detailed information
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
prompthistory show 5
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Commands
|
|
67
|
+
|
|
68
|
+
### `prompthistory search [query]`
|
|
69
|
+
|
|
70
|
+
Search through your prompt history with optional filters.
|
|
71
|
+
|
|
72
|
+
**Options:**
|
|
73
|
+
- `-p, --project <project>` - Filter by project path (partial match)
|
|
74
|
+
- `-f, --from <date>` - Filter from date (YYYY-MM-DD)
|
|
75
|
+
- `-t, --to <date>` - Filter to date (YYYY-MM-DD)
|
|
76
|
+
- `-l, --limit <number>` - Limit number of results (default: 20)
|
|
77
|
+
- `-u, --unique` - Show only unique prompts (deduplicate)
|
|
78
|
+
- `-c, --copy` - Copy selected result to clipboard
|
|
79
|
+
- `-i, --interactive` - Interactive mode with arrow key navigation
|
|
80
|
+
- `--include-slash-commands` - Include slash commands in results (excluded by default)
|
|
81
|
+
|
|
82
|
+
**Examples:**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Basic search
|
|
86
|
+
prompthistory search "commit"
|
|
87
|
+
|
|
88
|
+
# Search with project filter
|
|
89
|
+
prompthistory search "api" --project my-project
|
|
90
|
+
|
|
91
|
+
# Search with date range
|
|
92
|
+
prompthistory search "bug" --from 2026-01-01
|
|
93
|
+
|
|
94
|
+
# Deduplicated results
|
|
95
|
+
prompthistory search "refactor" --unique --limit 10
|
|
96
|
+
|
|
97
|
+
# Interactive mode with copy
|
|
98
|
+
prompthistory search "api" --interactive --copy
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `prompthistory list`
|
|
102
|
+
|
|
103
|
+
List recent prompts without searching.
|
|
104
|
+
|
|
105
|
+
**Options:**
|
|
106
|
+
- `-l, --limit <number>` - Number of prompts to show (default: 10)
|
|
107
|
+
- `-p, --project <project>` - Filter by project path
|
|
108
|
+
- `--include-slash-commands` - Include slash commands
|
|
109
|
+
|
|
110
|
+
**Examples:**
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Show 10 most recent prompts
|
|
114
|
+
prompthistory list
|
|
115
|
+
|
|
116
|
+
# Show 50 recent prompts
|
|
117
|
+
prompthistory list --limit 50
|
|
118
|
+
|
|
119
|
+
# Show recent prompts from specific project
|
|
120
|
+
prompthistory list --project my-project --limit 20
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `prompthistory show <index>`
|
|
124
|
+
|
|
125
|
+
Show detailed information about a specific prompt by its index from search/list results.
|
|
126
|
+
|
|
127
|
+
**Options:**
|
|
128
|
+
- `-c, --copy` - Copy prompt to clipboard
|
|
129
|
+
|
|
130
|
+
**Examples:**
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
prompthistory list --limit 5
|
|
134
|
+
prompthistory show 3
|
|
135
|
+
|
|
136
|
+
# Copy prompt to clipboard
|
|
137
|
+
prompthistory show 3 --copy
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `prompthistory export [query]`
|
|
141
|
+
|
|
142
|
+
Export search results to a file or stdout.
|
|
143
|
+
|
|
144
|
+
**Options:**
|
|
145
|
+
- `-p, --project <project>` - Filter by project path
|
|
146
|
+
- `-f, --from <date>` - Filter from date (YYYY-MM-DD)
|
|
147
|
+
- `-t, --to <date>` - Filter to date (YYYY-MM-DD)
|
|
148
|
+
- `-l, --limit <number>` - Limit number of results (default: 100)
|
|
149
|
+
- `--format <format>` - Output format: `json`, `csv`, or `txt` (default: json)
|
|
150
|
+
- `-o, --output <path>` - Output file path (prints to stdout if not specified)
|
|
151
|
+
|
|
152
|
+
**Examples:**
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Export as JSON to stdout
|
|
156
|
+
prompthistory export --limit 50 --format json
|
|
157
|
+
|
|
158
|
+
# Export to file
|
|
159
|
+
prompthistory export --format csv --output prompts.csv
|
|
160
|
+
|
|
161
|
+
# Export filtered results
|
|
162
|
+
prompthistory export "api" --project my-project --format txt -o api-prompts.txt
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Data Source
|
|
166
|
+
|
|
167
|
+
This tool reads from your OpenCode history file:
|
|
168
|
+
- **Location**: `~/.claude/history.jsonl`
|
|
169
|
+
- **Format**: JSON Lines (one JSON object per line)
|
|
170
|
+
- **Read-only**: Never modifies your history file
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Clone the repo
|
|
176
|
+
git clone https://github.com/junhoyeo/prompthistory.git
|
|
177
|
+
cd prompthistory
|
|
178
|
+
|
|
179
|
+
# Install dependencies
|
|
180
|
+
bun install
|
|
181
|
+
|
|
182
|
+
# Build
|
|
183
|
+
bun run build
|
|
184
|
+
|
|
185
|
+
# Test locally
|
|
186
|
+
bun run dev search "test"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Tech Stack
|
|
190
|
+
|
|
191
|
+
- **CLI Framework**: [Commander.js](https://github.com/tj/commander.js)
|
|
192
|
+
- **Fuzzy Search**: [Fuse.js](https://fusejs.io)
|
|
193
|
+
- **Output Formatting**: [chalk](https://github.com/chalk/chalk), [cli-table3](https://github.com/cli-table/cli-table3)
|
|
194
|
+
- **Interactive UI**: [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js)
|
|
195
|
+
- **Clipboard**: [clipboardy](https://github.com/sindresorhus/clipboardy)
|
|
196
|
+
- **Date Handling**: [date-fns](https://date-fns.org)
|
|
197
|
+
- **Schema Validation**: [Zod](https://zod.dev)
|
|
198
|
+
- **Build Tool**: [tsup](https://tsup.egoist.dev)
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT ยฉ [junhoyeo](https://github.com/junhoyeo)
|
|
203
|
+
|
|
204
|
+
## Related
|
|
205
|
+
|
|
206
|
+
- [OpenCode](https://github.com/cline/cline) - AI coding assistant
|
|
207
|
+
- [tokscale](https://github.com/junhoyeo/tokscale) - Track token usage from coding agents
|
|
208
|
+
|
|
209
|
+
## Contributing
|
|
210
|
+
|
|
211
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
Made with โค๏ธ by [@junhoyeo](https://github.com/junhoyeo)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { program } from "commander";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { writeFileSync } from "fs";
|
|
8
|
+
import chalk2 from "chalk";
|
|
9
|
+
import clipboard from "clipboardy";
|
|
10
|
+
import { select } from "@inquirer/prompts";
|
|
11
|
+
|
|
12
|
+
// src/core/parser.ts
|
|
13
|
+
import { createReadStream } from "fs";
|
|
14
|
+
import { createInterface } from "readline";
|
|
15
|
+
|
|
16
|
+
// src/core/schema.ts
|
|
17
|
+
import { z } from "zod/v4";
|
|
18
|
+
var MIN_TIMESTAMP = 1e12;
|
|
19
|
+
var MAX_DISPLAY_LENGTH = 500;
|
|
20
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21
|
+
var PastedContentSchema = z.object({
|
|
22
|
+
id: z.number(),
|
|
23
|
+
type: z.literal("text"),
|
|
24
|
+
content: z.string().optional(),
|
|
25
|
+
contentHash: z.string().optional()
|
|
26
|
+
});
|
|
27
|
+
var HistoryEntrySchema = z.object({
|
|
28
|
+
display: z.string(),
|
|
29
|
+
pastedContents: z.record(z.string(), PastedContentSchema).or(z.object({})),
|
|
30
|
+
timestamp: z.number().min(MIN_TIMESTAMP),
|
|
31
|
+
project: z.string(),
|
|
32
|
+
sessionId: z.string().regex(UUID_REGEX).optional()
|
|
33
|
+
}).passthrough();
|
|
34
|
+
function truncateDisplay(display) {
|
|
35
|
+
if (display.length <= MAX_DISPLAY_LENGTH) {
|
|
36
|
+
return display;
|
|
37
|
+
}
|
|
38
|
+
return display.substring(0, MAX_DISPLAY_LENGTH - 3) + "...";
|
|
39
|
+
}
|
|
40
|
+
function isSlashCommand(display) {
|
|
41
|
+
return display.trimStart().startsWith("/");
|
|
42
|
+
}
|
|
43
|
+
function parseEntry(line, lineNumber) {
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(line);
|
|
47
|
+
} catch {
|
|
48
|
+
console.warn(`[parseEntry] Malformed JSON at line ${lineNumber}: ${line.substring(0, 50)}...`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const result = HistoryEntrySchema.safeParse(parsed);
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
console.warn(
|
|
54
|
+
`[parseEntry] Schema validation failed at line ${lineNumber}: ${result.error.message}`
|
|
55
|
+
);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const entry = result.data;
|
|
59
|
+
return {
|
|
60
|
+
...entry,
|
|
61
|
+
pastedContents: entry.pastedContents,
|
|
62
|
+
_lineNumber: lineNumber,
|
|
63
|
+
_truncatedDisplay: truncateDisplay(entry.display),
|
|
64
|
+
_isSlashCommand: isSlashCommand(entry.display)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/core/parser.ts
|
|
69
|
+
async function parseHistory(filePath) {
|
|
70
|
+
const entries = [];
|
|
71
|
+
const fileStream = createReadStream(filePath);
|
|
72
|
+
const rl = createInterface({
|
|
73
|
+
input: fileStream,
|
|
74
|
+
crlfDelay: Infinity
|
|
75
|
+
});
|
|
76
|
+
let lineNumber = 0;
|
|
77
|
+
for await (const line of rl) {
|
|
78
|
+
lineNumber++;
|
|
79
|
+
if (!line.trim()) continue;
|
|
80
|
+
const entry = parseEntry(line, lineNumber);
|
|
81
|
+
if (entry) {
|
|
82
|
+
entries.push(entry);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return entries;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/search.ts
|
|
89
|
+
import Fuse from "fuse.js";
|
|
90
|
+
var HistorySearchEngine = class {
|
|
91
|
+
constructor(entries) {
|
|
92
|
+
this.entries = entries;
|
|
93
|
+
this.fuse = new Fuse(entries, {
|
|
94
|
+
keys: ["display", "project"],
|
|
95
|
+
threshold: 0.3,
|
|
96
|
+
includeScore: true,
|
|
97
|
+
includeMatches: true,
|
|
98
|
+
ignoreLocation: true,
|
|
99
|
+
minMatchCharLength: 2
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
search(options) {
|
|
103
|
+
let results;
|
|
104
|
+
if (options.query) {
|
|
105
|
+
const fuseResults = this.fuse.search(options.query);
|
|
106
|
+
results = fuseResults.map((r) => ({
|
|
107
|
+
entry: r.item,
|
|
108
|
+
score: r.score,
|
|
109
|
+
matches: r.matches?.map((m) => ({
|
|
110
|
+
key: m.key || "",
|
|
111
|
+
value: m.value || "",
|
|
112
|
+
indices: m.indices || []
|
|
113
|
+
}))
|
|
114
|
+
}));
|
|
115
|
+
} else {
|
|
116
|
+
results = this.entries.map((entry) => ({ entry }));
|
|
117
|
+
}
|
|
118
|
+
results = this.applyFilters(results, options);
|
|
119
|
+
if (options.limit) {
|
|
120
|
+
results = results.slice(0, options.limit);
|
|
121
|
+
}
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
applyFilters(results, options) {
|
|
125
|
+
let filtered = results;
|
|
126
|
+
if (options.project) {
|
|
127
|
+
const projectFilter = options.project.toLowerCase();
|
|
128
|
+
filtered = filtered.filter(
|
|
129
|
+
(r) => r.entry.project.toLowerCase().includes(projectFilter)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (options.from) {
|
|
133
|
+
filtered = filtered.filter((r) => r.entry.timestamp >= options.from.getTime());
|
|
134
|
+
}
|
|
135
|
+
if (options.to) {
|
|
136
|
+
const endOfDay = options.to.getTime() + 86399999;
|
|
137
|
+
filtered = filtered.filter((r) => r.entry.timestamp <= endOfDay);
|
|
138
|
+
}
|
|
139
|
+
if (!options.includeSlashCommands) {
|
|
140
|
+
filtered = filtered.filter((r) => !r.entry.display.trim().startsWith("/"));
|
|
141
|
+
}
|
|
142
|
+
if (options.unique) {
|
|
143
|
+
const seen = /* @__PURE__ */ new Set();
|
|
144
|
+
filtered = filtered.filter((r) => {
|
|
145
|
+
const key = r.entry.display.trim().toLowerCase();
|
|
146
|
+
if (seen.has(key)) return false;
|
|
147
|
+
seen.add(key);
|
|
148
|
+
return true;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return filtered;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/utils/formatter.ts
|
|
156
|
+
import chalk from "chalk";
|
|
157
|
+
import Table from "cli-table3";
|
|
158
|
+
import { formatDistanceToNow } from "date-fns";
|
|
159
|
+
function formatSearchResults(results, detailed = false) {
|
|
160
|
+
if (results.length === 0) {
|
|
161
|
+
return chalk.yellow("No results found");
|
|
162
|
+
}
|
|
163
|
+
const table = new Table({
|
|
164
|
+
head: [chalk.cyan("#"), chalk.cyan("Prompt"), chalk.cyan("Project"), chalk.cyan("Time")],
|
|
165
|
+
colWidths: [5, 60, 30, 15],
|
|
166
|
+
wordWrap: true
|
|
167
|
+
});
|
|
168
|
+
results.forEach((result, index) => {
|
|
169
|
+
const { entry } = result;
|
|
170
|
+
const display = truncate(entry.display, 200);
|
|
171
|
+
const project = entry.project.split("/").pop() || entry.project;
|
|
172
|
+
const timeAgo = formatDistanceToNow(new Date(entry.timestamp), { addSuffix: true });
|
|
173
|
+
table.push([
|
|
174
|
+
chalk.gray((index + 1).toString()),
|
|
175
|
+
display,
|
|
176
|
+
chalk.dim(project),
|
|
177
|
+
chalk.dim(timeAgo)
|
|
178
|
+
]);
|
|
179
|
+
});
|
|
180
|
+
return table.toString();
|
|
181
|
+
}
|
|
182
|
+
function formatDetailedResult(result) {
|
|
183
|
+
const { entry } = result;
|
|
184
|
+
const lines = [
|
|
185
|
+
chalk.bold.cyan("Prompt:"),
|
|
186
|
+
entry.display,
|
|
187
|
+
"",
|
|
188
|
+
chalk.bold.cyan("Project:"),
|
|
189
|
+
entry.project,
|
|
190
|
+
"",
|
|
191
|
+
chalk.bold.cyan("Timestamp:"),
|
|
192
|
+
new Date(entry.timestamp).toLocaleString(),
|
|
193
|
+
"",
|
|
194
|
+
chalk.bold.cyan("Time Ago:"),
|
|
195
|
+
formatDistanceToNow(new Date(entry.timestamp), { addSuffix: true })
|
|
196
|
+
];
|
|
197
|
+
if (entry.sessionId) {
|
|
198
|
+
lines.push("", chalk.bold.cyan("Session ID:"), entry.sessionId);
|
|
199
|
+
}
|
|
200
|
+
return lines.join("\n");
|
|
201
|
+
}
|
|
202
|
+
function truncate(text, maxLength) {
|
|
203
|
+
if (text.length <= maxLength) return text;
|
|
204
|
+
return text.substring(0, maxLength - 3) + "...";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/index.ts
|
|
208
|
+
var DEFAULT_HISTORY_PATH = join(homedir(), ".claude", "history.jsonl");
|
|
209
|
+
function exportResults(results, format, outputPath) {
|
|
210
|
+
let content;
|
|
211
|
+
switch (format) {
|
|
212
|
+
case "json":
|
|
213
|
+
content = JSON.stringify(results.map((r) => r.entry), null, 2);
|
|
214
|
+
break;
|
|
215
|
+
case "csv":
|
|
216
|
+
const headers = "timestamp,project,display,sessionId";
|
|
217
|
+
const rows = results.map((r) => {
|
|
218
|
+
const e = r.entry;
|
|
219
|
+
const escapedDisplay = `"${e.display.replace(/"/g, '""').replace(/\n/g, "\\n")}"`;
|
|
220
|
+
return `${e.timestamp},${e.project},${escapedDisplay},${e.sessionId || ""}`;
|
|
221
|
+
});
|
|
222
|
+
content = [headers, ...rows].join("\n");
|
|
223
|
+
break;
|
|
224
|
+
case "txt":
|
|
225
|
+
default:
|
|
226
|
+
content = results.map((r) => r.entry.display).join("\n\n---\n\n");
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
if (outputPath) {
|
|
230
|
+
writeFileSync(outputPath, content);
|
|
231
|
+
return `Exported ${results.length} results to ${outputPath}`;
|
|
232
|
+
}
|
|
233
|
+
return content;
|
|
234
|
+
}
|
|
235
|
+
async function interactiveSelect(results) {
|
|
236
|
+
if (results.length === 0) {
|
|
237
|
+
console.log(chalk2.yellow("No results to select from"));
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const choices = results.map((r, i) => ({
|
|
241
|
+
name: `${i + 1}. ${r.entry.display.substring(0, 80)}${r.entry.display.length > 80 ? "..." : ""}`,
|
|
242
|
+
value: i,
|
|
243
|
+
description: `${r.entry.project} - ${new Date(r.entry.timestamp).toLocaleDateString()}`
|
|
244
|
+
}));
|
|
245
|
+
const selectedIndex = await select({
|
|
246
|
+
message: "Select a prompt:",
|
|
247
|
+
choices,
|
|
248
|
+
pageSize: 15
|
|
249
|
+
});
|
|
250
|
+
return results[selectedIndex];
|
|
251
|
+
}
|
|
252
|
+
program.name("prompthistory").description("CLI tool to search and navigate OpenCode prompt history").version("0.1.0");
|
|
253
|
+
program.command("search").description("Search through prompt history").argument("[query]", "search term").option("-p, --project <project>", "filter by project path").option("-f, --from <date>", "filter from date (YYYY-MM-DD)").option("-t, --to <date>", "filter to date (YYYY-MM-DD)").option("-l, --limit <number>", "limit number of results", "20").option("-u, --unique", "show only unique prompts").option("--include-slash-commands", "include slash commands in results").option("-c, --copy", "copy selected result to clipboard").option("-i, --interactive", "interactive mode with arrow key navigation").action(async (query, options) => {
|
|
254
|
+
try {
|
|
255
|
+
const entries = await parseHistory(DEFAULT_HISTORY_PATH);
|
|
256
|
+
const searchEngine = new HistorySearchEngine(entries);
|
|
257
|
+
const searchOptions = {
|
|
258
|
+
query,
|
|
259
|
+
project: options.project,
|
|
260
|
+
from: options.from ? new Date(options.from) : void 0,
|
|
261
|
+
to: options.to ? new Date(options.to) : void 0,
|
|
262
|
+
limit: parseInt(options.limit, 10),
|
|
263
|
+
unique: options.unique,
|
|
264
|
+
includeSlashCommands: options.includeSlashCommands
|
|
265
|
+
};
|
|
266
|
+
const results = searchEngine.search(searchOptions);
|
|
267
|
+
if (options.interactive || options.copy) {
|
|
268
|
+
const selected = await interactiveSelect(results);
|
|
269
|
+
if (selected) {
|
|
270
|
+
if (options.copy) {
|
|
271
|
+
await clipboard.write(selected.entry.display);
|
|
272
|
+
console.log(chalk2.green("Copied to clipboard!"));
|
|
273
|
+
}
|
|
274
|
+
console.log(formatDetailedResult(selected));
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
console.log(formatSearchResults(results));
|
|
278
|
+
console.log(chalk2.dim(`
|
|
279
|
+
Found ${results.length} results`));
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error(chalk2.red("Error:"), error instanceof Error ? error.message : error);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
program.command("list").description("List recent prompts").option("-l, --limit <number>", "number of prompts to show", "10").option("-p, --project <project>", "filter by project path").option("--include-slash-commands", "include slash commands in results").action(async (options) => {
|
|
287
|
+
try {
|
|
288
|
+
const entries = await parseHistory(DEFAULT_HISTORY_PATH);
|
|
289
|
+
const sorted = entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
290
|
+
const searchEngine = new HistorySearchEngine(sorted);
|
|
291
|
+
const results = searchEngine.search({
|
|
292
|
+
project: options.project,
|
|
293
|
+
limit: parseInt(options.limit, 10),
|
|
294
|
+
includeSlashCommands: options.includeSlashCommands
|
|
295
|
+
});
|
|
296
|
+
console.log(formatSearchResults(results));
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error(chalk2.red("Error:"), error instanceof Error ? error.message : error);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
program.command("show").description("Show detailed information about a specific prompt").argument("<index>", "index from search/list results").option("-c, --copy", "copy prompt to clipboard").action(async (index, options) => {
|
|
303
|
+
try {
|
|
304
|
+
const entries = await parseHistory(DEFAULT_HISTORY_PATH);
|
|
305
|
+
const idx = parseInt(index, 10) - 1;
|
|
306
|
+
if (idx < 0 || idx >= entries.length) {
|
|
307
|
+
console.error(chalk2.red("Error: Invalid index"));
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
const sorted = entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
311
|
+
const entry = sorted[idx];
|
|
312
|
+
if (options.copy) {
|
|
313
|
+
await clipboard.write(entry.display);
|
|
314
|
+
console.log(chalk2.green("Copied to clipboard!"));
|
|
315
|
+
}
|
|
316
|
+
console.log(formatDetailedResult({ entry }));
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error(chalk2.red("Error:"), error instanceof Error ? error.message : error);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
program.command("export").description("Export search results to file").argument("[query]", "search term").option("-p, --project <project>", "filter by project path").option("-f, --from <date>", "filter from date (YYYY-MM-DD)").option("-t, --to <date>", "filter to date (YYYY-MM-DD)").option("-l, --limit <number>", "limit number of results", "100").option("--format <format>", "output format: json, csv, txt", "json").option("-o, --output <path>", "output file path").action(async (query, options) => {
|
|
323
|
+
try {
|
|
324
|
+
const entries = await parseHistory(DEFAULT_HISTORY_PATH);
|
|
325
|
+
const searchEngine = new HistorySearchEngine(entries);
|
|
326
|
+
const searchOptions = {
|
|
327
|
+
query,
|
|
328
|
+
project: options.project,
|
|
329
|
+
from: options.from ? new Date(options.from) : void 0,
|
|
330
|
+
to: options.to ? new Date(options.to) : void 0,
|
|
331
|
+
limit: parseInt(options.limit, 10)
|
|
332
|
+
};
|
|
333
|
+
const results = searchEngine.search(searchOptions);
|
|
334
|
+
const output = exportResults(results, options.format, options.output);
|
|
335
|
+
console.log(output);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error(chalk2.red("Error:"), error instanceof Error ? error.message : error);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
program.parse();
|
|
342
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/parser.ts","../src/core/schema.ts","../src/core/search.ts","../src/utils/formatter.ts"],"sourcesContent":["import { program } from 'commander';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { writeFileSync } from 'node:fs';\nimport chalk from 'chalk';\nimport clipboard from 'clipboardy';\nimport { select } from '@inquirer/prompts';\nimport { parseHistory } from './core/parser.js';\nimport { HistorySearchEngine } from './core/search.js';\nimport { formatSearchResults, formatDetailedResult } from './utils/formatter.js';\nimport type { SearchOptions, SearchResult } from './types/history.js';\n\nconst DEFAULT_HISTORY_PATH = join(homedir(), '.claude', 'history.jsonl');\n\nfunction exportResults(results: SearchResult[], format: string, outputPath?: string): string {\n let content: string;\n \n switch (format) {\n case 'json':\n content = JSON.stringify(results.map(r => r.entry), null, 2);\n break;\n case 'csv':\n const headers = 'timestamp,project,display,sessionId';\n const rows = results.map(r => {\n const e = r.entry;\n const escapedDisplay = `\"${e.display.replace(/\"/g, '\"\"').replace(/\\n/g, '\\\\n')}\"`;\n return `${e.timestamp},${e.project},${escapedDisplay},${e.sessionId || ''}`;\n });\n content = [headers, ...rows].join('\\n');\n break;\n case 'txt':\n default:\n content = results.map(r => r.entry.display).join('\\n\\n---\\n\\n');\n break;\n }\n \n if (outputPath) {\n writeFileSync(outputPath, content);\n return `Exported ${results.length} results to ${outputPath}`;\n }\n \n return content;\n}\n\nasync function interactiveSelect(results: SearchResult[]): Promise<SearchResult | null> {\n if (results.length === 0) {\n console.log(chalk.yellow('No results to select from'));\n return null;\n }\n\n const choices = results.map((r, i) => ({\n name: `${i + 1}. ${r.entry.display.substring(0, 80)}${r.entry.display.length > 80 ? '...' : ''}`,\n value: i,\n description: `${r.entry.project} - ${new Date(r.entry.timestamp).toLocaleDateString()}`,\n }));\n\n const selectedIndex = await select({\n message: 'Select a prompt:',\n choices,\n pageSize: 15,\n });\n\n return results[selectedIndex];\n}\n\nprogram\n .name('prompthistory')\n .description('CLI tool to search and navigate OpenCode prompt history')\n .version('0.1.0');\n\nprogram\n .command('search')\n .description('Search through prompt history')\n .argument('[query]', 'search term')\n .option('-p, --project <project>', 'filter by project path')\n .option('-f, --from <date>', 'filter from date (YYYY-MM-DD)')\n .option('-t, --to <date>', 'filter to date (YYYY-MM-DD)')\n .option('-l, --limit <number>', 'limit number of results', '20')\n .option('-u, --unique', 'show only unique prompts')\n .option('--include-slash-commands', 'include slash commands in results')\n .option('-c, --copy', 'copy selected result to clipboard')\n .option('-i, --interactive', 'interactive mode with arrow key navigation')\n .action(async (query, options) => {\n try {\n const entries = await parseHistory(DEFAULT_HISTORY_PATH);\n const searchEngine = new HistorySearchEngine(entries);\n\n const searchOptions: SearchOptions = {\n query,\n project: options.project,\n from: options.from ? new Date(options.from) : undefined,\n to: options.to ? new Date(options.to) : undefined,\n limit: parseInt(options.limit, 10),\n unique: options.unique,\n includeSlashCommands: options.includeSlashCommands,\n };\n\n const results = searchEngine.search(searchOptions);\n\n if (options.interactive || options.copy) {\n const selected = await interactiveSelect(results);\n if (selected) {\n if (options.copy) {\n await clipboard.write(selected.entry.display);\n console.log(chalk.green('Copied to clipboard!'));\n }\n console.log(formatDetailedResult(selected));\n }\n } else {\n console.log(formatSearchResults(results));\n console.log(chalk.dim(`\\nFound ${results.length} results`));\n }\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('list')\n .description('List recent prompts')\n .option('-l, --limit <number>', 'number of prompts to show', '10')\n .option('-p, --project <project>', 'filter by project path')\n .option('--include-slash-commands', 'include slash commands in results')\n .action(async (options) => {\n try {\n const entries = await parseHistory(DEFAULT_HISTORY_PATH);\n const sorted = entries\n .sort((a, b) => b.timestamp - a.timestamp);\n\n const searchEngine = new HistorySearchEngine(sorted);\n const results = searchEngine.search({\n project: options.project,\n limit: parseInt(options.limit, 10),\n includeSlashCommands: options.includeSlashCommands,\n });\n\n console.log(formatSearchResults(results));\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('show')\n .description('Show detailed information about a specific prompt')\n .argument('<index>', 'index from search/list results')\n .option('-c, --copy', 'copy prompt to clipboard')\n .action(async (index, options) => {\n try {\n const entries = await parseHistory(DEFAULT_HISTORY_PATH);\n const idx = parseInt(index, 10) - 1;\n \n if (idx < 0 || idx >= entries.length) {\n console.error(chalk.red('Error: Invalid index'));\n process.exit(1);\n }\n\n const sorted = entries.sort((a, b) => b.timestamp - a.timestamp);\n const entry = sorted[idx];\n \n if (options.copy) {\n await clipboard.write(entry.display);\n console.log(chalk.green('Copied to clipboard!'));\n }\n \n console.log(formatDetailedResult({ entry }));\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('export')\n .description('Export search results to file')\n .argument('[query]', 'search term')\n .option('-p, --project <project>', 'filter by project path')\n .option('-f, --from <date>', 'filter from date (YYYY-MM-DD)')\n .option('-t, --to <date>', 'filter to date (YYYY-MM-DD)')\n .option('-l, --limit <number>', 'limit number of results', '100')\n .option('--format <format>', 'output format: json, csv, txt', 'json')\n .option('-o, --output <path>', 'output file path')\n .action(async (query, options) => {\n try {\n const entries = await parseHistory(DEFAULT_HISTORY_PATH);\n const searchEngine = new HistorySearchEngine(entries);\n\n const searchOptions: SearchOptions = {\n query,\n project: options.project,\n from: options.from ? new Date(options.from) : undefined,\n to: options.to ? new Date(options.to) : undefined,\n limit: parseInt(options.limit, 10),\n };\n\n const results = searchEngine.search(searchOptions);\n const output = exportResults(results, options.format, options.output);\n console.log(output);\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import { createReadStream } from 'node:fs';\nimport { createInterface } from 'node:readline';\nimport type { EnrichedEntry } from '../types/history.js';\nimport { parseEntry } from './schema.js';\n\nexport async function parseHistory(filePath: string): Promise<EnrichedEntry[]> {\n const entries: EnrichedEntry[] = [];\n \n const fileStream = createReadStream(filePath);\n const rl = createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n });\n\n let lineNumber = 0;\n for await (const line of rl) {\n lineNumber++;\n if (!line.trim()) continue;\n \n const entry = parseEntry(line, lineNumber);\n if (entry) {\n entries.push(entry);\n }\n }\n\n return entries;\n}\n","import { z } from 'zod/v4';\nimport type { EnrichedEntry } from '../types/history.js';\n\nconst MIN_TIMESTAMP = 1_000_000_000_000; // Milliseconds (year ~2001)\nconst MAX_DISPLAY_LENGTH = 500;\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nconst PastedContentSchema = z.object({\n id: z.number(),\n type: z.literal('text'),\n content: z.string().optional(),\n contentHash: z.string().optional(),\n});\n\nexport const HistoryEntrySchema = z\n .object({\n display: z.string(),\n pastedContents: z.record(z.string(), PastedContentSchema).or(z.object({})),\n timestamp: z.number().min(MIN_TIMESTAMP),\n project: z.string(),\n sessionId: z.string().regex(UUID_REGEX).optional(),\n })\n .passthrough();\n\nexport type ParsedHistoryEntry = z.infer<typeof HistoryEntrySchema>;\n\nfunction truncateDisplay(display: string): string {\n if (display.length <= MAX_DISPLAY_LENGTH) {\n return display;\n }\n return display.substring(0, MAX_DISPLAY_LENGTH - 3) + '...';\n}\n\nfunction isSlashCommand(display: string): boolean {\n return display.trimStart().startsWith('/');\n}\n\nexport function parseEntry(\n line: string,\n lineNumber: number,\n): EnrichedEntry | null {\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(line);\n } catch {\n console.warn(`[parseEntry] Malformed JSON at line ${lineNumber}: ${line.substring(0, 50)}...`);\n return null;\n }\n\n const result = HistoryEntrySchema.safeParse(parsed);\n\n if (!result.success) {\n console.warn(\n `[parseEntry] Schema validation failed at line ${lineNumber}: ${result.error.message}`,\n );\n return null;\n }\n\n const entry = result.data;\n\n return {\n ...entry,\n pastedContents: entry.pastedContents as EnrichedEntry['pastedContents'],\n _lineNumber: lineNumber,\n _truncatedDisplay: truncateDisplay(entry.display),\n _isSlashCommand: isSlashCommand(entry.display),\n };\n}\n","import Fuse from 'fuse.js';\nimport type { EnrichedEntry, SearchOptions, SearchResult } from '../types/history.js';\n\nexport class HistorySearchEngine {\n private fuse: Fuse<EnrichedEntry>;\n private entries: EnrichedEntry[];\n\n constructor(entries: EnrichedEntry[]) {\n this.entries = entries;\n this.fuse = new Fuse(entries, {\n keys: ['display', 'project'],\n threshold: 0.3,\n includeScore: true,\n includeMatches: true,\n ignoreLocation: true,\n minMatchCharLength: 2,\n });\n }\n\n search(options: SearchOptions): SearchResult[] {\n let results: SearchResult[];\n\n if (options.query) {\n const fuseResults = this.fuse.search(options.query);\n results = fuseResults.map((r) => ({\n entry: r.item,\n score: r.score,\n matches: r.matches?.map((m) => ({\n key: m.key || '',\n value: m.value || '',\n indices: (m.indices || []) as Array<[number, number]>,\n })),\n }));\n } else {\n // Use stored entries array instead of accessing Fuse internals\n results = this.entries.map((entry) => ({ entry }));\n }\n\n // Apply filters\n results = this.applyFilters(results, options);\n\n // Apply limit\n if (options.limit) {\n results = results.slice(0, options.limit);\n }\n\n return results;\n }\n\n private applyFilters(results: SearchResult[], options: SearchOptions): SearchResult[] {\n let filtered = results;\n\n // Filter by project\n if (options.project) {\n const projectFilter = options.project.toLowerCase();\n filtered = filtered.filter((r) =>\n r.entry.project.toLowerCase().includes(projectFilter)\n );\n }\n\n // Filter by date range\n if (options.from) {\n filtered = filtered.filter((r) => r.entry.timestamp >= options.from!.getTime());\n }\n if (options.to) {\n // Add end-of-day (23:59:59.999) to include the entire day\n const endOfDay = options.to!.getTime() + 86399999;\n filtered = filtered.filter((r) => r.entry.timestamp <= endOfDay);\n }\n\n // Filter out slash commands\n if (!options.includeSlashCommands) {\n filtered = filtered.filter((r) => !r.entry.display.trim().startsWith('/'));\n }\n\n // Deduplicate\n if (options.unique) {\n const seen = new Set<string>();\n filtered = filtered.filter((r) => {\n const key = r.entry.display.trim().toLowerCase();\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n }\n\n return filtered;\n }\n}\n","import chalk from 'chalk';\nimport Table from 'cli-table3';\nimport { formatDistanceToNow } from 'date-fns';\nimport type { SearchResult } from '../types/history.js';\n\nexport function formatSearchResults(results: SearchResult[], detailed = false): string {\n if (results.length === 0) {\n return chalk.yellow('No results found');\n }\n\n const table = new Table({\n head: [chalk.cyan('#'), chalk.cyan('Prompt'), chalk.cyan('Project'), chalk.cyan('Time')],\n colWidths: [5, 60, 30, 15],\n wordWrap: true,\n });\n\n results.forEach((result, index) => {\n const { entry } = result;\n const display = truncate(entry.display, 200);\n const project = entry.project.split('/').pop() || entry.project;\n const timeAgo = formatDistanceToNow(new Date(entry.timestamp), { addSuffix: true });\n\n table.push([\n chalk.gray((index + 1).toString()),\n display,\n chalk.dim(project),\n chalk.dim(timeAgo),\n ]);\n });\n\n return table.toString();\n}\n\nexport function formatDetailedResult(result: SearchResult): string {\n const { entry } = result;\n const lines = [\n chalk.bold.cyan('Prompt:'),\n entry.display,\n '',\n chalk.bold.cyan('Project:'),\n entry.project,\n '',\n chalk.bold.cyan('Timestamp:'),\n new Date(entry.timestamp).toLocaleString(),\n '',\n chalk.bold.cyan('Time Ago:'),\n formatDistanceToNow(new Date(entry.timestamp), { addSuffix: true }),\n ];\n\n if (entry.sessionId) {\n lines.push('', chalk.bold.cyan('Session ID:'), entry.sessionId);\n }\n\n return lines.join('\\n');\n}\n\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.substring(0, maxLength - 3) + '...';\n}\n"],"mappings":"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","names":["chalk","chalk"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@junhoyeo/prompthistory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to search and navigate OpenCode prompt history",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"prompthistory": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"dev": "bun run src/index.ts",
|
|
17
|
+
"type-check": "tsc --noEmit",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"prepublishOnly": "bun run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"cli",
|
|
24
|
+
"opencode",
|
|
25
|
+
"history",
|
|
26
|
+
"prompt",
|
|
27
|
+
"search",
|
|
28
|
+
"fuzzy-search"
|
|
29
|
+
],
|
|
30
|
+
"author": "junhoyeo",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/junhoyeo/prompthistory.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/junhoyeo/prompthistory#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/junhoyeo/prompthistory/issues"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@inquirer/prompts": "^8.2.0",
|
|
42
|
+
"chalk": "^5.4.1",
|
|
43
|
+
"cli-table3": "^0.6.5",
|
|
44
|
+
"clipboardy": "^5.0.2",
|
|
45
|
+
"commander": "^12.1.0",
|
|
46
|
+
"date-fns": "^3.3.1",
|
|
47
|
+
"fuse.js": "^7.0.0",
|
|
48
|
+
"zod": "^4.3.5"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.17.0",
|
|
52
|
+
"@types/react": "^19.2.8",
|
|
53
|
+
"tsup": "^8.4.0",
|
|
54
|
+
"tsx": "^4.19.2",
|
|
55
|
+
"typescript": "^5.7.3",
|
|
56
|
+
"vitest": "^2.1.8"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|