@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.
Files changed (61) hide show
  1. package/README.md +42 -11
  2. package/checksums.txt +6 -6
  3. package/cmd/auth/auth.go +19 -5
  4. package/cmd/auth/auth_test.go +23 -0
  5. package/cmd/canvas/canvas.go +7 -7
  6. package/cmd/download_result.go +3 -2
  7. package/cmd/marketing.go +19 -3
  8. package/cmd/marketing_test.go +11 -5
  9. package/cmd/model.go +5 -4
  10. package/cmd/root.go +17 -5
  11. package/cmd/root_update_check_test.go +139 -0
  12. package/cmd/short_drama_test.go +2 -0
  13. package/cmd/source_test.go +27 -4
  14. package/cmd/space.go +199 -0
  15. package/cmd/space_test.go +469 -0
  16. package/cmd/upload_file_test.go +3 -1
  17. package/dist/checksums.txt +6 -6
  18. package/internal/adaptercli/commands_test.go +9 -5
  19. package/internal/auth/loopback.go +16 -11
  20. package/internal/auth/loopback_origins_test.go +152 -0
  21. package/internal/auth/space.go +26 -0
  22. package/internal/auth/space_test.go +42 -0
  23. package/internal/canvas/get.go +9 -2
  24. package/internal/commandnames/names.go +9 -4
  25. package/internal/common/client.go +23 -0
  26. package/internal/common/host_source.go +5 -0
  27. package/internal/common/host_source_test.go +9 -1
  28. package/internal/common/runner.go +5 -3
  29. package/internal/common/space.go +185 -0
  30. package/internal/common/space_test.go +132 -0
  31. package/internal/common/submit_run.go +2 -0
  32. package/internal/common/upload_file.go +2 -0
  33. package/internal/config/config.go +15 -12
  34. package/internal/generate_image/generate_image.go +3 -9
  35. package/internal/short_drama/submit_run.go +3 -9
  36. package/internal/space/session.go +153 -0
  37. package/internal/space/store.go +211 -0
  38. package/internal/space/store_test.go +246 -0
  39. package/internal/version/version.go +13 -0
  40. package/internal/version/version_test.go +38 -0
  41. package/internal/versioncheck/check.go +147 -0
  42. package/internal/versioncheck/check_test.go +204 -0
  43. package/internal/versioncheck/semver.go +84 -0
  44. package/package.json +1 -1
  45. package/scripts/canvas-command.js +47 -5
  46. package/scripts/install-wizard.js +1 -1
  47. package/scripts/run.js +15 -4
  48. package/scripts/telemetry.js +3 -1
  49. package/scripts/version-check.js +8 -1
  50. package/skills/short-drama/SKILL.md +12 -1
  51. package/skills/xyq-marketing-skill/SKILL.md +22 -3
  52. package/skills/xyq-marketing-skill/references/api.md +7 -1
  53. package/skills/xyq-marketing-skill/scripts/marketing.js +51 -19
  54. package/skills/xyq-nest-skill/SKILL.md +15 -4
  55. package/skills/xyq-nest-skill/commands/canvas-assets.md +1 -1
  56. package/skills/xyq-nest-skill/commands/canvas.md +5 -3
  57. package/skills/xyq-nest-skill/commands/get-credit-balance.md +2 -2
  58. package/skills/xyq-nest-skill/commands/model.md +1 -1
  59. package/skills/xyq-nest-skill/scripts/ensure-cli.js +8 -1
  60. package/skills/xyq-nest-skill/scripts/install.md +1 -1
  61. package/skills/xyq-nest-skill/workflows/canvas-edit.md +2 -2
package/README.md CHANGED
@@ -24,17 +24,43 @@ x-tt-env = ""
24
24
  | 技能 | 说明 | 路径 |
25
25
  |-------|-------------|------|
26
26
  | `xyq-short-drama-skill` | 短剧工作流技能,支持提交创作任务、上传参考文件、查询进度、列出会话文件和下载产物。 | `skills/short-drama/` |
27
- | `xyq-skill` | 图片生成与参考图编辑、视频生成、视频超分与擦字幕、异步结果交付、个人 Canvas 编辑、积分查询及登录授权。 | `skills/xyq-nest-skill/` |
27
+ | `xyq-skill` | 图片生成与参考图编辑、视频生成、视频超分与擦字幕、异步结果交付、个人或团队 Canvas 编辑、积分查询及登录授权。 | `skills/xyq-nest-skill/` |
28
28
  | `xyq-marketing-skill` | 商品图文营销一键成片:剧情广告、品牌大片、达人带货,含素材上传、进度查询与媒体下载。 | `skills/xyq-marketing-skill/` |
29
29
 
30
30
  ### 技能路由
31
31
 
32
- - 图片生成与参考图编辑、视频生成(含首尾帧和参考素材)、视频超分、擦字幕、结果查询、个人 Canvas 编辑、积分和授权由 `xyq-skill` 处理。
32
+ - 图片生成与参考图编辑、视频生成(含首尾帧和参考素材)、视频超分、擦字幕、结果查询、个人或团队 Canvas 编辑、积分和授权由 `xyq-skill` 处理。
33
33
  - 短剧生成、续写、改写、人物设定、分集创作和短剧会话文件处理使用 `xyq-short-drama-skill`。
34
34
  - 商品图文营销成片、剧情广告、品牌大片和达人带货视频使用 `xyq-marketing-skill`;由公开营销 API 完成创作编排。
35
35
 
36
36
  需要补充、选择或确认时,使用宿主实际暴露且当前模式允许的工具:Codex 的 `request_user_input` / `request_user_input_async`、WorkBuddy 的 `ask_user_question`;不可用时用普通聊天。
37
37
 
