@xelauvas/xela-cli 0.3.1 → 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/bin/xela.js +0 -12
- package/local/README.md +112 -0
- package/local/start-qwen.sh +47 -0
- package/local/start-tunnel.sh +36 -0
- package/local/xela-local.sh +139 -0
- package/package.json +9 -5
- package/run.sh +33 -0
- package/src/Tool.ts +1 -1
- package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
- package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
- package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
- package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
- package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
- package/src/_shims/bun_bundle.js +79 -4
- package/src/_shims/cjs_stub.cjs +32 -10
- package/src/_shims/loader.js +104 -5
- package/src/_shims/openai_adapter.ts +446 -230
- package/src/_shims/register.js +16 -3
- package/src/assistant/gate.ts +15 -0
- package/src/assistant/index.ts +89 -0
- package/src/bootstrap/state.ts +6 -1
- package/src/bridge/peerSessions.ts +18 -0
- package/src/cli/print.ts +5 -0
- package/src/commands/assistant/assistant.ts +17 -0
- package/src/commands/assistant/index.ts +11 -0
- package/src/commands/brief.ts +3 -1
- package/src/commands/buddy/buddy.ts +88 -0
- package/src/commands/buddy/index.ts +12 -0
- package/src/commands/model/model.tsx +8 -23
- package/src/commands/peers/index.ts +11 -0
- package/src/commands/peers/peers.ts +24 -0
- package/src/commands/proactive/proactive.ts +64 -0
- package/src/commands/proactive.ts +12 -0
- package/src/commands/workflows/index.ts +12 -0
- package/src/commands/workflows/workflows.ts +36 -0
- package/src/commands.ts +4 -8
- package/src/components/Messages.tsx +1 -1
- package/src/components/ModelPicker.tsx +4 -34
- package/src/components/Spinner.tsx +54 -1
- package/src/components/StructuredDiff/colorDiff.ts +3 -1
- package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
- package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
- package/src/components/permissions/PermissionRequest.tsx +4 -2
- package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
- package/src/constants/prompts.ts +26 -0
- package/src/ink/terminal.ts +9 -1
- package/src/jobs/classifier.ts +103 -0
- package/src/memdir/paths.ts +13 -1
- package/src/proactive/index.ts +158 -0
- package/src/proactive/useProactive.ts +78 -0
- package/src/screens/REPL.tsx +28 -8
- package/src/services/api/claude.ts +6 -1
- package/src/services/compact/autoCompact.ts +18 -4
- package/src/services/compact/cachedMCConfig.ts +1 -0
- package/src/services/compact/cachedMicrocompact.ts +113 -0
- package/src/services/compact/compact.ts +22 -2
- package/src/services/compact/reactiveCompact.ts +251 -0
- package/src/services/compact/snipCompact.ts +282 -0
- package/src/services/compact/snipProjection.ts +50 -0
- package/src/services/contextCollapse/index.ts +322 -0
- package/src/services/contextCollapse/operations.ts +187 -0
- package/src/services/contextCollapse/persist.ts +40 -0
- package/src/services/extractMemories/extractMemories.ts +11 -2
- package/src/services/skillSearch/featureCheck.ts +12 -0
- package/src/services/skillSearch/localSearch.ts +98 -0
- package/src/services/skillSearch/prefetch.ts +125 -0
- package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
- package/src/services/skillSearch/remoteSkillState.ts +21 -0
- package/src/services/skillSearch/signals.ts +7 -0
- package/src/services/skillSearch/telemetry.ts +9 -0
- package/src/setup.ts +10 -5
- package/src/skills/bundled/dream.ts +31 -0
- package/src/skills/bundled/hunter.ts +45 -0
- package/src/skills/bundled/index.ts +10 -23
- package/src/skills/bundled/runSkillGenerator.ts +61 -0
- package/src/skills/mcpSkills.ts +101 -0
- package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
- package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
- package/src/tasks.ts +22 -8
- package/src/tools/AgentTool/runAgent.ts +11 -0
- package/src/tools/BriefTool/BriefTool.ts +2 -0
- package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
- package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
- package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
- package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
- package/src/tools/MCPTool/MCPTool.ts +6 -0
- package/src/tools/MonitorTool/MonitorTool.ts +273 -0
- package/src/tools/MonitorTool/UI.tsx +25 -0
- package/src/tools/MonitorTool/events.ts +84 -0
- package/src/tools/MonitorTool/prompt.ts +45 -0
- package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
- package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
- package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
- package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
- package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
- package/src/tools/SendUserFileTool/prompt.ts +7 -0
- package/src/tools/SleepTool/SleepTool.ts +126 -0
- package/src/tools/SnipTool/SnipTool.ts +90 -0
- package/src/tools/SnipTool/prompt.ts +5 -0
- package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
- package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
- package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
- package/src/tools/ToolSearchTool/prompt.ts +1 -1
- package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
- package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
- package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
- package/src/tools/WebBrowserTool/prompt.ts +16 -0
- package/src/tools/WorkflowTool/UI.tsx +27 -0
- package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
- package/src/tools/WorkflowTool/bundled/index.ts +88 -0
- package/src/tools/WorkflowTool/constants.ts +7 -0
- package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
- package/src/tools/WorkflowTool/prompt.ts +38 -0
- package/src/tools/WorkflowTool/runtime.ts +331 -0
- package/src/tools.ts +36 -31
- package/src/utils/betas.ts +5 -0
- package/src/utils/context.ts +9 -0
- package/src/utils/conversationRecovery.ts +1 -1
- package/src/utils/forkedAgent.ts +4 -0
- package/src/utils/logoV2Utils.ts +7 -3
- package/src/utils/messageQueueManager.ts +23 -9
- package/src/utils/model/model.ts +10 -9
- package/src/utils/model/modelOptions.ts +0 -21
- package/src/utils/model/validateModel.ts +0 -19
- package/src/utils/permissions/permissionSetup.ts +5 -0
- package/src/utils/permissions/permissions.ts +25 -0
- package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
- package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
- package/src/utils/permissions/yoloClassifier.ts +28 -6
- package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
- package/src/utils/systemThemeWatcher.ts +62 -0
- package/src/utils/udsClient.ts +115 -0
- package/src/utils/udsMessaging.ts +157 -0
- package/src/utils/yaml.ts +251 -5
- package/src/utils/model/providerModels.ts +0 -168
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Command } from '../../commands.js'
|
|
2
|
+
import type { ToolUseContext } from '../../Tool.js'
|
|
3
|
+
import type { Message } from '../../types/message.js'
|
|
4
|
+
import type { Attachment } from '../../utils/attachments.js'
|
|
5
|
+
import { logForDebugging } from '../../utils/debug.js'
|
|
6
|
+
import { isSkillSearchEnabled } from './featureCheck.js'
|
|
7
|
+
import { searchSkills } from './localSearch.js'
|
|
8
|
+
import type { DiscoverySignal } from './signals.js'
|
|
9
|
+
|
|
10
|
+
export type PendingSkillPrefetch = {
|
|
11
|
+
promise: Promise<Attachment[]>
|
|
12
|
+
signal: DiscoverySignal
|
|
13
|
+
startedAt: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Skills already surfaced in this process, per agent, so suggestions are not repeated
|
|
17
|
+
const surfaced = new Map<string, Set<string>>()
|
|
18
|
+
|
|
19
|
+
function surfacedFor(context: ToolUseContext): Set<string> {
|
|
20
|
+
const key = (context as { agentId?: string }).agentId ?? 'main'
|
|
21
|
+
let set = surfaced.get(key)
|
|
22
|
+
if (!set) {
|
|
23
|
+
set = new Set()
|
|
24
|
+
surfaced.set(key, set)
|
|
25
|
+
}
|
|
26
|
+
return set
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function textOf(message: Message | undefined): string {
|
|
30
|
+
if (!message) return ''
|
|
31
|
+
const m = message as { type: string; message?: { content?: unknown } }
|
|
32
|
+
if (m.type !== 'user' && m.type !== 'assistant') return ''
|
|
33
|
+
const content = m.message?.content
|
|
34
|
+
if (typeof content === 'string') return content
|
|
35
|
+
if (!Array.isArray(content)) return ''
|
|
36
|
+
return (content as Array<{ type: string; text?: string; name?: string }>)
|
|
37
|
+
.map(b => (b.type === 'text' ? b.text ?? '' : b.type === 'tool_use' ? b.name ?? '' : ''))
|
|
38
|
+
.join(' ')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Skills that the listing already shows (bundled + MCP) are never "discovered". */
|
|
42
|
+
function discoverable(cmd: { loadedFrom?: string; source?: string }): boolean {
|
|
43
|
+
const from = cmd.loadedFrom ?? cmd.source
|
|
44
|
+
return from !== 'bundled' && from !== 'mcp' && from !== 'builtin'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function discover(
|
|
48
|
+
query: string,
|
|
49
|
+
signal: DiscoverySignal,
|
|
50
|
+
context: ToolUseContext,
|
|
51
|
+
): Promise<Attachment[]> {
|
|
52
|
+
if (!isSkillSearchEnabled()) return []
|
|
53
|
+
const text = query.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim()
|
|
54
|
+
if (text.length < 12) return []
|
|
55
|
+
// The session's command list travels on the tool-use context; loading it
|
|
56
|
+
// here would drag the whole command graph through the CJS require shim.
|
|
57
|
+
const all = (context.options as { commands?: Command[] } | undefined)?.commands ?? []
|
|
58
|
+
const commands = all.filter(discoverable)
|
|
59
|
+
if (commands.length === 0) {
|
|
60
|
+
logForDebugging('[skill-search] no commands on context; skipping discovery')
|
|
61
|
+
return []
|
|
62
|
+
}
|
|
63
|
+
logForDebugging(`[skill-search] ${signal}: ${commands.length} candidate skills, query "${text.slice(0, 80)}"`)
|
|
64
|
+
const already = surfacedFor(context)
|
|
65
|
+
const hits = searchSkills(text, commands, { limit: 4, exclude: already })
|
|
66
|
+
if (hits.length === 0) return []
|
|
67
|
+
for (const h of hits) {
|
|
68
|
+
already.add(h.name)
|
|
69
|
+
context.discoveredSkillNames?.add(h.name)
|
|
70
|
+
}
|
|
71
|
+
if (!context.discoveredSkillNames) {
|
|
72
|
+
;(context as { discoveredSkillNames?: Set<string> }).discoveredSkillNames = new Set(hits.map(h => h.name))
|
|
73
|
+
}
|
|
74
|
+
logForDebugging(`[skill-search] ${signal}: ${hits.map(h => `${h.name}(${h.score.toFixed(1)})`).join(', ')}`)
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
type: 'skill_discovery',
|
|
78
|
+
skills: hits.map(h => ({ name: h.name, description: h.description })),
|
|
79
|
+
signal,
|
|
80
|
+
source: 'native',
|
|
81
|
+
} as Attachment,
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Turn 0: the user's own words are the signal. Blocking, but the index is in-memory. */
|
|
86
|
+
export async function getTurnZeroSkillDiscovery(
|
|
87
|
+
input: string,
|
|
88
|
+
_messages: Message[],
|
|
89
|
+
context: ToolUseContext,
|
|
90
|
+
): Promise<Attachment[]> {
|
|
91
|
+
return discover(input, 'user_input', context)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Between turns: what the assistant just said and what the user asked
|
|
96
|
+
* before are the signal. Runs while the main turn is in flight; the result
|
|
97
|
+
* is collected by collectSkillDiscoveryPrefetch before the next model call.
|
|
98
|
+
*/
|
|
99
|
+
export function startSkillDiscoveryPrefetch(
|
|
100
|
+
signal: DiscoverySignal | null,
|
|
101
|
+
messages: Message[],
|
|
102
|
+
context: ToolUseContext,
|
|
103
|
+
): PendingSkillPrefetch | null {
|
|
104
|
+
if (!isSkillSearchEnabled()) return null
|
|
105
|
+
const lastAssistant = [...messages].reverse().find(m => m.type === 'assistant')
|
|
106
|
+
if (!lastAssistant) return null
|
|
107
|
+
const lastUser = [...messages].reverse().find(m => m.type === 'user' && !(m as { isMeta?: boolean }).isMeta)
|
|
108
|
+
const query = `${textOf(lastUser)} ${textOf(lastAssistant)}`.slice(0, 4000)
|
|
109
|
+
const effective: DiscoverySignal = signal ?? 'assistant_turn'
|
|
110
|
+
return {
|
|
111
|
+
promise: discover(query, effective, context).catch(() => []),
|
|
112
|
+
signal: effective,
|
|
113
|
+
startedAt: Date.now(),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function collectSkillDiscoveryPrefetch(
|
|
118
|
+
pending: PendingSkillPrefetch,
|
|
119
|
+
): Promise<Attachment[]> {
|
|
120
|
+
return pending.promise
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function resetSkillDiscoveryState(): void {
|
|
124
|
+
surfaced.clear()
|
|
125
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RemoteSkillMeta } from './remoteSkillState.js'
|
|
2
|
+
|
|
3
|
+
export type RemoteSkillLoadResult = { content: string; fromCache: boolean }
|
|
4
|
+
|
|
5
|
+
export async function loadRemoteSkill(meta: RemoteSkillMeta): Promise<RemoteSkillLoadResult> {
|
|
6
|
+
throw new Error(
|
|
7
|
+
`Remote skills are not available in this build (requested ${meta.slug}). Install the skill locally under .claude/skills or the config dir.`,
|
|
8
|
+
)
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote ("canonical") skills live on Anthropic infrastructure and are not
|
|
3
|
+
* reachable from a local build. The API is kept so SkillTool compiles and
|
|
4
|
+
* behaves: nothing is ever discovered, so the prefixed names are rejected
|
|
5
|
+
* with a clear message instead of a crash.
|
|
6
|
+
*/
|
|
7
|
+
export type RemoteSkillMeta = { slug: string; url: string; description?: string }
|
|
8
|
+
|
|
9
|
+
const CANONICAL_PREFIX = '_canonical_'
|
|
10
|
+
|
|
11
|
+
export function stripCanonicalPrefix(name: string): string | null {
|
|
12
|
+
return name.startsWith(CANONICAL_PREFIX) ? name.slice(CANONICAL_PREFIX.length) : null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getDiscoveredRemoteSkill(_slug: string): RemoteSkillMeta | undefined {
|
|
16
|
+
return undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function registerDiscoveredRemoteSkill(_meta: RemoteSkillMeta): void {}
|
|
20
|
+
|
|
21
|
+
export function clearDiscoveredRemoteSkills(): void {}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { logForDebugging } from '../../utils/debug.js'
|
|
2
|
+
|
|
3
|
+
export function logRemoteSkillLoaded(slug: string, details?: Record<string, unknown>): void {
|
|
4
|
+
logForDebugging(`[skill-search] remote skill loaded ${slug} ${details ? JSON.stringify(details) : ''}`)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function logSkillDiscovery(details: Record<string, unknown>): void {
|
|
8
|
+
logForDebugging(`[skill-search] ${JSON.stringify(details)}`)
|
|
9
|
+
}
|
package/src/setup.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable custom-rules/no-process-exit */
|
|
2
2
|
|
|
3
|
+
import { logForDebugging } from './utils/debug.js'
|
|
3
4
|
import { feature } from 'bun:bundle'
|
|
4
5
|
import chalk from 'chalk'
|
|
5
6
|
import {
|
|
@@ -93,11 +94,15 @@ export async function setup(
|
|
|
93
94
|
// and $CLAUDE_CODE_MESSAGING_SOCKET is exported before any hook
|
|
94
95
|
// (SessionStart in particular) can spawn and snapshot process.env.
|
|
95
96
|
if (feature('UDS_INBOX')) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
try {
|
|
98
|
+
const m = await import('./utils/udsMessaging.js')
|
|
99
|
+
await m.startUdsMessaging(
|
|
100
|
+
messagingSocketPath ?? m.getDefaultUdsSocketPath(),
|
|
101
|
+
{ isExplicit: messagingSocketPath !== undefined },
|
|
102
|
+
)
|
|
103
|
+
} catch (e) {
|
|
104
|
+
logForDebugging(`[uds] messaging server not started: ${String(e)}`)
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getAutoMemPath, isAutoMemoryEnabled } from '../../memdir/paths.js'
|
|
2
|
+
import { buildConsolidationPrompt } from '../../services/autoDream/consolidationPrompt.js'
|
|
3
|
+
import { getOriginalCwd } from '../../bootstrap/state.js'
|
|
4
|
+
import { getProjectDir } from '../../utils/sessionStorage.js'
|
|
5
|
+
import { registerBundledSkill } from '../bundledSkills.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* /dream: a manual memory-consolidation pass. Auto-dream runs the same
|
|
9
|
+
* prompt in a forked, read-only agent between sessions; this runs it in the
|
|
10
|
+
* main loop with normal permissions so the user can watch and steer.
|
|
11
|
+
*/
|
|
12
|
+
export function registerDreamSkill(): void {
|
|
13
|
+
registerBundledSkill({
|
|
14
|
+
name: 'dream',
|
|
15
|
+
description:
|
|
16
|
+
'Consolidate recent memory: distill daily logs and session learnings into well-organized memory files and refresh the index',
|
|
17
|
+
whenToUse:
|
|
18
|
+
'When the user asks to consolidate, clean up, reorganize or "dream" over memory, or when memory files have grown stale or duplicated.',
|
|
19
|
+
argumentHint: '[focus or extra instructions]',
|
|
20
|
+
userInvocable: true,
|
|
21
|
+
isEnabled: () => isAutoMemoryEnabled(),
|
|
22
|
+
async getPromptForCommand(args) {
|
|
23
|
+
const memoryRoot = getAutoMemPath()
|
|
24
|
+
const transcriptDir = getProjectDir(getOriginalCwd())
|
|
25
|
+
const extra = args?.trim()
|
|
26
|
+
? `\n\nFocus for this pass: ${args.trim()}`
|
|
27
|
+
: '\n\nThis is a manual pass started by the user; summarize what you changed when you are done.'
|
|
28
|
+
return [{ type: 'text', text: buildConsolidationPrompt(memoryRoot, transcriptDir, extra) }]
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { registerBundledSkill } from '../bundledSkills.js'
|
|
2
|
+
|
|
3
|
+
const HUNTER_PROMPT = `# Bug hunt {{scope}}
|
|
4
|
+
|
|
5
|
+
You are hunting for real defects in a set of changes and producing a review artifact the team can act on. Findings must be verified against the code, not guessed from the diff.
|
|
6
|
+
|
|
7
|
+
## Step 1: Establish the scope
|
|
8
|
+
- If a scope was given above, use it (a PR number via \`gh pr diff\`, a commit range, a branch, or a list of files).
|
|
9
|
+
- Otherwise use the working tree: \`git diff\` plus \`git diff --cached\`; if both are empty, use \`git diff origin/HEAD...HEAD\` or the last commit.
|
|
10
|
+
- List the changed files and skim the whole diff once before digging in.
|
|
11
|
+
|
|
12
|
+
## Step 2: Hunt
|
|
13
|
+
For each hunk, ask what could go wrong at runtime and check it by reading the surrounding code (callers, callees, types, tests), not only the diff:
|
|
14
|
+
- correctness: wrong conditions, off-by-one, null/undefined paths, error handling that swallows failures, async ordering, resource leaks
|
|
15
|
+
- contract breaks: changed signatures or return shapes with callers not updated, serialization or schema mismatches
|
|
16
|
+
- security: injection, path traversal, unsafe shell or SQL construction, secrets in logs
|
|
17
|
+
- concurrency and state: races, shared mutable state, missing cleanup
|
|
18
|
+
- tests: behaviour changed without a test, tests that cannot fail
|
|
19
|
+
Confirm each candidate with a concrete failure scenario (inputs or state, then the wrong outcome). Drop anything you cannot confirm or that is a style preference.
|
|
20
|
+
|
|
21
|
+
## Step 3: Write the artifact
|
|
22
|
+
Write a Markdown report to \`.claude/reviews/<YYYY-MM-DD>-hunter-<short-scope>.md\` (create the directory if needed) with:
|
|
23
|
+
1. Scope and how it was obtained
|
|
24
|
+
2. Findings, most severe first. For each: title, file:line, severity (blocker / high / medium / low), the failure scenario, and a suggested fix
|
|
25
|
+
3. Things checked and found fine (one line each), so nobody re-reviews them
|
|
26
|
+
4. Open questions that need the author
|
|
27
|
+
|
|
28
|
+
## Step 4: Report
|
|
29
|
+
Tell the user the path of the artifact and summarize the findings in a short list (one line per finding with file:line). If there are no confirmed findings, say so plainly and name what you checked.`
|
|
30
|
+
|
|
31
|
+
export function registerHunterSkill(): void {
|
|
32
|
+
registerBundledSkill({
|
|
33
|
+
name: 'hunter',
|
|
34
|
+
description:
|
|
35
|
+
'Hunts for verified bugs in a diff, PR or branch and writes a review artifact (Markdown report with findings, severities and fixes)',
|
|
36
|
+
whenToUse:
|
|
37
|
+
'When the user wants a thorough bug-focused review of changes with a written report, or asks to "hunt for bugs", "find what is wrong with this PR", or produce a review artifact.',
|
|
38
|
+
argumentHint: '[PR number | commit range | branch | files]',
|
|
39
|
+
userInvocable: true,
|
|
40
|
+
async getPromptForCommand(args) {
|
|
41
|
+
const scope = args?.trim() ? `\n\nScope requested: ${args.trim()}` : ''
|
|
42
|
+
return [{ type: 'text', text: HUNTER_PROMPT.replace('{{scope}}', scope) }]
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
}
|
|
@@ -11,6 +11,14 @@ import { registerSkillifySkill } from './skillify.js'
|
|
|
11
11
|
import { registerStuckSkill } from './stuck.js'
|
|
12
12
|
import { registerUpdateConfigSkill } from './updateConfig.js'
|
|
13
13
|
import { registerVerifySkill } from './verify.js'
|
|
14
|
+
// Feature-gated skills: imported statically (not via require) so they register
|
|
15
|
+
// in the same bundled-skill registry instance as everything else.
|
|
16
|
+
import { registerClaudeApiSkill } from './claudeApi.js'
|
|
17
|
+
import { registerDreamSkill } from './dream.js'
|
|
18
|
+
import { registerHunterSkill } from './hunter.js'
|
|
19
|
+
import { registerLoopSkill } from './loop.js'
|
|
20
|
+
import { registerRunSkillGeneratorSkill } from './runSkillGenerator.js'
|
|
21
|
+
import { registerScheduleRemoteAgentsSkill } from './scheduleRemoteAgents.js'
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
24
|
* Initialize all bundled skills.
|
|
@@ -33,47 +41,26 @@ export function initBundledSkills(): void {
|
|
|
33
41
|
registerBatchSkill()
|
|
34
42
|
registerStuckSkill()
|
|
35
43
|
if (feature('KAIROS') || feature('KAIROS_DREAM')) {
|
|
36
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
37
|
-
const { registerDreamSkill } = require('./dream.js')
|
|
38
|
-
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
39
44
|
registerDreamSkill()
|
|
40
45
|
}
|
|
41
46
|
if (feature('REVIEW_ARTIFACT')) {
|
|
42
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
43
|
-
const { registerHunterSkill } = require('./hunter.js')
|
|
44
|
-
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
45
47
|
registerHunterSkill()
|
|
46
48
|
}
|
|
47
49
|
if (feature('AGENT_TRIGGERS')) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
51
|
-
// /loop's isEnabled delegates to isKairosCronEnabled() — same lazy
|
|
52
|
-
// per-invocation pattern as the cron tools. Registered unconditionally;
|
|
53
|
-
// the skill's own isEnabled callback decides visibility.
|
|
50
|
+
// /loop's isEnabled delegates to isKairosCronEnabled(); registered
|
|
51
|
+
// unconditionally, the skill's own isEnabled decides visibility.
|
|
54
52
|
registerLoopSkill()
|
|
55
53
|
}
|
|
56
54
|
if (feature('AGENT_TRIGGERS_REMOTE')) {
|
|
57
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
58
|
-
const {
|
|
59
|
-
registerScheduleRemoteAgentsSkill,
|
|
60
|
-
} = require('./scheduleRemoteAgents.js')
|
|
61
|
-
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
62
55
|
registerScheduleRemoteAgentsSkill()
|
|
63
56
|
}
|
|
64
57
|
if (feature('BUILDING_CLAUDE_APPS')) {
|
|
65
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
66
|
-
const { registerClaudeApiSkill } = require('./claudeApi.js')
|
|
67
|
-
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
68
58
|
registerClaudeApiSkill()
|
|
69
59
|
}
|
|
70
60
|
if (shouldAutoEnableClaudeInChrome()) {
|
|
71
61
|
registerClaudeInChromeSkill()
|
|
72
62
|
}
|
|
73
63
|
if (feature('RUN_SKILL_GENERATOR')) {
|
|
74
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
75
|
-
const { registerRunSkillGeneratorSkill } = require('./runSkillGenerator.js')
|
|
76
|
-
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
77
64
|
registerRunSkillGeneratorSkill()
|
|
78
65
|
}
|
|
79
66
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'
|
|
2
|
+
import { registerBundledSkill } from '../bundledSkills.js'
|
|
3
|
+
|
|
4
|
+
const PROMPT = `# Skill generator {{request}}
|
|
5
|
+
|
|
6
|
+
You are creating a new reusable skill: a SKILL.md file that teaches a future session how to do one kind of task well.
|
|
7
|
+
|
|
8
|
+
## Step 1: Pin down the skill
|
|
9
|
+
From the request above (ask one short question if it is genuinely ambiguous):
|
|
10
|
+
- a kebab-case name (3 words max), e.g. \`release-notes\`
|
|
11
|
+
- a one-sentence description starting with "Use when ..." that says when the skill applies and what it produces
|
|
12
|
+
- the inputs it needs (arguments, files, environment) and the tools it should be allowed to use
|
|
13
|
+
|
|
14
|
+
## Step 2: Research before writing
|
|
15
|
+
Look at how the task is actually done in this repository or environment: existing scripts, conventions, commands that work, pitfalls visible in git history or docs. A skill that repeats generic advice is useless; capture the specific commands, paths and checks.
|
|
16
|
+
|
|
17
|
+
## Step 3: Write the skill
|
|
18
|
+
Create \`{{skillsDir}}/<name>/SKILL.md\` with this shape:
|
|
19
|
+
|
|
20
|
+
\`\`\`markdown
|
|
21
|
+
---
|
|
22
|
+
name: <name>
|
|
23
|
+
description: Use when <situation> — <what it does / produces>
|
|
24
|
+
argument-hint: <what to pass, if anything>
|
|
25
|
+
allowed-tools: <comma-separated tool names, only if it should be restricted>
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# <Title>
|
|
29
|
+
|
|
30
|
+
<One paragraph: purpose and when to use it.>
|
|
31
|
+
|
|
32
|
+
## Inputs
|
|
33
|
+
## Steps
|
|
34
|
+
<Numbered, concrete, with the exact commands and files. Include verification at the end of each major step.>
|
|
35
|
+
## Pitfalls
|
|
36
|
+
## Done when
|
|
37
|
+
<Observable completion criteria.>
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
Keep it under about 150 lines; move long reference material into sibling files in the same directory and link them by relative path.
|
|
41
|
+
|
|
42
|
+
## Step 4: Verify
|
|
43
|
+
Re-read the file as if you were a new session with no context. Fix anything that assumes knowledge the skill does not provide. Then tell the user the path, the name to invoke it with (\`/<name>\`), and a one-line summary.`
|
|
44
|
+
|
|
45
|
+
export function registerRunSkillGeneratorSkill(): void {
|
|
46
|
+
registerBundledSkill({
|
|
47
|
+
name: 'run-skill-generator',
|
|
48
|
+
aliases: ['skill-generator', 'generate-skill'],
|
|
49
|
+
description:
|
|
50
|
+
'Generates a new reusable skill (SKILL.md) for a task you describe, grounded in how the task is done in this environment',
|
|
51
|
+
whenToUse:
|
|
52
|
+
'When the user asks to create, generate or scaffold a new skill or slash command for a recurring task.',
|
|
53
|
+
argumentHint: '<what the skill should do>',
|
|
54
|
+
userInvocable: true,
|
|
55
|
+
async getPromptForCommand(args) {
|
|
56
|
+
const request = args?.trim() ? `\n\nRequest: ${args.trim()}` : ''
|
|
57
|
+
const skillsDir = `${getClaudeConfigHomeDir()}/skills`
|
|
58
|
+
return [{ type: 'text', text: PROMPT.replace('{{request}}', request).replace('{{skillsDir}}', skillsDir) }]
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Command } from '../commands.js'
|
|
2
|
+
import type { MCPServerConnection } from '../services/mcp/types.js'
|
|
3
|
+
import { logForDebugging } from '../utils/debug.js'
|
|
4
|
+
import { memoizeWithLRU } from '../utils/memoize.js'
|
|
5
|
+
import { parseYaml } from '../utils/yaml.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* MCP skills: an MCP server can publish skills as resources. Any resource
|
|
9
|
+
* whose URI uses the `skill://` scheme, or whose URI/name ends in SKILL.md,
|
|
10
|
+
* is read, its frontmatter parsed, and exposed as a prompt command named
|
|
11
|
+
* `mcp__<server>__<skill>` next to the server's prompts.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type Resource = { uri: string; name?: string; description?: string; mimeType?: string }
|
|
15
|
+
|
|
16
|
+
function isSkillResource(r: Resource): boolean {
|
|
17
|
+
const uri = r.uri || ''
|
|
18
|
+
return uri.startsWith('skill://') || /SKILL\.md$/i.test(uri) || /SKILL\.md$/i.test(r.name || '')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function splitFrontmatter(raw: string): { meta: Record<string, unknown>; body: string } {
|
|
22
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw)
|
|
23
|
+
if (!m) return { meta: {}, body: raw }
|
|
24
|
+
let meta: Record<string, unknown> = {}
|
|
25
|
+
try {
|
|
26
|
+
const parsed = parseYaml(m[1]!)
|
|
27
|
+
if (parsed && typeof parsed === 'object') meta = parsed as Record<string, unknown>
|
|
28
|
+
} catch {
|
|
29
|
+
// keep going with the body only
|
|
30
|
+
}
|
|
31
|
+
return { meta, body: raw.slice(m[0].length) }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeName(s: string): string {
|
|
35
|
+
return s.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function skillNameFromResource(r: Resource, meta: Record<string, unknown>): string {
|
|
39
|
+
if (typeof meta.name === 'string' && meta.name.trim()) return normalizeName(meta.name.trim())
|
|
40
|
+
const uri = r.uri.replace(/^skill:\/\//, '')
|
|
41
|
+
const parts = uri.split('/').filter(Boolean)
|
|
42
|
+
const last = parts[parts.length - 1] || ''
|
|
43
|
+
const base = /SKILL\.md$/i.test(last) ? parts[parts.length - 2] || 'skill' : last
|
|
44
|
+
return normalizeName(base)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const fetchMcpSkillsForClient = memoizeWithLRU(
|
|
48
|
+
async (client: MCPServerConnection): Promise<Command[]> => {
|
|
49
|
+
if (client.type !== 'connected') return []
|
|
50
|
+
if (!client.capabilities?.resources) return []
|
|
51
|
+
try {
|
|
52
|
+
const listed = (await client.client.listResources()) as { resources?: Resource[] }
|
|
53
|
+
const skills = (listed.resources || []).filter(isSkillResource)
|
|
54
|
+
if (skills.length === 0) return []
|
|
55
|
+
const commands: Command[] = []
|
|
56
|
+
for (const r of skills) {
|
|
57
|
+
try {
|
|
58
|
+
const read = (await client.client.readResource({ uri: r.uri })) as {
|
|
59
|
+
contents?: Array<{ text?: string; blob?: string }>
|
|
60
|
+
}
|
|
61
|
+
const text = (read.contents || []).map(c => c.text || '').join('\n').trim()
|
|
62
|
+
if (!text) continue
|
|
63
|
+
const { meta, body } = splitFrontmatter(text)
|
|
64
|
+
const skillName = skillNameFromResource(r, meta)
|
|
65
|
+
const description =
|
|
66
|
+
(typeof meta.description === 'string' && meta.description) || r.description || `Skill from ${client.name}`
|
|
67
|
+
commands.push({
|
|
68
|
+
type: 'prompt',
|
|
69
|
+
name: `mcp__${normalizeName(client.name)}__${skillName}`,
|
|
70
|
+
description,
|
|
71
|
+
hasUserSpecifiedDescription: true,
|
|
72
|
+
contentLength: body.length,
|
|
73
|
+
isEnabled: () => true,
|
|
74
|
+
isHidden: false,
|
|
75
|
+
isMcp: true,
|
|
76
|
+
progressMessage: 'running',
|
|
77
|
+
userFacingName() {
|
|
78
|
+
return `${client.name}:${skillName} (MCP skill)`
|
|
79
|
+
},
|
|
80
|
+
argNames: [],
|
|
81
|
+
source: 'mcp',
|
|
82
|
+
whenToUse: typeof meta['when-to-use'] === 'string' ? (meta['when-to-use'] as string) : undefined,
|
|
83
|
+
async getPromptForCommand(args: string) {
|
|
84
|
+
const prompt = args?.trim() ? `${body}\n\n## Arguments\n\n${args.trim()}` : body
|
|
85
|
+
return [{ type: 'text', text: prompt }]
|
|
86
|
+
},
|
|
87
|
+
} as unknown as Command)
|
|
88
|
+
} catch (e) {
|
|
89
|
+
logForDebugging(`[mcpSkills] ${client.name}: cannot read ${r.uri}: ${String(e)}`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
logForDebugging(`[mcpSkills] ${client.name}: ${commands.length} skill(s)`)
|
|
93
|
+
return commands
|
|
94
|
+
} catch (e) {
|
|
95
|
+
logForDebugging(`[mcpSkills] ${client.name}: resources/list failed: ${String(e)}`)
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
(client: MCPServerConnection) => client.name,
|
|
100
|
+
50,
|
|
101
|
+
)
|