feat: single source of truth for skills marketplace#15
Conversation
- Consolidate all skills data to marketplace/skills.json (15,062 skills) - Add marketplace/sources.json for curated repository sources (31 repos) - Add skillkit publish command for user skill submissions - Add auto-publish prompt to skillkit init - Add AI skill caching with localStorage (7-day TTL) - Add Submit to Marketplace button for AI-generated skills - Remove duplicate skills.json files (docs, core) - Remove legacy src/tui/ folder (replaced by packages/tui) - Update all imports to use single source files
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdds a PublishCommand to the CLI, centralizes marketplace sources into marketplace/sources.json, introduces client-side AI skill caching and submit UI in SkillGenerator, removes local skills manifests, and deletes the entire TUI layer and its related hooks/components. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as PublishCommand
participant FS as File System
participant Git as Git Repo
participant Browser
participant GitHub
User->>CLI: run `skillkit publish` (with options)
CLI->>FS: locate SKILL.md
FS-->>CLI: return frontmatter (name, description, tags)
CLI->>Git: read remote origin
Git-->>CLI: return owner/repo
CLI->>CLI: build skill JSON & issue body
CLI->>Browser: open pre-filled GitHub issue URL
Browser-->>GitHub: navigate to new issue form (pre-filled)
User->>GitHub: submit issue
sequenceDiagram
participant User
participant UI as SkillGenerator
participant Cache as localStorage
participant AI as AI Service
participant Browser
participant GitHub
User->>UI: click "Generate with AI"
UI->>Cache: normalize query & check cache (versioned)
alt cache hit
Cache-->>UI: return cached skill
UI->>UI: mark isFromCache, render generated view
else cache miss
UI->>AI: request generation
AI-->>UI: return generated skill
UI->>Cache: store generated skill
UI->>UI: render generated view
end
User->>UI: click "Submit"
UI->>UI: create issue body & URL
UI->>Browser: open pre-filled GitHub issue URL
Browser-->>GitHub: navigate to new issue form
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@docs/skillkit/components/SkillSubmitForm.tsx`:
- Around line 227-231: The checklist in SkillSubmitForm.tsx contains a
duplicated item ("Add to `marketplace/skills.json`"); edit the generated issue
body in the component (where the checklist string is defined/constructed in
SkillSubmitForm) and remove the redundant checklist line so only one "Add to
`marketplace/skills.json`" remains (or consolidate to a single, clear checklist
entry), ensuring the checklist items list/array or template text that renders
lines 229–230 is updated accordingly.
- Around line 254-258: In SkillSubmitForm.tsx the checklist rendered in the
component contains a duplicated item ("Add to `marketplace/skills.json`" / "Add
JSON entry to `marketplace/skills.json`"); locate the checklist array or JSX
fragment inside the SkillSubmitForm component that builds the three checkbox
lines and remove the redundant entry so each checklist line is unique (keep one
canonical wording for the marketplace/skills.json item and remove the
duplicate), then run a quick build/format to ensure JSX remains valid.
In `@marketplace/sources.json`:
- Around line 1-5: The $schema reference in sources.json points to a missing
file ("./sources-schema.json"); either add the referenced JSON Schema file to
the repo with the expected schema for sources.json, or remove/replace the
"$schema" property in sources.json so it no longer references a non-existent
"./sources-schema.json"; update the "$schema" key in the sources.json content to
a valid path or delete that property and commit the schema file if you choose to
create it.
In `@packages/cli/src/commands/publish.ts`:
- Around line 94-99: The Windows branch for opening URLs uses 'start' which
treats a quoted URL as a title; update the try block that builds openCmd (the
openCmd variable and the execSync call) so the Windows case uses 'cmd /c start
""' (i.e., include an empty title) and then execute with the URL quoted as
before (e.g., execSync(`${openCmd} "${issueUrl}"`, { stdio: 'ignore' })),
ensuring the code path referencing openCmd in publish.ts reliably opens the
browser on win32.
- Around line 145-177: parseFrontmatter currently only matches Unix newlines and
ignores multiline YAML tag lists; update the frontmatter regex in
parseFrontmatter to use \r?\n (e.g., /^---\r?\n([\s\S]*?)\r?\n---/) so CRLF
files are matched, and change line parsing to split only on the first colon (use
split with a limit) to preserve values containing colons. For the 'tags'
handling in parseFrontmatter/frontmatter.tags, detect both inline arrays
(existing logic) and multiline lists by treating an empty or dash-prefixed value
as the start of a block: when you encounter "tags:" with no inline array,
collect subsequent lines until you hit a line that looks like a new key
(contains ':' at top-level) or end, strip leading '-' and quotes, trim each
entry, and assign the resulting array to frontmatter.tags.
- Around line 62-69: The skill ID in the skillEntry object is built directly
from skillName and may contain spaces/capital letters; change the id generation
to use a slugified version of skillName (lowercase, replace spaces and invalid
chars with hyphens) while keeping the display name as
this.formatName(skillName). Update the code that builds skillEntry.id
(referencing skillEntry, id, skillName, repoInfo) to call a slugify utility (or
implement simple slug logic inline) so IDs are consistent and URL-safe; leave
description, source, tags (this.inferTags, frontmatter) unchanged.
🧹 Nitpick comments (2)
packages/core/src/marketplace/types.ts (1)
126-140: Consider moving the import to the top of the file.While placing the import mid-file works, it's unconventional and could reduce readability. Imports are typically grouped at the top of the file.
♻️ Suggested refactor
Move line 126 to the top of the file, after the comment block:
/** * Skill Marketplace Types * ... */ import sourcesData from '../../../../marketplace/sources.json' with { type: 'json' }; /** * Skill source repository */ export interface SkillSource { // ... }docs/skillkit/components/SkillGenerator.tsx (1)
97-158: Prevent unbounded localStorage growth.Expired entries are never pruned, so the cache can only grow over time. Consider pruning expired entries and capping total entries to avoid quota exhaustion and silent cache failures.
♻️ Proposed refactor (prune + cap)
const CACHE_KEY = 'skillkit_ai_cache'; const CACHE_VERSION = 1; +const MAX_CACHE_ENTRIES = 50; @@ function getSkillCache(): SkillCache { try { const cached = localStorage.getItem(CACHE_KEY); if (cached) { const parsed = JSON.parse(cached) as SkillCache; if (parsed.version === CACHE_VERSION) { + const weekMs = 7 * 24 * 60 * 60 * 1000; + const now = Date.now(); + let mutated = false; + for (const [key, value] of Object.entries(parsed.skills)) { + if (!value?.timestamp || now - value.timestamp >= weekMs) { + delete parsed.skills[key]; + mutated = true; + } + } + if (mutated) { + localStorage.setItem(CACHE_KEY, JSON.stringify(parsed)); + } return parsed; } } } catch { @@ function setCachedSkill(query: string, skill: AgentSkill): void { try { const cache = getSkillCache(); const key = normalizeQuery(query); cache.skills[key] = { skill, query: key, timestamp: Date.now(), }; + const entries = Object.entries(cache.skills); + if (entries.length > MAX_CACHE_ENTRIES) { + entries + .sort((a, b) => a[1].timestamp - b[1].timestamp) + .slice(0, entries.length - MAX_CACHE_ENTRIES) + .forEach(([k]) => delete cache.skills[k]); + } localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); } catch { // Ignore storage errors (quota exceeded, etc.) } }
| { | ||
| "$schema": "./sources-schema.json", | ||
| "version": 1, | ||
| "updatedAt": "2026-01-26", | ||
| "description": "Curated skill repository sources - THE SINGLE SOURCE OF TRUTH", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if sources-schema.json exists in the marketplace directory
fd -t f 'sources-schema.json' marketplace/
# Also check if it exists anywhere in the repository
echo "---"
fd -t f 'sources-schema.json'
# Verify the sources.json file exists
echo "---"
ls -la marketplace/sources.json 2>&1 || echo "sources.json not found"Repository: rohitg00/skillkit
Length of output: 142
Create the referenced schema file or remove the invalid schema reference.
The file references ./sources-schema.json via the $schema field, but this schema file does not exist in the repository. Either create the schema file or remove the schema reference.
🤖 Prompt for AI Agents
In `@marketplace/sources.json` around lines 1 - 5, The $schema reference in
sources.json points to a missing file ("./sources-schema.json"); either add the
referenced JSON Schema file to the repo with the expected schema for
sources.json, or remove/replace the "$schema" property in sources.json so it no
longer references a non-existent "./sources-schema.json"; update the "$schema"
key in the sources.json content to a valid path or delete that property and
commit the schema file if you choose to create it.
- Remove duplicate checklist items in SkillSubmitForm.tsx - Add missing sources-schema.json file - Add slugify method for URL-safe skill IDs - Fix Windows browser open command (empty title param) - Fix frontmatter parsing for CRLF and multiline tags
packages/cli/src/commands/publish.ts
Outdated
| if (value.startsWith('[')) { | ||
| frontmatter.tags = value | ||
| .slice(1, -1) | ||
| .split(',') | ||
| .map(t => t.trim().replace(/^["']|["']$/g, '')); |
There was a problem hiding this comment.
🟡 YAML array parsing truncates last tag when closing bracket is missing
The frontmatter parser for tags assumes the array is well-formed, causing incorrect parsing when the closing bracket is missing.
Click to expand
Issue
In parseFrontmatter(), when parsing tags in YAML array format, the code uses slice(1, -1) which assumes the value both starts with [ and ends with ]:
if (value.startsWith('[')) {
frontmatter.tags = value
.slice(1, -1) // Removes first and last char
.split(',')
.map(t => t.trim().replace(/^["']|["']$/g, ''));
}If the closing bracket is missing (e.g., tags: [react, typescript), slice(1, -1) will truncate the last character of the last tag, resulting in ['react', 'typescrip'] instead of the expected error handling or ['react', 'typescript'].
Impact
Users with malformed SKILL.md frontmatter may have their last tag silently truncated when publishing skills, leading to incorrect tag categorization in the marketplace.
Recommendation: Add validation to check that the value ends with ] before using slice(1, -1), or use a regex like /^\[(.*)\]$/ to properly extract the array contents.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@packages/cli/src/commands/publish.ts`:
- Around line 108-111: The fallback currently prints a truncated URL
(issueUrl.slice(0, 200) + '...') which prevents users in headless environments
from accessing the full publish link; update the fallback printing in publish.ts
to output the complete URL instead (use console.log(chalk.cyan(issueUrl)) or
equivalent) so the full issueUrl is visible when auto-open fails, keeping the
other log lines unchanged.
- Around line 63-66: Guard against an empty slug from this.slugify(skillName) by
validating skillSlug after it's computed (in publish command around the
skillSlug and skillEntry creation), and if it's empty replace it with a
deterministic fallback (e.g., a sanitized this.formatName(skillName) or a
generated fallback like `skill-<repo>-<shortid>`), then use that non-empty value
when building skillEntry.id; alternatively, throw a clear error prompting for a
valid skillName. Ensure the check references the skillSlug variable and uses
this.slugify / this.formatName to produce the fallback.
- Around line 221-225: The current GitHub URL parsing regex on the remote
variable stops at the first dot so repo names with dots (e.g., owner/repo.name)
are truncated; update the parsing logic (the regex and/or post-processing around
the match variable) to capture the full repo name including dots and strip an
optional .git suffix — for example, change the pattern to allow dots in the repo
capture group or capture up to an optional ".git" and then return { owner:
match[1], repo: cleanedRepo } where cleanedRepo is the repo capture with any
trailing ".git" removed.
🧹 Nitpick comments (1)
marketplace/sources-schema.json (1)
5-47: Consider locking down unknown fields.If this schema is used for validation, adding
additionalProperties: falseat the root and per-item prevents silent typos and drift.♻️ Proposed hardening
{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "SkillKit Sources", "description": "Schema for curated skill repository sources", "type": "object", + "additionalProperties": false, "required": ["version", "sources"], "properties": { @@ "sources": { "type": "array", "description": "List of curated skill sources", "items": { "type": "object", + "additionalProperties": false, "required": ["source", "name"], "properties": {
- Add TTY check to skip prompts in CI/non-interactive environments - Guard against empty slug IDs with validation - Print full URL in fallback (not truncated) - Fix repo name parsing for names containing dots - Add cache pruning and max entries limit (50) for localStorage
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@docs/skillkit/components/SkillGenerator.tsx`:
- Around line 256-294: The function submitToMarketplace contains an unused
variable slug and risks creating a URL that exceeds browser/GitHub length limits
because skillContent is truncated at 4000 chars; remove the unused slug
computation (slug) and reduce the slice/truncation limit for skillContent in
body (e.g., change from 4000 to ~2000) so encodedBody remains well under typical
URL size limits, update the truncation indicator accordingly, and keep the rest
of the logic (title, encodedBody, window.open) unchanged.
In `@packages/cli/src/commands/init.ts`:
- Around line 86-119: Replace direct process stream usage in
checkAndPromptPublish and prompt with the Clipanion-provided context streams:
use this.context.stdin.isTTY (instead of process.stdin.isTTY) when checking
interactivity in checkAndPromptPublish, and when creating the readline interface
in prompt use createInterface({ input: this.context.stdin, output:
this.context.stdout }) so injected/test streams are respected; update any
references to process.stdout/process.stdin in these methods to
this.context.stdout/this.context.stdin and keep the existing prompt/answer logic
unchanged.
| function submitToMarketplace(): void { | ||
| if (!generatedSkill) return; | ||
|
|
||
| const slug = generatedSkill.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); | ||
| const skillContent = generateSkillMd(generatedSkill); | ||
|
|
||
| const body = `## 📤 AI-Generated Skill: ${generatedSkill.title} | ||
|
|
||
| ### Skill Details | ||
| - **Name:** ${generatedSkill.name} | ||
| - **Description:** ${generatedSkill.description} | ||
| - **Tags:** ${generatedSkill.tags.join(', ')} | ||
| - **Version:** ${generatedSkill.version} | ||
|
|
||
| ### SKILL.md Content | ||
|
|
||
| <details> | ||
| <summary>Click to expand</summary> | ||
|
|
||
| \`\`\`markdown | ||
| ${skillContent.slice(0, 4000)}${skillContent.length > 4000 ? '\n...(truncated)' : ''} | ||
| \`\`\` | ||
|
|
||
| </details> | ||
|
|
||
| ### Checklist for Maintainer | ||
| - [ ] Review skill content for quality | ||
| - [ ] Add to \`marketplace/skills.json\` | ||
|
|
||
| --- | ||
| **Generated via:** AI Skill Generator | ||
| **Search query:** "${input}"`; | ||
|
|
||
| const title = encodeURIComponent(`[AI Skill] ${generatedSkill.title}`); | ||
| const encodedBody = encodeURIComponent(body); | ||
| const url = `https://github.com/rohitg00/skillkit/issues/new?title=${title}&body=${encodedBody}&labels=ai-generated,skill-submission`; | ||
|
|
||
| window.open(url, '_blank', 'noopener,noreferrer'); | ||
| } |
There was a problem hiding this comment.
Dead variable and URL length risk.
-
Dead code: The
slugvariable (line 259) is computed but never used in this function. -
URL length risk: After
encodeURIComponent, a 4000-char body can expand to ~12,000+ characters. Combined with title and labels, the URL may exceed browser/GitHub limits (~8KB), causing silent failures.
Suggested fix
function submitToMarketplace(): void {
if (!generatedSkill) return;
- const slug = generatedSkill.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
const skillContent = generateSkillMd(generatedSkill);
const body = `## 📤 AI-Generated Skill: ${generatedSkill.title}
### Skill Details
- **Name:** ${generatedSkill.name}
- **Description:** ${generatedSkill.description}
- **Tags:** ${generatedSkill.tags.join(', ')}
- **Version:** ${generatedSkill.version}
### SKILL.md Content
<details>
<summary>Click to expand</summary>
\`\`\`markdown
-${skillContent.slice(0, 4000)}${skillContent.length > 4000 ? '\n...(truncated)' : ''}
+${skillContent.slice(0, 2000)}${skillContent.length > 2000 ? '\n...(truncated)' : ''}
\`\`\`
</details>Consider reducing the truncation limit to ~2000 characters to stay safely within URL encoding limits.
🤖 Prompt for AI Agents
In `@docs/skillkit/components/SkillGenerator.tsx` around lines 256 - 294, The
function submitToMarketplace contains an unused variable slug and risks creating
a URL that exceeds browser/GitHub length limits because skillContent is
truncated at 4000 chars; remove the unused slug computation (slug) and reduce
the slice/truncation limit for skillContent in body (e.g., change from 4000 to
~2000) so encodedBody remains well under typical URL size limits, update the
truncation indicator accordingly, and keep the rest of the logic (title,
encodedBody, window.open) unchanged.
| private async checkAndPromptPublish(): Promise<void> { | ||
| // Skip in non-interactive environments (CI, piped input, etc.) | ||
| if (!process.stdin.isTTY) return; | ||
|
|
||
| const skillMdLocations = [ | ||
| join(process.cwd(), 'SKILL.md'), | ||
| join(process.cwd(), 'skills', 'SKILL.md'), | ||
| join(process.cwd(), '.claude', 'skills', 'SKILL.md'), | ||
| join(process.cwd(), '.cursor', 'skills', 'SKILL.md'), | ||
| ]; | ||
|
|
||
| const foundSkillMd = skillMdLocations.find(loc => existsSync(loc)); | ||
| if (!foundSkillMd) return; | ||
|
|
||
| console.log(); | ||
| console.log(chalk.yellow('Found SKILL.md in your project!')); | ||
| console.log(chalk.dim('Would you like to publish it to the SkillKit marketplace?')); | ||
| console.log(); | ||
|
|
||
| const answer = await this.prompt('Publish skill? [y/N]: '); | ||
|
|
||
| if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { | ||
| console.log(); | ||
| console.log(chalk.cyan('To publish your skill, run:')); | ||
| console.log(chalk.white(` skillkit publish ${foundSkillMd}`)); | ||
| console.log(); | ||
| } | ||
| } | ||
|
|
||
| private prompt(question: string): Promise<string> { | ||
| const rl = createInterface({ | ||
| input: process.stdin, | ||
| output: process.stdout, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the full init.ts file to understand the class structure
wc -l packages/cli/src/commands/init.tsRepository: rohitg00/skillkit
Length of output: 98
🏁 Script executed:
# Look at the broader context of this file
head -100 packages/cli/src/commands/init.ts | cat -nRepository: rohitg00/skillkit
Length of output: 4048
🏁 Script executed:
# Check if Clipanion is imported and used
rg "clipanion|Command|this\.context" packages/cli/src/commands/init.ts -A 2 -B 2Repository: rohitg00/skillkit
Length of output: 545
🏁 Script executed:
# Check package.json to see if Clipanion is a dependency
cat packages/cli/package.json | grep -A 5 -B 5 clipanionRepository: rohitg00/skillkit
Length of output: 349
🏁 Script executed:
# Search for other uses of this.context in the codebase
rg "this\.context" packages/cli/src --type ts -A 2 -B 2Repository: rohitg00/skillkit
Length of output: 50373
🏁 Script executed:
# Check if there are other prompt/stdin usages in the codebase
rg "createInterface|process\.stdin|this\.context\.stdin" packages/cli/src --type tsRepository: rohitg00/skillkit
Length of output: 344
🏁 Script executed:
# Look at the rest of the init.ts file to see the full prompt method
tail -30 packages/cli/src/commands/init.ts | cat -nRepository: rohitg00/skillkit
Length of output: 1095
🏁 Script executed:
# Search for Clipanion Command context types in node_modules or check imports
fd "clipanion" packages/cli/node_modules -o packages/cli/package-lock.json 2>/dev/null | head -5Repository: rohitg00/skillkit
Length of output: 43
🏁 Script executed:
# Check if there's a tsconfig or type definitions we can examine
find packages/cli -name "*.d.ts" -o -name "tsconfig.json" | head -5Repository: rohitg00/skillkit
Length of output: 87
🏁 Script executed:
# Search for Clipanion types
find packages/cli -name "*.d.ts" -type f | xargs grep -l "context\|stdin\|stdout" 2>/dev/null | head -5Repository: rohitg00/skillkit
Length of output: 43
🏁 Script executed:
# Check if there are other prompt implementations or context.stdin usage
rg "context\.stdin|context\.stdout" packages/cli/src --type ts | head -20Repository: rohitg00/skillkit
Length of output: 2258
🏁 Script executed:
# Look for any import of Clipanion types to understand the context type
rg "from 'clipanion'" packages/cli/src --type ts -A 5 -B 5Repository: rohitg00/skillkit
Length of output: 19902
🏁 Script executed:
# Check Clipanion version and look for documentation or type hints
cat packages/cli/package.json | grep -E "clipanion|version"Repository: rohitg00/skillkit
Length of output: 114
🏁 Script executed:
# Search web documentation for Clipanion context API
# Since we need to verify context properties, let's check if any test files use context
find packages/cli -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "context" 2>/dev/nullRepository: rohitg00/skillkit
Length of output: 45
🌐 Web query:
Clipanion 4.0 Command context stdin stdout properties API
💡 Result:
In Clipanion v4, commands receive a context object on this.context whose base shape is BaseContext, which includes these stdio properties:
import type {Readable, Writable} from 'stream';
type BaseContext = {
env: Record<string, string | undefined>;
stdin: Readable; // read input
stdout: Writable; // write normal output
stderr: Writable; // write error output
colorDepth: number;
};[1][2]
When you create/run a CLI, Cli.defaultContext is the default value you typically spread into your own context; it’s populated from the current Node process stdio (stdin/stdout/stderr) plus env and colorDepth. [2]
🏁 Script executed:
# Look for context usage with stdin specifically in the entire codebase
rg "context\.stdin" packages/ --type tsRepository: rohitg00/skillkit
Length of output: 43
🏁 Script executed:
# Check if there are any test files that might show how context is used
find packages -name "*.test.ts" -o -name "*.spec.ts" | head -10Repository: rohitg00/skillkit
Length of output: 560
🏁 Script executed:
# Look at the actual Clipanion package to see what context provides
# Check node_modules or type files
find packages/cli/node_modules/clipanion -name "*.d.ts" -o -name "*.ts" 2>/dev/null | head -10Repository: rohitg00/skillkit
Length of output: 43
🏁 Script executed:
# Check Clipanion and Node.js type definitions for isTTY property
# Look at how other parts of the codebase might use streams with isTTY
rg "isTTY" packages/ --type tsRepository: rohitg00/skillkit
Length of output: 132
🏁 Script executed:
# Check what types Readable and Writable are and if they have isTTY
# Also look for any existing context usage with type checks
rg "\.stdin|\.stdout|\.stderr" packages/cli/src/commands --type ts -B 2 -A 2 | head -50Repository: rohitg00/skillkit
Length of output: 3254
🏁 Script executed:
# Verify the suggested fix by looking at what NodeJS.ReadStream and NodeJS.WriteStream are
python3 << 'EOF'
import json
# Verify the logic: isTTY should be checked on both stdin and stdout
# stdin is a Readable, stdout is a Writable
# Both should have isTTY property if they're connected to a terminal
print("In Node.js:")
print("- process.stdin is a Readable with optional isTTY property")
print("- process.stdout is a Writable with optional isTTY property")
print("- Both are undefined if not a TTY")
print()
print("The suggested fix checks BOTH stdin.isTTY AND stdout.isTTY")
print("This is more robust than checking only stdin.isTTY")
print()
print("For tests/embedded usage:")
print("- Custom streams can be passed via context")
print("- If they're not real TTY streams, isTTY will be false/undefined")
print("- This would properly skip the interactive prompt")
EOFRepository: rohitg00/skillkit
Length of output: 147
Use Clipanion context streams instead of process.* to avoid hangs in tests and embedded environments.
The code directly accesses process.stdin and process.stdout instead of using this.context.stdin and this.context.stdout provided by Clipanion. This bypasses custom streams that may be injected during testing or embedded usage, potentially causing hangs or missed prompts in non-interactive environments. All other commands in the codebase already follow this pattern.
Suggested fix
- if (!process.stdin.isTTY) return;
+ const stdin = this.context.stdin as NodeJS.ReadStream;
+ const stdout = this.context.stdout as NodeJS.WriteStream;
+ if (!stdin.isTTY || !stdout.isTTY) return;
@@
- const rl = createInterface({
- input: process.stdin,
- output: process.stdout,
- });
+ const rl = createInterface({
+ input: stdin,
+ output: stdout,
+ });🤖 Prompt for AI Agents
In `@packages/cli/src/commands/init.ts` around lines 86 - 119, Replace direct
process stream usage in checkAndPromptPublish and prompt with the
Clipanion-provided context streams: use this.context.stdin.isTTY (instead of
process.stdin.isTTY) when checking interactivity in checkAndPromptPublish, and
when creating the readline interface in prompt use createInterface({ input:
this.context.stdin, output: this.context.stdout }) so injected/test streams are
respected; update any references to process.stdout/process.stdin in these
methods to this.context.stdout/this.context.stdin and keep the existing
prompt/answer logic unchanged.
Summary
marketplace/skills.json(15,062 skills)marketplace/sources.jsonfor curated repository sources (31 repos)skillkit publishcommand for user skill submissionsskillkit initskills.jsonfiles (docs, core)src/tui/folder (replaced bypackages/tui)Test plan
pnpm buildpassesskillkit publishcommand worksmarketplace/skills.jsonSummary by CodeRabbit
New Features
Removed Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.