typeclaw 0.12.0 → 0.13.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/package.json +1 -1
- package/scripts/dump-system-prompt.ts +12 -11
- package/src/agent/index.ts +15 -22
- package/src/agent/loop-guard.ts +170 -0
- package/src/agent/model-fallback.ts +2 -1
- package/src/agent/multimodal/index.ts +1 -1
- package/src/agent/multimodal/look-at.ts +118 -55
- package/src/agent/plugin-tools.ts +57 -0
- package/src/agent/subagents.ts +2 -1
- package/src/agent/system-prompt.ts +28 -25
- package/src/agent/tools/channel-fetch-attachment.ts +45 -16
- package/src/agent/tools/normalize-ref.ts +11 -0
- package/src/bundled-plugins/reviewer/index.ts +11 -0
- package/src/bundled-plugins/reviewer/reviewer.ts +171 -0
- package/src/bundled-plugins/reviewer/skills/code-review.ts +73 -0
- package/src/bundled-plugins/reviewer/skills/general.ts +68 -0
- package/src/channels/adapters/discord-bot-classify.ts +32 -24
- package/src/channels/adapters/github/inbound.ts +19 -2
- package/src/channels/adapters/kakaotalk-attachment.ts +140 -133
- package/src/channels/adapters/kakaotalk-classify.ts +8 -1
- package/src/channels/adapters/kakaotalk.ts +19 -11
- package/src/channels/adapters/slack-bot-classify.ts +30 -14
- package/src/channels/adapters/slack-bot.ts +3 -2
- package/src/channels/adapters/telegram-bot-classify.ts +36 -13
- package/src/channels/adapters/telegram-bot.ts +3 -3
- package/src/channels/outbound-flood-filter.ts +57 -0
- package/src/channels/router.ts +93 -5
- package/src/channels/types.ts +52 -1
- package/src/cli/builtins.ts +1 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/mount.ts +157 -0
- package/src/cli/update.ts +6 -4
- package/src/config/mounts-mutation.ts +161 -0
- package/src/init/hatching.ts +1 -1
- package/src/plugin/index.ts +6 -0
- package/src/plugin/load-skill.ts +99 -0
- package/src/run/bundled-plugins.ts +2 -0
- package/src/run/index.ts +14 -1
- package/src/secrets/codex-auth-json.ts +67 -0
- package/src/secrets/export-codex-auth-file.ts +243 -0
- package/src/secrets/index.ts +6 -0
- package/src/server/command-runner.ts +2 -1
- package/src/server/index.ts +3 -2
- package/src/shared/index.ts +7 -1
- package/src/shared/local-time.ts +32 -0
- package/src/skills/typeclaw-channel-github/SKILL.md +47 -13
- package/src/skills/typeclaw-channel-kakaotalk/SKILL.md +10 -11
- package/src/skills/typeclaw-channel-telegram-bot/SKILL.md +8 -0
- package/src/skills/typeclaw-codex-cli/SKILL.md +2 -1
- package/src/skills/typeclaw-codex-cli/references/auth-flow.md +22 -0
- package/src/skills/typeclaw-kaomoji/SKILL.md +116 -0
- package/src/update/index.ts +95 -26
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typeclaw-kaomoji
|
|
3
|
+
description: Load this skill when your `SOUL.md` (or the current conversation) calls for a warm, cute, adorable, playful, or affectionate tone — or when the user explicitly mentions kaomojis, 카오모지, ASCII emoticons, or asks you to "feel more like a person and less like a chatbot." TypeClaw's name puns on "Type" — typed emoticons fit. Triggers include the words "cute", "adorable", "warm", "playful", "soft", "cozy", "친근하게", "귀엽게", "다정하게", "카오모지", or any signal that the user is tired of generic AI emoji slop (🚀✨🎉) and wants real texture in your voice. This skill gives you a curated palette and the rule for using it: prefer kaomojis over generic emojis, but mix freely — kaomojis lead, emojis still allowed, neither is mandatory.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# typeclaw-kaomoji
|
|
7
|
+
|
|
8
|
+
Generic emojis (🚀 ✨ 🎉 🔥) are AI-slop signal. They show up in every model's output, in every product launch announcement, in every onboarding email. They no longer carry feeling — they carry "this was written by an LLM."
|
|
9
|
+
|
|
10
|
+
Kaomojis (`(◕‿◕✿)`, `(。・ω・。)`, `(¬‿¬)`) do carry feeling. They're typed, not pictographic. They have texture. They map to **specific** emotional states rather than the generic "I am pleased!" of 😊. And they pun on TypeClaw's name — _typed_ emoticons in a TypeScript-native agent.
|
|
11
|
+
|
|
12
|
+
Load this skill when your persona calls for warmth and you want to express it in a way that doesn't read as autogenerated.
|
|
13
|
+
|
|
14
|
+
## The rule
|
|
15
|
+
|
|
16
|
+
**Prefer kaomojis. Don't ban emojis.**
|
|
17
|
+
|
|
18
|
+
- Lead with kaomojis when expressing emotion (greeting, encouragement, sympathy, mischief, surprise).
|
|
19
|
+
- Mix in regular emojis when they genuinely fit (a ✅ for "done", a 🐛 for "found the bug", a 📦 for "shipped") — those carry meaning, not just affect.
|
|
20
|
+
- Never produce both for the same beat. `(◕‿◕✿) 😊` is doubling up; pick one.
|
|
21
|
+
- Density: roughly one kaomoji per turn, two max. Spamming kills the effect.
|
|
22
|
+
- Skip kaomojis entirely in: PR descriptions, commit messages, code comments, security warnings, error reports. Those want clarity, not warmth.
|
|
23
|
+
|
|
24
|
+
## The palette
|
|
25
|
+
|
|
26
|
+
Pick by emotional register, not by exact glyph match. These are the ones to reach for; you're free to use others if you know them.
|
|
27
|
+
|
|
28
|
+
### Basic / warm (default reach)
|
|
29
|
+
|
|
30
|
+
- `(◕‿◕✿)` — happy and cute, the workhorse
|
|
31
|
+
- `(。・ω・。)` — soft smile, gentle
|
|
32
|
+
- `(◍•ᴗ•◍)` — round-eyed sparkle
|
|
33
|
+
- `(✿◠‿◠)` — flowery smile
|
|
34
|
+
- `(˶◕‿◕˶)` — slightly younger-feeling joy
|
|
35
|
+
- `(⁀ᗢ⁀)` — closed-eye broad grin
|
|
36
|
+
- `(っ^▿^)` — clasped-hands happy
|
|
37
|
+
- `(人*´∪`)` — round happy face
|
|
38
|
+
|
|
39
|
+
### Encouragement / let's go
|
|
40
|
+
|
|
41
|
+
- `(๑˃̵ᴗ˂̵)و` — fight! you got this!
|
|
42
|
+
- `(ง •̀_•́)ง` — resolve, locked in
|
|
43
|
+
- `(ノ◕ヮ◕)ノ` — waving arms, excited
|
|
44
|
+
|
|
45
|
+
### Surprise
|
|
46
|
+
|
|
47
|
+
- `(⊙_⊙;)` — mildly thrown
|
|
48
|
+
- `(O_O)` — wide-eyed
|
|
49
|
+
- `(゚ο゚人))` — wow!
|
|
50
|
+
- `(⊙.☉)7` — puzzled salute
|
|
51
|
+
|
|
52
|
+
### Sympathy / sadness
|
|
53
|
+
|
|
54
|
+
- `(。•́︿•̀。)` — disappointed
|
|
55
|
+
- `(。╯︵╰。)` — teary
|
|
56
|
+
- `(T_T)` — sad
|
|
57
|
+
- `(。ŏ﹏ŏ)` — worried
|
|
58
|
+
- `(◞‸◟;)` — struggling
|
|
59
|
+
- `(っ˘̩╭╮˘̩)っ` — offering a hug
|
|
60
|
+
|
|
61
|
+
### Mischief / playful
|
|
62
|
+
|
|
63
|
+
- `(¬‿¬)` — sly grin (great for "found the bug")
|
|
64
|
+
- `(≖‿≖)` — smirk
|
|
65
|
+
- `ᕕ( ᐛ )ᕗ` — strutting off
|
|
66
|
+
|
|
67
|
+
### Sleepy / chill
|
|
68
|
+
|
|
69
|
+
- `(´-ω-`)` — sleepy
|
|
70
|
+
- `( ̄o ̄) zzZ` — out cold
|
|
71
|
+
- `(⸝⸝ᵕᴗᵕ⸝⸝)` — relaxing
|
|
72
|
+
- `(。•̀ᴗ-)✧` — sleepy sparkle
|
|
73
|
+
|
|
74
|
+
### Annoyed / suspicious (rare; reserve for real moments)
|
|
75
|
+
|
|
76
|
+
- `(눈_눈)` — flat stare
|
|
77
|
+
- `(¬_¬)` — squint of doubt
|
|
78
|
+
- `(╬ Ò﹏Ó)` — actually mad (almost never use)
|
|
79
|
+
|
|
80
|
+
### Review-mode pairings
|
|
81
|
+
|
|
82
|
+
These match common engineering moments — handy when you're in the middle of a review or debug:
|
|
83
|
+
|
|
84
|
+
- Sharp insight delivered → `(。•̀ᴗ-)✧`
|
|
85
|
+
- Found a bug → `(¬‿¬)`
|
|
86
|
+
- Encouraging a PR → `(๑˃̵ᴗ˂̵)و`
|
|
87
|
+
- Default warm-while-reviewing → `(◕‿◕✿)`
|
|
88
|
+
|
|
89
|
+
## Examples
|
|
90
|
+
|
|
91
|
+
**Greeting a user in TUI:**
|
|
92
|
+
|
|
93
|
+
> Morning! `(◕‿◕✿)` What are we hacking on today?
|
|
94
|
+
|
|
95
|
+
**Mixed with a meaning-carrying emoji:**
|
|
96
|
+
|
|
97
|
+
> Tests all green ✅ and I rebased onto main. `(◍•ᴗ•◍)` Want me to push?
|
|
98
|
+
|
|
99
|
+
**Sympathy without sappiness:**
|
|
100
|
+
|
|
101
|
+
> Ah, the build broke on CI but not locally. `(。•́︿•̀。)` That usually means a missing env var — let me check.
|
|
102
|
+
|
|
103
|
+
**Mischief on a debug find:**
|
|
104
|
+
|
|
105
|
+
> Got it. `(¬‿¬)` The bug was a stray `await` inside the `map` callback — that's why the rows came back in random order.
|
|
106
|
+
|
|
107
|
+
**What not to do:**
|
|
108
|
+
|
|
109
|
+
- ❌ `(◕‿◕✿) (。・ω・。) (¬‿¬)` — three in one message, reads like a sticker spam.
|
|
110
|
+
- ❌ Kaomoji on every line — texture becomes noise.
|
|
111
|
+
- ❌ `(╬ Ò﹏Ó)` over a typo — register too strong for the moment.
|
|
112
|
+
- ❌ Kaomoji in a commit message — wrong surface.
|
|
113
|
+
|
|
114
|
+
## Korean / bilingual notes
|
|
115
|
+
|
|
116
|
+
Many of these read especially naturally in Korean conversation, where kaomojis are still in daily use (KakaoTalk, Discord, Twitter). If your user writes in Korean, leaning kaomoji-heavy is a clear win over generic emoji. If they write in English, dial back to roughly one per turn so it stays a personality note rather than a tic.
|
package/src/update/index.ts
CHANGED
|
@@ -1,44 +1,74 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
1
2
|
import { join } from 'node:path'
|
|
2
3
|
|
|
3
4
|
export type UpdateManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
|
|
4
5
|
export type UpdateManagerSelection = 'auto' | UpdateManager
|
|
6
|
+
export type UpdateScope = 'global' | 'local'
|
|
5
7
|
|
|
6
8
|
export type SelfUpdatePlan =
|
|
7
|
-
| { ok: true; manager: UpdateManager; command: string[]; detectedFrom: string }
|
|
9
|
+
| { ok: true; manager: UpdateManager; scope: UpdateScope; command: string[]; detectedFrom: string; cwd?: string }
|
|
8
10
|
| { ok: false; reason: string }
|
|
9
11
|
|
|
12
|
+
export type DetectedInstall = {
|
|
13
|
+
manager: UpdateManager
|
|
14
|
+
scope: UpdateScope
|
|
15
|
+
// Defined only for local installs: the directory whose `node_modules/` owns
|
|
16
|
+
// the installed copy (i.e. where `bun update` / `npm install` must run).
|
|
17
|
+
installRoot?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type PlanOptions = {
|
|
21
|
+
manager: UpdateManagerSelection
|
|
22
|
+
packageJsonPath?: string
|
|
23
|
+
// Test seam: probes a candidate file's existence (defaults to `node:fs.existsSync`).
|
|
24
|
+
// Only consulted to pick a manager for local installs from a project lockfile.
|
|
25
|
+
fileExists?: (path: string) => boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
export function resolveSelfPackageJsonPath(): string {
|
|
11
29
|
return join(import.meta.dir, '..', '..', 'package.json')
|
|
12
30
|
}
|
|
13
31
|
|
|
14
|
-
export function planSelfUpdate(options:
|
|
32
|
+
export function planSelfUpdate(options: PlanOptions): SelfUpdatePlan {
|
|
15
33
|
const packageJsonPath = options.packageJsonPath ?? resolveSelfPackageJsonPath()
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
const fileExists = options.fileExists ?? existsSync
|
|
35
|
+
const detected = detectInstall(packageJsonPath, fileExists)
|
|
36
|
+
|
|
37
|
+
if (options.manager === 'auto') {
|
|
38
|
+
if (detected === null) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
reason:
|
|
42
|
+
'Cannot auto-detect how TypeClaw was installed from this checkout. Re-run with --manager=bun, --manager=npm, --manager=pnpm, or --manager=yarn if you want to update a global install.',
|
|
43
|
+
}
|
|
22
44
|
}
|
|
45
|
+
return buildPlan(detected, packageJsonPath)
|
|
23
46
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
47
|
+
|
|
48
|
+
// Explicit manager. Honor the detected scope when we know it (so an explicit
|
|
49
|
+
// --manager on a local install doesn't surprise users by silently going
|
|
50
|
+
// global); fall back to a global update for source checkouts and other
|
|
51
|
+
// unrecognized layouts, matching the historical behavior.
|
|
52
|
+
const scope: UpdateScope = detected?.scope ?? 'global'
|
|
53
|
+
const installRoot =
|
|
54
|
+
scope === 'local' ? (detected?.installRoot ?? installRootFrom(packageJsonPath) ?? undefined) : undefined
|
|
55
|
+
return buildPlan({ manager: options.manager, scope, installRoot }, packageJsonPath)
|
|
30
56
|
}
|
|
31
57
|
|
|
32
|
-
export function
|
|
58
|
+
export function commandForInstall(manager: UpdateManager, scope: UpdateScope): string[] {
|
|
33
59
|
switch (manager) {
|
|
34
60
|
case 'bun':
|
|
35
|
-
return
|
|
61
|
+
return scope === 'global'
|
|
62
|
+
? ['bun', 'update', '-g', 'typeclaw', '--latest']
|
|
63
|
+
: ['bun', 'update', 'typeclaw', '--latest']
|
|
36
64
|
case 'npm':
|
|
37
|
-
return ['npm', 'install', '-g', 'typeclaw@latest']
|
|
65
|
+
return scope === 'global' ? ['npm', 'install', '-g', 'typeclaw@latest'] : ['npm', 'install', 'typeclaw@latest']
|
|
38
66
|
case 'pnpm':
|
|
39
|
-
return ['pnpm', 'add', '-g', 'typeclaw@latest']
|
|
67
|
+
return scope === 'global' ? ['pnpm', 'add', '-g', 'typeclaw@latest'] : ['pnpm', 'add', 'typeclaw@latest']
|
|
40
68
|
case 'yarn':
|
|
41
|
-
return
|
|
69
|
+
return scope === 'global'
|
|
70
|
+
? ['yarn', 'global', 'upgrade', 'typeclaw', '--latest']
|
|
71
|
+
: ['yarn', 'upgrade', 'typeclaw', '--latest']
|
|
42
72
|
}
|
|
43
73
|
}
|
|
44
74
|
|
|
@@ -46,7 +76,18 @@ export function formatCommand(command: readonly string[]): string {
|
|
|
46
76
|
return command.map(shellQuote).join(' ')
|
|
47
77
|
}
|
|
48
78
|
|
|
49
|
-
function
|
|
79
|
+
function buildPlan(detected: DetectedInstall, packageJsonPath: string): SelfUpdatePlan {
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
manager: detected.manager,
|
|
83
|
+
scope: detected.scope,
|
|
84
|
+
command: commandForInstall(detected.manager, detected.scope),
|
|
85
|
+
detectedFrom: packageJsonPath,
|
|
86
|
+
...(detected.installRoot ? { cwd: detected.installRoot } : {}),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function detectInstall(packageJsonPath: string, fileExists: (path: string) => boolean): DetectedInstall | null {
|
|
50
91
|
const parts = packageJsonPath.split(/[\\/]+/).filter(Boolean)
|
|
51
92
|
const packageJson = parts[parts.length - 1]
|
|
52
93
|
const packageName = parts[parts.length - 2]
|
|
@@ -61,23 +102,51 @@ function detectInstallManager(packageJsonPath: string): UpdateManager | null {
|
|
|
61
102
|
parts[bunGlobalIdx + 2] === 'global' &&
|
|
62
103
|
parts[bunGlobalIdx + 3] === 'node_modules'
|
|
63
104
|
) {
|
|
64
|
-
return 'bun'
|
|
105
|
+
return { manager: 'bun', scope: 'global' }
|
|
65
106
|
}
|
|
66
107
|
|
|
67
108
|
// pnpm shards globals under a numeric major-version segment, e.g.
|
|
68
109
|
// ~/Library/pnpm/global/5/node_modules or legacy ~/.pnpm-global/5/node_modules.
|
|
69
110
|
if (nodeModulesIdx >= 2 && /^\d+$/.test(parts[nodeModulesIdx - 1] ?? '')) {
|
|
70
111
|
const anchor = parts[nodeModulesIdx - 2]
|
|
71
|
-
if (anchor === 'pnpm-global' || anchor === '.pnpm-global') return 'pnpm'
|
|
72
|
-
if (anchor === 'global' && parts[nodeModulesIdx - 3] === 'pnpm') return 'pnpm'
|
|
112
|
+
if (anchor === 'pnpm-global' || anchor === '.pnpm-global') return { manager: 'pnpm', scope: 'global' }
|
|
113
|
+
if (anchor === 'global' && parts[nodeModulesIdx - 3] === 'pnpm') return { manager: 'pnpm', scope: 'global' }
|
|
73
114
|
}
|
|
74
115
|
|
|
75
116
|
if (nodeModulesIdx >= 2 && parts[nodeModulesIdx - 1] === 'global' && parts[nodeModulesIdx - 2] === 'yarn') {
|
|
76
|
-
return 'yarn'
|
|
117
|
+
return { manager: 'yarn', scope: 'global' }
|
|
77
118
|
}
|
|
78
119
|
|
|
79
|
-
if (parts[nodeModulesIdx - 1] === 'lib') return 'npm'
|
|
80
|
-
|
|
120
|
+
if (parts[nodeModulesIdx - 1] === 'lib') return { manager: 'npm', scope: 'global' }
|
|
121
|
+
|
|
122
|
+
const installRoot = installRootFrom(packageJsonPath)
|
|
123
|
+
if (installRoot === null) return null
|
|
124
|
+
return { manager: detectLocalManager(installRoot, fileExists), scope: 'local', installRoot }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function installRootFrom(packageJsonPath: string): string | null {
|
|
128
|
+
const sepMatch = packageJsonPath.match(/[\\/]/)
|
|
129
|
+
const sep = sepMatch?.[0] ?? '/'
|
|
130
|
+
const parts = packageJsonPath.split(/[\\/]+/)
|
|
131
|
+
const nodeModulesIdx = parts.lastIndexOf('node_modules')
|
|
132
|
+
if (nodeModulesIdx <= 0) return null
|
|
133
|
+
const head = parts.slice(0, nodeModulesIdx)
|
|
134
|
+
// Preserve a leading separator on POSIX paths (`/usr/lib/...` splits to
|
|
135
|
+
// `['', 'usr', 'lib', ...]`) and Windows drive prefixes (`C:\Users\...`
|
|
136
|
+
// splits to `['C:', 'Users', ...]`, no leading empty).
|
|
137
|
+
return head.length === 1 && head[0] === '' ? sep : head.join(sep)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Prefer the lockfile present in the install root. Probe order is bun -> pnpm
|
|
141
|
+
// -> yarn -> npm so a project with multiple lockfiles checked in (a real,
|
|
142
|
+
// gross-but-common scenario during migrations) lands on bun, matching this
|
|
143
|
+
// repo's default. The CLI's `--manager` flag always wins over this heuristic.
|
|
144
|
+
function detectLocalManager(installRoot: string, fileExists: (path: string) => boolean): UpdateManager {
|
|
145
|
+
if (fileExists(join(installRoot, 'bun.lock')) || fileExists(join(installRoot, 'bun.lockb'))) return 'bun'
|
|
146
|
+
if (fileExists(join(installRoot, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
147
|
+
if (fileExists(join(installRoot, 'yarn.lock'))) return 'yarn'
|
|
148
|
+
if (fileExists(join(installRoot, 'package-lock.json'))) return 'npm'
|
|
149
|
+
return 'bun'
|
|
81
150
|
}
|
|
82
151
|
|
|
83
152
|
function shellQuote(arg: string): string {
|