@action-llama/action-llama 0.1.1

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 (153) hide show
  1. package/README.md +448 -0
  2. package/dist/agents/container-entry.d.ts +2 -0
  3. package/dist/agents/container-entry.d.ts.map +1 -0
  4. package/dist/agents/container-entry.js +173 -0
  5. package/dist/agents/container-entry.js.map +1 -0
  6. package/dist/agents/container-runner.d.ts +20 -0
  7. package/dist/agents/container-runner.d.ts.map +1 -0
  8. package/dist/agents/container-runner.js +208 -0
  9. package/dist/agents/container-runner.js.map +1 -0
  10. package/dist/agents/definitions/dev/AGENTS.md +44 -0
  11. package/dist/agents/definitions/dev/config-definition.json +39 -0
  12. package/dist/agents/definitions/devops/AGENTS.md +33 -0
  13. package/dist/agents/definitions/devops/config-definition.json +37 -0
  14. package/dist/agents/definitions/loader.d.ts +18 -0
  15. package/dist/agents/definitions/loader.d.ts.map +1 -0
  16. package/dist/agents/definitions/loader.js +59 -0
  17. package/dist/agents/definitions/loader.js.map +1 -0
  18. package/dist/agents/definitions/reviewer/AGENTS.md +37 -0
  19. package/dist/agents/definitions/reviewer/config-definition.json +24 -0
  20. package/dist/agents/definitions/schema.d.ts +38 -0
  21. package/dist/agents/definitions/schema.d.ts.map +1 -0
  22. package/dist/agents/definitions/schema.js +97 -0
  23. package/dist/agents/definitions/schema.js.map +1 -0
  24. package/dist/agents/prompt.d.ts +6 -0
  25. package/dist/agents/prompt.d.ts.map +1 -0
  26. package/dist/agents/prompt.js +42 -0
  27. package/dist/agents/prompt.js.map +1 -0
  28. package/dist/agents/runner.d.ts +14 -0
  29. package/dist/agents/runner.d.ts.map +1 -0
  30. package/dist/agents/runner.js +148 -0
  31. package/dist/agents/runner.js.map +1 -0
  32. package/dist/broker/index.d.ts +16 -0
  33. package/dist/broker/index.d.ts.map +1 -0
  34. package/dist/broker/index.js +53 -0
  35. package/dist/broker/index.js.map +1 -0
  36. package/dist/broker/router.d.ts +13 -0
  37. package/dist/broker/router.d.ts.map +1 -0
  38. package/dist/broker/router.js +71 -0
  39. package/dist/broker/router.js.map +1 -0
  40. package/dist/broker/routes/shutdown.d.ts +4 -0
  41. package/dist/broker/routes/shutdown.d.ts.map +1 -0
  42. package/dist/broker/routes/shutdown.js +37 -0
  43. package/dist/broker/routes/shutdown.js.map +1 -0
  44. package/dist/broker/routes/webhooks.d.ts +5 -0
  45. package/dist/broker/routes/webhooks.d.ts.map +1 -0
  46. package/dist/broker/routes/webhooks.js +50 -0
  47. package/dist/broker/routes/webhooks.js.map +1 -0
  48. package/dist/cli/commands/agent/add.d.ts +5 -0
  49. package/dist/cli/commands/agent/add.d.ts.map +1 -0
  50. package/dist/cli/commands/agent/add.js +86 -0
  51. package/dist/cli/commands/agent/add.js.map +1 -0
  52. package/dist/cli/commands/init.d.ts +2 -0
  53. package/dist/cli/commands/init.d.ts.map +1 -0
  54. package/dist/cli/commands/init.js +75 -0
  55. package/dist/cli/commands/init.js.map +1 -0
  56. package/dist/cli/commands/logs.d.ts +7 -0
  57. package/dist/cli/commands/logs.d.ts.map +1 -0
  58. package/dist/cli/commands/logs.js +121 -0
  59. package/dist/cli/commands/logs.js.map +1 -0
  60. package/dist/cli/commands/start.d.ts +5 -0
  61. package/dist/cli/commands/start.d.ts.map +1 -0
  62. package/dist/cli/commands/start.js +44 -0
  63. package/dist/cli/commands/start.js.map +1 -0
  64. package/dist/cli/commands/status.d.ts +4 -0
  65. package/dist/cli/commands/status.d.ts.map +1 -0
  66. package/dist/cli/commands/status.js +52 -0
  67. package/dist/cli/commands/status.js.map +1 -0
  68. package/dist/cli/main.d.ts +3 -0
  69. package/dist/cli/main.d.ts.map +1 -0
  70. package/dist/cli/main.js +75 -0
  71. package/dist/cli/main.js.map +1 -0
  72. package/dist/docker/container.d.ts +19 -0
  73. package/dist/docker/container.d.ts.map +1 -0
  74. package/dist/docker/container.js +73 -0
  75. package/dist/docker/container.js.map +1 -0
  76. package/dist/docker/image.d.ts +4 -0
  77. package/dist/docker/image.d.ts.map +1 -0
  78. package/dist/docker/image.js +38 -0
  79. package/dist/docker/image.js.map +1 -0
  80. package/dist/docker/network.d.ts +5 -0
  81. package/dist/docker/network.d.ts.map +1 -0
  82. package/dist/docker/network.js +23 -0
  83. package/dist/docker/network.js.map +1 -0
  84. package/dist/scheduler/event-queue.d.ts +12 -0
  85. package/dist/scheduler/event-queue.d.ts.map +1 -0
  86. package/dist/scheduler/event-queue.js +12 -0
  87. package/dist/scheduler/event-queue.js.map +1 -0
  88. package/dist/scheduler/index.d.ts +19 -0
  89. package/dist/scheduler/index.d.ts.map +1 -0
  90. package/dist/scheduler/index.js +192 -0
  91. package/dist/scheduler/index.js.map +1 -0
  92. package/dist/scheduler/types.d.ts +14 -0
  93. package/dist/scheduler/types.d.ts.map +1 -0
  94. package/dist/scheduler/types.js +2 -0
  95. package/dist/scheduler/types.js.map +1 -0
  96. package/dist/setup/prompts.d.ts +52 -0
  97. package/dist/setup/prompts.d.ts.map +1 -0
  98. package/dist/setup/prompts.js +656 -0
  99. package/dist/setup/prompts.js.map +1 -0
  100. package/dist/setup/scaffold.d.ts +11 -0
  101. package/dist/setup/scaffold.d.ts.map +1 -0
  102. package/dist/setup/scaffold.js +70 -0
  103. package/dist/setup/scaffold.js.map +1 -0
  104. package/dist/setup/validators.d.ts +23 -0
  105. package/dist/setup/validators.d.ts.map +1 -0
  106. package/dist/setup/validators.js +61 -0
  107. package/dist/setup/validators.js.map +1 -0
  108. package/dist/shared/config.d.ts +40 -0
  109. package/dist/shared/config.d.ts.map +1 -0
  110. package/dist/shared/config.js +46 -0
  111. package/dist/shared/config.js.map +1 -0
  112. package/dist/shared/credentials.d.ts +4 -0
  113. package/dist/shared/credentials.d.ts.map +1 -0
  114. package/dist/shared/credentials.js +21 -0
  115. package/dist/shared/credentials.js.map +1 -0
  116. package/dist/shared/git.d.ts +3 -0
  117. package/dist/shared/git.d.ts.map +1 -0
  118. package/dist/shared/git.js +24 -0
  119. package/dist/shared/git.js.map +1 -0
  120. package/dist/shared/logger.d.ts +6 -0
  121. package/dist/shared/logger.d.ts.map +1 -0
  122. package/dist/shared/logger.js +47 -0
  123. package/dist/shared/logger.js.map +1 -0
  124. package/dist/shared/paths.d.ts +8 -0
  125. package/dist/shared/paths.d.ts.map +1 -0
  126. package/dist/shared/paths.js +20 -0
  127. package/dist/shared/paths.js.map +1 -0
  128. package/dist/tui/App.d.ts +5 -0
  129. package/dist/tui/App.d.ts.map +1 -0
  130. package/dist/tui/App.js +85 -0
  131. package/dist/tui/App.js.map +1 -0
  132. package/dist/tui/render.d.ts +5 -0
  133. package/dist/tui/render.d.ts.map +1 -0
  134. package/dist/tui/render.js +9 -0
  135. package/dist/tui/render.js.map +1 -0
  136. package/dist/tui/status-tracker.d.ts +39 -0
  137. package/dist/tui/status-tracker.d.ts.map +1 -0
  138. package/dist/tui/status-tracker.js +73 -0
  139. package/dist/tui/status-tracker.js.map +1 -0
  140. package/dist/webhooks/providers/github.d.ts +9 -0
  141. package/dist/webhooks/providers/github.d.ts.map +1 -0
  142. package/dist/webhooks/providers/github.js +169 -0
  143. package/dist/webhooks/providers/github.js.map +1 -0
  144. package/dist/webhooks/registry.d.ts +13 -0
  145. package/dist/webhooks/registry.d.ts.map +1 -0
  146. package/dist/webhooks/registry.js +82 -0
  147. package/dist/webhooks/registry.js.map +1 -0
  148. package/dist/webhooks/types.d.ts +49 -0
  149. package/dist/webhooks/types.d.ts.map +1 -0
  150. package/dist/webhooks/types.js +2 -0
  151. package/dist/webhooks/types.js.map +1 -0
  152. package/docker/Dockerfile +29 -0
  153. package/package.json +67 -0
