tonedial 0.1.1 → 0.1.3
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/dist/cli.js +22 -8
- package/dist/commands/create.js +7 -7
- package/dist/commands/doctor.js +21 -14
- package/dist/commands/edit.js +3 -3
- package/dist/commands/get.js +41 -0
- package/dist/commands/init.js +6 -12
- package/dist/commands/save.js +3 -11
- package/dist/commands/set.js +28 -79
- package/dist/commands/show.js +25 -24
- package/dist/commands/transfer.js +0 -1
- package/dist/commands/update.js +89 -42
- package/dist/commands/use.js +3 -4
- package/dist/commands/wire.js +1 -1
- package/dist/core/anchors.js +1 -0
- package/dist/core/combos.js +6 -38
- package/dist/core/compile.js +2 -2
- package/dist/core/config.js +20 -9
- package/dist/core/i18n.js +76 -74
- package/dist/core/legacy-tone.js +6 -4
- package/dist/core/locales/es.js +38 -37
- package/dist/core/locales/ja.js +38 -37
- package/dist/core/locales/zh.js +38 -37
- package/dist/core/seeds.js +3 -3
- package/dist/core/selfupdate.js +69 -3
- package/dist/core/skill.js +28 -16
- package/dist/ui/format.js +3 -6
- package/package.json +1 -1
package/dist/core/selfupdate.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// Bounded release download and extraction; the candidate installer owns package replacement.
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { accessSync, constants, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
6
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { gunzipSync } from "node:zlib";
|
|
8
9
|
import { ConfigError } from "./config.js";
|
|
9
10
|
import { decodeUtf8, getBounded, urlIdentity } from "./net.js";
|
|
11
|
+
import { fileIdentity, onPath } from "./paths.js";
|
|
10
12
|
/** Where releases come from. Overridable for tests, exactly as `install.sh` overrides it. */
|
|
11
13
|
export const REGISTRY = "https://registry.npmjs.org";
|
|
12
14
|
/**
|
|
@@ -23,12 +25,76 @@ const METADATA_BOUNDS = { maxBytes: 1024 * 1024, timeoutMs: RELEASE_TIMEOUT_MS }
|
|
|
23
25
|
export function installedVersion(paths) {
|
|
24
26
|
try {
|
|
25
27
|
const raw = JSON.parse(readFileSync(join(paths.lib, "package.json"), "utf8"));
|
|
26
|
-
return typeof raw.version === "string"
|
|
28
|
+
return raw.name === "tonedial" && typeof raw.version === "string" && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]+)?$/.test(raw.version)
|
|
29
|
+
? raw.version : null;
|
|
27
30
|
}
|
|
28
31
|
catch {
|
|
29
32
|
return null;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
35
|
+
/** Bind npm's global target to this running copy before allowing package-manager writes. */
|
|
36
|
+
export function inspectNpmInstall(env, t) {
|
|
37
|
+
const executable = onPath("npm", env);
|
|
38
|
+
if (executable === null)
|
|
39
|
+
throw new ConfigError(t.update.npmMissing);
|
|
40
|
+
const entry = resolve(executable);
|
|
41
|
+
const query = (args) => {
|
|
42
|
+
const result = spawnSync(entry, args, { env, encoding: "utf8", timeout: RELEASE_TIMEOUT_MS, maxBuffer: 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
43
|
+
const value = result.stdout?.trim();
|
|
44
|
+
if (result.status !== 0 || !value || !isAbsolute(value) || /[\r\n\0]/.test(value))
|
|
45
|
+
throw new ConfigError(t.update.npmScopeUnknown);
|
|
46
|
+
return resolve(value);
|
|
47
|
+
};
|
|
48
|
+
const prefix = query(["prefix", "-g"]);
|
|
49
|
+
const root = join(query(["root", "-g", "--prefix", prefix]), "tonedial");
|
|
50
|
+
let install;
|
|
51
|
+
try {
|
|
52
|
+
const own = fileURLToPath(new URL("../../", import.meta.url));
|
|
53
|
+
const current = fileIdentity(own);
|
|
54
|
+
const target = fileIdentity(root);
|
|
55
|
+
const version = installedVersion({ lib: root });
|
|
56
|
+
if (lstatSync(root, { throwIfNoEntry: false })?.isSymbolicLink() || current === null || target === null || current.dev !== target.dev || current.ino !== target.ino || version === null) {
|
|
57
|
+
throw new ConfigError(t.update.npmScopeMismatch);
|
|
58
|
+
}
|
|
59
|
+
install = { entry, prefix, root, version };
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new ConfigError(t.update.npmScopeMismatch);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
// npm replaces the package by renaming it in this parent, not by editing its files.
|
|
66
|
+
accessSync(dirname(root), constants.W_OK | constants.X_OK);
|
|
67
|
+
}
|
|
68
|
+
catch (cause) {
|
|
69
|
+
const code = cause.code;
|
|
70
|
+
if (code === "EACCES" || code === "EPERM")
|
|
71
|
+
throw new ConfigError(t.update.npmPermissionDenied(code, prefix));
|
|
72
|
+
throw new ConfigError(t.update.npmScopeUnknown);
|
|
73
|
+
}
|
|
74
|
+
return install;
|
|
75
|
+
}
|
|
76
|
+
/** npm owns replacement; captured output cannot corrupt JSON or disclose npm configuration. */
|
|
77
|
+
export function updateNpmInstall(install, env, t) {
|
|
78
|
+
const current = inspectNpmInstall(env, t);
|
|
79
|
+
if (current.entry !== install.entry || current.prefix !== install.prefix || current.root !== install.root || current.version !== install.version)
|
|
80
|
+
throw new ConfigError(t.update.npmScopeMismatch);
|
|
81
|
+
const result = spawnSync(install.entry, ["update", "-g", "tonedial", "--prefix", install.prefix, "--json"], { env, encoding: "utf8", timeout: 120_000, maxBuffer: RELEASE_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
82
|
+
if (result.status !== 0) {
|
|
83
|
+
let code;
|
|
84
|
+
try {
|
|
85
|
+
code = JSON.parse(result.stdout ?? "null")?.error?.code;
|
|
86
|
+
}
|
|
87
|
+
catch { /* npm may fail before JSON output. */ }
|
|
88
|
+
code ??= /^npm (?:error|ERR!) code (EACCES|EPERM)\s*$/m.exec(result.stderr ?? "")?.[1];
|
|
89
|
+
if (code === "EACCES" || code === "EPERM")
|
|
90
|
+
throw new ConfigError(t.update.npmPermissionDenied(code, install.prefix));
|
|
91
|
+
throw new ConfigError(t.update.npmFailed);
|
|
92
|
+
}
|
|
93
|
+
const version = installedVersion({ lib: install.root });
|
|
94
|
+
if (version === null)
|
|
95
|
+
throw new ConfigError(t.update.versionUnknown);
|
|
96
|
+
return version;
|
|
97
|
+
}
|
|
32
98
|
/** Whether the one external tool this needs is here. Checked before anything is downloaded. */
|
|
33
99
|
export function haveTar() {
|
|
34
100
|
return spawnSync("tar", ["--version"], { stdio: "ignore" }).status === 0;
|
package/dist/core/skill.js
CHANGED
|
@@ -29,7 +29,14 @@ or repair decision. Never edit or recreate them directly.
|
|
|
29
29
|
/** Both entry points distinguish current tone settings from saved profiles. */
|
|
30
30
|
const PRESETS_MD = `## Current settings and saved presets
|
|
31
31
|
|
|
32
|
-
\`tonedial
|
|
32
|
+
\`tonedial get --json\` reports the current editable settings in a flat \`values\` object.
|
|
33
|
+
\`get <key> --json\` selects one of those keys, preserving numbers, literal text and nulls.
|
|
34
|
+
The keys are the twelve axes, \`language\`, \`speech_level\`, \`safeword\`, and
|
|
35
|
+
\`persona.age\`, \`persona.gender\`, \`persona.self_ref\`, \`persona.user_ref\`,
|
|
36
|
+
\`persona.note\`, \`persona.user\`, and \`persona.agent\`.
|
|
37
|
+
\`language\` selects the CLI interface, not the agent's reply language.
|
|
38
|
+
Read-only \`get\` neither changes settings nor reloads this conversation.
|
|
39
|
+
\`tonedial show --json\` adds the overview and artifact metadata. \`show <slug> --json\` previews that
|
|
33
40
|
preset's saved tone settings. \`unsaved\` on the current result means the current tone settings
|
|
34
41
|
have not been saved back to a profile; the current configuration itself is persisted.
|
|
35
42
|
For an explicit request to save a profile, use \`tonedial save "<display name>" --json\`.
|
|
@@ -86,7 +93,7 @@ Persona, address, safeword, and \`speech_level\` are separate from the axes.
|
|
|
86
93
|
\`playfulness\` increases the frequency and exaggeration of the chosen role's expressions:
|
|
87
94
|
literal wording at 1, one restrained touch at 2, two recognizable touches at 3,
|
|
88
95
|
frequent theatrical idioms at 4, and a character voice throughout at 5.
|
|
89
|
-
Role slang and figurative language are not filtered
|
|
96
|
+
Role slang and figurative language are not filtered.
|
|
90
97
|
\`profanity\` still grades actual swearing; \`bravado\` grades claims about effort.
|
|
91
98
|
Change one only when the user explicitly asks for that setting; preserve every other setting.
|
|
92
99
|
Use the user's chosen text and values, not the illustrative values below.
|
|
@@ -107,6 +114,7 @@ interpolating it into shell code.
|
|
|
107
114
|
| Persona manner or role note | \`tonedial set "persona.note=Patient guide" --json\` |
|
|
108
115
|
| Safeword phrase | \`tonedial set "safeword=plain answer please" --json\` |
|
|
109
116
|
| Honorific register system | \`tonedial set speech_level=polite --json\` |
|
|
117
|
+
| Rough, cheeky non-honorific endings | \`tonedial set speech_level=rough --json\` |
|
|
110
118
|
|
|
111
119
|
Age bands are \`unspecified\`, \`teens\`, \`20s\`, \`30s\`, \`40s\`, and \`50s+\`.
|
|
112
120
|
Gender values are \`unspecified\`, \`masculine\`, \`feminine\`, and \`neutral\`.
|
|
@@ -121,15 +129,17 @@ Agent experience and specialty are roleplay, not verified credentials or tool ca
|
|
|
121
129
|
Both descriptions lose to axes and task/permission rules, and may influence content.
|
|
122
130
|
A persona note expresses manner or role, loses to axes, and may also influence
|
|
123
131
|
content; it is not a way to override task or permission rules.
|
|
124
|
-
|
|
132
|
+
Persona notes have no length limit.
|
|
125
133
|
|
|
126
134
|
ToneDial has no guard toggle or automatic emergency register. All axis levels 1–5 are choices
|
|
127
135
|
the user can request. Report advisory \`riskAxes\` and combination notices without lowering
|
|
128
136
|
values, blocking the change, or asking for additional tone-risk confirmation. The host's task
|
|
129
137
|
and permission rules still apply independently.
|
|
130
|
-
For \`speech_level\`, choose \`polite\`, \`plain\`, or \`
|
|
138
|
+
For \`speech_level\`, choose \`auto\`, \`polite\`, \`plain\`, or \`rough\` only when the user requests that
|
|
131
139
|
register system. \`auto\` chooses address forms for the current relationship and scene,
|
|
132
140
|
keeping them consistent within each reply; \`polite\` and \`plain\` fix those forms.
|
|
141
|
+
\`plain\` means ordinary non-honorific forms; \`rough\` requests rough, cheeky non-honorific
|
|
142
|
+
sentence endings. Preserve the configured address terms and every independent axis.
|
|
133
143
|
It has no effect where the language lacks an honorific distinction. A request for more or
|
|
134
144
|
less formality changes wording and distance within the chosen forms, without silently changing
|
|
135
145
|
\`speech_level\`. Familiar polite, formal plain and affectionate swearing are valid combinations;
|
|
@@ -198,12 +208,13 @@ the user to complete CLI installation and explicit setup; do not install it or g
|
|
|
198
208
|
## Read the current state first
|
|
199
209
|
|
|
200
210
|
\`\`\`bash
|
|
201
|
-
tonedial
|
|
211
|
+
tonedial get --json
|
|
202
212
|
\`\`\`
|
|
203
213
|
|
|
204
|
-
The
|
|
205
|
-
|
|
206
|
-
\`
|
|
214
|
+
The \`values\` object carries the twelve axis levels and the separate settings under their
|
|
215
|
+
editable keys. An unset setting is \`null\`; description text is returned in full.
|
|
216
|
+
For the formatted overview, preset state and advisory \`riskAxes\`, use \`tonedial show --json\`.
|
|
217
|
+
Every command takes \`--json\` and answers with a \`schema\` field.
|
|
207
218
|
${JSON_MD}
|
|
208
219
|
Otherwise report the failed step
|
|
209
220
|
and stop that flow. Never report a failed mutation as saved; failures after a successful mutation
|
|
@@ -230,7 +241,7 @@ Report the movement back to the user in their own terms, from the \`moves\` fiel
|
|
|
230
241
|
- "stop flattering me" → \`flattery=1\`. Not \`warmth\`, which is a different observable.
|
|
231
242
|
- "be brief" → \`verbosity\` down. Change \`care\` only for requested coverage or organization changes.
|
|
232
243
|
- "more casual" → \`formality\` down. Change the register system only on an explicit request,
|
|
233
|
-
such as "use plain forms" → \`speech_level=plain\`.
|
|
244
|
+
such as "use plain forms" → \`speech_level=plain\`, or "use rough, cheeky plain forms" → \`speech_level=rough\`.
|
|
234
245
|
|
|
235
246
|
For a named preset, run \`tonedial list --json\` then \`tonedial use <slug> --json\`.
|
|
236
247
|
An explicit agent character or role request uses \`persona.agent\`; an explicit request to
|
|
@@ -308,7 +319,7 @@ During a test conversation, do not mutate ToneDial settings or perform work thro
|
|
|
308
319
|
*/
|
|
309
320
|
export const SKILL_TRAITS_MD = `# Axes
|
|
310
321
|
|
|
311
|
-
Twelve axes, each 1 to 5. Read the live values with \`tonedial
|
|
322
|
+
Twelve axes, each 1 to 5. Read the live values with \`tonedial get --json\`; this file says what
|
|
312
323
|
each one **owns**, which is what tells you which axis a request is actually about.
|
|
313
324
|
|
|
314
325
|
Axes name distinct aspects of a reply. Map the aspect the user named rather than changing
|
|
@@ -369,7 +380,6 @@ depends on the host and model.
|
|
|
369
380
|
- Functional status emoji can accompany low \`warmth\`; they are structure, not affection.
|
|
370
381
|
- Required task formats take precedence: a code or JSON result must not become decorative prose.
|
|
371
382
|
- \`refusal\` ≥ 4 with \`compliance\` ≥ 4 contradicts itself. The CLI warns and does not block.
|
|
372
|
-
- \`formality\` ≥ 4 with \`profanity\` ≥ 4 holds but reads oddly.
|
|
373
383
|
- \`warmth\` ≥ 4 with \`profanity\` ≥ 4, and \`compliance\` ≤ 2 with \`conviction\` ≤ 2, each get a
|
|
374
384
|
composite sentence in the artifact rather than being left to collide.
|
|
375
385
|
- Care 5 adds a caring organizer signature; high playfulness with high bravado adds a cocky expert signature. These are static instructions selected by the combination layer.
|
|
@@ -381,9 +391,11 @@ do not prove actual host activation or model compliance.
|
|
|
381
391
|
|
|
382
392
|
## Settings that are not axes
|
|
383
393
|
|
|
384
|
-
\`speech_level\` picks address forms — \`auto\`, \`polite\`, or \`
|
|
394
|
+
\`speech_level\` picks address forms — \`auto\`, \`polite\`, \`plain\`, or \`rough\` — where the language
|
|
385
395
|
has an honorific distinction, and does nothing where it does not. Auto adapts to the current
|
|
386
|
-
relationship and scene, staying consistent within a reply.
|
|
396
|
+
relationship and scene, staying consistent within a reply. Plain uses ordinary non-honorific forms;
|
|
397
|
+
rough uses rough, cheeky non-honorific endings while keeping configured address terms.
|
|
398
|
+
\`formality\` sets distance inside
|
|
387
399
|
the chosen forms; warmth and profanity keep their selected effects. The persona layer (\`age\`, \`gender\`, \`self_ref\`, \`user_ref\`,
|
|
388
400
|
\`user\`, \`agent\`, \`note\`) and the safeword are included in bundled and saved custom profiles.
|
|
389
401
|
\`persona.user\` is user context and \`persona.agent\` a fictional agent role, each limited to
|
|
@@ -393,7 +405,7 @@ them, and \`export\`/\`import\` transfer them. Older axis-only presets keep the
|
|
|
393
405
|
The seven bundled profiles supply localized role and address defaults; custom text remains literal.
|
|
394
406
|
Playfulness grades role idiom frequency from literal wording at 1 to theatrical voice throughout at 5.
|
|
395
407
|
Role slang and metaphors are not filtered or suppressed by low profanity or bravado.
|
|
396
|
-
Profanity grades actual swearing; bravado grades effort claims.
|
|
408
|
+
Profanity grades actual swearing; bravado grades effort claims.
|
|
397
409
|
`;
|
|
398
410
|
/**
|
|
399
411
|
* The Claude Code `/tone` command, installed only by `init --commands`.
|
|
@@ -403,12 +415,12 @@ Profanity grades actual swearing; bravado grades effort claims. The CLI carries
|
|
|
403
415
|
* command name stays `/tone` even though the tool is `tonedial`: it is what a person types
|
|
404
416
|
* mid-sentence, and the two extra characters are paid every time.
|
|
405
417
|
*/
|
|
406
|
-
export const TONE_COMMAND_VERSION =
|
|
418
|
+
export const TONE_COMMAND_VERSION = 11;
|
|
407
419
|
export const TONE_COMMAND_MD = `---
|
|
408
420
|
description: Check, change, or reload this agent's tone, preset, or ToneDial settings
|
|
409
421
|
---
|
|
410
422
|
|
|
411
|
-
Run \`tonedial
|
|
423
|
+
Run \`tonedial get --json\` and report the requested current settings from \`values\` in plain language.
|
|
412
424
|
Use the same installed CLI and environment throughout.
|
|
413
425
|
${JSON_MD}
|
|
414
426
|
Otherwise report the failed step and stop that flow.
|
package/dist/ui/format.js
CHANGED
|
@@ -11,7 +11,7 @@ export function nextStep(command, ui) {
|
|
|
11
11
|
/** One advisory notice from the shared axis metadata, without changing selected levels. */
|
|
12
12
|
export function riskWarnings(axes, t) {
|
|
13
13
|
const risks = riskyAxes(axes);
|
|
14
|
-
return
|
|
14
|
+
return risks.length ? [t.notice.riskAxes(risks)] : [];
|
|
15
15
|
}
|
|
16
16
|
/** Two retained phases restore their shared config in reverse order, with copyable IDs. */
|
|
17
17
|
export function recoveryRollbackLines(t, appId, nativeId) {
|
|
@@ -64,10 +64,7 @@ export function num(n) {
|
|
|
64
64
|
export function artifactSize(size, t) {
|
|
65
65
|
return t.artifact.size(num(size.chars));
|
|
66
66
|
}
|
|
67
|
-
/**
|
|
67
|
+
/** Contradictions stay advisory and never block the selected values. */
|
|
68
68
|
export function combination(w, t) {
|
|
69
|
-
|
|
70
|
-
return w.layer === "forbidden"
|
|
71
|
-
? `${t.combos.contradiction(w.rule)} ${sentence} ${t.combos.nothingBlocked}`
|
|
72
|
-
: `${t.combos.reads(w.rule)} ${sentence}`;
|
|
69
|
+
return `${t.combos.contradiction(w.rule)} ${t.combos[w.id]} ${t.combos.nothingBlocked}`;
|
|
73
70
|
}
|