-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathindex.ts
More file actions
214 lines (202 loc) · 6.85 KB
/
index.ts
File metadata and controls
214 lines (202 loc) · 6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import { BusEvent } from "@/bus/bus-event"
import { SessionID, MessageID } from "@/session/schema"
import z from "zod"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
import { Identifier } from "../id/id"
import PROMPT_INITIALIZE from "./template/initialize.txt"
import PROMPT_DISCOVER from "./template/discover.txt"
import PROMPT_REVIEW from "./template/review.txt"
import PROMPT_FEEDBACK from "./template/feedback.txt"
// altimate_change start — configure commands for external AI CLIs
import PROMPT_CONFIGURE_CLAUDE from "./template/configure-claude.txt"
import PROMPT_CONFIGURE_CODEX from "./template/configure-codex.txt"
// altimate_change end
import { MCP } from "../mcp"
import { Skill } from "../skill"
import { Log } from "../util/log"
export namespace Command {
export const Event = {
Executed: BusEvent.define(
"command.executed",
z.object({
name: z.string(),
sessionID: SessionID.zod,
arguments: z.string(),
messageID: MessageID.zod,
}),
),
}
export const Info = z
.object({
name: z.string(),
description: z.string().optional(),
agent: z.string().optional(),
model: z.string().optional(),
source: z.enum(["command", "mcp", "skill"]).optional(),
// workaround for zod not supporting async functions natively so we use getters
// https://zod.dev/v4/changelog?id=zfunction
template: z.promise(z.string()).or(z.string()),
subtask: z.boolean().optional(),
hints: z.array(z.string()),
})
.meta({
ref: "Command",
})
// for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it
export type Info = Omit<z.infer<typeof Info>, "template"> & { template: Promise<string> | string }
export function hints(template: string): string[] {
const result: string[] = []
const numbered = template.match(/\$\d+/g)
if (numbered) {
for (const match of [...new Set(numbered)].sort()) result.push(match)
}
if (template.includes("$ARGUMENTS")) result.push("$ARGUMENTS")
return result
}
export const Default = {
INIT: "init",
DISCOVER: "discover",
REVIEW: "review",
FEEDBACK: "feedback",
// altimate_change start
CONFIGURE_CLAUDE: "configure-claude",
CONFIGURE_CODEX: "configure-codex",
// altimate_change end
} as const
const state = Instance.state(async () => {
const cfg = await Config.get()
const result: Record<string, Info> = {
[Default.INIT]: {
name: Default.INIT,
description: "create/update AGENTS.md",
source: "command",
get template() {
return PROMPT_INITIALIZE.replace("${path}", Instance.worktree)
},
hints: hints(PROMPT_INITIALIZE),
},
[Default.DISCOVER]: {
name: Default.DISCOVER,
description: "scan data stack and set up connections",
source: "command",
get template() {
return PROMPT_DISCOVER
},
hints: hints(PROMPT_DISCOVER),
},
[Default.REVIEW]: {
name: Default.REVIEW,
description: "review changes [commit|branch|pr], defaults to uncommitted",
source: "command",
get template() {
return PROMPT_REVIEW.replace("${path}", Instance.worktree)
},
subtask: true,
hints: hints(PROMPT_REVIEW),
},
[Default.FEEDBACK]: {
name: Default.FEEDBACK,
description: "submit product feedback as a GitHub issue",
source: "command",
get template() {
return PROMPT_FEEDBACK
},
hints: hints(PROMPT_FEEDBACK),
},
// altimate_change start — configure commands for external AI CLIs
[Default.CONFIGURE_CLAUDE]: {
name: Default.CONFIGURE_CLAUDE,
description: "configure /altimate command in Claude Code",
source: "command",
get template() {
return PROMPT_CONFIGURE_CLAUDE
},
hints: hints(PROMPT_CONFIGURE_CLAUDE),
},
[Default.CONFIGURE_CODEX]: {
name: Default.CONFIGURE_CODEX,
description: "configure altimate skill in Codex CLI",
source: "command",
get template() {
return PROMPT_CONFIGURE_CODEX
},
hints: hints(PROMPT_CONFIGURE_CODEX),
},
// altimate_change end
}
for (const [name, command] of Object.entries(cfg.command ?? {})) {
result[name] = {
name,
agent: command.agent,
model: command.model,
description: command.description,
source: "command",
get template() {
return command.template
},
subtask: command.subtask,
hints: hints(command.template),
}
}
// MCP and skill loading must not prevent default commands from being served.
// Wrap each in try/catch so init, discover, review are always available.
// Note: MCP prompts can overwrite defaults (by name), but skills cannot
// (the `if (result[skill.name]) continue` guard preserves defaults over skills).
try {
for (const [name, prompt] of Object.entries(await MCP.prompts())) {
result[name] = {
name,
source: "mcp",
description: prompt.description,
get template() {
return MCP.getPrompt(
prompt.client,
prompt.name,
prompt.arguments
? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`]))
: {},
).then((template) => {
if (!template) throw new Error(`Failed to load MCP prompt: ${prompt.name}`)
return template.messages
.map((message) => (message.content.type === "text" ? message.content.text : ""))
.join("\n")
})
},
hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
}
}
} catch (e) {
Log.Default.warn("MCP prompt loading failed, continuing with defaults", {
error: e instanceof Error ? e.message : String(e),
})
}
// Add skills as invokable commands
try {
for (const skill of await Skill.all()) {
// Skip if a command with this name already exists
if (result[skill.name]) continue
result[skill.name] = {
name: skill.name,
description: skill.description,
source: "skill",
get template() {
return skill.content
},
hints: hints(skill.content),
}
}
} catch (e) {
Log.Default.warn("Skill loading failed, continuing with defaults", {
error: e instanceof Error ? e.message : String(e),
})
}
return result
})
export async function get(name: string) {
return state().then((x) => x[name])
}
export async function list() {
return state().then((x) => Object.values(x))
}
}