Merge pull request 'fix: 調整指令可用時機(anima-off/anima-affinity)' (#23) from fix-command-availability into main

Reviewed-on: https://gitea.jsc.idv.tw/jiantw83/jsc-anima/pulls/23
This commit was merged in pull request #23.
This commit is contained in:
2026-10-15 09:37:47 +00:00
15 changed files with 1688 additions and 12 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-anima", "name": "jsc-anima",
"version": "0.0.3", "version": "0.0.4",
"description": "動漫助理:用喜愛角色的口吻互動,附單行狀態列", "description": "動漫助理:用喜愛角色的口吻互動,附單行狀態列",
"license": "MIT", "license": "MIT",
"userConfig": { "userConfig": {
+1 -1
View File
@@ -2,7 +2,7 @@
# jsc-anima # jsc-anima
目前版本:0.0.3 目前版本:0.0.4
動漫助理:用喜愛角色的口吻和你互動,並在輸入框上方顯示單行狀態列(角色、模型、目錄、分支、5h/7d 剩餘額度、ctx、系統資源)。 動漫助理:用喜愛角色的口吻和你互動,並在輸入框上方顯示單行狀態列(角色、模型、目錄、分支、5h/7d 剩餘額度、ctx、系統資源)。
+12 -10
View File
@@ -16,10 +16,9 @@ import { CACHE_MS, addEntry, commandName, commitMessage, pageName, parseCatalog,
import type { Entry } from './wiki' import type { Entry } from './wiki'
import { DEFAULT_TYPES, RUN_SYSTEM, TODO_COMMAND, TODO_KEY, TYPES_KEY, USAGE, dueNow, nextId, parseArgs as parseTodo, reminderText, renderList } from './todo' import { DEFAULT_TYPES, RUN_SYSTEM, TODO_COMMAND, TODO_KEY, TYPES_KEY, USAGE, dueNow, nextId, parseArgs as parseTodo, reminderText, renderList } from './todo'
import type { Todo } from './todo' import type { Todo } from './todo'
import { NEED_ACTIVE, TOOL_COMMAND, registerTools, runSkillTool } from './skilltool' import { NEED_ACTIVE, OFF_COMMAND, TOOL_COMMAND, registerTools, runSkillTool } from './skilltool'
const ACTIVE_KEY = 'active' const ACTIVE_KEY = 'active'
const OFF_COMMAND = 'anima-off'
const AFFINITY_COMMAND = 'anima-affinity' const AFFINITY_COMMAND = 'anima-affinity'
const RAW_LOG_KEY = 'rawlog' const RAW_LOG_KEY = 'rawlog'
const RAW_LOG_MAX = 200 const RAW_LOG_MAX = 200
@@ -312,8 +311,7 @@ export const register: Register = (on, options) => {
await $.command.register({ name: c.slug, description: `啟動《${c.work}》的${c.name}` }) await $.command.register({ name: c.slug, description: `啟動《${c.work}》的${c.name}` })
} }
await $.command.register({ name: EXTRACT_COMMAND, description: '從輕小說章節萃取作品與角色包,確認後建立 PR' }) await $.command.register({ name: EXTRACT_COMMAND, description: '從輕小說章節萃取作品與角色包,確認後建立 PR' })
await $.command.register({ name: AFFINITY_COMMAND, description: '重置(reset)或手動設定(0-100)目前角色的好感度' }) await $.command.register({ name: AFFINITY_COMMAND, description: '重置(reset)或手動設定(0-100)好感度;未啟動時需指定角色代號' })
await $.command.register({ name: OFF_COMMAND, description: '離開角色,回到一般的 Claude Code' })
const saved = await $.store.get(ACTIVE_KEY) const saved = await $.store.get(ACTIVE_KEY)
active = flag('persist', true) && typeof saved === 'string' && findCharacter(saved) ? saved : undefined active = flag('persist', true) && typeof saved === 'string' && findCharacter(saved) ? saved : undefined
if (active && !toolRegistered) { if (active && !toolRegistered) {
@@ -331,6 +329,7 @@ export const register: Register = (on, options) => {
if (!isEnabled) return next(e) if (!isEnabled) return next(e)
if (e.command === EXTRACT_COMMAND) return { text: await runExtract($, e.args) } if (e.command === EXTRACT_COMMAND) return { text: await runExtract($, e.args) }
if (e.command === OFF_COMMAND) { if (e.command === OFF_COMMAND) {
if (!findCharacter(active)) return { text: NEED_ACTIVE }
active = undefined active = undefined
await $.store.delete(ACTIVE_KEY) await $.store.delete(ACTIVE_KEY)
return { text: '已離開角色,回到一般模式。' } return { text: '已離開角色,回到一般模式。' }
@@ -343,18 +342,21 @@ export const register: Register = (on, options) => {
return { text: c ? await todoRun($, c.name, c.persona, String(e.args ?? '')) : NEED_ACTIVE } return { text: c ? await todoRun($, c.name, c.persona, String(e.args ?? '')) : NEED_ACTIVE }
} }
if (e.command === AFFINITY_COMMAND) { if (e.command === AFFINITY_COMMAND) {
const c = findCharacter(active) // 第一個字若是角色代號就指定該角色;否則用目前啟動的角色。
if (!c) return { text: '請先啟動角色。' } const parts = String(e.args ?? '').trim().split(/\s+/).filter(Boolean)
const arg = String(e.args ?? '').trim() const named = parts.length ? findCharacter(parts[0]) : undefined
const c = named ?? findCharacter(active)
if (!c) return { text: '未啟動角色時請指定角色代號:anima-affinity <角色代號> [reset|0-100]。' }
const arg = (named ? parts.slice(1) : parts).join(' ')
const cur = await loadAffinity($, c.slug) const cur = await loadAffinity($, c.slug)
if (arg === 'reset') { if (arg === 'reset') {
await $.store.set(affinityKey(c.slug), initialAffinity()) await $.store.set(affinityKey(c.slug), initialAffinity())
return { text: '好感度已重置為 0。' } return { text: `${c.name}的好感度已重置為 0。` }
} }
const n = Number(arg) const n = Number(arg)
if (arg === '' || !Number.isFinite(n) || n < 0 || n > 100) return { text: `目前好感度 ${cur.score}(${stageName(cur.score)})。用法:reset 或 0-100 的數字。` } if (arg === '' || !Number.isFinite(n) || n < 0 || n > 100) return { text: `${c.name}目前好感度 ${cur.score}(${stageName(cur.score)})。用法:[角色代號] reset 或 0-100 的數字。` }
await $.store.set(affinityKey(c.slug), { ...cur, score: Math.round(n) }) await $.store.set(affinityKey(c.slug), { ...cur, score: Math.round(n) })
return { text: `好感度已設為 ${Math.round(n)}(${stageName(n)})。` } return { text: `${c.name}的好感度已設為 ${Math.round(n)}(${stageName(n)})。` }
} }
const c = findCharacter(e.command) const c = findCharacter(e.command)
if (!c) { if (!c) {
+2
View File
@@ -7,10 +7,12 @@ import {
} from './skillgen' } from './skillgen'
export const TOOL_COMMAND = 'anima-skill' export const TOOL_COMMAND = 'anima-skill'
export const OFF_COMMAND = 'anima-off'
export const NEED_ACTIVE = '請先啟動角色(/jsc-anima:{作品代號}-{角色代號})再使用這個工具。' export const NEED_ACTIVE = '請先啟動角色(/jsc-anima:{作品代號}-{角色代號})再使用這個工具。'
export async function registerTools($: Engine): Promise<void> { export async function registerTools($: Engine): Promise<void> {
await $.command.register({ name: TOOL_COMMAND, description: '依描述產生助理技能並建立 PR:<技能名稱> <描述>' }) await $.command.register({ name: TOOL_COMMAND, description: '依描述產生助理技能並建立 PR:<技能名稱> <描述>' })
await $.command.register({ name: OFF_COMMAND, description: '離開角色,回到一般的 Claude Code' })
await $.command.register({ name: TODO_COMMAND, description: '待辦事項:add/update/list/type/run/done' }) await $.command.register({ name: TODO_COMMAND, description: '待辦事項:add/update/list/type/run/done' })
} }
+3
View File
@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by Vitest.
# For information about cache directory tags see https://bford.info/cachedir/
@@ -0,0 +1 @@
{"version":1,"projects":{"":{"files":{"hooks/anima.test.ts":{"failed":true,"duration":0,"lastRun":1792056694665},"hooks/extract.test.ts":{"failed":true,"duration":0,"lastRun":1792056694665},"hooks/persist.test.ts":{"failed":true,"duration":0,"lastRun":1792056694665},"hooks/routing.test.ts":{"failed":true,"duration":0,"lastRun":1792056694665},"hooks/skilltool.test.ts":{"failed":true,"duration":0,"lastRun":1792056694665},"hooks/todo.test.ts":{"failed":true,"duration":0,"lastRun":1792056694665},"hooks/wiki.test.ts":{"failed":true,"duration":0,"lastRun":1792056694665}}}}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by Vitest.
# For information about cache directory tags see https://bford.info/cachedir/
+3
View File
@@ -0,0 +1,3 @@
{
"lockfileHash": "e3b0c442"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long