Address Greptile review on portability PR
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
a3f568dec7
commit
92c29f27c3
7 changed files with 129 additions and 185 deletions
|
|
@ -6,6 +6,7 @@ import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||||
|
import { createStoredZipArchive } from "./helpers/zip.js";
|
||||||
|
|
||||||
type EmbeddedPostgresInstance = {
|
type EmbeddedPostgresInstance = {
|
||||||
initialise(): Promise<void>;
|
initialise(): Promise<void>;
|
||||||
|
|
@ -182,29 +183,6 @@ function createCliEnv() {
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeUint16(target: Uint8Array, offset: number, value: number) {
|
|
||||||
target[offset] = value & 0xff;
|
|
||||||
target[offset + 1] = (value >>> 8) & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeUint32(target: Uint8Array, offset: number, value: number) {
|
|
||||||
target[offset] = value & 0xff;
|
|
||||||
target[offset + 1] = (value >>> 8) & 0xff;
|
|
||||||
target[offset + 2] = (value >>> 16) & 0xff;
|
|
||||||
target[offset + 3] = (value >>> 24) & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
function crc32(bytes: Uint8Array) {
|
|
||||||
let crc = 0xffffffff;
|
|
||||||
for (const byte of bytes) {
|
|
||||||
crc ^= byte;
|
|
||||||
for (let bit = 0; bit < 8; bit += 1) {
|
|
||||||
crc = (crc & 1) === 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (crc ^ 0xffffffff) >>> 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectTextFiles(root: string, current: string, files: Record<string, string>) {
|
function collectTextFiles(root: string, current: string, files: Record<string, string>) {
|
||||||
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
||||||
const absolutePath = path.join(current, entry.name);
|
const absolutePath = path.join(current, entry.name);
|
||||||
|
|
@ -218,71 +196,6 @@ function collectTextFiles(root: string, current: string, files: Record<string, s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStoredZipArchive(files: Record<string, string>, rootPath: string) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const localChunks: Uint8Array[] = [];
|
|
||||||
const centralChunks: Uint8Array[] = [];
|
|
||||||
let localOffset = 0;
|
|
||||||
let entryCount = 0;
|
|
||||||
|
|
||||||
for (const [relativePath, content] of Object.entries(files).sort(([left], [right]) => left.localeCompare(right))) {
|
|
||||||
const fileName = encoder.encode(`${rootPath}/${relativePath}`);
|
|
||||||
const body = encoder.encode(content);
|
|
||||||
const checksum = crc32(body);
|
|
||||||
|
|
||||||
const localHeader = new Uint8Array(30 + fileName.length);
|
|
||||||
writeUint32(localHeader, 0, 0x04034b50);
|
|
||||||
writeUint16(localHeader, 4, 20);
|
|
||||||
writeUint16(localHeader, 6, 0x0800);
|
|
||||||
writeUint16(localHeader, 8, 0);
|
|
||||||
writeUint32(localHeader, 14, checksum);
|
|
||||||
writeUint32(localHeader, 18, body.length);
|
|
||||||
writeUint32(localHeader, 22, body.length);
|
|
||||||
writeUint16(localHeader, 26, fileName.length);
|
|
||||||
localHeader.set(fileName, 30);
|
|
||||||
|
|
||||||
const centralHeader = new Uint8Array(46 + fileName.length);
|
|
||||||
writeUint32(centralHeader, 0, 0x02014b50);
|
|
||||||
writeUint16(centralHeader, 4, 20);
|
|
||||||
writeUint16(centralHeader, 6, 20);
|
|
||||||
writeUint16(centralHeader, 8, 0x0800);
|
|
||||||
writeUint16(centralHeader, 10, 0);
|
|
||||||
writeUint32(centralHeader, 16, checksum);
|
|
||||||
writeUint32(centralHeader, 20, body.length);
|
|
||||||
writeUint32(centralHeader, 24, body.length);
|
|
||||||
writeUint16(centralHeader, 28, fileName.length);
|
|
||||||
writeUint32(centralHeader, 42, localOffset);
|
|
||||||
centralHeader.set(fileName, 46);
|
|
||||||
|
|
||||||
localChunks.push(localHeader, body);
|
|
||||||
centralChunks.push(centralHeader);
|
|
||||||
localOffset += localHeader.length + body.length;
|
|
||||||
entryCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const centralDirectoryLength = centralChunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
||||||
const archive = new Uint8Array(
|
|
||||||
localChunks.reduce((sum, chunk) => sum + chunk.length, 0) + centralDirectoryLength + 22,
|
|
||||||
);
|
|
||||||
let offset = 0;
|
|
||||||
for (const chunk of localChunks) {
|
|
||||||
archive.set(chunk, offset);
|
|
||||||
offset += chunk.length;
|
|
||||||
}
|
|
||||||
const centralDirectoryOffset = offset;
|
|
||||||
for (const chunk of centralChunks) {
|
|
||||||
archive.set(chunk, offset);
|
|
||||||
offset += chunk.length;
|
|
||||||
}
|
|
||||||
writeUint32(archive, offset, 0x06054b50);
|
|
||||||
writeUint16(archive, offset + 8, entryCount);
|
|
||||||
writeUint16(archive, offset + 10, entryCount);
|
|
||||||
writeUint32(archive, offset + 12, centralDirectoryLength);
|
|
||||||
writeUint32(archive, offset + 16, centralDirectoryOffset);
|
|
||||||
|
|
||||||
return archive;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stopServerProcess(child: ServerProcess | null) {
|
async function stopServerProcess(child: ServerProcess | null) {
|
||||||
if (!child || child.exitCode !== null) return;
|
if (!child || child.exitCode !== null) return;
|
||||||
child.kill("SIGTERM");
|
child.kill("SIGTERM");
|
||||||
|
|
|
||||||
|
|
@ -3,96 +3,7 @@ import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { resolveInlineSourceFromPath } from "../commands/client/company.js";
|
import { resolveInlineSourceFromPath } from "../commands/client/company.js";
|
||||||
|
import { createStoredZipArchive } from "./helpers/zip.js";
|
||||||
function writeUint16(target: Uint8Array, offset: number, value: number) {
|
|
||||||
target[offset] = value & 0xff;
|
|
||||||
target[offset + 1] = (value >>> 8) & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeUint32(target: Uint8Array, offset: number, value: number) {
|
|
||||||
target[offset] = value & 0xff;
|
|
||||||
target[offset + 1] = (value >>> 8) & 0xff;
|
|
||||||
target[offset + 2] = (value >>> 16) & 0xff;
|
|
||||||
target[offset + 3] = (value >>> 24) & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
function crc32(bytes: Uint8Array) {
|
|
||||||
let crc = 0xffffffff;
|
|
||||||
for (const byte of bytes) {
|
|
||||||
crc ^= byte;
|
|
||||||
for (let bit = 0; bit < 8; bit += 1) {
|
|
||||||
crc = (crc & 1) === 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (crc ^ 0xffffffff) >>> 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createStoredZipArchive(files: Record<string, string>, rootPath: string) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const localChunks: Uint8Array[] = [];
|
|
||||||
const centralChunks: Uint8Array[] = [];
|
|
||||||
let localOffset = 0;
|
|
||||||
let entryCount = 0;
|
|
||||||
|
|
||||||
for (const [relativePath, content] of Object.entries(files).sort(([left], [right]) => left.localeCompare(right))) {
|
|
||||||
const fileName = encoder.encode(`${rootPath}/${relativePath}`);
|
|
||||||
const body = encoder.encode(content);
|
|
||||||
const checksum = crc32(body);
|
|
||||||
|
|
||||||
const localHeader = new Uint8Array(30 + fileName.length);
|
|
||||||
writeUint32(localHeader, 0, 0x04034b50);
|
|
||||||
writeUint16(localHeader, 4, 20);
|
|
||||||
writeUint16(localHeader, 6, 0x0800);
|
|
||||||
writeUint16(localHeader, 8, 0);
|
|
||||||
writeUint32(localHeader, 14, checksum);
|
|
||||||
writeUint32(localHeader, 18, body.length);
|
|
||||||
writeUint32(localHeader, 22, body.length);
|
|
||||||
writeUint16(localHeader, 26, fileName.length);
|
|
||||||
localHeader.set(fileName, 30);
|
|
||||||
|
|
||||||
const centralHeader = new Uint8Array(46 + fileName.length);
|
|
||||||
writeUint32(centralHeader, 0, 0x02014b50);
|
|
||||||
writeUint16(centralHeader, 4, 20);
|
|
||||||
writeUint16(centralHeader, 6, 20);
|
|
||||||
writeUint16(centralHeader, 8, 0x0800);
|
|
||||||
writeUint16(centralHeader, 10, 0);
|
|
||||||
writeUint32(centralHeader, 16, checksum);
|
|
||||||
writeUint32(centralHeader, 20, body.length);
|
|
||||||
writeUint32(centralHeader, 24, body.length);
|
|
||||||
writeUint16(centralHeader, 28, fileName.length);
|
|
||||||
writeUint32(centralHeader, 42, localOffset);
|
|
||||||
centralHeader.set(fileName, 46);
|
|
||||||
|
|
||||||
localChunks.push(localHeader, body);
|
|
||||||
centralChunks.push(centralHeader);
|
|
||||||
localOffset += localHeader.length + body.length;
|
|
||||||
entryCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const centralDirectoryLength = centralChunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
||||||
const archive = new Uint8Array(
|
|
||||||
localChunks.reduce((sum, chunk) => sum + chunk.length, 0) + centralDirectoryLength + 22,
|
|
||||||
);
|
|
||||||
|
|
||||||
let offset = 0;
|
|
||||||
for (const chunk of localChunks) {
|
|
||||||
archive.set(chunk, offset);
|
|
||||||
offset += chunk.length;
|
|
||||||
}
|
|
||||||
const centralDirectoryOffset = offset;
|
|
||||||
for (const chunk of centralChunks) {
|
|
||||||
archive.set(chunk, offset);
|
|
||||||
offset += chunk.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeUint32(archive, offset, 0x06054b50);
|
|
||||||
writeUint16(archive, offset + 8, entryCount);
|
|
||||||
writeUint16(archive, offset + 10, entryCount);
|
|
||||||
writeUint32(archive, offset + 12, centralDirectoryLength);
|
|
||||||
writeUint32(archive, offset + 16, centralDirectoryOffset);
|
|
||||||
|
|
||||||
return archive;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tempDirs: string[] = [];
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,11 @@ describe("renderCompanyImportResult", () => {
|
||||||
{ slug: "cto", id: "agent-2", action: "updated", name: "CTO", reason: "replace strategy" },
|
{ slug: "cto", id: "agent-2", action: "updated", name: "CTO", reason: "replace strategy" },
|
||||||
{ slug: "ops", id: null, action: "skipped", name: "Ops", reason: "skip strategy" },
|
{ slug: "ops", id: null, action: "skipped", name: "Ops", reason: "skip strategy" },
|
||||||
],
|
],
|
||||||
projects: [],
|
projects: [
|
||||||
|
{ slug: "app", id: "project-1", action: "created", name: "App", reason: null },
|
||||||
|
{ slug: "ops", id: "project-2", action: "updated", name: "Operations", reason: "replace strategy" },
|
||||||
|
{ slug: "archive", id: null, action: "skipped", name: "Archive", reason: "skip strategy" },
|
||||||
|
],
|
||||||
envInputs: [],
|
envInputs: [],
|
||||||
warnings: ["Review API keys"],
|
warnings: ["Review API keys"],
|
||||||
},
|
},
|
||||||
|
|
@ -318,7 +322,9 @@ describe("renderCompanyImportResult", () => {
|
||||||
expect(rendered).toContain("Company");
|
expect(rendered).toContain("Company");
|
||||||
expect(rendered).toContain("https://paperclip.example/PAP/dashboard");
|
expect(rendered).toContain("https://paperclip.example/PAP/dashboard");
|
||||||
expect(rendered).toContain("3 agents total (1 created, 1 updated, 1 skipped)");
|
expect(rendered).toContain("3 agents total (1 created, 1 updated, 1 skipped)");
|
||||||
|
expect(rendered).toContain("3 projects total (1 created, 1 updated, 1 skipped)");
|
||||||
expect(rendered).toContain("Agent results");
|
expect(rendered).toContain("Agent results");
|
||||||
|
expect(rendered).toContain("Project results");
|
||||||
expect(rendered).toContain("Using claude-local adapter");
|
expect(rendered).toContain("Using claude-local adapter");
|
||||||
expect(rendered).toContain("Review API keys");
|
expect(rendered).toContain("Review API keys");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
87
cli/src/__tests__/helpers/zip.ts
Normal file
87
cli/src/__tests__/helpers/zip.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
function writeUint16(target: Uint8Array, offset: number, value: number) {
|
||||||
|
target[offset] = value & 0xff;
|
||||||
|
target[offset + 1] = (value >>> 8) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeUint32(target: Uint8Array, offset: number, value: number) {
|
||||||
|
target[offset] = value & 0xff;
|
||||||
|
target[offset + 1] = (value >>> 8) & 0xff;
|
||||||
|
target[offset + 2] = (value >>> 16) & 0xff;
|
||||||
|
target[offset + 3] = (value >>> 24) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
function crc32(bytes: Uint8Array) {
|
||||||
|
let crc = 0xffffffff;
|
||||||
|
for (const byte of bytes) {
|
||||||
|
crc ^= byte;
|
||||||
|
for (let bit = 0; bit < 8; bit += 1) {
|
||||||
|
crc = (crc & 1) === 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (crc ^ 0xffffffff) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStoredZipArchive(files: Record<string, string>, rootPath: string) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const localChunks: Uint8Array[] = [];
|
||||||
|
const centralChunks: Uint8Array[] = [];
|
||||||
|
let localOffset = 0;
|
||||||
|
let entryCount = 0;
|
||||||
|
|
||||||
|
for (const [relativePath, content] of Object.entries(files).sort(([left], [right]) => left.localeCompare(right))) {
|
||||||
|
const fileName = encoder.encode(`${rootPath}/${relativePath}`);
|
||||||
|
const body = encoder.encode(content);
|
||||||
|
const checksum = crc32(body);
|
||||||
|
|
||||||
|
const localHeader = new Uint8Array(30 + fileName.length);
|
||||||
|
writeUint32(localHeader, 0, 0x04034b50);
|
||||||
|
writeUint16(localHeader, 4, 20);
|
||||||
|
writeUint16(localHeader, 6, 0x0800);
|
||||||
|
writeUint16(localHeader, 8, 0);
|
||||||
|
writeUint32(localHeader, 14, checksum);
|
||||||
|
writeUint32(localHeader, 18, body.length);
|
||||||
|
writeUint32(localHeader, 22, body.length);
|
||||||
|
writeUint16(localHeader, 26, fileName.length);
|
||||||
|
localHeader.set(fileName, 30);
|
||||||
|
|
||||||
|
const centralHeader = new Uint8Array(46 + fileName.length);
|
||||||
|
writeUint32(centralHeader, 0, 0x02014b50);
|
||||||
|
writeUint16(centralHeader, 4, 20);
|
||||||
|
writeUint16(centralHeader, 6, 20);
|
||||||
|
writeUint16(centralHeader, 8, 0x0800);
|
||||||
|
writeUint16(centralHeader, 10, 0);
|
||||||
|
writeUint32(centralHeader, 16, checksum);
|
||||||
|
writeUint32(centralHeader, 20, body.length);
|
||||||
|
writeUint32(centralHeader, 24, body.length);
|
||||||
|
writeUint16(centralHeader, 28, fileName.length);
|
||||||
|
writeUint32(centralHeader, 42, localOffset);
|
||||||
|
centralHeader.set(fileName, 46);
|
||||||
|
|
||||||
|
localChunks.push(localHeader, body);
|
||||||
|
centralChunks.push(centralHeader);
|
||||||
|
localOffset += localHeader.length + body.length;
|
||||||
|
entryCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const centralDirectoryLength = centralChunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
||||||
|
const archive = new Uint8Array(
|
||||||
|
localChunks.reduce((sum, chunk) => sum + chunk.length, 0) + centralDirectoryLength + 22,
|
||||||
|
);
|
||||||
|
let offset = 0;
|
||||||
|
for (const chunk of localChunks) {
|
||||||
|
archive.set(chunk, offset);
|
||||||
|
offset += chunk.length;
|
||||||
|
}
|
||||||
|
const centralDirectoryOffset = offset;
|
||||||
|
for (const chunk of centralChunks) {
|
||||||
|
archive.set(chunk, offset);
|
||||||
|
offset += chunk.length;
|
||||||
|
}
|
||||||
|
writeUint32(archive, offset, 0x06054b50);
|
||||||
|
writeUint16(archive, offset + 8, entryCount);
|
||||||
|
writeUint16(archive, offset + 10, entryCount);
|
||||||
|
writeUint32(archive, offset + 12, centralDirectoryLength);
|
||||||
|
writeUint32(archive, offset + 16, centralDirectoryOffset);
|
||||||
|
|
||||||
|
return archive;
|
||||||
|
}
|
||||||
|
|
@ -536,6 +536,18 @@ function summarizeImportAgentResults(agents: CompanyPortabilityImportResult["age
|
||||||
return `${agents.length} ${pluralize(agents.length, "agent")} total (${parts.join(", ")})`;
|
return `${agents.length} ${pluralize(agents.length, "agent")} total (${parts.join(", ")})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function summarizeImportProjectResults(projects: CompanyPortabilityImportResult["projects"]): string {
|
||||||
|
if (projects.length === 0) return "0 projects changed";
|
||||||
|
const created = projects.filter((project) => project.action === "created").length;
|
||||||
|
const updated = projects.filter((project) => project.action === "updated").length;
|
||||||
|
const skipped = projects.filter((project) => project.action === "skipped").length;
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (created > 0) parts.push(`${created} created`);
|
||||||
|
if (updated > 0) parts.push(`${updated} updated`);
|
||||||
|
if (skipped > 0) parts.push(`${skipped} skipped`);
|
||||||
|
return `${projects.length} ${pluralize(projects.length, "project")} total (${parts.join(", ")})`;
|
||||||
|
}
|
||||||
|
|
||||||
function actionChip(action: string): string {
|
function actionChip(action: string): string {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "create":
|
case "create":
|
||||||
|
|
@ -661,6 +673,7 @@ export function renderCompanyImportResult(
|
||||||
`${pc.bold("Target")} ${meta.targetLabel}`,
|
`${pc.bold("Target")} ${meta.targetLabel}`,
|
||||||
`${pc.bold("Company")} ${result.company.name} (${actionChip(result.company.action)})`,
|
`${pc.bold("Company")} ${result.company.name} (${actionChip(result.company.action)})`,
|
||||||
`${pc.bold("Agents")} ${summarizeImportAgentResults(result.agents)}`,
|
`${pc.bold("Agents")} ${summarizeImportAgentResults(result.agents)}`,
|
||||||
|
`${pc.bold("Projects")} ${summarizeImportProjectResults(result.projects)}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (meta.companyUrl) {
|
if (meta.companyUrl) {
|
||||||
|
|
@ -676,6 +689,15 @@ export function renderCompanyImportResult(
|
||||||
reason: agent.reason,
|
reason: agent.reason,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
appendPreviewExamples(
|
||||||
|
lines,
|
||||||
|
"Project results",
|
||||||
|
result.projects.map((project) => ({
|
||||||
|
action: project.action,
|
||||||
|
label: `${project.slug} -> ${project.name}`,
|
||||||
|
reason: project.reason,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
if (result.envInputs.length > 0) {
|
if (result.envInputs.length > 0) {
|
||||||
lines.push("");
|
lines.push("");
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ Exported packages follow the [Agent Companies specification](/companies/companie
|
||||||
my-company/
|
my-company/
|
||||||
├── COMPANY.md # Company metadata
|
├── COMPANY.md # Company metadata
|
||||||
├── agents/
|
├── agents/
|
||||||
│ ├── ceo/AGENTS.md # Agent instructions + frontmatter
|
│ ├── ceo/AGENT.md # Agent instructions + frontmatter
|
||||||
│ └── cto/AGENTS.md
|
│ └── cto/AGENT.md
|
||||||
├── projects/
|
├── projects/
|
||||||
│ └── main/PROJECT.md
|
│ └── main/PROJECT.md
|
||||||
├── skills/
|
├── skills/
|
||||||
|
|
@ -25,7 +25,7 @@ my-company/
|
||||||
```
|
```
|
||||||
|
|
||||||
- **COMPANY.md** defines company name, description, and metadata.
|
- **COMPANY.md** defines company name, description, and metadata.
|
||||||
- **AGENTS.md** files contain agent identity, role, and instructions.
|
- **AGENT.md** files contain agent identity, role, and instructions.
|
||||||
- **SKILL.md** files are compatible with the Agent Skills ecosystem.
|
- **SKILL.md** files are compatible with the Agent Skills ecosystem.
|
||||||
- **.paperclip.yaml** holds Paperclip-specific config (adapter types, env inputs, budgets) as an optional sidecar.
|
- **.paperclip.yaml** holds Paperclip-specific config (adapter types, env inputs, budgets) as an optional sidecar.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,8 +149,13 @@ function parseCompanyPackage(companyDir: string): CompanyPackage | null {
|
||||||
const agents: CompanyPortabilityManifest["agents"] = [];
|
const agents: CompanyPortabilityManifest["agents"] = [];
|
||||||
if (fs.existsSync(agentsDir)) {
|
if (fs.existsSync(agentsDir)) {
|
||||||
for (const agentSlug of fs.readdirSync(agentsDir)) {
|
for (const agentSlug of fs.readdirSync(agentsDir)) {
|
||||||
const agentMdPath = path.join(agentsDir, agentSlug, "AGENTS.md");
|
const agentMdName = fs.existsSync(path.join(agentsDir, agentSlug, "AGENT.md"))
|
||||||
if (!fs.existsSync(agentMdPath)) continue;
|
? "AGENT.md"
|
||||||
|
: fs.existsSync(path.join(agentsDir, agentSlug, "AGENTS.md"))
|
||||||
|
? "AGENTS.md"
|
||||||
|
: null;
|
||||||
|
if (!agentMdName) continue;
|
||||||
|
const agentMdPath = path.join(agentsDir, agentSlug, agentMdName);
|
||||||
|
|
||||||
const agentMd = fs.readFileSync(agentMdPath, "utf-8");
|
const agentMd = fs.readFileSync(agentMdPath, "utf-8");
|
||||||
const { data: agentData } = parseFrontmatter(agentMd);
|
const { data: agentData } = parseFrontmatter(agentMd);
|
||||||
|
|
@ -164,7 +169,7 @@ function parseCompanyPackage(companyDir: string): CompanyPackage | null {
|
||||||
agents.push({
|
agents.push({
|
||||||
slug: agentSlug,
|
slug: agentSlug,
|
||||||
name: agentName,
|
name: agentName,
|
||||||
path: `agents/${agentSlug}/AGENTS.md`,
|
path: `agents/${agentSlug}/${agentMdName}`,
|
||||||
skills,
|
skills,
|
||||||
role,
|
role,
|
||||||
title,
|
title,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue