tonedial 0.1.1 → 0.1.2
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 +1 -1
- package/dist/commands/create.js +7 -7
- package/dist/commands/doctor.js +21 -14
- package/dist/commands/edit.js +3 -3
- package/dist/commands/init.js +6 -12
- package/dist/commands/save.js +2 -10
- package/dist/commands/set.js +17 -54
- package/dist/commands/show.js +1 -5
- 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/combos.js +6 -38
- package/dist/core/config.js +0 -1
- package/dist/core/i18n.js +58 -60
- package/dist/core/locales/es.js +29 -30
- package/dist/core/locales/ja.js +29 -30
- package/dist/core/locales/zh.js +29 -30
- package/dist/core/seeds.js +3 -3
- package/dist/core/selfupdate.js +47 -2
- package/dist/core/skill.js +4 -5
- package/dist/ui/format.js +3 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,7 @@ export const JSON_SCHEMA = 3;
|
|
|
34
34
|
* run time is the one thing that breaks all three binary packagers at once. A test asserts this
|
|
35
35
|
* equals the manifest, which is the cheap half of the trade.
|
|
36
36
|
*/
|
|
37
|
-
export const VERSION = "0.1.
|
|
37
|
+
export const VERSION = "0.1.2";
|
|
38
38
|
const FLAG_NAMES = ["project", "check", "explain", "global", "here", "migrateLegacy", "auto", "dryRun", "fix", "rollback", "fromOperation", "fromPreset", "restorePreset", "preset", "slug", "acceptUsage", "lang", "commands", "tools"];
|
|
39
39
|
/** Command-line spelling for each flag, where it differs from the field name. */
|
|
40
40
|
const FLAG_SPELLING = {
|
package/dist/commands/create.js
CHANGED
|
@@ -11,7 +11,7 @@ import { guardTerminal } from "../ui/prompt.js";
|
|
|
11
11
|
import { wrap } from "../ui/width.js";
|
|
12
12
|
import { planConfig } from "./compile.js";
|
|
13
13
|
import { savePreset } from "./save.js";
|
|
14
|
-
import { applyAssignments } from "./set.js";
|
|
14
|
+
import { applyAssignments, settingsWarnings } from "./set.js";
|
|
15
15
|
export async function runCreate(ctx, args) {
|
|
16
16
|
try {
|
|
17
17
|
const state = planConfig(ctx);
|
|
@@ -23,7 +23,7 @@ export async function runCreate(ctx, args) {
|
|
|
23
23
|
config.persona.agent ??= defaults.agent;
|
|
24
24
|
let displayName = args[0]?.trim() ?? "";
|
|
25
25
|
let slug = ctx.flags.slug ?? undefined;
|
|
26
|
-
|
|
26
|
+
applyAssignments(config, args.slice(1), ctx.t, ctx.asJson);
|
|
27
27
|
if (ctx.canPrompt && !ctx.flags.auto && !ctx.flags.dryRun) {
|
|
28
28
|
const release = guardTerminal();
|
|
29
29
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -60,10 +60,9 @@ export async function runCreate(ctx, args) {
|
|
|
60
60
|
const chosen = await ask(label, value);
|
|
61
61
|
try {
|
|
62
62
|
const candidate = structuredClone(config);
|
|
63
|
-
|
|
63
|
+
applyAssignments(candidate, [`${key}=${chosen}`], ctx.t, false);
|
|
64
64
|
serializeConfig(candidate, ctx.t);
|
|
65
65
|
Object.assign(config, candidate);
|
|
66
|
-
warnings.push(...applied.warnings);
|
|
67
66
|
if (key === "persona.user" || key === "persona.agent") {
|
|
68
67
|
process.stdout.write(` ${ctx.t.create.descriptionCount([...chosen].length, PERSONA_DESCRIPTION_MAX)}\n`);
|
|
69
68
|
}
|
|
@@ -97,9 +96,10 @@ export async function runCreate(ctx, args) {
|
|
|
97
96
|
return {
|
|
98
97
|
...result,
|
|
99
98
|
json: { ...result.json, ...(result.json.created === true ? { config: toCompileInput(config) } : {}) },
|
|
100
|
-
warnings:
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
warnings: result.json.created === true ? [
|
|
100
|
+
...settingsWarnings(config, ctx.t, ctx.asJson), ...riskWarnings(config.axes, ctx.t),
|
|
101
|
+
...checkCombos(config.axes).map((notice) => combination(notice, ctx.t)),
|
|
102
|
+
] : [],
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
catch (cause) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -7,7 +7,7 @@ import { runRebuildNative } from "./wire.js";
|
|
|
7
7
|
import { compile, measure } from "../core/compile.js";
|
|
8
8
|
import { ConfigError, defaultConfig, isConfigV2, parseConfig } from "../core/config.js";
|
|
9
9
|
import { inspectOperations, inspectRollback } from "../core/operations.js";
|
|
10
|
-
import { join } from "node:path";
|
|
10
|
+
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";
|
|
@@ -681,9 +681,11 @@ async function doctorReport(ctx, showRollbackCommands = true, showCodexAction =
|
|
|
681
681
|
const appHealthy = diagnosis.layout.kind === "current" && diagnosis.layout.cause === "none" && seeds.every(({ state }) => state === "current");
|
|
682
682
|
const lines = [
|
|
683
683
|
` ${ctx.ui.bold((appHealthy ? ctx.ui.success : ctx.ui.warn)(ctx.t.doctor.applicationHealth(appHealthy)))}`,
|
|
684
|
-
...
|
|
685
|
-
|
|
686
|
-
|
|
684
|
+
...(integrations.length || nativeAttention ? [
|
|
685
|
+
...section(ctx.t.doctor.connectionsHeading, ctx.ui),
|
|
686
|
+
...wrap(ctx.t.doctor.connectionHealth(nativeAttention), ctx.ui.width - 2).map((line) => ` ${nativeAttention ? ctx.ui.warn(line) : line}`),
|
|
687
|
+
...wrap(ctx.t.doctor.hookSettingScope, ctx.ui.width - 2).map(line => ` ${line}`),
|
|
688
|
+
] : []),
|
|
687
689
|
...section(ctx.t.doctor.checksHeading, ctx.ui),
|
|
688
690
|
...renderCheckBlocks(shownChecks, ctx.flags.verbose ? [] : hookSettings, integrations, ctx),
|
|
689
691
|
// A repair receipt presents its new IDs once in native→app order. Standalone diagnosis
|
|
@@ -753,8 +755,7 @@ async function runRollback(ctx, operationId) {
|
|
|
753
755
|
* The seeds in home against the ones this package carries.
|
|
754
756
|
*
|
|
755
757
|
* An npm update replaces the code and never touches `~/.tonedial/`, so a corrected anchor would
|
|
756
|
-
* otherwise never reach an existing user.
|
|
757
|
-
* what tells someone there is a reason to.
|
|
758
|
+
* otherwise never reach an existing user. `doctor --fix` refreshes generated application files.
|
|
758
759
|
*/
|
|
759
760
|
function seedCheck(ctx, config, seeds) {
|
|
760
761
|
// Zero means `init` never ran, which `use` and `wire` reach on their own and which nothing is
|
|
@@ -771,7 +772,7 @@ function seedCheck(ctx, config, seeds) {
|
|
|
771
772
|
state: "problem",
|
|
772
773
|
detail: ctx.t.doctor.seedsAttention,
|
|
773
774
|
files: stale.map(({ seed, state }) => ({ path: seed.path, reason: state })),
|
|
774
|
-
...(stale.every(({ state }) => state === "missing" || state === "stale") ? { repair: repairCommand(ctx, "
|
|
775
|
+
...(stale.every(({ state }) => state === "missing" || state === "stale") ? { repair: repairCommand(ctx, "doctor --fix") } : {}),
|
|
775
776
|
};
|
|
776
777
|
}
|
|
777
778
|
/** Whether the skill is linked, copied, someone else's, or simply not installed. */
|
|
@@ -1050,15 +1051,23 @@ function elsewhereReport(rows, ctx) {
|
|
|
1050
1051
|
}
|
|
1051
1052
|
function report(check, ctx) {
|
|
1052
1053
|
const paint = check.state === "problem" ? ctx.ui.error : check.state === "ok" ? ctx.ui.success : ctx.ui.dim;
|
|
1053
|
-
const head = ` ${paint(MARK[check.state])} ${ctx.ui.bold(humanText(check.name))}`;
|
|
1054
|
+
const head = ` ${paint(MARK[check.state])} ${ctx.ui.bold(humanText(check.name === "seeds" ? ctx.t.doctor.seedsHeading : check.name))}`;
|
|
1054
1055
|
// Keep the marker and check name together, then give every detail a stable four-cell hanging
|
|
1055
1056
|
// indent. A fixed name column made a narrow doctor report spend most of its width on padding.
|
|
1056
|
-
const body = (check.humanDetail ?? [check.detail]).flatMap(detail => detail.split("\n"))
|
|
1057
|
+
const body = (check.files?.length ? [] : check.humanDetail ?? [check.detail]).flatMap(detail => detail.split("\n"))
|
|
1057
1058
|
.flatMap(line => receiptLines(humanText(line), ctx.ui.width - 6));
|
|
1058
1059
|
const lines = [head, ...body.map((line) => ` ${check.state === "ok" ? ctx.ui.dim(line) : line}`)];
|
|
1059
|
-
|
|
1060
|
-
lines.push(
|
|
1061
|
-
|
|
1060
|
+
if (check.files?.length) {
|
|
1061
|
+
lines.push(` ${humanText(ctx.paths.root)}`);
|
|
1062
|
+
for (const reason of ["missing", "stale", "modified", "foreign", "unreadable"]) {
|
|
1063
|
+
const files = check.files.filter(file => file.reason === reason);
|
|
1064
|
+
if (!files.length)
|
|
1065
|
+
continue;
|
|
1066
|
+
lines.push(...wrap(ctx.t.doctor.seedReason[reason], ctx.ui.width - 6).map(line => ` ${line}`));
|
|
1067
|
+
for (const file of files) {
|
|
1068
|
+
lines.push(...wrap(humanText(relative(ctx.paths.root, file.path)), ctx.ui.width - 8).map(line => ` ${line}`));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1062
1071
|
}
|
|
1063
1072
|
if (check.repair)
|
|
1064
1073
|
lines.push(...nextStep(check.repair, ctx.ui));
|
|
@@ -1092,7 +1101,5 @@ function unconnectedReport(settings, ctx) {
|
|
|
1092
1101
|
return [
|
|
1093
1102
|
` ${ctx.ui.dim(MARK.note)} ${ctx.ui.bold(ctx.t.doctor.hookGroupHeading)}`,
|
|
1094
1103
|
...wrap(ctx.t.doctor.hookGroupUnknown(tools), ctx.ui.width - 4).map(line => ` ${line}`),
|
|
1095
|
-
...wrap(ctx.t.doctor.hookGroupMissing, ctx.ui.width - 4).map(line => ` ${line}`),
|
|
1096
|
-
...wrap(ctx.t.doctor.hookGroupAction, ctx.ui.width - 4).map(line => ` ${line}`),
|
|
1097
1104
|
];
|
|
1098
1105
|
}
|
package/dist/commands/edit.js
CHANGED
|
@@ -11,6 +11,7 @@ import { check as checkCombos } from "../core/combos.js";
|
|
|
11
11
|
import { artifactSize, combination, humanText, riskWarnings } from "../ui/format.js";
|
|
12
12
|
import { captureRawEntry, planChange, rawReadChange, rawReadCheck } from "../wiring/apply.js";
|
|
13
13
|
import { emptyWiring } from "../wiring/ownership.js";
|
|
14
|
+
import { settingsWarnings } from "./set.js";
|
|
14
15
|
/**
|
|
15
16
|
* The editor to hand the file to. `$VISUAL` wins over `$EDITOR` by long convention — a user
|
|
16
17
|
* who sets both means the first for interactive work, which this is.
|
|
@@ -227,10 +228,9 @@ export async function runEdit(ctx, args) {
|
|
|
227
228
|
}
|
|
228
229
|
/** The notices every other state-changing command emits, through the same owners they use. */
|
|
229
230
|
function stateWarnings(ctx, config) {
|
|
230
|
-
const p = config.persona;
|
|
231
231
|
return [
|
|
232
|
-
...checkCombos(config.axes
|
|
233
|
-
...(
|
|
232
|
+
...checkCombos(config.axes).map((w) => combination(w, ctx.t)),
|
|
233
|
+
...settingsWarnings(config, ctx.t, ctx.asJson),
|
|
234
234
|
...riskWarnings(config.axes, ctx.t),
|
|
235
235
|
];
|
|
236
236
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -219,11 +219,10 @@ export async function runInit(entry, _args) {
|
|
|
219
219
|
addReceipt(message);
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
addReceipt(notice, ctx.ui.risk);
|
|
222
|
+
if (applied) {
|
|
223
|
+
for (const notice of riskWarnings(config.axes, ctx.t))
|
|
224
|
+
addReceipt(notice, ctx.ui.risk);
|
|
225
|
+
}
|
|
227
226
|
lines.push("");
|
|
228
227
|
// Skills and controls invoke bare tonedial, so command reachability needs its own hint.
|
|
229
228
|
if (onPath("tonedial", ctx.env) === null) {
|
|
@@ -298,10 +297,6 @@ async function rebuildExisting(entry) {
|
|
|
298
297
|
const resultHeading = ctx.canPrompt && !ctx.flags.auto && !ctx.flags.dryRun
|
|
299
298
|
? section(copy.init.resultHeading, ctx.ui) : [];
|
|
300
299
|
const app = await runAppRebuild(ctx);
|
|
301
|
-
const riskIntro = [
|
|
302
|
-
...receiptLines(copy.init.risk, ctx.ui.width - 2).map(line => ` ${ctx.ui.warn(line)}`),
|
|
303
|
-
...receiptLines(ctx.t.notice.modelExpectation, ctx.ui.width - 2).map(line => ` ${line}`),
|
|
304
|
-
];
|
|
305
300
|
if (ctx.flags.dryRun) {
|
|
306
301
|
const answers = await ask(ctx, copy, false, selection);
|
|
307
302
|
const tools = selection ?? answers.tools;
|
|
@@ -309,7 +304,7 @@ async function rebuildExisting(entry) {
|
|
|
309
304
|
throw new ConfigError(ctx.t.errors.commandsNeedClaude);
|
|
310
305
|
const native = await runRebuildNative({ ...ctx, flags: { ...ctx.flags, tools: tools.length ? tools.join(",") : "none" } }, app.legacy, app.proposedConfig);
|
|
311
306
|
return {
|
|
312
|
-
lines: [...(ctx.canPrompt ? [] : introduction), ...app.lines, ...native.lines
|
|
307
|
+
lines: [...(ctx.canPrompt ? [] : introduction), ...app.lines, ...native.lines],
|
|
313
308
|
json: { ...app.json, agentRoot: ctx.agent.root, live: ctx.agent.live,
|
|
314
309
|
scope: ctx.agent.live ? "global" : "project", scopeExplicit, language: app.proposedConfig.language,
|
|
315
310
|
seedVersion: SEED_VERSION, tools, native: native.json,
|
|
@@ -320,7 +315,7 @@ async function rebuildExisting(entry) {
|
|
|
320
315
|
};
|
|
321
316
|
}
|
|
322
317
|
if (app.rebuiltConfig === null)
|
|
323
|
-
return { ...app, lines: [...(ctx.canPrompt ? [] : introduction), ...resultHeading, ...app.lines
|
|
318
|
+
return { ...app, lines: [...(ctx.canPrompt ? [] : introduction), ...resultHeading, ...app.lines] };
|
|
324
319
|
const answers = await ask(ctx, copy, false, selection);
|
|
325
320
|
const tools = selection ?? answers.tools;
|
|
326
321
|
if (ctx.flags.commands && !tools.includes("claude"))
|
|
@@ -331,7 +326,6 @@ async function rebuildExisting(entry) {
|
|
|
331
326
|
const after = codexHandoff(scoped, native.json.applied === true || native.json.reason === "nothing", codexConfigured);
|
|
332
327
|
return {
|
|
333
328
|
lines: [...(ctx.canPrompt ? [] : introduction), ...resultHeading, ...app.lines, ...native.lines,
|
|
334
|
-
...riskIntro,
|
|
335
329
|
...recoveryRollbackLines(ctx.t, app.json.operationId, native.json.operationId)],
|
|
336
330
|
...(after ? { after } : {}),
|
|
337
331
|
json: {
|
package/dist/commands/save.js
CHANGED
|
@@ -73,20 +73,13 @@ export async function savePreset(ctx, displayName, state, options = {}) {
|
|
|
73
73
|
};
|
|
74
74
|
const count = Object.keys(values).length;
|
|
75
75
|
const countLabel = ctx.t.preset.nonDefault(count);
|
|
76
|
-
const warnings = [];
|
|
77
|
-
if (replaced)
|
|
78
|
-
warnings.push(ctx.t.preset.replaced(slug));
|
|
79
|
-
// Sparsity survives on one reason after the omit-defaults rule fell: fewer extremes mean
|
|
80
|
-
// fewer internal collisions. It is guidance, so it warns and never blocks.
|
|
81
|
-
if (count < 3 || count > 7) {
|
|
82
|
-
warnings.push(ctx.t.preset.sparsity(slug, count));
|
|
83
|
-
}
|
|
84
76
|
return {
|
|
85
77
|
// Truncated, not folded. A display name has no length cap, and folding one across two lines
|
|
86
78
|
// makes the slug beside it harder to read than losing the tail does.
|
|
87
79
|
lines: [
|
|
88
80
|
...(options.create ? result.lines : []),
|
|
89
|
-
|
|
81
|
+
...wrap(replaced ? ctx.t.preset.replaced(slug) : ctx.t.preset.savedTarget(slug), ctx.ui.width - 2)
|
|
82
|
+
.map((line) => ` ${ctx.ui.success(ctx.ui.bold(line))}`),
|
|
90
83
|
` ${truncate(humanText(displayName), Math.max(1, ctx.ui.width - cells(` ${countLabel}`)))} ${ctx.ui.dim(countLabel)}`,
|
|
91
84
|
` ${ctx.t.artifact.generated}`,
|
|
92
85
|
...(ctx.flags.verbose ? [` ${ctx.ui.dim(artifactSize(size, ctx.t))}`] : []),
|
|
@@ -94,6 +87,5 @@ export async function savePreset(ctx, displayName, state, options = {}) {
|
|
|
94
87
|
],
|
|
95
88
|
json: { slug, displayName, values, settings: preset.settings, nonDefaultAxes: count, replaced, chars: size.chars,
|
|
96
89
|
...(options.create ? { created: true, diff: result.diff, operationId: result.operationId } : {}) },
|
|
97
|
-
warnings,
|
|
98
90
|
};
|
|
99
91
|
}
|
package/dist/commands/set.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// job is to say so. Every selected level remains unchanged, including contradictions.
|
|
6
6
|
import { AXIS_DEFAULT, AXIS_ORDER, riskyAxes } from "../core/anchors.js";
|
|
7
7
|
import { check as checkCombos, PERSONA_DESCRIPTION_MAX } from "../core/combos.js";
|
|
8
|
-
import { AGE_BANDS, GENDERS,
|
|
8
|
+
import { AGE_BANDS, GENDERS, SPEECH_LEVELS, ConfigError, SETTING_KEYS, isAxis, nearest, nearestAxis, serializeConfig, } from "../core/config.js";
|
|
9
9
|
import { toCompileInput } from "../core/resolve.js";
|
|
10
10
|
import { LANGUAGES, catalogue, isLanguage } from "../core/i18n.js";
|
|
11
11
|
import { presetSettings, resolvePreset } from "../core/presets.js";
|
|
@@ -31,36 +31,15 @@ function bucketAge(value, t, asJson) {
|
|
|
31
31
|
}
|
|
32
32
|
/** The settable persona keys, in the order `set --help` and the error message list them. */
|
|
33
33
|
const PERSONA_FIELDS = ["age", "gender", "self_ref", "user_ref", "note", "user", "agent"];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
harsh: "critique", critic: "critique", blunt: "critique",
|
|
40
|
-
praise: "flattery", compliment: "flattery", flatter: "flattery",
|
|
41
|
-
warm: "warmth", kind: "warmth", empath: "warmth",
|
|
42
|
-
opinion: "conviction", decisive: "conviction",
|
|
43
|
-
thorough: "care", proactive: "care",
|
|
44
|
-
obedient: "compliance", grumble: "compliance",
|
|
45
|
-
refuse: "refusal", boast: "bravado", brag: "bravado",
|
|
46
|
-
};
|
|
47
|
-
/** The note is a content channel, so `set` says what an axis would own better. It never edits. */
|
|
48
|
-
function noteWarnings(note, t) {
|
|
49
|
-
const out = [];
|
|
50
|
-
if ([...note].length > NOTE_MAX) {
|
|
51
|
-
out.push(t.persona.noteOverCap([...note].length, NOTE_MAX));
|
|
52
|
-
}
|
|
53
|
-
const lower = note.toLowerCase();
|
|
54
|
-
const named = [...new Set(Object.entries(OBSERVABLE_WORDS)
|
|
55
|
-
.filter(([word]) => lower.includes(word))
|
|
56
|
-
.map(([, axis]) => axis))];
|
|
57
|
-
if (named.length) {
|
|
58
|
-
out.push(t.persona.noteNamesAxis(named));
|
|
34
|
+
/** Report limitations in the final settings, never superseded interview answers. */
|
|
35
|
+
export function settingsWarnings(config, t, asJson) {
|
|
36
|
+
const warnings = [];
|
|
37
|
+
if (config.safeword && [...config.safeword].length < 4) {
|
|
38
|
+
warnings.push(t.settings.safewordShort(asJson ? config.safeword : humanText(config.safeword)));
|
|
59
39
|
}
|
|
60
|
-
return
|
|
40
|
+
return warnings;
|
|
61
41
|
}
|
|
62
42
|
function assign(config, key, value, t, asJson) {
|
|
63
|
-
const warnings = [];
|
|
64
43
|
if (key === "guards") {
|
|
65
44
|
throw new ConfigError(t.settings.guardsRetired);
|
|
66
45
|
}
|
|
@@ -70,23 +49,20 @@ function assign(config, key, value, t, asJson) {
|
|
|
70
49
|
if (key === "safeword") {
|
|
71
50
|
const phrase = value.trim();
|
|
72
51
|
config.safeword = phrase === "" ? null : phrase;
|
|
73
|
-
|
|
74
|
-
warnings.push(t.settings.safewordShort(asJson ? config.safeword : humanText(config.safeword)));
|
|
75
|
-
}
|
|
76
|
-
return warnings;
|
|
52
|
+
return;
|
|
77
53
|
}
|
|
78
54
|
if (key === "speech_level") {
|
|
79
55
|
if (!SPEECH_LEVELS.includes(value)) {
|
|
80
56
|
throw new ConfigError(t.settings.speechLevel(SPEECH_LEVELS));
|
|
81
57
|
}
|
|
82
58
|
config.speech_level = value;
|
|
83
|
-
return
|
|
59
|
+
return;
|
|
84
60
|
}
|
|
85
61
|
if (key === "language") {
|
|
86
62
|
if (!isLanguage(value))
|
|
87
63
|
throw new ConfigError(t.errors.languageUnsupported(asJson ? value : humanText(value), LANGUAGES));
|
|
88
64
|
config.language = value;
|
|
89
|
-
return
|
|
65
|
+
return;
|
|
90
66
|
}
|
|
91
67
|
if (key.startsWith("persona.")) {
|
|
92
68
|
const field = key.slice("persona.".length);
|
|
@@ -109,8 +85,6 @@ function assign(config, key, value, t, asJson) {
|
|
|
109
85
|
break;
|
|
110
86
|
case "note":
|
|
111
87
|
config.persona.note = empty ? null : value;
|
|
112
|
-
if (!empty)
|
|
113
|
-
warnings.push(...noteWarnings(value, t));
|
|
114
88
|
break;
|
|
115
89
|
case "user":
|
|
116
90
|
case "agent": {
|
|
@@ -132,7 +106,7 @@ function assign(config, key, value, t, asJson) {
|
|
|
132
106
|
// first. An axis error already states its range; this one owed the same.
|
|
133
107
|
throw new ConfigError(t.persona.unknownField(asJson ? field : humanText(field), PERSONA_FIELDS));
|
|
134
108
|
}
|
|
135
|
-
return
|
|
109
|
+
return;
|
|
136
110
|
}
|
|
137
111
|
if (key.startsWith("subagent.")) {
|
|
138
112
|
throw new ConfigError(t.axis.subagentUnsupported);
|
|
@@ -154,10 +128,9 @@ function assign(config, key, value, t, asJson) {
|
|
|
154
128
|
config.axes[key] = level;
|
|
155
129
|
config.unsaved = true;
|
|
156
130
|
}
|
|
157
|
-
return warnings;
|
|
158
131
|
}
|
|
159
132
|
function comboWarnings(config, t) {
|
|
160
|
-
return checkCombos(config.axes
|
|
133
|
+
return checkCombos(config.axes).map((w) => combination(w, t));
|
|
161
134
|
}
|
|
162
135
|
const UNSET = "—";
|
|
163
136
|
/**
|
|
@@ -221,16 +194,12 @@ function readKey(config, key, t) {
|
|
|
221
194
|
*/
|
|
222
195
|
function applyPairs(config, pairs, t, asJson) {
|
|
223
196
|
const settingsBefore = JSON.stringify(presetSettings(config));
|
|
224
|
-
const warnings = [];
|
|
225
197
|
const changed = [];
|
|
226
198
|
const moves = [];
|
|
227
|
-
let personaTouched = false;
|
|
228
199
|
for (const [key, value] of pairs) {
|
|
229
200
|
const before = readKey(config, key, t);
|
|
230
|
-
|
|
201
|
+
assign(config, key, value, t, asJson);
|
|
231
202
|
changed.push(key);
|
|
232
|
-
if (key.startsWith("persona.") && value.trim() !== "")
|
|
233
|
-
personaTouched = true;
|
|
234
203
|
const after = readKey(config, key, t);
|
|
235
204
|
// Only a real move. `set formality=3` on an axis already at 3 was reporting `3 → 3`, which
|
|
236
205
|
// is the misleading `from` this line exists to prevent.
|
|
@@ -240,9 +209,7 @@ function applyPairs(config, pairs, t, asJson) {
|
|
|
240
209
|
}
|
|
241
210
|
if (settingsBefore !== JSON.stringify(presetSettings(config)))
|
|
242
211
|
config.unsaved = true;
|
|
243
|
-
|
|
244
|
-
warnings.push(t.notice.personaUnanchored);
|
|
245
|
-
return { warnings, changed, moves };
|
|
212
|
+
return { warnings: settingsWarnings(config, t, asJson), changed, moves };
|
|
246
213
|
}
|
|
247
214
|
/** The same assignments and validation for direct tuning and preset creation. */
|
|
248
215
|
export function applyAssignments(config, args, t, asJson) {
|
|
@@ -276,18 +243,14 @@ async function commit(ctx, configChange, config, applied) {
|
|
|
276
243
|
};
|
|
277
244
|
}
|
|
278
245
|
export async function runSet(ctx, args) {
|
|
279
|
-
let result;
|
|
280
246
|
if (!args.length) {
|
|
281
247
|
if (!ctx.canPrompt || !interactive()) {
|
|
282
248
|
throw new ConfigError(ctx.t.axis.needsArgument);
|
|
283
249
|
}
|
|
284
|
-
|
|
250
|
+
return runDial(ctx);
|
|
285
251
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
result = await commit(ctx, configChange, config, applyAssignments(config, args, ctx.t, ctx.asJson));
|
|
289
|
-
}
|
|
290
|
-
return { ...result, warnings: [...(result.warnings ?? []), ctx.t.notice.modelExpectation] };
|
|
252
|
+
const { change: configChange, config } = planConfig(ctx);
|
|
253
|
+
return commit(ctx, configChange, config, applyAssignments(config, args, ctx.t, ctx.asJson));
|
|
291
254
|
}
|
|
292
255
|
/**
|
|
293
256
|
* `tonedial address` — the two terms, and nothing else.
|
package/dist/commands/show.js
CHANGED
|
@@ -148,13 +148,9 @@ export function runShow(ctx, args) {
|
|
|
148
148
|
const [first, ...rest] = wrap(legend, ui.width - 4);
|
|
149
149
|
lines.push(` ${ui.accent("*")} ${ui.dim(first)}`, ...rest.map((line) => ` ${ui.dim(line)}`), ...nextStep(preview ? `tonedial use ${preview.slug}` : needsSave ? saveCommand : "tonedial show --explain", ui).slice(1));
|
|
150
150
|
}
|
|
151
|
-
const combinations = checkCombos(axes
|
|
151
|
+
const combinations = checkCombos(axes);
|
|
152
152
|
const warnings = combinations.map((w) => combination(w, ctx.t));
|
|
153
|
-
if (personaSet) {
|
|
154
|
-
warnings.push(ctx.t.notice.personaUnanchored);
|
|
155
|
-
}
|
|
156
153
|
warnings.push(...riskWarnings(axes, ctx.t));
|
|
157
|
-
warnings.push(ctx.t.notice.modelExpectation);
|
|
158
154
|
if (activeConflict && preview === null)
|
|
159
155
|
warnings.push(ctx.t.preset.activeConflict(config.preset));
|
|
160
156
|
return {
|
package/dist/commands/update.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// Update
|
|
1
|
+
// Update through the installation owner and report application-state work separately.
|
|
2
2
|
import { VERSION } from "../cli.js";
|
|
3
3
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "node:fs";
|
|
4
4
|
import { TextDecoder } from "node:util";
|
|
5
5
|
import { ConfigError, parseConfig } from "../core/config.js";
|
|
6
|
-
import { AGENT_TOOLS, UPDATE_COMMAND, installShape } from "../core/paths.js";
|
|
7
|
-
import { REGISTRY, downloadRelease, haveTar, installRelease, installedVersion, latestRelease } from "../core/selfupdate.js";
|
|
6
|
+
import { AGENT_TOOLS, UPDATE_COMMAND, installShape, shellQuote } from "../core/paths.js";
|
|
7
|
+
import { REGISTRY, downloadRelease, haveTar, inspectNpmInstall, installRelease, installedVersion, latestRelease, updateNpmInstall } from "../core/selfupdate.js";
|
|
8
8
|
import { confirm } from "../ui/prompt.js";
|
|
9
9
|
import { SEED_VERSION, inspectSeeds, recordedSeedSelection } from "../core/seeds.js";
|
|
10
|
-
import { humanText } from "../ui/format.js";
|
|
10
|
+
import { humanText, receiptLines } from "../ui/format.js";
|
|
11
11
|
import { urlIdentity } from "../core/net.js";
|
|
12
12
|
/** A damaged state file must not disable package guidance or reveal its parser excerpt. */
|
|
13
13
|
function updateConfig(ctx) {
|
|
@@ -84,16 +84,40 @@ export async function runUpdate(ctx) {
|
|
|
84
84
|
stateIssue = "unreadable";
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
const lines = [
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
const lines = [` ${ctx.ui.bold(ctx.t.update.running(VERSION))}`, ""];
|
|
88
|
+
const preview = () => {
|
|
89
|
+
if (!ctx.asJson) {
|
|
90
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
91
|
+
lines.length = 0;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
94
|
let replaced = null;
|
|
95
|
+
let installed = null;
|
|
96
|
+
let outcome;
|
|
95
97
|
if (shape !== "curl") {
|
|
96
|
-
|
|
98
|
+
if (ctx.flags.dryRun) {
|
|
99
|
+
outcome = "dry-run";
|
|
100
|
+
lines.push(` ${ctx.t.update.toUpdate}`, ` ${ctx.ui.accent(command)}`, ` ${ctx.t.wiring.dryRun}`);
|
|
101
|
+
}
|
|
102
|
+
else if (!ctx.flags.auto && !ctx.canPrompt) {
|
|
103
|
+
outcome = "approval-required";
|
|
104
|
+
lines.push(` ${ctx.t.update.toUpdate}`, ` ${ctx.ui.accent(command)}`, ...receiptLines(ctx.t.update.needsApproval, ctx.ui.width - 2).map(line => ` ${line}`));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const target = inspectNpmInstall(ctx.env, ctx.t);
|
|
108
|
+
lines.push(` ${ctx.t.update.toUpdate}`, ` ${ctx.ui.accent(`${command} --prefix "${shellQuote(humanText(target.prefix))}"`)}`);
|
|
109
|
+
preview();
|
|
110
|
+
if (ctx.flags.auto || (await confirm(ctx.t.update.npmQuestion)) === "yes") {
|
|
111
|
+
installed = updateNpmInstall(target, ctx.env, ctx.t);
|
|
112
|
+
replaced = installed === target.version ? null : installed;
|
|
113
|
+
outcome = replaced === null ? "unchanged" : "updated";
|
|
114
|
+
lines.push(` ${ctx.ui.success(ctx.t.update.npmComplete(installed))}`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
outcome = "declined";
|
|
118
|
+
lines.push(` ${ctx.t.update.declined}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
97
121
|
}
|
|
98
122
|
else {
|
|
99
123
|
// Before the network, like `install.sh` checks its tools before it fetches anything.
|
|
@@ -104,29 +128,38 @@ export async function runUpdate(ctx) {
|
|
|
104
128
|
if (release.version === VERSION) {
|
|
105
129
|
// Nothing downloaded. The common case is an install that is already current, and paying
|
|
106
130
|
// for the whole package to be told so is a cost with nothing on the other side of it.
|
|
131
|
+
installed = VERSION;
|
|
132
|
+
outcome = "unchanged";
|
|
107
133
|
lines.push(` ${ctx.ui.success(ctx.t.update.already(release.version))}`);
|
|
108
134
|
}
|
|
109
135
|
else if (ctx.flags.dryRun) {
|
|
110
|
-
|
|
136
|
+
outcome = "dry-run";
|
|
137
|
+
lines.push(` ${ctx.t.update.wouldReplace(humanText(ctx.paths.lib), humanText(release.version))}`);
|
|
111
138
|
}
|
|
112
139
|
else if (!ctx.flags.auto && !ctx.canPrompt) {
|
|
113
140
|
// JSON never asks, including on a terminal. Explicit --auto supplies approval through
|
|
114
141
|
// the same branch as human output; absent approval, report the existing no-install result.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
else if (ctx.flags.auto || (await confirm(ctx.t.update.question(release.version))) === "yes") {
|
|
118
|
-
const tarball = await downloadRelease(release, ctx.t);
|
|
119
|
-
lines.push(` ${ctx.ui.accent(ctx.t.update.found(release.version, tarball.byteLength))}`);
|
|
120
|
-
installRelease(ctx.paths, tarball, ctx.t);
|
|
121
|
-
// **What landed, not what was claimed.** The registry's `version` is metadata; a stub or
|
|
122
|
-
// a mistake serving one number and a different tarball made `update` report a version it
|
|
123
|
-
// had not installed — and, because the no-op check compares that metadata against this
|
|
124
|
-
// process's own constant, swap again on every run forever.
|
|
125
|
-
replaced = installedVersion(ctx.paths) ?? release.version;
|
|
126
|
-
lines.push(` ${ctx.ui.success(ctx.ui.bold(ctx.t.update.replaced(replaced)))}`);
|
|
142
|
+
outcome = "approval-required";
|
|
143
|
+
lines.push(...receiptLines(ctx.t.update.needsApproval, ctx.ui.width - 2).map(line => ` ${line}`));
|
|
127
144
|
}
|
|
128
145
|
else {
|
|
129
|
-
lines.push(` ${ctx.t.update.
|
|
146
|
+
lines.push(` ${ctx.t.update.wouldReplace(humanText(ctx.paths.lib), humanText(release.version))}`);
|
|
147
|
+
preview();
|
|
148
|
+
if (ctx.flags.auto || (await confirm(ctx.t.update.question(release.version))) === "yes") {
|
|
149
|
+
const tarball = await downloadRelease(release, ctx.t);
|
|
150
|
+
lines.push(` ${ctx.ui.accent(ctx.t.update.found(release.version, tarball.byteLength))}`);
|
|
151
|
+
installRelease(ctx.paths, tarball, ctx.t);
|
|
152
|
+
installed = installedVersion(ctx.paths);
|
|
153
|
+
if (installed === null)
|
|
154
|
+
throw new ConfigError(ctx.t.update.versionUnknown);
|
|
155
|
+
replaced = installed;
|
|
156
|
+
outcome = "updated";
|
|
157
|
+
lines.push(` ${ctx.ui.success(ctx.ui.bold(ctx.t.update.replaced(installed)))}`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
outcome = "declined";
|
|
161
|
+
lines.push(` ${ctx.t.update.declined}`);
|
|
162
|
+
}
|
|
130
163
|
}
|
|
131
164
|
}
|
|
132
165
|
const warnings = [];
|
|
@@ -134,20 +167,33 @@ export async function runUpdate(ctx) {
|
|
|
134
167
|
// `seedFiles` are constants in the copy that has just been replaced, so a release that moved
|
|
135
168
|
// the anchors would be reported against the old ones — which is the exact gap this command
|
|
136
169
|
// exists to close. Say the one thing that is still true and stop measuring.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
170
|
+
const inspectable = replaced === null ? stale ?? [] : [];
|
|
171
|
+
const routine = inspectable.filter(({ state }) => state === "missing" || state === "stale");
|
|
172
|
+
const problems = inspectable.filter(({ state }) => state !== "missing" && state !== "stale");
|
|
173
|
+
const fresh = stateIssue === "missing" && ![ctx.paths.agents, ctx.paths.tone, ctx.paths.presets, ctx.paths.skill].some(path => {
|
|
174
|
+
try {
|
|
175
|
+
return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
if (stateIssue !== null && !fresh)
|
|
182
|
+
warnings.push(ctx.t.update.stateIssue[stateIssue]);
|
|
183
|
+
if (routine.length)
|
|
184
|
+
lines.push("", ` ${ctx.t.update.refreshFiles(routine.length)}`);
|
|
185
|
+
if (problems.length) {
|
|
186
|
+
lines.push("", ` ${ctx.t.wiring.stateRoot}`, ` ${humanText(ctx.paths.root)}`);
|
|
187
|
+
for (const reason of ["modified", "foreign", "unreadable"]) {
|
|
188
|
+
const group = problems.filter(({ state }) => state === reason);
|
|
189
|
+
if (group.length)
|
|
190
|
+
warnings.push([ctx.t.doctor.seedReason[reason], ...group.map(({ seed }) => seed.label)].join("\n"));
|
|
149
191
|
}
|
|
150
192
|
}
|
|
193
|
+
const stateRecoveryRequired = !fresh && (stateIssue !== null || config?.seedVersion === 0 || inspectable.length > 0 || replaced !== null);
|
|
194
|
+
const recoveryCommand = fresh ? "tonedial init" : problems.length ? "tonedial doctor" : "tonedial doctor --fix";
|
|
195
|
+
if (fresh || stateRecoveryRequired)
|
|
196
|
+
lines.push("", ` ${fresh ? ctx.t.update.setupNeeded : problems.length ? ctx.t.update.reviewState : ctx.t.update.thenRefresh}`, ` ${ctx.ui.accent(recoveryCommand)}`);
|
|
151
197
|
return {
|
|
152
198
|
lines,
|
|
153
199
|
json: {
|
|
@@ -155,17 +201,18 @@ export async function runUpdate(ctx) {
|
|
|
155
201
|
shape,
|
|
156
202
|
command,
|
|
157
203
|
replaced,
|
|
204
|
+
installedVersion: installed,
|
|
205
|
+
outcome,
|
|
158
206
|
seedVersion: config?.seedVersion ?? null,
|
|
159
207
|
stateIssue,
|
|
160
|
-
stateRecoveryRequired
|
|
161
|
-
recoveryCommand
|
|
208
|
+
stateRecoveryRequired,
|
|
209
|
+
recoveryCommand,
|
|
162
210
|
// Null after a swap: this process is the old package, and reporting its constants as the
|
|
163
211
|
// installed ones would be a claim it cannot make.
|
|
164
212
|
packageSeedVersion: replaced === null ? SEED_VERSION : null,
|
|
165
213
|
staleSeeds: replaced === null ? (stale?.map(({ seed }) => seed.label) ?? null) : null,
|
|
166
214
|
seedIssues: replaced === null ? (stale?.map(({ seed, state }) => ({ path: seed.path, reason: state })) ?? null) : null,
|
|
167
|
-
//
|
|
168
|
-
// must not read silence here as "you are up to date".
|
|
215
|
+
// npm owns registry/cache resolution; only the curl branch observes remote metadata.
|
|
169
216
|
checkedRemote: shape === "curl",
|
|
170
217
|
},
|
|
171
218
|
warnings,
|
package/dist/commands/use.js
CHANGED
|
@@ -65,7 +65,7 @@ export async function runUse(ctx, args) {
|
|
|
65
65
|
risk: ctx.t.notice.risk,
|
|
66
66
|
roleplay: config.axes.playfulness >= 4 ? ctx.t.notice.roleplay : null,
|
|
67
67
|
};
|
|
68
|
-
const notices = [notice.safeword, ...(notice.riskAxes ? [notice.riskAxes] : [])
|
|
68
|
+
const notices = [notice.safeword, ...(notice.riskAxes ? [notice.riskAxes] : [])];
|
|
69
69
|
const lines = [
|
|
70
70
|
...wrap(ctx.t.preset.active(truncate(humanText(preset.displayName), ui.width - 12)), ui.width - 2)
|
|
71
71
|
.map((line) => ` ${ui.success(ui.bold(line))}`),
|
|
@@ -74,7 +74,7 @@ export async function runUse(ctx, args) {
|
|
|
74
74
|
// Folded here rather than at the printer: `lines` are laid out by the command that built
|
|
75
75
|
// them, and a printer that reflowed them would break every column in the gauge block.
|
|
76
76
|
"",
|
|
77
|
-
...notices.flatMap((n) => wrap(humanText(n), ui.width - 2).map((folded) => ` ${n === notice.riskAxes ? ui.risk(folded) :
|
|
77
|
+
...notices.flatMap((n) => wrap(humanText(n), ui.width - 2).map((folded) => ` ${n === notice.riskAxes ? ui.risk(folded) : folded}`)),
|
|
78
78
|
...project,
|
|
79
79
|
...nextStep("tonedial show", ui),
|
|
80
80
|
];
|
|
@@ -93,8 +93,7 @@ export async function runUse(ctx, args) {
|
|
|
93
93
|
project: projectPath,
|
|
94
94
|
},
|
|
95
95
|
warnings: [
|
|
96
|
-
...(
|
|
97
|
-
...checkCombos(config.axes, config.persona, config.speech_level).map((w) => combination(w, ctx.t)),
|
|
96
|
+
...checkCombos(config.axes).map((w) => combination(w, ctx.t)),
|
|
98
97
|
],
|
|
99
98
|
};
|
|
100
99
|
}
|