scriptnow-cli 0.3.94__py3-none-any.whl
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.
- cli_anything/__init__.py +0 -0
- cli_anything/scriptnow/README.md +345 -0
- cli_anything/scriptnow/__init__.py +3 -0
- cli_anything/scriptnow/__main__.py +6 -0
- cli_anything/scriptnow/scriptnow_cli.py +10354 -0
- cli_anything/scriptnow/skills/SKILL.md +305 -0
- cli_anything/scriptnow/ui.py +124 -0
- cli_anything/scriptnow/utils/__init__.py +1 -0
- cli_anything/scriptnow/utils/diag.py +163 -0
- cli_anything/scriptnow/utils/session.py +548 -0
- cli_anything/scriptnow/utils/upgrade.py +331 -0
- scriptnow_cli-0.3.94.dist-info/METADATA +88 -0
- scriptnow_cli-0.3.94.dist-info/RECORD +17 -0
- scriptnow_cli-0.3.94.dist-info/WHEEL +5 -0
- scriptnow_cli-0.3.94.dist-info/entry_points.txt +2 -0
- scriptnow_cli-0.3.94.dist-info/licenses/LICENSE +21 -0
- scriptnow_cli-0.3.94.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scriptnow
|
|
3
|
+
description: Operate ScriptNow as a governed creative-production client. Use for platform projects, planning backfill, writing candidates, adoption, storyboard delivery, or exports.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ScriptNow runtime contract
|
|
7
|
+
|
|
8
|
+
This is a runtime entrypoint, not a product manual. Do not expand it into a
|
|
9
|
+
workflow from memory and do not treat local files as ScriptNow projects.
|
|
10
|
+
|
|
11
|
+
> **CLI 安装 / 升级(生产源优先)**:PyPI 可用版本以项目页为准;首次发布完成前使用平台源。安装/升级优先从平台分发域名
|
|
12
|
+
> `https://sn.igeewa.com/downloads/scriptnow-cli/` 直装 wheel(不依赖 git),GitHub
|
|
13
|
+
> codeload / git+https 仅兜底。已安装用户用 `scriptnow self-upgrade`(自动按
|
|
14
|
+
> 「生产源 → codeload → git+https」尝试),或 `scriptnow config on` 开启后台自动升级。
|
|
15
|
+
|
|
16
|
+
> **分阶段创作(novel)**:`storymap phases` 预览叙事结构(three_act/hero_journey/
|
|
17
|
+
> kishotenketsu/linear/custom)推导的阶段计划;`storymap append-phase` 提交下一个
|
|
18
|
+
> 未完成阶段(Novel 按全书章区间规划,不强制一阶段一卷;轮轮以已采纳前缀相接,合起来是一部完整连贯的作品)。阶段只
|
|
19
|
+
> 约束跨章宏观走向,不干预单章内的节奏、伏笔与钩子。
|
|
20
|
+
|
|
21
|
+
> **结构库(可复用叙事结构模板,双域)**:把多阶段结构命名保存为模板后跨项目按 key
|
|
22
|
+
> 复用——`storymap structure-save <key> @structure.json [--description 说明]
|
|
23
|
+
> [--medium novel|script|both]`;`storymap structures` 列出内置 + 已存模板(含适用类型
|
|
24
|
+
> 与描述);`storymap structure-delete <key>` 删除。存库后 `project create --structure
|
|
25
|
+
> <key>` 或设入 direction 后 `storymap phases` 自动解析。未知 key 不报错,按 custom 兜底。
|
|
26
|
+
|
|
27
|
+
> **粗纲(分集/分章大纲·粗纲,双域)**:在集纲/章纲之前,按叙事结构阶段写一段具体剧情
|
|
28
|
+
> 纲要(竖屏剧规范「分集大纲·粗纲」)。剧本先执行
|
|
29
|
+
> `scriptnow script rough-outline-example <pid> --json`,小说执行
|
|
30
|
+
> `scriptnow novel rough-outline-example <pid> --json`。叙事结构只提供阶段与范围建议;作者可调整边界,须连续覆盖全集。
|
|
31
|
+
> Script 先统筹全剧与宏观阶段,再严格按 `rough-outline-example` 返回的
|
|
32
|
+
> `generation_batches` 分批深化;批次大小来自项目策略,不得自行假定总集数或固定 5 集。
|
|
33
|
+
> summary 按动态篇幅与事件数建议展开入口、连续行动、
|
|
34
|
+
> 阻力升级、证据/关系变化、转折、代价和出口;禁止一句话粗纲。再填写 key_beats(标题|描述)+ anchor_ids(须为已
|
|
35
|
+
> 采纳蓝图锚点)。长篇剧本执行 `scriptnow script rough-outline-start <pid> --json`
|
|
36
|
+
> 开隔离链;每阶段先执行 `scriptnow script rough-outline-phase-preview <pid> <phase_key> @file.json --json`,
|
|
37
|
+
> 经用户明确决定和完整 confirm/claim 链取得凭证后,执行
|
|
38
|
+
> `scriptnow script rough-outline-phase <pid> <phase_key> @file.json --review-token <token> --json`,
|
|
39
|
+
> 再用 `scriptnow script rough-outline-progress <pid> --json` 回读。`rough-outline-phase-preview` 会先检查单阶段连续边界、因果链与事件密度,
|
|
40
|
+
> 通过后才登记审阅包;完整 `rough-outline-check` 仅用于十阶段汇总文件;
|
|
41
|
+
> 每次回读必须向人显示“阶段 X / 共 N 阶段”、当前阶段与已完成阶段,不得只在后台维护 JSON;
|
|
42
|
+
> 上游返工加 `--restart-from` 使下游失效。全部完成并取得汇总审阅凭证后,执行
|
|
43
|
+
> `scriptnow script rough-outline-propose <pid> --review-token <aggregate_token> --json` 形成完整平台候选,
|
|
44
|
+
> 再由作者用 `rough-outline-adopt` 采纳。分集大纲稿导出:
|
|
45
|
+
> `scriptnow export create <pid> --domain script --units <全集场次> --form planning
|
|
46
|
+
> --front-matter outline`(剧名→故事梗概→人物小传→粗纲→集纲)。
|
|
47
|
+
> 完整交付可用 `--sections synopsis,characters,rough_outline,story_map,manuscript`;
|
|
48
|
+
> 平台固定按梗概→人物小传→粗纲→小说章纲/剧本集纲→正文排序,缺少已采纳材料时先补齐再导出。
|
|
49
|
+
|
|
50
|
+
> **StoryMap 隔离重建(script,替代一次生成完整80集)**:已有 StoryMap 需要重建时,
|
|
51
|
+
> 不要一次生成全集。用 `script storymap-rebuild-start <pid>` 开启隔离会话(冻结阶段计划),
|
|
52
|
+
> 逐阶段:`storymap phases` 查看阶段边界 → 本地生成该阶段集纲 → `storymap-rebuild-check
|
|
53
|
+
> <pid> <phase_key> @episodes.json`(重复度/因果/场名/状态变化)→ `storymap-rebuild-phase
|
|
54
|
+
> <pid> <phase_key> @episodes.json` 累积。全部阶段完成(会话 ready)后 `storymap-rebuild-propose`
|
|
55
|
+
> 形成完整替换候选(走普通 propose,不改现有 StoryMap);用户明确确认后才经
|
|
56
|
+
> `storymap adopt`(--confirm)替换旧结构。被替换的旧结构自动归档:script 用
|
|
57
|
+
> `script storymap-archives <pid>` 列出、`script storymap-archive <pid> <archive_id>`
|
|
58
|
+
> 查看单份(含旧集场结构与各场正文快照),novel 镜像 `novel storymap-archives` /
|
|
59
|
+
> `novel storymap-archive`。
|
|
60
|
+
|
|
61
|
+
> **StoryMap 隔离重建(novel)**:长期小说需要重建 StoryMap 时同样不要一次生成完整长卷。
|
|
62
|
+
> 命令链镜像 script:`scriptnow novel storymap-rebuild-start` / `storymap-rebuild` /
|
|
63
|
+
> `storymap-rebuild-phase` / `storymap-rebuild-phase-preview` / `storymap-rebuild-check` /
|
|
64
|
+
> `storymap-rebuild-propose`。必须先采纳小说粗纲(粗纲位于章纲之前;先 `novel
|
|
65
|
+
> rough-outline-example <pid>` 取结构建议,作者可调整边界、须连续覆盖全书),再开启隔离会话
|
|
66
|
+
> (冻结全书章区间阶段计划,不强制阶段=卷);逐阶段:本地生成该章区间的章纲 → `storymap-rebuild-check
|
|
67
|
+
> <pid> <phase_key> @chapters.json`(重复度/因果/章名/状态变化)→ `storymap-rebuild-phase
|
|
68
|
+
> <pid> <phase_key> @chapters.json` 累积。全部阶段完成(会话 ready)后 `storymap-rebuild-propose`
|
|
69
|
+
> 形成完整替换候选(走普通 propose,不改现有 StoryMap);用户明确确认后才经
|
|
70
|
+
> `storymap adopt`(--confirm)替换旧结构,禁止一次生成完整长卷。被替换的旧结构自动归档:
|
|
71
|
+
> novel 用 `novel storymap-archives <pid>` 列出、`novel storymap-archive <pid> <archive_id>`
|
|
72
|
+
> 查看单份(含旧卷章结构与各章正文快照);script 镜像 `script storymap-archives` /
|
|
73
|
+
> `script storymap-archive`,两域归档均用于重建影响审阅与回滚决策。
|
|
74
|
+
|
|
75
|
+
> **新增卷/章 = 纯追加通道(服务端硬门禁,禁止用全量替换承载新增)**:
|
|
76
|
+
> 已有 StoryMap 需要新增卷/章时,只允许追加通道 `storymap append-volume <pid> @volumes.json`
|
|
77
|
+
> / `storymap append-chapters <pid> <volume_id> @chapters.json` / `storymap append-phase
|
|
78
|
+
> <pid>`(按阶段计划追加),已有卷章的 id/序号/标题完全不动。服务端按候选形状硬门禁:
|
|
79
|
+
> `novel propose storymap` / `script propose storymap` 提交纯追加形状(仅尾部新增、已有单元
|
|
80
|
+
> 全不动)会被拒绝并指引追加通道;任意位置纯新增(头部/中间插入新卷章)同样被拒(服务端
|
|
81
|
+
> 形状门禁 R2)。全置换(retained=0、不保留任何现有单元)的普通全量提案也被拒(R1)——
|
|
82
|
+
> 恢复旧结构唯一合法通道是 `novel/script storymap-restore`(服务端按归档镜像校验放行),
|
|
83
|
+
> 全新结构仅限首次创建(空结构)或 storymap-rebuild-* 隔离链。真正重构(合并/重排/删除卷、
|
|
84
|
+
> 改标题,且保留至少一个现有单元)仍走全量 propose → `storymap adopt --confirm` 高危确认链
|
|
85
|
+
> (被替换结构自动归档)。`storymap adopt` 采纳前会显示「将移除 N 单元」警告——移除存在即
|
|
86
|
+
> 重构意图,纯新增必须走追加通道。
|
|
87
|
+
> 事故回滚:`novel storymap-restore <pid> <archive_id>` / `script storymap-restore <pid> <archive_id>`
|
|
88
|
+
> 把归档卷章/集场导出为恢复候选 JSON(服务端已拦截纯追加恢复,恢复=覆盖回旧结构,
|
|
89
|
+
> 走完整 review 链后 `storymap adopt --confirm` / `script adopt-storymap` 确认采纳)。
|
|
90
|
+
|
|
91
|
+
## Mandatory bootstrap — before any ScriptNow action
|
|
92
|
+
|
|
93
|
+
1. Run `scriptnow agent-guide --json`.
|
|
94
|
+
2. Read its `rules`, then state the next user decision in plain language.
|
|
95
|
+
3. Use `scriptnow --help` or the exact subcommand's `--help` when a parameter,
|
|
96
|
+
JSON shape, current state, or safety boundary is uncertain.
|
|
97
|
+
4. Read platform state before proposing a write. After every successful write,
|
|
98
|
+
read it back and report only the server-confirmed result.
|
|
99
|
+
|
|
100
|
+
If the bootstrap cannot be run, do not create, mutate, adopt, export, or claim
|
|
101
|
+
completion. Explain the missing prerequisite and wait.
|
|
102
|
+
|
|
103
|
+
For outline, cores, blueprint, or StoryMap files, always use the matching complete
|
|
104
|
+
command before confirmation: `scriptnow review propose-preview novel <project_id>
|
|
105
|
+
<kind> <file> --json` or `scriptnow review propose-preview script <project_id>
|
|
106
|
+
<kind> <file> --json`, where `<kind>` is one of `outline`, `cores`, `blueprint`,
|
|
107
|
+
or `storymap`. It derives the exact review resource kind and id. Never
|
|
108
|
+
guess those values. After the human explicitly decides, run
|
|
109
|
+
`scriptnow review confirm <packet_id> --decision retain --evidence "<exact human words>" --json`,
|
|
110
|
+
then `scriptnow review status <packet_id> --json` and
|
|
111
|
+
`scriptnow review claim <packet_id> --json`. Pass claim's `token` field (not
|
|
112
|
+
`packet_id`) to the target write command. If
|
|
113
|
+
the reviewed content changes, preview it again.
|
|
114
|
+
|
|
115
|
+
## Non-negotiable behavior
|
|
116
|
+
|
|
117
|
+
- The platform is the only project fact source. Do not invent project IDs,
|
|
118
|
+
paths, status, JSON schemas, or completion states.
|
|
119
|
+
- State aggregation is authoritative: `adopted` and `adopted_human` both mean
|
|
120
|
+
finalized content, with `adopted_human` preferred when both exist.
|
|
121
|
+
`chapter list`/`book` and `scene list`/`scene show` report that revision as
|
|
122
|
+
`adopted_revision`, expose `adopted_human`, and list only `candidate`/
|
|
123
|
+
`active` revisions as pending candidates. Use `--revision` to inspect a
|
|
124
|
+
pending candidate explicitly.
|
|
125
|
+
- Keep creative writes for one project serial to avoid candidate/version
|
|
126
|
+
conflicts. Different projects may run concurrently; the CLI safely
|
|
127
|
+
coordinates automatic refresh for a shared login session on macOS/Linux.
|
|
128
|
+
- Use CLI commands for every platform action; local files are temporary drafts
|
|
129
|
+
only. Return creative drafts through `propose` so the platform validates them.
|
|
130
|
+
An author's delegation to an external Agent covers guidance, reading,
|
|
131
|
+
orchestration, presentation, and the specifically requested generate/propose
|
|
132
|
+
work only. It never expands to adoption, StoryMap replacement, deletion, or
|
|
133
|
+
publishing.
|
|
134
|
+
- Planning is backfill-first: locally prepare `story_cores`, `blueprint`, and
|
|
135
|
+
`storymap`, then `propose`; platform generation is a fallback.
|
|
136
|
+
- Story cores accept 1–3 candidate drafts so the human can choose one; every
|
|
137
|
+
submitted draft must still be substantive: a complete premise/concept, five distinct
|
|
138
|
+
angles, and either Novel narrative constraints or at least two concrete
|
|
139
|
+
entries in each Script details dimension. Blueprints must cover world,
|
|
140
|
+
character, relationship, character_arc, plot, and foreshadow anchors with a
|
|
141
|
+
concrete, actionable description for every anchor (typically 50–200 characters; guidance only, not a hard gate). Both `propose` and
|
|
142
|
+
`adopt` require `planning-quality=pass`; revise/block must be repaired first.
|
|
143
|
+
- Character bibles must be substantive at creation: profile with at least
|
|
144
|
+
desire/fear/weakness/goal/inner_need, plus background/traits/arc/key_relationship/
|
|
145
|
+
secret/wound where possible. planning-quality REVISEs profiles <200 chars or
|
|
146
|
+
missing required keys; `script bible-example` shows the structure.
|
|
147
|
+
- Beats and episode/chapter outlines must be CONCRETE plot content (who does what,
|
|
148
|
+
to whom, with which object, where). Generic meta-writing like "推进矛盾 / 留下钩子 /
|
|
149
|
+
本场目标" is rejected by planning-quality (REVISE); preflight check before
|
|
150
|
+
submission runs `planning-quality storymap` (storymap group has no standalone
|
|
151
|
+
propose-preflight command). Correct: "阿澄把录音机放在柜台按下播放键,店里收音机声戛然而止".
|
|
152
|
+
- A StoryMap container is not a completed outline: every Script episode must
|
|
153
|
+
carry flat `logline`, `active_goal`, `conflict`, `turn`, `state_changes`, and
|
|
154
|
+
`anchor_ids`; every Novel chapter must carry `outline` with `summary` or
|
|
155
|
+
`logline`, `active_goal`, `conflict`, `turn`, and `state_changes` (anchors may
|
|
156
|
+
come from `outline.anchor_ids` or beats). Run `planning-quality` across the
|
|
157
|
+
full map before adoption or batch prose generation.
|
|
158
|
+
- Structural growth is append-only: add volumes/chapters only via
|
|
159
|
+
`storymap append-volume` / `storymap append-chapters` (existing ids, titles,
|
|
160
|
+
and ordering never change). New chapter beats must reference blueprint
|
|
161
|
+
anchors that already exist (`anchor_ids`); blueprint updates must keep every
|
|
162
|
+
anchor referenced by adopted StoryMap beats — missing anchors are rejected.
|
|
163
|
+
StoryMap replacement is a high-risk override that requires explicit user
|
|
164
|
+
authorization (`--confirm`) and archives the replaced structure
|
|
165
|
+
automatically.
|
|
166
|
+
- Storyboarding is also backfill-first: read `storyboard state` and `assets`,
|
|
167
|
+
run `source-preflight` before every append, register the source, then locally extract and author a valid `ScriptOut` under
|
|
168
|
+
the mounted Skills. Return it with `storyboard propose`. Platform analysis and
|
|
169
|
+
generation are fallback-only; continuity is a director/user decision. Never
|
|
170
|
+
guess an unknown episode range. Use the audited `source-range` or
|
|
171
|
+
`source-revoke --confirm` path instead of database access. Then use
|
|
172
|
+
`storyboard candidate-preview` to review the exact saved candidate; only a
|
|
173
|
+
later explicit decision may flow through `review confirm` → `review claim` →
|
|
174
|
+
`storyboard adopt --review-token`.
|
|
175
|
+
- Scene planning boards are explicit, single-scene platform actions: use
|
|
176
|
+
`storyboard scene-board list|inspect`, then `upload PROJECT SCENE FILE --layout auto --mode annotated` or
|
|
177
|
+
`generate PROJECT SCENE --layout auto --mode annotated` only when requested. The server derives layout,
|
|
178
|
+
pages, shot IDs, and digest; never write `shot.frame_refs` or bypass the API. Inspect
|
|
179
|
+
`reference_validation`: when the image proxy rejects asset images, the platform preserves the failed Attempt
|
|
180
|
+
and retries in a new no-reference Attempt. Re-upload rejected images before claiming visual consistency.
|
|
181
|
+
Generated references and boards are workspace-persisted; the platform encodes local media as base64 for later
|
|
182
|
+
multi-reference generation. Agents must use returned platform URLs and never inspect workspace paths directly.
|
|
183
|
+
- Never adopt a chapter, scene, or StoryMap without the user's explicit current
|
|
184
|
+
decision. StoryMap replacement also needs its CLI confirmation path.
|
|
185
|
+
- 逐章/逐场创作双模式(dual-mode chapter/scene creation, the user must choose
|
|
186
|
+
explicitly and the platform does not block): final prose is authored by a
|
|
187
|
+
real in-platform AgentScope Agent by default. Platform-led is the default and
|
|
188
|
+
recommended — `chapter/scene generate` produces a platform candidate →
|
|
189
|
+
`review preview` for human review → `adopt`. Only when the user explicitly
|
|
190
|
+
chooses local creation does the Agent write prose locally, backfill the
|
|
191
|
+
candidate via `chapter propose` / `scene propose`, then `review preview` →
|
|
192
|
+
`adopt --human`. Without an explicit choice, platform-led applies; never
|
|
193
|
+
default to or steer the user toward local-led writing.
|
|
194
|
+
- Creative flow is layer-by-layer in a fixed order: adopt story cores and blueprint
|
|
195
|
+
(`novel propose cores` → `adopt-core`; `novel propose blueprint` →
|
|
196
|
+
`adopt-blueprint`) first, then the synopsis outline (`novel outline` +
|
|
197
|
+
`outline-adopt`), then the rough outline (`rough-outline-example` →
|
|
198
|
+
`rough-outline-check` → `novel rough-outline` → `rough-outline-adopt`;
|
|
199
|
+
Script uses its `rough-outline-start` isolated chain), and only then plan the
|
|
200
|
+
StoryMap where episode/chapter outlines are delivered together (`propose
|
|
201
|
+
storymap` → `adopt`, `planning-quality` must pass). Cores/blueprint must
|
|
202
|
+
precede the synopsis; the rough outline depends on adopted cores/blueprint
|
|
203
|
+
anchors and the synopsis, and must precede StoryMap. Each gate is enforced by
|
|
204
|
+
the backend.
|
|
205
|
+
- Legacy projects remain readable/exportable, but a missing chapter/episode
|
|
206
|
+
outline must be backfilled before new prose. Use `chapter outline PROJECT
|
|
207
|
+
CHAPTER @outline.json` for one Novel chapter, `chapter outline-batch PROJECT
|
|
208
|
+
@outlines.json` to backfill many chapters at once (synthesised into one
|
|
209
|
+
structure candidate), or `script episode-outline PROJECT EPISODE
|
|
210
|
+
@outline.json` for one Script episode; then run `planning-quality` across the
|
|
211
|
+
full map before adoption.
|
|
212
|
+
- Background generation returns a `run_id`; poll `scriptnow run status` instead
|
|
213
|
+
of long blocking waits. On failure, repair from `status.error/detail`, then
|
|
214
|
+
inspect `scriptnow run events <run_id> --json` (`events=[]` means no events).
|
|
215
|
+
Run status also exposes persisted operation stage/progress. Fallback platform
|
|
216
|
+
StoryMap generation checkpoints at most three Script episodes or five Novel
|
|
217
|
+
chapters per batch and resumes tracking the same run after a service restart.
|
|
218
|
+
- Follow each command's returned actionable error detail exactly. Agent CLI
|
|
219
|
+
requests preserve the sanitized original domain detail when the public
|
|
220
|
+
Chinese fallback is generic; `--json` failures use
|
|
221
|
+
`{ok:false,error:{type,status,detail}}` without a traceback. Do not substitute
|
|
222
|
+
an unvalidated structure or silently retry with invented data.
|
|
223
|
+
- For rough and episode planning, treat unknown causal dependencies as sequential. Concurrency settings are ceilings for proven-independent work, never proof of independence. Reuse a checkpoint only when frozen execution identity, input signature, and predecessor chain match; unsigned or incompatible history regenerates the suffix without changing adopted content.
|
|
224
|
+
- CLI quality diagnostics are human opt-in only. An Agent must never run
|
|
225
|
+
`doctor --enable-diagnostics`, `feedback --send`, or `feedback --send --yes`
|
|
226
|
+
on its own. Only after the user explicitly requests diagnostics may the Agent
|
|
227
|
+
enable a short window; sending still requires the user's separate confirmation.
|
|
228
|
+
`doctor --disable-diagnostics` stops collection and `doctor --clear-errors`
|
|
229
|
+
deletes local v2 events. v2 never contains arguments, details, notes, paths,
|
|
230
|
+
identifiers, or creative content; legacy v1 files are never uploaded.
|
|
231
|
+
- For Novel `chapter propose`, each `block.text` is only that block's prose: never
|
|
232
|
+
embed another `blocks` JSON document in it. Ordinary JSON text is allowed; if
|
|
233
|
+
the platform rejects embedded Novel blocks, repair from its detail and regenerate.
|
|
234
|
+
- Skill delivery is progressive: use `skill mounts` and normal `skill detail`
|
|
235
|
+
summaries first. Full personal instructions require an explicit user request
|
|
236
|
+
and `skill detail --include-instructions`; never fetch them speculatively.
|
|
237
|
+
- If a mounted Skill is wrong or blocks generation, do not archive the global
|
|
238
|
+
Skill or rebuild the project. Only after explicit user approval run `skill
|
|
239
|
+
unmount <project_id> <skill_id> --confirm --json`; it disables that one
|
|
240
|
+
project mount, reads mounts back for verification, and leaves other projects
|
|
241
|
+
and versions untouched. A project with no enabled methodology Skill is not
|
|
242
|
+
ready for writing.
|
|
243
|
+
- `skill setup <project_id>` is the default pre-writing co-creation path: fetch
|
|
244
|
+
`skill setup <project_id> --json` for server-recommended presets (dialogue
|
|
245
|
+
styles, pacing, forbidden words; script domain also carries Method DNA axes),
|
|
246
|
+
walk the author through the choices in editor language, then submit
|
|
247
|
+
`--answers @answers.json --confirm --json`; the server compiles and mounts and
|
|
248
|
+
the receipt must show `gate_passed`/`mounted` (script: `method_dna` binding).
|
|
249
|
+
`skill craft` remains the deep six-question channel; old personal Skills keep
|
|
250
|
+
working.
|
|
251
|
+
- `skill craft` preflight, its creation receipt, the mount gate, and runtime
|
|
252
|
+
must resolve the same complete methodology reference; never replace it with a
|
|
253
|
+
summary or leak a cross-tenant detail.
|
|
254
|
+
- For Script writing, read the project-locked `script_format` before loading a
|
|
255
|
+
personal Skill. Vertical short-form, Chinese screenplay, and Hollywood each
|
|
256
|
+
have distinct generation, frontend, and export contracts. A personal Skill
|
|
257
|
+
extends the selected contract; it never overrides it or merges dialogue
|
|
258
|
+
across an intervening action block.
|
|
259
|
+
- Creating a script project (`project create --medium script`) defaults to
|
|
260
|
+
`script_format=chinese-short` (vertical short-drama storyboard format) unless
|
|
261
|
+
`--script-format chinese|hollywood` is passed explicitly. In an interactive
|
|
262
|
+
terminal with no `--script-format`, the author is prompted to choose the
|
|
263
|
+
format before creation (never silently locked); pass a value from the three
|
|
264
|
+
supported formats and read it back from project state before writing.
|
|
265
|
+
|
|
266
|
+
## Output discipline
|
|
267
|
+
|
|
268
|
+
Before any creative write, show the complete human-readable review packet. The
|
|
269
|
+
human chooses retain / adjust / change direction. Only an explicit retain may
|
|
270
|
+
activate a one-time token bound to the exact human-readable JSON content digest;
|
|
271
|
+
parser-added defaults must not manufacture a content change. Changed content must
|
|
272
|
+
be shown again. JSON stays backstage and never substitutes for the preview.
|
|
273
|
+
|
|
274
|
+
Any explicit decision typed by the human in conversation or on the platform is a human
|
|
275
|
+
decision. The Agent may call `review confirm` only to record those exact words; it must
|
|
276
|
+
never infer or fabricate them. Then use `review status`, claim the one-time credential with
|
|
277
|
+
`review claim`, and pass it to the target write command. Use
|
|
278
|
+
`review status` to read a user's later adjustment without asking them to repeat
|
|
279
|
+
it. `review preview` may return a `review_url` for long content, but opening the
|
|
280
|
+
page is optional; a user edit saved directly in the frontend is already a human
|
|
281
|
+
decision and must not trigger a second confirmation. Never expose token copying
|
|
282
|
+
or JSON editing as a user task.
|
|
283
|
+
|
|
284
|
+
Candidate submission and candidate adoption are two separate creative
|
|
285
|
+
decisions. Never use implicit `--adopt`. After propose, use
|
|
286
|
+
`review candidate-preview` to show the canonical platform candidate; only then
|
|
287
|
+
confirm, claim a new exact-content credential, and call the matching adopt
|
|
288
|
+
command.
|
|
289
|
+
|
|
290
|
+
Keep user-facing replies to: current fact, one proposed next decision, and the
|
|
291
|
+
result after platform read-back. Never dump this file, terminal installation
|
|
292
|
+
commands, hidden reasoning, or a generic tutorial into a creative deliverable.
|
|
293
|
+
|
|
294
|
+
For command catalogues and human setup material, use the packaged README only
|
|
295
|
+
when needed; they are reference material, not model prompt content.
|
|
296
|
+
|
|
297
|
+
## Unified creative Skill plan
|
|
298
|
+
|
|
299
|
+
CLI, Creator and creative runs use the server creative-skill-plan. Before writing, use ready-check --unit-id <unit-id> to inspect the adopted narrative stage, unit function, personal and built-in methods, and execution readiness. Submit narrative_stage and unit_function with episode/chapter outline candidates; adoption activates them. Never infer stages from episode percentages. Pacing advice is not a veto; selection, reading and application are distinct evidence.
|
|
300
|
+
|
|
301
|
+
For Script Method DNA, use only the server-backed `skill method-current`,
|
|
302
|
+
`method-compile`, `method-compare`, `method-bind`, and `method-resolve` commands.
|
|
303
|
+
The CLI must not compile rules, infer activation, or build prompt fragments. Before
|
|
304
|
+
writing a unit, inspect `ready-check --unit-id` or `skill method-resolve --unit-id`
|
|
305
|
+
for active and inactive rules, reasons, required reads, and the verification plan.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""scriptnow-cli 界面规范层 —— 轻量 ANSI 着色与排版。
|
|
2
|
+
|
|
3
|
+
原则:规范、克制、不复杂化。只做着色与对齐,不做 TUI。
|
|
4
|
+
颜色默认仅在输出到 TTY 时启用;可用 ``NO_COLOR`` / ``SCRIPTNOW_NO_COLOR``
|
|
5
|
+
环境变量或全局 ``--no-color`` 关闭,用 ``SCRIPTNOW_FORCE_COLOR=1`` 强制开启
|
|
6
|
+
(管道、CI 或演示场景)。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import random
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
_RESET = "\033[0m"
|
|
16
|
+
_BOLD = "\033[1m"
|
|
17
|
+
|
|
18
|
+
# ScriptNow 品牌金(与 Hermes Agent CLI 的金色一致):常规强调。
|
|
19
|
+
GOLD = "\033[1;38;2;255;215;0m"
|
|
20
|
+
# Matrix 荧光绿(黑底绿字):进入横幅主题色。
|
|
21
|
+
MATRIX = "\033[1;38;2;0;255;65m"
|
|
22
|
+
MATRIX_DIM = "\033[2;38;2;0;200;50m"
|
|
23
|
+
CYAN = "\033[36m" # 标题 / key
|
|
24
|
+
GREEN = "\033[32m" # 成功
|
|
25
|
+
RED = "\033[31m" # 错误
|
|
26
|
+
YELLOW = "\033[33m" # 警告
|
|
27
|
+
GREY = "\033[90m" # 次要信息
|
|
28
|
+
|
|
29
|
+
TAGLINE = "从灵感到成书 —— agent-native 创作 CLI"
|
|
30
|
+
|
|
31
|
+
# 进入横幅字符画(品牌 logo,等宽字体下展示)
|
|
32
|
+
LOGO = r""" d888888o. ,o888888o. 8 888888888o. 8 8888 8 888888888o 8888888 8888888888 b. 8 ,o888888o. `8.`888b ,8'
|
|
33
|
+
.`8888:' `88. 8888 `88. 8 8888 `88. 8 8888 8 8888 `88. 8 8888 888o. 8 . 8888 `88. `8.`888b ,8'
|
|
34
|
+
8.`8888. Y8 ,8 8888 `8. 8 8888 `88 8 8888 8 8888 `88 8 8888 Y88888o. 8 ,8 8888 `8b `8.`888b ,8'
|
|
35
|
+
`8.`8888. 88 8888 8 8888 ,88 8 8888 8 8888 ,88 8 8888 .`Y888888o. 8 88 8888 `8b `8.`888b .b ,8'
|
|
36
|
+
`8.`8888. 88 8888 8 8888. ,88' 8 8888 8 8888. ,88' 8 8888 8o. `Y888888o. 8 88 8888 88 `8.`888b 88b ,8'
|
|
37
|
+
`8.`8888. 88 8888 8 888888888P' 8 8888 8 888888888P' 8 8888 8`Y8o. `Y88888o8 88 8888 88 `8.`888b .`888b,8'
|
|
38
|
+
`8.`8888. 88 8888 8 8888`8b 8 8888 8 8888 8 8888 8 `Y8o. `Y8888 88 8888 ,8P `8.`888b8.`8888'
|
|
39
|
+
8b `8.`8888. `8 8888 .8' 8 8888 `8b. 8 8888 8 8888 8 8888 8 `Y8o. `Y8 `8 8888 ,8P `8.`888`8.`88'
|
|
40
|
+
`8b. ;8.`8888 8888 ,88' 8 8888 `8b. 8 8888 8 8888 8 8888 8 `Y8o.` ` 8888 ,88' `8.`8' `8,`'
|
|
41
|
+
`Y8888P ,88P' `8888888P' 8 8888 `88. 8 8888 8 8888 8 8888 8 `Yo `8888888P' `8.` `8'"""
|
|
42
|
+
|
|
43
|
+
# 随机字符画:保留原轮廓与明暗层次,每次运行都不同。
|
|
44
|
+
# 高密度池 = "scriptnow" 字母(双倍权重,随机拼出品牌字样)+ 原字母/数字/符号池(不丢弃);
|
|
45
|
+
# 低密度位置用稀疏字符。设 SCRIPTNOW_FIXED_LOGO=1 可固定为原始字符画。
|
|
46
|
+
_HI_POOL = "scriptnowscriptnowBDHWO80@%&$#"
|
|
47
|
+
_LO_POOL = "oOcs.~"
|
|
48
|
+
_LOGO_LOW_CHARS = frozenset("'`.,-~ ")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _logo_lines() -> str:
|
|
52
|
+
if os.environ.get("SCRIPTNOW_FIXED_LOGO") == "1":
|
|
53
|
+
return LOGO
|
|
54
|
+
rows = []
|
|
55
|
+
for line in LOGO.splitlines():
|
|
56
|
+
rows.append(
|
|
57
|
+
"".join(
|
|
58
|
+
(random.choice(_LO_POOL) if ch in _LOGO_LOW_CHARS else random.choice(_HI_POOL))
|
|
59
|
+
if ch.strip()
|
|
60
|
+
else " "
|
|
61
|
+
for ch in line
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
return "\n".join(rows)
|
|
65
|
+
|
|
66
|
+
_no_color = False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def init(no_color: bool = False) -> None:
|
|
70
|
+
"""Set the CLI-wide color policy (called once from the entry group)."""
|
|
71
|
+
global _no_color
|
|
72
|
+
_no_color = no_color
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def enabled() -> bool:
|
|
76
|
+
if _no_color:
|
|
77
|
+
return False
|
|
78
|
+
if os.environ.get("NO_COLOR") or os.environ.get("SCRIPTNOW_NO_COLOR"):
|
|
79
|
+
return False
|
|
80
|
+
if os.environ.get("SCRIPTNOW_FORCE_COLOR") == "1":
|
|
81
|
+
return True
|
|
82
|
+
return sys.stdout.isatty()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def paint(text: str, code: str = "") -> str:
|
|
86
|
+
"""Wrap *text* in *code* when color is enabled, else return it unchanged."""
|
|
87
|
+
if not code or not enabled():
|
|
88
|
+
return text
|
|
89
|
+
return f"{code}{text}{_RESET}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def banner(version: str, *, logo: bool = True) -> str:
|
|
93
|
+
"""Concise brand banner. 规范、不复杂:``logo=True`` 时带进入横幅字符画
|
|
94
|
+
(裸运行欢迎页),``logo=False`` 只输出品牌行(--help 顶部,避免干扰)。
|
|
95
|
+
Matrix 主题:荧光绿字(黑底绿字)。"""
|
|
96
|
+
head = [paint("ScriptNow CLI", MATRIX), paint(f"v{version} · {TAGLINE}", MATRIX_DIM)]
|
|
97
|
+
if logo:
|
|
98
|
+
return "\n".join([paint(_logo_lines(), MATRIX), *head])
|
|
99
|
+
return "\n".join(head)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def ok(text: str) -> str:
|
|
103
|
+
return paint(f"✓ {text}", GREEN)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def warn(text: str) -> str:
|
|
107
|
+
return paint(f"! {text}", YELLOW)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def error(text: str) -> str:
|
|
111
|
+
return paint(f"✗ {text}", RED)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def dim(text: str) -> str:
|
|
115
|
+
return paint(text, GREY)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def section(text: str) -> str:
|
|
119
|
+
return paint(text, CYAN)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def kv(key: str, value: object) -> str:
|
|
123
|
+
"""``key: value`` line with the key tinted (stable, minimal)."""
|
|
124
|
+
return f"{paint(key, CYAN)}: {value}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shared utilities for the ScriptNow CLI harness."""
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Opt-in, content-free CLI quality diagnostics (schema v2)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from contextlib import suppress
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
MAX_ERROR_ENTRIES = 50
|
|
13
|
+
MAX_ENABLE_MINUTES = 24 * 60
|
|
14
|
+
_EVENT_KEYS = {"ts", "command_key", "error_code", "phase"}
|
|
15
|
+
COMMAND_KEY_ALLOWLIST = frozenset(
|
|
16
|
+
{
|
|
17
|
+
"unknown", "login", "doctor", "feedback", "project.create", "project.list",
|
|
18
|
+
"chapter.generate", "chapter.propose", "chapter.adopt", "scene.generate",
|
|
19
|
+
"scene.propose", "scene.adopt", "storymap.propose", "storymap.adopt",
|
|
20
|
+
"run.status", "run.events", "export.create",
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
ERROR_CODE_ALLOWLIST = frozenset(
|
|
24
|
+
{
|
|
25
|
+
"CLI_UNKNOWN", "CLI_AUTH_EXPIRED", "CLI_USAGE_UNKNOWN_OPTION",
|
|
26
|
+
"CLI_USAGE_UNKNOWN_COMMAND", "CLI_HTTP_409", "CLI_HTTP_4XX",
|
|
27
|
+
"CLI_HTTP_5XX", "CLI_NETWORK",
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _config_dir() -> Path:
|
|
33
|
+
override = os.environ.get("SCRIPTNOW_CLI_CONFIG")
|
|
34
|
+
if override:
|
|
35
|
+
return Path(override).parent
|
|
36
|
+
return Path(os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))) / "scriptnow-cli"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _errors_path() -> Path:
|
|
40
|
+
return _config_dir() / "errors-v2.jsonl"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _state_path() -> Path:
|
|
44
|
+
return _config_dir() / "diagnostics-v2.json"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def diagnostics_enabled_until() -> int | None:
|
|
48
|
+
try:
|
|
49
|
+
enabled_until = int(
|
|
50
|
+
json.loads(_state_path().read_text(encoding="utf-8")).get("enabled_until", 0)
|
|
51
|
+
)
|
|
52
|
+
if enabled_until > int(time.time()):
|
|
53
|
+
return enabled_until
|
|
54
|
+
_state_path().unlink(missing_ok=True)
|
|
55
|
+
_errors_path().unlink(missing_ok=True)
|
|
56
|
+
return None
|
|
57
|
+
except (OSError, ValueError, TypeError):
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def enable_diagnostics(minutes: int = 60) -> int:
|
|
62
|
+
if minutes < 1 or minutes > MAX_ENABLE_MINUTES:
|
|
63
|
+
raise ValueError(f"minutes must be between 1 and {MAX_ENABLE_MINUTES}")
|
|
64
|
+
path = _state_path()
|
|
65
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
enabled_until = int(time.time()) + minutes * 60
|
|
67
|
+
path.write_text(json.dumps({"schema_version": 2, "enabled_until": enabled_until}), encoding="utf-8")
|
|
68
|
+
with suppress(OSError):
|
|
69
|
+
path.chmod(0o600)
|
|
70
|
+
return enabled_until
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def disable_diagnostics() -> None:
|
|
74
|
+
_state_path().unlink(missing_ok=True)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _error_code(detail: str) -> str:
|
|
78
|
+
value = str(detail).lower()
|
|
79
|
+
if "401" in value or "登录状态已失效" in str(detail):
|
|
80
|
+
return "CLI_AUTH_EXPIRED"
|
|
81
|
+
if "no such option" in value:
|
|
82
|
+
return "CLI_USAGE_UNKNOWN_OPTION"
|
|
83
|
+
if "no such command" in value:
|
|
84
|
+
return "CLI_USAGE_UNKNOWN_COMMAND"
|
|
85
|
+
if value.startswith("http 409"):
|
|
86
|
+
return "CLI_HTTP_409"
|
|
87
|
+
if value.startswith("http 4"):
|
|
88
|
+
return "CLI_HTTP_4XX"
|
|
89
|
+
if value.startswith("http 5"):
|
|
90
|
+
return "CLI_HTTP_5XX"
|
|
91
|
+
if "network error" in value or "connection" in value:
|
|
92
|
+
return "CLI_NETWORK"
|
|
93
|
+
return "CLI_UNKNOWN"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _command_key(command: str) -> str:
|
|
97
|
+
value = command.strip().lower()
|
|
98
|
+
if not value or len(value) > 80 or any(marker in value for marker in "/:="):
|
|
99
|
+
return "unknown"
|
|
100
|
+
words = value.split()
|
|
101
|
+
if len(words) > 3 or any(not re.fullmatch(r"[a-z0-9_.-]{1,32}", word) for word in words):
|
|
102
|
+
return "unknown"
|
|
103
|
+
candidate = ".".join(words)
|
|
104
|
+
return candidate if candidate in COMMAND_KEY_ALLOWLIST else "unknown"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _phase(error_code: str) -> str:
|
|
108
|
+
if error_code.startswith("CLI_AUTH"):
|
|
109
|
+
return "auth"
|
|
110
|
+
if error_code.startswith("CLI_USAGE"):
|
|
111
|
+
return "validation"
|
|
112
|
+
if error_code == "CLI_NETWORK":
|
|
113
|
+
return "transport"
|
|
114
|
+
if error_code.startswith("CLI_HTTP"):
|
|
115
|
+
return "platform"
|
|
116
|
+
return "unknown"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def record_error(*, command: str, args: tuple[str, ...], detail: str) -> str:
|
|
120
|
+
del args
|
|
121
|
+
error_code = _error_code(detail)
|
|
122
|
+
if diagnostics_enabled_until() is None:
|
|
123
|
+
return error_code
|
|
124
|
+
try:
|
|
125
|
+
path = _errors_path()
|
|
126
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
entry = {
|
|
128
|
+
"ts": int(time.time()),
|
|
129
|
+
"command_key": _command_key(command),
|
|
130
|
+
"error_code": error_code,
|
|
131
|
+
"phase": _phase(error_code),
|
|
132
|
+
}
|
|
133
|
+
lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else []
|
|
134
|
+
lines.append(json.dumps(entry, separators=(",", ":")))
|
|
135
|
+
path.write_text("\n".join(lines[-MAX_ERROR_ENTRIES:]) + "\n", encoding="utf-8")
|
|
136
|
+
with suppress(OSError):
|
|
137
|
+
path.chmod(0o600)
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
return error_code
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def recent_errors(limit: int = 20) -> list[dict[str, object]]:
|
|
144
|
+
if diagnostics_enabled_until() is None or not _errors_path().exists():
|
|
145
|
+
return []
|
|
146
|
+
output: list[dict[str, object]] = []
|
|
147
|
+
for line in _errors_path().read_text(encoding="utf-8").splitlines()[-limit:]:
|
|
148
|
+
try:
|
|
149
|
+
value = json.loads(line)
|
|
150
|
+
except ValueError:
|
|
151
|
+
continue
|
|
152
|
+
if isinstance(value, dict) and set(value) == _EVENT_KEYS:
|
|
153
|
+
output.append(value)
|
|
154
|
+
return output
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def clear_errors() -> bool:
|
|
158
|
+
try:
|
|
159
|
+
_errors_path().unlink(missing_ok=True)
|
|
160
|
+
(_config_dir() / "errors.jsonl").unlink(missing_ok=True)
|
|
161
|
+
return True
|
|
162
|
+
except OSError:
|
|
163
|
+
return False
|