During a repo sync (scan button), if a skill was removed from the source
and is still assigned to agents, the skill is now automatically detached
from those agents before deletion, rather than leaving it attached.
Manual delete-by-source is unchanged (still blocks if in use).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GitHub/sks_sh pruned skills don't have project/workspace context needed for
the CompanySkillProjectScanConflict/Skipped types. Orphaned skills are
silently deleted; skills still used by agents emit a warning instead of
a conflict entry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies the scan endpoint returns skipped and conflicts arrays in the
response, covering the prune path where removed skills are either
skipped (orphaned) or added to conflicts (still used by agents).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When re-scanning existing sources, diff skills in the DB against the
current source manifest. Skills no longer in the source are either:
- Added to conflicts (if still used by agents)
- Deleted via deleteSkill (if orphaned), added to skipped
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that agents with canCreateAgents permission can call the
scan-projects endpoint and that scanProjectWorkspaces is invoked.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use Set<string> instead of Map for tracking unique source locators
- Remove dead skillsAtSource destructuring from loop
- Remove redundant deriveCanonicalSkillKey call (already set by readUrlSkillImports)
- Invert slug check to continue guard for cleaner flow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adapters installed with the old --no-save flag are not tracked in
package.json and get pruned when another adapter is installed. This
adds ensurePackageOnDisk() which detects missing packages and
reinstalls them from npm before reload or server boot attempts to
import them, fixing ENOENT errors for previously-pruned adapters.
Fixes FAR-47
Co-Authored-By: Paperclip <noreply@paperclip.ing>
npm install --no-save does not record dependencies in package.json,
so when a second adapter is installed, npm prunes the first as
extraneous. Removing --no-save ensures all installed adapters are
tracked in the managed package.json and persist across installs.
Fixes FAR-47
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add an async mutex (promise-chain FIFO queue) to serialise all adapter
mutation routes (install, reinstall, reload, delete) so that concurrent
requests cannot race on npm, the adapter-plugins.json store, or the
in-memory adapter registry.
Also switch adapter-plugin-store file writes to atomic write-tmp-then-rename
to prevent partial/corrupted reads from concurrent processes.
Includes the packageName.trim() fix for whitespace-induced npm failures.
9 new tests covering mutex serialisation, error recovery, FIFO ordering,
and atomic store operations.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Previously the server unconditionally overwrote PAPERCLIP_API_URL on
startup, clobbering any value set externally (e.g., via Kubernetes
ConfigMap). Now only sets the default if the env var is not already set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace redundant listFull() call with acceptedSkills to avoid extra DB round-trip
- Check slug conflicts against full acceptedSkills list instead of just same-source skills
- Call upsertAcceptedSkill after persisting to keep in-memory list current
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When "Scan project workspaces for skills" runs, now also iterates all
existing GitHub/sks_sh skills and re-fetches their source repos to
detect newly added skills. New skills are upserted automatically.
Skips sources that fail, logged as warnings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When updateSkillAuth(null) is called, the underlying secret row was
left orphaned. Now deletes the secret via secretsSvc.remove() before
clearing sourceAuthSecretId from metadata.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Scope metadata update WHERE clause to companyId for defence-in-depth
- Add CompanySkillUpdateAuth inferred type export to match other schemas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove duplicate `delete` method (identical to `remove`)
- Route delete-by-source through confirmation dialog with source
locator displayed and "Remove all from source" button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pre-check all skills for agent usage before deleting any in deleteBySource
to prevent partial/failed deletions
- Delete (rotate to empty) the skill-pat:<skillId> secret when a skill is
deleted to prevent orphaned PAT secrets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add optional authToken to skill import for GitHub private repos
- Store PAT as encrypted company secret (skill-pat:{skillId})
- Thread auth token through ghFetch, fetchText, fetchJson, and all GitHub resolution functions
- Add PATCH /companies/:companyId/skills/:skillId/auth for managing PAT per skill
- Add DELETE /companies/:companyId/skills/by-source for bulk deleting skills from a repo
- Preserve sourceAuthSecretId across skill re-imports/updates
- UI: Add PAT input field in import form for GitHub URLs
- UI: Add SkillAuthSection with ShieldCheck icon for viewing/updating/removing PAT
- UI: Add trash icon next to source label for delete-by-source
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When updateSkillAuth(null) is called, the underlying secret row was
left orphaned. Now deletes the secret via secretsSvc.remove() before
clearing sourceAuthSecretId from metadata.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Install custom tooling in the production stage via direct binaries and apt
so it doesn't break the base stage build.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The fork added build-time tooling (kubectl, kubeseal, uv, nano, vim) that
is not needed inside the container build and was causing repeated build
failures due to URL/checksum drift. These tools belong in the runtime
environment, not the image build.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- kubectl: pin to v1.32.0 instead of dynamic stable.txt (which was
returning a version with no matching binary, causing 404)
- kubeseal: fix URL to use versioned tarball (v0.36.6) instead of
/latest which had no unversioned asset, causing 404
- also removed wget (no longer needed after removing keyring/apt)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- kubectl: pin to v1.32.0 instead of dynamic stable.txt (which was
returning a version with no matching binary, causing 404)
- kubeseal: fix URL to use versioned tarball (v0.36.6) instead of
/latest which had no unversioned asset, causing 404
- also removed wget (no longer needed after removing keyring/apt)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The GitHub CLI keyring approach requires a hardcoded SHA256 checksum
that drifts as the keyring file is updated upstream, causing build
failures. Replace with direct binary tarball download which is simpler
and has no checksum drift issue.
Also removed wget (only needed for keyring download).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The GitHub CLI keyring approach requires a hardcoded SHA256 checksum
that drifts as the keyring file is updated upstream, causing build
failures. Replace with direct binary tarball download which is simpler
and has no checksum drift issue.
Also removed wget (only needed for keyring download).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The hardcoded checksum was out of date, causing sha256sum verification
to fail and abort the build.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
wget is called immediately after apt-get install but was not included
in the package list, causing the build to fail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Scope metadata update WHERE clause to companyId for defence-in-depth
- Add CompanySkillUpdateAuth inferred type export to match other schemas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove duplicate `delete` method (identical to `remove`)
- Route delete-by-source through confirmation dialog with source
locator displayed and "Remove all from source" button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pre-check all skills for agent usage before deleting any in deleteBySource
to prevent partial/failed deletions
- Delete (rotate to empty) the skill-pat:<skillId> secret when a skill is
deleted to prevent orphaned PAT secrets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add optional authToken to skill import for GitHub private repos
- Store PAT as encrypted company secret (skill-pat:{skillId})
- Thread auth token through ghFetch, fetchText, fetchJson, and all GitHub resolution functions
- Add PATCH /companies/:companyId/skills/:skillId/auth for managing PAT per skill
- Add DELETE /companies/:companyId/skills/by-source for bulk deleting skills from a repo
- Preserve sourceAuthSecretId across skill re-imports/updates
- UI: Add PAT input field in import form for GitHub URLs
- UI: Add SkillAuthSection with ShieldCheck icon for viewing/updating/removing PAT
- UI: Add trash icon next to source label for delete-by-source
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update runner name and GHCR image path in build workflow to reflect
the repo transfer from cpfarhood/paperclip to farhoodliquor/paperclip.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Required by the custom minimax provider in opencode.json which uses
@ai-sdk/anthropic to hit minimax's Anthropic-compatible API endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>