@action-llama/action-llama 0.1.4 → 0.2.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/README.md +63 -59
- package/dist/agents/container-entry.js +36 -9
- package/dist/agents/container-entry.js.map +1 -1
- package/dist/agents/prompt.d.ts.map +1 -1
- package/dist/agents/prompt.js +17 -6
- package/dist/agents/prompt.js.map +1 -1
- package/dist/cli/commands/new.d.ts.map +1 -1
- package/dist/cli/commands/new.js +5 -25
- package/dist/cli/commands/new.js.map +1 -1
- package/dist/cli/commands/{agent/add.d.ts → setup.d.ts} +1 -2
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +60 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +3 -0
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +1 -37
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/main.js +6 -9
- package/dist/cli/main.js.map +1 -1
- package/dist/credentials/builtins/anthropic-key.d.ts +4 -0
- package/dist/credentials/builtins/anthropic-key.d.ts.map +1 -0
- package/dist/credentials/builtins/anthropic-key.js +69 -0
- package/dist/credentials/builtins/anthropic-key.js.map +1 -0
- package/dist/credentials/builtins/github-token.d.ts +4 -0
- package/dist/credentials/builtins/github-token.d.ts.map +1 -0
- package/dist/credentials/builtins/github-token.js +19 -0
- package/dist/credentials/builtins/github-token.js.map +1 -0
- package/dist/credentials/builtins/github-webhook-secret.d.ts +4 -0
- package/dist/credentials/builtins/github-webhook-secret.d.ts.map +1 -0
- package/dist/credentials/builtins/github-webhook-secret.js +12 -0
- package/dist/credentials/builtins/github-webhook-secret.js.map +1 -0
- package/dist/credentials/builtins/id-rsa.d.ts +4 -0
- package/dist/credentials/builtins/id-rsa.d.ts.map +1 -0
- package/dist/credentials/builtins/id-rsa.js +67 -0
- package/dist/credentials/builtins/id-rsa.js.map +1 -0
- package/dist/credentials/builtins/index.d.ts +3 -0
- package/dist/credentials/builtins/index.d.ts.map +1 -0
- package/dist/credentials/builtins/index.js +15 -0
- package/dist/credentials/builtins/index.js.map +1 -0
- package/dist/credentials/builtins/sentry-client-secret.d.ts +4 -0
- package/dist/credentials/builtins/sentry-client-secret.d.ts.map +1 -0
- package/dist/credentials/builtins/sentry-client-secret.js +12 -0
- package/dist/credentials/builtins/sentry-client-secret.js.map +1 -0
- package/dist/credentials/builtins/sentry-token.d.ts +4 -0
- package/dist/credentials/builtins/sentry-token.d.ts.map +1 -0
- package/dist/credentials/builtins/sentry-token.js +72 -0
- package/dist/credentials/builtins/sentry-token.js.map +1 -0
- package/dist/credentials/prompter.d.ts +14 -0
- package/dist/credentials/prompter.d.ts.map +1 -0
- package/dist/credentials/prompter.js +65 -0
- package/dist/credentials/prompter.js.map +1 -0
- package/dist/credentials/registry.d.ts +16 -0
- package/dist/credentials/registry.d.ts.map +1 -0
- package/dist/credentials/registry.js +25 -0
- package/dist/credentials/registry.js.map +1 -0
- package/dist/credentials/schema.d.ts +23 -0
- package/dist/credentials/schema.d.ts.map +1 -0
- package/dist/credentials/schema.js +6 -0
- package/dist/credentials/schema.js.map +1 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +14 -4
- package/dist/scheduler/index.js.map +1 -1
- package/dist/setup/prompts.d.ts +1 -43
- package/dist/setup/prompts.d.ts.map +1 -1
- package/dist/setup/prompts.js +43 -605
- package/dist/setup/prompts.js.map +1 -1
- package/dist/setup/scaffold.d.ts +1 -2
- package/dist/setup/scaffold.d.ts.map +1 -1
- package/dist/setup/scaffold.js +62 -29
- package/dist/setup/scaffold.js.map +1 -1
- package/dist/shared/config.d.ts +1 -2
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +16 -6
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/credentials.d.ts +2 -0
- package/dist/shared/credentials.d.ts.map +1 -1
- package/dist/shared/credentials.js +17 -0
- package/dist/shared/credentials.js.map +1 -1
- package/dist/shared/paths.d.ts +0 -1
- package/dist/shared/paths.d.ts.map +1 -1
- package/dist/shared/paths.js +0 -3
- package/dist/shared/paths.js.map +1 -1
- package/dist/webhooks/definitions/github.d.ts +3 -0
- package/dist/webhooks/definitions/github.d.ts.map +1 -0
- package/dist/webhooks/definitions/github.js +38 -0
- package/dist/webhooks/definitions/github.js.map +1 -0
- package/dist/webhooks/definitions/registry.d.ts +4 -0
- package/dist/webhooks/definitions/registry.d.ts.map +1 -0
- package/dist/webhooks/definitions/registry.js +14 -0
- package/dist/webhooks/definitions/registry.js.map +1 -0
- package/dist/webhooks/definitions/schema.d.ts +19 -0
- package/dist/webhooks/definitions/schema.d.ts.map +1 -0
- package/dist/webhooks/definitions/schema.js +2 -0
- package/dist/webhooks/definitions/schema.js.map +1 -0
- package/dist/webhooks/definitions/sentry.d.ts +3 -0
- package/dist/webhooks/definitions/sentry.d.ts.map +1 -0
- package/dist/webhooks/definitions/sentry.js +22 -0
- package/dist/webhooks/definitions/sentry.js.map +1 -0
- package/dist/webhooks/providers/sentry.d.ts +9 -0
- package/dist/webhooks/providers/sentry.d.ts.map +1 -0
- package/dist/webhooks/providers/sentry.js +108 -0
- package/dist/webhooks/providers/sentry.js.map +1 -0
- package/dist/webhooks/registry.js +2 -2
- package/dist/webhooks/registry.js.map +1 -1
- package/dist/webhooks/types.d.ts +5 -1
- package/dist/webhooks/types.d.ts.map +1 -1
- package/package.json +4 -3
- package/dist/agents/definitions/dev/AGENTS.md +0 -44
- package/dist/agents/definitions/dev/config-definition.json +0 -39
- package/dist/agents/definitions/devops/AGENTS.md +0 -33
- package/dist/agents/definitions/devops/config-definition.json +0 -37
- package/dist/agents/definitions/loader.d.ts +0 -18
- package/dist/agents/definitions/loader.d.ts.map +0 -1
- package/dist/agents/definitions/loader.js +0 -59
- package/dist/agents/definitions/loader.js.map +0 -1
- package/dist/agents/definitions/reviewer/AGENTS.md +0 -37
- package/dist/agents/definitions/reviewer/config-definition.json +0 -24
- package/dist/agents/definitions/schema.d.ts +0 -38
- package/dist/agents/definitions/schema.d.ts.map +0 -1
- package/dist/agents/definitions/schema.js +0 -97
- package/dist/agents/definitions/schema.js.map +0 -1
- package/dist/cli/commands/agent/add.d.ts.map +0 -1
- package/dist/cli/commands/agent/add.js +0 -86
- package/dist/cli/commands/agent/add.js.map +0 -1
package/dist/setup/prompts.js
CHANGED
|
@@ -1,381 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
}
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import { validateGitHubToken } from "./validators.js";
|
|
3
|
+
import { writeCredential, writeStructuredCredential } from "../shared/credentials.js";
|
|
4
|
+
import { resolveCredential } from "../credentials/registry.js";
|
|
5
|
+
import { promptCredential } from "../credentials/prompter.js";
|
|
6
|
+
/**
|
|
7
|
+
* Write credential values to disk based on the definition's field count.
|
|
8
|
+
* Single-field: plain text (backward compatible). Multi-field: JSON.
|
|
9
|
+
*/
|
|
10
|
+
function writeCredentialValues(def, values) {
|
|
11
|
+
if (Object.keys(values).length === 0)
|
|
12
|
+
return; // e.g. pi_auth
|
|
13
|
+
if (def.fields.length === 1) {
|
|
14
|
+
const fieldName = def.fields[0].name;
|
|
15
|
+
writeCredential(def.filename, values[fieldName]);
|
|
121
16
|
}
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
}
|
|
17
|
+
else {
|
|
18
|
+
writeStructuredCredential(def.filename, values);
|
|
159
19
|
}
|
|
160
|
-
return filter;
|
|
161
20
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
|
|
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
|
-
}
|
|
21
|
+
/**
|
|
22
|
+
* Prompt for a credential and write it to disk.
|
|
23
|
+
* Returns the prompt result (values + optional params), or undefined if skipped.
|
|
24
|
+
*/
|
|
25
|
+
async function promptAndStoreCredential(def) {
|
|
26
|
+
const result = await promptCredential(def);
|
|
27
|
+
if (result && Object.keys(result.values).length > 0) {
|
|
28
|
+
writeCredentialValues(def, result.values);
|
|
217
29
|
}
|
|
218
|
-
return
|
|
30
|
+
return result;
|
|
219
31
|
}
|
|
220
32
|
// --- Full interactive setup (new command) ---
|
|
221
33
|
export async function runSetup() {
|
|
222
34
|
console.log("\n=== Action Llama — Setup ===\n");
|
|
223
|
-
// Step 1:
|
|
224
|
-
console.log("--- Step 1:
|
|
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");
|
|
35
|
+
// Step 1: Credentials
|
|
36
|
+
console.log("--- Step 1: Credentials ---\n");
|
|
244
37
|
// GitHub token (always required)
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
}
|
|
38
|
+
const githubTokenDef = resolveCredential("github-token");
|
|
39
|
+
const githubTokenResult = await promptAndStoreCredential(githubTokenDef);
|
|
40
|
+
if (!githubTokenResult)
|
|
41
|
+
throw new Error("GitHub token is required");
|
|
42
|
+
const githubToken = githubTokenResult.values.token;
|
|
268
43
|
console.log("Validating GitHub token...");
|
|
269
|
-
let githubUser;
|
|
270
|
-
let availableRepos;
|
|
271
44
|
try {
|
|
272
45
|
const result = await validateGitHubToken(githubToken);
|
|
273
|
-
|
|
274
|
-
availableRepos = result.repos;
|
|
275
|
-
console.log(`Authenticated as: ${githubUser} (${availableRepos.length} repos found)\n`);
|
|
46
|
+
console.log(`Authenticated as: ${result.user} (${result.repos.length} repos found)\n`);
|
|
276
47
|
}
|
|
277
48
|
catch (err) {
|
|
278
49
|
throw new Error(`GitHub validation failed: ${err.message}`);
|
|
279
50
|
}
|
|
280
51
|
// SSH key
|
|
281
52
|
console.log("--- Git SSH Key ---\n");
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
}
|
|
53
|
+
const sshKeyDef = resolveCredential("id_rsa");
|
|
54
|
+
const sshKeyResult = await promptAndStoreCredential(sshKeyDef);
|
|
55
|
+
const sshKey = sshKeyResult?.values.key;
|
|
355
56
|
// Anthropic auth
|
|
356
57
|
console.log("\n--- Anthropic Auth ---\n");
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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");
|
|
58
|
+
const anthropicDef = resolveCredential("anthropic-key");
|
|
59
|
+
const anthropicResult = await promptAndStoreCredential(anthropicDef);
|
|
60
|
+
const anthropicKey = anthropicResult?.values.token;
|
|
61
|
+
// Step 2: LLM defaults
|
|
62
|
+
console.log("\n--- Step 2: LLM Defaults ---\n");
|
|
379
63
|
const modelName = await select({
|
|
380
64
|
message: "Select model:",
|
|
381
65
|
choices: [
|
|
@@ -385,7 +69,7 @@ export async function runSetup() {
|
|
|
385
69
|
],
|
|
386
70
|
default: "claude-sonnet-4-20250514",
|
|
387
71
|
});
|
|
388
|
-
|
|
72
|
+
await select({
|
|
389
73
|
message: "Thinking level:",
|
|
390
74
|
choices: [
|
|
391
75
|
{ name: "off", value: "off" },
|
|
@@ -396,261 +80,15 @@ export async function runSetup() {
|
|
|
396
80
|
],
|
|
397
81
|
default: "medium",
|
|
398
82
|
});
|
|
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
83
|
// Build global config
|
|
462
84
|
const globalConfig = {};
|
|
463
|
-
if (githubWebhookSecret) {
|
|
464
|
-
globalConfig.webhooks = { githubSecretCredential: "github-webhook-secret" };
|
|
465
|
-
}
|
|
466
85
|
return {
|
|
467
86
|
globalConfig,
|
|
468
|
-
agents,
|
|
469
87
|
secrets: {
|
|
470
88
|
githubToken,
|
|
471
|
-
sentryToken,
|
|
472
89
|
anthropicKey,
|
|
473
90
|
sshKey,
|
|
474
|
-
githubWebhookSecret,
|
|
475
91
|
},
|
|
476
92
|
};
|
|
477
93
|
}
|
|
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
94
|
//# sourceMappingURL=prompts.js.map
|