siluzan-tso-cli 1.1.22-beta.14 → 1.1.22-beta.15
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 +1 -1
- package/dist/index.js +70 -9
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/pmax-create-template.md +1 -1
- package/dist/skill/references/google-ads/google-ads.md +1 -1
- package/dist/skill/references/google-ads/pmax-api.md +4 -2
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
51
51
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
> **注意**:当前为测试版(1.1.22-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.22-beta.15),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -114697,6 +114697,26 @@ async function uploadPmaxLocalVideo(opts) {
|
|
|
114697
114697
|
}
|
|
114698
114698
|
);
|
|
114699
114699
|
}
|
|
114700
|
+
function resolvePmaxVideoAbsPath(relOrAbs, baseDir = process.cwd()) {
|
|
114701
|
+
const trimmed = relOrAbs.trim();
|
|
114702
|
+
if (isAbsolute5(trimmed)) return trimmed;
|
|
114703
|
+
return resolve9(baseDir, trimmed);
|
|
114704
|
+
}
|
|
114705
|
+
function validatePmaxVideoPathQuick(absPath) {
|
|
114706
|
+
if (!VIDEO_UPLOAD_SUFFIX.test(absPath)) {
|
|
114707
|
+
return `videoPath \u6269\u5C55\u540D\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u4F7F\u7528 .mp4 / .mov / .webm / .avi / .mpeg / .mpg`;
|
|
114708
|
+
}
|
|
114709
|
+
if (!existsSync3(absPath)) {
|
|
114710
|
+
return `\u89C6\u9891\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${absPath}`;
|
|
114711
|
+
}
|
|
114712
|
+
try {
|
|
114713
|
+
if (readFileSync8(absPath).length === 0) return `\u89C6\u9891\u6587\u4EF6\u4E3A\u7A7A\uFF1A${absPath}`;
|
|
114714
|
+
} catch (e) {
|
|
114715
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
114716
|
+
return `\u65E0\u6CD5\u8BFB\u53D6\u89C6\u9891\u6587\u4EF6\uFF08${absPath}\uFF09\uFF1A${msg}`;
|
|
114717
|
+
}
|
|
114718
|
+
return null;
|
|
114719
|
+
}
|
|
114700
114720
|
|
|
114701
114721
|
// src/commands/ad/pmax-create.ts
|
|
114702
114722
|
async function runAdPmaxCreate(opts) {
|
|
@@ -115281,12 +115301,35 @@ async function runAdPmaxAssetsUpdate(opts) {
|
|
|
115281
115301
|
async function runAdPmaxYoutubeLink(opts) {
|
|
115282
115302
|
const accountId = requireAccountId(opts.account);
|
|
115283
115303
|
const assetGroupId = opts.assetGroupId.trim();
|
|
115304
|
+
const youtubeInput = opts.youtubeUrlOrId?.trim();
|
|
115305
|
+
const videoPathInput = opts.videoPath?.trim();
|
|
115306
|
+
if (!opts.bodyFile) {
|
|
115307
|
+
if (youtubeInput && videoPathInput) {
|
|
115308
|
+
console.error("\n\u274C --youtube \u4E0E --video-path \u53EA\u80FD\u4E8C\u9009\u4E00\n");
|
|
115309
|
+
process.exit(1);
|
|
115310
|
+
}
|
|
115311
|
+
if (!youtubeInput && !videoPathInput) {
|
|
115312
|
+
console.error("\n\u274C \u8BF7\u6307\u5B9A --youtube\u3001--video-path \u6216 --body-file\n");
|
|
115313
|
+
process.exit(1);
|
|
115314
|
+
}
|
|
115315
|
+
if (videoPathInput) {
|
|
115316
|
+
const pathErr = validatePmaxVideoPathQuick(resolvePmaxVideoAbsPath(videoPathInput));
|
|
115317
|
+
if (pathErr) {
|
|
115318
|
+
console.error(`
|
|
115319
|
+
\u274C ${pathErr}
|
|
115320
|
+
`);
|
|
115321
|
+
process.exit(1);
|
|
115322
|
+
}
|
|
115323
|
+
}
|
|
115324
|
+
}
|
|
115284
115325
|
await withGoogleApi(opts, async (config, googleApiUrl) => {
|
|
115285
115326
|
let data;
|
|
115327
|
+
let linkedTarget = youtubeInput;
|
|
115286
115328
|
try {
|
|
115287
115329
|
if (opts.bodyFile) {
|
|
115288
115330
|
const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "youtube");
|
|
115289
115331
|
const body = loadPmaxJsonFile(opts.bodyFile);
|
|
115332
|
+
linkedTarget = typeof body["youtubeUrlOrId"] === "string" ? body["youtubeUrlOrId"].trim() : linkedTarget;
|
|
115290
115333
|
const raw = await pmaxApiFetch(
|
|
115291
115334
|
url,
|
|
115292
115335
|
config,
|
|
@@ -115295,12 +115338,28 @@ async function runAdPmaxYoutubeLink(opts) {
|
|
|
115295
115338
|
);
|
|
115296
115339
|
data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
|
|
115297
115340
|
} else {
|
|
115341
|
+
if (videoPathInput) {
|
|
115342
|
+
const absVideo = resolvePmaxVideoAbsPath(videoPathInput);
|
|
115343
|
+
if (!opts.jsonOut) console.log(`
|
|
115344
|
+
\u{1F4E4} \u4E0A\u4F20\u672C\u5730\u89C6\u9891\uFF08PyAPI\uFF09\u2026`);
|
|
115345
|
+
linkedTarget = await uploadPmaxLocalVideo({
|
|
115346
|
+
config,
|
|
115347
|
+
googleApiUrl,
|
|
115348
|
+
accountId,
|
|
115349
|
+
absVideoPath: absVideo,
|
|
115350
|
+
title: opts.videoTitle,
|
|
115351
|
+
description: opts.videoDescription,
|
|
115352
|
+
verbose: opts.verbose,
|
|
115353
|
+
onProgress: opts.jsonOut ? void 0 : (msg) => console.log(` ${msg}`)
|
|
115354
|
+
});
|
|
115355
|
+
if (!opts.jsonOut) console.log(` video_id\uFF1A${linkedTarget}`);
|
|
115356
|
+
}
|
|
115298
115357
|
data = await postPmaxYoutubeLink({
|
|
115299
115358
|
config,
|
|
115300
115359
|
googleApiUrl,
|
|
115301
115360
|
accountId,
|
|
115302
115361
|
assetGroupId,
|
|
115303
|
-
youtubeUrlOrId:
|
|
115362
|
+
youtubeUrlOrId: linkedTarget,
|
|
115304
115363
|
campaignId: opts.campaignId,
|
|
115305
115364
|
assetName: opts.assetName,
|
|
115306
115365
|
verbose: opts.verbose
|
|
@@ -115309,7 +115368,7 @@ async function runAdPmaxYoutubeLink(opts) {
|
|
|
115309
115368
|
} catch (err) {
|
|
115310
115369
|
console.error(
|
|
115311
115370
|
`
|
|
115312
|
-
\u274C \u94FE\u63A5 YouTube \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
115371
|
+
\u274C \u94FE\u63A5/\u66FF\u6362 YouTube \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
115313
115372
|
`
|
|
115314
115373
|
);
|
|
115315
115374
|
process.exit(1);
|
|
@@ -115324,7 +115383,8 @@ async function runAdPmaxYoutubeLink(opts) {
|
|
|
115324
115383
|
return;
|
|
115325
115384
|
}
|
|
115326
115385
|
console.log(`
|
|
115327
|
-
\u2705 \u5DF2\u94FE\u63A5 YouTube \u5230\u8D44\u4EA7\u7EC4 ${assetGroupId}`);
|
|
115386
|
+
\u2705 \u5DF2\u94FE\u63A5/\u66FF\u6362 YouTube \u5230\u8D44\u4EA7\u7EC4 ${assetGroupId}`);
|
|
115387
|
+
if (linkedTarget) console.log(` youtube\uFF1A${linkedTarget}`);
|
|
115328
115388
|
console.log(` assetResourceName\uFF1A${data["assetResourceName"] ?? "\u2014"}
|
|
115329
115389
|
`);
|
|
115330
115390
|
});
|
|
@@ -116269,18 +116329,19 @@ function register20(program2) {
|
|
|
116269
116329
|
}
|
|
116270
116330
|
);
|
|
116271
116331
|
addPmaxJsonOptions(
|
|
116272
|
-
adCmd.command("pmax-youtube-link").description(
|
|
116332
|
+
adCmd.command("pmax-youtube-link").description(
|
|
116333
|
+
"\u4E3A\u8D44\u4EA7\u7EC4\u94FE\u63A5\u6216\u66FF\u6362 YouTube \u89C6\u9891\uFF08POST .../youtube\uFF1B\u4F1A\u5148\u79FB\u9664\u5DF2\u6709 YOUTUBE_VIDEO \u518D\u6302\u65B0\u7247\uFF09\n --youtube\uFF1A\u5DF2\u6709 URL/ID\uFF1B--video-path\uFF1A\u672C\u5730\u6587\u4EF6\u7ECF PyAPI \u4E0A\u4F20\u540E\u94FE\u63A5"
|
|
116334
|
+
).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--asset-group-id <id>", "\u8D44\u4EA7\u7EC4 ID").option("--campaign-id <id>", "\u6240\u5C5E\u6D3B\u52A8 ID\uFF08\u5EFA\u8BAE\u4F20\uFF09").option("--youtube <urlOrId>", "YouTube URL \u6216 11 \u4F4D\u89C6\u9891 ID\uFF08\u4E0E --video-path \u4E8C\u9009\u4E00\uFF09").option("--video-path <path>", "\u672C\u5730\u89C6\u9891\u8DEF\u5F84\uFF08PyAPI \u4E0A\u4F20\uFF1B\u4E0E --youtube \u4E8C\u9009\u4E00\uFF09").option("--video-title <title>", "PyAPI \u4E0A\u4F20\u6807\u9898\uFF08\u9ED8\u8BA4\u6587\u4EF6\u540D\uFF09").option("--video-description <text>", "PyAPI \u4E0A\u4F20\u63CF\u8FF0").option("--asset-name <name>", "\u8D44\u4EA7\u663E\u793A\u540D").option("--body-file <path>", "\u5B8C\u6574 JSON body\uFF08\u542B youtubeUrlOrId\uFF1B\u8986\u76D6 --youtube/--video-path\uFF09").option("-t, --token <token>", "Auth Token")
|
|
116273
116335
|
).action(
|
|
116274
116336
|
async (opts) => {
|
|
116275
|
-
if (!opts.bodyFile && !opts.youtube?.trim()) {
|
|
116276
|
-
console.error("\n\u274C \u8BF7\u6307\u5B9A --youtube \u6216 --body-file\n");
|
|
116277
|
-
process.exit(1);
|
|
116278
|
-
}
|
|
116279
116337
|
await runAdPmaxYoutubeLink({
|
|
116280
116338
|
account: opts.account,
|
|
116281
116339
|
assetGroupId: opts.assetGroupId,
|
|
116282
116340
|
campaignId: opts.campaignId,
|
|
116283
|
-
youtubeUrlOrId: opts.youtube
|
|
116341
|
+
youtubeUrlOrId: opts.youtube,
|
|
116342
|
+
videoPath: opts.videoPath,
|
|
116343
|
+
videoTitle: opts.videoTitle,
|
|
116344
|
+
videoDescription: opts.videoDescription,
|
|
116284
116345
|
assetName: opts.assetName,
|
|
116285
116346
|
bodyFile: opts.bodyFile,
|
|
116286
116347
|
token: opts.token,
|
package/dist/skill/_meta.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
| 文案超长 | `pmax-validate --json-out` 读 `lengthViolations`(含完整 `text`);**勿自动截断**,列改写方案给用户确认后再改 JSON 并重跑 validate(同 Search `campaign-validate`) |
|
|
17
17
|
| 金额 | JSON 填**主币种「元」**;CLI 提交前 `budget`、`targetCpa_BidingAmount` ×100 |
|
|
18
18
|
| 图片 | **只填 `imagePaths`** 指向本地 PNG/JPEG;`pmax-create` 会自动 multipart 上传并用 assetId 创建(勿把 Base64 提交进 Git) |
|
|
19
|
-
| 视频 |
|
|
19
|
+
| 视频 | 创建:`videoPath` / `youtubeUrlOrId`(二选一,`pmax-create` 后自动链接)。**换片**:`ad pmax-youtube-link --youtube …` 或 `--video-path …`(会替换资产组内已有 YouTube 视频) |
|
|
20
20
|
| 改已上线 PMax | `ad pmax-get` / `pmax-edit` / `pmax-assets-update` 等(见 `references/google-ads/pmax-api.md`) |
|
|
21
21
|
| 列表复核 | `ad campaigns -a <id> --json-out ./snap`,`channelTypeV2` 应为 `PERFORMANCE_MAX` |
|
|
22
22
|
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
| 改资产组 | `ad pmax-asset-group-edit` |
|
|
63
63
|
| 删资产组 | `ad pmax-asset-group-edit --status REMOVED`(软删;网关无 DELETE 端点) |
|
|
64
64
|
| 改资产 | `ad pmax-assets-update --config-file …` |
|
|
65
|
-
| YouTube | `ad pmax-youtube-link` |
|
|
65
|
+
| YouTube 链接/替换 | `ad pmax-youtube-link`(`--youtube` 或 `--video-path`) |
|
|
66
66
|
| 信号 | `ad pmax-signals-get` / `ad pmax-signals-set`;受众下拉 `ad pmax-audiences` |
|
|
67
67
|
| 图片库 | `ad pmax-image-upload` |
|
|
68
68
|
| 报表 | `ad pmax-report-asset-groups` / `ad pmax-report-geo` |
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| `ad pmax-asset-group-create` | 新建资产组 |
|
|
24
24
|
| `ad pmax-asset-group-edit` | 编辑资产组 |
|
|
25
25
|
| `ad pmax-assets-update` | 更新资产 |
|
|
26
|
-
| `ad pmax-youtube-link` |
|
|
26
|
+
| `ad pmax-youtube-link` | 关联/替换 YouTube(会移除资产组内已有视频再挂新片) |
|
|
27
27
|
| `ad pmax-signals-get` / `ad pmax-signals-set` | 读/写信号(**全量** PUT) |
|
|
28
28
|
| `ad pmax-audiences` | 受众列表 |
|
|
29
29
|
| `ad pmax-image-upload` | 单张图片上传 |
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
|
|
43
43
|
- **`videoPath`**:本地 `.mp4` 等;`pmax-create` 经 **GoogleAdsPyAPI** `POST {googleApiUrl}/pyapi/video/upload` 上传,轮询 `GET .../upload/status` 得 `video_id`,再 `POST .../asset-group/{id}/youtube` 链接(与 `youtubeUrlOrId` 二选一)。
|
|
44
44
|
- **`youtubeUrlOrId`**:已有 YouTube URL 或 11 位 ID;创建成功后自动链接。
|
|
45
|
-
-
|
|
45
|
+
- **已创建系列换视频**:`ad pmax-youtube-link`(同一接口;后端先删旧 `YOUTUBE_VIDEO` 再链接新片):
|
|
46
|
+
- `--youtube "<url或ID>"` 或 `--video-path ./new.mp4`(可选 `--video-title`)
|
|
47
|
+
- 示例:`siluzan-tso ad pmax-youtube-link -a <id> --asset-group-id <agId> --campaign-id <cid> --video-path ./promo-v2.mp4`
|
|
46
48
|
|
|
47
49
|
---
|
|
48
50
|
|
|
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
$PKG_NAME = 'siluzan-tso-cli'
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
$PKG_VERSION = '1.1.22-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.22-beta.15'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
15
|
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
@@ -9,7 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
readonly PKG_VERSION="1.1.22-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.22-beta.15"
|
|
13
13
|
readonly CLI_BIN="siluzan-tso"
|
|
14
14
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
15
15
|
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|