@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.
Files changed (50) hide show
  1. package/README.md +41 -2
  2. package/checksums.txt +6 -6
  3. package/cmd/auth/auth.go +136 -142
  4. package/cmd/auth/auth_test.go +134 -0
  5. package/cmd/canvas/canvas.go +241 -0
  6. package/cmd/canvas/canvas_test.go +182 -0
  7. package/cmd/get_credit_balance.go +37 -0
  8. package/cmd/get_credit_balance_test.go +73 -0
  9. package/cmd/root.go +31 -5
  10. package/cmd/root_test.go +69 -0
  11. package/cmd/short_drama_test.go +5 -7
  12. package/cmd/update/update.go +34 -5
  13. package/cmd/update/update_test.go +84 -0
  14. package/dist/checksums.txt +6 -0
  15. package/dist/xyq-canvas-command-runtime.cjs +16 -0
  16. package/dist/xyq-canvas-command-runtime.cjs.LEGAL.txt +599 -0
  17. package/dist/xyq-canvas-command-runtime.cjs.sha256 +2 -0
  18. package/internal/auth/auth_test.go +813 -0
  19. package/internal/auth/browser_darwin.go +14 -0
  20. package/internal/auth/browser_env.go +35 -0
  21. package/internal/auth/browser_linux.go +14 -0
  22. package/internal/auth/browser_windows.go +14 -0
  23. package/internal/auth/identity.go +89 -0
  24. package/internal/auth/loopback.go +324 -0
  25. package/internal/auth/manager.go +338 -144
  26. package/internal/auth/store.go +303 -0
  27. package/internal/auth/store_file_unix.go +176 -0
  28. package/internal/auth/store_file_windows.go +11 -0
  29. package/internal/auth/types.go +72 -0
  30. package/internal/canvas/allocate.go +70 -0
  31. package/internal/canvas/apply.go +279 -0
  32. package/internal/canvas/canvas_test.go +559 -0
  33. package/internal/canvas/create.go +380 -0
  34. package/internal/canvas/get.go +156 -0
  35. package/internal/canvas/types.go +68 -0
  36. package/internal/canvas/upload.go +250 -0
  37. package/internal/common/access_key.go +42 -6
  38. package/internal/common/access_key_test.go +113 -0
  39. package/internal/common/client.go +119 -21
  40. package/internal/common/client_test.go +213 -0
  41. package/internal/common/get_credit_balance.go +61 -0
  42. package/internal/common/get_credit_balance_test.go +75 -0
  43. package/internal/common/runner.go +15 -0
  44. package/internal/config/config.go +11 -28
  45. package/internal/config/config_test.go +3 -18
  46. package/package.json +9 -2
  47. package/scripts/canvas-command.js +881 -0
  48. package/scripts/run.js +21 -4
  49. package/skills/short-drama/SKILL.md +4 -4
  50. package/skills/xyq-nest-skill/SKILL.md +11 -1
@@ -128,6 +128,7 @@ func TestRootHelpListsSupportedCommands(t *testing.T) {
128
128
  "erase-video-subtitle",
129
129
  "generate-video",
130
130
  "download-result",
131
+ "get-credit-balance",
131
132
  "get-thread",
132
133
  "list-thread-file",
133
134
  "query-result",
@@ -140,7 +141,7 @@ func TestRootHelpListsSupportedCommands(t *testing.T) {
140
141
  t.Fatalf("help output = %q, want %q", got, want)
141
142
  }
142
143
  }
