packmd 0.0.1 → 1.0.1

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.
Files changed (3) hide show
  1. package/README.md +150 -0
  2. package/dist/index.js +435 -1
  3. package/package.json +38 -5
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ ![PackMD Opengraph Image](https://packmd.vercel.app/og.png)
2
+
3
+ ## packmd
4
+
5
+ **PackMD** is a command‑line tool that converts any GitHub repository, local directory, or web page into a clean, token‑efficient Markdown digest – ready to paste into ChatGPT, Claude, or any LLM.
6
+
7
+ ### Installation
8
+
9
+ **Global (recommended)**
10
+
11
+ ```bash
12
+ npm install -g packmd
13
+ ```
14
+
15
+ Now you can use `packmd` from anywhere:
16
+
17
+ ```bash
18
+ packmd --help
19
+ ```
20
+
21
+ **Run without installing (using npx)**
22
+
23
+ ```bash
24
+ npx packmd <target> [options]
25
+ ```
26
+
27
+ **From source (monorepo)**
28
+
29
+ ```bash
30
+ git clone https://github.com/thelastofinusa/packmd.git
31
+ cd packmd
32
+ bun install
33
+ cd packages/cli
34
+ bun run build
35
+ # run locally
36
+ node dist/index.js <target> [options]
37
+ ```
38
+
39
+ ### Quick Start
40
+
41
+ ```bash
42
+ # Generate a digest from a public GitHub repo
43
+ packmd https://github.com/vercel/next.js -o next.md
44
+
45
+ # Scrape a documentation website (requires Jina API key for higher limits)
46
+ packmd https://react.dev --jina-api-key YOUR_KEY --copy
47
+
48
+ # Digest the current directory (respects .gitignore)
49
+ packmd .
50
+
51
+ # Digest a local folder with custom options
52
+ packmd ~/projects/my-app -m 300 -s 50 --exclude "*.test.js"
53
+ ```
54
+
55
+ ### Usage
56
+
57
+ ```
58
+ packmd [target] [options]
59
+ ```
60
+
61
+ | Argument | Description |
62
+ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `target` | GitHub URL (e.g., `https://github.com/owner/repo`), owner/repo slug, local directory path, or any webpage URL. Defaults to current directory (`.`). |
64
+
65
+ **Options**
66
+
67
+ | Flag | Description |
68
+ | ----------------------------- | ------------------------------------------------------------------------------------------ |
69
+ | `-o, --output <path>` | Save the generated Markdown to a specific file (e.g., `digest.md`). |
70
+ | `-c, --copy` | Copy the output directly to your clipboard (no file saved). |
71
+ | `-t, --token <token>` | GitHub Personal Access Token for private repositories or higher rate limits. |
72
+ | `-m, --max-files <number>` | Maximum number of files to include (default: `200`). |
73
+ | `-s, --max-file-size <kb>` | Maximum file size in KB (files larger are skipped) (default: `100`). |
74
+ | `-i, --include <patterns...>` | Glob patterns to explicitly include (e.g., `"*.ts" "*.md"`). |
75
+ | `-e, --exclude <patterns...>` | Glob patterns to explicitly ignore (e.g., `"test/**" "*.log"`). |
76
+ | `--jina-api-key <key>` | Jina Reader API key – raises the free rate limit from 20 to 500 requests per minute. |
77
+ | `--no-gitignore` | Ignore `.gitignore` rules when scanning a local directory (by default they are respected). |
78
+ | `-v, --version` | Show the version number. |
79
+ | `-h, --help` | Show help. |
80
+
81
+ > **Note:** When using `--output`, if the file already exists, you will be prompted to overwrite or enter a new name.
82
+
83
+ ### Examples
84
+
85
+ **GitHub repository**
86
+
87
+ ```bash
88
+ # Basic
89
+ packmd facebook/react -o react.md
90
+
91
+ # With token (for private repos)
92
+ packmd my-org/private-repo -t ghp_xxxxx -o private.md
93
+
94
+ # Limit to 300 files, exclude everything in the "examples" folder
95
+ packmd vercel/next.js -m 300 -e "examples/**" -o next-limited.md
96
+ ```
97
+
98
+ **Web page (via Jina Reader)**
99
+
100
+ ```bash
101
+ # Simple scrape
102
+ packmd https://docs.nestjs.com --copy
103
+
104
+ # With Jina API key (higher rate limit)
105
+ packmd https://tailwindcss.com/docs --jina-api-key jina_xxxxx -o tailwind.md
106
+ ```
107
+
108
+ **Local directory**
109
+
110
+ ```bash
111
+ # Digest the current folder
112
+ packmd .
113
+
114
+ # Digest a specific path
115
+ packmd ~/code/my-project -o project.md
116
+
117
+ # Override .gitignore and include only TypeScript files
118
+ packmd ./src --no-gitignore -i "*.ts" "*.tsx"
119
+ ```
120
+
121
+ **Combine options**
122
+
123
+ ```bash
124
+ packmd https://github.com/expressjs/express -o express.md -m 100 -s 50 -e "test/**" "*.spec.js"
125
+ ```
126
+
127
+ ### How It Works
128
+
129
+ The CLI uses the same core engine (`@packmd/core`) as the web app:
130
+
131
+ 1. **Input detection** – determines if the target is a GitHub repo, a web page, or a local path.
132
+ 2. **Fetching** – for GitHub, uses the GitHub API; for web pages, uses Jina Reader; for local, uses Node.js `fs`.
133
+ 3. **Filtering** – applies ignore rules, globs, size limits, and binary detection.
134
+ 4. **Processing** – builds a directory tree and downloads file contents (in parallel for GitHub).
135
+ 5. **Markdown generation** – creates a header with metadata, an ASCII tree, and fenced code blocks for each file.
136
+
137
+ Progress is shown via a live spinner (`ora`). Output can be saved to a file or copied to your clipboard.
138
+
139
+ ### Notes
140
+
141
+ - **GitHub rate limits** – Without a token, you are limited to 60 requests per hour. Use `-t` to raise this to 5,000 per hour.
142
+ - **Jina rate limits** – Without an API key, you get 20 requests per minute. Provide `--jina-api-key` for 500 RPM.
143
+ - The CLI respects `.gitignore` by default. Use `--no-gitignore` to scan everything.
144
+ - **Binary files** (images, fonts, archives) are automatically skipped.
145
+
146
+ ---
147
+
148
+ ### License
149
+
150
+ MIT © [Holiday](https://github.com/thelastofinusa)
package/dist/index.js CHANGED
@@ -1,4 +1,438 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- console.log("Hello, World!");
4
+ import { Command } from "commander";
5
+
6
+ // src/actions/run.ts
7
+ import color2 from "picocolors";
8
+ import clipboard from "clipboardy";
9
+ import fs4 from "fs/promises";
10
+ import path5 from "path";
11
+ import inquirer2 from "inquirer";
12
+ import ora from "ora";
13
+
14
+ // package.json
15
+ var name = "packmd";
16
+ var description = "Convert GitHub repositories and webpages into AI-ready Markdown digests";
17
+ var version = "1.0.1";
18
+
19
+ // src/utils/version-check.ts
20
+ import fs from "fs/promises";
21
+ import os from "os";
22
+ import path from "path";
23
+ import color from "picocolors";
24
+ var REGISTRY_URL = `https://registry.npmjs.org/${name}/latest`;
25
+ var CACHE_PATH = path.join(os.homedir(), `.${name}`, "version-check.json");
26
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
27
+ var FETCH_TIMEOUT_MS = 1500;
28
+ function compareVersions(a, b) {
29
+ const pa = a.split(".").map(Number);
30
+ const pb = b.split(".").map(Number);
31
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
32
+ const na = pa[i] || 0;
33
+ const nb = pb[i] || 0;
34
+ if (na > nb) return 1;
35
+ if (na < nb) return -1;
36
+ }
37
+ return 0;
38
+ }
39
+ async function readCache() {
40
+ try {
41
+ return JSON.parse(await fs.readFile(CACHE_PATH, "utf-8"));
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ async function writeCache(data) {
47
+ try {
48
+ await fs.mkdir(path.dirname(CACHE_PATH), { recursive: true });
49
+ await fs.writeFile(CACHE_PATH, JSON.stringify(data), "utf-8");
50
+ } catch {
51
+ }
52
+ }
53
+ async function fetchLatestVersion() {
54
+ const controller = new AbortController();
55
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
56
+ try {
57
+ const res = await fetch(REGISTRY_URL, {
58
+ signal: controller.signal,
59
+ headers: { Accept: "application/vnd.npm.install-v1+json" }
60
+ // lightweight abbreviated response
61
+ });
62
+ if (!res.ok) return null;
63
+ const data = await res.json();
64
+ return typeof data.version === "string" ? data.version : null;
65
+ } catch {
66
+ return null;
67
+ } finally {
68
+ clearTimeout(timeout);
69
+ }
70
+ }
71
+ async function checkForUpdate(currentVersion) {
72
+ const cache = await readCache();
73
+ const isFresh = cache && Date.now() - cache.lastChecked < CACHE_TTL_MS;
74
+ const latestVersion = isFresh ? cache.latestVersion : await fetchLatestVersion();
75
+ if (!latestVersion) return null;
76
+ if (!isFresh) await writeCache({ lastChecked: Date.now(), latestVersion });
77
+ return compareVersions(latestVersion, currentVersion) > 0 ? latestVersion : null;
78
+ }
79
+ function printUpdateNotice(currentVersion, latestVersion) {
80
+ console.log();
81
+ console.log(
82
+ color.yellow(` \u2191 Update available: `) + color.dim(currentVersion) + color.yellow(" \u2192 ") + color.green(latestVersion)
83
+ );
84
+ console.log(
85
+ color.dim(` Run `) + color.cyan(`npm install -g ${name}@latest`) + color.dim(` to update.`)
86
+ );
87
+ console.log();
88
+ }
89
+
90
+ // src/handlers/github.ts
91
+ import { fetchGithubRepo } from "@packmd/core";
92
+ async function handleGitHub(target, options, spinner) {
93
+ const result = await fetchGithubRepo(target, {
94
+ token: options.token,
95
+ maxFiles: Number(options.maxFiles),
96
+ maxFileSizeKB: Number(options.maxFileSize),
97
+ includeGlobs: options.include || [],
98
+ excludeGlobs: options.exclude || [],
99
+ onProgress: (msg) => {
100
+ spinner.text = msg;
101
+ }
102
+ });
103
+ return result.markdown;
104
+ }
105
+
106
+ // src/handlers/local.ts
107
+ import path4 from "path";
108
+ import { DEFAULT_IGNORES, buildDigestHeader } from "@packmd/core";
109
+
110
+ // src/utils/fs.ts
111
+ import fs2 from "fs/promises";
112
+ import path2 from "path";
113
+ import { minimatch } from "minimatch";
114
+ async function walkLocalDir(dir, base, excludeGlobs, includeGlobs, maxFileSizeKB, maxFiles, gitignore = null, collected = []) {
115
+ if (collected.length >= maxFiles) return collected;
116
+ const entries = await fs2.readdir(dir);
117
+ for (const name2 of entries) {
118
+ if (collected.length >= maxFiles) break;
119
+ const fullPath = path2.join(dir, name2);
120
+ const relativePath = path2.relative(base, fullPath).replace(/\\/g, "/");
121
+ const isExcluded = excludeGlobs.some(
122
+ (g) => minimatch(relativePath, g, { dot: true })
123
+ );
124
+ if (isExcluded) continue;
125
+ const stats = await fs2.stat(fullPath);
126
+ if (gitignore?.ignores(
127
+ stats.isDirectory() ? `${relativePath}/` : relativePath
128
+ )) {
129
+ continue;
130
+ }
131
+ if (stats.isDirectory()) {
132
+ await walkLocalDir(
133
+ fullPath,
134
+ base,
135
+ excludeGlobs,
136
+ includeGlobs,
137
+ maxFileSizeKB,
138
+ maxFiles,
139
+ gitignore,
140
+ collected
141
+ );
142
+ } else if (stats.isFile()) {
143
+ if (includeGlobs.length > 0) {
144
+ const isIncluded = includeGlobs.some(
145
+ (g) => minimatch(relativePath, g, { dot: true })
146
+ );
147
+ if (!isIncluded) continue;
148
+ }
149
+ const sizeKB = stats.size / 1024;
150
+ if (sizeKB <= maxFileSizeKB) {
151
+ const content = await fs2.readFile(fullPath, "utf-8");
152
+ collected.push({ path: relativePath, content });
153
+ }
154
+ }
155
+ }
156
+ return collected;
157
+ }
158
+
159
+ // src/utils/gitignore.ts
160
+ import fs3 from "fs/promises";
161
+ import path3 from "path";
162
+ import ignore from "ignore";
163
+ async function loadGitignore(startDir) {
164
+ const ig = ignore();
165
+ let found = false;
166
+ let dir = startDir;
167
+ while (true) {
168
+ try {
169
+ const content = await fs3.readFile(path3.join(dir, ".gitignore"), "utf-8");
170
+ ig.add(content);
171
+ found = true;
172
+ } catch {
173
+ }
174
+ try {
175
+ await fs3.access(path3.join(dir, ".git"));
176
+ break;
177
+ } catch {
178
+ }
179
+ const parent = path3.dirname(dir);
180
+ if (parent === dir) break;
181
+ dir = parent;
182
+ }
183
+ return found ? ig : null;
184
+ }
185
+
186
+ // src/handlers/local.ts
187
+ async function handleLocalDir(target, options, spinner) {
188
+ const absolutePath = path4.resolve(process.cwd(), target);
189
+ const excludeGlobs = options.exclude || DEFAULT_IGNORES;
190
+ const includeGlobs = options.include || [];
191
+ const maxFiles = Number(options.maxFiles);
192
+ const maxFileSizeKB = Number(options.maxFileSize);
193
+ const gitignore = options.gitignore === false ? null : await loadGitignore(absolutePath);
194
+ const files = await walkLocalDir(
195
+ absolutePath,
196
+ absolutePath,
197
+ excludeGlobs,
198
+ includeGlobs,
199
+ maxFileSizeKB,
200
+ maxFiles,
201
+ gitignore
202
+ );
203
+ spinner.text = `Processing ${files.length} local files..`;
204
+ const totalChars = files.reduce((sum, f) => sum + f.content.length, 0);
205
+ const estTokens = Math.round(totalChars / 4);
206
+ const header = buildDigestHeader({
207
+ title: `Local Digest \u2014 \`${path4.basename(absolutePath)}\``,
208
+ meta: {
209
+ Path: `\`${absolutePath}\``,
210
+ Date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
211
+ Files: files.length,
212
+ "Est. tokens": `~${estTokens.toLocaleString()}`
213
+ }
214
+ });
215
+ const markdownParts = [header];
216
+ for (const file of files) {
217
+ markdownParts.push(`## File: ${file.path}
218
+ \`\`\`
219
+ ${file.content}
220
+ \`\`\``);
221
+ }
222
+ return markdownParts.join("\n\n");
223
+ }
224
+
225
+ // src/handlers/webpage.ts
226
+ import { buildDigestHeader as buildDigestHeader2, scrapeWebPage } from "@packmd/core";
227
+ async function handleWebpage(target, options) {
228
+ const scraped = await scrapeWebPage(target, {
229
+ jinaApiKey: options.jinaApiKey
230
+ });
231
+ const estTokens = Math.round((scraped.content?.length || 0) / 4);
232
+ const header = buildDigestHeader2({
233
+ icon: "\u{1F310}",
234
+ title: scraped.title || target,
235
+ meta: {
236
+ Source: `\`${target}\``,
237
+ Date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
238
+ "Est. tokens": `~${estTokens.toLocaleString()}`
239
+ }
240
+ });
241
+ return `${header}
242
+
243
+ ${scraped.content || ""}`;
244
+ }
245
+
246
+ // src/prompts/github.ts
247
+ import inquirer from "inquirer";
248
+ async function promptGithubOptions(currentOptions) {
249
+ const { customize } = await inquirer.prompt([
250
+ {
251
+ type: "confirm",
252
+ name: "customize",
253
+ message: "GitHub URL detected. Would you like to configure options?",
254
+ default: false
255
+ }
256
+ ]);
257
+ if (!customize) return currentOptions;
258
+ const answers = await inquirer.prompt([
259
+ {
260
+ type: "input",
261
+ name: "maxFiles",
262
+ message: "Maximum number of files to include:",
263
+ default: currentOptions.maxFiles || "200",
264
+ validate: (val) => !isNaN(Number(val)) || "Please enter a valid number"
265
+ },
266
+ {
267
+ type: "input",
268
+ name: "maxFileSize",
269
+ message: "Maximum file size threshold (in KB):",
270
+ default: currentOptions.maxFileSize || "100",
271
+ validate: (val) => !isNaN(Number(val)) || "Please enter a valid number"
272
+ },
273
+ {
274
+ type: "password",
275
+ name: "token",
276
+ message: "GitHub Token (Optional - for private or larger repos):",
277
+ default: currentOptions.token || ""
278
+ }
279
+ ]);
280
+ return { ...currentOptions, ...answers };
281
+ }
282
+
283
+ // src/actions/run.ts
284
+ async function runAction(target, options) {
285
+ console.log("");
286
+ console.log(color2.cyan(`${name} \u2014 AI Markdown Packager`));
287
+ const updateCheck = checkForUpdate(version).catch(() => null);
288
+ let finalOptions = { ...options };
289
+ try {
290
+ let digest = "";
291
+ const spinner = ora();
292
+ if (target.startsWith("http://") || target.startsWith("https://")) {
293
+ const targetUrl = new URL(target);
294
+ const isGitHub = targetUrl.hostname.includes("github.com");
295
+ if (isGitHub) {
296
+ finalOptions = await promptGithubOptions(finalOptions);
297
+ spinner.start("Fetching repository. Please wait..");
298
+ digest = await handleGitHub(target, finalOptions, spinner);
299
+ } else {
300
+ spinner.start("Scraping webpage. Please wait..");
301
+ digest = await handleWebpage(target, finalOptions);
302
+ }
303
+ } else {
304
+ spinner.start("Scanning local directory. Please wait..");
305
+ digest = await handleLocalDir(target, finalOptions, spinner);
306
+ }
307
+ let outputPath = finalOptions.copy ? null : finalOptions.output || "pack.md";
308
+ if (outputPath) {
309
+ let finalPath = outputPath;
310
+ while (true) {
311
+ let fileExists = false;
312
+ try {
313
+ await fs4.access(finalPath);
314
+ fileExists = true;
315
+ } catch {
316
+ fileExists = false;
317
+ }
318
+ if (fileExists) {
319
+ const { overwrite } = await inquirer2.prompt([
320
+ {
321
+ type: "confirm",
322
+ name: "overwrite",
323
+ message: `File ${color2.cyan(finalPath)} already exists. Overwrite?`,
324
+ default: false
325
+ }
326
+ ]);
327
+ if (overwrite) {
328
+ break;
329
+ } else {
330
+ const { newFileName } = await inquirer2.prompt([
331
+ {
332
+ type: "input",
333
+ name: "newFileName",
334
+ message: "Please enter a new file name:",
335
+ default: "pack-new.md",
336
+ validate: (val) => val.trim().length > 0 || "File name cannot be empty."
337
+ }
338
+ ]);
339
+ finalPath = newFileName;
340
+ }
341
+ } else {
342
+ break;
343
+ }
344
+ }
345
+ await fs4.writeFile(finalPath, digest, { encoding: "utf-8", flag: "w" });
346
+ const gitignorePath = path5.join(process.cwd(), ".gitignore");
347
+ let gitignoreContent = "";
348
+ try {
349
+ gitignoreContent = await fs4.readFile(gitignorePath, "utf-8");
350
+ } catch {
351
+ }
352
+ const isIgnored = gitignoreContent.split("\n").map((line) => line.trim()).includes(finalPath);
353
+ if (!isIgnored) {
354
+ const prefix = gitignoreContent.endsWith("\n") || gitignoreContent === "" ? "" : "\n";
355
+ await fs4.appendFile(gitignorePath, `${prefix}${finalPath}
356
+ `, "utf-8");
357
+ spinner.succeed(
358
+ `Generated successfully! ${color2.cyan(finalPath)} was added to .gitignore.`
359
+ );
360
+ } else {
361
+ spinner.succeed("Markdown generated successfully!");
362
+ }
363
+ }
364
+ if (finalOptions.copy) {
365
+ await clipboard.write(digest);
366
+ spinner.succeed("Markdown compiled successfully.");
367
+ }
368
+ const latestVersion = await updateCheck;
369
+ if (latestVersion) printUpdateNotice(version, latestVersion);
370
+ console.log("");
371
+ } catch (error) {
372
+ console.error(
373
+ color2.red(`
374
+ \u2716 ${error.message || "An unexpected error occurred"}`)
375
+ );
376
+ process.exit(1);
377
+ }
378
+ }
379
+
380
+ // src/utils/program.ts
381
+ var cliOptions = [
382
+ {
383
+ flags: "-o, --output <path>",
384
+ description: "Specify a custom file path to save the generated Markdown digest"
385
+ },
386
+ {
387
+ flags: "-t, --token <token>",
388
+ description: "Provide a GitHub Personal Access Token for private repositories or extended rate limits"
389
+ },
390
+ {
391
+ flags: "-m, --max-files <number>",
392
+ description: "Limit the total number of files to include in the final digest",
393
+ defaultValue: "200"
394
+ },
395
+ {
396
+ flags: "-s, --max-file-size <number>",
397
+ description: "Set the maximum allowed size (in KB) for individual files",
398
+ defaultValue: "100"
399
+ },
400
+ {
401
+ flags: "-i, --include <patterns...>",
402
+ description: "Specify glob patterns to explicitly include certain files or directories"
403
+ },
404
+ {
405
+ flags: "-e, --exclude <patterns...>",
406
+ description: "Specify glob patterns to explicitly ignore certain files or directories"
407
+ },
408
+ {
409
+ flags: "-c, --copy",
410
+ description: "Automatically copy the generated Markdown digest to your clipboard",
411
+ defaultValue: false
412
+ },
413
+ {
414
+ flags: "--jina-api-key <key>",
415
+ description: "Provide a Jina API key for enhanced webpage scraping and parsing"
416
+ },
417
+ {
418
+ flags: "--no-gitignore",
419
+ description: "Don't respect .gitignore rules when scanning a local directory"
420
+ }
421
+ ];
422
+
423
+ // src/index.ts
424
+ var program = new Command();
425
+ program.name(name).description(description).version(version, "-v, --version", "Output the version number").argument(
426
+ "[target]",
427
+ "GitHub URL, Webpage URL, or local directory path (defaults to current directory)",
428
+ "."
429
+ );
430
+ cliOptions.forEach(({ flags, description: description2, defaultValue }) => {
431
+ if (defaultValue !== void 0) {
432
+ program.option(flags, description2, defaultValue);
433
+ } else {
434
+ program.option(flags, description2);
435
+ }
436
+ });
437
+ program.action(runAction);
438
+ program.parse();
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "packmd",
3
- "version": "0.0.1",
3
+ "description": "Convert GitHub repositories and webpages into AI-ready Markdown digests",
4
+ "version": "1.0.1",
4
5
  "license": "MIT",
5
6
  "type": "module",
6
7
  "bin": {
@@ -13,13 +14,15 @@
13
14
  ],
14
15
  "scripts": {
15
16
  "build": "tsup",
16
- "dev": "tsx src/index.ts",
17
- "start": "node dist/index.js",
17
+ "start": "tsx src/index.js",
18
+ "start:node": "node dist/index.js",
19
+ "clean": "rm -rf dist node_modules .turbo",
18
20
  "prepublishOnly": "bun run build",
19
21
  "pushout": "npm publish",
20
22
  "pushout-inc": "npm version patch --no-git-tag-version && bun run pushout"
21
23
  },
22
24
  "devDependencies": {
25
+ "@types/inquirer": "^9.0.10",
23
26
  "@types/node": "^26.1.1",
24
27
  "tsup": "^8.5.1",
25
28
  "tsx": "^4.23.1",
@@ -27,7 +30,37 @@
27
30
  },
28
31
  "dependencies": {
29
32
  "chalk": "^5.6.2",
33
+ "clipboardy": "^5.3.2",
30
34
  "commander": "^15.0.0",
31
- "ora": "^9.4.1"
32
- }
35
+ "ignore": "^7.0.6",
36
+ "inquirer": "^14.0.2",
37
+ "minimatch": "^10.2.5",
38
+ "ora": "^9.4.1",
39
+ "@packmd/core": "^1.0.0",
40
+ "picocolors": "^1.1.1"
41
+ },
42
+ "keywords": [
43
+ "packmd",
44
+ "markdown",
45
+ "github",
46
+ "llm",
47
+ "codebase",
48
+ "digest",
49
+ "cli",
50
+ "generator",
51
+ "scraper",
52
+ "developer-tools",
53
+ "chatgpt",
54
+ "claude"
55
+ ],
56
+ "author": "Holiday (https://github.com/thelastofinusa)",
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/thelastofinusa/packmd.git",
60
+ "directory": "packages/cli"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/thelastofinusa/packmd/issues"
64
+ },
65
+ "homepage": "https://packmd.vercel.app/docs"
33
66
  }