Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package-lock.json
*-debug.log
*-error.log
/.nyc_output
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

All notable changes to HackMD-CLI will be documented in this file.

## 2.5.0

### Added

- Personal folder commands: `folders`, `folders create`, `folders update`, `folders delete`, and `folders order`
- Team folder commands: `team-folders`, `team-folders create`, `team-folders update`, `team-folders delete`, and `team-folders order`
- Agent skill (`hackmd-cli/SKILL.md`) with auto-packaging scripts for Cursor and other AI tooling

### Changed

- Upgraded `@hackmd/api` to v2.6.0

### Fixed

- Folder update command now returns silently on success
- Folder command examples use hex color codes

## 2.4.0

### Added
Expand Down
391 changes: 358 additions & 33 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hackmd/hackmd-cli",
"version": "2.4.0",
"version": "2.5.0",
"author": "HackMD Team",
"bin": {
"hackmd-cli": "./bin/run"
Expand Down
13 changes: 11 additions & 2 deletions src/commands/notes/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
editor,
noteContent,
notePermission,
noteTags,
noteTitle,
parentFolderId,
} from '../../flags'
Expand Down Expand Up @@ -47,6 +48,7 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n
help: Flags.help({char: 'h'}),
parentFolderId,
readPermission: notePermission,
tags: noteTags,
title: noteTitle,
writePermission: notePermission,
...ux.table.flags(),
Expand All @@ -56,7 +58,7 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n
const {flags} = await this.parse(CreateCommand)
const pipeString = safeStdinRead()

