Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3ea9dc0
feat: add safe PPE request routing
Aug 11, 2026
5b5f173
feat: add personal canvas create and read core
Aug 11, 2026
b904586
feat: add canvas apply and upload core
Aug 11, 2026
c64e642
feat: expose personal canvas commands
Aug 11, 2026
46257b2
feat: add local LibTV canvas planner
Aug 11, 2026
6ea42f5
ci: add beta release channel
Aug 11, 2026
463f6ad
feat: define provider-neutral CanvasPlan contract
Aug 11, 2026
3a9b1c5
feat: materialize CanvasPlan documents
Aug 11, 2026
6e7289e
feat: add secure CanvasPlan journals
Aug 11, 2026
ff8caeb
feat: execute CanvasPlan transactions safely
Aug 11, 2026
ac5bdf6
test: cover CanvasPlan recovery boundaries
Aug 11, 2026
1bc6d73
feat: isolate LibTV export execution
Aug 11, 2026
f8ed45f
feat: checkpoint canvas media uploads
Aug 11, 2026
24258bc
feat: add one-command canvas import
Aug 11, 2026
ca6c494
test: cover one-command canvas import
Aug 11, 2026
aced62f
feat: make LibTV plans executable
Aug 11, 2026
1970081
feat: verify official LibTV CLI downloads
Aug 11, 2026
e78c159
feat: export LibTV URLs through official CLI
Aug 11, 2026
e36a01a
docs: document verified LibTV imports
Aug 11, 2026
263c648
fix(canvas): add guided import progress
Aug 11, 2026
6a9f588
feat(canvas): stream verified upload readers
Aug 11, 2026
07a715f
feat(canvas): fingerprint exported media safely
Aug 11, 2026
59682af
fix(canvas): resume media uploads safely
Aug 11, 2026
10f2b65
fix(canvas): reconcile ambiguous canvas writes
Aug 11, 2026
f498e63
test(canvas): cover safe write reconciliation
Aug 11, 2026
ee755ab
fix(canvas): resume imports without replaying writes
Aug 11, 2026
5a45372
feat(canvas): guide interactive imports
Aug 11, 2026
873824d
test(canvas): cover resilient one-shot imports
Aug 11, 2026
5d88edd
feat(canvas): add interactive import TUI
Aug 11, 2026
8afa883
feat(auth): support runtime access key updates
Aug 11, 2026
f57a77a
feat(canvas): preflight LibTV authentication
Aug 11, 2026
01c472d
feat(canvas): add secure import credential prompts
Aug 11, 2026
0a21fca
feat(canvas): validate Pippit import credentials
Aug 11, 2026
30e065e
feat(canvas): keep imports running through auth recovery
Aug 11, 2026
0b93601
test(canvas): cover authenticated resumable imports
Aug 11, 2026
998f510
feat(auth): add secure CLI credential storage
Aug 11, 2026
47886cb
feat(auth): harden the browser callback flow
Aug 11, 2026
0e96f8c
feat(auth): provision managed access keys in browser login
Aug 11, 2026
b22b9ee
test(auth): cover secure browser credentials
Aug 11, 2026
ee10a44
feat(cli): add browser login commands
Aug 11, 2026
4fb7e02
feat(canvas): resume imports through browser reauthorization
Aug 11, 2026
4a38233
docs(auth): document browser login and CI override
Aug 11, 2026
6393bbc
fix(auth): initialize the secure file fallback
Aug 12, 2026
840d7a3
fix(canvas): retry only proven authentication rejections
Aug 12, 2026
3d6b25b
fix(update): keep CLI credentials out of child processes
Aug 12, 2026
c49bf19
refactor(auth): receive managed keys from web
Aug 12, 2026
8eab426
test(auth): cover direct browser credential delivery
Aug 12, 2026
0eeeb14
fix(auth): accept signed browser callback transport params
Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,33 @@ jobs:
node-version: "20"
registry-url: "https://registry.npmjs.org"

- name: Select npm dist-tag
id: npm_channel
shell: bash
run: |
set -euo pipefail
version="$(node -p "require('./package.json').version")"
if [[ "${GITHUB_REF_NAME}" != "v${version}" ]]; then
echo "Tag ${GITHUB_REF_NAME} does not match package.json version ${version}" >&2
exit 1
fi
case "${version}" in
*-beta.*)
dist_tag="beta"
;;
*-*)
echo "Unsupported prerelease channel in ${version}; only beta releases are publishable" >&2
exit 1
;;
*)
dist_tag="latest"
;;
esac
echo "dist_tag=${dist_tag}" >> "${GITHUB_OUTPUT}"

