tonedial 0.1.2 → 0.1.4

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.
@@ -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 show --json\` reports the current settings. \`show <slug> --json\` previews that
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\`.
@@ -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\`.
@@ -127,9 +135,11 @@ ToneDial has no guard toggle or automatic emergency register. All axis levels 1
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 \`auto\` only when the user requests that
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;
@@ -167,6 +177,14 @@ an active association needs the user's explicit choice and must not reset saved
167
177
  Do not edit files, change the state root, remove locks or grant hook trust yourself.
168
178
  The user chooses native tools and scope separately; foreign provider containers are not
169
179
  application outputs. A successful app rebuild can still report native attention.
180
+ If repair fails, report its phase, affected path, safe error code and operation ID.
181
+ Some file changes may already be applied; a failure is not evidence that all files stayed unchanged.
182
+ Preserve the operation record and follow the reported scoped read-only diagnosis.
183
+ Default doctor output focuses on blockers; \`--verbose\` and JSON retain the operation history.
184
+ For an explicitly selected stopped operation, \`doctor --fix --from-operation <id>\` continues
185
+ application repair using valid current settings first; it does not undo the earlier changes.
186
+ Preview that exact ID and scope with \`--dry-run\` before applying the approved repair.
187
+ Interactive selection requires the full displayed ID; Enter cancels without writing.
170
188
  \`doctor --fix --dry-run\` previews app and selected native changes without writing or
171
189
  printing private prior values. Keep both reported IDs when recovery changes app and native
172
190
  files; follow the receipt and roll back native connections before application files.
@@ -198,12 +216,13 @@ the user to complete CLI installation and explicit setup; do not install it or g
198
216
  ## Read the current state first
199
217
 
200
218
  \`\`\`bash
201
- tonedial show --json
219
+ tonedial get --json
202
220
  \`\`\`
203
221
 
204
- The payload carries \`axes\` (twelve axes, each 1–5), \`defaults\`, \`groups\`, \`kinds\`,
205
- \`riskAxes\`, \`persona\`, \`safeword\`, \`speech_level\`, and \`chars\`. Every command takes \`--json\` and answers with a
206
- \`schema\` field.
222
+ The \`values\` object carries the twelve axis levels and the separate settings under their
223
+ editable keys. An unset setting is \`null\`; description text is returned in full.
224
+ For the formatted overview, preset state and advisory \`riskAxes\`, use \`tonedial show --json\`.
225
+ Every command takes \`--json\` and answers with a \`schema\` field.
207
226
  ${JSON_MD}
208
227
  Otherwise report the failed step
209
228
  and stop that flow. Never report a failed mutation as saved; failures after a successful mutation
@@ -230,7 +249,7 @@ Report the movement back to the user in their own terms, from the \`moves\` fiel
230
249
  - "stop flattering me" → \`flattery=1\`. Not \`warmth\`, which is a different observable.
231
250
  - "be brief" → \`verbosity\` down. Change \`care\` only for requested coverage or organization changes.
232
251
  - "more casual" → \`formality\` down. Change the register system only on an explicit request,
233
- such as "use plain forms" → \`speech_level=plain\`.
252
+ such as "use plain forms" → \`speech_level=plain\`, or "use rough, cheeky plain forms" → \`speech_level=rough\`.
234
253
 
235
254
  For a named preset, run \`tonedial list --json\` then \`tonedial use <slug> --json\`.
236
255
  An explicit agent character or role request uses \`persona.agent\`; an explicit request to
@@ -308,7 +327,7 @@ During a test conversation, do not mutate ToneDial settings or perform work thro
308
327
  */
309
328
  export const SKILL_TRAITS_MD = `# Axes
310
329
 
