shuadouyin 0.3.0 → 0.3.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/.dy-build-commit CHANGED
@@ -1 +1 @@
1
- 741cb7e2e4f3e23fc0a809a5b3765f372fc39a04
1
+ 3e3d62daa90acf4ce33daae5b2162c7b89d5ddcf
package/dist/app.js CHANGED
@@ -35,6 +35,7 @@ import { voiceMemories, voiceMemoryText } from "./operations/voice-memory.js";
35
35
  import { write } from "./operations/write.js";
36
36
  import { quiet } from "./progress.js";
37
37
  import { Store } from "./store.js";
38
+ import { Workspaces } from "./workspace.js";
38
39
  export { isWrite, resourceID, validate } from "./operations/validate.js";
39
40
  // Commands that need the work itself in view, and are therefore blocked by the
40
41
  // chat overlay. Reads only: a write states its own target and already fails
@@ -210,6 +211,10 @@ export class App {
210
211
  const session = await this.chrome.connect(active, this.timeout, live ? "https://live.douyin.com" : undefined);
211
212
  try {
212
213
  const result = await this.read(session, operation, input, active);
214
+ // Every page answer carries the setup question until it is answered. It
215
+ // is attached here rather than deep inside each operation so no command
216
+ // has to remember, and so a command added later cannot forget.
217
+ const setup = await setupNotice(this.store.base);
213
218
  if (operation === "chat.read" && this.store.bot_id) {
214
219
  if (!result || typeof result !== "object")
215
220
  throw new CliError("state_corrupt", "Chat read did not return a conversation window");
@@ -223,7 +228,7 @@ export class App {
223
228
  }
224
229
  if (!["status", "auth.status"].includes(operation))
225
230
  await this.store.append(operation, result);
226
- return result;
231
+ return setup && result && typeof result === "object" ? { ...result, setup } : result;
227
232
  }
228
233
  catch (error) {
229
234
  // Cancellation is the caller's choice and its outcome is not a
@@ -695,6 +700,29 @@ async function waitForProfileWorks(session, signal) {
695
700
  signal.throwIfAborted();
696
701
  }
697
702
  }
703
+ // The setup question, carried with the first answer instead of blocking it.
704
+ //
705
+ // A block was tried first and is wrong: someone who wants to look at a few videos
706
+ // before naming the bot would be stopped by an error, which is a worse experience
707
+ // than a nudge they can act on or ignore. A gate also has to decide which commands
708
+ // count, and every list like that is wrong at the edges.
709
+ //
710
+ // A notice rides along with whatever was asked for. The result the caller wanted is
711
+ // still there; the missing setup is stated once, in the same envelope, with the two
712
+ // questions to ask. It appears on every answer until the owner answers, because a
713
+ // notice that appears once is one a caller can miss.
714
+ export async function setupNotice(base) {
715
+ const state = await new Workspaces(base).onboarding().catch(() => null);
716
+ // `unknown` means the file is missing: a workspace that never ran init.
717
+ if (!state || state.status === "complete" || state.status === "skipped")
718
+ return null;
719
+ return {
720
+ status: state.status,
721
+ ask: ["What should this bot be called, and how should it sound?", "What must it never do?"],
722
+ next: "Save the persona with `dy workspace get AGENTS.md` for the revision, `dy workspace set AGENTS.md --text-file <file> --if-match <revision>`, then `dy onboarding complete`. If the owner would rather decide later, run `dy onboarding skip`.",
723
+ why: "Until this is answered the bot browses and comments as a generic assistant. Nothing in the output would say the owner was never asked.",
724
+ };
725
+ }
698
726
  function publicBrowser(state) {
699
727
  return { running: true, headless: state.headless, pid: state.pid, started_at: state.started_at };
700
728
  }
package/dist/errors.js CHANGED
@@ -35,6 +35,9 @@ function category(code) {
35
35
  "already_exists",
36
36
  "revision_required",
37
37
  "revision_conflict",
38
+ // The bot has no persona yet. This is the installation's own state, not a
39
+ // failure of the command, and it is answered by asking the owner.
40
+ "setup_required",
38
41
  "account_mismatch",
39
42
  "migration_pending",
40
43
  // A gate that refused because of the installation's own recorded
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shuadouyin",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "packageManager": "pnpm@10.34.7",
5
5
  "description": "Browse Douyin with a persistent bot persona and a memory of its own",
6
6
  "type": "module",
@@ -8,67 +8,47 @@ allowed-tools: Bash(dy:*) Bash(npx dy:*)
8
8
 
9
9
  `dy` 是本地能力工具。当前 agent 负责理解、判断、对话、总结与调度。用 `dy schema` 查看可用命令;尊重用户当前授权和已有账号状态。
10
10
 
11
- ## 先确认 dy 装好了
12
-
13
- ```bash
14
- dy --version
15
- ```
11
+ ## 第一次用:先问人设
16
12
 
17
- 有版本号就直接用。没有就装:
13
+ 新工作区里人格还是模板。这时候每个页面命令的返回里都会带一个 `setup` 字段:
18
14
 
19
- ```bash
20
- npm install -g shuadouyin
15
+ ```json
16
+ "setup": {
17
+ "status": "pending",
18
+ "ask": ["这个 bot 叫什么,说话什么调子?", "有什么事它绝对不能做?"],
19
+ "next": "…怎么保存人格…",
20
+ "why": "不回答的话,bot 会用通用助手的声音说话,而输出里没有任何东西会提示主人没被问过"
21
+ }
21
22
  ```
22
23
 
23
- 包叫 `shuadouyin`,命令叫 `dy`。**唯一需要的是本机已有的 Chrome**,不下载浏览器。装完 `dy init` 建工作区,`dy auth login` 扫码登录抖音。
24
+ **命令照常执行**,`setup` 只是搭在结果上。看到它就先问主人这两个问题,别等主人自己想起来。
24
25
 
25
- 从源码装也行(会构建一次,几秒):
26
+ 1. `dy workspace get AGENTS.md` 拿 revision
27
+ 2. 改完 `dy workspace set AGENTS.md --text-file <file> --if-match <revision>`
28
+ 3. `dy onboarding complete`
26
29
 
27
- ```bash
28
- npm install -g github:9Ashwin/ShuaDouyin
29
- ```
30
+ 主人说「以后再说」就跑 `dy onboarding skip`——跳过也是回答,之后不再提示。
30
31
 
31
- 装 skill 到 agent 目录(可选,这样其他 agent 也能读到):
32
-
33
- ```bash
34
- npx skills add https://github.com/9Ashwin/ShuaDouyin --skill dy
35
- ```
36
-
37
- 或者用 CLI 自己装:
38
-
39
- ```bash
40
- dy skill install
41
- ```
32
+ `setup` 只在新工作区出现。它没有拦你,是因为拦过一版:想先看几条视频再起名的人会被错误挡住,那比提示更糟。
42
33
 
43
- ### 更新 dy
34
+ ## 先确认 dy 装好了
44
35
 
45
36
  ```bash
46
- dy update --check
37
+ dy --version
47
38
  ```
48
39
 
49
- 告诉你四件事:这份是怎么装的(`checkout` / `npm-global` / `npm-local` / `managed-release`)、版本号、是哪个 commit 构建的、有没有更新的。
50
-
51
- **判断新旧用 `behind`。** 版本号告诉你跨了几层功能,但一层里可能有几十个提交,所以只有 commit 能回答「我是不是最新的」。
52
-
53
- 按 `update` 字段给的命令更新。从 npm 装的:
54
-
55
- ```bash
56
- npm install -g shuadouyin@latest
57
- ```
40
+ 有版本号就直接用。没有、或者要更新、要装 skill 到别的 agent 目录,读 `references/setup.md`——那里有安装、更新和各种安装形态的说明。
58
41
 
59
- 从源码装的:
42
+ ## 每次开始对话,先读人格
60
43
 
61
44
  ```bash
62
- npm install -g github:9Ashwin/ShuaDouyin@latest
45
+ dy workspace get AGENTS.md
46
+ dy context show --query "<这一轮要做什么>"
63
47
  ```
64
48
 
65
- **从源码装的话,不加 `@latest` 或 `--force` 会显示 up to date 而不更新**——npm 记录的是 commit,不是分支,它认为已经满足要求了。npm 包按版本号,没这个问题。
49
+ **这不是可选的一步。** 那个文件第一行是 bot 的名字,`## 身份与声音` 里是它说话的样子。不读就用你自己默认的语气回答,那等于把主人设的人格扔了——而且从输出里看不出来是漏读了还是没照做。
66
50
 
67
- 更新完 skill 也要重装一次,否则 agent 读到的是旧说明:
68
-
69
- ```bash
70
- npx skills add https://github.com/9Ashwin/ShuaDouyin --skill dy
71
- ```
51
+ 读了之后,你的每一句话都用那个身份说,包括「我」的自称、句子的长短、有没有语气词。人格里写着「话多、爱笑、用短句和语气词」,而你回一段四平八稳的分析,那就是没照着做。
72
52
 
73
53
  ## 快速开始
74
54
 
@@ -207,6 +187,7 @@ dy guide # 这份文档的完整内容
207
187
 
208
188
  | 你在做什么 | 读 |
209
189
  | --- | --- |
190
+ | 安装、更新、卸载、磁盘占用 | `references/setup.md` |
210
191
  | 不知道该用哪个命令 | `references/commands.md` |
211
192
  | 浏览、观察、评论、私信、分享、发图 | `references/browse-and-grow.md` |
212
193
  | 进直播间、发弹幕、送礼 | `references/live.md` |
@@ -250,7 +231,19 @@ dy guide # 这份文档的完整内容
250
231
 
251
232
  ## 用什么语气说话
252
233
 
253
- `dy` 是在养一个 bot,不是跑一条流水线。读 `workspace get AGENTS.md` 的人格,用那个身份对用户说话。
234
+ `dy` 是在养一个 bot,不是跑一条流水线。语气来自 `AGENTS.md`,不是你自己的默认风格。
235
+
236
+ **人格写了什么,就照什么说。** 不是「参考一下」,是执行:
237
+
238
+ | 人格里写着 | 该有的样子 | 不该有的样子 |
239
+ | --- | --- | --- |
240
+ | 话多、爱笑、用语气词 | 「这条好好笑,评论区都在接梗欸」 | 「该视频评论区呈现互动特征」 |
241
+ | 沉稳、有观察力、先给事实 | 「三帧里两帧是字幕,中间那帧在讲结论」 | 「哇这个也太有意思了吧」 |
242
+ | 简洁、只说结论 | 「没发出去,按钮 0×0」 | 「经过排查,我发现了问题的根本原因在于……」 |
243
+
244
+ 写完先看一眼:这句话换个 bot 也说得出来吗?说得出来,就说明人格没进去。
245
+
246
+ **名字用文件里的。** 第一行是「阿鱼」就别自称「猫猫」,改了名字也得先改文件。
254
247
 
255
248
  **不要用交付工单的口吻。** 这些词一出现,人格就没了:
256
249
 
@@ -0,0 +1,100 @@
1
+ # 安装与更新
2
+
3
+ 只在需要的时候读这个文件。日常使用看 `SKILL.md`。
4
+
5
+ ## 装 dy
6
+
7
+ ```bash
8
+ npm install -g shuadouyin
9
+ ```
10
+
11
+ 包叫 `shuadouyin`,命令叫 `dy`。发布包里带的是构建好的 `dist/`,装上就能跑,不需要本地编译。
12
+
13
+ **唯一需要的是本机已有的 Chrome**,不下载浏览器。
14
+
15
+ 装完:
16
+
17
+ ```bash
18
+ dy init # 建工作区
19
+ dy auth login # 扫码登录抖音(有头 Chrome)
20
+ ```
21
+
22
+ 从源码装也行,会构建一次,几秒:
23
+
24
+ ```bash
25
+ npm install -g github:9Ashwin/ShuaDouyin
26
+ ```
27
+
28
+ 源码安装需要 `esbuild` 的平台二进制,而它是通过 esbuild 自己的安装脚本下载的。如果 npm 被配置成跳过生命周期脚本(本仓库的 `.npmrc` 就设了 `ignore-scripts`,有些人也是全局设的),安装会失败并提示 `Cannot find package 'esbuild'`。加 `--ignore-scripts=false` 重装即可。
29
+
30
+ ## 装 skill 到 agent 目录
31
+
32
+ 这样别的 agent 也能读到:
33
+
34
+ ```bash
35
+ npx skills add https://github.com/9Ashwin/ShuaDouyin --skill dy
36
+ ```
37
+
38
+ 或者用 CLI 自己装:
39
+
40
+ ```bash
41
+ dy skill install
42
+ ```
43
+
44
+ `dy skill status` 检查已装的副本和仓库是否一致。
45
+
46
+ ## 更新
47
+
48
+ ```bash
49
+ dy update --check
50
+ ```
51
+
52
+ 告诉你四件事:
53
+
54
+ - 这份是怎么装的(`checkout` / `npm-global` / `npm-local` / `managed-release`)
55
+ - 版本号
56
+ - 是哪个 commit 构建的
57
+ - 有没有更新的
58
+
59
+ **判断新旧用 `behind` 和 commit。** 版本号说明跨了几层功能,但一层里可能有几十个提交,所以只有 commit 能回答「我是不是最新的」。
60
+
61
+ 按 `update` 字段给的命令更新。从 npm 装的:
62
+
63
+ ```bash
64
+ npm install -g shuadouyin@latest
65
+ ```
66
+
67
+ 从源码装的:
68
+
69
+ ```bash
70
+ npm install -g github:9Ashwin/ShuaDouyin@latest
71
+ ```
72
+
73
+ **从源码装的话,不加 `@latest` 或 `--force` 会显示 up to date 而不更新**——npm 记录的是 commit,不是分支,它认为已经满足要求了。npm 包按版本号,没这个问题。
74
+
75
+ 更新完 skill 也要重装一次,否则 agent 读到的是旧说明:
76
+
77
+ ```bash
78
+ npx skills add https://github.com/9Ashwin/ShuaDouyin --skill dy
79
+ ```
80
+
81
+ ## 卸载
82
+
83
+ ```bash
84
+ npm uninstall -g shuadouyin
85
+ ```
86
+
87
+ 卸载保留 `~/.dy` 里的 bot、记忆和账号登录状态。
88
+
89
+ 要连状态一起清掉,手动删 `~/.dy`。删之前可以跑 `dy browser clean` 先回收缓存和截图——那部分通常占绝大部分空间,而登录和记忆都在别的文件里。
90
+
91
+ ## 磁盘
92
+
93
+ `~/.dy` 会持续增长,但增长的几乎都不是 dy 自己的数据。实测一台用了几天的是 1.5G,其中 1.2G 是 Chrome 的 HTTP 缓存、104M 是截图,dy 的事件、记忆和人格一共 2.5M。
94
+
95
+ ```bash
96
+ dy browser clean # 看能回收多少
97
+ dy browser clean --apply # 真的删
98
+ ```
99
+
100
+ 缓存和截图超过 512MB 时,Chrome 启动前会自动清一次。Cookie、登录数据、事件日志、收据、记忆、人格、快照永远不动。