@shigureni/minion 0.2.0 → 0.4.0

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/cli/src/index.ts CHANGED
@@ -20,10 +20,10 @@ Usage:
20
20
  minion <command> -h Show help for a command
21
21
 
22
22
  Commands:
23
- start 起動する (デフォルト)
23
+ start 起動する (すでに起動中なら何もしない)
24
24
  dev 停止してデバッグビルドで起動し直す
25
25
  kill 停止する
26
- reboot 停止してリリースビルドで起動し直す
26
+ reboot 停止してリリースビルドで起動し直す (デフォルト)
27
27
  status 起動状況を表示する
28
28
  dex 実績とミニオン図鑑を表示する
29
29
  config list 設定値を一覧表示する
@@ -35,7 +35,8 @@ Global flags:
35
35
  -v, --version Show version`
36
36
 
37
37
  const argv = process.argv.slice(2)
38
- const args = argv.length === 0 ? ["start"] : argv
38
+ // 引数なしの `minion` は「古いプロセスを止めて起動し直す」= reboot に落とす。
39
+ const args = argv.length === 0 ? ["reboot"] : argv
39
40
 
40
41
  const request = toRequest(args)
41
42
 
@@ -13,10 +13,12 @@ export const help = `Usage: minion dev
13
13
  export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
14
14
  const killResult = c.env.minion.app.kill()
15
15
  if (killResult instanceof Error) return c.text(killResult.message, 500)
16
+ // 何も止めていないときは kill の行を出さず、起動結果だけを表示する。
17
+ const killLine = killResult.kind === "killed" ? [formatKillResult(killResult)] : []
16
18
  const startResult = await c.env.minion.app.start({ debug: true })
17
19
  if (startResult instanceof Error) {
18
- return c.text([formatKillResult(killResult), startResult.message].join("\n"), 500)
20
+ return c.text([...killLine, startResult.message].join("\n"), 500)
19
21
  }
20
- const text = [formatKillResult(killResult), formatStartResult(startResult)].join("\n")
22
+ const text = [...killLine, formatStartResult(startResult)].join("\n")
21
23
  return c.text(text, isStartError(startResult) ? 500 : 200)
22
24
  })
