feat(shortcuts): introduce TypedShortcut framework - #1110
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a typed-shortcut protocol and reflection binder, many arg-type validators, RuntimeContext typed-args storage, typed help, migrates im +messages-send to TypedShortcut, refactors registration to descriptor/mountable, and updates auth/scope call sites to use descriptor accessors. ChangesTyped Shortcut Framework & ImMessagesSend Migration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
|
🚀 PR Preview Install Guide🧰 CLI updatenpm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@5cd34b1f701828064b9162c35ddcbbdf61cd46a9🧩 Skill updatenpx skills add larksuite/cli#feat/shortcut-protocol -y -g |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (3)
shortcuts/register.go (1)
126-135: ⚡ Quick winAdd a duplicate shortcut key guard before mounting.
Right now, duplicate
service/commandentries across legacy + typed lists are appended silently. A small guard here prevents accidental double-mount regressions.Suggested patch
byService := make(map[string][]common.Mountable) + seen := make(map[string]struct{}) for _, d := range allShortcuts { m, ok := d.(common.Mountable) if !ok { panic(fmt.Sprintf("shortcut %s/%s missing Mountable", d.GetService(), d.GetCommand())) } + key := d.GetService() + "/" + d.GetCommand() + if _, exists := seen[key]; exists { + panic(fmt.Sprintf("duplicate shortcut registration: %s", key)) + } + seen[key] = struct{}{} byService[d.GetService()] = append(byService[d.GetService()], m) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/register.go` around lines 126 - 135, There is no duplicate-key guard when building byService from allShortcuts, so identical service/command pairs can be silently appended; add a seen set (map[string]struct{}) keyed by fmt.Sprintf("%s/%s", d.GetService(), d.GetCommand()) inside the loop that iterates allShortcuts and before appending to byService check the seen map and panic or return an error/log a fatal if the key already exists to prevent double-mounts; update code around the byService creation, the loop over allShortcuts, and use the common.Mountable assertion (m, ok := d.(common.Mountable)) together with the seen check to locate the duplicate and stop execution.shortcuts/im/im_messages_send_test.go (1)
85-86: ⚡ Quick winUse
cmdutil.TestFactory(t, config)in unit tests instead of&cmdutil.Factory{}.Line 85 and Line 115 instantiate a bare factory directly. Please switch both mount tests to
cmdutil.TestFactory(...)to align with the test harness contract.As per coding guidelines, "
**/*_test.go: Usecmdutil.TestFactory(t, config)for test factories in unit tests".Also applies to: 115-116
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/im/im_messages_send_test.go` around lines 85 - 86, Replace the direct instantiation of cmdutil.Factory (used when mounting the command with ImMessagesSend.MountWithContext(context.Background(), imParent, &cmdutil.Factory{})) with the test harness helper cmdutil.TestFactory(t, config); update both occurrences (the one at the ImMessagesSend.MountWithContext call and the second mount test near lines 115–116) to call cmdutil.TestFactory(t, config) so the tests use the proper test factory contract, passing the current *testing.T and the appropriate test config.shortcuts/im/builders_test.go (1)
363-418: ⚡ Quick winExercise typed-args behavior directly in these ImMessagesSend tests.
Most new calls pass
&ImMessagesSendArgs{}and rely on runtime flags, so these cases can still pass even if typed field usage regresses. Prefer constructing meaningfulImMessagesSendArgsper case (or binding once through the framework path) so Validate/DryRun coverage tracks the typed contract.Also applies to: 705-718
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/im/builders_test.go` around lines 363 - 418, Tests for ImMessagesSend call Validate with an empty &ImMessagesSendArgs{} and only runtime flags, so they don't exercise the typed-args contract; update each failing subtest to construct and pass a meaningful ImMessagesSendArgs that matches the scenario (e.g., set Text for the "valid text" case, set Video and VideoCover for the "video with video-cover" case, set only Video for the "video without video-cover" case, set only VideoCover for the "video-cover without video" case, and set MsgType plus Image for the "conflicting explicit msg-type" case) before calling ImMessagesSend.Validate(context.Background(), args, runtime), and apply the same change to the similar tests at the other location (lines ~705-718) so Validate/DryRun exercises the typed fields instead of relying solely on runtime flags; alternatively bind args via the framework path once so the typed struct is populated for each test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@shortcuts/common/argstype/safe_path.go`:
- Around line 35-36: The current check uses filepath.Clean(s) which can hide
parent-directory segments (e.g. "a/../b") — first scan the raw input string s
for any literal ".." path segments and reject if found before calling
filepath.Clean; implement this by splitting s on both '/' and '\' (or use
strings.FieldsFunc) and returning an error if any segment == "..", then continue
with the existing clean := filepath.Clean(s) and the remaining checks; reference
the existing variable clean and the use of filepath.Clean in safe_path.go when
making the change.
In `@shortcuts/common/argstype/spreadsheet_ref.go`:
- Around line 28-31: The URL token extraction currently checks segments from
strings.Split(trimmed, "/") and accepts segments that start with "shtcn" even
when they include query or fragment suffixes (e.g., "shtcn...?..."). Update the
extraction logic to strip any query ('?') or fragment ('#') portion from each
segment before checking HasPrefix and before returning SpreadsheetRef; do the
same fix in the other loop/block referenced around the SheetRef validation (the
code that returns SpreadsheetRef(seg) and the similar check at lines ~55-61).
Ensure you sanitize each seg by cutting at the first '?' or '#' (or parse the
URL and use the path segments) so only the raw token is validated and returned.
In `@shortcuts/common/argstype/user_open_id_list.go`:
- Around line 32-46: In UserOpenIDList.ValidateValue, add an explicit guard to
reject an explicitly empty parsed list (check len(l) == 0) and return an
errs.ValidationError referencing the flagName and a clear message like "empty
user open_id list" so inputs like ",," fail fast; keep the existing per-item
"ou_" prefix checks (function: ValidateValue on type UserOpenIDList, variables:
l and flagName, helper itoa) and ensure the new empty-list error uses the same
errs.Problem Category/Subtype pattern as the other errors.
In `@shortcuts/common/binder.go`:
- Around line 308-320: The code always calls cmd.Flags().GetString for both
pointer and non-pointer fields (s.FlagName) and then reflect.Converts that
string into the target type, which breaks for non-string leaves (bool/int) and
can panic; change the binding in binder.go (the s.IsPtr branch and the
non-pointer branch that use fv and elemType) to detect the target kind (use
elemType := fv.Type().Elem() for pointers and target := fv.Type() for
non-pointers) and switch on kind to call the appropriate flag getter (GetBool,
GetInt/GetInt64/GetUint, GetFloat64, GetStringSlice, etc.), handle errors from
those getters, and then set the reflect.Value using
reflect.ValueOf(parsedValue).Convert(target) (or set a new pointer when s.IsPtr)
so typed leaves are bound with their native types instead of always using
strings.
In `@shortcuts/common/typed_help_test.go`:
- Around line 29-33: In TestTypedHelp_RendersSections, the test must isolate
config state by setting the LARKSUITE_CLI_CONFIG_DIR environment variable; at
the start of the test (inside TestTypedHelp_RendersSections, before creating the
cobra.Command or calling walkArgs/reflect.TypeOf(&helpDemoArgs{})), call
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) so the test uses a temporary
config directory and doesn't depend on ambient state.
- Around line 33-34: The test currently ignores the error returned by
walkArgs(reflect.TypeOf(&helpDemoArgs{})); change this to capture the error
(e.g. specs, err := walkArgs(...)) and fail fast if err != nil by calling the
test helper (e.g. t.Fatalf or t.Fatal) with the error message before calling
buildTypedHelp; keep using the same symbols walkArgs, helpDemoArgs, specs, and
buildTypedHelp so the rest of the test remains unchanged.
In `@shortcuts/common/typed_help.go`:
- Around line 147-156: renderGlobalFlags currently uses fs.VisitAll and appends
every flag (via the loop containing fs.VisitAll and globals = append(...))
without checking for hidden flags, causing flags with f.Hidden==true to appear
in "GLOBAL FLAGS"; update the VisitAll callback to skip flags where f.Hidden is
true (i.e., add an early return when f.Hidden), preserving the existing dedup
logic (checks against rendered and seen) and still appending only non-hidden
flags to the globals slice (refer to fs.VisitAll, the closure's f variable, the
rendered/seen maps, and globalFlag{Name:..., Shorthand:..., Usage:...}).
- Around line 28-29: Change the typed help output to write to stderr and omit
hidden flags when rendering global flags: replace uses of cmd.OutOrStdout() with
cmd.ErrOrStderr() (so human-readable help goes to stderr), and update
renderGlobalFlags (the VisitAll iteration over flags) to skip flags where
f.Hidden is true (e.g., continue if f.Hidden) so hidden flags are not listed
under GLOBAL FLAGS. Ensure both changes reference the existing cmd object and
the renderGlobalFlags/VisitAll flag iteration so behavior and signatures remain
unchanged.
In `@shortcuts/common/typed_shortcut_test.go`:
- Around line 65-66: Replace direct construction of cmdutil.Factory (the
&cmdutil.Factory{} passed into ts.MountWithContext and other test calls) with
the test helper cmdutil.TestFactory(t, config); before creating the factory,
call t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) to isolate config state;
create or pass an appropriate minimal config value into cmdutil.TestFactory(t,
config) for these tests (references: ts.MountWithContext, cmdutil.Factory,
cmdutil.TestFactory, t.Setenv, "LARKSUITE_CLI_CONFIG_DIR").
In `@shortcuts/common/typed_shortcut.go`:
- Around line 189-192: The anonymous Execute wrapper currently calls s.Execute
unconditionally which can panic if s.Execute is nil; update the wrapper (the
Execute func that receives context.Context and *RuntimeContext and calls
rt.TypedArgs()) to first check if s.Execute == nil and return a clear error
(e.g., using fmt.Errorf or errors.New) when nil, otherwise invoke s.Execute(c,
args, rt) as before so missing handlers fail gracefully instead of panicking.
In `@shortcuts/im/helpers.go`:
- Around line 88-94: validateMediaFlagPath currently ignores os.IsNotExist
errors so missing local files pass validation; update the fio.Stat error
handling in validateMediaFlagPath to treat any non-nil err (including
os.IsNotExist) as a validation failure by returning output.ErrValidation("%s:
%v", flagName, err) when err != nil, leaving the early-returns for
empty/http/media-key values unchanged.
In `@shortcuts/register_test.go`:
- Around line 82-90: The test for AllShortcuts currently only compares lengths
after appending and can pass if slices alias; change it to capture the original
slice (orig := AllShortcuts()), then call got := AllShortcuts(), append to got,
and assert that orig's length and element values are unchanged by the append
(e.g., len(orig) == len(AllShortcuts()) and elements of orig equal elements of
AllShortcuts()); use the AllShortcuts symbol and the test function in
shortcuts/register_test.go to locate and replace the existing
length-after-append check so the test proves the returned slice is a true
defensive copy rather than an alias.
---
Nitpick comments:
In `@shortcuts/im/builders_test.go`:
- Around line 363-418: Tests for ImMessagesSend call Validate with an empty
&ImMessagesSendArgs{} and only runtime flags, so they don't exercise the
typed-args contract; update each failing subtest to construct and pass a
meaningful ImMessagesSendArgs that matches the scenario (e.g., set Text for the
"valid text" case, set Video and VideoCover for the "video with video-cover"
case, set only Video for the "video without video-cover" case, set only
VideoCover for the "video-cover without video" case, and set MsgType plus Image
for the "conflicting explicit msg-type" case) before calling
ImMessagesSend.Validate(context.Background(), args, runtime), and apply the same
change to the similar tests at the other location (lines ~705-718) so
Validate/DryRun exercises the typed fields instead of relying solely on runtime
flags; alternatively bind args via the framework path once so the typed struct
is populated for each test.
In `@shortcuts/im/im_messages_send_test.go`:
- Around line 85-86: Replace the direct instantiation of cmdutil.Factory (used
when mounting the command with
ImMessagesSend.MountWithContext(context.Background(), imParent,
&cmdutil.Factory{})) with the test harness helper cmdutil.TestFactory(t,
config); update both occurrences (the one at the ImMessagesSend.MountWithContext
call and the second mount test near lines 115–116) to call
cmdutil.TestFactory(t, config) so the tests use the proper test factory
contract, passing the current *testing.T and the appropriate test config.
In `@shortcuts/register.go`:
- Around line 126-135: There is no duplicate-key guard when building byService
from allShortcuts, so identical service/command pairs can be silently appended;
add a seen set (map[string]struct{}) keyed by fmt.Sprintf("%s/%s",
d.GetService(), d.GetCommand()) inside the loop that iterates allShortcuts and
before appending to byService check the seen map and panic or return an
error/log a fatal if the key already exists to prevent double-mounts; update
code around the byService creation, the loop over allShortcuts, and use the
common.Mountable assertion (m, ok := d.(common.Mountable)) together with the
seen check to locate the duplicate and stop execution.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 209aa335-569d-4ed7-8c1a-08a780d09bdd
📒 Files selected for processing (42)
cmd/auth/login.gocmd/auth/login_interactive.gocmd/auth/login_test.gocmd/diagnose_scope_test.gocmd/error_auth_hint.goerrs/subtypes_shortcut.goerrs/subtypes_shortcut_test.goshortcuts/common/argstype/chat_id.goshortcuts/common/argstype/chat_id_test.goshortcuts/common/argstype/media_input.goshortcuts/common/argstype/media_input_test.goshortcuts/common/argstype/safe_path.goshortcuts/common/argstype/safe_path_test.goshortcuts/common/argstype/spreadsheet_ref.goshortcuts/common/argstype/spreadsheet_ref_test.goshortcuts/common/argstype/user_open_id.goshortcuts/common/argstype/user_open_id_list.goshortcuts/common/argstype/user_open_id_list_test.goshortcuts/common/argstype/user_open_id_test.goshortcuts/common/binder.goshortcuts/common/binder_test.goshortcuts/common/protocol.goshortcuts/common/protocol_test.goshortcuts/common/runner.goshortcuts/common/runner_args_test.goshortcuts/common/typed_help.goshortcuts/common/typed_help_test.goshortcuts/common/typed_shortcut.goshortcuts/common/typed_shortcut_test.goshortcuts/common/types.goshortcuts/common/types_test.goshortcuts/im/builders_test.goshortcuts/im/helpers.goshortcuts/im/helpers_test.goshortcuts/im/im_messages_send.goshortcuts/im/im_messages_send_test.goshortcuts/im/protocol.goshortcuts/im/protocol_test.goshortcuts/im/shortcuts.goshortcuts/im/validate_media_test.goshortcuts/register.goshortcuts/register_test.go
6d67e5b to
bd95794
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
shortcuts/im/helpers.go (1)
92-93:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFail fast on missing local media paths.
Line 92 currently treats missing files as valid input. This defers a user-input error to a later stage and weakens preflight validation.
Suggested fix
import ( "bytes" "context" "encoding/binary" "encoding/json" "fmt" "io" "math" "net/http" "net/url" - "os" "path" "path/filepath" "regexp" "strconv" "strings" @@ func validateMediaFlagPath(fio fileio.FileIO, flagName, value string) error { if value == "" || strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") || isMediaKey(value) { return nil } - if _, err := fio.Stat(value); err != nil && !os.IsNotExist(err) { + if _, err := fio.Stat(value); err != nil { return output.ErrValidation("%s: %v", flagName, err) } return nil }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/im/helpers.go` around lines 92 - 93, The preflight validation in shortcuts/im/helpers.go currently ignores os.IsNotExist errors when statting the path; change the check in the block that calls fio.Stat(value) (the validation that returns output.ErrValidation("%s: %v", flagName, err)) to treat any non-nil err — including os.IsNotExist — as a validation failure so missing local media paths fail fast; locate the fio.Stat(value) call and remove the special-case os.IsNotExist(err) condition so the function that returns output.ErrValidation using flagName and value runs whenever err != nil.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@shortcuts/im/helpers.go`:
- Around line 92-93: The preflight validation in shortcuts/im/helpers.go
currently ignores os.IsNotExist errors when statting the path; change the check
in the block that calls fio.Stat(value) (the validation that returns
output.ErrValidation("%s: %v", flagName, err)) to treat any non-nil err —
including os.IsNotExist — as a validation failure so missing local media paths
fail fast; locate the fio.Stat(value) call and remove the special-case
os.IsNotExist(err) condition so the function that returns output.ErrValidation
using flagName and value runs whenever err != nil.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a01b8809-103e-4b21-a758-f64431a04621
📒 Files selected for processing (42)
cmd/auth/login.gocmd/auth/login_interactive.gocmd/auth/login_test.gocmd/diagnose_scope_test.gocmd/error_auth_hint.goerrs/subtypes_shortcut.goerrs/subtypes_shortcut_test.goshortcuts/common/argstype/chat_id.goshortcuts/common/argstype/chat_id_test.goshortcuts/common/argstype/media_input.goshortcuts/common/argstype/media_input_test.goshortcuts/common/argstype/safe_path.goshortcuts/common/argstype/safe_path_test.goshortcuts/common/argstype/spreadsheet_ref.goshortcuts/common/argstype/spreadsheet_ref_test.goshortcuts/common/argstype/user_open_id.goshortcuts/common/argstype/user_open_id_list.goshortcuts/common/argstype/user_open_id_list_test.goshortcuts/common/argstype/user_open_id_test.goshortcuts/common/binder.goshortcuts/common/binder_test.goshortcuts/common/protocol.goshortcuts/common/protocol_test.goshortcuts/common/runner.goshortcuts/common/runner_args_test.goshortcuts/common/typed_help.goshortcuts/common/typed_help_test.goshortcuts/common/typed_shortcut.goshortcuts/common/typed_shortcut_test.goshortcuts/common/types.goshortcuts/common/types_test.goshortcuts/im/builders_test.goshortcuts/im/helpers.goshortcuts/im/helpers_test.goshortcuts/im/im_messages_send.goshortcuts/im/im_messages_send_test.goshortcuts/im/protocol.goshortcuts/im/protocol_test.goshortcuts/im/shortcuts.goshortcuts/im/validate_media_test.goshortcuts/register.goshortcuts/register_test.go
✅ Files skipped from review due to trivial changes (2)
- shortcuts/common/argstype/user_open_id.go
- shortcuts/im/validate_media_test.go
🚧 Files skipped from review as they are similar to previous changes (38)
- shortcuts/common/types.go
- errs/subtypes_shortcut.go
- errs/subtypes_shortcut_test.go
- shortcuts/common/argstype/safe_path_test.go
- shortcuts/common/runner_args_test.go
- shortcuts/common/argstype/spreadsheet_ref_test.go
- shortcuts/common/argstype/user_open_id_list_test.go
- shortcuts/common/typed_help_test.go
- cmd/auth/login_interactive.go
- cmd/error_auth_hint.go
- shortcuts/common/argstype/chat_id_test.go
- shortcuts/common/argstype/user_open_id_test.go
- shortcuts/common/argstype/spreadsheet_ref.go
- shortcuts/common/argstype/media_input.go
- shortcuts/common/argstype/chat_id.go
- shortcuts/im/shortcuts.go
- shortcuts/common/types_test.go
- cmd/auth/login.go
- cmd/diagnose_scope_test.go
- shortcuts/common/argstype/user_open_id_list.go
- shortcuts/im/protocol_test.go
- shortcuts/common/argstype/safe_path.go
- shortcuts/im/protocol.go
- shortcuts/register.go
- shortcuts/common/protocol.go
- cmd/auth/login_test.go
- shortcuts/common/runner.go
- shortcuts/common/argstype/media_input_test.go
- shortcuts/im/im_messages_send_test.go
- shortcuts/common/binder_test.go
- shortcuts/im/helpers_test.go
- shortcuts/im/builders_test.go
- shortcuts/common/typed_shortcut.go
- shortcuts/common/typed_shortcut_test.go
- shortcuts/common/binder.go
- shortcuts/im/im_messages_send.go
- shortcuts/register_test.go
- shortcuts/common/typed_help.go
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1110 +/- ##
==========================================
+ Coverage 69.21% 69.27% +0.05%
==========================================
Files 639 647 +8
Lines 59794 60615 +821
==========================================
+ Hits 41389 41992 +603
- Misses 15059 15221 +162
- Partials 3346 3402 +56 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
bd95794 to
c82ad7b
Compare
Add strongly-typed shortcut protocol that coexists with the legacy common.Shortcut. The new common.TypedShortcut[T] is a generic outer wrapper backed by a reflect-driven binder; both legacy and typed shortcuts satisfy a new common.Mountable / common.ShortcutDescriptor interface pair so register.go can dispatch either through the same pipeline. Framework (shortcuts/common): - protocol.go — Mountable / ShortcutDescriptor / OneOfMarker / Validatable / Normalizable[T] / ArgsValidator / Maybe[T] / HelpExample - binder.go — reflect Args walk, intra-Args flag-tag uniqueness panic, cobra flag registration, bindFlags + bindMaybe, runNormalize (via MethodByName dispatch — Normalizable[T] can't be type-asserted through a non-generic interface), runValidateValue, runFrameworkRules for required / enum / OneOf / group - typed_shortcut.go — TypedShortcut[T] struct, 8 descriptor methods, mountTyped adapter that synthesizes a legacy Shortcut shell and reuses runShortcut verbatim (identity / scopes / @file / stdin / jq / dry-run / high-risk gate) - typed_help.go — sectioned --help (CHOOSE ONE / OPTIONAL / EXAMPLES) with cmdutil.GetRisk/GetTips passthrough so typed shortcuts keep the Risk: and Tips: blocks - runner.go — typedArgs lifecycle slot on RuntimeContext - types.go — 5 GetX accessors on *Shortcut so legacy shortcuts satisfy ShortcutDescriptor alongside the existing pointer-receiver scope methods Typed primitives (shortcuts/common/argstype): - ChatID / UserOpenID / UserOpenIDList — prefix-validated identifiers - SafePath — cwd-relative, rejects absolute paths and ".." segments - MediaInput — tri-state (URL bypass / img_xxx-file_xxx key bypass / SafePath delegation) - SpreadsheetRef — Normalize extracts shtcn token from feishu URLs Error contract (errs): - 3 new Subtype constants: shortcut_oneof_missing / shortcut_oneof_multiple / shortcut_group_incomplete. Per-field failures (required / enum / typed primitive format) reuse the existing SubtypeInvalidArgument so no new error type is introduced. Registry refactor (shortcuts + cmd): - AllShortcuts() now returns []common.ShortcutDescriptor; legacy shortcuts are boxed as *Shortcut (pointer required for the pointer-receiver scope methods), typed shortcuts boxed as Mountable - Register dispatches via the Mountable interface - cmd/auth/login, cmd/auth/login_interactive, cmd/error_auth_hint, cmd/diagnose_scope_test, shortcuts/register_test (shortcuts.json generator) updated to read through GetService / GetCommand / GetAuthTypes / GetDescription / DeclaredScopesForIdentity - shortcutSupportsIdentity helpers accept ShortcutDescriptor Pilot (shortcuts/im): - protocol.go — MessageTarget / MessageContent (with seven content variants) / VideoContent (paired video + cover) / RawContent (--content with explicit msg-type, validates JSON in ValidateValue) - im_messages_send.go — migrated to TypedShortcut[*ImMessagesSendArgs]. Inline Validate closure is replaced by framework-derived checks (OneOf target, OneOf content, VideoContent group, typed-primitive formats, RawContent JSON). Helpers (resolveMediaContent, wrapMarkdownAsPostForDryRun, normalizeAtMentions, etc.) reused verbatim; only the field-access pattern changes from runtime.Str("x") to args.X. - shortcuts.go — new TypedShortcuts() exporter; ImMessagesSend removed from the legacy Shortcuts() slice so it is not double-mounted - register.go wires addTyped(im.TypedShortcuts()) into init Known follow-ups: - runFrameworkRules and bindFlags do not recurse into OneOf bucket / group sub-structs; im messages-send compensates with a local bindMessagesSendArgs + validateVideoGroup. Generalizing the binder to recurse will let future migrations drop the local shim. - common.ValidateChatID / common.ValidateUserID become redundant once all legacy shortcuts that call them migrate; can be retired with the last legacy caller. Refs: docs/superpowers/specs/2026-05-26-shortcut-protocol-design.md
c82ad7b to
a07239b
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@shortcuts/im/im_messages_send_test.go`:
- Around line 40-49: The test currently only compares lengths and iterates
values which allows duplicates like ["bot","bot"] to pass; change the assertion
to compare unique auth types by converting ImMessagesSend.GetAuthTypes() into a
map/set (e.g., seen := map[string]bool{}) then ensure every expected key in
wantAuth exists in seen and that seen has no unexpected keys and equal
cardinality to wantAuth; use ImMessagesSend.GetAuthTypes to locate the call and
update the loop/checks to detect duplicates and missing members.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 71545eb9-59c3-4571-8da0-3176d56f82db
📒 Files selected for processing (42)
cmd/auth/login.gocmd/auth/login_interactive.gocmd/auth/login_test.gocmd/diagnose_scope_test.gocmd/error_auth_hint.goerrs/subtypes_shortcut.goerrs/subtypes_shortcut_test.goshortcuts/common/argstype/chat_id.goshortcuts/common/argstype/chat_id_test.goshortcuts/common/argstype/media_input.goshortcuts/common/argstype/media_input_test.goshortcuts/common/argstype/safe_path.goshortcuts/common/argstype/safe_path_test.goshortcuts/common/argstype/spreadsheet_ref.goshortcuts/common/argstype/spreadsheet_ref_test.goshortcuts/common/argstype/user_open_id.goshortcuts/common/argstype/user_open_id_list.goshortcuts/common/argstype/user_open_id_list_test.goshortcuts/common/argstype/user_open_id_test.goshortcuts/common/binder.goshortcuts/common/binder_test.goshortcuts/common/protocol.goshortcuts/common/protocol_test.goshortcuts/common/runner.goshortcuts/common/runner_args_test.goshortcuts/common/typed_help.goshortcuts/common/typed_help_test.goshortcuts/common/typed_shortcut.goshortcuts/common/typed_shortcut_test.goshortcuts/common/types.goshortcuts/common/types_test.goshortcuts/im/builders_test.goshortcuts/im/helpers.goshortcuts/im/helpers_test.goshortcuts/im/im_messages_send.goshortcuts/im/im_messages_send_test.goshortcuts/im/protocol.goshortcuts/im/protocol_test.goshortcuts/im/shortcuts.goshortcuts/im/validate_media_test.goshortcuts/register.goshortcuts/register_test.go
✅ Files skipped from review due to trivial changes (1)
- shortcuts/im/validate_media_test.go
🚧 Files skipped from review as they are similar to previous changes (37)
- shortcuts/common/typed_help_test.go
- shortcuts/common/argstype/chat_id.go
- shortcuts/common/runner_args_test.go
- errs/subtypes_shortcut_test.go
- cmd/auth/login.go
- cmd/auth/login_interactive.go
- shortcuts/common/types_test.go
- shortcuts/common/argstype/spreadsheet_ref.go
- shortcuts/im/helpers.go
- shortcuts/common/runner.go
- shortcuts/common/argstype/spreadsheet_ref_test.go
- shortcuts/common/protocol.go
- shortcuts/common/argstype/media_input_test.go
- shortcuts/common/argstype/user_open_id_list.go
- shortcuts/im/builders_test.go
- shortcuts/common/argstype/user_open_id_test.go
- cmd/diagnose_scope_test.go
- errs/subtypes_shortcut.go
- shortcuts/common/typed_help.go
- shortcuts/im/protocol.go
- shortcuts/common/typed_shortcut.go
- shortcuts/common/argstype/user_open_id.go
- shortcuts/common/argstype/safe_path.go
- shortcuts/common/argstype/chat_id_test.go
- shortcuts/common/protocol_test.go
- shortcuts/im/protocol_test.go
- shortcuts/common/typed_shortcut_test.go
- cmd/error_auth_hint.go
- shortcuts/common/types.go
- shortcuts/register.go
- cmd/auth/login_test.go
- shortcuts/register_test.go
- shortcuts/im/helpers_test.go
- shortcuts/common/binder_test.go
- shortcuts/common/binder.go
- shortcuts/common/argstype/media_input.go
- shortcuts/im/im_messages_send.go
- im_messages_send_test: assert auth-type set membership (reject duplicates / missing members) per CodeRabbit review - binder_coverage_test: cover bindMaybe, bindBuckets / bindBucketInner, bucketLeafValue, runNormalize / asString, checkGroup, checkEnumAndRequired, and group recursion in runValidateValue — lifts patch coverage over the 60% gate
There was a problem hiding this comment.
🧹 Nitpick comments (2)
shortcuts/common/binder_coverage_test.go (2)
159-171: ⚡ Quick winAssert pointer normalization explicitly.
TokenPtris set up to exercise pointer normalization but never asserted. Add a direct check so this branch is validated, not just executed.Proposed assertion
if args.Token != "c:raw" { t.Errorf("Token = %q, want c:raw", args.Token) } +if args.TokenPtr == nil || *args.TokenPtr != "c:xy" { + t.Errorf("TokenPtr = %v, want c:xy", args.TokenPtr) +} if got := buf.String(); got == "" || !bytes.Contains([]byte(got), []byte("hint: raw")) { t.Errorf("stderr = %q, want it to contain the normalize hint", got) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/common/binder_coverage_test.go` around lines 159 - 171, After calling runNormalize with bcNormArgs (ptr created by bcNormField("xy")), add an explicit assertion that the pointer-normalized value was updated: check the dereferenced TokenPtr on args (e.g., *args.TokenPtr) equals the expected normalized string ("c:xy") and fail the test with t.Fatalf or t.Errorf if it does not match; reference bcNormField, bcNormArgs, TokenPtr, runNormalize and args when locating where to add this assertion.
28-29: ⚡ Quick winStop discarding setup errors in tests.
Several tests ignore errors from
walkArgs,registerFlags, andParseFlags. This can mask regressions and make failures harder to diagnose.Suggested pattern to apply across this file
- specs, _ := walkArgs(reflect.TypeOf(&bcMaybeArgs{})) - _ = registerFlags(cmd, specs) - _ = cmd.ParseFlags([]string{"--m-on"}) + specs, err := walkArgs(reflect.TypeOf(&bcMaybeArgs{})) + if err != nil { + t.Fatalf("walkArgs: %v", err) + } + if err := registerFlags(cmd, specs); err != nil { + t.Fatalf("registerFlags: %v", err) + } + if err := cmd.ParseFlags([]string{"--m-on"}); err != nil { + t.Fatalf("ParseFlags: %v", err) + }Also applies to: 53-54, 119-120, 161-162, 180-180, 201-204, 217-217, 221-223, 243-243, 247-248, 259-260, 267-268, 286-286
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/common/binder_coverage_test.go` around lines 28 - 29, The tests are discarding errors from setup calls (e.g., walkArgs, registerFlags, ParseFlags) which can hide failures; update each call site (such as the walkArgs(reflect.TypeOf(&bcMaybeArgs{})) call, registerFlags(cmd, specs), and any ParseFlags uses) to check the returned error and fail the test immediately (use t.Fatalf or t.Fatalf-like helper or testify/require.NoError) with a clear message including the error; ensure you propagate and assert errors for all occurrences listed so test setup failures cause visible test failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@shortcuts/common/binder_coverage_test.go`:
- Around line 159-171: After calling runNormalize with bcNormArgs (ptr created
by bcNormField("xy")), add an explicit assertion that the pointer-normalized
value was updated: check the dereferenced TokenPtr on args (e.g.,
*args.TokenPtr) equals the expected normalized string ("c:xy") and fail the test
with t.Fatalf or t.Errorf if it does not match; reference bcNormField,
bcNormArgs, TokenPtr, runNormalize and args when locating where to add this
assertion.
- Around line 28-29: The tests are discarding errors from setup calls (e.g.,
walkArgs, registerFlags, ParseFlags) which can hide failures; update each call
site (such as the walkArgs(reflect.TypeOf(&bcMaybeArgs{})) call,
registerFlags(cmd, specs), and any ParseFlags uses) to check the returned error
and fail the test immediately (use t.Fatalf or t.Fatalf-like helper or
testify/require.NoError) with a clear message including the error; ensure you
propagate and assert errors for all occurrences listed so test setup failures
cause visible test failures.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 77a27179-29fe-4765-9b55-186259dd15c4
📒 Files selected for processing (2)
shortcuts/common/binder_coverage_test.goshortcuts/im/im_messages_send_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- shortcuts/im/im_messages_send_test.go
The im +messages-send TypedShortcut pilot was exploratory only; revert it while retaining the TypedShortcut framework for future migrations. - shortcuts/im: restored to pre-pilot state (im_messages_send.go back to legacy common.Shortcut; deleted protocol.go, protocol_test.go, im_messages_send_test.go; shortcuts.go drops TypedShortcuts()). - shortcuts/register.go: removed addTyped(im.TypedShortcuts()) wiring and the now-unused addTyped helper; legacy addLegacy + Mountable dispatch retained. - shortcuts/common, argstype, errs subtypes, cmd/auth adapters: kept. - framework doc comments: replaced examples referencing the removed pilot types (MessageTarget/MessageContent/VideoContent/RawContent) with neutral descriptions; noted no typed shortcut is registered today. Framework now has no production caller. Verified: go build ./... and go test ./shortcuts/... ./errs/... ./cmd/auth/... ./cmd/ all pass.
After reverting the im pilot in ccf654d the TypedShortcut framework has no production caller, so the CI deadcode check flagged five methods as newly unreachable. Resolve without breaking the framework: - Remove TypedShortcut.Mount — a convenience wrapper over MountWithContext with zero callers (production OR test); not part of any interface contract. - Add focused unit tests for the four ShortcutDescriptor methods required by the Mountable contract — GetAuthTypes plus ScopesForIdentity / ConditionalScopesForIdentity / DeclaredScopesForIdentity, with table-driven coverage of the user / bot / fallback / dedupe branches. Locally verified: go run golang.org/x/tools/cmd/deadcode@v0.31.0 -test ./... against HEAD vs origin/main yields an empty diff.
bindFlags and bindBuckets only populated top-level leaves and OneOf buckets; a top-level group sub-struct (a regular nested struct without OneOf() marker) had its inner flags registered and group-completeness checked, but its field values were never written back into the Args struct — Execute would read empty values from args.Group.X. Add bindGroups as the top-level counterpart to bindBuckets for IsGroup fields, and invoke it from mountTyped's Validate closure right after bindBuckets. Behavior mirrors bindBuckets / bindBucketInner: - Value-type top-level group: always populated; inner fields receive cobra flag values (including defaults). - Pointer-type top-level group: allocated iff at least one inner flag was Changed, so a nil group still signals "user did not engage this group" while a non-nil group means "the user opted into it". Tests cover four cases: value group with explicit flags, value group with defaults applied, pointer group allocated when an inner flag is set, pointer group left nil when nothing is set. Verified: go build ./..., go test ./shortcuts/common/..., and `go run golang.org/x/tools/cmd/deadcode@v0.31.0 -test ./...` against HEAD vs origin/main yields zero new dead code. This closes the previously-documented limitation that group sub-structs had to be nested inside an OneOf bucket to actually bind values.
…om any inner flag The previous checkOneOf required group / bucket variants inside a OneOf to mark exactly one inner field with `oneof_trigger:"true"`, so the framework could identify "the flag that signals the variant was selected". This forces the Args writer to think about an implementation detail and produces a misleading shortcut_oneof_missing when a user supplies only a companion field (e.g. --video-cover without --video) — the user actually attempted that variant, but the framework couldn't see it. Replace the explicit trigger with implicit detection: any inner flag of a group / bucket variant that is Changed counts as attempting that variant. The follow-up checkGroup catches the partial-fill case and surfaces a shortcut_group_incomplete pointing at the missing flag, which is strictly more useful than the prior oneof_missing. Code changes: - binder.go: drop fieldSpec.OneOfTrig, drop parseFieldSpec's oneof_trigger tag handling, rewrite checkOneOf with the inferred-attempt logic, delete the isTrigger helper (now inlined and simplified). - typed_help.go: renderFlagsInBucket flattens all inner flags to the parent's indent (no more "trigger vs companion" visual distinction, matching the framework's new semantics). - binder_test.go: add three behavioural tests covering the new contract: (1) companion alone surfaces group_incomplete (not oneof_missing), (2) full group passes, (3) simple variant + group companion both attempted yields oneof_multiple. Verified: go build ./..., go test ./shortcuts/common/..., `go run golang.org/x/tools/cmd/deadcode@v0.31.0 -test ./...` against HEAD vs origin/main yields zero new dead code. Net: -1 tag, -1 field on fieldSpec, -1 helper function, simpler writer ergonomics, more accurate error messages.
CI fast-gate's `gofmt -l .` flagged an alignment issue in contentBucket: gofmt aligns struct field tags to the longest *named* tag-bearing field in the group; my hand-written extra spaces in `Text *string \`flag:"ct"\`` got normalised to a single space. Behaviour-neutral; tests still pass.
…ortcut.Mount Three fixes surfaced by a dogfood migration of an existing legacy shortcut to the TypedShortcut framework. 1. Required flags rendered under OPTIONAL The typed help renderer had no notion of required vs optional and lumped every top-level leaf into a single OPTIONAL section. Add renderRequiredSection that lists fields whose `required` tag is set under a REQUIRED: header; the updated renderOptionalSection skips those so each leaf appears in exactly one section. 2. Default values silently dropped Fields tagged `default:"x"` showed no `(default "x")` suffix, unlike cobra's legacy default help. Add a formatLeafLine helper that appends `(default "x")` whenever fieldSpec.DefaultValue is non-empty; the REQUIRED, OPTIONAL, and CHOOSE ONE bucket renderers all reuse it for consistent information density. 3. TypedShortcut.Mount was missing Legacy common.Shortcut exposes BOTH .Mount(parent, factory) and .MountWithContext. The framework dropped .Mount in 8d8acb8 to satisfy deadcode, but a dogfood migration revealed the asymmetry forces every migrating shortcut's tests to also switch to MountWithContext. Restore the 3-line Mount convenience method (delegates to MountWithContext with a background context) and add a unit test exercising it directly — the test doubles as API documentation and keeps deadcode happy. Tests added: - TestTypedShortcut_Mount: verifies the legacy-shaped Mount(parent, factory) call still wires the subcommand - TestTypedHelp_RequiredSectionAndDefaults: asserts REQUIRED comes before OPTIONAL, --limit lands in REQUIRED not OPTIONAL, `(default "20")` appears for --page-size, and a plain --verbose carries no default suffix Verified locally: go build ./..., go test ./shortcuts/common/..., gofmt -l, and `go run golang.org/x/tools/cmd/deadcode@v0.31.0 -test ./...` vs origin/main — all pass with zero new dead code.
…i-state Maybe[T] solved a real problem (distinguishing "user did not provide this flag" from "user provided the type's zero value, e.g. --notify=false or --limit 0"), but it added a framework-specific wrapper that every migrator had to learn. Two independent dogfood migrations both flagged it as the guide's biggest cognitive bump. This change retires Maybe[T] and reuses the existing OneOf pointer convention at the top level: *T now means "optional with tri-state semantics — nil iff the user did not set the flag". The rule is the same whether the *T sits inside a OneOf bucket (Chat *ChatID — variant not selected) or at the top level of an Args struct (Notify *bool — flag not given), so users learn one concept instead of two. Code: - binder.go: bindLeaf now gates pointer allocation behind cmd.Flags().Changed(name); previously a *T top-level pointer was always allocated, defeating the "nil = not given" semantic. fieldSpec loses the IsMaybe field; parseFieldSpec drops Maybe-shape detection; registerFlags / bindFlags drop their IsMaybe branches; the bindMaybe helper is deleted. - protocol.go: Maybe[T] type removed. - protocol_test.go: TestMaybe_UnsetVsZero removed. - binder_coverage_test.go: bindMaybe tests replaced with two TestBindLeaf_Ptr* tests covering the new "*T top-level pointer" contract — nil when absent, non-nil with the user's value (including the tri-state case --notify=false) when explicitly set. Verified: go build ./..., go test ./shortcuts/common/..., gofmt clean, and `go run golang.org/x/tools/cmd/deadcode@v0.31.0 -test ./...` vs origin/main shows zero new dead code.
UserOpenIDList ([]string) had zero production callers and cannot be bound as a typed flag field: the binder only handles string/bool/int scalars, so a []string field panics at bind time (reflect.Value.Convert: string cannot convert to []string). Remove the type, its ParseUserOpenIDList helper and the test instead of special-casing slice binding for a type nobody uses.
Bring TypedShortcut[T] to parity with legacy common.Shortcut on two flag capabilities that were silently missing on the typed path: - @file / stdin: declare `input:"file,stdin"` on an Args field; the binder resolves @path (file) and - (stdin) before binding, recursing into OneOf buckets and groups. resolveInputForFlag is extracted from the legacy resolveInputFlags so both paths behave identically. - enum: registerLeaf registers shell completion and typed help renders the candidate list, matching legacy. enum/input tags on non-string fields now error at Mount time instead of being silently skipped at runtime. Legacy behavior unchanged. Adds binder_input_enum_test.go (11 cases).
Close the last legacy/typed capability gaps so migration never downgrades: - []string fields: registerLeaf/bindLeaf gain a Slice case. Default maps to cobra StringSlice (comma-separated, repeatable); `split:"none"` opts into StringArray (repeatable, no comma split). Non-[]string slices and split on non-slice fields error at Mount time. bucketLeafValue handles slices in OneOf/group too. - per-flag hidden: `hidden:"true"` tag → fieldSpec.Hidden → MarkHidden; typed help skips hidden flags (parity with legacy Flag.Hidden). - @file/stdin discoverability: typed help now appends a (supports @file / - for stdin) hint for input-tagged flags, matching legacy. Adds binder_slice_hidden_test.go (11 cases).
…unt parity Two parity edge cases from a follow-up audit: - []<named string> element types (type ID string; field []ID) passed parse validation but panicked at bind via reflect.Convert([]string -> []ID). Build the slice element-by-element with SetString (stringsToSlice) so plain []string, named slice types and named element types all bind correctly. - nil Execute: mountTyped now skips mounting (matching legacy Shortcut.MountWithContext) instead of mounting a command that errors only at invocation, keeping the command tree identical after migration. Adds binder_namedslice_nilexec_test.go (4 cases).
Summary
Introduces a strongly-typed shortcut protocol that coexists with the legacy
common.Shortcut. A genericTypedShortcut[T]outer wrapper, a reflect-driven binder, six typed primitives, and a sectioned--helprenderer give shortcut authors compile-time argument types and structured error envelopes (shortcut_oneof_missing/shortcut_oneof_multiple/shortcut_group_incomplete) without forking the existingrunShortcutpipeline.Note
Update (ccf654d): the
im +messages-sendpilot was reverted. The pilotwas an exploratory end-to-end validation of the framework only. It has been
rolled back so this PR lands the framework alone —
im +messages-sendstays on the legacy
common.Shortcutpath, identical tomain. Theframework retains no production caller yet; the first real migration will
come in a follow-up PR. See the "Net result after revert" section below for
the current diff. The Changes / Test Plan sections below describe the
original pilot commits (a07239b / ad4368e) and are kept for history.
Net result after revert (current PR diff)
shortcuts/common/framework (protocol / binder / typed_shortcut / typed_help / runner / types),shortcuts/common/argstype/primitives,errsshortcut subtypes,cmd/auth+register.goShortcutDescriptor/Mountabledual-track dispatch.shortcuts/im/restored to its pre-pilot (main) state —im_messages_send.goback to legacycommon.Shortcut;protocol.go/protocol_test.go/im_messages_send_test.godeleted;shortcuts.gono longer exportsTypedShortcuts().addTyped(im.TypedShortcuts())and the now-unusedaddTypedhelper dropped fromregister.go; framework doc comments that referenced the pilot types (MessageTarget/MessageContent/VideoContent/RawContent) rewritten as neutral descriptions.go build ./...andgo test ./shortcuts/... ./errs/... ./cmd/auth/... ./cmd/all pass;gofmtclean; no dangling references to the removed pilot types remain in the framework.Changes (original pilot — see Update note above)
shortcuts/common/protocol.go—Mountable/ShortcutDescriptor/OneOfMarker/Validatable/Normalizable[T]/ArgsValidator/Maybe[T]/HelpExampleshortcuts/common/binder.go— reflect Args walker with intra-Args tag-uniqueness panic, cobra flag registration, value binding,bindBucketsfor nested OneOf / group sub-structs,runNormalize(reflect-dispatched, generic-safe),runValidateValue(recurses into buckets / groups),runFrameworkRules(required / enum / OneOf / group, recursive)shortcuts/common/typed_shortcut.go—TypedShortcut[T]with 8 metadata methods +mountTypedadapter that synthesizes a legacy shell and reuses the existing identity / scopes /@file/ stdin / jq / dry-run / high-risk gateshortcuts/common/typed_help.go— sectioned--help(CHOOSE ONE <FIELD>/OPTIONAL/EXAMPLES/GLOBAL FLAGS) withRisk:/Tips:passthroughshortcuts/common/argstype/— 6 typed primitives:ChatID(oc_ prefix),UserOpenID(ou_ prefix),UserOpenIDList(CSV),SafePath(cwd-relative, rejects abs /..),MediaInput(URL / key / path tri-state),SpreadsheetRef(URL → token Normalize)errs/subtypes_shortcut.go—SubtypeShortcutOneOfMissing/SubtypeShortcutOneOfMultiple/SubtypeShortcutGroupIncompleteshortcuts/common/types.go— 5Get*accessors on*Shortcut(GetService/GetCommand/GetDescription/GetAuthTypes/GetRisk)shortcuts/common/runner.go—typedArgs anylifecycle slot +TypedArgs/SetTypedArgsaccessors onRuntimeContextshortcuts/register.go—AllShortcuts()returns[]ShortcutDescriptor;RegisterShortcutsWithContextdispatchesMountableso legacy and typed shortcuts share one path;addLegacyhelper boxes each domain's contributions (theaddTypedhelper andimpilot wiring were later reverted — see Update note)Add(reverted)shortcuts/im/protocol.go—MessageTarget/MessageContent/VideoContent/RawContentModify(reverted; back to legacy)shortcuts/im/im_messages_send.go— migrated toTypedShortcut[*ImMessagesSendArgs]Modify(reverted)shortcuts/im/shortcuts.go— newTypedShortcuts()exportercmd/auth/login.go,cmd/auth/login_interactive.go,cmd/auth/login_test.go,cmd/error_auth_hint.go,cmd/diagnose_scope_test.go,shortcuts/register_test.go— read shortcuts throughShortcutDescriptorinterface methods (GetService/GetCommand/GetAuthTypes/DeclaredScopesForIdentity);shortcutSupportsIdentityaccepts the interfaceTest Plan
go build ./...and unit tests pass (go test ./shortcuts/... ./errs/... ./cmd/auth/... ./cmd/)gofmtclean; no dangling references to reverted pilot typesunused-lint hazard from retaining the frameworkim +messages-sendreverts to its existing (already-tested) legacy behavior.Related Issues
N/A
Summary by CodeRabbit
New Features
Bug Fixes
Tests