persona-test-cli 0.1.3 → 0.1.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.
package/README.md CHANGED
@@ -39,7 +39,7 @@ authorize page loads decides the scope of the issued token.
39
39
  | `whoami` | anyone | Show current identity + available commands |
40
40
  | `campaigns list/create/edit/link/stats` | anyone | Share links — scoped to your own unless admin |
41
41
  | `personas list/create/edit` | anyone | Ad-copy personas — scoped to your own unless admin |
42
- | `events tail` | anyone | Poll new impression/click events |
42
+ | `events tail` | anyone | Poll new impression/click events (starts from now; `--since <iso>` to replay history first) |
43
43
  | `users list/approve/reject` | **admin only** | Registration approval queue |
44
44
 
45
45
  Run any command with `--help` for its full options and copy-pasteable examples.
@@ -2,8 +2,10 @@ import { apiRequest } from "../http.js";
2
2
  import { requireSession } from "../session.js";
3
3
  import { jsonMode } from "../output.js";
4
4
  import { action } from "../output.js";
5
+ import { fail, EXIT_CODES } from "../errors.js";
5
6
  export const EVENTS_TAIL_USAGE = ` persona-test-cli events tail
6
- persona-test-cli events tail --slug summer --interval 3`;
7
+ persona-test-cli events tail --slug summer --interval 3
8
+ persona-test-cli events tail --since 2026-08-30T00:00:00Z # 回看历史,再接实时`;
7
9
  const TAIL_USAGE = EVENTS_TAIL_USAGE;
8
10
  function sleep(ms) {
9
11
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -12,17 +14,31 @@ export function eventsCommand(program) {
12
14
  const cmd = program.command("events").description("查看展示/点击事件");
13
15
  cmd
14
16
  .command("tail")
15
- .description("轮询打印新的展示/点击事件,Ctrl+C 退出")
17
+ .description("从现在开始轮询打印新的展示/点击事件,Ctrl+C 退出(要回看历史用 --since)")
16
18
  .option("--slug <slug>", "只看某条分享链接的事件")
17
19
  .option("--interval <seconds>", "轮询间隔秒数", "5")
20
+ .option("--since <iso>", "从这个 ISO 时间点开始回放,再接实时(默认从启动时刻开始)")
18
21
  .option("--base-url <url>")
19
22
  .addHelpText("after", `\n示例:\n${TAIL_USAGE}`)
20
23
  .action(action(async (opts) => {
21
24
  const { baseUrl, token } = requireSession(opts.baseUrl);
22
25
  const intervalMs = Math.max(1, Number(opts.interval) || 5) * 1000;
23
- let since = new Date(0).toISOString();
26
+ // Default to "now", not the epoch: `tail` promises *new* events, and
27
+ // starting at 1970 made the first poll replay up to 50 historical
28
+ // events before showing anything live.
29
+ let since = new Date().toISOString();
30
+ if (opts.since) {
31
+ const parsed = new Date(opts.since);
32
+ if (Number.isNaN(parsed.getTime())) {
33
+ fail(`--since 不是合法的时间: ${opts.since}(用 ISO 格式,如 2026-08-30T00:00:00Z)`, {
34
+ exitCode: EXIT_CODES.validation,
35
+ usage: TAIL_USAGE,
36
+ });
37
+ }
38
+ since = parsed.toISOString();
39
+ }
24
40
  if (!jsonMode) {
25
- console.log(`轮询中(每 ${opts.interval} 秒)... Ctrl+C 退出`);
41
+ console.log(`轮询中(每 ${opts.interval} 秒)... Ctrl+C 退出${opts.since ? ` — 回放起点 ${since}` : ""}`);
26
42
  }
27
43
  for (;;) {
28
44
  const query = new URLSearchParams({ since, limit: "50" });
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { dirname, join } from "node:path";
5
- import { Command } from "commander";
5
+ import { Command, CommanderError } from "commander";
6
6
  import { registerCommand } from "./commands/register.js";
7
7
  import { loginCommand } from "./commands/login.js";
8
8
  import { logoutCommand } from "./commands/logout.js";
@@ -12,7 +12,8 @@ import { personasCommand } from "./commands/personas.js";
12
12
  import { eventsCommand } from "./commands/events.js";
13
13
  import { usersCommand } from "./commands/users.js";
14
14
  import { examplesCommand } from "./commands/examples.js";
15
- import { jsonMode } from "./output.js";
15
+ import { jsonMode, printError } from "./output.js";
16
+ import { CliError, EXIT_CODES } from "./errors.js";
16
17
  // Reads package.json's version whether running from source (cli/index.ts,
17
18
  // package.json next to it) or from the built dist/index.js (package.json one
18
19
  // directory up) — avoids a hardcoded version string drifting from package.json.
@@ -30,6 +31,17 @@ function readVersion() {
30
31
  }
31
32
  const version = readVersion();
32
33
  const program = new Command();
34
+ // Commander hardcodes exit code 1 for every usage/argument problem (missing
35
+ // option, unknown flag, bad value) and writes the message to stderr itself.
36
+ // `examples` documents 3 = 参数校验失败, so take over both halves: exitOverride
37
+ // stops commander calling process.exit(1), and configureOutput silences its
38
+ // stderr write so every error flows through printError() — which honours --json
39
+ // and applies the documented exit code.
40
+ //
41
+ // MUST come before the subcommands are registered: commander copies
42
+ // _exitCallback and _outputConfiguration onto a child at addCommand() time,
43
+ // so declaring this at the bottom leaves every subcommand untouched.
44
+ program.exitOverride().configureOutput({ writeErr: () => { } });
33
45
  program
34
46
  .name("persona-test-cli")
35
47
  .description("persona-test 运维 CLI —— admin 和已审核通过的普通用户都能用,普通用户只能看到/编辑自己创建的 campaigns/personas。\n" +
@@ -57,4 +69,16 @@ personasCommand(program);
57
69
  eventsCommand(program);
58
70
  usersCommand(program); // [仅 admin] — see the description on each of its subcommands
59
71
  examplesCommand(program);
60
- program.parseAsync(process.argv);
72
+ program.parseAsync(process.argv).catch((err) => {
73
+ if (err instanceof CommanderError) {
74
+ // --help / -v travel through the throw path too, but they are normal exits.
75
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version")
76
+ return;
77
+ // Commander bakes its own "error: " prefix into some messages; printError
78
+ // adds ours, which would otherwise read "错误: error: unknown command ...".
79
+ const message = err.message.replace(/^error:\s*/i, "");
80
+ printError(new CliError(message, { exitCode: EXIT_CODES.validation }));
81
+ return;
82
+ }
83
+ printError(err);
84
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "persona-test-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "运维 CLI for persona-test — admin 和已审核通过的普通用户都能用;设备码浏览器授权登录,管理分享链接(campaigns)和画像(personas),普通用户只能看到/编辑自己创建的。",
5
5
  "type": "module",
6
6
  "bin": {