Skip to content

feat: import local skill folders and zip bundles#52

Closed
furtherref wants to merge 1 commit into
mainfrom
agent/ai-gpt/34bd329a
Closed

feat: import local skill folders and zip bundles#52
furtherref wants to merge 1 commit into
mainfrom
agent/ai-gpt/34bd329a

Conversation

@furtherref
Copy link
Copy Markdown
Owner

@furtherref furtherref commented May 12, 2026

Summary

  • Add a local skill import API and core client types for importing uploaded skill bundles with per-item created, skipped, and failed results.
  • Add browser-side folder / zip parsing plus a local upload panel in the create skill dialog, including duplicate handling, file limits, localized copy, and preview metadata.
  • Harden uploaded file path validation and keep nested SKILL.md files inside the detected parent skill bundle.

Test Plan

  • cd server && go test ./internal/handler -run 'TestImportLocalSkills|TestNormalizeLocalUploadedSkillFilePathRejectsBackslashTraversal|TestRuntimeLocalSkillImportFlow_EndToEnd' -count=1
  • pnpm --filter @multica/core test -- api/client.test.ts
  • pnpm --filter @multica/views test -- skills/utils/local-skill-upload.test.ts skills/components/local-skill-upload-panel.test.tsx skills/components/create-skill-dialog.test.tsx locales/parity.test.ts
  • pnpm --filter @multica/views typecheck
  • pnpm --filter @multica/core typecheck
  • git diff --check origin/main...HEAD

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
multica-web Ready Ready Preview, Comment May 23, 2026 12:07pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
multica-docs Ignored Ignored Preview May 23, 2026 12:07pm

chatgpt-codex-connector[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37035c9b6c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/internal/handler/skill_import_local.go
Repository owner deleted a comment from chatgpt-codex-connector Bot May 12, 2026
Repository owner deleted a comment from chatgpt-codex-connector Bot May 12, 2026
Repository owner deleted a comment from chatgpt-codex-connector Bot May 12, 2026
Repository owner deleted a comment from chatgpt-codex-connector Bot May 12, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0870f7fb9c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/api/schemas.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df9dfa4b78

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b00250611d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c8cc522b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 194c336f81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb2181ce58

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b958684115

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts Outdated
Comment thread packages/views/skills/utils/local-skill-upload.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c1cf15efa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/internal/handler/skill_import_local.go
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5a5949fe8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8c0840ecc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/utils/local-skill-upload.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cdd8168723

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/internal/handler/skill_import_local.go
@furtherref furtherref closed this May 16, 2026
@furtherref furtherref reopened this May 23, 2026
furtherref added a commit that referenced this pull request May 23, 2026
Keep both PR #52's local skill import feature (schemas, client methods,
tests) and main's cloud runtime node + grouped issues additions.
Deduplicate EMPTY_CREATE_AGENT_FROM_TEMPLATE_RESPONSE import; adopt
main's hast-util-to-html version (^9.0.5) while keeping PR's jszip dep.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
furtherref added a commit that referenced this pull request May 23, 2026
- Stop reading folder files past the per-skill byte budget
  (MAX_SKILL_ACCEPTED_BYTES) so large folders don't freeze the browser
- Report "unreadable_skill_md" instead of "missing_skill_md" when
  SKILL.md exists but is binary or oversized (folder + zip paths)
- Reject hidden (.dot) and metadata (Thumbs.db, desktop.ini) file paths
  server-side in normalizeLocalUploadedSkillFilePath
- Add i18n for the new unreadable_skill_md error message (en + zh-Hans)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: acec3a103f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/views/skills/components/local-skill-upload-panel.tsx Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa2e0cfb3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +293 to +300
if (path !== joinPath(root, "SKILL.md") && !isHiddenPath(path) && !isMetadataFile(path)) {
const count = supportingFileCountsByRoot.get(root) ?? 0;
if (count >= MAX_SKILL_FILES) {
entries.push({ path, skippedReason: "too_many_files" });
continue;
}
supportingFileCountsByRoot.set(root, count + 1);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count zip supporting files only after text validation

In readZipFile, the per-root supporting-file counter is incremented before the file content is decoded and validated, but binary/non-UTF-8 files are only rejected later in buildLocalSkillCandidates. This means skipped binary files still consume the 128-file budget for zip uploads; for example, a zip with 128 binary files followed by a valid template will mark the valid template as too_many_files, even though folder uploads (and the stated skip policy) do not count rejected binaries. Defer the count increment until after a file is confirmed importable text.

Useful? React with 👍 / 👎.

}
supportingFileCountsByRoot.set(root, count + 1);
}
const blob = await entry.async("blob");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce bundle byte budget before inflating zip entries

This loop inflates each selected zip entry with entry.async("blob") without applying the 8 MiB per-skill bundle cap first. A valid-looking archive with 128 entries near 1 MiB each can therefore allocate roughly 128 MiB in-browser before the later candidate-building step skips most files as bundle_too_large, which can cause significant UI stalls or memory pressure. Apply a per-root byte budget check before blob inflation (or keep excess entries as skipped metadata) so zip uploads are bounded like folder uploads.

