@pippit-dev/cli 0.0.26 → 0.0.28

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/checksums.txt CHANGED
@@ -1,6 +1,6 @@
1
- 852db25f92e31d7791332a67d9fe321e09471ac59a4051a86cd10d120df5504b pippit-tool-cli-0.0.26-darwin-amd64.tar.gz
2
- d6b01e0b0784bc951785a9120adf5b81aeb8fd9e9591a9f5e361d59ad521043d pippit-tool-cli-0.0.26-darwin-arm64.tar.gz
3
- 8cab08ec5fa9fb5c2c933ef986313134b98bfd118c9d5997b6b328d63017143f pippit-tool-cli-0.0.26-linux-amd64.tar.gz
4
- 81f2783696ddfd374507d21d10a2854f8ac3fbb2df32bcb78559f0b9153b41f3 pippit-tool-cli-0.0.26-linux-arm64.tar.gz
5
- b089a0a1f27cc7e27e7905ad32863a90e8f401b914a9133e49476142e51e5824 pippit-tool-cli-0.0.26-windows-amd64.zip
6
- 41df908598017d53b857b475c080793f81956c9706c6d990d875d48432b27395 pippit-tool-cli-0.0.26-windows-arm64.zip
1
+ 91f6452453768a4af724e7f478c1d5834b117dfd57545eb28f79838bcefee72c pippit-tool-cli-0.0.28-darwin-amd64.tar.gz
2
+ 72784be227214d5821793241ec19c0d41227cbcf1bf10b8d8a3675b8a1093219 pippit-tool-cli-0.0.28-darwin-arm64.tar.gz
3
+ 1b7197befef5930b91816a1021a7ab66b9f3d95c0cbd5b0ba5fea20c1a7a1173 pippit-tool-cli-0.0.28-linux-amd64.tar.gz
4
+ 2452ba5f9dc74b516af47e4e4620cfcc247ffe0b2d46a847681910442535f62b pippit-tool-cli-0.0.28-linux-arm64.tar.gz
5
+ bdb744c4e3cc57774c97148665ba99a6952fd4a1293902728e16a655544b20dc pippit-tool-cli-0.0.28-windows-amd64.zip
6
+ 52e660a8b334067fa056021dcf68f1bb13a0385f6792a45b93277463cd583200 pippit-tool-cli-0.0.28-windows-arm64.zip
@@ -1,24 +1,44 @@
1
1
  package updatecmd
2
2
 
3
3
  import (
4
+ "bytes"
5
+ "encoding/json"
4
6
  "fmt"
5
7
  "io"
8
+ "net/http"
6
9
  "os"
7
10
  "os/exec"
8
11
  "path/filepath"
9
12
  "runtime"
10
13
  "strings"
14
+ "sync"
15
+ "time"
11
16
 
17
+ "github.com/Pippit-dev/pippit-cli/internal/version"
12
18
  "github.com/spf13/cobra"
13
19
  )
14
20
 
15
21
  const defaultPackage = "@pippit-dev/cli"
16
22
 
23
+ const (
24
+ defaultTelemetryBaseURL = "https://xyq.jianying.com"
25
+ telemetryPath = "/api/biz/v1/skill/report_telemetry"
26
+ telemetryAuthHeader = "Bearer pippit-cli-skill-telemetry"
27
+ telemetryWaitTimeout = time.Second
28
+ )
29
+
17
30
  var legacyGlobalSkills = []string{
18
31
  "pippit-short-drama-skill",
19
32
  "xyq-nest-skill",
20
33
  }
21
34
 
35
+ var telemetrySkillNames = []string{
36
+ "xyq-skill",
37
+ "xyq-short-drama-skill",
38
+ }
39
+
40
+ var telemetryHTTPClient = &http.Client{Timeout: telemetryWaitTimeout}
41
+
22
42
  // NewCommand builds the update command.
