tonedial 0.1.6 → 0.1.7
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/README.es.md +37 -26
- package/README.ja.md +37 -26
- package/README.ko.md +37 -26
- package/README.md +37 -26
- package/README.zh.md +37 -26
- package/dist/cli.js +12 -9
- package/dist/commands/compile.js +1 -1
- package/dist/commands/doctor.js +6 -2
- package/dist/commands/get.js +3 -1
- package/dist/commands/rebuild.js +1 -1
- package/dist/commands/show.js +3 -1
- package/dist/commands/test.js +2 -1
- package/dist/commands/toggle.js +23 -0
- package/dist/core/anchors.js +2 -0
- package/dist/core/compile.js +7 -2
- package/dist/core/config.js +8 -2
- package/dist/core/i18n.js +26 -8
- package/dist/core/legacy-tone.js +7 -2
- package/dist/core/locales/es.js +13 -4
- package/dist/core/locales/ja.js +13 -4
- package/dist/core/locales/zh.js +13 -4
- package/dist/core/reader.js +16 -2
- package/dist/core/resolve.js +1 -0
- package/dist/core/seeds.js +3 -3
- package/dist/core/skill.js +37 -10
- package/package.json +1 -1
package/README.zh.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**给编程代理一个角色。**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
ToneDial 是本地 CLI,用于调整编程代理的语气并在不同项目中复用。
|
|
6
6
|
|
|
7
7
|
**7 个角色 · 12 个语气轴 · 5 种 CLI 语言 · 零运行时依赖**
|
|
8
8
|
|
|
@@ -19,9 +19,36 @@
|
|
|
19
19
|
|
|
20
20
|
**[查看全部 7 个角色 →](https://github.com/awesometic/tonedial/blob/main/docs/EXAMPLES.zh.md)**
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## 同一模型,更精简的计划
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
在 `gpt-5.6-sol` / `high` 下,一个全新会话使用 Codex 默认设置,另一个使用下方完整的 `vibe-coder` 配置。
|
|
25
|
+
`verbosity=1` 只是该配置的一部分,并非唯一差异。
|
|
26
|
+
|
|
27
|
+
**指令**
|
|
28
|
+
|
|
29
|
+
> I want to make ToneDial easier for first-time users. The README should get them from installation through `tonedial init` to their first preview with `tonedial test codex --preset <slug>`, and the CLI should make that next step obvious after `init`. Give me a prioritized three-step improvement plan for the README and first-run CLI flow.
|
|
30
|
+
|
|
31
|
+
<img src="https://raw.githubusercontent.com/awesometic/tonedial/main/docs/media/readme/zh/verbosity-comparison.svg" alt="GPT-5.6 Sol high 使用 Codex 默认设置和完整 vibe-coder 配置回答同一条 ToneDial 改进计划指令;配置后的英文回复从 148 个词缩短到 82 个词" width="760">
|
|
32
|
+
|
|
33
|
+
<sub>同一指令 · 每个新会话的首次完整回复 · 英文回复 148 个词 → 82 个词。这是一次示例性对比,并非只隔离单个轴的基准测试。回复用于展示语气差异;产品行为和命令请以下方指南为准。 <a href="https://github.com/awesometic/tonedial/blob/main/docs/media/readme/verbosity-comparison.json">输入与生成记录</a>.</sub>
|
|
34
|
+
|
|
35
|
+
## 开始试用
|
|
36
|
+
|
|
37
|
+
需要 **Node 24 或更高版本**和 npm。
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install --global tonedial
|
|
41
|
+
tonedial init --tools none --global --lang zh
|
|
42
|
+
tonedial set
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
设置时会先展示文件变更并请求批准。无需连接代理即可先打开面板试用。
|
|
46
|
+
|
|
47
|
+
[连接代理](https://github.com/awesometic/tonedial/blob/main/docs/USAGE.md#connecting-an-agent) · [使用临时设置试用](https://github.com/awesometic/tonedial/blob/main/docs/TESTING.md#isolated-candidate)
|
|
48
|
+
|
|
49
|
+
## 复制一种语气
|
|
50
|
+
|
|
51
|
+
复制完整配置,或把它作为自定义设置的起点。
|
|
25
52
|
每条命令都会保存并应用完整配置;`--auto` 会批准显示的更改。
|
|
26
53
|
|
|
27
54
|
实际 `tonedial test codex` 回复 · `gpt-6.1-sol` / `high` · 每组配置保留问题的首次回答 · 仅对话,不编辑文件。 [输入与生成记录](https://github.com/awesometic/tonedial/blob/main/docs/media/readme/zh/use-cases.json).
|
|
@@ -35,7 +62,7 @@
|
|
|
35
62
|
话少、直接选定推荐方案,简短汇报做完了什么、还剩什么的编程搭档。
|
|
36
63
|
|
|
37
64
|
<details>
|
|
38
|
-
<summary
|
|
65
|
+
<summary>配置与实际回复</summary>
|
|
39
66
|
|
|
40
67
|
```bash
|
|
41
68
|
tonedial create '彻底放手的氛围编程' --slug vibe-coder --auto \
|
|
@@ -51,8 +78,6 @@ tonedial create '彻底放手的氛围编程' --slug vibe-coder --auto \
|
|
|
51
78
|
|
|
52
79
|
试试这个语气: `tonedial test codex --preset vibe-coder`
|
|
53
80
|
|
|
54
|
-
</details>
|
|
55
|
-
|
|
56
81
|
**Codex**
|
|
57
82
|
|
|
58
83
|
> 老板,我选把 `null` 和 `undefined` 转为空字符串,避免 `trim()` 报错:
|
|
@@ -63,13 +88,14 @@ tonedial create '彻底放手的氛围编程' --slug vibe-coder --auto \
|
|
|
63
88
|
> }
|
|
64
89
|
> ```
|
|
65
90
|
|
|
91
|
+
</details>
|
|
66
92
|
|
|
67
93
|
### 需要安慰的时候
|
|
68
94
|
|
|
69
95
|
温柔、爱夸人,偶尔说几句安慰的话,像个让人安心的搭档。
|
|
70
96
|
|
|
71
97
|
<details>
|
|
72
|
-
<summary
|
|
98
|
+
<summary>配置与实际回复</summary>
|
|
73
99
|
|
|
74
100
|
```bash
|
|
75
101
|
tonedial create '需要安慰的时候' --slug steady-companion --auto \
|
|
@@ -85,8 +111,6 @@ tonedial create '需要安慰的时候' --slug steady-companion --auto \
|
|
|
85
111
|
|
|
86
112
|
试试这个语气: `tonedial test codex --preset steady-companion`
|
|
87
113
|
|
|
88
|
-
</details>
|
|
89
|
-
|
|
90
114
|
**Codex**
|
|
91
115
|
|
|
92
116
|
> 我的选择:搭档,把 `null` 和 `undefined` 当作空字符串,其他输入保留原来的处理方式。
|
|
@@ -101,13 +125,14 @@ tonedial create '需要安慰的时候' --slug steady-companion --auto \
|
|
|
101
125
|
>
|
|
102
126
|
> 你贴出的函数很清楚,指出触发条件很细致,聚焦一个方案很明智,要求直接替换的版本也很周到。辛苦了,我陪你拆问题,慢慢来,放心先喘口气——这个空值小坑已经有块补丁了。
|
|
103
127
|
|
|
128
|
+
</details>
|
|
104
129
|
|
|
105
130
|
### 就喜欢嘴狠的
|
|
106
131
|
|
|
107
132
|
脏话照说,嘲讽照给,活也照样干利索的狠角色。
|
|
108
133
|
|
|
109
134
|
<details>
|
|
110
|
-
<summary
|
|
135
|
+
<summary>配置与实际回复</summary>
|
|
111
136
|
|
|
112
137
|
```bash
|
|
113
138
|
tonedial create '就喜欢嘴狠的' --slug rough-but-reliable --auto \
|
|
@@ -123,8 +148,6 @@ tonedial create '就喜欢嘴狠的' --slug rough-but-reliable --auto \
|
|
|
123
148
|
|
|
124
149
|
试试这个语气: `tonedial test codex --preset rough-but-reliable`
|
|
125
150
|
|
|
126
|
-
</details>
|
|
127
|
-
|
|
128
151
|
**Codex**
|
|
129
152
|
|
|
130
153
|
> 喂,老子选空值返回空字符串这招,一行就把这他妈的崩溃点收拾利索:
|
|
@@ -137,23 +160,11 @@ tonedial create '就喜欢嘴狠的' --slug rough-but-reliable --auto \
|
|
|
137
160
|
>
|
|
138
161
|
> `null` 或 `undefined` 返回 `""`,`" Ada "` 返回 `"Ada"`。
|
|
139
162
|
|
|
163
|
+
</details>
|
|
164
|
+
|
|
140
165
|
Codex 开始对话后,输入上面的问题即可。
|
|
141
166
|
模型与推理强度由 Codex 设置,与 ToneDial 配置分开。
|
|
142
167
|
|
|
143
|
-
## 开始试用
|
|
144
|
-
|
|
145
|
-
需要 **Node 24 或更高版本**和 npm。
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
npm install --global tonedial
|
|
149
|
-
tonedial init --tools none --global --lang zh
|
|
150
|
-
tonedial set
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
设置时会先展示文件变更并请求批准。无需连接代理即可先打开面板试用。
|
|
154
|
-
|
|
155
|
-
[连接代理](https://github.com/awesometic/tonedial/blob/main/docs/USAGE.md#connecting-an-agent) · [使用临时设置试用](https://github.com/awesometic/tonedial/blob/main/docs/TESTING.md#isolated-candidate)
|
|
156
|
-
|
|
157
168
|
## 选择你的角色
|
|
158
169
|
|
|
159
170
|
| 预设 | 你 → 代理 |
|
package/dist/cli.js
CHANGED
|
@@ -30,13 +30,14 @@ import { runEject } from "./commands/eject.js";
|
|
|
30
30
|
import { runInit } from "./commands/init.js";
|
|
31
31
|
import { runUnwire, runWire } from "./commands/wire.js";
|
|
32
32
|
import { runTest } from "./commands/test.js";
|
|
33
|
+
import { runOff, runOn } from "./commands/toggle.js";
|
|
33
34
|
export const JSON_SCHEMA = 3;
|
|
34
35
|
/**
|
|
35
36
|
* Stated here rather than read from `package.json`, because reading a package-relative file at
|
|
36
37
|
* run time is the one thing that breaks all three binary packagers at once. A test asserts this
|
|
37
38
|
* equals the manifest, which is the cheap half of the trade.
|
|
38
39
|
*/
|
|
39
|
-
export const VERSION = "0.1.
|
|
40
|
+
export const VERSION = "0.1.7";
|
|
40
41
|
const FLAG_NAMES = ["project", "check", "explain", "global", "here", "migrateLegacy", "auto", "dryRun", "fix", "rollback", "quarantineOperation", "fromOperation", "fromPreset", "restorePreset", "preset", "slug", "acceptUsage", "lang", "commands", "tools"];
|
|
41
42
|
/** Command-line spelling for each flag, where it differs from the field name. */
|
|
42
43
|
const FLAG_SPELLING = {
|
|
@@ -49,6 +50,8 @@ const FLAG_SPELLING = {
|
|
|
49
50
|
};
|
|
50
51
|
/** Each command declares its accepted flags and positional limit; null means unlimited. */
|
|
51
52
|
const COMMANDS = {
|
|
53
|
+
on: { run: runOn, flags: [], max: 0, context: "state" },
|
|
54
|
+
off: { run: runOff, flags: [], max: 0, context: "state" },
|
|
52
55
|
set: { run: runSet, flags: [], max: null, context: "state" },
|
|
53
56
|
get: { run: runGet, flags: [], max: 1 },
|
|
54
57
|
test: { run: runTest, flags: ["project", "dryRun", "preset", "acceptUsage"], max: 1 },
|
|
@@ -81,6 +84,8 @@ export const COMMAND_NAMES = Object.keys(COMMANDS);
|
|
|
81
84
|
/** Intent groups share one command inventory between compact and full help. */
|
|
82
85
|
const COMMAND_GROUPS = [
|
|
83
86
|
["tone", [
|
|
87
|
+
["on", "on"],
|
|
88
|
+
["off", "off"],
|
|
84
89
|
["set", "set <key>=<value> …"],
|
|
85
90
|
["get", "get [key]"],
|
|
86
91
|
["address", "address [self user]"],
|
|
@@ -112,6 +117,8 @@ const COMMAND_GROUPS = [
|
|
|
112
117
|
];
|
|
113
118
|
const COMMAND_SYNTAX = COMMAND_GROUPS.flatMap(([, rows]) => rows);
|
|
114
119
|
const COMMAND_EXAMPLES = {
|
|
120
|
+
on: ["tonedial on"],
|
|
121
|
+
off: ["tonedial off"],
|
|
115
122
|
set: ["tonedial set", "tonedial set warmth=5 critique=4", "tonedial set persona.agent=reviewer"],
|
|
116
123
|
get: ["tonedial get", "tonedial get language", "tonedial get speech_level --json"],
|
|
117
124
|
test: ["tonedial test codex --dry-run", "tonedial test claude --project .", "tonedial test codex --preset my-tone --accept-usage"],
|
|
@@ -274,16 +281,12 @@ function columns(env, stream, isTTY) {
|
|
|
274
281
|
return isTTY ? stream.columns : undefined;
|
|
275
282
|
}
|
|
276
283
|
/**
|
|
277
|
-
* Words a person reaches for when they want to stop,
|
|
278
|
-
*
|
|
279
|
-
* There is no off switch to give them, so the answer names the acts that do exist and says so.
|
|
284
|
+
* Words a person reaches for when they want to stop, but which are not command spellings.
|
|
280
285
|
*
|
|
281
286
|
* A deliberate ceiling: the list is guessed, not measured — nobody has reported typing any of
|
|
282
|
-
* them. Replace it with a measured one when a user reports a word that is not here
|
|
283
|
-
* if the answer turns out to be the list of seventeen after all. The cost of being wrong is one
|
|
284
|
-
* string; the cost of being right is that `tonedial off` stops dead-ending in that list.
|
|
287
|
+
* them. Replace it with a measured one when a user reports a word that is not here.
|
|
285
288
|
*/
|
|
286
|
-
const STOPPING = ["
|
|
289
|
+
const STOPPING = ["disable", "stop", "quit", "uninstall", "remove", "delete"];
|
|
287
290
|
export async function run(argv, env = process.env, cwd = process.cwd(), isTTY = process.stdout.isTTY === true, isErrTTY = process.stderr.isTTY === true) {
|
|
288
291
|
const options = {
|
|
289
292
|
json: { type: "boolean", default: false },
|
|
@@ -387,7 +390,7 @@ export async function run(argv, env = process.env, cwd = process.cwd(), isTTY =
|
|
|
387
390
|
// one: `stop` is two edits from `show` and `delete` three from `reset`, so the match would
|
|
388
391
|
// answer a person trying to leave with the command they were leaving.
|
|
389
392
|
if (STOPPING.includes(name))
|
|
390
|
-
return fail(asJson, say.errors.
|
|
393
|
+
return fail(asJson, say.errors.stoppingHelp, 2, uiErr);
|
|
391
394
|
return fail(asJson, say.errors.notACommand(asJson ? name : humanText(name), COMMAND_NAMES, nearest(name, COMMAND_NAMES)), 2, uiErr);
|
|
392
395
|
}
|
|
393
396
|
const flags = {
|
package/dist/commands/compile.js
CHANGED
|
@@ -107,7 +107,7 @@ export async function runCompile(ctx) {
|
|
|
107
107
|
` ${ctx.ui.dim(artifactSize(size, ctx.t))}`,
|
|
108
108
|
...nextStep(check && !current ? "tonedial compile" : "tonedial show", ctx.ui),
|
|
109
109
|
],
|
|
110
|
-
json: { path: ctx.paths.tone, agentsPath: ctx.paths.agents, written: !check, ...size },
|
|
110
|
+
json: { path: ctx.paths.tone, agentsPath: ctx.paths.agents, enabled: config.enabled, written: !check, ...size },
|
|
111
111
|
warnings: [],
|
|
112
112
|
};
|
|
113
113
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -11,6 +11,7 @@ import { join, relative } from "node:path";
|
|
|
11
11
|
import { AGENT_TOOLS, CONTEXT_ARTIFACT_FILENAME, CONTEXT_GUIDE_FILENAME, UPDATE_COMMAND, connectionScopeFlag, onPath, readerPath, resolveAgentPaths, shimScript, installShape, canonicalDestination, shellQuote } from "../core/paths.js";
|
|
12
12
|
import { SKILL_NAME } from "../core/skill.js";
|
|
13
13
|
import { GUIDE_FORMAT_MARKER } from "../core/reader.js";
|
|
14
|
+
import { DELIVERY_OFF_MARKER } from "../core/anchors.js";
|
|
14
15
|
import { inspect as inspectSkill, skillPath } from "../wiring/skill.js";
|
|
15
16
|
import { candidateRcPaths, pathLineState, rcPath, withPathLine } from "../wiring/shell.js";
|
|
16
17
|
import { SEED_VERSION, inspectContextLayout, inspectSeeds, recordedSeedSelection, } from "../core/seeds.js";
|
|
@@ -94,7 +95,9 @@ function readerHealth(ctx, config, configRead, seeds, pairRequired) {
|
|
|
94
95
|
temporary = mkdtempSync(join(tmpdir(), "tonedial-reader-check-"));
|
|
95
96
|
temporary = realpathSync(temporary);
|
|
96
97
|
const guide = `${GUIDE_FORMAT_MARKER}\nToneDial diagnostic guide.\n`;
|
|
97
|
-
const artifact =
|
|
98
|
+
const artifact = config.enabled
|
|
99
|
+
? "ToneDial diagnostic tone — 한글 🌿.\n"
|
|
100
|
+
: `<!-- generated by tonedial · preset: unsaved -->\n<!-- tonedial-style:1 verbosity=3 care=3 -->\n${DELIVERY_OFF_MARKER}\nToneDial diagnostic tone — 한글 🌿.\n`;
|
|
98
101
|
writeFileSync(join(temporary, CONTEXT_GUIDE_FILENAME), guide, { flag: "wx", mode: 0o600 });
|
|
99
102
|
writeFileSync(join(temporary, CONTEXT_ARTIFACT_FILENAME), artifact, { flag: "wx", mode: 0o600 });
|
|
100
103
|
const body = guide + "\n\n" + artifact;
|
|
@@ -110,7 +113,8 @@ function readerHealth(ctx, config, configRead, seeds, pairRequired) {
|
|
|
110
113
|
maxBuffer: 8192,
|
|
111
114
|
});
|
|
112
115
|
const expected = provider === "antigravity"
|
|
113
|
-
? JSON.stringify({ injectSteps: [{ ephemeralMessage: body }] }) + "\n"
|
|
116
|
+
? JSON.stringify({ injectSteps: config.enabled ? [{ ephemeralMessage: body }] : [] }) + "\n"
|
|
117
|
+
: config.enabled ? body : "";
|
|
114
118
|
if (probe.error || probe.status !== 0 || probe.stderr !== "" || probe.stdout !== expected)
|
|
115
119
|
return result;
|
|
116
120
|
}
|
package/dist/commands/get.js
CHANGED
|
@@ -33,9 +33,11 @@ export function runGet(ctx, args) {
|
|
|
33
33
|
return [` ${ctx.ui.accent(padEnd(key, column))} ${first ?? ""}`,
|
|
34
34
|
...rest.map(line => `${" ".repeat(column + 4)}${line}`)];
|
|
35
35
|
});
|
|
36
|
+
if (requested === undefined && !config.enabled)
|
|
37
|
+
lines.unshift(` ${ctx.ui.bold(ctx.t.delivery.status(false))}`, "");
|
|
36
38
|
if (requested !== undefined) {
|
|
37
39
|
const copy = catalogue(isLanguage(config.language) ? config.language : detectLanguage(ctx.env));
|
|
38
40
|
lines.push("", ...settingHelp(requested, copy).flatMap(hint => wrap(hint, ctx.ui.width - 2).map(line => ` ${ctx.ui.dim(line)}`)));
|
|
39
41
|
}
|
|
40
|
-
return { lines, json: { values } };
|
|
42
|
+
return { lines, json: { enabled: config.enabled, values } };
|
|
41
43
|
}
|
package/dist/commands/rebuild.js
CHANGED
|
@@ -18,7 +18,7 @@ import { wrap } from "../ui/width.js";
|
|
|
18
18
|
import { parseToolSelection } from "../wiring/targets.js";
|
|
19
19
|
import { riskWarnings } from "../ui/format.js";
|
|
20
20
|
const CONFIG_MAX_BYTES = 1024 * 1024;
|
|
21
|
-
const scalarFields = ["preset", "unsaved", "safeword", "speech_level", "language"];
|
|
21
|
+
const scalarFields = ["enabled", "preset", "unsaved", "safeword", "speech_level", "language"];
|
|
22
22
|
const personaFields = ["age", "gender", "note", "user", "agent"];
|
|
23
23
|
function capturedText(capture) {
|
|
24
24
|
if (capture.type !== "regular" || capture.bytes === null)
|
package/dist/commands/show.js
CHANGED
|
@@ -69,7 +69,8 @@ export function runShow(ctx, args) {
|
|
|
69
69
|
const gauges = [gaugeHeader(ui, ctx.t), ...gaugeBlock(axes, ui)];
|
|
70
70
|
const gaugeWidth = Math.max(...gauges.map((line) => maxCells(stripVTControlCharacters(line))));
|
|
71
71
|
const detailWidth = ui.terminal && ui.width >= 119 ? ui.width - gaugeWidth - 4 : ui.width - 2;
|
|
72
|
-
const details = [ui.bold(ctx.t.artifact.settingsHeading)
|
|
72
|
+
const details = [ui.bold(ctx.t.artifact.settingsHeading),
|
|
73
|
+
...(config.enabled ? [] : [ctx.t.delivery.status(false)])];
|
|
73
74
|
if (personaSet) {
|
|
74
75
|
const bits = [
|
|
75
76
|
age ? humanText(age) : null,
|
|
@@ -163,6 +164,7 @@ export function runShow(ctx, args) {
|
|
|
163
164
|
json: {
|
|
164
165
|
preset: preview ? preview.slug : config.preset,
|
|
165
166
|
preview: preview !== null,
|
|
167
|
+
enabled: config.enabled,
|
|
166
168
|
activeKnown: preview !== null || !activeConflict,
|
|
167
169
|
// Layered rather than flattened: the skill has to tell a forbidden contradiction from a
|
|
168
170
|
// cosmetic one, and a joined string cannot carry that.
|
package/dist/commands/test.js
CHANGED
|
@@ -58,7 +58,8 @@ export async function runTest(ctx, args) {
|
|
|
58
58
|
}
|
|
59
59
|
input = toCompileInput(withPreset(config, preview));
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
// A test is an explicit one-shot preview, independent of the persistent native delivery switch.
|
|
62
|
+
const tone = compile({ ...input, enabled: true });
|
|
62
63
|
const installed = PROVIDERS.map((provider) => ({ provider, program: onPath(provider, ctx.env) }))
|
|
63
64
|
.filter((entry) => entry.program !== null);
|
|
64
65
|
let selected = requested === undefined ? undefined : installed.find((entry) => entry.provider === requested);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Pause or resume native tone delivery while preserving the saved settings and full artifact.
|
|
2
|
+
import { serializeConfig } from "../core/config.js";
|
|
3
|
+
import { wrap } from "../ui/width.js";
|
|
4
|
+
import { applyPlanned, planArtifact, planConfig } from "./compile.js";
|
|
5
|
+
async function setDelivery(ctx, enabled) {
|
|
6
|
+
const { change: configChange, config } = planConfig(ctx);
|
|
7
|
+
const changed = config.enabled !== enabled;
|
|
8
|
+
config.enabled = enabled;
|
|
9
|
+
const { change: toneChange, size } = planArtifact(ctx, config);
|
|
10
|
+
const finalConfig = { ...configChange, after: serializeConfig(config, ctx.t) };
|
|
11
|
+
await applyPlanned(ctx, [configChange, toneChange], [finalConfig]);
|
|
12
|
+
const message = enabled
|
|
13
|
+
? changed ? ctx.t.delivery.on : ctx.t.delivery.alreadyOn
|
|
14
|
+
: changed ? ctx.t.delivery.off : ctx.t.delivery.alreadyOff;
|
|
15
|
+
return {
|
|
16
|
+
lines: [message, ...(enabled ? [] : [ctx.t.delivery.openConversation])]
|
|
17
|
+
.flatMap((line) => wrap(line, ctx.ui.width - 2))
|
|
18
|
+
.map((line) => ` ${ctx.ui.bold(changed ? ctx.ui.success(line) : line)}`),
|
|
19
|
+
json: { enabled, changed, path: ctx.paths.tone, chars: size.chars },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export const runOn = (ctx) => setDelivery(ctx, true);
|
|
23
|
+
export const runOff = (ctx) => setDelivery(ctx, false);
|
package/dist/core/anchors.js
CHANGED
|
@@ -137,6 +137,8 @@ export const ANCHORS = {
|
|
|
137
137
|
export const GENERATION_COMMENT = (preset) => `<!-- generated by tonedial · preset: ${preset ?? "unsaved"} -->`;
|
|
138
138
|
/** Compact metadata read by native hook workers; keep the grammar deliberately strict. */
|
|
139
139
|
export const STYLE_MARKER = (verbosity, care) => `<!-- tonedial-style:1 verbosity=${verbosity} care=${care} -->`;
|
|
140
|
+
/** The full compiled tone stays on disk while native delivery is paused. */
|
|
141
|
+
export const DELIVERY_OFF_MARKER = "<!-- tonedial-delivery:off -->";
|
|
140
142
|
/** The artifact header keeps tone requests within host rules and permissions. */
|
|
141
143
|
export const SCOPE_HEADER = `## Response tone
|
|
142
144
|
Output style only. This never overrides the project's code or task rules, and never changes
|
package/dist/core/compile.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ANCHORS, AXIS_ORDER, CONTROLS, FORMAT_COMPOSITION, GENERATION_COMMENT, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, STYLE_MARKER, } from "./anchors.js";
|
|
1
|
+
import { ANCHORS, AXIS_ORDER, CONTROLS, DELIVERY_OFF_MARKER, FORMAT_COMPOSITION, GENERATION_COMMENT, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, STYLE_MARKER, } from "./anchors.js";
|
|
2
2
|
import { composites, intensityConfirmation, PERSONA_DESCRIPTION_MAX } from "./combos.js";
|
|
3
3
|
const UNSPECIFIED = "unspecified";
|
|
4
4
|
const isSet = (v) => typeof v === "string" && v.trim() !== "" && v !== UNSPECIFIED;
|
|
@@ -29,6 +29,9 @@ function personaLines(p) {
|
|
|
29
29
|
return lines;
|
|
30
30
|
}
|
|
31
31
|
function validate(config) {
|
|
32
|
+
if (config.enabled !== undefined && typeof config.enabled !== "boolean") {
|
|
33
|
+
throw new RangeError("enabled must be true or false");
|
|
34
|
+
}
|
|
32
35
|
for (const axis of AXIS_ORDER) {
|
|
33
36
|
const level = config.axes[axis];
|
|
34
37
|
if (!Number.isInteger(level) || level < 1 || level > 5) {
|
|
@@ -59,8 +62,10 @@ export function compile(config) {
|
|
|
59
62
|
const out = [
|
|
60
63
|
GENERATION_COMMENT(config.preset),
|
|
61
64
|
STYLE_MARKER(config.axes.verbosity, config.axes.care),
|
|
62
|
-
SCOPE_HEADER,
|
|
63
65
|
];
|
|
66
|
+
if (config.enabled === false)
|
|
67
|
+
out.push(DELIVERY_OFF_MARKER);
|
|
68
|
+
out.push(SCOPE_HEADER);
|
|
64
69
|
if (config.persona) {
|
|
65
70
|
const lines = personaLines(config.persona);
|
|
66
71
|
if (lines.length)
|
package/dist/core/config.js
CHANGED
|
@@ -17,6 +17,7 @@ export const RESOURCE_TYPES = ["file", "directory", "symlink"];
|
|
|
17
17
|
export function defaultConfig() {
|
|
18
18
|
return {
|
|
19
19
|
schema: CONFIG_SCHEMA,
|
|
20
|
+
enabled: true,
|
|
20
21
|
preset: null,
|
|
21
22
|
unsaved: false,
|
|
22
23
|
axes: { ...AXIS_DEFAULT },
|
|
@@ -512,7 +513,7 @@ export function parseConfig(text, t = catalogue("en").cli) {
|
|
|
512
513
|
// An unknown key is refused by name rather than silently dropped on the next write. This
|
|
513
514
|
// file is user-editable, and a tool that eats what a person typed has no business asking
|
|
514
515
|
// them to trust it with someone else's files later.
|
|
515
|
-
const KNOWN = ["schema", "preset", "unsaved", "axes", "persona", "safeword", "guards", "fixed_width",
|
|
516
|
+
const KNOWN = ["schema", "enabled", "preset", "unsaved", "axes", "persona", "safeword", "guards", "fixed_width",
|
|
516
517
|
"speech_level", "language", "seedVersion", "managedSeeds", "wiring"];
|
|
517
518
|
const unknown = Object.keys(raw).filter((k) => !KNOWN.includes(k));
|
|
518
519
|
if (unknown.length)
|
|
@@ -541,6 +542,7 @@ export function parseConfig(text, t = catalogue("en").cli) {
|
|
|
541
542
|
}
|
|
542
543
|
}
|
|
543
544
|
const common = {
|
|
545
|
+
enabled: raw.enabled === undefined ? true : requiredBoolean(raw.enabled, "enabled", t),
|
|
544
546
|
preset: asString(raw.preset, "preset", t),
|
|
545
547
|
unsaved: raw.unsaved === true,
|
|
546
548
|
axes: readAxes(raw.axes, t),
|
|
@@ -569,6 +571,9 @@ export function serializeConfig(config, t = catalogue("en").cli) {
|
|
|
569
571
|
const wiring = readWiringV2(current.wiring, t);
|
|
570
572
|
const text = JSON.stringify({
|
|
571
573
|
schema: CONFIG_SCHEMA,
|
|
574
|
+
// On is the historical default. Omit it so ordinary writes retain the released shape;
|
|
575
|
+
// only the meaningful paused state needs a persisted byte.
|
|
576
|
+
...(current.enabled ? {} : { enabled: false }),
|
|
572
577
|
preset: current.preset,
|
|
573
578
|
unsaved: current.unsaved,
|
|
574
579
|
axes,
|
|
@@ -599,6 +604,7 @@ export function prepareApprovedMigration(legacy, t = catalogue("en").cli) {
|
|
|
599
604
|
return {
|
|
600
605
|
config: {
|
|
601
606
|
schema: CONFIG_SCHEMA,
|
|
607
|
+
enabled: legacy.enabled,
|
|
602
608
|
preset: legacy.preset,
|
|
603
609
|
unsaved: legacy.unsaved,
|
|
604
610
|
axes: { ...legacy.axes },
|
|
@@ -617,7 +623,7 @@ export function prepareApprovedMigration(legacy, t = catalogue("en").cli) {
|
|
|
617
623
|
},
|
|
618
624
|
};
|
|
619
625
|
}
|
|
620
|
-
export const USER_SETTING_KEYS = ["preset", "unsaved", "axes", "persona", "safeword", "speech_level", "language"];
|
|
626
|
+
export const USER_SETTING_KEYS = ["enabled", "preset", "unsaved", "axes", "persona", "safeword", "speech_level", "language"];
|
|
621
627
|
/** Recover supported user values only. Opaque delivery metadata never becomes new authority. */
|
|
622
628
|
export function recoverSettings(text, t = catalogue("en").cli) {
|
|
623
629
|
let raw;
|
package/dist/core/i18n.js
CHANGED
|
@@ -37,6 +37,14 @@ const enMessages = {
|
|
|
37
37
|
launchFailed: (provider) => `Could not start ${provider}. Check its installation and try again.`,
|
|
38
38
|
selectionCancelled: "No provider selected.",
|
|
39
39
|
},
|
|
40
|
+
delivery: {
|
|
41
|
+
on: "ToneDial on · delivery marker cleared",
|
|
42
|
+
off: "ToneDial off · delivery marker saved · settings and TONE.md kept",
|
|
43
|
+
alreadyOn: "ToneDial already on · no changes",
|
|
44
|
+
alreadyOff: "ToneDial already off · no changes",
|
|
45
|
+
openConversation: "Open conversations may retain an earlier tone. Use a new conversation for a clean no-ToneDial comparison.",
|
|
46
|
+
status: (enabled) => `ToneDial ${enabled ? "on" : "off"}`,
|
|
47
|
+
},
|
|
40
48
|
axis: {
|
|
41
49
|
range: (axis, given) => `${axis}: expected 1–5 (got ${given})`,
|
|
42
50
|
unknown: (name, nearest) => `${name}: not an axis${nearest ? `. Did you mean ${nearest}?` : ""}`,
|
|
@@ -443,6 +451,8 @@ const enMessages = {
|
|
|
443
451
|
firstSteps: "Set up your tone, then inspect it:",
|
|
444
452
|
examples: "Examples",
|
|
445
453
|
commands: {
|
|
454
|
+
on: "resume saved tone delivery",
|
|
455
|
+
off: "pause tone delivery · keep settings and TONE.md",
|
|
446
456
|
init: "set up and refresh bundled files",
|
|
447
457
|
set: "change axes, role or settings · bare opens dial",
|
|
448
458
|
get: "read current setting values · key adds its meaning and choices",
|
|
@@ -501,10 +511,9 @@ const enMessages = {
|
|
|
501
511
|
errors: {
|
|
502
512
|
notACommand: (name, known, nearest) => `${name} is not a command.${nearest ? ` Did you mean ${nearest}?` : ""} ` +
|
|
503
513
|
`Available: ${known.join(", ")}`,
|
|
504
|
-
|
|
505
|
-
"
|
|
506
|
-
"
|
|
507
|
-
"npm uninstall -g tonedial removes the command.",
|
|
514
|
+
stoppingHelp: "Use tonedial off to pause tone delivery without deleting settings; tonedial on resumes it. " +
|
|
515
|
+
"tonedial reset restores axis defaults. tonedial eject --global or --project <path> " +
|
|
516
|
+
"disconnects that scope. Once all connections are gone, npm uninstall -g tonedial removes the command.",
|
|
508
517
|
notAnOption: (flag) => `${flag}: not an option. List: tonedial --help`,
|
|
509
518
|
flagNotAccepted: (command, flag) => `${command}: does not accept --${flag}`,
|
|
510
519
|
tooManyArguments: (command, max, got) => `${command}: maximum ${max} argument${max === 1 ? "" : "s"}, got ${got}`,
|
|
@@ -700,6 +709,14 @@ const koMessages = {
|
|
|
700
709
|
launchFailed: (provider) => `${provider}를 시작하지 못했습니다. 설치 상태를 확인하고 다시 시도해 주세요.`,
|
|
701
710
|
selectionCancelled: "도구를 선택하지 않았습니다.",
|
|
702
711
|
},
|
|
712
|
+
delivery: {
|
|
713
|
+
on: "ToneDial 켜짐 · 전달 중지 마커 제거",
|
|
714
|
+
off: "ToneDial 꺼짐 · 전달 중지 마커 저장 · 설정과 TONE.md 유지",
|
|
715
|
+
alreadyOn: "ToneDial이 이미 켜져 있음 · 변경 없음",
|
|
716
|
+
alreadyOff: "ToneDial이 이미 꺼져 있음 · 변경 없음",
|
|
717
|
+
openConversation: "열린 대화에는 이전 말투가 남을 수 있습니다. ToneDial이 없는 상태를 제대로 비교하려면 새 대화를 여세요.",
|
|
718
|
+
status: (enabled) => `ToneDial ${enabled ? "켜짐" : "꺼짐"}`,
|
|
719
|
+
},
|
|
703
720
|
axis: {
|
|
704
721
|
range: (axis, given) => `${axis}: 1~5만 쓸 수 있습니다(입력: ${given})`,
|
|
705
722
|
unknown: (name, nearest) => `${name}: 축이 아닙니다${nearest ? `. ${nearest} 말씀이신가요?` : ""}`,
|
|
@@ -1109,6 +1126,8 @@ const koMessages = {
|
|
|
1109
1126
|
firstSteps: "말투를 설정한 뒤 확인하세요:",
|
|
1110
1127
|
examples: "사용 예",
|
|
1111
1128
|
commands: {
|
|
1129
|
+
on: "저장한 말투 전달 재개",
|
|
1130
|
+
off: "말투 전달 일시정지 · 설정·TONE.md 유지",
|
|
1112
1131
|
init: "초기 설정과 기본 파일 갱신",
|
|
1113
1132
|
set: "축·역할·설정 변경 · 인자 없으면 다이얼",
|
|
1114
1133
|
get: "현재 설정값 조회 · 키를 지정하면 의미와 선택지도 표시",
|
|
@@ -1167,10 +1186,9 @@ const koMessages = {
|
|
|
1167
1186
|
errors: {
|
|
1168
1187
|
notACommand: (name, known, nearest) => `${name}: 명령이 아닙니다.${nearest ? ` ${nearest} 말씀이신가요?` : ""} ` +
|
|
1169
1188
|
`사용 가능: ${known.join(", ")}`,
|
|
1170
|
-
|
|
1171
|
-
"
|
|
1172
|
-
"
|
|
1173
|
-
"연결을 모두 해제한 뒤 npm uninstall -g tonedial로 명령을 삭제할 수 있습니다.",
|
|
1189
|
+
stoppingHelp: "설정을 지우지 않고 말투 전달만 멈추려면 tonedial off를 실행하세요. tonedial on으로 다시 켤 수 있습니다. " +
|
|
1190
|
+
"tonedial reset은 축을 기본값으로 돌립니다. tonedial eject --global이나 --project <path>는 " +
|
|
1191
|
+
"해당 범위의 연결을 해제합니다. 연결을 모두 해제한 뒤 npm uninstall -g tonedial로 명령을 삭제할 수 있습니다.",
|
|
1174
1192
|
notAnOption: (flag) => `${flag}: 옵션이 아닙니다. 목록: tonedial --help`,
|
|
1175
1193
|
flagNotAccepted: (command, flag) => `${command}: --${flag} 옵션을 받지 않습니다`,
|
|
1176
1194
|
tooManyArguments: (command, max, got) => `${command}: 인자는 최대 ${max}개지만 ${got}개를 받았습니다`,
|
package/dist/core/legacy-tone.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Decode known current and historical English artifact bodies. Unknown prose is never a data source.
|
|
2
|
-
import { ANCHORS, AXIS_DEFAULT, AXIS_ORDER, CONTROLS, FORMAT_COMPOSITION, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, } from "./anchors.js";
|
|
2
|
+
import { ANCHORS, AXIS_DEFAULT, AXIS_ORDER, CONTROLS, DELIVERY_OFF_MARKER, FORMAT_COMPOSITION, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, } from "./anchors.js";
|
|
3
3
|
import { AGE_BANDS, GENDERS, defaultConfig, recoverSettings, safewordRejection, } from "./config.js";
|
|
4
4
|
import { composites, intensityConfirmation } from "./combos.js";
|
|
5
5
|
// Historical producer text is decoder evidence only, never a current tone instruction.
|
|
@@ -355,11 +355,16 @@ export function recoverLegacyTone(text, t) {
|
|
|
355
355
|
const styleMarker = /^<!-- tonedial-style:1 verbosity=([1-5]) care=([1-5]) -->\n/.exec(afterGeneration);
|
|
356
356
|
if (afterGeneration.startsWith("<!-- tonedial-style:") && styleMarker === null)
|
|
357
357
|
return null;
|
|
358
|
-
const
|
|
358
|
+
const afterStyle = afterGeneration.slice(styleMarker?.[0].length ?? 0);
|
|
359
|
+
const deliveryMarker = afterStyle.startsWith(`${DELIVERY_OFF_MARKER}\n`) ? `${DELIVERY_OFF_MARKER}\n` : "";
|
|
360
|
+
if (afterStyle.startsWith("<!-- tonedial-delivery:") && deliveryMarker === "")
|
|
361
|
+
return null;
|
|
362
|
+
const prefix = `${marker[0]}${styleMarker?.[0] ?? ""}${deliveryMarker}${SCOPE_HEADER}\n`;
|
|
359
363
|
if (!source.startsWith(prefix))
|
|
360
364
|
return null;
|
|
361
365
|
let body = source.slice(prefix.length);
|
|
362
366
|
const config = defaultConfig();
|
|
367
|
+
config.enabled = deliveryMarker !== "" ? false : true;
|
|
363
368
|
const unknown = [];
|
|
364
369
|
if (body.startsWith("### Persona\n")) {
|
|
365
370
|
const end = body.indexOf("\n### Rules\n");
|
package/dist/core/locales/es.js
CHANGED
|
@@ -26,6 +26,14 @@ const messages = {
|
|
|
26
26
|
launchFailed: (provider) => `No se pudo iniciar ${provider}. Comprueba su instalación y vuelve a intentarlo.`,
|
|
27
27
|
selectionCancelled: "No se eligió ningún proveedor.",
|
|
28
28
|
},
|
|
29
|
+
delivery: {
|
|
30
|
+
on: "ToneDial activado · marcador de pausa eliminado",
|
|
31
|
+
off: "ToneDial desactivado · marcador de pausa guardado · se conservan los ajustes y TONE.md",
|
|
32
|
+
alreadyOn: "La entrega del tono ya está activada · sin cambios",
|
|
33
|
+
alreadyOff: "La entrega del tono ya está desactivada · sin cambios",
|
|
34
|
+
openConversation: "Las conversaciones abiertas pueden conservar el tono anterior. Usa una conversación nueva para comparar el estado sin ToneDial.",
|
|
35
|
+
status: (enabled) => `Entrega del tono ${enabled ? "activada" : "desactivada"}`,
|
|
36
|
+
},
|
|
29
37
|
axis: {
|
|
30
38
|
range: (axis, given) => `${axis}: se esperaba 1–5 (se recibió ${given})`,
|
|
31
39
|
unknown: (name, nearest) => `${name}: no es un eje${nearest ? `. ¿Querías decir ${nearest}?` : ""}`,
|
|
@@ -428,6 +436,8 @@ const messages = {
|
|
|
428
436
|
firstSteps: "Configura el tono y revísalo:",
|
|
429
437
|
examples: "Ejemplos",
|
|
430
438
|
commands: {
|
|
439
|
+
on: "reanudar la entrega del tono guardado",
|
|
440
|
+
off: "pausar la entrega · conservar ajustes y TONE.md",
|
|
431
441
|
init: "configurar y actualizar archivos incluidos",
|
|
432
442
|
set: "cambiar ejes, rol o ajustes · sin argumentos abre el dial",
|
|
433
443
|
get: "consultar valores actuales · una clave muestra su significado y opciones",
|
|
@@ -486,10 +496,9 @@ const messages = {
|
|
|
486
496
|
errors: {
|
|
487
497
|
notACommand: (name, known, nearest) => `${name} no es un comando.${nearest ? ` ¿Querías decir ${nearest}?` : ""} ` +
|
|
488
498
|
`Disponibles: ${known.join(", ")}`,
|
|
489
|
-
|
|
490
|
-
"
|
|
491
|
-
"
|
|
492
|
-
"npm uninstall -g tonedial elimina el comando.",
|
|
499
|
+
stoppingHelp: "Usa tonedial off para pausar la entrega sin borrar los ajustes y tonedial on para reanudarla. " +
|
|
500
|
+
"tonedial reset restablece los ejes. tonedial eject --global o --project <path> desconecta ese ámbito. " +
|
|
501
|
+
"Tras quitar todas las conexiones, npm uninstall -g tonedial elimina el comando.",
|
|
493
502
|
notAnOption: (flag) => `${flag}: no es una opción. Lista: tonedial --help`,
|
|
494
503
|
flagNotAccepted: (command, flag) => `${command}: no admite --${flag}`,
|
|
495
504
|
tooManyArguments: (command, max, got) => `${command}: máximo ${max} argumento${max === 1 ? "" : "s"}; se recibieron ${got}`,
|
package/dist/core/locales/ja.js
CHANGED
|
@@ -26,6 +26,14 @@ const messages = {
|
|
|
26
26
|
launchFailed: (provider) => `${provider} を起動できません。インストール状況を確認して再試行してください。`,
|
|
27
27
|
selectionCancelled: "プロバイダーが選択されていません。",
|
|
28
28
|
},
|
|
29
|
+
delivery: {
|
|
30
|
+
on: "ToneDialをオンにしました · 配信停止マーカーを削除",
|
|
31
|
+
off: "ToneDialをオフにしました · 配信停止マーカーを保存 · 設定とTONE.mdは保持",
|
|
32
|
+
alreadyOn: "口調の配信はすでにオンです · 変更なし",
|
|
33
|
+
alreadyOff: "口調の配信はすでにオフです · 変更なし",
|
|
34
|
+
openConversation: "開いている会話には以前の口調が残る場合があります。ToneDialなしの状態と正確に比較するには新しい会話を使ってください。",
|
|
35
|
+
status: (enabled) => `口調の配信:${enabled ? "オン" : "オフ"}`,
|
|
36
|
+
},
|
|
29
37
|
axis: {
|
|
30
38
|
range: (axis, given) => `${axis}: 1–5を指定してください(入力:${given})`,
|
|
31
39
|
unknown: (name, nearest) => `${name}: 軸ではありません${nearest ? `。${nearest} のことですか?` : ""}`,
|
|
@@ -428,6 +436,8 @@ const messages = {
|
|
|
428
436
|
firstSteps: "口調を設定し、確認します:",
|
|
429
437
|
examples: "使用例",
|
|
430
438
|
commands: {
|
|
439
|
+
on: "保存した口調の配信を再開",
|
|
440
|
+
off: "口調の配信を一時停止 · 設定とTONE.mdは保持",
|
|
431
441
|
init: "初期設定と同梱ファイルの更新",
|
|
432
442
|
set: "軸・役割・設定を変更 · 引数なしでダイヤル",
|
|
433
443
|
get: "現在の設定値を確認 · キー指定で意味と選択肢も表示",
|
|
@@ -486,10 +496,9 @@ const messages = {
|
|
|
486
496
|
errors: {
|
|
487
497
|
notACommand: (name, known, nearest) => `${name} はコマンドではありません。${nearest ? `${nearest} のことですか?` : ""} ` +
|
|
488
498
|
`選択肢:${known.join(", ")}`,
|
|
489
|
-
|
|
490
|
-
"tonedial reset は軸の既定値を復元します。tonedial eject --global または " +
|
|
491
|
-
"
|
|
492
|
-
"npm uninstall -g tonedial でコマンドを削除できます。",
|
|
499
|
+
stoppingHelp: "設定を削除せずに口調の配信を止めるには tonedial off を実行し、再開するには tonedial on を実行してください。" +
|
|
500
|
+
"tonedial reset は軸の既定値を復元します。tonedial eject --global または --project <path> でその範囲を解除します。" +
|
|
501
|
+
"全接続を解除した後、npm uninstall -g tonedial でコマンドを削除できます。",
|
|
493
502
|
notAnOption: (flag) => `${flag}: オプションではありません。一覧:tonedial --help`,
|
|
494
503
|
flagNotAccepted: (command, flag) => `${command}: --${flag} は使えません`,
|
|
495
504
|
tooManyArguments: (command, max, got) => `${command}: 引数は最大 ${max}個、入力は ${got}個です`,
|