@pippit-dev/cli 0.0.17 → 0.0.18

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 CHANGED
@@ -41,6 +41,11 @@ Only `.doc`, `.docx`, and `.txt` file extensions are supported.
41
41
  `+download-result` downloads the result URL to
42
42
  the `--output-path` file path.
43
43
 
44
+ Short drama command errors are appended to a daily local log file under
45
+ `~/.pippit_tool_cli/logs/yyyy-mm-dd.log`. The path is built with the current
46
+ user home directory and the platform path separator, so it works on macOS,
47
+ Linux, and Windows.
48
+
44
49
  ## HTTP Client
45
50
 
46
51
  Command modules should receive `common.Runner` for service calls. Runtime
package/checksums.txt CHANGED
@@ -1,6 +1,6 @@
1
- dc08d1d25fe9fb2d2ed4dc5c2dec42b999ffe9f42f7dfac723d50f85d9107099 pippit-tool-cli-0.0.17-darwin-amd64.tar.gz
2
- f696c2aec0901638716b5037231fd47916c557b6157c1dcff559c894d7992c5a pippit-tool-cli-0.0.17-darwin-arm64.tar.gz
3
- ee4386da9ba02e5dfd93786b319e42af2a41c1fa01c20151232b87ebc81ef343 pippit-tool-cli-0.0.17-linux-amd64.tar.gz
4
- 795c624e273acb9edb0f4851d767d4f58b947e8dc056a7246a64bbb30b085bc1 pippit-tool-cli-0.0.17-linux-arm64.tar.gz
5
- 0e3eb11d38fd04686317df1185195e12180265b9bd7bbb1de2b68a145d72f339 pippit-tool-cli-0.0.17-windows-amd64.zip
6
- acccf51f98a58f3d887a6e2c9131b8acbbc246b6c518ebe8c26b28ad20ed0a54 pippit-tool-cli-0.0.17-windows-arm64.zip
1
+ 2cd3bb5e214d85004196806643c3b7d0a8ceb141ef3df58d1bbfc7aa9642f6ab pippit-tool-cli-0.0.18-darwin-amd64.tar.gz
2
+ 8fb57bef9d7e81b8bcc3421f2179879b656134c23387104e01a1090022f6e7c9 pippit-tool-cli-0.0.18-darwin-arm64.tar.gz
3
+ bdde278cbee36f2b90602b1b58f7be05790a186b3adbc90b8dd8fd970b48b20c pippit-tool-cli-0.0.18-linux-amd64.tar.gz
4
+ 878c6b0cd41a2cafdd62f99be98968eee05f0d16de0c6b173f84bb0328c1bef6 pippit-tool-cli-0.0.18-linux-arm64.tar.gz
5
+ e4423154ba1a52625f255e11060459844a4c5bcf3aab21af682f3e1adcce82cd pippit-tool-cli-0.0.18-windows-amd64.zip
6
+ cca47eab19bdc7200aabcea2d59a535c63267fe293f993b1013aa049a4be16b4 pippit-tool-cli-0.0.18-windows-arm64.zip
@@ -3,6 +3,8 @@ package short_drama
3
3
  import (
4
4
  "fmt"
5
5
  "io"
6
+ "path/filepath"
7
+ "strconv"
6
8
  "strings"
7
9
 
8
10
  "github.com/Pippit-dev/pippit-cli/internal/common"
@@ -35,7 +37,12 @@ func newShortDramaSubmitRunCommand(stdout, stderr io.Writer, runner *common.Runn
35
37
  Use: "+submit-run",
36
38
  Short: "Submit a Run task for the short drama scene",
37
39
  Args: cobra.NoArgs,
38
- RunE: func(cmd *cobra.Command, _ []string) error {
40
+ RunE: withErrorLog("short-drama +submit-run", func() map[string]string {
41
+ return map[string]string{
42
+ "thread_id": opts.ThreadID,
43
+ "asset_count": strconv.Itoa(len(opts.AssetIDs)),
44
+ }
45
+ }, func(cmd *cobra.Command, _ []string) error {
39
46
  opts.Message = strings.TrimSpace(opts.Message)
40
47
  opts.ThreadID = strings.TrimSpace(opts.ThreadID)
41
48
 
@@ -48,7 +55,7 @@ func newShortDramaSubmitRunCommand(stdout, stderr io.Writer, runner *common.Runn
48
55
  return err
49
56
  }
50
57
  return writeJSON(stdout, result)
51
- },
58
+ }),
52
59
  }
53
60
  cmd.SetOut(stdout)
54
61
  cmd.SetErr(stderr)
@@ -65,7 +72,11 @@ func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Run
65
72
  Use: "+upload-file",
66
73
  Short: "Upload a file for the short drama scene",
67
74
  Args: cobra.NoArgs,
68
- RunE: func(cmd *cobra.Command, _ []string) error {
75
+ RunE: withErrorLog("short-drama +upload-file", func() map[string]string {
76
+ return map[string]string{
77
+ "file_name": fileNameForLog(opts.Path),
78
+ }
79
+ }, func(cmd *cobra.Command, _ []string) error {
69
80
  opts.Path = strings.TrimSpace(opts.Path)
70
81
 
71
82
  if opts.Path == "" {
@@ -77,7 +88,7 @@ func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Run
77
88
  return err
78
89
  }
79
90
  return writeJSON(stdout, result)
80
- },
91
+ }),
81
92
  }