311
- Twelve axes, each 1 to 5. Read the live values with \`tonedial show --json\`; this file says what
330
+ Twelve axes, each 1 to 5. Read the live values with \`tonedial get --json\`; this file says what
312
331
  each one **owns**, which is what tells you which axis a request is actually about.
313
332
 
314
333
  Axes name distinct aspects of a reply. Map the aspect the user named rather than changing
@@ -380,9 +399,11 @@ do not prove actual host activation or model compliance.
380
399
 
381
400
  ## Settings that are not axes
382
401
 
383
- \`speech_level\` picks address forms — \`auto\`, \`polite\`, or \`plain\` — where the language
402
+ \`speech_level\` picks address forms — \`auto\`, \`polite\`, \`plain\`, or \`rough\` — where the language
384
403
  has an honorific distinction, and does nothing where it does not. Auto adapts to the current
385
- relationship and scene, staying consistent within a reply. \`formality\` sets distance inside
404
+ relationship and scene, staying consistent within a reply. Plain uses ordinary non-honorific forms;
405
+ rough uses rough, cheeky non-honorific endings while keeping configured address terms.
406
+ \`formality\` sets distance inside
386
407
  the chosen forms; warmth and profanity keep their selected effects. The persona layer (\`age\`, \`gender\`, \`self_ref\`, \`user_ref\`,
387
408
  \`user\`, \`agent\`, \`note\`) and the safeword are included in bundled and saved custom profiles.
388
409
  \`persona.user\` is user context and \`persona.agent\` a fictional agent role, each limited to
@@ -402,12 +423,12 @@ Profanity grades actual swearing; bravado grades effort claims.
402
423
  * command name stays `/tone` even though the tool is `tonedial`: it is what a person types
403
424
  * mid-sentence, and the two extra characters are paid every time.
404
425
  */
405
- export const TONE_COMMAND_VERSION = 9;
426
+ export const TONE_COMMAND_VERSION = 12;
406
427
  export const TONE_COMMAND_MD = `---
407
428
  description: Check, change, or reload this agent's tone, preset, or ToneDial settings
408
429
  ---
409
430
 
