@pippit-dev/cli 1.0.41 → 1.0.43
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 +42 -11
- package/checksums.txt +6 -6
- package/cmd/auth/auth.go +19 -5
- package/cmd/auth/auth_test.go +23 -0
- package/cmd/canvas/canvas.go +7 -7
- package/cmd/download_result.go +3 -2
- package/cmd/marketing.go +19 -3
- package/cmd/marketing_test.go +11 -5
- package/cmd/model.go +5 -4
- package/cmd/root.go +17 -5
- package/cmd/root_update_check_test.go +139 -0
- package/cmd/short_drama_test.go +2 -0
- package/cmd/source_test.go +27 -4
- package/cmd/space.go +199 -0
- package/cmd/space_test.go +469 -0
- package/cmd/upload_file_test.go +3 -1
- package/dist/checksums.txt +6 -6
- package/internal/adaptercli/commands_test.go +9 -5
- package/internal/auth/loopback.go +16 -11
- package/internal/auth/loopback_origins_test.go +152 -0
- package/internal/auth/space.go +26 -0
- package/internal/auth/space_test.go +42 -0
- package/internal/canvas/get.go +9 -2
- package/internal/commandnames/names.go +9 -4
- package/internal/common/client.go +23 -0
- package/internal/common/host_source.go +5 -0
- package/internal/common/host_source_test.go +9 -1
- package/internal/common/runner.go +5 -3
- package/internal/common/space.go +185 -0
- package/internal/common/space_test.go +132 -0
- package/internal/common/submit_run.go +2 -0
- package/internal/common/upload_file.go +2 -0
- package/internal/config/config.go +15 -12
- package/internal/generate_image/generate_image.go +3 -9
- package/internal/short_drama/submit_run.go +3 -9
- package/internal/space/session.go +153 -0
- package/internal/space/store.go +211 -0
- package/internal/space/store_test.go +246 -0
- package/internal/version/version.go +13 -0
- package/internal/version/version_test.go +38 -0
- package/internal/versioncheck/check.go +147 -0
- package/internal/versioncheck/check_test.go +204 -0
- package/internal/versioncheck/semver.go +84 -0
- package/package.json +1 -1
- package/scripts/canvas-command.js +47 -5
- package/scripts/install-wizard.js +1 -1
- package/scripts/run.js +15 -4
- package/scripts/telemetry.js +3 -1
- package/scripts/version-check.js +8 -1
- package/skills/short-drama/SKILL.md +12 -1
- package/skills/xyq-marketing-skill/SKILL.md +22 -3
- package/skills/xyq-marketing-skill/references/api.md +7 -1
- package/skills/xyq-marketing-skill/scripts/marketing.js +51 -19
- package/skills/xyq-nest-skill/SKILL.md +15 -4
- package/skills/xyq-nest-skill/commands/canvas-assets.md +1 -1
- package/skills/xyq-nest-skill/commands/canvas.md +5 -3
- package/skills/xyq-nest-skill/commands/get-credit-balance.md +2 -2
- package/skills/xyq-nest-skill/commands/model.md +1 -1
- package/skills/xyq-nest-skill/scripts/ensure-cli.js +8 -1
- package/skills/xyq-nest-skill/scripts/install.md +1 -1
- package/skills/xyq-nest-skill/workflows/canvas-edit.md +2 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package versioncheck
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"regexp"
|
|
5
|
+
"strconv"
|
|
6
|
+
"strings"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
var semverPattern = regexp.MustCompile(`^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$`)
|
|
10
|
+
|
|
11
|
+
type semver struct {
|
|
12
|
+
core [3]uint64
|
|
13
|
+
pre []string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func parseVersion(value string) (semver, bool) {
|
|
17
|
+
var parsed semver
|
|
18
|
+
if len(value) > 256 {
|
|
19
|
+
return parsed, false
|
|
20
|
+
}
|
|
21
|
+
parts := semverPattern.FindStringSubmatch(value)
|
|
22
|
+
if parts == nil {
|
|
23
|
+
return parsed, false
|
|
24
|
+
}
|
|
25
|
+
for index := range parsed.core {
|
|
26
|
+
part, err := strconv.ParseUint(parts[index+1], 10, 64)
|
|
27
|
+
if err != nil {
|
|
28
|
+
return parsed, false
|
|
29
|
+
}
|
|
30
|
+
parsed.core[index] = part
|
|
31
|
+
}
|
|
32
|
+
if parts[4] != "" {
|
|
33
|
+
parsed.pre = strings.Split(parts[4], ".")
|
|
34
|
+
for _, identifier := range parsed.pre {
|
|
35
|
+
if numeric(identifier) && len(identifier) > 1 && identifier[0] == '0' {
|
|
36
|
+
return parsed, false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return parsed, true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func isNewer(latest, current string) bool {
|
|
44
|
+
a, validA := parseVersion(latest)
|
|
45
|
+
b, validB := parseVersion(current)
|
|
46
|
+
if !validA || !validB {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
for index, part := range a.core {
|
|
50
|
+
if part != b.core[index] {
|
|
51
|
+
return part > b.core[index]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if len(a.pre) == 0 || len(b.pre) == 0 {
|
|
55
|
+
return len(a.pre) == 0 && len(b.pre) != 0
|
|
56
|
+
}
|
|
57
|
+
for index, part := range a.pre {
|
|
58
|
+
if index >= len(b.pre) {
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
other := b.pre[index]
|
|
62
|
+
if part == other {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
numericA, numericB := numeric(part), numeric(other)
|
|
66
|
+
if numericA != numericB {
|
|
67
|
+
return !numericA
|
|
68
|
+
}
|
|
69
|
+
if numericA && len(part) != len(other) {
|
|
70
|
+
return len(part) > len(other)
|
|
71
|
+
}
|
|
72
|
+
return part > other
|
|
73
|
+
}
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func numeric(value string) bool {
|
|
78
|
+
for _, character := range value {
|
|
79
|
+
if character < '0' || character > '9' {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return value != ""
|
|
84
|
+
}
|
package/package.json
CHANGED
|
@@ -17,6 +17,8 @@ const DEFAULT_SDK_MODULE = path.join(
|
|
|
17
17
|
"xyq-canvas-command-runtime.cjs"
|
|
18
18
|
);
|
|
19
19
|
const MAX_INPUT_BYTES = 64 * 1024 * 1024;
|
|
20
|
+
const UPGRADE_NOTICE_PREFIX = "[pippit-tool-cli] New version available:";
|
|
21
|
+
const LOGIN_UPGRADE_NOTICE = "[pippit-tool-cli] If browser authorization fails, update the CLI and run pippit-tool-cli login again.";
|
|
20
22
|
const PERSISTENCE_VERSION = 1;
|
|
21
23
|
const OPTIONAL_SCHEMA = Symbol("optional canvas command schema");
|
|
22
24
|
const CHECKPOINT_COMMANDS = new Set([
|
|
@@ -46,13 +48,32 @@ const COMMAND_HELP = `用法:
|
|
|
46
48
|
list 返回简短索引;describe 按层展示字段和可选操作,使用 --path 展开 schema_path。
|
|
47
49
|
guide 按主题说明时间、引用和工作流;schema 显式导出完整 schema(可能很大)。
|
|
48
50
|
这些发现入口无需登录;run 需要目标画布的访问权限。
|
|
51
|
+
run 使用默认空间,可通过 --team-id <ID> 覆盖;0 为个人空间。
|
|
49
52
|
`;
|
|
50
53
|
|
|
54
|
+
function canvasSpaceArgs(args) {
|
|
55
|
+
const clean = [];
|
|
56
|
+
let teamID;
|
|
57
|
+
for (let i = 0; i < args.length; i++) {
|
|
58
|
+
if (args[i] === "--team-id" || args[i].startsWith("--team-id=")) {
|
|
59
|
+
const value = args[i] === "--team-id" ? args[++i] : args[i].slice("--team-id=".length);
|
|
60
|
+
if (teamID !== undefined || !/^\d+$/.test(value || "") || BigInt(value) > 9223372036854775807n) throw new Error("team-id 必须为唯一的 0 或正整数 ID");
|
|
61
|
+
teamID = BigInt(value).toString();
|
|
62
|
+
} else clean.push(args[i]);
|
|
63
|
+
}
|
|
64
|
+
return { args: clean, teamID };
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
function isCanvasCommand(args) {
|
|
52
|
-
|
|
68
|
+
// Global team-id may appear before or after the command. Other flags retain
|
|
69
|
+
// their existing parser and are never forwarded into canvas mutation JSON.
|
|
70
|
+
let start = 0;
|
|
71
|
+
while (args[start] === "--team-id" || (args[start] || "").startsWith("--team-id=")) start += args[start] === "--team-id" ? 2 : 1;
|
|
72
|
+
return args[start] === "canvas" && args[start + 1] === "command";
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
function parseCanvasCommandArgs(args) {
|
|
76
|
+
args = canvasSpaceArgs(args).args;
|
|
56
77
|
if (!isCanvasCommand(args)) throw new Error("仅支持 canvas command 命令");
|
|
57
78
|
const values = args.slice(2);
|
|
58
79
|
if (!values.length || values[0] === "--help" || values[0] === "-h") {
|
|
@@ -144,6 +165,7 @@ function spawnProcess(invocation, args, options = {}) {
|
|
|
144
165
|
}
|
|
145
166
|
const child = spawn(invocation.command, [...(invocation.prefixArgs || []), ...args], {
|
|
146
167
|
cwd: options.cwd,
|
|
168
|
+
env: invocation.env || process.env,
|
|
147
169
|
shell: false,
|
|
148
170
|
stdio: options.interactive ? "inherit" : ["pipe", "pipe", "pipe"],
|
|
149
171
|
windowsHide: true,
|
|
@@ -193,19 +215,34 @@ class NativeCanvasClient {
|
|
|
193
215
|
this.invocation = invocation;
|
|
194
216
|
this.cwd = options.cwd;
|
|
195
217
|
this.stderr = options.stderr || process.stderr;
|
|
218
|
+
this.teamID = options.teamID;
|
|
196
219
|
}
|
|
197
220
|
|
|
198
221
|
async runJSON(args, input) {
|
|
222
|
+
if (args[0] === "canvas" && this.teamID !== undefined) args = [...args, "--team-id", this.teamID];
|
|
223
|
+
if (args[0] === "canvas" && this.spaceContext) args = [...args, "--space-context", this.spaceContext];
|
|
199
224
|
const result = await spawnProcess(this.invocation, args, {
|
|
200
225
|
cwd: this.cwd,
|
|
201
226
|
input: input === undefined ? undefined : JSON.stringify(input),
|
|
202
227
|
});
|
|
203
228
|
if (result.exitCode !== 0) throw new NativeCommandError(args, result);
|
|
229
|
+
let output;
|
|
204
230
|
try {
|
|
205
|
-
|
|
231
|
+
output = JSON.parse(result.stdout);
|
|
232
|
+
// The first canvas get resolves the space in Go. Reuse its binding for
|
|
233
|
+
// every SDK transport call, even if another process changes the default.
|
|
234
|
+
if (args[0] === "canvas" && output.team_id !== undefined) {
|
|
235
|
+
this.teamID = output.team_id;
|
|
236
|
+
this.spaceContext = output.space_context;
|
|
237
|
+
}
|
|
206
238
|
} catch (error) {
|
|
207
239
|
throw new Error(`原生 CLI 未返回有效 JSON(${args.join(" ")}):${error.message}`);
|
|
208
240
|
}
|
|
241
|
+
const notices = result.stderr.split(/\r?\n/).filter((line) =>
|
|
242
|
+
line.startsWith(UPGRADE_NOTICE_PREFIX) || line === LOGIN_UPGRADE_NOTICE
|
|
243
|
+
);
|
|
244
|
+
if (notices.length) this.stderr.write(`${notices.join("\n")}\n`);
|
|
245
|
+
return output;
|
|
209
246
|
}
|
|
210
247
|
|
|
211
248
|
async ensureAuthenticated() {
|
|
@@ -777,6 +814,7 @@ function executePublicCommand(route, definitions, input) {
|
|
|
777
814
|
}
|
|
778
815
|
|
|
779
816
|
async function runCanvasCommand(args, options = {}) {
|
|
817
|
+
const { teamID } = canvasSpaceArgs(args);
|
|
780
818
|
const parsed = parseCanvasCommandArgs(args);
|
|
781
819
|
const stdout = options.stdout || process.stdout;
|
|
782
820
|
const stderr = options.stderr || process.stderr;
|
|
@@ -833,8 +871,11 @@ async function runCanvasCommand(args, options = {}) {
|
|
|
833
871
|
const input = parseInputJSON(parsed, cwd);
|
|
834
872
|
const route = routes.get(parsed.commandName);
|
|
835
873
|
validatePublicCommandInput(route, input);
|
|
836
|
-
const nativeClient = new NativeCanvasClient(options.nativeInvocation, { cwd, stderr });
|
|
874
|
+
const nativeClient = new NativeCanvasClient(options.nativeInvocation, { cwd, stderr, teamID });
|
|
837
875
|
const authStatus = await nativeClient.ensureCanvasAccess(parsed.canvasId);
|
|
876
|
+
const credentialScope = authStatus.credential_scope && nativeClient.teamID && nativeClient.teamID !== "0"
|
|
877
|
+
? `${authStatus.credential_scope}:team:${nativeClient.teamID}`
|
|
878
|
+
: authStatus.credential_scope;
|
|
838
879
|
const requiresDurableCheckpoint = CHECKPOINT_COMMANDS.has(parsed.commandName) ||
|
|
839
880
|
(parsed.commandName === "apply_mutations" && input.checkpointBefore === true);
|
|
840
881
|
if (requiresDurableCheckpoint && !authStatus.credential_scope) {
|
|
@@ -844,7 +885,7 @@ async function runCanvasCommand(args, options = {}) {
|
|
|
844
885
|
const checkpointStore = authStatus.credential_scope
|
|
845
886
|
? createFileCheckpointStore({
|
|
846
887
|
canvasId: parsed.canvasId,
|
|
847
|
-
credentialScope
|
|
888
|
+
credentialScope,
|
|
848
889
|
stateDirectory: options.stateDirectory,
|
|
849
890
|
})
|
|
850
891
|
: undefined;
|
|
@@ -863,7 +904,7 @@ async function runCanvasCommand(args, options = {}) {
|
|
|
863
904
|
const persistence = authStatus.credential_scope
|
|
864
905
|
? createFilePersistence({
|
|
865
906
|
canvasId: parsed.canvasId,
|
|
866
|
-
credentialScope
|
|
907
|
+
credentialScope,
|
|
867
908
|
stateDirectory: options.stateDirectory,
|
|
868
909
|
})
|
|
869
910
|
: createMemoryPersistence();
|
|
@@ -954,6 +995,7 @@ module.exports = {
|
|
|
954
995
|
createSchemaFactory,
|
|
955
996
|
definitionToJSON,
|
|
956
997
|
ensureNodeRuntimeGlobals,
|
|
998
|
+
canvasSpaceArgs,
|
|
957
999
|
isCanvasCommand,
|
|
958
1000
|
parseCanvasCommandArgs,
|
|
959
1001
|
runCanvasCommand,
|
|
@@ -21,7 +21,7 @@ PIPPIT_CLI_INSTALL_PACKAGE can override the package/version to install.
|
|
|
21
21
|
|
|
22
22
|
Options:
|
|
23
23
|
--source HOST Real host identifier reported as host_platform; unknown hosts omit it
|
|
24
|
-
|
|
24
|
+
Missing or blank values default to PIPPIT_CLI_SOURCE, then known host runtime markers
|
|
25
25
|
-h, --help Show this help without installing, updating, or sending telemetry
|
|
26
26
|
`;
|
|
27
27
|
|
package/scripts/run.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
const { execFileSync } = require('child_process');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { isCanvasCommand, runCanvasCommand } = require('./canvas-command');
|
|
6
|
+
const { isCanvasCommand, parseCanvasCommandArgs, runCanvasCommand, canvasSpaceArgs } = require('./canvas-command');
|
|
7
7
|
const { maybeWarnNewVersion } = require('./version-check');
|
|
8
8
|
|
|
9
9
|
const ext = process.platform === 'win32' ? '.exe' : '';
|
|
10
10
|
const bin = path.join(__dirname, '..', 'bin', 'pippit-tool-cli' + ext);
|
|
11
11
|
const args = process.argv.slice(2);
|
|
12
|
+
// The launcher owns the notice for this invocation, including native children.
|
|
13
|
+
const nativeEnv = { ...process.env, PIPPIT_CLI_UPDATE_CHECKED: '1' };
|
|
12
14
|
|
|
13
15
|
const oldBin = bin + '.old';
|
|
14
16
|
function restoreOldBinary() {
|
|
@@ -25,7 +27,7 @@ function restoreOldBinary() {
|
|
|
25
27
|
|
|
26
28
|
function runCanvas() {
|
|
27
29
|
runCanvasCommand(args, {
|
|
28
|
-
nativeInvocation: { command: bin },
|
|
30
|
+
nativeInvocation: { command: bin, env: nativeEnv },
|
|
29
31
|
})
|
|
30
32
|
.then((exitCode) => {
|
|
31
33
|
if (exitCode) process.exitCode = exitCode;
|
|
@@ -43,7 +45,16 @@ function main() {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const helpRequested = args.includes('--help') || args.includes('-h') || args[0] === 'help';
|
|
46
|
-
if (isCanvasCommand(args) && (args[2] !== 'run' || helpRequested)) {
|
|
48
|
+
if (isCanvasCommand(args) && (canvasSpaceArgs(args).args[2] !== 'run' || helpRequested)) {
|
|
49
|
+
// Discovery commands also need update notices. Keep help and invalid
|
|
50
|
+
// commands free of network checks and cache writes.
|
|
51
|
+
if (!helpRequested) {
|
|
52
|
+
try {
|
|
53
|
+
if (parseCanvasCommandArgs(args).action !== 'help') maybeWarnNewVersion(args);
|
|
54
|
+
} catch (_) {
|
|
55
|
+
// runCanvas reports argument errors through the existing path.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
47
58
|
return runCanvas();
|
|
48
59
|
}
|
|
49
60
|
|
|
@@ -88,7 +99,7 @@ function main() {
|
|
|
88
99
|
runCanvas();
|
|
89
100
|
} else {
|
|
90
101
|
try {
|
|
91
|
-
execFileSync(bin, args, { stdio: 'inherit' });
|
|
102
|
+
execFileSync(bin, args, { stdio: 'inherit', env: nativeEnv });
|
|
92
103
|
} catch (e) {
|
|
93
104
|
process.exit(e.status || 1);
|
|
94
105
|
}
|
package/scripts/telemetry.js
CHANGED
|
@@ -21,12 +21,14 @@ function telemetryBaseURL() {
|
|
|
21
21
|
|
|
22
22
|
// Keep the same attribution rules as native CLI common.DetectHostSource.
|
|
23
23
|
function resolveHostSource(explicit) {
|
|
24
|
-
if (explicit !== undefined) return explicit.trim();
|
|
24
|
+
if (explicit !== undefined && explicit.trim()) return explicit.trim();
|
|
25
25
|
if ((process.env.PIPPIT_CLI_SOURCE || "").trim()) return process.env.PIPPIT_CLI_SOURCE.trim();
|
|
26
26
|
const hosts = new Set();
|
|
27
27
|
for (const [key, host] of [
|
|
28
28
|
["CODEX_THREAD_ID", "codex"], ["CODEX_SESSION_ID", "codex"],
|
|
29
29
|
["CLAUDECODE", "claude_code"], ["CURSOR_AGENT", "cursor"], ["GEMINI_CLI", "gemini_cli"],
|
|
30
|
+
["OPENCODE", "opencode"], ["QWEN_CODE", "qwen_code"], ["CLINE_ACTIVE", "cline"],
|
|
31
|
+
["COPILOT_CLI", "github_copilot_cli"], ["COPILOT_AGENT_SESSION_ID", "github_copilot_cli"],
|
|
30
32
|
]) {
|
|
31
33
|
const value = (process.env[key] || "").trim().toLowerCase();
|
|
32
34
|
if (value && value !== "0" && value !== "false") hosts.add(host);
|
package/scripts/version-check.js
CHANGED
|
@@ -5,6 +5,7 @@ const { runSilent } = require("./platform");
|
|
|
5
5
|
const { DEFAULT_PKG } = require("./skills");
|
|
6
6
|
|
|
7
7
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
const RETRY_INTERVAL_MS = 5 * 60 * 1000;
|
|
8
9
|
|
|
9
10
|
function defaultCacheFile() {
|
|
10
11
|
return path.join(os.homedir(), ".pippit_tool_cli", "version-check.json");
|
|
@@ -49,7 +50,7 @@ function writeCache(cacheFile, data) {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function fetchLatestVersion(pkg = DEFAULT_PKG) {
|
|
52
|
-
return runSilent("npm", ["view", pkg, "version"], { timeout:
|
|
53
|
+
return runSilent("npm", ["view", pkg, "version"], { timeout: 1000 }).toString().trim();
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
function shouldSkip(args, env) {
|
|
@@ -70,6 +71,7 @@ function maybeWarnNewVersion(args = [], opts = {}) {
|
|
|
70
71
|
const cacheFile = opts.cacheFile || defaultCacheFile();
|
|
71
72
|
const cache = readCache(cacheFile);
|
|
72
73
|
const cacheFresh = cache && now - cache.checkedAt < CHECK_INTERVAL_MS;
|
|
74
|
+
if (cacheFresh && !cache.latest && now - cache.checkedAt < RETRY_INTERVAL_MS) return;
|
|
73
75
|
|
|
74
76
|
let latest = cacheFresh ? cache.latest : "";
|
|
75
77
|
if (!latest) {
|
|
@@ -77,6 +79,7 @@ function maybeWarnNewVersion(args = [], opts = {}) {
|
|
|
77
79
|
latest = (opts.fetchLatestVersion || fetchLatestVersion)(opts.pkg || DEFAULT_PKG);
|
|
78
80
|
writeCache(cacheFile, { latest, checkedAt: now });
|
|
79
81
|
} catch (_) {
|
|
82
|
+
writeCache(cacheFile, { latest: "", checkedAt: now });
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
}
|
|
@@ -86,10 +89,14 @@ function maybeWarnNewVersion(args = [], opts = {}) {
|
|
|
86
89
|
|
|
87
90
|
const warn = opts.warn || console.error;
|
|
88
91
|
warn(`[pippit-tool-cli] New version available: ${current} -> ${latest}. Run: pippit-tool-cli update`);
|
|
92
|
+
if (args[0] === "login") {
|
|
93
|
+
warn("[pippit-tool-cli] If browser authorization fails, update the CLI and run pippit-tool-cli login again.");
|
|
94
|
+
}
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
module.exports = {
|
|
92
98
|
CHECK_INTERVAL_MS,
|
|
99
|
+
RETRY_INTERVAL_MS,
|
|
93
100
|
compareSemver,
|
|
94
101
|
defaultCacheFile,
|
|
95
102
|
maybeWarnNewVersion,
|
|
@@ -16,6 +16,17 @@ metadata:
|
|
|
16
16
|
2. 需要业务调用时先按 [授权](commands/auth.md) 执行 `status`,已有登录可复用。
|
|
17
17
|
3. 按下表读取命中的命令说明。提交、续接和处理后端问题必须读 [创作与会话续接](workflows/creation.md);查询或取得产物必须读 [轮询与文件交付](workflows/poll-and-deliver.md)。
|
|
18
18
|
|
|
19
|
+
## Space Selection
|
|
20
|
+
|
|
21
|
+
业务命令支持个人和团队空间,复用同一个 CLI 登录凭据。执行前先检查 `pippit-tool-cli space --help` 和业务命令的 `--team-id`;旧版不支持时先更新 CLI,不能忽略团队选择后继续收费操作。
|
|
22
|
+
|
|
23
|
+
- `space list` 统一输出 JSON,列出个人和当前账号的团队;顶层 `current` 表示已保存的默认空间,列表项 `current: true` 标记当前选择,无法识别本地账号时顶层为 `null`。`team_id` 保留字符串,`0` 为个人。列表中的团队不代表有生成权限、有效订阅或可用积分,最终由服务端判断。
|
|
24
|
+
- 用户明确要求长期切换时,运行 `space use TEAM_ID`;`space use 0` 保存个人空间。`use` 必须指定 ID,成功后返回保存的空间,不等待交互输入。只为本次任务选择空间时使用 `--team-id TEAM_ID`,不修改用户默认值;不能按模糊名称或剩余积分自动选团队。
|
|
25
|
+
- 新创作未指定空间时,读取并沿用保存的默认空间,首次使用为个人。向用户说明本次空间与积分账户,不重复索要已有授权。一次工作流开始后保存 TeamID,分开的上传、提交、查询和余额命令均显式传相同 `--team-id`,不能中途重新读取默认值。
|
|
26
|
+
- 已有任务优先沿用提交结果中的 `team_id` 或 CLI 的任务记录;切换默认团队后可以直接按原 thread/run 查询,CLI 自动恢复原空间。不要把当前默认 TeamID 强加给旧任务。跨设备或旧任务没有记录时,使用已知原空间;未知且用户也未指定时先核实,不能轮流试团队。
|
|
27
|
+
- 保存上传结果的资产 ID 和 `team_id`,提交结果的 thread/run ID 和 `team_id`。跨空间资产或任务冲突时停止;无权限、会员失效或余额不足不回退个人或其它团队。提交成功但本地记录写入失败时,保留返回 ID,用原 `--team-id` 查询,不重新生成。
|
|
28
|
+
- CLI 向同源 API 统一透传当前团队的 TeamID;模型接口内部继续按个人身份查询;模型列表成功不代表团队有生成权限。Canvas 同样使用所选空间;操作已有画布时沿用该画布原空间,切换默认空间后仍显式传入原 `--team-id`。
|
|
29
|
+
|
|
19
30
|
## 意图路由
|
|
20
31
|
|
|
21
32
|
| 用户意图 | CLI | 必读文档 |
|
|
@@ -44,7 +55,7 @@ metadata:
|
|
|
44
55
|
|
|
45
56
|
## 宿主来源统计
|
|
46
57
|
|
|
47
|
-
优先从宿主的系统身份、当前工具运行环境和明确的平台元数据确认真实宿主,并在每次提交主动填写 `--source
|
|
58
|
+
优先从宿主的系统身份、当前工具运行环境和明确的平台元数据确认真实宿主,并在每次提交主动填写 `--source`;不要仅因字段可选就跳过。未传 `--source`、显式空值或纯空白时,CLI 依次尝试 `PIPPIT_CLI_SOURCE` 和已核实的宿主运行标记。只上报稳定的平台名,不上报标记中的会话 ID;来源冲突或仍无法确认则省略,不询问用户、不扫描登录凭据或安装目录。
|
|
48
59
|
|
|
49
60
|
仅 `short-drama +submit-run` 静默附加可选 `--source`,用于统计:豆包办公 `doubao_office`、WorkBuddy `workbuddy`、Codex `codex`;其它宿主使用可信环境中的真实稳定标识,无法确认则省略。不从创作文本猜来源,不附带版本、会话 ID、用户信息或 prompt,不向用户询问,不改写 `--message`。上传、查询、下载不附加此参数;旧 CLI 的 `--help` 不支持时省略,不为统计字段重提任务。
|
|
50
61
|
|
|
@@ -22,6 +22,25 @@ metadata:
|
|
|
22
22
|
|
|
23
23
|
先按 [接口契约](references/api.md) 准备请求;需要完整执行示例时读 [商品图到营销视频](examples/product-video.md)。
|
|
24
24
|
|
|
25
|
+
## Space Selection
|
|
26
|
+
|
|
27
|
+
业务命令支持个人和团队空间,复用同一个 CLI 登录凭据。执行前先检查 `pippit-tool-cli space --help` 和业务命令的 `--team-id`;旧版不支持时先更新 CLI,不能忽略团队选择后继续收费操作。
|
|
28
|
+
|
|
29
|
+
- `space list` 统一输出 JSON,列出个人和当前账号的团队;顶层 `current` 表示已保存的默认空间,列表项 `current: true` 标记当前选择,无法识别本地账号时顶层为 `null`。`team_id` 保留字符串,`0` 为个人。列表中的团队不代表有生成权限、有效订阅或可用积分,最终由服务端判断。
|
|
30
|
+
- 用户明确要求长期切换时,运行 `space use TEAM_ID`;`space use 0` 保存个人空间。`use` 必须指定 ID,成功后返回保存的空间,不等待交互输入。只为本次任务选择空间时使用 `--team-id TEAM_ID`,不修改用户默认值;不能按模糊名称或剩余积分自动选团队。
|
|
31
|
+
- 新创作未指定空间时,读取并沿用保存的默认空间,首次使用为个人。向用户说明本次空间与积分账户,不重复索要已有授权。一次工作流开始后保存 TeamID,分开的上传、提交、查询和余额命令均显式传相同 `--team-id`,不能中途重新读取默认值。
|
|
32
|
+
- 已有任务优先沿用提交结果中的 `team_id` 或 CLI 的任务记录;切换默认团队后可以直接按原 thread/run 查询,CLI 自动恢复原空间。不要把当前默认 TeamID 强加给旧任务。跨设备或旧任务没有记录时,使用已知原空间;未知且用户也未指定时先核实,不能轮流试团队。
|
|
33
|
+
- 保存上传结果的资产 ID 和 `team_id`,提交结果的 thread/run ID 和 `team_id`。跨空间资产或任务冲突时停止;无权限、会员失效或余额不足不回退个人或其它团队。提交成功但本地记录写入失败时,保留返回 ID,用原 `--team-id` 查询,不重新生成。
|
|
34
|
+
- CLI 向同源 API 统一透传当前团队的 TeamID;模型接口内部继续按个人身份查询;模型列表成功不代表团队有生成权限。Canvas 同样使用所选空间;操作已有画布时沿用该画布原空间,切换默认空间后仍显式传入原 `--team-id`。
|
|
35
|
+
|
|
36
|
+
营销脚本的 `upload / generate / query / balance` 都支持 `--team-id TEAM_ID`。预览也读取同一空间;`query --wait` 固定 TeamID,本地账号可识别时同时固定账号和 API origin,切换账号则停止。无本地账号记录时,显式传入原 `--team-id` 仍可轮询,由 API 按当前凭据鉴权;不能读取默认值代替原任务空间。不要手工构造内部 `space_context`;使用原任务查询命令恢复。不同脚本调用之间仍需由宿主保留任务原 TeamID。
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
node scripts/marketing.js upload --file /path/to/product.png --team-id TEAM_ID
|
|
40
|
+
node scripts/marketing.js generate --request /path/to/request.json --execute --team-id TEAM_ID
|
|
41
|
+
node scripts/marketing.js query --thread-id THREAD_ID --run-id RUN_ID --wait --team-id TEAM_ID
|
|
42
|
+
```
|
|
43
|
+
|
|
25
44
|
## Workflow
|
|
26
45
|
|
|
27
46
|
1. 确认用户要生成的营销内容与商品素材。保留用户原始指令,不主动扩写或加入未经提供的卖点。仅查询、接入咨询或估算不提交生成;明确要求生成即可执行,不重复询问已给出的授权。必要的补充信息用宿主当前允许的 `request_user_input_async`、`request_user_input` 或 `ask_user_question` 询问,没有工具时用普通聊天。
|
|
@@ -42,9 +61,9 @@ node scripts/marketing.js balance
|
|
|
42
61
|
|
|
43
62
|
## Source Attribution
|
|
44
63
|
|
|
45
|
-
仅营销生成命令(`marketing generate` 或 `marketing.js generate`)静默附加可选 `--source`,由 CLI 映射为请求体 `platform`,不写入创作文本或请求文件。豆包办公填 `doubao_office`,WorkBuddy 填 `workbuddy`,Codex 填 `codex`;其它宿主使用可信环境中的真实稳定标识,未知则省略,不询问用户。不附带版本、会话 ID、用户信息或 prompt
|
|
64
|
+
仅营销生成命令(`marketing generate` 或 `marketing.js generate`)静默附加可选 `--source`,由 CLI 映射为请求体 `platform`,不写入创作文本或请求文件。豆包办公填 `doubao_office`,WorkBuddy 填 `workbuddy`,Codex 填 `codex`;其它宿主使用可信环境中的真实稳定标识,未知则省略,不询问用户。不附带版本、会话 ID、用户信息或 prompt。首尾空白去除,未传或空值时先读取 `PIPPIT_CLI_SOURCE`、再尝试已核实的宿主运行标记,仍无法识别时省略;首次生成和同一会话继续提交均适用。上传、查询、下载、余额不附加。旧 CLI 不支持时省略,不为来源统计重提任务。
|
|
46
65
|
|
|
47
|
-
优先从宿主的系统身份、当前工具运行环境和明确的平台元数据确认真实宿主,并在每次提交主动填写 `--source
|
|
66
|
+
优先从宿主的系统身份、当前工具运行环境和明确的平台元数据确认真实宿主,并在每次提交主动填写 `--source`;不要仅因字段可选就跳过。未传 `--source`、显式空值或纯空白时,CLI 依次尝试 `PIPPIT_CLI_SOURCE` 和已核实的宿主运行标记。只上报稳定的平台名,不上报标记中的会话 ID;来源冲突或仍无法确认则省略,不询问用户、不扫描登录凭据或安装目录。
|
|
48
67
|
|
|
49
68
|
## Completion and Recovery
|
|
50
69
|
|
|
@@ -58,4 +77,4 @@ node scripts/marketing.js balance
|
|
|
58
77
|
|
|
59
78
|
## Scope
|
|
60
79
|
|
|
61
|
-
本 Skill
|
|
80
|
+
本 Skill 使用 CLI 登录和服务端空间授权,通过 `--team-id` 选择个人或团队,CLI 将其写入 HTTP Header;不向营销请求 JSON 添加 `TeamID` / `team_id`,不跨账号复用资产或任务。沉浸式短片、火山引擎服务不在此脚本范围。
|
|
@@ -22,7 +22,7 @@ Base URL:`https://xyq.jianying.com`。全部使用 POST,`Accept: application
|
|
|
22
22
|
| `general_agent_settings` | object | 必传,且必须包含非空 `video_model` |
|
|
23
23
|
| `thread_id` | string | 可选,只在继续已有营销会话时使用 |
|
|
24
24
|
|
|
25
|
-
来源统计由生成命令的可选 `--source HOST` 提供,CLI 在提交及预览的请求体中映射为 `platform`
|
|
25
|
+
来源统计由生成命令的可选 `--source HOST` 提供,CLI 在提交及预览的请求体中映射为 `platform` 字符串;不传或去除首尾空白后为空时,依次尝试 `PIPPIT_CLI_SOURCE` 和已核实的宿主运行标记,仍未知则省略。请求文件仍只填写上表中的创作字段,不直接添加 `source` 或 `platform`。
|
|
26
26
|
|
|
27
27
|
设置块必须传对象,且 `video_model` 必填。2026-09-22 实际调用及服务端 `validateMarketingGeneralAgentSettings` 均确认:`{}` 或仅有比例/时长会返回 `ret=2`、缺少 `video_model`。不再按文档中的默认策略说明发送空对象;预览和提交都先本地校验。用户未指定模型时先询问,已有明确选择授权时按授权选择,不隐式降级。
|
|
28
28
|
|
|
@@ -63,3 +63,9 @@ HTTP 成功还需检查 `ret` 为 `"0"` 或 `0`;失败保留 `errmsg` 和 `log
|
|
|
63
63
|
查询接口透传 Run 状态;仅在 3 时提取媒体,4/5 时提取失败原因,其它状态返回空媒体数组。因此 9 的空 `video_urls` 不能解释为生成失败。状态 3 也不保证有视频;以真实媒体列表判断交付。未知数值保留原始响应并停止,不自动重提或无期限轮询。
|
|
64
64
|
|
|
65
65
|
一条 thread 可以有多个 Run。问卷、达人图或工具执行确认会产生后续 Run,原 `run_id` 不会自动变成最新运行。确认后从网页或可用的 `pippit-tool-cli get-thread --thread-id THREAD_ID` 读取新的真实 run_id,再传给营销查询命令;不要猜 ID 或为查询创建新 Run。
|
|
66
|
+
|
|
67
|
+
## Space Context
|
|
68
|
+
|
|
69
|
+
原生 CLI 和 Skill 脚本均支持 `--team-id TEAM_ID`;`0` 为个人,正整数为团队。空间通过 API origin 上的 `X-Team-Id` Header 传递,保持本文件中的 JSON 请求字段不变。非 Canvas 业务默认使用 `space use` 保存的空间;旧任务按本地记录恢复原空间。团队列表为 `space list`,由 CLI 使用同一 AK 调用 `/api/biz/v1/skill/get_lite_team_list`,不直连 commerce Web 接口。
|
|
70
|
+
|
|
71
|
+
营销响应增加 `team_id` 和供脚本固定轮询使用的 `space_context`;无可用本地账号记录时 `space_context` 为空,脚本通过显式 `--team-id` 固定轮询空间。二者只是本地路由信息,不能替代服务端鉴权。预览增加 `team_id`,不发送生成请求。权限或积分错误不自动切空间重试。
|
|
@@ -113,15 +113,29 @@ function invokeCLI(args, input, timeout, invocation = resolveCLI()) {
|
|
|
113
113
|
clearTimeout(timer);
|
|
114
114
|
if (failure) return reject(failure);
|
|
115
115
|
if (code !== 0) return reject(new Error(`CLI 调用失败:${stderr.trim() || `退出码 ${code}`};请求未重试。若不支持 marketing 命令,请更新 CLI。`));
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
catch (_) { reject(new Error('CLI 未返回有效 JSON;提交结果可能不明确,请勿自动重提')); }
|
|
116
|
+
let result;
|
|
117
|
+
try { result = args.includes('--help') ? stdout : JSON.parse(stdout); }
|
|
118
|
+
catch (_) { return reject(new Error('CLI 未返回有效 JSON;提交结果可能不明确,请勿自动重提')); }
|
|
119
|
+
// Relay successful update and space notices; backend logs stay buffered.
|
|
120
|
+
for (const notice of stderr.split(/\r?\n/)) {
|
|
121
|
+
if (notice.startsWith('[pippit-tool-cli] New version available:') ||
|
|
122
|
+
notice === '[pippit-tool-cli] If browser authorization fails, update the CLI and run pippit-tool-cli login again.' ||
|
|
123
|
+
notice.startsWith('本次空间:') ||
|
|
124
|
+
notice.startsWith('请求已成功,但空间记录保存失败:') ||
|
|
125
|
+
notice === '本地空间记录不可用,将使用显式 --team-id;本次不保存空间记录。') {
|
|
126
|
+
process.stderr.write(`${notice}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
resolve(result);
|
|
119
130
|
});
|
|
120
131
|
child.stdin.end(input);
|
|
121
132
|
});
|
|
122
133
|
}
|
|
123
134
|
|
|
124
|
-
function createClient({ request = https.request, timeout = 60000, invoke = invokeCLI } = {}) {
|
|
135
|
+
function createClient({ request = https.request, timeout = 60000, invoke = invokeCLI, teamID, spaceContext } = {}) {
|
|
136
|
+
const scopeArgs = [];
|
|
137
|
+
if (teamID !== undefined) scopeArgs.push('--team-id', normalizeTeamID(teamID));
|
|
138
|
+
if (spaceContext) scopeArgs.push('--space-context', spaceContext);
|
|
125
139
|
function open(url, method, headers, body) {
|
|
126
140
|
return new Promise((resolve, reject) => {
|
|
127
141
|
const target = new URL(url);
|
|
@@ -142,13 +156,14 @@ function createClient({ request = https.request, timeout = 60000, invoke = invok
|
|
|
142
156
|
});
|
|
143
157
|
}
|
|
144
158
|
|
|
145
|
-
async function api(action, body, source) {
|
|
159
|
+
async function api(action, body, source, execute = true) {
|
|
146
160
|
requireValue(['generate', 'query', 'balance'].includes(action), '无效营销 API 操作');
|
|
147
|
-
const args = ['marketing', action, '--timeout', `${timeout}ms
|
|
161
|
+
const args = ['marketing', action, '--timeout', `${timeout}ms`, ...scopeArgs];
|
|
148
162
|
let input;
|
|
149
163
|
if (action === 'generate') {
|
|
150
164
|
validate(body);
|
|
151
|
-
args.push('--request', '-'
|
|
165
|
+
args.push('--request', '-');
|
|
166
|
+
if (execute) args.push('--execute');
|
|
152
167
|
if (source !== undefined) {
|
|
153
168
|
// Probe before submitting; optional attribution must not break older CLIs.
|
|
154
169
|
let help = '';
|
|
@@ -158,13 +173,14 @@ function createClient({ request = https.request, timeout = 60000, invoke = invok
|
|
|
158
173
|
input = JSON.stringify(body);
|
|
159
174
|
}
|
|
160
175
|
if (action === 'query') args.push('--thread-id', body.thread_id, '--run-id', body.run_id);
|
|
161
|
-
|
|
176
|
+
const result = await invoke(args, input, timeout);
|
|
177
|
+
return action === 'generate' && !execute ? result : checkResponse(action, result, body);
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
async function upload(file) {
|
|
165
181
|
const stat = await fs.promises.stat(file);
|
|
166
182
|
requireValue(stat.isFile() && stat.size > 0 && stat.size < 500000000, '上传需要非空文件且小于 500 MB');
|
|
167
|
-
return checkResponse('upload', await invoke(['marketing', 'upload', '--file', path.resolve(file), '--timeout', `${timeout}ms
|
|
183
|
+
return checkResponse('upload', await invoke(['marketing', 'upload', '--file', path.resolve(file), '--timeout', `${timeout}ms`, ...scopeArgs], undefined, timeout));
|
|
168
184
|
}
|
|
169
185
|
|
|
170
186
|
async function download(url, destination) {
|
|
@@ -205,7 +221,8 @@ const HELP = `小云雀营销 Skill(Node.js >= 16)
|
|
|
205
221
|
node marketing.js query --thread-id ID --run-id ID [--wait] [--max-wait 900] [--output-dir DIR]
|
|
206
222
|
node marketing.js balance
|
|
207
223
|
所有 API 调用复用 pippit-tool-cli 登录态;未登录先运行 pippit-tool-cli login,无需提供 access_token。generate 默认仅预览。
|
|
208
|
-
--source HOST:仅生成提交使用,映射为请求体 platform
|
|
224
|
+
--source HOST:仅生成提交使用,映射为请求体 platform;未传或空值时尝试环境识别,来源未知则省略。
|
|
225
|
+
--team-id ID:本次使用团队;0 为个人,不改变 space use 保存的默认空间。轮询固定使用原任务空间。
|
|
209
226
|
--timeout 秒数:单请求总时限,默认 60;--max-wait:轮询总时限,默认 900。
|
|
210
227
|
query 输出 API 原始响应;有 --output-dir 时成功结果附带 downloaded_files。
|
|
211
228
|
退出码:0 成功/单次查询进行中;1 输入或接口错误;2 生成失败/取消/无视频;3 等待超时;4 等待用户交互;5 未知或未指定状态。
|
|
@@ -213,12 +230,14 @@ query 输出 API 原始响应;有 --output-dir 时成功结果附带 downloade
|
|
|
213
230
|
|
|
214
231
|
// Keep runtime marker semantics aligned with internal/common/host_source.go.
|
|
215
232
|
function resolveHostSource(explicit, env = process.env) {
|
|
216
|
-
if (explicit !== undefined) return explicit.trim();
|
|
233
|
+
if (explicit !== undefined && explicit.trim()) return explicit.trim();
|
|
217
234
|
if ((env.PIPPIT_CLI_SOURCE || '').trim()) return env.PIPPIT_CLI_SOURCE.trim();
|
|
218
235
|
const hosts = new Set();
|
|
219
236
|
for (const [key, host] of [
|
|
220
237
|
['CODEX_THREAD_ID', 'codex'], ['CODEX_SESSION_ID', 'codex'],
|
|
221
238
|
['CLAUDECODE', 'claude_code'], ['CURSOR_AGENT', 'cursor'], ['GEMINI_CLI', 'gemini_cli'],
|
|
239
|
+
['OPENCODE', 'opencode'], ['QWEN_CODE', 'qwen_code'], ['CLINE_ACTIVE', 'cline'],
|
|
240
|
+
['COPILOT_CLI', 'github_copilot_cli'], ['COPILOT_AGENT_SESSION_ID', 'github_copilot_cli'],
|
|
222
241
|
]) {
|
|
223
242
|
const value = (env[key] || '').trim().toLowerCase();
|
|
224
243
|
if (value && value !== '0' && value !== 'false') hosts.add(host);
|
|
@@ -226,12 +245,18 @@ function resolveHostSource(explicit, env = process.env) {
|
|
|
226
245
|
return hosts.size === 1 ? [...hosts][0] : '';
|
|
227
246
|
}
|
|
228
247
|
|
|
248
|
+
function normalizeTeamID(value) {
|
|
249
|
+
requireValue(typeof value === 'string' && /^\d+$/.test(value), 'team-id 必须为 0 或正整数');
|
|
250
|
+
requireValue(BigInt(value) <= 9223372036854775807n, 'team-id 超出 int64 范围');
|
|
251
|
+
return BigInt(value).toString();
|
|
252
|
+
}
|
|
253
|
+
|
|
229
254
|
function parseArgs(argv) {
|
|
230
255
|
const [action, ...rest] = argv;
|
|
231
256
|
requireValue(Object.hasOwnProperty.call(PATHS, action), '操作必须是 generate/upload/query/balance');
|
|
232
257
|
const options = {};
|
|
233
258
|
const flags = ['execute', 'dry-run', 'wait'];
|
|
234
|
-
const allowed = { generate: ['request', 'execute', 'dry-run', 'source'], upload: ['file'], query: ['thread-id', 'run-id', 'wait', 'max-wait', 'output-dir'], balance: [] }[action].concat('timeout');
|
|
259
|
+
const allowed = { generate: ['request', 'execute', 'dry-run', 'source'], upload: ['file'], query: ['thread-id', 'run-id', 'wait', 'max-wait', 'output-dir'], balance: [] }[action].concat('timeout', 'team-id');
|
|
235
260
|
for (let i = 0; i < rest.length; i++) {
|
|
236
261
|
const name = rest[i].replace(/^--/, '');
|
|
237
262
|
requireValue(rest[i].startsWith('--') && allowed.includes(name) && !(name in options), `无效或重复参数:${rest[i]}`);
|
|
@@ -241,6 +266,7 @@ function parseArgs(argv) {
|
|
|
241
266
|
options[name] = rest[++i];
|
|
242
267
|
}
|
|
243
268
|
}
|
|
269
|
+
if (options['team-id'] !== undefined) options['team-id'] = normalizeTeamID(options['team-id']);
|
|
244
270
|
requireValue(!(options.execute && options['dry-run']), '--execute 和 --dry-run 不能同时使用');
|
|
245
271
|
for (const [name, fallback, max] of [['timeout', 60, 1800], ['max-wait', 900, 86400]]) {
|
|
246
272
|
const value = options[name] === undefined ? fallback : Number(options[name]);
|
|
@@ -259,26 +285,32 @@ async function main(argv, { out = console.log, clientFactory = createClient, now
|
|
|
259
285
|
if (options.source !== undefined || source) options.source = source;
|
|
260
286
|
requireValue(options.request, 'generate 需要 --request JSON 文件');
|
|
261
287
|
body = validate(JSON.parse(await fs.promises.readFile(options.request, 'utf8')));
|
|
262
|
-
if (!options.execute) {
|
|
263
|
-
const source = (options.source || '').trim();
|
|
264
|
-
out(JSON.stringify({ dry_run: true, url: BASE + PATHS.generate, body: source ? { ...body, platform: source } : body }));
|
|
265
|
-
return 0;
|
|
266
|
-
}
|
|
267
288
|
}
|
|
268
289
|
if (action === 'query') {
|
|
269
290
|
requireValue(nonempty(options['thread-id']) && nonempty(options['run-id']), 'query 需要 thread-id 和 run-id');
|
|
270
291
|
body = { thread_id: options['thread-id'], run_id: options['run-id'] };
|
|
271
292
|
}
|
|
272
293
|
if (action === 'upload') requireValue(nonempty(options.file), 'upload 需要 --file');
|
|
273
|
-
const client = clientFactory({ timeout: options.timeout * 1000 });
|
|
294
|
+
const client = clientFactory({ timeout: options.timeout * 1000, teamID: options['team-id'] });
|
|
295
|
+
if (action === 'generate' && !options.execute) {
|
|
296
|
+
out(JSON.stringify(await client.api(action, body, options.source, false)));
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
let fixedScope;
|
|
300
|
+
let fixedTeamID = options['team-id'];
|
|
274
301
|
let result;
|
|
275
302
|
const deadline = now() + options['max-wait'] * 1000;
|
|
276
303
|
do {
|
|
277
304
|
// A poll request cannot overrun the remaining wait budget.
|
|
278
305
|
const pollClient = action === 'query' && options.wait
|
|
279
|
-
? clientFactory({ timeout: Math.min(options.timeout * 1000, Math.max(1, deadline - now())) }) : client;
|
|
306
|
+
? clientFactory({ timeout: Math.min(options.timeout * 1000, Math.max(1, deadline - now())), teamID: fixedTeamID, spaceContext: fixedScope }) : client;
|
|
280
307
|
result = action === 'upload' ? await client.upload(options.file) : await pollClient.api(action, body, options.source);
|
|
308
|
+
if (action === 'query' && options.wait && fixedTeamID !== undefined) requireValue(result.team_id === fixedTeamID, '任务空间发生变化,已停止轮询');
|
|
309
|
+
if (action === 'query' && options.wait && fixedScope) requireValue(result.space_context === fixedScope, '任务空间或账号发生变化,已停止轮询');
|
|
281
310
|
if (action !== 'query' || !options.wait || !WAIT_STATES.includes(String(result.data.run_state))) break;
|
|
311
|
+
requireValue(nonempty(result.team_id) && (nonempty(result.space_context) || fixedTeamID !== undefined), 'CLI 未返回固定空间信息,已停止轮询;请使用原 --team-id 查询同一任务,不要重提');
|
|
312
|
+
fixedScope = result.space_context;
|
|
313
|
+
fixedTeamID = result.team_id;
|
|
282
314
|
if (now() >= deadline) {
|
|
283
315
|
out(JSON.stringify({ ...result, wait_timed_out: true })); return 3;
|
|
284
316
|
}
|