- name: Run tests
run: npm test

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
Expand All @@ -40,6 +67,6 @@ jobs:
cp dist/checksums.txt checksums.txt

- name: Publish to npm
run: npm publish --provenance --access public
run: npm publish --provenance --access public --tag "${{ steps.npm_channel.outputs.dist_tag }}"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Test

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Run tests
run: npm test
64 changes: 62 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,71 @@ python3 skills/xyq-nest-skill/scripts/download_results.py \
- 超时:连续轮询 48 小时无结果则停止。
- 错误重试:单次失败可重试 1 次,连续 3 次失败则停止。

## Canvas beta

Canvas beta 暴露个人漫剧画布的通用原子命令,不在服务端增加任何第三方“导入”语义:

```bash
npx @pippit-dev/cli@beta install
pippit-tool-cli login

# 仅测试 PPE 时配置;生产环境不要设置
export PIPPIT_CLI_PPE_ENV="ppe_cli_canvas_ak"
# 也可以在任意命令上使用:--ppe-env ppe_cli_canvas_ak

pippit-tool-cli canvas create --title "Imported draft" --request-id request_001 --wait
pippit-tool-cli canvas upload --path ./clip.mp4
pippit-tool-cli canvas get --asset-id CANVAS_ASSET_ID
pippit-tool-cli canvas apply --project-id PROJECT_ID --file ./patch.json
```

`canvas create/get/apply/upload` 的输出均为单行 JSON,所有资源 ID 保持字符串,便于脚本和 Agent 调用。`create` 的 `request_id` 当前用于追踪和恢复,不是跨服务崩溃窗口的严格幂等键;请求结果不明确时不要盲目重试。beta 的 `apply` 每次只接受一个 transaction(可以包含多个 patches),并严格检查该 transaction 的 ACK 和每个资产版本;当前服务端仍不保证跨资产 all-or-nothing,调用方应在写后执行 `get` 校验,并持久化自己的 operation journal。

`login` 会打开小云雀网页完成授权,并把本机设备专属凭证保存到系统安全凭证库;不会在终端显示 Access Key。后续原生 CLI 命令会自动读取该凭证。`XYQ_ACCESS_KEY` 仅作为 CI/Agent 的显式覆盖保留,且优先级高于网页登录凭证;配置错误时不会静默回退到个人登录。

PPE 只影响登录完成后的 Pippit API 同源业务请求,不改变登录账号或凭证。CLI 不会把 Access Key、`x-tt-env`、`x-use-ppe` 或 `x-schedule-vdc` 转发给第三方绝对 URL;`--ppe-env` 的优先级高于 `PIPPIT_CLI_PPE_ENV`,二者都未提供时访问生产环境。同一个有效登录凭证可用于生产环境和 PPE。

### 一键导入 LibTV 画布

LibTV 迁移只是上述通用画布能力的 CLI 编排层,服务端不识别 LibTV。普通用户只需启动交互式导入:

```bash
pippit-tool-cli --ppe-env ppe_cli_canvas_ak canvas import
```

CLI 会在交互终端中显示彩色向导:使用 ↑/↓ 移动、Enter 确认,逐步选择来源、journal 策略与是否打开结果,只有每个项目唯一的 LibTV 链接需要粘贴。journal 选择 Automatic 即使用权限受控的自动路径,不需要设置环境变量。交互式导入遇到已知的非致命降级时会输出 warning 后自动继续,并在最终 JSON 中保留 `degradation_count`。源端节点处理、素材下载与 Pippit 素材上传会在 stderr 显示已处理/总数/剩余数,画布创建、写入和回读校验会显示当前阶段;最终 stdout 仍只输出一行 JSON。设置 `PIPPIT_CLI_ACCESSIBLE=1` 可切换为无控制序列的朴素提示模式。

供 Agent、CI 或其它非交互场景使用时,仍可显式传入 `--from`、`--url`、`--accept-degradations` 和 `--open`;`--journal` 始终可选,省略时使用自动路径。

给定链接后,CLI 会通过官方 LibTV CLI 完成网页授权与草稿/素材导出,再依次调用通用的 `upload`、`create`、内部 ID 分配、单 transaction `apply` 和 `get` 全量校验。

