@pippit-dev/cli 1.0.17 → 1.0.20
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 +41 -2
- package/checksums.txt +6 -6
- package/cmd/auth/auth.go +136 -142
- package/cmd/auth/auth_test.go +134 -0
- package/cmd/canvas/canvas.go +241 -0
- package/cmd/canvas/canvas_test.go +182 -0
- package/cmd/get_credit_balance.go +37 -0
- package/cmd/get_credit_balance_test.go +73 -0
- package/cmd/root.go +31 -5
- package/cmd/root_test.go +69 -0
- package/cmd/short_drama_test.go +5 -7
- package/cmd/update/update.go +34 -5
- package/cmd/update/update_test.go +84 -0
- package/dist/checksums.txt +6 -0
- package/dist/xyq-canvas-command-runtime.cjs +16 -0
- package/dist/xyq-canvas-command-runtime.cjs.LEGAL.txt +599 -0
- package/dist/xyq-canvas-command-runtime.cjs.sha256 +2 -0
- package/internal/auth/auth_test.go +813 -0
- package/internal/auth/browser_darwin.go +14 -0
- package/internal/auth/browser_env.go +35 -0
- package/internal/auth/browser_linux.go +14 -0
- package/internal/auth/browser_windows.go +14 -0
- package/internal/auth/identity.go +89 -0
- package/internal/auth/loopback.go +324 -0
- package/internal/auth/manager.go +338 -144
- package/internal/auth/store.go +303 -0
- package/internal/auth/store_file_unix.go +176 -0
- package/internal/auth/store_file_windows.go +11 -0
- package/internal/auth/types.go +72 -0
- package/internal/canvas/allocate.go +70 -0
- package/internal/canvas/apply.go +279 -0
- package/internal/canvas/canvas_test.go +559 -0
- package/internal/canvas/create.go +380 -0
- package/internal/canvas/get.go +156 -0
- package/internal/canvas/types.go +68 -0
- package/internal/canvas/upload.go +250 -0
- package/internal/common/access_key.go +42 -6
- package/internal/common/access_key_test.go +113 -0
- package/internal/common/client.go +119 -21
- package/internal/common/client_test.go +213 -0
- package/internal/common/get_credit_balance.go +61 -0
- package/internal/common/get_credit_balance_test.go +75 -0
- package/internal/common/runner.go +15 -0
- package/internal/config/config.go +11 -28
- package/internal/config/config_test.go +3 -18
- package/package.json +9 -2
- package/scripts/canvas-command.js +881 -0
- package/scripts/run.js +21 -4
- package/skills/short-drama/SKILL.md +4 -4
- package/skills/xyq-nest-skill/SKILL.md +11 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"net/http"
|
|
6
|
+
"net/http/httptest"
|
|
7
|
+
"net/url"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"strings"
|
|
11
|
+
"sync/atomic"
|
|
12
|
+
"testing"
|
|
13
|
+
"time"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
type observedHeaders struct {
|
|
17
|
+
authorization string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func TestHTTPClientScopesCredentialsToBaseOrigin(t *testing.T) {
|
|
21
|
+
apiHeaders := make(chan observedHeaders, 1)
|
|
22
|
+
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
23
|
+
apiHeaders <- captureRoutingHeaders(r)
|
|
24
|
+
_, _ = w.Write([]byte(`{}`))
|
|
25
|
+
}))
|
|
26
|
+
defer api.Close()
|
|
27
|
+
|
|
28
|
+
thirdPartyHeaders := make(chan observedHeaders, 1)
|
|
29
|
+
thirdParty := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
30
|
+
thirdPartyHeaders <- captureRoutingHeaders(r)
|
|
31
|
+
_, _ = w.Write([]byte(`{}`))
|
|
32
|
+
}))
|
|
33
|
+
defer thirdParty.Close()
|
|
34
|
+
|
|
35
|
+
client := NewHTTPClient(
|
|
36
|
+
api.URL,
|
|
37
|
+
time.Second,
|
|
38
|
+
NewAccessKeyAuthorizer("secret-ak"),
|
|
39
|
+
)
|
|
40
|
+
if err := client.SendRequest(context.Background(), "/api/test", nil, nil); err != nil {
|
|
41
|
+
t.Fatalf("same-origin SendRequest() error = %v", err)
|
|
42
|
+
}
|
|
43
|
+
gotAPI := <-apiHeaders
|
|
44
|
+
if gotAPI.authorization != "Bearer secret-ak" {
|
|
45
|
+
t.Fatalf("same-origin Authorization = %q", gotAPI.authorization)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
thirdPartyURL := thirdParty.URL + "/asset.mp4"
|
|
49
|
+
err := client.SendRequestWithHeaders(context.Background(), thirdPartyURL, nil, map[string]string{
|
|
50
|
+
"Authorization": "Bearer caller-supplied",
|
|
51
|
+
}, nil)
|
|
52
|
+
if err != nil {
|
|
53
|
+
t.Fatalf("third-party SendRequest() error = %v", err)
|
|
54
|
+
}
|
|
55
|
+
gotThirdParty := <-thirdPartyHeaders
|
|
56
|
+
if gotThirdParty != (observedHeaders{}) {
|
|
57
|
+
t.Fatalf("third-party sensitive headers leaked: %#v", gotThirdParty)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func TestHTTPClientTreatsSameOriginAbsoluteURLAsAPI(t *testing.T) {
|
|
62
|
+
headers := make(chan observedHeaders, 1)
|
|
63
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
64
|
+
headers <- captureRoutingHeaders(r)
|
|
65
|
+
_, _ = w.Write([]byte(`{}`))
|
|
66
|
+
}))
|
|
67
|
+
defer server.Close()
|
|
68
|
+
|
|
69
|
+
client := NewHTTPClient(
|
|
70
|
+
server.URL+"/base-path",
|
|
71
|
+
time.Second,
|
|
72
|
+
NewAccessKeyAuthorizer("same-origin-ak"),
|
|
73
|
+
)
|
|
74
|
+
if err := client.SendRequest(context.Background(), server.URL+"/api/absolute", nil, nil); err != nil {
|
|
75
|
+
t.Fatalf("SendRequest() error = %v", err)
|
|
76
|
+
}
|
|
77
|
+
got := <-headers
|
|
78
|
+
if got.authorization != "Bearer same-origin-ak" {
|
|
79
|
+
t.Fatalf("same-origin absolute headers = %#v", got)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func TestHTTPClientRejectsAPICrossOriginRedirectBeforeSendingBody(t *testing.T) {
|
|
84
|
+
var thirdPartyRequests atomic.Int32
|
|
85
|
+
thirdParty := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
86
|
+
thirdPartyRequests.Add(1)
|
|
87
|
+
_, _ = w.Write([]byte(`{}`))
|
|
88
|
+
}))
|
|
89
|
+
defer thirdParty.Close()
|
|
90
|
+
|
|
91
|
+
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
92
|
+
http.Redirect(w, r, thirdParty.URL+"/redirected-asset", http.StatusTemporaryRedirect)
|
|
93
|
+
}))
|
|
94
|
+
defer api.Close()
|
|
95
|
+
|
|
96
|
+
client := NewHTTPClient(
|
|
97
|
+
api.URL,
|
|
98
|
+
time.Second,
|
|
99
|
+
NewAccessKeyAuthorizer("redirect-ak"),
|
|
100
|
+
)
|
|
101
|
+
err := client.SendRequest(context.Background(), "/api/redirect", map[string]string{"canvas": "private"}, nil)
|
|
102
|
+
if err == nil || !strings.Contains(err.Error(), "拒绝 Pippit API 跨域重定向") {
|
|
103
|
+
t.Fatalf("SendRequest() error = %v, want cross-origin redirect rejection", err)
|
|
104
|
+
}
|
|
105
|
+
if got := thirdPartyRequests.Load(); got != 0 {
|
|
106
|
+
t.Fatalf("third party received %d API redirect requests, want 0", got)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func TestHTTPClientDoesNotRestoreAuthorizationAfterCrossOriginRedirect(t *testing.T) {
|
|
111
|
+
finalHeaders := make(chan observedHeaders, 1)
|
|
112
|
+
var apiURL string
|
|
113
|
+
thirdParty := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
114
|
+
if got := captureRoutingHeaders(r); got != (observedHeaders{}) {
|
|
115
|
+
t.Errorf("cross-origin hop leaked protected headers: %#v", got)
|
|
116
|
+
}
|
|
117
|
+
http.Redirect(w, r, apiURL+"/final", http.StatusFound)
|
|
118
|
+
}))
|
|
119
|
+
defer thirdParty.Close()
|
|
120
|
+
|
|
121
|
+
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
122
|
+
finalHeaders <- captureRoutingHeaders(r)
|
|
123
|
+
_, _ = w.Write([]byte(`{}`))
|
|
124
|
+
}))
|
|
125
|
+
defer api.Close()
|
|
126
|
+
apiURL = api.URL
|
|
127
|
+
|
|
128
|
+
client := NewHTTPClient(
|
|
129
|
+
api.URL,
|
|
130
|
+
time.Second,
|
|
131
|
+
NewAccessKeyAuthorizer("redirect-bounce-ak"),
|
|
132
|
+
)
|
|
133
|
+
if err := client.SendRequest(context.Background(), thirdParty.URL+"/bounce", nil, nil); err != nil {
|
|
134
|
+
t.Fatalf("SendRequest() error = %v", err)
|
|
135
|
+
}
|
|
136
|
+
if got := <-finalHeaders; got != (observedHeaders{}) {
|
|
137
|
+
t.Fatalf("trusted return hop restored protected headers: %#v", got)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
func TestHTTPClientPreventsCallerAuthorizationOverride(t *testing.T) {
|
|
142
|
+
headers := make(chan observedHeaders, 1)
|
|
143
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
144
|
+
headers <- captureRoutingHeaders(r)
|
|
145
|
+
_, _ = w.Write([]byte(`{}`))
|
|
146
|
+
}))
|
|
147
|
+
defer server.Close()
|
|
148
|
+
|
|
149
|
+
client := NewHTTPClient(
|
|
150
|
+
server.URL,
|
|
151
|
+
time.Second,
|
|
152
|
+
NewAccessKeyAuthorizer("production-ak"),
|
|
153
|
+
)
|
|
154
|
+
err := client.SendRequestWithHeaders(context.Background(), "/api/test", nil, map[string]string{
|
|
155
|
+
"Authorization": "Bearer caller-override",
|
|
156
|
+
}, nil)
|
|
157
|
+
if err != nil {
|
|
158
|
+
t.Fatalf("SendRequest() error = %v", err)
|
|
159
|
+
}
|
|
160
|
+
got := <-headers
|
|
161
|
+
if got.authorization != "Bearer production-ak" {
|
|
162
|
+
t.Fatalf("protected headers = %#v", got)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func TestHTTPClientMultipartRequestInjectsOnlyAuthorization(t *testing.T) {
|
|
167
|
+
headers := make(chan observedHeaders, 1)
|
|
168
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
169
|
+
headers <- captureRoutingHeaders(r)
|
|
170
|
+
_, _ = w.Write([]byte(`{}`))
|
|
171
|
+
}))
|
|
172
|
+
defer server.Close()
|
|
173
|
+
|
|
174
|
+
filePath := filepath.Join(t.TempDir(), "sample.txt")
|
|
175
|
+
if err := os.WriteFile(filePath, []byte("sample"), 0o600); err != nil {
|
|
176
|
+
t.Fatal(err)
|
|
177
|
+
}
|
|
178
|
+
client := NewHTTPClient(
|
|
179
|
+
server.URL,
|
|
180
|
+
time.Second,
|
|
181
|
+
NewAccessKeyAuthorizer("upload-ak"),
|
|
182
|
+
)
|
|
183
|
+
err := client.SendMultipartRequest(context.Background(), "/api/upload", nil, MultipartFile{
|
|
184
|
+
FieldName: "file",
|
|
185
|
+
Path: filePath,
|
|
186
|
+
}, nil)
|
|
187
|
+
if err != nil {
|
|
188
|
+
t.Fatalf("SendMultipartRequest() error = %v", err)
|
|
189
|
+
}
|
|
190
|
+
got := <-headers
|
|
191
|
+
if got.authorization != "Bearer upload-ak" {
|
|
192
|
+
t.Fatalf("multipart headers = %#v", got)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func TestSameOriginUsesEffectiveDefaultPorts(t *testing.T) {
|
|
197
|
+
httpsDefault, _ := url.Parse("https://xyq.jianying.com/api")
|
|
198
|
+
httpsExplicit, _ := url.Parse("https://XYQ.JIANYING.COM:443/asset")
|
|
199
|
+
if !sameOrigin(httpsDefault, httpsExplicit) {
|
|
200
|
+
t.Fatal("sameOrigin() = false for equivalent HTTPS origins")
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
differentPort, _ := url.Parse("https://xyq.jianying.com:8443/api")
|
|
204
|
+
if sameOrigin(httpsDefault, differentPort) {
|
|
205
|
+
t.Fatal("sameOrigin() = true for different ports")
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func captureRoutingHeaders(r *http.Request) observedHeaders {
|
|
210
|
+
return observedHeaders{
|
|
211
|
+
authorization: r.Header.Get("Authorization"),
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
|
|
7
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// GetCreditBalanceResult is the JSON output printed by `pippit-tool-cli get-credit-balance`.
|
|
11
|
+
type GetCreditBalanceResult struct {
|
|
12
|
+
TotalRemainAmount int64 `json:"total_remain_amount,string"`
|
|
13
|
+
LogID string `json:"-"`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type getCreditBalanceResponse struct {
|
|
17
|
+
Ret string `json:"ret"`
|
|
18
|
+
Errmsg string `json:"errmsg"`
|
|
19
|
+
LogID string `json:"log_id"`
|
|
20
|
+
Data *getCreditBalanceResponseData `json:"data"`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type getCreditBalanceResponseData struct {
|
|
24
|
+
TotalRemainAmount *int64 `json:"total_remain_amount,string"`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// GetCreditBalance queries the effective credit balance of the access-key owner.
|
|
28
|
+
func GetCreditBalance(ctx context.Context, runner *Runner) (*GetCreditBalanceResult, error) {
|
|
29
|
+
if runner == nil || runner.Client == nil {
|
|
30
|
+
return nil, fmt.Errorf("get_credit_balance 运行器客户端缺失")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var resp getCreditBalanceResponse
|
|
34
|
+
if err := runner.Client.SendRequest(ctx, getCreditBalancePath(runner), struct{}{}, &resp); err != nil {
|
|
35
|
+
return nil, fmt.Errorf("获取积分余额请求失败: %w", err)
|
|
36
|
+
}
|
|
37
|
+
if resp.Ret != "0" {
|
|
38
|
+
if resp.Errmsg == "" {
|
|
39
|
+
resp.Errmsg = "未知错误"
|
|
40
|
+
}
|
|
41
|
+
return nil, NewLogIDError(fmt.Sprintf("获取积分余额请求返回失败: ret=%s errmsg=%s", resp.Ret, resp.Errmsg), resp.LogID)
|
|
42
|
+
}
|
|
43
|
+
if resp.Data == nil {
|
|
44
|
+
return nil, NewLogIDError("get_credit_balance 响应缺少 data", resp.LogID)
|
|
45
|
+
}
|
|
46
|
+
if resp.Data.TotalRemainAmount == nil {
|
|
47
|
+
return nil, NewLogIDError("get_credit_balance 响应缺少 data.total_remain_amount", resp.LogID)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return &GetCreditBalanceResult{
|
|
51
|
+
TotalRemainAmount: *resp.Data.TotalRemainAmount,
|
|
52
|
+
LogID: resp.LogID,
|
|
53
|
+
}, nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func getCreditBalancePath(runner *Runner) string {
|
|
57
|
+
if runner != nil && runner.Config != nil && runner.Config.Paths != nil && runner.Config.Paths.GetCreditBalance != "" {
|
|
58
|
+
return runner.Config.Paths.GetCreditBalance
|
|
59
|
+
}
|
|
60
|
+
return config.GetCreditBalancePath
|
|
61
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/bytedance/sonic"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
type creditBalanceFakeClient struct {
|
|
13
|
+
response string
|
|
14
|
+
path string
|
|
15
|
+
body any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func (c *creditBalanceFakeClient) SendRequest(_ context.Context, path string, body any, out any) error {
|
|
19
|
+
c.path = path
|
|
20
|
+
c.body = body
|
|
21
|
+
return sonic.Unmarshal([]byte(c.response), out)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func (c *creditBalanceFakeClient) SendRequestWithHeaders(ctx context.Context, path string, body any, _ map[string]string, out any) error {
|
|
25
|
+
return c.SendRequest(ctx, path, body, out)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func (c *creditBalanceFakeClient) SendMultipartRequest(context.Context, string, map[string]string, MultipartFile, any) error {
|
|
29
|
+
return fmt.Errorf("unexpected multipart request")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func TestGetCreditBalanceReturnsAmount(t *testing.T) {
|
|
33
|
+
client := &creditBalanceFakeClient{
|
|
34
|
+
response: `{"ret":"0","log_id":"log_balance","data":{"total_remain_amount":"4321"}}`,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
result, err := GetCreditBalance(context.Background(), &Runner{Client: client})
|
|
38
|
+
if err != nil {
|
|
39
|
+
t.Fatalf("GetCreditBalance() error = %v", err)
|
|
40
|
+
}
|
|
41
|
+
if result.TotalRemainAmount != 4321 {
|
|
42
|
+
t.Fatalf("TotalRemainAmount = %d, want 4321", result.TotalRemainAmount)
|
|
43
|
+
}
|
|
44
|
+
if result.LogID != "log_balance" {
|
|
45
|
+
t.Fatalf("LogID = %q, want log_balance", result.LogID)
|
|
46
|
+
}
|
|
47
|
+
if client.path != "/api/biz/v1/skill/get_credit_balance" {
|
|
48
|
+
t.Fatalf("path = %q, want get_credit_balance path", client.path)
|
|
49
|
+
}
|
|
50
|
+
if _, ok := client.body.(struct{}); !ok {
|
|
51
|
+
t.Fatalf("body = %#v, want an empty POST body", client.body)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func TestGetCreditBalanceRequiresOptionalAmount(t *testing.T) {
|
|
56
|
+
client := &creditBalanceFakeClient{
|
|
57
|
+
response: `{"ret":"0","log_id":"log_missing","data":{}}`,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_, err := GetCreditBalance(context.Background(), &Runner{Client: client})
|
|
61
|
+
if err == nil || !strings.Contains(err.Error(), "data.total_remain_amount") || !strings.Contains(err.Error(), "log_id=log_missing") {
|
|
62
|
+
t.Fatalf("GetCreditBalance() error = %v, want missing amount and log_id", err)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func TestGetCreditBalanceBusinessErrorIncludesLogID(t *testing.T) {
|
|
67
|
+
client := &creditBalanceFakeClient{
|
|
68
|
+
response: `{"ret":"16008","errmsg":"查询失败","log_id":"log_123"}`,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_, err := GetCreditBalance(context.Background(), &Runner{Client: client})
|
|
72
|
+
if err == nil || !strings.Contains(err.Error(), "ret=16008 errmsg=查询失败") || !strings.Contains(err.Error(), "log_id=log_123") {
|
|
73
|
+
t.Fatalf("GetCreditBalance() error = %v, want business error and log_id", err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
package common
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
4
7
|
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
5
8
|
)
|
|
6
9
|
|
|
10
|
+
// AuthManager is the credential boundary shared by native CLI commands. It
|
|
11
|
+
// resolves explicit CI credentials first and browser-managed credentials
|
|
12
|
+
// second, without exposing either value to command output.
|
|
13
|
+
type AuthManager interface {
|
|
14
|
+
ResolveAccessKey(context.Context) (string, error)
|
|
15
|
+
Login(context.Context, auth.LoginOptions) (*auth.Credential, error)
|
|
16
|
+
Status(context.Context) (*auth.Status, error)
|
|
17
|
+
Logout(context.Context, bool) error
|
|
18
|
+
CredentialScope(context.Context) (string, error)
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
// Runner carries runtime dependencies for command execution.
|
|
8
22
|
type Runner struct {
|
|
9
23
|
Config *config.Config
|
|
10
24
|
Client Client
|
|
25
|
+
Auth AuthManager
|
|
11
26
|
}
|
|
12
27
|
|
|
13
28
|
func NewRunner(cfg *config.Config, client Client) *Runner {
|
|
@@ -10,10 +10,9 @@ const (
|
|
|
10
10
|
DefaultBaseURL = "https://xyq.jianying.com"
|
|
11
11
|
DefaultHTTPTimeout = 30 * time.Minute
|
|
12
12
|
DefaultAuthTTL = 30 * time.Second
|
|
13
|
-
DefaultOAuthClientKey = "mock-cli"
|
|
14
|
-
DefaultOAuthBaseURL = "https://passport.bytedance.com"
|
|
15
13
|
DefaultAuthStoreServiceName = "pippit-cli"
|
|
16
14
|
SubmitRunPath = "/api/biz/v1/skill/submit_run"
|
|
15
|
+
GetCreditBalancePath = "/api/biz/v1/skill/get_credit_balance"
|
|
17
16
|
GetThreadPath = "/api/biz/v1/skill/get_thread"
|
|
18
17
|
UploadFilePath = "/api/biz/v1/skill/upload_file"
|
|
19
18
|
ListThreadFilePath = "/api/biz/v1/skill/list_thread_file"
|
|
@@ -27,22 +26,15 @@ type Config struct {
|
|
|
27
26
|
HTTPTimeout time.Duration
|
|
28
27
|
AuthTTL time.Duration
|
|
29
28
|
AccessKey string
|
|
30
|
-
OAuth *OAuth
|
|
31
29
|
Paths *Paths
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
type OAuth struct {
|
|
35
|
-
ClientKey string
|
|
36
|
-
BaseURL string
|
|
37
|
-
StoreServiceName string
|
|
38
|
-
Scopes []string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
32
|
type Paths struct {
|
|
42
|
-
SubmitRun
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
SubmitRun string
|
|
34
|
+
GetCreditBalance string
|
|
35
|
+
GetThread string
|
|
36
|
+
UploadFile string
|
|
37
|
+
ListThreadFile string
|
|
46
38
|
}
|
|
47
39
|
|
|
48
40
|
// Load resolves the built-in runtime config.
|
|
@@ -52,21 +44,12 @@ func Load() *Config {
|
|
|
52
44
|
HTTPTimeout: DefaultHTTPTimeout,
|
|
53
45
|
AuthTTL: DefaultAuthTTL,
|
|
54
46
|
AccessKey: strings.TrimSpace(os.Getenv(EnvXYQAccessKey)),
|
|
55
|
-
OAuth: resolveOAuth(),
|
|
56
47
|
Paths: &Paths{
|
|
57
|
-
SubmitRun:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
SubmitRun: SubmitRunPath,
|
|
49
|
+
GetCreditBalance: GetCreditBalancePath,
|
|
50
|
+
GetThread: GetThreadPath,
|
|
51
|
+
UploadFile: UploadFilePath,
|
|
52
|
+
ListThreadFile: ListThreadFilePath,
|
|
61
53
|
},
|
|
62
54
|
}
|
|
63
55
|
}
|
|
64
|
-
|
|
65
|
-
func resolveOAuth() *OAuth {
|
|
66
|
-
return &OAuth{
|
|
67
|
-
ClientKey: DefaultOAuthClientKey,
|
|
68
|
-
BaseURL: DefaultOAuthBaseURL,
|
|
69
|
-
StoreServiceName: DefaultAuthStoreServiceName,
|
|
70
|
-
Scopes: []string{"user_info", "aigc_generate"},
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -17,27 +17,12 @@ func TestLoadUsesDefaultConfig(t *testing.T) {
|
|
|
17
17
|
if cfg.AccessKey != "" {
|
|
18
18
|
t.Fatalf("AccessKey = %q, want empty", cfg.AccessKey)
|
|
19
19
|
}
|
|
20
|
-
if cfg.OAuth.ClientKey != DefaultOAuthClientKey {
|
|
21
|
-
t.Fatalf("OAuth.ClientKey = %q, want %q", cfg.OAuth.ClientKey, DefaultOAuthClientKey)
|
|
22
|
-
}
|
|
23
|
-
if cfg.OAuth.StoreServiceName != DefaultAuthStoreServiceName {
|
|
24
|
-
t.Fatalf("OAuth.StoreServiceName = %q, want %q", cfg.OAuth.StoreServiceName, DefaultAuthStoreServiceName)
|
|
25
|
-
}
|
|
26
|
-
if cfg.OAuth.BaseURL != DefaultOAuthBaseURL {
|
|
27
|
-
t.Fatalf("OAuth.BaseURL = %q, want %q", cfg.OAuth.BaseURL, DefaultOAuthBaseURL)
|
|
28
|
-
}
|
|
29
|
-
wantScopes := []string{"user_info", "aigc_generate"}
|
|
30
|
-
if len(cfg.OAuth.Scopes) != len(wantScopes) {
|
|
31
|
-
t.Fatalf("OAuth.Scopes = %#v, want %#v", cfg.OAuth.Scopes, wantScopes)
|
|
32
|
-
}
|
|
33
|
-
for i := range wantScopes {
|
|
34
|
-
if cfg.OAuth.Scopes[i] != wantScopes[i] {
|
|
35
|
-
t.Fatalf("OAuth.Scopes = %#v, want %#v", cfg.OAuth.Scopes, wantScopes)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
20
|
if cfg.Paths.SubmitRun != SubmitRunPath {
|
|
39
21
|
t.Fatalf("SubmitRun path = %q, want %q", cfg.Paths.SubmitRun, SubmitRunPath)
|
|
40
22
|
}
|
|
23
|
+
if cfg.Paths.GetCreditBalance != GetCreditBalancePath {
|
|
24
|
+
t.Fatalf("GetCreditBalance path = %q, want %q", cfg.Paths.GetCreditBalance, GetCreditBalancePath)
|
|
25
|
+
}
|
|
41
26
|
}
|
|
42
27
|
|
|
43
28
|
func TestLoadReadsAccessKey(t *testing.T) {
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pippit-dev/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "Pippit CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pippit-tool-cli": "scripts/run.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"postinstall": "node scripts/install.js",
|
|
10
|
-
"
|
|
10
|
+
"prepack": "node scripts/prepare-canvas-runtime.js",
|
|
11
|
+
"prepare:canvas-runtime": "node scripts/prepare-canvas-runtime.js",
|
|
12
|
+
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/install-wizard.test.js && node scripts/canvas-command.test.js && node scripts/prepare-canvas-runtime.test.js && go test ./... && go vet ./...",
|
|
13
|
+
"verify:canvas-runtime": "node scripts/prepare-canvas-runtime.js --check-only"
|
|
11
14
|
},
|
|
12
15
|
"os": [
|
|
13
16
|
"darwin",
|
|
@@ -32,11 +35,15 @@
|
|
|
32
35
|
"skills",
|
|
33
36
|
"scripts/install.js",
|
|
34
37
|
"scripts/install-wizard.js",
|
|
38
|
+
"scripts/canvas-command.js",
|
|
35
39
|
"scripts/platform.js",
|
|
36
40
|
"scripts/run.js",
|
|
37
41
|
"scripts/skills.js",
|
|
38
42
|
"scripts/telemetry.js",
|
|
39
43
|
"scripts/version-check.js",
|
|
44
|
+
"dist/xyq-canvas-command-runtime.cjs",
|
|
45
|
+
"dist/xyq-canvas-command-runtime.cjs.LEGAL.txt",
|
|
46
|
+
"dist/xyq-canvas-command-runtime.cjs.sha256",
|
|
40
47
|
"checksums.txt",
|
|
41
48
|
"README.md",
|
|
42
49
|
"LICENSE"
|