82
93
  cmd.SetOut(stdout)
83
94
  cmd.SetErr(stderr)
@@ -92,7 +103,13 @@ func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common
92
103
  Use: "+download-result",
93
104
  Short: "Download a generated result URL",
94
105
  Args: cobra.NoArgs,
95
- RunE: func(cmd *cobra.Command, _ []string) error {
106
+ RunE: withErrorLog("short-drama +download-result", func() map[string]string {
107
+ return map[string]string{
108
+ "output_path": opts.OutputPath,
109
+ "has_url": strconv.FormatBool(strings.TrimSpace(opts.URL) != ""),
110
+ "workers": strconv.Itoa(opts.Workers),
111
+ }
112
+ }, func(cmd *cobra.Command, _ []string) error {
96
113
  opts.OutputPath = strings.TrimSpace(opts.OutputPath)
97
114
  if opts.OutputPath == "" {
98
115
  return fmt.Errorf("--output-path is required")
@@ -110,7 +127,7 @@ func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common
110
127
  return err
111
128
  }
112
129
  return writeJSON(stdout, result)
113
- },
130
+ }),
114
131
  }
115
132
  cmd.SetOut(stdout)
116
133
  cmd.SetErr(stderr)
@@ -127,7 +144,12 @@ func newShortDramaGetThreadCommand(stdout, stderr io.Writer, runner *common.Runn
127
144
  Use: "+get-thread",
128
145
  Short: "Get a short drama thread detail",
129
146
  Args: cobra.NoArgs,
130
- RunE: func(cmd *cobra.Command, _ []string) error {
147
+ RunE: withErrorLog("short-drama +get-thread", func() map[string]string {
148
+ return map[string]string{
149
+ "thread_id": opts.ThreadID,
150
+ "run_id": opts.RunID,
151
+ }
152
+ }, func(cmd *cobra.Command, _ []string) error {
131
153
  opts.ThreadID = strings.TrimSpace(opts.ThreadID)
132
154
  if opts.ThreadID == "" {
133
155
  return fmt.Errorf("--thread-id is required")
@@ -140,7 +162,7 @@ func newShortDramaGetThreadCommand(stdout, stderr io.Writer, runner *common.Runn
140
162
  }
141
163
  _, err = fmt.Fprintln(stdout, result.ReadableText)
142
164
  return err
143
- },
165
+ }),
144
166
  }
145
167
  cmd.SetOut(stdout)
146
168
  cmd.SetErr(stderr)
@@ -156,7 +178,13 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
156
178
  Use: "+list-thread-file",
157
179
  Short: "List files in a short drama thread",
158
180
  Args: cobra.NoArgs,
159
- RunE: func(cmd *cobra.Command, _ []string) error {
181
+ RunE: withErrorLog("short-drama +list-thread-file", func() map[string]string {
182
+ return map[string]string{
183
+ "thread_id": opts.ThreadID,
184
+ "page_num": strconv.Itoa(opts.PageNum),
185
+ "page_size": strconv.Itoa(opts.PageSize),
186
+ }
187
+ }, func(cmd *cobra.Command, _ []string) error {
160
188
  opts.ThreadID = strings.TrimSpace(opts.ThreadID)
161
189
  if opts.ThreadID == "" {
162
190
  return fmt.Errorf("--thread-id is required")
@@ -173,7 +201,7 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
173
201
  return err
174
202
  }
175
203
  return writeJSON(stdout, result)
176
- },
204
+ }),
177
205
  }