生产环境使用时删除 `--ppe-env ppe_cli_canvas_ak`。交互式导入会在下载 LibTV 项目和素材之前依次校验小云雀与 LibTV 登录;小云雀未登录或凭证失效时会直接打开浏览器授权,成功后在同一进程继续,不再要求粘贴 Access Key。

首次运行时,若本机没有 LibTV CLI,导入器只会从 LibTV 官方静态域下载固定版本 1.1.3 的对应平台 ZIP,并同时校验 ZIP 和可执行文件的内置 SHA-256;不会执行远程安装脚本。若官方 LibTV CLI 尚未登录,它会打开 `libtv login web --open` 的官方网页授权流程,导入器本身不读取浏览器 Cookie 或 LibTV credential 文件。

`--accept-degradations` 表示接受计划中明确列出的不可移植节点。例如没有生成结果的图片/视频节点会保留为空占位,LibTV 私有 `video-clip` 会降级成空的 Pippit video-composite。交互式导入会显示中文 warning 后自动继续;非交互调用未传该参数时,CLI 会在任何 Pippit 写入前停止。

导入状态会写入权限为 `0600` 的本地 journal。素材上传、画布创建或 transaction 结果不明确时,CLI 会保留已获得的持久 ID 并拒绝盲目重复写入;重复执行同一条命令会优先 query-back 恢复。只有 root 和所有伴生资产逐一通过 canonical hash 校验后,命令才返回 `state=verified` 并执行 `--open`。

需要单独检查或生成 ID-neutral `pippit-canvas-plan/0.1` 时,仍可使用纯本地 adapter:

```bash
pippit-tool-cli libtv plan \
--snapshot ./libtv-snapshot.json \
--media-manifest ./bundle-media.json \
--title "Imported draft" \
--output ./canvas-plan.json
```

完整 provider 边界见 `adapters/libtv/README.md`。

## 短剧工作流技能

包发布后可以通过 npm 安装。安装器会按当前系统下载匹配的预构建二进制文件,支持 macOS、Linux 和 Windows:

