@pippit-dev/cli 0.0.1

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.
@@ -0,0 +1,49 @@
1
+ package common
2
+
3
+ import (
4
+ "context"
5
+ "crypto/sha1"
6
+ "encoding/hex"
7
+ "time"
8
+ )
9
+
10
+ // UploadFileOptions is the stable command-facing request shape for file upload.
11
+ type UploadFileOptions struct {
12
+ Path string `json:"path"`
13
+ FileName string `json:"file_name"`
14
+ Purpose string `json:"purpose"`
15
+ Mock bool `json:"mock"`
16
+ }
17
+
18
+ // UploadFileResult is the JSON envelope printed by `pippit-cli short-drama +upload-file`.
19
+ type UploadFileResult struct {
20
+ Scene string `json:"scene"`
21
+ FileID string `json:"file_id"`
22
+ Status string `json:"status"`
23
+ Uploaded string `json:"uploaded_at"`
24
+ Request UploadFileOptions `json:"request"`
25
+ }
26
+
27
+ func UploadFile(ctx context.Context, opts UploadFileOptions, _ *Runner) (*UploadFileResult, error) {
28
+ if err := ctx.Err(); err != nil {
29
+ return nil, err
30
+ }
31
+ key := opts.Purpose + "\x00" + opts.FileName + "\x00" + opts.Path
32
+ id := stableID(key, 10)
33
+ return &UploadFileResult{
34
+ Scene: "short-drama",
35
+ FileID: "file_mock_" + id,
36
+ Status: "uploaded",
37
+ Uploaded: time.Now().UTC().Format(time.RFC3339),
38
+ Request: opts,
39
+ }, nil
40
+ }
41
+
42
+ func stableID(key string, n int) string {
43
+ sum := sha1.Sum([]byte(key))
44
+ id := hex.EncodeToString(sum[:])
45
+ if n > len(id) {
46
+ return id
47
+ }
48
+ return id[:n]
49
+ }
@@ -0,0 +1,72 @@
1
+ package config
2
+
3
+ import (
4
+ "os"
5
+ "strings"
6
+ "time"
7
+ )
8
+
9
+ const (
10
+ DefaultBaseURL = "https://xyq.jianying.com"
11
+ DefaultHTTPTimeout = 30 * time.Second
12
+ DefaultAuthTTL = 30 * time.Second
13
+ DefaultOAuthClientKey = "mock-cli"
14
+ DefaultOAuthBaseURL = "https://passport.bytedance.com"
15
+ DefaultAuthStoreServiceName = "pippit-cli"
16
+ SubmitRunPath = "/api/biz/v1/skill/submit_run"
17
+ GetThreadPath = "/api/biz/v1/skill/get_thread"
18
+ UploadFilePath = "/api/biz/v1/skill/upload_file"
19
+ ListThreadFilePath = "/api/biz/v1/skill/list_thread_file"
20
+ EnvXYQAccessKey = "XYQ_ACCESS_KEY"
21
+ )
22
+
23
+ // Config holds runtime settings selected by the root command and passed down
24
+ // into lower layers.
25
+ type Config struct {
26
+ BaseURL string
27
+ HTTPTimeout time.Duration
28
+ AuthTTL time.Duration
29
+ AccessKey string
30
+ OAuth *OAuth
31
+ Paths *Paths
32
+ }
33
+
34
+ type OAuth struct {
35
+ ClientKey string
36
+ BaseURL string
37
+ StoreServiceName string
38
+ Scopes []string
39
+ }
40
+
41
+ type Paths struct {
42
+ SubmitRun string
43
+ GetThread string
44
+ UploadFile string
45
+ ListThreadFile string
46
+ }
47
+
48
+ // Load resolves the built-in runtime config.
49
+ func Load() *Config {
50
+ return &Config{
51
+ BaseURL: DefaultBaseURL,
52
+ HTTPTimeout: DefaultHTTPTimeout,
53
+ AuthTTL: DefaultAuthTTL,
54
+ AccessKey: strings.TrimSpace(os.Getenv(EnvXYQAccessKey)),
55
+ OAuth: resolveOAuth(),
56
+ Paths: &Paths{
57
+ SubmitRun: SubmitRunPath,
58
+ GetThread: GetThreadPath,
59
+ UploadFile: UploadFilePath,
60
+ ListThreadFile: ListThreadFilePath,
61
+ },
62
+ }
63
+ }
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
+ }
@@ -0,0 +1,49 @@
1
+ package config
2
+
3
+ import "testing"
4
+
5
+ func TestLoadUsesDefaultConfig(t *testing.T) {
6
+ t.Setenv(EnvXYQAccessKey, "")
7
+ cfg := Load()
8
+ if cfg.BaseURL != DefaultBaseURL {
9
+ t.Fatalf("BaseURL = %q, want %q", cfg.BaseURL, DefaultBaseURL)
10
+ }
11
+ if cfg.HTTPTimeout != DefaultHTTPTimeout {
12
+ t.Fatalf("HTTPTimeout = %s, want %s", cfg.HTTPTimeout, DefaultHTTPTimeout)
13
+ }
14
+ if cfg.AuthTTL != DefaultAuthTTL {
15
+ t.Fatalf("AuthTTL = %s, want %s", cfg.AuthTTL, DefaultAuthTTL)
16
+ }
17
+ if cfg.AccessKey != "" {
18
+ t.Fatalf("AccessKey = %q, want empty", cfg.AccessKey)
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
+ if cfg.Paths.SubmitRun != SubmitRunPath {
39
+ t.Fatalf("SubmitRun path = %q, want %q", cfg.Paths.SubmitRun, SubmitRunPath)
40
+ }
41
+ }
42
+
43
+ func TestLoadReadsAccessKey(t *testing.T) {
44
+ t.Setenv(EnvXYQAccessKey, " test-token ")
45
+ cfg := Load()
46
+ if cfg.AccessKey != "test-token" {
47
+ t.Fatalf("AccessKey = %q, want trimmed token", cfg.AccessKey)
48
+ }
49
+ }
@@ -0,0 +1,137 @@
1
+ package short_drama
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "github.com/Pippit-dev/pippit-cli/internal/common"
8
+ "github.com/Pippit-dev/pippit-cli/internal/config"
9
+ )
10
+
11
+ // GetThreadOptions is the stable command-facing request shape for thread lookup.
12
+ type GetThreadOptions struct {
13
+ ThreadID string `json:"thread_id"`
14
+ RunID string `json:"run_id,omitempty"`
15
+ AfterSeq int `json:"after_seq"`
16
+ }
17
+
18
+ // ThreadEntry is a compact message or artifact entry inside a thread run.
19
+ type ThreadEntry struct {
20
+ ID string `json:"id"`
21
+ Role string `json:"role"`
22
+ Content []any `json:"content"`
23
+ }
24
+
25
+ // GetThreadResult is the JSON envelope printed by `pippit-cli short-drama +get-thread`.
26
+ type GetThreadResult struct {
27
+ Messages []*ThreadEntry `json:"messages"`
28
+ }
29
+
30
+ type getThreadResponse struct {
31
+ Ret string `json:"ret"`
32
+ Errmsg string `json:"errmsg"`
33
+ Data struct {
34
+ Thread struct {
35
+ RunList []getThreadRun `json:"run_list"`
36
+ } `json:"thread"`
37
+ } `json:"data"`
38
+ }
39
+
40
+ type getThreadRun struct {
41
+ State int `json:"state"`
42
+ FailReason string `json:"fail_reason"`
43
+ EntryList []getThreadEntry `json:"entry_list"`
44
+ }
45
+
46
+ type getThreadEntry struct {
47
+ Message *getThreadMessage `json:"message"`
48
+ Artifact *getThreadArtifact `json:"artifact"`
49
+ }
50
+
51
+ type getThreadMessage struct {
52
+ MessageID string `json:"message_id"`
53
+ Role string `json:"role"`
54
+ Content []any `json:"content"`
55
+ ClientToolCalls []any `json:"client_tool_calls"`
56
+ }
57
+
58
+ type getThreadArtifact struct {
59
+ ArtifactID string `json:"artifact_id"`
60
+ Role string `json:"role"`
61
+ Content []any `json:"content"`
62
+ }
63
+
64
+ func GetThread(ctx context.Context, opts *GetThreadOptions, runner *common.Runner) (*GetThreadResult, error) {
65
+ if runner == nil || runner.Client == nil {
66
+ return nil, fmt.Errorf("get_thread runner client is required")
67
+ }
68
+
69
+ body := map[string]any{
70
+ "thread_id": opts.ThreadID,
71
+ "after_seq": opts.AfterSeq,
72
+ }
73
+ if opts.RunID != "" {
74
+ body["run_id"] = opts.RunID
75
+ }
76
+
77
+ var resp getThreadResponse
78
+ if err := runner.Client.SendRequest(ctx, getThreadPath(runner), body, &resp); err != nil {
79
+ return nil, fmt.Errorf("get_thread request failed: %w", err)
80
+ }
81
+ if resp.Ret != "0" {
82
+ if resp.Errmsg == "" {
83
+ resp.Errmsg = "unknown error"
84
+ }
85
+ return nil, fmt.Errorf("get_thread failed: ret=%s errmsg=%s", resp.Ret, resp.Errmsg)
86
+ }
87
+ if len(resp.Data.Thread.RunList) == 0 {
88
+ return nil, fmt.Errorf("get_thread response missing data.thread.run_list")
89
+ }
90
+
91
+ run := resp.Data.Thread.RunList[0]
92
+ // 4: failed
93
+ // 5: canceled
94
+ if run.State == 4 {
95
+ if run.FailReason == "" {
96
+ run.FailReason = "unknown failure"
97
+ }
98
+ return nil, fmt.Errorf("get_thread run failed: %s", run.FailReason)
99
+ }
100
+ if run.State == 5 {
101
+ return nil, fmt.Errorf("get_thread run canceled")
102
+ }
103
+
104
+ return &GetThreadResult{
105
+ Messages: extractThreadEntries(run),
106
+ }, nil
107
+ }
108
+
109
+ func extractThreadEntries(run getThreadRun) []*ThreadEntry {
110
+ entries := make([]*ThreadEntry, 0, len(run.EntryList))
111
+ for _, entry := range run.EntryList {
112
+ if entry.Message != nil {
113
+ content := append([]any(nil), entry.Message.Content...)
114
+ content = append(content, entry.Message.ClientToolCalls...)
115
+ entries = append(entries, &ThreadEntry{
116
+ ID: entry.Message.MessageID,
117
+ Role: entry.Message.Role,
118
+ Content: content,
119
+ })
120
+ }
121
+ if entry.Artifact != nil {
122
+ entries = append(entries, &ThreadEntry{
123
+ ID: entry.Artifact.ArtifactID,
124
+ Role: entry.Artifact.Role,
125
+ Content: entry.Artifact.Content,
126
+ })
127
+ }
128
+ }
129
+ return entries
130
+ }
131
+
132
+ func getThreadPath(runner *common.Runner) string {
133
+ if runner != nil && runner.Config != nil && runner.Config.Paths != nil && runner.Config.Paths.GetThread != "" {
134
+ return runner.Config.Paths.GetThread
135
+ }
136
+ return config.GetThreadPath
137
+ }
@@ -0,0 +1,47 @@
1
+ package short_drama
2
+
3
+ import (
4
+ "context"
5
+ "crypto/sha1"
6
+ "encoding/hex"
7
+ )
8
+
9
+ // MockClient returns deterministic IDs so the demo chain is easy to test.
10
+ type MockClient struct{}
11
+
12
+ func (MockClient) SubmitRun(ctx context.Context, opts SubmitRunOptions) (*SubmitRunResult, error) {
13
+ if err := ctx.Err(); err != nil {
14
+ return nil, err
15
+ }
16
+ key := opts.ThreadID + "\x00" + opts.Message
17
+ id := stableIDMock(key, 12)
18
+ return &SubmitRunResult{
19
+ ThreadID: "thread_mock_" + id[:6],
20
+ RunID: "run_mock_" + id[6:],
21
+ WebThreadLink: "https://xyq.jianying.com/mock/thread_mock_" + id[:6],
22
+ }, nil
23
+ }
24
+
25
+ func (MockClient) GetThread(ctx context.Context, opts GetThreadOptions) (*GetThreadResult, error) {
26
+ if err := ctx.Err(); err != nil {
27
+ return nil, err
28
+ }
29
+ return &GetThreadResult{
30
+ Messages: []*ThreadEntry{
31
+ {
32
+ ID: "message_mock_" + stableIDMock(opts.ThreadID, 8),
33
+ Role: "assistant",
34
+ Content: []any{"mock thread message"},
35
+ },
36
+ },
37
+ }, nil
38
+ }
39
+
40
+ func stableIDMock(key string, n int) string {
41
+ sum := sha1.Sum([]byte(key))
42
+ id := hex.EncodeToString(sum[:])
43
+ if n > len(id) {
44
+ return id
45
+ }
46
+ return id[:n]
47
+ }
@@ -0,0 +1,81 @@
1
+ package short_drama
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "github.com/Pippit-dev/pippit-cli/internal/common"
8
+ "github.com/Pippit-dev/pippit-cli/internal/config"
9
+ )
10
+
11
+ // SubmitRunOptions is the stable command-facing request shape for short drama run submission.
12
+ type SubmitRunOptions struct {
13
+ Message string `json:"message"`
14
+ ThreadID string `json:"thread_id,omitempty"`
15
+ AssetIDs []string `json:"asset_ids,omitempty"`
16
+ }
17
+
18
+ // SubmitRunResult is the JSON envelope printed by `pippit-cli short-drama +submit-run`.
19
+ type SubmitRunResult struct {
20
+ ThreadID string `json:"thread_id"`
21
+ RunID string `json:"run_id"`
22
+ WebThreadLink string `json:"web_thread_link"`
23
+ }
24
+
25
+ type submitRunResponse struct {
26
+ Ret string `json:"ret"`
27
+ Errmsg string `json:"errmsg"`
28
+ Data struct {
29
+ WebThreadLink string `json:"web_thread_link"`
30
+ Run struct {
31
+ ThreadID string `json:"thread_id"`
32
+ RunID string `json:"run_id"`
33
+ } `json:"run"`
34
+ } `json:"data"`
35
+ }
36
+
37
+ func SubmitRun(ctx context.Context, opts *SubmitRunOptions, runner *common.Runner) (*SubmitRunResult, error) {
38
+ if runner == nil || runner.Client == nil {
39
+ return nil, fmt.Errorf("submit_run runner client is required")
40
+ }
41
+
42
+ body := map[string]any{
43
+ "message": opts.Message,
44
+ }
45
+ if opts.ThreadID != "" {
46
+ body["thread_id"] = opts.ThreadID
47
+ }
48
+ if len(opts.AssetIDs) > 0 {
49
+ body["asset_ids"] = opts.AssetIDs
50
+ }
51
+ body["agent_name"] = "pippit_nest_novel_agent"
52
+
53
+ var resp submitRunResponse
54
+ if err := runner.Client.SendRequest(ctx, submitRunPath(runner), body, &resp); err != nil {
55
+ return nil, fmt.Errorf("submit_run request failed: %w", err)
56
+ }
57
+ if resp.Ret != "0" {
58
+ if resp.Errmsg == "" {
59
+ resp.Errmsg = "unknown error"
60
+ }
61
+ return nil, fmt.Errorf("submit_run failed: ret=%s errmsg=%s", resp.Ret, resp.Errmsg)
62
+ }
63
+ if resp.Data.Run.ThreadID == "" {
64
+ return nil, fmt.Errorf("submit_run response missing data.run.thread_id")
65
+ }
66
+ if resp.Data.Run.RunID == "" {
67
+ return nil, fmt.Errorf("submit_run response missing data.run.run_id")
68
+ }
69
+ return &SubmitRunResult{
70
+ ThreadID: resp.Data.Run.ThreadID,
71
+ RunID: resp.Data.Run.RunID,
72
+ WebThreadLink: resp.Data.WebThreadLink,
73
+ }, nil
74
+ }
75
+
76
+ func submitRunPath(runner *common.Runner) string {
77
+ if runner != nil && runner.Config != nil && runner.Config.Paths != nil && runner.Config.Paths.SubmitRun != "" {
78
+ return runner.Config.Paths.SubmitRun
79
+ }
80
+ return config.SubmitRunPath
81
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@pippit-dev/cli",
3
+ "version": "0.0.1",
4
+ "description": "Pippit CLI",
5
+ "bin": {
6
+ "pippit-cli": "scripts/run.js"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node scripts/install.js",
10
+ "test": "go test ./... && go vet ./..."
11
+ },
12
+ "os": [
13
+ "darwin",
14
+ "linux",
15
+ "win32"
16
+ ],
17
+ "cpu": [
18
+ "x64",
19
+ "arm64"
20
+ ],
21
+ "engines": {
22
+ "node": ">=16"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/Pippit-dev/cli.git"
27
+ },
28
+ "license": "MIT",
29
+ "files": [
30
+ "cmd",
31
+ "internal",
32
+ "skills",
33
+ "scripts/install.js",
34
+ "scripts/install-wizard.js",
35
+ "scripts/platform.js",
36
+ "scripts/run.js",
37
+ "scripts/skills.js",
38
+ "checksums.txt",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { isWindows, run, runSilent } = require("./platform");
6
+ const { DEFAULT_PKG, installGlobalPackageSkills } = require("./skills");
7
+
8
+ const PKG = process.env.PIPPIT_CLI_INSTALL_PACKAGE || DEFAULT_PKG;
9
+
10
+ function getGloballyInstalledVersion() {
11
+ try {
12
+ const out = runSilent("npm", ["list", "-g", DEFAULT_PKG], { timeout: 15000 });
13
+ const match = out.toString().match(/@(\d+\.\d+\.\d+[^\s]*)/);
14
+ return match ? match[1] : "unknown";
15
+ } catch (_) {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ function whichPippitCli() {
21
+ try {
22
+ const prefix = runSilent("npm", ["prefix", "-g"], { timeout: 15000 }).toString().trim();
23
+ const bin = isWindows
24
+ ? path.join(prefix, "pippit-cli.cmd")
25
+ : path.join(prefix, "bin", "pippit-cli");
26
+ if (fs.existsSync(bin)) return bin;
27
+ } catch (_) {
28
+ // Fall back to PATH lookup.
29
+ }
30
+
31
+ try {
32
+ const cmd = isWindows ? "where" : "which";
33
+ return runSilent(cmd, ["pippit-cli"]).toString().split("\n")[0].trim();
34
+ } catch (_) {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ function main() {
40
+ const installed = getGloballyInstalledVersion();
41
+ if (installed) {
42
+ console.log(`pippit-cli is already installed globally (${installed}).`);
43
+ } else {
44
+ console.log(`Installing ${PKG} globally...`);
45
+ run("npm", ["install", "-g", PKG], { timeout: 120000 });
46
+ }
47
+
48
+ console.log("Installing pippit-cli skills...");
49
+ try {
50
+ installGlobalPackageSkills(DEFAULT_PKG);
51
+ } catch (err) {
52
+ if (!installed) {
53
+ throw err;
54
+ }
55
+ console.log("Existing global package does not contain skills; reinstalling...");
56
+ run("npm", ["install", "-g", PKG], { timeout: 120000 });
57
+ installGlobalPackageSkills(DEFAULT_PKG);
58
+ }
59
+
60
+ const bin = whichPippitCli();
61
+ if (!bin) {
62
+ console.error("pippit-cli was installed, but no global command was found in npm prefix.");
63
+ console.error("Check that npm's global bin directory is in PATH.");
64
+ process.exit(1);
65
+ }
66
+
67
+ console.log(`pippit-cli is ready: ${bin}`);
68
+ console.log("Try: pippit-cli short-drama +submit-run --message \"写一个短剧开头\"");
69
+ }
70
+
71
+ main();