From 83c052a0d3d63209e6a72acba51da39244a6382c Mon Sep 17 00:00:00 2001 From: shanglei Date: Thu, 6 Aug 2026 16:42:42 +0800 Subject: [PATCH 1/2] feat(risk): make the risk taxonomy typed and fail closed on a bad one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Misspelling a shortcut's risk level used to be silent and destructive: `Risk: "high-risk-wrtie"` compiled, passed CI, and at runtime the confirmation gate compared against the literal "high-risk-write", missed, and ran the command. --yes was never even registered, so the user could not have confirmed if they had wanted to. 64 high-risk write commands sit behind that comparison. The taxonomy was declared four times (internal/core, internal/cmdutil, errs, extension/platform) and only the plugin SDK's copy was typed. Three layers now, because no single one is enough: - Type. internal/core.Risk is a defined type and the single definition; cmdutil and shortcuts/common re-export it, extension/platform keeps its own SDK-facing type but derives its constants from core and converts via Core/FromCore, and errs keeps the wire strings (it cannot import core without a cycle). A consistency test pins all three together. Shortcut.Risk, SetRisk/GetRisk, cmdmeta.Meta.Risk and the service command spec are typed; the 548 declarations under shortcuts/ and the SetRisk calls under cmd/ use the constants. Values crossing a string boundary — the generated service catalog, cobra annotations — go through core.ParseRisk instead of a bare conversion. Note what the type does not do: an untyped literal still converts, so `Risk: "high-risk-wrtie"` compiles. That is why the other two layers exist rather than being belt-and-braces. - CI. The quality gate rejects a manifest risk outside the closed enum — the manifest is exported from the live tree, so this sees every mounted command's real annotation — and a new risk-literal rule rejects a hand-written level in cmd/ and shortcuts/, at a zero baseline. The sheets flag-defs generator validates its JSON input the same way. - Runtime. cmdutil.EnforceRiskDeclaration refuses to run a command whose declared level is not in the taxonomy, and RequiresConfirmation treats an unrecognised level as the highest tier rather than as read. LARKSUITE_CLI_ALLOW_INVALID_RISK downgrades the refusal to "confirm first"; it cannot downgrade it to "run". --yes is registered for an invalid declaration too, so the downgrade path is usable. Tests cover the reproduction (a misspelled level must not execute, with or without --yes), the downgrade switch, the unchanged high-risk contract, the lower tiers staying ungated, ParseRisk's absent/valid/invalid split, and the cross-package consistency of the three declarations. --- cmd/api/api.go | 2 +- cmd/auth/check.go | 2 +- cmd/auth/list.go | 2 +- cmd/auth/login.go | 2 +- cmd/auth/logout.go | 2 +- cmd/auth/scopes.go | 2 +- cmd/auth/status.go | 2 +- cmd/completion/completion.go | 2 +- cmd/config/bind.go | 2 +- cmd/config/default_as.go | 2 +- cmd/config/init.go | 2 +- cmd/config/keychain_downgrade.go | 2 +- cmd/config/plugins.go | 2 +- cmd/config/policy.go | 2 +- cmd/config/remove.go | 2 +- cmd/config/show.go | 2 +- cmd/config/strict_mode.go | 2 +- cmd/doctor/doctor.go | 2 +- cmd/event/bus.go | 2 +- cmd/event/consume.go | 2 +- cmd/event/list.go | 2 +- cmd/event/schema.go | 2 +- cmd/event/status.go | 2 +- cmd/event/stop.go | 2 +- cmd/platform_bootstrap_test.go | 2 +- cmd/plugin_integration_test.go | 2 +- cmd/profile/add.go | 2 +- cmd/profile/list.go | 2 +- cmd/profile/remove.go | 2 +- cmd/profile/rename.go | 2 +- cmd/profile/use.go | 2 +- cmd/prune_test.go | 2 +- cmd/root_risk_help_test.go | 4 +- cmd/service/affordance_test.go | 2 +- cmd/service/service.go | 28 ++- cmd/skill/skill.go | 4 +- cmd/update/update.go | 2 +- cmd/whoami/whoami.go | 2 +- errs/subtypes.go | 5 + extension/platform/risk.go | 49 +++-- internal/cmdmeta/meta.go | 4 +- internal/cmdpolicy/engine.go | 6 +- internal/cmdutil/risk.go | 25 ++- internal/cmdutil/risk_gate.go | 77 ++++++++ internal/cmdutil/risk_test.go | 4 +- internal/core/risk.go | 84 ++++++++- internal/core/risk_test.go | 122 ++++++++++++ internal/envvars/envvars.go | 7 + .../cmd/manifest-export/collect.go | 2 +- internal/qualitygate/manifest/io_test.go | 27 +++ internal/qualitygate/manifest/schema.go | 11 ++ internal/qualitygate/rules/risklit.go | 148 +++++++++++++++ internal/qualitygate/rules/risklit_test.go | 97 ++++++++++ internal/qualitygate/rules/run.go | 7 + internal/schema/assembler.go | 4 +- internal/schema/lint.go | 5 +- shortcuts/application/slash_command_create.go | 2 +- shortcuts/application/slash_command_delete.go | 2 +- shortcuts/application/slash_command_list.go | 2 +- shortcuts/application/slash_command_update.go | 2 +- shortcuts/apps/apps_access_scope_get.go | 2 +- shortcuts/apps/apps_access_scope_set.go | 2 +- shortcuts/apps/apps_analytics.go | 2 +- shortcuts/apps/apps_automation_create.go | 2 +- shortcuts/apps/apps_automation_disable.go | 2 +- shortcuts/apps/apps_automation_enable.go | 2 +- shortcuts/apps/apps_automation_get.go | 2 +- shortcuts/apps/apps_automation_list.go | 2 +- shortcuts/apps/apps_automation_update.go | 2 +- shortcuts/apps/apps_cache_clear.go | 2 +- shortcuts/apps/apps_cache_delete.go | 2 +- shortcuts/apps/apps_cache_get.go | 2 +- shortcuts/apps/apps_chat.go | 2 +- shortcuts/apps/apps_create.go | 2 +- shortcuts/apps/apps_db_audit_list.go | 2 +- shortcuts/apps/apps_db_audit_set.go | 4 +- shortcuts/apps/apps_db_audit_status.go | 2 +- shortcuts/apps/apps_db_changelog_list.go | 2 +- shortcuts/apps/apps_db_data_export.go | 2 +- shortcuts/apps/apps_db_data_import.go | 2 +- shortcuts/apps/apps_db_env_create.go | 2 +- shortcuts/apps/apps_db_env_migrate.go | 4 +- shortcuts/apps/apps_db_execute.go | 2 +- shortcuts/apps/apps_db_quota_get.go | 2 +- shortcuts/apps/apps_db_recovery.go | 4 +- shortcuts/apps/apps_db_table_get.go | 2 +- shortcuts/apps/apps_db_table_list.go | 2 +- shortcuts/apps/apps_env.go | 6 +- shortcuts/apps/apps_env_pull.go | 2 +- shortcuts/apps/apps_file_delete.go | 2 +- shortcuts/apps/apps_file_download.go | 2 +- shortcuts/apps/apps_file_get.go | 2 +- shortcuts/apps/apps_file_list.go | 2 +- shortcuts/apps/apps_file_quota_get.go | 2 +- shortcuts/apps/apps_file_sign.go | 2 +- shortcuts/apps/apps_file_upload.go | 2 +- shortcuts/apps/apps_get.go | 2 +- shortcuts/apps/apps_html_publish.go | 2 +- shortcuts/apps/apps_init.go | 2 +- shortcuts/apps/apps_list.go | 2 +- shortcuts/apps/apps_logs.go | 4 +- shortcuts/apps/apps_metrics.go | 2 +- shortcuts/apps/apps_openapi_key_create.go | 2 +- shortcuts/apps/apps_openapi_key_delete.go | 2 +- shortcuts/apps/apps_openapi_key_disable.go | 2 +- shortcuts/apps/apps_openapi_key_enable.go | 2 +- shortcuts/apps/apps_openapi_key_get.go | 2 +- shortcuts/apps/apps_openapi_key_list.go | 2 +- shortcuts/apps/apps_openapi_key_reset.go | 2 +- shortcuts/apps/apps_openapi_key_update.go | 2 +- shortcuts/apps/apps_plugin_install.go | 2 +- shortcuts/apps/apps_plugin_list.go | 2 +- shortcuts/apps/apps_plugin_uninstall.go | 2 +- shortcuts/apps/apps_release_create.go | 2 +- shortcuts/apps/apps_release_get.go | 2 +- shortcuts/apps/apps_release_list.go | 2 +- shortcuts/apps/apps_role.go | 10 +- shortcuts/apps/apps_role_member.go | 8 +- shortcuts/apps/apps_role_test.go | 4 +- shortcuts/apps/apps_session_create.go | 2 +- shortcuts/apps/apps_session_get.go | 2 +- shortcuts/apps/apps_session_list.go | 2 +- shortcuts/apps/apps_session_messages_list.go | 2 +- shortcuts/apps/apps_session_stop.go | 2 +- shortcuts/apps/apps_traces.go | 4 +- shortcuts/apps/apps_update.go | 2 +- shortcuts/apps/git_credential.go | 6 +- shortcuts/base/base_advperm_disable.go | 2 +- shortcuts/base/base_advperm_enable.go | 2 +- shortcuts/base/base_block_create.go | 2 +- shortcuts/base/base_block_delete.go | 2 +- shortcuts/base/base_block_list.go | 2 +- shortcuts/base/base_block_move.go | 2 +- shortcuts/base/base_block_rename.go | 2 +- shortcuts/base/base_copy.go | 2 +- shortcuts/base/base_create.go | 2 +- shortcuts/base/base_data_query.go | 2 +- shortcuts/base/base_form_create.go | 2 +- shortcuts/base/base_form_delete.go | 2 +- shortcuts/base/base_form_detail.go | 2 +- shortcuts/base/base_form_get.go | 2 +- shortcuts/base/base_form_list.go | 2 +- shortcuts/base/base_form_questions_create.go | 2 +- shortcuts/base/base_form_questions_delete.go | 2 +- shortcuts/base/base_form_questions_list.go | 2 +- shortcuts/base/base_form_questions_update.go | 2 +- shortcuts/base/base_form_submit.go | 2 +- shortcuts/base/base_form_update.go | 2 +- shortcuts/base/base_get.go | 2 +- shortcuts/base/base_resolve.go | 4 +- shortcuts/base/base_role_create.go | 2 +- shortcuts/base/base_role_delete.go | 2 +- shortcuts/base/base_role_get.go | 2 +- shortcuts/base/base_role_list.go | 2 +- shortcuts/base/base_role_test.go | 2 +- shortcuts/base/base_role_update.go | 2 +- shortcuts/base/base_shortcuts_test.go | 2 +- shortcuts/base/dashboard_arrange.go | 2 +- shortcuts/base/dashboard_block_create.go | 2 +- shortcuts/base/dashboard_block_delete.go | 2 +- shortcuts/base/dashboard_block_get.go | 2 +- shortcuts/base/dashboard_block_get_data.go | 2 +- shortcuts/base/dashboard_block_list.go | 2 +- shortcuts/base/dashboard_block_update.go | 2 +- shortcuts/base/dashboard_create.go | 2 +- shortcuts/base/dashboard_delete.go | 2 +- shortcuts/base/dashboard_get.go | 2 +- shortcuts/base/dashboard_list.go | 2 +- shortcuts/base/dashboard_update.go | 2 +- shortcuts/base/field_create.go | 2 +- shortcuts/base/field_delete.go | 2 +- shortcuts/base/field_get.go | 2 +- shortcuts/base/field_list.go | 2 +- shortcuts/base/field_search_options.go | 2 +- shortcuts/base/field_update.go | 2 +- shortcuts/base/record_batch_create.go | 2 +- shortcuts/base/record_batch_update.go | 2 +- shortcuts/base/record_delete.go | 2 +- shortcuts/base/record_get.go | 2 +- shortcuts/base/record_history_list.go | 2 +- shortcuts/base/record_list.go | 2 +- shortcuts/base/record_search.go | 2 +- shortcuts/base/record_share_link_create.go | 2 +- shortcuts/base/record_upload_attachment.go | 6 +- shortcuts/base/record_upsert.go | 2 +- shortcuts/base/table_copy.go | 4 +- shortcuts/base/table_create.go | 2 +- shortcuts/base/table_delete.go | 2 +- shortcuts/base/table_get.go | 2 +- shortcuts/base/table_list.go | 2 +- shortcuts/base/table_update.go | 2 +- shortcuts/base/view_create.go | 2 +- shortcuts/base/view_delete.go | 2 +- shortcuts/base/view_get.go | 2 +- shortcuts/base/view_get_card.go | 2 +- shortcuts/base/view_get_filter.go | 2 +- shortcuts/base/view_get_group.go | 2 +- shortcuts/base/view_get_sort.go | 2 +- shortcuts/base/view_get_timebar.go | 2 +- shortcuts/base/view_get_visible_fields.go | 2 +- shortcuts/base/view_list.go | 2 +- shortcuts/base/view_rename.go | 2 +- shortcuts/base/view_set_card.go | 2 +- shortcuts/base/view_set_filter.go | 2 +- shortcuts/base/view_set_group.go | 2 +- shortcuts/base/view_set_sort.go | 2 +- shortcuts/base/view_set_timebar.go | 2 +- shortcuts/base/view_set_visible_fields.go | 2 +- shortcuts/base/workflow_create.go | 2 +- shortcuts/base/workflow_disable.go | 2 +- shortcuts/base/workflow_enable.go | 2 +- shortcuts/base/workflow_get.go | 2 +- shortcuts/base/workflow_list.go | 2 +- shortcuts/base/workflow_update.go | 2 +- shortcuts/calendar/calendar_agenda.go | 2 +- shortcuts/calendar/calendar_create.go | 2 +- shortcuts/calendar/calendar_freebusy.go | 2 +- shortcuts/calendar/calendar_get.go | 2 +- shortcuts/calendar/calendar_meeting.go | 2 +- shortcuts/calendar/calendar_room_find.go | 2 +- shortcuts/calendar/calendar_rsvp.go | 2 +- shortcuts/calendar/calendar_search_event.go | 2 +- shortcuts/calendar/calendar_suggestion.go | 2 +- shortcuts/calendar/calendar_update.go | 2 +- shortcuts/common/runner.go | 12 +- shortcuts/common/runner_risk_gate_test.go | 173 ++++++++++++++++++ shortcuts/common/types.go | 23 ++- shortcuts/contact/contact_get_user.go | 2 +- shortcuts/contact/contact_search_bot.go | 2 +- shortcuts/contact/contact_search_user.go | 2 +- shortcuts/doc/doc_media_download.go | 2 +- shortcuts/doc/doc_media_insert.go | 2 +- shortcuts/doc/doc_media_preview.go | 2 +- shortcuts/doc/doc_media_upload.go | 2 +- shortcuts/doc/doc_resource_cover.go | 6 +- shortcuts/doc/docs_create.go | 2 +- shortcuts/doc/docs_fetch.go | 2 +- shortcuts/doc/docs_history.go | 6 +- shortcuts/doc/docs_search.go | 2 +- shortcuts/doc/docs_update.go | 2 +- shortcuts/drive/drive_add_comment.go | 2 +- shortcuts/drive/drive_add_reply.go | 2 +- shortcuts/drive/drive_apply_permission.go | 2 +- shortcuts/drive/drive_batch_query_comments.go | 2 +- shortcuts/drive/drive_cover.go | 2 +- shortcuts/drive/drive_create_folder.go | 2 +- shortcuts/drive/drive_create_shortcut.go | 2 +- shortcuts/drive/drive_delete.go | 2 +- shortcuts/drive/drive_delete_reply.go | 2 +- shortcuts/drive/drive_download.go | 2 +- shortcuts/drive/drive_export.go | 2 +- shortcuts/drive/drive_export_download.go | 2 +- shortcuts/drive/drive_import.go | 2 +- shortcuts/drive/drive_inspect.go | 2 +- shortcuts/drive/drive_list_comments.go | 2 +- shortcuts/drive/drive_list_replies.go | 2 +- shortcuts/drive/drive_member_add.go | 2 +- shortcuts/drive/drive_member_list.go | 2 +- shortcuts/drive/drive_move.go | 2 +- .../drive/drive_permission_get_setting.go | 2 +- shortcuts/drive/drive_preview.go | 2 +- shortcuts/drive/drive_pull.go | 2 +- shortcuts/drive/drive_push.go | 2 +- shortcuts/drive/drive_react_reply.go | 2 +- shortcuts/drive/drive_resolve_comment.go | 2 +- shortcuts/drive/drive_search.go | 2 +- shortcuts/drive/drive_secure_label.go | 4 +- shortcuts/drive/drive_status.go | 2 +- shortcuts/drive/drive_sync.go | 2 +- shortcuts/drive/drive_task_result.go | 2 +- shortcuts/drive/drive_update_reply.go | 2 +- shortcuts/drive/drive_upload.go | 2 +- shortcuts/drive/drive_version.go | 8 +- shortcuts/event/subscribe.go | 2 +- shortcuts/im/im_chat_create.go | 2 +- shortcuts/im/im_chat_list.go | 2 +- shortcuts/im/im_chat_members_list.go | 2 +- shortcuts/im/im_chat_messages_list.go | 2 +- shortcuts/im/im_chat_search.go | 2 +- shortcuts/im/im_chat_update.go | 2 +- shortcuts/im/im_feed_group_list.go | 2 +- shortcuts/im/im_feed_group_list_item.go | 2 +- shortcuts/im/im_feed_group_query_item.go | 2 +- shortcuts/im/im_feed_shortcut_create.go | 2 +- shortcuts/im/im_feed_shortcut_list.go | 2 +- shortcuts/im/im_feed_shortcut_remove.go | 2 +- shortcuts/im/im_flag_cancel.go | 2 +- shortcuts/im/im_flag_create.go | 2 +- shortcuts/im/im_flag_list.go | 2 +- shortcuts/im/im_messages_mget.go | 2 +- shortcuts/im/im_messages_reply.go | 2 +- .../im/im_messages_resources_download.go | 2 +- shortcuts/im/im_messages_search.go | 2 +- shortcuts/im/im_messages_send.go | 2 +- shortcuts/im/im_threads_messages_list.go | 2 +- shortcuts/mail/mail_decline_receipt.go | 2 +- shortcuts/mail/mail_draft_create.go | 2 +- shortcuts/mail/mail_draft_edit.go | 2 +- shortcuts/mail/mail_draft_send.go | 2 +- shortcuts/mail/mail_forward.go | 2 +- shortcuts/mail/mail_lint_html.go | 2 +- shortcuts/mail/mail_message.go | 2 +- shortcuts/mail/mail_message_modify.go | 2 +- shortcuts/mail/mail_message_trash.go | 2 +- shortcuts/mail/mail_messages.go | 2 +- shortcuts/mail/mail_reply.go | 2 +- shortcuts/mail/mail_reply_all.go | 2 +- shortcuts/mail/mail_send.go | 2 +- shortcuts/mail/mail_send_receipt.go | 2 +- shortcuts/mail/mail_share_to_chat.go | 2 +- shortcuts/mail/mail_signature.go | 2 +- shortcuts/mail/mail_template_create.go | 2 +- shortcuts/mail/mail_template_update.go | 2 +- shortcuts/mail/mail_thread.go | 2 +- shortcuts/mail/mail_triage.go | 2 +- shortcuts/mail/mail_watch.go | 2 +- shortcuts/markdown/markdown_create.go | 2 +- shortcuts/markdown/markdown_diff.go | 2 +- shortcuts/markdown/markdown_fetch.go | 2 +- shortcuts/markdown/markdown_overwrite.go | 2 +- shortcuts/markdown/markdown_patch.go | 2 +- shortcuts/minutes/minutes_apply_permission.go | 2 +- shortcuts/minutes/minutes_detail.go | 2 +- shortcuts/minutes/minutes_download.go | 2 +- shortcuts/minutes/minutes_search.go | 2 +- shortcuts/minutes/minutes_speaker_replace.go | 2 +- shortcuts/minutes/minutes_summary.go | 2 +- shortcuts/minutes/minutes_todo.go | 2 +- shortcuts/minutes/minutes_update.go | 2 +- shortcuts/minutes/minutes_upload.go | 2 +- shortcuts/minutes/minutes_word_replace.go | 2 +- shortcuts/note/note_detail.go | 2 +- shortcuts/note/note_transcript.go | 2 +- shortcuts/okr/okr_batch_create.go | 2 +- shortcuts/okr/okr_create.go | 2 +- shortcuts/okr/okr_cycle_detail.go | 2 +- shortcuts/okr/okr_cycle_list.go | 2 +- shortcuts/okr/okr_image_upload.go | 2 +- shortcuts/okr/okr_indicator_update.go | 2 +- shortcuts/okr/okr_patch.go | 2 +- shortcuts/okr/okr_progress_create.go | 2 +- shortcuts/okr/okr_progress_delete.go | 2 +- shortcuts/okr/okr_progress_get.go | 2 +- shortcuts/okr/okr_progress_list.go | 2 +- shortcuts/okr/okr_progress_update.go | 2 +- shortcuts/okr/okr_reorder.go | 2 +- shortcuts/okr/okr_weight.go | 2 +- .../sheets/backward/lark_sheets_cell_data.go | 10 +- .../backward/lark_sheets_cell_images.go | 2 +- .../lark_sheets_cell_style_and_merge.go | 8 +- .../sheets/backward/lark_sheets_dropdown.go | 8 +- .../backward/lark_sheets_filter_views.go | 20 +- .../backward/lark_sheets_float_images.go | 12 +- .../lark_sheets_row_column_management.go | 10 +- .../backward/lark_sheets_sheet_management.go | 8 +- .../lark_sheets_spreadsheet_management.go | 6 +- shortcuts/sheets/internal/gen/main.go | 9 + shortcuts/sheets/lark_sheet_batch_update.go | 10 +- shortcuts/sheets/lark_sheet_changeset.go | 2 +- shortcuts/sheets/lark_sheet_formula_verify.go | 2 +- shortcuts/sheets/lark_sheet_history_list.go | 2 +- shortcuts/sheets/lark_sheet_history_revert.go | 4 +- shortcuts/sheets/lark_sheet_object_crud.go | 16 +- shortcuts/sheets/lark_sheet_object_list.go | 2 +- .../sheets/lark_sheet_range_operations.go | 16 +- shortcuts/sheets/lark_sheet_read_data.go | 6 +- shortcuts/sheets/lark_sheet_revision_get.go | 2 +- shortcuts/sheets/lark_sheet_search_replace.go | 4 +- .../sheets/lark_sheet_sheet_structure.go | 14 +- shortcuts/sheets/lark_sheet_table_io.go | 4 +- shortcuts/sheets/lark_sheet_workbook.go | 22 +-- shortcuts/sheets/lark_sheet_write_cells.go | 10 +- shortcuts/slides/slides_add_slide.go | 2 +- shortcuts/slides/slides_create.go | 2 +- shortcuts/slides/slides_delete_slide.go | 2 +- shortcuts/slides/slides_history.go | 6 +- shortcuts/slides/slides_media_upload.go | 2 +- shortcuts/slides/slides_replace_pages.go | 2 +- shortcuts/slides/slides_replace_slide.go | 2 +- shortcuts/slides/slides_screenshot.go | 2 +- shortcuts/slides/slides_update_slide.go | 2 +- shortcuts/slides/slides_xml_get.go | 2 +- shortcuts/task/shortcuts.go | 2 +- shortcuts/task/task_assign.go | 2 +- shortcuts/task/task_comment.go | 2 +- shortcuts/task/task_complete.go | 2 +- shortcuts/task/task_followers.go | 2 +- shortcuts/task/task_get_my_tasks.go | 2 +- shortcuts/task/task_get_related_tasks.go | 2 +- shortcuts/task/task_reminder.go | 2 +- shortcuts/task/task_reopen.go | 2 +- shortcuts/task/task_search.go | 2 +- shortcuts/task/task_set_ancestor.go | 2 +- shortcuts/task/task_tasklist_search.go | 2 +- shortcuts/task/task_update.go | 2 +- shortcuts/task/task_upload_attachment.go | 2 +- shortcuts/task/tasklist_add_task.go | 2 +- shortcuts/task/tasklist_create.go | 2 +- shortcuts/task/tasklist_members.go | 2 +- shortcuts/vc/vc_detail.go | 2 +- shortcuts/vc/vc_meeting_events.go | 2 +- shortcuts/vc/vc_meeting_join.go | 2 +- shortcuts/vc/vc_meeting_leave.go | 2 +- shortcuts/vc/vc_meeting_list_active.go | 2 +- shortcuts/vc/vc_meeting_message_send.go | 2 +- shortcuts/vc/vc_notes.go | 2 +- shortcuts/vc/vc_recording.go | 2 +- shortcuts/vc/vc_search.go | 2 +- shortcuts/whiteboard/whiteboard_export.go | 4 +- shortcuts/whiteboard/whiteboard_update.go | 4 +- shortcuts/wiki/wiki_delete.go | 2 +- shortcuts/wiki/wiki_member_add.go | 2 +- shortcuts/wiki/wiki_member_list.go | 2 +- shortcuts/wiki/wiki_member_remove.go | 2 +- shortcuts/wiki/wiki_member_test.go | 2 +- shortcuts/wiki/wiki_move.go | 2 +- shortcuts/wiki/wiki_move_to_drive.go | 2 +- shortcuts/wiki/wiki_node_copy.go | 2 +- shortcuts/wiki/wiki_node_create.go | 2 +- shortcuts/wiki/wiki_node_delete.go | 2 +- shortcuts/wiki/wiki_node_get.go | 2 +- shortcuts/wiki/wiki_node_list.go | 2 +- shortcuts/wiki/wiki_space_create.go | 2 +- shortcuts/wiki/wiki_space_list.go | 2 +- 424 files changed, 1383 insertions(+), 570 deletions(-) create mode 100644 internal/cmdutil/risk_gate.go create mode 100644 internal/core/risk_test.go create mode 100644 internal/qualitygate/rules/risklit.go create mode 100644 internal/qualitygate/rules/risklit_test.go create mode 100644 shortcuts/common/runner_risk_gate_test.go diff --git a/cmd/api/api.go b/cmd/api/api.go index 4d1a039f1a..742d44e301 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -118,7 +118,7 @@ Examples: cmdutil.RegisterFlagCompletion(cmd, "format", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return []string{"json", "ndjson", "table", "csv"}, cobra.ShellCompDirectiveNoFileComp }) - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/auth/check.go b/cmd/auth/check.go index 41f211174c..f48a46b9b3 100644 --- a/cmd/auth/check.go +++ b/cmd/auth/check.go @@ -48,7 +48,7 @@ func newCmdAuthCheck( cmd.Flags().StringVar(&opts.Scope, "scope", "", "scopes to check (space-separated)") cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output") cmd.MarkFlagRequired("scope") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/auth/list.go b/cmd/auth/list.go index f9666084ff..5f68c4b0ab 100644 --- a/cmd/auth/list.go +++ b/cmd/auth/list.go @@ -46,7 +46,7 @@ func newCmdAuthList( }, } cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 6cbbce2811..db156f2fed 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -69,7 +69,7 @@ to generate QR codes (supports ASCII and PNG formats).`, }, } cmdutil.SetSupportedIdentities(cmd, []string{"user"}) - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) cmd.Flags().StringVar(&opts.Scope, "scope", "", "scopes to request (space- or comma-separated). Combines additively with --domain/--recommend") cmd.Flags().BoolVar(&opts.Recommend, "recommend", false, "request only recommended (auto-approve) scopes") diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index 30803dd67e..157a77fd10 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -36,7 +36,7 @@ func NewCmdAuthLogout(f *cmdutil.Factory, runF func(*LogoutOptions) error) *cobr }, } cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output") - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/auth/scopes.go b/cmd/auth/scopes.go index 91f290f980..9da9f1a8f7 100644 --- a/cmd/auth/scopes.go +++ b/cmd/auth/scopes.go @@ -43,7 +43,7 @@ func NewCmdAuthScopes(f *cmdutil.Factory, runF func(*ScopesOptions) error) *cobr cmd.Flags().StringVar(&opts.Format, "format", "json", "output format: json (default) | pretty") cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/auth/status.go b/cmd/auth/status.go index e022186330..1dd0ae76a3 100644 --- a/cmd/auth/status.go +++ b/cmd/auth/status.go @@ -46,7 +46,7 @@ func newCmdAuthStatus( cmd.Flags().BoolVar(&opts.Verify, "verify", false, "verify token against server (requires network)") cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/completion/completion.go b/cmd/completion/completion.go index 3b2a4b7bd7..d01b75ca5b 100644 --- a/cmd/completion/completion.go +++ b/cmd/completion/completion.go @@ -38,6 +38,6 @@ func NewCmdCompletion(f *cmdutil.Factory) *cobra.Command { }, } cmdutil.DisableAuthCheck(cmd) - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/config/bind.go b/cmd/config/bind.go index 8f239124f3..2ce1bc76b4 100644 --- a/cmd/config/bind.go +++ b/cmd/config/bind.go @@ -116,7 +116,7 @@ Interactive terminal use: run with no flags to enter the TUI form.`, cmd.Flags().StringVar(&opts.Identity, "identity", "", "identity preset (bot-only|user-default); defaults to bot-only in flag mode (safer: no impersonation)") cmd.Flags().BoolVar(&opts.Force, "force", false, "confirm a risky transition (currently: bot-only → user-default identity change in flag mode)") cmd.Flags().StringVar(&opts.Lang, "lang", "", "language preference (e.g. zh or zh_cn)") - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/config/default_as.go b/cmd/config/default_as.go index c0cb23fb2d..fe385bb2f3 100644 --- a/cmd/config/default_as.go +++ b/cmd/config/default_as.go @@ -52,6 +52,6 @@ func NewCmdConfigDefaultAs(f *cmdutil.Factory) *cobra.Command { return nil }, } - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/config/init.go b/cmd/config/init.go index 635474519d..35a181d268 100644 --- a/cmd/config/init.go +++ b/cmd/config/init.go @@ -110,7 +110,7 @@ func NewCmdConfigInit(f *cmdutil.Factory, runF func(*ConfigInitOptions) error) * cmd.Flags().StringVar(&opts.Lang, "lang", "", "language preference (e.g. zh or zh_cn)") cmd.Flags().StringVar(&opts.ProfileName, "name", "", "create or update a named profile (append instead of replace)") cmd.Flags().BoolVar(&opts.ForceInit, "force-init", false, forceInitUsageWithBind) - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/config/keychain_downgrade.go b/cmd/config/keychain_downgrade.go index cf35518588..2ae81404b9 100644 --- a/cmd/config/keychain_downgrade.go +++ b/cmd/config/keychain_downgrade.go @@ -44,7 +44,7 @@ reports "already downgraded" and exits 0.`, return configKeychainDowngradeRun(f) }, } - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/config/plugins.go b/cmd/config/plugins.go index ed98315ca1..d68e6118b1 100644 --- a/cmd/config/plugins.go +++ b/cmd/config/plugins.go @@ -56,7 +56,7 @@ the plugin name as the prefix at registration time, so an entry return runConfigPluginsShow(f) }, } - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/config/policy.go b/cmd/config/policy.go index 609b1bf212..0ee6a5f69b 100644 --- a/cmd/config/policy.go +++ b/cmd/config/policy.go @@ -36,7 +36,7 @@ func newCmdConfigPolicyShow(f *cmdutil.Factory) *cobra.Command { return runConfigPolicyShow(f) }, } - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/config/remove.go b/cmd/config/remove.go index 74dd0e8476..073e626cb8 100644 --- a/cmd/config/remove.go +++ b/cmd/config/remove.go @@ -33,7 +33,7 @@ func NewCmdConfigRemove(f *cmdutil.Factory, runF func(*ConfigRemoveOptions) erro return configRemoveRun(opts) }, } - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/config/show.go b/cmd/config/show.go index 810f3eb914..624ab06ea3 100644 --- a/cmd/config/show.go +++ b/cmd/config/show.go @@ -35,7 +35,7 @@ func NewCmdConfigShow(f *cmdutil.Factory, runF func(*ConfigShowOptions) error) * return configShowRun(opts) }, } - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/config/strict_mode.go b/cmd/config/strict_mode.go index 136fa8cf0d..810d68d583 100644 --- a/cmd/config/strict_mode.go +++ b/cmd/config/strict_mode.go @@ -68,7 +68,7 @@ explicit user confirmation — never run on your own initiative.`, cmd.Flags().BoolVar(&global, "global", false, "set at global level (applies to all profiles)") cmd.Flags().BoolVar(&reset, "reset", false, "reset profile setting to inherit global") - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/doctor/doctor.go b/cmd/doctor/doctor.go index 6e4a621933..681f6c2746 100644 --- a/cmd/doctor/doctor.go +++ b/cmd/doctor/doctor.go @@ -58,7 +58,7 @@ func newCmdDoctor(f *cmdutil.Factory, projector *recovery.Projector) *cobra.Comm } cmdutil.DisableAuthCheck(cmd) cmd.Flags().BoolVar(&opts.Offline, "offline", false, "skip network checks (only verify local state)") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/event/bus.go b/cmd/event/bus.go index 277a95a345..033bb546a2 100644 --- a/cmd/event/bus.go +++ b/cmd/event/bus.go @@ -81,7 +81,7 @@ func NewCmdBus(f *cmdutil.Factory, snap *catalog.Snapshot) *cobra.Command { cmd.Flags().StringVar(&domain, "domain", "", "API domain") _ = cmd.Flags().MarkHidden("domain") - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/event/consume.go b/cmd/event/consume.go index 538e7065c9..fc364620c8 100644 --- a/cmd/event/consume.go +++ b/cmd/event/consume.go @@ -76,7 +76,7 @@ Use 'event schema ' for parameter details.`, _ = cmd.RegisterFlagCompletionFunc("as", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"user", "bot", "auto"}, cobra.ShellCompDirectiveNoFileComp }) - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/event/list.go b/cmd/event/list.go index beeb419099..d4556c2d91 100644 --- a/cmd/event/list.go +++ b/cmd/event/list.go @@ -33,7 +33,7 @@ func NewCmdList(f *cmdutil.Factory, snap *catalog.Snapshot) *cobra.Command { "Only list EventKeys of this domain. Valid domains: %s", strings.Join(snap.Domains(), ", "), )) - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/event/schema.go b/cmd/event/schema.go index 0896ba0b6b..8dd3c4e5b9 100644 --- a/cmd/event/schema.go +++ b/cmd/event/schema.go @@ -29,7 +29,7 @@ func NewCmdSchema(f *cmdutil.Factory, snap *catalog.Snapshot) *cobra.Command { }, } cmd.Flags().BoolVar(&asJSON, "json", false, "Emit the EventKey definition + resolved schema as JSON (for AI / scripts)") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/event/status.go b/cmd/event/status.go index c602998a27..e6ddc48ded 100644 --- a/cmd/event/status.go +++ b/cmd/event/status.go @@ -38,7 +38,7 @@ func NewCmdStatus(f *cmdutil.Factory) *cobra.Command { cmd.Flags().BoolVar(&asJSON, "json", false, "Emit status as JSON (for AI / scripts)") cmd.Flags().BoolVar(¤t, "current", false, "Only show status for the current profile's app") cmd.Flags().BoolVar(&failOnOrphan, "fail-on-orphan", false, "Exit 2 when any orphan bus is detected (default: always exit 0)") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/event/stop.go b/cmd/event/stop.go index 725390c9f2..03e4d4e5ee 100644 --- a/cmd/event/stop.go +++ b/cmd/event/stop.go @@ -70,7 +70,7 @@ Exit code: 2 if any target was refused or errored, 0 otherwise. cmd.Flags().BoolVar(&o.all, "all", false, "Stop all running bus daemons") cmd.Flags().BoolVar(&o.force, "force", false, "Stop even with active consumers; on shutdown-timeout also SIGKILL the bus") cmd.Flags().BoolVar(&o.asJSON, "json", false, "Emit results as JSON (for AI / scripts)") - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/platform_bootstrap_test.go b/cmd/platform_bootstrap_test.go index 7e4f9e4771..e1e876e4fd 100644 --- a/cmd/platform_bootstrap_test.go +++ b/cmd/platform_bootstrap_test.go @@ -74,7 +74,7 @@ func fakeTree(t *testing.T) *cobra.Command { return root } -func addLeaf(parent *cobra.Command, use, risk string) { +func addLeaf(parent *cobra.Command, use string, risk cmdutil.Risk) { leaf := &cobra.Command{ Use: use, RunE: func(*cobra.Command, []string) error { return nil }, diff --git a/cmd/plugin_integration_test.go b/cmd/plugin_integration_test.go index 150cb821ae..a4273cd93b 100644 --- a/cmd/plugin_integration_test.go +++ b/cmd/plugin_integration_test.go @@ -93,7 +93,7 @@ func syntheticTree() (*cobra.Command, *cobra.Command) { Use: "+write", RunE: func(*cobra.Command, []string) error { return nil }, } - cmdutil.SetRisk(leaf, "write") + cmdutil.SetRisk(leaf, cmdutil.RiskWrite) group.AddCommand(leaf) return root, leaf } diff --git a/cmd/profile/add.go b/cmd/profile/add.go index d384c5ba1f..c35fc2d5b6 100644 --- a/cmd/profile/add.go +++ b/cmd/profile/add.go @@ -47,7 +47,7 @@ func NewCmdProfileAdd(f *cmdutil.Factory) *cobra.Command { _ = cmd.MarkFlagRequired("name") _ = cmd.MarkFlagRequired("app-id") - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/profile/list.go b/cmd/profile/list.go index 2f8681d4c2..a6aa7b6af1 100644 --- a/cmd/profile/list.go +++ b/cmd/profile/list.go @@ -42,7 +42,7 @@ func NewCmdProfileList(f *cmdutil.Factory) *cobra.Command { return profileListRun(f) }, } - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/cmd/profile/remove.go b/cmd/profile/remove.go index f937dd8327..6001a9ff6a 100644 --- a/cmd/profile/remove.go +++ b/cmd/profile/remove.go @@ -30,7 +30,7 @@ func NewCmdProfileRemove(f *cmdutil.Factory) *cobra.Command { cmdutil.SetTips(cmd, []string{ "AI agents: Do NOT remove profiles unless the user explicitly asks. This is destructive and clears all associated credentials.", }) - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/profile/rename.go b/cmd/profile/rename.go index 9506870f3b..2fd214fcf4 100644 --- a/cmd/profile/rename.go +++ b/cmd/profile/rename.go @@ -25,7 +25,7 @@ func NewCmdProfileRename(f *cmdutil.Factory) *cobra.Command { return profileRenameRun(f, args[0], args[1]) }, } - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/profile/use.go b/cmd/profile/use.go index 3a5c3c3634..24907738d9 100644 --- a/cmd/profile/use.go +++ b/cmd/profile/use.go @@ -29,7 +29,7 @@ func NewCmdProfileUse(f *cmdutil.Factory) *cobra.Command { cmdutil.SetTips(cmd, []string{ "AI agents: Do NOT switch profiles unless the user explicitly asks.", }) - cmdutil.SetRisk(cmd, "write") + cmdutil.SetRisk(cmd, cmdutil.RiskWrite) return cmd } diff --git a/cmd/prune_test.go b/cmd/prune_test.go index db8f901db4..91f1ba8f80 100644 --- a/cmd/prune_test.go +++ b/cmd/prune_test.go @@ -355,7 +355,7 @@ func TestStrictModeStub_PreservesOriginalMetadata(t *testing.T) { RunE: func(*cobra.Command, []string) error { return nil }, } cmdutil.SetSupportedIdentities(userOnly, []string{"user"}) - cmdutil.SetRisk(userOnly, "read") + cmdutil.SetRisk(userOnly, cmdutil.RiskRead) svc.AddCommand(userOnly) pruneForStrictMode(root, core.StrictModeBot) diff --git a/cmd/root_risk_help_test.go b/cmd/root_risk_help_test.go index e9b22826f2..e00b978526 100644 --- a/cmd/root_risk_help_test.go +++ b/cmd/root_risk_help_test.go @@ -32,7 +32,7 @@ func TestHelpFunc_RendersRiskLineWhenAnnotated(t *testing.T) { installTipsHelpFunc(root, nilSkills, nil, nil) child := &cobra.Command{Use: "delete", Short: "delete a file"} - cmdutil.SetRisk(child, "high-risk-write") + cmdutil.SetRisk(child, cmdutil.RiskHighRiskWrite) root.AddCommand(child) out := rendersHelp(t, child) @@ -59,7 +59,7 @@ func TestHelpFunc_RiskLinePrecedesTips(t *testing.T) { installTipsHelpFunc(root, nilSkills, nil, nil) child := &cobra.Command{Use: "delete", Short: "delete a file"} - cmdutil.SetRisk(child, "high-risk-write") + cmdutil.SetRisk(child, cmdutil.RiskHighRiskWrite) cmdutil.SetTips(child, []string{"use --yes to confirm"}) root.AddCommand(child) diff --git a/cmd/service/affordance_test.go b/cmd/service/affordance_test.go index 48476ad0b7..ffdaee5cf9 100644 --- a/cmd/service/affordance_test.go +++ b/cmd/service/affordance_test.go @@ -197,7 +197,7 @@ func TestPrepareShortcutHelp(t *testing.T) { sc := &cobra.Command{Use: "+create", Short: "Create an event"} cmdmeta.SetSource(sc, cmdmeta.SourceShortcut, false) cmdmeta.SetAffordanceRef(sc, "calendar", "+create") - cmdutil.SetRisk(sc, "write") + cmdutil.SetRisk(sc, cmdutil.RiskWrite) cmdutil.SetTips(sc, []string{"start/end 收 ISO 8601"}) if !PrepareShortcutHelp(sc, nil) { diff --git a/cmd/service/service.go b/cmd/service/service.go index 28d2711973..b8c0cba631 100644 --- a/cmd/service/service.go +++ b/cmd/service/service.go @@ -190,7 +190,7 @@ type methodCommandSpec struct { method meta.Method schemaPath string // "service.resource.method", for the --help hint servicePath string // service HTTP base path - risk string // RiskRead | RiskWrite | RiskHighRiskWrite + risk cmdutil.Risk // RiskRead | RiskWrite | RiskHighRiskWrite; see newMethodCommandSpec for the string boundary restricts bool // method declares accessTokens (identity-restricted) identities []string // permitted --as values; empty when unrestricted params []meta.Field // path/query params -> typed flags @@ -206,6 +206,22 @@ type methodCommandSpec struct { serviceName string // owning service name (e.g. "approval"), for the lazy affordance lookup } +// methodRisk converts the generated catalog's risk string into the typed +// taxonomy. The catalog is generated data, so this is a string boundary and +// goes through core.ParseRisk rather than a bare conversion. +// +// A value outside the taxonomy is kept as-is instead of being normalised +// away: the command then carries an invalid declaration, which the runtime +// gate (cmdutil.EnforceRiskDeclaration) refuses. Substituting a default here +// would silently repair a catalog bug at the worst possible place — the one +// that decides whether a destructive call needs confirmation. +func methodRisk(m meta.Method) cmdutil.Risk { + if risk, err := core.ParseRisk(m.Risk); err == nil { + return risk + } + return cmdutil.Risk(m.Risk) +} + // methodPaginates reports whether a method takes a page_token param, the signal // that makes the --page-all/--page-limit/--page-delay flags meaningful. func methodPaginates(m meta.Method) bool { @@ -224,7 +240,7 @@ func newMethodCommandSpec(ref apicatalog.MethodRef) methodCommandSpec { schemaPath: ref.SchemaPath(), servicePath: ref.Service.ServicePath, serviceName: ref.Service.Name, - risk: m.Risk, + risk: methodRisk(m), restricts: m.RestrictsIdentity(), identities: m.Identities(), params: m.Params(), @@ -304,7 +320,7 @@ func buildMethodCommand(ctx context.Context, f *cmdutil.Factory, spec methodComm cmd.Flags().Bool("json", false, "shorthand for --format json") cmd.Flags().StringVarP(&opts.JqExpr, "jq", "q", "", "jq expression to filter JSON output") cmd.Flags().BoolVar(&opts.DryRun, "dry-run", false, "print request without executing") - if spec.risk == cmdutil.RiskHighRiskWrite { + if cmdutil.RequiresConfirmation(spec.risk) { cmd.Flags().Bool("yes", false, "confirm high-risk operation") } // --file only for body methods that actually declare file-type fields. @@ -409,7 +425,11 @@ func serviceMethodRun(opts *ServiceMethodOptions) error { return serviceDryRun(f, request, config, opts) } - if opts.Method.Risk == cmdutil.RiskHighRiskWrite { + risk := methodRisk(opts.Method) + if err := cmdutil.EnforceRiskDeclaration(opts.SchemaPath, risk); err != nil { + return err + } + if cmdutil.RequiresConfirmation(risk) { if yes, _ := opts.Cmd.Flags().GetBool("yes"); !yes { return cmdutil.RequireConfirmation(opts.SchemaPath) } diff --git a/cmd/skill/skill.go b/cmd/skill/skill.go index 351dda7771..a0d73d8ba0 100644 --- a/cmd/skill/skill.go +++ b/cmd/skill/skill.go @@ -94,7 +94,7 @@ func newListCmd(f *cmdutil.Factory) *cobra.Command { } // --json is a no-op (list is always JSON), accepted only to stay symmetric with read. cmd.Flags().Bool("json", false, "no-op (list output is always JSON)") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) cmdutil.DisableAuthCheck(cmd) return cmd } @@ -151,7 +151,7 @@ func newReadCmd(f *cmdutil.Factory) *cobra.Command { }, } cmd.Flags().BoolVar(&asJSON, "json", false, "output as a JSON envelope instead of raw markdown") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) cmdutil.DisableAuthCheck(cmd) return cmd } diff --git a/cmd/update/update.go b/cmd/update/update.go index 750941f9e8..b3ed18bfae 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -118,7 +118,7 @@ Use --check to only check for updates without installing.`, cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output") cmd.Flags().BoolVar(&opts.Force, "force", false, "force reinstall even if already up to date") cmd.Flags().BoolVar(&opts.Check, "check", false, "only check for updates, do not install") - cmdutil.SetRisk(cmd, "high-risk-write") + cmdutil.SetRisk(cmd, cmdutil.RiskHighRiskWrite) return cmd } diff --git a/cmd/whoami/whoami.go b/cmd/whoami/whoami.go index 5fcf7126d7..f64fccf662 100644 --- a/cmd/whoami/whoami.go +++ b/cmd/whoami/whoami.go @@ -80,7 +80,7 @@ func newCmdWhoami(f *cmdutil.Factory, projector *recovery.Projector) *cobra.Comm // mode exists. cmd.Flags().Bool("json", true, "deprecated: output is always JSON") _ = cmd.Flags().MarkHidden("json") - cmdutil.SetRisk(cmd, "read") + cmdutil.SetRisk(cmd, cmdutil.RiskRead) return cmd } diff --git a/errs/subtypes.go b/errs/subtypes.go index a3968ac2d2..2dc636f172 100644 --- a/errs/subtypes.go +++ b/errs/subtypes.go @@ -84,6 +84,11 @@ const ( SubtypeFileIO Subtype = "file_io" // local file I/O failure (mkdir / write / read) SubtypeExternalTool Subtype = "external_tool" // an external tool the CLI shells out to (git, npx) failed at runtime; the tool output is in the message SubtypeStorage Subtype = "storage" // local persistence failure (e.g. config file save) + // SubtypeInvalidRiskDeclaration marks a command whose declared risk level + // is outside the closed read|write|high-risk-write taxonomy. It is a + // declaration bug, not a user error: the command is refused rather than + // run at an unknown risk level. + SubtypeInvalidRiskDeclaration Subtype = "invalid_risk_declaration" // Generic untyped error lifted to InternalError uses SubtypeUnknown. ) diff --git a/extension/platform/risk.go b/extension/platform/risk.go index 287c5ff8ae..6892e98e0e 100644 --- a/extension/platform/risk.go +++ b/extension/platform/risk.go @@ -3,7 +3,7 @@ package platform -import "fmt" +import "github.com/larksuite/cli/internal/core" // Risk is the three-tier risk taxonomy declared on every command. // @@ -12,21 +12,25 @@ import "fmt" // Crossing the string boundary (yaml, cobra annotation) goes through // ParseRisk so typos surface as `risk_invalid` rather than silently // flowing through. +// +// This stays a plugin-SDK type of its own — the exported signature does not +// change — but the taxonomy itself is no longer defined twice: the constants +// below are derived from internal/core, and Core/FromCore convert between the +// two. A consistency test pins the two value sets together. type Risk string const ( - RiskRead Risk = "read" - RiskWrite Risk = "write" - RiskHighRiskWrite Risk = "high-risk-write" + RiskRead = Risk(core.RiskRead) + RiskWrite = Risk(core.RiskWrite) + RiskHighRiskWrite = Risk(core.RiskHighRiskWrite) ) -// riskOrder maps the Risk taxonomy to a comparable rank. The pruning -// engine compares ranks for the MaxRisk axis. -var riskOrder = map[Risk]int{ - RiskRead: 0, - RiskWrite: 1, - RiskHighRiskWrite: 2, -} +// Core converts to the internal taxonomy. The two types carry identical +// values, so the conversion is total in both directions. +func (r Risk) Core() core.Risk { return core.Risk(r) } + +// FromCore converts an internal Risk into the SDK-facing type. +func FromCore(r core.Risk) Risk { return Risk(r) } // ParseRisk converts a raw string (yaml, cobra annotation) into a Risk. // @@ -43,28 +47,19 @@ var riskOrder = map[Risk]int{ // annotation is developer code, not user input — strict matching is // the typo-catch mechanism, not a normalisation opportunity. func ParseRisk(s string) (Risk, error) { - if s == "" { - return "", nil + r, err := core.ParseRisk(s) + if err != nil { + return "", err } - r := Risk(s) - if _, ok := riskOrder[r]; !ok { - return "", fmt.Errorf("invalid risk %q: must be read|write|high-risk-write", s) - } - return r, nil + return Risk(r), nil } // IsValid reports whether r is one of the three recognised values. -func (r Risk) IsValid() bool { - _, ok := riskOrder[r] - return ok -} +func (r Risk) IsValid() bool { return core.Risk(r).IsValid() } // Rank returns the comparable rank of r. ok=false when r is not in the -// closed taxonomy. -func (r Risk) Rank() (rank int, ok bool) { - rank, ok = riskOrder[r] - return rank, ok -} +// closed taxonomy. The pruning engine compares ranks for the MaxRisk axis. +func (r Risk) Rank() (rank int, ok bool) { return core.Risk(r).Rank() } // String returns the underlying string. Useful for yaml/json output // and cobra annotation injection. diff --git a/internal/cmdmeta/meta.go b/internal/cmdmeta/meta.go index 81d7624cd3..535e06a882 100644 --- a/internal/cmdmeta/meta.go +++ b/internal/cmdmeta/meta.go @@ -65,7 +65,7 @@ const ( // engine and hook selectors. type Meta struct { Domain string - Risk string + Risk cmdutil.Risk Identities []string } @@ -208,7 +208,7 @@ func Generated(cmd *cobra.Command) bool { // ok=false signals "unknown" -- the policy engine treats this as // fail-closed (deny with risk_not_annotated) whenever a Rule without // AllowUnannotated=true is active, and as allow otherwise. -func Risk(cmd *cobra.Command) (level string, ok bool) { +func Risk(cmd *cobra.Command) (level cmdutil.Risk, ok bool) { for c := cmd; c != nil; c = c.Parent() { if level, ok = cmdutil.GetRisk(c); ok { return level, true diff --git a/internal/cmdpolicy/engine.go b/internal/cmdpolicy/engine.go index 2624f00177..c9640375c6 100644 --- a/internal/cmdpolicy/engine.go +++ b/internal/cmdpolicy/engine.go @@ -134,8 +134,8 @@ func (e *Engine) EvaluateOne(cmd *cobra.Command) Decision { // // The "absent" case (no risk_level annotation at all) is per-rule: // each rule's AllowUnannotated decides, so it lives inside evalRule. - cmdRiskStr, hasRisk := cmdmeta.Risk(cmd) - cmdRisk := platform.Risk(cmdRiskStr) + cmdRiskRaw, hasRisk := cmdmeta.Risk(cmd) + cmdRisk := platform.FromCore(cmdRiskRaw) var ( cmdRank int cmdRankOk bool @@ -146,7 +146,7 @@ func (e *Engine) EvaluateOne(cmd *cobra.Command) Decision { return Decision{ Allowed: false, ReasonCode: "risk_invalid", - Reason: fmt.Sprintf("invalid risk %q; did you mean %q?", cmdRiskStr, suggestRisk(cmdRiskStr)), + Reason: fmt.Sprintf("invalid risk %q; did you mean %q?", cmdRiskRaw, suggestRisk(cmdRiskRaw.String())), } } } diff --git a/internal/cmdutil/risk.go b/internal/cmdutil/risk.go index 29ce402bb7..4b917efc53 100644 --- a/internal/cmdutil/risk.go +++ b/internal/cmdutil/risk.go @@ -10,6 +10,11 @@ import ( const riskLevelAnnotationKey = "risk_level" +// Risk is re-exported from core so command code gets the risk vocabulary, the +// type and the SetRisk/GetRisk helpers from one package. core stays the single +// source of truth — this is an alias, not a second type. +type Risk = core.Risk + // Risk level constants — aliases of the canonical core.Risk* values, re-exported // here so command code gets the risk vocabulary and the SetRisk/GetRisk helpers // from one package. core is the single source of truth. @@ -24,22 +29,32 @@ const ( // shortcuts/common. Levels follow the three-tier convention: RiskRead | // RiskWrite | RiskHighRiskWrite. Framework-level confirmation gating only // acts on RiskHighRiskWrite. -func SetRisk(cmd *cobra.Command, level string) { +// +// The parameter is typed: a misspelled level cannot reach the annotation from +// Go code. Values arriving as strings (generated service metadata, plugin +// manifests) must go through core.ParseRisk at that boundary. +func SetRisk(cmd *cobra.Command, level Risk) { if level == "" { return } if cmd.Annotations == nil { cmd.Annotations = map[string]string{} } - cmd.Annotations[riskLevelAnnotationKey] = level + cmd.Annotations[riskLevelAnnotationKey] = level.String() } // GetRisk returns the static risk level. ok is true when the command has a // risk annotation. -func GetRisk(cmd *cobra.Command) (level string, ok bool) { +// +// The annotation map is a string boundary, so the stored value is not +// necessarily in the closed taxonomy — a command mounted from generated +// metadata or replaced by a policy stub can carry anything. ok only reports +// presence; callers that gate on the value must check level.IsValid() and +// fail closed, never treat an unrecognised level as the lowest tier. +func GetRisk(cmd *cobra.Command) (level Risk, ok bool) { if cmd.Annotations == nil { return "", false } - level, ok = cmd.Annotations[riskLevelAnnotationKey] - return level, ok && level != "" + raw, ok := cmd.Annotations[riskLevelAnnotationKey] + return Risk(raw), ok && raw != "" } diff --git a/internal/cmdutil/risk_gate.go b/internal/cmdutil/risk_gate.go new file mode 100644 index 0000000000..a56a65bb3f --- /dev/null +++ b/internal/cmdutil/risk_gate.go @@ -0,0 +1,77 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package cmdutil + +import ( + "os" + "strings" + + "github.com/larksuite/cli/errs" + "github.com/larksuite/cli/internal/envvars" +) + +// The runtime half of the risk-declaration defence. The type (core.Risk) stops +// a bare string from reaching a declaration, and the manifest CI rule rejects +// a value outside the enum before it ships — but neither covers a value that +// arrives at runtime through a string boundary: generated service metadata, a +// cobra annotation rewritten by a policy stub, a hand-built command in a test. +// This is where such a value is caught, and it fails closed: an unrecognised +// level is never treated as `read`. +// +// The rules: +// +// - declared high-risk-write → confirmation gate (--yes) +// - declared outside the taxonomy → refuse to execute; it is a declaration +// bug, and running the command is the one outcome that cannot be undone +// - refusal downgraded via env → treat as the highest tier, i.e. still +// gated on --yes, never allowed through silently +// - absent / read / write → no gate + +// InvalidRiskDeclaration reports whether level is present but outside the +// closed taxonomy. An absent level ("") is a legal state — it means the +// command is not annotated and defaults to read — and is deliberately not +// reported here, so "unannotated" and "misspelled" never collapse into one +// branch. +func InvalidRiskDeclaration(level Risk) bool { + return level != "" && !level.IsValid() +} + +// RequiresConfirmation reports whether a declared level must pass the --yes +// gate. Invalid levels are included: if the refusal is downgraded they still +// have to be confirmed, and if it is not, EnforceRiskDeclaration has already +// rejected the call. +func RequiresConfirmation(level Risk) bool { + return level == RiskHighRiskWrite || InvalidRiskDeclaration(level) +} + +// EnforceRiskDeclaration returns a non-nil error when a command must not run +// because its declared risk level is not in the closed taxonomy. Call it +// before the confirmation gate; a nil return means the declaration is usable +// (possibly after being downgraded to the highest tier by the env switch). +// +// action identifies the operation for the agent, in the same shape used by +// RequireConfirmation ("drive +delete", "drive.files.delete"). +func EnforceRiskDeclaration(action string, level Risk) error { + if !InvalidRiskDeclaration(level) { + return nil + } + if allowInvalidRisk() { + return nil + } + return errs.NewInternalError(errs.SubtypeInvalidRiskDeclaration, + "%s declares risk %q, which is not one of read|write|high-risk-write", action, level). + WithHint("this is a bug in the command declaration; report it. Set %s=1 to run the command under the highest-risk confirmation gate instead", envvars.CliAllowInvalidRisk) +} + +// allowInvalidRisk reports whether the operator opted into the downgrade. Any +// unrecognised value counts as "not set": an escape hatch that fails open on +// a typo would reintroduce the bug it exists to work around. +func allowInvalidRisk() bool { + switch strings.TrimSpace(strings.ToLower(os.Getenv(envvars.CliAllowInvalidRisk))) { + case "1", "true", "on", "yes", "y": + return true + default: + return false + } +} diff --git a/internal/cmdutil/risk_test.go b/internal/cmdutil/risk_test.go index 760e004e4c..3e5e2505db 100644 --- a/internal/cmdutil/risk_test.go +++ b/internal/cmdutil/risk_test.go @@ -18,9 +18,9 @@ func TestSetRisk_EmptyLevelShortCircuits(t *testing.T) { } func TestSetRisk_PopulatesLevel(t *testing.T) { - cases := []string{"read", "write", "high-risk-write"} + cases := []Risk{RiskRead, RiskWrite, RiskHighRiskWrite} for _, level := range cases { - t.Run(level, func(t *testing.T) { + t.Run(level.String(), func(t *testing.T) { cmd := &cobra.Command{Use: "test"} SetRisk(cmd, level) got, ok := GetRisk(cmd) diff --git a/internal/core/risk.go b/internal/core/risk.go index 4c9014010b..ea50d9d878 100644 --- a/internal/core/risk.go +++ b/internal/core/risk.go @@ -3,13 +3,91 @@ package core +import "fmt" + +// Risk is the three-tier risk taxonomy declared on every command. +// +// A defined type (not an alias of string) so no `string` value can reach a +// declaration without a conversion, and so editors offer the three constants. +// Note what the type does NOT do: an untyped literal still converts +// implicitly, so `Risk: "high-risk-wrtie"` compiles. Keeping typos out is +// therefore a three-layer job — the type here, the quality-gate rules that +// reject a hand-written literal and a manifest value outside the enum, and +// the runtime gate (cmdutil.EnforceRiskDeclaration) that refuses to run a +// command whose level it does not recognise. +// +// Crossing a string boundary — cobra annotations, generated service metadata, +// plugin manifests — goes through ParseRisk so a bad value surfaces as an +// error instead of flowing through as the lowest tier. +// +// This is the single source of truth for the taxonomy. internal/cmdutil +// re-exports these constants for command code, extension/platform mirrors +// them for the plugin SDK (its own defined type, converted here), and errs +// carries the wire-level strings; all three are pinned to these values by +// consistency tests. +type Risk string + // Risk levels — the three-tier convention used across the CLI. They live here, // at the leaf, so the envelope renderer (internal/schema) and the command // toolkit (internal/cmdutil) share one vocabulary without a renderer depending // on command utilities. Framework confirmation gating acts only on // RiskHighRiskWrite. const ( - RiskRead = "read" - RiskWrite = "write" - RiskHighRiskWrite = "high-risk-write" + RiskRead Risk = "read" + RiskWrite Risk = "write" + RiskHighRiskWrite Risk = "high-risk-write" ) + +// riskOrder maps the taxonomy to a comparable rank. Absence from this map is +// what makes a value invalid — the map is the closed enum. +var riskOrder = map[Risk]int{ + RiskRead: 0, + RiskWrite: 1, + RiskHighRiskWrite: 2, +} + +// ParseRisk converts a raw string (cobra annotation, generated metadata, +// plugin manifest) into a Risk. +// +// - s == "" → ("", nil) "not specified" +// - s in the enum → (Risk(s), nil) OK +// - anything else → ("", error) invalid +// +// The absent-vs-invalid split mirrors the cmdpolicy engine's +// risk_not_annotated vs risk_invalid reason codes: callers can treat the +// ("", nil) case as "not specified" without losing the distinction from a +// typo, which is a code bug and must never be silently downgraded. +// +// Matching is strict: "Read" / "READ" / " read " are all rejected. These +// values come from developer code and generated metadata, not from user +// input — strict matching is the typo-catch mechanism, not a normalisation +// opportunity. +func ParseRisk(s string) (Risk, error) { + if s == "" { + return "", nil + } + r := Risk(s) + if _, ok := riskOrder[r]; !ok { + return "", fmt.Errorf("invalid risk %q: must be read|write|high-risk-write", s) + } + return r, nil +} + +// IsValid reports whether r is one of the three recognised values. The empty +// value is not valid — callers that accept "not specified" must check for it +// separately, so absent and invalid never collapse into one branch. +func (r Risk) IsValid() bool { + _, ok := riskOrder[r] + return ok +} + +// Rank returns the comparable rank of r. ok=false when r is outside the +// closed taxonomy. +func (r Risk) Rank() (rank int, ok bool) { + rank, ok = riskOrder[r] + return rank, ok +} + +// String returns the underlying string, for annotation injection and +// json/yaml output. +func (r Risk) String() string { return string(r) } diff --git a/internal/core/risk_test.go b/internal/core/risk_test.go new file mode 100644 index 0000000000..c05b4f015e --- /dev/null +++ b/internal/core/risk_test.go @@ -0,0 +1,122 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package core_test + +import ( + "testing" + + "github.com/larksuite/cli/errs" + "github.com/larksuite/cli/extension/platform" + "github.com/larksuite/cli/internal/core" +) + +// ParseRisk keeps three outcomes apart: absent, valid, invalid. Collapsing +// absent into invalid would make every unannotated command a declaration bug; +// collapsing invalid into absent is the fail-open this whole change exists to +// remove. +func TestParseRisk(t *testing.T) { + for _, tc := range []struct { + name string + in string + want core.Risk + wantErr bool + }{ + {name: "absent", in: "", want: ""}, + {name: "read", in: "read", want: core.RiskRead}, + {name: "write", in: "write", want: core.RiskWrite}, + {name: "high risk write", in: "high-risk-write", want: core.RiskHighRiskWrite}, + {name: "transposed letters", in: "high-risk-wrtie", wantErr: true}, + {name: "capitalised", in: "Read", wantErr: true}, + {name: "upper case", in: "READ", wantErr: true}, + {name: "padded", in: " read ", wantErr: true}, + {name: "unknown tier", in: "danger", wantErr: true}, + } { + t.Run(tc.name, func(t *testing.T) { + got, err := core.ParseRisk(tc.in) + if tc.wantErr { + if err == nil { + t.Fatalf("ParseRisk(%q) = (%q, nil), want an error", tc.in, got) + } + if got != "" { + t.Errorf("ParseRisk(%q) returned %q alongside the error, want the zero value so a caller that ignores err cannot use it", tc.in, got) + } + return + } + if err != nil { + t.Fatalf("ParseRisk(%q) returned error %v", tc.in, err) + } + if got != tc.want { + t.Errorf("ParseRisk(%q) = %q, want %q", tc.in, got, tc.want) + } + }) + } +} + +// A misspelled level must never rank as a valid tier — Rank's ok=false is what +// makes callers fail closed instead of comparing against rank 0 (read). +func TestRiskRankAndValidity(t *testing.T) { + ranks := map[core.Risk]int{core.RiskRead: 0, core.RiskWrite: 1, core.RiskHighRiskWrite: 2} + for level, want := range ranks { + if !level.IsValid() { + t.Errorf("%q.IsValid() = false, want true", level) + } + got, ok := level.Rank() + if !ok || got != want { + t.Errorf("%q.Rank() = (%d,%v), want (%d,true)", level, got, ok, want) + } + } + for _, bad := range []core.Risk{"", "high-risk-wrtie", "READ", "danger"} { + if bad.IsValid() { + t.Errorf("%q.IsValid() = true, want false", bad) + } + if _, ok := bad.Rank(); ok { + t.Errorf("%q.Rank() reported ok=true, want false", bad) + } + } +} + +// The taxonomy is declared in three places for three audiences: core (the +// truth), platform (the plugin SDK's own type, so the SDK does not export an +// internal one) and errs (the wire strings on the confirmation envelope, +// which cannot import core without an import cycle). They must not drift. +func TestRiskTaxonomyIsConsistentAcrossPackages(t *testing.T) { + pairs := []struct { + core core.Risk + platform platform.Risk + wire string + }{ + {core.RiskRead, platform.RiskRead, errs.RiskRead}, + {core.RiskWrite, platform.RiskWrite, errs.RiskWrite}, + {core.RiskHighRiskWrite, platform.RiskHighRiskWrite, errs.RiskHighRiskWrite}, + } + for _, p := range pairs { + if string(p.platform) != string(p.core) { + t.Errorf("platform risk %q != core risk %q", p.platform, p.core) + } + if p.wire != string(p.core) { + t.Errorf("errs wire risk %q != core risk %q", p.wire, p.core) + } + if p.platform.Core() != p.core { + t.Errorf("%q.Core() = %q, want %q", p.platform, p.platform.Core(), p.core) + } + if platform.FromCore(p.core) != p.platform { + t.Errorf("FromCore(%q) = %q, want %q", p.core, platform.FromCore(p.core), p.platform) + } + } + + // Value sets must be identical, not merely overlapping: a value one side + // accepts and the other rejects is exactly the gap a plugin could fall + // into. + for _, s := range []string{"read", "write", "high-risk-write", "high-risk-wrtie", "unknown", ""} { + coreRisk, coreErr := core.ParseRisk(s) + platformRisk, platformErr := platform.ParseRisk(s) + if (coreErr == nil) != (platformErr == nil) { + t.Errorf("ParseRisk(%q): core err=%v, platform err=%v — the two taxonomies disagree", s, coreErr, platformErr) + continue + } + if string(coreRisk) != string(platformRisk) { + t.Errorf("ParseRisk(%q) = core %q, platform %q", s, coreRisk, platformRisk) + } + } +} diff --git a/internal/envvars/envvars.go b/internal/envvars/envvars.go index 16419e4db4..8f5a664acf 100644 --- a/internal/envvars/envvars.go +++ b/internal/envvars/envvars.go @@ -20,6 +20,13 @@ const ( // Content safety scanning mode CliContentSafetyMode = "LARKSUITE_CLI_CONTENT_SAFETY_MODE" + // Escape hatch for the risk-declaration gate: when set to a truthy value, + // a command whose declared risk is outside the closed taxonomy is treated + // as the highest tier (confirmation still required) instead of being + // refused outright. It can only soften "refuse" into "confirm" — there is + // no value that lets an unrecognised risk run unconfirmed. + CliAllowInvalidRisk = "LARKSUITE_CLI_ALLOW_INVALID_RISK" + CliAgentName = "LARKSUITE_CLI_AGENT_NAME" CliAgentTrace = "LARKSUITE_CLI_AGENT_TRACE" diff --git a/internal/qualitygate/cmd/manifest-export/collect.go b/internal/qualitygate/cmd/manifest-export/collect.go index 35adef8a79..47b5963a13 100644 --- a/internal/qualitygate/cmd/manifest-export/collect.go +++ b/internal/qualitygate/cmd/manifest-export/collect.go @@ -102,7 +102,7 @@ func commandFromCobra(c *cobra.Command, defaultFields map[string][]string) manif DefaultFields: defaultFields[path], } if risk, ok := cmdmeta.Risk(c); ok { - entry.Risk = risk + entry.Risk = risk.String() } c.Flags().VisitAll(func(f *pflag.Flag) { diff --git a/internal/qualitygate/manifest/io_test.go b/internal/qualitygate/manifest/io_test.go index 497749cd4d..5de160fdf0 100644 --- a/internal/qualitygate/manifest/io_test.go +++ b/internal/qualitygate/manifest/io_test.go @@ -79,6 +79,33 @@ func TestValidateRejectsInvalidSource(t *testing.T) { } } +// The manifest is exported from the live command tree, so this check sees +// every mounted command's real annotation — including the ones whose risk +// arrived as a string and never met the Go type. +func TestValidateRejectsRiskOutsideTaxonomy(t *testing.T) { + for _, risk := range []string{"high-risk-wrtie", "Read", "READ", "danger", " read "} { + m := Manifest{SchemaVersion: 1, Commands: []Command{ + {Path: "docs +fetch", CanonicalPath: "docs +fetch", Source: SourceShortcut, Risk: risk}, + }} + if err := m.Validate(KindCommandManifest); err == nil { + t.Errorf("risk %q passed validation, want rejection", risk) + } + } +} + +func TestValidateAcceptsTaxonomyRisks(t *testing.T) { + // Empty stays legal: an unannotated command is a known, separate state + // (the policy engine reports it as risk_not_annotated). + for _, risk := range []string{"", "read", "write", "high-risk-write"} { + m := Manifest{SchemaVersion: 1, Commands: []Command{ + {Path: "docs +fetch", CanonicalPath: "docs +fetch", Source: SourceShortcut, Risk: risk}, + }} + if err := m.Validate(KindCommandManifest); err != nil { + t.Errorf("risk %q rejected: %v", risk, err) + } + } +} + func TestReadFileValidatesInput(t *testing.T) { path := filepath.Join(t.TempDir(), "manifest.json") if err := os.WriteFile(path, []byte(`{"schema_version":999,"commands":[]}`), 0o644); err != nil { diff --git a/internal/qualitygate/manifest/schema.go b/internal/qualitygate/manifest/schema.go index 14fba314a0..f274bcef35 100644 --- a/internal/qualitygate/manifest/schema.go +++ b/internal/qualitygate/manifest/schema.go @@ -6,6 +6,8 @@ package manifest import ( "fmt" "strings" + + "github.com/larksuite/cli/internal/core" ) type Source string @@ -131,6 +133,15 @@ func validateCommand(kind string, i int, cmd Command) error { if err := validateString(prefix+".risk", cmd.Risk, false); err != nil { return err } + // risk is a closed enum, not free text. The manifest is exported from the + // live command tree, so this is the check that sees every mounted + // command's real annotation — including commands whose risk arrived as a + // string (generated service metadata) and therefore never met the Go + // type. A misspelled level fails the gate here instead of silently + // downgrading a destructive command to the lowest tier at runtime. + if _, err := core.ParseRisk(cmd.Risk); err != nil { + return fmt.Errorf("%s.risk is invalid: %q must be one of read|write|high-risk-write", prefix, cmd.Risk) + } switch cmd.Source { case SourceBuiltin, SourceShortcut, SourceService: default: diff --git a/internal/qualitygate/rules/risklit.go b/internal/qualitygate/rules/risklit.go new file mode 100644 index 0000000000..56e508fa16 --- /dev/null +++ b/internal/qualitygate/rules/risklit.go @@ -0,0 +1,148 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package rules + +import ( + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + + "github.com/larksuite/cli/internal/qualitygate/report" + "github.com/larksuite/cli/internal/vfs" +) + +// RuleRiskLiteral is the rule id reported for a hand-written risk level. +const RuleRiskLiteral = "risk-literal" + +// CheckRiskLiterals rejects string literals used as a command's risk level. +// +// Why a rule and not just the type: core.Risk is a defined string type, so an +// untyped literal still converts implicitly — `Risk: "high-risk-wrtie"` +// compiles. The type stops a `string` variable from flowing in and gives the +// IDE the candidate list, but only this check keeps the taxonomy flowing +// through the constants, where a typo is a build error at the use site. +// +// Scope is every non-test Go file under cmd/ and shortcuts/, scanned in full +// rather than incrementally: the tree is at zero occurrences after the +// literal migration, so there is no baseline to carry and no way for an +// untouched file to drift back. +func CheckRiskLiterals(repo string) ([]report.Diagnostic, error) { + paths, err := riskLiteralFiles(repo) + if err != nil { + return nil, err + } + var diags []report.Diagnostic + for _, path := range paths { + src, err := vfs.ReadFile(filepath.Join(repo, filepath.FromSlash(path))) + if err != nil { + return nil, err + } + diags = append(diags, riskLiteralsInFile(path, string(src))...) + } + return diags, nil +} + +// generatedHeader is the Go convention marking machine-written source. +var generatedHeader = regexp.MustCompile(`(?m)^// Code generated .* DO NOT EDIT\.$`) + +func riskLiteralsInFile(path, src string) []report.Diagnostic { + // Generated files are not where a human writes a declaration: the rule + // would report the emitter's output instead of its input, and the fix + // would be overwritten by the next `go generate`. The generator validates + // its own source data against the enum. + if generatedHeader.MatchString(src) { + return nil + } + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, path, src, 0) + if err != nil || file == nil { + // A file that does not parse is not this rule's problem: the build + // and vet report it with a far better message. + return nil + } + var diags []report.Diagnostic + add := func(pos token.Pos, literal, where string) { + diags = append(diags, riskLiteralDiagnostic(path, fset.Position(pos).Line, literal, where)) + } + ast.Inspect(file, func(n ast.Node) bool { + switch node := n.(type) { + case *ast.KeyValueExpr: + // `Risk: "read"` in any struct literal. + key, ok := node.Key.(*ast.Ident) + if !ok || key.Name != "Risk" { + return true + } + if lit, ok := stringLiteral(node.Value); ok { + add(node.Value.Pos(), lit, "a Risk field") + } + case *ast.CallExpr: + // `SetRisk(cmd, "read")` / `cmdutil.SetRisk(cmd, "read")`. + if !isSetRiskCall(node.Fun) || len(node.Args) != 2 { + return true + } + if lit, ok := stringLiteral(node.Args[1]); ok { + add(node.Args[1].Pos(), lit, "SetRisk") + } + } + return true + }) + return diags +} + +func riskLiteralDiagnostic(path string, line int, literal, where string) report.Diagnostic { + return report.Diagnostic{ + Rule: RuleRiskLiteral, + Action: report.ActionReject, + File: path, + Line: line, + Message: "risk level " + strconv.Quote(literal) + " is written as a string literal in " + where, + Suggestion: "use the constants (common.RiskRead / common.RiskWrite / common.RiskHighRiskWrite, or the cmdutil.Risk* equivalents) so a typo fails the build instead of silently downgrading the command", + } +} + +func isSetRiskCall(fun ast.Expr) bool { + switch f := fun.(type) { + case *ast.Ident: + return f.Name == "SetRisk" + case *ast.SelectorExpr: + return f.Sel != nil && f.Sel.Name == "SetRisk" + } + return false +} + +func stringLiteral(e ast.Expr) (string, bool) { + lit, ok := e.(*ast.BasicLit) + if !ok || lit.Kind != token.STRING { + return "", false + } + value, err := strconv.Unquote(lit.Value) + if err != nil { + return "", false + } + return value, true +} + +func riskLiteralFiles(repo string) ([]string, error) { + var out []string + for _, root := range []string{"cmd", "shortcuts"} { + // A missing root is not a failure: the quality gate also runs against + // fixture trees that contain only the manifest under test. + if _, err := vfs.Stat(filepath.Join(repo, root)); err != nil { + if os.IsNotExist(err) { + continue + } + return nil, err + } + if err := walkErrorFactFiles(repo, root, &out); err != nil { + return nil, err + } + } + sort.Strings(out) + return out, nil +} diff --git a/internal/qualitygate/rules/risklit_test.go b/internal/qualitygate/rules/risklit_test.go new file mode 100644 index 0000000000..4e95c005c1 --- /dev/null +++ b/internal/qualitygate/rules/risklit_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package rules + +import ( + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/larksuite/cli/internal/qualitygate/report" +) + +func TestRiskLiteralsFlagsHandWrittenLevels(t *testing.T) { + src := `package shortcuts + +import "github.com/larksuite/cli/shortcuts/common" + +var Good = common.Shortcut{Risk: common.RiskHighRiskWrite} +var Bad = common.Shortcut{Risk: "high-risk-write"} +var Typo = common.Shortcut{Risk: "high-risk-wrtie"} + +func mount(cmd *cobra.Command) { + cmdutil.SetRisk(cmd, cmdutil.RiskRead) + cmdutil.SetRisk(cmd, "read") + SetRisk(cmd, "write") +} +` + diags := riskLiteralsInFile("shortcuts/x/x.go", src) + if len(diags) != 4 { + t.Fatalf("got %d diagnostics, want 4:\n%s", len(diags), formatRiskDiags(diags)) + } + for _, d := range diags { + if d.Rule != RuleRiskLiteral { + t.Errorf("rule = %q, want %q", d.Rule, RuleRiskLiteral) + } + if d.Action != report.ActionReject { + t.Errorf("action = %q, want %q", d.Action, report.ActionReject) + } + if d.Line == 0 { + t.Errorf("diagnostic %q has no line number", d.Message) + } + } + // The typo is the case that matters most: the type accepts it, so this + // rule is the only build-time check that sees it. + if !strings.Contains(formatRiskDiags(diags), "high-risk-wrtie") { + t.Errorf("the misspelled literal was not reported:\n%s", formatRiskDiags(diags)) + } +} + +func TestRiskLiteralsIgnoresConstantsAndUnrelatedFields(t *testing.T) { + src := `package shortcuts + +var s = common.Shortcut{Risk: common.RiskWrite, Description: "high-risk-write"} +var m = map[string]string{"risk": "high-risk-write"} + +func f() { setSomethingElse(cmd, "read") } +` + if diags := riskLiteralsInFile("shortcuts/x/x.go", src); len(diags) != 0 { + t.Fatalf("got %d diagnostics, want 0:\n%s", len(diags), formatRiskDiags(diags)) + } +} + +// The rule ships with a zero baseline: every risk declaration in the tree +// already goes through the constants. Without this, a rule that quietly +// tolerates existing violations would never fail on a new one either. +func TestRepoHasNoRiskLiterals(t *testing.T) { + diags, err := CheckRiskLiterals(repoRoot(t)) + if err != nil { + t.Fatalf("CheckRiskLiterals: %v", err) + } + if len(diags) != 0 { + t.Fatalf("the tree must stay at zero risk literals, found %d:\n%s", len(diags), formatRiskDiags(diags)) + } +} + +func formatRiskDiags(diags []report.Diagnostic) string { + var b strings.Builder + for _, d := range diags { + b.WriteString(d.File) + b.WriteString(":") + b.WriteString(strings.TrimSpace(d.Message)) + b.WriteString("\n") + } + return b.String() +} + +func repoRoot(t *testing.T) string { + t.Helper() + _, file, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller failed") + } + // internal/qualitygate/rules/risklit_test.go -> repo root + return filepath.Clean(filepath.Join(filepath.Dir(file), "..", "..", "..")) +} diff --git a/internal/qualitygate/rules/run.go b/internal/qualitygate/rules/run.go index 35acea1ca9..ccb754cc66 100644 --- a/internal/qualitygate/rules/run.go +++ b/internal/qualitygate/rules/run.go @@ -93,6 +93,13 @@ func Run(ctx context.Context, opts Options) ([]report.Diagnostic, facts.Facts, e diags = append(diags, dryDiags...) outputDiags, outputFacts := CheckDefaultOutput(m) diags = append(diags, outputDiags...) + // Full-tree, not incremental: the risk taxonomy is at zero literals, so + // the check is self-consistent with no baseline to maintain. + riskLitDiags, err := CheckRiskLiterals(opts.Repo) + if err != nil { + return nil, facts.Facts{}, err + } + diags = append(diags, riskLitDiags...) errorFacts, errorDiags, err := CollectRepoErrorFacts(opts.Repo, changed, opts.ChangedFrom != "") if err != nil { return nil, facts.Facts{}, err diff --git a/internal/schema/assembler.go b/internal/schema/assembler.go index 6b1c43f0df..f08bf18975 100644 --- a/internal/schema/assembler.go +++ b/internal/schema/assembler.go @@ -145,7 +145,7 @@ func buildInputSchema(m meta.Method) *InputSchema { addInputObject(is, "data", "", m.Data(), false, "--data") addInputObject(is, "file", "Binary file uploads. Each property is a file field with format:binary; CLI maps each to --file =.", m.Files(), false, "--file") - if m.Risk == core.RiskHighRiskWrite { + if core.Risk(m.Risk) == core.RiskHighRiskWrite { falseVal := false is.Properties.Set("yes", Property{ Type: "boolean", @@ -206,7 +206,7 @@ func buildMeta(m meta.Method) *Meta { if m.Risk != "" { out.Risk = m.Risk } else { - out.Risk = core.RiskRead + out.Risk = core.RiskRead.String() } if m.DocURL != "" { out.DocURL = m.DocURL diff --git a/internal/schema/lint.go b/internal/schema/lint.go index 7600527c53..0d3d3ddb3b 100644 --- a/internal/schema/lint.go +++ b/internal/schema/lint.go @@ -81,7 +81,8 @@ func lintEnvelope(env Envelope) []error { } // ---- L3: cross-field self-consistency ---- - dangerExpected := env.Meta.Risk == core.RiskWrite || env.Meta.Risk == core.RiskHighRiskWrite + envRisk := core.Risk(env.Meta.Risk) + dangerExpected := envRisk == core.RiskWrite || envRisk == core.RiskHighRiskWrite if env.Meta.Danger != dangerExpected { errs = append(errs, fmt.Errorf("L3: _meta.danger=%v inconsistent with risk=%q", env.Meta.Danger, env.Meta.Risk)) } @@ -92,7 +93,7 @@ func lintEnvelope(env Envelope) []error { if env.InputSchema != nil && env.InputSchema.Properties != nil { _, hasYes = env.InputSchema.Properties.Map["yes"] } - wantYes := env.Meta.Risk == core.RiskHighRiskWrite + wantYes := envRisk == core.RiskHighRiskWrite if hasYes != wantYes { errs = append(errs, fmt.Errorf("L3: inputSchema `yes` property=%v inconsistent with risk=%q", hasYes, env.Meta.Risk)) } diff --git a/shortcuts/application/slash_command_create.go b/shortcuts/application/slash_command_create.go index ecc45c2ea9..43f50d3e06 100644 --- a/shortcuts/application/slash_command_create.go +++ b/shortcuts/application/slash_command_create.go @@ -18,7 +18,7 @@ var SlashCommandCreate = common.Shortcut{ Service: "application", Command: "+slash-command-create", Description: "Register a slash command (/ command) on the current bound Open Platform app; --force converts a name collision into an update (idempotent re-run)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"application:app_slash_command:write"}, ConditionalScopes: []string{ "application:app_slash_command:read", // only the --force collision path lists to resolve the id diff --git a/shortcuts/application/slash_command_delete.go b/shortcuts/application/slash_command_delete.go index 3bbec2335f..36916a2a32 100644 --- a/shortcuts/application/slash_command_delete.go +++ b/shortcuts/application/slash_command_delete.go @@ -19,7 +19,7 @@ var SlashCommandDelete = common.Shortcut{ Service: "application", Command: "+slash-command-delete", Description: "Delete a slash command from the current bound app (high-risk: irreversible; recreating the same name yields a new command_id)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"application:app_slash_command:write"}, ConditionalScopes: []string{ "application:app_slash_command:read", // only the --command by-name path diff --git a/shortcuts/application/slash_command_list.go b/shortcuts/application/slash_command_list.go index 79b43c150c..7aa8584d46 100644 --- a/shortcuts/application/slash_command_list.go +++ b/shortcuts/application/slash_command_list.go @@ -16,7 +16,7 @@ var SlashCommandList = common.Shortcut{ Service: "application", Command: "+slash-command-list", Description: "List all slash commands (/ commands) registered on the currently bound Open Platform app; source of command_id for update/delete", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"application:app_slash_command:read"}, AuthTypes: []string{"bot", "user"}, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { diff --git a/shortcuts/application/slash_command_update.go b/shortcuts/application/slash_command_update.go index c6669af96b..a63a64246b 100644 --- a/shortcuts/application/slash_command_update.go +++ b/shortcuts/application/slash_command_update.go @@ -51,7 +51,7 @@ var SlashCommandUpdate = common.Shortcut{ Service: "application", Command: "+slash-command-update", Description: "Update description / localized descriptions / icon of a slash command on the current bound app, addressed by --command-id or by name via --command", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"application:app_slash_command:write"}, ConditionalScopes: []string{ "application:app_slash_command:read", // only the --command by-name path lists to resolve the id diff --git a/shortcuts/apps/apps_access_scope_get.go b/shortcuts/apps/apps_access_scope_get.go index 9b90903ff4..bdf9ab9ea0 100644 --- a/shortcuts/apps/apps_access_scope_get.go +++ b/shortcuts/apps/apps_access_scope_get.go @@ -19,7 +19,7 @@ var AppsAccessScopeGet = common.Shortcut{ Service: appsService, Command: "+access-scope-get", Description: "Get app access scope configuration", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +access-scope-get --app-id ", }, diff --git a/shortcuts/apps/apps_access_scope_set.go b/shortcuts/apps/apps_access_scope_set.go index 9e0b8d5f30..1a7f5d586a 100644 --- a/shortcuts/apps/apps_access_scope_set.go +++ b/shortcuts/apps/apps_access_scope_set.go @@ -25,7 +25,7 @@ var AppsAccessScopeSet = common.Shortcut{ Service: appsService, Command: "+access-scope-set", Description: "Set app access scope (specific / public / tenant)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ `Example: lark-cli apps +access-scope-set --app-id --scope tenant`, `Example: lark-cli apps +access-scope-set --app-id --scope public --require-login`, diff --git a/shortcuts/apps/apps_analytics.go b/shortcuts/apps/apps_analytics.go index 1f7afd1fdd..69a04ef8a7 100644 --- a/shortcuts/apps/apps_analytics.go +++ b/shortcuts/apps/apps_analytics.go @@ -22,7 +22,7 @@ var AppsAnalyticsList = common.Shortcut{ Service: appsService, Command: "+analytics-list", Description: "List online app user and page-view analytics", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +analytics-list --app-id --analytics users --granularity week", "Tip: analytics timestamps use nanoseconds; use +metric-list for request/runtime metrics.", diff --git a/shortcuts/apps/apps_automation_create.go b/shortcuts/apps/apps_automation_create.go index cbc720118e..4aaeabe54b 100644 --- a/shortcuts/apps/apps_automation_create.go +++ b/shortcuts/apps/apps_automation_create.go @@ -19,7 +19,7 @@ var AppsAutomationCreate = common.Shortcut{ Service: appsService, Command: "+automation-create", Description: "Create an automation trigger (cron/record-change/webhook/feishu-approval); created disabled", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +automation-create --app-id --name daily --trigger-type cron --cron '0 9 * * *'", "Example: lark-cli apps +automation-create --app-id --name onUpd --trigger-type record-change --table --event UPDATE", diff --git a/shortcuts/apps/apps_automation_disable.go b/shortcuts/apps/apps_automation_disable.go index a0ead653b1..3cef82c59a 100644 --- a/shortcuts/apps/apps_automation_disable.go +++ b/shortcuts/apps/apps_automation_disable.go @@ -15,7 +15,7 @@ var AppsAutomationDisable = common.Shortcut{ Service: appsService, Command: "+automation-disable", Description: "Disable an automation trigger (stops auto-firing; does not delete)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{"Example: lark-cli apps +automation-disable --app-id --name "}, Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, diff --git a/shortcuts/apps/apps_automation_enable.go b/shortcuts/apps/apps_automation_enable.go index 4979a9c1fc..52f6139f78 100644 --- a/shortcuts/apps/apps_automation_enable.go +++ b/shortcuts/apps/apps_automation_enable.go @@ -17,7 +17,7 @@ var AppsAutomationEnable = common.Shortcut{ Service: appsService, Command: "+automation-enable", Description: "Enable (activate) an automation trigger", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{"Example: lark-cli apps +automation-enable --app-id --name "}, Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, diff --git a/shortcuts/apps/apps_automation_get.go b/shortcuts/apps/apps_automation_get.go index f9dd1f1ea9..517c81ca82 100644 --- a/shortcuts/apps/apps_automation_get.go +++ b/shortcuts/apps/apps_automation_get.go @@ -17,7 +17,7 @@ var AppsAutomationGet = common.Shortcut{ Service: appsService, Command: "+automation-get", Description: "Get an automation trigger's config (webhook Bearer Token redacted)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +automation-get --app-id --name ", }, diff --git a/shortcuts/apps/apps_automation_list.go b/shortcuts/apps/apps_automation_list.go index 8890950378..abe22f2658 100644 --- a/shortcuts/apps/apps_automation_list.go +++ b/shortcuts/apps/apps_automation_list.go @@ -18,7 +18,7 @@ var AppsAutomationList = common.Shortcut{ Service: appsService, Command: "+automation-list", Description: "List a Miaoda app's automation triggers (cron/record-change/webhook/feishu-approval)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +automation-list --app-id ", "Example: lark-cli apps +automation-list --app-id --trigger-type webhook", diff --git a/shortcuts/apps/apps_automation_update.go b/shortcuts/apps/apps_automation_update.go index 09bd402550..4d0830c563 100644 --- a/shortcuts/apps/apps_automation_update.go +++ b/shortcuts/apps/apps_automation_update.go @@ -24,7 +24,7 @@ var AppsAutomationUpdate = common.Shortcut{ Service: appsService, Command: "+automation-update", Description: "Update a trigger's condition/description, or manage webhook URL/Token via dedicated flags", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +automation-update --app-id --name t1 --trigger-type cron --cron '0 10 * * *' --yes", "Example: lark-cli apps +automation-update --app-id --name rc1 --trigger-type record-change --table --event UPDATE --fields '[\"fld1\"]' --yes", diff --git a/shortcuts/apps/apps_cache_clear.go b/shortcuts/apps/apps_cache_clear.go index 602eb13750..a0f42f3d54 100644 --- a/shortcuts/apps/apps_cache_clear.go +++ b/shortcuts/apps/apps_cache_clear.go @@ -19,7 +19,7 @@ var AppsCacheClear = common.Shortcut{ Service: appsService, Command: "+cache-clear", Description: "Clear all cache entries for the app in the given environment", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +cache-clear --app-id --environment dev --yes", }, diff --git a/shortcuts/apps/apps_cache_delete.go b/shortcuts/apps/apps_cache_delete.go index 572dfc4d11..ecbed3e338 100644 --- a/shortcuts/apps/apps_cache_delete.go +++ b/shortcuts/apps/apps_cache_delete.go @@ -19,7 +19,7 @@ var AppsCacheDelete = common.Shortcut{ Service: appsService, Command: "+cache-delete", Description: "Delete a single business cache key (idempotent)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +cache-delete --app-id --environment dev --key ", }, diff --git a/shortcuts/apps/apps_cache_get.go b/shortcuts/apps/apps_cache_get.go index 73dd26233c..481abf92c0 100644 --- a/shortcuts/apps/apps_cache_get.go +++ b/shortcuts/apps/apps_cache_get.go @@ -20,7 +20,7 @@ var AppsCacheGet = common.Shortcut{ Service: appsService, Command: "+cache-get", Description: "Get a business cache key's value and metadata", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +cache-get --app-id --key spotbonus:2026:winners:list:v1", "Example: lark-cli apps +cache-get --app-id --environment online --key ", diff --git a/shortcuts/apps/apps_chat.go b/shortcuts/apps/apps_chat.go index bcad45362c..48e932a803 100644 --- a/shortcuts/apps/apps_chat.go +++ b/shortcuts/apps/apps_chat.go @@ -27,7 +27,7 @@ var AppsChat = common.Shortcut{ Service: appsService, Command: "+chat", Description: "Send a message to a session to start/continue a conversation", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ `Example: lark-cli apps +chat --app-id --session-id --message "做一个待办清单页面"`, `Example: lark-cli apps +chat --app-id --session-id --message "把首页标题改为 我的待办"`, diff --git a/shortcuts/apps/apps_create.go b/shortcuts/apps/apps_create.go index 9e2b403529..39bac73de2 100644 --- a/shortcuts/apps/apps_create.go +++ b/shortcuts/apps/apps_create.go @@ -20,7 +20,7 @@ var AppsCreate = common.Shortcut{ Service: appsService, Command: "+create", Description: "Create a new app", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ `Example: lark-cli apps +create --name "审批系统" --app-type full_stack`, `Example: lark-cli apps +create --name "活动页" --app-type html --description "活动报名"`, diff --git a/shortcuts/apps/apps_db_audit_list.go b/shortcuts/apps/apps_db_audit_list.go index 1b3545bf77..ac94ac7d80 100644 --- a/shortcuts/apps/apps_db_audit_list.go +++ b/shortcuts/apps/apps_db_audit_list.go @@ -25,7 +25,7 @@ var AppsDBAuditList = common.Shortcut{ Service: appsService, Command: "+db-audit-list", Description: "List row-change audit events for one or more tables (cursor pagination)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-audit-list --app-id --table orders", "Multiple tables: repeat --table; filter time with --since 7d / --until 2026-04-15.", diff --git a/shortcuts/apps/apps_db_audit_set.go b/shortcuts/apps/apps_db_audit_set.go index 5833d3a865..2c208466c7 100644 --- a/shortcuts/apps/apps_db_audit_set.go +++ b/shortcuts/apps/apps_db_audit_set.go @@ -24,7 +24,7 @@ var AppsDBAuditEnable = common.Shortcut{ Service: appsService, Command: "+db-audit-enable", Description: "Enable row-change audit logging for a table", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +db-audit-enable --app-id --table orders --retention 30d", }, @@ -86,7 +86,7 @@ var AppsDBAuditDisable = common.Shortcut{ Service: appsService, Command: "+db-audit-disable", Description: "Disable row-change audit logging for a table", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +db-audit-disable --app-id --table orders", }, diff --git a/shortcuts/apps/apps_db_audit_status.go b/shortcuts/apps/apps_db_audit_status.go index 6221eea36b..2c22b78891 100644 --- a/shortcuts/apps/apps_db_audit_status.go +++ b/shortcuts/apps/apps_db_audit_status.go @@ -19,7 +19,7 @@ var AppsDBAuditStatus = common.Shortcut{ Service: appsService, Command: "+db-audit-status", Description: "Show table audit (row-change tracking) status", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-audit-status --app-id ", "Check one table: --table orders", diff --git a/shortcuts/apps/apps_db_changelog_list.go b/shortcuts/apps/apps_db_changelog_list.go index d007b4b024..3c715bfdea 100644 --- a/shortcuts/apps/apps_db_changelog_list.go +++ b/shortcuts/apps/apps_db_changelog_list.go @@ -23,7 +23,7 @@ var AppsDBChangelogList = common.Shortcut{ Service: appsService, Command: "+db-changelog-list", Description: "List a Miaoda app database's DDL change history (cursor pagination)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-changelog-list --app-id ", "Pin a single change with --change-id; filter time with --since 7d / --until 2026-04-15.", diff --git a/shortcuts/apps/apps_db_data_export.go b/shortcuts/apps/apps_db_data_export.go index 9ac45e6898..57464d2a3d 100644 --- a/shortcuts/apps/apps_db_data_export.go +++ b/shortcuts/apps/apps_db_data_export.go @@ -34,7 +34,7 @@ var AppsDBDataExport = common.Shortcut{ Service: appsService, Command: "+db-data-export", Description: "Export rows from a Miaoda app table to a local file (csv/json/sql)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-data-export --app-id --table orders --output ./orders.csv", "Format follows the --output extension: .csv / .json / .sql (default csv).", diff --git a/shortcuts/apps/apps_db_data_import.go b/shortcuts/apps/apps_db_data_import.go index ac94906a09..f6b09d2829 100644 --- a/shortcuts/apps/apps_db_data_import.go +++ b/shortcuts/apps/apps_db_data_import.go @@ -32,7 +32,7 @@ var AppsDBDataImport = common.Shortcut{ Service: appsService, Command: "+db-data-import", Description: "Import rows from a local csv/json file into a Miaoda app table", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +db-data-import --app-id --file ./orders.csv --yes", "Table defaults to the file name; override with --table.", diff --git a/shortcuts/apps/apps_db_env_create.go b/shortcuts/apps/apps_db_env_create.go index 9e0830dbd9..490d3d33ae 100644 --- a/shortcuts/apps/apps_db_env_create.go +++ b/shortcuts/apps/apps_db_env_create.go @@ -22,7 +22,7 @@ var AppsDBEnvCreate = common.Shortcut{ Service: appsService, Command: "+db-env-create", Description: "Create a DB environment (split single-env DB into dev/online, irreversible)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +db-env-create --environment dev --sync-data --app-id --yes", }, diff --git a/shortcuts/apps/apps_db_env_migrate.go b/shortcuts/apps/apps_db_env_migrate.go index 363463c280..3c1cdba057 100644 --- a/shortcuts/apps/apps_db_env_migrate.go +++ b/shortcuts/apps/apps_db_env_migrate.go @@ -24,7 +24,7 @@ var AppsDBEnvDiff = common.Shortcut{ Service: appsService, Command: "+db-env-diff", Description: "Preview pending dev→online schema changes (no apply)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-env-diff --app-id ", "Apply the previewed changes with +db-env-migrate --yes.", @@ -73,7 +73,7 @@ var AppsDBEnvMigrate = common.Shortcut{ Service: appsService, Command: "+db-env-migrate", Description: "Publish pending dev→online schema changes (irreversible)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +db-env-migrate --app-id --yes", "Preview first with +db-env-diff.", diff --git a/shortcuts/apps/apps_db_execute.go b/shortcuts/apps/apps_db_execute.go index ceae294cad..590f2b7c5f 100644 --- a/shortcuts/apps/apps_db_execute.go +++ b/shortcuts/apps/apps_db_execute.go @@ -52,7 +52,7 @@ var AppsDBExecute = common.Shortcut{ Service: appsService, Command: "+db-execute", Description: "Execute SQL (SELECT / DML / DDL) against a Miaoda app database", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ `Example: lark-cli apps +db-execute --app-id --sql "SELECT * FROM orders LIMIT 10" --yes`, `Example: lark-cli apps +db-execute --app-id --environment dev --file ./migration.sql --yes`, diff --git a/shortcuts/apps/apps_db_quota_get.go b/shortcuts/apps/apps_db_quota_get.go index ab7abca33a..543c0efaf6 100644 --- a/shortcuts/apps/apps_db_quota_get.go +++ b/shortcuts/apps/apps_db_quota_get.go @@ -19,7 +19,7 @@ var AppsDBQuotaGet = common.Shortcut{ Service: appsService, Command: "+db-quota-get", Description: "Get an app's database storage usage", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-quota-get --app-id ", "Example: lark-cli apps +db-quota-get --app-id --environment dev", diff --git a/shortcuts/apps/apps_db_recovery.go b/shortcuts/apps/apps_db_recovery.go index 83a4d50664..dafca7ae72 100644 --- a/shortcuts/apps/apps_db_recovery.go +++ b/shortcuts/apps/apps_db_recovery.go @@ -24,7 +24,7 @@ var AppsDBRecoveryDiff = common.Shortcut{ Service: appsService, Command: "+db-recovery-diff", Description: "Preview restoring the database to a point in time (PITR diff)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-recovery-diff --app-id --target 2h", "Apply with +db-recovery-apply --target --yes.", @@ -77,7 +77,7 @@ var AppsDBRecoveryApply = common.Shortcut{ Service: appsService, Command: "+db-recovery-apply", Description: "Restore the database to a point in time (overwrites current data, irreversible)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +db-recovery-apply --app-id --target 2026-04-15T10:00:00Z --yes", "Preview first with +db-recovery-diff.", diff --git a/shortcuts/apps/apps_db_table_get.go b/shortcuts/apps/apps_db_table_get.go index 91f6a8841f..32db513fd7 100644 --- a/shortcuts/apps/apps_db_table_get.go +++ b/shortcuts/apps/apps_db_table_get.go @@ -26,7 +26,7 @@ var AppsDBTableGet = common.Shortcut{ Service: appsService, Command: "+db-table-get", Description: "Get a table's structure: columns, indexes and constraints", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-table-get --app-id --table ", "Tip: filter fields with --jq (json format), e.g. -q '.data.columns[].name'", diff --git a/shortcuts/apps/apps_db_table_list.go b/shortcuts/apps/apps_db_table_list.go index a117c18797..d541084da4 100644 --- a/shortcuts/apps/apps_db_table_list.go +++ b/shortcuts/apps/apps_db_table_list.go @@ -31,7 +31,7 @@ var AppsDBTableList = common.Shortcut{ Service: appsService, Command: "+db-table-list", Description: "List tables in an app database (cursor pagination)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +db-table-list --app-id ", "Tip: filter fields with --jq, e.g. -q '.data.items[].name'", diff --git a/shortcuts/apps/apps_env.go b/shortcuts/apps/apps_env.go index 57c05b5e07..5f6fb46d82 100644 --- a/shortcuts/apps/apps_env.go +++ b/shortcuts/apps/apps_env.go @@ -23,7 +23,7 @@ var AppsEnvVarList = common.Shortcut{ Service: appsService, Command: "+env-list", Description: "List app environment variables", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +env-list --app-id ", }, @@ -74,7 +74,7 @@ var AppsEnvVarSet = common.Shortcut{ Service: appsService, Command: "+env-set", Description: "Set an app environment variable", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +env-set --app-id --key FOO --value bar", }, @@ -158,7 +158,7 @@ var AppsEnvVarDelete = common.Shortcut{ Service: appsService, Command: "+env-delete", Description: "Delete app environment variables", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +env-delete --app-id --key FOO --yes", }, diff --git a/shortcuts/apps/apps_env_pull.go b/shortcuts/apps/apps_env_pull.go index ebfc918474..81d59971a1 100644 --- a/shortcuts/apps/apps_env_pull.go +++ b/shortcuts/apps/apps_env_pull.go @@ -34,7 +34,7 @@ var AppsEnvPull = common.Shortcut{ Service: appsService, Command: "+env-pull", Description: "Pull app startup env vars into the local project .env.local", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +env-pull --app-id ", }, diff --git a/shortcuts/apps/apps_file_delete.go b/shortcuts/apps/apps_file_delete.go index 153f40a84b..57b597c325 100644 --- a/shortcuts/apps/apps_file_delete.go +++ b/shortcuts/apps/apps_file_delete.go @@ -23,7 +23,7 @@ var AppsFileDelete = common.Shortcut{ Service: appsService, Command: "+file-delete", Description: "Delete one or more files by remote path (batch)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +file-delete --app-id --path /1858537546760216.png --yes", "Repeat --path for batch delete.", diff --git a/shortcuts/apps/apps_file_download.go b/shortcuts/apps/apps_file_download.go index ac87079f62..b05d3f866b 100644 --- a/shortcuts/apps/apps_file_download.go +++ b/shortcuts/apps/apps_file_download.go @@ -24,7 +24,7 @@ var AppsFileDownload = common.Shortcut{ Service: appsService, Command: "+file-download", Description: "Download a file to a local path (via a signed URL)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +file-download --app-id --path /1858537546760216.png --output ./logo.png", "Example (omit --output): lark-cli apps +file-download --app-id --path /1858537546760216.png # saves to ./1858537546760216.png", diff --git a/shortcuts/apps/apps_file_get.go b/shortcuts/apps/apps_file_get.go index 7fd99af234..d7967007d5 100644 --- a/shortcuts/apps/apps_file_get.go +++ b/shortcuts/apps/apps_file_get.go @@ -19,7 +19,7 @@ var AppsFileGet = common.Shortcut{ Service: appsService, Command: "+file-get", Description: "Get a single file's metadata by path", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +file-get --app-id --path /1858537546760216.png", "Tip: extract a single field with --jq, e.g. -q '.size_bytes' or -q '.download_url'", diff --git a/shortcuts/apps/apps_file_list.go b/shortcuts/apps/apps_file_list.go index 36050dc555..4818a1d272 100644 --- a/shortcuts/apps/apps_file_list.go +++ b/shortcuts/apps/apps_file_list.go @@ -37,7 +37,7 @@ var AppsFileList = common.Shortcut{ Service: appsService, Command: "+file-list", Description: "List files in a Miaoda app's storage (cursor pagination)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +file-list --app-id ", "Tip: filter fields with --jq, e.g. -q '.data.items[].path'", diff --git a/shortcuts/apps/apps_file_quota_get.go b/shortcuts/apps/apps_file_quota_get.go index bc3c2f7c83..49acba0207 100644 --- a/shortcuts/apps/apps_file_quota_get.go +++ b/shortcuts/apps/apps_file_quota_get.go @@ -19,7 +19,7 @@ var AppsFileQuotaGet = common.Shortcut{ Service: appsService, Command: "+file-quota-get", Description: "Get an app's file-storage usage", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +file-quota-get --app-id ", "Tip: get just the usage percent with -q '.usage_percent'", diff --git a/shortcuts/apps/apps_file_sign.go b/shortcuts/apps/apps_file_sign.go index df5a19b449..8e81efb0cb 100644 --- a/shortcuts/apps/apps_file_sign.go +++ b/shortcuts/apps/apps_file_sign.go @@ -23,7 +23,7 @@ var AppsFileSign = common.Shortcut{ Service: appsService, Command: "+file-sign", Description: "Generate a temporary signed download URL for a file", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +file-sign --app-id --path /1858537546760216.png", "Tip: curl the signed_url directly to download.", diff --git a/shortcuts/apps/apps_file_upload.go b/shortcuts/apps/apps_file_upload.go index 0fef5bab52..a6e68c7795 100644 --- a/shortcuts/apps/apps_file_upload.go +++ b/shortcuts/apps/apps_file_upload.go @@ -30,7 +30,7 @@ var AppsFileUpload = common.Shortcut{ Service: appsService, Command: "+file-upload", Description: "Upload a local file to an app's storage", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +file-upload --app-id --file ./logo.png", "Example: lark-cli apps +file-upload --app-id --file ./report.pdf -q '.path' # print the platform-generated file path", diff --git a/shortcuts/apps/apps_get.go b/shortcuts/apps/apps_get.go index a1671ea1a2..097ca94501 100644 --- a/shortcuts/apps/apps_get.go +++ b/shortcuts/apps/apps_get.go @@ -18,7 +18,7 @@ var AppsGet = common.Shortcut{ Service: appsService, Command: "+get", Description: "Get a single app's detail by app ID or meta token (returns app_type, name, description, publish status, etc.)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +get --app-id ", "Example: lark-cli apps +get --app-id ", diff --git a/shortcuts/apps/apps_html_publish.go b/shortcuts/apps/apps_html_publish.go index a4a149863f..506268759a 100644 --- a/shortcuts/apps/apps_html_publish.go +++ b/shortcuts/apps/apps_html_publish.go @@ -23,7 +23,7 @@ var AppsHTMLPublish = common.Shortcut{ Service: appsService, Command: "+html-publish", Description: "Publish HTML to an app (returns url or release_id depending on app type)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +html-publish --app-id --path ./dist", "Example: lark-cli apps +html-publish --app-id --path ./site --dry-run", diff --git a/shortcuts/apps/apps_init.go b/shortcuts/apps/apps_init.go index f159756d02..c67110f097 100644 --- a/shortcuts/apps/apps_init.go +++ b/shortcuts/apps/apps_init.go @@ -99,7 +99,7 @@ var AppsInit = common.Shortcut{ Service: appsService, Command: "+init", Description: "Initialize an app's code and local development environment", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +init --app-id --dir ", "Example: lark-cli apps +init --app-id --dir --dry-run", diff --git a/shortcuts/apps/apps_list.go b/shortcuts/apps/apps_list.go index d42ab6f049..d2c52f812c 100644 --- a/shortcuts/apps/apps_list.go +++ b/shortcuts/apps/apps_list.go @@ -23,7 +23,7 @@ var AppsList = common.Shortcut{ Service: appsService, Command: "+list", Description: "List apps visible to the calling user (cursor pagination)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +list", "Example: lark-cli apps +list --keyword ", diff --git a/shortcuts/apps/apps_logs.go b/shortcuts/apps/apps_logs.go index 0123ad5303..906aa51ab1 100644 --- a/shortcuts/apps/apps_logs.go +++ b/shortcuts/apps/apps_logs.go @@ -38,7 +38,7 @@ var AppsLogList = common.Shortcut{ Service: appsService, Command: "+log-list", Description: "Search online app logs with observability filters", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +log-list --app-id --level error --keyword timeout --since 1h", "Tip: use --page-token from the response to fetch the next page.", @@ -100,7 +100,7 @@ var AppsLogGet = common.Shortcut{ Service: appsService, Command: "+log-get", Description: "Get one online app log by log ID", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +log-get --app-id --log-id ", "Tip: +log-get searches online logs with limit=1; use +log-list first if the log ID is unknown.", diff --git a/shortcuts/apps/apps_metrics.go b/shortcuts/apps/apps_metrics.go index 1f68e48a13..d4acef5c5c 100644 --- a/shortcuts/apps/apps_metrics.go +++ b/shortcuts/apps/apps_metrics.go @@ -27,7 +27,7 @@ var AppsMetricList = common.Shortcut{ Service: appsService, Command: "+metric-list", Description: "List online app request, latency, CPU, and memory metrics", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +metric-list --app-id --metric requests --series total --since 1d", "Tip: metric timestamps use seconds; use +analytics-list for PV/UV-style analytics.", diff --git a/shortcuts/apps/apps_openapi_key_create.go b/shortcuts/apps/apps_openapi_key_create.go index 173e412844..f6734154c7 100644 --- a/shortcuts/apps/apps_openapi_key_create.go +++ b/shortcuts/apps/apps_openapi_key_create.go @@ -18,7 +18,7 @@ var AppsOpenAPIKeyCreate = common.Shortcut{ Service: appsService, Command: "+openapi-key-create", Description: "Create an open API key (returns the raw secret once)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +openapi-key-create --app-id --name partner-test", "Example: lark-cli apps +openapi-key-create --app-id --name orders-readonly --scope-api 'GET /openapi/orders'", diff --git a/shortcuts/apps/apps_openapi_key_delete.go b/shortcuts/apps/apps_openapi_key_delete.go index 88b7717bc4..e5a8736a02 100644 --- a/shortcuts/apps/apps_openapi_key_delete.go +++ b/shortcuts/apps/apps_openapi_key_delete.go @@ -17,7 +17,7 @@ var AppsOpenAPIKeyDelete = common.Shortcut{ Service: appsService, Command: "+openapi-key-delete", Description: "Delete an open API key (irreversible; prefer +openapi-key-disable)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +openapi-key-delete --app-id --key-id --yes", "Preview: add --dry-run to see the request without deleting", diff --git a/shortcuts/apps/apps_openapi_key_disable.go b/shortcuts/apps/apps_openapi_key_disable.go index 4174b7e739..ae1bccec82 100644 --- a/shortcuts/apps/apps_openapi_key_disable.go +++ b/shortcuts/apps/apps_openapi_key_disable.go @@ -14,7 +14,7 @@ var AppsOpenAPIKeyDisable = common.Shortcut{ Service: appsService, Command: "+openapi-key-disable", Description: "Disable an open API key (minimal safety brake)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{"Example: lark-cli apps +openapi-key-disable --app-id --key-id "}, Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, diff --git a/shortcuts/apps/apps_openapi_key_enable.go b/shortcuts/apps/apps_openapi_key_enable.go index c2df7a8256..95154aaa7b 100644 --- a/shortcuts/apps/apps_openapi_key_enable.go +++ b/shortcuts/apps/apps_openapi_key_enable.go @@ -20,7 +20,7 @@ var AppsOpenAPIKeyEnable = common.Shortcut{ Service: appsService, Command: "+openapi-key-enable", Description: "Enable an open API key", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{"Example: lark-cli apps +openapi-key-enable --app-id --key-id "}, Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, diff --git a/shortcuts/apps/apps_openapi_key_get.go b/shortcuts/apps/apps_openapi_key_get.go index 20ddf6bce7..61cd07d4ea 100644 --- a/shortcuts/apps/apps_openapi_key_get.go +++ b/shortcuts/apps/apps_openapi_key_get.go @@ -18,7 +18,7 @@ var AppsOpenAPIKeyGet = common.Shortcut{ Service: appsService, Command: "+openapi-key-get", Description: "Get an open API key detail (secret redacted)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +openapi-key-get --app-id --key-id ", }, diff --git a/shortcuts/apps/apps_openapi_key_list.go b/shortcuts/apps/apps_openapi_key_list.go index f61f6987c4..4caa7e1186 100644 --- a/shortcuts/apps/apps_openapi_key_list.go +++ b/shortcuts/apps/apps_openapi_key_list.go @@ -18,7 +18,7 @@ var AppsOpenAPIKeyList = common.Shortcut{ Service: appsService, Command: "+openapi-key-list", Description: "List an app's open API keys (secrets redacted)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +openapi-key-list --app-id ", "Example: lark-cli apps +openapi-key-list --app-id --limit 10", diff --git a/shortcuts/apps/apps_openapi_key_reset.go b/shortcuts/apps/apps_openapi_key_reset.go index 7013d84e97..272a64a794 100644 --- a/shortcuts/apps/apps_openapi_key_reset.go +++ b/shortcuts/apps/apps_openapi_key_reset.go @@ -17,7 +17,7 @@ var AppsOpenAPIKeyReset = common.Shortcut{ Service: appsService, Command: "+openapi-key-reset", Description: "Reset (rotate) an open API key; returns a new raw secret once", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +openapi-key-reset --app-id --key-id --yes", "Preview: add --dry-run to see the request without rotating", diff --git a/shortcuts/apps/apps_openapi_key_update.go b/shortcuts/apps/apps_openapi_key_update.go index e6ea7f0d02..9419aab565 100644 --- a/shortcuts/apps/apps_openapi_key_update.go +++ b/shortcuts/apps/apps_openapi_key_update.go @@ -15,7 +15,7 @@ var AppsOpenAPIKeyUpdate = common.Shortcut{ Service: appsService, Command: "+openapi-key-update", Description: "Update an open API key's name and/or scope", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +openapi-key-update --app-id --key-id --name partner-prod", }, diff --git a/shortcuts/apps/apps_plugin_install.go b/shortcuts/apps/apps_plugin_install.go index e0fda3ecc2..48af02734b 100644 --- a/shortcuts/apps/apps_plugin_install.go +++ b/shortcuts/apps/apps_plugin_install.go @@ -29,7 +29,7 @@ var AppsPluginInstall = common.Shortcut{ Service: appsService, Command: "+plugin-install", Description: "Install a plugin package (download, extract, update package.json)", - Risk: "write", + Risk: common.RiskWrite, ConditionalScopes: []string{"spark:app:read"}, Scopes: []string{}, AuthTypes: []string{"user"}, diff --git a/shortcuts/apps/apps_plugin_list.go b/shortcuts/apps/apps_plugin_list.go index 7f5f7e18b7..4c13a544e8 100644 --- a/shortcuts/apps/apps_plugin_list.go +++ b/shortcuts/apps/apps_plugin_list.go @@ -18,7 +18,7 @@ var AppsPluginList = common.Shortcut{ Service: appsService, Command: "+plugin-list", Description: "List locally installed plugin packages and their installation status", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{}, Tips: []string{ "Run in project root (like npm); does NOT take --app-id", diff --git a/shortcuts/apps/apps_plugin_uninstall.go b/shortcuts/apps/apps_plugin_uninstall.go index b71d3b3d07..8ac3f20b14 100644 --- a/shortcuts/apps/apps_plugin_uninstall.go +++ b/shortcuts/apps/apps_plugin_uninstall.go @@ -19,7 +19,7 @@ var AppsPluginUninstall = common.Shortcut{ Service: appsService, Command: "+plugin-uninstall", Description: "Uninstall a plugin package (remove from node_modules and package.json)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{}, Tips: []string{ "Run in project root (like npm); does NOT take --app-id", diff --git a/shortcuts/apps/apps_release_create.go b/shortcuts/apps/apps_release_create.go index a33a792074..96c6f3bfd6 100644 --- a/shortcuts/apps/apps_release_create.go +++ b/shortcuts/apps/apps_release_create.go @@ -18,7 +18,7 @@ var AppsReleaseCreate = common.Shortcut{ Service: appsService, Command: "+release-create", Description: "Create a release for an app (returns release_id for status polling)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +release-create --app-id ", "Example: lark-cli apps +release-create --app-id --branch sprint/default --dry-run", diff --git a/shortcuts/apps/apps_release_get.go b/shortcuts/apps/apps_release_get.go index c0dfa79b54..a64cab623d 100644 --- a/shortcuts/apps/apps_release_get.go +++ b/shortcuts/apps/apps_release_get.go @@ -18,7 +18,7 @@ var AppsReleaseGet = common.Shortcut{ Service: appsService, Command: "+release-get", Description: "Get a single release's status/detail by release ID", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +release-get --app-id --release-id ", }, diff --git a/shortcuts/apps/apps_release_list.go b/shortcuts/apps/apps_release_list.go index df94680718..638e7e84b8 100644 --- a/shortcuts/apps/apps_release_list.go +++ b/shortcuts/apps/apps_release_list.go @@ -19,7 +19,7 @@ var AppsReleaseList = common.Shortcut{ Service: appsService, Command: "+release-list", Description: "List an app's release history (most recent first)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +release-list --app-id ", "Tip: filter fields with --jq, e.g. -q '.data.releases[].release_id'", diff --git a/shortcuts/apps/apps_role.go b/shortcuts/apps/apps_role.go index 7cb61a0605..1d371c6dbe 100644 --- a/shortcuts/apps/apps_role.go +++ b/shortcuts/apps/apps_role.go @@ -25,7 +25,7 @@ var AppsRoleList = common.Shortcut{ Service: appsService, Command: "+role-list", Description: "List app roles", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +role-list --app-id ", "Example: lark-cli apps +role-list --app-id --name Admin --page-size 20", @@ -78,7 +78,7 @@ var AppsRoleGet = common.Shortcut{ Service: appsService, Command: "+role-get", Description: "Get an app role", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +role-get --app-id --role-id ", "--role-id is not a human-readable role name; if only a name is known, run +role-list --name and use its unique returned role_id before calling +role-get", @@ -119,7 +119,7 @@ var AppsRoleCreate = common.Shortcut{ Service: appsService, Command: "+role-create", Description: "Create an app role", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +role-create --app-id --name Admin", "Example: lark-cli apps +role-create --app-id --name Admin --description 'Can manage orders'", @@ -185,7 +185,7 @@ var AppsRoleUpdate = common.Shortcut{ Service: appsService, Command: "+role-update", Description: "Update an app role", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +role-update --app-id --role-id --name Operator", }, @@ -244,7 +244,7 @@ var AppsRoleDelete = common.Shortcut{ Service: appsService, Command: "+role-delete", Description: "Delete an app role", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +role-delete --app-id --role-id --yes", "A delete request alone is not explicit confirmation: first show the exact app, role, current member scope, and irreversible impact; use --yes only after the user confirms that impact", diff --git a/shortcuts/apps/apps_role_member.go b/shortcuts/apps/apps_role_member.go index bd0b9b98ca..16f4d72892 100644 --- a/shortcuts/apps/apps_role_member.go +++ b/shortcuts/apps/apps_role_member.go @@ -20,7 +20,7 @@ var AppsRoleMemberList = common.Shortcut{ Service: appsService, Command: "+role-member-list", Description: "List app role members", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +role-member-list --app-id --role-id ", "Example: lark-cli apps +role-member-list --app-id --role-id --member-type user", @@ -89,7 +89,7 @@ var AppsRoleMemberAdd = common.Shortcut{ Service: appsService, Command: "+role-member-add", Description: "Add app role members", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +role-member-add --app-id --role-id --users ou_x", "Example: lark-cli apps +role-member-add --app-id --role-id --users ou_x,ou_y --departments od-x --chats oc_x", @@ -145,7 +145,7 @@ var AppsRoleMemberRemove = common.Shortcut{ Service: appsService, Command: "+role-member-remove", Description: "Remove app role members", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Tips: []string{ "Example: lark-cli apps +role-member-remove --app-id --role-id --users ou_x --yes", "Example: lark-cli apps +role-member-remove --app-id --role-id --all --yes", @@ -203,7 +203,7 @@ var AppsRoleMatchList = common.Shortcut{ Service: appsService, Command: "+role-match-list", Description: "List app roles matching a user", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +role-match-list --app-id --user-id ", }, diff --git a/shortcuts/apps/apps_role_test.go b/shortcuts/apps/apps_role_test.go index fe47470f86..3c73a44600 100644 --- a/shortcuts/apps/apps_role_test.go +++ b/shortcuts/apps/apps_role_test.go @@ -30,7 +30,7 @@ func TestAppsRoleMetadata(t *testing.T) { tests := []struct { name string command string - risk string + risk common.Risk scopes []string }{ {"list", AppsRoleList.Command, AppsRoleList.Risk, AppsRoleList.Scopes}, @@ -46,7 +46,7 @@ func TestAppsRoleMetadata(t *testing.T) { "update": "+role-update", "delete": "+role-delete", } - wantRisks := map[string]string{ + wantRisks := map[string]common.Risk{ "list": "read", "get": "read", "create": "write", diff --git a/shortcuts/apps/apps_session_create.go b/shortcuts/apps/apps_session_create.go index 3d97435c40..67ee94200a 100644 --- a/shortcuts/apps/apps_session_create.go +++ b/shortcuts/apps/apps_session_create.go @@ -18,7 +18,7 @@ var AppsSessionCreate = common.Shortcut{ Service: appsService, Command: "+session-create", Description: "Create a session under an app", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +session-create --app-id ", }, diff --git a/shortcuts/apps/apps_session_get.go b/shortcuts/apps/apps_session_get.go index b8c9bc8f1f..9ee973ddde 100644 --- a/shortcuts/apps/apps_session_get.go +++ b/shortcuts/apps/apps_session_get.go @@ -19,7 +19,7 @@ var AppsSessionGet = common.Shortcut{ Service: appsService, Command: "+session-get", Description: "Read a session's current status, queued turns, and latest turn", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +session-get --app-id --session-id ", }, diff --git a/shortcuts/apps/apps_session_list.go b/shortcuts/apps/apps_session_list.go index 9e2eac7b98..42ce3a71d6 100644 --- a/shortcuts/apps/apps_session_list.go +++ b/shortcuts/apps/apps_session_list.go @@ -17,7 +17,7 @@ var AppsSessionList = common.Shortcut{ Service: appsService, Command: "+session-list", Description: "List sessions under an app (cursor pagination)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +session-list --app-id ", "Tip: filter fields with --jq, e.g. -q '.data.sessions[].session_id'", diff --git a/shortcuts/apps/apps_session_messages_list.go b/shortcuts/apps/apps_session_messages_list.go index ef18053954..2336cdc722 100644 --- a/shortcuts/apps/apps_session_messages_list.go +++ b/shortcuts/apps/apps_session_messages_list.go @@ -21,7 +21,7 @@ var AppsSessionMessagesList = common.Shortcut{ Service: appsService, Command: "+session-messages-list", Description: "List the reply messages of a session turn (page_token pagination)", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +session-messages-list --app-id --session-id --turn-id ", "Tip: turn_id comes from `+session-get` latest_turn.turn_id; page with --page-token ", diff --git a/shortcuts/apps/apps_session_stop.go b/shortcuts/apps/apps_session_stop.go index 028480b606..d71d6f4c6b 100644 --- a/shortcuts/apps/apps_session_stop.go +++ b/shortcuts/apps/apps_session_stop.go @@ -20,7 +20,7 @@ var AppsSessionStop = common.Shortcut{ Service: appsService, Command: "+session-stop", Description: "Stop (interrupt) the running turn of a session", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +session-stop --app-id --session-id --turn-id ", }, diff --git a/shortcuts/apps/apps_traces.go b/shortcuts/apps/apps_traces.go index e22c950738..15cc45cefd 100644 --- a/shortcuts/apps/apps_traces.go +++ b/shortcuts/apps/apps_traces.go @@ -24,7 +24,7 @@ var AppsTraceList = common.Shortcut{ Service: appsService, Command: "+trace-list", Description: "Search online app traces with observability filters", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +trace-list --app-id --trace-id ", "Tip: use --page-token from the response to fetch the next page.", @@ -80,7 +80,7 @@ var AppsTraceGet = common.Shortcut{ Service: appsService, Command: "+trace-get", Description: "Get one online app trace by trace ID", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +trace-get --app-id --trace-id ", "Tip: use +trace-list first if the trace ID is unknown.", diff --git a/shortcuts/apps/apps_update.go b/shortcuts/apps/apps_update.go index 3e926e9778..0bbb89efa4 100644 --- a/shortcuts/apps/apps_update.go +++ b/shortcuts/apps/apps_update.go @@ -18,7 +18,7 @@ var AppsUpdate = common.Shortcut{ Service: appsService, Command: "+update", Description: "Partially update an app (only provided fields are sent)", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ `Example: lark-cli apps +update --app-id --name "新名称"`, `Example: lark-cli apps +update --app-id --description "..."`, diff --git a/shortcuts/apps/git_credential.go b/shortcuts/apps/git_credential.go index fcf6187121..ab14d4c1de 100644 --- a/shortcuts/apps/git_credential.go +++ b/shortcuts/apps/git_credential.go @@ -44,7 +44,7 @@ var AppsGitCredentialInit = common.Shortcut{ Service: appsService, Command: "+git-credential-init", Description: "Initialize Git credentials and a URL-scoped Git helper for an app repository", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +git-credential-init --app-id ", }, @@ -130,7 +130,7 @@ var AppsGitCredentialRemove = common.Shortcut{ Service: appsService, Command: "+git-credential-remove", Description: "Remove local Git credentials and the URL-scoped Git helper for an app repository", - Risk: "write", + Risk: common.RiskWrite, Tips: []string{ "Example: lark-cli apps +git-credential-remove --app-id ", }, @@ -204,7 +204,7 @@ var AppsGitCredentialList = common.Shortcut{ Service: appsService, Command: "+git-credential-list", Description: "List local Git credentials for app repositories", - Risk: "read", + Risk: common.RiskRead, Tips: []string{ "Example: lark-cli apps +git-credential-list", }, diff --git a/shortcuts/base/base_advperm_disable.go b/shortcuts/base/base_advperm_disable.go index 7d403aa583..f3ac728cc4 100644 --- a/shortcuts/base/base_advperm_disable.go +++ b/shortcuts/base/base_advperm_disable.go @@ -19,7 +19,7 @@ var BaseAdvpermDisable = common.Shortcut{ Service: "base", Command: "+advperm-disable", Description: "Disable advanced permissions for a Base", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:app:update"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/base_advperm_enable.go b/shortcuts/base/base_advperm_enable.go index f7c4d8afc9..4ada71848f 100644 --- a/shortcuts/base/base_advperm_enable.go +++ b/shortcuts/base/base_advperm_enable.go @@ -19,7 +19,7 @@ var BaseAdvpermEnable = common.Shortcut{ Service: "base", Command: "+advperm-enable", Description: "Enable advanced permissions for a Base", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:app:update"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/base_block_create.go b/shortcuts/base/base_block_create.go index 40b3b23cb8..c2f19d814f 100644 --- a/shortcuts/base/base_block_create.go +++ b/shortcuts/base/base_block_create.go @@ -13,7 +13,7 @@ var BaseBaseBlockCreate = common.Shortcut{ Service: "base", Command: "+base-block-create", Description: "Create a block", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:block:create"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/base_block_delete.go b/shortcuts/base/base_block_delete.go index 3faf28430c..1469b10693 100644 --- a/shortcuts/base/base_block_delete.go +++ b/shortcuts/base/base_block_delete.go @@ -13,7 +13,7 @@ var BaseBaseBlockDelete = common.Shortcut{ Service: "base", Command: "+base-block-delete", Description: "Delete a block", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:block:delete"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/base_block_list.go b/shortcuts/base/base_block_list.go index c0862f09cd..5337d22a74 100644 --- a/shortcuts/base/base_block_list.go +++ b/shortcuts/base/base_block_list.go @@ -13,7 +13,7 @@ var BaseBaseBlockList = common.Shortcut{ Service: "base", Command: "+base-block-list", Description: "List blocks in a base", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:block:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/base_block_move.go b/shortcuts/base/base_block_move.go index 1b1198d531..f0a933c716 100644 --- a/shortcuts/base/base_block_move.go +++ b/shortcuts/base/base_block_move.go @@ -13,7 +13,7 @@ var BaseBaseBlockMove = common.Shortcut{ Service: "base", Command: "+base-block-move", Description: "Move a block", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:block:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/base_block_rename.go b/shortcuts/base/base_block_rename.go index f1926898ef..b9392896ce 100644 --- a/shortcuts/base/base_block_rename.go +++ b/shortcuts/base/base_block_rename.go @@ -13,7 +13,7 @@ var BaseBaseBlockRename = common.Shortcut{ Service: "base", Command: "+base-block-rename", Description: "Rename a block", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:block:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/base_copy.go b/shortcuts/base/base_copy.go index 5e2210a85c..a26afd5a7a 100644 --- a/shortcuts/base/base_copy.go +++ b/shortcuts/base/base_copy.go @@ -13,7 +13,7 @@ var BaseBaseCopy = common.Shortcut{ Service: "base", Command: "+base-copy", Description: "Copy a base resource", - Risk: "write", + Risk: common.RiskWrite, UserScopes: []string{"base:app:copy"}, BotScopes: []string{"base:app:copy", "docs:permission.member:create"}, AuthTypes: authTypes(), diff --git a/shortcuts/base/base_create.go b/shortcuts/base/base_create.go index d1108d6324..a991825b50 100644 --- a/shortcuts/base/base_create.go +++ b/shortcuts/base/base_create.go @@ -13,7 +13,7 @@ var BaseBaseCreate = common.Shortcut{ Service: "base", Command: "+base-create", Description: "Create a new base resource", - Risk: "write", + Risk: common.RiskWrite, UserScopes: []string{ "base:app:create", "base:table:read", diff --git a/shortcuts/base/base_data_query.go b/shortcuts/base/base_data_query.go index 616680a7ca..cead110bac 100644 --- a/shortcuts/base/base_data_query.go +++ b/shortcuts/base/base_data_query.go @@ -15,7 +15,7 @@ var BaseDataQuery = common.Shortcut{ Service: "base", Command: "+data-query", Description: "Query and analyze Base data with JSON DSL (aggregation, filter, sort)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:table:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/base_form_create.go b/shortcuts/base/base_form_create.go index 238892aa7f..666c82dcc4 100644 --- a/shortcuts/base/base_form_create.go +++ b/shortcuts/base/base_form_create.go @@ -15,7 +15,7 @@ var BaseFormCreate = common.Shortcut{ Service: "base", Command: "+form-create", Description: "Create a form in a Base table", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:form:create"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_delete.go b/shortcuts/base/base_form_delete.go index 75735ba70e..f226b185f0 100644 --- a/shortcuts/base/base_form_delete.go +++ b/shortcuts/base/base_form_delete.go @@ -13,7 +13,7 @@ var BaseFormDelete = common.Shortcut{ Service: "base", Command: "+form-delete", Description: "Delete a form in a Base table", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:form:delete"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_detail.go b/shortcuts/base/base_form_detail.go index 4dc765003f..2715b3de1e 100644 --- a/shortcuts/base/base_form_detail.go +++ b/shortcuts/base/base_form_detail.go @@ -13,7 +13,7 @@ var BaseFormDetail = common.Shortcut{ Service: "base", Command: "+form-detail", Description: "Get form detail by share token", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:form:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_get.go b/shortcuts/base/base_form_get.go index a4c3a2eeb3..cd3cf4d18c 100644 --- a/shortcuts/base/base_form_get.go +++ b/shortcuts/base/base_form_get.go @@ -15,7 +15,7 @@ var BaseFormGet = common.Shortcut{ Service: "base", Command: "+form-get", Description: "Get a form in a Base table", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:form:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_list.go b/shortcuts/base/base_form_list.go index e2b81251e0..c15b159d86 100644 --- a/shortcuts/base/base_form_list.go +++ b/shortcuts/base/base_form_list.go @@ -16,7 +16,7 @@ var BaseFormsList = common.Shortcut{ Service: "base", Command: "+form-list", Description: "List all forms in a Base table (auto-paginated)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:form:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_questions_create.go b/shortcuts/base/base_form_questions_create.go index eff59dfb58..9b1d9697a2 100644 --- a/shortcuts/base/base_form_questions_create.go +++ b/shortcuts/base/base_form_questions_create.go @@ -18,7 +18,7 @@ var BaseFormQuestionsCreate = common.Shortcut{ Service: "base", Command: "+form-questions-create", Description: "Create questions for a form in a Base table", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:form:update"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_questions_delete.go b/shortcuts/base/base_form_questions_delete.go index b2875e4625..12a2ba6f73 100644 --- a/shortcuts/base/base_form_questions_delete.go +++ b/shortcuts/base/base_form_questions_delete.go @@ -14,7 +14,7 @@ var BaseFormQuestionsDelete = common.Shortcut{ Service: "base", Command: "+form-questions-delete", Description: "Delete questions from a form in a Base table", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:form:update"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_questions_list.go b/shortcuts/base/base_form_questions_list.go index 3384fd3d94..f112981e90 100644 --- a/shortcuts/base/base_form_questions_list.go +++ b/shortcuts/base/base_form_questions_list.go @@ -16,7 +16,7 @@ var BaseFormQuestionsList = common.Shortcut{ Service: "base", Command: "+form-questions-list", Description: "List questions of a form in a Base table", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:form:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_questions_update.go b/shortcuts/base/base_form_questions_update.go index 44abf51d38..522cc8b64c 100644 --- a/shortcuts/base/base_form_questions_update.go +++ b/shortcuts/base/base_form_questions_update.go @@ -17,7 +17,7 @@ var BaseFormQuestionsUpdate = common.Shortcut{ Service: "base", Command: "+form-questions-update", Description: "Update questions of a form in a Base table", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:form:update"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_form_submit.go b/shortcuts/base/base_form_submit.go index af1d2adc80..2efdcfee96 100644 --- a/shortcuts/base/base_form_submit.go +++ b/shortcuts/base/base_form_submit.go @@ -26,7 +26,7 @@ var BaseFormSubmit = common.Shortcut{ Service: "base", Command: "+form-submit", Description: "Submit a form (fill and submit form data)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:form:update", "docs:document.media:upload"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/base_form_update.go b/shortcuts/base/base_form_update.go index 53096e2068..bfddf3127a 100644 --- a/shortcuts/base/base_form_update.go +++ b/shortcuts/base/base_form_update.go @@ -15,7 +15,7 @@ var BaseFormUpdate = common.Shortcut{ Service: "base", Command: "+form-update", Description: "Update a form in a Base table", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:form:update"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_get.go b/shortcuts/base/base_get.go index 6a869ff4a4..e0b306dfbc 100644 --- a/shortcuts/base/base_get.go +++ b/shortcuts/base/base_get.go @@ -13,7 +13,7 @@ var BaseBaseGet = common.Shortcut{ Service: "base", Command: "+base-get", Description: "Get a base resource", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:app:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true)}, diff --git a/shortcuts/base/base_resolve.go b/shortcuts/base/base_resolve.go index 0bd4c49593..043adfd409 100644 --- a/shortcuts/base/base_resolve.go +++ b/shortcuts/base/base_resolve.go @@ -26,7 +26,7 @@ var BaseURLResolve = common.Shortcut{ Service: "base", Command: "+url-resolve", Description: "Resolve a Base-related URL into Base coordinates", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{}, ConditionalScopes: []string{ "base:block:read", @@ -102,7 +102,7 @@ var BaseTitleResolve = common.Shortcut{ Service: "base", Command: "+title-resolve", Description: "Resolve a Base title or keyword through Drive search", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"search:docs:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/base/base_role_create.go b/shortcuts/base/base_role_create.go index 7e557d59f4..db8f052ac3 100644 --- a/shortcuts/base/base_role_create.go +++ b/shortcuts/base/base_role_create.go @@ -20,7 +20,7 @@ var BaseRoleCreate = common.Shortcut{ Service: "base", Command: "+role-create", Description: "Create a custom role in a Base", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:role:create"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/base_role_delete.go b/shortcuts/base/base_role_delete.go index c36de7c5be..bebc0ecf69 100644 --- a/shortcuts/base/base_role_delete.go +++ b/shortcuts/base/base_role_delete.go @@ -19,7 +19,7 @@ var BaseRoleDelete = common.Shortcut{ Service: "base", Command: "+role-delete", Description: "Delete a custom role (system roles cannot be deleted)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:role:delete"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/base_role_get.go b/shortcuts/base/base_role_get.go index a064ceec62..df3f1dcb83 100644 --- a/shortcuts/base/base_role_get.go +++ b/shortcuts/base/base_role_get.go @@ -19,7 +19,7 @@ var BaseRoleGet = common.Shortcut{ Service: "base", Command: "+role-get", Description: "Get full config of a role", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:role:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_role_list.go b/shortcuts/base/base_role_list.go index 93a52e9fb5..75f7473962 100644 --- a/shortcuts/base/base_role_list.go +++ b/shortcuts/base/base_role_list.go @@ -19,7 +19,7 @@ var BaseRoleList = common.Shortcut{ Service: "base", Command: "+role-list", Description: "List all roles in a Base", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:role:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/base/base_role_test.go b/shortcuts/base/base_role_test.go index 17f8a601af..adcc8622d6 100644 --- a/shortcuts/base/base_role_test.go +++ b/shortcuts/base/base_role_test.go @@ -210,7 +210,7 @@ func TestBaseRoleShortcutMetadata(t *testing.T) { name string s common.Shortcut command string - risk string + risk common.Risk scopes []string }{ {"create", BaseRoleCreate, "+role-create", "write", []string{"base:role:create"}}, diff --git a/shortcuts/base/base_role_update.go b/shortcuts/base/base_role_update.go index 1baa967056..da4bcfb2f9 100644 --- a/shortcuts/base/base_role_update.go +++ b/shortcuts/base/base_role_update.go @@ -20,7 +20,7 @@ var BaseRoleUpdate = common.Shortcut{ Service: "base", Command: "+role-update", Description: "Update a role config (delta merge, only changed fields needed)", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:role:update"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index 51d38f89c6..7a13db1b77 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -207,7 +207,7 @@ func TestBaseFieldUpdateRisk(t *testing.T) { } func TestBaseDeleteShortcutsRisk(t *testing.T) { - cases := map[string]string{ + cases := map[string]common.Risk{ BaseFieldDelete.Command: BaseFieldDelete.Risk, BaseViewDelete.Command: BaseViewDelete.Risk, BaseRecordDelete.Command: BaseRecordDelete.Risk, diff --git a/shortcuts/base/dashboard_arrange.go b/shortcuts/base/dashboard_arrange.go index 381633221f..b0968ee5f6 100644 --- a/shortcuts/base/dashboard_arrange.go +++ b/shortcuts/base/dashboard_arrange.go @@ -13,7 +13,7 @@ var BaseDashboardArrange = common.Shortcut{ Service: "base", Command: "+dashboard-arrange", Description: "Auto-arrange dashboard blocks layout (server-side smart layout)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:dashboard:update"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_block_create.go b/shortcuts/base/dashboard_block_create.go index 5031202c21..af6710aeb3 100644 --- a/shortcuts/base/dashboard_block_create.go +++ b/shortcuts/base/dashboard_block_create.go @@ -16,7 +16,7 @@ var BaseDashboardBlockCreate = common.Shortcut{ Service: "base", Command: "+dashboard-block-create", Description: "Create a block in a dashboard", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:dashboard:create"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_block_delete.go b/shortcuts/base/dashboard_block_delete.go index 2e78a59953..36b2c031ed 100644 --- a/shortcuts/base/dashboard_block_delete.go +++ b/shortcuts/base/dashboard_block_delete.go @@ -13,7 +13,7 @@ var BaseDashboardBlockDelete = common.Shortcut{ Service: "base", Command: "+dashboard-block-delete", Description: "Delete a dashboard block", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:dashboard:delete"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_block_get.go b/shortcuts/base/dashboard_block_get.go index a07626ff49..c3ceca295b 100644 --- a/shortcuts/base/dashboard_block_get.go +++ b/shortcuts/base/dashboard_block_get.go @@ -14,7 +14,7 @@ var BaseDashboardBlockGet = common.Shortcut{ Service: "base", Command: "+dashboard-block-get", Description: "Get a dashboard block by ID", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:dashboard:read"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_block_get_data.go b/shortcuts/base/dashboard_block_get_data.go index d86f6ee901..bb79299e10 100644 --- a/shortcuts/base/dashboard_block_get_data.go +++ b/shortcuts/base/dashboard_block_get_data.go @@ -13,7 +13,7 @@ var BaseDashboardBlockGetData = common.Shortcut{ Service: "base", Command: "+dashboard-block-get-data", Description: "Get computed data for a dashboard chart block", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:dashboard:read"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_block_list.go b/shortcuts/base/dashboard_block_list.go index 90ce38cd1f..ccb96ae3ef 100644 --- a/shortcuts/base/dashboard_block_list.go +++ b/shortcuts/base/dashboard_block_list.go @@ -14,7 +14,7 @@ var BaseDashboardBlockList = common.Shortcut{ Service: "base", Command: "+dashboard-block-list", Description: "List blocks in a dashboard", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:dashboard:read"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_block_update.go b/shortcuts/base/dashboard_block_update.go index f3e38b35a3..fe1c6f2a0c 100644 --- a/shortcuts/base/dashboard_block_update.go +++ b/shortcuts/base/dashboard_block_update.go @@ -15,7 +15,7 @@ var BaseDashboardBlockUpdate = common.Shortcut{ Service: "base", Command: "+dashboard-block-update", Description: "Update a dashboard block", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:dashboard:update"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_create.go b/shortcuts/base/dashboard_create.go index d0ac6ab296..a16f2b824a 100644 --- a/shortcuts/base/dashboard_create.go +++ b/shortcuts/base/dashboard_create.go @@ -13,7 +13,7 @@ var BaseDashboardCreate = common.Shortcut{ Service: "base", Command: "+dashboard-create", Description: "Create a dashboard in a base", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:dashboard:create"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_delete.go b/shortcuts/base/dashboard_delete.go index 587d5f8a46..66e7753142 100644 --- a/shortcuts/base/dashboard_delete.go +++ b/shortcuts/base/dashboard_delete.go @@ -13,7 +13,7 @@ var BaseDashboardDelete = common.Shortcut{ Service: "base", Command: "+dashboard-delete", Description: "Delete a dashboard", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:dashboard:delete"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_get.go b/shortcuts/base/dashboard_get.go index a42642790e..c8e848ccd8 100644 --- a/shortcuts/base/dashboard_get.go +++ b/shortcuts/base/dashboard_get.go @@ -13,7 +13,7 @@ var BaseDashboardGet = common.Shortcut{ Service: "base", Command: "+dashboard-get", Description: "Get a dashboard by ID", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:dashboard:read"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_list.go b/shortcuts/base/dashboard_list.go index 86ff3a580f..426d4ac56b 100644 --- a/shortcuts/base/dashboard_list.go +++ b/shortcuts/base/dashboard_list.go @@ -14,7 +14,7 @@ var BaseDashboardList = common.Shortcut{ Service: "base", Command: "+dashboard-list", Description: "List dashboards in a base", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:dashboard:read"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/dashboard_update.go b/shortcuts/base/dashboard_update.go index f9dda81a47..9be75e2410 100644 --- a/shortcuts/base/dashboard_update.go +++ b/shortcuts/base/dashboard_update.go @@ -13,7 +13,7 @@ var BaseDashboardUpdate = common.Shortcut{ Service: "base", Command: "+dashboard-update", Description: "Update a dashboard", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:dashboard:update"}, AuthTypes: authTypes(), HasFormat: true, diff --git a/shortcuts/base/field_create.go b/shortcuts/base/field_create.go index 121117627e..3e0ba8b31f 100644 --- a/shortcuts/base/field_create.go +++ b/shortcuts/base/field_create.go @@ -13,7 +13,7 @@ var BaseFieldCreate = common.Shortcut{ Service: "base", Command: "+field-create", Description: "Create a field", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:field:create"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/field_delete.go b/shortcuts/base/field_delete.go index 8b6a0a7931..f574a0efff 100644 --- a/shortcuts/base/field_delete.go +++ b/shortcuts/base/field_delete.go @@ -13,7 +13,7 @@ var BaseFieldDelete = common.Shortcut{ Service: "base", Command: "+field-delete", Description: "Delete a field by ID or name", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:field:delete"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), fieldRefFlag(true)}, diff --git a/shortcuts/base/field_get.go b/shortcuts/base/field_get.go index a272b54c5e..69e7679625 100644 --- a/shortcuts/base/field_get.go +++ b/shortcuts/base/field_get.go @@ -13,7 +13,7 @@ var BaseFieldGet = common.Shortcut{ Service: "base", Command: "+field-get", Description: "Get a field by ID or name", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:field:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), fieldRefFlag(true)}, diff --git a/shortcuts/base/field_list.go b/shortcuts/base/field_list.go index cb6f84d231..6647b562a5 100644 --- a/shortcuts/base/field_list.go +++ b/shortcuts/base/field_list.go @@ -13,7 +13,7 @@ var BaseFieldList = common.Shortcut{ Service: "base", Command: "+field-list", Description: "List fields in a table", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:field:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/field_search_options.go b/shortcuts/base/field_search_options.go index 6742a6132f..13cfebc335 100644 --- a/shortcuts/base/field_search_options.go +++ b/shortcuts/base/field_search_options.go @@ -13,7 +13,7 @@ var BaseFieldSearchOptions = common.Shortcut{ Service: "base", Command: "+field-search-options", Description: "Search select options of a field", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:field:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/field_update.go b/shortcuts/base/field_update.go index 71aaba7b64..b7b4507ab8 100644 --- a/shortcuts/base/field_update.go +++ b/shortcuts/base/field_update.go @@ -13,7 +13,7 @@ var BaseFieldUpdate = common.Shortcut{ Service: "base", Command: "+field-update", Description: "Update a field by ID or name", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:field:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_batch_create.go b/shortcuts/base/record_batch_create.go index e6ec24af85..ed94ea9f94 100644 --- a/shortcuts/base/record_batch_create.go +++ b/shortcuts/base/record_batch_create.go @@ -13,7 +13,7 @@ var BaseRecordBatchCreate = common.Shortcut{ Service: "base", Command: "+record-batch-create", Description: "Batch create records", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:record:create"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_batch_update.go b/shortcuts/base/record_batch_update.go index 0fc23a1797..c4770f0a1e 100644 --- a/shortcuts/base/record_batch_update.go +++ b/shortcuts/base/record_batch_update.go @@ -13,7 +13,7 @@ var BaseRecordBatchUpdate = common.Shortcut{ Service: "base", Command: "+record-batch-update", Description: "Batch update records with record-specific fields", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:record:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_delete.go b/shortcuts/base/record_delete.go index 281376d5ed..26a69914dc 100644 --- a/shortcuts/base/record_delete.go +++ b/shortcuts/base/record_delete.go @@ -13,7 +13,7 @@ var BaseRecordDelete = common.Shortcut{ Service: "base", Command: "+record-delete", Description: "Delete one or more records by ID", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:record:delete"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_get.go b/shortcuts/base/record_get.go index 6ba1eda592..5b518c31ae 100644 --- a/shortcuts/base/record_get.go +++ b/shortcuts/base/record_get.go @@ -14,7 +14,7 @@ var BaseRecordGet = common.Shortcut{ Service: "base", Command: "+record-get", Description: "Get one or more records by ID", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:record:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_history_list.go b/shortcuts/base/record_history_list.go index a45b9740be..19b07931fd 100644 --- a/shortcuts/base/record_history_list.go +++ b/shortcuts/base/record_history_list.go @@ -13,7 +13,7 @@ var BaseRecordHistoryList = common.Shortcut{ Service: "base", Command: "+record-history-list", Description: "List record change history", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:history:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_list.go b/shortcuts/base/record_list.go index d65636040a..f50aea74ea 100644 --- a/shortcuts/base/record_list.go +++ b/shortcuts/base/record_list.go @@ -14,7 +14,7 @@ var BaseRecordList = common.Shortcut{ Service: "base", Command: "+record-list", Description: "List records in a table", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:record:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_search.go b/shortcuts/base/record_search.go index 600bc71ff0..a346249425 100644 --- a/shortcuts/base/record_search.go +++ b/shortcuts/base/record_search.go @@ -14,7 +14,7 @@ var BaseRecordSearch = common.Shortcut{ Service: "base", Command: "+record-search", Description: "Search records in a table", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:record:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_share_link_create.go b/shortcuts/base/record_share_link_create.go index 522369fcbc..76f9ef7e61 100644 --- a/shortcuts/base/record_share_link_create.go +++ b/shortcuts/base/record_share_link_create.go @@ -13,7 +13,7 @@ var BaseRecordShareLinkCreate = common.Shortcut{ Service: "base", Command: "+record-share-link-create", Description: "Generate share links for one or more records (max 100 per request)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:record:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_upload_attachment.go b/shortcuts/base/record_upload_attachment.go index ce87c0be76..5059c5a20d 100644 --- a/shortcuts/base/record_upload_attachment.go +++ b/shortcuts/base/record_upload_attachment.go @@ -46,7 +46,7 @@ var BaseRecordUploadAttachment = common.Shortcut{ Service: "base", Command: "+record-upload-attachment", Description: "Upload one or more local files and append the returned file_token values to a Base attachment cell", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:record:update", "base:field:read", "docs:document.media:upload"}, AuthTypes: authTypes(), Flags: []common.Flag{ @@ -75,7 +75,7 @@ var BaseRecordDownloadAttachment = common.Shortcut{ Service: "base", Command: "+record-download-attachment", Description: "Download Base record attachments by record-id, optionally filtering by file-token", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:record:read", "docs:document.media:download"}, AuthTypes: authTypes(), Flags: []common.Flag{ @@ -105,7 +105,7 @@ var BaseRecordRemoveAttachment = common.Shortcut{ Service: "base", Command: "+record-remove-attachment", Description: "Remove one or more file_token values from a Base record attachment cell", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:record:update", "base:field:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/record_upsert.go b/shortcuts/base/record_upsert.go index 12a26f7fd9..94f1eb18a1 100644 --- a/shortcuts/base/record_upsert.go +++ b/shortcuts/base/record_upsert.go @@ -13,7 +13,7 @@ var BaseRecordUpsert = common.Shortcut{ Service: "base", Command: "+record-upsert", Description: "Create or update a record", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:record:create", "base:record:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/table_copy.go b/shortcuts/base/table_copy.go index 58b77a5e62..f5a1091384 100644 --- a/shortcuts/base/table_copy.go +++ b/shortcuts/base/table_copy.go @@ -25,7 +25,7 @@ var BaseTableCopy = common.Shortcut{ Service: "base", Command: "+table-copy", Description: "Copy a table by ID or name; structure only by default", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{tableCopyScope}, AuthTypes: authTypes(), Flags: []common.Flag{ @@ -55,7 +55,7 @@ var BaseTableCopyStatus = common.Shortcut{ Service: "base", Command: "+table-copy-status", Description: "Get one table copy task status", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{tableCopyScope}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/table_create.go b/shortcuts/base/table_create.go index 2175080e14..29f1b85342 100644 --- a/shortcuts/base/table_create.go +++ b/shortcuts/base/table_create.go @@ -13,7 +13,7 @@ var BaseTableCreate = common.Shortcut{ Service: "base", Command: "+table-create", Description: "Create a table and optional fields/views", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:table:create", "base:field:read", "base:field:create", "base:field:update", "base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/table_delete.go b/shortcuts/base/table_delete.go index 0426d5e3fd..01d88927d4 100644 --- a/shortcuts/base/table_delete.go +++ b/shortcuts/base/table_delete.go @@ -13,7 +13,7 @@ var BaseTableDelete = common.Shortcut{ Service: "base", Command: "+table-delete", Description: "Delete a table by ID or name", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:table:delete"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true)}, diff --git a/shortcuts/base/table_get.go b/shortcuts/base/table_get.go index ac29a8ee3a..5f686d5b11 100644 --- a/shortcuts/base/table_get.go +++ b/shortcuts/base/table_get.go @@ -13,7 +13,7 @@ var BaseTableGet = common.Shortcut{ Service: "base", Command: "+table-get", Description: "Get a table by ID or name", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:table:read", "base:field:read", "base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true)}, diff --git a/shortcuts/base/table_list.go b/shortcuts/base/table_list.go index 308d631989..bc3f92b2c9 100644 --- a/shortcuts/base/table_list.go +++ b/shortcuts/base/table_list.go @@ -13,7 +13,7 @@ var BaseTableList = common.Shortcut{ Service: "base", Command: "+table-list", Description: "List tables in a base", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:table:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/table_update.go b/shortcuts/base/table_update.go index 12d453e47d..3f8935449c 100644 --- a/shortcuts/base/table_update.go +++ b/shortcuts/base/table_update.go @@ -13,7 +13,7 @@ var BaseTableUpdate = common.Shortcut{ Service: "base", Command: "+table-update", Description: "Rename a table by ID or name", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:table:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_create.go b/shortcuts/base/view_create.go index 9f803a3612..e0272657b4 100644 --- a/shortcuts/base/view_create.go +++ b/shortcuts/base/view_create.go @@ -13,7 +13,7 @@ var BaseViewCreate = common.Shortcut{ Service: "base", Command: "+view-create", Description: "Create one or more views", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_delete.go b/shortcuts/base/view_delete.go index 493e726bee..51882bebcb 100644 --- a/shortcuts/base/view_delete.go +++ b/shortcuts/base/view_delete.go @@ -13,7 +13,7 @@ var BaseViewDelete = common.Shortcut{ Service: "base", Command: "+view-delete", Description: "Delete a view by ID or name", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_get.go b/shortcuts/base/view_get.go index 635c57cbd4..5d25d6c721 100644 --- a/shortcuts/base/view_get.go +++ b/shortcuts/base/view_get.go @@ -13,7 +13,7 @@ var BaseViewGet = common.Shortcut{ Service: "base", Command: "+view-get", Description: "Get a view by ID or name", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_get_card.go b/shortcuts/base/view_get_card.go index 10fa43c70f..073d30689d 100644 --- a/shortcuts/base/view_get_card.go +++ b/shortcuts/base/view_get_card.go @@ -13,7 +13,7 @@ var BaseViewGetCard = common.Shortcut{ Service: "base", Command: "+view-get-card", Description: "Get view card configuration", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_get_filter.go b/shortcuts/base/view_get_filter.go index 60ef0efebf..1975086de6 100644 --- a/shortcuts/base/view_get_filter.go +++ b/shortcuts/base/view_get_filter.go @@ -13,7 +13,7 @@ var BaseViewGetFilter = common.Shortcut{ Service: "base", Command: "+view-get-filter", Description: "Get view filter configuration", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_get_group.go b/shortcuts/base/view_get_group.go index c201786f07..f88227522b 100644 --- a/shortcuts/base/view_get_group.go +++ b/shortcuts/base/view_get_group.go @@ -13,7 +13,7 @@ var BaseViewGetGroup = common.Shortcut{ Service: "base", Command: "+view-get-group", Description: "Get view group configuration", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_get_sort.go b/shortcuts/base/view_get_sort.go index 99c7797dd8..5e07b6aafe 100644 --- a/shortcuts/base/view_get_sort.go +++ b/shortcuts/base/view_get_sort.go @@ -13,7 +13,7 @@ var BaseViewGetSort = common.Shortcut{ Service: "base", Command: "+view-get-sort", Description: "Get view sort configuration", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_get_timebar.go b/shortcuts/base/view_get_timebar.go index 7575b50f27..9815fc18a5 100644 --- a/shortcuts/base/view_get_timebar.go +++ b/shortcuts/base/view_get_timebar.go @@ -13,7 +13,7 @@ var BaseViewGetTimebar = common.Shortcut{ Service: "base", Command: "+view-get-timebar", Description: "Get view timebar configuration", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_get_visible_fields.go b/shortcuts/base/view_get_visible_fields.go index 3e97c633d9..0ec61af3c5 100644 --- a/shortcuts/base/view_get_visible_fields.go +++ b/shortcuts/base/view_get_visible_fields.go @@ -13,7 +13,7 @@ var BaseViewGetVisibleFields = common.Shortcut{ Service: "base", Command: "+view-get-visible-fields", Description: "Get view visible fields configuration", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)}, diff --git a/shortcuts/base/view_list.go b/shortcuts/base/view_list.go index 166784368e..26239a6627 100644 --- a/shortcuts/base/view_list.go +++ b/shortcuts/base/view_list.go @@ -13,7 +13,7 @@ var BaseViewList = common.Shortcut{ Service: "base", Command: "+view-list", Description: "List views in a table", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:view:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_rename.go b/shortcuts/base/view_rename.go index 22ab08a4bc..452683ad5b 100644 --- a/shortcuts/base/view_rename.go +++ b/shortcuts/base/view_rename.go @@ -13,7 +13,7 @@ var BaseViewRename = common.Shortcut{ Service: "base", Command: "+view-rename", Description: "Rename a view by ID or name", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_set_card.go b/shortcuts/base/view_set_card.go index 3a0a46d8a7..baf4c1b085 100644 --- a/shortcuts/base/view_set_card.go +++ b/shortcuts/base/view_set_card.go @@ -13,7 +13,7 @@ var BaseViewSetCard = common.Shortcut{ Service: "base", Command: "+view-set-card", Description: "Set view card configuration", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_set_filter.go b/shortcuts/base/view_set_filter.go index c3a97762b8..6e9a15c8df 100644 --- a/shortcuts/base/view_set_filter.go +++ b/shortcuts/base/view_set_filter.go @@ -13,7 +13,7 @@ var BaseViewSetFilter = common.Shortcut{ Service: "base", Command: "+view-set-filter", Description: "Set view filter configuration", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_set_group.go b/shortcuts/base/view_set_group.go index 5247ca9474..70721c8109 100644 --- a/shortcuts/base/view_set_group.go +++ b/shortcuts/base/view_set_group.go @@ -13,7 +13,7 @@ var BaseViewSetGroup = common.Shortcut{ Service: "base", Command: "+view-set-group", Description: "Set view group configuration", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_set_sort.go b/shortcuts/base/view_set_sort.go index 743e722c3f..0867916b2c 100644 --- a/shortcuts/base/view_set_sort.go +++ b/shortcuts/base/view_set_sort.go @@ -13,7 +13,7 @@ var BaseViewSetSort = common.Shortcut{ Service: "base", Command: "+view-set-sort", Description: "Set view sort configuration", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_set_timebar.go b/shortcuts/base/view_set_timebar.go index f037aacc82..c071708fe6 100644 --- a/shortcuts/base/view_set_timebar.go +++ b/shortcuts/base/view_set_timebar.go @@ -13,7 +13,7 @@ var BaseViewSetTimebar = common.Shortcut{ Service: "base", Command: "+view-set-timebar", Description: "Set view timebar configuration", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/view_set_visible_fields.go b/shortcuts/base/view_set_visible_fields.go index e1b54b1210..5798d4dd4f 100644 --- a/shortcuts/base/view_set_visible_fields.go +++ b/shortcuts/base/view_set_visible_fields.go @@ -13,7 +13,7 @@ var BaseViewSetVisibleFields = common.Shortcut{ Service: "base", Command: "+view-set-visible-fields", Description: "Set view visible fields", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:view:write_only"}, AuthTypes: authTypes(), Flags: []common.Flag{ diff --git a/shortcuts/base/workflow_create.go b/shortcuts/base/workflow_create.go index 9bf9fe562f..2ddc12cc24 100644 --- a/shortcuts/base/workflow_create.go +++ b/shortcuts/base/workflow_create.go @@ -14,7 +14,7 @@ var BaseWorkflowCreate = common.Shortcut{ Service: "base", Command: "+workflow-create", Description: "Create a new workflow in a base", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:workflow:create"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/workflow_disable.go b/shortcuts/base/workflow_disable.go index 945114ffec..9bc1765167 100644 --- a/shortcuts/base/workflow_disable.go +++ b/shortcuts/base/workflow_disable.go @@ -14,7 +14,7 @@ var BaseWorkflowDisable = common.Shortcut{ Service: "base", Command: "+workflow-disable", Description: "Disable a workflow in a base", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:workflow:update"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/workflow_enable.go b/shortcuts/base/workflow_enable.go index 3cca469c33..04b4fccbbd 100644 --- a/shortcuts/base/workflow_enable.go +++ b/shortcuts/base/workflow_enable.go @@ -14,7 +14,7 @@ var BaseWorkflowEnable = common.Shortcut{ Service: "base", Command: "+workflow-enable", Description: "Enable a workflow in a base", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:workflow:update"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/workflow_get.go b/shortcuts/base/workflow_get.go index 5e5abbb17c..cd5bd93b97 100644 --- a/shortcuts/base/workflow_get.go +++ b/shortcuts/base/workflow_get.go @@ -14,7 +14,7 @@ var BaseWorkflowGet = common.Shortcut{ Service: "base", Command: "+workflow-get", Description: "Get a single workflow definition (including steps) from a base", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:workflow:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/workflow_list.go b/shortcuts/base/workflow_list.go index d78661d9a8..e291966eb1 100644 --- a/shortcuts/base/workflow_list.go +++ b/shortcuts/base/workflow_list.go @@ -14,7 +14,7 @@ var BaseWorkflowList = common.Shortcut{ Service: "base", Command: "+workflow-list", Description: "List all workflows in a base (auto-paginated)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"base:workflow:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/base/workflow_update.go b/shortcuts/base/workflow_update.go index ea13a3e17a..79a24500b1 100644 --- a/shortcuts/base/workflow_update.go +++ b/shortcuts/base/workflow_update.go @@ -14,7 +14,7 @@ var BaseWorkflowUpdate = common.Shortcut{ Service: "base", Command: "+workflow-update", Description: "Replace a workflow's full definition (title and/or steps) in a base", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"base:workflow:update"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/calendar/calendar_agenda.go b/shortcuts/calendar/calendar_agenda.go index 0df6b8d86d..3a676f8630 100644 --- a/shortcuts/calendar/calendar_agenda.go +++ b/shortcuts/calendar/calendar_agenda.go @@ -169,7 +169,7 @@ var CalendarAgenda = common.Shortcut{ Service: "calendar", Command: "+agenda", Description: "View calendar agenda (defaults to today)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"calendar:calendar.event:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_create.go b/shortcuts/calendar/calendar_create.go index 361ebf92a0..c47a41cf3a 100644 --- a/shortcuts/calendar/calendar_create.go +++ b/shortcuts/calendar/calendar_create.go @@ -112,7 +112,7 @@ var CalendarCreate = common.Shortcut{ Service: "calendar", Command: "+create", Description: "Create a calendar event and optionally invite attendees", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"calendar:calendar.event:create", "calendar:calendar.event:update"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_freebusy.go b/shortcuts/calendar/calendar_freebusy.go index 2b62f5d7d9..6176efa18e 100644 --- a/shortcuts/calendar/calendar_freebusy.go +++ b/shortcuts/calendar/calendar_freebusy.go @@ -46,7 +46,7 @@ var CalendarFreebusy = common.Shortcut{ Service: "calendar", Command: "+freebusy", Description: "Query user free/busy and RSVP status", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"calendar:calendar.free_busy:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_get.go b/shortcuts/calendar/calendar_get.go index 1b4593e3de..7699167cd7 100644 --- a/shortcuts/calendar/calendar_get.go +++ b/shortcuts/calendar/calendar_get.go @@ -179,7 +179,7 @@ var CalendarGet = common.Shortcut{ Service: "calendar", Command: "+get", Description: "Get a single calendar event detail by calendar-id and event-id", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"calendar:calendar.event:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_meeting.go b/shortcuts/calendar/calendar_meeting.go index e5e76ed8f1..c5e4cbd94e 100644 --- a/shortcuts/calendar/calendar_meeting.go +++ b/shortcuts/calendar/calendar_meeting.go @@ -122,7 +122,7 @@ var CalendarMeeting = common.Shortcut{ Service: "calendar", Command: "+meeting", Description: "Get meeting info for calendar events (meeting_id, meeting_note)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"calendar:calendar.event:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_room_find.go b/shortcuts/calendar/calendar_room_find.go index 7b447f5239..928035652e 100644 --- a/shortcuts/calendar/calendar_room_find.go +++ b/shortcuts/calendar/calendar_room_find.go @@ -282,7 +282,7 @@ var CalendarRoomFind = common.Shortcut{ Service: "calendar", Command: "+room-find", Description: "Find available meeting room candidates for one or more event time slots", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"calendar:calendar.free_busy:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_rsvp.go b/shortcuts/calendar/calendar_rsvp.go index 2433978383..12e20c91e6 100644 --- a/shortcuts/calendar/calendar_rsvp.go +++ b/shortcuts/calendar/calendar_rsvp.go @@ -17,7 +17,7 @@ var CalendarRsvp = common.Shortcut{ Service: "calendar", Command: "+rsvp", Description: "Reply to a calendar event (accept/decline/tentative)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"calendar:calendar.event:reply"}, AuthTypes: []string{"user", "bot"}, HasFormat: false, diff --git a/shortcuts/calendar/calendar_search_event.go b/shortcuts/calendar/calendar_search_event.go index db2723c4a1..3050598bf0 100644 --- a/shortcuts/calendar/calendar_search_event.go +++ b/shortcuts/calendar/calendar_search_event.go @@ -172,7 +172,7 @@ var CalendarSearchEvent = common.Shortcut{ Service: "calendar", Command: "+search-event", Description: "Search calendar events by keyword, time range, and attendees", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"calendar:calendar.event:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_suggestion.go b/shortcuts/calendar/calendar_suggestion.go index ebdd48af70..e4d29b8d50 100644 --- a/shortcuts/calendar/calendar_suggestion.go +++ b/shortcuts/calendar/calendar_suggestion.go @@ -191,7 +191,7 @@ var CalendarSuggestion = common.Shortcut{ Service: "calendar", Command: "+suggestion", Description: "Intelligently suggest available time blocks based on unclear time ranges", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"calendar:calendar.free_busy:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/calendar/calendar_update.go b/shortcuts/calendar/calendar_update.go index a8fdc508a3..d42c7bed0c 100644 --- a/shortcuts/calendar/calendar_update.go +++ b/shortcuts/calendar/calendar_update.go @@ -21,7 +21,7 @@ var CalendarUpdate = common.Shortcut{ Service: "calendar", Command: "+update", Description: "Update a calendar event and incrementally add or remove attendees", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"calendar:calendar.event:update"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/common/runner.go b/shortcuts/common/runner.go index e12224f0cd..808322d17a 100644 --- a/shortcuts/common/runner.go +++ b/shortcuts/common/runner.go @@ -980,8 +980,12 @@ func runShortcut(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut, botOnly bo return handleShortcutDryRun(f, rctx, s) } - if s.Risk == "high-risk-write" && !rctx.Bool("yes") { - return cmdutil.RequireConfirmation(s.Service + " " + s.Command) + action := s.Service + " " + s.Command + if err := cmdutil.EnforceRiskDeclaration(action, s.Risk); err != nil { + return err + } + if cmdutil.RequiresConfirmation(s.Risk) && !rctx.Bool("yes") { + return cmdutil.RequireConfirmation(action) } if err := s.Execute(rctx.ctx, rctx); err != nil { @@ -1338,7 +1342,9 @@ func registerShortcutFlagsWithContext(ctx context.Context, cmd *cobra.Command, f }) } ensureJSONShorthand(cmd, s) - if s.Risk == "high-risk-write" { + // An invalid declaration also gets --yes registered: without the flag the + // operator who downgrades the refusal would have no way to confirm. + if cmdutil.RequiresConfirmation(s.Risk) { cmd.Flags().Bool("yes", false, "confirm high-risk operation") } if s.PrintFlagSchema != nil { diff --git a/shortcuts/common/runner_risk_gate_test.go b/shortcuts/common/runner_risk_gate_test.go new file mode 100644 index 0000000000..ce7274f680 --- /dev/null +++ b/shortcuts/common/runner_risk_gate_test.go @@ -0,0 +1,173 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package common + +import ( + "context" + "errors" + "strings" + "testing" + + "github.com/larksuite/cli/errs" + "github.com/larksuite/cli/internal/core" + "github.com/larksuite/cli/internal/envvars" +) + +// misspeltHighRisk is the exact failure this change exists to remove: a +// high-risk-write declaration with two letters transposed. Before the runtime +// gate, the framework compared the declaration against the literal +// "high-risk-write", missed, and ran a destructive command with no +// confirmation and no --yes flag to confirm with. +const misspeltHighRisk = core.Risk("high-risk-wrtie") + +func riskGateShortcut(risk core.Risk, executed *bool) Shortcut { + return Shortcut{ + Service: "test", + Command: "+risk-gate", + Description: "risk gate fixture", + Risk: risk, + AuthTypes: []string{"bot"}, + Execute: func(context.Context, *RuntimeContext) error { + *executed = true + return nil + }, + } +} + +func runRiskGateShortcut(t *testing.T, s *Shortcut, args ...string) error { + t.Helper() + factory := newTestFactory() + cmd := newTestShortcutCmd(s, factory) + parseArgs := append(append([]string(nil), args...), "--as=bot") + if err := cmd.ParseFlags(parseArgs); err != nil { + t.Fatalf("ParseFlags(%v) error = %v", parseArgs, err) + } + return runShortcut(cmd, factory, s, true) +} + +// The reproduction case. A misspelled level must never execute: the command +// is refused outright, because the framework cannot tell whether the author +// meant `write` or `high-risk-write`, and running is the outcome that cannot +// be undone. +func TestRiskGateRefusesMisspelledDeclaration(t *testing.T) { + executed := false + s := riskGateShortcut(misspeltHighRisk, &executed) + + err := runRiskGateShortcut(t, &s) + + if executed { + t.Fatal("Execute ran despite an unrecognised risk declaration — this is the fail-open the gate must prevent") + } + var internalErr *errs.InternalError + if !errors.As(err, &internalErr) { + t.Fatalf("err = %v (%T), want *errs.InternalError", err, err) + } + if internalErr.Subtype != errs.SubtypeInvalidRiskDeclaration { + t.Errorf("subtype = %q, want %q", internalErr.Subtype, errs.SubtypeInvalidRiskDeclaration) + } + if !strings.Contains(err.Error(), string(misspeltHighRisk)) { + t.Errorf("error %q does not name the offending value %q", err, misspeltHighRisk) + } +} + +// Passing --yes must not buy past a broken declaration: the level is unknown, +// so confirmation cannot be what unblocks it. +func TestRiskGateRefusesMisspelledDeclarationEvenWithYes(t *testing.T) { + executed := false + s := riskGateShortcut(misspeltHighRisk, &executed) + + err := runRiskGateShortcut(t, &s, "--yes") + + if executed { + t.Fatal("Execute ran with --yes despite an unrecognised risk declaration") + } + var internalErr *errs.InternalError + if !errors.As(err, &internalErr) { + t.Fatalf("err = %v (%T), want *errs.InternalError", err, err) + } +} + +// The escape hatch may only soften "refuse" into "confirm". If it ever lets an +// unrecognised level through unconfirmed, it has reintroduced the bug. +func TestRiskGateDowngradeStillRequiresConfirmation(t *testing.T) { + t.Setenv(envvars.CliAllowInvalidRisk, "1") + + executed := false + s := riskGateShortcut(misspeltHighRisk, &executed) + + err := runRiskGateShortcut(t, &s) + + if executed { + t.Fatal("Execute ran under the downgrade switch without confirmation") + } + var confirmErr *errs.ConfirmationRequiredError + if !errors.As(err, &confirmErr) { + t.Fatalf("err = %v (%T), want *errs.ConfirmationRequiredError", err, err) + } +} + +func TestRiskGateDowngradeRunsWhenConfirmed(t *testing.T) { + t.Setenv(envvars.CliAllowInvalidRisk, "1") + + executed := false + s := riskGateShortcut(misspeltHighRisk, &executed) + + if err := runRiskGateShortcut(t, &s, "--yes"); err != nil { + t.Fatalf("run with --yes returned %v, want nil", err) + } + if !executed { + t.Fatal("Execute did not run after the operator downgraded and confirmed") + } +} + +// The unchanged contract, pinned so the gate rewrite cannot regress it. +func TestRiskGateHighRiskWriteRequiresYes(t *testing.T) { + executed := false + s := riskGateShortcut(core.RiskHighRiskWrite, &executed) + + err := runRiskGateShortcut(t, &s) + + if executed { + t.Fatal("high-risk-write executed without --yes") + } + var confirmErr *errs.ConfirmationRequiredError + if !errors.As(err, &confirmErr) { + t.Fatalf("err = %v (%T), want *errs.ConfirmationRequiredError", err, err) + } + + executed = false + if err := runRiskGateShortcut(t, &s, "--yes"); err != nil { + t.Fatalf("run with --yes returned %v, want nil", err) + } + if !executed { + t.Fatal("high-risk-write did not execute with --yes") + } +} + +// read / write / unannotated must stay ungated — a gate that fires on +// everything gets routed around. +func TestRiskGateLeavesLowerTiersAlone(t *testing.T) { + for _, risk := range []core.Risk{"", core.RiskRead, core.RiskWrite} { + executed := false + s := riskGateShortcut(risk, &executed) + if err := runRiskGateShortcut(t, &s); err != nil { + t.Fatalf("risk %q: run returned %v, want nil", risk, err) + } + if !executed { + t.Errorf("risk %q: Execute did not run", risk) + } + } +} + +// --yes has to exist on a command with a broken declaration, otherwise the +// operator who downgrades the refusal has no way to confirm. +func TestRiskGateRegistersYesForMisspelledDeclaration(t *testing.T) { + executed := false + s := riskGateShortcut(misspeltHighRisk, &executed) + cmd := mountTestShortcut(t, s) + + if cmd.Flags().Lookup("yes") == nil { + t.Fatal("--yes not registered for a command whose risk declaration is unrecognised") + } +} diff --git a/shortcuts/common/types.go b/shortcuts/common/types.go index 02bc04dd50..a649f01f0c 100644 --- a/shortcuts/common/types.go +++ b/shortcuts/common/types.go @@ -6,9 +6,24 @@ package common import ( "context" + "github.com/larksuite/cli/internal/core" "github.com/spf13/cobra" ) +// Risk is re-exported from core so shortcut declarations get the type and the +// vocabulary from the package they already import. core stays the single +// source of truth — this is an alias, not a second type. +type Risk = core.Risk + +// Risk level constants — re-exported from core so shortcut declarations get +// the vocabulary from the package they already import. core is the single +// source of truth; these are aliases, not a second definition. +const ( + RiskRead = core.RiskRead + RiskWrite = core.RiskWrite + RiskHighRiskWrite = core.RiskHighRiskWrite +) + // Flag.Input source constants. const ( File = "file" // support @path to read value from a file @@ -33,10 +48,10 @@ type Shortcut struct { Service string Command string Description string - Risk string // "read" | "write" | "high-risk-write" (empty defaults to "read") - Scopes []string // unconditional pre-flight scopes (fallback when UserScopes/BotScopes are empty) - UserScopes []string // optional: user-identity unconditional scopes (overrides Scopes when non-empty) - BotScopes []string // optional: bot-identity unconditional scopes (overrides Scopes when non-empty) + Risk core.Risk // RiskRead | RiskWrite | RiskHighRiskWrite (empty defaults to RiskRead) + Scopes []string // unconditional pre-flight scopes (fallback when UserScopes/BotScopes are empty) + UserScopes []string // optional: user-identity unconditional scopes (overrides Scopes when non-empty) + BotScopes []string // optional: bot-identity unconditional scopes (overrides Scopes when non-empty) // ConditionalScopes are additional scopes that only some execution paths // need (for example a default mode vs. a lighter --quick mode, or a diff --git a/shortcuts/contact/contact_get_user.go b/shortcuts/contact/contact_get_user.go index 16747f112c..1995d07c94 100644 --- a/shortcuts/contact/contact_get_user.go +++ b/shortcuts/contact/contact_get_user.go @@ -17,7 +17,7 @@ var ContactGetUser = common.Shortcut{ Service: "contact", Command: "+get-user", Description: "Get user info (omit user_id for self; provide user_id for specific user)", - Risk: "read", + Risk: common.RiskRead, UserScopes: []string{"contact:user.basic_profile:readonly"}, BotScopes: []string{"contact:user.base:readonly", "contact:contact.base:readonly"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/contact/contact_search_bot.go b/shortcuts/contact/contact_search_bot.go index ce7fa76565..c64c8da37d 100644 --- a/shortcuts/contact/contact_search_bot.go +++ b/shortcuts/contact/contact_search_bot.go @@ -85,7 +85,7 @@ var ContactSearchBot = common.Shortcut{ Service: "contact", Command: "+search-bot", Description: "Search bots (apps) by keyword — across the tenant, or inside specific chats (requires --as user)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"search:bot"}, AuthTypes: []string{"user"}, Flags: []common.Flag{ diff --git a/shortcuts/contact/contact_search_user.go b/shortcuts/contact/contact_search_user.go index b1a43b841a..0adde917c3 100644 --- a/shortcuts/contact/contact_search_user.go +++ b/shortcuts/contact/contact_search_user.go @@ -134,7 +134,7 @@ var ContactSearchUser = common.Shortcut{ Service: "contact", Command: "+search-user", Description: "Search Lark/Feishu users by keyword, open_id list, or filter (requires --as user)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"contact:user:search"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/doc/doc_media_download.go b/shortcuts/doc/doc_media_download.go index 1d28481f16..8d7ce473ff 100644 --- a/shortcuts/doc/doc_media_download.go +++ b/shortcuts/doc/doc_media_download.go @@ -20,7 +20,7 @@ var DocMediaDownload = common.Shortcut{ Service: "docs", Command: "+media-download", Description: "Download document media or whiteboard thumbnail (auto-detects extension)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:document.media:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/doc/doc_media_insert.go b/shortcuts/doc/doc_media_insert.go index 495f5067cb..5ea3d11207 100644 --- a/shortcuts/doc/doc_media_insert.go +++ b/shortcuts/doc/doc_media_insert.go @@ -47,7 +47,7 @@ var DocMediaInsert = common.Shortcut{ Service: "docs", Command: "+media-insert", Description: "Insert a local image or file into a Lark document (4-step orchestration + auto-rollback); appends to end by default, or inserts relative to a text selection with --selection-with-ellipsis", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.media:upload", "docx:document:write_only", "docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/doc/doc_media_preview.go b/shortcuts/doc/doc_media_preview.go index 6bc88d8e47..c4a0049f4b 100644 --- a/shortcuts/doc/doc_media_preview.go +++ b/shortcuts/doc/doc_media_preview.go @@ -22,7 +22,7 @@ var DocMediaPreview = common.Shortcut{ Service: "docs", Command: "+media-preview", Description: "Preview document media file (auto-detects extension)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:document.media:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/doc/doc_media_upload.go b/shortcuts/doc/doc_media_upload.go index 37d9515151..ed3ade39a5 100644 --- a/shortcuts/doc/doc_media_upload.go +++ b/shortcuts/doc/doc_media_upload.go @@ -18,7 +18,7 @@ var DocMediaUpload = common.Shortcut{ Service: "docs", Command: "+media-upload", Description: "Upload media file (image/attachment) to a document block", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.media:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/doc/doc_resource_cover.go b/shortcuts/doc/doc_resource_cover.go index 940e752246..6c3be329bb 100644 --- a/shortcuts/doc/doc_resource_cover.go +++ b/shortcuts/doc/doc_resource_cover.go @@ -51,7 +51,7 @@ var DocResourceDownload = common.Shortcut{ Service: "docs", Command: "+resource-download", Description: "Download a document resource (type=cover downloads the cover image content)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docx:document:readonly", "docs:document.media:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -150,7 +150,7 @@ var DocResourceUpdate = common.Shortcut{ Service: "docs", Command: "+resource-update", Description: "Upload and update a document resource (type=cover)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docx:document:readonly", "docx:document:write_only", "docs:document.media:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -252,7 +252,7 @@ var DocResourceDelete = common.Shortcut{ Service: "docs", Command: "+resource-delete", Description: "Delete a document resource (type=cover is idempotent when empty)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docx:document:readonly", "docx:document:write_only"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/doc/docs_create.go b/shortcuts/doc/docs_create.go index d035a77075..3e6bbbc36e 100644 --- a/shortcuts/doc/docs_create.go +++ b/shortcuts/doc/docs_create.go @@ -18,7 +18,7 @@ var DocsCreate = common.Shortcut{ Service: "docs", Command: "+create", Description: "Create a Lark document", - Risk: "write", + Risk: common.RiskWrite, AuthTypes: []string{"user", "bot"}, Scopes: []string{"docx:document:create"}, Flags: concatFlags( diff --git a/shortcuts/doc/docs_fetch.go b/shortcuts/doc/docs_fetch.go index f68c9fe01c..7c97c25460 100644 --- a/shortcuts/doc/docs_fetch.go +++ b/shortcuts/doc/docs_fetch.go @@ -18,7 +18,7 @@ var DocsFetch = common.Shortcut{ Service: "docs", Command: "+fetch", Description: "Fetch Lark document content", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/doc/docs_history.go b/shortcuts/doc/docs_history.go index 389c1a3f6c..b06d796a59 100644 --- a/shortcuts/doc/docs_history.go +++ b/shortcuts/doc/docs_history.go @@ -99,7 +99,7 @@ var DocsHistoryList = common.Shortcut{ Service: "docs", Command: "+history-list", Description: "List Lark document history versions", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -152,7 +152,7 @@ var DocsHistoryRevert = common.Shortcut{ Service: "docs", Command: "+history-revert", Description: "Revert a Lark document to a historical version", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docx:document:write_only", "docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -208,7 +208,7 @@ var DocsHistoryRevertStatus = common.Shortcut{ Service: "docs", Command: "+history-revert-status", Description: "Get Lark document history revert task status", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/doc/docs_search.go b/shortcuts/doc/docs_search.go index d08f05cd00..1e8ff9300a 100644 --- a/shortcuts/doc/docs_search.go +++ b/shortcuts/doc/docs_search.go @@ -23,7 +23,7 @@ var DocsSearch = common.Shortcut{ Service: "docs", Command: "+search", Description: "Search Lark docs, Wiki, and spreadsheet files (Search v2: doc_wiki/search)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"search:docs:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/doc/docs_update.go b/shortcuts/doc/docs_update.go index 9238924b1c..3efce38853 100644 --- a/shortcuts/doc/docs_update.go +++ b/shortcuts/doc/docs_update.go @@ -18,7 +18,7 @@ var DocsUpdate = common.Shortcut{ Service: "docs", Command: "+update", Description: "Update a Lark document", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docx:document:write_only", "docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: concatFlags( diff --git a/shortcuts/drive/drive_add_comment.go b/shortcuts/drive/drive_add_comment.go index 294b18e1c0..49a344b9e4 100644 --- a/shortcuts/drive/drive_add_comment.go +++ b/shortcuts/drive/drive_add_comment.go @@ -130,7 +130,7 @@ var DriveAddComment = common.Shortcut{ Service: "drive", Command: "+add-comment", Description: "Add a comment to doc/docx/file/sheet/slides/base(bitable); file targets support selected extensions and full comments only", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{ "drive:drive.metadata:readonly", "docx:document:readonly", diff --git a/shortcuts/drive/drive_add_reply.go b/shortcuts/drive/drive_add_reply.go index 96e563540e..ca860693f5 100644 --- a/shortcuts/drive/drive_add_reply.go +++ b/shortcuts/drive/drive_add_reply.go @@ -43,7 +43,7 @@ var DriveAddReply = common.Shortcut{ Service: "drive", Command: "+add-reply", Description: "Add a reply to an existing comment on doc/docx/sheet/file/slides/base(bitable)/apps, with URL parsing and Wiki token unwrapping", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.comment:create"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_apply_permission.go b/shortcuts/drive/drive_apply_permission.go index 8b211c7a2c..bb2a44c769 100644 --- a/shortcuts/drive/drive_apply_permission.go +++ b/shortcuts/drive/drive_apply_permission.go @@ -155,7 +155,7 @@ var DriveApplyPermission = common.Shortcut{ Service: "drive", Command: "+apply-permission", Description: "Apply to the owner for view or edit permission on a Drive resource", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:permission.member:apply"}, AuthTypes: []string{"user"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_batch_query_comments.go b/shortcuts/drive/drive_batch_query_comments.go index 30bd1adb8f..2294a38714 100644 --- a/shortcuts/drive/drive_batch_query_comments.go +++ b/shortcuts/drive/drive_batch_query_comments.go @@ -53,7 +53,7 @@ var DriveBatchQueryComments = common.Shortcut{ Service: "drive", Command: "+batch-query-comments", Description: "Batch get comments by comment ID for doc/docx/sheet/file/slides/base(bitable)/apps, with URL parsing and Wiki token unwrapping", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:document.comment:read"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_cover.go b/shortcuts/drive/drive_cover.go index 72a6fc46b6..948d6591d0 100644 --- a/shortcuts/drive/drive_cover.go +++ b/shortcuts/drive/drive_cover.go @@ -18,7 +18,7 @@ var DriveCover = common.Shortcut{ Service: "drive", Command: "+cover", Description: "List or download stable cover presets for a Drive file", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:file:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_create_folder.go b/shortcuts/drive/drive_create_folder.go index 4cdeec577c..d0dbd0fcaa 100644 --- a/shortcuts/drive/drive_create_folder.go +++ b/shortcuts/drive/drive_create_folder.go @@ -38,7 +38,7 @@ var DriveCreateFolder = common.Shortcut{ Service: "drive", Command: "+create-folder", Description: "Create a folder in Drive", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"space:folder:create"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_create_shortcut.go b/shortcuts/drive/drive_create_shortcut.go index b794996310..26d2cdafd5 100644 --- a/shortcuts/drive/drive_create_shortcut.go +++ b/shortcuts/drive/drive_create_shortcut.go @@ -53,7 +53,7 @@ var DriveCreateShortcut = common.Shortcut{ Service: "drive", Command: "+create-shortcut", Description: "Create a Drive shortcut in another folder", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"space:document:shortcut"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_delete.go b/shortcuts/drive/drive_delete.go index ce08e71cca..0eddf7723f 100644 --- a/shortcuts/drive/drive_delete.go +++ b/shortcuts/drive/drive_delete.go @@ -39,7 +39,7 @@ var DriveDelete = common.Shortcut{ Service: "drive", Command: "+delete", Description: "Delete a file or folder in Drive", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"space:document:delete", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_delete_reply.go b/shortcuts/drive/drive_delete_reply.go index e7b9dbb3fa..590eabea84 100644 --- a/shortcuts/drive/drive_delete_reply.go +++ b/shortcuts/drive/drive_delete_reply.go @@ -30,7 +30,7 @@ var DriveDeleteReply = common.Shortcut{ Service: "drive", Command: "+delete-reply", Description: "Delete a reply of a comment on doc/docx/sheet/file/slides/base(bitable)/apps, with URL parsing and Wiki token unwrapping", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"docs:document.comment:write_only"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_download.go b/shortcuts/drive/drive_download.go index 93b6b469f7..3f7e8ae6e2 100644 --- a/shortcuts/drive/drive_download.go +++ b/shortcuts/drive/drive_download.go @@ -109,7 +109,7 @@ var DriveDownload = common.Shortcut{ Service: "drive", Command: "+download", Description: "Download a file from Drive to local", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:file:download"}, // Metadata is only required when --output is omitted and the CLI needs the // remote title as the pre-download fallback filename. diff --git a/shortcuts/drive/drive_export.go b/shortcuts/drive/drive_export.go index fb437aac1b..f4b45e501b 100644 --- a/shortcuts/drive/drive_export.go +++ b/shortcuts/drive/drive_export.go @@ -40,7 +40,7 @@ var DriveExport = common.Shortcut{ Service: "drive", Command: "+export", Description: "Export a doc/docx/sheet/bitable/slides or wiki document to a local file with limited polling", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{ "docs:document.content:read", "docs:document:export", diff --git a/shortcuts/drive/drive_export_download.go b/shortcuts/drive/drive_export_download.go index daed6cad2a..01f74619f2 100644 --- a/shortcuts/drive/drive_export_download.go +++ b/shortcuts/drive/drive_export_download.go @@ -17,7 +17,7 @@ var DriveExportDownload = common.Shortcut{ Service: "drive", Command: "+export-download", Description: "Download an exported file by file_token", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{ "docs:document:export", }, diff --git a/shortcuts/drive/drive_import.go b/shortcuts/drive/drive_import.go index 36c6e73265..824ec0fbaa 100644 --- a/shortcuts/drive/drive_import.go +++ b/shortcuts/drive/drive_import.go @@ -20,7 +20,7 @@ var DriveImport = common.Shortcut{ Service: "drive", Command: "+import", Description: "Import a local file to Drive as a cloud document (docx, sheet, bitable, slides)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{ "docs:document.media:upload", "docs:document:import", diff --git a/shortcuts/drive/drive_inspect.go b/shortcuts/drive/drive_inspect.go index a549aaf93f..adb2a3f2f6 100644 --- a/shortcuts/drive/drive_inspect.go +++ b/shortcuts/drive/drive_inspect.go @@ -25,7 +25,7 @@ var DriveInspect = common.Shortcut{ Service: "drive", Command: "+inspect", Description: "Inspect a Lark document URL to get its type, title, and canonical token (with wiki unwrapping)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:drive.metadata:readonly"}, ConditionalScopes: []string{"wiki:node:retrieve"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_list_comments.go b/shortcuts/drive/drive_list_comments.go index e0e170c842..9b316d3c16 100644 --- a/shortcuts/drive/drive_list_comments.go +++ b/shortcuts/drive/drive_list_comments.go @@ -49,7 +49,7 @@ var DriveListComments = common.Shortcut{ Service: "drive", Command: "+list-comments", Description: "List comments for doc/docx/sheet/file/slides/base(bitable)/apps, with URL parsing and Wiki token unwrapping", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:document.comment:read"}, ConditionalScopes: []string{"wiki:node:retrieve"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_list_replies.go b/shortcuts/drive/drive_list_replies.go index 8b9fa749ba..7284d5093e 100644 --- a/shortcuts/drive/drive_list_replies.go +++ b/shortcuts/drive/drive_list_replies.go @@ -33,7 +33,7 @@ var DriveListReplies = common.Shortcut{ Service: "drive", Command: "+list-replies", Description: "List replies of a comment on doc/docx/sheet/file/slides/base(bitable)/apps, with URL parsing and Wiki token unwrapping", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:document.comment:read"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_member_add.go b/shortcuts/drive/drive_member_add.go index 8f3540e9a6..745e72fba8 100644 --- a/shortcuts/drive/drive_member_add.go +++ b/shortcuts/drive/drive_member_add.go @@ -63,7 +63,7 @@ var DriveMemberAdd = common.Shortcut{ Service: "drive", Command: "+member-add", Description: "Add a collaborator/member permission to a Drive resource", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"docs:permission.member:create"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/drive/drive_member_list.go b/shortcuts/drive/drive_member_list.go index 367b709086..3c120916d2 100644 --- a/shortcuts/drive/drive_member_list.go +++ b/shortcuts/drive/drive_member_list.go @@ -237,7 +237,7 @@ var DriveMemberList = common.Shortcut{ Service: "drive", Command: "+member-list", Description: "List collaborator/member permissions on a Drive resource", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:permission.member:retrieve"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/drive/drive_move.go b/shortcuts/drive/drive_move.go index d61ae1a7b5..123e95dd5b 100644 --- a/shortcuts/drive/drive_move.go +++ b/shortcuts/drive/drive_move.go @@ -19,7 +19,7 @@ var DriveMove = common.Shortcut{ Service: "drive", Command: "+move", Description: "Move a file or folder to another location in Drive", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"space:document:move"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_permission_get_setting.go b/shortcuts/drive/drive_permission_get_setting.go index 960bdab2ce..1341d243fe 100644 --- a/shortcuts/drive/drive_permission_get_setting.go +++ b/shortcuts/drive/drive_permission_get_setting.go @@ -222,7 +222,7 @@ var DrivePermissionGetSetting = common.Shortcut{ Service: "drive", Command: "+permission-get-setting", Description: "Get public access, sharing, collaborator management, security, and comment permission settings", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:permission.setting:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/drive/drive_preview.go b/shortcuts/drive/drive_preview.go index d5804cb95c..3ccb28b86a 100644 --- a/shortcuts/drive/drive_preview.go +++ b/shortcuts/drive/drive_preview.go @@ -17,7 +17,7 @@ var DrivePreview = common.Shortcut{ Service: "drive", Command: "+preview", Description: "View or download Drive file content, or list and fetch available preview artifacts", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:file:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_pull.go b/shortcuts/drive/drive_pull.go index b49646f9be..ef5c2754ec 100644 --- a/shortcuts/drive/drive_pull.go +++ b/shortcuts/drive/drive_pull.go @@ -71,7 +71,7 @@ var DrivePull = common.Shortcut{ Service: "drive", Command: "+pull", Description: "One-way file-level mirror of a Drive folder onto a local directory (Drive → local)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"drive:drive.metadata:readonly", "drive:file:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_push.go b/shortcuts/drive/drive_push.go index 46050a9242..f1f026d4f7 100644 --- a/shortcuts/drive/drive_push.go +++ b/shortcuts/drive/drive_push.go @@ -76,7 +76,7 @@ var DrivePush = common.Shortcut{ Service: "drive", Command: "+push", Description: "File-level mirror of a local directory onto a Drive folder (local → Drive; remote-only directories are not removed)", - Risk: "write", + Risk: common.RiskWrite, // Narrowed scopes follow the precedent set by drive +status / +pull: // drive:drive is policy-disabled in some tenants, so this shortcut sticks // to the smallest set the *core* path needs. space:folder:create is diff --git a/shortcuts/drive/drive_react_reply.go b/shortcuts/drive/drive_react_reply.go index 81622de691..410a954049 100644 --- a/shortcuts/drive/drive_react_reply.go +++ b/shortcuts/drive/drive_react_reply.go @@ -78,7 +78,7 @@ var DriveReactReply = common.Shortcut{ Service: "drive", Command: "+react-reply", Description: "Add or remove an emoji reaction on a comment reply for doc/docx/sheet/file/slides/base(bitable)/apps, with URL parsing and Wiki token unwrapping", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.comment:write_only"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_resolve_comment.go b/shortcuts/drive/drive_resolve_comment.go index c4c56e410f..58b67ffcd6 100644 --- a/shortcuts/drive/drive_resolve_comment.go +++ b/shortcuts/drive/drive_resolve_comment.go @@ -83,7 +83,7 @@ func newDriveCommentSolvedShortcut(cfg driveCommentSolvedConfig) common.Shortcut Service: "drive", Command: cfg.Command, Description: cfg.Description, - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.comment:write_only"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_search.go b/shortcuts/drive/drive_search.go index 90ed637b79..5e74d0a3f5 100644 --- a/shortcuts/drive/drive_search.go +++ b/shortcuts/drive/drive_search.go @@ -71,7 +71,7 @@ var DriveSearch = common.Shortcut{ Service: "drive", Command: "+search", Description: "Search Lark docs, Wiki, and spreadsheet files with flat filters (Search v2: doc_wiki/search)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"search:docs:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/drive/drive_secure_label.go b/shortcuts/drive/drive_secure_label.go index 4b22b03720..55b1da9e44 100644 --- a/shortcuts/drive/drive_secure_label.go +++ b/shortcuts/drive/drive_secure_label.go @@ -72,7 +72,7 @@ var DriveSecureLabelList = common.Shortcut{ Service: "drive", Command: "+secure-label-list", Description: "List secure labels available to the current user", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{secureLabelReadScope}, AuthTypes: []string{"user"}, HasFormat: true, @@ -116,7 +116,7 @@ var DriveSecureLabelUpdate = common.Shortcut{ Service: "drive", Command: "+secure-label-update", Description: "Update the secure label on a Drive file or document", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{secureLabelUpdateScope}, AuthTypes: []string{"user"}, Tips: []string{ diff --git a/shortcuts/drive/drive_status.go b/shortcuts/drive/drive_status.go index 45554f3846..fbfce2f72b 100644 --- a/shortcuts/drive/drive_status.go +++ b/shortcuts/drive/drive_status.go @@ -57,7 +57,7 @@ var DriveStatus = common.Shortcut{ Service: "drive", Command: "+status", Description: "Compare a local directory with a Drive folder by exact hash or quick modified_time", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:drive.metadata:readonly"}, ConditionalScopes: []string{"drive:file:download"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_sync.go b/shortcuts/drive/drive_sync.go index d4d7057475..50f69ab856 100644 --- a/shortcuts/drive/drive_sync.go +++ b/shortcuts/drive/drive_sync.go @@ -55,7 +55,7 @@ var DriveSync = common.Shortcut{ Service: "drive", Command: "+sync", Description: "Two-way sync between a local directory and a Drive folder", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"drive:drive.metadata:readonly"}, ConditionalScopes: []string{ "drive:file:download", diff --git a/shortcuts/drive/drive_task_result.go b/shortcuts/drive/drive_task_result.go index bca52a0e55..e82926b592 100644 --- a/shortcuts/drive/drive_task_result.go +++ b/shortcuts/drive/drive_task_result.go @@ -29,7 +29,7 @@ var DriveTaskResult = common.Shortcut{ Service: "drive", Command: "+task_result", Description: "Poll async task result for import, export, drive move/delete, wiki move, wiki move-to-drive, or wiki delete operations", - Risk: "read", + Risk: common.RiskRead, // This shortcut multiplexes multiple backend APIs with different scope // requirements, so scenario-specific prechecks are handled in Validate. Scopes: []string{}, diff --git a/shortcuts/drive/drive_update_reply.go b/shortcuts/drive/drive_update_reply.go index a4c1c62102..0ae630e5d5 100644 --- a/shortcuts/drive/drive_update_reply.go +++ b/shortcuts/drive/drive_update_reply.go @@ -39,7 +39,7 @@ var DriveUpdateReply = common.Shortcut{ Service: "drive", Command: "+update-reply", Description: "Update the content of a comment reply on doc/docx/sheet/file/slides/base(bitable)/apps, with URL parsing and Wiki token unwrapping", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.comment:write_only"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/drive/drive_upload.go b/shortcuts/drive/drive_upload.go index a02941fcca..2c041dc0b8 100644 --- a/shortcuts/drive/drive_upload.go +++ b/shortcuts/drive/drive_upload.go @@ -89,7 +89,7 @@ var DriveUpload = common.Shortcut{ Service: "drive", Command: "+upload", Description: "Upload a local file to Drive", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"drive:file:upload", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/drive/drive_version.go b/shortcuts/drive/drive_version.go index e560dde49f..571ff222af 100644 --- a/shortcuts/drive/drive_version.go +++ b/shortcuts/drive/drive_version.go @@ -145,7 +145,7 @@ var DriveVersionHistory = common.Shortcut{ Service: "drive", Command: "+version-history", Description: "List the version history of a Drive file", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:file:download"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -248,7 +248,7 @@ var DriveVersionGet = common.Shortcut{ Service: "drive", Command: "+version-get", Description: "Download a specific version of a Drive file", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:file:download"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -363,7 +363,7 @@ var DriveVersionRevert = common.Shortcut{ Service: "drive", Command: "+version-revert", Description: "Revert a Drive file to a specific historical version", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"drive:file:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -410,7 +410,7 @@ var DriveVersionDelete = common.Shortcut{ Service: "drive", Command: "+version-delete", Description: "Delete a specific historical version of a Drive file", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"drive:file:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/event/subscribe.go b/shortcuts/event/subscribe.go index 8a36da2b8d..d15c774ead 100644 --- a/shortcuts/event/subscribe.go +++ b/shortcuts/event/subscribe.go @@ -84,7 +84,7 @@ var EventSubscribe = common.Shortcut{ Service: "event", Command: "+subscribe", Description: "Subscribe to Lark events via WebSocket (NDJSON output)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{}, // no direct OAPI; scopes depend on subscribed event types AuthTypes: []string{"bot"}, // Hidden: superseded by `event consume`. Kept executable so existing diff --git a/shortcuts/im/im_chat_create.go b/shortcuts/im/im_chat_create.go index 0f8a35431e..8ee82c497c 100644 --- a/shortcuts/im/im_chat_create.go +++ b/shortcuts/im/im_chat_create.go @@ -25,7 +25,7 @@ var ImChatCreate = common.Shortcut{ Service: "im", Command: "+chat-create", Description: "Create a group chat or topic chat; user/bot; --chat-mode group|topic; private/public; invites users/bots; optionally sets bot manager", - Risk: "write", + Risk: common.RiskWrite, UserScopes: []string{"im:chat:create_by_user"}, BotScopes: []string{"im:chat:create"}, AuthTypes: []string{"bot", "user"}, diff --git a/shortcuts/im/im_chat_list.go b/shortcuts/im/im_chat_list.go index f4a07c66e9..ca5bd93e29 100644 --- a/shortcuts/im/im_chat_list.go +++ b/shortcuts/im/im_chat_list.go @@ -48,7 +48,7 @@ var ImChatList = common.Shortcut{ Service: "im", Command: "+chat-list", Description: "List chats the current user/bot is a member of; defaults to groups; pass --types=p2p,group to include p2p single chats (user-only); user/bot; supports sorting, auto-pagination, --exclude-muted (user-only)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"im:chat:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/im/im_chat_members_list.go b/shortcuts/im/im_chat_members_list.go index c4f81a72b0..8c90100dad 100644 --- a/shortcuts/im/im_chat_members_list.go +++ b/shortcuts/im/im_chat_members_list.go @@ -40,7 +40,7 @@ var ImChatMembersList = common.Shortcut{ Service: "im", Command: "+chat-members-list", Description: "List members of a chat; returns separate users[] / bots[] buckets; callable as user or bot; --member-types filters which kinds to return; --page-all pagination; surfaces truncations[] when the server caps a bucket", - Risk: "read", + Risk: common.RiskRead, // Declare the narrowest scope the API accepts so tokens carrying only // im:chat.members:read are honored (same rationale as +chat-list). Scopes: []string{"im:chat.members:read"}, diff --git a/shortcuts/im/im_chat_messages_list.go b/shortcuts/im/im_chat_messages_list.go index 8e3114216f..4c258a2e1a 100644 --- a/shortcuts/im/im_chat_messages_list.go +++ b/shortcuts/im/im_chat_messages_list.go @@ -27,7 +27,7 @@ var ImChatMessageList = common.Shortcut{ Service: "im", Command: "+chat-messages-list", Description: "List messages in a chat or P2P conversation; user/bot; accepts --chat-id or --user-id, resolves P2P chat_id, supports time range, --order asc/desc sorting, auto-pagination", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"im:message:readonly"}, UserScopes: []string{"im:message.group_msg:get_as_user", "im:message.p2p_msg:get_as_user", "im:message.reactions:read"}, BotScopes: []string{"im:message.group_msg", "im:message.p2p_msg:readonly", "im:message.reactions:read"}, diff --git a/shortcuts/im/im_chat_search.go b/shortcuts/im/im_chat_search.go index 4d8aa355f4..a9829471a4 100644 --- a/shortcuts/im/im_chat_search.go +++ b/shortcuts/im/im_chat_search.go @@ -73,7 +73,7 @@ var ImChatSearch = common.Shortcut{ Service: "im", Command: "+chat-search", Description: "Search visible group chats by --query keyword and/or --member-ids; user/bot; e.g. look up chat_id by group name; supports type filters, sorting, auto-pagination, and --exclude-muted (user identity only)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"im:chat:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/im/im_chat_update.go b/shortcuts/im/im_chat_update.go index 0e7411fb9b..b15682db16 100644 --- a/shortcuts/im/im_chat_update.go +++ b/shortcuts/im/im_chat_update.go @@ -19,7 +19,7 @@ var ImChatUpdate = common.Shortcut{ Service: "im", Command: "+chat-update", Description: "Update group chat name or description; user/bot; updates a chat's name or description", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"im:chat:update"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/im/im_feed_group_list.go b/shortcuts/im/im_feed_group_list.go index a82c589559..7dcaa86f6f 100644 --- a/shortcuts/im/im_feed_group_list.go +++ b/shortcuts/im/im_feed_group_list.go @@ -33,7 +33,7 @@ var ImFeedGroupList = common.Shortcut{ Service: "im", Command: "+feed-group-list", Description: "List the caller's feed groups (tags); user-only; supports `--page-all` auto-pagination", - Risk: "read", + Risk: common.RiskRead, UserScopes: []string{feedGroupReadScope}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_feed_group_list_item.go b/shortcuts/im/im_feed_group_list_item.go index b6270f75fc..b8742e32b1 100644 --- a/shortcuts/im/im_feed_group_list_item.go +++ b/shortcuts/im/im_feed_group_list_item.go @@ -29,7 +29,7 @@ var ImFeedGroupListItem = common.Shortcut{ Service: "im", Command: "+feed-group-list-item", Description: "List feed cards in a feed group (tag); user-only; enriches each item with chat_name resolved from feed_id; supports --page-all auto-pagination", - Risk: "read", + Risk: common.RiskRead, UserScopes: []string{feedGroupReadScope, chatReadScope}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_feed_group_query_item.go b/shortcuts/im/im_feed_group_query_item.go index 74006de803..967b534d33 100644 --- a/shortcuts/im/im_feed_group_query_item.go +++ b/shortcuts/im/im_feed_group_query_item.go @@ -19,7 +19,7 @@ var ImFeedGroupQueryItem = common.Shortcut{ Service: "im", Command: "+feed-group-query-item", Description: "Look up specific feed cards in a feed group (tag) by ID; user-only; enriches each item with chat_name resolved from feed_id", - Risk: "read", + Risk: common.RiskRead, UserScopes: []string{feedGroupReadScope, chatReadScope}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_feed_shortcut_create.go b/shortcuts/im/im_feed_shortcut_create.go index b39a194fe8..309ec8e23d 100644 --- a/shortcuts/im/im_feed_shortcut_create.go +++ b/shortcuts/im/im_feed_shortcut_create.go @@ -18,7 +18,7 @@ var ImFeedShortcutCreate = common.Shortcut{ Service: "im", Command: "+feed-shortcut-create", Description: "Add chats to the user's feed shortcuts; user-only; batch up to 10 chat IDs per call; --head/--tail controls insertion order", - Risk: "write", + Risk: common.RiskWrite, UserScopes: []string{feedShortcutWriteScope}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_feed_shortcut_list.go b/shortcuts/im/im_feed_shortcut_list.go index 75194c873c..7496a062b0 100644 --- a/shortcuts/im/im_feed_shortcut_list.go +++ b/shortcuts/im/im_feed_shortcut_list.go @@ -25,7 +25,7 @@ var ImFeedShortcutList = common.Shortcut{ Service: "im", Command: "+feed-shortcut-list", Description: "List one page of the user's feed shortcuts; user-only; first call omits --page-token, subsequent calls pass the previous response's page_token; each entry is auto-enriched with the full per-type info object attached as `detail` (pass --no-detail to skip)", - Risk: "read", + Risk: common.RiskRead, UserScopes: []string{feedShortcutReadScope}, ConditionalUserScopes: []string{chatBatchQueryScope}, AuthTypes: []string{"user"}, diff --git a/shortcuts/im/im_feed_shortcut_remove.go b/shortcuts/im/im_feed_shortcut_remove.go index e007881707..b311ca239c 100644 --- a/shortcuts/im/im_feed_shortcut_remove.go +++ b/shortcuts/im/im_feed_shortcut_remove.go @@ -16,7 +16,7 @@ var ImFeedShortcutRemove = common.Shortcut{ Service: "im", Command: "+feed-shortcut-remove", Description: "Remove chats from the user's feed shortcuts; user-only; batch up to 10 chat IDs per call; per-item failures return ok:false with failed_shortcuts", - Risk: "write", + Risk: common.RiskWrite, UserScopes: []string{feedShortcutWriteScope}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_flag_cancel.go b/shortcuts/im/im_flag_cancel.go index 0c6c9cee50..e8c4e3c267 100644 --- a/shortcuts/im/im_flag_cancel.go +++ b/shortcuts/im/im_flag_cancel.go @@ -18,7 +18,7 @@ var ImFlagCancel = common.Shortcut{ Service: "im", Command: "+flag-cancel", Description: "Cancel (remove) a bookmark. When no --flag-type is given, best-effort double-cancel: removes message layer and (when chat_type is determinable) feed layer", - Risk: "write", + Risk: common.RiskWrite, UserScopes: flagWriteLookupScopes, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_flag_create.go b/shortcuts/im/im_flag_create.go index c45cbae883..ef0d6ce8e5 100644 --- a/shortcuts/im/im_flag_create.go +++ b/shortcuts/im/im_flag_create.go @@ -17,7 +17,7 @@ var ImFlagCreate = common.Shortcut{ Service: "im", Command: "+flag-create", Description: "Create a bookmark on a message; user-only; defaults to message-layer flag; use --flag-type feed for feed-layer flag (item_type auto-detected from chat mode)", - Risk: "write", + Risk: common.RiskWrite, UserScopes: flagWriteLookupScopes, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_flag_list.go b/shortcuts/im/im_flag_list.go index e7680a8e16..152ef90095 100644 --- a/shortcuts/im/im_flag_list.go +++ b/shortcuts/im/im_flag_list.go @@ -26,7 +26,7 @@ var ImFlagList = common.Shortcut{ Service: "im", Command: "+flag-list", Description: "List bookmarks; user-only; auto-enriches feed-type thread entries with message content; supports `--page-all` auto-pagination", - Risk: "read", + Risk: common.RiskRead, UserScopes: []string{flagReadScope}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/im/im_messages_mget.go b/shortcuts/im/im_messages_mget.go index d13b487852..ada09b1c70 100644 --- a/shortcuts/im/im_messages_mget.go +++ b/shortcuts/im/im_messages_mget.go @@ -21,7 +21,7 @@ var ImMessagesMGet = common.Shortcut{ Service: "im", Command: "+messages-mget", Description: "Batch get messages by IDs; user/bot; fetches up to 50 om_ message IDs, formats sender names, expands thread replies", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"im:message:readonly"}, UserScopes: []string{"im:message.group_msg:get_as_user", "im:message.p2p_msg:get_as_user", "im:message.reactions:read"}, BotScopes: []string{"im:message.group_msg", "im:message.p2p_msg:readonly", "im:message.reactions:read"}, diff --git a/shortcuts/im/im_messages_reply.go b/shortcuts/im/im_messages_reply.go index 1aaf4ba83b..e782e0ee67 100644 --- a/shortcuts/im/im_messages_reply.go +++ b/shortcuts/im/im_messages_reply.go @@ -18,7 +18,7 @@ var ImMessagesReply = common.Shortcut{ Service: "im", Command: "+messages-reply", Description: "Reply to a message (supports thread replies); user/bot; supports text/markdown/post/media replies, reply-in-thread, idempotency key", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"im:message:send_as_bot"}, UserScopes: []string{"im:message.send_as_user", "im:message"}, BotScopes: []string{"im:message:send_as_bot"}, diff --git a/shortcuts/im/im_messages_resources_download.go b/shortcuts/im/im_messages_resources_download.go index 1327e00f18..5470e7a863 100644 --- a/shortcuts/im/im_messages_resources_download.go +++ b/shortcuts/im/im_messages_resources_download.go @@ -25,7 +25,7 @@ var ImMessagesResourcesDownload = common.Shortcut{ Service: "im", Command: "+messages-resources-download", Description: "Download images/files from a message; user/bot; downloads image/file resources by message-id and file-key to a safe relative output path", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"im:message:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/im/im_messages_search.go b/shortcuts/im/im_messages_search.go index fb2a440a54..614e1ef216 100644 --- a/shortcuts/im/im_messages_search.go +++ b/shortcuts/im/im_messages_search.go @@ -30,7 +30,7 @@ var ImMessagesSearch = common.Shortcut{ Service: "im", Command: "+messages-search", Description: "Search messages across chats (supports keyword, sender, time range filters) with user or bot identity; filters by chat/sender/attachment/time, enriches results via mget and chats batch_query", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"search:message", "im:message.reactions:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/im/im_messages_send.go b/shortcuts/im/im_messages_send.go index 8633aab16d..6ce2a85b77 100644 --- a/shortcuts/im/im_messages_send.go +++ b/shortcuts/im/im_messages_send.go @@ -20,7 +20,7 @@ var ImMessagesSend = common.Shortcut{ Service: "im", Command: "+messages-send", Description: "Send a message to a chat or direct message; user/bot; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"im:message:send_as_bot"}, UserScopes: []string{"im:message.send_as_user", "im:message"}, BotScopes: []string{"im:message:send_as_bot"}, diff --git a/shortcuts/im/im_threads_messages_list.go b/shortcuts/im/im_threads_messages_list.go index 0c24ad2e43..b346afc455 100644 --- a/shortcuts/im/im_threads_messages_list.go +++ b/shortcuts/im/im_threads_messages_list.go @@ -27,7 +27,7 @@ var ImThreadsMessagesList = common.Shortcut{ Service: "im", Command: "+threads-messages-list", Description: "List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports --order asc/desc sorting, auto-pagination", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"im:message:readonly"}, UserScopes: []string{"im:message.group_msg:get_as_user", "im:message.p2p_msg:get_as_user", "im:message.reactions:read"}, BotScopes: []string{"im:message.group_msg", "im:message.p2p_msg:readonly", "im:message.reactions:read"}, diff --git a/shortcuts/mail/mail_decline_receipt.go b/shortcuts/mail/mail_decline_receipt.go index 8acf03b16e..6fe3d5c2be 100644 --- a/shortcuts/mail/mail_decline_receipt.go +++ b/shortcuts/mail/mail_decline_receipt.go @@ -26,7 +26,7 @@ var MailDeclineReceipt = common.Shortcut{ Service: "mail", Command: "+decline-receipt", Description: "Dismiss the read-receipt request banner on an incoming mail by clearing its READ_RECEIPT_REQUEST label, without sending a receipt. Use when the user wants to silence the prompt but refuse to confirm they have read it. Idempotent — safe to re-run.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{ "mail:user_mailbox.message:modify", "mail:user_mailbox.message:readonly", diff --git a/shortcuts/mail/mail_draft_create.go b/shortcuts/mail/mail_draft_create.go index 3160291933..470a1b2a8a 100644 --- a/shortcuts/mail/mail_draft_create.go +++ b/shortcuts/mail/mail_draft_create.go @@ -37,7 +37,7 @@ var MailDraftCreate = common.Shortcut{ Service: "mail", Command: "+draft-create", Description: "Create a brand-new mail draft from scratch (NOT for reply or forward). For reply drafts use +reply; for forward drafts use +forward. Only use +draft-create when composing a new email with no parent message.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_draft_edit.go b/shortcuts/mail/mail_draft_edit.go index 0bfbf998ad..59ade65c4d 100644 --- a/shortcuts/mail/mail_draft_edit.go +++ b/shortcuts/mail/mail_draft_edit.go @@ -22,7 +22,7 @@ var MailDraftEdit = common.Shortcut{ Service: "mail", Command: "+draft-edit", Description: "Use when updating an existing mail draft without sending it. Prefer this shortcut over calling raw drafts.get or drafts.update directly, because it performs draft-safe MIME read/patch/write editing while preserving unchanged structure, attachments, and headers where possible.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox.message:readonly", "mail:user_mailbox:readonly"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_draft_send.go b/shortcuts/mail/mail_draft_send.go index b07acf6365..b7117dd0cf 100644 --- a/shortcuts/mail/mail_draft_send.go +++ b/shortcuts/mail/mail_draft_send.go @@ -87,7 +87,7 @@ var MailDraftSend = common.Shortcut{ "failures, and aggregates the results. Use after the drafts have " + "already been created (via the Lark client, +draft-create, or the " + "drafts.create API).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"mail:user_mailbox.message:send"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_forward.go b/shortcuts/mail/mail_forward.go index 20a14577ae..4392459fc9 100644 --- a/shortcuts/mail/mail_forward.go +++ b/shortcuts/mail/mail_forward.go @@ -23,7 +23,7 @@ var MailForward = common.Shortcut{ Service: "mail", Command: "+forward", Description: "Forward a message and save as draft (default). Use --confirm-send to send immediately after user confirmation. Original message block included automatically.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox.message:readonly", "mail:user_mailbox:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_lint_html.go b/shortcuts/mail/mail_lint_html.go index 8eafeb6796..b84c54d3fb 100644 --- a/shortcuts/mail/mail_lint_html.go +++ b/shortcuts/mail/mail_lint_html.go @@ -32,7 +32,7 @@ var MailLintHTML = common.Shortcut{ Service: "mail", Command: "+lint-html", Description: "Lint mail HTML body for compatibility / safety / Larksuite-native rules. Returns warnings/errors and (always) auto-fixed cleaned_html. Read-only: no draft, no API call. Use this BEFORE creating a draft to preview what the writing-path lint would change.", - Risk: "read", + Risk: common.RiskRead, // No API call → no scope requirement. Scopes: []string{}, // Identity-agnostic: lint is local pure-CPU. Both user and bot diff --git a/shortcuts/mail/mail_message.go b/shortcuts/mail/mail_message.go index 6e79e06a32..c73a84e5f5 100644 --- a/shortcuts/mail/mail_message.go +++ b/shortcuts/mail/mail_message.go @@ -15,7 +15,7 @@ var MailMessage = common.Shortcut{ Service: "mail", Command: "+message", Description: "Use only when reading full content for one email by one message ID. For multiple message IDs, use mail +messages; do not loop mail +message. Returns normalized body content plus attachments metadata, including inline images.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/mail/mail_message_modify.go b/shortcuts/mail/mail_message_modify.go index d88d93d6eb..f19daae279 100644 --- a/shortcuts/mail/mail_message_modify.go +++ b/shortcuts/mail/mail_message_modify.go @@ -25,7 +25,7 @@ var MailMessageModify = common.Shortcut{ Service: "mail", Command: "+message-modify", Description: "Modify existing mail messages by adding/removing label IDs or moving them to a folder. Batches message IDs in groups of 20 and keeps output compact.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify"}, ConditionalScopes: []string{ "mail:user_mailbox.folder:read", diff --git a/shortcuts/mail/mail_message_trash.go b/shortcuts/mail/mail_message_trash.go index b1b0f64788..8ebc9f85dc 100644 --- a/shortcuts/mail/mail_message_trash.go +++ b/shortcuts/mail/mail_message_trash.go @@ -16,7 +16,7 @@ var MailMessageTrash = common.Shortcut{ Service: "mail", Command: "+message-trash", Description: "Soft-delete existing mail messages. Batches message IDs in groups of 20 and calls batch_trash sequentially. Requires --yes.", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"mail:user_mailbox.message:modify"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_messages.go b/shortcuts/mail/mail_messages.go index 0d4fe3dd82..f3700c0224 100644 --- a/shortcuts/mail/mail_messages.go +++ b/shortcuts/mail/mail_messages.go @@ -24,7 +24,7 @@ var MailMessages = common.Shortcut{ Service: "mail", Command: "+messages", Description: "Use when reading full content for multiple emails by message ID. You may pass more than 20 IDs; the CLI handles them in batches of 20 and merges output while preserving request order.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/mail/mail_reply.go b/shortcuts/mail/mail_reply.go index 11acea385b..4e22746b5c 100644 --- a/shortcuts/mail/mail_reply.go +++ b/shortcuts/mail/mail_reply.go @@ -20,7 +20,7 @@ var MailReply = common.Shortcut{ Service: "mail", Command: "+reply", Description: "Reply to a message and save as draft (default). Use --confirm-send to send immediately after user confirmation. Sets Re: subject, In-Reply-To, and References headers automatically.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox.message:readonly", "mail:user_mailbox:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_reply_all.go b/shortcuts/mail/mail_reply_all.go index 3c2d19bef8..5292b598f5 100644 --- a/shortcuts/mail/mail_reply_all.go +++ b/shortcuts/mail/mail_reply_all.go @@ -20,7 +20,7 @@ var MailReplyAll = common.Shortcut{ Service: "mail", Command: "+reply-all", Description: "Reply to all recipients and save as draft (default). Use --confirm-send to send immediately after user confirmation. Includes all original To and CC automatically.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox.message:readonly", "mail:user_mailbox:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_send.go b/shortcuts/mail/mail_send.go index fde49ac404..fad28645e4 100644 --- a/shortcuts/mail/mail_send.go +++ b/shortcuts/mail/mail_send.go @@ -19,7 +19,7 @@ var MailSend = common.Shortcut{ Service: "mail", Command: "+send", Description: "Compose a new email and save as draft (default). Use --confirm-send to send immediately after user confirmation.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:send", "mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_send_receipt.go b/shortcuts/mail/mail_send_receipt.go index 681e22d274..48e7737c25 100644 --- a/shortcuts/mail/mail_send_receipt.go +++ b/shortcuts/mail/mail_send_receipt.go @@ -67,7 +67,7 @@ var MailSendReceipt = common.Shortcut{ Service: "mail", Command: "+send-receipt", Description: "Send a read-receipt reply for an incoming message that requested one (i.e. carries the READ_RECEIPT_REQUEST label). Body is auto-generated (subject / recipient / send time / read time) to match the Lark client's receipt format — callers cannot customize it, matching the industry norm that read-receipt bodies are system-generated templates, not free-form replies. Intended for agent use after the user confirms.", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{ "mail:user_mailbox.message:send", "mail:user_mailbox.message:modify", diff --git a/shortcuts/mail/mail_share_to_chat.go b/shortcuts/mail/mail_share_to_chat.go index 63021485f0..78a3d1cee4 100644 --- a/shortcuts/mail/mail_share_to_chat.go +++ b/shortcuts/mail/mail_share_to_chat.go @@ -23,7 +23,7 @@ var MailShareToChat = common.Shortcut{ Service: "mail", Command: "+share-to-chat", Description: "Share an email or thread as a card to a Lark IM chat.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{ "mail:user_mailbox.message:readonly", "im:message", diff --git a/shortcuts/mail/mail_signature.go b/shortcuts/mail/mail_signature.go index 8d443071bd..5f230058d5 100644 --- a/shortcuts/mail/mail_signature.go +++ b/shortcuts/mail/mail_signature.go @@ -18,7 +18,7 @@ var MailSignature = common.Shortcut{ Service: "mail", Command: "+signature", Description: "List or view email signatures with default usage info.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"mail:user_mailbox:readonly"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/mail/mail_template_create.go b/shortcuts/mail/mail_template_create.go index 4fa4d7e0ca..fe91e58620 100644 --- a/shortcuts/mail/mail_template_create.go +++ b/shortcuts/mail/mail_template_create.go @@ -16,7 +16,7 @@ var MailTemplateCreate = common.Shortcut{ Service: "mail", Command: "+template-create", Description: "Create a personal mail template. Scans HTML local paths (reusing draft inline-image detection), uploads inline images and non-inline attachments to Drive, rewrites HTML to cid: references, and POSTs a Template payload to mail.user_mailbox.templates.create.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/mail/mail_template_update.go b/shortcuts/mail/mail_template_update.go index 5ee6bd0be9..f5ade4c23f 100644 --- a/shortcuts/mail/mail_template_update.go +++ b/shortcuts/mail/mail_template_update.go @@ -17,7 +17,7 @@ var MailTemplateUpdate = common.Shortcut{ Service: "mail", Command: "+template-update", Description: "Update an existing mail template. Supports --inspect (read-only projection), --print-patch-template (prints a JSON skeleton for --patch-file), and flat flags (--set-subject / --set-name / etc). Internally it GETs the template, applies the patch, rewrites local paths to cid: refs, and PUTs a full-replace update (no optimistic locking: last-write-wins).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/mail/mail_thread.go b/shortcuts/mail/mail_thread.go index 19d210b792..a43ac6e98a 100644 --- a/shortcuts/mail/mail_thread.go +++ b/shortcuts/mail/mail_thread.go @@ -46,7 +46,7 @@ var MailThread = common.Shortcut{ Service: "mail", Command: "+thread", Description: "Use when querying a full mail conversation/thread by thread ID. Returns all messages in chronological order, including replies and drafts, with body content and attachments metadata, including inline images.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/mail/mail_triage.go b/shortcuts/mail/mail_triage.go index 0a3a6d1f2d..56231f936a 100644 --- a/shortcuts/mail/mail_triage.go +++ b/shortcuts/mail/mail_triage.go @@ -53,7 +53,7 @@ var MailTriage = common.Shortcut{ Service: "mail", Command: "+triage", Description: `List mail summaries (date/from/subject/message_id). Use --query for full-text search, --filter for exact-match conditions.`, - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/mail/mail_watch.go b/shortcuts/mail/mail_watch.go index b7a907fbaf..d070896899 100644 --- a/shortcuts/mail/mail_watch.go +++ b/shortcuts/mail/mail_watch.go @@ -96,7 +96,7 @@ var MailWatch = common.Shortcut{ Service: "mail", Command: "+watch", Description: "Watch for incoming mail events via WebSocket (requires scope mail:event and bot event mail.user_mailbox.event.message_received_v1 added). Run with --print-output-schema to see per-format field reference before parsing output.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"mail:event", "mail:user_mailbox.event.mail_address:read", "mail:user_mailbox:readonly", "mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, AuthTypes: []string{"user"}, Flags: []common.Flag{ diff --git a/shortcuts/markdown/markdown_create.go b/shortcuts/markdown/markdown_create.go index 46750f45e3..824dc83676 100644 --- a/shortcuts/markdown/markdown_create.go +++ b/shortcuts/markdown/markdown_create.go @@ -17,7 +17,7 @@ var MarkdownCreate = common.Shortcut{ Service: "markdown", Command: "+create", Description: "Create a Markdown file in Drive", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"drive:file:upload", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/markdown/markdown_diff.go b/shortcuts/markdown/markdown_diff.go index 44a21a2cdf..67c9dfd1ba 100644 --- a/shortcuts/markdown/markdown_diff.go +++ b/shortcuts/markdown/markdown_diff.go @@ -433,7 +433,7 @@ var MarkdownDiff = common.Shortcut{ Service: "markdown", Command: "+diff", Description: "Compare remote Markdown versions or compare remote Markdown against a local file", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:file:download"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/markdown/markdown_fetch.go b/shortcuts/markdown/markdown_fetch.go index a53115f52b..af25eee678 100644 --- a/shortcuts/markdown/markdown_fetch.go +++ b/shortcuts/markdown/markdown_fetch.go @@ -18,7 +18,7 @@ var MarkdownFetch = common.Shortcut{ Service: "markdown", Command: "+fetch", Description: "Fetch a Markdown file from Drive", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"drive:file:download"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/markdown/markdown_overwrite.go b/shortcuts/markdown/markdown_overwrite.go index ff6584c02b..5354d5d7a2 100644 --- a/shortcuts/markdown/markdown_overwrite.go +++ b/shortcuts/markdown/markdown_overwrite.go @@ -16,7 +16,7 @@ var MarkdownOverwrite = common.Shortcut{ Service: "markdown", Command: "+overwrite", Description: "Overwrite an existing Markdown file in Drive", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"drive:file:upload", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/markdown/markdown_patch.go b/shortcuts/markdown/markdown_patch.go index ef0b86e56c..87b4e1e740 100644 --- a/shortcuts/markdown/markdown_patch.go +++ b/shortcuts/markdown/markdown_patch.go @@ -31,7 +31,7 @@ var MarkdownPatch = common.Shortcut{ Service: "markdown", Command: "+patch", Description: "Patch a Markdown file in Drive via fetch-local-replace-overwrite", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"drive:file:download", "drive:file:upload", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_apply_permission.go b/shortcuts/minutes/minutes_apply_permission.go index 18703e6a92..86cdd8b6ba 100644 --- a/shortcuts/minutes/minutes_apply_permission.go +++ b/shortcuts/minutes/minutes_apply_permission.go @@ -19,7 +19,7 @@ var MinutesApplyPermission = common.Shortcut{ Service: "minutes", Command: "+apply-permission", Description: "Apply for view or edit permission on a minute", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"minutes:permission:apply"}, AuthTypes: []string{"user"}, Flags: []common.Flag{ diff --git a/shortcuts/minutes/minutes_detail.go b/shortcuts/minutes/minutes_detail.go index 7043e0bf0e..9046d9a5db 100644 --- a/shortcuts/minutes/minutes_detail.go +++ b/shortcuts/minutes/minutes_detail.go @@ -283,7 +283,7 @@ var MinutesDetail = common.Shortcut{ Service: "minutes", Command: "+detail", Description: "Query minute details with selective artifact flags (summary, todo, chapter, transcript, keyword)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"minutes:minutes.basic:read", "minutes:minutes.artifacts:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_download.go b/shortcuts/minutes/minutes_download.go index b0ace77ae6..68ccb328e0 100644 --- a/shortcuts/minutes/minutes_download.go +++ b/shortcuts/minutes/minutes_download.go @@ -41,7 +41,7 @@ var MinutesDownload = common.Shortcut{ Service: "minutes", Command: "+download", Description: "Download audio/video media file of a minute", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"minutes:minutes.media:export"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_search.go b/shortcuts/minutes/minutes_search.go index 93e8045acc..56aa78c9ae 100644 --- a/shortcuts/minutes/minutes_search.go +++ b/shortcuts/minutes/minutes_search.go @@ -223,7 +223,7 @@ var MinutesSearch = common.Shortcut{ Service: "minutes", Command: "+search", Description: "Search minutes by keyword, owners, participants, and time range with user or bot identity", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"minutes:minutes.search:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_speaker_replace.go b/shortcuts/minutes/minutes_speaker_replace.go index d69a14835b..e368a061f1 100644 --- a/shortcuts/minutes/minutes_speaker_replace.go +++ b/shortcuts/minutes/minutes_speaker_replace.go @@ -24,7 +24,7 @@ var MinutesSpeakerReplace = common.Shortcut{ Service: "minutes", Command: "+speaker-replace", Description: "Replace a speaker in a minute's transcript (rebind from one user to another)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"minutes:minutes:readonly", "minutes:minutes:update"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_summary.go b/shortcuts/minutes/minutes_summary.go index a96f024d33..b803cc9fe0 100644 --- a/shortcuts/minutes/minutes_summary.go +++ b/shortcuts/minutes/minutes_summary.go @@ -21,7 +21,7 @@ var MinutesSummary = common.Shortcut{ Service: "minutes", Command: "+summary", Description: "Replace the AI summary of a minute", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"minutes:minutes:update"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_todo.go b/shortcuts/minutes/minutes_todo.go index 93c84ea071..bcf4212866 100644 --- a/shortcuts/minutes/minutes_todo.go +++ b/shortcuts/minutes/minutes_todo.go @@ -36,7 +36,7 @@ var MinutesTodo = common.Shortcut{ Service: "minutes", Command: "+todo", Description: "Add, update, or delete todo item(s) on a minute", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"minutes:minutes:update"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_update.go b/shortcuts/minutes/minutes_update.go index 6b9f93783f..94d769029c 100644 --- a/shortcuts/minutes/minutes_update.go +++ b/shortcuts/minutes/minutes_update.go @@ -21,7 +21,7 @@ var MinutesUpdate = common.Shortcut{ Service: "minutes", Command: "+update", Description: "Update a minute's title", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"minutes:minutes:update"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_upload.go b/shortcuts/minutes/minutes_upload.go index 8911e82c5f..f32ab01c98 100644 --- a/shortcuts/minutes/minutes_upload.go +++ b/shortcuts/minutes/minutes_upload.go @@ -23,7 +23,7 @@ var MinutesUpload = common.Shortcut{ Service: "minutes", Command: "+upload", Description: "Upload a media file token to generate a minute", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"minutes:minutes.upload:write"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/minutes/minutes_word_replace.go b/shortcuts/minutes/minutes_word_replace.go index db7f2a9b41..17d52118f4 100644 --- a/shortcuts/minutes/minutes_word_replace.go +++ b/shortcuts/minutes/minutes_word_replace.go @@ -31,7 +31,7 @@ var MinutesWordReplace = common.Shortcut{ Service: "minutes", Command: "+word-replace", Description: "Batch replace words in a minute's transcript", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"minutes:minutes:update"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/note/note_detail.go b/shortcuts/note/note_detail.go index 21d3c14bae..291d6cb385 100644 --- a/shortcuts/note/note_detail.go +++ b/shortcuts/note/note_detail.go @@ -21,7 +21,7 @@ var NoteDetail = common.Shortcut{ Service: "note", Command: "+detail", Description: "Get note detail (display type, document tokens) by note_id", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"vc:note:read"}, AuthTypes: []string{"user"}, Flags: []common.Flag{ diff --git a/shortcuts/note/note_transcript.go b/shortcuts/note/note_transcript.go index fe84f05783..912e8e6739 100644 --- a/shortcuts/note/note_transcript.go +++ b/shortcuts/note/note_transcript.go @@ -54,7 +54,7 @@ var NoteTranscript = common.Shortcut{ Service: "note", Command: "+transcript", Description: "Fetch the unified note transcript and save it to a file", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"vc:note:read"}, AuthTypes: []string{"user"}, Flags: []common.Flag{ diff --git a/shortcuts/okr/okr_batch_create.go b/shortcuts/okr/okr_batch_create.go index 8c5c5c0bb6..20db312cff 100644 --- a/shortcuts/okr/okr_batch_create.go +++ b/shortcuts/okr/okr_batch_create.go @@ -184,7 +184,7 @@ var OKRBatchCreate = common.Shortcut{ Service: "okr", Command: "+batch-create", Description: "Batch create OKR objectives and key results with rollback on failure", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_create.go b/shortcuts/okr/okr_create.go index 3d674723fc..a984621c02 100644 --- a/shortcuts/okr/okr_create.go +++ b/shortcuts/okr/okr_create.go @@ -236,7 +236,7 @@ var OKRCreate = common.Shortcut{ Service: "okr", Command: "+create", Description: "Create a single OKR objective or key result", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_cycle_detail.go b/shortcuts/okr/okr_cycle_detail.go index 41be7b23f1..b49d30c25b 100644 --- a/shortcuts/okr/okr_cycle_detail.go +++ b/shortcuts/okr/okr_cycle_detail.go @@ -20,7 +20,7 @@ var OKRCycleDetail = common.Shortcut{ Service: "okr", Command: "+cycle-detail", Description: "List objectives and key results under an OKR cycle", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"okr:okr.content:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_cycle_list.go b/shortcuts/okr/okr_cycle_list.go index f6d6f74261..feeb29f894 100644 --- a/shortcuts/okr/okr_cycle_list.go +++ b/shortcuts/okr/okr_cycle_list.go @@ -87,7 +87,7 @@ var OKRListCycles = common.Shortcut{ Service: "okr", Command: "+cycle-list", Description: "List okr cycles of a certain user", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"okr:okr.period:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_image_upload.go b/shortcuts/okr/okr_image_upload.go index 118efd2da4..c5a0286a11 100644 --- a/shortcuts/okr/okr_image_upload.go +++ b/shortcuts/okr/okr_image_upload.go @@ -30,7 +30,7 @@ var OKRUploadImage = common.Shortcut{ Service: "okr", Command: "+upload-image", Description: "Upload an image for use in OKR progress rich text", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.progress.file:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/okr/okr_indicator_update.go b/shortcuts/okr/okr_indicator_update.go index bbf6c1d714..f615ef6ddb 100644 --- a/shortcuts/okr/okr_indicator_update.go +++ b/shortcuts/okr/okr_indicator_update.go @@ -66,7 +66,7 @@ var OKRIndicatorUpdate = common.Shortcut{ Service: "okr", Command: "+indicator-update", Description: "Update the indicator current value for an objective or key result", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_patch.go b/shortcuts/okr/okr_patch.go index 2ccb21ab89..a8f731db76 100644 --- a/shortcuts/okr/okr_patch.go +++ b/shortcuts/okr/okr_patch.go @@ -157,7 +157,7 @@ var OKRPatch = common.Shortcut{ Service: "okr", Command: "+patch", Description: "Patch an OKR objective or key result (content, notes, score, deadline)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_progress_create.go b/shortcuts/okr/okr_progress_create.go index 3a56d5d9df..4cdb83ce23 100644 --- a/shortcuts/okr/okr_progress_create.go +++ b/shortcuts/okr/okr_progress_create.go @@ -113,7 +113,7 @@ var OKRCreateProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-create", Description: "Create an OKR progress", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.progress:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_progress_delete.go b/shortcuts/okr/okr_progress_delete.go index 4ae321a40c..2146b9bb8e 100644 --- a/shortcuts/okr/okr_progress_delete.go +++ b/shortcuts/okr/okr_progress_delete.go @@ -18,7 +18,7 @@ var OKRDeleteProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-delete", Description: "Delete an OKR progress by ID", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"okr:okr.progress:delete"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_progress_get.go b/shortcuts/okr/okr_progress_get.go index e98e0944a4..c6b8878f17 100644 --- a/shortcuts/okr/okr_progress_get.go +++ b/shortcuts/okr/okr_progress_get.go @@ -19,7 +19,7 @@ var OKRGetProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-get", Description: "Get an OKR progress by ID", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"okr:okr.progress:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_progress_list.go b/shortcuts/okr/okr_progress_list.go index c1d483df52..2bbf3c5233 100644 --- a/shortcuts/okr/okr_progress_list.go +++ b/shortcuts/okr/okr_progress_list.go @@ -19,7 +19,7 @@ var OKRListProgress = common.Shortcut{ Service: "okr", Command: "+progress-list", Description: "List progress for an objective or key result", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"okr:okr.progress:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_progress_update.go b/shortcuts/okr/okr_progress_update.go index 52ae3b86aa..327daeb6b8 100644 --- a/shortcuts/okr/okr_progress_update.go +++ b/shortcuts/okr/okr_progress_update.go @@ -85,7 +85,7 @@ var OKRUpdateProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-update", Description: "Update an OKR progress", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.progress:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_reorder.go b/shortcuts/okr/okr_reorder.go index 829c2eac5f..d0e3baed45 100644 --- a/shortcuts/okr/okr_reorder.go +++ b/shortcuts/okr/okr_reorder.go @@ -245,7 +245,7 @@ var OKRReorder = common.Shortcut{ Service: "okr", Command: "+reorder", Description: "Adjust the position (order) of OKR objectives or key results", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/okr/okr_weight.go b/shortcuts/okr/okr_weight.go index 1b5704e177..c1dd009301 100644 --- a/shortcuts/okr/okr_weight.go +++ b/shortcuts/okr/okr_weight.go @@ -226,7 +226,7 @@ var OKRWeight = common.Shortcut{ Service: "okr", Command: "+weight", Description: "Adjust the weight of OKR objectives or key results", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/backward/lark_sheets_cell_data.go b/shortcuts/sheets/backward/lark_sheets_cell_data.go index 77a1571be2..2a21ccb12f 100644 --- a/shortcuts/sheets/backward/lark_sheets_cell_data.go +++ b/shortcuts/sheets/backward/lark_sheets_cell_data.go @@ -28,7 +28,7 @@ var SheetRead = common.Shortcut{ Service: "sheets", Command: "+read", Description: "Read spreadsheet cell values", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -104,7 +104,7 @@ var SheetWrite = common.Shortcut{ Service: "sheets", Command: "+write", Description: "Write to spreadsheet cells (overwrite mode)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -186,7 +186,7 @@ var SheetAppend = common.Shortcut{ Service: "sheets", Command: "+append", Description: "Append rows to a spreadsheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -266,7 +266,7 @@ var SheetFind = common.Shortcut{ Service: "sheets", Command: "+find", Description: "Find cells in a spreadsheet", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -350,7 +350,7 @@ var SheetReplace = common.Shortcut{ Service: "sheets", Command: "+replace", Description: "Find and replace cell values in a spreadsheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_cell_images.go b/shortcuts/sheets/backward/lark_sheets_cell_images.go index 6f1a3cb566..ceb4ae1892 100644 --- a/shortcuts/sheets/backward/lark_sheets_cell_images.go +++ b/shortcuts/sheets/backward/lark_sheets_cell_images.go @@ -21,7 +21,7 @@ var SheetWriteImage = common.Shortcut{ Service: "sheets", Command: "+write-image", Description: "Write an image into a spreadsheet cell", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_cell_style_and_merge.go b/shortcuts/sheets/backward/lark_sheets_cell_style_and_merge.go index 30622c052c..ab4baf1740 100644 --- a/shortcuts/sheets/backward/lark_sheets_cell_style_and_merge.go +++ b/shortcuts/sheets/backward/lark_sheets_cell_style_and_merge.go @@ -59,7 +59,7 @@ var SheetSetStyle = common.Shortcut{ Service: "sheets", Command: "+set-style", Description: "Set cell style for a range", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -141,7 +141,7 @@ var SheetBatchSetStyle = common.Shortcut{ Service: "sheets", Command: "+batch-set-style", Description: "Batch set cell styles for multiple ranges", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -227,7 +227,7 @@ var SheetMergeCells = common.Shortcut{ Service: "sheets", Command: "+merge-cells", Description: "Merge cells in a spreadsheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -292,7 +292,7 @@ var SheetUnmergeCells = common.Shortcut{ Service: "sheets", Command: "+unmerge-cells", Description: "Unmerge (split) cells in a spreadsheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_dropdown.go b/shortcuts/sheets/backward/lark_sheets_dropdown.go index 6e07dad3ee..e3cef84c15 100644 --- a/shortcuts/sheets/backward/lark_sheets_dropdown.go +++ b/shortcuts/sheets/backward/lark_sheets_dropdown.go @@ -107,7 +107,7 @@ var SheetSetDropdown = common.Shortcut{ Service: "sheets", Command: "+set-dropdown", Description: "Set dropdown list on a cell range", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -168,7 +168,7 @@ var SheetUpdateDropdown = common.Shortcut{ Service: "sheets", Command: "+update-dropdown", Description: "Update dropdown list settings", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -235,7 +235,7 @@ var SheetGetDropdown = common.Shortcut{ Service: "sheets", Command: "+get-dropdown", Description: "Get dropdown list settings for a range", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -279,7 +279,7 @@ var SheetDeleteDropdown = common.Shortcut{ Service: "sheets", Command: "+delete-dropdown", Description: "Delete dropdown list from cell ranges", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_filter_views.go b/shortcuts/sheets/backward/lark_sheets_filter_views.go index fc8105e185..ae383c3fbc 100644 --- a/shortcuts/sheets/backward/lark_sheets_filter_views.go +++ b/shortcuts/sheets/backward/lark_sheets_filter_views.go @@ -43,7 +43,7 @@ var SheetCreateFilterView = common.Shortcut{ Service: "sheets", Command: "+create-filter-view", Description: "Create a filter view", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -98,7 +98,7 @@ var SheetUpdateFilterView = common.Shortcut{ Service: "sheets", Command: "+update-filter-view", Description: "Update a filter view", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -154,7 +154,7 @@ var SheetListFilterViews = common.Shortcut{ Service: "sheets", Command: "+list-filter-views", Description: "List all filter views in a sheet", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -187,7 +187,7 @@ var SheetGetFilterView = common.Shortcut{ Service: "sheets", Command: "+get-filter-view", Description: "Get a filter view by ID", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -221,7 +221,7 @@ var SheetDeleteFilterView = common.Shortcut{ Service: "sheets", Command: "+delete-filter-view", Description: "Delete a filter view", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -255,7 +255,7 @@ var SheetCreateFilterViewCondition = common.Shortcut{ Service: "sheets", Command: "+create-filter-view-condition", Description: "Create a filter condition on a filter view", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -297,7 +297,7 @@ var SheetUpdateFilterViewCondition = common.Shortcut{ Service: "sheets", Command: "+update-filter-view-condition", Description: "Update a filter condition on a filter view", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -350,7 +350,7 @@ var SheetListFilterViewConditions = common.Shortcut{ Service: "sheets", Command: "+list-filter-view-conditions", Description: "List all filter conditions of a filter view", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -386,7 +386,7 @@ var SheetGetFilterViewCondition = common.Shortcut{ Service: "sheets", Command: "+get-filter-view-condition", Description: "Get a filter condition by column", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -424,7 +424,7 @@ var SheetDeleteFilterViewCondition = common.Shortcut{ Service: "sheets", Command: "+delete-filter-view-condition", Description: "Delete a filter condition from a filter view", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_float_images.go b/shortcuts/sheets/backward/lark_sheets_float_images.go index e0ebc79e79..7981cb71d7 100644 --- a/shortcuts/sheets/backward/lark_sheets_float_images.go +++ b/shortcuts/sheets/backward/lark_sheets_float_images.go @@ -66,7 +66,7 @@ var SheetMediaUpload = common.Shortcut{ Service: "sheets", Command: "+media-upload", Description: "Upload a local image for use as a floating image and return the file_token", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.media:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -316,7 +316,7 @@ var SheetCreateFloatImage = common.Shortcut{ Service: "sheets", Command: "+create-float-image", Description: "Create a floating image on a sheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -369,7 +369,7 @@ var SheetUpdateFloatImage = common.Shortcut{ Service: "sheets", Command: "+update-float-image", Description: "Update a floating image", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -418,7 +418,7 @@ var SheetGetFloatImage = common.Shortcut{ Service: "sheets", Command: "+get-float-image", Description: "Get a floating image by ID", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -452,7 +452,7 @@ var SheetListFloatImages = common.Shortcut{ Service: "sheets", Command: "+list-float-images", Description: "List all floating images in a sheet", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -485,7 +485,7 @@ var SheetDeleteFloatImage = common.Shortcut{ Service: "sheets", Command: "+delete-float-image", Description: "Delete a floating image", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_row_column_management.go b/shortcuts/sheets/backward/lark_sheets_row_column_management.go index ea1e913a85..d07747dd44 100644 --- a/shortcuts/sheets/backward/lark_sheets_row_column_management.go +++ b/shortcuts/sheets/backward/lark_sheets_row_column_management.go @@ -16,7 +16,7 @@ var SheetAddDimension = common.Shortcut{ Service: "sheets", Command: "+add-dimension", Description: "Add rows or columns at the end of a sheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -75,7 +75,7 @@ var SheetInsertDimension = common.Shortcut{ Service: "sheets", Command: "+insert-dimension", Description: "Insert rows or columns at a specified position", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -148,7 +148,7 @@ var SheetUpdateDimension = common.Shortcut{ Service: "sheets", Command: "+update-dimension", Description: "Update row or column properties (visibility, size)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -237,7 +237,7 @@ var SheetMoveDimension = common.Shortcut{ Service: "sheets", Command: "+move-dimension", Description: "Move rows or columns to a new position", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -309,7 +309,7 @@ var SheetDeleteDimension = common.Shortcut{ Service: "sheets", Command: "+delete-dimension", Description: "Delete rows or columns", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_sheet_management.go b/shortcuts/sheets/backward/lark_sheets_sheet_management.go index 4988fdd4c0..4c7e82369d 100644 --- a/shortcuts/sheets/backward/lark_sheets_sheet_management.go +++ b/shortcuts/sheets/backward/lark_sheets_sheet_management.go @@ -453,7 +453,7 @@ var SheetCreateSheet = common.Shortcut{ Service: "sheets", Command: "+create-sheet", Description: "Create a sheet in an existing spreadsheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -504,7 +504,7 @@ var SheetCopySheet = common.Shortcut{ Service: "sheets", Command: "+copy-sheet", Description: "Copy a sheet within a spreadsheet", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -578,7 +578,7 @@ var SheetDeleteSheet = common.Shortcut{ Service: "sheets", Command: "+delete-sheet", Description: "Delete a sheet from a spreadsheet", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -618,7 +618,7 @@ var SheetUpdateSheet = common.Shortcut{ Service: "sheets", Command: "+update-sheet", Description: "Update sheet properties", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/backward/lark_sheets_spreadsheet_management.go b/shortcuts/sheets/backward/lark_sheets_spreadsheet_management.go index f06b5b2adf..f643ab1bce 100644 --- a/shortcuts/sheets/backward/lark_sheets_spreadsheet_management.go +++ b/shortcuts/sheets/backward/lark_sheets_spreadsheet_management.go @@ -23,7 +23,7 @@ var SheetInfo = common.Shortcut{ Service: "sheets", Command: "+info", Description: "View spreadsheet metadata and sheet information", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet.meta:read", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -82,7 +82,7 @@ var SheetCreate = common.Shortcut{ Service: "sheets", Command: "+create", Description: "Create a spreadsheet (optional header row and initial data)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:create", "sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ @@ -197,7 +197,7 @@ var SheetExport = common.Shortcut{ Service: "sheets", Command: "+export", Description: "Export a spreadsheet (async task polling + optional download)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"docs:document:export", "drive:file:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/sheets/internal/gen/main.go b/shortcuts/sheets/internal/gen/main.go index 4f481ca5ea..5dc3fa4218 100644 --- a/shortcuts/sheets/internal/gen/main.go +++ b/shortcuts/sheets/internal/gen/main.go @@ -21,6 +21,8 @@ import ( "runtime" "sort" "strings" + + "github.com/larksuite/cli/internal/core" ) type flagDef struct { @@ -145,6 +147,13 @@ func genFlagDefs(dir string) { cd := defs[k] fmt.Fprintf(&b, "%q: {\n", k) if cd.Risk != "" { + // flag-defs.json is hand-maintained data, so risk crosses a + // string boundary here. Reject a value outside the taxonomy at + // generation time rather than emitting it into Go source, where + // the untyped literal would convert silently. + if _, err := core.ParseRisk(cd.Risk); err != nil { + log.Fatalf("data/flag-defs.json: %q: %v", k, err) + } fmt.Fprintf(&b, "Risk: %q,\n", cd.Risk) } if cd.Flags != nil { diff --git a/shortcuts/sheets/lark_sheet_batch_update.go b/shortcuts/sheets/lark_sheet_batch_update.go index a6a5e0a5c3..5bb0759e04 100644 --- a/shortcuts/sheets/lark_sheet_batch_update.go +++ b/shortcuts/sheets/lark_sheet_batch_update.go @@ -43,7 +43,7 @@ var BatchUpdate = common.Shortcut{ Service: "sheets", Command: "+batch-update", Description: "Execute a batch of write shortcuts as a single atomic request (rolls back on failure by default).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -155,7 +155,7 @@ var CellsBatchSetStyle = common.Shortcut{ Service: "sheets", Command: "+cells-batch-set-style", Description: "Apply one style block to many sheet-prefixed ranges in one atomic batch.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -259,7 +259,7 @@ var CellsBatchClear = common.Shortcut{ Service: "sheets", Command: "+cells-batch-clear", Description: "Clear content/formats across many sheet-prefixed ranges in one atomic batch (irreversible).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -335,7 +335,7 @@ var DropdownUpdate = common.Shortcut{ Service: "sheets", Command: "+dropdown-update", Description: "Install or replace one dropdown across many sheet-prefixed ranges atomically.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -381,7 +381,7 @@ var DropdownDelete = common.Shortcut{ Service: "sheets", Command: "+dropdown-delete", Description: "Clear dropdowns from many sheet-prefixed ranges atomically (irreversible).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_changeset.go b/shortcuts/sheets/lark_sheet_changeset.go index 8a962f944b..00d5fbfe04 100644 --- a/shortcuts/sheets/lark_sheet_changeset.go +++ b/shortcuts/sheets/lark_sheet_changeset.go @@ -29,7 +29,7 @@ var ChangesetGet = common.Shortcut{ Service: "sheets", Command: "+changeset-get", Description: "Fetch the raw changeset (edit actions) between two versions, to review whether an AI edit fulfilled the request.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_formula_verify.go b/shortcuts/sheets/lark_sheet_formula_verify.go index 62b6e1f42d..e591cfe7b6 100644 --- a/shortcuts/sheets/lark_sheet_formula_verify.go +++ b/shortcuts/sheets/lark_sheet_formula_verify.go @@ -28,7 +28,7 @@ var FormulaVerify = common.Shortcut{ Service: "sheets", Command: "+formula-verify", Description: "Scan formulas / cell errors and return a recalc.py-shaped status report (success / errors_found / partial).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_history_list.go b/shortcuts/sheets/lark_sheet_history_list.go index 6d1343ea73..4204cc3017 100644 --- a/shortcuts/sheets/lark_sheet_history_list.go +++ b/shortcuts/sheets/lark_sheet_history_list.go @@ -46,7 +46,7 @@ var HistoryList = common.Shortcut{ Service: "sheets", Command: "+history-list", Description: "List a spreadsheet's edit history versions (history_version_id, create_time, action, all_block_revision).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_history_revert.go b/shortcuts/sheets/lark_sheet_history_revert.go index c5e275d7c8..28aa67ab27 100644 --- a/shortcuts/sheets/lark_sheet_history_revert.go +++ b/shortcuts/sheets/lark_sheet_history_revert.go @@ -89,7 +89,7 @@ var HistoryRevert = common.Shortcut{ Service: "sheets", Command: "+history-revert", Description: "Revert a spreadsheet to a given history version (asynchronous; poll with +history-revert-status).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -135,7 +135,7 @@ var HistoryRevertStatus = common.Shortcut{ Service: "sheets", Command: "+history-revert-status", Description: "Poll the status of a history revert (in-progress / success / failure).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_object_crud.go b/shortcuts/sheets/lark_sheet_object_crud.go index 83f3b550ee..f08c7e8967 100644 --- a/shortcuts/sheets/lark_sheet_object_crud.go +++ b/shortcuts/sheets/lark_sheet_object_crud.go @@ -125,7 +125,7 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { Service: "sheets", Command: spec.commandPrefix + "-create", Description: "Create a " + strings.TrimPrefix(spec.commandPrefix, "+") + " object via the manage_*_object tool.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -222,7 +222,7 @@ func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut { Service: "sheets", Command: spec.commandPrefix + "-update", Description: "Update an existing " + strings.TrimPrefix(spec.commandPrefix, "+") + " object (read-modify-write; consult --list first).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -306,7 +306,7 @@ func newObjectDeleteShortcut(spec objectCRUDSpec) common.Shortcut { Service: "sheets", Command: spec.commandPrefix + "-delete", Description: "Delete a " + strings.TrimPrefix(spec.commandPrefix, "+") + " object (irreversible).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -825,9 +825,9 @@ func floatImageProperties(runtime flagView, uploadedImageToken string, requireIm } func newFloatImageWriteShortcut(command, description, op string, withIDFlag, isHighRisk bool) common.Shortcut { - risk := "write" + risk := common.RiskWrite if isHighRisk { - risk = "high-risk-write" + risk = common.RiskHighRiskWrite } flags := flagsFor(command) return common.Shortcut{ @@ -1007,7 +1007,7 @@ var FilterCreate = common.Shortcut{ Service: "sheets", Command: "+filter-create", Description: "Create a sheet-level filter (one per sheet).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -1082,7 +1082,7 @@ var FilterUpdate = common.Shortcut{ Service: "sheets", Command: "+filter-update", Description: "Update the sheet-level filter (overwrite rules + range).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -1150,7 +1150,7 @@ var FilterDelete = common.Shortcut{ Service: "sheets", Command: "+filter-delete", Description: "Remove the sheet-level filter (irreversible).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_object_list.go b/shortcuts/sheets/lark_sheet_object_list.go index 9450f7f8c0..9bb197a776 100644 --- a/shortcuts/sheets/lark_sheet_object_list.go +++ b/shortcuts/sheets/lark_sheet_object_list.go @@ -39,7 +39,7 @@ func newObjectListShortcut(spec objectListSpec) common.Shortcut { Service: "sheets", Command: spec.command, Description: spec.description, - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_range_operations.go b/shortcuts/sheets/lark_sheet_range_operations.go index 78acf66337..5235418271 100644 --- a/shortcuts/sheets/lark_sheet_range_operations.go +++ b/shortcuts/sheets/lark_sheet_range_operations.go @@ -34,7 +34,7 @@ var CellsClear = common.Shortcut{ Service: "sheets", Command: "+cells-clear", Description: "Clear cell content, formats, or both within a range (irreversible).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -143,7 +143,7 @@ func newMergeShortcut(command, desc, op string, withMergeType bool) common.Short Service: "sheets", Command: command, Description: desc, - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -243,7 +243,7 @@ var RowsResize = common.Shortcut{ Service: "sheets", Command: "+rows-resize", Description: "Resize rows in pixels: --range + --height for one uniform height, --heights '{\"1\":50,\"2:20\":30,\"21\":\"auto\"}' for per-row heights in one atomic call, or --type standard/auto (--range is 1-based A1 like \"2:10\" or \"5\").", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -261,7 +261,7 @@ var ColsResize = common.Shortcut{ Service: "sheets", Command: "+cols-resize", Description: "Resize columns in pixels (NOT Excel char units): --range + --width for one uniform width, --widths '{\"A\":100,\"C:E\":120}' for per-column widths in one atomic call, or --type standard to reset (--range is column letters like \"A:E\" or \"C\"; no auto for cols).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -632,7 +632,7 @@ var RangeMove = common.Shortcut{ Service: "sheets", Command: "+range-move", Description: "Cut a range and paste it at a new location (optionally cross-sheet).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -648,7 +648,7 @@ var RangeCopy = common.Shortcut{ Service: "sheets", Command: "+range-copy", Description: "Copy a range to a new location (--paste-type controls what is copied).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -666,7 +666,7 @@ var RangeFill = common.Shortcut{ Service: "sheets", Command: "+range-fill", Description: "Autofill a target range from a source template (copy / linear / growth / date series).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -705,7 +705,7 @@ var RangeSort = common.Shortcut{ Service: "sheets", Command: "+range-sort", Description: "Sort rows within a range by one or more columns.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_read_data.go b/shortcuts/sheets/lark_sheet_read_data.go index 9b52dde719..6a0c4e7d15 100644 --- a/shortcuts/sheets/lark_sheet_read_data.go +++ b/shortcuts/sheets/lark_sheet_read_data.go @@ -34,7 +34,7 @@ var CellsGet = common.Shortcut{ Service: "sheets", Command: "+cells-get", Description: "Read one or more cell ranges with values, formulas, and optional styles / comments / data validation.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -127,7 +127,7 @@ var CsvGet = common.Shortcut{ Service: "sheets", Command: "+csv-get", Description: "Read a range as CSV (with [row=N] line prefix by default).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -246,7 +246,7 @@ var DropdownGet = common.Shortcut{ Service: "sheets", Command: "+dropdown-get", Description: "Read the dropdown / data-validation configuration on a range.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_revision_get.go b/shortcuts/sheets/lark_sheet_revision_get.go index 102f0dd978..b81507c9e2 100644 --- a/shortcuts/sheets/lark_sheet_revision_get.go +++ b/shortcuts/sheets/lark_sheet_revision_get.go @@ -26,7 +26,7 @@ var RevisionGet = common.Shortcut{ Service: "sheets", Command: "+revision-get", Description: "Get the spreadsheet's current document revision (version number).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_search_replace.go b/shortcuts/sheets/lark_sheet_search_replace.go index 3691f1146e..b26fabbaf6 100644 --- a/shortcuts/sheets/lark_sheet_search_replace.go +++ b/shortcuts/sheets/lark_sheet_search_replace.go @@ -23,7 +23,7 @@ var CellsSearch = common.Shortcut{ Service: "sheets", Command: "+cells-search", Description: "Find cells matching --find in a spreadsheet (case / regex / whole-cell / formula-text controls).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -109,7 +109,7 @@ var CellsReplace = common.Shortcut{ Service: "sheets", Command: "+cells-replace", Description: "Find and replace text in a spreadsheet (case / regex / whole-cell / formula-text controls).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_sheet_structure.go b/shortcuts/sheets/lark_sheet_sheet_structure.go index e5c83e8176..2788697b8a 100644 --- a/shortcuts/sheets/lark_sheet_sheet_structure.go +++ b/shortcuts/sheets/lark_sheet_sheet_structure.go @@ -33,7 +33,7 @@ var SheetInfo = common.Shortcut{ Service: "sheets", Command: "+sheet-info", Description: "Get a sub-sheet's layout metadata: row heights, column widths, hidden rows/cols, merges, groups, freeze.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -123,7 +123,7 @@ var DimInsert = common.Shortcut{ Service: "sheets", Command: "+dim-insert", Description: "Insert blank rows or columns at a given position.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -198,7 +198,7 @@ var DimDelete = common.Shortcut{ Service: "sheets", Command: "+dim-delete", Description: "Delete rows or columns (irreversible).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -287,7 +287,7 @@ var DimFreeze = common.Shortcut{ Service: "sheets", Command: "+dim-freeze", Description: "Freeze the first N rows or columns; --count 0 unfreezes the chosen dimension.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -377,7 +377,7 @@ func dimRangeOpInput(runtime flagView, token, sheetID, sheetName, op string) (ma } // newDimRangeOpShortcut builds the shared shape for hide / unhide. -func newDimRangeOpShortcut(command, desc, op, risk string) common.Shortcut { +func newDimRangeOpShortcut(command, desc, op string, risk common.Risk) common.Shortcut { return common.Shortcut{ Service: "sheets", Command: command, @@ -426,7 +426,7 @@ func newDimGroupShortcut(command, desc, op string) common.Shortcut { Service: "sheets", Command: command, Description: desc, - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -569,7 +569,7 @@ var DimMove = common.Shortcut{ Service: "sheets", Command: "+dim-move", Description: "Move a contiguous block of rows or columns to a new position (re-numbers neighbors).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_table_io.go b/shortcuts/sheets/lark_sheet_table_io.go index 12fb08bc86..78b08d60fe 100644 --- a/shortcuts/sheets/lark_sheet_table_io.go +++ b/shortcuts/sheets/lark_sheet_table_io.go @@ -47,7 +47,7 @@ var TablePut = common.Shortcut{ Service: "sheets", Command: "+table-put", Description: "Write a typed table (columns with types + rows) into an existing spreadsheet; numbers and dates stay type-faithful.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:read", "sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -1145,7 +1145,7 @@ var TableGet = common.Shortcut{ Service: "sheets", Command: "+table-get", Description: "Read sheets back into the typed table protocol (mirror of +table-put); column types are inferred from number_format so the output feeds straight to +table-put or a DataFrame.", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_workbook.go b/shortcuts/sheets/lark_sheet_workbook.go index a0201baa70..8926d4e771 100644 --- a/shortcuts/sheets/lark_sheet_workbook.go +++ b/shortcuts/sheets/lark_sheet_workbook.go @@ -39,7 +39,7 @@ var WorkbookInfo = common.Shortcut{ Service: "sheets", Command: "+workbook-info", Description: "List sub-sheets of a spreadsheet with metadata (sheet_id, title, dimensions, freeze, hidden).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -81,7 +81,7 @@ var SheetCreate = common.Shortcut{ Service: "sheets", Command: "+sheet-create", Description: "Create a new sub-sheet with an optional position and initial dimensions.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -216,7 +216,7 @@ var SheetDelete = common.Shortcut{ Service: "sheets", Command: "+sheet-delete", Description: "Delete a sub-sheet (irreversible).", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -258,7 +258,7 @@ var SheetRename = common.Shortcut{ Service: "sheets", Command: "+sheet-rename", Description: "Rename a sub-sheet.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -305,7 +305,7 @@ var SheetMove = common.Shortcut{ Service: "sheets", Command: "+sheet-move", Description: "Move a sub-sheet to a new position.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:read", "sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -400,7 +400,7 @@ var SheetCopy = common.Shortcut{ Service: "sheets", Command: "+sheet-copy", Description: "Duplicate a sub-sheet, optionally renaming and repositioning the copy.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -464,7 +464,7 @@ func newSheetVisibilityShortcut(command, desc, op string) common.Shortcut { Service: "sheets", Command: command, Description: desc, - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -513,7 +513,7 @@ var SheetSetTabColor = common.Shortcut{ Service: "sheets", Command: "+sheet-set-tab-color", Description: "Set or clear the tab color of a sub-sheet.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -573,7 +573,7 @@ var WorkbookCreate = common.Shortcut{ Service: "sheets", Command: "+workbook-create", Description: "Create a new spreadsheet, optionally pre-filled with untyped --values or typed --sheets (type-faithful one-step create + write).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:create", "sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -1643,7 +1643,7 @@ var WorkbookExport = common.Shortcut{ Service: "sheets", Command: "+workbook-export", Description: "Export a spreadsheet to xlsx or a single sheet to csv (async + poll + optional download).", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"sheets:spreadsheet:read", "docs:document:export", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -1834,7 +1834,7 @@ var WorkbookImport = common.Shortcut{ Service: "sheets", Command: "+workbook-import", Description: "Import a local xlsx/xls/csv file as a new spreadsheet (async + poll). Reuses the drive import core with type fixed to sheet.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"docs:document.media:upload", "docs:document:import"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/sheets/lark_sheet_write_cells.go b/shortcuts/sheets/lark_sheet_write_cells.go index 5415dcbad5..1b046d8e22 100644 --- a/shortcuts/sheets/lark_sheet_write_cells.go +++ b/shortcuts/sheets/lark_sheet_write_cells.go @@ -43,7 +43,7 @@ var CellsSet = common.Shortcut{ Service: "sheets", Command: "+cells-set", Description: "Write values / formulas / styles / comments / data validation / embed-image to a cell range.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -119,7 +119,7 @@ var CellsSetStyle = common.Shortcut{ Service: "sheets", Command: "+cells-set-style", Description: "Apply style flags to every cell in a range (values / formulas untouched).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -209,7 +209,7 @@ var CsvPut = common.Shortcut{ Service: "sheets", Command: "+csv-put", Description: "Paste RFC-4180 CSV into a sheet at --start-cell (values or formulas: a leading = is evaluated as a formula; no styles / comments; auto-expands sheet if needed).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -401,7 +401,7 @@ var DropdownSet = common.Shortcut{ Service: "sheets", Command: "+dropdown-set", Description: "Attach a dropdown / data-validation list to every cell in --range.", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, @@ -756,7 +756,7 @@ var CellsSetImage = common.Shortcut{ Service: "sheets", Command: "+cells-set-image", Description: "Embed a local image into a single cell (uploads via drive, then set_cell_range with rich_text embed-image).", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"sheets:spreadsheet:write_only", "drive:file:upload"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/slides/slides_add_slide.go b/shortcuts/slides/slides_add_slide.go index 75607e28a4..604a641033 100644 --- a/shortcuts/slides/slides_add_slide.go +++ b/shortcuts/slides/slides_add_slide.go @@ -37,7 +37,7 @@ var SlidesAddSlide = common.Shortcut{ Service: "slides", Command: "+add-slide", Description: "Add one page to an existing presentation ( placeholders are auto-uploaded and replaced with file_token)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"slides:presentation:update", "slides:presentation:write_only"}, // Both extras are path-dependent, so they stay conditional rather than // gating every call: wiki:node:read only when --presentation is a wiki URL, diff --git a/shortcuts/slides/slides_create.go b/shortcuts/slides/slides_create.go index 50e123b49a..7f160abc79 100644 --- a/shortcuts/slides/slides_create.go +++ b/shortcuts/slides/slides_create.go @@ -26,7 +26,7 @@ var SlidesCreate = common.Shortcut{ Service: "slides", Command: "+create", Description: "Create a Lark Slides presentation", - Risk: "write", + Risk: common.RiskWrite, AuthTypes: []string{"user", "bot"}, // docs:document.media:upload is required by the @-placeholder upload path. // Declared up-front (matching the convention used by other multi-API shortcuts diff --git a/shortcuts/slides/slides_delete_slide.go b/shortcuts/slides/slides_delete_slide.go index e23547fd8c..210b8e008c 100644 --- a/shortcuts/slides/slides_delete_slide.go +++ b/shortcuts/slides/slides_delete_slide.go @@ -27,7 +27,7 @@ var SlidesDeleteSlide = common.Shortcut{ Service: "slides", Command: "+delete-slide", Description: "Delete one page from a presentation by slide_id", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"slides:presentation:update", "slides:presentation:write_only"}, // wiki:node:read is required only when --presentation is a wiki URL. ConditionalScopes: []string{"wiki:node:read"}, diff --git a/shortcuts/slides/slides_history.go b/shortcuts/slides/slides_history.go index 250971b736..1437988ff1 100644 --- a/shortcuts/slides/slides_history.go +++ b/shortcuts/slides/slides_history.go @@ -105,7 +105,7 @@ var SlidesHistoryList = common.Shortcut{ Service: "slides", Command: "+history-list", Description: "List Slides presentation history versions", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"slides:presentation:read"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, @@ -168,7 +168,7 @@ var SlidesHistoryRevert = common.Shortcut{ Service: "slides", Command: "+history-revert", Description: "Revert a Slides presentation to a historical version", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"slides:presentation:update", "slides:presentation:write_only"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, @@ -231,7 +231,7 @@ var SlidesHistoryRevertStatus = common.Shortcut{ Service: "slides", Command: "+history-revert-status", Description: "Get Slides history revert task status", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"slides:presentation:read"}, ConditionalScopes: []string{"wiki:node:read"}, AuthTypes: []string{"user", "bot"}, diff --git a/shortcuts/slides/slides_media_upload.go b/shortcuts/slides/slides_media_upload.go index 3d50422f34..3326319fbc 100644 --- a/shortcuts/slides/slides_media_upload.go +++ b/shortcuts/slides/slides_media_upload.go @@ -34,7 +34,7 @@ var SlidesMediaUpload = common.Shortcut{ Service: "slides", Command: "+media-upload", Description: "Upload a local image to a slides presentation and return the file_token (use as )", - Risk: "write", + Risk: common.RiskWrite, // wiki:node:read is required by the wiki-URL resolution path. Declared // up-front (matching the convention used by other multi-API shortcuts) so // users without it get the standard auth login --scope hint at pre-flight. diff --git a/shortcuts/slides/slides_replace_pages.go b/shortcuts/slides/slides_replace_pages.go index 2991a84a27..2304dbe0e2 100644 --- a/shortcuts/slides/slides_replace_pages.go +++ b/shortcuts/slides/slides_replace_pages.go @@ -40,7 +40,7 @@ var SlidesReplacePages = common.Shortcut{ Service: "slides", Command: "+replace-pages", Description: "Deprecated — use +update-slide once per page (in place: keeps slide_id and page order; elements written back with their original ids keep them). This rebuild changes slide_id and every element id; not atomic", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"slides:presentation:update", "slides:presentation:write_only"}, // wiki:node:read is required only when --presentation is a wiki URL. ConditionalScopes: []string{"wiki:node:read"}, diff --git a/shortcuts/slides/slides_replace_slide.go b/shortcuts/slides/slides_replace_slide.go index b681faf8c0..a5b2508f22 100644 --- a/shortcuts/slides/slides_replace_slide.go +++ b/shortcuts/slides/slides_replace_slide.go @@ -42,7 +42,7 @@ var SlidesReplaceSlide = common.Shortcut{ Service: "slides", Command: "+replace-slide", Description: "Replace elements on a slide via block_replace / block_insert parts (auto-injects id + on shape elements)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"slides:presentation:update", "slides:presentation:write_only"}, // wiki:node:read is required only when --presentation is a wiki URL. ConditionalScopes: []string{"wiki:node:read"}, diff --git a/shortcuts/slides/slides_screenshot.go b/shortcuts/slides/slides_screenshot.go index 035c7e77df..694f170ed6 100644 --- a/shortcuts/slides/slides_screenshot.go +++ b/shortcuts/slides/slides_screenshot.go @@ -40,7 +40,7 @@ var SlidesScreenshot = common.Shortcut{ Service: "slides", Command: "+screenshot", Description: "Save up to 10 slide screenshots to local files without printing Base64 image data", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"slides:presentation:screenshot"}, // wiki:node:read is required only when --presentation is a wiki URL. ConditionalScopes: []string{"wiki:node:read"}, diff --git a/shortcuts/slides/slides_update_slide.go b/shortcuts/slides/slides_update_slide.go index 9d34afbe1d..dd241bc610 100644 --- a/shortcuts/slides/slides_update_slide.go +++ b/shortcuts/slides/slides_update_slide.go @@ -41,7 +41,7 @@ var SlidesUpdateSlide = common.Shortcut{ Service: "slides", Command: "+update-slide", Description: "Apply a full XML to an existing slide, replacing the page in one request (keeps slide_id and page order)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"slides:presentation:update", "slides:presentation:write_only"}, // wiki:node:read is required only when --presentation is a wiki URL. ConditionalScopes: []string{"wiki:node:read"}, diff --git a/shortcuts/slides/slides_xml_get.go b/shortcuts/slides/slides_xml_get.go index 5c9323c3fc..3792132adb 100644 --- a/shortcuts/slides/slides_xml_get.go +++ b/shortcuts/slides/slides_xml_get.go @@ -23,7 +23,7 @@ var SlidesXMLGet = common.Shortcut{ Service: "slides", Command: "+xml-get", Description: "Fetch presentation XML or one slide XML", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"slides:presentation:read"}, // wiki:node:read is required only when --presentation is a wiki URL. ConditionalScopes: []string{"wiki:node:read"}, diff --git a/shortcuts/task/shortcuts.go b/shortcuts/task/shortcuts.go index 4112ebd847..93de1ac414 100644 --- a/shortcuts/task/shortcuts.go +++ b/shortcuts/task/shortcuts.go @@ -198,7 +198,7 @@ var CreateTask = common.Shortcut{ Service: "task", Command: "+create", Description: "create a task", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_assign.go b/shortcuts/task/task_assign.go index d0134a5189..31259fbec7 100644 --- a/shortcuts/task/task_assign.go +++ b/shortcuts/task/task_assign.go @@ -19,7 +19,7 @@ var AssignTask = common.Shortcut{ Service: "task", Command: "+assign", Description: "assign or remove task members", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_comment.go b/shortcuts/task/task_comment.go index 576c959c6a..de38f2caa5 100644 --- a/shortcuts/task/task_comment.go +++ b/shortcuts/task/task_comment.go @@ -16,7 +16,7 @@ var CommentTask = common.Shortcut{ Service: "task", Command: "+comment", Description: "add a comment to a task", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:comment:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_complete.go b/shortcuts/task/task_complete.go index 9b46886572..69f9445f72 100644 --- a/shortcuts/task/task_complete.go +++ b/shortcuts/task/task_complete.go @@ -19,7 +19,7 @@ var CompleteTask = common.Shortcut{ Service: "task", Command: "+complete", Description: "mark a task as complete", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_followers.go b/shortcuts/task/task_followers.go index 3016ad1106..9dbaecfcc3 100644 --- a/shortcuts/task/task_followers.go +++ b/shortcuts/task/task_followers.go @@ -19,7 +19,7 @@ var FollowersTask = common.Shortcut{ Service: "task", Command: "+followers", Description: "manage task followers", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_get_my_tasks.go b/shortcuts/task/task_get_my_tasks.go index 334430cac5..f772a1f574 100644 --- a/shortcuts/task/task_get_my_tasks.go +++ b/shortcuts/task/task_get_my_tasks.go @@ -21,7 +21,7 @@ var GetMyTasks = common.Shortcut{ Service: "task", Command: "+get-my-tasks", Description: "List tasks assigned to me", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"task:task:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/task/task_get_related_tasks.go b/shortcuts/task/task_get_related_tasks.go index d1725e6821..cf0eb459db 100644 --- a/shortcuts/task/task_get_related_tasks.go +++ b/shortcuts/task/task_get_related_tasks.go @@ -24,7 +24,7 @@ var GetRelatedTasks = common.Shortcut{ Service: "task", Command: "+get-related-tasks", Description: "list tasks related to me", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"task:task:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/task/task_reminder.go b/shortcuts/task/task_reminder.go index 029ab21c6d..7f62141668 100644 --- a/shortcuts/task/task_reminder.go +++ b/shortcuts/task/task_reminder.go @@ -20,7 +20,7 @@ var ReminderTask = common.Shortcut{ Service: "task", Command: "+reminder", Description: "manage task reminders", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_reopen.go b/shortcuts/task/task_reopen.go index 0790ee0d8e..2702fcdd8d 100644 --- a/shortcuts/task/task_reopen.go +++ b/shortcuts/task/task_reopen.go @@ -17,7 +17,7 @@ var ReopenTask = common.Shortcut{ Service: "task", Command: "+reopen", Description: "reopen a completed task", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_search.go b/shortcuts/task/task_search.go index e017f6ba14..7b0e578f09 100644 --- a/shortcuts/task/task_search.go +++ b/shortcuts/task/task_search.go @@ -24,7 +24,7 @@ var SearchTask = common.Shortcut{ Service: "task", Command: "+search", Description: "search tasks", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"task:task:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/task/task_set_ancestor.go b/shortcuts/task/task_set_ancestor.go index 0eb666efc3..f82fd37782 100644 --- a/shortcuts/task/task_set_ancestor.go +++ b/shortcuts/task/task_set_ancestor.go @@ -17,7 +17,7 @@ var SetAncestorTask = common.Shortcut{ Service: "task", Command: "+set-ancestor", Description: "set or clear a task ancestor", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_tasklist_search.go b/shortcuts/task/task_tasklist_search.go index e9291dcb27..91199c62c6 100644 --- a/shortcuts/task/task_tasklist_search.go +++ b/shortcuts/task/task_tasklist_search.go @@ -24,7 +24,7 @@ var SearchTasklist = common.Shortcut{ Service: "task", Command: "+tasklist-search", Description: "search tasklists", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"task:tasklist:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/task/task_update.go b/shortcuts/task/task_update.go index 00cf7f8c48..1203130b7d 100644 --- a/shortcuts/task/task_update.go +++ b/shortcuts/task/task_update.go @@ -21,7 +21,7 @@ var UpdateTask = common.Shortcut{ Service: "task", Command: "+update", Description: "update task attributes", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/task_upload_attachment.go b/shortcuts/task/task_upload_attachment.go index 099e3f66b7..2b3d8e1469 100644 --- a/shortcuts/task/task_upload_attachment.go +++ b/shortcuts/task/task_upload_attachment.go @@ -39,7 +39,7 @@ var UploadAttachmentTask = common.Shortcut{ Service: "task", Command: "+upload-attachment", Description: "upload a local file as an attachment to a task", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:attachment:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/tasklist_add_task.go b/shortcuts/task/tasklist_add_task.go index 330baceeca..55cfa2abd0 100644 --- a/shortcuts/task/tasklist_add_task.go +++ b/shortcuts/task/tasklist_add_task.go @@ -19,7 +19,7 @@ var AddTaskToTasklist = common.Shortcut{ Service: "task", Command: "+tasklist-task-add", Description: "add tasks to a tasklist", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/tasklist_create.go b/shortcuts/task/tasklist_create.go index 3360aec824..66642474a9 100644 --- a/shortcuts/task/tasklist_create.go +++ b/shortcuts/task/tasklist_create.go @@ -21,7 +21,7 @@ var CreateTasklist = common.Shortcut{ Service: "task", Command: "+tasklist-create", Description: "create a tasklist and optionally add tasks", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:tasklist:write", "task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/task/tasklist_members.go b/shortcuts/task/tasklist_members.go index 2d36033627..a95ced995a 100644 --- a/shortcuts/task/tasklist_members.go +++ b/shortcuts/task/tasklist_members.go @@ -19,7 +19,7 @@ var MembersTasklist = common.Shortcut{ Service: "task", Command: "+tasklist-members", Description: "manage tasklist members", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"task:tasklist:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/vc/vc_detail.go b/shortcuts/vc/vc_detail.go index 31945f6b30..18c0515a2c 100644 --- a/shortcuts/vc/vc_detail.go +++ b/shortcuts/vc/vc_detail.go @@ -162,7 +162,7 @@ var VCDetail = common.Shortcut{ Service: "vc", Command: "+detail", Description: "Get meeting details including note_id and minute_token by meeting IDs", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"vc:meeting.meetingevent:read", "vc:record:readonly"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/vc/vc_meeting_events.go b/shortcuts/vc/vc_meeting_events.go index e4f20f7460..e2baa67949 100644 --- a/shortcuts/vc/vc_meeting_events.go +++ b/shortcuts/vc/vc_meeting_events.go @@ -51,7 +51,7 @@ var VCMeetingEvents = common.Shortcut{ Service: "vc", Command: "+meeting-events", Description: "List meeting events by meeting ID", - Risk: "read", + Risk: common.RiskRead, // UAT exposes user-granted scopes, so the framework can preflight the user // recommendation. TAT has no scope metadata; keep the bot recommendation // conditional so it is available to diagnostics without a local preflight. diff --git a/shortcuts/vc/vc_meeting_join.go b/shortcuts/vc/vc_meeting_join.go index 7ac2037bd1..16df72d27f 100644 --- a/shortcuts/vc/vc_meeting_join.go +++ b/shortcuts/vc/vc_meeting_join.go @@ -26,7 +26,7 @@ var VCMeetingJoin = common.Shortcut{ Service: "vc", Command: "+meeting-join", Description: "Join a meeting by meeting number (bot join)", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"vc:meeting.bot.join:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/vc/vc_meeting_leave.go b/shortcuts/vc/vc_meeting_leave.go index 959c7c90ac..0264e3256c 100644 --- a/shortcuts/vc/vc_meeting_leave.go +++ b/shortcuts/vc/vc_meeting_leave.go @@ -18,7 +18,7 @@ var VCMeetingLeave = common.Shortcut{ Service: "vc", Command: "+meeting-leave", Description: "Leave a meeting by meeting ID", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"vc:meeting.bot.join:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/vc/vc_meeting_list_active.go b/shortcuts/vc/vc_meeting_list_active.go index 0e36273f88..470285ef93 100644 --- a/shortcuts/vc/vc_meeting_list_active.go +++ b/shortcuts/vc/vc_meeting_list_active.go @@ -22,7 +22,7 @@ var VCMeetingListActive = common.Shortcut{ Service: "vc", Command: "+meeting-list-active", Description: "List active meetings for the current identity or target user", - Risk: "read", + Risk: common.RiskRead, // UAT exposes user-granted scopes, so the framework can preflight the user // recommendation. TAT has no scope metadata; keep the bot recommendation // conditional so it is available to diagnostics without a local preflight. diff --git a/shortcuts/vc/vc_meeting_message_send.go b/shortcuts/vc/vc_meeting_message_send.go index 03ead38063..02f5495ac7 100644 --- a/shortcuts/vc/vc_meeting_message_send.go +++ b/shortcuts/vc/vc_meeting_message_send.go @@ -27,7 +27,7 @@ var VCMeetingMessageSend = common.Shortcut{ Service: "vc", Command: "+meeting-message-send", Description: "Send an in-meeting text message or reaction emoji", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"vc:meeting.message:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/vc/vc_notes.go b/shortcuts/vc/vc_notes.go index 3c36c01adc..083508a26b 100644 --- a/shortcuts/vc/vc_notes.go +++ b/shortcuts/vc/vc_notes.go @@ -548,7 +548,7 @@ var VCNotes = common.Shortcut{ Service: "vc", Command: "+notes", Description: "Query meeting notes (via meeting-ids, minute-tokens, or calendar-event-ids)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"vc:note:read"}, // minimum scope; additional per-flag scopes checked in Validate AuthTypes: []string{"user"}, Hidden: true, // hidden from --help; prefer vc +detail, minutes +detail, or note +detail diff --git a/shortcuts/vc/vc_recording.go b/shortcuts/vc/vc_recording.go index 67384493c7..edf50e164b 100644 --- a/shortcuts/vc/vc_recording.go +++ b/shortcuts/vc/vc_recording.go @@ -89,7 +89,7 @@ var VCRecording = common.Shortcut{ Service: "vc", Command: "+recording", Description: "Query minute_token from meeting-ids or calendar-event-ids", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"vc:record:readonly"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/vc/vc_search.go b/shortcuts/vc/vc_search.go index f0bac1abac..32a025f6dd 100644 --- a/shortcuts/vc/vc_search.go +++ b/shortcuts/vc/vc_search.go @@ -173,7 +173,7 @@ var VCSearch = common.Shortcut{ Service: "vc", Command: "+search", Description: "Search meeting records by keyword, time range, participant, organizer, or meeting room (requires at least one filter)", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"vc:meeting.search:read"}, AuthTypes: []string{"user"}, HasFormat: true, diff --git a/shortcuts/whiteboard/whiteboard_export.go b/shortcuts/whiteboard/whiteboard_export.go index de64f37b1c..738a871df0 100644 --- a/shortcuts/whiteboard/whiteboard_export.go +++ b/shortcuts/whiteboard/whiteboard_export.go @@ -250,7 +250,7 @@ var WhiteboardExport = common.Shortcut{ Service: "whiteboard", Command: "+export", Description: WhiteboardExportDescription, - Risk: "read", + Risk: common.RiskRead, Scopes: wbExportScopes, AuthTypes: wbExportAuthTypes, Flags: wbExportFlags, @@ -265,7 +265,7 @@ var WhiteboardQuery = common.Shortcut{ Service: "whiteboard", Command: "+query", Description: WhiteboardExportDescription, - Risk: "read", + Risk: common.RiskRead, Scopes: wbExportScopes, AuthTypes: wbExportAuthTypes, Flags: wbQueryFlags, diff --git a/shortcuts/whiteboard/whiteboard_update.go b/shortcuts/whiteboard/whiteboard_update.go index 2afcf752b9..dd3c12466e 100644 --- a/shortcuts/whiteboard/whiteboard_update.go +++ b/shortcuts/whiteboard/whiteboard_update.go @@ -141,7 +141,7 @@ var WhiteboardUpdate = common.Shortcut{ Service: "whiteboard", Command: "+update", Description: WhiteboardUpdateDescription, - Risk: "write", + Risk: common.RiskWrite, Scopes: wbUpdateScopes, AuthTypes: wbUpdateAuthTypes, Flags: wbUpdateFlags, @@ -156,7 +156,7 @@ var WhiteboardUpdateOld = common.Shortcut{ Service: "docs", Command: "+whiteboard-update", Description: WhiteboardUpdateDescription, - Risk: "write", + Risk: common.RiskWrite, Scopes: wbUpdateScopes, AuthTypes: wbUpdateAuthTypes, Flags: wbUpdateFlags, diff --git a/shortcuts/wiki/wiki_delete.go b/shortcuts/wiki/wiki_delete.go index 3c3eb8358c..183528a092 100644 --- a/shortcuts/wiki/wiki_delete.go +++ b/shortcuts/wiki/wiki_delete.go @@ -35,7 +35,7 @@ var WikiDeleteSpace = common.Shortcut{ Service: "wiki", Command: "+delete-space", Description: "Delete a wiki space, polling the async delete task when needed", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"wiki:space:write_only", "wiki:space:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/wiki/wiki_member_add.go b/shortcuts/wiki/wiki_member_add.go index a7fc86394c..1d6e87ed14 100644 --- a/shortcuts/wiki/wiki_member_add.go +++ b/shortcuts/wiki/wiki_member_add.go @@ -21,7 +21,7 @@ var WikiMemberAdd = common.Shortcut{ Service: "wiki", Command: "+member-add", Description: "Add a member to a wiki space", - Risk: "write", + Risk: common.RiskWrite, // The API also accepts wiki:wiki, but the framework's preflight does // exact-string scope matching (see +space-list), so declare the narrowest // scope so tokens that only carry wiki:member:create aren't false-rejected. diff --git a/shortcuts/wiki/wiki_member_list.go b/shortcuts/wiki/wiki_member_list.go index 8dbe84db95..7e74a599f4 100644 --- a/shortcuts/wiki/wiki_member_list.go +++ b/shortcuts/wiki/wiki_member_list.go @@ -27,7 +27,7 @@ var WikiMemberList = common.Shortcut{ Service: "wiki", Command: "+member-list", Description: "List members of a wiki space", - Risk: "read", + Risk: common.RiskRead, // Same exact-match-scope rationale as +space-list: declare the narrowest // scope the API takes so tokens carrying only wiki:member:retrieve are // accepted. diff --git a/shortcuts/wiki/wiki_member_remove.go b/shortcuts/wiki/wiki_member_remove.go index 1a0a36447a..54ea7ffad0 100644 --- a/shortcuts/wiki/wiki_member_remove.go +++ b/shortcuts/wiki/wiki_member_remove.go @@ -21,7 +21,7 @@ var WikiMemberRemove = common.Shortcut{ Service: "wiki", Command: "+member-remove", Description: "Remove a member from a wiki space", - Risk: "write", + Risk: common.RiskWrite, // The API also accepts wiki:wiki; we declare the narrowest valid scope so // tokens carrying only wiki:member:update aren't false-rejected by the // exact-string scope preflight (see +space-list for the full reasoning). diff --git a/shortcuts/wiki/wiki_member_test.go b/shortcuts/wiki/wiki_member_test.go index 63626ca276..a4bc65d114 100644 --- a/shortcuts/wiki/wiki_member_test.go +++ b/shortcuts/wiki/wiki_member_test.go @@ -64,7 +64,7 @@ func TestWikiMemberShortcutsDeclareRiskAndAuth(t *testing.T) { cases := []struct { name string shortcut common.Shortcut - risk string + risk common.Risk }{ {"+member-add", WikiMemberAdd, "write"}, {"+member-remove", WikiMemberRemove, "write"}, diff --git a/shortcuts/wiki/wiki_move.go b/shortcuts/wiki/wiki_move.go index 8ee798bcc5..61781e9500 100644 --- a/shortcuts/wiki/wiki_move.go +++ b/shortcuts/wiki/wiki_move.go @@ -41,7 +41,7 @@ var WikiMove = common.Shortcut{ Service: "wiki", Command: "+move", Description: "Move a wiki node, or move a Drive document into Wiki", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"wiki:node:move", "wiki:node:read", "wiki:space:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/wiki/wiki_move_to_drive.go b/shortcuts/wiki/wiki_move_to_drive.go index 1eae6022a5..47a86f1b30 100644 --- a/shortcuts/wiki/wiki_move_to_drive.go +++ b/shortcuts/wiki/wiki_move_to_drive.go @@ -40,7 +40,7 @@ var WikiMoveToDrive = common.Shortcut{ Service: "wiki", Command: "+move-to-drive", Description: "Move a wiki node to a Drive folder, polling the async task until it finishes", - Risk: "write", + Risk: common.RiskWrite, // The move endpoint's wiki:wiki / wiki:node:move / // space:document:move list is an OR-set, while Shortcut.Scopes is an // ALL-required preflight. Use the registry's highest-priority candidate diff --git a/shortcuts/wiki/wiki_node_copy.go b/shortcuts/wiki/wiki_node_copy.go index 67a5d27124..6efe4cb769 100644 --- a/shortcuts/wiki/wiki_node_copy.go +++ b/shortcuts/wiki/wiki_node_copy.go @@ -19,7 +19,7 @@ var WikiNodeCopy = common.Shortcut{ Service: "wiki", Command: "+node-copy", Description: "Copy a wiki node to a target space or parent node", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, Scopes: []string{"wiki:node:copy"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/wiki/wiki_node_create.go b/shortcuts/wiki/wiki_node_create.go index a03d7e2017..480a6e5bd6 100644 --- a/shortcuts/wiki/wiki_node_create.go +++ b/shortcuts/wiki/wiki_node_create.go @@ -52,7 +52,7 @@ var WikiNodeCreate = common.Shortcut{ Service: "wiki", Command: "+node-create", Description: "Create a wiki node with automatic space resolution", - Risk: "write", + Risk: common.RiskWrite, Scopes: []string{"wiki:node:create", "wiki:node:read", "wiki:space:read"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ diff --git a/shortcuts/wiki/wiki_node_delete.go b/shortcuts/wiki/wiki_node_delete.go index fc33c507d3..b4b5e5bd84 100644 --- a/shortcuts/wiki/wiki_node_delete.go +++ b/shortcuts/wiki/wiki_node_delete.go @@ -47,7 +47,7 @@ var WikiNodeDelete = common.Shortcut{ Service: "wiki", Command: "+node-delete", Description: "Delete a wiki node, polling the async delete task when needed", - Risk: "high-risk-write", + Risk: common.RiskHighRiskWrite, // API spec lists wiki:node:create as the only declared scope for the // delete endpoint. Naming is unfortunate, but the scope-preflight needs // the literal string. diff --git a/shortcuts/wiki/wiki_node_get.go b/shortcuts/wiki/wiki_node_get.go index 62ccdd54d6..66de9752f7 100644 --- a/shortcuts/wiki/wiki_node_get.go +++ b/shortcuts/wiki/wiki_node_get.go @@ -53,7 +53,7 @@ var WikiNodeGet = common.Shortcut{ Service: "wiki", Command: "+node-get", Description: "Get wiki node details by node_token, obj_token, or Lark URL", - Risk: "read", + Risk: common.RiskRead, Scopes: []string{"wiki:node:retrieve"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, diff --git a/shortcuts/wiki/wiki_node_list.go b/shortcuts/wiki/wiki_node_list.go index 494ca0fc3f..ae1755697a 100644 --- a/shortcuts/wiki/wiki_node_list.go +++ b/shortcuts/wiki/wiki_node_list.go @@ -26,7 +26,7 @@ var WikiNodeList = common.Shortcut{ Service: "wiki", Command: "+node-list", Description: "List wiki nodes in a space or under a parent node", - Risk: "read", + Risk: common.RiskRead, // Same exact-match-scope reasoning as +space-list: declare the // narrowest scope the upstream API accepts so we don't false-reject // tokens that only carry wiki:node:retrieve. diff --git a/shortcuts/wiki/wiki_space_create.go b/shortcuts/wiki/wiki_space_create.go index 30e62ab761..2111607091 100644 --- a/shortcuts/wiki/wiki_space_create.go +++ b/shortcuts/wiki/wiki_space_create.go @@ -23,7 +23,7 @@ var WikiSpaceCreate = common.Shortcut{ Service: "wiki", Command: "+space-create", Description: "Create a wiki space", - Risk: "write", + Risk: common.RiskWrite, // The API accepts wiki:wiki or wiki:space:write_only. The framework's // scope preflight does exact-string matching (see +space-list), so // declare the narrowest form the API takes to avoid false-rejecting diff --git a/shortcuts/wiki/wiki_space_list.go b/shortcuts/wiki/wiki_space_list.go index df752d253b..b437798b0a 100644 --- a/shortcuts/wiki/wiki_space_list.go +++ b/shortcuts/wiki/wiki_space_list.go @@ -26,7 +26,7 @@ var WikiSpaceList = common.Shortcut{ Service: "wiki", Command: "+space-list", Description: "List wiki spaces accessible to the caller", - Risk: "read", + Risk: common.RiskRead, // Declare the narrowest valid scope: the upstream API accepts any of // wiki:wiki / wiki:wiki:readonly / wiki:space:retrieve, but the // framework's preflight does exact-string scope matching (see From 67ed241f5a45030282bc7471f07a3ee70d11c340 Mon Sep 17 00:00:00 2001 From: shanglei Date: Thu, 6 Aug 2026 17:03:22 +0800 Subject: [PATCH 2/2] refactor(risk): split the risk-literal scan and unexport its predicate riskLiteralsInFile carried the whole ast.Inspect switch inline: two node shapes, each with its own guard chain, at a cyclomatic complexity of 12. Each shape is now its own function returning a riskLiteralUse, so the walker reads as "did this node use a literal, and where", and no function in the file is above 5. invalidRiskDeclaration has no caller outside cmdutil, so it is no longer part of the package's exported surface. --- internal/cmdutil/risk_gate.go | 8 ++-- internal/qualitygate/rules/risklit.go | 64 ++++++++++++++++++--------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/internal/cmdutil/risk_gate.go b/internal/cmdutil/risk_gate.go index a56a65bb3f..b6df99075f 100644 --- a/internal/cmdutil/risk_gate.go +++ b/internal/cmdutil/risk_gate.go @@ -28,12 +28,12 @@ import ( // gated on --yes, never allowed through silently // - absent / read / write → no gate -// InvalidRiskDeclaration reports whether level is present but outside the +// invalidRiskDeclaration reports whether level is present but outside the // closed taxonomy. An absent level ("") is a legal state — it means the // command is not annotated and defaults to read — and is deliberately not // reported here, so "unannotated" and "misspelled" never collapse into one // branch. -func InvalidRiskDeclaration(level Risk) bool { +func invalidRiskDeclaration(level Risk) bool { return level != "" && !level.IsValid() } @@ -42,7 +42,7 @@ func InvalidRiskDeclaration(level Risk) bool { // have to be confirmed, and if it is not, EnforceRiskDeclaration has already // rejected the call. func RequiresConfirmation(level Risk) bool { - return level == RiskHighRiskWrite || InvalidRiskDeclaration(level) + return level == RiskHighRiskWrite || invalidRiskDeclaration(level) } // EnforceRiskDeclaration returns a non-nil error when a command must not run @@ -53,7 +53,7 @@ func RequiresConfirmation(level Risk) bool { // action identifies the operation for the agent, in the same shape used by // RequireConfirmation ("drive +delete", "drive.files.delete"). func EnforceRiskDeclaration(action string, level Risk) error { - if !InvalidRiskDeclaration(level) { + if !invalidRiskDeclaration(level) { return nil } if allowInvalidRisk() { diff --git a/internal/qualitygate/rules/risklit.go b/internal/qualitygate/rules/risklit.go index 56e508fa16..87720225a3 100644 --- a/internal/qualitygate/rules/risklit.go +++ b/internal/qualitygate/rules/risklit.go @@ -67,34 +67,56 @@ func riskLiteralsInFile(path, src string) []report.Diagnostic { return nil } var diags []report.Diagnostic - add := func(pos token.Pos, literal, where string) { - diags = append(diags, riskLiteralDiagnostic(path, fset.Position(pos).Line, literal, where)) - } ast.Inspect(file, func(n ast.Node) bool { - switch node := n.(type) { - case *ast.KeyValueExpr: - // `Risk: "read"` in any struct literal. - key, ok := node.Key.(*ast.Ident) - if !ok || key.Name != "Risk" { - return true - } - if lit, ok := stringLiteral(node.Value); ok { - add(node.Value.Pos(), lit, "a Risk field") - } - case *ast.CallExpr: - // `SetRisk(cmd, "read")` / `cmdutil.SetRisk(cmd, "read")`. - if !isSetRiskCall(node.Fun) || len(node.Args) != 2 { - return true - } - if lit, ok := stringLiteral(node.Args[1]); ok { - add(node.Args[1].Pos(), lit, "SetRisk") - } + if use, ok := riskLiteralUsedBy(n); ok { + diags = append(diags, riskLiteralDiagnostic(path, fset.Position(use.pos).Line, use.value, use.where)) } return true }) return diags } +// riskLiteralUse is one hand-written level found in the tree, with enough +// position to point the author at it. +type riskLiteralUse struct { + value string + pos token.Pos + where string +} + +func riskLiteralUsedBy(n ast.Node) (riskLiteralUse, bool) { + switch node := n.(type) { + case *ast.KeyValueExpr: + return riskFieldLiteral(node) + case *ast.CallExpr: + return setRiskArgLiteral(node) + } + return riskLiteralUse{}, false +} + +func riskFieldLiteral(node *ast.KeyValueExpr) (riskLiteralUse, bool) { + key, ok := node.Key.(*ast.Ident) + if !ok || key.Name != "Risk" { + return riskLiteralUse{}, false + } + value, ok := stringLiteral(node.Value) + if !ok { + return riskLiteralUse{}, false + } + return riskLiteralUse{value: value, pos: node.Value.Pos(), where: "a Risk field"}, true +} + +func setRiskArgLiteral(node *ast.CallExpr) (riskLiteralUse, bool) { + if !isSetRiskCall(node.Fun) || len(node.Args) != 2 { + return riskLiteralUse{}, false + } + value, ok := stringLiteral(node.Args[1]) + if !ok { + return riskLiteralUse{}, false + } + return riskLiteralUse{value: value, pos: node.Args[1].Pos(), where: "SetRisk"}, true +} + func riskLiteralDiagnostic(path string, line int, literal, where string) report.Diagnostic { return report.Diagnostic{ Rule: RuleRiskLiteral,