38
+ ## 个人与团队空间
39
+
40
+ 同一个 AK 可以切换个人与所属团队,无需重新登录。首次使用默认个人;当前账号与 API origin 的选择独立保存。
41
+
42
+ ```bash
43
+ pippit-tool-cli space list # 查看个人和所属团队,并标记当前默认空间
44
+ pippit-tool-cli space use 123456 # 保存团队为默认空间
45
+ pippit-tool-cli space use 0 # 保存个人为默认空间
46
+ pippit-tool-cli get-credit-balance --team-id 123456 # 仅本次使用团队
47
+ pippit-tool-cli get-credit-balance --team-id 0 # 仅本次使用个人
48
+ ```
49
+
50
+ `space` 仅提供 `list` 和 `use <TEAM_ID>`,统一输出 JSON。`list` 返回完整分页后的 `spaces` 和已保存的默认空间 `current`,列表项用 `current: true` 标记当前选择;`use` 必须指定 ID,不等待交互输入,成功后返回保存的空间。团队 ID 按 int64 的十进制字符串处理,不接受负数、小数或溢出值。切换到团队前核实账号所属团队列表,失败保留旧选择;切回个人不依赖列表接口。
51
+
52
+ 新任务按「单次 `--team-id` → 已保存默认 → 个人」确定空间;上传、提交、轮询固定同一空间。生成、视频处理、短剧、上传、会话查询、文件列表、积分、营销与 Canvas 命令均适用。同源 API 请求统一携带当前团队的 `X-Team-Id`,由 API 决定是否使用;模型接口内部仍按个人身份查询;Canvas 复用相同空间选择,`--team-id 0` 可单次切回个人。帮助、安装和版本命令无需读取空间或登录。
53
+
54
+ 显式 `--team-id` 不要求 CLI 能读取本地账号标识或空间记录,认证继续使用当前 AK/adapter,由 API 校验团队权限。本地记录可用时照常保存;不可用时本次仍固定使用指定空间,宿主应保存返回的任务 ID 和 `team_id`,后续显式传入原 TeamID。`space use` 保存默认空间仍需要可识别的账号;`space list` 在账号不可识别时返回 `current: null`。
55
+
56
+ 提交成功会按当前账号、API origin 保存 thread/run 的空间记录,上传成功保存资产归属。之后即使切到另一团队,直接 `get-thread --thread-id ...`、`query-result`、`list-thread-file` 或 `marketing query` 仍查询原任务;带原 thread 的续写也使用原空间。显式指定与记录冲突的团队会在请求前停止,已知跨空间素材同样会拦截。跨设备或旧任务无记录时应显式传原 `--team-id`;失败不会遍历其它团队。查询成功后可补齐记录。
57
+
58
+ 空间记录位于系统用户配置目录的 `pippit-cli/spaces/`,按账号与 API origin 摘要隔离,每条记录原子保存,不存 AK。空间账号标识使用本地登录凭证的 UID 摘要,同 UID 换 AK 保留选择。损坏记录报错,不当作首次使用;按错误提示用原 `--team-id` 查询恢复。保存失败不把已经成功的提交改成失败:输出保留 ID 与 `team_id`,警告写 stderr,后续沿用原空间查询,不能重提。
59
+
60
+ 业务 stdout 保留既有结构;上传和提交增加字符串 `team_id`,`status` 增加 `default_space`,空间提示写 stderr。`marketing.js query --wait` 自动向各次 CLI 子进程传递固定空间;有账号记录时同时传递账号/API origin 约束,切换账号会停止。无本地账号记录时可显式传入 `--team-id` 轮询,各次请求仍由 API 鉴权;默认空间切换不会改变已固定的 TeamID。团队出现在列表中不等于有 WRITE 权限、有效会员或可用积分;最终由服务端授权和扣费,CLI 不自动回退。
61
+
62
+ `space list/use TEAM_ID` 依赖 API 部署 `/api/biz/v1/skill/get_lite_team_list` 的 AK 适配入口,业务团队使用还依赖对应 API 团队权限实现;不直连 commerce Web 接口。宿主 Agent 的选择、任务恢复与不回退规则已写入三个 Skill 的 `Space Selection` 章节。
63
+
38
64
  ## 小云雀营销成片技能
39
65
 
