@pippit-dev/cli 1.0.41 → 1.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -11
- package/checksums.txt +6 -6
- package/cmd/auth/auth.go +19 -5
- package/cmd/auth/auth_test.go +23 -0
- package/cmd/canvas/canvas.go +7 -7
- package/cmd/download_result.go +3 -2
- package/cmd/marketing.go +19 -3
- package/cmd/marketing_test.go +11 -5
- package/cmd/model.go +5 -4
- package/cmd/root.go +17 -5
- package/cmd/root_update_check_test.go +139 -0
- package/cmd/short_drama_test.go +2 -0
- package/cmd/source_test.go +27 -4
- package/cmd/space.go +199 -0
- package/cmd/space_test.go +469 -0
- package/cmd/upload_file_test.go +3 -1
- package/dist/checksums.txt +6 -6
- package/internal/adaptercli/commands_test.go +9 -5
- package/internal/auth/loopback.go +16 -11
- package/internal/auth/loopback_origins_test.go +152 -0
- package/internal/auth/space.go +26 -0
- package/internal/auth/space_test.go +42 -0
- package/internal/canvas/get.go +9 -2
- package/internal/commandnames/names.go +9 -4
- package/internal/common/client.go +23 -0
- package/internal/common/host_source.go +5 -0
- package/internal/common/host_source_test.go +9 -1
- package/internal/common/runner.go +5 -3
- package/internal/common/space.go +185 -0
- package/internal/common/space_test.go +132 -0
- package/internal/common/submit_run.go +2 -0
- package/internal/common/upload_file.go +2 -0
- package/internal/config/config.go +15 -12
- package/internal/generate_image/generate_image.go +3 -9
- package/internal/short_drama/submit_run.go +3 -9
- package/internal/space/session.go +153 -0
- package/internal/space/store.go +211 -0
- package/internal/space/store_test.go +246 -0
- package/internal/version/version.go +13 -0
- package/internal/version/version_test.go +38 -0
- package/internal/versioncheck/check.go +147 -0
- package/internal/versioncheck/check_test.go +204 -0
- package/internal/versioncheck/semver.go +84 -0
- package/package.json +1 -1
- package/scripts/canvas-command.js +47 -5
- package/scripts/install-wizard.js +1 -1
- package/scripts/run.js +15 -4
- package/scripts/telemetry.js +3 -1
- package/scripts/version-check.js +8 -1
- package/skills/short-drama/SKILL.md +12 -1
- package/skills/xyq-marketing-skill/SKILL.md +22 -3
- package/skills/xyq-marketing-skill/references/api.md +7 -1
- package/skills/xyq-marketing-skill/scripts/marketing.js +51 -19
- package/skills/xyq-nest-skill/SKILL.md +15 -4
- package/skills/xyq-nest-skill/commands/canvas-assets.md +1 -1
- package/skills/xyq-nest-skill/commands/canvas.md +5 -3
- package/skills/xyq-nest-skill/commands/get-credit-balance.md +2 -2
- package/skills/xyq-nest-skill/commands/model.md +1 -1
- package/skills/xyq-nest-skill/scripts/ensure-cli.js +8 -1
- package/skills/xyq-nest-skill/scripts/install.md +1 -1
- package/skills/xyq-nest-skill/workflows/canvas-edit.md +2 -2
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"strings"
|
|
10
|
+
"testing"
|
|
11
|
+
"time"
|
|
12
|
+
|
|
13
|
+
"github.com/Pippit-dev/pippit-cli/internal/adapter"
|
|
14
|
+
"github.com/Pippit-dev/pippit-cli/internal/version"
|
|
15
|
+
"github.com/spf13/cobra"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
type noSelfUpdateAdapter struct{ adapter.Default }
|
|
19
|
+
|
|
20
|
+
func (noSelfUpdateAdapter) CLIOptions() adapter.CLIOptions {
|
|
21
|
+
options := adapter.Default{}.CLIOptions()
|
|
22
|
+
options.SelfUpdateEnabled = false
|
|
23
|
+
return options
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func setupRootUpdateCache(t *testing.T) string {
|
|
27
|
+
t.Helper()
|
|
28
|
+
home := t.TempDir()
|
|
29
|
+
t.Setenv("HOME", home)
|
|
30
|
+
t.Setenv("USERPROFILE", home)
|
|
31
|
+
for _, key := range []string{"CI", "PIPPIT_CLI_DISABLE_UPDATE_CHECK", "PIPPIT_CLI_UPDATE_CHECKED"} {
|
|
32
|
+
t.Setenv(key, "")
|
|
33
|
+
}
|
|
34
|
+
previous := version.Version
|
|
35
|
+
version.Version = "1.0.41"
|
|
36
|
+
t.Cleanup(func() { version.Version = previous })
|
|
37
|
+
cacheFile := filepath.Join(home, ".pippit_tool_cli", "version-check.json")
|
|
38
|
+
if err := os.MkdirAll(filepath.Dir(cacheFile), 0700); err != nil {
|
|
39
|
+
t.Fatal(err)
|
|
40
|
+
}
|
|
41
|
+
data := fmt.Sprintf(`{"latest":"1.0.42","checkedAt":%d}`, time.Now().UnixMilli())
|
|
42
|
+
if err := os.WriteFile(cacheFile, []byte(data), 0600); err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
45
|
+
return cacheFile
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func TestNativeBusinessInvocationReportsUpdateBeforeUnchangedJSON(t *testing.T) {
|
|
49
|
+
setupRootUpdateCache(t)
|
|
50
|
+
for _, commandName := range []string{"probe", "login"} {
|
|
51
|
+
for range 2 {
|
|
52
|
+
var stdout, stderr bytes.Buffer
|
|
53
|
+
root := NewRootCommand(&stdout, &stderr)
|
|
54
|
+
probe := &cobra.Command{Use: "probe"}
|
|
55
|
+
if commandName == "login" {
|
|
56
|
+
probe, _, _ = root.Find([]string{"login"})
|
|
57
|
+
} else {
|
|
58
|
+
root.AddCommand(probe)
|
|
59
|
+
}
|
|
60
|
+
probe.RunE = func(*cobra.Command, []string) error {
|
|
61
|
+
if !strings.Contains(stderr.String(), "1.0.41 -> 1.0.42") {
|
|
62
|
+
t.Fatal("business operation started before the synchronous notice")
|
|
63
|
+
}
|
|
64
|
+
_, err := fmt.Fprintln(&stdout, `{"ok":true}`)
|
|
65
|
+
return err
|
|
66
|
+
}
|
|
67
|
+
root.SetArgs([]string{commandName})
|
|
68
|
+
if err := root.Execute(); err != nil {
|
|
69
|
+
t.Fatal(err)
|
|
70
|
+
}
|
|
71
|
+
if !json.Valid(stdout.Bytes()) || stdout.String() != "{\"ok\":true}\n" ||
|
|
72
|
+
strings.Count(stderr.String(), "New version available") != 1 {
|
|
73
|
+
t.Fatalf("stdout/stderr contract changed: %q / %q", stdout.String(), stderr.String())
|
|
74
|
+
}
|
|
75
|
+
if (commandName == "login") != strings.Contains(stderr.String(), "authorization fails") {
|
|
76
|
+
t.Fatal("login-specific notice was omitted or leaked into another command")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func TestNativeUpdateCheckRespectsAdapterAndInvocationBoundaries(t *testing.T) {
|
|
83
|
+
setupRootUpdateCache(t)
|
|
84
|
+
for _, scenario := range []string{"help", "version", "empty", "invalid", "invalid flag", "update", "adapter", "dev", "CI", "PIPPIT_CLI_DISABLE_UPDATE_CHECK", "PIPPIT_CLI_UPDATE_CHECKED"} {
|
|
85
|
+
t.Run(scenario, func(t *testing.T) {
|
|
86
|
+
policy := adapter.Adapter(adapter.Default{})
|
|
87
|
+
args := []string{"probe"}
|
|
88
|
+
switch scenario {
|
|
89
|
+
case "help":
|
|
90
|
+
args = []string{"probe", "--help"}
|
|
91
|
+
case "version":
|
|
92
|
+
args = []string{"--version"}
|
|
93
|
+
case "empty":
|
|
94
|
+
args = []string{}
|
|
95
|
+
case "invalid":
|
|
96
|
+
args = []string{"unknown-command"}
|
|
97
|
+
case "invalid flag":
|
|
98
|
+
args = []string{"probe", "--unknown"}
|
|
99
|
+
case "update":
|
|
100
|
+
args = []string{"update"}
|
|
101
|
+
case "adapter":
|
|
102
|
+
policy = noSelfUpdateAdapter{}
|
|
103
|
+
case "dev":
|
|
104
|
+
previous := version.Version
|
|
105
|
+
version.Version = "dev"
|
|
106
|
+
t.Cleanup(func() { version.Version = previous })
|
|
107
|
+
default:
|
|
108
|
+
t.Setenv(scenario, "1")
|
|
109
|
+
}
|
|
110
|
+
var stdout, stderr bytes.Buffer
|
|
111
|
+
root := NewRootCommandWithAdapter(&stdout, &stderr, policy)
|
|
112
|
+
root.AddCommand(&cobra.Command{Use: "probe", Run: func(*cobra.Command, []string) {}})
|
|
113
|
+
if scenario == "update" {
|
|
114
|
+
update, _, _ := root.Find([]string{"update"})
|
|
115
|
+
update.RunE = func(*cobra.Command, []string) error { return nil }
|
|
116
|
+
}
|
|
117
|
+
root.SetArgs(args)
|
|
118
|
+
err := root.Execute()
|
|
119
|
+
if (scenario == "invalid" || scenario == "invalid flag") != (err != nil) {
|
|
120
|
+
t.Fatalf("unexpected command result: %v", err)
|
|
121
|
+
}
|
|
122
|
+
if strings.Contains(stderr.String(), "New version available") {
|
|
123
|
+
t.Fatalf("skipped invocation emitted an update notice: %s", stderr.String())
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func TestConstructingRootHasNoVersionCheckSideEffects(t *testing.T) {
|
|
130
|
+
cacheFile := setupRootUpdateCache(t)
|
|
131
|
+
if err := os.Remove(cacheFile); err != nil {
|
|
132
|
+
t.Fatal(err)
|
|
133
|
+
}
|
|
134
|
+
var stdout, stderr bytes.Buffer
|
|
135
|
+
_ = NewRootCommand(&stdout, &stderr)
|
|
136
|
+
if _, err := os.Stat(cacheFile); !os.IsNotExist(err) || stdout.Len() != 0 || stderr.Len() != 0 {
|
|
137
|
+
t.Fatal("constructing the root queried versions, wrote cache or produced output")
|
|
138
|
+
}
|
|
139
|
+
}
|
package/cmd/short_drama_test.go
CHANGED
|
@@ -14,6 +14,7 @@ import (
|
|
|
14
14
|
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
15
15
|
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
16
16
|
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
17
|
+
"github.com/Pippit-dev/pippit-cli/internal/space"
|
|
17
18
|
"github.com/Pippit-dev/pippit-cli/internal/version"
|
|
18
19
|
"github.com/bytedance/sonic"
|
|
19
20
|
"github.com/spf13/cobra"
|
|
@@ -965,6 +966,7 @@ func newTestRootCommandWithAccessKey(t *testing.T, stdout, stderr io.Writer, bas
|
|
|
965
966
|
client := common.NewHTTPClient(cfg.BaseURL, cfg.HTTPTimeout, common.NewAccessKeyAuthorizer(cfg.AccessKey))
|
|
966
967
|
runner := common.NewRunner(cfg, client)
|
|
967
968
|
runner.Auth = auth.NewManager(cfg)
|
|
969
|
+
runner.SpaceStore = &space.Store{Dir: t.TempDir()}
|
|
968
970
|
return newRootCommand(stdout, stderr, runner)
|
|
969
971
|
}
|
|
970
972
|
|
package/cmd/source_test.go
CHANGED
|
@@ -16,7 +16,7 @@ import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
func TestSkillSubmitSourcePreservesCreativeRequest(t *testing.T) {
|
|
19
|
-
for _, key := range []string{"PIPPIT_CLI_SOURCE", "CODEX_THREAD_ID", "CODEX_SESSION_ID", "CLAUDECODE", "CURSOR_AGENT", "GEMINI_CLI"} {
|
|
19
|
+
for _, key := range []string{"PIPPIT_CLI_SOURCE", "CODEX_THREAD_ID", "CODEX_SESSION_ID", "CLAUDECODE", "CURSOR_AGENT", "GEMINI_CLI", "OPENCODE", "QWEN_CODE", "CLINE_ACTIVE", "COPILOT_CLI", "COPILOT_AGENT_SESSION_ID"} {
|
|
20
20
|
t.Setenv(key, "")
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -130,6 +130,10 @@ func TestSkillSubmitSourcePreservesCreativeRequest(t *testing.T) {
|
|
|
130
130
|
if received["platform"] != "codex" {
|
|
131
131
|
t.Errorf("runtime host not detected: %#v", received["platform"])
|
|
132
132
|
}
|
|
133
|
+
execute("--source", "")
|
|
134
|
+
if received["platform"] != "codex" {
|
|
135
|
+
t.Error("explicit empty must allow runtime fallback")
|
|
136
|
+
}
|
|
133
137
|
t.Setenv("PIPPIT_CLI_SOURCE", " workbuddy ")
|
|
134
138
|
execute()
|
|
135
139
|
if received["platform"] != "workbuddy" {
|
|
@@ -139,11 +143,30 @@ func TestSkillSubmitSourcePreservesCreativeRequest(t *testing.T) {
|
|
|
139
143
|
if received["platform"] != "doubao_office" {
|
|
140
144
|
t.Error("explicit flag must win")
|
|
141
145
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
for _, flags := range [][]string{{"--source", ""}, {"--source", " \t "}, {"--source="}, {"--source=codex", "--source="}} {
|
|
147
|
+
execute(flags...)
|
|
148
|
+
if received["platform"] != "workbuddy" {
|
|
149
|
+
t.Errorf("blank source must allow environment fallback: %v, got %#v", flags, received["platform"])
|
|
150
|
+
}
|
|
145
151
|
}
|
|
146
152
|
})
|
|
153
|
+
for _, host := range []struct{ key, want string }{
|
|
154
|
+
{"OPENCODE", "opencode"}, {"QWEN_CODE", "qwen_code"},
|
|
155
|
+
{"CLINE_ACTIVE", "cline"}, {"COPILOT_CLI", "github_copilot_cli"},
|
|
156
|
+
{"COPILOT_AGENT_SESSION_ID", "github_copilot_cli"},
|
|
157
|
+
} {
|
|
158
|
+
t.Run(host.key, func(t *testing.T) {
|
|
159
|
+
t.Setenv(host.key, "session-must-not-be-reported")
|
|
160
|
+
execute("--source", "")
|
|
161
|
+
if received["platform"] != host.want {
|
|
162
|
+
t.Errorf("platform = %#v, want %q", received["platform"], host.want)
|
|
163
|
+
}
|
|
164
|
+
delete(received, "platform")
|
|
165
|
+
if !reflect.DeepEqual(received, wantBody) || query != wantQuery {
|
|
166
|
+
t.Error("runtime source changed creative request or ROI query")
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
}
|
|
147
170
|
// A fresh invocation without source must not reuse previous attribution.
|
|
148
171
|
execute()
|
|
149
172
|
if _, exists := received["platform"]; exists {
|
package/cmd/space.go
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
|
|
8
|
+
"github.com/Pippit-dev/pippit-cli/internal/commandnames"
|
|
9
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
10
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
11
|
+
"github.com/Pippit-dev/pippit-cli/internal/space"
|
|
12
|
+
"github.com/spf13/cobra"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func prepareCommandSpace(cmd *cobra.Command, runner *common.Runner, stderr io.Writer) error {
|
|
16
|
+
if runner == nil {
|
|
17
|
+
return nil
|
|
18
|
+
}
|
|
19
|
+
teamFlag := cmd.Flags().Lookup("team-id")
|
|
20
|
+
var explicit *string
|
|
21
|
+
if teamFlag != nil && teamFlag.Changed {
|
|
22
|
+
id, err := space.ParseTeamID(teamFlag.Value.String())
|
|
23
|
+
if err != nil {
|
|
24
|
+
return err
|
|
25
|
+
}
|
|
26
|
+
explicit = &id
|
|
27
|
+
}
|
|
28
|
+
top := cmd
|
|
29
|
+
for top.Parent() != nil && top.Parent().Parent() != nil {
|
|
30
|
+
top = top.Parent()
|
|
31
|
+
}
|
|
32
|
+
switch top.Name() {
|
|
33
|
+
case commandnames.Space, commandnames.Login, commandnames.Logout, commandnames.Status, commandnames.Update, commandnames.Completion, commandnames.Help, commandnames.DownloadResult:
|
|
34
|
+
return nil
|
|
35
|
+
}
|
|
36
|
+
raw, _ := cmd.Flags().GetString("space-context")
|
|
37
|
+
store, err := common.SpaceStoreForRunner(cmd.Context(), runner)
|
|
38
|
+
if err != nil {
|
|
39
|
+
if explicit == nil || raw != "" {
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
if stderr != nil {
|
|
43
|
+
fmt.Fprintln(stderr, "本地空间记录不可用,将使用显式 --team-id;本次不保存空间记录。")
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if store == nil && explicit == nil && raw == "" {
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
session := &space.Session{Store: store, Explicit: explicit, Stderr: stderr, Identity: func(ctx context.Context) (string, error) { return common.SpaceAccount(ctx, runner) }}
|
|
50
|
+
if raw != "" {
|
|
51
|
+
session.Expected, err = space.DecodeBinding(raw)
|
|
52
|
+
if err != nil {
|
|
53
|
+
return err
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
ctx := common.WithSpace(cmd.Context(), session)
|
|
57
|
+
cmd.SetContext(ctx)
|
|
58
|
+
// Business requests resolve their space from the final request body.
|
|
59
|
+
return nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type listedSpace struct {
|
|
63
|
+
space.Selection
|
|
64
|
+
CurrentRole *int32 `json:"current_role,omitempty"`
|
|
65
|
+
Current bool `json:"current"`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func loadSpaces(ctx context.Context, runner *common.Runner, store *space.Store) ([]listedSpace, error) {
|
|
69
|
+
if store != nil {
|
|
70
|
+
ctx = common.WithSpace(ctx, &space.Session{Store: store, Identity: func(ctx context.Context) (string, error) { return common.SpaceAccount(ctx, runner) }})
|
|
71
|
+
}
|
|
72
|
+
result := []listedSpace{{Selection: space.Personal()}}
|
|
73
|
+
seenTeams := map[string]bool{"0": true}
|
|
74
|
+
seenCursors := map[string]bool{}
|
|
75
|
+
cursor := "0"
|
|
76
|
+
for {
|
|
77
|
+
if seenCursors[cursor] {
|
|
78
|
+
return nil, fmt.Errorf("团队列表分页游标重复,未保存空间选择")
|
|
79
|
+
}
|
|
80
|
+
seenCursors[cursor] = true
|
|
81
|
+
var response struct {
|
|
82
|
+
Ret string `json:"ret"`
|
|
83
|
+
Errmsg string `json:"errmsg"`
|
|
84
|
+
LogID string `json:"log_id"`
|
|
85
|
+
Data *struct {
|
|
86
|
+
Teams []struct {
|
|
87
|
+
TeamID string `json:"team_id"`
|
|
88
|
+
Name string `json:"team_name"`
|
|
89
|
+
Role *int32 `json:"current_role"`
|
|
90
|
+
} `json:"team_info_list"`
|
|
91
|
+
NextCursor string `json:"next_cursor"`
|
|
92
|
+
HasMore bool `json:"has_more"`
|
|
93
|
+
} `json:"data"`
|
|
94
|
+
}
|
|
95
|
+
if err := runner.Client.SendRequest(ctx, config.GetLiteTeamListPath, map[string]any{"cursor": cursor, "count": 100}, &response); err != nil {
|
|
96
|
+
return nil, fmt.Errorf("获取团队列表失败,默认空间未改变: %w", err)
|
|
97
|
+
}
|
|
98
|
+
if response.Ret != "0" {
|
|
99
|
+
return nil, common.NewLogIDError("获取团队列表失败: "+response.Errmsg, response.LogID)
|
|
100
|
+
}
|
|
101
|
+
if response.Data == nil {
|
|
102
|
+
return nil, fmt.Errorf("团队列表响应缺少 data")
|
|
103
|
+
}
|
|
104
|
+
for _, team := range response.Data.Teams {
|
|
105
|
+
id, err := space.ParseTeamID(team.TeamID)
|
|
106
|
+
if err != nil || id == "0" {
|
|
107
|
+
return nil, fmt.Errorf("团队列表返回无效 team_id")
|
|
108
|
+
}
|
|
109
|
+
if !seenTeams[id] {
|
|
110
|
+
result = append(result, listedSpace{Selection: space.Selection{TeamID: id, Name: team.Name}, CurrentRole: team.Role})
|
|
111
|
+
seenTeams[id] = true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if !response.Data.HasMore {
|
|
115
|
+
return result, nil
|
|
116
|
+
}
|
|
117
|
+
if response.Data.NextCursor == "" {
|
|
118
|
+
return nil, fmt.Errorf("团队列表未返回下一页游标,未保存空间选择")
|
|
119
|
+
}
|
|
120
|
+
cursor = response.Data.NextCursor
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func newSpaceCommand(stdout io.Writer, runner *common.Runner) *cobra.Command {
|
|
125
|
+
root := &cobra.Command{
|
|
126
|
+
Use: commandnames.Space,
|
|
127
|
+
Short: "查看和切换个人或团队默认空间",
|
|
128
|
+
Args: cobra.NoArgs,
|
|
129
|
+
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
130
|
+
return cmd.Help()
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
list := &cobra.Command{Use: "list", Args: cobra.NoArgs, Short: "列出个人和团队空间,标记当前选择", RunE: func(cmd *cobra.Command, _ []string) error {
|
|
134
|
+
store, err := common.SpaceStoreForRunner(cmd.Context(), runner)
|
|
135
|
+
if err != nil {
|
|
136
|
+
return err
|
|
137
|
+
}
|
|
138
|
+
var selected *space.Selection
|
|
139
|
+
if store != nil {
|
|
140
|
+
current, err := store.Current()
|
|
141
|
+
if err != nil {
|
|
142
|
+
return err
|
|
143
|
+
}
|
|
144
|
+
selected = ¤t
|
|
145
|
+
}
|
|
146
|
+
spaces, err := loadSpaces(cmd.Context(), runner, store)
|
|
147
|
+
if err != nil {
|
|
148
|
+
return err
|
|
149
|
+
}
|
|
150
|
+
for i := range spaces {
|
|
151
|
+
spaces[i].Current = selected != nil && spaces[i].TeamID == selected.TeamID
|
|
152
|
+
}
|
|
153
|
+
return common.WriteJSON(stdout, map[string]any{"spaces": spaces, "current": selected})
|
|
154
|
+
}}
|
|
155
|
+
use := &cobra.Command{Use: "use <TEAM_ID>", Args: cobra.ExactArgs(1), Short: "保存默认空间;0 为个人,正整数为团队", RunE: func(cmd *cobra.Command, args []string) error {
|
|
156
|
+
id, err := space.ParseTeamID(args[0])
|
|
157
|
+
if err != nil {
|
|
158
|
+
return err
|
|
159
|
+
}
|
|
160
|
+
store, err := common.SpaceStoreForRunner(cmd.Context(), runner)
|
|
161
|
+
if err != nil {
|
|
162
|
+
return err
|
|
163
|
+
}
|
|
164
|
+
if store == nil {
|
|
165
|
+
return fmt.Errorf("无法识别当前账号,不能保存默认空间")
|
|
166
|
+
}
|
|
167
|
+
selected := space.Personal()
|
|
168
|
+
if id != "0" {
|
|
169
|
+
spaces, err := loadSpaces(cmd.Context(), runner, store)
|
|
170
|
+
if err != nil {
|
|
171
|
+
return err
|
|
172
|
+
}
|
|
173
|
+
found := false
|
|
174
|
+
for _, item := range spaces {
|
|
175
|
+
if item.TeamID == id {
|
|
176
|
+
selected = item.Selection
|
|
177
|
+
found = true
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if !found {
|
|
182
|
+
return fmt.Errorf("当前账号的团队列表中没有 %s,默认空间未改变", id)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
account, err := common.SpaceAccount(cmd.Context(), runner)
|
|
186
|
+
if err != nil {
|
|
187
|
+
return err
|
|
188
|
+
}
|
|
189
|
+
if account != store.Account {
|
|
190
|
+
return fmt.Errorf("账号已改变,默认空间未保存;请重新选择")
|
|
191
|
+
}
|
|
192
|
+
if err := store.SaveDefault(selected); err != nil {
|
|
193
|
+
return err
|
|
194
|
+
}
|
|
195
|
+
return common.WriteJSON(stdout, selected)
|
|
196
|
+
}}
|
|
197
|
+
root.AddCommand(list, use)
|
|
198
|
+
return root
|
|
199
|
+
}
|