diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..efc448d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,43 @@ +name: Publish Package + +on: + push: + tags: + - 'v*' + +permissions: + id-token: write # Required for OIDC + contents: read + +jobs: + publish: + name: Publish to npm + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.28.0 + + - name: Use Node.js 24 + uses: actions/setup-node@v4 + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build project + run: pnpm run build + + - name: Run tests + run: pnpm run test + + - name: Publish to npm + run: npm publish diff --git a/CHANGELOG.md b/CHANGELOG.md index 8361514..342fd3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,111 @@ All notable changes to HackMD-CLI will be documented in this file. +## 2.4.0 + +### Added + +- Smoke tests for built CLI binary verification +- Integration tests for login, notes, and whoami commands +- GitHub Actions workflow for automated unit and smoke test execution +- GitHub Actions workflow for automated package publishing to npm +- pnpm as the package manager + +### Changed + +- Migrated from Yarn to pnpm for dependency management +- Upgraded Node.js requirement from >=12.0.0 to >=24.0.0 +- Modernized ESLint configuration (migrated from `.eslintrc` to `eslint.config.mjs`) +- Upgraded ESLint dependencies and fixed linting issues +- Updated test infrastructure and configuration +- Improved CI/CD workflows + +### Fixed + +- Various linting issues across the codebase +- Build process to run before smoke tests +- Config test workflow configuration + +## 2.3.2 + +### Changed + +- Replaced oclif-completion with custom fork for autocomplete functionality + +## 2.3.1 + +### Added + +- Added autocomplete plugin support + +## 2.3.0 + +### Changed + +- Updated README documentation +- Added `/lib` to package files + +## 2.2.0 + +### Added + +- Support `--editor` option for team note creation + +### Changed + +- Migrated from npm to Yarn as package manager +- Upgraded oclif dependencies +- Upgraded various dependencies + +### Fixed + +- Removed dev-cli references +- Fixed lint errors and typing issues +- Added test helper improvements + +## 2.1.0 + +### Added + +- Support `$EDITOR` environment variable for creating notes with content + +### Changed + +- Upgraded Node.js requirement from >=10.0.0 to >=12.0.0 +- Upgraded oclif to version 18 +- Updated CI workflow to use Node.js 18 + +### Fixed + +- Fixed config file creation to ensure it's present with proper initialization +- Fixed tslint issues + +## 2.0.2 + +### Fixed + +- Fixed stdin unavailable read when accessing stdin.fd + +## 2.0.1 + +### Added + +- Added `export` command to export note content + +### Changed + +- Upgraded Node.js requirement to >=17.0.0 +- Replaced lodash with lodash.defaults package for smaller bundle size + +### Fixed + +- Fixed stdin stream not being consumed and being paused +- Removed teamPath from notes create command documentation + +### Removed + +- Removed unused inquirer dependency + ## 2.0.0 ### Added diff --git a/README.md b/README.md index 620683e..c43264b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ * [Commands](#commands) * [Configuration](#configuration) * [License](#license) +* [Changelog](#changelog) ## v2 notice @@ -25,12 +26,10 @@ ```sh-session $ npm install -g @hackmd/hackmd-cli -# or using pnpm -$ pnpm add -g @hackmd/hackmd-cli $ hackmd-cli COMMAND running command... $ hackmd-cli (--version|-v) -@hackmd/hackmd-cli/2.3.2 darwin-arm64 node-v18.14.2 +@hackmd/hackmd-cli/2.4.0 darwin-arm64 node-v24.13.0 $ hackmd-cli --help [COMMAND] USAGE $ hackmd-cli COMMAND @@ -168,7 +167,7 @@ EXAMPLES # A note to be exported ``` -_See code: [src/commands/export.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/export.ts)_ +_See code: [src/commands/export.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/export.ts)_ ## `hackmd-cli help [COMMANDS]` @@ -222,7 +221,7 @@ EXAMPLES BnC6gN0_TfStV2KKmPPXeg Welcome to your team's workspace null CLI-test ``` -_See code: [src/commands/history.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/history.ts)_ +_See code: [src/commands/history.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/history.ts)_ ## `hackmd-cli login` @@ -244,7 +243,7 @@ EXAMPLES Login successfully ``` -_See code: [src/commands/login.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/login.ts)_ +_See code: [src/commands/login.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/login.ts)_ ## `hackmd-cli logout` @@ -265,7 +264,7 @@ EXAMPLES You've logged out successfully ``` -_See code: [src/commands/logout.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/logout.ts)_ +_See code: [src/commands/logout.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/logout.ts)_ ## `hackmd-cli notes` @@ -299,7 +298,7 @@ EXAMPLES raUuSTetT5uQbqQfLnz9lA CLI test note gvfz2UB5THiKABQJQnLs6Q null ``` -_See code: [src/commands/notes/index.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/notes/index.ts)_ +_See code: [src/commands/notes/index.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/notes/index.ts)_ ## `hackmd-cli notes create` @@ -307,9 +306,9 @@ Create a note ``` USAGE - $ hackmd-cli notes create [-h] [--title ] [--content ] [--readPermission ] - [--writePermission ] [--commentPermission ] [-e] [--columns | -x] [--sort ] [--filter - ] [--output csv|json|yaml | | [--csv | --no-truncate]] [--no-header | ] + $ hackmd-cli notes create [--commentPermission ] [--content ] [-e] [-h] [--readPermission ] + [--title ] [--writePermission ] [--columns | -x] [--sort ] [--filter ] [--output + csv|json|yaml | | [--csv | --no-truncate]] [--no-header | ] FLAGS -e, --editor create note with $EDITOR @@ -369,7 +368,7 @@ Update note content ``` USAGE - $ hackmd-cli notes update [-h] [--noteId ] [--content ] + $ hackmd-cli notes update [--content ] [-h] [--noteId ] FLAGS -h, --help Show CLI help. @@ -416,7 +415,7 @@ EXAMPLES BnC6gN0_TfStV2KKmPPXeg Welcome to your team's workspace null CLI-test ``` -_See code: [src/commands/team-notes/index.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/team-notes/index.ts)_ +_See code: [src/commands/team-notes/index.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/team-notes/index.ts)_ ## `hackmd-cli team-notes create` @@ -424,8 +423,8 @@ Create a team note ``` USAGE - $ hackmd-cli team-notes create [-h] [--teamPath ] [--title ] [--content ] [--readPermission - ] [--writePermission ] [--commentPermission ] [-e] [--columns | -x] [--sort ] + $ hackmd-cli team-notes create [--commentPermission ] [--content ] [-e] [-h] [--readPermission ] + [--teamPath ] [--title ] [--writePermission ] [--columns | -x] [--sort ] [--filter ] [--output csv|json|yaml | | [--csv | --no-truncate]] [--no-header | ] FLAGS @@ -467,7 +466,7 @@ Delete a team note ``` USAGE - $ hackmd-cli team-notes delete [-h] [--teamPath ] [--noteId ] + $ hackmd-cli team-notes delete [-h] [--noteId ] [--teamPath ] FLAGS -h, --help Show CLI help. @@ -487,7 +486,7 @@ Update team note content ``` USAGE - $ hackmd-cli team-notes update [-h] [--teamPath ] [--noteId ] [--content ] + $ hackmd-cli team-notes update [--content ] [-h] [--noteId ] [--teamPath ] FLAGS -h, --help Show CLI help. @@ -533,7 +532,7 @@ EXAMPLES f76308a6-d77a-41f6-86d0-8ada426a6fb4 CLI test team CLI-test 82f7f3d9-4079-4c78-8a00-14094272ece9 ``` -_See code: [src/commands/teams.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/teams.ts)_ +_See code: [src/commands/teams.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/teams.ts)_ ## `hackmd-cli version` @@ -586,9 +585,13 @@ EXAMPLES 82f7f3d9-4079-4c78-8a00-14094272ece9 Ming-Hsiu Tsai null gvfz2UB5THiKABQJQnLs6Q ``` -_See code: [src/commands/whoami.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.3.2/src/commands/whoami.ts)_ +_See code: [src/commands/whoami.ts](https://github.com/hackmdio/hackmd-cli/blob/v2.4.0/src/commands/whoami.ts)_ ## License MIT + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history. diff --git a/hackmd-cli.skill b/hackmd-cli.skill new file mode 100644 index 0000000..be0cb01 Binary files /dev/null and b/hackmd-cli.skill differ diff --git a/hackmd-cli/SKILL.md b/hackmd-cli/SKILL.md new file mode 100644 index 0000000..d1f8ed4 --- /dev/null +++ b/hackmd-cli/SKILL.md @@ -0,0 +1,155 @@ +--- +name: hackmd-cli +description: HackMD command-line interface for managing notes and team notes. Use this skill when users want to create, read, update, delete, or export HackMD notes via CLI, manage team notes, list teams, view browsing history, or automate HackMD workflows. Triggers on mentions of hackmd-cli, HackMD CLI, or requests to interact with HackMD notes programmatically. +--- + +# HackMD CLI + +Command-line tool for managing HackMD notes and team notes via the HackMD API. + +## Setup + +### Install + +```bash +npm install -g @hackmd/hackmd-cli +``` + +### Configure Access Token + +Create an API token at [hackmd.io/settings#api](https://hackmd.io/settings#api), then configure: + +```bash +# Interactive login (saves to ~/.hackmd/config.json) +hackmd-cli login + +# Or via environment variable +export HMD_API_ACCESS_TOKEN=YOUR_TOKEN +``` + +For HackMD EE instances, also set the API endpoint: + +```bash +export HMD_API_ENDPOINT_URL=https://your.hackmd-ee.endpoint +``` + +## Commands + +### Authentication + +```bash +hackmd-cli login # Set access token interactively +hackmd-cli logout # Clear stored credentials +hackmd-cli whoami # Show current user info +``` + +### Personal Notes + +```bash +# List all notes +hackmd-cli notes + +# Get specific note +hackmd-cli notes --noteId= + +# 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 e2d9c15..7f4451e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hackmd/hackmd-cli", - "version": "2.3.2", + "version": "2.4.0", "author": "HackMD Team", "bin": { "hackmd-cli": "./bin/run" @@ -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..41ea67e --- /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 { + process.exitCode = 1; +} diff --git a/scripts/watch-skill.mjs b/scripts/watch-skill.mjs new file mode 100644 index 0000000..a544f37 --- /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 {execSync} from 'node:child_process'; +import {watch} from 'node:fs'; + +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); + } +});