siluzan-tso-cli 1.0.0-beta.4

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.
Files changed (46) hide show
  1. package/assets/siluzan-ads/SKILL.md +774 -0
  2. package/dist/commands/account-history.d.ts +13 -0
  3. package/dist/commands/account-history.js +87 -0
  4. package/dist/commands/account-manage.d.ts +71 -0
  5. package/dist/commands/account-manage.js +217 -0
  6. package/dist/commands/ad.d.ts +161 -0
  7. package/dist/commands/ad.js +486 -0
  8. package/dist/commands/ai-creation.d.ts +54 -0
  9. package/dist/commands/ai-creation.js +184 -0
  10. package/dist/commands/balance.d.ts +9 -0
  11. package/dist/commands/balance.js +68 -0
  12. package/dist/commands/clue.d.ts +16 -0
  13. package/dist/commands/clue.js +103 -0
  14. package/dist/commands/config.d.ts +17 -0
  15. package/dist/commands/config.js +122 -0
  16. package/dist/commands/forewarning.d.ts +78 -0
  17. package/dist/commands/forewarning.js +239 -0
  18. package/dist/commands/init.d.ts +10 -0
  19. package/dist/commands/init.js +141 -0
  20. package/dist/commands/invoice.d.ts +38 -0
  21. package/dist/commands/invoice.js +187 -0
  22. package/dist/commands/keyword.d.ts +14 -0
  23. package/dist/commands/keyword.js +125 -0
  24. package/dist/commands/list-accounts.d.ts +11 -0
  25. package/dist/commands/list-accounts.js +83 -0
  26. package/dist/commands/open-account.d.ts +205 -0
  27. package/dist/commands/open-account.js +456 -0
  28. package/dist/commands/optimize.d.ts +39 -0
  29. package/dist/commands/optimize.js +143 -0
  30. package/dist/commands/report.d.ts +55 -0
  31. package/dist/commands/report.js +274 -0
  32. package/dist/commands/stats.d.ts +13 -0
  33. package/dist/commands/stats.js +109 -0
  34. package/dist/commands/transfer.d.ts +13 -0
  35. package/dist/commands/transfer.js +52 -0
  36. package/dist/config/defaults.d.ts +6 -0
  37. package/dist/config/defaults.js +11 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +1161 -0
  40. package/dist/templates/load-templates.d.ts +4 -0
  41. package/dist/templates/load-templates.js +24 -0
  42. package/dist/types/ads.d.ts +126 -0
  43. package/dist/types/ads.js +4 -0
  44. package/dist/utils/auth.d.ts +37 -0
  45. package/dist/utils/auth.js +203 -0
  46. package/package.json +48 -0
