@shigureni/minion 0.1.0 → 0.2.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/README.md +79 -33
- package/cli/src/app.ts +2 -0
- package/cli/src/factory.ts +8 -1
- package/cli/src/index.ts +15 -2
- package/cli/src/lib/body-validator.ts +13 -0
- package/cli/src/lib/format.ts +36 -0
- package/cli/src/router.ts +2 -1
- package/cli/src/routes/config/get/[key]/route.ts +7 -7
- package/cli/src/routes/config/list/route.ts +4 -5
- package/cli/src/routes/config/set/[key]/[value]/route.ts +5 -7
- package/cli/src/routes/dev/route.ts +12 -7
- package/cli/src/routes/dex/route.ts +66 -0
- package/cli/src/routes/kill/route.ts +7 -5
- package/cli/src/routes/not-found.ts +1 -1
- package/cli/src/routes/reboot/route.ts +12 -7
- package/cli/src/routes/start/route.ts +7 -6
- package/cli/src/routes/status/route.ts +5 -5
- package/lib/engine/app/app-paths.ts +46 -0
- package/lib/engine/app/app-runner.ts +235 -0
- package/lib/engine/app/source-hash.ts +34 -0
- package/lib/engine/collection/achievements.ts +143 -0
- package/lib/engine/collection/collection-store.ts +43 -0
- package/lib/engine/collection/collection-tracker.ts +97 -0
- package/lib/engine/collection/moon.ts +26 -0
- package/lib/engine/collection/species.ts +275 -0
- package/lib/engine/config/config-store.ts +36 -0
- package/lib/engine/errors.ts +20 -0
- package/lib/engine/fs/file-system.ts +31 -0
- package/lib/engine/fs/json-file-store.ts +57 -0
- package/lib/engine/fs/memory-file-system.ts +106 -0
- package/lib/engine/fs/node-file-system.ts +87 -0
- package/lib/engine/gateway/gateway-daemon.ts +14 -0
- package/lib/engine/gateway/gateway-routes.ts +17 -0
- package/lib/engine/gateway/gateway-server.ts +126 -0
- package/lib/engine/gateway/pet-behavior.ts +136 -0
- package/lib/engine/gateway/resolve-daemon-script.ts +7 -0
- package/lib/engine/gateway/sessions.ts +127 -0
- package/lib/engine/process/memory-process-runner.ts +67 -0
- package/lib/engine/process/node-process-runner.ts +51 -0
- package/lib/engine/process/process-runner.ts +21 -0
- package/lib/engine/random/memory-random-source.ts +22 -0
- package/lib/engine/random/node-random-source.ts +7 -0
- package/lib/engine/random/random-source.ts +9 -0
- package/lib/engine/stats/date-window.ts +31 -0
- package/lib/engine/stats/session-stats-tracker.ts +125 -0
- package/lib/engine/stats/stats-collector.ts +100 -0
- package/lib/engine/stats/stats-snapshot.ts +31 -0
- package/lib/engine/stats/token-usage-tracker.ts +170 -0
- package/lib/engine/time/clock.ts +11 -0
- package/lib/engine/time/memory-clock.ts +26 -0
- package/lib/engine/time/node-clock.ts +7 -0
- package/lib/index.ts +65 -0
- package/lib/minion.ts +194 -0
- package/package.json +9 -1
- package/swift/Sources/open-minion/open_minion.swift +2 -1
- package/tsconfig.json +3 -2
- package/cli/src/gateway.ts +0 -204
- package/cli/src/lib/process.ts +0 -204
package/README.md
CHANGED
|
@@ -1,65 +1,111 @@
|
|
|
1
1
|
# minion
|
|
2
2
|
|
|
3
|
-
A tiny desktop pet that walks around your screen and reacts to your
|
|
4
|
-
[Claude Code](https://claude.com/claude-code) sessions —
|
|
5
|
-
running around while
|
|
3
|
+
A tiny desktop pet that walks around your macOS screen and reacts to your
|
|
4
|
+
[Claude Code](https://claude.com/claude-code) sessions — napping while you're
|
|
5
|
+
idle, running around while Claude is busy working.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
pet should behave.
|
|
7
|
+
The more you use Claude Code, the more you collect: heavy usage, late-night
|
|
8
|
+
sessions, and day streaks unlock achievements and rare minions in a built-in dex.
|
|
10
9
|
|
|
11
10
|
## Requirements
|
|
12
11
|
|
|
13
12
|
- macOS
|
|
14
13
|
- [Bun](https://bun.sh)
|
|
15
|
-
- Xcode Command Line Tools (`xcode-select --install`) — needed once, to compile
|
|
16
|
-
|
|
14
|
+
- Xcode Command Line Tools (`xcode-select --install`) — needed once, to compile
|
|
15
|
+
the pet on first run
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## Quick start
|
|
19
18
|
|
|
20
19
|
```sh
|
|
21
20
|
bunx @shigureni/minion
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
that watches your Claude Code sessions.
|
|
23
|
+
The first run compiles the pet (takes a minute), then it appears on your screen
|
|
24
|
+
and starts watching your Claude Code sessions. Later runs reuse the build.
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
To keep the `minion` command around:
|
|
29
27
|
|
|
30
28
|
```sh
|
|
31
29
|
bun add -g @shigureni/minion
|
|
32
30
|
minion
|
|
33
31
|
```
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
## Commands
|
|
36
34
|
|
|
37
|
-
| Command
|
|
38
|
-
|
|
|
39
|
-
| `minion` / `minion start`
|
|
40
|
-
| `minion
|
|
41
|
-
| `minion
|
|
42
|
-
| `minion
|
|
43
|
-
| `minion
|
|
44
|
-
| `minion config list`
|
|
45
|
-
| `minion
|
|
46
|
-
| `minion config set <key> <value>` | Set a config value. |
|
|
35
|
+
| Command | What it does |
|
|
36
|
+
| -------------------------------- | ---------------------------------------- |
|
|
37
|
+
| `minion` / `minion start` | Start the pet. No-op if already running. |
|
|
38
|
+
| `minion kill` | Stop the pet and its gateway. |
|
|
39
|
+
| `minion reboot` | Restart. |
|
|
40
|
+
| `minion status` | Is it running? |
|
|
41
|
+
| `minion dex` | Your achievements and minion collection. |
|
|
42
|
+
| `minion config list / get / set` | Tweak settings. |
|
|
43
|
+
| `minion dev` | Restart with a debug build. |
|
|
47
44
|
|
|
48
|
-
Add `-h` to any command for
|
|
45
|
+
Add `-h` to any command for details. State lives in `~/.minion`.
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
## The dex
|
|
48
|
+
|
|
49
|
+
`minion dex` turns your Claude Code habits into a collection game. It reads your
|
|
50
|
+
session activity and token usage (from Claude Code's local transcripts) and
|
|
51
|
+
unlocks two kinds of things:
|
|
52
|
+
|
|
53
|
+
- **Achievements** — milestones like your first session, a 7-day streak, running
|
|
54
|
+
5 sessions at once, or burning a million tokens in a day.
|
|
55
|
+
- **Minions** — the five common species come out at different times of day; the
|
|
56
|
+
rare ones demand something special: 5+ parallel sessions, a week-long streak,
|
|
57
|
+
hopping across 10 different projects, 10M lifetime tokens, coding at 3am...
|
|
58
|
+
|
|
59
|
+
Undiscovered entries stay hidden as `???` until you earn them. Sprites aren't
|
|
60
|
+
wired into the desktop pet yet — for now the dex is a text-only collection — but
|
|
61
|
+
per-species art hooks are already in place.
|
|
62
|
+
|
|
63
|
+
## Use it as a library
|
|
64
|
+
|
|
65
|
+
Everything the CLI does is also a programmable API:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { Minion } from "@shigureni/minion"
|
|
69
|
+
|
|
70
|
+
const minion = new Minion()
|
|
71
|
+
// { kind: "started", pid } | { kind: "build-failed", ... } | ... | Error — the library never throws
|
|
72
|
+
const result = await minion.app.start()
|
|
73
|
+
|
|
74
|
+
const stats = minion.stats.collect()
|
|
75
|
+
const { species } = minion.collection.evaluate(stats)
|
|
76
|
+
console.log(`right now: ${species.name}`)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Everything is injectable. Bring your own species/achievement catalogs — including
|
|
80
|
+
your own art via each entry's `asset` field — or run fully sandboxed in tests:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// Custom catalog: your species, your conditions, your sprites.
|
|
84
|
+
const custom = new Minion({
|
|
85
|
+
species: [
|
|
86
|
+
{
|
|
87
|
+
id: "sunny",
|
|
88
|
+
name: "Sunny Pal",
|
|
89
|
+
rarity: "common",
|
|
90
|
+
description: "Appears during the day.",
|
|
91
|
+
condition: (s) => s.timeBucket === "day",
|
|
92
|
+
asset: "sprites/sunny.png",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Sandbox: no real disk, processes, or clock.
|
|
98
|
+
const sandbox = Minion.inMemory()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
See `lib/index.ts` for the full API surface.
|
|
52
102
|
|
|
53
103
|
## Development
|
|
54
104
|
|
|
55
105
|
```sh
|
|
56
106
|
bun install
|
|
57
|
-
bun run check # format + lint + type-check
|
|
107
|
+
bun run check # format + lint + type-check
|
|
58
108
|
bun run test
|
|
59
|
-
bun run fmt
|
|
60
|
-
bun run lint
|
|
61
109
|
```
|
|
62
110
|
|
|
63
|
-
|
|
64
|
-
the structure of jobantenna-cli) — adding a command means adding a route file and
|
|
65
|
-
wiring it into `cli/src/app.ts`.
|
|
111
|
+
Architecture notes and contributor conventions live in [CLAUDE.md](CLAUDE.md).
|
package/cli/src/app.ts
CHANGED
|
@@ -4,6 +4,7 @@ import configGet from "@/routes/config/get/[key]/route"
|
|
|
4
4
|
import configList from "@/routes/config/list/route"
|
|
5
5
|
import configSet from "@/routes/config/set/[key]/[value]/route"
|
|
6
6
|
import dev from "@/routes/dev/route"
|
|
7
|
+
import dex from "@/routes/dex/route"
|
|
7
8
|
import kill from "@/routes/kill/route"
|
|
8
9
|
import { notFound } from "@/routes/not-found"
|
|
9
10
|
import reboot from "@/routes/reboot/route"
|
|
@@ -17,6 +18,7 @@ base.notFound(notFound)
|
|
|
17
18
|
export const app = base
|
|
18
19
|
.post("/start", ...start)
|
|
19
20
|
.post("/dev", ...dev)
|
|
21
|
+
.post("/dex", ...dex)
|
|
20
22
|
.post("/kill", ...kill)
|
|
21
23
|
.post("/reboot", ...reboot)
|
|
22
24
|
.post("/status", ...status)
|
package/cli/src/factory.ts
CHANGED
package/cli/src/index.ts
CHANGED
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
import { app } from "@/app"
|
|
3
3
|
import { postJson } from "@/lib/post-json"
|
|
4
4
|
import { toRequest } from "@/router"
|
|
5
|
+
import { Minion } from "@lib/minion"
|
|
5
6
|
|
|
6
7
|
import pkg from "../../package.json" with { type: "json" }
|
|
7
8
|
|
|
9
|
+
const minion = new Minion({
|
|
10
|
+
onEvent: (event) => {
|
|
11
|
+
if (event.type === "build-start") console.log("ビルド中...")
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
const env = { minion }
|
|
15
|
+
|
|
8
16
|
const HELP = `minion - open-minion CLI
|
|
9
17
|
|
|
10
18
|
Usage:
|
|
@@ -17,6 +25,7 @@ Commands:
|
|
|
17
25
|
kill 停止する
|
|
18
26
|
reboot 停止してリリースビルドで起動し直す
|
|
19
27
|
status 起動状況を表示する
|
|
28
|
+
dex 実績とミニオン図鑑を表示する
|
|
20
29
|
config list 設定値を一覧表示する
|
|
21
30
|
config get <key> 設定値を取得する
|
|
22
31
|
config set <key> <value> 設定値を書き込む
|
|
@@ -41,13 +50,17 @@ if (request.global.help) {
|
|
|
41
50
|
process.exit()
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
const helpResponse = await app.request(
|
|
53
|
+
const helpResponse = await app.request(
|
|
54
|
+
request.path,
|
|
55
|
+
postJson({ ...request.body, help: "true" }),
|
|
56
|
+
env,
|
|
57
|
+
)
|
|
45
58
|
console.log(helpResponse.ok ? await helpResponse.text() : HELP)
|
|
46
59
|
process.exit()
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
try {
|
|
50
|
-
const response = await app.request(request.path, postJson(request.body))
|
|
63
|
+
const response = await app.request(request.path, postJson(request.body), env)
|
|
51
64
|
const text = await response.text()
|
|
52
65
|
|
|
53
66
|
if (!response.ok) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator"
|
|
2
|
+
import type { z } from "zod"
|
|
3
|
+
|
|
4
|
+
// zValidator の 400 応答(zod エラーの JSON ダンプ)を CLI 向けの短いテキストにする。
|
|
5
|
+
// strict スキーマと組み合わせて、未知のフラグを黙って無視せずエラーにする。
|
|
6
|
+
export function bodyValidator<T extends z.ZodType>(schema: T) {
|
|
7
|
+
return zValidator("json", schema, (result, c) => {
|
|
8
|
+
if (!result.success) {
|
|
9
|
+
const message = result.error.issues.map((issue) => issue.message).join("\n")
|
|
10
|
+
return c.text(message, 400)
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MinionAppStatus,
|
|
3
|
+
MinionKillResult,
|
|
4
|
+
MinionStartResult,
|
|
5
|
+
} from "@lib/engine/app/app-runner"
|
|
6
|
+
|
|
7
|
+
// ライブラリは構造化された結果を返す。日本語の表示文字列への整形はCLIの責務。
|
|
8
|
+
|
|
9
|
+
export function formatStartResult(result: MinionStartResult): string {
|
|
10
|
+
switch (result.kind) {
|
|
11
|
+
case "started":
|
|
12
|
+
return `起動した (pid ${result.pid})`
|
|
13
|
+
case "already-running":
|
|
14
|
+
return `すでに起動中 (pid ${result.pid})`
|
|
15
|
+
case "build-failed":
|
|
16
|
+
return "ビルド失敗"
|
|
17
|
+
case "lock-conflict":
|
|
18
|
+
return "他の起動処理と競合したため中止"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isStartError(result: MinionStartResult): boolean {
|
|
23
|
+
return result.kind === "build-failed" || result.kind === "lock-conflict"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatKillResult(result: MinionKillResult): string {
|
|
27
|
+
return result.kind === "killed" ? `停止した (pid ${result.pid})` : "起動していない"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function formatAppStatus(status: MinionAppStatus): string {
|
|
31
|
+
const appLine = status.app.running ? `起動中 (pid ${status.app.pid})` : "停止中"
|
|
32
|
+
const gatewayLine = status.gateway.running
|
|
33
|
+
? `gateway起動中 (pid ${status.gateway.pid})`
|
|
34
|
+
: "gateway停止中"
|
|
35
|
+
return [appLine, gatewayLine].join("\n")
|
|
36
|
+
}
|
package/cli/src/router.ts
CHANGED
|
@@ -40,6 +40,7 @@ export function toRequest(args: string[]) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// `/` や `:` を含む値(URLなど)がルーティングを壊さないようエンコードする。Hono がパラメータ取得時にデコードする。
|
|
44
|
+
const path = `/${segments.map(encodeURIComponent).join("/")}`
|
|
44
45
|
return { path, body, global }
|
|
45
46
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
4
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { readConfig } from "@/lib/process"
|
|
6
5
|
|
|
7
|
-
const schema = z.
|
|
6
|
+
const schema = z.strictObject({})
|
|
8
7
|
|
|
9
8
|
export const help = `Usage: minion config get <key>
|
|
10
9
|
|
|
11
10
|
設定値を取得する。`
|
|
12
11
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
14
|
-
const key = c.req.param("key")
|
|
15
|
-
const
|
|
16
|
-
return c.text(
|
|
12
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
13
|
+
const key = c.req.param("key") ?? ""
|
|
14
|
+
const value = c.env.minion.config.get(key)
|
|
15
|
+
if (value === undefined) return c.text(`未設定: ${key}`, 404)
|
|
16
|
+
return c.text(value)
|
|
17
17
|
})
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
4
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { readConfig } from "@/lib/process"
|
|
6
5
|
|
|
7
|
-
const schema = z.
|
|
6
|
+
const schema = z.strictObject({})
|
|
8
7
|
|
|
9
8
|
export const help = `Usage: minion config list
|
|
10
9
|
|
|
11
10
|
設定値を一覧表示する。`
|
|
12
11
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
14
|
-
const config =
|
|
12
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
13
|
+
const config = c.env.minion.config.list()
|
|
15
14
|
const lines = Object.entries(config).map(([k, v]) => `${k} = ${v}`)
|
|
16
15
|
return c.text(lines.join("\n"))
|
|
17
16
|
})
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
4
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { readConfig, writeConfig } from "@/lib/process"
|
|
6
5
|
|
|
7
|
-
const schema = z.
|
|
6
|
+
const schema = z.strictObject({})
|
|
8
7
|
|
|
9
8
|
export const help = `Usage: minion config set <key> <value>
|
|
10
9
|
|
|
11
10
|
設定値を書き込む。`
|
|
12
11
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
12
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
14
13
|
const key = c.req.param("key") ?? ""
|
|
15
14
|
const value = c.req.param("value") ?? ""
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
writeConfig(config)
|
|
15
|
+
const setError = c.env.minion.config.set(key, value)
|
|
16
|
+
if (setError) return c.text(setError.message, 500)
|
|
19
17
|
return c.text(`${key} = ${value}`)
|
|
20
18
|
})
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
|
+
import { formatKillResult, formatStartResult, isStartError } from "@/lib/format"
|
|
4
5
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { killApp, startApp } from "@/lib/process"
|
|
6
6
|
|
|
7
|
-
const schema = z.
|
|
7
|
+
const schema = z.strictObject({})
|
|
8
8
|
|
|
9
9
|
export const help = `Usage: minion dev
|
|
10
10
|
|
|
11
11
|
一旦停止してから、デバッグビルドで起動し直す。`
|
|
12
12
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
14
|
+
const killResult = c.env.minion.app.kill()
|
|
15
|
+
if (killResult instanceof Error) return c.text(killResult.message, 500)
|
|
16
|
+
const startResult = await c.env.minion.app.start({ debug: true })
|
|
17
|
+
if (startResult instanceof Error) {
|
|
18
|
+
return c.text([formatKillResult(killResult), startResult.message].join("\n"), 500)
|
|
19
|
+
}
|
|
20
|
+
const text = [formatKillResult(killResult), formatStartResult(startResult)].join("\n")
|
|
21
|
+
return c.text(text, isStartError(startResult) ? 500 : 200)
|
|
17
22
|
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
|
+
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
+
import type { MinionRarity } from "@lib/engine/collection/species"
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({})
|
|
8
|
+
|
|
9
|
+
export const help = `Usage: minion dex
|
|
10
|
+
|
|
11
|
+
実績とミニオン図鑑を表示する。セッション数・同時実行数・時間帯・トークン消費量を
|
|
12
|
+
元に実績を解除し、条件を満たすと珍しいミニオンが出現する。`
|
|
13
|
+
|
|
14
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
15
|
+
const minion = c.env.minion
|
|
16
|
+
const stats = minion.stats.collect()
|
|
17
|
+
if (stats instanceof Error) return c.text(stats.message, 500)
|
|
18
|
+
const evaluation = minion.collection.evaluate(stats)
|
|
19
|
+
if (evaluation instanceof Error) return c.text(evaluation.message, 500)
|
|
20
|
+
const { achievements, species } = minion.collection.dex()
|
|
21
|
+
|
|
22
|
+
const lines: string[] = []
|
|
23
|
+
|
|
24
|
+
lines.push(
|
|
25
|
+
`現在のミニオン: ${evaluation.species.name} (${rarityLabel(evaluation.species.rarity)})`,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const newlyUnlocked = evaluation.newlyUnlockedAchievements
|
|
29
|
+
if (newlyUnlocked.length > 0) {
|
|
30
|
+
lines.push("")
|
|
31
|
+
lines.push("実績を解除した!")
|
|
32
|
+
for (const a of newlyUnlocked) lines.push(` ★ ${a.name} — ${a.description}`)
|
|
33
|
+
}
|
|
34
|
+
if (evaluation.newlyDiscoveredSpecies.length > 0) {
|
|
35
|
+
lines.push("")
|
|
36
|
+
for (const discovered of evaluation.newlyDiscoveredSpecies) {
|
|
37
|
+
lines.push(`新しいミニオンを発見した! ${discovered.name} (${rarityLabel(discovered.rarity)})`)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const unlockedCount = achievements.filter((a) => a.unlocked).length
|
|
42
|
+
lines.push("")
|
|
43
|
+
lines.push(`== 実績 (${unlockedCount}/${achievements.length}) ==`)
|
|
44
|
+
for (const a of achievements) {
|
|
45
|
+
const mark = a.unlocked ? "[x]" : "[ ]"
|
|
46
|
+
const detail = a.unlocked ? a.description : "???"
|
|
47
|
+
lines.push(`${mark} ${a.name} — ${detail}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const discoveredCount = species.filter((s) => s.discovered).length
|
|
51
|
+
lines.push("")
|
|
52
|
+
lines.push(`== ミニオン図鑑 (${discoveredCount}/${species.length}) ==`)
|
|
53
|
+
for (const s of species) {
|
|
54
|
+
const mark = s.discovered ? "[x]" : "[ ]"
|
|
55
|
+
const label = s.discovered
|
|
56
|
+
? `${s.name} (${rarityLabel(s.rarity)}) — ${s.description}`
|
|
57
|
+
: `??? (${rarityLabel(s.rarity)})`
|
|
58
|
+
lines.push(`${mark} ${label}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return c.text(lines.join("\n"))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
function rarityLabel(rarity: MinionRarity): string {
|
|
65
|
+
return rarity === "rare" ? "レア" : "コモン"
|
|
66
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
|
+
import { formatKillResult } from "@/lib/format"
|
|
4
5
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { killApp } from "@/lib/process"
|
|
6
6
|
|
|
7
|
-
const schema = z.
|
|
7
|
+
const schema = z.strictObject({})
|
|
8
8
|
|
|
9
9
|
export const help = `Usage: minion kill
|
|
10
10
|
|
|
11
11
|
アプリとgatewayを停止する。`
|
|
12
12
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
14
|
-
|
|
13
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
14
|
+
const result = c.env.minion.app.kill()
|
|
15
|
+
if (result instanceof Error) return c.text(result.message, 500)
|
|
16
|
+
return c.text(formatKillResult(result))
|
|
15
17
|
})
|
|
@@ -2,7 +2,7 @@ import type { NotFoundHandler } from "hono"
|
|
|
2
2
|
|
|
3
3
|
export const notFound: NotFoundHandler = (c) => {
|
|
4
4
|
return c.text(
|
|
5
|
-
`使い方: minion [start|dev|kill|reboot|status|config]\n不明なコマンド: ${c.req.path}`,
|
|
5
|
+
`使い方: minion [start|dev|kill|reboot|status|dex|config]\n不明なコマンド: ${c.req.path}`,
|
|
6
6
|
404,
|
|
7
7
|
)
|
|
8
8
|
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
|
+
import { formatKillResult, formatStartResult, isStartError } from "@/lib/format"
|
|
4
5
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { killApp, startApp } from "@/lib/process"
|
|
6
6
|
|
|
7
|
-
const schema = z.
|
|
7
|
+
const schema = z.strictObject({})
|
|
8
8
|
|
|
9
9
|
export const help = `Usage: minion reboot
|
|
10
10
|
|
|
11
11
|
一旦停止してから、リリースビルドで起動し直す。`
|
|
12
12
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
14
|
+
const killResult = c.env.minion.app.kill()
|
|
15
|
+
if (killResult instanceof Error) return c.text(killResult.message, 500)
|
|
16
|
+
const startResult = await c.env.minion.app.start()
|
|
17
|
+
if (startResult instanceof Error) {
|
|
18
|
+
return c.text([formatKillResult(killResult), startResult.message].join("\n"), 500)
|
|
19
|
+
}
|
|
20
|
+
const text = [formatKillResult(killResult), formatStartResult(startResult)].join("\n")
|
|
21
|
+
return c.text(text, isStartError(startResult) ? 500 : 200)
|
|
17
22
|
})
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
|
+
import { formatStartResult, isStartError } from "@/lib/format"
|
|
4
5
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { startApp } from "@/lib/process"
|
|
6
6
|
|
|
7
|
-
const schema = z.
|
|
7
|
+
const schema = z.strictObject({})
|
|
8
8
|
|
|
9
9
|
export const help = `Usage: minion start
|
|
10
10
|
|
|
11
11
|
アプリをリリースビルドで起動する。すでに起動中なら何もしない。`
|
|
12
12
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
14
|
-
const
|
|
15
|
-
return c.text(message)
|
|
13
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
14
|
+
const result = await c.env.minion.app.start()
|
|
15
|
+
if (result instanceof Error) return c.text(result.message, 500)
|
|
16
|
+
return c.text(formatStartResult(result), isStartError(result) ? 500 : 200)
|
|
16
17
|
})
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator"
|
|
2
1
|
import { z } from "zod"
|
|
3
2
|
import { factory } from "@/factory"
|
|
3
|
+
import { bodyValidator } from "@/lib/body-validator"
|
|
4
|
+
import { formatAppStatus } from "@/lib/format"
|
|
4
5
|
import { helpGuard } from "@/lib/help-guard"
|
|
5
|
-
import { statusApp } from "@/lib/process"
|
|
6
6
|
|
|
7
|
-
const schema = z.
|
|
7
|
+
const schema = z.strictObject({})
|
|
8
8
|
|
|
9
9
|
export const help = `Usage: minion status
|
|
10
10
|
|
|
11
11
|
アプリとgatewayの起動状況を表示する。`
|
|
12
12
|
|
|
13
|
-
export default factory.createHandlers(helpGuard(help),
|
|
14
|
-
return c.text(
|
|
13
|
+
export default factory.createHandlers(helpGuard(help), bodyValidator(schema), async (c) => {
|
|
14
|
+
return c.text(formatAppStatus(c.env.minion.app.status()))
|
|
15
15
|
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { homedir } from "node:os"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
|
|
4
|
+
export type MinionPaths = {
|
|
5
|
+
/** `~/.minion` by default — state lives here independent of where the package itself is installed. */
|
|
6
|
+
dataDir: string
|
|
7
|
+
/** The `swift/` directory containing the Xcode Swift Package. */
|
|
8
|
+
appRoot: string
|
|
9
|
+
buildPath: string
|
|
10
|
+
binPath: string
|
|
11
|
+
debugBinPath: string
|
|
12
|
+
dataFile: string
|
|
13
|
+
pidFile: string
|
|
14
|
+
gatewayPidFile: string
|
|
15
|
+
configFile: string
|
|
16
|
+
sessionStatsFile: string
|
|
17
|
+
usageScanFile: string
|
|
18
|
+
collectionFile: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type Props = {
|
|
22
|
+
/** The directory containing `swift/` (the package root). */
|
|
23
|
+
packageRoot: string
|
|
24
|
+
dataDir?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveMinionPaths(props: Props): MinionPaths {
|
|
28
|
+
const dataDir = props.dataDir ?? join(homedir(), ".minion")
|
|
29
|
+
const appRoot = join(props.packageRoot, "swift")
|
|
30
|
+
const buildPath = join(dataDir, "build")
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
dataDir,
|
|
34
|
+
appRoot,
|
|
35
|
+
buildPath,
|
|
36
|
+
binPath: join(buildPath, "release", "open-minion"),
|
|
37
|
+
debugBinPath: join(buildPath, "debug", "open-minion"),
|
|
38
|
+
dataFile: join(dataDir, "data.json"),
|
|
39
|
+
pidFile: join(dataDir, "pid"),
|
|
40
|
+
gatewayPidFile: join(dataDir, "gateway.pid"),
|
|
41
|
+
configFile: join(dataDir, "config.json"),
|
|
42
|
+
sessionStatsFile: join(dataDir, "session-stats.json"),
|
|
43
|
+
usageScanFile: join(dataDir, "usage-scan.json"),
|
|
44
|
+
collectionFile: join(dataDir, "collection.json"),
|
|
45
|
+
}
|
|
46
|
+
}
|