const options: CreateNoteOptions = {
const options: CreateNoteOptions & {tags?: string[]} = {
commentPermission: flags.commentPermission as CommentPermissionType,
content: pipeString || flags.content,
parentFolderId: flags.parentFolderId,
Expand All @@ -65,6 +67,10 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n
writePermission: flags.writePermission as NotePermissionRole,
}

if (flags.tags !== undefined) {
options.tags = flags.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
}

if (flags.editor) {
try {
const mdFile = temporaryMD()
Expand All @@ -78,14 +84,17 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n

try {
const APIClient = await this.getAPIClient()
const note = await APIClient.createNote(options)
const note = await APIClient.createNote(options as CreateNoteOptions)

ux.table(
[note],
{
id: {
header: 'ID',
},
tags: {
get: row => (row.tags ?? []).join(', '),
},
teamPath: {
header: 'Team path',
},
Expand Down
3 changes: 3 additions & 0 deletions src/commands/notes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ raUuSTetT5uQbqQfLnz9lA CLI test note gvfz2UB5THiKABQJQnLs6Q n
id: {
header: 'ID',
},
tags: {
get: row => (row.tags ?? []).join(', '),
},
teamPath: {
header: 'Team Path',
},
Expand Down
32 changes: 22 additions & 10 deletions src/commands/notes/update.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
import type {UpdateNoteOptions} from '@hackmd/api'
import type {NotePermissionRole, UpdateNoteOptions} from '@hackmd/api'

import {Flags} from '@oclif/core'

import HackMDCommand from '../../command'
import {noteContent, noteId, parentFolderId} from '../../flags'
import {
noteContent, noteId, notePermission, noteTags, parentFolderId, permalink,
} from '../../flags'

export default class Update extends HackMDCommand {
static description = 'Update note content'
static description = 'Update note'
static examples = [
"$ hackmd-cli notes update --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --content='# A new title'",
"$ hackmd-cli notes update --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --parentFolderId=fc7a3d48-4a07-4cbf-bf4f-e65dd896e01c --content='# A new title'",
'$ hackmd-cli notes update --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --readPermission=owner --writePermission=owner',
'$ hackmd-cli notes update --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --tags=tag1,tag2',
]
static flags = {
content: noteContent,
help: Flags.help({char: 'h'}),
noteId,
parentFolderId,
permalink,
readPermission: notePermission,
tags: noteTags,
writePermission: notePermission,
}

async run() {
const {flags} = await this.parse(Update)
const {content, noteId, parentFolderId} = flags
const {content, noteId, parentFolderId, permalink, readPermission, tags, writePermission} = flags

if (!noteId) {
this.error('Flag noteId could not be empty')
}

const payload: UpdateNoteOptions = {
content,
parentFolderId,
}
const payload: UpdateNoteOptions & {tags?: string[]} = {}

if (content !== undefined) payload.content = content
if (parentFolderId !== undefined) payload.parentFolderId = parentFolderId
if (readPermission !== undefined) payload.readPermission = readPermission as NotePermissionRole
if (writePermission !== undefined) payload.writePermission = writePermission as NotePermissionRole
if (permalink !== undefined) payload.permalink = permalink
if (tags !== undefined) payload.tags = tags.split(',').map((t: string) => t.trim()).filter(Boolean)

try {
const APIClient = await this.getAPIClient()
await APIClient.updateNote(noteId, payload)
await APIClient.updateNote(noteId, payload as UpdateNoteOptions)
} catch (error) {
this.log('Update note content failed')
this.log('Update note failed')
this.error(error as Error)
}
}
Expand Down
16 changes: 12 additions & 4 deletions src/commands/team-notes/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fs from 'node:fs'

import HackMDCommand from '../../command'
import {
commentPermission, editor, noteContent, notePermission, noteTitle, parentFolderId, teamPath,
commentPermission, editor, noteContent, notePermission, noteTags, noteTitle, parentFolderId, teamPath,
} from '../../flags'
import {openEditor} from '../../open-editor'
import {safeStdinRead, temporaryMD} from '../../utils'
Expand Down Expand Up @@ -38,6 +38,7 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n
help: Flags.help({char: 'h'}),
parentFolderId,
readPermission: notePermission,
tags: noteTags,
teamPath,
title: noteTitle,
writePermission: notePermission,
Expand All @@ -48,8 +49,8 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n
const {flags} = await this.parse(Create)
const pipeString = safeStdinRead()

const {commentPermission, content, parentFolderId, readPermission, teamPath, title, writePermission} = flags
const options: CreateNoteOptions = {
const {commentPermission, content, parentFolderId, readPermission, teamPath, tags, title, writePermission} = flags
const options: CreateNoteOptions & {tags?: string[]} = {
commentPermission: commentPermission as CommentPermissionType,
content: pipeString || content,
parentFolderId,
Expand All @@ -58,6 +59,10 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n
writePermission: writePermission as NotePermissionRole,
}

if (tags !== undefined) {
options.tags = tags.split(',').map((t: string) => t.trim()).filter(Boolean)
}

if (!teamPath) {
this.error('Flag teamPath could not be empty')
}
Expand All @@ -75,12 +80,15 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q n

try {
const APIClient = await this.getAPIClient()
const note = await APIClient.createTeamNote(teamPath, options)
const note = await APIClient.createTeamNote(teamPath, options as CreateNoteOptions)

ux.table([note], {
id: {
header: 'ID',
},
tags: {
get: row => (row.tags ?? []).join(', '),
},
teamPath: {
header: 'Team path',
},
Expand Down
3 changes: 3 additions & 0 deletions src/commands/team-notes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ BnC6gN0_TfStV2KKmPPXeg Welcome to your team's workspace null CLI-test`,
id: {
header: 'ID',
},
tags: {
get: row => (row.tags ?? []).join(', '),
},
teamPath: {
header: 'Team path',
},
Expand Down
30 changes: 20 additions & 10 deletions src/commands/team-notes/update.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import type {UpdateNoteOptions} from '@hackmd/api'
import type {NotePermissionRole, UpdateNoteOptions} from '@hackmd/api'

import {Flags} from '@oclif/core'

import HackMDCommand from '../../command'
import {
noteContent, noteId, parentFolderId, teamPath,
noteContent, noteId, notePermission, noteTags, parentFolderId, permalink, teamPath,
} from '../../flags'

export default class Update extends HackMDCommand {
static description = 'Update team note content'
static description = 'Update team note'
static examples = [
"$ hackmd-cli team-notes update --teamPath=CLI-test --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --content='# A new title'",
"$ hackmd-cli team-notes update --teamPath=CLI-test --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --parentFolderId=fc7a3d48-4a07-4cbf-bf4f-e65dd896e01c --content='# A new title'",
'$ hackmd-cli team-notes update --teamPath=CLI-test --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --readPermission=owner --writePermission=owner',
'$ hackmd-cli team-notes update --teamPath=CLI-test --noteId=WNkLM6gkS0Cg2cQ8rv7bYA --tags=tag1,tag2',
]
static flags = {
content: noteContent,
help: Flags.help({char: 'h'}),
noteId,
parentFolderId,
permalink,
readPermission: notePermission,
tags: noteTags,
teamPath,
writePermission: notePermission,
}

async run() {
const {flags} = await this.parse(Update)
const {content, noteId, parentFolderId, teamPath} = flags
const {content, noteId, parentFolderId, permalink, readPermission, tags, teamPath, writePermission} = flags

if (!teamPath) {
this.error('Flag teamPath could not be empty')
Expand All @@ -33,16 +39,20 @@ export default class Update extends HackMDCommand {
this.error('Flag noteId could not be empty')
}

const payload: UpdateNoteOptions = {
content,
parentFolderId,
}
const payload: UpdateNoteOptions & {tags?: string[]} = {}

if (content !== undefined) payload.content = content
if (parentFolderId !== undefined) payload.parentFolderId = parentFolderId
if (readPermission !== undefined) payload.readPermission = readPermission as NotePermissionRole
if (writePermission !== undefined) payload.writePermission = writePermission as NotePermissionRole
if (permalink !== undefined) payload.permalink = permalink
if (tags !== undefined) payload.tags = tags.split(',').map((t: string) => t.trim()).filter(Boolean)

try {
const APIClient = await this.getAPIClient()
await APIClient.updateTeamNote(teamPath, noteId, payload)
await APIClient.updateTeamNote(teamPath, noteId, payload as UpdateNoteOptions)
} catch (error) {
this.log('Update team note content failed')
this.log('Update team note failed')
this.error(error as Error)
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export const commentPermission = Flags.string({
description: 'set comment permission: disabled, forbidden, owners, signed_in_users, everyone',
})

export const permalink = Flags.string({
description: 'note permalink',
})

export const noteTags = Flags.string({
description: 'set note tags, comma-separated (e.g. tag1,tag2)',
})

export const editor = Flags.boolean({
char: 'e',
description: 'create note with $EDITOR',
Expand Down