Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var AppsAccessScopeGet = common.Shortcut{ Service: appsService, Command: "+access-scope-get", Description: "Get Miaoda app access scope configuration", Risk: "read", Scopes: []string{"spark:app:read"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "app-id", Desc: "app ID", Required: true}, }, Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if strings.TrimSpace(rctx.Str("app-id")) == "" { return output.ErrValidation("--app-id is required") } return nil }, DryRun: func(ctx context.Context, rctx *common.RuntimeContext) *common.DryRunAPI { appID := strings.TrimSpace(rctx.Str("app-id")) return common.NewDryRunAPI(). GET(fmt.Sprintf("%s/apps/%s/access-scope", apiBasePath, validate.EncodePathSegment(appID))). Desc("Get Miaoda app access scope") }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { appID := strings.TrimSpace(rctx.Str("app-id")) path := fmt.Sprintf("%s/apps/%s/access-scope", apiBasePath, validate.EncodePathSegment(appID)) data, err := rctx.CallAPI("GET", path, nil, nil) if err != nil { return err } rctx.OutFormat(data, nil, func(w io.Writer) { fmt.Fprintf(w, "scope: %v\n", data["scope"]) }) return nil }, }
AppsAccessScopeGet reads the current access scope configuration of a Miaoda app. 响应原样透传服务端契约(字符串 scope 枚举 All/Tenant/Range + 拆分的 users/departments/chats 数组)。
var AppsAccessScopeSet = common.Shortcut{ Service: appsService, Command: "+access-scope-set", Description: "Set Miaoda app access scope (specific / public / tenant)", Risk: "write", Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "app-id", Desc: "app ID", Required: true}, {Name: "scope", Desc: "scope: specific | public | tenant", Required: true, Enum: []string{"specific", "public", "tenant"}}, {Name: "targets", Desc: `targets JSON array: [{"type":"user|department|chat","id":"..."}, ...]`}, {Name: "apply-enabled", Type: "bool", Desc: "allow apply for access (scope=specific)"}, {Name: "approver", Desc: "approver open_id (when --apply-enabled; server allows exactly one)"}, {Name: "require-login", Type: "bool", Desc: "require login (scope=public)"}, }, Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if strings.TrimSpace(rctx.Str("app-id")) == "" { return output.ErrValidation("--app-id is required") } return validateAccessScopeFlags(rctx) }, DryRun: func(ctx context.Context, rctx *common.RuntimeContext) *common.DryRunAPI { appID := strings.TrimSpace(rctx.Str("app-id")) dry := common.NewDryRunAPI(). PUT(fmt.Sprintf("%s/apps/%s/access-scope", apiBasePath, validate.EncodePathSegment(appID))). Desc("Set Miaoda app access scope") body, bodyErr := buildAccessScopeBody(rctx) if bodyErr != nil { dry.Set("body_error", bodyErr.Error()) } else { dry.Body(body) } return dry }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { body, err := buildAccessScopeBody(rctx) if err != nil { return err } appID := strings.TrimSpace(rctx.Str("app-id")) path := fmt.Sprintf("%s/apps/%s/access-scope", apiBasePath, validate.EncodePathSegment(appID)) data, err := rctx.CallAPI("PUT", path, nil, body) if err != nil { return err } rctx.OutFormat(data, nil, func(w io.Writer) { fmt.Fprintf(w, "access-scope set: %s\n", rctx.Str("scope")) }) return nil }, }
AppsAccessScopeSet sets the app's access scope (specific / public / tenant).
var AppsCreate = common.Shortcut{ Service: appsService, Command: "+create", Description: "Create a new Miaoda app", Risk: "write", Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "name", Desc: "app display name", Required: true}, {Name: "app-type", Desc: "app type (currently only: HTML)", Required: true}, {Name: "description", Desc: "app description"}, {Name: "icon-url", Desc: "app icon URL (server uses default if omitted)"}, }, Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if strings.TrimSpace(rctx.Str("name")) == "" { return output.ErrValidation("--name is required") } appType := strings.TrimSpace(rctx.Str("app-type")) if appType == "" { return output.ErrValidation("--app-type is required") } if !validAppTypes[appType] { return output.ErrValidation(fmt.Sprintf("--app-type %q is not supported (allowed: HTML)", appType)) } return nil }, DryRun: func(ctx context.Context, rctx *common.RuntimeContext) *common.DryRunAPI { return common.NewDryRunAPI(). POST(apiBasePath + "/apps"). Desc("Create a Miaoda app"). Body(buildAppsCreateBody(rctx)) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { data, err := rctx.CallAPI("POST", apiBasePath+"/apps", nil, buildAppsCreateBody(rctx)) if err != nil { return err } rctx.OutFormat(data, nil, func(w io.Writer) { fmt.Fprintf(w, "created: %s\n", common.GetString(data, "app", "app_id")) }) return nil }, }
AppsCreate creates a new Miaoda app.
var AppsHTMLPublish = common.Shortcut{ Service: appsService, Command: "+html-publish", Description: "Publish HTML to a Miaoda app (single multipart POST returns the access URL)", Risk: "write", Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "app-id", Desc: "Miaoda app ID", Required: true}, {Name: "path", Desc: "path to HTML file or directory", Required: true}, {Name: "allow-sensitive", Type: "bool", Desc: "skip the credential-file scan (allow .env / .npmrc / .aws/credentials / etc. in the publish payload)"}, }, Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if strings.TrimSpace(rctx.Str("app-id")) == "" { return output.ErrValidation("--app-id is required") } path := strings.TrimSpace(rctx.Str("path")) if path == "" { return output.ErrValidation("--path is required") } if rctx.Bool("allow-sensitive") { return nil } candidates, err := walkHTMLPublishCandidates(rctx.FileIO(), path) if err != nil { return nil } var hits []string for _, c := range candidates { if isSensitiveCandidate(path, c) { hits = append(hits, c.RelPath) } } if len(hits) > 0 { return sensitiveCandidatesError(hits) } return nil }, DryRun: func(ctx context.Context, rctx *common.RuntimeContext) *common.DryRunAPI { appID := strings.TrimSpace(rctx.Str("app-id")) path := strings.TrimSpace(rctx.Str("path")) dry := common.NewDryRunAPI() dry.Desc("Upload tar.gz + publish HTML (multipart, returns url)") dry.POST(fmt.Sprintf("%s/apps/%s/upload_and_release_html_code", apiBasePath, validate.EncodePathSegment(appID))). Set("content_type", "multipart/form-data") candidates, err := walkHTMLPublishCandidates(rctx.FileIO(), path) if err != nil { dry.Set("path_error", err.Error()) return dry } if err := ensureIndexHTML(candidates); err != nil { dry.Set("validation_error", err.Error()) } dry.Set("file_count", len(candidates)) var totalSize int64 names := make([]string, 0, len(candidates)) for _, c := range candidates { totalSize += c.Size names = append(names, c.RelPath) } dry.Set("total_size_bytes", totalSize) dry.Set("files", names) if rctx.Bool("allow-sensitive") { var waived []string for _, c := range candidates { if isSensitiveCandidate(path, c) { waived = append(waived, c.RelPath) } } if len(waived) > 0 { dry.Set("sensitive_waived", waived) dry.Set("sensitive_waived_summary", fmt.Sprintf("%d credential file(s) included because --allow-sensitive is set", len(waived))) } } return dry }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { spec := appsHTMLPublishSpec{ AppID: strings.TrimSpace(rctx.Str("app-id")), Path: strings.TrimSpace(rctx.Str("path")), } client := appsHTMLPublishAPI{runtime: rctx} out, err := runHTMLPublish(ctx, rctx.FileIO(), client, spec) if err != nil { return err } rctx.OutFormat(out, nil, func(w io.Writer) { if url, ok := out["url"].(string); ok && url != "" { fmt.Fprintf(w, "url: %s\n", url) } }) return nil }, }
AppsHTMLPublish packs --path as tar.gz and uploads + publishes via one multipart POST.
var AppsList = common.Shortcut{ Service: appsService, Command: "+list", Description: "List Miaoda apps owned by the calling user (cursor pagination)", Risk: "read", Scopes: []string{"spark:app:read"}, AuthTypes: []string{"user"}, HasFormat: true, Hidden: true, Flags: []common.Flag{ {Name: "page-size", Type: "int", Default: "20", Desc: "page size"}, {Name: "page-token", Desc: "pagination cursor from previous response"}, }, DryRun: func(ctx context.Context, rctx *common.RuntimeContext) *common.DryRunAPI { return common.NewDryRunAPI(). GET(apiBasePath + "/apps"). Desc("List Miaoda apps"). Params(buildAppsListParams(rctx)) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { data, err := rctx.CallAPI("GET", apiBasePath+"/apps", buildAppsListParams(rctx), nil) if err != nil { return err } items, _ := data["items"].([]interface{}) rctx.OutFormat(data, nil, func(w io.Writer) { rows := make([]map[string]interface{}, 0, len(items)) for _, item := range items { m, ok := item.(map[string]interface{}) if !ok { continue } rows = append(rows, map[string]interface{}{ "app_id": m["app_id"], "name": m["name"], "updated_at": m["updated_at"], }) } output.PrintTable(w, rows) }) return nil }, }
AppsList lists Miaoda apps owned by the calling user (cursor pagination).
Hidden from --help / tab completion (Hidden: true) so agents do not discover it as a way to enumerate / search applications. Direct invocation still works for humans who know the command. When agents need an existing app_id, they should ask the user to provide either the Miaoda app URL (extract app_id from the path segment after /app/) or the app_id string directly; see lark-apps SKILL.md.
var AppsUpdate = common.Shortcut{ Service: appsService, Command: "+update", Description: "Partially update a Miaoda app (only provided fields are sent)", Risk: "write", Scopes: []string{"spark:app:write"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "app-id", Desc: "app ID", Required: true}, {Name: "name", Desc: "new app display name"}, {Name: "description", Desc: "new app description"}, }, Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if strings.TrimSpace(rctx.Str("app-id")) == "" { return output.ErrValidation("--app-id is required") } body := buildAppsUpdateBody(rctx) if len(body) == 0 { return output.ErrValidation("provide at least one of --name or --description") } return nil }, DryRun: func(ctx context.Context, rctx *common.RuntimeContext) *common.DryRunAPI { appID := strings.TrimSpace(rctx.Str("app-id")) return common.NewDryRunAPI(). PATCH(fmt.Sprintf("%s/apps/%s", apiBasePath, validate.EncodePathSegment(appID))). Desc("Update a Miaoda app"). Body(buildAppsUpdateBody(rctx)) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { appID := strings.TrimSpace(rctx.Str("app-id")) path := fmt.Sprintf("%s/apps/%s", apiBasePath, validate.EncodePathSegment(appID)) data, err := rctx.CallAPI("PATCH", path, nil, buildAppsUpdateBody(rctx)) if err != nil { return err } rctx.OutFormat(data, nil, func(w io.Writer) { fmt.Fprintf(w, "updated: %s\n", common.GetString(data, "app", "app_id")) }) return nil }, }
AppsUpdate partially updates a Miaoda app's name / description.
Functions ¶
Types ¶
This section is empty.