@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.
Files changed (137) hide show
  1. package/bin/xela.js +0 -12
  2. package/local/README.md +112 -0
  3. package/local/start-qwen.sh +47 -0
  4. package/local/start-tunnel.sh +36 -0
  5. package/local/xela-local.sh +139 -0
  6. package/package.json +9 -5
  7. package/run.sh +33 -0
  8. package/src/Tool.ts +1 -1
  9. package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
  10. package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
  11. package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
  12. package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
  13. package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
  14. package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
  15. package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
  16. package/src/_shims/bun_bundle.js +79 -4
  17. package/src/_shims/cjs_stub.cjs +32 -10
  18. package/src/_shims/loader.js +104 -5
  19. package/src/_shims/openai_adapter.ts +446 -230
  20. package/src/_shims/register.js +16 -3
  21. package/src/assistant/gate.ts +15 -0
  22. package/src/assistant/index.ts +89 -0
  23. package/src/bootstrap/state.ts +6 -1
  24. package/src/bridge/peerSessions.ts +18 -0
  25. package/src/cli/print.ts +5 -0
  26. package/src/commands/assistant/assistant.ts +17 -0
  27. package/src/commands/assistant/index.ts +11 -0
  28. package/src/commands/brief.ts +3 -1
  29. package/src/commands/buddy/buddy.ts +88 -0
  30. package/src/commands/buddy/index.ts +12 -0
  31. package/src/commands/model/model.tsx +8 -23
  32. package/src/commands/peers/index.ts +11 -0
  33. package/src/commands/peers/peers.ts +24 -0
  34. package/src/commands/proactive/proactive.ts +64 -0
  35. package/src/commands/proactive.ts +12 -0
  36. package/src/commands/workflows/index.ts +12 -0
  37. package/src/commands/workflows/workflows.ts +36 -0
  38. package/src/commands.ts +4 -8
  39. package/src/components/Messages.tsx +1 -1
  40. package/src/components/ModelPicker.tsx +4 -34
  41. package/src/components/Spinner.tsx +54 -1
  42. package/src/components/StructuredDiff/colorDiff.ts +3 -1
  43. package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
  44. package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
  45. package/src/components/permissions/PermissionRequest.tsx +4 -2
  46. package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
  47. package/src/constants/prompts.ts +26 -0
  48. package/src/ink/terminal.ts +9 -1
  49. package/src/jobs/classifier.ts +103 -0
  50. package/src/memdir/paths.ts +13 -1
  51. package/src/proactive/index.ts +158 -0
  52. package/src/proactive/useProactive.ts +78 -0
  53. package/src/screens/REPL.tsx +28 -8
  54. package/src/services/api/claude.ts +6 -1
  55. package/src/services/compact/autoCompact.ts +18 -4
  56. package/src/services/compact/cachedMCConfig.ts +1 -0
  57. package/src/services/compact/cachedMicrocompact.ts +113 -0
  58. package/src/services/compact/compact.ts +22 -2
  59. package/src/services/compact/reactiveCompact.ts +251 -0
  60. package/src/services/compact/snipCompact.ts +282 -0
  61. package/src/services/compact/snipProjection.ts +50 -0
  62. package/src/services/contextCollapse/index.ts +322 -0
  63. package/src/services/contextCollapse/operations.ts +187 -0
  64. package/src/services/contextCollapse/persist.ts +40 -0
  65. package/src/services/extractMemories/extractMemories.ts +11 -2
  66. package/src/services/skillSearch/featureCheck.ts +12 -0
  67. package/src/services/skillSearch/localSearch.ts +98 -0
  68. package/src/services/skillSearch/prefetch.ts +125 -0
  69. package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
  70. package/src/services/skillSearch/remoteSkillState.ts +21 -0
  71. package/src/services/skillSearch/signals.ts +7 -0
  72. package/src/services/skillSearch/telemetry.ts +9 -0
  73. package/src/setup.ts +10 -5
  74. package/src/skills/bundled/dream.ts +31 -0
  75. package/src/skills/bundled/hunter.ts +45 -0
  76. package/src/skills/bundled/index.ts +10 -23
  77. package/src/skills/bundled/runSkillGenerator.ts +61 -0
  78. package/src/skills/mcpSkills.ts +101 -0
  79. package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
  80. package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
  81. package/src/tasks.ts +22 -8
  82. package/src/tools/AgentTool/runAgent.ts +11 -0
  83. package/src/tools/BriefTool/BriefTool.ts +2 -0
  84. package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
  85. package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
  86. package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
  87. package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
  88. package/src/tools/MCPTool/MCPTool.ts +6 -0
  89. package/src/tools/MonitorTool/MonitorTool.ts +273 -0
  90. package/src/tools/MonitorTool/UI.tsx +25 -0
  91. package/src/tools/MonitorTool/events.ts +84 -0
  92. package/src/tools/MonitorTool/prompt.ts +45 -0
  93. package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
  94. package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
  95. package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
  96. package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
  97. package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
  98. package/src/tools/SendUserFileTool/prompt.ts +7 -0
  99. package/src/tools/SleepTool/SleepTool.ts +126 -0
  100. package/src/tools/SnipTool/SnipTool.ts +90 -0
  101. package/src/tools/SnipTool/prompt.ts +5 -0
  102. package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
  103. package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
  104. package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
  105. package/src/tools/ToolSearchTool/prompt.ts +1 -1
  106. package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
  107. package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
  108. package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
  109. package/src/tools/WebBrowserTool/prompt.ts +16 -0
  110. package/src/tools/WorkflowTool/UI.tsx +27 -0
  111. package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
  112. package/src/tools/WorkflowTool/bundled/index.ts +88 -0
  113. package/src/tools/WorkflowTool/constants.ts +7 -0
  114. package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
  115. package/src/tools/WorkflowTool/prompt.ts +38 -0
  116. package/src/tools/WorkflowTool/runtime.ts +331 -0
  117. package/src/tools.ts +36 -31
  118. package/src/utils/betas.ts +5 -0
  119. package/src/utils/context.ts +9 -0
  120. package/src/utils/conversationRecovery.ts +1 -1
  121. package/src/utils/forkedAgent.ts +4 -0
  122. package/src/utils/logoV2Utils.ts +7 -3
  123. package/src/utils/messageQueueManager.ts +23 -9
  124. package/src/utils/model/model.ts +10 -9
  125. package/src/utils/model/modelOptions.ts +0 -21
  126. package/src/utils/model/validateModel.ts +0 -19
  127. package/src/utils/permissions/permissionSetup.ts +5 -0
  128. package/src/utils/permissions/permissions.ts +25 -0
  129. package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
  130. package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
  131. package/src/utils/permissions/yoloClassifier.ts +28 -6
  132. package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
  133. package/src/utils/systemThemeWatcher.ts +62 -0
  134. package/src/utils/udsClient.ts +115 -0
  135. package/src/utils/udsMessaging.ts +157 -0
  136. package/src/utils/yaml.ts +251 -5
  137. 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,7 @@
1
+ /** What triggered a skill discovery pass. */
2
+ export type DiscoverySignal =
3
+ | 'user_input'
4
+ | 'assistant_turn'
5
+ | 'subagent_spawn'
6
+ | 'tool_call'
7
+ | 'explicit'
@@ -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
- const m = await import('./utils/udsMessaging.js')
97
- await m.startUdsMessaging(
98
- messagingSocketPath ?? m.getDefaultUdsSocketPath(),
99
- { isExplicit: messagingSocketPath !== undefined },
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
- /* eslint-disable @typescript-eslint/no-require-imports */
49
- const { registerLoopSkill } = require('./loop.js')
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
+ )