Files
AgentCompany/src/main/skills/import-url.ts
T
ZacharyZhang-NY 00b0e0aaed refactor(ui+skills): split import.ts + primitives.tsx god files
Two more > 400 LOC modules split along responsibility seams. Zero
behavior change — public APIs re-export from the original file paths,
so every consumer import stays working.

| Original                              | Split into                    | LOC each |
|---------------------------------------|-------------------------------|----------|
| src/main/skills/import.ts (458)       | import (orchestrator) /       | 132      |
|                                       | discovery (findSkillMdFiles,  | 153      |
|                                       | buildLocalInventory)          |          |
|                                       | materialize                   | 24       |
|                                       | import-local                  | 147      |
|                                       | import-url                    | 85       |
| src/renderer/components/ui/           | primitives (barrel re-export) | 11       |
| primitives.tsx (419)                  | Card / Eyebrow / Badge /      | 9–59     |
|                                       | Spinner / Panel / Field /     | each     |
|                                       | Input / Select / TextArea /   |          |
|                                       | ActionButton / SectionButton  |          |

`createSkillImporter` still returns `{ importFromSource, importFromGitHub,
importFromLocal, importFromUrl, cleanupSkillFiles, parseGitHubUrl,
findSkillMdFiles, buildLocalInventory }` — every existing consumer
resolves unchanged.

`primitives.tsx` is now a pure barrel re-exporting the 11 primitives.
Consumers can migrate to direct imports (`../ui/Card`) opportunistically
but the barrel path keeps working.
2026-04-17 16:35:30 -04:00

86 lines
2.8 KiB
TypeScript

import { randomUUID } from "node:crypto";
import type { AppDatabase } from "../database";
import type { FileInventoryEntry, SkillImportResult } from "../skill-types.js";
import { parseFrontmatterMarkdown } from "../skill-format.js";
import { deriveCanonicalSkillKey, normalizeSkillSlug } from "../skill-keys.js";
import { materializeSkillFiles } from "./materialize";
export interface UrlImportContext {
db: AppDatabase;
logger: {
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
};
skillFilesDir: (companyId: string, slug: string) => string;
}
export async function importFromUrl(
ctx: UrlImportContext,
companyId: string,
url: string,
safeFetch: (target: string) => Promise<Response>,
): Promise<SkillImportResult[]> {
const { db, logger, skillFilesDir } = ctx;
const response = await safeFetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch skill from URL: ${response.status} ${url}`);
}
const markdown = await response.text();
const parsed = parseFrontmatterMarkdown(markdown);
const nowTs = new Date().toISOString();
let urlSlug: string | null = null;
try {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split("/").filter(Boolean);
const lastPart = pathParts[pathParts.length - 1] ?? "";
urlSlug = normalizeSkillSlug(lastPart.replace(/\.md$/i, ""));
} catch (err) {
logger.warn(`[skill-import] Unable to parse skill URL for slug derivation (${url}):`, err instanceof Error ? err.message : String(err));
}
const rawSlug =
normalizeSkillSlug(parsed.frontmatter.slug as string | undefined) ??
urlSlug ??
"skill";
const inventory: FileInventoryEntry[] = [
{ path: "SKILL.md", kind: "skill" },
];
const trustLevel = "markdown_only" as const;
const key = deriveCanonicalSkillKey(companyId, {
slug: rawSlug,
sourceType: "url",
sourceLocator: url,
metadata: null,
});
const skillId = randomUUID();
await materializeSkillFiles(skillFilesDir(companyId, rawSlug), markdown, inventory);
const { isUpdate } = db.upsertCompanySkill({
id: skillId,
companyId,
key,
slug: rawSlug,
name: (typeof parsed.frontmatter.name === "string" ? parsed.frontmatter.name : null) ?? rawSlug,
description: typeof parsed.frontmatter.description === "string" ? parsed.frontmatter.description : null,
sourceType: "url",
sourceLocator: url,
sourceRef: null,
trustLevel,
compatibility: "compatible",
fileInventory: JSON.stringify(inventory),
metadata: null,
createdAt: nowTs,
updatedAt: nowTs,
});
const results: SkillImportResult[] = [
{ skillId, key, name: rawSlug, isUpdate },
];
logger.info(`Skill ${isUpdate ? "updated" : "imported"} from URL: ${key}`);
return results;
}