@@ -0,0 +1,239 @@
1
+ import { loadConfig, apiFetch } from "../utils/auth.js";
2
+ const VALID_MEDIA_TYPES = ["Google", "TikTok"];
3
+ export async function runForewarningList(opts) {
4
+ if (!VALID_MEDIA_TYPES.includes(opts.media)) {
5
+ console.error(`\n❌ 不支持的媒体类型:${opts.media}\n 可选:${VALID_MEDIA_TYPES.join(" | ")}\n`);
6
+ process.exit(1);
7
+ }
8
+ const config = loadConfig(opts.token);
9
+ const params = new URLSearchParams();
10
+ // 后端使用 1-based PageIndex
11
+ params.set("PageIndex", String(opts.page ?? 1));
12
+ params.set("PageSize", String(opts.pageSize ?? 20));
13
+ if (opts.keyword)
14
+ params.set("Name", opts.keyword);
15
+ if (opts.account)
16
+ params.set("MediaAccountId", opts.account);
17
+ if (opts.startDate)
18
+ params.set("startDate", opts.startDate);
19
+ if (opts.endDate)
20
+ params.set("endDate", opts.endDate);
21
+ const url = `${config.apiBaseUrl}/query/smart-strategy/settings/${opts.media}?${params}`;
22
+ let data;
23
+ try {
24
+ data = await apiFetch(url, config, {}, opts.verbose);
25
+ }
26
+ catch (err) {
27
+ console.error(`\n❌ 查询失败:${err instanceof Error ? err.message : String(err)}\n`);
28
+ process.exit(1);
29
+ }
30
+ if (opts.json) {
31
+ console.log(JSON.stringify(data, null, 2));
32
+ return;
33
+ }
34
+ const items = data.results ?? [];
35
+ const total = data.totalResultCount ?? 0;
36
+ const page = opts.page ?? 1;
37
+ const pageSize = opts.pageSize ?? 20;
38
+ console.log(`\n智能预警规则(第 ${page}/${Math.ceil(total / pageSize)} 页,共 ${total} 条)\n`);
39
+ if (items.length === 0) {
40
+ console.log(" 暂无数据。\n");
41
+ return;
42
+ }
43
+ const header = [
44
+ "名称".padEnd(30),
45
+ "类型".padEnd(16),
46
+ "已停用".padEnd(8),
47
+ "ID",
48
+ ].join(" ");
49
+ console.log(" " + header);
50
+ console.log(" " + "-".repeat(header.length));
51
+ for (const item of items) {
52
+ console.log(" " + [
53
+ (item.data?.name ?? "").padEnd(30),
54
+ (item.data?.templateType ?? "").padEnd(16),
55
+ (item.stopped ? "是" : "否").padEnd(8),
56
+ item.entityId ?? "",
57
+ ].join(" "));
58
+ }
59
+ console.log();
60
+ }
61
+ export async function runForewarningRecords(opts) {
62
+ if (!VALID_MEDIA_TYPES.includes(opts.media)) {
63
+ console.error(`\n❌ 不支持的媒体类型:${opts.media}\n`);
64
+ process.exit(1);
65
+ }
66
+ const config = loadConfig(opts.token);
67
+ // ruleId 为空时传 "all" 表示查所有规则的记录
68
+ const ruleId = opts.ruleId ?? "all";
69
+ const params = new URLSearchParams();
70
+ // 后端使用 1-based PageIndex
71
+ params.set("PageIndex", String(opts.page ?? 1));
72
+ params.set("PageSize", String(opts.pageSize ?? 20));
73
+ if (opts.status)
74
+ params.set("ExecResult", opts.status);
75
+ if (opts.account)
76
+ params.set("MediaAccountId", opts.account);
77
+ if (opts.keyword)
78
+ params.set("Name", opts.keyword);
79
+ if (opts.startDate)
80
+ params.set("startDate", opts.startDate);
81
+ if (opts.endDate)
82
+ params.set("endDate", opts.endDate);
83
+ const url = `${config.apiBaseUrl}/query/smart-strategy/settings/${opts.media}/${ruleId}/trigger-historys?${params}`;
84
+ let data;
85
+ try {
86
+ data = await apiFetch(url, config, {}, opts.verbose);
87
+ }
88
+ catch (err) {
89
+ console.error(`\n❌ 查询失败:${err instanceof Error ? err.message : String(err)}\n`);
90
+ process.exit(1);
91
+ }
92
+ if (opts.json) {
93
+ console.log(JSON.stringify(data, null, 2));
94
+ return;
95
+ }
96
+ const items = data.results ?? [];
97
+ const total = data.totalResultCount ?? 0;
98
+ const page = opts.page ?? 1;
99
+ const pageSize = opts.pageSize ?? 20;
100
+ console.log(`\n预警记录(第 ${page}/${Math.ceil(total / pageSize)} 页,共 ${total} 条)\n`);
101
+ if (items.length === 0) {
102
+ console.log(" 暂无数据。\n");
103
+ return;
104
+ }
105
+ for (const item of items) {
106
+ const d = item.data ?? {};
107
+ const ruleName = d.settingData?.name ?? d.settingId ?? "—";
108
+ console.log(` [${d.execResult ?? "—"}] ${ruleName} 触发时间: ${d.execTime ?? "—"}`);
109
+ }
110
+ console.log();
111
+ }
112
+ export async function runForewarningStart(opts) {
113
+ const config = loadConfig(opts.token);
114
+ const url = `${config.apiBaseUrl}/command/smart-strategy/settings/${opts.media}/${opts.id}/start`;
115
+ try {
116
+ await apiFetch(url, config, { method: "POST", body: "null" }, opts.verbose);
117
+ }
118
+ catch (err) {
119
+ console.error(`\n❌ 启动失败:${err instanceof Error ? err.message : String(err)}\n`);
120
+ process.exit(1);
121
+ }
122
+ console.log("\n✅ 预警规则已启动\n");
123
+ }
124
+ export async function runForewarningStop(opts) {
125
+ const config = loadConfig(opts.token);
126
+ const url = `${config.apiBaseUrl}/command/smart-strategy/settings/${opts.media}/${opts.id}/stop`;
127
+ try {
128
+ await apiFetch(url, config, { method: "POST", body: "null" }, opts.verbose);
129
+ }
130
+ catch (err) {
131
+ console.error(`\n❌ 停止失败:${err instanceof Error ? err.message : String(err)}\n`);
132
+ process.exit(1);
133
+ }
134
+ console.log("\n✅ 预警规则已停止\n");
135
+ }
136
+ export async function runForewarningDelete(opts) {
137
+ const config = loadConfig(opts.token);
138
+ const url = `${config.apiBaseUrl}/command/smart-strategy/settings/${opts.media}/${opts.id}`;
139
+ try {
140
+ await apiFetch(url, config, { method: "DELETE" }, opts.verbose);
141
+ }
142
+ catch (err) {
143
+ console.error(`\n❌ 删除失败:${err instanceof Error ? err.message : String(err)}\n`);
144
+ process.exit(1);
145
+ }
146
+ console.log("\n✅ 预警规则已删除\n");
147
+ }
148
+ const WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
149
+ /** 将 ForewarningCreateOptions 组装成接口所需的 body */
150
+ function buildRuleBody(opts) {
151
+ const accountIds = opts.accounts.split(",").map((s) => s.trim()).filter(Boolean);
152
+ const notifyIds = opts.notify ? opts.notify.split(",").map((s) => s.trim()).filter(Boolean) : [];
153
+ return {
154
+ TemplateType: "Customization",
155
+ MediaType: opts.media,
156
+ Name: opts.name,
157
+ ExecScopeSetting: {
158
+ ScopeType: opts.scopeType ?? "Campaign",
159
+ MediaAccounts: accountIds,
160
+ MatchPattern: "Default",
161
+ WhiteList: [],
162
+ BlockedList: [],
163
+ },
164
+ ConditionSetting: {
165
+ MatchConditions: [
166
+ {
167
+ Field: opts.field,
168
+ ComparisonOperator: opts.operator,
169
+ CompareWith: "Fixed",
170
+ CompareValue: opts.value,
171
+ CompareDays: opts.days ?? 1,
172
+ CompareUnit: "Yuan",
173
+ },
174
+ ],
175
+ LogicOperator: "AND",
176
+ },
177
+ ExecActionSetting: {
178
+ Enabled: false,
179
+ ExecActions: [],
180
+ PeriodLimitType: "Day",
181
+ PeriodLimitTimes: 1,
182
+ },
183
+ ExecTimeSetting: {
184
+ TimeFrequency: opts.frequency ?? "QuarterHour",
185
+ DaysFrequency: WEEKDAYS,
186
+ BeginTime: "00:00",
187
+ EndTime: "23:59",
188
+ TimeZone: 8,
189
+ },
190
+ NotifySetting: {
191
+ NotifyBy: "MediaAccount",
192
+ Enabled: notifyIds.length > 0,
193
+ NotifyObjects: notifyIds,
194
+ },
195
+ };
196
+ }
197
+ /**
198
+ * 创建或更新预警规则。
199
+ * 不传 --id 时新建(POST),传 --id 时更新(PUT)。
200
+ */
201
+ export async function runForewarningCreate(opts) {
202
+ if (!VALID_MEDIA_TYPES.includes(opts.media)) {
203
+ console.error(`\n❌ 不支持的媒体类型:${opts.media}\n 可选:${VALID_MEDIA_TYPES.join(" | ")}\n`);
204
+ process.exit(1);
205
+ }
206
+ const VALID_OPERATORS = ["GREATER_EQUALS", "GREATER", "LESS_EQUALS", "LESS", "EQUALS"];
207
+ if (!VALID_OPERATORS.includes(opts.operator)) {
208
+ console.error(`\n❌ 无效的比较运算符:${opts.operator}\n 可选:${VALID_OPERATORS.join(" | ")}\n`);
209
+ process.exit(1);
210
+ }
211
+ const config = loadConfig(opts.token);
212
+ const base = `${config.apiBaseUrl}/command/smart-strategy/settings/${opts.media}`;
213
+ const isUpdate = Boolean(opts.id);
214
+ const url = isUpdate ? `${base}/${opts.id}` : base;
215
+ const method = isUpdate ? "PUT" : "POST";
216
+ const body = buildRuleBody(opts);
217
+ try {
218
+ await apiFetch(url, config, { method, body: JSON.stringify(body) }, opts.verbose);
219
+ }
220
+ catch (err) {
221
+ const action = isUpdate ? "更新" : "创建";
222
+ console.error(`\n❌ ${action}失败:${err instanceof Error ? err.message : String(err)}\n`);
223
+ process.exit(1);
224
+ }
225
+ console.log(`\n✅ 预警规则已${isUpdate ? "更新" : "创建"}:${opts.name}\n`);
226
+ }
227
+ export async function runForewarningGet(opts) {
228
+ const config = loadConfig(opts.token);
229
+ const url = `${config.apiBaseUrl}/query/smart-strategy/settings/${opts.media}/${opts.id}`;
230
+ let data;
231
+ try {
232
+ data = await apiFetch(url, config, {}, opts.verbose);
233
+ }
234
+ catch (err) {
235
+ console.error(`\n❌ 查询失败:${err instanceof Error ? err.message : String(err)}\n`);
236
+ process.exit(1);
237
+ }
238
+ console.log(JSON.stringify(data, null, 2));
239
+ }
@@ -0,0 +1,10 @@
1
+ /** 各平台 skill 安装目录(使用 siluzan-tso 作为目录名) */
2
+ export type AiTarget = "cursor" | "claude" | "openclaw-workspace" | "openclaw-global" | "workbuddy-workspace" | "workbuddy-global";
3
+ export interface InitOptions {
4
+ cwd: string;
5
+ aiTargets: string;
6
+ /** 自定义安装目录(绝对或相对路径),与 --ai 互斥 */
7
+ dir?: string;
8
+ force: boolean;
9
+ }
10
+ export declare function runInit(options: InitOptions): Promise<void>;
@@ -0,0 +1,141 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as fsSync from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { getSkillFiles } from "../templates/load-templates.js";
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const TARGET_DIRS = {
9
+ cursor: (cwd) => path.join(cwd, ".cursor", "skills", "siluzan-tso"),
10
+ claude: (cwd) => path.join(cwd, ".claude", "skills", "siluzan-tso"),
11
+ "openclaw-workspace": (cwd) => path.join(cwd, "skills", "siluzan-tso"),
12
+ "openclaw-global": (_cwd, home) => path.join(home, ".openclaw", "skills", "siluzan-tso"),
13
+ "workbuddy-workspace": (cwd) => path.join(cwd, ".workbuddy", "skills", "siluzan-tso"),
14
+ "workbuddy-global": (_cwd, home) => path.join(home, ".workbuddy", "skills", "siluzan-tso"),
15
+ };
16
+ function parseTargets(raw) {
17
+ const normalized = raw.trim().toLowerCase();
18
+ if (normalized === "all") {
19
+ return [
20
+ "cursor",
21
+ "claude",
22
+ "openclaw-workspace",
23
+ "openclaw-global",
24
+ "workbuddy-workspace",
25
+ "workbuddy-global",
26
+ ];
27
+ }
28
+ const parts = normalized
29
+ .split(",")
30
+ .map((s) => s.trim())
31
+ .map((p) => {
32
+ if (p === "openclaw")
33
+ return "openclaw-workspace";
34
+ if (p === "workbuddy")
35
+ return "workbuddy-workspace";
36
+ return p;
37
+ });
38
+ const allowed = [
39
+ "cursor", "claude",
40
+ "openclaw-workspace", "openclaw-global",
41
+ "workbuddy-workspace", "workbuddy-global",
42
+ ];
43
+ const result = [];
44
+ for (const p of parts) {
45
+ if (!allowed.includes(p)) {
46
+ console.error(`未知平台: ${p}。可选: cursor, claude, openclaw-workspace, openclaw-global, workbuddy-workspace, workbuddy-global, all`);
47
+ process.exitCode = 1;
48
+ return [];
49
+ }
50
+ result.push(p);
51
+ }
52
+ return [...new Set(result)];
53
+ }
54
+ function assetsRoot() {
55
+ // dist/commands/init.js → ../../assets
56
+ return path.join(__dirname, "..", "..", "assets");
57
+ }
58
+ /** 将 skill 文件写入指定目录,返回是否有文件被写入 */
59
+ async function writeSkillFilesToDir(destDir, skillFiles, force) {
60
+ await fs.mkdir(destDir, { recursive: true });
61
+ let anyWritten = false;
62
+ for (const [relativePath, content] of Object.entries(skillFiles)) {
63
+ const fullPath = path.join(destDir, relativePath);
64
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
65
+ try {
66
+ await fs.access(fullPath);
67
+ if (!force) {
68
+ console.warn(`跳过(已存在,使用 --force 覆盖): ${fullPath}`);
69
+ continue;
70
+ }
71
+ }
72
+ catch {
73
+ // 文件不存在,继续写入
74
+ }
75
+ await fs.writeFile(fullPath, content, "utf8");
76
+ console.log(`已写入: ${fullPath}`);
77
+ anyWritten = true;
78
+ }
79
+ return anyWritten;
80
+ }
81
+ /** 持久化安装记录到 ~/.siluzan/config.json(siluzan-tso 专用键) */
82
+ function saveInstalledTargets(entries) {
83
+ const CONFIG_FILE = path.join(os.homedir(), ".siluzan", "config.json");
84
+ try {
85
+ fsSync.mkdirSync(path.dirname(CONFIG_FILE), { recursive: true });
86
+ let existing = {};
87
+ if (fsSync.existsSync(CONFIG_FILE)) {
88
+ existing = JSON.parse(fsSync.readFileSync(CONFIG_FILE, "utf8"));
89
+ }
90
+ // 使用独立键 tsoInstalledTargets,与 CSO 的 installedTargets 互不干扰
91
+ const prev = Array.isArray(existing.tsoInstalledTargets)
92
+ ? existing.tsoInstalledTargets
93
+ : [];
94
+ const merged = new Map();
95
+ for (const e of [...prev, ...entries]) {
96
+ merged.set(`${e.target}::${e.cwd}`, e);
97
+ }
98
+ fsSync.writeFileSync(CONFIG_FILE, JSON.stringify({ ...existing, tsoInstalledTargets: [...merged.values()] }, null, 2), "utf8");
99
+ if (process.platform !== "win32") {
100
+ fsSync.chmodSync(CONFIG_FILE, 0o600);
101
+ }
102
+ }
103
+ catch {
104
+ // 记录失败不影响安装流程
105
+ }
106
+ }
107
+ export async function runInit(options) {
108
+ const home = os.homedir();
109
+ const skillFiles = await getSkillFiles(assetsRoot());
110
+ const installedEntries = [];
111
+ if (options.dir) {
112
+ // 自定义目录模式
113
+ const destDir = path.resolve(options.cwd, options.dir);
114
+ console.log(`安装目标目录:${destDir}`);
115
+ const anyWritten = await writeSkillFilesToDir(destDir, skillFiles, options.force);
116
+ if (anyWritten)
117
+ installedEntries.push({ target: "custom", cwd: "", dir: destDir });
118
+ }
119
+ else {
120
+ // 预定义平台模式
121
+ const targets = parseTargets(options.aiTargets);
122
+ if (targets.length === 0)
123
+ return;
124
+ for (const target of targets) {
125
+ const destDir = TARGET_DIRS[target](options.cwd, home);
126
+ console.log(`[${target}] → ${destDir}`);
127
+ const anyWritten = await writeSkillFilesToDir(destDir, skillFiles, options.force);
128
+ if (anyWritten) {
129
+ const isGlobal = target === "openclaw-global" || target === "workbuddy-global";
130
+ installedEntries.push({ target, cwd: isGlobal ? "" : options.cwd });
131
+ }
132
+ }
133
+ }
134
+ if (installedEntries.length > 0) {
135
+ saveInstalledTargets(installedEntries);
136
+ }
137
+ console.log("\n下一步:");
138
+ console.log("1. 若还未登录,请先运行:siluzan-cso login (siluzan-tso 与 siluzan-cso 共用同一 Token)");
139
+ console.log("2. OpenClaw 全局技能若未生效,请在 ~/.openclaw/openclaw.json 的 skills.load.extraDirs 中加入技能父目录。");
140
+ console.log("3. WorkBuddy 技能安装后重启 WorkBuddy 即可生效。");
141
+ }
@@ -0,0 +1,38 @@
1
+ export interface InvoiceListOptions {
2
+ token?: string;
3
+ keyword?: string;
4
+ startDate?: string;
5
+ endDate?: string;
6
+ page?: number;
7
+ pageSize?: number;
8
+ json?: boolean;
9
+ verbose?: boolean;
10
+ }
11
+ export declare function runInvoiceList(opts: InvoiceListOptions): Promise<void>;
12
+ export interface InvoiceBillableOptions {
13
+ token?: string;
14
+ media?: string;
15
+ currency?: string;
16
+ startDate?: string;
17
+ endDate?: string;
18
+ page?: number;
19
+ pageSize?: number;
20
+ wallet?: boolean;
21
+ json?: boolean;
22
+ verbose?: boolean;
23
+ }
24
+ export declare function runInvoiceBillable(opts: InvoiceBillableOptions): Promise<void>;
25
+ export interface InvoiceApplyOptions {
26
+ token?: string;
27
+ /** 要开票的订单 entityId,多个逗号分隔 */
28
+ billIds: string[];
29
+ billType: string;
30
+ mediaType?: string;
31
+ /** JSON 字符串,包含发票抬头信息 */
32
+ invoiceInfo: string;
33
+ /** JSON 字符串,包含收件人信息 */
34
+ recipientInfo: string;
35
+ json?: boolean;
36
+ verbose?: boolean;
37
+ }
38
+ export declare function runInvoiceApply(opts: InvoiceApplyOptions): Promise<void>;
@@ -0,0 +1,187 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import { loadConfig, apiFetch, fetchDataPermission } from "../utils/auth.js";
5
+ /**
6
+ * 若 config 中没有 dataPermission,自动从主平台权限菜单 API 获取并缓存到 config.json。
7
+ * 返回附有 dataPermission 的 config 副本。
8
+ */
9
+ async function ensureDataPermission(config) {
10
+ if (config.dataPermission)
11
+ return config;
12
+ if (!config.mainApiUrl)
13
+ return config;
14
+ const dp = await fetchDataPermission(config.mainApiUrl, config.authToken);
15
+ if (!dp)
16
+ return config;
17
+ // 写入 config.json 缓存,供后续调用直接使用
18
+ const configPath = path.join(os.homedir(), ".siluzan", "config.json");
19
+ try {
20
+ const raw = fs.existsSync(configPath)
21
+ ? JSON.parse(fs.readFileSync(configPath, "utf8"))
22
+ : {};
23
+ raw.dataPermission = dp;
24
+ fs.writeFileSync(configPath, JSON.stringify(raw, null, 2), "utf8");
25
+ }
26
+ catch {
27
+ // 写入失败不影响本次请求
28
+ }
29
+ return { ...config, dataPermission: dp };
30
+ }
31
+ /** 返回 YYYY-MM-DD 格式的日期字符串 */
32
+ function formatDate(d) {
33
+ return d.toISOString().slice(0, 10);
34
+ }
35
+ export async function runInvoiceList(opts) {
36
+ const config = await ensureDataPermission(loadConfig(opts.token));
37
+ // 后端 startDate/endDate 为必填,未指定时默认最近 3 个月
38
+ const endDate = opts.endDate ?? formatDate(new Date());
39
+ const startDefault = new Date();
40
+ startDefault.setMonth(startDefault.getMonth() - 3);
41
+ const startDate = opts.startDate ?? formatDate(startDefault);
42
+ const params = new URLSearchParams();
43
+ // 后端使用 1-based pageIndex
44
+ params.set("pageIndex", String(opts.page ?? 1));
45
+ params.set("pageSize", String(opts.pageSize ?? 20));
46
+ // searchValue 必须传(即使为空字符串)
47
+ params.set("searchValue", opts.keyword ?? "");
48
+ params.set("startDate", startDate);
49
+ params.set("endDate", endDate);
50
+ const url = `${config.apiBaseUrl}/invoice-request?${params}`;
51
+ let data;
52
+ try {
53
+ data = await apiFetch(url, config, {}, opts.verbose);
54
+ }
55
+ catch (err) {
56
+ console.error(`\n❌ 查询失败:${err instanceof Error ? err.message : String(err)}\n`);
57
+ process.exit(1);
58
+ }
59
+ if (opts.json) {
60
+ console.log(JSON.stringify(data, null, 2));
61
+ return;
62
+ }
63
+ const items = data.results ?? [];
64
+ const total = data.totalResultCount ?? 0;
65
+ const page = opts.page ?? 1;
66
+ const pageSize = opts.pageSize ?? 20;
67
+ console.log(`\n开票记录(第 ${page}/${Math.ceil(total / pageSize)} 页,共 ${total} 条)\n`);
68
+ if (items.length === 0) {
69
+ console.log(" 暂无数据。\n");
70
+ return;
71
+ }
72
+ const header = [
73
+ "申请单号".padEnd(20),
74
+ "企业".padEnd(20),
75
+ "类型".padEnd(16),
76
+ "状态".padEnd(10),
77
+ "申请时间",
78
+ ].join(" ");
79
+ console.log(" " + header);
80
+ console.log(" " + "-".repeat(header.length));
81
+ for (const item of items) {
82
+ console.log(" " + [
83
+ (item.requestNo ?? "").padEnd(20),
84
+ (item.companyName ?? "").padEnd(20),
85
+ (item.billType ?? "").padEnd(16),
86
+ (item.invoiceStatus ?? "").padEnd(10),
87
+ item.applyTime ?? "",
88
+ ].join(" "));
89
+ }
90
+ console.log();
91
+ }
92
+ export async function runInvoiceBillable(opts) {
93
+ const config = await ensureDataPermission(loadConfig(opts.token));
94
+ const params = new URLSearchParams();
95
+ params.set("pageNumber", String(opts.page ?? 1));
96
+ params.set("pageSize", String(opts.pageSize ?? 20));
97
+ if (opts.media)
98
+ params.set("mediaType", opts.media);
99
+ if (opts.currency)
100
+ params.set("currencyCode", opts.currency);
101
+ if (opts.startDate)
102
+ params.set("startDate", opts.startDate);
103
+ if (opts.endDate)
104
+ params.set("endDate", opts.endDate);
105
+ // 钱包开票走独立接口
106
+ const endpoint = opts.wallet
107
+ ? `${config.apiBaseUrl}/command/WalletRecharge/SearchByInvoiceRequest`
108
+ : `${config.apiBaseUrl}/query/amountaccount/SearchByInvoiceRequest`;
109
+ const url = `${endpoint}?${params}`;
110
+ let data;
111
+ try {
112
+ data = await apiFetch(url, config, {}, opts.verbose);
113
+ }
114
+ catch (err) {
115
+ console.error(`\n❌ 查询失败:${err instanceof Error ? err.message : String(err)}\n`);
116
+ process.exit(1);
117
+ }
118
+ if (opts.json) {
119
+ console.log(JSON.stringify(data, null, 2));
120
+ return;
121
+ }
122
+ const items = data.results ?? [];
123
+ const total = data.totalResultCount ?? 0;
124
+ const page = opts.page ?? 1;
125
+ const pageSize = opts.pageSize ?? 20;
126
+ const label = opts.wallet ? "钱包可开票记录" : "充值可开票订单";
127
+ console.log(`\n${label}(第 ${page}/${Math.ceil(total / pageSize)} 页,共 ${total} 条)\n`);
128
+ if (items.length === 0) {
129
+ console.log(" 暂无数据。\n");
130
+ return;
131
+ }
132
+ const header = [
133
+ "媒体".padEnd(10),
134
+ "币种".padEnd(8),
135
+ "金额".padEnd(12),
136
+ "状态".padEnd(16),
137
+ "账户名称".padEnd(20),
138
+ "创建时间",
139
+ ].join(" ");
140
+ console.log(" " + header);
141
+ console.log(" " + "-".repeat(header.length));
142
+ for (const item of items) {
143
+ console.log(" " + [
144
+ (item.mediaAccountType ?? "").padEnd(10),
145
+ (item.currencyCode ?? "").padEnd(8),
146
+ String(item.amounts ?? "").padEnd(12),
147
+ (item.invoiceState ?? item.rechargeStatus ?? "").padEnd(16),
148
+ (item.mediaCustomerName ?? "").padEnd(20),
149
+ (item.createdDateTime ?? "").slice(0, 19),
150
+ ].join(" "));
151
+ }
152
+ console.log();
153
+ }
154
+ export async function runInvoiceApply(opts) {
155
+ const config = await ensureDataPermission(loadConfig(opts.token));
156
+ let invoiceInfomation;
157
+ let recipientInfomation;
158
+ try {
159
+ invoiceInfomation = JSON.parse(opts.invoiceInfo);
160
+ recipientInfomation = JSON.parse(opts.recipientInfo);
161
+ }
162
+ catch {
163
+ console.error("\n❌ --invoice-info 或 --recipient-info 不是合法的 JSON\n");
164
+ process.exit(1);
165
+ }
166
+ const body = {
167
+ Bills: opts.billIds,
168
+ BillType: opts.billType,
169
+ MediaType: opts.mediaType,
170
+ InvoiceInfomation: invoiceInfomation,
171
+ RecipientInfomation: recipientInfomation,
172
+ };
173
+ const url = `${config.apiBaseUrl}/invoice-request`;
174
+ let data;
175
+ try {
176
+ data = await apiFetch(url, config, { method: "POST", body: JSON.stringify(body) }, opts.verbose);
177
+ }
178
+ catch (err) {
179
+ console.error(`\n❌ 申请失败:${err instanceof Error ? err.message : String(err)}\n`);
180
+ process.exit(1);
181
+ }
182
+ if (opts.json) {
183
+ console.log(JSON.stringify(data, null, 2));
184
+ return;
185
+ }
186
+ console.log(`\n✅ 开票申请已提交(${opts.billIds.length} 条订单)\n`);
187
+ }
@@ -0,0 +1,14 @@
1
+ export interface KeywordSuggestOptions {
2
+ token?: string;
3
+ /** 搜索词,可多次指定 */
4
+ keywords: string[];
5
+ /** 公司/产品网址(选填),填写后触发网址拓词 + 关键字推荐融合流程 */
6
+ url?: string;
7
+ /** 过滤:必须包含的词,逗号/空格分隔 */
8
+ include?: string;
9
+ /** 过滤:不包含的词,逗号/空格分隔 */
10
+ exclude?: string;
11
+ json?: boolean;
12
+ verbose?: boolean;
13
+ }
14
+ export declare function runKeywordSuggest(opts: KeywordSuggestOptions): Promise<void>;