40
66
  入口:[skills/xyq-marketing-skill/SKILL.md](skills/xyq-marketing-skill/SKILL.md)。使用 [官网](https://xyq.jianying.com/cli?tab=api) 已开放的营销 API,支持素材上传、营销视频提交、结果查询与下载、积分查询。安装器会从 `skills/` 自动安装该 Skill,也可以单独安装:
@@ -53,7 +79,7 @@ node skills/xyq-marketing-skill/scripts/marketing.js generate --request request.
53
79
  node skills/xyq-marketing-skill/scripts/marketing.js query --thread-id THREAD_ID --run-id RUN_ID --wait --output-dir ./results
54
80
  ```
55
81
 
56
- 参数与错误处理见 [接口契约](skills/xyq-marketing-skill/references/api.md),完整示例见 [商品图到营销视频](skills/xyq-marketing-skill/examples/product-video.md)。这是 Skill 脚本入口,不是新增的 Go CLI 子命令。营销 API 未公开团队切换字段,不宣称支持团队空间切换。
82
+ 参数与错误处理见 [接口契约](skills/xyq-marketing-skill/references/api.md),完整示例见 [商品图到营销视频](skills/xyq-marketing-skill/examples/product-video.md)。Skill 脚本通过原生 `marketing` 命令执行;两者均支持 `--team-id`,空间经 HTTP Header 传递,营销请求 JSON 无需增加字段。
57
83
 
58
84
  维护后运行 `node scripts/marketing-skill.test.js` 和 `node scripts/skills.test.js`,验证请求、上传、任务状态、下载与 Skill 引用。测试使用本地模拟服务,不会创建真实付费任务。
59
85
 
@@ -64,7 +90,7 @@ node skills/xyq-marketing-skill/scripts/marketing.js query --thread-id THREAD_ID
64
90
  | 操作 | CLI | 文档 |
65
91
  | --- | --- | --- |
66
92
  | 登录授权 | `status` / `login` / `logout` | [授权](skills/xyq-nest-skill/commands/auth.md) |
67
- | 个人 Canvas 画布与节点编辑 | `canvas` | [画布](skills/xyq-nest-skill/commands/canvas.md) |
93
+ | 个人或团队 Canvas 画布与节点编辑 | `canvas` | [画布](skills/xyq-nest-skill/commands/canvas.md) |
68
94
  | 生图、参考图编辑 | `generate-image` | [图片](skills/xyq-nest-skill/commands/generate-image.md) |
69
95
  | 查看可用图片/视频模型、参数配置 | `model list` / `model describe` | [模型发现](skills/xyq-nest-skill/commands/model.md) |
70
96
  | 生视频、首尾帧 | `generate-video` | [视频](skills/xyq-nest-skill/commands/generate-video.md) |
@@ -149,7 +175,7 @@ pippit-tool-cli list-thread-file --thread-id thread_123 --page-num 1 --page-size
149
175
  pippit-tool-cli download-result --output-path ./thread_123/results/result.mp4 --url URL --updated-at 1779716734
150
176
  ```
151
177
 
152
- `get-credit-balance`: 使用当前登录凭证查询个人有效积分余额,并输出 `{"total_remain_amount":"123"}`;零余额会显式输出为 `"0"`。加 `--with-log-id` 可在输出中同时保留本次请求的 `log_id`。
178
+ `get-credit-balance`: 使用当前登录凭证查询所选个人或团队空间的有效积分余额,并输出 `{"total_remain_amount":"123"}`;零余额会显式输出为 `"0"`。加 `--with-log-id` 可在输出中同时保留本次请求的 `log_id`。
153
179
 
154
180
  `+submit-run`: 输出 `thread_id`、`run_id` 和 `web_thread_link`;其中 `--message` 为必填参数。
155
181
  `get-thread`: 请求中带 `version=v2`,并输出 `readable_text`。
@@ -161,7 +187,7 @@ pippit-tool-cli download-result --output-path ./thread_123/results/result.mp4 --
161
187
 
162
188
  ## Canvas 原子命令
163
189
 
164
- CLI 提供个人漫剧画布的通用原子命令,不包含特定来源的导入或转换逻辑:
190
+ CLI 提供个人和团队漫剧画布的通用原子命令,不包含特定来源的导入或转换逻辑:
165
191
 
166
192
  ```bash
167
193
  # 首次使用时打开小云雀网页授权
@@ -176,6 +202,8 @@ pippit-tool-cli canvas upload --path ./reference.png
176
202
  pippit-tool-cli canvas apply --project-id PROJECT_ID --file ./patch.json
177
203
  ```
178
204
 
205
+ 五个命令复用 `space use TEAM_ID` 保存的默认空间,也支持单次 `--team-id TEAM_ID`,个人空间使用 `0`。操作已有画布时显式使用其原 `--team-id`,CLI 不根据画布 ID 自动选择团队。`canvas get` 返回实际 `team_id`,本地账号可识别时还返回 `space_context`;`canvas command run` 据此固定后续子进程的空间与账号/API origin,期间切换默认空间不影响当前操作。本地恢复记录和检查点按账号、团队与画布隔离,原有个人记录保持可读。
206
+
179
207
  五个命令均输出单行 JSON,资源 ID 保持字符串。`allocate` 只预留 ID,实际资产仍由后续 `apply` transaction 创建。`create` 的 `request_id` 用于追踪,不是跨服务崩溃窗口的严格幂等键;写请求结果不明确时不要盲目重放,应先使用 `canvas get` 回读确认。`apply` 当前只接受一个 transaction,但该 transaction 可以包含多个 patches;CLI 会严格检查 transaction ACK 和每个目标资产的新版本。
180
208
 
181
209
  通过 npm 安装的 CLI 还提供基于同一 Canvas SDK 的语义命令目录:
@@ -308,7 +336,7 @@ pippit-tool-cli model describe MiniMax-H3
308
336
  pippit-tool-cli model list --refresh
309
337
  ```
310
338
 
311
- 使用当前个人登录凭证查询服务端 Skill 模型接口;图片 scene 为 `web_image_agent`,视频为 `web_turbo_video_generator`,不传 TeamID。成功结果按账号、环境和场景隔离缓存 5 分钟,`--refresh` 强制刷新。失败时提示重试,不回退静态列表或过期缓存。`describe` 提供可直接传给生成命令的参数:图片比例保留数字枚举并附比例说明,视频比例转换为字符串,同时整理分辨率、图片推理强度、视频时长和素材限制;不展示内部 `config_key`,未知比例枚举跳过。图片列表和详情仅用 `name` 标识模型,不展示底层模型枚举;搜索、详情查询和 `generate-image --model` 均使用名称,包含空格时加引号。图片详情保留原始 `parameter_config`,包括条件、必选标记、选项说明和参数组合约束。生成仍由服务端校验;服务端尚未开放图片场景或提交参数时会返回错误,不回退静态模型清单。详见 [模型发现](skills/xyq-nest-skill/commands/model.md)。
339
+ 使用当前登录凭证查询服务端 Skill 模型接口;图片 scene 为 `web_image_agent`,视频为 `web_turbo_video_generator`。团队空间的请求携带 `X-Team-Id`,API 内部仍按个人身份查询模型。成功结果按账号、环境和场景隔离缓存 5 分钟,`--refresh` 强制刷新。失败时提示重试,不回退静态列表或过期缓存。`describe` 提供可直接传给生成命令的参数:图片比例保留数字枚举并附比例说明,视频比例转换为字符串,同时整理分辨率、图片推理强度、视频时长和素材限制;不展示内部 `config_key`,未知比例枚举跳过。图片列表和详情仅用 `name` 标识模型,不展示底层模型枚举;搜索、详情查询和 `generate-image --model` 均使用名称,包含空格时加引号。图片详情保留原始 `parameter_config`,包括条件、必选标记、选项说明和参数组合约束。生成仍由服务端校验;服务端尚未开放图片场景或提交参数时会返回错误,不回退静态模型清单。详见 [模型发现](skills/xyq-nest-skill/commands/model.md)。
312
340
 
313
341
  ## 生视频 CLI
314
342
 
@@ -412,6 +440,7 @@ pippit-tool-cli query-result \
412
440
  - 同源判断由公共 HTTP client 按 scheme、hostname、有效端口执行;只有同源 API 请求才调用 `PrepareAPIRequest`。非同源请求清除默认认证和 adapter 管理的敏感字段;API 跨域重定向会被拒绝。
413
441
  - `AuthProvided` 只表示 adapter 已附加认证材料,不代表服务端验证通过;失败立即返回,不回退到本机认证。`UseDefaultAuth` 才调用默认认证器,未知决策会被拒绝。
414
442
  - CLI 策略与 Cobra 解耦。`DefaultAllowRootCommands` 明确指定是否默认放行一级业务命令;为 false 时只开放 `AllowedRootCommands` 白名单,nil 和空列表都表示没有白名单命令。默认 adapter 将该开关设为 true,本机认证和自更新仍受各自开关控制;标准帮助和版本行为仍由 Cobra 提供。可通过 `auth.WithCLIOptions` 将本机认证能力配置应用于独立 Manager。
443
+ - `space` 按普通业务命令白名单开放,不随 `LocalCredentialsEnabled` 隐藏。`space list` 复用请求认证;没有可识别账号时仍可查询列表,JSON 的 `current` 为 `null`。`space use` 需要稳定账号标识,缺失时明确报错;不读取或写入匿名空间配置。显式 `--team-id` 的业务请求仍可使用 adapter 认证。关闭本地凭据后,未用于实际认证的环境 AK 不作为宿主账号标识。
415
444
  - 业务 client 与更新遥测 client 都在构造时包装 Transport;遥测 client 属于命令实例,避免使用不同 adapter 的实例共享可变客户端。
416
445
 
417
446
  例如,自定义 adapter 可以复用默认实现,仅调整命令能力,并在自己的程序入口注入:
@@ -452,7 +481,7 @@ npx @pippit-dev/cli install --source HOST
452
481
  pippit-tool-cli update --source HOST
453
482
  ```
454
483
 
455
- 直接使用 `npm install -g @pippit-dev/cli` 时,可由宿主在该次命令的环境中设置 `PIPPIT_CLI_SOURCE`。安装和更新均按显式 `--source`(命令支持时)、`PIPPIT_CLI_SOURCE`、已核实宿主运行标记的顺序识别来源;首尾空白去除,显式空值清空来源。该值不持久化;生成命令运行环境中若也存在此变量,同样会读取它作为宿主来源,具体兜底规则见下文。
484
+ 直接使用 `npm install -g @pippit-dev/cli` 时,可由宿主在该次命令的环境中设置 `PIPPIT_CLI_SOURCE`。安装和更新均按非空显式 `--source`(命令支持时)、`PIPPIT_CLI_SOURCE`、已核实宿主运行标记的顺序识别来源;去掉首尾空白后,未传或空值继续尝试下一来源。该值不持久化;生成命令运行环境中若也存在此变量,同样会读取它作为宿主来源,具体兜底规则见下文。
456
485
 
457
486
  `report_telemetry` 新增可选请求字段 `host_platform` 上报宿主标识;原有 `source` 保留 `npm_install`、`npx_install`、`cli_update`,`event` 区分 install/update,`platform` 仍表示操作系统。服务端通过独立的 `host_platform` 指标标签统计宿主来源。帮助不安装也不上报,`PIPPIT_CLI_DISABLE_TELEMETRY=1` 仍可关闭上报。内部 `install-cli.js` 仅安装二进制,沿用不安装 Skill、不上报的原有行为。
458
487
 
@@ -460,16 +489,18 @@ pippit-tool-cli update --source HOST
460
489
 
461
490
  生成提交命令均支持可选 `--source`:`generate-image`、`generate-video`、`video-super-resolution`、`erase-video-subtitle`、`short-drama +submit-run`、`marketing generate`。
462
491
 
463
- 由宿主 Agent 根据实际环境静默填写稳定标识,例如豆包办公 `doubao_office`、WorkBuddy `workbuddy`、Codex `codex`。其它来源可使用其真实产品标识;来源未知时省略,不询问用户,也不从 prompt 猜测。该值去掉首尾空白后写入请求顶层 `platform`,仅供统计,不参与创作、模型选择或鉴权;无法解析来源或显式空值时不发送该字段。省略 `--source` 时,CLI 按以下顺序尽力补齐来源:
492
+ 由宿主 Agent 根据实际环境静默填写稳定标识,例如豆包办公 `doubao_office`、WorkBuddy `workbuddy`、Codex `codex`。其它来源可使用其真实产品标识;来源未知时省略,不询问用户,也不从 prompt 猜测。该值去掉首尾空白后写入请求顶层 `platform`,仅供统计,不参与创作、模型选择或鉴权;无法解析来源时不发送该字段。省略 `--source`、显式空值或纯空白时,CLI 按以下顺序尽力补齐来源:
464
493
 
465
494
  1. 宿主显式设置的 `PIPPIT_CLI_SOURCE`。
466
- 2. 运行标记:`CODEX_THREAD_ID` / `CODEX_SESSION_ID` → `codex`,`CLAUDECODE` → `claude_code`,`CURSOR_AGENT` → `cursor`,`GEMINI_CLI` → `gemini_cli`。
495
+ 2. 运行标记:`CODEX_THREAD_ID` / `CODEX_SESSION_ID` → `codex`,`CLAUDECODE` → `claude_code`,`CURSOR_AGENT` → `cursor`,`GEMINI_CLI` → `gemini_cli`,`OPENCODE` → `opencode`,`QWEN_CODE` → `qwen_code`,`CLINE_ACTIVE` → `cline`,`COPILOT_CLI` / `COPILOT_AGENT_SESSION_ID` → `github_copilot_cli`。
467
496
  3. 标记缺失、为 0/false 或不同宿主标记冲突时不猜测,省略来源。
468
497
 
469
- 显式 `--source` 始终优先,显式空值可关闭本次归因。仅消费环境标记是否存在,不上传会话标识,不根据 API Key、已安装软件、普通终端名或用户 prompt 猜来源,也不持久化。查询、上传、下载、Canvas 不附加来源。营销脚本的预览与提交采用相同规则;原生 update 也复用该解析,仍写入 `host_platform`,保留旧 source。豆包办公、WorkBuddy 等尚无已核实运行标记的宿主,应优先主动传参或设置上述环境变量。
498
+ 非空显式 `--source` 始终优先,空值继续尝试环境识别。仅消费环境标记是否存在,不上传会话标识,不根据 API Key、已安装软件、普通终端名或用户 prompt 猜来源,也不持久化。查询、上传、下载、Canvas 不附加来源。营销脚本的预览与提交采用相同规则;原生 update 也复用该解析,仍写入 `host_platform`,保留旧 source。豆包办公、WorkBuddy 等尚无已核实运行标记的宿主,应优先主动传参或设置上述环境变量。
470
499
 
471
500
  Cursor 标记依据:[官方终端文档](https://docs.cursor.com/en/agent/terminal);Gemini 标记依据:[官方命令文档](https://geminicli.com/docs/reference/commands/)。Codex 标记已在本机运行环境核实;Claude Code 标记已在本机安装产物核实。
472
501
 
502
+ 新增标记依据:[OpenCode 启动入口](https://github.com/anomalyco/opencode/blob/388406238bd5ca15564a762840a2362c3a45bd9c/packages/opencode/src/index.ts#L75-L77)、[Qwen Code shell 环境](https://github.com/QwenLM/qwen-code/blob/e6e2c9efd1dd48bf93143510f7ed1c2413bcea6b/packages/core/src/services/shellExecutionService.ts#L988-L994)、[Cline VS Code 终端环境](https://github.com/cline/cline/blob/e6ad24c43aa2d7551cecdbb97a15269566d3650f/apps/vscode/src/hosts/vscode/terminal/VscodeTerminalRegistry.ts#L28-L34)、[GitHub Copilot CLI 变更记录](https://github.com/github/copilot-cli/blob/main/changelog.md)(0.0.421 增加 `COPILOT_CLI`,1.0.29 增加 `COPILOT_AGENT_SESSION_ID`)。只在子进程实际继承标记时识别,不保证所有宿主版本或启动方式都提供这些标记。
503
+
473
504
  ```bash
474
505
  pippit-tool-cli generate-video --prompt "小猫在花园散步" --model Seedance_2.0_mini --source workbuddy
475
506
  ```
package/checksums.txt CHANGED
@@ -1,6 +1,6 @@
1
- 6f7daba79b3423b148e30373bb87e7e8998df3d76be2196e39f84af209f0ea50 pippit-tool-cli-1.0.41-darwin-amd64.tar.gz
2
- 9f03ae01c213a76f45b0dfa110272b45e5b5ecd54b188313c9d256ccbe512980 pippit-tool-cli-1.0.41-darwin-arm64.tar.gz
3
- ea603412d1c5b0baa42efd34bec92fcd0907333babd64a6e08514d4e2dcb86e5 pippit-tool-cli-1.0.41-linux-amd64.tar.gz
4
- cebd1f0f34157624c0fc8203eec306bd6c7e31a2db44089f52bca9c74c57ee03 pippit-tool-cli-1.0.41-linux-arm64.tar.gz
5
- 729f413373d185964a42c178a7e0eeafc3fca490670fb753dc36fb38bb47aaf7 pippit-tool-cli-1.0.41-windows-amd64.zip
6
- b49491ae751e72f994da9388777a43f1b6b5781399dc2da15ced6d74f48dad6d pippit-tool-cli-1.0.41-windows-arm64.zip
1
+ e7ee32acef1c0a16912c46500ae7f15c46d920953792d1012886fac7e6424b2e pippit-tool-cli-1.0.43-darwin-amd64.tar.gz
2
+ 57e4df86eaa244cd9845eb966299c8c2e1186f74495da0c16f0b378fb4bc5125 pippit-tool-cli-1.0.43-darwin-arm64.tar.gz
3
+ 21823c7a675ebcaadebe2ddbfa0324b9e939220d4dd74d5d5d4546f5e6938060 pippit-tool-cli-1.0.43-linux-amd64.tar.gz
4
+ e387bc8eaeafa430a9a594ed1cc381964953b598875b520a6d009a12aec9d619 pippit-tool-cli-1.0.43-linux-arm64.tar.gz
5
+ 4569d389a20c75f0485ecc24ad5034308a818f294886dabdf07b2804e915f3a2 pippit-tool-cli-1.0.43-windows-amd64.zip
6
+ 078fb4fb09353a58095e1370668884664aad1760ae065983bb23abfdcd9939d1 pippit-tool-cli-1.0.43-windows-arm64.zip
package/cmd/auth/auth.go CHANGED
@@ -11,15 +11,17 @@ import (
11
11
  internal_auth "github.com/Pippit-dev/pippit-cli/internal/auth"
12
12
  "github.com/Pippit-dev/pippit-cli/internal/commandnames"
13
13
  "github.com/Pippit-dev/pippit-cli/internal/common"
14
+ "github.com/Pippit-dev/pippit-cli/internal/space"
14
15
  "github.com/spf13/cobra"
15
16
  )
16
17
 
17
18
  type loginResult struct {
18
- LoggedIn bool `json:"logged_in"`
19
- Source string `json:"source"`
20
- UID string `json:"uid,omitempty"`
21
- CredentialScope string `json:"credential_scope,omitempty"`
22
- ExpiresAt string `json:"expires_at,omitempty"`
19
+ DefaultSpace *space.Selection `json:"default_space,omitempty"`
20
+ LoggedIn bool `json:"logged_in"`
21
+ Source string `json:"source"`
22
+ UID string `json:"uid,omitempty"`
23
+ CredentialScope string `json:"credential_scope,omitempty"`
24
+ ExpiresAt string `json:"expires_at,omitempty"`
23
25
  }
24
26
 
25
27
  type logoutResult struct {
@@ -80,6 +82,18 @@ func NewStatusCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Co
80
82
  return err
81
83
  }
82
84
  result := loginResult{LoggedIn: status.LoggedIn, Source: status.Source, UID: status.UID, CredentialScope: status.CredentialScope}
85
+ store, scopeErr := common.SpaceStoreForRunner(command.Context(), runner)
86
+ if scopeErr == nil && store != nil {
87
+ selected, err := store.Current()
88
+ scopeErr = err
89
+ if err == nil {
90
+ result.DefaultSpace = &selected
91
+ }
92
+ }
93
+ if scopeErr != nil {
94
+ // Space metadata failures must not be mistaken for expired login.
95
+ fmt.Fprintf(stderr, "无法读取默认空间:%v\n", scopeErr)
96
+ }
83
97
  if !status.ExpiresAt.IsZero() {
84
98
  result.ExpiresAt = status.ExpiresAt.Format(time.RFC3339)
85
99
  }
@@ -5,6 +5,8 @@ import (
5
5
  "context"
6
6
  "encoding/json"
7
7
  "io"
8
+ "os"
9
+ "path/filepath"
8
10
  "strings"
9
11
  "testing"
10
12
  "time"
@@ -12,6 +14,7 @@ import (
12
14
  internal_auth "github.com/Pippit-dev/pippit-cli/internal/auth"
13
15
  "github.com/Pippit-dev/pippit-cli/internal/common"
14
16
  "github.com/Pippit-dev/pippit-cli/internal/config"
17
+ "github.com/Pippit-dev/pippit-cli/internal/space"
15
18
  )
16
19
 
17
20
  type fakeAuthManager struct {
@@ -132,3 +135,23 @@ func TestStatusAndLogoutDoNotModifyExplicitEnvironmentCredential(t *testing.T) {
132
135
  t.Fatalf("logout result/stderr = %#v/%q", result, logoutErr.String())
133
136
  }
134
137
  }
138
+
139
+ func TestStatusDoesNotMistakeSpaceFailureForLoggedOut(t *testing.T) {
140
+ path := filepath.Join(t.TempDir(), "blocked")
141
+ if err := os.WriteFile(path, []byte("file"), 0600); err != nil {
142
+ t.Fatal(err)
143
+ }
144
+ manager := &fakeAuthManager{status: &internal_auth.Status{LoggedIn: true, Source: "browser", UID: "123"}}
145
+ runner := &common.Runner{Config: &config.Config{}, Auth: manager, SpaceStore: &space.Store{Dir: path}}
146
+ var out, warnings bytes.Buffer
147
+ if err := NewStatusCommand(&out, &warnings, runner).Execute(); err != nil {
148
+ t.Fatal(err)
149
+ }
150
+ var result loginResult
151
+ if err := json.Unmarshal(out.Bytes(), &result); err != nil {
152
+ t.Fatal(err)
153
+ }
154
+ if !result.LoggedIn || result.DefaultSpace != nil || !strings.Contains(warnings.String(), "无法读取默认空间") {
155
+ t.Fatalf("status=%s warning=%s", out.String(), warnings.String())
156
+ }
157
+ }
@@ -18,12 +18,12 @@ import (
18
18
 
19
19
  const maxApplyRequestBytes = 64 << 20
20
20
 
21
- // NewCommand builds the provider-neutral personal Canvas command tree.
21
+ // NewCommand builds the provider-neutral Canvas command tree.
22
22
  func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
23
23
  cmd := &cobra.Command{
24
24
  Use: "canvas",
25
- Short: "Create and operate personal novel canvases",
26
- Long: "Create and operate personal novel canvases.\n\n" + CommandDiscoveryHelp,
25
+ Short: "Create and operate novel canvases",
26
+ Long: "Create and operate novel canvases in the selected personal or team space.\n\n" + CommandDiscoveryHelp,
27
27
  Args: cobra.NoArgs,
28
28
  }
29
29
  cmd.SetOut(stdout)
@@ -62,7 +62,7 @@ func newCreateCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Co
62
62
  var opts canvascore.CreateOptions
63
63
  cmd := &cobra.Command{
64
64
  Use: "create",
65
- Short: "Create a personal novel Canvas project",
65
+ Short: "Create a novel Canvas project in the selected space",
66
66
  Args: cobra.NoArgs,
67
67
  RunE: func(cmd *cobra.Command, _ []string) error {
68
68
  if strings.TrimSpace(opts.RequestID) == "" {
@@ -100,7 +100,7 @@ func newGetCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Comma
100
100
  var assetIDs []string
101
101
  cmd := &cobra.Command{
102
102
  Use: "get",
103
- Short: "Get personal Canvas assets by ID",
103
+ Short: "Get Canvas assets by ID in the selected space",
104
104
  Args: cobra.NoArgs,
105
105
  RunE: func(cmd *cobra.Command, _ []string) error {
106
106
  result, err := canvascore.Get(cmd.Context(), canvascore.GetOptions{AssetIDs: assetIDs}, runner)
@@ -150,7 +150,7 @@ func newApplyCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Com
150
150
  cmd.SetErr(stderr)
151
151
  flags := cmd.Flags()
152
152
  flags.StringVar(&filePath, "file", "-", "BatchPatch JSON request file, or - for stdin")
153
- flags.StringVar(&projectID, "project-id", "", "personal novel project ID as a decimal string")
153
+ flags.StringVar(&projectID, "project-id", "", "novel project ID as a decimal string")
154
154
  flags.BoolVar(&transportResult, "transport-result", false, "return explicit transaction outcomes for transport decoding")
155
155
  if err := flags.MarkHidden("transport-result"); err != nil {
156
156
  panic(err)
@@ -162,7 +162,7 @@ func newUploadCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Co
162
162
  var opts canvascore.UploadOptions
163
163
  cmd := &cobra.Command{
164
164
  Use: "upload",
165
- Short: "Upload a file to personal assets and wait until it is queryable",
165
+ Short: "Upload a file to the selected space and wait until it is queryable",
166
166
  Args: cobra.NoArgs,
167
167
  RunE: func(cmd *cobra.Command, _ []string) error {
168
168
  result, err := canvascore.Upload(cmd.Context(), opts, runner)
@@ -6,6 +6,7 @@ import (
6
6
  "strconv"
7
7
  "strings"
8
8
 
9
+ "github.com/Pippit-dev/pippit-cli/internal/commandnames"
9
10
  "github.com/Pippit-dev/pippit-cli/internal/common"
10
11
  "github.com/spf13/cobra"
11
12
  )
@@ -14,10 +15,10 @@ func newDownloadResultCommand(stdout, stderr io.Writer, runner *common.Runner) *
14
15
  var opts common.DownloadResultOptions
15
16
 
16
17
  cmd := &cobra.Command{
17
- Use: "download-result",
18
+ Use: commandnames.DownloadResult,
18
19
  Short: "Download a generated result URL",
19
20
  Args: cobra.NoArgs,
20
- RunE: withErrorLog("download-result", func() map[string]string {
21
+ RunE: withErrorLog(commandnames.DownloadResult, func() map[string]string {
21
22
  fields := map[string]string{
22
23
  "output_path": opts.OutputPath,
23
24
  "has_url": strconv.FormatBool(strings.TrimSpace(opts.URL) != ""),
package/cmd/marketing.go CHANGED
@@ -18,8 +18,8 @@ import (
18
18
  )
19
19
 
20
20
  var marketingPaths = map[string]string{
21
- "generate": "/api/biz/v1/agent/submit_marketing_run",
22
- "query": "/api/biz/v1/agent/query_generate_video_result",
21
+ "generate": config.SubmitMarketingRunPath,
22
+ "query": config.QueryGenerateVideoResultPath,
23
23
  "upload": config.UploadFilePath,
24
24
  "balance": config.GetCreditBalancePath,
25
25
  }
@@ -103,7 +103,19 @@ func newMarketingAction(action string, stdout, stderr io.Writer, runner *common.
103
103
  }
104
104
  body = value
105
105
  if !execute {
106
- return common.WriteJSON(stdout, map[string]any{"dry_run": true, "url": config.DefaultBaseURL + marketingPaths[action], "body": body})
106
+ if session := common.SpaceSession(cmd.Context()); session != nil {
107
+ if err := session.CheckIdentity(cmd.Context()); err != nil {
108
+ return err
109
+ }
110
+ ref, err := common.SpaceReferences(body)
111
+ if err != nil {
112
+ return err
113
+ }
114
+ if _, err := session.Resolve(ref); err != nil {
115
+ return err
116
+ }
117
+ }
118
+ return common.WriteJSON(stdout, map[string]any{"dry_run": true, "url": config.DefaultBaseURL + marketingPaths[action], "body": body, "team_id": common.SpaceTeamID(cmd.Context())})
107
119
  }
108
120
  case "query":
109
121
  if strings.TrimSpace(threadID) == "" || strings.TrimSpace(runID) == "" {
@@ -143,6 +155,10 @@ func newMarketingAction(action string, stdout, stderr io.Writer, runner *common.
143
155
  _ = json.Unmarshal(result["log_id"], &logID)
144
156
  return common.NewLogIDError(fmt.Sprintf("营销 API 失败: ret=%s errmsg=%s", ret, message), logID)
145
157
  }
158
+ result["team_id"], _ = json.Marshal(common.SpaceTeamID(ctx))
159
+ if session := common.SpaceSession(ctx); session != nil {
160
+ result["space_context"], _ = json.Marshal(session.Token())
161
+ }
146
162
  return common.WriteJSON(stdout, result)
147
163
  }
148
164
  return command
@@ -17,6 +17,7 @@ import (
17
17
  "github.com/Pippit-dev/pippit-cli/internal/adapter"
18
18
  "github.com/Pippit-dev/pippit-cli/internal/auth"
19
19
  "github.com/Pippit-dev/pippit-cli/internal/config"
20
+ "github.com/Pippit-dev/pippit-cli/internal/space"
20
21
  )
21
22
 
22
23
  type marketingCredentialStore struct {
@@ -128,6 +129,7 @@ func TestMarketingUsesSharedBrowserAuth(t *testing.T) {
128
129
  cfg.AccessKey = ""
129
130
  store := &marketingCredentialStore{credential: &auth.Credential{AccessKey: "browser-secret", UID: "user", DeviceID: "device", ExpiredAt: time.Now().Add(time.Hour).Unix()}}
130
131
  runner := newRootRunner(cfg)
132
+ runner.SpaceStore = &space.Store{Dir: t.TempDir()}
131
133
  runner.Auth = auth.NewManager(cfg, auth.WithCredentialStore(store))
132
134
  var output bytes.Buffer
133
135
  root := newRootCommand(&output, io.Discard, runner)
@@ -149,7 +151,7 @@ func TestMarketingUsesSharedBrowserAuth(t *testing.T) {
149
151
  if err := root.Execute(); err != nil {
150
152
  t.Fatal(err)
151
153
  }
152
- if calls != 1 || store.loads != 1 {
154
+ if calls != 1 || store.loads != 2 {
153
155
  t.Fatalf("HTTP calls=%d credential loads=%d", calls, store.loads)
154
156
  }
155
157
  if strings.Contains(output.String(), "browser-secret") || !strings.Contains(output.String(), "log-test") {
@@ -187,7 +189,7 @@ func TestMarketingAuthFailureAndEnvironmentPrecedence(t *testing.T) {
187
189
  root.SetArgs([]string{"marketing", "balance"})
188
190
  err := root.Execute()
189
191
  if scenario == "override" {
190
- if err != nil || calls != 1 || store.loads != 0 {
192
+ if err != nil || calls != 1 || store.loads != 1 {
191
193
  t.Fatalf("override: err=%v calls=%d loads=%d", err, calls, store.loads)
192
194
  }
193
195
  } else if err == nil || !strings.Contains(err.Error(), "pippit-tool-cli login") || calls != 0 {
@@ -197,7 +199,7 @@ func TestMarketingAuthFailureAndEnvironmentPrecedence(t *testing.T) {
197
199
  }
198
200
  }
199
201
 
200
- func TestMarketingPreviewAndHelpDoNotReadCredentials(t *testing.T) {
202
+ func TestMarketingOfflineCommandsReadIdentityOnlyWhenNeeded(t *testing.T) {
201
203
  for _, args := range [][]string{{"marketing", "--help"}, {"marketing", "generate", "--request", "-"}, {"marketing", "query"}} {
202
204
  cfg := config.Load()
203
205
  cfg.AccessKey = ""
@@ -214,8 +216,12 @@ func TestMarketingPreviewAndHelpDoNotReadCredentials(t *testing.T) {
214
216
  if args[1] == "query" && err == nil {
215
217
  t.Fatal("missing IDs accepted")
216
218
  }
217
- if store.loads != 0 {
218
- t.Fatal("offline operation accessed credentials")
219
+ wantLoads := 1 // Resolve the local account for space selection, without requiring login.
220
+ if args[1] == "--help" {
221
+ wantLoads = 0
222
+ }
223
+ if store.loads != wantLoads {
224
+ t.Fatalf("%v: credential loads=%d, want %d", args, store.loads, wantLoads)
219
225
  }
220
226
  }
221
227
  }
package/cmd/model.go CHANGED
@@ -6,6 +6,7 @@ import (
6
6
  "strings"
7
7
  "time"
8
8
 
9
+ "github.com/Pippit-dev/pippit-cli/internal/commandnames"
9
10
  "github.com/Pippit-dev/pippit-cli/internal/common"
10
11
  "github.com/Pippit-dev/pippit-cli/internal/models"
11
12
  "github.com/spf13/cobra"
@@ -40,11 +41,11 @@ func newModelCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Com
40
41
  return common.WriteJSON(stdout, output)
41
42
  }
42
43
  cmd := &cobra.Command{
43
- Use: "model [name-or-key]",
44
+ Use: commandnames.Model + " [name-or-key]",
44
45
  Short: "Discover available image and video models and their server configuration",
45
46
  Long: "Query available image and video models using your current credentials. Select images by their full display name (in quotes), videos by key. Successful queries are cached locally for 5 minutes. Use --refresh to bypass the cache; retry if a query fails.",
46
47
  Args: cobra.MaximumNArgs(1),
47
- RunE: withErrorLog("model", nil, func(cmd *cobra.Command, args []string) error {
48
+ RunE: withErrorLog(commandnames.Model, nil, func(cmd *cobra.Command, args []string) error {
48
49
  return query(cmd, args, len(args) > 0)
49
50
  }),
50
51
  }
@@ -56,14 +57,14 @@ func newModelCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Com
56
57
  Use: "list [query]", Aliases: []string{"search"},
57
58
  Short: "List models; search images by name, videos by key or name",
58
59
  Args: cobra.MaximumNArgs(1),
59
- RunE: withErrorLog("model list", nil, func(cmd *cobra.Command, args []string) error {
60
+ RunE: withErrorLog(commandnames.Model+" list", nil, func(cmd *cobra.Command, args []string) error {
60
61
  return query(cmd, args, false)
61
62
  }),
62
63
  })
63
64
  cmd.AddCommand(&cobra.Command{
64
65
  Use: "describe <name-or-key>", Short: "Show model parameters; use the full image name or video key",
65
66
  Args: cobra.ExactArgs(1),
66
- RunE: withErrorLog("model describe", nil, func(cmd *cobra.Command, args []string) error {
67
+ RunE: withErrorLog(commandnames.Model+" describe", nil, func(cmd *cobra.Command, args []string) error {
67
68
  return query(cmd, args, true)
68
69
  }),
69
70
  })
package/cmd/root.go CHANGED
@@ -20,6 +20,7 @@ import (
20
20
  "github.com/Pippit-dev/pippit-cli/internal/common"
21
21
  "github.com/Pippit-dev/pippit-cli/internal/config"
22
22
  "github.com/Pippit-dev/pippit-cli/internal/version"
23
+ "github.com/Pippit-dev/pippit-cli/internal/versioncheck"
23
24
  "github.com/spf13/cobra"
24
25
  )
25
26
 
@@ -86,17 +87,27 @@ func newRootCommandWithAdapter(stdout, stderr io.Writer, runner *common.Runner,
86
87
  SilenceUsage: true,
87
88
  SilenceErrors: true,
88
89
  }
89
- // Only commands already exposing --source receive attribution. Explicit empty
90
- // flags suppress fallback, and no source is persisted between CLI processes.
90
+ // Only commands already exposing --source receive attribution. Missing or blank
91
+ // flags allow fallback, and no source is persisted between CLI processes.
92
+ // 子命令执行 pre hook
91
93
  root.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
94
+ if policy.CLIOptions().SelfUpdateEnabled && cmd.Runnable() {
95
+ versioncheck.Check(cmd.Context(), stderr, version.BuildVersion(), cmd.Name())
96
+ }
92
97
  flag := cmd.Flags().Lookup("source")
93
- if flag != nil && !flag.Changed {
98
+ if flag != nil && (!flag.Changed || strings.TrimSpace(flag.Value.String()) == "") {
94
99
  if source := common.DetectHostSource(); source != "" {
95
- return cmd.Flags().Set("source", source)
100
+ if err := cmd.Flags().Set("source", source); err != nil {
101
+ return err
102
+ }
96
103
  }
97
104
  }
98
- return nil
105
+ return prepareCommandSpace(cmd, runner, stderr)
99
106
  }
107
+ // 定义根命令和子命令都能使用的字符串参数
108
+ root.PersistentFlags().String("team-id", "", "本次业务操作的空间:0 为个人空间,正整数为团队空间;不改变默认空间")
109
+ root.PersistentFlags().String("space-context", "", "固定跨进程操作的账号、API origin 和空间")
110
+ _ = root.PersistentFlags().MarkHidden("space-context")
100
111
  root.CompletionOptions.DisableDefaultCmd = true
101
112
  root.SetVersionTemplate("{{.Version}}\n")
102
113
  root.SetOut(stdout)
@@ -104,6 +115,7 @@ func newRootCommandWithAdapter(stdout, stderr io.Writer, runner *common.Runner,
104
115
  root.AddCommand(authcmd.NewLoginCommand(stdout, stderr, runner))
105
116
  root.AddCommand(authcmd.NewStatusCommand(stdout, stderr, runner))
106
117
  root.AddCommand(authcmd.NewLogoutCommand(stdout, stderr, runner))
118
+ root.AddCommand(newSpaceCommand(stdout, runner))
107
119
  root.AddCommand(canvascmd.NewCommand(stdout, stderr, runner))
108
120
  root.AddCommand(newDownloadResultCommand(stdout, stderr, runner))
109
121
  root.AddCommand(newGetCreditBalanceCommand(stdout, stderr, runner))