143
- for _, unwanted := range []string{"completion", "version "} {
144
+ for _, unwanted := range []string{"completion", "\n version "} {
144
145
  if strings.Contains(got, unwanted) {
145
146
  t.Fatalf("help output = %q, should not contain %q", got, unwanted)
146
147
  }
@@ -971,14 +972,11 @@ func assertAccessKeyGuidance(t *testing.T, err error) {
971
972
  t.Fatal("error = nil, want access key guidance")
972
973
  }
973
974
  msg := err.Error()
974
- if !strings.Contains(msg, "XYQ_ACCESS_KEY 缺失") {
975
+ if !strings.Contains(msg, "pippit-tool-cli login") {
975
976
  t.Fatalf("error = %q, want access key guidance", err)
976
977
  }
977
- if !strings.Contains(msg, "https://xyq.jianying.com/home?tab_name=home") {
978
- t.Fatalf("error = %q, want access key settings URL", err)
979
- }
980
- if !strings.Contains(msg, `export XYQ_ACCESS_KEY="<your-access-key>"`) {
981
- t.Fatalf("error = %q, want setup command guidance", err)
978
+ if !strings.Contains(msg, "XYQ_ACCESS_KEY") {
979
+ t.Fatalf("error = %q, want CI override guidance", err)
982
980
  }
983
981
  }
984
982
 
@@ -14,6 +14,7 @@ import (
14
14
  "sync"
15
15
  "time"
16
16
 
17
+ "github.com/Pippit-dev/pippit-cli/internal/auth"
17
18
  "github.com/Pippit-dev/pippit-cli/internal/version"
18
19
  "github.com/spf13/cobra"
19
20
  )
@@ -219,20 +220,48 @@ func runInherit(stderr io.Writer, name string, args ...string) error {
219
220
 
220
221
  func runInheritEnv(stderr io.Writer, env []string, name string, args ...string) error {
221
222
  cmd := command(name, args...)
222
- if len(env) > 0 {
223
- cmd.Env = append(os.Environ(), env...)
224
- }
223
+ cmd.Env = sanitizedUpdateEnv(os.Environ(), env)
225
224
  cmd.Stdout = stderr
226
225
  cmd.Stderr = stderr
227
226
  return cmd.Run()
228
227
  }
229
228
 
229
+ func sanitizedUpdateEnv(environment, overrides []string) []string {
230
+ merged := overlayEnvironment(environment, overrides)
231
+ return auth.SanitizedBrowserEnv(merged)
232
+ }
233
+
234
+ func overlayEnvironment(environment, overrides []string) []string {
235
+ result := make([]string, 0, len(environment)+len(overrides))
236
+ indexes := make(map[string]int, len(environment)+len(overrides))
237
+ for _, entry := range append(append([]string(nil), environment...), overrides...) {
238
+ name, _, found := strings.Cut(entry, "=")
239
+ if !found || strings.TrimSpace(name) == "" {
240
+ continue
241
+ }
242
+ key := name
243
+ if runtime.GOOS == "windows" {
244
+ key = strings.ToUpper(key)
245
+ }
246
+ if index, exists := indexes[key]; exists {
247
+ result[index] = entry
248
+ continue
249
+ }
250
+ indexes[key] = len(result)
251
+ result = append(result, entry)
252
+ }
253
+ return result
254
+ }
230
255
  func command(name string, args ...string) *exec.Cmd {
256
+ var cmd *exec.Cmd
231
257
  if runtime.GOOS == "windows" {
232
258
  cmdArgs := append([]string{"/c", name}, args...)
233
- return exec.Command("cmd.exe", cmdArgs...)
259
+ cmd = exec.Command("cmd.exe", cmdArgs...)
260
+ } else {
261
+ cmd = exec.Command(name, args...)
234
262
  }
235
- return exec.Command(name, args...)
263
+ cmd.Env = sanitizedUpdateEnv(os.Environ(), nil)
264
+ return cmd
236
265
  }
237
266
 
238
267
  func prepareSelfReplace() (func(), error) {
@@ -171,3 +171,87 @@ func TestStripPrereleaseVersion(t *testing.T) {
171
171
  }
172
172
  }
173
173
  }
174
+
175
+ func TestRunInheritEnvSanitizesPippitCredentials(t *testing.T) {
176
+ if runtime.GOOS == "windows" {
177
+ t.Skip("test uses a POSIX shell script to capture the child environment")
178
+ }
179
+
180
+ binDir := t.TempDir()
181
+ capturePath := filepath.Join(t.TempDir(), "environment.txt")
182
+ commandPath := filepath.Join(binDir, "capture-update-environment")
183
+ script := "#!/bin/sh\n/usr/bin/env > \"$CAPTURE_ENV\"\n"
184
+ if err := os.WriteFile(commandPath, []byte(script), 0o755); err != nil {
185
+ t.Fatal(err)
186
+ }
187
+
188
+ basePath := binDir + string(os.PathListSeparator) + "/base/path"
189
+ overriddenPath := binDir + string(os.PathListSeparator) + "/overridden/path"
190
+ t.Setenv("PATH", basePath)
191
+ t.Setenv("CAPTURE_ENV", capturePath)
192
+ t.Setenv("SAFE_INHERITED", "kept")
193
+ t.Setenv("XYQ_ACCESS_KEY", "xyq-secret")
194
+ t.Setenv("PIPPIT_ACCESS_KEY", "pippit-secret")
195
+ t.Setenv("PIPPIT_AK", "legacy-secret")
196
+ t.Setenv("PIPPIT_CLI_TOKEN", "pippit-token")
197
+ t.Setenv("XYQ_CLIENT_SECRET", "xyq-client-secret")
198
+ t.Setenv("NPM_TOKEN", "npm-secret")
199
+ t.Setenv("NODE_AUTH_TOKEN", "registry-secret")
200
+
201
+ var stderr bytes.Buffer
202
+ err := runInheritEnv(&stderr, []string{
203
+ "PATH=" + overriddenPath,
204
+ "SAFE_INHERITED=overridden",
205
+ "SAFE_EXPLICIT=kept",
206
+ "PIPPIT_CLI_SKIP_SKILLS=1",
207
+ "XYQ_ACCESS_KEY=override-must-not-leak",
208
+ "PIPPIT_OVERRIDE_SECRET=override-must-not-leak",
209
+ "REGISTRY_TOKEN=explicit-registry-secret",
210
+ }, "capture-update-environment")
211
+ if err != nil {
212
+ t.Fatalf("runInheritEnv() error = %v, stderr = %s", err, stderr.String())
213
+ }
214
+
215
+ captured, err := os.ReadFile(capturePath)
216
+ if err != nil {
217
+ t.Fatal(err)
218
+ }
219
+ got := parseEnvironment(string(captured))
220
+ for _, forbidden := range []string{
221
+ "XYQ_ACCESS_KEY",
222
+ "PIPPIT_ACCESS_KEY",
223
+ "PIPPIT_AK",
224
+ "PIPPIT_CLI_TOKEN",
225
+ "XYQ_CLIENT_SECRET",
226
+ "PIPPIT_OVERRIDE_SECRET",
227
+ } {
228
+ if value, exists := got[forbidden]; exists {
229
+ t.Fatalf("child environment retained %s=%q", forbidden, value)
230
+ }
231
+ }
232
+ for name, want := range map[string]string{
233
+ "PATH": overriddenPath,
234
+ "CAPTURE_ENV": capturePath,
235
+ "SAFE_INHERITED": "overridden",
236
+ "SAFE_EXPLICIT": "kept",
237
+ "PIPPIT_CLI_SKIP_SKILLS": "1",
238
+ "NPM_TOKEN": "npm-secret",
239
+ "NODE_AUTH_TOKEN": "registry-secret",
240
+ "REGISTRY_TOKEN": "explicit-registry-secret",
241
+ } {
242
+ if value := got[name]; value != want {
243
+ t.Fatalf("child environment %s = %q, want %q", name, value, want)
244
+ }
245
+ }
246
+ }
247
+
248
+ func parseEnvironment(environment string) map[string]string {
249
+ result := make(map[string]string)
250
+ for _, entry := range strings.Split(environment, "\n") {
251
+ name, value, found := strings.Cut(entry, "=")
252
+ if found {
253
+ result[name] = value
254
+ }
255
+ }
256
+ return result
257
+ }
@@ -0,0 +1,6 @@
1
+ 8bbce765a6e25262d7b65c426f74809cb1b56f38e8509c821cff8a5b06d59ba9 pippit-tool-cli-1.0.20-darwin-amd64.tar.gz
2
+ 0cc1d8f2cf1197dcc2992f19e593279e9bc4fb1c72f25f220a933288ab7c8877 pippit-tool-cli-1.0.20-darwin-arm64.tar.gz
3
+ 38e1fb6590cbe62876625ffbc027099992db1a0cb42099c4b3b64e1cc5aae5b3 pippit-tool-cli-1.0.20-linux-amd64.tar.gz
4
+ d44a96fa3cadd7da7e9e1279cb49659d2d9ab7da12f8695267deeb4fd08b33bc pippit-tool-cli-1.0.20-linux-arm64.tar.gz
5
+ 53d350752fcacb60667f0a1733b9edc0be4e2b77744e042f90726584493fb506 pippit-tool-cli-1.0.20-windows-amd64.zip
6
+ 2c04a6f002f3aa0005e6cab4eb18dba5d87bc4f1e158b432bf19c658e4f59b1f pippit-tool-cli-1.0.20-windows-arm64.zip