Useful? React with 👍 / 👎.

Comment on lines +301 to +304
const blob = await entry.async("blob");
entries.push({
path,
file: new File([blob], basename(path)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip hidden zip entries before inflating blob contents

readZipFile still materializes blob data for hidden/metadata entries under a detected skill root, even though those files are always dropped later by buildLocalSkillCandidates. In archives containing many large dotfiles (for example .git-style paths), this eagerly allocates substantial browser memory for files that can never be imported, which can slow or freeze preview. Short-circuit hidden/metadata entries before entry.async("blob") and record them as skipped instead of inflating them.

Useful? React with 👍 / 👎.

Add a fourth creation method to the Add Skill dialog: "Upload folder or
zip". Users can select local skill directories via folder picker, zip
files, or drag-and-drop, preview detected skills with editable names and
descriptions, and import up to 16 skills per batch.

Browser-side parsing (packages/views/skills/utils/local-skill-upload.ts):
- Path normalization rejecting absolute, traversal, hidden, and metadata paths
- SKILL.md discovery and multi-skill grouping by nearest skill root
- UTF-8 validation with fatal TextDecoder, null byte detection
- Per-file (1 MiB), per-skill (128 files, 8 MiB), per-batch (16 skills) limits
- Zip entry pre-inflate size check to avoid decompression bombs
- Zip entries scoped to detected skill roots only

Upload panel (packages/views/skills/components/local-skill-upload-panel.tsx):
- Dashed drop zone with folder picker and zip picker buttons
- Single-skill and multi-skill preview with editable fields
- Batch selection with dynamic enable/disable at the 16-skill cap
- Import progress and structured result summary (created/skipped/failed)
- File input value reset for re-selection of same path
- Full en + zh-Hans localization

Backend API (server/internal/handler/skill_import_local.go):
- POST /api/skills/import-local with per-item structured results
- Server-side validation: path rules, per-file and bundle size caps,
  hidden/metadata file rejection, null byte sanitization
- MaxBytesReader at 32 MiB to bound JSON decode memory
- Partial success: duplicate names reported as skipped, not failed

API client (packages/core/api/):
- importLocalSkills method with Zod response schema and parseWithFallback
- Malformed response graceful degradation to empty result

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe14574965

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +91 to +93
if (parts.includes("..")) {
return { ok: false, reason: "path_traversal" };
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject dot path segments during upload normalization

normalizeUploadPath rejects .. but still accepts . segments, so entries like skill/./templates/a.md are treated as valid in preview and sent to the API. The backend path validator (normalizeLocalUploadedSkillFilePath) rejects . segments and returns invalid_file_path, causing imports that looked valid in the UI to fail at submit time. This is reproducible with zips produced by tools that preserve ./ path components; align client normalization with the server by rejecting or normalizing . segments before candidate creation.

Useful? React with 👍 / 👎.

Comment on lines +91 to +92
if strings.TrimSpace(skill.Content) == "" {
return "missing_skill_md"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept empty SKILL.md content for local imports

This endpoint rejects any imported skill whose content is empty or whitespace-only (missing_skill_md), but SKILL.md presence is already established by the upload parser and regular skill creation allows empty content. As a result, a valid bundle containing an intentionally blank SKILL.md passes preview and then fails only at import time, creating inconsistent behavior between local import and POST /api/skills.

Useful? React with 👍 / 👎.

const [summary, setSummary] = useState<ImportSummary | null>(null);

const selectedCandidates = useMemo(
() => candidates.filter((candidate) => candidate.valid && candidate.selected && candidate.name.trim()),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce batch cap independently of editable names

The selected-count gate is derived from selectedCandidates, which excludes entries whose name.trim() is empty. In multi-skill uploads, a user can temporarily blank one selected name to drop the counted total below 16, select an additional skill, then restore the blanked name and submit more than 16 skills; the request then fails with the server's too many skills error even though the UI was supposed to enforce the cap. Count selected valid candidates separately from name validity when enforcing the checkbox/import limit.

Useful? React with 👍 / 👎.

Comment on lines +122 to +130
const skillRoots = normalizedInputs
.filter((input) =>
basename(input.path) === "SKILL.md" &&
(input.skippedReason !== undefined ||
(!!input.file && input.file.size <= MAX_SKILL_FILE_BYTES)),
)
.map((input) => dirname(input.path))
.sort()
.filter((root, index, roots) => !hasAncestorSkillRoot(root, roots.slice(0, index)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep child skill roots when parent SKILL.md is unreadable

Skill-root discovery currently treats any SKILL.md (including one already marked unreadable/oversized) as an ancestor root, so a bad top-level SKILL.md suppresses all nested valid roots via hasAncestorSkillRoot. In a bundle containing an unreadable root SKILL.md plus valid subdir/SKILL.md skills, only the invalid top-level candidate is returned and the valid child skills become unimportable. Ancestor filtering should ignore invalid parent roots or defer parent-child collapsing until the parent SKILL.md is confirmed readable.

Useful? React with 👍 / 👎.

@furtherref furtherref closed this May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant