@sweny-ai/core 0.1.51 → 0.1.53

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 (40) hide show
  1. package/dist/__tests__/publish.test.js +146 -0
  2. package/dist/__tests__/schema.test.js +156 -1
  3. package/dist/__tests__/skills.test.js +135 -4
  4. package/dist/__tests__/templates.test.d.ts +1 -0
  5. package/dist/__tests__/templates.test.js +49 -0
  6. package/dist/__tests__/version-consistency.test.js +7 -7
  7. package/dist/cli/config-file.d.ts +1 -1
  8. package/dist/cli/config-file.js +1 -1
  9. package/dist/cli/credentials.d.ts +1 -1
  10. package/dist/cli/credentials.js +1 -1
  11. package/dist/cli/credentials.test.js +1 -1
  12. package/dist/cli/e2e.d.ts +5 -1
  13. package/dist/cli/e2e.js +7 -3
  14. package/dist/cli/e2e.test.js +46 -1
  15. package/dist/cli/main.js +21 -7
  16. package/dist/cli/new.d.ts +101 -0
  17. package/dist/cli/new.js +670 -0
  18. package/dist/cli/new.test.d.ts +1 -0
  19. package/dist/cli/{init.test.js → new.test.js} +216 -78
  20. package/dist/cli/publish.d.ts +34 -0
  21. package/dist/cli/publish.js +368 -0
  22. package/dist/cli/templates.d.ts +13 -0
  23. package/dist/cli/templates.js +181 -0
  24. package/dist/executor.js +63 -20
  25. package/dist/executor.test.js +233 -0
  26. package/dist/index.d.ts +5 -5
  27. package/dist/index.js +3 -3
  28. package/dist/mcp.d.ts +2 -0
  29. package/dist/mcp.js +8 -0
  30. package/dist/mcp.test.js +67 -0
  31. package/dist/schema.d.ts +428 -18
  32. package/dist/schema.js +71 -4
  33. package/dist/skills/custom-loader.d.ts +12 -12
  34. package/dist/skills/custom-loader.js +105 -50
  35. package/dist/types.d.ts +18 -1
  36. package/dist/workflows/browser.js +3 -3
  37. package/package.json +10 -2
  38. package/dist/cli/init.d.ts +0 -53
  39. package/dist/cli/init.js +0 -521
  40. /package/dist/{cli/init.test.d.ts → __tests__/publish.test.d.ts} +0 -0
package/dist/cli/e2e.js CHANGED
@@ -614,15 +614,19 @@ async function askFlowQuestions(type) {
614
614
  }
615
615
  return config;
616
616
  }
617
- // ── Interactive wizard ─────────────────────────────────────────────────
618
617
  /**
619
618
  * Interactive E2E setup wizard — prompts through flow selection,
620
619
  * then generates .sweny/e2e/*.yml and .env additions.
621
620
  */