@@ -0,0 +1,656 @@
1
+ import { input, select, checkbox, confirm } from "@inquirer/prompts";
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { validateGitHubToken, validateSentryToken, validateSentryProjects, validateAnthropicApiKey, validateOAuthTokenFormat } from "./validators.js";
5
+ import { loadCredential } from "../shared/credentials.js";
6
+ import { CREDENTIALS_DIR } from "../shared/paths.js";
7
+ import { listBuiltinDefinitions, loadDefinition } from "../agents/definitions/loader.js";
8
+ // --- Shared: configure a single agent from a definition ---
9
+ export async function configureAgent(definition, context) {
10
+ // Agent name
11
+ const name = await input({
12
+ message: "Agent name:",
13
+ default: definition.name,
14
+ validate: (v) => {
15
+ const trimmed = v.trim();
16
+ if (!trimmed)
17
+ return "Name is required";
18
+ if (context.existingAgentNames?.includes(trimmed))
19
+ return `Agent "${trimmed}" already exists`;
20
+ return true;
21
+ },
22
+ });
23
+ // Repos
24
+ const repoChoices = context.availableRepos.map((r) => ({
25
+ name: r.fullName,
26
+ value: r.fullName,
27
+ }));
28
+ const repos = await checkbox({
29
+ message: `Repos for ${name}:`,
30
+ choices: repoChoices,
31
+ validate: (v) => (v.length > 0 ? true : "Select at least one repo"),
32
+ });
33
+ // Credentials — handle required and optional
34
+ const credentials = [...definition.credentials.required];
35
+ let sentryToken;
36
+ let sentryOrg;
37
+ let sentryProjectSlugs = [];
38
+ for (const cred of definition.credentials.optional) {
39
+ if (cred === "sentry-token") {
40
+ const result = await promptSentryCredential();
41
+ sentryToken = result.sentryToken;
42
+ sentryOrg = result.sentryOrg;
43
+ sentryProjectSlugs = result.sentryProjectSlugs;
44
+ if (sentryToken) {
45
+ credentials.push("sentry-token");
46
+ }
47
+ }
48
+ }
49
+ // Params — prompt for each non-credential param; resolve credential params
50
+ const params = {};
51
+ for (const [key, paramDef] of Object.entries(definition.params)) {
52
+ // Credential-linked params are populated by the credential handler above
53
+ if (paramDef.credential === "sentry-token") {
54
+ if (key === "sentryOrg" && sentryOrg) {
55
+ params[key] = sentryOrg;
56
+ }
57
+ else if (key === "sentryProjects" && sentryProjectSlugs.length > 0) {
58
+ params[key] = sentryProjectSlugs;
59
+ }
60
+ continue;
61
+ }
62
+ // Resolve special defaults
63
+ let defaultValue = paramDef.default;
64
+ if (defaultValue === "$githubUser") {
65
+ defaultValue = context.githubUser;
66
+ }
67
+ if (paramDef.type === "string") {
68
+ const value = await input({
69
+ message: `${paramDef.description}:`,
70
+ default: defaultValue,
71
+ ...(paramDef.required ? { validate: (v) => v.trim().length > 0 ? true : `${key} is required` } : {}),
72
+ });
73
+ if (value.trim()) {
74
+ params[key] = value.trim();
75
+ }
76
+ }
77
+ else if (paramDef.type === "string[]") {
78
+ const value = await input({
79
+ message: `${paramDef.description} (comma-separated):`,
80
+ default: defaultValue,
81
+ });
82
+ if (value.trim()) {
83
+ params[key] = value.split(",").map((s) => s.trim()).filter(Boolean);
84
+ }
85
+ }
86
+ }
87
+ // Webhook trigger
88
+ const useWebhooks = await confirm({
89
+ message: `Listen for webhooks? (${definition.webhooks.description})`,
90
+ default: true,
91
+ });
92
+ let webhooks;
93
+ if (useWebhooks) {
94
+ const filter = buildWebhookFilter(definition, repos, params);
95
+ webhooks = { filters: [filter] };
96
+ }
97
+ // Schedule trigger
98
+ const useSchedule = await confirm({
99
+ message: "Also run on a schedule (polling)?",
100
+ default: !useWebhooks,
101
+ });
102
+ let schedule;
103
+ if (useSchedule) {
104
+ schedule = await input({
105
+ message: `${name} poll interval (cron):`,
106
+ default: definition.defaultSchedule,
107
+ });
108
+ }
109
+ // Select prompt based on trigger mode
110
+ const prompt = useWebhooks ? definition.prompts.webhook : definition.prompts.schedule;
111
+ // Webhook secret
112
+ let githubWebhookSecret;
113
+ if (useWebhooks) {
114
+ const existingSecret = loadCredential("github-webhook-secret");
115
+ if (!existingSecret) {
116
+ githubWebhookSecret = (await input({
117
+ message: "GitHub webhook secret (set this same value in your GitHub webhook settings):",
118
+ validate: (v) => (v.trim().length > 0 ? true : "Secret is required to verify webhook payloads"),
119
+ })).trim();
120
+ }
121
+ }
122
+ // Build agent config
123
+ const config = {
124
+ name,
125
+ credentials,
126
+ model: context.modelConfig,
127
+ prompt,
128
+ repos,
129
+ ...(schedule ? { schedule } : {}),
130
+ ...(webhooks ? { webhooks } : {}),
131
+ ...(Object.keys(params).length > 0 ? { params } : {}),
132
+ };
133
+ return {
134
+ agent: { name, template: definition.name, config },
135
+ secrets: { sentryToken, githubWebhookSecret },
136
+ usesWebhooks: useWebhooks,
137
+ };
138
+ }
139
+ // --- Build webhook filter from definition + params ---
140
+ function buildWebhookFilter(definition, repos, params) {
141
+ const filter = {
142
+ source: "github",
143
+ repos,
144
+ events: definition.webhooks.events,
145
+ actions: definition.webhooks.actions,
146
+ };
147
+ // Inject param values into filter via webhookFilter mappings
148
+ for (const [key, paramDef] of Object.entries(definition.params)) {
149
+ if (paramDef.webhookFilter && params[key] !== undefined) {
150
+ const value = params[key];
151
+ const field = paramDef.webhookFilter.field;
152
+ if (paramDef.webhookFilter.wrap === "array") {
153
+ filter[field] = [value];
154
+ }
155
+ else {
156
+ filter[field] = value;
157
+ }
158
+ }
159
+ }
160
+ return filter;
161
+ }
162
+ // --- Sentry credential handler ---
163
+ async function promptSentryCredential() {
164
+ let sentryToken;
165
+ let sentryOrg;
166
+ let sentryProjectSlugs = [];
167
+ const existingSentryToken = loadCredential("sentry-token");
168
+ if (existingSentryToken) {
169
+ const reuse = await confirm({
170
+ message: `Found existing Sentry token in ${CREDENTIALS_DIR}/sentry-token. Use it?`,
171
+ default: true,
172
+ });
173
+ if (reuse) {
174
+ sentryToken = existingSentryToken;
175
+ }
176
+ }
177
+ if (!sentryToken) {
178
+ const useSentry = await confirm({
179
+ message: "Configure Sentry integration?",
180
+ default: false,
181
+ });
182
+ if (useSentry) {
183
+ sentryToken = (await input({
184
+ message: "Sentry auth token:",
185
+ validate: (v) => (v.trim().length > 0 ? true : "Token is required"),
186
+ })).trim();
187
+ }
188
+ }
189
+ if (sentryToken) {
190
+ console.log("Validating Sentry token...");
191
+ try {
192
+ const { organizations } = await validateSentryToken(sentryToken);
193
+ if (organizations.length === 0)
194
+ throw new Error("No organizations found");
195
+ if (organizations.length === 1) {
196
+ sentryOrg = organizations[0].slug;
197
+ console.log(`Organization: ${sentryOrg}\n`);
198
+ }
199
+ else {
200
+ sentryOrg = await select({
201
+ message: "Select Sentry organization:",
202
+ choices: organizations.map((o) => ({ name: `${o.name} (${o.slug})`, value: o.slug })),
203
+ });
204
+ }
205
+ const { projects } = await validateSentryProjects(sentryToken, sentryOrg);
206
+ if (projects.length > 0) {
207
+ sentryProjectSlugs = await checkbox({
208
+ message: "Select Sentry projects to monitor:",
209
+ choices: projects.map((p) => ({ name: p.name, value: p.slug })),
210
+ });
211
+ }
212
+ }
213
+ catch (err) {
214
+ console.log(`Sentry validation failed: ${err.message}. Skipping Sentry.\n`);
215
+ sentryToken = undefined;
216
+ }
217
+ }
218
+ return { sentryToken, sentryOrg, sentryProjectSlugs };
219
+ }
220
+ // --- Full interactive setup (init command) ---
221
+ export async function runSetup() {
222
+ console.log("\n=== Action Llama — Setup ===\n");
223
+ // Step 1: Agent Selection
224
+ console.log("--- Step 1: Agents ---\n");
225
+ const builtinDefs = listBuiltinDefinitions();
226
+ const selectedDefNames = await checkbox({
227
+ message: "Which agents do you want to create?",
228
+ choices: builtinDefs.map((d) => ({
229
+ name: `${d.name} — ${d.label} (${d.description})`,
230
+ value: d.name,
231
+ })),
232
+ validate: (v) => (v.length > 0 ? true : "Select at least one agent"),
233
+ });
234
+ const selectedDefs = selectedDefNames.map((name) => loadDefinition(name));
235
+ // Collect all required/optional credentials from selected definitions
236
+ const allOptionalCredentials = new Set();
237
+ for (const def of selectedDefs) {
238
+ for (const cred of def.credentials.optional) {
239
+ allOptionalCredentials.add(cred);
240
+ }
241
+ }
242
+ // Step 2: Credentials
243
+ console.log("\n--- Step 2: Credentials ---\n");
244
+ // GitHub token (always required)
245
+ const existingGithubToken = loadCredential("github-token");
246
+ let githubToken;
247
+ if (existingGithubToken) {
248
+ const reuse = await confirm({
249
+ message: `Found existing GitHub token in ${CREDENTIALS_DIR}/github-token. Use it?`,
250
+ default: true,
251
+ });
252
+ if (reuse) {
253
+ githubToken = existingGithubToken;
254
+ }
255
+ else {
256
+ githubToken = (await input({
257
+ message: "GitHub Personal Access Token (needs repo, workflow scopes):",
258
+ validate: (v) => (v.trim().length > 0 ? true : "Token is required"),
259
+ })).trim();
260
+ }
261
+ }
262
+ else {
263
+ githubToken = (await input({
264
+ message: "GitHub Personal Access Token (needs repo, workflow scopes):",
265
+ validate: (v) => (v.trim().length > 0 ? true : "Token is required"),
266
+ })).trim();
267
+ }
268
+ console.log("Validating GitHub token...");
269
+ let githubUser;
270
+ let availableRepos;
271
+ try {
272
+ const result = await validateGitHubToken(githubToken);
273
+ githubUser = result.user;
274
+ availableRepos = result.repos;
275
+ console.log(`Authenticated as: ${githubUser} (${availableRepos.length} repos found)\n`);
276
+ }
277
+ catch (err) {
278
+ throw new Error(`GitHub validation failed: ${err.message}`);
279
+ }
280
+ // SSH key
281
+ console.log("--- Git SSH Key ---\n");
282
+ const existingSshKey = existsSync(resolve(CREDENTIALS_DIR, "id_rsa"));
283
+ let sshKey;
284
+ if (existingSshKey) {
285
+ const reuse = await confirm({
286
+ message: `Found existing SSH key in ${CREDENTIALS_DIR}/id_rsa. Use it?`,
287
+ default: true,
288
+ });
289
+ if (!reuse) {
290
+ sshKey = await promptSshKey();
291
+ }
292
+ }
293
+ else {
294
+ sshKey = await promptSshKey();
295
+ }
296
+ // Sentry token (only if any selected definition has it as optional)
297
+ let sentryToken;
298
+ let sentryOrg;
299
+ let sentryProjectSlugs = [];
300
+ if (allOptionalCredentials.has("sentry-token")) {
301
+ console.log("\n--- Sentry ---\n");
302
+ const existingSentryToken = loadCredential("sentry-token");
303
+ if (existingSentryToken) {
304
+ const reuse = await confirm({
305
+ message: `Found existing Sentry token in ${CREDENTIALS_DIR}/sentry-token. Use it?`,
306
+ default: true,
307
+ });
308
+ if (reuse) {
309
+ sentryToken = existingSentryToken;
310
+ }
311
+ }
312
+ if (!sentryToken) {
313
+ const useSentry = await confirm({
314
+ message: "Configure Sentry integration?",
315
+ default: false,
316
+ });
317
+ if (useSentry) {
318
+ sentryToken = (await input({
319
+ message: "Sentry auth token:",
320
+ validate: (v) => (v.trim().length > 0 ? true : "Token is required"),
321
+ })).trim();
322
+ }
323
+ }
324
+ if (sentryToken) {
325
+ console.log("Validating Sentry token...");
326
+ try {
327
+ const { organizations } = await validateSentryToken(sentryToken);
328
+ if (organizations.length === 0) {
329
+ throw new Error("No organizations found");
330
+ }
331
+ if (organizations.length === 1) {
332
+ sentryOrg = organizations[0].slug;
333
+ console.log(`Organization: ${sentryOrg}\n`);
334
+ }
335
+ else {
336
+ sentryOrg = await select({
337
+ message: "Select Sentry organization:",
338
+ choices: organizations.map((o) => ({ name: `${o.name} (${o.slug})`, value: o.slug })),
339
+ });
340
+ }
341
+ const { projects } = await validateSentryProjects(sentryToken, sentryOrg);
342
+ if (projects.length > 0) {
343
+ sentryProjectSlugs = await checkbox({
344
+ message: "Select Sentry projects to monitor:",
345
+ choices: projects.map((p) => ({ name: p.name, value: p.slug })),
346
+ });
347
+ }
348
+ }
349
+ catch (err) {
350
+ console.log(`Sentry validation failed: ${err.message}. Skipping Sentry.\n`);
351
+ sentryToken = undefined;
352
+ }
353
+ }
354
+ }
355
+ // Anthropic auth
356
+ console.log("\n--- Anthropic Auth ---\n");
357
+ const existingAnthropicKey = loadCredential("anthropic-key");
358
+ let authType;
359
+ let anthropicKey;
360
+ if (existingAnthropicKey) {
361
+ const reuse = await confirm({
362
+ message: `Found existing Anthropic credential in ${CREDENTIALS_DIR}/anthropic-key. Use it?`,
363
+ default: true,
364
+ });
365
+ if (reuse) {
366
+ anthropicKey = existingAnthropicKey;
367
+ authType = anthropicKey.includes("sk-ant-oat") ? "oauth_token" : "api_key";
368
+ console.log(`Using existing credential (detected type: ${authType}).\n`);
369
+ }
370
+ else {
371
+ ({ authType, anthropicKey } = await promptAnthropicAuth());
372
+ }
373
+ }
374
+ else {
375
+ ({ authType, anthropicKey } = await promptAnthropicAuth());
376
+ }
377
+ // Step 3: LLM defaults
378
+ console.log("\n--- Step 3: LLM Defaults ---\n");
379
+ const modelName = await select({
380
+ message: "Select model:",
381
+ choices: [
382
+ { name: "claude-sonnet-4-20250514 (recommended)", value: "claude-sonnet-4-20250514" },
383
+ { name: "claude-opus-4-20250514", value: "claude-opus-4-20250514" },
384
+ { name: "claude-haiku-3-5-20241022", value: "claude-haiku-3-5-20241022" },
385
+ ],
386
+ default: "claude-sonnet-4-20250514",
387
+ });
388
+ const thinkingLevel = await select({
389
+ message: "Thinking level:",
390
+ choices: [
391
+ { name: "off", value: "off" },
392
+ { name: "minimal", value: "minimal" },
393
+ { name: "low", value: "low" },
394
+ { name: "medium (recommended)", value: "medium" },
395
+ { name: "high", value: "high" },
396
+ ],
397
+ default: "medium",
398
+ });
399
+ const modelConfig = {
400
+ provider: "anthropic",
401
+ model: modelName,
402
+ thinkingLevel,
403
+ authType,
404
+ };
405
+ // Step 4: Configure each agent
406
+ console.log("\n--- Step 4: Configure Agents ---\n");
407
+ const agents = [];
408
+ let anyWebhooks = false;
409
+ let firstGithubWebhookSecret;
410
+ for (const def of selectedDefs) {
411
+ console.log(`\n --- Configure ${def.name} agent ---\n`);
412
+ // For init flow, we pre-populate sentry params from the top-level credential gathering
413
+ // so we skip the per-agent sentry prompt.
414
+ // We accomplish this by building params for credential-linked params here.
415
+ const prePopulatedParams = {};
416
+ for (const [key, paramDef] of Object.entries(def.params)) {
417
+ if (paramDef.credential === "sentry-token") {
418
+ if (key === "sentryOrg" && sentryOrg) {
419
+ prePopulatedParams[key] = sentryOrg;
420
+ }
421
+ else if (key === "sentryProjects" && sentryProjectSlugs.length > 0) {
422
+ prePopulatedParams[key] = sentryProjectSlugs;
423
+ }
424
+ }
425
+ }
426
+ // Use configureAgentInit for init flow (skips credential prompts done globally)
427
+ const result = await configureAgentInit(def, {
428
+ availableRepos,
429
+ githubUser,
430
+ modelConfig,
431
+ }, prePopulatedParams, sentryToken ? true : false);
432
+ if (result.usesWebhooks) {
433
+ anyWebhooks = true;
434
+ if (!firstGithubWebhookSecret) {
435
+ firstGithubWebhookSecret = result.secrets.githubWebhookSecret;
436
+ }
437
+ }
438
+ agents.push(result.agent);
439
+ }
440
+ // GitHub webhook secret (if any agent uses webhooks and not already prompted)
441
+ let githubWebhookSecret = firstGithubWebhookSecret;
442
+ if (anyWebhooks && !githubWebhookSecret) {
443
+ console.log("\n--- GitHub Webhook Secret ---\n");
444
+ const existingSecret = loadCredential("github-webhook-secret");
445
+ if (existingSecret) {
446
+ const reuse = await confirm({
447
+ message: `Found existing webhook secret in ${CREDENTIALS_DIR}/github-webhook-secret. Use it?`,
448
+ default: true,
449
+ });
450
+ if (reuse) {
451
+ githubWebhookSecret = existingSecret;
452
+ }
453
+ }
454
+ if (!githubWebhookSecret) {
455
+ githubWebhookSecret = (await input({
456
+ message: "GitHub webhook secret (set this same value in your GitHub webhook settings):",
457
+ validate: (v) => (v.trim().length > 0 ? true : "Secret is required to verify webhook payloads"),
458
+ })).trim();
459
+ }
460
+ }
461
+ // Build global config
462
+ const globalConfig = {};
463
+ if (githubWebhookSecret) {
464
+ globalConfig.webhooks = { githubSecretCredential: "github-webhook-secret" };
465
+ }
466
+ return {
467
+ globalConfig,
468
+ agents,
469
+ secrets: {
470
+ githubToken,
471
+ sentryToken,
472
+ anthropicKey,
473
+ sshKey,
474
+ githubWebhookSecret,
475
+ },
476
+ };
477
+ }
478
+ /**
479
+ * Init-flow variant: configures an agent from a definition, but skips optional
480
+ * credential prompts (those were handled globally in runSetup).
481
+ */
482
+ async function configureAgentInit(definition, context, prePopulatedParams, hasSentryToken) {
483
+ const name = await input({
484
+ message: `Agent name:`,
485
+ default: definition.name,
486
+ });
487
+ const repoChoices = context.availableRepos.map((r) => ({
488
+ name: r.fullName,
489
+ value: r.fullName,
490
+ }));
491
+ const repos = await checkbox({
492
+ message: `Repos for ${name}:`,
493
+ choices: repoChoices,
494
+ validate: (v) => (v.length > 0 ? true : "Select at least one repo"),
495
+ });
496
+ // Credentials from definition
497
+ const credentials = [...definition.credentials.required];
498
+ if (hasSentryToken && definition.credentials.optional.includes("sentry-token")) {
499
+ credentials.push("sentry-token");
500
+ }
501
+ // Params — prompt for non-credential params, use pre-populated for credential params
502
+ const params = { ...prePopulatedParams };
503
+ for (const [key, paramDef] of Object.entries(definition.params)) {
504
+ if (paramDef.credential)
505
+ continue; // Already handled
506
+ let defaultValue = paramDef.default;
507
+ if (defaultValue === "$githubUser") {
508
+ defaultValue = context.githubUser;
509
+ }
510
+ if (paramDef.type === "string") {
511
+ const value = await input({
512
+ message: `${paramDef.description}:`,
513
+ default: defaultValue,
514
+ ...(paramDef.required ? { validate: (v) => v.trim().length > 0 ? true : `${key} is required` } : {}),
515
+ });
516
+ if (value.trim()) {
517
+ params[key] = value.trim();
518
+ }
519
+ }
520
+ else if (paramDef.type === "string[]") {
521
+ const value = await input({
522
+ message: `${paramDef.description} (comma-separated):`,
523
+ default: defaultValue,
524
+ });
525
+ if (value.trim()) {
526
+ params[key] = value.split(",").map((s) => s.trim()).filter(Boolean);
527
+ }
528
+ }
529
+ }
530
+ // Webhook trigger
531
+ const useWebhooks = await confirm({
532
+ message: `Listen for webhooks? (${definition.webhooks.description})`,
533
+ default: true,
534
+ });
535
+ let webhooks;
536
+ if (useWebhooks) {
537
+ const filter = buildWebhookFilter(definition, repos, params);
538
+ webhooks = { filters: [filter] };
539
+ }
540
+ // Schedule trigger
541
+ const useSchedule = await confirm({
542
+ message: `Also run on a schedule (polling)?`,
543
+ default: !useWebhooks,
544
+ });
545
+ let schedule;
546
+ if (useSchedule) {
547
+ schedule = await input({
548
+ message: `${name} poll interval (cron):`,
549
+ default: definition.defaultSchedule,
550
+ });
551
+ }
552
+ const prompt = useWebhooks ? definition.prompts.webhook : definition.prompts.schedule;
553
+ // Build agent config
554
+ const config = {
555
+ name,
556
+ credentials,
557
+ model: context.modelConfig,
558
+ prompt,
559
+ repos,
560
+ ...(schedule ? { schedule } : {}),
561
+ ...(webhooks ? { webhooks } : {}),
562
+ ...(Object.keys(params).length > 0 ? { params } : {}),
563
+ };
564
+ return {
565
+ agent: { name, template: definition.name, config },
566
+ secrets: {},
567
+ usesWebhooks: useWebhooks,
568
+ };
569
+ }
570
+ // --- Add agent to existing project ---
571
+ export async function runAddAgent(opts) {
572
+ console.log("\n=== Action Llama — Add Agent ===\n");
573
+ const result = await configureAgent(opts.definition, {
574
+ availableRepos: opts.availableRepos,
575
+ githubUser: opts.githubUser,
576
+ modelConfig: opts.modelConfig,
577
+ existingAgentNames: opts.existingAgentNames,
578
+ });
579
+ return {
580
+ agent: result.agent,
581
+ secrets: result.secrets,
582
+ };
583
+ }
584
+ // --- SSH key prompt ---
585
+ async function promptSshKey() {
586
+ const defaultPath = resolve(process.env.HOME || "~", ".ssh", "id_rsa");
587
+ const keyPath = await input({
588
+ message: `Path to SSH private key for git operations (leave empty to use system default):`,
589
+ default: existsSync(defaultPath) ? defaultPath : "",
590
+ });
591
+ if (!keyPath.trim()) {
592
+ console.log("No SSH key configured — git will use your system SSH config.\n");
593
+ return undefined;
594
+ }
595
+ const resolvedPath = resolve(keyPath.trim());
596
+ if (!existsSync(resolvedPath)) {
597
+ throw new Error(`SSH key not found at ${resolvedPath}`);
598
+ }
599
+ const content = readFileSync(resolvedPath, "utf-8");
600
+ console.log("SSH key loaded.\n");
601
+ return content;
602
+ }
603
+ // --- Anthropic auth prompt ---
604
+ async function promptAnthropicAuth() {
605
+ const authMethod = await select({
606
+ message: "How do you want to authenticate with Anthropic?",
607
+ choices: [
608
+ { name: "Use existing pi auth (already ran `pi /login` or `claude setup-token`)", value: "pi_auth" },
609
+ { name: "Enter an API key (sk-ant-api...)", value: "api_key" },
610
+ { name: "Enter an OAuth token (sk-ant-oat...)", value: "oauth_token" },
611
+ ],
612
+ });
613
+ if (authMethod === "pi_auth") {
614
+ const { AuthStorage, ModelRegistry } = await import("@mariozechner/pi-coding-agent");
615
+ const authStorage = AuthStorage.create();
616
+ const registry = new ModelRegistry(authStorage);
617
+ const available = await registry.getAvailable();
618
+ const hasAnthropic = available.some((m) => m.provider === "anthropic");
619
+ if (!hasAnthropic) {
620
+ throw new Error("No Anthropic credentials found in pi auth storage (~/.pi/agent/auth.json). " +
621
+ "Run `pi /login` first, or choose a different auth method.");
622
+ }
623
+ console.log("Found existing Anthropic credentials in pi auth storage.\n");
624
+ return { authType: "pi_auth", anthropicKey: undefined };
625
+ }
626
+ else if (authMethod === "api_key") {
627
+ let anthropicKey = (await input({
628
+ message: "Anthropic API key:",
629
+ validate: (v) => (v.trim().length > 0 ? true : "Key is required"),
630
+ })).trim();
631
+ console.log("Validating API key...");
632
+ try {
633
+ await validateAnthropicApiKey(anthropicKey);
634
+ console.log("API key validated.\n");
635
+ }
636
+ catch (err) {
637
+ throw new Error(`Anthropic validation failed: ${err.message}`);
638
+ }
639
+ return { authType: "api_key", anthropicKey };
640
+ }
641
+ else {
642
+ let anthropicKey = (await input({
643
+ message: "Anthropic OAuth token (from `claude setup-token`):",
644
+ validate: (v) => (v.trim().length > 0 ? true : "Token is required"),
645
+ })).trim();
646
+ try {
647
+ validateOAuthTokenFormat(anthropicKey);
648
+ console.log("OAuth token format looks valid. It will be verified on first agent run.\n");
649
+ }
650
+ catch (err) {
651
+ throw new Error(err.message);
652
+ }
653
+ return { authType: "oauth_token", anthropicKey };
654
+ }
655
+ }
656
+ //# sourceMappingURL=prompts.js.map