@lark-project/cli
Version:
飞书项目插件开发工具
110 lines (109 loc) • 7.59 kB
TypeScript
import type { PointDiffEntry } from "../local-config";
/**
* 删除(点位 / 属性)感知闸口 + 三档授权模型。
*
* 定位(务必如实):这套**最大化"让用户感知删除 + 逼守约定的 AI 喊人",不是技术强制**。
* 唯一硬保证:删除不会在第一次调用就发生(exit 2,fail-closed);CLI 在 AI 下游,保证不了
* 那第二步授权是「人」做的。真正"挡任何 AI"只能放 harness(之上)或后端(之外),不在此。
* 详见 docs/superpowers/specs/2026-06-17-ai-property-deletion-gate-design.md。
*/
/** 汇总一份 diff 里所有「会删除的东西」为 canonical token:点位 `<type>[<key>]`、属性 `<type>[<key>].<array>.<id>`。 */
export declare function deletionTokens(diffs: PointDiffEntry[]): string[];
/**
* nonce 绑死「本次删除集 + 所属插件」:tokens 或 pluginId 任一变 → nonce 变 → 旧 grant 失效。
* pluginId 进 hash 输入(而非可见 token)→ 授权按工程隔离:A、B 两个工程即便删除集逐字节相同,
* nonce 也不同,grant 不会跨工程串用;allow-delete 仍只按完整 nonce 匹配、无需知道 pluginId。
* 稳定排序后取 sha256 前 12 位。
*/
export declare function computeNonce(tokens: string[], pluginId: string): string;
/** 项目 `<cwd>/.lpm/config.json` 或全局 `~/.lpm/config.json` 任一 `allowDelete:true` → standing allow。 */
export declare function hasStandingAllow(cwd?: string): boolean;
/** 分别返回项目 / 全局 standing allow 的开启状态(供 `lpm allow-delete --status`)。 */
export declare function getStandingAllowStatus(cwd?: string): {
project: boolean;
global: boolean;
};
export type StandingScope = 'project' | 'global';
/** 设/清 standing allow(高风险弃权——调用方负责在设置当下打风险警示 + 取用户确认)。返回写入的文件路径。 */
export declare function setStandingAllow(scope: StandingScope, allow: boolean, cwd?: string): string;
/**
* 写一次性删除授权(由 `lpm allow-delete` 在用户确认后调用)。返回写入路径。
* cwd 必须是插件工程根,否则 throw——与 writePendingDeletion / setStandingAllow 一致,绝不在错误目录 litter .lpm/。
* 调用方(allow-delete)负责在 token 兜底流里 catch:那条流靠回传 grant token 放行,本地 grant 文件对它无用。
*/
export declare function writeDeleteGrant(nonce: string, tokens: string[], cwd?: string): string;
/** 命中则消费(删除授权文件)并返回 true;一次性、用完即焚。 */
export declare function consumeDeleteGrant(nonce: string, cwd?: string): boolean;
/** 授权是否存在(不消费)——`set` 用它「放行但不消费」,把一次性消费留给 `update` 的真推送。 */
export declare function hasDeleteGrant(nonce: string, cwd?: string): boolean;
export interface DeletionTokenPayload {
v: 1;
pluginId: string;
nonce: string;
tokens: string[];
/** 仅供 allow-delete 展示权威清单的精简 diff(type/key/name/status/removedProperties),不含字段级 changes。 */
diffs: PointDiffEntry[];
}
/** 把「本次删除集 + 所属插件」编码成自包含、可 copy-paste 的 token(`LPMDEL1.<base64url(payload)>`)。 */
export declare function encodeDeletionToken(pluginId: string, diffs: PointDiffEntry[]): string;
/** 解码 + 自校验(nonce 必须 = computeNonce(tokens, pluginId))。破损 / 篡改 / 非 token / 空 → undefined。 */
export declare function decodeDeletionToken(token: string | undefined): DeletionTokenPayload | undefined;
/**
* grant token 是否授权「此刻这批删除」:解码 + 校验 token 的 nonce 等于用「当前真实删除集 + pluginId」
* 重算的 nonce。token 多删 / 少删 / 属于别的工程 → nonce 不等 → 不放行。
*/
export declare function deletionGrantTokenPasses(token: string | undefined, pluginId: string, diffs: PointDiffEntry[]): boolean;
export interface PendingDeletion {
nonce: string;
tokens: string[];
/** 被拦时的删除条目(只留展示所需字段),供 `lpm allow-delete` 打权威清单——不依赖重算。 */
diffs: PointDiffEntry[];
/** 所属插件——供 allow-delete 的 nonce 路径重新编出 grant token G(沙箱回传用)。旧记录可能缺省。 */
pluginId?: string;
}
/** 闸口拦下时持久化本次删除清单(`set` 在写本地前就 exit 2,draft 未落盘,故 allow-delete 不能靠重算)。 */
export declare function writePendingDeletion(nonce: string, diffs: PointDiffEntry[], pluginId?: string, cwd?: string): void;
export declare function readPendingDeletion(nonce: string, cwd?: string): PendingDeletion | undefined;
export declare function removePendingDeletion(nonce: string, cwd?: string): void;
export interface DeletionGateResult {
tokens: string[];
nonce: string;
hasDeletion: boolean;
/** 是否放行:无删除、或 standing allow 命中、或一次性 grant 命中(已消费)、或 grant token 校验通过。 */
pass: boolean;
via: 'none' | 'standing' | 'grant' | 'grant-token';
}
/**
* 评估删除闸口。无删除 → pass=true / hasDeletion=false。调用方:`!pass && hasDeletion` → 打 notice + exit 2。
* 放行优先级:standing allow → grant token(无状态、跨沙箱)→ 本地一次性 grant 文件。
* `opts.consume`(默认 true):本地 grant **文件**命中时是否消费(grant token 是无状态的,本就不消费)。
* `update`(不可逆推送)消费;`set`(可逆写本地)只「探测不消费」,把一次性消费留给 update——授权一次两步都过。
* `opts.grantToken`:用户 `allow-delete` 打印、经命令行 `--delete-grant` 回传的 grant token(沙箱场景)。
*/
export declare function tryPassDeletionGate(diffs: PointDiffEntry[], pluginId: string, cwd?: string, opts?: {
consume?: boolean;
grantToken?: string;
}): DeletionGateResult;
export declare function countDeletions(diffs: PointDiffEntry[]): {
points: number;
properties: number;
};
/**
* 顶部汇总横幅——「扫一眼也漏不掉」。无删除返回 ''。
* 先打**总数**再分维度,避免「0 个点位、…」开头那个 0 抢视觉、让人误读成"没删东西"
* (只删属性、点位本身没删时尤其会被埋)。
*/
export declare function deletionBanner(diffs: PointDiffEntry[]): string;
/** 删除清单两段:整点位 / 属性。每段为空则不出现。 */
export declare function formatDeletionLines(diffs: PointDiffEntry[]): string[];
/**
* 删除被拦时写 stderr。文案承重:**把授权指派给「用户」、不给 AI 递自助解锁配方**。
* `lpm allow-delete` 由用户在自己终端跑、打权威清单 + 强制确认。
*
* 主路径(同一项目目录):pending / grant 落项目 <cwd>/.lpm/(沙箱 bind-mount 的共享 workdir,
* 沙箱内外同绝对路径可见)。打印 `lpm --cwd <abs> allow-delete <nonce>`——带 `--cwd` 让用户在任意
* 目录都能跑、且必定落到同一个项目 .lpm/(防止在错的目录跑找不到 pending)。
* 兜底(极少:项目目录都不共享):自包含 token——用户 `allow-delete <token>` → grant token G →
* agent `update --delete-grant <G>`,全程不依赖任何共享目录。`pluginId` 进 pending + token。
*/
export declare function writeDeletionBlockNotice(diffs: PointDiffEntry[], nonce: string, pluginId: string, cwd?: string): void;