@@ -13,10 +13,12 @@ export const help = `Usage: minion reboot
13
13
  export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
14
14
  const killResult = c.env.minion.app.kill()
15
15
  if (killResult instanceof Error) return c.text(killResult.message, 500)
16
+ // 何も止めていないときは kill の行を出さず、起動結果だけを表示する。
17
+ const killLine = killResult.kind === "killed" ? [formatKillResult(killResult)] : []
16
18
  const startResult = await c.env.minion.app.start()
17
19
  if (startResult instanceof Error) {
18
- return c.text([formatKillResult(killResult), startResult.message].join("\n"), 500)
20
+ return c.text([...killLine, startResult.message].join("\n"), 500)
19
21
  }
20
- const text = [formatKillResult(killResult), formatStartResult(startResult)].join("\n")
22
+ const text = [...killLine, formatStartResult(startResult)].join("\n")
21
23
  return c.text(text, isStartError(startResult) ? 500 : 200)
22
24
  })
@@ -21,6 +21,20 @@ export const ACTIONS: { durationMs: [number, number]; weight: number }[] = [
21
21
  { weight: 2, durationMs: [2000, 3000] }, // 12 食べる2
22
22
  ]
23
23
 
24
+ // 稼働していないセッション用の「静かな」行動だけの部分集合(clipIndexはACTIONS/Swiftの
25
+ // 並びを指す)。移動・ジャンプ・食事は含めず、待機と座り姿勢をゆっくり切り替える。
26
+ export const SLEEPING_ACTIONS: {
27
+ clipIndex: number
28
+ durationMs: [number, number]
29
+ weight: number
30
+ }[] = [
31
+ { clipIndex: 0, weight: 40, durationMs: [6000, 12000] }, // 待機
32
+ { clipIndex: 7, weight: 15, durationMs: [6000, 12000] }, // 座る1
33
+ { clipIndex: 8, weight: 15, durationMs: [6000, 12000] }, // 座る2
34
+ { clipIndex: 9, weight: 15, durationMs: [6000, 12000] }, // 座る3
35
+ { clipIndex: 10, weight: 15, durationMs: [6000, 12000] }, // 座る4
36
+ ]
37
+
24
38
  export type PetAction = { clipIndex: number; durationMs: number }
25
39
 
26
40
  export function pickAction(random: MinionRandomSource): PetAction {
@@ -43,6 +57,26 @@ export function pickAction(random: MinionRandomSource): PetAction {
43
57
  return { clipIndex: chosen, durationMs: min + random.next() * (max - min) }
44
58
  }
45
59
 
60
+ export function pickSleepingAction(random: MinionRandomSource): PetAction {
61
+ const roll = random.next() * 100
62
+ let cumulative = 0
63
+ let chosen = SLEEPING_ACTIONS[0]
64
+
65
+ for (const action of SLEEPING_ACTIONS) {
66
+ cumulative += action.weight
67
+ if (roll < cumulative) {
68
+ chosen = action
69
+ break
70
+ }
71
+ }
72
+
73
+ const [min, max] = chosen?.durationMs ?? [0, 0]
74
+ return {
75
+ clipIndex: chosen?.clipIndex ?? IDLE_CLIP,
76
+ durationMs: min + random.next() * (max - min),
77
+ }
78
+ }
79
+
46
80
  export type PetBehavior = {
47
81
  running: boolean
48
82
  name: string
@@ -85,9 +119,7 @@ export class PetBehaviorEngine {
85
119
  const existing = this.behaviors.get(id)
86
120
 
87
121
  if (!existing) {
88
- const action = info.running
89
- ? pickAction(this.random)
90
- : { clipIndex: IDLE_CLIP, durationMs: 0 }
122
+ const action = info.running ? pickAction(this.random) : pickSleepingAction(this.random)
91
123
  this.behaviors.set(id, {
92
124
  running: info.running,
93
125
  name: info.name,
@@ -105,20 +137,16 @@ export class PetBehaviorEngine {
105
137
 
106
138
  if (existing.running !== info.running) {
107
139
  existing.running = info.running
108
- if (info.running) {
109
- const action = pickAction(this.random)
110
- existing.clipIndex = action.clipIndex
111
- existing.actionEndsAt = now + action.durationMs
112
- } else {
113
- // 稼働が止まったら、直前のアクションの途中コマで固まらないよう待機姿勢に戻す。
114
- existing.clipIndex = IDLE_CLIP
115
- }
116
- dirty = true
117
- } else if (info.running && now >= existing.actionEndsAt) {
118
- const action = pickAction(this.random)
140
+ // 稼働が止まったときも静かな姿勢を選び直す(走りの途中コマで固まらないように)
141
+ const action = info.running ? pickAction(this.random) : pickSleepingAction(this.random)
119
142
  existing.clipIndex = action.clipIndex
120
143
  existing.actionEndsAt = now + action.durationMs
121
144
  dirty = true
145
+ } else if (now >= existing.actionEndsAt) {
146
+ const action = info.running ? pickAction(this.random) : pickSleepingAction(this.random)
147
+ if (action.clipIndex !== existing.clipIndex) dirty = true
148
+ existing.clipIndex = action.clipIndex
149
+ existing.actionEndsAt = now + action.durationMs
122
150
  }
123
151
  }
124
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shigureni/minion",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Desktop pet that reacts to your Claude Code session activity",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,4 +1,4 @@
1
- // swift-tools-version: 6.3
1
+ // swift-tools-version: 5.9
2
2
  import PackageDescription
3
3
 
4
4
  let package = Package(
@@ -14,6 +14,5 @@ let package = Package(
14
14
  .copy("Resources/ChickBlue.png")
15
15
  ]
16
16
  ),
17
- ],
18
- swiftLanguageModes: [.v6]
17
+ ]
19
18
  )
@@ -280,6 +280,9 @@ final class Pet {
280
280
  window.setFrameOrigin(NSPoint(x: x, y: y))
281
281
  }
282
282
 
283
+ // スポーン時の向きはランダム。移動する行動を取るまでこの向きを保つ。
284
+ facingLeft = Bool.random()
285
+
283
286
  window.orderFrontRegardless()
284
287
  }
285
288