178
206
  cmd.SetOut(stdout)
179
207
  cmd.SetErr(stderr)
@@ -183,6 +211,28 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
183
211
  return cmd
184
212
  }
185
213
 
214
+ func withErrorLog(command string, fields func() map[string]string, run func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
215
+ return func(cmd *cobra.Command, args []string) error {
216
+ err := run(cmd, args)
217
+ if err != nil {
218
+ logFields := map[string]string(nil)
219
+ if fields != nil {
220
+ logFields = fields()
221
+ }
222
+ _ = common.AppendDailyErrorLog(command, err, logFields)
223
+ }
224
+ return err
225
+ }
226
+ }
227
+
228
+ func fileNameForLog(path string) string {
229
+ path = strings.TrimSpace(path)
230
+ if path == "" {
231
+ return ""
232
+ }
233
+ return filepath.Base(path)
234
+ }
235
+
186
236
  func writeJSON(w io.Writer, v any) error {
187
237
  data, err := sonic.Marshal(v)
188
238
  if err != nil {
@@ -9,6 +9,7 @@ import (
9
9
  "path/filepath"
10
10
  "strings"
11
11
  "testing"
12
+ "time"
12
13
 
13
14
  "github.com/Pippit-dev/pippit-cli/internal/common"
14
15
  "github.com/Pippit-dev/pippit-cli/internal/config"
@@ -17,6 +18,19 @@ import (
17
18
  "github.com/spf13/cobra"
18
19
  )
19
20
 
21
+ func TestMain(m *testing.M) {
22
+ home, err := os.MkdirTemp("", "pippit-cli-test-home")
23
+ if err != nil {
24
+ panic(err)
25
+ }
26
+ _ = os.Setenv("HOME", home)
27
+ _ = os.Setenv("USERPROFILE", home)
28
+
29
+ code := m.Run()
30
+ _ = os.RemoveAll(home)
31
+ os.Exit(code)
32
+ }
33
+
20
34
  func TestShortDramaSubmitRun(t *testing.T) {
21
35
  server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22
36
  if r.Method != http.MethodPost {
@@ -597,6 +611,8 @@ func TestShortDramaGetThread(t *testing.T) {
597
611
  }
598
612
 
599
613
  func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
614
+ clearDailyErrorLog(t)
615
+
600
616
  var stdout, stderr bytes.Buffer
601
617
  root := NewRootCommand(&stdout, &stderr)
602
618
  root.SetArgs([]string{"short-drama", "+get-thread"})
@@ -608,6 +624,17 @@ func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
608
624
  if !strings.Contains(err.Error(), "--thread-id is required") {
609
625
  t.Fatalf("error = %q, want thread-id validation", err)
610
626
  }
627
+
628
+ entries := readDailyErrorLog(t)
629
+ if len(entries) != 1 {
630
+ t.Fatalf("log entries = %d, want 1: %#v", len(entries), entries)
631
+ }
632
+ if entries[0]["command"] != "short-drama +get-thread" {
633
+ t.Fatalf("command = %v, want get-thread", entries[0]["command"])
634
+ }
635
+ if entries[0]["error"] != "--thread-id is required" {
636
+ t.Fatalf("error = %v, want thread-id validation", entries[0]["error"])
637
+ }
611
638
  }
612
639
 
613
640
  func TestShortDramaGetThreadRequiresAccessKey(t *testing.T) {
@@ -787,6 +814,41 @@ func decodeJSON(t *testing.T, data []byte) map[string]any {
787
814
  return got
788
815
  }
789
816
 
817
+ func clearDailyErrorLog(t *testing.T) {
818
+ t.Helper()
819
+ if err := os.RemoveAll(filepath.Join(testHomeDir(t), ".pippit_tool_cli", "logs")); err != nil {
820
+ t.Fatalf("RemoveAll logs: %v", err)
821
+ }
822
+ }
823
+
824
+ func readDailyErrorLog(t *testing.T) []map[string]any {
825
+ t.Helper()
826
+ path := filepath.Join(testHomeDir(t), ".pippit_tool_cli", "logs", time.Now().Format("2006-01-02")+".log")
827
+ data, err := os.ReadFile(path)
828
+ if err != nil {
829
+ t.Fatalf("ReadFile(%s): %v", path, err)
830
+ }
831
+ lines := strings.Split(strings.TrimSpace(string(data)), "\n")
832
+ entries := make([]map[string]any, 0, len(lines))
833
+ for _, line := range lines {
834
+ var entry map[string]any
835
+ if err := sonic.Unmarshal([]byte(line), &entry); err != nil {
836
+ t.Fatalf("decode log line: %v\n%s", err, line)
837
+ }
838
+ entries = append(entries, entry)
839
+ }
840
+ return entries
841
+ }
842
+
843
+ func testHomeDir(t *testing.T) string {
844
+ t.Helper()
845
+ home, err := os.UserHomeDir()
846
+ if err != nil {
847
+ t.Fatalf("UserHomeDir(): %v", err)
848
+ }
849
+ return home
850
+ }
851
+
790
852
  func chdirTemp(t *testing.T) string {
791
853
  t.Helper()
792
854
  cwd := t.TempDir()
@@ -0,0 +1,91 @@
1
+ package common
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "time"
8
+
9
+ "github.com/bytedance/sonic"
10
+ )
11
+
12
+ const (
13
+ errorLogDirName = ".pippit_tool_cli"
14
+ errorLogSubdir = "logs"
15
+ )
16
+
17
+ type errorLogEntry struct {
18
+ Time string `json:"time"`
19
+ Command string `json:"command"`
20
+ Fields map[string]string `json:"fields,omitempty"`
21
+ Error string `json:"error"`
22
+ }
23
+
24
+ // AppendDailyErrorLog appends one CLI error record to ~/.pippit_tool_cli/logs/yyyy-mm-dd.log.
25
+ func AppendDailyErrorLog(command string, err error, fields map[string]string) error {
26
+ if err == nil {
27
+ return nil
28
+ }
29
+ now := time.Now()
30
+ path, pathErr := dailyErrorLogPath(now)
31
+ if pathErr != nil {
32
+ return pathErr
33
+ }
34
+ if mkErr := os.MkdirAll(filepath.Dir(path), 0o700); mkErr != nil {
35
+ return mkErr
36
+ }
37
+
38
+ entry := errorLogEntry{
39
+ Time: now.Format(time.RFC3339),
40
+ Command: strings.TrimSpace(command),
41
+ Fields: cleanErrorLogFields(fields),
42
+ Error: err.Error(),
43
+ }
44
+ data, marshalErr := sonic.Marshal(entry)
45
+ if marshalErr != nil {
46
+ return marshalErr
47
+ }
48
+ data = append(data, '\n')
49
+ file, openErr := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
50
+ if openErr != nil {
51
+ return openErr
52
+ }
53
+ defer file.Close()
54
+ _, writeErr := file.Write(data)
55
+ return writeErr
56
+ }
57
+
58
+ func dailyErrorLogPath(now time.Time) (string, error) {
59
+ home, err := os.UserHomeDir()
60
+ if err != nil {
61
+ return "", err
62
+ }
63
+ return filepath.Join(home, errorLogDirName, errorLogSubdir, now.Format("2006-01-02")+".log"), nil
64
+ }
65
+
66
+ func cleanErrorLogFields(fields map[string]string) map[string]string {
67
+ if len(fields) == 0 {
68
+ return nil
69
+ }
70
+ cleaned := make(map[string]string, len(fields))
71
+ for k, v := range fields {
72
+ key := strings.TrimSpace(k)
73
+ value := strings.TrimSpace(v)
74
+ if key == "" || value == "" || isSensitiveLogField(key) {
75
+ continue
76
+ }
77
+ cleaned[key] = value
78
+ }
79
+ if len(cleaned) == 0 {
80
+ return nil
81
+ }
82
+ return cleaned
83
+ }
84
+
85
+ func isSensitiveLogField(key string) bool {
86
+ key = strings.ToLower(key)
87
+ return strings.Contains(key, "access") ||
88
+ strings.Contains(key, "authorization") ||
89
+ strings.Contains(key, "secret") ||
90
+ strings.Contains(key, "token")
91
+ }
@@ -0,0 +1,61 @@
1
+ package common
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ "time"
10
+
11
+ "github.com/bytedance/sonic"
12
+ )
13
+
14
+ func TestAppendDailyErrorLog(t *testing.T) {
15
+ home := t.TempDir()
16
+ t.Setenv("HOME", home)
17
+ t.Setenv("USERPROFILE", home)
18
+
19
+ err := AppendDailyErrorLog("short-drama +get-thread", fmt.Errorf("request failed"), map[string]string{
20
+ "thread_id": "thread_123",
21
+ "access_key": "secret",
22
+ "empty": "",
23
+ })
24
+ if err != nil {
25
+ t.Fatalf("AppendDailyErrorLog(): %v", err)
26
+ }
27
+ if err := AppendDailyErrorLog("short-drama +get-thread", fmt.Errorf("second failure"), nil); err != nil {
28
+ t.Fatalf("AppendDailyErrorLog() second call: %v", err)
29
+ }
30
+
31
+ path := filepath.Join(home, ".pippit_tool_cli", "logs", time.Now().Format("2006-01-02")+".log")
32
+ data, err := os.ReadFile(path)
33
+ if err != nil {
34
+ t.Fatalf("ReadFile(%s): %v", path, err)
35
+ }
36
+ lines := strings.Split(strings.TrimSpace(string(data)), "\n")
37
+ if len(lines) != 2 {
38
+ t.Fatalf("log lines = %d, want 2\n%s", len(lines), string(data))
39
+ }
40
+
41
+ var first map[string]any
42
+ if err := sonic.Unmarshal([]byte(lines[0]), &first); err != nil {
43
+ t.Fatalf("decode first log line: %v\n%s", err, lines[0])
44
+ }
45
+ if first["command"] != "short-drama +get-thread" {
46
+ t.Fatalf("command = %v, want get-thread command", first["command"])
47
+ }
48
+ if first["error"] != "request failed" {
49
+ t.Fatalf("error = %v, want request failed", first["error"])
50
+ }
51
+ fields, ok := first["fields"].(map[string]any)
52
+ if !ok {
53
+ t.Fatalf("fields = %#v, want object", first["fields"])
54
+ }
55
+ if fields["thread_id"] != "thread_123" {
56
+ t.Fatalf("thread_id = %v, want thread_123", fields["thread_id"])
57
+ }
58
+ if _, ok := fields["access_key"]; ok {
59
+ t.Fatalf("access_key should be omitted from log fields: %#v", fields)
60
+ }
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Pippit CLI",
5
5
  "bin": {
6
6
  "pippit-tool-cli": "scripts/run.js"