treezip 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +29 -0
- package/dist/compress.d.ts +19 -0
- package/dist/compress.js +134 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/renderer.d.ts +2 -0
- package/dist/renderer.js +46 -0
- package/dist/scanner.d.ts +11 -0
- package/dist/scanner.js +64 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# treezip
|
|
2
|
+
|
|
3
|
+
Compressed directory trees for AI prompts. Zero dependencies.
|
|
4
|
+
|
|
5
|
+
`tree` output is verbose — `treezip` compresses it using brace grouping, directory collapsing, and truncation so you can fit more codebase structure into fewer tokens.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g treezip
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx treezip ./my-project
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Before / After
|
|
20
|
+
|
|
21
|
+
**`tree` (43 lines):**
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
my-project/
|
|
25
|
+
├── docs
|
|
26
|
+
│ ├── api-reference.md
|
|
27
|
+
│ ├── contributing.md
|
|
28
|
+
│ └── getting-started.md
|
|
29
|
+
├── package.json
|
|
30
|
+
├── public
|
|
31
|
+
│ └── images
|
|
32
|
+
│ ├── avatar.png
|
|
33
|
+
│ ├── background.png
|
|
34
|
+
│ ├── badge.png
|
|
35
|
+
│ ├── banner.png
|
|
36
|
+
│ ├── feature.png
|
|
37
|
+
│ ├── hero.png
|
|
38
|
+
│ ├── icon.png
|
|
39
|
+
│ ├── logo.png
|
|
40
|
+
│ ├── pattern.png
|
|
41
|
+
│ ├── placeholder.png
|
|
42
|
+
│ └── screenshot.png
|
|
43
|
+
├── README.md
|
|
44
|
+
├── src
|
|
45
|
+
│ ├── App.tsx
|
|
46
|
+
│ ├── components
|
|
47
|
+
│ │ ├── Button.tsx
|
|
48
|
+
│ │ ├── Card.tsx
|
|
49
|
+
│ │ ├── Header.tsx
|
|
50
|
+
│ │ ├── Modal.tsx
|
|
51
|
+
│ │ └── Sidebar.tsx
|
|
52
|
+
│ ├── hooks
|
|
53
|
+
│ │ ├── useApi.ts
|
|
54
|
+
│ │ ├── useAuth.ts
|
|
55
|
+
│ │ └── useTheme.ts
|
|
56
|
+
│ ├── index.ts
|
|
57
|
+
│ └── utils
|
|
58
|
+
│ ├── api.ts
|
|
59
|
+
│ ├── auth.ts
|
|
60
|
+
│ ├── format.ts
|
|
61
|
+
│ └── validate.ts
|
|
62
|
+
├── tests
|
|
63
|
+
│ ├── Button.test.tsx
|
|
64
|
+
│ ├── Card.test.tsx
|
|
65
|
+
│ ├── Header.test.tsx
|
|
66
|
+
│ ├── Modal.test.tsx
|
|
67
|
+
│ ├── setup.ts
|
|
68
|
+
│ └── Sidebar.test.tsx
|
|
69
|
+
└── tsconfig.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**`treezip` (17 lines):**
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
my-project/
|
|
76
|
+
├── {.gitignore,README.md,package.json,tsconfig.json}
|
|
77
|
+
├── docs/{api-reference,contributing,getting-started}.md
|
|
78
|
+
├── public/images/{avatar,background,badge,banner,feature,...}.png
|
|
79
|
+
├── src/
|
|
80
|
+
│ ├── {App.tsx,index.ts}
|
|
81
|
+
│ ├── components/{Button,Card,Header,Modal,Sidebar}.tsx
|
|
82
|
+
│ ├── hooks/{useApi,useAuth,useTheme}.ts
|
|
83
|
+
│ └── utils/{api,auth,format,validate}.ts
|
|
84
|
+
└── tests/
|
|
85
|
+
├── setup.ts
|
|
86
|
+
└── {Button,Card,Header,Modal,Sidebar}.test.tsx
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**60% fewer lines.** Same information. Every brace group decompresses to exactly one file per entry.
|
|
90
|
+
|
|
91
|
+
## How it works
|
|
92
|
+
|
|
93
|
+
| Pattern | Meaning | Example |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `{a,b,c}.ext` | Siblings sharing an extension | `{Button,Card,Modal}.tsx` |
|
|
96
|
+
| `{a.x,b.y}` | Siblings with different extensions | `{README.md,tsconfig.json}` |
|
|
97
|
+
| `dir/{stems}.ext` | All files in dir share an extension | `utils/{api,auth,format}.ts` |
|
|
98
|
+
| `dir/file` | Single-child directory collapsed | `scripts/build.sh` |
|
|
99
|
+
| `{a,b,...}.ext` | Truncated group (more files exist) | `images/{logo,hero,...}.png` |
|
|
100
|
+
|
|
101
|
+
### Compression rules
|
|
102
|
+
|
|
103
|
+
1. **Mixed directories** (files + subdirs): all files grouped into one `{...}` set
|
|
104
|
+
2. **Files-only directories**: grouped by shared extension (compound-aware: `.test.ts`, `.config.js`)
|
|
105
|
+
3. **Inline collapse**: directories that compress to a single item shown inline
|
|
106
|
+
4. **Single-child chain**: `a/b/c.txt` when each level has one child
|
|
107
|
+
5. **Truncation**: groups with >8 items show the first 5 + `...`
|
|
108
|
+
|
|
109
|
+
### Default ignores
|
|
110
|
+
|
|
111
|
+
`node_modules`, `.git`, `.DS_Store`, `__pycache__`, `.pnpm`
|
|
112
|
+
|
|
113
|
+
## Output header
|
|
114
|
+
|
|
115
|
+
Every output includes a self-documenting header:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
# COMPRESSED FILE TREE — AI READ GUIDE
|
|
119
|
+
# {a,b,c}.ext → siblings sharing extension | scripts/f.js → collapsed single-child dir
|
|
120
|
+
# {...}.ext → truncated list (more exist) | decompress: expand braces × each entry = 1 path
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This lets LLMs interpret the format without extra instructions.
|
|
124
|
+
|
|
125
|
+
## Programmatic API
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { scan, compress, render, countNodes } from "treezip";
|
|
129
|
+
|
|
130
|
+
const tree = scan("./my-project");
|
|
131
|
+
const compressed = compress(tree);
|
|
132
|
+
const { dirs, files } = countNodes(tree);
|
|
133
|
+
const output = render("my-project/", compressed);
|
|
134
|
+
console.log(output);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { scan, countNodes } from "./scanner.js";
|
|
4
|
+
import { compress } from "./compress.js";
|
|
5
|
+
import { render } from "./renderer.js";
|
|
6
|
+
const HEADER = `# COMPRESSED FILE TREE — AI READ GUIDE
|
|
7
|
+
# {a,b,c}.ext → siblings sharing extension | scripts/f.js → collapsed single-child dir
|
|
8
|
+
# {...}.ext → truncated list (more exist) | decompress: expand braces × each entry = 1 path
|
|
9
|
+
#`;
|
|
10
|
+
function main() {
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
13
|
+
console.log("Usage: treezip [directory]");
|
|
14
|
+
console.log(" Outputs a compressed, AI-optimized directory tree.");
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
const target = args.find((a) => !a.startsWith("-")) ?? ".";
|
|
18
|
+
const rootPath = resolve(target);
|
|
19
|
+
const tree = scan(rootPath);
|
|
20
|
+
const compressed = compress(tree);
|
|
21
|
+
const { dirs, files } = countNodes(tree);
|
|
22
|
+
// Use the original argument (or .) as the display root name, with trailing /
|
|
23
|
+
const displayRoot = target.replace(/\/$/, "") + "/";
|
|
24
|
+
const body = render(displayRoot, compressed);
|
|
25
|
+
console.log(HEADER);
|
|
26
|
+
console.log(body);
|
|
27
|
+
console.log(`\n${dirs} directories, ${files} files`);
|
|
28
|
+
}
|
|
29
|
+
main();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TreeNode } from "./scanner.js";
|
|
2
|
+
export type CompressedItem = {
|
|
3
|
+
kind: "file";
|
|
4
|
+
name: string;
|
|
5
|
+
} | {
|
|
6
|
+
kind: "brace-group";
|
|
7
|
+
stems: string[];
|
|
8
|
+
ext: string;
|
|
9
|
+
truncated: boolean;
|
|
10
|
+
} | {
|
|
11
|
+
kind: "dir";
|
|
12
|
+
name: string;
|
|
13
|
+
children: CompressedItem[];
|
|
14
|
+
} | {
|
|
15
|
+
kind: "inline-dir";
|
|
16
|
+
prefix: string;
|
|
17
|
+
item: CompressedItem;
|
|
18
|
+
};
|
|
19
|
+
export declare function compress(node: TreeNode): CompressedItem[];
|
package/dist/compress.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const MAX_GROUP_ITEMS = 8;
|
|
2
|
+
const TRUNCATED_SHOW = 5;
|
|
3
|
+
// ── Public API ───────────────────────────────────────────────────────
|
|
4
|
+
export function compress(node) {
|
|
5
|
+
if (!node.isDir)
|
|
6
|
+
return [{ kind: "file", name: node.name }];
|
|
7
|
+
return compressChildren(node);
|
|
8
|
+
}
|
|
9
|
+
// ── Core logic ───────────────────────────────────────────────────────
|
|
10
|
+
function compressChildren(node) {
|
|
11
|
+
const files = node.children.filter((c) => !c.isDir);
|
|
12
|
+
const dirs = node.children.filter((c) => c.isDir);
|
|
13
|
+
const hasFiles = files.length > 0;
|
|
14
|
+
const hasDirs = dirs.length > 0;
|
|
15
|
+
// Recursively compress each subdir first
|
|
16
|
+
const compressedDirs = dirs.map((d) => compressDir(d));
|
|
17
|
+
if (hasFiles && hasDirs) {
|
|
18
|
+
// Rule 1 — Mixed directory: group ALL files into one brace group
|
|
19
|
+
const fileItems = files.length >= 2
|
|
20
|
+
? [makeBraceGroupFullNames(files.map((f) => f.name))]
|
|
21
|
+
: [{ kind: "file", name: files[0].name }];
|
|
22
|
+
return [...fileItems, ...compressedDirs];
|
|
23
|
+
}
|
|
24
|
+
if (hasFiles && !hasDirs) {
|
|
25
|
+
// Rule 2 — Files-only directory: group by extension
|
|
26
|
+
return groupByExtension(files.map((f) => f.name));
|
|
27
|
+
}
|
|
28
|
+
// Dirs only
|
|
29
|
+
return compressedDirs;
|
|
30
|
+
}
|
|
31
|
+
/** Compress a single directory node, applying inline/single-child collapse */
|
|
32
|
+
function compressDir(node) {
|
|
33
|
+
// Rule 4 — Single-child chain collapse
|
|
34
|
+
const chain = [node.name];
|
|
35
|
+
let current = node;
|
|
36
|
+
while (current.children.length === 1) {
|
|
37
|
+
const only = current.children[0];
|
|
38
|
+
if (only.isDir) {
|
|
39
|
+
chain.push(only.name);
|
|
40
|
+
current = only;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Single file child → full chain collapse
|
|
44
|
+
return {
|
|
45
|
+
kind: "inline-dir",
|
|
46
|
+
prefix: chain.join("/"),
|
|
47
|
+
item: { kind: "file", name: only.name },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const chainPrefix = chain.join("/");
|
|
52
|
+
const children = compressChildren(current);
|
|
53
|
+
// Rule 3 — Inline collapse: if files-only dir compresses to 1 item
|
|
54
|
+
const onlyFiles = current.children.every((c) => !c.isDir);
|
|
55
|
+
if (onlyFiles && children.length === 1) {
|
|
56
|
+
return { kind: "inline-dir", prefix: chainPrefix, item: children[0] };
|
|
57
|
+
}
|
|
58
|
+
return { kind: "dir", name: chainPrefix, children };
|
|
59
|
+
}
|
|
60
|
+
// ── Extension-based grouping (Rule 2) ────────────────────────────────
|
|
61
|
+
function getExtensionCandidates(filename) {
|
|
62
|
+
const parts = filename.split(".");
|
|
63
|
+
if (parts.length <= 1)
|
|
64
|
+
return []; // no extension (e.g., LICENSE, gitignore)
|
|
65
|
+
const candidates = [];
|
|
66
|
+
// Longest first: .test.ts before .ts
|
|
67
|
+
for (let i = 1; i < parts.length; i++) {
|
|
68
|
+
candidates.push("." + parts.slice(i).join("."));
|
|
69
|
+
}
|
|
70
|
+
candidates.reverse(); // longest first
|
|
71
|
+
return candidates;
|
|
72
|
+
}
|
|
73
|
+
function getStem(filename, ext) {
|
|
74
|
+
return filename.slice(0, filename.length - ext.length);
|
|
75
|
+
}
|
|
76
|
+
function groupByExtension(filenames) {
|
|
77
|
+
// Build extension → files mapping
|
|
78
|
+
const extToFiles = new Map();
|
|
79
|
+
for (const f of filenames) {
|
|
80
|
+
for (const ext of getExtensionCandidates(f)) {
|
|
81
|
+
if (!extToFiles.has(ext))
|
|
82
|
+
extToFiles.set(ext, []);
|
|
83
|
+
extToFiles.get(ext).push(f);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Sort by extension length (longest first) for greedy assignment
|
|
87
|
+
const sortedExts = [...extToFiles.entries()]
|
|
88
|
+
.filter(([, files]) => files.length >= 2)
|
|
89
|
+
.sort((a, b) => b[0].length - a[0].length);
|
|
90
|
+
const assigned = new Set();
|
|
91
|
+
const groups = [];
|
|
92
|
+
for (const [ext, extFiles] of sortedExts) {
|
|
93
|
+
const unassigned = extFiles.filter((f) => !assigned.has(f));
|
|
94
|
+
if (unassigned.length >= 2) {
|
|
95
|
+
groups.push({ ext, files: unassigned.sort() });
|
|
96
|
+
for (const f of unassigned)
|
|
97
|
+
assigned.add(f);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Remaining files are standalones
|
|
101
|
+
const standalones = filenames.filter((f) => !assigned.has(f)).sort();
|
|
102
|
+
const result = [];
|
|
103
|
+
// Standalones first
|
|
104
|
+
if (standalones.length >= 2) {
|
|
105
|
+
result.push(makeBraceGroupFullNames(standalones));
|
|
106
|
+
}
|
|
107
|
+
else if (standalones.length === 1) {
|
|
108
|
+
result.push({ kind: "file", name: standalones[0] });
|
|
109
|
+
}
|
|
110
|
+
// Then extension groups (sorted by extension alphabetically)
|
|
111
|
+
groups.sort((a, b) => a.ext.localeCompare(b.ext));
|
|
112
|
+
for (const g of groups) {
|
|
113
|
+
const stems = g.files.map((f) => getStem(f, g.ext));
|
|
114
|
+
const truncated = stems.length > MAX_GROUP_ITEMS;
|
|
115
|
+
result.push({
|
|
116
|
+
kind: "brace-group",
|
|
117
|
+
stems: truncated ? stems.slice(0, TRUNCATED_SHOW) : stems,
|
|
118
|
+
ext: g.ext,
|
|
119
|
+
truncated,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
125
|
+
function makeBraceGroupFullNames(names) {
|
|
126
|
+
const sorted = [...names].sort();
|
|
127
|
+
const truncated = sorted.length > MAX_GROUP_ITEMS;
|
|
128
|
+
return {
|
|
129
|
+
kind: "brace-group",
|
|
130
|
+
stems: truncated ? sorted.slice(0, TRUNCATED_SHOW) : sorted,
|
|
131
|
+
ext: "",
|
|
132
|
+
truncated,
|
|
133
|
+
};
|
|
134
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const PIPE = "│ ";
|
|
2
|
+
const TEE = "├── ";
|
|
3
|
+
const ELBOW = "└── ";
|
|
4
|
+
const SPACE = " ";
|
|
5
|
+
export function render(rootName, items) {
|
|
6
|
+
const lines = [rootName];
|
|
7
|
+
renderItems(items, "", lines);
|
|
8
|
+
return lines.join("\n");
|
|
9
|
+
}
|
|
10
|
+
function renderItems(items, prefix, lines) {
|
|
11
|
+
for (let i = 0; i < items.length; i++) {
|
|
12
|
+
const isLast = i === items.length - 1;
|
|
13
|
+
const connector = isLast ? ELBOW : TEE;
|
|
14
|
+
const childPrefix = prefix + (isLast ? SPACE : PIPE);
|
|
15
|
+
const item = items[i];
|
|
16
|
+
switch (item.kind) {
|
|
17
|
+
case "file":
|
|
18
|
+
lines.push(`${prefix}${connector}${item.name}`);
|
|
19
|
+
break;
|
|
20
|
+
case "brace-group":
|
|
21
|
+
lines.push(`${prefix}${connector}${formatBraceGroup(item.stems, item.ext, item.truncated)}`);
|
|
22
|
+
break;
|
|
23
|
+
case "inline-dir":
|
|
24
|
+
lines.push(`${prefix}${connector}${item.prefix}/${formatInlineItem(item.item)}`);
|
|
25
|
+
break;
|
|
26
|
+
case "dir":
|
|
27
|
+
lines.push(`${prefix}${connector}${item.name}/`);
|
|
28
|
+
renderItems(item.children, childPrefix, lines);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function formatBraceGroup(stems, ext, truncated) {
|
|
34
|
+
const inner = truncated ? [...stems, "..."].join(",") : stems.join(",");
|
|
35
|
+
return `{${inner}}${ext}`;
|
|
36
|
+
}
|
|
37
|
+
function formatInlineItem(item) {
|
|
38
|
+
switch (item.kind) {
|
|
39
|
+
case "file":
|
|
40
|
+
return item.name;
|
|
41
|
+
case "brace-group":
|
|
42
|
+
return formatBraceGroup(item.stems, item.ext, item.truncated);
|
|
43
|
+
default:
|
|
44
|
+
return "?";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface TreeNode {
|
|
2
|
+
name: string;
|
|
3
|
+
isDir: boolean;
|
|
4
|
+
children: TreeNode[];
|
|
5
|
+
}
|
|
6
|
+
export declare function scan(rootPath: string, ignores?: Set<string>): TreeNode;
|
|
7
|
+
/** Count total files and directories in a TreeNode (excludes root) */
|
|
8
|
+
export declare function countNodes(node: TreeNode): {
|
|
9
|
+
dirs: number;
|
|
10
|
+
files: number;
|
|
11
|
+
};
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readdirSync, statSync, lstatSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const DEFAULT_IGNORES = new Set([
|
|
4
|
+
"node_modules",
|
|
5
|
+
".git",
|
|
6
|
+
".DS_Store",
|
|
7
|
+
".Spotlight-V100",
|
|
8
|
+
".Trashes",
|
|
9
|
+
"__pycache__",
|
|
10
|
+
".pnpm",
|
|
11
|
+
]);
|
|
12
|
+
export function scan(rootPath, ignores = DEFAULT_IGNORES) {
|
|
13
|
+
const stat = statSync(rootPath);
|
|
14
|
+
const name = rootPath.split("/").filter(Boolean).pop() ?? rootPath;
|
|
15
|
+
if (!stat.isDirectory()) {
|
|
16
|
+
return { name, isDir: false, children: [] };
|
|
17
|
+
}
|
|
18
|
+
const entries = readdirSync(rootPath).filter((e) => !ignores.has(e)).sort();
|
|
19
|
+
const files = [];
|
|
20
|
+
const dirs = [];
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const fullPath = join(rootPath, entry);
|
|
23
|
+
let entryStat;
|
|
24
|
+
try {
|
|
25
|
+
entryStat = lstatSync(fullPath);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Skip symlinks
|
|
31
|
+
if (entryStat.isSymbolicLink())
|
|
32
|
+
continue;
|
|
33
|
+
if (entryStat.isDirectory()) {
|
|
34
|
+
dirs.push(scan(fullPath, ignores));
|
|
35
|
+
}
|
|
36
|
+
else if (entryStat.isFile()) {
|
|
37
|
+
files.push({ name: entry, isDir: false, children: [] });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Files first (alphabetical), then dirs (alphabetical)
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
isDir: true,
|
|
44
|
+
children: [...files, ...dirs],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Count total files and directories in a TreeNode (excludes root) */
|
|
48
|
+
export function countNodes(node) {
|
|
49
|
+
let dirs = 0;
|
|
50
|
+
let files = 0;
|
|
51
|
+
function walk(n) {
|
|
52
|
+
for (const child of n.children) {
|
|
53
|
+
if (child.isDir) {
|
|
54
|
+
dirs++;
|
|
55
|
+
walk(child);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
files++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
walk(node);
|
|
63
|
+
return { dirs, files };
|
|
64
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "treezip",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Compressed tree output for AI-optimized codebase summaries",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"treezip": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"dev": "tsx src/cli.ts",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/yigitkonur/treezip.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/yigitkonur/treezip",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/yigitkonur/treezip/issues"
|
|
34
|
+
},
|
|
35
|
+
"author": "Yigit Konur",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^25.5.0",
|
|
38
|
+
"tsx": "^4.19.0",
|
|
39
|
+
"typescript": "^5.7.0",
|
|
40
|
+
"vitest": "^3.0.0"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"tree",
|
|
44
|
+
"compressed",
|
|
45
|
+
"ai",
|
|
46
|
+
"codebase",
|
|
47
|
+
"cli",
|
|
48
|
+
"directory",
|
|
49
|
+
"structure",
|
|
50
|
+
"llm",
|
|
51
|
+
"context",
|
|
52
|
+
"brace-expansion",
|
|
53
|
+
"npx"
|
|
54
|
+
],
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18"
|
|
58
|
+
}
|
|
59
|
+
}
|