feat(export): add Tauri commands for transcript, task, and Obsidian export
Three new commands: export_transcriptions (MD/TXT), export_tasks (JSON/CSV), and export_to_obsidian (MD with wikilinks and checklists). All write plain text files to a user-selected directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,3 +39,4 @@ arboard = "3.6.1"
|
|||||||
tauri-plugin-mcp = "0.7.1"
|
tauri-plugin-mcp = "0.7.1"
|
||||||
sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio"] }
|
sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio"] }
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
chrono = "0.4"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main","tasks-float"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-always-on-top","core:window:allow-minimize","core:window:allow-toggle-maximize","core:window:allow-is-maximized","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus","opener:default","dialog:default","global-shortcut:allow-register","global-shortcut:allow-unregister"]}}
|
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main","tasks-float"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-always-on-top","core:window:allow-minimize","core:window:allow-toggle-maximize","core:window:allow-is-maximized","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus","opener:default","dialog:default","global-shortcut:allow-register","global-shortcut:allow-unregister","notification:default"]}}
|
||||||
@@ -2498,6 +2498,204 @@
|
|||||||
"const": "mcp:deny-list-tools",
|
"const": "mcp:deny-list-tools",
|
||||||
"markdownDescription": "Denies the list_tools command without any pre-configured scope."
|
"markdownDescription": "Denies the list_tools command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:default",
|
||||||
|
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-batch",
|
||||||
|
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-cancel",
|
||||||
|
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-check-permissions",
|
||||||
|
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-create-channel",
|
||||||
|
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-delete-channel",
|
||||||
|
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-active",
|
||||||
|
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-pending",
|
||||||
|
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-is-permission-granted",
|
||||||
|
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-list-channels",
|
||||||
|
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-notify",
|
||||||
|
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-permission-state",
|
||||||
|
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-action-types",
|
||||||
|
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-listener",
|
||||||
|
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-remove-active",
|
||||||
|
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-request-permission",
|
||||||
|
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-show",
|
||||||
|
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-batch",
|
||||||
|
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-cancel",
|
||||||
|
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-check-permissions",
|
||||||
|
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-create-channel",
|
||||||
|
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-delete-channel",
|
||||||
|
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-active",
|
||||||
|
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-pending",
|
||||||
|
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-is-permission-granted",
|
||||||
|
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-list-channels",
|
||||||
|
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-notify",
|
||||||
|
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-permission-state",
|
||||||
|
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-action-types",
|
||||||
|
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-listener",
|
||||||
|
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-remove-active",
|
||||||
|
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-request-permission",
|
||||||
|
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-show",
|
||||||
|
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -2498,6 +2498,204 @@
|
|||||||
"const": "mcp:deny-list-tools",
|
"const": "mcp:deny-list-tools",
|
||||||
"markdownDescription": "Denies the list_tools command without any pre-configured scope."
|
"markdownDescription": "Denies the list_tools command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:default",
|
||||||
|
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-batch",
|
||||||
|
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-cancel",
|
||||||
|
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-check-permissions",
|
||||||
|
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-create-channel",
|
||||||
|
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-delete-channel",
|
||||||
|
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-active",
|
||||||
|
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-pending",
|
||||||
|
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-is-permission-granted",
|
||||||
|
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-list-channels",
|
||||||
|
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-notify",
|
||||||
|
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-permission-state",
|
||||||
|
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-action-types",
|
||||||
|
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-listener",
|
||||||
|
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-remove-active",
|
||||||
|
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-request-permission",
|
||||||
|
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-show",
|
||||||
|
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-batch",
|
||||||
|
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-cancel",
|
||||||
|
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-check-permissions",
|
||||||
|
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-create-channel",
|
||||||
|
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-delete-channel",
|
||||||
|
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-active",
|
||||||
|
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-pending",
|
||||||
|
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-is-permission-granted",
|
||||||
|
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-list-channels",
|
||||||
|
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-notify",
|
||||||
|
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-permission-state",
|
||||||
|
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-action-types",
|
||||||
|
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-listener",
|
||||||
|
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-remove-active",
|
||||||
|
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-request-permission",
|
||||||
|
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-show",
|
||||||
|
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
282
src-tauri/src/commands/export.rs
Normal file
282
src-tauri/src/commands/export.rs
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
use kon_storage::{list_subtasks, list_tasks, list_transcripts, TaskRow, TranscriptRow};
|
||||||
|
|
||||||
|
/// Slugify a string for use in filenames.
|
||||||
|
fn slugify(s: &str) -> String {
|
||||||
|
s.to_lowercase()
|
||||||
|
.chars()
|
||||||
|
.map(|c| if c.is_alphanumeric() { c } else { '-' })
|
||||||
|
.collect::<String>()
|
||||||
|
.split('-')
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("-")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a `created_at` timestamp into a filename-safe prefix.
|
||||||
|
/// Expected format: "YYYY-MM-DD HH:MM:SS" → "YYYY-MM-DD_HH-MM-SS"
|
||||||
|
fn timestamp_to_filename(created_at: &str) -> String {
|
||||||
|
created_at
|
||||||
|
.replace(' ', "_")
|
||||||
|
.replace(':', "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Markdown content for a single transcription with YAML frontmatter.
|
||||||
|
fn transcription_to_markdown(t: &TranscriptRow) -> String {
|
||||||
|
let title = t.title.as_deref().unwrap_or("Untitled");
|
||||||
|
let word_count = t.text.split_whitespace().count();
|
||||||
|
format!(
|
||||||
|
"---\ntitle: \"{}\"\ndate: \"{}\"\nduration_seconds: {}\nword_count: {}\n---\n\n{}",
|
||||||
|
title, t.created_at, t.duration, word_count, t.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Obsidian-compatible Markdown for a transcription.
|
||||||
|
fn transcription_to_obsidian(t: &TranscriptRow, task_titles: &[String]) -> String {
|
||||||
|
let title = t.title.as_deref().unwrap_or("Untitled");
|
||||||
|
let mut md = format!(
|
||||||
|
"---\ntitle: \"{}\"\ndate: \"{}\"\nduration: {}\ntags: [kon, transcription]\n---\n\n{}",
|
||||||
|
title, t.created_at, t.duration, t.text
|
||||||
|
);
|
||||||
|
|
||||||
|
if !task_titles.is_empty() {
|
||||||
|
md.push_str("\n\n## Extracted Tasks\n\n");
|
||||||
|
for title in task_titles {
|
||||||
|
md.push_str(&format!("- [[{}]]\n", title));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
md
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Obsidian-compatible Markdown for a task.
|
||||||
|
fn task_to_obsidian(task: &TaskRow, subtasks: &[TaskRow]) -> String {
|
||||||
|
let title = &task.text;
|
||||||
|
let mut md = format!(
|
||||||
|
"---\ntitle: \"{}\"\nstatus: \"{}\"\npriority: \"{}\"\nproject: \"{}\"\ncreated: \"{}\"\nsource_transcription: \"{}\"\n---\n\n{}",
|
||||||
|
title,
|
||||||
|
task.status,
|
||||||
|
task.priority,
|
||||||
|
task.project.as_deref().unwrap_or(""),
|
||||||
|
task.created_at,
|
||||||
|
task.source_transcript_id.as_deref().unwrap_or(""),
|
||||||
|
task.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !subtasks.is_empty() {
|
||||||
|
md.push_str("\n\n## Micro-steps\n\n");
|
||||||
|
for st in subtasks {
|
||||||
|
let check = if st.done { "x" } else { " " };
|
||||||
|
md.push_str(&format!("- [{}] {}\n", check, st.text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
md
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a CSV row, escaping fields that contain commas or quotes.
|
||||||
|
fn csv_escape(field: &str) -> String {
|
||||||
|
if field.contains(',') || field.contains('"') || field.contains('\n') {
|
||||||
|
format!("\"{}\"", field.replace('"', "\"\""))
|
||||||
|
} else {
|
||||||
|
field.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Export all transcriptions as individual files (Markdown or plain text).
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn export_transcriptions(
|
||||||
|
state: tauri::State<'_, AppState>,
|
||||||
|
directory: String,
|
||||||
|
format: String,
|
||||||
|
) -> Result<u32, String> {
|
||||||
|
let dir = Path::new(&directory);
|
||||||
|
if !dir.exists() {
|
||||||
|
return Err(format!("Directory does not exist: {}", directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
let transcripts = list_transcripts(&state.db, i64::MAX)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let mut count = 0u32;
|
||||||
|
for t in &transcripts {
|
||||||
|
let prefix = timestamp_to_filename(&t.created_at);
|
||||||
|
let (ext, content) = match format.as_str() {
|
||||||
|
"txt" => ("txt", t.text.clone()),
|
||||||
|
_ => ("md", transcription_to_markdown(t)),
|
||||||
|
};
|
||||||
|
let filename = format!("{}_transcription.{}", prefix, ext);
|
||||||
|
let path = dir.join(&filename);
|
||||||
|
std::fs::write(&path, &content)
|
||||||
|
.map_err(|e| format!("Failed to write {}: {}", filename, e))?;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Export all top-level tasks with subtasks as JSON or CSV.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn export_tasks(
|
||||||
|
state: tauri::State<'_, AppState>,
|
||||||
|
directory: String,
|
||||||
|
format: String,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let dir = Path::new(&directory);
|
||||||
|
if !dir.exists() {
|
||||||
|
return Err(format!("Directory does not exist: {}", directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
let all_tasks = list_tasks(&state.db).await.map_err(|e| e.to_string())?;
|
||||||
|
let top_level: Vec<&TaskRow> = all_tasks.iter().filter(|t| t.parent_task_id.is_none()).collect();
|
||||||
|
|
||||||
|
let today = chrono::Local::now().format("%Y-%m-%d").to_string();
|
||||||
|
let (filename, content) = match format.as_str() {
|
||||||
|
"csv" => {
|
||||||
|
let mut csv = String::from("id,text,priority,project,status,bucket,effort,done,done_at,created_at,parent_task_id\n");
|
||||||
|
for task in &top_level {
|
||||||
|
csv.push_str(&format!(
|
||||||
|
"{},{},{},{},{},{},{},{},{},{},\n",
|
||||||
|
csv_escape(&task.id),
|
||||||
|
csv_escape(&task.text),
|
||||||
|
csv_escape(&task.priority),
|
||||||
|
csv_escape(task.project.as_deref().unwrap_or("")),
|
||||||
|
csv_escape(&task.status),
|
||||||
|
csv_escape(&task.bucket),
|
||||||
|
csv_escape(task.effort.as_deref().unwrap_or("")),
|
||||||
|
task.done,
|
||||||
|
csv_escape(task.done_at.as_deref().unwrap_or("")),
|
||||||
|
csv_escape(&task.created_at),
|
||||||
|
));
|
||||||
|
// Include subtasks
|
||||||
|
let subs = list_subtasks(&state.db, &task.id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
for st in &subs {
|
||||||
|
csv.push_str(&format!(
|
||||||
|
"{},{},{},{},{},{},{},{},{},{},{}\n",
|
||||||
|
csv_escape(&st.id),
|
||||||
|
csv_escape(&st.text),
|
||||||
|
csv_escape(&st.priority),
|
||||||
|
csv_escape(st.project.as_deref().unwrap_or("")),
|
||||||
|
csv_escape(&st.status),
|
||||||
|
csv_escape(&st.bucket),
|
||||||
|
csv_escape(st.effort.as_deref().unwrap_or("")),
|
||||||
|
st.done,
|
||||||
|
csv_escape(st.done_at.as_deref().unwrap_or("")),
|
||||||
|
csv_escape(&st.created_at),
|
||||||
|
csv_escape(&task.id),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(format!("kon-tasks-{}.csv", today), csv)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// JSON format
|
||||||
|
let mut tasks_json = Vec::new();
|
||||||
|
for task in &top_level {
|
||||||
|
let subs = list_subtasks(&state.db, &task.id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let subtasks_json: Vec<serde_json::Value> = subs
|
||||||
|
.iter()
|
||||||
|
.map(|st| {
|
||||||
|
serde_json::json!({
|
||||||
|
"id": st.id,
|
||||||
|
"text": st.text,
|
||||||
|
"priority": st.priority,
|
||||||
|
"project": st.project,
|
||||||
|
"status": st.status,
|
||||||
|
"bucket": st.bucket,
|
||||||
|
"effort": st.effort,
|
||||||
|
"done": st.done,
|
||||||
|
"done_at": st.done_at,
|
||||||
|
"created_at": st.created_at,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
tasks_json.push(serde_json::json!({
|
||||||
|
"id": task.id,
|
||||||
|
"text": task.text,
|
||||||
|
"priority": task.priority,
|
||||||
|
"project": task.project,
|
||||||
|
"status": task.status,
|
||||||
|
"bucket": task.bucket,
|
||||||
|
"effort": task.effort,
|
||||||
|
"done": task.done,
|
||||||
|
"done_at": task.done_at,
|
||||||
|
"created_at": task.created_at,
|
||||||
|
"notes": task.notes,
|
||||||
|
"source_transcript_id": task.source_transcript_id,
|
||||||
|
"subtasks": subtasks_json,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let json_str = serde_json::to_string_pretty(&tasks_json)
|
||||||
|
.map_err(|e| format!("JSON serialization failed: {}", e))?;
|
||||||
|
(format!("kon-tasks-{}.json", today), json_str)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = dir.join(&filename);
|
||||||
|
std::fs::write(&path, &content)
|
||||||
|
.map_err(|e| format!("Failed to write {}: {}", filename, e))?;
|
||||||
|
|
||||||
|
Ok(path.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Export transcriptions and tasks as Obsidian-compatible Markdown.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn export_to_obsidian(
|
||||||
|
state: tauri::State<'_, AppState>,
|
||||||
|
directory: String,
|
||||||
|
) -> Result<u32, String> {
|
||||||
|
let dir = Path::new(&directory);
|
||||||
|
if !dir.exists() {
|
||||||
|
return Err(format!("Directory does not exist: {}", directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
let transcripts = list_transcripts(&state.db, i64::MAX)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let all_tasks = list_tasks(&state.db).await.map_err(|e| e.to_string())?;
|
||||||
|
let top_level: Vec<&TaskRow> = all_tasks.iter().filter(|t| t.parent_task_id.is_none()).collect();
|
||||||
|
|
||||||
|
let mut count = 0u32;
|
||||||
|
|
||||||
|
// Export transcriptions
|
||||||
|
for t in &transcripts {
|
||||||
|
// Find tasks sourced from this transcription
|
||||||
|
let linked_titles: Vec<String> = top_level
|
||||||
|
.iter()
|
||||||
|
.filter(|task| task.source_transcript_id.as_deref() == Some(&t.id))
|
||||||
|
.map(|task| task.text.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let content = transcription_to_obsidian(t, &linked_titles);
|
||||||
|
let prefix = timestamp_to_filename(&t.created_at);
|
||||||
|
let filename = format!("{}_transcription.md", prefix);
|
||||||
|
let path = dir.join(&filename);
|
||||||
|
std::fs::write(&path, &content)
|
||||||
|
.map_err(|e| format!("Failed to write {}: {}", filename, e))?;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export tasks
|
||||||
|
for task in &top_level {
|
||||||
|
let subs = list_subtasks(&state.db, &task.id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let content = task_to_obsidian(task, &subs);
|
||||||
|
let slug = slugify(&task.text);
|
||||||
|
let filename = format!("task-{}.md", slug);
|
||||||
|
let path = dir.join(&filename);
|
||||||
|
std::fs::write(&path, &content)
|
||||||
|
.map_err(|e| format!("Failed to write {}: {}", filename, e))?;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod audio;
|
pub mod audio;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
|
pub mod export;
|
||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
pub mod history;
|
pub mod history;
|
||||||
pub mod llm;
|
pub mod llm;
|
||||||
|
|||||||
@@ -192,6 +192,10 @@ pub fn run() {
|
|||||||
// Hardware
|
// Hardware
|
||||||
commands::hardware::probe_system,
|
commands::hardware::probe_system,
|
||||||
commands::hardware::rank_models,
|
commands::hardware::rank_models,
|
||||||
|
// Export
|
||||||
|
commands::export::export_transcriptions,
|
||||||
|
commands::export::export_tasks,
|
||||||
|
commands::export::export_to_obsidian,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running Kon");
|
.expect("error while running Kon");
|
||||||
|
|||||||
Reference in New Issue
Block a user