Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ func buildInternalWithConfig(ctx context.Context, inv cmdutil.InvocationContext,
// mechanically unchanged.
var hasConcealedCommands bool
runtime.surface, hasConcealedCommands = applyDistributionPresentation(rootCmd, cfg.presentation, denied)
rootCmd.SetUsageTemplate(rewrittenRootUsageTemplate(runtime.surface))

// Resolve skill assets and canonical references before installing hooks.
// A declared customization is a build-integrity boundary: failure must
Expand Down
12 changes: 6 additions & 6 deletions cmd/doctor/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,24 +241,24 @@
return nil
}

// checkCLIUpdate actively queries the npm registry for the latest version.
// checkCLIUpdate actively queries the configured source for its target version.
// Unlike the root-level async check, this does a synchronous fetch with timeout
// and works regardless of build version (dev builds included).
func checkCLIUpdate() []checkResult {
latest, err := fetchLatestForDoctor()
target, err := fetchLatestForDoctor()
if err != nil {
return []checkResult{warn("cli_update", "check failed: "+err.Error(), "")}
}
current := build.Version
if update.IsNewer(latest, current) {
if target.Available(current) {
return []checkResult{warn("cli_update",
fmt.Sprintf("%s → %s available", current, latest),
fmt.Sprintf("%s → %s available", current, target.Version),
"run: lark-cli update")}
}
return []checkResult{pass("cli_update", latest+" (up to date)")}
return []checkResult{pass("cli_update", target.Version+" (up to date)")}

Check warning on line 258 in cmd/doctor/doctor.go

View check run for this annotation

Codecov / codecov/patch

cmd/doctor/doctor.go#L258

Added line #L258 was not covered by tests
}

var fetchLatestForDoctor = update.FetchLatest
var fetchLatestForDoctor = update.FetchTarget

func finishDoctor(f *cmdutil.Factory, checks []checkResult) error {
allOK := true
Expand Down
45 changes: 43 additions & 2 deletions cmd/doctor/doctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,61 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/spf13/cobra"

extcred "github.com/larksuite/cli/extension/credential"
exttransport "github.com/larksuite/cli/extension/transport"
"github.com/larksuite/cli/internal/build"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/credential"
"github.com/larksuite/cli/internal/distribution"
"github.com/larksuite/cli/internal/recovery"
"github.com/larksuite/cli/internal/surface"
"github.com/larksuite/cli/internal/update"
)

type doctorManifestProvider struct{ manifestURL string }

func (doctorManifestProvider) Name() string { return "doctor-manifest-test" }
func (doctorManifestProvider) ResolveInterceptor(context.Context) exttransport.Interceptor {
return nil
}
func (p doctorManifestProvider) ResolveManifestURL(context.Context) string {
return p.manifestURL
}

func TestCheckCLIUpdateReportsDifferentOpaqueManifestTarget(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = fmt.Fprintf(w, `{"schema":1,"version":"older-channel","artifacts":{"skills":{"url":"https://distribution.example/skills.zip","checksum":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},%q:{"url":"https://distribution.example/cli.zip","checksum":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}}}`, distribution.CurrentPlatformKey())
}))
defer server.Close()
previousProvider := exttransport.GetProvider()
previousFetch := fetchLatestForDoctor
previousClient := distribution.DefaultClient
previousVersion := build.Version
exttransport.Register(doctorManifestProvider{manifestURL: server.URL})
distribution.DefaultClient = server.Client()
fetchLatestForDoctor = update.FetchTarget
build.Version = "newer-channel"
t.Cleanup(func() {
exttransport.Register(previousProvider)
fetchLatestForDoctor = previousFetch
distribution.DefaultClient = previousClient
build.Version = previousVersion
})
checks := checkCLIUpdate()
if len(checks) != 1 || checks[0].Status != "warn" || !strings.Contains(checks[0].Message, "older-channel") {
t.Fatalf("checks = %#v", checks)
}
}

