mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-18 11:06:19 +00:00
feat: support bookmark folders (#253)
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { z } from 'zod'
|
||||
import { BookmarkAdapter } from '@/adapters/BookmarkAdapter'
|
||||
import { ActionHandler } from '../ActionHandler'
|
||||
|
||||
const CreateBookmarkFolderInputSchema = z.object({
|
||||
title: z.string().describe('Folder name'),
|
||||
parentId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Parent folder ID (defaults to "1" = Bookmarks Bar)'),
|
||||
})
|
||||
|
||||
const CreateBookmarkFolderOutputSchema = z.object({
|
||||
id: z.string().describe('Created folder ID'),
|
||||
title: z.string().describe('Folder name'),
|
||||
parentId: z.string().optional().describe('Parent folder ID'),
|
||||
dateAdded: z.number().optional().describe('Creation timestamp'),
|
||||
})
|
||||
|
||||
type CreateBookmarkFolderInput = z.infer<typeof CreateBookmarkFolderInputSchema>
|
||||
type CreateBookmarkFolderOutput = z.infer<
|
||||
typeof CreateBookmarkFolderOutputSchema
|
||||
>
|
||||
|
||||
export class CreateBookmarkFolderAction extends ActionHandler<
|
||||
CreateBookmarkFolderInput,
|
||||
CreateBookmarkFolderOutput
|
||||
> {
|
||||
readonly inputSchema = CreateBookmarkFolderInputSchema
|
||||
private bookmarkAdapter = new BookmarkAdapter()
|
||||
|
||||
async execute(
|
||||
input: CreateBookmarkFolderInput,
|
||||
): Promise<CreateBookmarkFolderOutput> {
|
||||
const created = await this.bookmarkAdapter.createBookmarkFolder({
|
||||
title: input.title,
|
||||
parentId: input.parentId,
|
||||
})
|
||||
|
||||
return {
|
||||
id: created.id,
|
||||
title: created.title,
|
||||
parentId: created.parentId,
|
||||
dateAdded: created.dateAdded,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { z } from 'zod'
|
||||
import { BookmarkAdapter } from '@/adapters/BookmarkAdapter'
|
||||
import { ActionHandler } from '../ActionHandler'
|
||||
|
||||
const GetBookmarkChildrenInputSchema = z.object({
|
||||
folderId: z.string().describe('Folder ID to get children from'),
|
||||
})
|
||||
|
||||
const GetBookmarkChildrenOutputSchema = z.object({
|
||||
children: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
url: z.string().optional(),
|
||||
parentId: z.string().optional(),
|
||||
dateAdded: z.number().optional(),
|
||||
isFolder: z.boolean(),
|
||||
}),
|
||||
),
|
||||
count: z.number(),
|
||||
})
|
||||
|
||||
type GetBookmarkChildrenInput = z.infer<typeof GetBookmarkChildrenInputSchema>
|
||||
type GetBookmarkChildrenOutput = z.infer<typeof GetBookmarkChildrenOutputSchema>
|
||||
|
||||
export class GetBookmarkChildrenAction extends ActionHandler<
|
||||
GetBookmarkChildrenInput,
|
||||
GetBookmarkChildrenOutput
|
||||
> {
|
||||
readonly inputSchema = GetBookmarkChildrenInputSchema
|
||||
private bookmarkAdapter = new BookmarkAdapter()
|
||||
|
||||
async execute(
|
||||
input: GetBookmarkChildrenInput,
|
||||
): Promise<GetBookmarkChildrenOutput> {
|
||||
const results = await this.bookmarkAdapter.getBookmarkChildren(
|
||||
input.folderId,
|
||||
)
|
||||
|
||||
const children = results.map((node) => ({
|
||||
id: node.id,
|
||||
title: node.title,
|
||||
url: node.url,
|
||||
parentId: node.parentId,
|
||||
dateAdded: node.dateAdded,
|
||||
isFolder: !node.url,
|
||||
}))
|
||||
|
||||
return {
|
||||
children,
|
||||
count: children.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { z } from 'zod'
|
||||
import { BookmarkAdapter } from '@/adapters/BookmarkAdapter'
|
||||
import { ActionHandler } from '../ActionHandler'
|
||||
|
||||
const MoveBookmarkInputSchema = z.object({
|
||||
id: z.string().describe('Bookmark or folder ID to move'),
|
||||
parentId: z.string().optional().describe('New parent folder ID'),
|
||||
index: z.number().int().min(0).optional().describe('Position within parent'),
|
||||
})
|
||||
|
||||
const MoveBookmarkOutputSchema = z.object({
|
||||
id: z.string().describe('Moved bookmark ID'),
|
||||
title: z.string().describe('Bookmark title'),
|
||||
url: z.string().optional().describe('Bookmark URL (undefined if folder)'),
|
||||
parentId: z.string().optional().describe('New parent folder ID'),
|
||||
index: z.number().optional().describe('New position within parent'),
|
||||
})
|
||||
|
||||
type MoveBookmarkInput = z.infer<typeof MoveBookmarkInputSchema>
|
||||
type MoveBookmarkOutput = z.infer<typeof MoveBookmarkOutputSchema>
|
||||
|
||||
export class MoveBookmarkAction extends ActionHandler<
|
||||
MoveBookmarkInput,
|
||||
MoveBookmarkOutput
|
||||
> {
|
||||
readonly inputSchema = MoveBookmarkInputSchema
|
||||
private bookmarkAdapter = new BookmarkAdapter()
|
||||
|
||||
async execute(input: MoveBookmarkInput): Promise<MoveBookmarkOutput> {
|
||||
const destination: { parentId?: string; index?: number } = {}
|
||||
if (input.parentId !== undefined) destination.parentId = input.parentId
|
||||
if (input.index !== undefined) destination.index = input.index
|
||||
|
||||
const moved = await this.bookmarkAdapter.moveBookmark(input.id, destination)
|
||||
|
||||
return {
|
||||
id: moved.id,
|
||||
title: moved.title,
|
||||
url: moved.url,
|
||||
parentId: moved.parentId,
|
||||
index: moved.index,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { z } from 'zod'
|
||||
import { BookmarkAdapter } from '@/adapters/BookmarkAdapter'
|
||||
import { ActionHandler } from '../ActionHandler'
|
||||
|
||||
const RemoveBookmarkTreeInputSchema = z.object({
|
||||
id: z.string().describe('Folder ID to remove'),
|
||||
confirm: z.boolean().describe('Must be true to confirm recursive deletion'),
|
||||
})
|
||||
|
||||
const RemoveBookmarkTreeOutputSchema = z.object({
|
||||
success: z.boolean().describe('Whether the folder was removed'),
|
||||
message: z.string().describe('Result message'),
|
||||
})
|
||||
|
||||
type RemoveBookmarkTreeInput = z.infer<typeof RemoveBookmarkTreeInputSchema>
|
||||
type RemoveBookmarkTreeOutput = z.infer<typeof RemoveBookmarkTreeOutputSchema>
|
||||
|
||||
export class RemoveBookmarkTreeAction extends ActionHandler<
|
||||
RemoveBookmarkTreeInput,
|
||||
RemoveBookmarkTreeOutput
|
||||
> {
|
||||
readonly inputSchema = RemoveBookmarkTreeInputSchema
|
||||
private bookmarkAdapter = new BookmarkAdapter()
|
||||
|
||||
async execute(
|
||||
input: RemoveBookmarkTreeInput,
|
||||
): Promise<RemoveBookmarkTreeOutput> {
|
||||
if (input.confirm !== true) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
'Recursive deletion requires confirm: true. This will permanently delete the folder and all its contents.',
|
||||
}
|
||||
}
|
||||
|
||||
await this.bookmarkAdapter.removeBookmarkTree(input.id)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Removed folder ${input.id} and all its contents`,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,6 +205,128 @@ export class BookmarkAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bookmark folder
|
||||
*
|
||||
* @param title - Folder name
|
||||
* @param parentId - Parent folder ID (defaults to "1" = Bookmarks Bar)
|
||||
* @returns Created folder node
|
||||
*/
|
||||
async createBookmarkFolder(options: {
|
||||
title: string
|
||||
parentId?: string
|
||||
}): Promise<chrome.bookmarks.BookmarkTreeNode> {
|
||||
const { title, parentId = '1' } = options
|
||||
logger.debug(
|
||||
`[BookmarkAdapter] Creating bookmark folder: "${title}" in parent ${parentId}`,
|
||||
)
|
||||
|
||||
try {
|
||||
const created = await chrome.bookmarks.create({
|
||||
title,
|
||||
parentId,
|
||||
})
|
||||
logger.debug(
|
||||
`[BookmarkAdapter] Created folder: ${created.id} - ${created.title}`,
|
||||
)
|
||||
return created
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error)
|
||||
logger.error(
|
||||
`[BookmarkAdapter] Failed to create bookmark folder: ${errorMessage}`,
|
||||
)
|
||||
throw new Error(`Failed to create bookmark folder: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get direct children of a folder
|
||||
*
|
||||
* @param folderId - Folder ID to get children from
|
||||
* @returns Array of child nodes
|
||||
*/
|
||||
async getBookmarkChildren(
|
||||
folderId: string,
|
||||
): Promise<chrome.bookmarks.BookmarkTreeNode[]> {
|
||||
logger.debug(`[BookmarkAdapter] Getting children of folder: ${folderId}`)
|
||||
|
||||
try {
|
||||
const children = await chrome.bookmarks.getChildren(folderId)
|
||||
logger.debug(
|
||||
`[BookmarkAdapter] Found ${children.length} children in folder ${folderId}`,
|
||||
)
|
||||
return children
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error)
|
||||
logger.error(
|
||||
`[BookmarkAdapter] Failed to get bookmark children: ${errorMessage}`,
|
||||
)
|
||||
throw new Error(`Failed to get bookmark children: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a bookmark or folder to a new location
|
||||
*
|
||||
* @param id - Bookmark or folder ID to move
|
||||
* @param destination - New location
|
||||
* @returns Updated bookmark node
|
||||
*/
|
||||
async moveBookmark(
|
||||
id: string,
|
||||
destination: { parentId?: string; index?: number },
|
||||
): Promise<chrome.bookmarks.BookmarkTreeNode> {
|
||||
logger.debug(
|
||||
`[BookmarkAdapter] Moving bookmark ${id} to parent ${destination.parentId}, index ${destination.index}`,
|
||||
)
|
||||
|
||||
try {
|
||||
const moved = await chrome.bookmarks.move(id, destination)
|
||||
logger.debug(
|
||||
`[BookmarkAdapter] Moved bookmark ${id} to ${moved.parentId}`,
|
||||
)
|
||||
return moved
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error)
|
||||
logger.error(
|
||||
`[BookmarkAdapter] Failed to move bookmark ${id}: ${errorMessage}`,
|
||||
)
|
||||
throw new Error(`Failed to move bookmark: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a folder and all its contents recursively
|
||||
*
|
||||
* @param id - Folder ID to remove
|
||||
* @throws if id is a root node ("0", "1", "2")
|
||||
*/
|
||||
async removeBookmarkTree(id: string): Promise<void> {
|
||||
const protectedIds = ['0', '1', '2']
|
||||
if (protectedIds.includes(id)) {
|
||||
throw new Error(
|
||||
`Cannot delete protected bookmark folder: ${id}. Root folders (Bookmarks Bar, Other Bookmarks, Mobile Bookmarks) cannot be deleted.`,
|
||||
)
|
||||
}
|
||||
|
||||
logger.debug(`[BookmarkAdapter] Removing bookmark tree: ${id}`)
|
||||
|
||||
try {
|
||||
await chrome.bookmarks.removeTree(id)
|
||||
logger.debug(`[BookmarkAdapter] Removed bookmark tree: ${id}`)
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error)
|
||||
logger.error(
|
||||
`[BookmarkAdapter] Failed to remove bookmark tree ${id}: ${errorMessage}`,
|
||||
)
|
||||
throw new Error(`Failed to remove bookmark tree: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten bookmark tree into array
|
||||
* @private
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
*/
|
||||
import { ActionRegistry } from '@/actions/ActionRegistry'
|
||||
import { CreateBookmarkAction } from '@/actions/bookmark/CreateBookmarkAction'
|
||||
import { CreateBookmarkFolderAction } from '@/actions/bookmark/CreateBookmarkFolderAction'
|
||||
import { GetBookmarkChildrenAction } from '@/actions/bookmark/GetBookmarkChildrenAction'
|
||||
import { GetBookmarksAction } from '@/actions/bookmark/GetBookmarksAction'
|
||||
import { MoveBookmarkAction } from '@/actions/bookmark/MoveBookmarkAction'
|
||||
import { RemoveBookmarkAction } from '@/actions/bookmark/RemoveBookmarkAction'
|
||||
import { RemoveBookmarkTreeAction } from '@/actions/bookmark/RemoveBookmarkTreeAction'
|
||||
import { CaptureScreenshotAction } from '@/actions/browser/CaptureScreenshotAction'
|
||||
import { CaptureScreenshotPointerAction } from '@/actions/browser/CaptureScreenshotPointerAction'
|
||||
import { ClearAction } from '@/actions/browser/ClearAction'
|
||||
@@ -200,6 +204,19 @@ export class BrowserOSController {
|
||||
this.actionRegistry.register('getBookmarks', new GetBookmarksAction())
|
||||
this.actionRegistry.register('createBookmark', new CreateBookmarkAction())
|
||||
this.actionRegistry.register('removeBookmark', new RemoveBookmarkAction())
|
||||
this.actionRegistry.register(
|
||||
'createBookmarkFolder',
|
||||
new CreateBookmarkFolderAction(),
|
||||
)
|
||||
this.actionRegistry.register(
|
||||
'getBookmarkChildren',
|
||||
new GetBookmarkChildrenAction(),
|
||||
)
|
||||
this.actionRegistry.register('moveBookmark', new MoveBookmarkAction())
|
||||
this.actionRegistry.register(
|
||||
'removeBookmarkTree',
|
||||
new RemoveBookmarkTreeAction(),
|
||||
)
|
||||
|
||||
this.actionRegistry.register('searchHistory', new SearchHistoryAction())
|
||||
this.actionRegistry.register(
|
||||
|
||||
@@ -12,7 +12,15 @@ export {
|
||||
sendKeys,
|
||||
} from './tools/advanced'
|
||||
// Bookmark Management
|
||||
export { createBookmark, getBookmarks, removeBookmark } from './tools/bookmarks'
|
||||
export {
|
||||
createBookmark,
|
||||
createBookmarkFolder,
|
||||
getBookmarkChildren,
|
||||
getBookmarks,
|
||||
moveBookmark,
|
||||
removeBookmark,
|
||||
removeBookmarkTree,
|
||||
} from './tools/bookmarks'
|
||||
// Content Extraction
|
||||
export { getPageContent } from './tools/content'
|
||||
// Coordinate-based
|
||||
@@ -61,7 +69,15 @@ import {
|
||||
executeJavaScript,
|
||||
sendKeys,
|
||||
} from './tools/advanced'
|
||||
import { createBookmark, getBookmarks, removeBookmark } from './tools/bookmarks'
|
||||
import {
|
||||
createBookmark,
|
||||
createBookmarkFolder,
|
||||
getBookmarkChildren,
|
||||
getBookmarks,
|
||||
moveBookmark,
|
||||
removeBookmark,
|
||||
removeBookmarkTree,
|
||||
} from './tools/bookmarks'
|
||||
import { getPageContent } from './tools/content'
|
||||
import { clickCoordinates, typeAtCoordinates } from './tools/coordinates'
|
||||
import { getRecentHistory, searchHistory } from './tools/history'
|
||||
@@ -90,7 +106,7 @@ import {
|
||||
} from './tools/tab-management'
|
||||
import { closeWindow, createWindow } from './tools/window-management'
|
||||
|
||||
// Array export for convenience (33 tools total)
|
||||
// Array export for convenience (37 tools total)
|
||||
export const allControllerTools = [
|
||||
getActiveTab,
|
||||
listTabs,
|
||||
@@ -122,6 +138,10 @@ export const allControllerTools = [
|
||||
getBookmarks,
|
||||
createBookmark,
|
||||
removeBookmark,
|
||||
createBookmarkFolder,
|
||||
getBookmarkChildren,
|
||||
moveBookmark,
|
||||
removeBookmarkTree,
|
||||
searchHistory,
|
||||
getRecentHistory,
|
||||
createWindow,
|
||||
|
||||
@@ -115,3 +115,182 @@ export const removeBookmark = defineTool<z.ZodRawShape, Context, Response>({
|
||||
response.appendResponseLine(`Removed bookmark ${bookmarkId}`)
|
||||
},
|
||||
})
|
||||
|
||||
export const createBookmarkFolder = defineTool<
|
||||
z.ZodRawShape,
|
||||
Context,
|
||||
Response
|
||||
>({
|
||||
name: 'browser_create_bookmark_folder',
|
||||
description: 'Create a new bookmark folder',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: false,
|
||||
},
|
||||
schema: {
|
||||
title: z.string().describe('Folder name'),
|
||||
parentId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Parent folder ID (defaults to Bookmarks Bar)'),
|
||||
windowId: z.number().optional().describe('Window ID for routing'),
|
||||
},
|
||||
handler: async (request, response, context) => {
|
||||
const { title, parentId, windowId } = request.params as {
|
||||
title: string
|
||||
parentId?: string
|
||||
windowId?: number
|
||||
}
|
||||
|
||||
const result = await context.executeAction('createBookmarkFolder', {
|
||||
title,
|
||||
parentId,
|
||||
windowId,
|
||||
})
|
||||
const data = result as {
|
||||
id: string
|
||||
title: string
|
||||
parentId?: string
|
||||
}
|
||||
|
||||
response.appendResponseLine(`Created folder: ${data.title}`)
|
||||
response.appendResponseLine(`ID: ${data.id}`)
|
||||
if (data.parentId) {
|
||||
response.appendResponseLine(`Parent: ${data.parentId}`)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const getBookmarkChildren = defineTool<z.ZodRawShape, Context, Response>(
|
||||
{
|
||||
name: 'browser_get_bookmark_children',
|
||||
description: 'Get direct children of a bookmark folder',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: true,
|
||||
},
|
||||
schema: {
|
||||
folderId: z.string().describe('Folder ID to get children from'),
|
||||
windowId: z.number().optional().describe('Window ID for routing'),
|
||||
},
|
||||
handler: async (request, response, context) => {
|
||||
const { folderId, windowId } = request.params as {
|
||||
folderId: string
|
||||
windowId?: number
|
||||
}
|
||||
|
||||
const result = await context.executeAction('getBookmarkChildren', {
|
||||
folderId,
|
||||
windowId,
|
||||
})
|
||||
const data = result as {
|
||||
children: Array<{
|
||||
id: string
|
||||
title: string
|
||||
url?: string
|
||||
isFolder: boolean
|
||||
}>
|
||||
count: number
|
||||
}
|
||||
|
||||
response.appendResponseLine(`Folder contains ${data.count} items:`)
|
||||
response.appendResponseLine('')
|
||||
|
||||
for (const child of data.children) {
|
||||
if (child.isFolder) {
|
||||
response.appendResponseLine(
|
||||
`[${child.id}] 📁 ${child.title} (folder)`,
|
||||
)
|
||||
} else {
|
||||
response.appendResponseLine(`[${child.id}] ${child.title}`)
|
||||
if (child.url) {
|
||||
response.appendResponseLine(` ${child.url}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export const moveBookmark = defineTool<z.ZodRawShape, Context, Response>({
|
||||
name: 'browser_move_bookmark',
|
||||
description: 'Move a bookmark or folder to a new location',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: false,
|
||||
},
|
||||
schema: {
|
||||
bookmarkId: z.string().describe('Bookmark or folder ID to move'),
|
||||
parentId: z.string().optional().describe('New parent folder ID'),
|
||||
index: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.optional()
|
||||
.describe('Position within parent (0-based)'),
|
||||
windowId: z.number().optional().describe('Window ID for routing'),
|
||||
},
|
||||
handler: async (request, response, context) => {
|
||||
const { bookmarkId, parentId, index, windowId } = request.params as {
|
||||
bookmarkId: string
|
||||
parentId?: string
|
||||
index?: number
|
||||
windowId?: number
|
||||
}
|
||||
|
||||
const result = await context.executeAction('moveBookmark', {
|
||||
id: bookmarkId,
|
||||
parentId,
|
||||
index,
|
||||
windowId,
|
||||
})
|
||||
const data = result as {
|
||||
id: string
|
||||
title: string
|
||||
parentId?: string
|
||||
index?: number
|
||||
}
|
||||
|
||||
response.appendResponseLine(`Moved: ${data.title}`)
|
||||
if (data.parentId) {
|
||||
response.appendResponseLine(`New parent: ${data.parentId}`)
|
||||
}
|
||||
if (data.index !== undefined) {
|
||||
response.appendResponseLine(`Position: ${data.index}`)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const removeBookmarkTree = defineTool<z.ZodRawShape, Context, Response>({
|
||||
name: 'browser_remove_bookmark_tree',
|
||||
description:
|
||||
'Remove a bookmark folder and all its contents recursively. Requires confirm: true.',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: false,
|
||||
},
|
||||
schema: {
|
||||
folderId: z.string().describe('Folder ID to remove'),
|
||||
confirm: z.boolean().describe('Must be true to confirm recursive deletion'),
|
||||
windowId: z.number().optional().describe('Window ID for routing'),
|
||||
},
|
||||
handler: async (request, response, context) => {
|
||||
const { folderId, confirm, windowId } = request.params as {
|
||||
folderId: string
|
||||
confirm: boolean
|
||||
windowId?: number
|
||||
}
|
||||
|
||||
const result = await context.executeAction('removeBookmarkTree', {
|
||||
id: folderId,
|
||||
confirm,
|
||||
windowId,
|
||||
})
|
||||
const data = result as {
|
||||
success: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
response.appendResponseLine(data.message)
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user