skillfish 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import pc from 'picocolors';
9
9
  import { trackInstall } from '../telemetry.js';
10
10
  import { isValidPath, parseFrontmatter, deriveSkillName, toTitleCase, truncate, batchMap, createJsonOutput, isInputTTY, isTTY, } from '../utils.js';
11
11
  import { getDetectedAgents, AGENT_CONFIGS } from '../lib/agents.js';
12
- import { findAllSkillMdFiles, fetchSkillMdContent, SKILL_FILENAME, RateLimitError, RepoNotFoundError, NetworkError, GitHubApiError, } from '../lib/github.js';
12
+ import { findAllSkillMdFiles, fetchSkillMdContent, fetchDefaultBranch, SKILL_FILENAME, RateLimitError, RepoNotFoundError, NetworkError, GitHubApiError, } from '../lib/github.js';
13
13
  import { installSkill } from '../lib/installer.js';
14
14
  import { EXIT_CODES, isValidName } from '../lib/constants.js';
15
15
  // === Command Definition ===
@@ -113,9 +113,21 @@ Examples:
113
113
  exitWithError('Invalid repository format. Use: owner/repo', EXIT_CODES.INVALID_ARGS);
114
114
  }
115
115
  // 1. Discover or select skills
116
- const discoveryResult = explicitPath
117
- ? { paths: [explicitPath], branch: undefined }
118
- : await discoverSkillPaths(owner, repo, installAll, jsonMode, jsonOutput, skillNameArg);
116
+ let discoveryResult;
117
+ if (explicitPath) {
118
+ // For explicit paths, we still need to fetch the default branch for degit
119
+ try {
120
+ const branch = await fetchDefaultBranch(owner, repo);
121
+ discoveryResult = { paths: [explicitPath], branch };
122
+ }
123
+ catch (err) {
124
+ // If we can't fetch the branch, let degit try its own detection
125
+ discoveryResult = { paths: [explicitPath], branch: undefined };
126
+ }
127
+ }
128
+ else {
129
+ discoveryResult = await discoverSkillPaths(owner, repo, installAll, jsonMode, jsonOutput, skillNameArg);
130
+ }
119
131
  if (!discoveryResult || discoveryResult.paths.length === 0) {
120
132
  if (jsonMode) {
121
133
  outputJsonAndExit(EXIT_CODES.NOT_FOUND);
@@ -21,7 +21,7 @@ export interface InstallResult {
21
21
  export interface InstallOptions {
22
22
  force: boolean;
23
23
  baseDir: string;
24
- /** Branch to clone from. If not specified, degit will use default branch detection. */
24
+ /** Branch to clone from. If not specified, giget will use the repository's default branch. */
25
25
  branch?: string;
26
26
  }
27
27
  export interface CopyResult {
@@ -6,18 +6,18 @@ import { existsSync, mkdirSync, cpSync, rmSync, lstatSync, readdirSync, } from '
6
6
  import { homedir } from 'os';
7
7
  import { join } from 'path';
8
8
  import { randomUUID } from 'crypto';
9
- import degit from 'degit';
9
+ import { downloadTemplate } from 'giget';
10
10
  import { SKILL_FILENAME } from './github.js';
11
11
  /**
12
- * Validates a branch name for safe use in degit paths.
12
+ * Validates a branch name for safe use in giget source strings.
13
13
  * Git branch names can contain alphanumerics, dots, hyphens, underscores, and slashes.
14
- * We explicitly reject '#' which is used as a delimiter in degit syntax.
14
+ * We explicitly reject '#' which is used as a delimiter in giget syntax.
15
15
  */
16
16
  function isValidBranchName(branch) {
17
17
  if (!branch || branch.length > 255)
18
18
  return false;
19
19
  // Allow alphanumerics, dots, hyphens, underscores, and slashes (for feature branches)
20
- // Reject anything else, especially '#' which would break degit parsing
20
+ // Reject anything else, especially '#' which would break giget parsing
21
21
  return /^[\w./-]+$/.test(branch) && !branch.includes('#');
22
22
  }
23
23
  // === Error Types ===
@@ -102,20 +102,24 @@ export async function installSkill(owner, repo, skillPath, skillName, agents, op
102
102
  const tmpDir = join(homedir(), '.cache', 'skillfish', `${owner}-${repo}-${randomUUID()}`);
103
103
  mkdirSync(tmpDir, { recursive: true, mode: 0o700 });
104
104
  try {
105
- // Download skill
106
- // Build degit path: owner/repo[/subpath][#branch]
105
+ // Download skill using giget (tarball-based, works reliably on all repo sizes)
106
+ // Build giget source: github:owner/repo[/subpath][#branch]
107
107
  const downloadPath = skillPath === SKILL_FILENAME ? '' : skillPath;
108
- let degitPath = downloadPath ? `${owner}/${repo}/${downloadPath}` : `${owner}/${repo}`;
108
+ let source = downloadPath
109
+ ? `github:${owner}/${repo}/${downloadPath}`
110
+ : `github:${owner}/${repo}`;
109
111
  // Append branch if specified (critical for repos with non-standard default branches like 'canary')
110
112
  // Validate branch name to prevent injection attacks via malformed branch names
111
113
  if (branch) {
112
114
  if (!isValidBranchName(branch)) {
113
115
  throw new Error(`Invalid branch name: ${branch}`);
114
116
  }
115
- degitPath = `${degitPath}#${branch}`;
117
+ source = `${source}#${branch}`;
116
118
  }
117
- const emitter = degit(degitPath, { cache: false, force: true });
118
- await emitter.clone(tmpDir);
119
+ await downloadTemplate(source, {
120
+ dir: tmpDir,
121
+ forceClean: true,
122
+ });
119
123
  // Validate download
120
124
  const skillMdPath = join(tmpDir, SKILL_FILENAME);
121
125
  if (!existsSync(skillMdPath)) {
@@ -154,11 +158,8 @@ export async function installSkill(owner, repo, skillPath, skillName, agents, op
154
158
  }
155
159
  else {
156
160
  const errMsg = err instanceof Error ? err.message : String(err);
157
- // Provide more helpful error messages for common degit failures
158
- if (errMsg.includes('could not find commit hash for HEAD')) {
159
- result.failureReason = `Could not clone repository. The branch or path may not exist, or there may be a network issue. Tried: ${owner}/${repo}${skillPath !== SKILL_FILENAME ? `/${skillPath}` : ''}`;
160
- }
161
- else if (errMsg.includes('404')) {
161
+ // Provide more helpful error messages for common failures
162
+ if (errMsg.includes('404') || errMsg.includes('Not Found')) {
162
163
  result.failureReason = `Repository or path not found: ${owner}/${repo}${skillPath !== SKILL_FILENAME ? `/${skillPath}` : ''}`;
163
164
  }
164
165
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillfish",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Install AI agent skills from GitHub with a single command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,7 +57,7 @@
57
57
  "dependencies": {
58
58
  "@clack/prompts": "^0.11.0",
59
59
  "commander": "^14.0.2",
60
- "degit": "^2.8.4",
60
+ "giget": "^3.1.1",
61
61
  "picocolors": "^1.1.1"
62
62
  },
63
63
  "devDependencies": {