func TestNewCmdDoctor_FlagParsing(t *testing.T) {
f, _, _, _ := cmdutil.TestFactory(t, &core.CliConfig{
AppID: "test-app", AppSecret: "test-secret", Brand: core.BrandFeishu,
Expand Down Expand Up @@ -109,9 +150,9 @@ func TestDoctorRunDoesNotFetchUpdateWhenCommandIsConcealed(t *testing.T) {
t.Cleanup(func() { fetchLatestForDoctor = oldFetch })

fetches := 0
fetchLatestForDoctor = func() (string, error) {
fetchLatestForDoctor = func() (update.Target, error) {
fetches++
return "9.9.9", nil
return update.Target{Version: "9.9.9"}, nil
}
plan := surface.NewPlan(map[surface.CommandID]surface.CommandState{
surface.CommandUpdate: surface.CommandConcealed,
Expand Down
5 changes: 3 additions & 2 deletions cmd/event/console_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

"github.com/larksuite/cli/internal/core"
eventlib "github.com/larksuite/cli/internal/event"
"github.com/larksuite/cli/internal/urlrewrite"
)

// Landing-page contract for the scan-to-enable deep link, verified against the
Expand Down Expand Up @@ -73,13 +74,13 @@
return "", err
}
host := core.ResolveEndpoints(brand).Open
return fmt.Sprintf("%s%s?%s=%s&addons=%s", host, addonsLandingPath, addonsClientIDParam, appID, encoded), nil
return urlrewrite.Rewrite(fmt.Sprintf("%s%s?%s=%s&addons=%s", host, addonsLandingPath, addonsClientIDParam, appID, encoded)), nil
}

// consoleLandingURL is the bare landing page (no addons) — fallback when encoding fails.
func consoleLandingURL(brand core.LarkBrand, appID string) string {
host := core.ResolveEndpoints(brand).Open
return fmt.Sprintf("%s%s?%s=%s", host, addonsLandingPath, addonsClientIDParam, appID)
return urlrewrite.Rewrite(fmt.Sprintf("%s%s?%s=%s", host, addonsLandingPath, addonsClientIDParam, appID))

Check warning on line 83 in cmd/event/console_url.go

View check run for this annotation

Codecov / codecov/patch

cmd/event/console_url.go#L83

Added line #L83 was not covered by tests
}

// addonsHintURL returns the scan URL, degrading to the bare landing page on encode error.
Expand Down
9 changes: 8 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"github.com/larksuite/cli/internal/cmdpolicy"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/deprecation"
"github.com/larksuite/cli/internal/distribution"
"github.com/larksuite/cli/internal/flagalias"
"github.com/larksuite/cli/internal/hook"
"github.com/larksuite/cli/internal/output"
Expand Down Expand Up @@ -142,7 +143,13 @@
var (
checkCachedUpdate = update.CheckCached
refreshUpdateCache = update.RefreshCache
initializeSkillsCheck = skillscheck.Init
initializeSkillsCheck = func(version string) {
sourceIdentity := skillscheck.OfficialSourceIdentity
if manifestURL, enabled, err := distribution.ResolveManifestURL(context.Background()); err == nil && enabled {
sourceIdentity = distribution.ManifestSourceIdentity(manifestURL)

Check warning on line 149 in cmd/root.go

View check run for this annotation

Codecov / codecov/patch

cmd/root.go#L149

Added line #L149 was not covered by tests
}
skillscheck.InitForSource(version, sourceIdentity)
}
)

// setupNotices wires both the binary update notice and the skills
Expand Down
15 changes: 14 additions & 1 deletion cmd/root_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
package cmd

import (
"fmt"
"strings"

"github.com/larksuite/cli/internal/surface"
"github.com/larksuite/cli/internal/urlrewrite"
)

// rootHelpFragment is one framework-owned root-help fragment. A fragment with
Expand Down Expand Up @@ -142,13 +144,24 @@ Skills setup (one-time, humans): npx skills add larksuite/cli -g -y — https://
var rootUsageTemplate = renderRootUsageTemplate(nil)

func renderRootUsageTemplate(plan *surface.Plan) string {
return renderRootUsageTemplateWithSkillsURL(plan, "https://github.com/larksuite/cli#agent-skills")
}

func renderRootUsageTemplateWithSkillsURL(plan *surface.Plan, skillsURL string) string {
var b strings.Builder
b.WriteString(rootUsageTemplatePrefix)
b.WriteString(renderRootHelpFragments(rootUsageSynopsis, plan))
b.WriteString(rootUsageTemplateSuffix)
if plan.CanReference(surface.CommandSkillsRead) {
b.WriteString(skillsSetupFooter)
b.WriteString(fmt.Sprintf(`{{if not .HasParent}}

Skills setup (one-time, humans): npx skills add larksuite/cli -g -y — %s{{end}}`, skillsURL))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
b.WriteByte('\n')
return b.String()
}

func rewrittenRootUsageTemplate(plan *surface.Plan) string {
return renderRootUsageTemplateWithSkillsURL(plan,
urlrewrite.Rewrite("https://github.com/larksuite/cli#agent-skills"))
}
13 changes: 13 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/larksuite/cli/internal/recovery"
"github.com/larksuite/cli/internal/registry"
"github.com/larksuite/cli/internal/surface"
testurlrewrite "github.com/larksuite/cli/internal/testutil/urlrewrite"
)

// TestPersistentPreRunE_AuthCheckDisabledAnnotations verifies that
Expand Down Expand Up @@ -90,6 +92,17 @@ func TestRootLong_AgentSkillsLinkTargetsReadmeSection(t *testing.T) {
}
}

func TestBuildRewritesRootSkillsHelpURLAfterProviderRegistration(t *testing.T) {
testurlrewrite.Register(t, func(rawURL string) string {
return strings.Replace(rawURL, "github.com", "mirror.example.test", 1)
})

_, root, _ := buildInternal(context.Background(), buildInvocationForTest(t), WithoutPlugins())
if got := root.UsageTemplate(); !strings.Contains(got, "https://mirror.example.test/larksuite/cli#agent-skills") {
t.Fatalf("root help URL was not rewritten:\n%s", got)
}
}

func TestConfigureFlagCompletions(t *testing.T) {
t.Cleanup(func() { cmdutil.SetFlagCompletionsEnabled(false) })

Expand Down
82 changes: 82 additions & 0 deletions cmd/update/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package cmdupdate

import (
"context"
"fmt"

"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/internal/distribution"
"github.com/larksuite/cli/internal/output"
)

func runManifestUpdate(ctx context.Context, opts *UpdateOptions, manifestURL string) error {
streams := opts.Factory.IOStreams
current := currentVersion()
manifest, err := distribution.FetchManifest(ctx, manifestURL)
if err != nil {
return reportDistributionError(opts, err)

Check warning on line 20 in cmd/update/manifest.go

View check run for this annotation

Codecov / codecov/patch

cmd/update/manifest.go#L20

Added line #L20 was not covered by tests
}
target := manifest.Version
if opts.Check {
return reportManifestStatus(opts, current, target, true)
}
if !opts.Force && target == current {
return reportManifestStatus(opts, current, target, false)

Check warning on line 27 in cmd/update/manifest.go

View check run for this annotation

Codecov / codecov/patch

cmd/update/manifest.go#L27

Added line #L27 was not covered by tests
}
if !opts.JSON {
fmt.Fprintf(streams.ErrOut, "Updating lark-cli %s %s %s from the configured distribution ...\n", current, symArrow(), target)

Check warning on line 30 in cmd/update/manifest.go

View check run for this annotation

Codecov / codecov/patch

cmd/update/manifest.go#L30

Added line #L30 was not covered by tests
}
if err := distribution.Install(ctx, manifest, distribution.InstallOptions{}); err != nil {
return reportDistributionError(opts, err)
}
if opts.JSON {
output.PrintJson(streams.Out, map[string]interface{}{
"ok": true, "source": "manifest",
"previous_version": current, "current_version": target, "target_version": target,
"action": "updated", "skills_action": "synced",
"message": fmt.Sprintf("lark-cli updated from %s to %s", current, target),
})
return nil

Check warning on line 42 in cmd/update/manifest.go

View check run for this annotation

Codecov / codecov/patch

cmd/update/manifest.go#L35-L42

Added lines #L35 - L42 were not covered by tests
}
fmt.Fprintf(streams.ErrOut, "\n%s Successfully updated lark-cli and Skills from %s to %s\n", symOK(), current, target)
return nil

Check warning on line 45 in cmd/update/manifest.go

View check run for this annotation

Codecov / codecov/patch

cmd/update/manifest.go#L44-L45

Added lines #L44 - L45 were not covered by tests
}

func reportManifestStatus(opts *UpdateOptions, current, target string, check bool) error {
streams := opts.Factory.IOStreams
action := "already_up_to_date"
message := fmt.Sprintf("lark-cli %s matches the configured target", current)
if current != target {
action = "update_available"
message = fmt.Sprintf("lark-cli %s %s configured target %s", current, symArrow(), target)
}
if opts.JSON {
result := map[string]interface{}{
"ok": true, "source": "manifest",
"previous_version": current, "current_version": current, "target_version": target,
"action": action, "message": message,
}
if check {
result["auto_update"] = true
}
output.PrintJson(streams.Out, result)
return nil
}
if current == target {
fmt.Fprintf(streams.ErrOut, "%s %s\n", symOK(), message)
} else {
fmt.Fprintf(streams.ErrOut, "Configured target: %s %s %s\n\nRun `lark-cli update` to install.\n", current, symArrow(), target)

Check warning on line 71 in cmd/update/manifest.go

View check run for this annotation

Codecov / codecov/patch

cmd/update/manifest.go#L68-L71

Added lines #L68 - L71 were not covered by tests
}
return nil

Check warning on line 73 in cmd/update/manifest.go

View check run for this annotation

Codecov / codecov/patch

cmd/update/manifest.go#L73

Added line #L73 was not covered by tests
}

func reportDistributionError(opts *UpdateOptions, typed errs.TypedError) error {
errType := "update_error"
if problem, ok := errs.ProblemOf(typed); ok && problem.Category == errs.CategoryNetwork {
errType = "network"
}
return reportError(opts, opts.Factory.IOStreams, errType, typed)
}
Loading
Loading