@sweny-ai/core 0.1.51 → 0.1.52
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/dist/__tests__/publish.test.d.ts +1 -0
- package/dist/__tests__/publish.test.js +146 -0
- package/dist/__tests__/schema.test.js +156 -1
- package/dist/__tests__/skills.test.js +135 -4
- package/dist/__tests__/templates.test.d.ts +1 -0
- package/dist/__tests__/templates.test.js +49 -0
- package/dist/__tests__/version-consistency.test.js +2 -2
- package/dist/cli/init.d.ts +27 -4
- package/dist/cli/init.js +158 -197
- package/dist/cli/init.test.js +127 -78
- package/dist/cli/main.js +2 -0
- package/dist/cli/publish.d.ts +34 -0
- package/dist/cli/publish.js +368 -0
- package/dist/cli/templates.d.ts +13 -0
- package/dist/cli/templates.js +181 -0
- package/dist/executor.js +63 -20
- package/dist/executor.test.js +233 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +8 -0
- package/dist/mcp.test.js +67 -0
- package/dist/schema.d.ts +428 -18
- package/dist/schema.js +71 -4
- package/dist/skills/custom-loader.d.ts +12 -12
- package/dist/skills/custom-loader.js +105 -50
- package/dist/types.d.ts +18 -1
- package/dist/workflows/browser.js +3 -3
- package/package.json +6 -2
package/dist/cli/init.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sweny init — Interactive setup wizard
|
|
3
3
|
*
|
|
4
|
+
* Workflow-first: pick a template or describe what you want, then SWEny
|
|
5
|
+
* infers which skills are needed and only asks for those credentials.
|
|
6
|
+
*
|
|
4
7
|
* Pure functions for file generation + thin @clack/prompts interactive layer.
|
|
5
8
|
* Tests cover the pure functions; the interactive wizard is thin glue.
|
|
6
9
|
*/
|
|
@@ -8,8 +11,13 @@ import * as fs from "node:fs";
|
|
|
8
11
|
import * as path from "node:path";
|
|
9
12
|
import * as p from "@clack/prompts";
|
|
10
13
|
import chalk from "chalk";
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
import { WORKFLOW_TEMPLATES } from "./templates.js";
|
|
15
|
+
// ── Skill → Credential mapping ────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Maps skill IDs (as used in workflow nodes) to the credentials they require.
|
|
18
|
+
* When a workflow uses a skill, we collect only those credentials.
|
|
19
|
+
*/
|
|
20
|
+
export const SKILL_CREDENTIALS = {
|
|
13
21
|
github: [
|
|
14
22
|
{
|
|
15
23
|
key: "GITHUB_TOKEN",
|
|
@@ -48,12 +56,6 @@ export const PROVIDER_CREDENTIALS = {
|
|
|
48
56
|
},
|
|
49
57
|
],
|
|
50
58
|
newrelic: [{ key: "NR_API_KEY", url: "https://one.newrelic.com/api-keys" }],
|
|
51
|
-
cloudwatch: [
|
|
52
|
-
{ key: "AWS_ACCESS_KEY_ID", hint: "IAM user access key" },
|
|
53
|
-
{ key: "AWS_SECRET_ACCESS_KEY", hint: "IAM user secret key" },
|
|
54
|
-
{ key: "AWS_REGION", hint: "e.g. us-east-1", default: "us-east-1" },
|
|
55
|
-
],
|
|
56
|
-
"github-issues": [],
|
|
57
59
|
linear: [
|
|
58
60
|
{
|
|
59
61
|
key: "LINEAR_API_KEY",
|
|
@@ -72,11 +74,9 @@ export const PROVIDER_CREDENTIALS = {
|
|
|
72
74
|
url: "https://id.atlassian.com/manage-profile/security/api-tokens",
|
|
73
75
|
},
|
|
74
76
|
],
|
|
75
|
-
console: [],
|
|
76
77
|
slack: [{ key: "SLACK_BOT_TOKEN", url: "https://api.slack.com/apps" }],
|
|
77
78
|
discord: [{ key: "DISCORD_WEBHOOK_URL", hint: "Server Settings > Integrations > Webhooks" }],
|
|
78
|
-
|
|
79
|
-
webhook: [{ key: "NOTIFICATION_WEBHOOK_URL", hint: "Your webhook endpoint URL" }],
|
|
79
|
+
notification: [{ key: "NOTIFICATION_WEBHOOK_URL", hint: "Your webhook endpoint URL" }],
|
|
80
80
|
};
|
|
81
81
|
const ALWAYS_CREDENTIALS = [
|
|
82
82
|
{
|
|
@@ -85,14 +85,63 @@ const ALWAYS_CREDENTIALS = [
|
|
|
85
85
|
hint: "Claude API key",
|
|
86
86
|
},
|
|
87
87
|
];
|
|
88
|
+
// ── Backward compat alias ─────────────────────────────────────────────
|
|
89
|
+
// Old tests/code may reference PROVIDER_CREDENTIALS
|
|
90
|
+
export const PROVIDER_CREDENTIALS = {
|
|
91
|
+
...SKILL_CREDENTIALS,
|
|
92
|
+
"github-issues": [],
|
|
93
|
+
cloudwatch: [
|
|
94
|
+
{ key: "AWS_ACCESS_KEY_ID", hint: "IAM user access key" },
|
|
95
|
+
{ key: "AWS_SECRET_ACCESS_KEY", hint: "IAM user secret key" },
|
|
96
|
+
{ key: "AWS_REGION", hint: "e.g. us-east-1", default: "us-east-1" },
|
|
97
|
+
],
|
|
98
|
+
console: [],
|
|
99
|
+
teams: [{ key: "TEAMS_WEBHOOK_URL", hint: "Channel > Connectors > Incoming Webhook" }],
|
|
100
|
+
webhook: [{ key: "NOTIFICATION_WEBHOOK_URL", hint: "Your webhook endpoint URL" }],
|
|
101
|
+
};
|
|
88
102
|
// ── Functions ──────────────────────────────────────────────────────────
|
|
103
|
+
/**
|
|
104
|
+
* Extract all unique skill IDs from a workflow template YAML string.
|
|
105
|
+
*/
|
|
106
|
+
export function extractSkillsFromYaml(yaml) {
|
|
107
|
+
const skills = new Set();
|
|
108
|
+
// Match skills: [github, linear, sentry] patterns
|
|
109
|
+
const matches = yaml.matchAll(/skills:\s*\[([^\]]+)\]/g);
|
|
110
|
+
for (const m of matches) {
|
|
111
|
+
for (const skill of m[1].split(",")) {
|
|
112
|
+
skills.add(skill.trim());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return [...skills];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Gather credentials required for a set of skills.
|
|
119
|
+
* Always includes ANTHROPIC_API_KEY. Deduplicates by key name.
|
|
120
|
+
*/
|
|
121
|
+
export function collectCredentialsForSkills(skills) {
|
|
122
|
+
const seen = new Map();
|
|
123
|
+
for (const cred of ALWAYS_CREDENTIALS) {
|
|
124
|
+
seen.set(cred.key, cred);
|
|
125
|
+
}
|
|
126
|
+
for (const skill of skills) {
|
|
127
|
+
const creds = SKILL_CREDENTIALS[skill];
|
|
128
|
+
if (!creds)
|
|
129
|
+
continue;
|
|
130
|
+
for (const cred of creds) {
|
|
131
|
+
if (!seen.has(cred.key)) {
|
|
132
|
+
seen.set(cred.key, cred);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return Array.from(seen.values());
|
|
137
|
+
}
|
|
89
138
|
/**
|
|
90
139
|
* Gather all unique credentials required for the selected providers.
|
|
91
140
|
* Always includes ANTHROPIC_API_KEY. Deduplicates by key name.
|
|
141
|
+
* @deprecated Use collectCredentialsForSkills instead — kept for backward compat.
|
|
92
142
|
*/
|
|
93
143
|
export function collectCredentials(selections) {
|
|
94
144
|
const seen = new Map();
|
|
95
|
-
// Always-present credentials first
|
|
96
145
|
for (const cred of ALWAYS_CREDENTIALS) {
|
|
97
146
|
seen.set(cred.key, cred);
|
|
98
147
|
}
|
|
@@ -174,23 +223,49 @@ export function detectGitRemote(cwd) {
|
|
|
174
223
|
return null;
|
|
175
224
|
}
|
|
176
225
|
/**
|
|
177
|
-
*
|
|
226
|
+
* Infer source-control-provider from git remote detection.
|
|
227
|
+
*/
|
|
228
|
+
function inferSourceControl(gitInfo) {
|
|
229
|
+
return gitInfo?.provider ?? "github";
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Infer issue-tracker-provider from skills in the workflow.
|
|
233
|
+
*/
|
|
234
|
+
function inferIssueTracker(skills, sourceControl) {
|
|
235
|
+
if (skills.includes("linear"))
|
|
236
|
+
return "linear";
|
|
237
|
+
if (skills.includes("jira"))
|
|
238
|
+
return "jira";
|
|
239
|
+
return sourceControl === "github" ? "github-issues" : "github-issues";
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Infer observability-provider from skills in the workflow.
|
|
243
|
+
*/
|
|
244
|
+
function inferObservability(skills) {
|
|
245
|
+
if (skills.includes("datadog"))
|
|
246
|
+
return "datadog";
|
|
247
|
+
if (skills.includes("sentry"))
|
|
248
|
+
return "sentry";
|
|
249
|
+
if (skills.includes("betterstack"))
|
|
250
|
+
return "betterstack";
|
|
251
|
+
if (skills.includes("newrelic"))
|
|
252
|
+
return "newrelic";
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Generate .sweny.yml content from inferred providers.
|
|
178
257
|
*/
|
|
179
|
-
export function buildSwenyYml(
|
|
258
|
+
export function buildSwenyYml(sourceControl, observability, issueTracker) {
|
|
180
259
|
const lines = [];
|
|
181
260
|
lines.push("# .sweny.yml — SWEny project configuration");
|
|
182
261
|
lines.push("# Secrets (API keys, tokens) go in .env (gitignored).");
|
|
183
262
|
lines.push("# Docs: https://docs.sweny.ai/cli");
|
|
184
263
|
lines.push("");
|
|
185
|
-
lines.push(`source-control-provider: ${
|
|
186
|
-
if (
|
|
187
|
-
lines.push(`observability-provider: ${
|
|
188
|
-
}
|
|
189
|
-
lines.push(`issue-tracker-provider: ${selections.issueTracker}`);
|
|
190
|
-
// Omit notification-provider when "console" (it's the default)
|
|
191
|
-
if (selections.notification !== "console") {
|
|
192
|
-
lines.push(`notification-provider: ${selections.notification}`);
|
|
264
|
+
lines.push(`source-control-provider: ${sourceControl}`);
|
|
265
|
+
if (observability !== null) {
|
|
266
|
+
lines.push(`observability-provider: ${observability}`);
|
|
193
267
|
}
|
|
268
|
+
lines.push(`issue-tracker-provider: ${issueTracker}`);
|
|
194
269
|
lines.push("");
|
|
195
270
|
return lines.join("\n");
|
|
196
271
|
}
|
|
@@ -256,8 +331,13 @@ function cancel() {
|
|
|
256
331
|
process.exit(0);
|
|
257
332
|
}
|
|
258
333
|
/**
|
|
259
|
-
* Interactive setup wizard —
|
|
260
|
-
*
|
|
334
|
+
* Interactive setup wizard — workflow-first approach.
|
|
335
|
+
*
|
|
336
|
+
* 1. Detect git remote (infer source control)
|
|
337
|
+
* 2. Pick a workflow template
|
|
338
|
+
* 3. Infer providers from workflow skills
|
|
339
|
+
* 4. Collect only the credentials those skills need
|
|
340
|
+
* 5. Generate files
|
|
261
341
|
*/
|
|
262
342
|
export async function runInit() {
|
|
263
343
|
const cwd = process.cwd();
|
|
@@ -265,7 +345,7 @@ export async function runInit() {
|
|
|
265
345
|
p.intro("Let's set up SWEny");
|
|
266
346
|
const gitInfo = detectGitRemote(cwd);
|
|
267
347
|
if (gitInfo) {
|
|
268
|
-
p.log.info(`Detected
|
|
348
|
+
p.log.info(`Detected: ${chalk.cyan(gitInfo.remote)} (${gitInfo.provider})`);
|
|
269
349
|
}
|
|
270
350
|
// ── Existing file check ─────────────────────────────────────────────
|
|
271
351
|
const configPath = path.join(cwd, ".sweny.yml");
|
|
@@ -281,148 +361,51 @@ export async function runInit() {
|
|
|
281
361
|
process.exit(0);
|
|
282
362
|
}
|
|
283
363
|
}
|
|
284
|
-
// ──
|
|
285
|
-
const
|
|
286
|
-
message: "
|
|
287
|
-
initialValue: gitInfo?.provider ?? "github",
|
|
288
|
-
options: [
|
|
289
|
-
{
|
|
290
|
-
value: "github",
|
|
291
|
-
label: "GitHub" + (gitInfo?.provider === "github" ? " (detected)" : ""),
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
value: "gitlab",
|
|
295
|
-
label: "GitLab" + (gitInfo?.provider === "gitlab" ? " (detected)" : ""),
|
|
296
|
-
},
|
|
297
|
-
],
|
|
298
|
-
});
|
|
299
|
-
if (p.isCancel(sourceControl))
|
|
300
|
-
cancel();
|
|
301
|
-
// ── Screen 2: Observability ─────────────────────────────────────────
|
|
302
|
-
const obsRaw = await p.select({
|
|
303
|
-
message: "Observability provider",
|
|
364
|
+
// ── Step 1: Pick a workflow ─────────────────────────────────────────
|
|
365
|
+
const templateChoice = await p.select({
|
|
366
|
+
message: "What do you want to do?",
|
|
304
367
|
options: [
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
{ value: "
|
|
311
|
-
{ value: "__none", label: "None" },
|
|
368
|
+
...WORKFLOW_TEMPLATES.map((t) => ({
|
|
369
|
+
value: t.id,
|
|
370
|
+
label: t.name,
|
|
371
|
+
hint: t.description,
|
|
372
|
+
})),
|
|
373
|
+
{ value: "__blank", label: "Start blank", hint: "just set up config, I'll create workflows later" },
|
|
312
374
|
],
|
|
313
375
|
});
|
|
314
|
-
if (p.isCancel(
|
|
376
|
+
if (p.isCancel(templateChoice))
|
|
315
377
|
cancel();
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
observability = obsRaw;
|
|
378
|
+
// ── Step 2: Infer everything from the workflow ──────────────────────
|
|
379
|
+
const template = WORKFLOW_TEMPLATES.find((t) => t.id === templateChoice);
|
|
380
|
+
const workflowSkills = template ? extractSkillsFromYaml(template.yaml) : [];
|
|
381
|
+
const sourceControl = inferSourceControl(gitInfo);
|
|
382
|
+
const issueTracker = inferIssueTracker(workflowSkills, sourceControl);
|
|
383
|
+
const observability = inferObservability(workflowSkills);
|
|
384
|
+
// Collect credentials for the workflow's skills (+ always ANTHROPIC_API_KEY)
|
|
385
|
+
const credentials = collectCredentialsForSkills(workflowSkills);
|
|
386
|
+
// ── Step 3: Summary + confirm ───────────────────────────────────────
|
|
387
|
+
const files = [".sweny.yml", ".env"];
|
|
388
|
+
if (template)
|
|
389
|
+
files.push(`.sweny/workflows/${template.id}.yml`);
|
|
390
|
+
const inferred = [
|
|
391
|
+
` Source control: ${sourceControl}${gitInfo ? " (detected)" : ""}`,
|
|
392
|
+
` Issue tracker: ${issueTracker}`,
|
|
393
|
+
];
|
|
394
|
+
if (observability) {
|
|
395
|
+
inferred.push(` Observability: ${observability}`);
|
|
337
396
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
message: "Issue tracker",
|
|
341
|
-
initialValue: sourceControl === "github" ? "github-issues" : undefined,
|
|
342
|
-
options: [
|
|
343
|
-
{ value: "github-issues", label: "GitHub Issues" },
|
|
344
|
-
{ value: "linear", label: "Linear" },
|
|
345
|
-
{ value: "jira", label: "Jira" },
|
|
346
|
-
],
|
|
347
|
-
});
|
|
348
|
-
if (p.isCancel(issueTracker))
|
|
349
|
-
cancel();
|
|
350
|
-
// ── Screen 4: Notification ──────────────────────────────────────────
|
|
351
|
-
const notification = await p.select({
|
|
352
|
-
message: "Notification channel",
|
|
353
|
-
initialValue: "console",
|
|
354
|
-
options: [
|
|
355
|
-
{ value: "console", label: "Console (default)" },
|
|
356
|
-
{ value: "slack", label: "Slack" },
|
|
357
|
-
{ value: "discord", label: "Discord" },
|
|
358
|
-
{ value: "teams", label: "Microsoft Teams" },
|
|
359
|
-
{ value: "webhook", label: "Webhook" },
|
|
360
|
-
],
|
|
361
|
-
});
|
|
362
|
-
if (p.isCancel(notification))
|
|
363
|
-
cancel();
|
|
364
|
-
// ── Screen 5: GitHub Action ─────────────────────────────────────────
|
|
365
|
-
const wantAction = await p.confirm({
|
|
366
|
-
message: "Set up a GitHub Action workflow?",
|
|
367
|
-
initialValue: true,
|
|
368
|
-
});
|
|
369
|
-
if (p.isCancel(wantAction))
|
|
370
|
-
cancel();
|
|
371
|
-
let cronExpression = null;
|
|
372
|
-
if (wantAction) {
|
|
373
|
-
const schedule = await p.select({
|
|
374
|
-
message: "How often should the triage run?",
|
|
375
|
-
options: [
|
|
376
|
-
{ value: "0 9 * * *", label: "Daily (9 AM UTC)" },
|
|
377
|
-
{ value: "0 9 * * 1", label: "Weekly (Monday 9 AM UTC)" },
|
|
378
|
-
{ value: "__custom", label: "Custom cron expression" },
|
|
379
|
-
],
|
|
380
|
-
});
|
|
381
|
-
if (p.isCancel(schedule))
|
|
382
|
-
cancel();
|
|
383
|
-
if (schedule === "__custom") {
|
|
384
|
-
const customCron = await p.text({
|
|
385
|
-
message: "Enter cron expression (e.g. 0 8 * * 1-5)",
|
|
386
|
-
validate: (v) => {
|
|
387
|
-
if (!v || v.trim().length === 0)
|
|
388
|
-
return "Cron expression is required";
|
|
389
|
-
if (!/^[\d*,\/-]+(\s+[\d*,\/-]+){4}$/.test(v.trim()))
|
|
390
|
-
return "Must be a valid 5-field cron expression";
|
|
391
|
-
return undefined;
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
if (p.isCancel(customCron))
|
|
395
|
-
cancel();
|
|
396
|
-
cronExpression = customCron.trim();
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
cronExpression = schedule;
|
|
400
|
-
}
|
|
397
|
+
if (workflowSkills.length > 0) {
|
|
398
|
+
inferred.push(` Skills: ${workflowSkills.join(", ")}`);
|
|
401
399
|
}
|
|
402
|
-
// ── Assemble selections ─────────────────────────────────────────────
|
|
403
|
-
const selections = {
|
|
404
|
-
sourceControl: sourceControl,
|
|
405
|
-
observability,
|
|
406
|
-
issueTracker: issueTracker,
|
|
407
|
-
notification: notification,
|
|
408
|
-
githubAction: !!wantAction,
|
|
409
|
-
cronExpression,
|
|
410
|
-
};
|
|
411
|
-
const credentials = collectCredentials(selections);
|
|
412
|
-
// ── Screen 6: Summary ───────────────────────────────────────────────
|
|
413
|
-
const files = [".sweny.yml", ".env"];
|
|
414
|
-
if (selections.githubAction)
|
|
415
|
-
files.push(".github/workflows/sweny.yml");
|
|
416
400
|
p.log.message([
|
|
417
401
|
chalk.bold("Files to create:"),
|
|
418
402
|
...files.map((f) => ` ${chalk.cyan(f)}`),
|
|
419
403
|
"",
|
|
420
|
-
chalk.bold("
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
`
|
|
424
|
-
`
|
|
425
|
-
selections.githubAction ? ` GitHub Action: ${selections.cronExpression}` : " GitHub Action: no",
|
|
404
|
+
chalk.bold("Inferred from workflow:"),
|
|
405
|
+
...inferred,
|
|
406
|
+
"",
|
|
407
|
+
chalk.bold(`Credentials needed: ${credentials.length}`),
|
|
408
|
+
...credentials.map((c) => ` ${chalk.dim(c.key)}`),
|
|
426
409
|
].join("\n"));
|
|
427
410
|
const confirmed = await p.confirm({
|
|
428
411
|
message: "Create these files?",
|
|
@@ -434,15 +417,14 @@ export async function runInit() {
|
|
|
434
417
|
p.cancel("Setup cancelled.");
|
|
435
418
|
process.exit(0);
|
|
436
419
|
}
|
|
437
|
-
// ──
|
|
420
|
+
// ── Step 4: Write files ─────────────────────────────────────────────
|
|
438
421
|
// 1. Write .sweny.yml
|
|
439
|
-
fs.writeFileSync(configPath, buildSwenyYml(
|
|
422
|
+
fs.writeFileSync(configPath, buildSwenyYml(sourceControl, observability, issueTracker), "utf-8");
|
|
440
423
|
p.log.success("Created .sweny.yml");
|
|
441
424
|
// 2. Write .env
|
|
442
425
|
const envPath = path.join(cwd, ".env");
|
|
443
426
|
if (fs.existsSync(envPath)) {
|
|
444
427
|
const existing = fs.readFileSync(envPath, "utf-8");
|
|
445
|
-
// Parse defined keys (ignore comment lines)
|
|
446
428
|
const definedKeys = new Set(existing
|
|
447
429
|
.split("\n")
|
|
448
430
|
.filter((l) => !l.trimStart().startsWith("#") && l.includes("="))
|
|
@@ -466,31 +448,13 @@ export async function runInit() {
|
|
|
466
448
|
fs.writeFileSync(envPath, buildEnvTemplate(credentials), "utf-8");
|
|
467
449
|
p.log.success("Created .env");
|
|
468
450
|
}
|
|
469
|
-
// 3.
|
|
470
|
-
if (
|
|
471
|
-
const workflowDir = path.join(cwd, ".
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
initialValue: false,
|
|
477
|
-
});
|
|
478
|
-
if (p.isCancel(overwriteWf))
|
|
479
|
-
cancel();
|
|
480
|
-
if (!overwriteWf) {
|
|
481
|
-
p.log.info("Skipped GitHub Action workflow");
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
fs.mkdirSync(workflowDir, { recursive: true });
|
|
485
|
-
fs.writeFileSync(workflowPath, buildActionWorkflow(credentials, selections.cronExpression), "utf-8");
|
|
486
|
-
p.log.success("Created .github/workflows/sweny.yml");
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
fs.mkdirSync(workflowDir, { recursive: true });
|
|
491
|
-
fs.writeFileSync(workflowPath, buildActionWorkflow(credentials, selections.cronExpression), "utf-8");
|
|
492
|
-
p.log.success("Created .github/workflows/sweny.yml");
|
|
493
|
-
}
|
|
451
|
+
// 3. Workflow template
|
|
452
|
+
if (template) {
|
|
453
|
+
const workflowDir = path.join(cwd, ".sweny", "workflows");
|
|
454
|
+
fs.mkdirSync(workflowDir, { recursive: true });
|
|
455
|
+
const templatePath = path.join(workflowDir, `${template.id}.yml`);
|
|
456
|
+
fs.writeFileSync(templatePath, template.yaml, "utf-8");
|
|
457
|
+
p.log.success(`Created .sweny/workflows/${template.id}.yml`);
|
|
494
458
|
}
|
|
495
459
|
// 4. .gitignore check
|
|
496
460
|
const gitignorePath = path.join(cwd, ".gitignore");
|
|
@@ -505,17 +469,14 @@ export async function runInit() {
|
|
|
505
469
|
p.log.warn(chalk.yellow("No .gitignore found — make sure .env is not committed"));
|
|
506
470
|
}
|
|
507
471
|
// 5. Next steps
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
" sweny
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
" sweny triage --dry-run",
|
|
518
|
-
].join("\n"), "Next steps");
|
|
519
|
-
// 6. Done
|
|
472
|
+
const credUrls = credentials.filter((c) => c.url).map((c) => ` ${c.key}: ${c.url}`);
|
|
473
|
+
const steps = ["1. Fill in your API keys in .env:", ...credUrls, "", "2. Verify connectivity:", " sweny check"];
|
|
474
|
+
if (template) {
|
|
475
|
+
steps.push("", `3. Run your workflow:`, ` sweny workflow run .sweny/workflows/${template.id}.yml`);
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
steps.push("", "3. Create your first workflow:", ' sweny workflow create "describe what you want"');
|
|
479
|
+
}
|
|
480
|
+
p.note(steps.join("\n"), "Next steps");
|
|
520
481
|
p.outro("You're all set!");
|
|
521
482
|
}
|