410
- Run \`tonedial show --json\` and report the requested current settings in plain language.
431
+ Run \`tonedial get --json\` and report the requested current settings from \`values\` in plain language.
411
432
  Use the same installed CLI and environment throughout.
412
433
  ${JSON_MD}
413
434
  Otherwise report the failed step and stop that flow.
@@ -2358,6 +2358,8 @@ export async function gate(changes, opts) {
2358
2358
  actions.push(...effects.final.map((effect, index) => effectAction(effect, "final", index)));
2359
2359
  const done = [];
2360
2360
  let journalStarted = false;
2361
+ let phase = "prepare";
2362
+ let target = null;
2361
2363
  try {
2362
2364
  handle = acquireOperation(opts.paths, "mutation");
2363
2365
  // Revalidate every target, including content-equal/no-op changes, while this owner is held.
@@ -2372,6 +2374,8 @@ export async function gate(changes, opts) {
2372
2374
  const createdParents = new Map();
2373
2375
  for (let index = 0; index < effects.initial.length; index++) {
2374
2376
  const change = effects.initial[index].change;
2377
+ phase = "write";
2378
+ target = change.path;
2375
2379
  try {
2376
2380
  actions[index] = prepareFileWrite(change, actions[index], opts.t);
2377
2381
  checkpointJournal(handle, completed, actions);
@@ -2382,12 +2386,14 @@ export async function gate(changes, opts) {
2382
2386
  checkpointJournal(handle, completed, actions);
2383
2387
  }
2384
2388
  catch (cause) {
2385
- throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done));
2389
+ throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done), { cause });
2386
2390
  }
2387
2391
  }
2388
2392
  for (let index = 0; index < operationActions.length; index++) {
2389
2393
  const planned = operationActions[index];
2390
2394
  const actionIndex = effects.initial.length + index;
2395
+ phase = "write";
2396
+ target = planned.action.path;
2391
2397
  try {
2392
2398
  assertWrittenAliases(actions, completed, opts.t);
2393
2399
  planned.prepare?.((action) => {
@@ -2401,12 +2407,14 @@ export async function gate(changes, opts) {
2401
2407
  checkpointJournal(handle, completed, actions);
2402
2408
  }
2403
2409
  catch (cause) {
2404
- throw new ConfigError(opts.t.errors.couldNotWrite(planned.action.path, cause.message, done));
2410
+ throw new ConfigError(opts.t.errors.couldNotWrite(planned.action.path, cause.message, done), { cause });
2405
2411
  }
2406
2412
  }
2407
2413
  for (let index = 0; index < effects.final.length; index++) {
2408
2414
  const change = effects.final[index].change;
2409
2415
  const actionIndex = effects.initial.length + operationActions.length + index;
2416
+ phase = "write";
2417
+ target = change.path;
2410
2418
  try {
2411
2419
  assertWrittenAliases(actions, completed, opts.t);
2412
2420
  actions[actionIndex] = prepareFileWrite(change, actions[actionIndex], opts.t);
@@ -2418,12 +2426,28 @@ export async function gate(changes, opts) {
2418
2426
  checkpointJournal(handle, completed, actions);
2419
2427
  }
2420
2428
  catch (cause) {
2421
- throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done));
2429
+ throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done), { cause });
2422
2430
  }
2423
2431
  }
2432
+ phase = "verify";
2433
+ target = null;
2424
2434
  await opts.verify?.();
2435
+ for (const index of completed) {
2436
+ const action = actions[index];
2437
+ if (action.kind !== "entry-rebuild")
2438
+ continue;
2439
+ target = action.path;
2440
+ const { metadata } = validateEntryRebuildAction(action, true);
2441
+ if (!entrySnapshotMatches(action.path, action.after, metadata, opts.t)) {
2442
+ throw new ConfigError(opts.t.errors.staleTarget(action.path), { cause: { code: "VERIFY_MISMATCH", path: action.path } });
2443
+ }
2444
+ }
2445
+ target = null;
2425
2446
  assertWrittenAliases(actions, completed, opts.t);
2447
+ phase = "finalize";
2448
+ target = join(opts.paths.operations, `${handle.owner.operationId}.json`);
2426
2449
  finishJournal(handle, "complete");
2450
+ target = opts.paths.operationsLock;
2427
2451
  releaseOperation(handle);
2428
2452
  return { applied: true, lines, reason: "written", diff, operationId: handle.owner.operationId };
2429
2453
  }
@@ -2444,9 +2468,35 @@ export async function gate(changes, opts) {
2444
2468
  }
2445
2469
  if (opts.recoveryPresentation && journalStarted) {
2446
2470
  const operationId = handle.owner.operationId;
2471
+ const knownCodes = [
2472
+ "EACCES", "EPERM", "ENOSPC", "EROFS", "ENOENT", "EIO", "ESTALE", "EEXIST",
2473
+ "ENOTDIR", "EISDIR", "ENOTEMPTY", "EBUSY", "VERIFY_MISMATCH",
2474
+ ];
2475
+ // Only planned paths and owned bookkeeping may leave the recovery error boundary.
2476
+ const paths = [...new Set([
2477
+ ...[...changes, ...finalChanges].flatMap(change => [change.path, change.realPath]),
2478
+ ...actions.flatMap(action => [action.path, action.realPath]),
2479
+ opts.paths.operationsLock, join(opts.paths.operations, `${operationId}.json`),
2480
+ ])].sort((left, right) => right.length - left.length);
2481
+ let detail = cause;
2482
+ let code = "UNKNOWN";
2483
+ let reported = null;
2484
+ // shortcut: inspect four cause layers; extend if another recovery wrapper nests deeper.
2485
+ for (let depth = 0; depth < 4 && detail !== null && typeof detail === "object"; depth++) {
2486
+ const error = detail;
2487
+ if (code === "UNKNOWN" && typeof error.code === "string" && knownCodes.includes(error.code))
2488
+ code = error.code;
2489
+ if (reported === null && typeof error.path === "string" && isAbsolute(error.path)) {
2490
+ const candidate = error.path;
2491
+ reported = paths.find(path => path === candidate) ?? paths.find(path => lexicallyInside(candidate, path)) ?? null;
2492
+ }
2493
+ detail = error.cause;
2494
+ }
2495
+ const affected = reported ?? target;
2496
+ const path = affected === null ? null : humanText(affected);
2447
2497
  throw new ConfigError(typeof opts.recoveryPresentation === "object" && opts.recoveryPresentation.failureMessage !== undefined
2448
- ? opts.recoveryPresentation.failureMessage(operationId)
2449
- : `app rebuild did not complete; preserve operation ${operationId} for recovery`);
2498
+ ? opts.recoveryPresentation.failureMessage(operationId, phase, path, code)
2499
+ : opts.t.recovery.failure(operationId, phase, path, code));
2450
2500
  }
2451
2501
  if (cause instanceof ConfigError)
2452
2502
  throw cause;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonedial",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Set a coding agent's manner once and keep it across every project.",
5
5
  "license": "MIT",
6
6
  "type": "module",