@pippit-dev/cli 0.0.1 → 0.0.3

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/cmd/auth/auth.go CHANGED
@@ -1,144 +1,144 @@
1
1
  package authcmd
2
2
 
3
- import (
4
- "fmt"
5
- "io"
6
- "strings"
7
- "time"
8
-
9
- "github.com/Pippit-dev/pippit-cli/internal/auth"
10
- "github.com/Pippit-dev/pippit-cli/internal/common"
11
- "github.com/bytedance/sonic"
12
- "github.com/spf13/cobra"
13
- )
14
-
15
- type checkResult struct {
16
- Pending bool `json:"pending"`
17
- State any `json:"state,omitempty"`
18
- }
19
-
20
- func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
21
- cmd := &cobra.Command{
22
- Use: "auth",
23
- Short: "Manage Pippit OAuth login state",
24
- }
25
- cmd.SetOut(stdout)
26
- cmd.SetErr(stderr)
27
- cmd.AddCommand(newLoginCommand(stdout, stderr, runner))
28
- cmd.AddCommand(newCheckCommand(stdout, stderr, runner))
29
- cmd.AddCommand(newStatusCommand(stdout, stderr, runner))
30
- cmd.AddCommand(newLogoutCommand(stdout, stderr, runner))
31
- return cmd
32
- }
33
-
34
- func newLoginCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
35
- cmd := &cobra.Command{
36
- Use: "login",
37
- Short: "Start an OAuth device login flow",
38
- Args: cobra.NoArgs,
39
- RunE: func(cmd *cobra.Command, _ []string) error {
40
- if runner == nil || runner.AuthAuthorizer == nil {
41
- return fmt.Errorf("auth manager is required")
42
- }
43
- flow, err := runner.AuthAuthorizer.NewLoginFlow(cmd.Context())
44
- if err != nil {
45
- return err
46
- }
47
- return writeJSON(stdout, flow)
48
- },
49
- }
50
- cmd.SetOut(stdout)
51
- cmd.SetErr(stderr)
52
- return cmd
53
- }
54
-
55
- func newCheckCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
56
- var deviceCode string
57
- cmd := &cobra.Command{
58
- Use: "check",
59
- Short: "Check whether an OAuth device login has completed",
60
- Args: cobra.NoArgs,
61
- RunE: func(cmd *cobra.Command, _ []string) error {
62
- deviceCode = strings.TrimSpace(deviceCode)
63
- if deviceCode == "" {
64
- return fmt.Errorf("--device-code is required")
65
- }
66
- if runner == nil || runner.AuthAuthorizer == nil {
67
- return fmt.Errorf("auth manager is required")
68
- }
69
- state, err := runner.AuthAuthorizer.CheckLogin(cmd.Context(), deviceCode)
70
- if auth.IsLoginPending(err) {
71
- return writeJSON(stdout, checkResult{Pending: true})
72
- }
73
- if err != nil {
74
- return err
75
- }
76
- v := map[string]any{
77
- "logged_in": state.LoggedIn,
78
- "expires_at": state.ExpiresAt.Format(time.RFC3339),
79
- }
80
- return writeJSON(stdout, checkResult{State: v})
81
- },
82
- }
83
- cmd.SetOut(stdout)
84
- cmd.SetErr(stderr)
85
- cmd.Flags().StringVar(&deviceCode, "device-code", "", "device code returned by auth login")
86
- return cmd
87
- }
88
-
89
- func newStatusCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
90
- cmd := &cobra.Command{
91
- Use: "status",
92
- Short: "Show current OAuth login state",
93
- Args: cobra.NoArgs,
94
- RunE: func(cmd *cobra.Command, _ []string) error {
95
- if runner == nil || runner.AuthAuthorizer == nil {
96
- return fmt.Errorf("auth manager is required")
97
- }
98
- state, err := runner.AuthAuthorizer.State(cmd.Context())
99
- if err != nil {
100
- return err
101
- }
102
- if !state.LoggedIn {
103
- return fmt.Errorf("not logged in")
104
- }
105
- v := map[string]any{
106
- "logged_in": state.LoggedIn,
107
- "expires_at": state.ExpiresAt.Format(time.RFC3339),
108
- }
109
- return writeJSON(stdout, v)
110
- },
111
- }
112
- cmd.SetOut(stdout)
113
- cmd.SetErr(stderr)
114
- return cmd
115
- }
116
-
117
- func newLogoutCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
118
- cmd := &cobra.Command{
119
- Use: "logout",
120
- Short: "Clear local OAuth login state",
121
- Args: cobra.NoArgs,
122
- RunE: func(cmd *cobra.Command, _ []string) error {
123
- if runner == nil || runner.AuthAuthorizer == nil {
124
- return fmt.Errorf("auth manager is required")
125
- }
126
- if err := runner.AuthAuthorizer.Logout(cmd.Context()); err != nil {
127
- return err
128
- }
129
- return writeJSON(stdout, map[string]bool{"logged_out": true})
130
- },
131
- }
132
- cmd.SetOut(stdout)
133
- cmd.SetErr(stderr)
134
- return cmd
135
- }
136
-
137
- func writeJSON(w io.Writer, v any) error {
138
- data, err := sonic.Marshal(v)
139
- if err != nil {
140
- return err
141
- }
142
- _, err = fmt.Fprintln(w, string(data))
143
- return err
144
- }
3
+ //import (
4
+ // "fmt"
5
+ // "io"
6
+ // "strings"
7
+ // "time"
8
+ //
9
+ // "github.com/Pippit-dev/pippit-cli/internal/auth"
10
+ // "github.com/Pippit-dev/pippit-cli/internal/common"
11
+ // "github.com/bytedance/sonic"
12
+ // "github.com/spf13/cobra"
13
+ //)
14
+ //
15
+ //type checkResult struct {
16
+ // Pending bool `json:"pending"`
17
+ // State any `json:"state,omitempty"`
18
+ //}
19
+ //
20
+ //func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
21
+ // cmd := &cobra.Command{
22
+ // Use: "auth",
23
+ // Short: "Manage Pippit OAuth login state",
24
+ // }
25
+ // cmd.SetOut(stdout)
26
+ // cmd.SetErr(stderr)
27
+ // cmd.AddCommand(newLoginCommand(stdout, stderr, runner))
28
+ // cmd.AddCommand(newCheckCommand(stdout, stderr, runner))
29
+ // cmd.AddCommand(newStatusCommand(stdout, stderr, runner))
30
+ // cmd.AddCommand(newLogoutCommand(stdout, stderr, runner))
31
+ // return cmd
32
+ //}
33
+ //
34
+ //func newLoginCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
35
+ // cmd := &cobra.Command{
36
+ // Use: "login",
37
+ // Short: "Start an OAuth device login flow",
38
+ // Args: cobra.NoArgs,
39
+ // RunE: func(cmd *cobra.Command, _ []string) error {
40
+ // if runner == nil || runner.AuthAuthorizer == nil {
41
+ // return fmt.Errorf("auth manager is required")
42
+ // }
43
+ // flow, err := runner.AuthAuthorizer.NewLoginFlow(cmd.Context())
44
+ // if err != nil {
45
+ // return err
46
+ // }
47
+ // return writeJSON(stdout, flow)
48
+ // },
49
+ // }
50
+ // cmd.SetOut(stdout)
51
+ // cmd.SetErr(stderr)
52
+ // return cmd
53
+ //}
54
+ //
55
+ //func newCheckCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
56
+ // var deviceCode string
57
+ // cmd := &cobra.Command{
58
+ // Use: "check",
59
+ // Short: "Check whether an OAuth device login has completed",
60
+ // Args: cobra.NoArgs,
61
+ // RunE: func(cmd *cobra.Command, _ []string) error {
62
+ // deviceCode = strings.TrimSpace(deviceCode)
63
+ // if deviceCode == "" {
64
+ // return fmt.Errorf("--device-code is required")
65
+ // }
66
+ // if runner == nil || runner.AuthAuthorizer == nil {
67
+ // return fmt.Errorf("auth manager is required")
68
+ // }
69
+ // state, err := runner.AuthAuthorizer.CheckLogin(cmd.Context(), deviceCode)
70
+ // if auth.IsLoginPending(err) {
71
+ // return writeJSON(stdout, checkResult{Pending: true})
72
+ // }
73
+ // if err != nil {
74
+ // return err
75
+ // }
76
+ // v := map[string]any{
77
+ // "logged_in": state.LoggedIn,
78
+ // "expires_at": state.ExpiresAt.Format(time.RFC3339),
79
+ // }
80
+ // return writeJSON(stdout, checkResult{State: v})
81
+ // },
82
+ // }
83
+ // cmd.SetOut(stdout)
84
+ // cmd.SetErr(stderr)
85
+ // cmd.Flags().StringVar(&deviceCode, "device-code", "", "device code returned by auth login")
86
+ // return cmd
87
+ //}
88
+ //
89
+ //func newStatusCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
90
+ // cmd := &cobra.Command{
91
+ // Use: "status",
92
+ // Short: "Show current OAuth login state",
93
+ // Args: cobra.NoArgs,
94
+ // RunE: func(cmd *cobra.Command, _ []string) error {
95
+ // if runner == nil || runner.AuthAuthorizer == nil {
96
+ // return fmt.Errorf("auth manager is required")
97
+ // }
98
+ // state, err := runner.AuthAuthorizer.State(cmd.Context())
99
+ // if err != nil {
100
+ // return err
101
+ // }
102
+ // if !state.LoggedIn {
103
+ // return fmt.Errorf("not logged in")
104
+ // }
105
+ // v := map[string]any{
106
+ // "logged_in": state.LoggedIn,
107
+ // "expires_at": state.ExpiresAt.Format(time.RFC3339),
108
+ // }
109
+ // return writeJSON(stdout, v)
110
+ // },
111
+ // }
112
+ // cmd.SetOut(stdout)
113
+ // cmd.SetErr(stderr)
114
+ // return cmd
115
+ //}
116
+ //
117
+ //func newLogoutCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
118
+ // cmd := &cobra.Command{
119
+ // Use: "logout",
120
+ // Short: "Clear local OAuth login state",
121
+ // Args: cobra.NoArgs,
122
+ // RunE: func(cmd *cobra.Command, _ []string) error {
123
+ // if runner == nil || runner.AuthAuthorizer == nil {
124
+ // return fmt.Errorf("auth manager is required")
125
+ // }
126
+ // if err := runner.AuthAuthorizer.Logout(cmd.Context()); err != nil {
127
+ // return err
128
+ // }
129
+ // return writeJSON(stdout, map[string]bool{"logged_out": true})
130
+ // },
131
+ // }
132
+ // cmd.SetOut(stdout)
133
+ // cmd.SetErr(stderr)
134
+ // return cmd
135
+ //}
136
+ //
137
+ //func writeJSON(w io.Writer, v any) error {
138
+ // data, err := sonic.Marshal(v)
139
+ // if err != nil {
140
+ // return err
141
+ // }
142
+ // _, err = fmt.Fprintln(w, string(data))
143
+ // return err
144
+ //}
package/cmd/root.go CHANGED
@@ -7,7 +7,6 @@ import (
7
7
  // authcmd "github.com/Pippit-dev/pippit-cli/cmd/auth"
8
8
  "github.com/Pippit-dev/pippit-cli/cmd/short_drama"
9
9
  updatecmd "github.com/Pippit-dev/pippit-cli/cmd/update"
10
- "github.com/Pippit-dev/pippit-cli/internal/auth"
11
10
  "github.com/Pippit-dev/pippit-cli/internal/common"
12
11
  "github.com/Pippit-dev/pippit-cli/internal/config"
13
12
  "github.com/spf13/cobra"
@@ -20,9 +19,8 @@ func Execute() error {
20
19
 
21
20
  func NewRootCommand(stdout, stderr io.Writer) *cobra.Command {
22
21
  cfg := config.Load()
23
- authManager := auth.NewManager(cfg)
24
22
  client := common.NewHTTPClient(cfg.BaseURL, cfg.HTTPTimeout, common.NewAccessKeyAuthorizer(cfg.AccessKey))
25
- runner := common.NewRunner(cfg, client, authManager)
23
+ runner := common.NewRunner(cfg, client)
26
24
  return newRootCommand(stdout, stderr, runner)
27
25
  }
28
26
 
@@ -10,7 +10,6 @@ import (
10
10
  "strings"
11
11
  "testing"
12
12
 
13
- "github.com/Pippit-dev/pippit-cli/internal/auth"
14
13
  "github.com/Pippit-dev/pippit-cli/internal/common"
15
14
  "github.com/Pippit-dev/pippit-cli/internal/config"
16
15
  "github.com/bytedance/sonic"
@@ -542,9 +541,8 @@ func newTestRootCommandWithAccessKey(t *testing.T, stdout, stderr io.Writer, bas
542
541
  cfg := config.Load()
543
542
  cfg.BaseURL = baseURL
544
543
  cfg.AccessKey = accessKey
545
- authAuthorizer := auth.NewManager(cfg)
546
544
  client := common.NewHTTPClient(cfg.BaseURL, cfg.HTTPTimeout, common.NewAccessKeyAuthorizer(cfg.AccessKey))
547
- runner := common.NewRunner(cfg, client, authAuthorizer)
545
+ runner := common.NewRunner(cfg, client)
548
546
  return newRootCommand(stdout, stderr, runner)
549
547
  }
550
548
 
@@ -1,146 +1,146 @@
1
1
  package auth
2
2
 
3
- import (
4
- "context"
5
- "errors"
6
- "fmt"
7
- "net/http"
8
- "sync"
9
- "time"
10
-
11
- "code.byted.org/passport/auth_client/go/authsdk"
12
-
13
- "github.com/Pippit-dev/pippit-cli/internal/config"
14
- )
15
-
16
- type Authorizer interface {
17
- Refresh(ctx context.Context, ensureTTL time.Duration) error
18
- Inject(ctx context.Context, req *http.Request) error
19
- NewLoginFlow(ctx context.Context) (*LoginFlow, error)
20
- CheckLogin(ctx context.Context, deviceCode string) (*State, error)
21
- State(ctx context.Context) (*State, error)
22
- Logout(ctx context.Context) error
23
- }
24
-
25
- type OAuthManager struct {
26
- cfg *config.Config
27
- mu sync.Mutex
28
- client *authsdk.Client
29
- }
30
-
31
- type LoginFlow struct {
32
- DeviceCode string `json:"device_code"`
33
- UserCode string `json:"user_code"`
34
- VerificationURI string `json:"verification_uri"`
35
- }
36
-
37
- type State struct {
38
- LoggedIn bool `json:"logged_in"`
39
- ExpiresAt time.Time `json:"expires_at,omitempty"`
40
- }
41
-
42
- func NewManager(cfg *config.Config) *OAuthManager {
43
- return &OAuthManager{cfg: cfg}
44
- }
45
-
46
- func (m *OAuthManager) NewLoginFlow(ctx context.Context) (*LoginFlow, error) {
47
- client, err := m.clientInstance()
48
- if err != nil {
49
- return nil, err
50
- }
51
- flow, err := client.Authenticator().NewLoginFlow(ctx)
52
- if err != nil {
53
- return nil, err
54
- }
55
- return &LoginFlow{
56
- DeviceCode: flow.DeviceCode,
57
- UserCode: flow.UserCode,
58
- VerificationURI: flow.VerificationURI,
59
- }, nil
60
- }
61
-
62
- func (m *OAuthManager) CheckLogin(ctx context.Context, deviceCode string) (*State, error) {
63
- client, err := m.clientInstance()
64
- if err != nil {
65
- return nil, err
66
- }
67
- state, err := client.Authenticator().CheckLogin(ctx, deviceCode)
68
- if err != nil {
69
- return nil, err
70
- }
71
- return authState(state), nil
72
- }
73
-
74
- func (m *OAuthManager) State(ctx context.Context) (*State, error) {
75
- client, err := m.clientInstance()
76
- if err != nil {
77
- return nil, err
78
- }
79
- state, err := client.Authorizer().State(ctx)
80
- if err != nil {
81
- return nil, err
82
- }
83
- return authState(state), nil
84
- }
85
-
86
- func (m *OAuthManager) Refresh(ctx context.Context, ensureTTL time.Duration) error {
87
- client, err := m.clientInstance()
88
- if err != nil {
89
- return err
90
- }
91
- _, err = client.Authorizer().Refresh(ctx, ensureTTL)
92
- return err
93
- }
94
-
95
- func (m *OAuthManager) Inject(ctx context.Context, req *http.Request) error {
96
- client, err := m.clientInstance()
97
- if err != nil {
98
- return err
99
- }
100
- return client.Authorizer().Inject(ctx, req)
101
- }
102
-
103
- func (m *OAuthManager) Logout(ctx context.Context) error {
104
- client, err := m.clientInstance()
105
- if err != nil {
106
- return err
107
- }
108
- return client.Authorizer().Logout(ctx)
109
- }
110
-
111
- func IsLoginPending(err error) bool {
112
- return errors.Is(err, authsdk.ErrLoginPending)
113
- }
114
-
115
- func (m *OAuthManager) clientInstance() (*authsdk.Client, error) {
116
- m.mu.Lock()
117
- defer m.mu.Unlock()
118
- if m.client != nil {
119
- return m.client, nil
120
- }
121
- if m.cfg == nil || m.cfg.OAuth == nil {
122
- return nil, errors.New("oauth config is required")
123
- }
124
- oauth := m.cfg.OAuth
125
- client, err := authsdk.NewClient(authsdk.Config{
126
- ClientKey: oauth.ClientKey,
127
- BaseURL: oauth.BaseURL,
128
- StoreServiceName: oauth.StoreServiceName,
129
- Scopes: oauth.Scopes,
130
- })
131
- if err != nil {
132
- return nil, fmt.Errorf("initialize oauth client: %w", err)
133
- }
134
- m.client = client
135
- return client, nil
136
- }
137
-
138
- func authState(state *authsdk.AuthStateView) *State {
139
- if state == nil {
140
- return &State{}
141
- }
142
- return &State{
143
- LoggedIn: state.LoggedIn,
144
- ExpiresAt: state.ExpiresAt,
145
- }
146
- }
3
+ //import (
4
+ // "context"
5
+ // "errors"
6
+ // "fmt"
7
+ // "net/http"
8
+ // "sync"
9
+ // "time"
10
+ //
11
+ // "code.byted.org/passport/auth_client/go/authsdk"
12
+ //
13
+ // "github.com/Pippit-dev/pippit-cli/internal/config"
14
+ //)
15
+ //
16
+ //type Authorizer interface {
17
+ // Refresh(ctx context.Context, ensureTTL time.Duration) error
18
+ // Inject(ctx context.Context, req *http.Request) error
19
+ // NewLoginFlow(ctx context.Context) (*LoginFlow, error)
20
+ // CheckLogin(ctx context.Context, deviceCode string) (*State, error)
21
+ // State(ctx context.Context) (*State, error)
22
+ // Logout(ctx context.Context) error
23
+ //}
24
+ //
25
+ //type OAuthManager struct {
26
+ // cfg *config.Config
27
+ // mu sync.Mutex
28
+ // client *authsdk.Client
29
+ //}
30
+ //
31
+ //type LoginFlow struct {
32
+ // DeviceCode string `json:"device_code"`
33
+ // UserCode string `json:"user_code"`
34
+ // VerificationURI string `json:"verification_uri"`
35
+ //}
36
+ //
37
+ //type State struct {
38
+ // LoggedIn bool `json:"logged_in"`
39
+ // ExpiresAt time.Time `json:"expires_at,omitempty"`
40
+ //}
41
+ //
42
+ //func NewManager(cfg *config.Config) *OAuthManager {
43
+ // return &OAuthManager{cfg: cfg}
44
+ //}
45
+ //
46
+ //func (m *OAuthManager) NewLoginFlow(ctx context.Context) (*LoginFlow, error) {
47
+ // client, err := m.clientInstance()
48
+ // if err != nil {
49
+ // return nil, err
50
+ // }
51
+ // flow, err := client.Authenticator().NewLoginFlow(ctx)
52
+ // if err != nil {
53
+ // return nil, err
54
+ // }
55
+ // return &LoginFlow{
56
+ // DeviceCode: flow.DeviceCode,
57
+ // UserCode: flow.UserCode,
58
+ // VerificationURI: flow.VerificationURI,
59
+ // }, nil
60
+ //}
61
+ //
62
+ //func (m *OAuthManager) CheckLogin(ctx context.Context, deviceCode string) (*State, error) {
63
+ // client, err := m.clientInstance()
64
+ // if err != nil {
65
+ // return nil, err
66
+ // }
67
+ // state, err := client.Authenticator().CheckLogin(ctx, deviceCode)
68
+ // if err != nil {
69
+ // return nil, err
70
+ // }
71
+ // return authState(state), nil
72
+ //}
73
+ //
74
+ //func (m *OAuthManager) State(ctx context.Context) (*State, error) {
75
+ // client, err := m.clientInstance()
76
+ // if err != nil {
77
+ // return nil, err
78
+ // }
79
+ // state, err := client.Authorizer().State(ctx)
80
+ // if err != nil {
81
+ // return nil, err
82
+ // }
83
+ // return authState(state), nil
84
+ //}
85
+ //
86
+ //func (m *OAuthManager) Refresh(ctx context.Context, ensureTTL time.Duration) error {
87
+ // client, err := m.clientInstance()
88
+ // if err != nil {
89
+ // return err
90
+ // }
91
+ // _, err = client.Authorizer().Refresh(ctx, ensureTTL)
92
+ // return err
93
+ //}
94
+ //
95
+ //func (m *OAuthManager) Inject(ctx context.Context, req *http.Request) error {
96
+ // client, err := m.clientInstance()
97
+ // if err != nil {
98
+ // return err
99
+ // }
100
+ // return client.Authorizer().Inject(ctx, req)
101
+ //}
102
+ //
103
+ //func (m *OAuthManager) Logout(ctx context.Context) error {
104
+ // client, err := m.clientInstance()
105
+ // if err != nil {
106
+ // return err
107
+ // }
108
+ // return client.Authorizer().Logout(ctx)
109
+ //}
110
+ //
111
+ //func IsLoginPending(err error) bool {
112
+ // return errors.Is(err, authsdk.ErrLoginPending)
113
+ //}
114
+ //
115
+ //func (m *OAuthManager) clientInstance() (*authsdk.Client, error) {
116
+ // m.mu.Lock()
117
+ // defer m.mu.Unlock()
118
+ // if m.client != nil {
119
+ // return m.client, nil
120
+ // }
121
+ // if m.cfg == nil || m.cfg.OAuth == nil {
122
+ // return nil, errors.New("oauth config is required")
123
+ // }
124
+ // oauth := m.cfg.OAuth
125
+ // client, err := authsdk.NewClient(authsdk.Config{
126
+ // ClientKey: oauth.ClientKey,
127
+ // BaseURL: oauth.BaseURL,
128
+ // StoreServiceName: oauth.StoreServiceName,
129
+ // Scopes: oauth.Scopes,
130
+ // })
131
+ // if err != nil {
132
+ // return nil, fmt.Errorf("initialize oauth client: %w", err)
133
+ // }
134
+ // m.client = client
135
+ // return client, nil
136
+ //}
137
+ //
138
+ //func authState(state *authsdk.AuthStateView) *State {
139
+ // if state == nil {
140
+ // return &State{}
141
+ // }
142
+ // return &State{
143
+ // LoggedIn: state.LoggedIn,
144
+ // ExpiresAt: state.ExpiresAt,
145
+ // }
146
+ //}
@@ -21,9 +21,11 @@ func (a *accessKeyAuthorizer) Inject(ctx context.Context, req *http.Request) err
21
21
  if err := ctx.Err(); err != nil {
22
22
  return err
23
23
  }
24
- if a.accessKey == "" {
24
+ if a.accessKey == "" && req.Method == http.MethodPost {
25
25
  return fmt.Errorf("%s is required for authenticated requests", config.EnvXYQAccessKey)
26
26
  }
27
- req.Header.Set("Authorization", "Bearer "+a.accessKey)
27
+ if a.accessKey != "" {
28
+ req.Header.Set("Authorization", "Bearer "+a.accessKey)
29
+ }
28
30
  return nil
29
31
  }
@@ -7,6 +7,7 @@ import (
7
7
  "io"
8
8
  "net/http"
9
9
  "net/url"
10
+ "reflect"
10
11
  "strings"
11
12
  "time"
12
13
 
@@ -40,50 +41,77 @@ func NewHTTPClient(baseURL string, timeout time.Duration, authorizer RequestAuth
40
41
  }
41
42
 
42
43
  func (c *httpClient) SendRequest(ctx context.Context, path string, body any, out any) error {
43
- if c.authorizer == nil {
44
- return fmt.Errorf("authorized request requires authorizer")
45
- }
46
- req, err := c.newJSONRequest(ctx, path, body)
47
- if err != nil {
48
- return err
49
- }
50
- if err := c.authorizer.Inject(ctx, req); err != nil {
51
- return fmt.Errorf("inject auth headers: %w", err)
44
+ method := http.MethodPost
45
+ if body == nil {
46
+ method = http.MethodGet
52
47
  }
53
- return c.do(req, out)
54
- }
55
48
 
56
- func (c *httpClient) newJSONRequest(ctx context.Context, path string, body any) (*http.Request, error) {
57
49
  reqURL, err := c.resolveURL(path, nil)
58
50
  if err != nil {
59
- return nil, err
51
+ return err
60
52
  }
53
+
61
54
  var reader io.Reader
62
55
  if body != nil {
63
56
  payload, err := sonic.Marshal(body)
64
57
  if err != nil {
65
- return nil, fmt.Errorf("encode POST body: %w", err)
58
+ return fmt.Errorf("encode body: %w", err)
66
59
  }
67
60
  reader = bytes.NewReader(payload)
68
61
  }
69
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, reader)
62
+
63
+ req, err := http.NewRequestWithContext(ctx, method, reqURL, reader)
70
64
  if err != nil {
71
- return nil, fmt.Errorf("build POST request: %w", err)
65
+ return fmt.Errorf("build %s request: %w", method, err)
72
66
  }
73
67
  if body != nil {
74
68
  req.Header.Set("Content-Type", "application/json")
75
69
  }
76
- return req, nil
70
+
71
+ if c.authorizer == nil {
72
+ return fmt.Errorf("authorized request requires authorizer")
73
+ }
74
+ if err := c.authorizer.Inject(ctx, req); err != nil {
75
+ return fmt.Errorf("inject auth headers: %w", err)
76
+ }
77
+
78
+ c.injectHeaders(req)
79
+
80
+ // If out is **http.Response, return the raw response for streaming (e.g. file download).
81
+ if out != nil {
82
+ if rv := reflect.ValueOf(out); rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Ptr {
83
+ if rv.Elem().Type().Elem() == reflect.TypeOf(http.Response{}) {
84
+ resp, err := c.httpClient.Do(req)
85
+ if err != nil {
86
+ return fmt.Errorf("%s %s failed: %w", method, reqURL, err)
87
+ }
88
+ if resp.StatusCode >= 400 {
89
+ defer resp.Body.Close()
90
+ return fmt.Errorf("%s %s returned HTTP %d", method, reqURL, resp.StatusCode)
91
+ }
92
+ rv.Elem().Set(reflect.ValueOf(resp))
93
+ return nil
94
+ }
95
+ }
96
+ }
97
+
98
+ req.Header.Set("Accept", "application/json")
99
+
100
+ return c.do(req, out)
77
101
  }
78
102
 
79
- func (c *httpClient) do(req *http.Request, out any) error {
103
+ func (c *httpClient) injectHeaders(req *http.Request) {
80
104
  for k, values := range c.headers {
81
105
  for _, v := range values {
82
106
  req.Header.Add(k, v)
83
107
  }
84
108
  }
85
- req.Header.Set("Accept", "application/json")
109
+ req.Header.Set("User-Agent", "Pippit-CLI/1.0")
110
+ req.Header.Set("x-use-ppe", "1")
111
+ req.Header.Set("x-tt-env", "ppe_harness_novel_v2")
112
+ }
86
113
 
114
+ func (c *httpClient) do(req *http.Request, out any) error {
87
115
  resp, err := c.httpClient.Do(req)
88
116
  if err != nil {
89
117
  return fmt.Errorf("%s %s failed: %w", req.Method, req.URL.String(), err)
@@ -15,6 +15,10 @@ import (
15
15
  "time"
16
16
  )
17
17
 
18
+ // DownloadClient is a minimal interface for downloading files via HTTP GET.
19
+ // It is satisfied by common.Client so that download logic can use the same
20
+ // HTTP infrastructure (headers, auth, timeouts) as API calls.
21
+
18
22
  // DownloadResultOptions is the command-facing shape for downloading one result URL.
19
23
  type DownloadResultOptions struct {
20
24
  URL string `json:"url"`
@@ -51,7 +55,7 @@ type downloadTaskResult struct {
51
55
  err error
52
56
  }
53
57
 
54
- func DownloadResult(ctx context.Context, opts DownloadResultOptions, _ *Runner) (*DownloadResultResponse, error) {
58
+ func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Runner) (*DownloadResultResponse, error) {
55
59
  if err := ctx.Err(); err != nil {
56
60
  return nil, err
57
61
  }
@@ -104,7 +108,6 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, _ *Runner)
104
108
  workers = len(tasks)
105
109
  }
106
110
 
107
- client := &http.Client{Timeout: 600 * time.Second}
108
111
  taskCh := make(chan downloadTask)
109
112
  resultCh := make(chan downloadTaskResult, len(tasks))
110
113
 
@@ -116,7 +119,7 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, _ *Runner)
116
119
  for task := range taskCh {
117
120
  resultCh <- downloadTaskResult{
118
121
  filepath: task.filepath,
119
- err: downloadFileWithRetry(ctx, client, task.url, task.filepath),
122
+ err: downloadFileWithRetry(ctx, runner.Client, task.url, task.filepath),
120
123
  }
121
124
  }
122
125
  }()
@@ -167,7 +170,7 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, _ *Runner)
167
170
  return result, nil
168
171
  }
169
172
 
170
- func downloadFileWithRetry(ctx context.Context, client *http.Client, rawURL string, targetPath string) error {
173
+ func downloadFileWithRetry(ctx context.Context, client Client, rawURL string, targetPath string) error {
171
174
  var lastErr error
172
175
  for attempt := 0; attempt <= maxDownloadRetries; attempt++ {
173
176
  if attempt > 0 {
@@ -203,23 +206,13 @@ func isRetryableError(err error) bool {
203
206
  return false
204
207
  }
205
208
 
206
- func downloadFile(ctx context.Context, client *http.Client, rawURL string, targetPath string) error {
207
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
208
- if err != nil {
209
- return fmt.Errorf("build download request: %w", err)
210
- }
211
- req.Header.Set("User-Agent", "Pippit-CLI/1.0")
212
-
213
- resp, err := client.Do(req)
214
- if err != nil {
209
+ func downloadFile(ctx context.Context, client Client, rawURL string, targetPath string) error {
210
+ var resp *http.Response
211
+ if err := client.SendRequest(ctx, rawURL, nil, &resp); err != nil {
215
212
  return err
216
213
  }
217
214
  defer resp.Body.Close()
218
215
 
219
- if resp.StatusCode >= 400 {
220
- return fmt.Errorf("HTTP %d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
221
- }
222
-
223
216
  tmpPath := targetPath + ".tmp"
224
217
  out, err := os.Create(tmpPath)
225
218
  if err != nil {
@@ -1,21 +1,18 @@
1
1
  package common
2
2
 
3
3
  import (
4
- "github.com/Pippit-dev/pippit-cli/internal/auth"
5
4
  "github.com/Pippit-dev/pippit-cli/internal/config"
6
5
  )
7
6
 
8
7
  // Runner carries runtime dependencies for command execution.
9
8
  type Runner struct {
10
- Config *config.Config
11
- Client Client
12
- AuthAuthorizer auth.Authorizer
9
+ Config *config.Config
10
+ Client Client
13
11
  }
14
12
 
15
- func NewRunner(cfg *config.Config, client Client, authAuthorizer auth.Authorizer) *Runner {
13
+ func NewRunner(cfg *config.Config, client Client) *Runner {
16
14
  return &Runner{
17
- Config: cfg,
18
- Client: client,
19
- AuthAuthorizer: authAuthorizer,
15
+ Config: cfg,
16
+ Client: client,
20
17
  }
21
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Pippit CLI",
5
5
  "bin": {
6
6
  "pippit-cli": "scripts/run.js"