Skip to content

Commit 435bad5

Browse files
OpenCode Agentclaude
andcommitted
feat: rename zip preset to archive, add tar.gz support
- Renamed ZipLoader to ArchiveLoader to support multiple archive formats - Added tar package dependency for tar.gz extraction support - ArchiveLoader now auto-detects format (.zip, .tar.gz, .tgz) - Updated CLI create/init/refresh commands to use archive preset - Renamed ZipSourceConfig to ArchiveSourceConfig in core types - Updated config validation to accept type: "archive" - Validation now accepts both .zip and .tar.gz files - Renamed and updated tests from zip-loader to archive-loader Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 0135609 commit 435bad5

12 files changed

Lines changed: 301 additions & 111 deletions

File tree

packages/cli/src/commands/create.ts

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ import type { DocsetConfig } from "@codemcp/knowledge-core";
1111

1212
export const createCommand = new Command("create")
1313
.description("Create a new docset using presets")
14-
.requiredOption("--preset <type>", "Preset type: git-repo or local-folder")
14+
.requiredOption(
15+
"--preset <type>",
16+
"Preset type: git-repo, local-folder, or archive",
17+
)
1518
.requiredOption("--id <id>", "Unique docset ID")
1619
.requiredOption("--name <name>", "Human-readable docset name")
1720
.option("--description <desc>", "Docset description")
18-
.option("--url <url>", "Git repository URL (required for git-repo preset)")
21+
.option(
22+
"--url <url>",
23+
"Git repository URL (git-repo) or archive file URL (archive preset)",
24+
)
1925
.option(
2026
"--path <path>",
21-
"Local folder path (required for local-folder preset)",
27+
"Local folder path (local-folder) or local archive file path (archive preset)",
2228
)
2329
.option("--branch <branch>", "Git branch (default: main)", "main")
2430
.action(async (options) => {
@@ -59,9 +65,11 @@ export const createCommand = new Command("create")
5965
newDocset = await createGitRepoDocset(options);
6066
} else if (options.preset === "local-folder") {
6167
newDocset = await createLocalFolderDocset(options);
68+
} else if (options.preset === "archive") {
69+
newDocset = await createArchiveDocset(options);
6270
} else {
6371
throw new Error(
64-
`Unknown preset: ${options.preset}. Use 'git-repo' or 'local-folder'`,
72+
`Unknown preset: ${options.preset}. Use 'git-repo', 'local-folder', or 'archive'`,
6573
);
6674
}
6775

@@ -140,3 +148,63 @@ async function createLocalFolderDocset(options: any): Promise<DocsetConfig> {
140148
],
141149
};
142150
}
151+
152+
async function createArchiveDocset(options: any): Promise<DocsetConfig> {
153+
if (!options.path && !options.url) {
154+
throw new Error("Either --path or --url is required for archive preset");
155+
}
156+
157+
// If path is provided, validate it exists
158+
if (options.path) {
159+
const fullPath = path.resolve(options.path);
160+
try {
161+
const stat = await fs.stat(fullPath);
162+
if (!stat.isFile()) {
163+
throw new Error(`Path is not a file: ${options.path}`);
164+
}
165+
const lowerPath = options.path.toLowerCase();
166+
if (
167+
!lowerPath.endsWith(".zip") &&
168+
!lowerPath.endsWith(".tar.gz") &&
169+
!lowerPath.endsWith(".tgz")
170+
) {
171+
throw new Error(
172+
`File is not a supported archive format (zip, tar.gz): ${options.path}`,
173+
);
174+
}
175+
} catch {
176+
throw new Error(`Path does not exist or is invalid: ${options.path}`);
177+
}
178+
}
179+
180+
// If URL is provided, validate it's a valid URL
181+
if (options.url) {
182+
try {
183+
new URL(options.url);
184+
} catch {
185+
throw new Error(`Invalid URL format: ${options.url}`);
186+
}
187+
}
188+
189+
const source: any = {
190+
type: "archive",
191+
};
192+
193+
if (options.path) {
194+
source.path = options.path;
195+
}
196+
if (options.url) {
197+
source.url = options.url;
198+
}
199+
if (options.paths) {
200+
source.paths = options.paths.split(",");
201+
}
202+
203+
return {
204+
id: options.id,
205+
name: options.name,
206+
description:
207+
options.description || `Archive: ${options.path || options.url}`,
208+
sources: [source],
209+
};
210+
}

packages/cli/src/commands/init.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from "@codemcp/knowledge-core";
1717
import {
1818
GitRepoLoader,
19-
ZipLoader,
19+
ArchiveLoader,
2020
WebSourceType,
2121
} from "@codemcp/knowledge-content-loader";
2222

