@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.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/cmd/auth/auth.go +144 -0
- package/cmd/root.go +42 -0
- package/cmd/short_drama/short_drama.go +196 -0
- package/cmd/short_drama_test.go +587 -0
- package/cmd/update/update.go +124 -0
- package/internal/auth/manager.go +146 -0
- package/internal/common/access_key.go +29 -0
- package/internal/common/client.go +137 -0
- package/internal/common/download_results.go +243 -0
- package/internal/common/list_thread_file.go +104 -0
- package/internal/common/runner.go +21 -0
- package/internal/common/upload_file.go +49 -0
- package/internal/config/config.go +72 -0
- package/internal/config/config_test.go +49 -0
- package/internal/short_drama/get_thread.go +137 -0
- package/internal/short_drama/mock.go +47 -0
- package/internal/short_drama/submit_run.go +81 -0
- package/package.json +45 -0
- package/scripts/install-wizard.js +71 -0
- package/scripts/install.js +170 -0
- package/scripts/platform.js +28 -0
- package/scripts/run.js +62 -0
- package/scripts/skills.js +32 -0
- package/skills/short-drama/SKILL.md +280 -0
|
@@ -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();
|