From 05c7a3dc673fd8b77f9a79dd0eab8fa0f2575c8f Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Wed, 14 Jan 2026 15:56:04 -0800 Subject: [PATCH 1/2] feat: add skill for hackmd-cli --- hackmd-cli.skill | Bin 0 -> 1704 bytes hackmd-cli/SKILL.md | 155 ++++++++++++++++++++++++++++++++++++++ package.json | 4 +- scripts/package-skill.mjs | 150 ++++++++++++++++++++++++++++++++++++ scripts/watch-skill.mjs | 23 ++++++ 5 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 hackmd-cli.skill create mode 100644 hackmd-cli/SKILL.md create mode 100644 scripts/package-skill.mjs create mode 100644 scripts/watch-skill.mjs diff --git a/hackmd-cli.skill b/hackmd-cli.skill new file mode 100644 index 0000000000000000000000000000000000000000..be0cb0191c54b8a037fc9d5812cdb18c3ce63115 GIT binary patch literal 1704 zcmZ{ldoPh^A_fomMEgzSJ)LUi1{-t*^cGI(gjBh*oi`&b9pnD75ZDDFDg5HeByFZ z3Hfod@h?7@6=1CcML}QY^)a(X#eu9>_W6^c?!oSojGOQaJxrHwa+X1Ix}~wIPP|Nv zG&ZkUId9Zb>0C&U8}zD?FSys2?j{sV>AgMWtvn%IP%#W(NgokwrF_BK@^cptYN#K6 z<4l{B#5Pxxkm)kd?il;IhbS(p%wsMTEFeG17CNmoI*Cw-u!NvWh-lk@T?8jj0QDtJ zh`{DSUk8mgRoXgW4!O$P&g52hVvBwK@htYy?TRkk4Zsg+t0sKTy!|B{z8E5VyT7#B zQPktiGDlhGJq6Ktb1+&v+*90E;d^nIA~j9g=`1g>u^EWQ3S)E-r)Ej`p%#5i}PKL(*Ctbw$X zxD)fH3O6WccmihH$LiQ-znkBhSsDwqCsduMlvE_>DfZjW=AU|pY_s#Qk@WAs)L;F) z5Ctk*n}UL}ON-PWdbsoi!UFI^DVw_EmVB2`dA%`Ks{4$UY=%pu-Qd{P@C>6-j!Tdd&=D*~!GBqx4A5+(y#n;Wd0e;*!Yi42NAtEqVgn+;ebSN zf5b6lb*GD}W~94!!lk6+cVlNfe6?T`s;^!{VoCg_W(Lysh2vv-F{Y-?rp}A<^i($b z_nP)P!tT&-e)}1drd=UaPDcLBZM|~V*lbP90Vt)fwgg2V)}kpy-eB;f?_Tji$RB%~ zF_)HWrI=O}j}7GsZ_peCLRz-Yh10jSTRz*>Lo;s8UsDX{yVXch%n{gVIWvTMxXzjp zG*P02KEf44TLr4>`s$M1XQw|ogQe3KFq7V)%CVw0g1t{0)uOfEmwANEmvx_(JED0X zgwtl|!ZdRh%|@=1G`fQL8O3WI5z#f%$c3%r0_$QjY+A!9b_{JQ5sgT)H_t;0nX9V} zsmS1p0&2%agjd?-xZu4E_zBFvZ*K-(?xu3 + +# Create note +hackmd-cli notes create --content='# Title' --title='My Note' +hackmd-cli notes create --readPermission=owner --writePermission=owner + +# Create from file/stdin +cat README.md | hackmd-cli notes create + +# Create with editor +hackmd-cli notes create -e + +# Update note +hackmd-cli notes update --noteId= --content='# New Content' + +# Delete note +hackmd-cli notes delete --noteId= +``` + +### Team Notes + +```bash +# List team notes +hackmd-cli team-notes --teamPath= + +# Create team note +hackmd-cli team-notes create --teamPath= --content='# Team Doc' + +# Update team note +hackmd-cli team-notes update --teamPath= --noteId= --content='# Updated' + +# Delete team note +hackmd-cli team-notes delete --teamPath= --noteId= +``` + +### Teams & History + +```bash +hackmd-cli teams # List accessible teams +hackmd-cli history # List browsing history +``` + +### Export + +```bash +hackmd-cli export --noteId= # Export note content to stdout +``` + +## Permissions + +Available permission values: + +| Permission Type | Values | +|----------------|--------| +| `--readPermission` | `owner`, `signed_in`, `guest` | +| `--writePermission` | `owner`, `signed_in`, `guest` | +| `--commentPermission` | `disabled`, `forbidden`, `owners`, `signed_in_users`, `everyone` | + +## Output Formats + +All list commands support: + +```bash +--output=json # JSON output +--output=yaml # YAML output +--output=csv # CSV output (or --csv) +--no-header # Hide table headers +--no-truncate # Don't truncate long values +--columns=id,title # Show specific columns +--filter=name=foo # Filter by property +--sort=title # Sort by property (prepend '-' for descending) +-x, --extended # Show additional columns +``` + +## Common Workflows + +### Sync local file to HackMD + +```bash +# Create new note from file +cat doc.md | hackmd-cli notes create --title="My Doc" + +# Update existing note from file +cat doc.md | hackmd-cli notes update --noteId= +``` + +### Export note to local file + +```bash +hackmd-cli export --noteId= > note.md +``` + +### List notes as JSON for scripting + +```bash +hackmd-cli notes --output=json | jq '.[] | .id' +``` + +### Find note by title + +```bash +hackmd-cli notes --filter=title=README +``` diff --git a/package.json b/package.json index 85c4f5a..7f4451e 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,9 @@ "test": "mocha --forbid-only \"test/**/*.test.ts\" --exclude \"test/smoke/**/*\"", "test:unit": "mocha --forbid-only \"test/**/*.test.ts\" --exclude \"test/integration/**/*\" --exclude \"test/smoke/**/*\"", "test:smoke": "pnpm run build && mocha --forbid-only \"test/smoke/**/*.test.ts\"", - "version": "oclif readme && git add README.md" + "version": "oclif readme && git add README.md", + "skill:package": "node scripts/package-skill.mjs", + "skill:watch": "node scripts/watch-skill.mjs" }, "types": "dist/index.d.ts" } diff --git a/scripts/package-skill.mjs b/scripts/package-skill.mjs new file mode 100644 index 0000000..593ecdc --- /dev/null +++ b/scripts/package-skill.mjs @@ -0,0 +1,150 @@ +#!/usr/bin/env node +/** + * Package the hackmd-cli skill into a .skill file (zip format) + * + * Usage: node scripts/package-skill.mjs + */ + +import {execSync} from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const SKILL_DIR = path.join(__dirname, '..', 'hackmd-cli'); +const SKILL_MD = path.join(SKILL_DIR, 'SKILL.md'); +const OUTPUT_FILE = path.join(__dirname, '..', 'hackmd-cli.skill'); + +function validateSkill() { + // Check SKILL.md exists + if (!fs.existsSync(SKILL_MD)) { + throw new Error('SKILL.md not found in hackmd-cli/'); + } + + const content = fs.readFileSync(SKILL_MD, 'utf8'); + + // Check frontmatter exists + if (!content.startsWith('---')) { + throw new Error('No YAML frontmatter found'); + } + + // Extract frontmatter + const match = content.match(/^---\n([\s\S]*?)\n---/); + if (!match) { + throw new Error('Invalid frontmatter format'); + } + + const frontmatter = match[1]; + + // Simple YAML parsing for required fields + const nameMatch = frontmatter.match(/^name:\s*(.+)$/m); + const descMatch = frontmatter.match(/^description:\s*(.+)$/m); + + if (!nameMatch) { + throw new Error("Missing 'name' in frontmatter"); + } + + if (!descMatch) { + throw new Error("Missing 'description' in frontmatter"); + } + + const name = nameMatch[1].trim(); + const description = descMatch[1].trim(); + + // Validate name format (hyphen-case) + if (!/^[a-z0-9-]+$/.test(name)) { + throw new Error(`Name '${name}' should be hyphen-case (lowercase letters, digits, and hyphens only)`); + } + + if (name.startsWith('-') || name.endsWith('-') || name.includes('--')) { + throw new Error(`Name '${name}' cannot start/end with hyphen or contain consecutive hyphens`); + } + + if (name.length > 64) { + throw new Error(`Name is too long (${name.length} characters). Maximum is 64 characters.`); + } + + // Validate description + if (description.includes('<') || description.includes('>')) { + throw new Error('Description cannot contain angle brackets (< or >)'); + } + + if (description.length > 1024) { + throw new Error(`Description is too long (${description.length} characters). Maximum is 1024 characters.`); + } + + return {description, name}; +} + +function packageSkill() { + console.log('šŸ“¦ Packaging skill: hackmd-cli\n'); + + // Validate + console.log('šŸ” Validating skill...'); + try { + validateSkill(); + console.log('āœ… Skill is valid!\n'); + } catch (error) { + console.error(`āŒ Validation failed: ${error.message}`); + throw error; + } + + // Remove existing .skill file + if (fs.existsSync(OUTPUT_FILE)) { + fs.unlinkSync(OUTPUT_FILE); + } + + // Create zip file using system zip command + try { + // Get all files in skill directory + const files = getAllFiles(SKILL_DIR); + + if (files.length === 0) { + throw new Error('No files found in skill directory'); + } + + // Use zip command from parent directory to maintain folder structure + const parentDir = path.dirname(SKILL_DIR); + const skillDirName = path.basename(SKILL_DIR); + + execSync(`zip -r "${OUTPUT_FILE}" "${skillDirName}"`, { + cwd: parentDir, + stdio: 'pipe', + }); + + // List what was added + for (const file of files) { + const relative = path.relative(parentDir, file); + console.log(` Added: ${relative}`); + } + + console.log(`\nāœ… Successfully packaged skill to: ${OUTPUT_FILE}`); + } catch (error) { + console.error(`āŒ Error creating .skill file: ${error.message}`); + throw error; + } +} + +function getAllFiles(dir) { + const files = []; + const entries = fs.readdirSync(dir, {withFileTypes: true}); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...getAllFiles(fullPath)); + } else { + files.push(fullPath); + } + } + + return files; +} + +try { + packageSkill(); +} catch (error) { + process.exitCode = 1; +} diff --git a/scripts/watch-skill.mjs b/scripts/watch-skill.mjs new file mode 100644 index 0000000..07a786f --- /dev/null +++ b/scripts/watch-skill.mjs @@ -0,0 +1,23 @@ +#!/usr/bin/env node +/** + * Watch for changes in hackmd-cli/ and auto-rebuild the .skill file + * + * Usage: node scripts/watch-skill.mjs + */ + +import {watch} from 'node:fs'; +import {execSync} from 'node:child_process'; + +const SKILL_DIR = './hackmd-cli'; + +console.log(`šŸ‘€ Watching ${SKILL_DIR} for changes...`); +console.log('Press Ctrl+C to stop\n'); + +watch(SKILL_DIR, {recursive: true}, (eventType, filename) => { + console.log(`Change detected: ${filename}`); + try { + execSync('npm run skill:package', {stdio: 'inherit'}); + } catch (error) { + console.error('Error packaging skill:', error.message); + } +}); From 8b7cb40c0e9a7856b76af86795469c3cb9489ca3 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Wed, 14 Jan 2026 15:58:34 -0800 Subject: [PATCH 2/2] style: linting issues --- scripts/package-skill.mjs | 2 +- scripts/watch-skill.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/package-skill.mjs b/scripts/package-skill.mjs index 593ecdc..41ea67e 100644 --- a/scripts/package-skill.mjs +++ b/scripts/package-skill.mjs @@ -145,6 +145,6 @@ function getAllFiles(dir) { try { packageSkill(); -} catch (error) { +} catch { process.exitCode = 1; } diff --git a/scripts/watch-skill.mjs b/scripts/watch-skill.mjs index 07a786f..a544f37 100644 --- a/scripts/watch-skill.mjs +++ b/scripts/watch-skill.mjs @@ -5,8 +5,8 @@ * Usage: node scripts/watch-skill.mjs */ -import {watch} from 'node:fs'; import {execSync} from 'node:child_process'; +import {watch} from 'node:fs'; const SKILL_DIR = './hackmd-cli';