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.
@@ -61,7 +61,6 @@ const messages = {
61
61
  `tonedial use ${was} を実行し、${tried} として保存してください。`,
62
62
  noLatin: (name) => `「${name}」: スラッグに使える英字か数字がありません`,
63
63
  nameTooLong: (given, cap) => `表示名:${given}文字 · 上限 ${cap}`,
64
- sparsity: (slug, count) => `${slug}: 変更軸 ${count}個 · 推奨3–7(5±2)。極端な値が少ないほど競合を抑えられます`,
65
64
  replaced: (slug) => `${slug}: 既存プリセットを置き換えました`,
66
65
  imported: (slug, replaced) => `${slug}: ${replaced ? "置き換えました" : "読み込みました"}`,
67
66
  unreadable: (path, reason) => `${path}\n読み取れません:${reason}`,
@@ -140,8 +139,6 @@ const messages = {
140
139
  addressNone: "未設定 · Enterで未設定のまま保持",
141
140
  age: (given, bands) => `persona.age=${given} · 選択肢:${bands.join(", ")}`,
142
141
  gender: (options) => `persona.gender · 選択肢:${options.join(", ")}`,
143
- noteOverCap: (given, cap) => `persona.note: ${given}文字 · 推奨上限 ${cap} を超えています`,
144
- noteNamesAxis: (axes) => `persona.note が ${axes.join(", ")} 軸と重複しています。軸には基準文があり、競合時は軸が優先されます。`,
145
142
  callsYou: (term) => `ユーザー呼称 ${term}`,
146
143
  callsItself: (term) => `自称 ${term}`,
147
144
  noteChars: (count) => `メモ ${count}文字`,
@@ -258,14 +255,14 @@ const messages = {
258
255
  inspectScope: "引き続き使う場合は、この範囲を確認してください:",
259
256
  disconnectScope: "不要なら、この範囲の全エージェントの接続を解除してください:",
260
257
  scope: "確認範囲:プロジェクト",
261
- toLookAt: (count) => `${count}件が要確認 · --fix で可能な修復を実施`,
258
+ toLookAt: (count) => `確認が必要:${count}件`,
262
259
  needsAPerson: "手動確認が必要 · 上の詳細を参照",
263
260
  toneMatches: "config.json と一致",
264
261
  toneNotWritten: "未書き込み",
265
262
  toneDisagrees: "config.json と不一致 · 直接編集か書き込み中断",
263
+ seedsHeading: "同梱ファイル",
266
264
  seedsOk: (version) => `バージョン ${version}`,
267
265
  seedsNotRun: "同梱ファイルなし · ここでは init 未実行",
268
- seedsStale: (labels) => `更新が必要:${labels.length}件 · ${labels.join(", ")}`,
269
266
  seedsAttention: "同梱ファイルの確認が必要",
270
267
  recoveryPending: "実行中または復旧待ちの操作があります。変更はしていません。tonedial doctor --json でIDを確認してください。記録済みの復元は doctor --fix --rollback ID、停止済みの有効な操作からの再構築は doctor --fix --from-operation ID を使います。実行中または所有者不明の操作は保護されます。",
271
268
  contextCurrent: "AGENTS.md と TONE.md の構成は最新です。",
@@ -301,13 +298,12 @@ const messages = {
301
298
  rollbackAction: { applied: "復元", "already-reverted": "復元済み", changed: "変更あり · 保持", unsupported: "未対応 · 保持" },
302
299
  rollbackQuestion: "選択した操作を復元しますか?",
303
300
  seedReason: {
304
- missing: "不足 · 必要なエージェントを選んで init を実行",
305
- stale: "古い同梱ファイル · init による更新を確認",
306
- modified: "ローカル変更あり · 置換前に変更を確認",
307
- foreign: "ToneDialの所有ではありません。保持し、専用の状態ディレクトリを選んでください。",
308
- unreadable: "安全に読み取れません。パスと権限を確認してください。",
301
+ missing: "同梱ファイルなし",
302
+ stale: "同梱ファイルの更新あり",
303
+ modified: "ローカルで変更済み · 置換前に確認",
304
+ foreign: "ToneDialの管理対象外 · 保持",
305
+ unreadable: "安全に読み取れません · パスと権限を確認",
309
306
  },
310
- seedsBehind: (had, has) => `ホームの同梱版 ${had} · パッケージ版 ${has}。npm更新では既存のホームファイルは更新されません`,
311
307
  skillLinked: "同梱コピーへのリンクあり",
312
308
  skillCopied: "コピー済み · この環境はリンク非対応",
313
309
  skillForeign: (path) => `${path}: ToneDialがインストールしたものではないため置き換えません`,
@@ -324,9 +320,7 @@ const messages = {
324
320
  },
325
321
  hookSettingScope: "フック状態は確認対象の設定ファイルのみから取得します。ホスト側の上書きとセッションへの配信は未検証です。",
326
322
  hookGroupHeading: "エージェントのフック",
327
- hookGroupUnknown: (tools) => `フックの有効状態を確認できません:${tools.join(", ")}。`,
328
- hookGroupMissing: "この範囲には、これらのエージェント用の管理対象フックが登録されていません。",
329
- hookGroupAction: "ToneDialを接続するには、この範囲で init を実行し、これらのエージェントを選んでください。",
323
+ hookGroupUnknown: (tools) => `未接続:${tools.join(", ")}`,
330
324
  hookNotConnected: "確認する管理対象フックがありません。接続するには同じ範囲でこのエージェントを選んで init を実行してください。",
331
325
  hookNeedsInspection: "アプリで有効状態を確認する前に、報告されたフック設定かプログラムの問題を解決してください。",
332
326
  hookGuidance: {
@@ -370,20 +364,32 @@ const messages = {
370
364
  "npm prefix -g でグローバルパスを確認し、\nその bin ディレクトリ(macOS/Linux)をPATHに追加してください。",
371
365
  },
372
366
  notice: {
373
- modelExpectation: "モデルによっては期待した口調にならない場合があります。",
374
367
  riskIntro: "娯楽用であり、実務での利用は推奨しません。注意が必要な口調では、虚偽・省略・誇張によって判断や作業報告が歪む可能性があります。! は参考の注意表示で、測定した確率ではありません。",
375
368
  riskAxes: (axes) => `! ${axes.join(", ")} · ${[axes.some(axis => axis === "profanity" || axis === "critique") ? "荒い表現" : null, axes.some(axis => axis === "flattery" || axis === "conviction" || axis === "bravado") ? "偏った判断・誇張した報告" : null, axes.some(axis => axis === "compliance" || axis === "refusal") ? "性急な実行・拒否" : null].filter(Boolean).join("; ")}`,
376
- personaUnanchored: "ペルソナには固定の基準文がなく、軸が優先されます。説明文は内容にも影響する場合があります。" +
377
- "役柄は資格の証明にも権限の付与にもなりません。",
378
369
  roleplay: "playfulness が高いと「畏れながら」「この関数、帳簿から消しましょうか」など役柄の言葉や隠語を頻繁に、大げさに使うよう求めます。無礼・粗暴に聞こえる場合があり、ToneDialはこうした表現を除外しません。",
379
370
  risk: "娯楽用です。実務利用は推奨しません",
380
371
  },
381
372
  update: {
382
373
  running: (version) => `tonedial ${version}`,
383
- installedBy: (shape) => `インストール方式 ${shape}`,
384
- toUpdate: "更新方法:",
385
- thenRefresh: "その後、保存済み設定を保持してアプリのファイルを確認・再構築してください:",
386
- neverInitialised: "init 未実行 · 更新するホームファイルなし",
374
+ toUpdate: "パッケージの更新:",
375
+ thenRefresh: "アプリのファイルを更新:",
376
+ setupNeeded: "ToneDialの初期設定:",
377
+ refreshFiles: (count) => `更新可能な同梱ファイル:${count}件`,
378
+ reviewState: "問題のあるアプリファイルを確認:",
379
+ npmQuestion: " npmで更新しますか?",
380
+ npmComplete: (version) => `npm更新完了 · インストール済み ${version}`,
381
+ npmMissing: "PATHにnpmがありません。このToneDialをインストールしたNode/npm環境を使用してください。",
382
+ npmScopeUnknown: "npmのグローバルインストール先を確認できません。npm prefix -g と npm root -g を確認して再試行してください。",
383
+ npmScopeMismatch: "このToneDialは選択したnpmが管理するグローバルパッケージではありません。対応するNode/npm環境を使用してください。更新は実行していません。",
384
+ npmFailed: "npmの更新に失敗しました。権限とレジストリ設定を確認し、表示されたコマンドを実行してnpmの診断を確認してください。",
385
+ versionUnknown: "インストールされたToneDialのバージョンを確認できません。パッケージの状態を確認して再試行してください。",
386
+ stateIssue: {
387
+ missing: "保存済み設定がありません。",
388
+ unsafe: "保存済み設定を安全に読み取れません。リンクとファイル形式を確認してください。",
389
+ changed: "確認中に保存済み設定が変更されました。変更が終わってから再試行してください。",
390
+ invalid: "保存済み設定が無効です。",
391
+ unreadable: "アプリの状態を読み取れません。パスとアクセス権を確認してください。",
392
+ },
387
393
  fetching: (registry) => `確認中 · ${registry}`,
388
394
  found: (version, bytes) => `${version}、${bytes.toLocaleString("ja")}バイト`,
389
395
  already: (version) => `${version} はインストール済みです`,
@@ -391,11 +397,10 @@ const messages = {
391
397
  question: (version) => ` このインストールを ${version} に置き換えますか?`,
392
398
  replaced: (version) => `更新済み · ${version}`,
393
399
  declined: "キャンセル · 既存インストールを保持",
394
- onlyCurl: (shape) => `${shape} 経由のインストール · その方式で更新してください`,
395
400
  noTar: "更新には tar が必要ですがPATHにありません",
396
401
  notAPackage: "ダウンロードしたものはToneDialパッケージではありません",
397
402
  linkInPackage: "リリースにシンボリックリンクがありますが、公開ファイルには不要です",
398
- needsApproval: "インストールの置換には承認が必要ですがターミナルがありません。--auto で再実行してください。",
403
+ needsApproval: "更新の承認が必要です。更新は実行していません。承認するには tonedial update --auto を実行してください。",
399
404
  integrityMismatch: "ダウンロードのハッシュがレジストリと一致しません",
400
405
  notOurs: (dir) => `${dir}: ToneDialが作成したものではありません。手動で削除してください`,
401
406
  swapFailed: (reason) => `インストール停止:${reason}。再試行前に復旧の詳細を確認してください。`,
@@ -459,14 +464,8 @@ const messages = {
459
464
  },
460
465
  combos: {
461
466
  contradiction: (rule) => `${rule} は自己矛盾しています。`,
462
- reads: (rule) => `${rule}:`,
463
467
  refusalCompliance: "一方の軸は拒否の明示、他方は無言の即時実行を求めます。どちらかを下げてください。",
464
- formalSelfRef: "儀礼的な表現と親しい自称が対照的になります。",
465
- casualUserRef: "親しい表現と敬称が対照的になります。",
466
- teensFormal: "10代のペルソナに儀礼的な表現を合わせると芝居がかって聞こえる場合があります。",
467
- seniorJuniorSelfRef: "設定年齢と自称が異なる立場を示唆しています。",
468
468
  nothingBlocked: "ブロックはしていません。",
469
- addressUnlikely: "短い回答や感情の薄い回答では呼称が省かれる場合があります。役柄が強いほど頻出することもあります。",
470
469
  },
471
470
  errors: {
472
471
  notACommand: (name, known, nearest) => `${name} はコマンドではありません。${nearest ? `${nearest} のことですか?` : ""} ` +
@@ -628,7 +627,7 @@ export const ja = {
628
627
  "persona.gender": "モデルに連想させるジェンダー",
629
628
  "persona.self_ref": "エージェントの自称",
630
629
  "persona.user_ref": "エージェントが使うユーザーの呼称",
631
- "persona.note": "軸で表せない役割 · 200文字超で警告",
630
+ "persona.note": "軸で表せない役割や話し方",
632
631
  "persona.user": "ユーザーの背景 · 上限200文字",
633
632
  "persona.agent": "架空の役割 · 上限200文字 · 資格は未検証で、能力や権限は付与しません",
634
633
  },
@@ -61,7 +61,6 @@ const messages = {
61
61
  `请运行 tonedial use ${was},然后另存为 ${tried}。`,
62
62
  noLatin: (name) => `“${name}”:没有可用于标识名的拉丁字母或数字`,
63
63
  nameTooLong: (given, cap) => `显示名称:${given} 字符 · 上限 ${cap}`,
64
- sparsity: (slug, count) => `${slug}:${count} 个变更语气轴 · 建议 3–7(5±2)。极端值越少,冲突越少`,
65
64
  replaced: (slug) => `${slug}:已替换现有预设`,
66
65
  imported: (slug, replaced) => `${slug}:${replaced ? "已替换" : "已导入"}`,
67
66
  unreadable: (path, reason) => `${path}\n无法读取:${reason}`,
@@ -140,8 +139,6 @@ const messages = {
140
139
  addressNone: "当前未设置 · Enter 保持未设置",
141
140
  age: (given, bands) => `persona.age=${given} · 可选:${bands.join(", ")}`,
142
141
  gender: (options) => `persona.gender · 可选:${options.join(", ")}`,
143
- noteOverCap: (given, cap) => `persona.note:${given} 字符 · 超过建议上限 ${cap}`,
144
- noteNamesAxis: (axes) => `persona.note 与 ${axes.join(", ")} 语气轴重叠。语气轴有固定基准,冲突时优先。`,
145
142
  callsYou: (term) => `用户称呼 ${term}`,
146
143
  callsItself: (term) => `自称 ${term}`,
147
144
  noteChars: (count) => `备注 ${count} 字符`,
@@ -258,14 +255,14 @@ const messages = {
258
255
  inspectScope: "若要继续使用这些连接,请检查此范围:",
259
256
  disconnectScope: "若不再需要,请断开此范围内的所有代理:",
260
257
  scope: "检查范围:项目",
261
- toLookAt: (count) => `${count} 项需要处理 · --fix 修复可处理的问题`,
258
+ toLookAt: (count) => `${count} 项需要检查`,
262
259
  needsAPerson: "需要人工检查 · 参见上方详情",
263
260
  toneMatches: "与 config.json 一致",
264
261
  toneNotWritten: "未写入",
265
262
  toneDisagrees: "与 config.json 不同 · 可能被直接编辑或写入中断",
263
+ seedsHeading: "内置文件",
266
264
  seedsOk: (version) => `版本 ${version}`,
267
265
  seedsNotRun: "无内置文件 · 尚未在此运行 init",
268
- seedsStale: (labels) => `${labels.length} 项需要刷新:${labels.join(", ")}`,
269
266
  seedsAttention: "内置文件需要处理",
270
267
  recoveryPending: "有操作正在运行或需要恢复,未作任何修改。请用 tonedial doctor --json 查看 ID。已有回滚记录用 doctor --fix --rollback ID;已停止的有效操作可用 doctor --fix --from-operation ID 重建应用。运行中或所有者不明确的操作仍受保护。",
271
268
  contextCurrent: "AGENTS.md 与 TONE.md 布局已是最新。",
@@ -301,13 +298,12 @@ const messages = {
301
298
  rollbackAction: { applied: "恢复", "already-reverted": "已恢复", changed: "已更改 · 保留", unsupported: "不支持 · 保留" },
302
299
  rollbackQuestion: "恢复所选操作吗?",
303
300
  seedReason: {
304
- missing: "缺失 · 请选择所需代理并运行 init",
305
- stale: "旧版内置文件 · 请检查 init 刷新计划",
306
- modified: "本地已修改 · 替换前请检查变更",
307
- foreign: "不属于 ToneDial。请保留文件并选择专用状态目录。",
308
- unreadable: "无法安全读取,请检查路径及权限。",
301
+ missing: "内置文件缺失",
302
+ stale: "内置文件有更新",
303
+ modified: "本地已修改 · 替换前需检查",
304
+ foreign: "不由 ToneDial 管理 · 已保留",
305
+ unreadable: "无法安全读取 · 请检查路径和权限",
309
306
  },
310
- seedsBehind: (had, has) => `主目录内置文件版本 ${had} · 包版本 ${has}。npm 更新不会刷新主目录中的现有文件`,
311
307
  skillLinked: "已链接到内置副本",
312
308
  skillCopied: "已复制 · 平台不支持链接",
313
309
  skillForeign: (path) => `${path}:不是由 ToneDial 安装,因此不会替换`,
@@ -324,9 +320,7 @@ const messages = {
324
320
  },
325
321
  hookSettingScope: "钩子状态仅来自所检查的设置文件;主机覆盖项和会话注入尚未验证。",
326
322
  hookGroupHeading: "代理钩子",
327
- hookGroupUnknown: (tools) => `无法确认以下工具的钩子启用状态:${tools.join(", ")}。`,
328
- hookGroupMissing: "此范围内未为这些代理注册受管理的 ToneDial 钩子。",
329
- hookGroupAction: "要连接 ToneDial,请在此范围运行 init 并选择这些代理。",
323
+ hookGroupUnknown: (tools) => `未连接:${tools.join(", ")}`,
330
324
  hookNotConnected: "没有可检查的受管理钩子。要连接,请在同一范围运行 init 并选择此代理。",
331
325
  hookNeedsInspection: "在应用中检查启用状态前,请先解决报告的钩子设置或程序问题。",
332
326
  hookGuidance: {
@@ -370,20 +364,32 @@ const messages = {
370
364
  "运行 npm prefix -g 查找全局前缀,\n再将其 bin 目录(macOS/Linux)加入 PATH。",
371
365
  },
372
366
  notice: {
373
- modelExpectation: "模型可能无法呈现预期的语气。",
374
367
  riskIntro: "本工具用于娱乐,不建议用于生产工作。风险语气可能通过虚假、遗漏或夸张歪曲判断和工作报告。! 是风险提示,不是测得的概率。",
375
368
  riskAxes: (axes) => `! ${axes.join(", ")} · ${[axes.some(axis => axis === "profanity" || axis === "critique") ? "粗俗表达" : null, axes.some(axis => axis === "flattery" || axis === "conviction" || axis === "bravado") ? "偏颇判断或夸张报告" : null, axes.some(axis => axis === "compliance" || axis === "refusal") ? "仓促行动或拒绝" : null].filter(Boolean).join("; ")}`,
376
- personaUnanchored: "人设没有固定行为基准,语气轴优先。描述文字也可能影响内容。" +
377
- "角色扮演不验证资历,也不授予权限。",
378
369
  roleplay: "playfulness 较高时,会要求更频繁、夸张地使用“臣斗胆进言”“把这函数从账上划掉?”等角色用语和黑话。这些话可能显得无礼或粗暴,ToneDial 不会过滤。",
379
370
  risk: "仅供娱乐,不建议用于生产工作",
380
371
  },
381
372
  update: {
382
373
  running: (version) => `tonedial ${version}`,
383
- installedBy: (shape) => `安装方式 ${shape}`,
384
- toUpdate: "更新方法:",
385
- thenRefresh: "然后保留已保存的设置,检查并重建应用文件:",
386
- neverInitialised: "尚未运行 init · 无主目录文件需要刷新",
374
+ toUpdate: "更新软件包:",
375
+ thenRefresh: "更新应用文件:",
376
+ setupNeeded: "初始化 ToneDial:",
377
+ refreshFiles: (count) => `可更新的内置文件:${count} 个`,
378
+ reviewState: "检查有问题的应用文件:",
379
+ npmQuestion: " 使用 npm 更新吗?",
380
+ npmComplete: (version) => `npm 更新完成 · 已安装 ${version}`,
381
+ npmMissing: "PATH 中未找到 npm。请使用安装此 ToneDial 的 Node/npm 环境。",
382
+ npmScopeUnknown: "无法确认 npm 的全局安装目录。请检查 npm prefix -g 和 npm root -g 后重试。",
383
+ npmScopeMismatch: "当前 ToneDial 不是所选 npm 管理的全局软件包。请使用对应的 Node/npm 环境。未执行更新。",
384
+ npmFailed: "npm 更新失败。请检查权限和镜像设置,并直接运行显示的命令查看 npm 的诊断信息。",
385
+ versionUnknown: "无法确认安装的 ToneDial 版本。请检查软件包的安装状态后重试。",
386
+ stateIssue: {
387
+ missing: "保存的设置缺失。",
388
+ unsafe: "无法安全读取保存的设置。请检查链接和文件类型。",
389
+ changed: "检查期间保存的设置已更改。请等待更改完成后重试。",
390
+ invalid: "保存的设置无效。",
391
+ unreadable: "无法读取应用状态。请检查路径和权限。",
392
+ },
387
393
  fetching: (registry) => `正在检查 · ${registry}`,
388
394
  found: (version, bytes) => `${version},${bytes.toLocaleString("zh")} 字节`,
389
395
  already: (version) => `${version} 已安装`,
@@ -391,11 +397,10 @@ const messages = {
391
397
  question: (version) => ` 将此安装替换为 ${version} 吗?`,
392
398
  replaced: (version) => `已更新 · ${version}`,
393
399
  declined: "已取消 · 保留现有安装",
394
- onlyCurl: (shape) => `通过 ${shape} 安装 · 请使用对应方式更新`,
395
400
  noTar: "更新需要 tar,但 PATH 中未找到",
396
401
  notAPackage: "下载内容不是 ToneDial 包",
397
402
  linkInPackage: "发行包包含符号链接,但发布文件不应依赖链接",
398
- needsApproval: "替换安装需要批准,但当前没有终端。请使用 --auto 重新运行。",
403
+ needsApproval: "更新需要批准,尚未执行。请运行 tonedial update --auto 批准更新。",
399
404
  integrityMismatch: "下载哈希与仓库记录不一致",
400
405
  notOurs: (dir) => `${dir}:不是由 ToneDial 创建,请自行移除`,
401
406
  swapFailed: (reason) => `安装已停止:${reason}。重试前请检查恢复详情。`,
@@ -459,14 +464,8 @@ const messages = {
459
464
  },
460
465
  combos: {
461
466
  contradiction: (rule) => `${rule} 自相矛盾。`,
462
- reads: (rule) => `${rule}:`,
463
467
  refusalCompliance: "一个语气轴要求明确罢工,另一个要求不加评论立即执行。请降低其中一项。",
464
- formalSelfRef: "庄重措辞与亲近的自称形成反差。",
465
- casualUserRef: "亲近措辞与敬称形成反差。",
466
- teensFormal: "青少年人设搭配庄重措辞,可能显得戏剧化。",
467
- seniorJuniorSelfRef: "所设年龄和自称暗示不同地位。",
468
468
  nothingBlocked: "没有阻止任何设置。",
469
- addressUnlikely: "简短或感情淡薄的回答可能省略称呼;角色扮演较强时称呼可能更频繁。",
470
469
  },
471
470
  errors: {
472
471
  notACommand: (name, known, nearest) => `${name} 不是命令。${nearest ? `是否想输入 ${nearest}?` : ""} ` +
@@ -628,7 +627,7 @@ export const zh = {
628
627
  "persona.gender": "模型可能联想的性别",
629
628
  "persona.self_ref": "代理自称",
630
629
  "persona.user_ref": "代理对用户的称呼",
631
- "persona.note": "语气轴无法表达的角色 · 超过 200 字符会警告",
630
+ "persona.note": "语气轴无法表达的角色或表达方式",
632
631
  "persona.user": "用户背景 · 最多 200 字符",
633
632
  "persona.agent": "虚构代理角色 · 最多 200 字符 · 不验证资历,不授予能力或权限",
634
633
  },
@@ -11,7 +11,7 @@ import { captureRawEntry, caseRenameMatchesCurrent, isNoop, planChange, planEntr
11
11
  import { BUNDLED, builtinPresetDir, LEGACY_BUILTIN_SLUGS, RETIRED_PRESETS } from "./presets.js";
12
12
  import { SKILL_MD, SKILL_NAME, SKILL_TRAITS_MD } from "./skill.js";
13
13
  // Bump for changed generated content; freshness also checks each managed seed's bytes/provenance.
14
- export const SEED_VERSION = 16;
14
+ export const SEED_VERSION = 17;
15
15
  /** The state-root operating boundaries. The compiler owns the current tone separately. */
16
16
  export const GUIDE_MD = `${GUIDE_FORMAT_MARKER}
17
17
  ToneDial changes manner only; it never expands task scope or permissions.
@@ -73,10 +73,10 @@ export function seedFiles(paths, selection) {
73
73
  };
74
74
  const native = [
75
75
  reader,
76
- { id: "skill/body", version: 16, path: join(paths.skill, SKILL_NAME, "SKILL.md"), label: `skill/${SKILL_NAME}/SKILL.md`, contents: SKILL_MD },
76
+ { id: "skill/body", version: 17, path: join(paths.skill, SKILL_NAME, "SKILL.md"), label: `skill/${SKILL_NAME}/SKILL.md`, contents: SKILL_MD },
77
77
  {
78
78
  id: "skill/traits",
79
- version: 8,
79
+ version: 9,
80
80
  path: join(paths.skill, SKILL_NAME, "references", "traits.md"),
81
81
  label: `skill/${SKILL_NAME}/references/traits.md`,
82
82
  contents: SKILL_TRAITS_MD,
@@ -3,10 +3,12 @@ import { spawnSync } from "node:child_process";
3
3
  import { createHash } from "node:crypto";
4
4
  import { 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 { 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,55 @@ 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" ? raw.version : null;
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
+ try {
51
+ const own = fileURLToPath(new URL("../../", import.meta.url));
52
+ const current = fileIdentity(own);
53
+ const target = fileIdentity(root);
54
+ const version = installedVersion({ lib: root });
55
+ if (lstatSync(root, { throwIfNoEntry: false })?.isSymbolicLink() || current === null || target === null || current.dev !== target.dev || current.ino !== target.ino || version === null) {
56
+ throw new ConfigError(t.update.npmScopeMismatch);
57
+ }
58
+ return { entry, prefix, root, version };
59
+ }
60
+ catch {
61
+ throw new ConfigError(t.update.npmScopeMismatch);
62
+ }
63
+ }
64
+ /** npm owns replacement; captured output cannot corrupt JSON or disclose npm configuration. */
65
+ export function updateNpmInstall(install, env, t) {
66
+ const current = inspectNpmInstall(env, t);
67
+ if (current.entry !== install.entry || current.prefix !== install.prefix || current.root !== install.root || current.version !== install.version)
68
+ throw new ConfigError(t.update.npmScopeMismatch);
69
+ 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"] });
70
+ if (result.status !== 0)
71
+ throw new ConfigError(t.update.npmFailed);
72
+ const version = installedVersion({ lib: install.root });
73
+ if (version === null)
74
+ throw new ConfigError(t.update.versionUnknown);
75
+ return version;
76
+ }
32
77
  /** Whether the one external tool this needs is here. Checked before anything is downloaded. */
33
78
  export function haveTar() {
34
79
  return spawnSync("tar", ["--version"], { stdio: "ignore" }).status === 0;
@@ -86,7 +86,7 @@ Persona, address, safeword, and \`speech_level\` are separate from the axes.
86
86
  \`playfulness\` increases the frequency and exaggeration of the chosen role's expressions:
87
87
  literal wording at 1, one restrained touch at 2, two recognizable touches at 3,
88
88
  frequent theatrical idioms at 4, and a character voice throughout at 5.
89
- Role slang and figurative language are not filtered; the CLI warns about rude or violent idioms.
89
+ Role slang and figurative language are not filtered.
90
90
  \`profanity\` still grades actual swearing; \`bravado\` grades claims about effort.
91
91
  Change one only when the user explicitly asks for that setting; preserve every other setting.
92
92
  Use the user's chosen text and values, not the illustrative values below.
@@ -121,7 +121,7 @@ Agent experience and specialty are roleplay, not verified credentials or tool ca
121
121
  Both descriptions lose to axes and task/permission rules, and may influence content.
122
122
  A persona note expresses manner or role, loses to axes, and may also influence
123
123
  content; it is not a way to override task or permission rules.
124
- The CLI warns above 200 Unicode code points instead of rejecting the note; report its warnings.
124
+ Persona notes have no length limit.
125
125
 
126
126
  ToneDial has no guard toggle or automatic emergency register. All axis levels 1–5 are choices
127
127
  the user can request. Report advisory \`riskAxes\` and combination notices without lowering
@@ -369,7 +369,6 @@ depends on the host and model.
369
369
  - Functional status emoji can accompany low \`warmth\`; they are structure, not affection.
370
370
  - Required task formats take precedence: a code or JSON result must not become decorative prose.
371
371
  - \`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
372
  - \`warmth\` ≥ 4 with \`profanity\` ≥ 4, and \`compliance\` ≤ 2 with \`conviction\` ≤ 2, each get a
374
373
  composite sentence in the artifact rather than being left to collide.
375
374
  - 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.
@@ -393,7 +392,7 @@ them, and \`export\`/\`import\` transfer them. Older axis-only presets keep the
393
392
  The seven bundled profiles supply localized role and address defaults; custom text remains literal.
394
393
  Playfulness grades role idiom frequency from literal wording at 1 to theatrical voice throughout at 5.
395
394
  Role slang and metaphors are not filtered or suppressed by low profanity or bravado.
396
- Profanity grades actual swearing; bravado grades effort claims. The CLI carries advisory examples.
395
+ Profanity grades actual swearing; bravado grades effort claims.
397
396
  `;
398
397
  /**
399
398
  * The Claude Code `/tone` command, installed only by `init --commands`.
@@ -403,7 +402,7 @@ Profanity grades actual swearing; bravado grades effort claims. The CLI carries
403
402
  * command name stays `/tone` even though the tool is `tonedial`: it is what a person types
404
403
  * mid-sentence, and the two extra characters are paid every time.
405
404
  */
406
- export const TONE_COMMAND_VERSION = 8;
405
+ export const TONE_COMMAND_VERSION = 9;
407
406
  export const TONE_COMMAND_MD = `---
408
407
  description: Check, change, or reload this agent's tone, preset, or ToneDial settings
409
408
  ---
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 [...(risks.length ? [t.notice.riskAxes(risks)] : []), ...(axes.playfulness >= 4 ? [t.notice.roleplay] : [])];
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
- /** Render combination warnings with the same layer distinction in every command. */
67
+ /** Contradictions stay advisory and never block the selected values. */
68
68
  export function combination(w, t) {
69
- const sentence = t.combos[w.id];
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonedial",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Set a coding agent's manner once and keep it across every project.",
5
5
  "license": "MIT",
6
6
  "type": "module",