23
43
  func NewCommand(stdout, stderr io.Writer) *cobra.Command {
24
44
  cmd := &cobra.Command{
@@ -57,6 +77,7 @@ func runUpdate(stdout, stderr io.Writer) error {
57
77
  return fmt.Errorf("update pippit-tool-cli skills: %w", err)
58
78
  }
59
79
 
80
+ reportBundledSkillTelemetry("update", "cli_update", stderr)
60
81
  fmt.Fprintln(stdout, "pippit-tool-cli and skills updated")
61
82
  return nil
62
83
  }
@@ -105,6 +126,95 @@ func cleanupLegacyGlobalSkills(globalSkillsDir string) error {
105
126
  return nil
106
127
  }
107
128
 
129
+ type telemetryPayload struct {
130
+ Event string `json:"event"`
131
+ SkillName string `json:"skill_name"`
132
+ Source string `json:"source"`
133
+ CliVersion string `json:"cli_version"`
134
+ Platform string `json:"platform"`
135
+ Arch string `json:"arch"`
136
+ }
137
+
138
+ func reportBundledSkillTelemetry(event string, source string, stderr io.Writer) {
139
+ if os.Getenv("PIPPIT_CLI_DISABLE_TELEMETRY") == "1" {
140
+ return
141
+ }
142
+ var wg sync.WaitGroup
143
+ for _, skillName := range telemetrySkillNames {
144
+ payload := telemetryPayload{
145
+ Event: event,
146
+ SkillName: skillName,
147
+ Source: source,
148
+ CliVersion: telemetryCliVersion(),
149
+ Platform: runtime.GOOS,
150
+ Arch: runtime.GOARCH,
151
+ }
152
+ wg.Add(1)
153
+ go func(payload telemetryPayload) {
154
+ defer wg.Done()
155
+ if err := reportSkillTelemetry(payload); err != nil && os.Getenv("PIPPIT_CLI_DEBUG_TELEMETRY") == "1" {
156
+ fmt.Fprintf(stderr, "[pippit-tool-cli] telemetry failed: %v\n", err)
157
+ }
158
+ }(payload)
159
+ }
160
+ done := make(chan struct{})
161
+ go func() {
162
+ wg.Wait()
163
+ close(done)
164
+ }()
165
+ select {
166
+ case <-done:
167
+ case <-time.After(telemetryWaitTimeout):
168
+ }
169
+ }
170
+
171
+ func reportSkillTelemetry(payload telemetryPayload) error {
172
+ body, err := json.Marshal(payload)
173
+ if err != nil {
174
+ return err
175
+ }
176
+
177
+ req, err := http.NewRequest(http.MethodPost, telemetryBaseURL()+telemetryPath, bytes.NewReader(body))
178
+ if err != nil {
179
+ return err
180
+ }
181
+ req.Header.Set("Content-Type", "application/json")
182
+ req.Header.Set("Authorization", telemetryAuthHeader)
183
+ req.Header.Set("x-use-ppe", "1")
184
+ req.Header.Set("x-tt-env", "ppe_harness_novel_v2")
185
+
186
+ resp, err := telemetryHTTPClient.Do(req)
187
+ if err != nil {
188
+ return err
189
+ }
190
+ defer resp.Body.Close()
191
+ _, _ = io.Copy(io.Discard, resp.Body)
192
+ if resp.StatusCode >= 400 {
193
+ return fmt.Errorf("telemetry returned HTTP %d", resp.StatusCode)
194
+ }
195
+ return nil
196
+ }
197
+
198
+ func telemetryBaseURL() string {
199
+ for _, key := range []string{"PIPPIT_CLI_TELEMETRY_BASE_URL", "XYQ_OPENAPI_BASE", "XYQ_BASE_URL"} {
200
+ if value := strings.TrimRight(strings.TrimSpace(os.Getenv(key)), "/"); value != "" {
201
+ return value
202
+ }
203
+ }
204
+ return defaultTelemetryBaseURL
205
+ }
206
+
207
+ func telemetryCliVersion() string {
208
+ return stripPrereleaseVersion(version.Current())
209
+ }
210
+
211
+ func stripPrereleaseVersion(value string) string {
212
+ if idx := strings.Index(value, "-"); idx >= 0 {
213
+ return value[:idx]
214
+ }
215
+ return value
216
+ }
217
+
108
218
  func runInherit(stderr io.Writer, name string, args ...string) error {
109
219
  return runInheritEnv(stderr, nil, name, args...)
110
220
  }
@@ -2,11 +2,15 @@ package updatecmd
2
2
 
3
3
  import (
4
4
  "bytes"
5
+ "encoding/json"
6
+ "net/http"
7
+ "net/http/httptest"
5
8
  "os"
6
9
  "path/filepath"
7
10
  "runtime"
8
11
  "strings"
9
12
  "testing"
13
+ "time"
10
14
  )
11
15
 
12
16
  func TestInstallSkillsInstallsAllBundledSkills(t *testing.T) {
@@ -70,3 +74,108 @@ func TestCleanupLegacyGlobalSkills(t *testing.T) {
70
74
  }
71
75
  }
72
76
  }