@@ -267,16 +267,18 @@ export const initCommand = new Command("init")
267267
path.join(localPath, `.agentic-source-${index}.json`),
268268
JSON.stringify(metadata, null, 2),
269269
);
270-
} else if (source.type === "zip") {
271-
// Handle zip file initialization
272-
const loader = new ZipLoader();
270+
} else if (source.type === "archive") {
271+
// Handle archive file initialization (zip, tar.gz, etc.)
272+
const loader = new ArchiveLoader();
273273
const sourceUrl = source.url || source.path || "";
274274

275-
console.log(chalk.gray(` Using ZipLoader for zip extraction`));
275+
console.log(
276+
chalk.gray(` Using ArchiveLoader for archive extraction`),
277+
);
276278

277279
const webSourceConfig = {
278280
url: sourceUrl,
279-
type: WebSourceType.ZIP,
281+
type: WebSourceType.ARCHIVE,
280282
options: {
281283
paths: source.paths || [],
282284
},
@@ -286,15 +288,15 @@ export const initCommand = new Command("init")
286288
const validation = loader.validateConfig(webSourceConfig);
287289
if (validation !== true) {
288290
throw new Error(
289-
`Invalid zip source configuration: ${validation}`,
291+
`Invalid archive source configuration: ${validation}`,
290292
);
291293
}
292294

293-
// Load content using ZipLoader
295+
// Load content using ArchiveLoader
294296
const result = await loader.load(webSourceConfig, localPath);
295297

296298
if (!result.success) {
297-
throw new Error(`Zip loading failed: ${result.error}`);
299+
throw new Error(`Archive loading failed: ${result.error}`);
298300
}
299301

300302
// Collect discovered paths for config update
@@ -303,7 +305,7 @@ export const initCommand = new Command("init")
303305
totalFiles += result.files.length;
304306
console.log(
305307
chalk.green(
306-
` ✅ Extracted ${result.files.length} files from zip`,
308+
` ✅ Extracted ${result.files.length} files from archive`,
307309
),
308310
);
309311

packages/cli/src/commands/refresh.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import {
1414
calculateLocalPath,
1515
ensureKnowledgeGitignoreSync,
1616
} from "@codemcp/knowledge-core";
17-
import { ZipLoader, WebSourceType } from "@codemcp/knowledge-content-loader";
17+
import {
18+
ArchiveLoader,
19+
WebSourceType,
20+
} from "@codemcp/knowledge-content-loader";
1821

1922
interface DocsetMetadata {
2023
docset_id: string;
@@ -169,8 +172,8 @@ async function refreshDocset(
169172
);
170173
totalFiles += sourceFiles.files_count;
171174
refreshedSources.push(sourceFiles);
172-
} else if (source.type === "zip") {
173-
const sourceFiles = await refreshZipSource(
175+
} else if (source.type === "archive") {
176+
const sourceFiles = await refreshArchiveSource(
174177
source,
175178
localPath,
176179
index,
@@ -372,7 +375,7 @@ async function refreshGitSource(
372375
}
373376
}
374377

375-
async function refreshZipSource(
378+
async function refreshArchiveSource(
376379
source: any,
377380
localPath: string,
378381
index: number,
@@ -393,10 +396,10 @@ async function refreshZipSource(
393396
}
394397

395398
const sourceUrl = source.url || source.path || "";
396-
const loader = new ZipLoader();
399+
const loader = new ArchiveLoader();
397400
const webSourceConfig = {
398401
url: sourceUrl,
399-
type: WebSourceType.ZIP,
402+
type: WebSourceType.ARCHIVE,
400403
options: {
401404
paths: source.paths || [],
402405
},
@@ -439,12 +442,12 @@ async function refreshZipSource(
439442
const result = await loader.load(webSourceConfig, localPath);
440443

441444
if (!result.success) {
442-
throw new Error(`Zip refresh failed: ${result.error}`);
445+
throw new Error(`Archive refresh failed: ${result.error}`);
443446
}
444447

445448
const metadata: SourceMetadata = {
446449
source_url: sourceUrl,
447-
source_type: "zip",
450+
source_type: "archive",
448451
downloaded_at: new Date().toISOString(),
449452
files_count: result.files.length,
450453
files: result.files,

packages/content-loader/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
},
3131
"dependencies": {
3232
"adm-zip": "0.5.16",
33-
"simple-git": "^3.22.0"
33+
"simple-git": "^3.22.0",
34+
"tar": "7.5.9"
3435
},
3536
"devDependencies": {
3637
"@eslint/js": "^9.34.0",
3738
"@types/adm-zip": "0.5.7",
3839
"@types/node": "^24.3.0",
40+
"@types/tar": "7.0.87",
3941
"eslint": "^9.34.0",
4042
"rimraf": "^6.0.1",
4143
"typescript": "^5.9.2",

0 commit comments

Comments
 (0)