622
- export async function runE2eInit() {
621
+ export async function runE2eInit(options = {}) {
623
622
  const cwd = process.cwd();
624
623
  // ── Screen 1: Intro ──────────────────────────────────────────
625
- p.intro("Let's set up end-to-end testing for your app");
624
+ if (!options.skipIntro) {
625
+ p.intro("Let's set up end-to-end testing for your app");
626
+ }
627
+ else {
628
+ p.log.step("Setting up end-to-end browser testing");
629
+ }
626
630
  // Check for existing files
627
631
  const e2eDir = path.join(cwd, ".sweny", "e2e");
628
632
  if (fs.existsSync(e2eDir)) {
@@ -1,7 +1,34 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { stringify as stringifyYaml, parse as parseYaml } from "yaml";
3
3
  import { resolveTemplateVars, buildE2eVars, buildSetupNode, buildReportNode, buildCleanupNode, buildFlowNodes, buildFlowWorkflow, buildE2eEnvTemplate, } from "./e2e.js";
4
4
  import { workflowZ, validateWorkflow } from "../schema.js";
5
+ // ── Clack mock for runE2eInit tests ────────────────────────────────────
6
+ // Hoisted spies so `vi.mock` (also hoisted) can reference them.
7
+ const { introSpy, logStepSpy } = vi.hoisted(() => ({
8
+ introSpy: vi.fn(),
9
+ logStepSpy: vi.fn(),
10
+ }));
11
+ vi.mock("@clack/prompts", () => ({
12
+ intro: (...args) => introSpy(...args),
13
+ outro: vi.fn(),
14
+ cancel: vi.fn(),
15
+ log: {
16
+ step: (...args) => logStepSpy(...args),
17
+ info: vi.fn(),
18
+ success: vi.fn(),
19
+ warn: vi.fn(),
20
+ message: vi.fn(),
21
+ },
22
+ // Abort the wizard right after the intro/step decision so we can assert on spies.
23
+ multiselect: vi.fn(async () => {
24
+ throw new Error("__abort_test__");
25
+ }),
26
+ select: vi.fn(),
27
+ confirm: vi.fn(),
28
+ text: vi.fn(),
29
+ isCancel: vi.fn(() => false),
30
+ spinner: vi.fn(() => ({ start: vi.fn(), stop: vi.fn(), message: vi.fn() })),
31
+ }));
5
32
  describe("resolveTemplateVars", () => {
6
33
  it("replaces known variables", () => {
7
34
  const result = resolveTemplateVars("Navigate to {base_url}/signup", {
@@ -636,3 +663,21 @@ describe("buildFlowWorkflow — node naming conventions", () => {
636
663
  }
637
664
  });
638
665
  });
666
+ describe("runE2eInit — skipIntro option", () => {
667
+ beforeEach(() => {
668
+ introSpy.mockClear();
669
+ logStepSpy.mockClear();
670
+ });
671
+ it("calls p.intro when skipIntro is not set", async () => {
672
+ const { runE2eInit } = await import("./e2e.js");
673
+ await expect(runE2eInit()).rejects.toThrow("__abort_test__");
674
+ expect(introSpy).toHaveBeenCalledTimes(1);
675
+ expect(logStepSpy).not.toHaveBeenCalledWith("Setting up end-to-end browser testing");
676
+ });
677
+ it("skips p.intro and logs a step when skipIntro: true", async () => {
678
+ const { runE2eInit } = await import("./e2e.js");
679
+ await expect(runE2eInit({ skipIntro: true })).rejects.toThrow("__abort_test__");
680
+ expect(introSpy).not.toHaveBeenCalled();
681
+ expect(logStepSpy).toHaveBeenCalledWith("Setting up end-to-end browser testing");
682
+ });
683
+ });
package/dist/cli/main.js CHANGED
@@ -22,12 +22,13 @@ import { DagRenderer } from "./renderer.js";
22
22
  import * as readline from "node:readline";
23
23
  import { loadDotenv, loadConfigFile } from "./config-file.js";
24
24
  import { buildCredentialMap } from "./credentials.js";
25
- import { runInit } from "./init.js";
25
+ import { runNew } from "./new.js";
26
26
  import { runE2eInit, runE2eRun } from "./e2e.js";
27
27
  import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs, validateWarnings, } from "./config.js";
28
28
  import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, formatResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
29
29
  import { checkProviderConnectivity } from "./check.js";
30
30
  import { registerSetupCommand } from "./setup.js";
31
+ import { registerPublishCommand } from "./publish.js";
31
32
  // ── Stream observer (NDJSON) ────────────────────────────────────────
32
33
  /**
33
34
  * Create an observer that writes NDJSON ExecutionEvents to stdout.
@@ -56,19 +57,28 @@ const program = new Command()
56
57
  .name("sweny")
57
58
  .description("SWEny CLI \u2014 autonomous engineering workflows")
58
59
  .version(version);
59
- // ── sweny init ────────────────────────────────────────────────────────
60
+ // ── sweny new ─────────────────────────────────────────────────────────
60
61
  program
61
- .command("init")
62
- .description("Interactive setup wizard — creates .sweny.yml, .env template, and optional GitHub Action")
62
+ .command("new")
63
+ .description("Create a new workflow — interactive picker or direct template")
63
64
  .action(async () => {
64
- await runInit();
65
+ await runNew();
66
+ });
67
+ // ── sweny init (deprecated alias) ─────────────────────────────────────
68
+ program
69
+ .command("init", { hidden: true })
70
+ .description("[DEPRECATED] Use `sweny new` instead")
71
+ .action(async () => {
72
+ console.warn("\x1B[33m ⚠ `sweny init` is deprecated and will be removed in v6. Use `sweny new` instead.\x1B[0m\n");
73
+ await runNew();
65
74
  });
66
75
  // ── sweny e2e ────────────────────────────────────────────────────────
67
76
  const e2eCmd = program.command("e2e").description("End-to-end browser testing");
68
77
  e2eCmd
69
78
  .command("init")
70
- .description("Interactive wizard — generates .sweny/e2e/*.yml workflow files")
79
+ .description("[DEPRECATED] Use `sweny new` and pick 'End-to-end browser testing'")
71
80
  .action(async () => {
81
+ console.warn("\x1B[33m ⚠ `sweny e2e init` is deprecated and will be removed in v6. Use `sweny new` and pick 'End-to-end browser testing'.\x1B[0m\n");
72
82
  await runE2eInit();
73
83
  });
74
84
  e2eCmd
@@ -100,6 +110,7 @@ program
100
110
  process.exit(hasFailure ? 1 : 0);
101
111
  });
102
112
  registerSetupCommand(program);
113
+ registerPublishCommand(program);
103
114
  // ── Credential map builder ──────────────────────────────────────────
104
115
  // buildCredentialMap lives in ./credentials.ts so tests can import it
105
116
  // without triggering main.ts's top-level program.parse() side effect.
@@ -808,9 +819,12 @@ workflowCmd
808
819
  .action(workflowExportAction);
809
820
  workflowCmd
810
821
  .command("create <description>")
811
- .description("Generate a new workflow from a natural language description")
822
+ .description("[DEPRECATED] Use `sweny new` and pick 'Describe your own'")
812
823
  .option("--json", "Output workflow JSON to stdout (no interactive prompt)")
813
824
  .action(async (description, options) => {
825
+ if (!options.json) {
826
+ console.warn("\x1B[33m ⚠ `sweny workflow create` is deprecated and will be removed in v6. Use `sweny new` and pick 'Describe your own'.\x1B[0m\n");
827
+ }
814
828
  const skills = configuredSkills();
815
829
  const claude = new ClaudeClient({
816
830
  maxTurns: 3,
@@ -0,0 +1,101 @@
1
+ /**
2
+ * sweny new — Interactive workflow creation wizard
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
+ *
7
+ * Pure functions for file generation + thin @clack/prompts interactive layer.
8
+ * Tests cover the pure functions; the interactive wizard is thin glue.
9
+ */
10
+ export interface Credential {
11
+ key: string;
12
+ hint?: string;
13
+ url?: string;
14
+ default?: string;
15
+ }
16
+ export interface InitSelections {
17
+ sourceControl: string;
18
+ observability: string | null;
19
+ issueTracker: string;
20
+ notification: string;
21
+ githubAction: boolean;
22
+ cronExpression: string | null;
23
+ }
24
+ export interface GitRemoteInfo {
25
+ provider: "github" | "gitlab";
26
+ remote: string;
27
+ }
28
+ /**
29
+ * Maps skill IDs (as used in workflow nodes) to the credentials they require.
30
+ * When a workflow uses a skill, we collect only those credentials.
31
+ */
32
+ export declare const SKILL_CREDENTIALS: Record<string, Credential[]>;
33
+ export declare const PROVIDER_CREDENTIALS: Record<string, Credential[]>;
34
+ /**
35
+ * Extract all unique skill IDs used by nodes in a workflow YAML string.
36
+ *
37
+ * Handles both inline (`skills: [github, linear]`) and block-style arrays:
38
+ *
39
+ * ```yaml
40
+ * skills:
41
+ * - github
42
+ * - linear
43
+ * ```
44
+ *
45
+ * Returns `[]` for malformed YAML or YAML without node skills.
46
+ */
47
+ export declare function extractSkillsFromYaml(yaml: string): string[];
48
+ /**
49
+ * Gather credentials required for a set of skills.
50
+ * Always includes ANTHROPIC_API_KEY. Deduplicates by key name.
51
+ */
52
+ export declare function collectCredentialsForSkills(skills: string[]): Credential[];
53
+ /**
54
+ * Gather all unique credentials required for the selected providers.
55
+ * Always includes ANTHROPIC_API_KEY. Deduplicates by key name.
56
+ * @deprecated Use collectCredentialsForSkills instead — kept for backward compat.
57
+ */
58
+ export declare function collectCredentials(selections: InitSelections): Credential[];
59
+ /**
60
+ * Detect the git remote origin from `.git/config`.
61
+ * Returns provider + normalised remote (e.g. "github.com/owner/repo"), or null.
62
+ */
63
+ export declare function detectGitRemote(cwd: string): GitRemoteInfo | null;
64
+ /**
65
+ * Generate .sweny.yml content from inferred providers.
66
+ */
67
+ export declare function buildSwenyYml(sourceControl: string, observability: string | null, issueTracker: string): string;
68
+ /**
69
+ * Generate .env template with comments for each credential.
70
+ */
71
+ export declare function buildEnvTemplate(credentials: Credential[]): string;
72
+ /**
73
+ * Generate a GitHub Actions workflow YAML for SWEny.
74
+ * Only includes credentials WITHOUT defaults as secrets.
75
+ */
76
+ export declare function buildActionWorkflow(credentials: Credential[], cronExpression: string): string;
77
+ /**
78
+ * Interactive workflow creation wizard — workflow-first approach.
79
+ *
80
+ * Flow:
81
+ * 1. Detect git remote (used to infer source-control provider)
82
+ * 2. Pick one of four branches:
83
+ * - A built-in `WORKFLOW_TEMPLATES` entry → write that workflow file
84
+ * - `__custom` → LLM generates a workflow from a user description
85
+ * - `__e2e` → delegate to the e2e browser-testing wizard in `./e2e.ts`
86
+ * - `__blank` → only set up config / credentials (no workflow file)
87
+ * 3. Infer observability + issue-tracker providers from the workflow's skills
88
+ * 4. Collect only the credentials those skills need (+ ANTHROPIC_API_KEY)
89
+ * 5. Write files idempotently:
90
+ * - `.sweny.yml` is never overwritten — if it exists, we keep it
91
+ * - `.env` is append-only (dedupes by key, incl. commented placeholders)
92
+ * - Workflow files in `.sweny/workflows/` prompt before overwriting
93
+ *
94
+ * Safe to re-run in an existing project to add additional workflows.
95
+ */
96
+ export declare function runNew(): Promise<void>;
97
+ /**
98
+ * @deprecated Use `runNew` instead. Kept as a named re-export so external
99
+ * callers (e.g. create-sweny, plugin skills) don't break during the migration.
100
+ */
101
+ export declare const runInit: typeof runNew;