77
+
78
+ func TestReportSkillTelemetry(t *testing.T) {
79
+ var gotAuth string
80
+ var gotHeaders http.Header
81
+ var gotPayload telemetryPayload
82
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83
+ if r.URL.Path != telemetryPath {
84
+ t.Fatalf("path = %q, want %q", r.URL.Path, telemetryPath)
85
+ }
86
+ gotAuth = r.Header.Get("Authorization")
87
+ gotHeaders = r.Header.Clone()
88
+ if err := json.NewDecoder(r.Body).Decode(&gotPayload); err != nil {
89
+ t.Fatalf("decode request body: %v", err)
90
+ }
91
+ _, _ = w.Write([]byte(`{"ret":"0","errmsg":""}`))
92
+ }))
93
+ defer server.Close()
94
+
95
+ t.Setenv("PIPPIT_CLI_TELEMETRY_BASE_URL", server.URL+"/")
96
+ err := reportSkillTelemetry(telemetryPayload{
97
+ Event: "update",
98
+ SkillName: "xyq-skill",
99
+ Source: "cli_update",
100
+ CliVersion: "0.0.26",
101
+ Platform: "darwin",
102
+ Arch: "arm64",
103
+ })
104
+ if err != nil {
105
+ t.Fatalf("reportSkillTelemetry() error = %v", err)
106
+ }
107
+ if gotAuth != telemetryAuthHeader {
108
+ t.Fatalf("Authorization = %q, want %q", gotAuth, telemetryAuthHeader)
109
+ }
110
+ if got := gotHeaders.Get("x-use-ppe"); got != "1" {
111
+ t.Fatalf("x-use-ppe = %q, want 1", got)
112
+ }
113
+ if got := gotHeaders.Get("x-tt-env"); got != "ppe_harness_novel_v2" {
114
+ t.Fatalf("x-tt-env = %q, want ppe_harness_novel_v2", got)
115
+ }
116
+ if gotPayload.Event != "update" || gotPayload.SkillName != "xyq-skill" || gotPayload.Source != "cli_update" {
117
+ t.Fatalf("payload = %#v", gotPayload)
118
+ }
119
+ }
120
+
121
+ func TestReportBundledSkillTelemetryWaitsBriefly(t *testing.T) {
122
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
123
+ time.Sleep(2 * time.Second)
124
+ _, _ = w.Write([]byte(`{"ret":"0","errmsg":""}`))
125
+ }))
126
+ defer server.Close()
127
+
128
+ t.Setenv("PIPPIT_CLI_TELEMETRY_BASE_URL", server.URL)
129
+ start := time.Now()
130
+ reportBundledSkillTelemetry("update", "cli_update", &bytes.Buffer{})
131
+ if elapsed := time.Since(start); elapsed > 1500*time.Millisecond {
132
+ t.Fatalf("reportBundledSkillTelemetry() blocked for %v, want <= 1.5s", elapsed)
133
+ }
134
+ }
135
+
136
+ func TestReportBundledSkillTelemetryReportsBothSkills(t *testing.T) {
137
+ skillNames := make(chan string, len(telemetrySkillNames))
138
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
139
+ var payload telemetryPayload
140
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
141
+ t.Fatalf("decode request body: %v", err)
142
+ }
143
+ skillNames <- payload.SkillName
144
+ _, _ = w.Write([]byte(`{"ret":"0","errmsg":""}`))
145
+ }))
146
+ defer server.Close()
147
+
148
+ t.Setenv("PIPPIT_CLI_TELEMETRY_BASE_URL", server.URL)
149
+ reportBundledSkillTelemetry("update", "cli_update", &bytes.Buffer{})
150
+
151
+ got := map[string]bool{}
152
+ for i := 0; i < len(telemetrySkillNames); i++ {
153
+ got[<-skillNames] = true
154
+ }
155
+ for _, skillName := range telemetrySkillNames {
156
+ if !got[skillName] {
157
+ t.Fatalf("missing telemetry for %s, got %#v", skillName, got)
158
+ }
159
+ }
160
+ }
161
+
162
+ func TestTelemetryBaseURL(t *testing.T) {
163
+ t.Setenv("PIPPIT_CLI_TELEMETRY_BASE_URL", " ")
164
+ t.Setenv("XYQ_OPENAPI_BASE", "https://example.com///")
165
+ if got := telemetryBaseURL(); got != "https://example.com" {
166
+ t.Fatalf("telemetryBaseURL() = %q, want https://example.com", got)
167
+ }
168
+ }
169
+
170
+ func TestStripPrereleaseVersion(t *testing.T) {
171
+ cases := map[string]string{
172
+ "0.0.27": "0.0.27",
173
+ "0.0.27-rc.1": "0.0.27",
174
+ "1.2.3-beta.4": "1.2.3",
175
+ }
176
+ for input, want := range cases {
177
+ if got := stripPrereleaseVersion(input); got != want {
178
+ t.Fatalf("stripPrereleaseVersion(%q) = %q, want %q", input, got, want)
179
+ }
180
+ }
181
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
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
- "test": "node scripts/version-check.test.js && node scripts/skills.test.js && go test ./... && go vet ./..."
10
+ "test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/install-wizard.test.js && go test ./... && go vet ./..."
11
11
  },