```bash
npx @pippit-dev/cli@latest install
export XYQ_ACCESS_KEY="<access-key>"
pippit-tool-cli login
pippit-tool-cli --version
pippit-tool-cli short-drama +submit-run --message "写一个赛博朋克短剧开头"
pippit-tool-cli short-drama +upload-file --path ./reference.doc
Expand Down Expand Up @@ -333,4 +391,6 @@ pippit-tool-cli query-result \

## 鉴权

`short-drama +submit-run`、`get-thread`、`list-thread-file`、`short-drama +upload-file` 以及 `xyq-skill` Python 脚本都使用 `Authorization: Bearer <XYQ_ACCESS_KEY>` 鉴权。OAuth 命令代码仍保留在仓库中,但短剧运行时请求不使用 OAuth。
原生 CLI 命令通过 `pippit-tool-cli login` 获取并安全保存的设备专属凭证鉴权。可用 `pippit-tool-cli status` 查看状态、`pippit-tool-cli logout` 清除本机网页登录凭证;这些命令都不会输出 Access Key。CI/Agent 可继续显式设置 `XYQ_ACCESS_KEY`,它会覆盖本机网页登录凭证。

`skills/xyq-nest-skill/scripts` 下的独立 Python 脚本尚未接入原生 CLI 的安全凭证库,当前仍需要 `XYQ_ACCESS_KEY`;不要把这一限制误解为 `pippit-tool-cli` 原生命令仍需手工配置 AK。
115 changes: 115 additions & 0 deletions adapters/libtv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# LibTV canvas adapter

This directory is a provider adapter, not a Pippit API client. It converts a
LibTV snapshot into the ID-neutral `pippit-canvas-plan/0.1` contract. It does
not read an access key, choose a Pippit environment, allocate Pippit asset IDs,
upload files, create a project, write assets, bind a canvas, or use team state.

Export a LibTV URL with the official LibTV CLI, then generate a plan:

```bash
node adapters/libtv/cli.mjs export \
--url 'https://www.liblib.tv/canvas?projectId=<project-id>' \
--output-dir ./libtv-bundle
```

Before an import starts reading a project or downloading media, callers can
run the independent authentication preflight:

```bash
node adapters/libtv/cli.mjs auth
```

This command only prepares the verified LibTV CLI and runs `account info`. If
the account is not authenticated, it runs `login web --open` and verifies the
account again. It never receives or reads a project URL, node, or media file.
Progress and browser-login output go to stderr; successful stdout remains one
JSON object using `pippit-libtv-auth-result/0.1`. `--non-interactive` reports
`AUTH_REQUIRED` without trying to read any project data, so an orchestration
layer can retry after arranging authentication.

An explicit `--libtv-cli`, `LIBTV_CLI_BINARY`, or `LIBTV_CLI_PATH` opts into a
user-managed binary after a version check. Without an explicit override, the
exporter never executes `libtv` from `PATH` or `~/.libtv`; it goes directly to
the verified cache/bootstrap path. This prevents an ambient binary from
bypassing the pin. Bootstrap installs the pinned official 1.1.3 ZIP into the
private Pippit tool cache and supports
Darwin, Linux, and Windows on arm64/x64; it verifies both ZIP and binary against
embedded SHA-256 values, uses `0700` directories/binary, and never executes a
remote installer or script. Windows extraction uses the built-in `tar.exe`
(Windows 10+); absence of a safe local extractor fails closed. Set
`PIPPIT_CLI_LIBTV_CACHE_DIR` to override the cache root.

After locating a verified CLI, the exporter probes existing official CLI
credentials with `libtv account info`. If none are available, interactive use
runs `libtv login web --open`; `--non-interactive` instead fails with an
actionable login message. Login child output is redirected to stderr so stdout
remains one machine-readable JSON object. Project, node-detail, media-download,
and final count progress also goes to stderr. Phase lines precede CLI setup,
authentication, and project fetch; every media download emits a start line
before the potentially long transfer. The adapter never reads browser
cookies or the LibTV credential file. The LibTV child receives only a small
runtime/login environment allowlist. Unknown variables, SSH agent sockets, and
all unlisted key/token/password values are omitted. HTTP/HTTPS/SOCKS proxy URLs
are passed only when they contain no user information.

The output directory must not already exist. Export is staged privately and
renamed atomically, so cancellation, permission denial, or a partial media
download leaves no final bundle. The successful stdout object uses
`pippit-libtv-export-result/0.1` and returns `plan_path`, `snapshot_path`,
`media_manifest_path`, and absolute local paths for each media item.

The bundle contains:

- a URL- and credential-sanitized `snapshot.json`;
- `media-manifest.json` (`pippit-libtv-media-manifest/0.1`) with bundle-relative
paths, byte sizes, and bare lowercase SHA-256 digests;
- local files downloaded through official `libtv download`, preserving LibTV's
source-account permission and watermark behavior;
- `plan.json` (`pippit-canvas-plan/0.1`).

To convert an existing snapshot instead:

```bash
node adapters/libtv/cli.mjs plan \
--snapshot ./libtv-snapshot.json \
--media-manifest ./bundle-media.json \
--title "My imported canvas" \
--output ./canvas-plan.json
```

`--media-manifest` is optional. It may provide `sourceNodeId` + `fileName` rows
for an older export, or `source_node_id`, `relative_path`, `sha256`, and
`media_type` rows for a local bundle. Existing prototype manifests may also
contain Pippit IDs or authorization metadata; the adapter deliberately ignores
those fields and never copies them into the plan.

The generated plan is written with mode `0600`. Official URL export always
uses bundle-relative `local_path` + `sha256` and omits source URLs. Legacy
snapshot-only conversion may still accept an absolute HTTPS media URL, but its
fingerprint strips query strings and authentication fields.

The generic canvas executor owns the remaining steps:

1. resolve/download and upload each `required_media` item;
2. create a personal novel canvas;
3. allocate Pippit IDs and materialize the logical nodes/edges/groups;
4. apply a provider-neutral `canvas.write` transaction;
5. query the assets back and verify them.

Plan IDs (`node:*`, `group:*`, `edge:*`, and `media:*`) are logical and stable
within the source snapshot. They are never Pippit asset IDs. The executor must
allocate new personal asset IDs and keep the logical-to-Pippit mapping in its
resume journal. A plan deliberately has no creation timestamp, so the same
snapshot and media mapping produce byte-for-byte stable JSON; the executor
should hash the complete plan when deriving its operation identity.

The v0.1 adapter fails closed for unsupported node types or dangling edges.
Supported source types are `group`, `image`, `video`, `audio`, and
`video-clip`. Empty LibTV image/video generation nodes are preserved as
`image-placeholder` / `video-placeholder` with an explicit degradation; they
are not mistaken for partially downloaded media.

LibTV `video-clip` nodes do not carry a portable generated result. The plan
preserves their input references and records an explicit degradation to an
empty Pippit `video-composite` placeholder.
Loading