12
12
  "os": [
13
13
  "darwin",
@@ -35,6 +35,7 @@
35
35
  "scripts/platform.js",
36
36
  "scripts/run.js",
37
37
  "scripts/skills.js",
38
+ "scripts/telemetry.js",
38
39
  "scripts/version-check.js",
39
40
  "checksums.txt",
40
41
  "README.md",
@@ -4,8 +4,17 @@ const fs = require("fs");
4
4
  const path = require("path");
5
5
  const { isWindows, run, runSilent } = require("./platform");
6
6
  const { DEFAULT_PKG, installGlobalPackageSkills } = require("./skills");
7
+ const { reportBundledSkillTelemetry } = require("./telemetry");
7
8
 
8
- const PKG = process.env.PIPPIT_CLI_INSTALL_PACKAGE || DEFAULT_PKG;
9
+ const VERSION = require("../package.json").version.replace(/-.*$/, "");
10
+
11
+ function defaultInstallPackage() {
12
+ return `${DEFAULT_PKG}@${VERSION}`;
13
+ }
14
+
15
+ function installPackage() {
16
+ return process.env.PIPPIT_CLI_INSTALL_PACKAGE || defaultInstallPackage();
17
+ }
9
18
 
10
19
  function getGloballyInstalledVersion() {
11
20
  try {
@@ -37,13 +46,14 @@ function whichPippitToolCli() {
37
46
  }
38
47
 
39
48
  function main() {
49
+ const pkg = installPackage();
40
50
  const installed = getGloballyInstalledVersion();
41
51
  if (installed) {
42
- console.log(`Updating global pippit-tool-cli (${installed}) via ${PKG}...`);
52
+ console.log(`Updating global pippit-tool-cli (${installed}) via ${pkg}...`);
43
53
  } else {
44
- console.log(`Installing ${PKG} globally...`);
54
+ console.log(`Installing ${pkg} globally...`);
45
55
  }
46
- run("npm", ["install", "-g", PKG], {
56
+ run("npm", ["install", "-g", pkg], {
47
57
  timeout: 120000,
48
58
  env: { ...process.env, PIPPIT_CLI_SKIP_SKILLS: "1" },
49
59
  });
@@ -56,7 +66,7 @@ function main() {
56
66
  throw err;
57
67
  }
58
68
  console.log("Existing global package does not contain skills; reinstalling...");
59
- run("npm", ["install", "-g", PKG], { timeout: 120000 });
69
+ run("npm", ["install", "-g", pkg], { timeout: 120000 });
60
70
  installGlobalPackageSkills(DEFAULT_PKG);
61
71
  }
62
72
 
@@ -68,7 +78,16 @@ function main() {
68
78
  }
69
79
 
70
80
  console.log(`pippit-tool-cli is ready: ${bin}`);
81
+ reportBundledSkillTelemetry("install", "npx_install");
71
82
  console.log("Try: pippit-tool-cli short-drama +submit-run --message \"写一个短剧开头\"");
72
83
  }
73
84
 
74
- main();
85
+ if (require.main === module) {
86
+ main();
87
+ }
88
+
89
+ module.exports = {
90
+ defaultInstallPackage,
91
+ installPackage,
92
+ main,
93
+ };
@@ -6,6 +6,7 @@ const os = require("os");
6
6
  const path = require("path");
7
7
  const { isWindows, run } = require("./platform");
8
8
  const { cleanupLegacyGlobalSkills, installSkillsFromRoot } = require("./skills");
9
+ const { reportBundledSkillTelemetry } = require("./telemetry");
9
10
 
10
11
  const VERSION = require("../package.json").version.replace(/-.*$/, "");
11
12
  const REPO = "Pippit-dev/cli";
@@ -134,6 +135,7 @@ function install() {
134
135
 
135
136
  if (process.env.PIPPIT_CLI_SKIP_SKILLS !== "1") {
136
137
  installSkillsFromRoot(ROOT);
138
+ reportBundledSkillTelemetry("install", "npm_install");
137
139
  } else {
138
140
  cleanupLegacyGlobalSkills();
139
141
  }
package/scripts/run.js CHANGED
@@ -38,7 +38,7 @@ if (process.platform === "win32" && fs.existsSync(oldBin)) {
38
38
  // Match the lark-cli install entry: `npx @pippit-dev/cli@latest install`
39
39
  // should run the JS setup flow before the native binary exists.
40
40
  if (args[0] === "install") {
41
- require("./install-wizard.js");
41
+ require("./install-wizard.js").main();
42
42
  } else {
43
43
  maybeWarnNewVersion(args);
44
44
 
@@ -0,0 +1,83 @@
1
+ const os = require("os");
2
+ const http = require("http");
3
+ const https = require("https");
4
+
5
+ const VERSION = require("../package.json").version.replace(/-.*$/, "");
6
+ const DEFAULT_BASE_URL = "https://xyq.jianying.com";
7
+ const REPORT_PATH = "/api/biz/v1/skill/report_telemetry";
8
+ const AUTH_HEADER = "Bearer pippit-cli-skill-telemetry";
9
+ const SKILL_NAMES = [
10
+ "xyq-skill",
11
+ "xyq-short-drama-skill",
12
+ ];
13
+
14
+ function telemetryBaseURL() {
15
+ for (const key of ["PIPPIT_CLI_TELEMETRY_BASE_URL", "XYQ_OPENAPI_BASE", "XYQ_BASE_URL"]) {
16
+ const value = (process.env[key] || "").trim().replace(/\/+$/, "");
17
+ if (value) return value;
18
+ }
19
+ return DEFAULT_BASE_URL;
20
+ }
21
+
22
+ function reportBundledSkillTelemetry(event, source) {
23
+ if (process.env.PIPPIT_CLI_DISABLE_TELEMETRY === "1") {
24
+ return;
25
+ }
26
+ for (const skillName of SKILL_NAMES) {
27
+ reportSkillTelemetry({
28
+ event,
29
+ skill_name: skillName,
30
+ source,
31
+ cli_version: VERSION,
32
+ platform: os.platform(),
33
+ arch: os.arch(),
34
+ });
35
+ }
36
+ }
37
+
38
+ function reportSkillTelemetry(payload) {
39
+ const body = JSON.stringify(payload);
40
+ const url = new URL(`${telemetryBaseURL()}${REPORT_PATH}`);
41
+ const client = url.protocol === "http:" ? http : https;
42
+
43
+ try {
44
+ const req = client.request(url, {
45
+ method: "POST",
46
+ timeout: 2000,
47
+ headers: {
48
+ "Content-Type": "application/json",
49
+ "Content-Length": Buffer.byteLength(body),
50
+ "Authorization": AUTH_HEADER,
51
+ "x-use-ppe": "1",
52
+ "x-tt-env": "ppe_harness_novel_v2",
53
+ },
54
+ }, (res) => {
55
+ res.resume();
56
+ if (process.env.PIPPIT_CLI_DEBUG_TELEMETRY === "1" && res.statusCode >= 400) {
57
+ console.warn(`[pippit-tool-cli] telemetry failed: HTTP ${res.statusCode}`);
58
+ }
59
+ });
60
+
61
+ req.on("timeout", () => {
62
+ req.destroy(new Error("telemetry request timeout"));
63
+ });
64
+ req.on("error", (err) => {
65
+ if (process.env.PIPPIT_CLI_DEBUG_TELEMETRY === "1") {
66
+ const msg = err && err.message ? err.message : String(err);
67
+ console.warn(`[pippit-tool-cli] telemetry failed: ${msg}`);
68
+ }
69
+ });
70
+ req.end(body);
71
+ } catch (err) {
72
+ if (process.env.PIPPIT_CLI_DEBUG_TELEMETRY === "1") {
73
+ const msg = err && err.message ? err.message : String(err);
74
+ console.warn(`[pippit-tool-cli] telemetry failed: ${msg}`);
75
+ }
76
+ }
77
+ }
78
+
79
+ module.exports = {
80
+ reportBundledSkillTelemetry,
81
+ reportSkillTelemetry,
82
+ telemetryBaseURL,
83
+ };