@sweny-ai/core 0.1.183 → 0.1.185

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Marketplace install — fetch workflows from swenyai/workflows and
2
+ * Marketplace install — fetch workflows from swenyai/marketplace and
3
3
  * adapt them to the user's .sweny.yml providers.
4
4
  *
5
5
  * Pure functions for fetch/mismatch/adapt; file writes delegate to
@@ -7,8 +7,13 @@
7
7
  */
8
8
  import type { Skill, SkillCategory, Workflow, Claude, Logger } from "../types.js";
9
9
  import type { FileConfig } from "./config-file.js";
10
- export declare const MARKETPLACE_REPO = "swenyai/workflows";
11
- export declare const MARKETPLACE_RAW_BASE = "https://raw.githubusercontent.com/swenyai/workflows/main";
10
+ export declare const MARKETPLACE_REPO = "swenyai/marketplace";
11
+ export declare const MARKETPLACE_BRANCH = "main";
12
+ export declare const MARKETPLACE_RAW_BASE = "https://raw.githubusercontent.com/swenyai/marketplace/main";
13
+ /** One recursive tree call lists every workflow file; the marketplace publishes no index of its own. */
14
+ export declare const MARKETPLACE_TREE_URL = "https://api.github.com/repos/swenyai/marketplace/git/trees/main?recursive=1";
15
+ /** Directories under `workflows/` in the marketplace repo, in lookup order (official wins on an id clash). */
16
+ export declare const MARKETPLACE_SOURCES: readonly ["official", "community"];
12
17
  export interface MarketplaceEntry {
13
18
  id: string;
14
19
  name: string;
@@ -23,7 +28,13 @@ export interface FetchedWorkflow {
23
28
  id: string;
24
29
  yaml: string;
25
30
  }
31
+ /** Fetch `workflows/official/<id>.yml`, then `workflows/community/<id>.yml`, from swenyai/marketplace. */
26
32
  export declare function fetchMarketplaceWorkflow(id: string): Promise<FetchedWorkflow>;
33
+ /**
34
+ * Build the browse list from the marketplace repo itself: one GitHub tree call
35
+ * to list `workflows/{official,community}/*.yml`, then each file's name,
36
+ * description and skills. Files that fail to fetch or parse are skipped.
37
+ */
27
38
  export declare function fetchMarketplaceIndex(opts?: {
28
39
  timeoutMs?: number;
29
40
  }): Promise<MarketplaceEntry[]>;
@@ -90,7 +101,7 @@ export interface InstallResult {
90
101
  alreadyExists?: boolean;
91
102
  }
92
103
  /**
93
- * Fetch a workflow from swenyai/workflows, adapt if provider mismatch is detected
104
+ * Fetch a workflow from swenyai/marketplace, adapt if provider mismatch is detected
94
105
  * AND an agent is available, then write files idempotently.
95
106
  *
96
107
  * When `overwrite` is false (default) and the workflow file already exists,
@@ -1,6 +1,6 @@
1
1
  // packages/core/src/cli/marketplace.ts
2
2
  /**
3
- * Marketplace install — fetch workflows from swenyai/workflows and
3
+ * Marketplace install — fetch workflows from swenyai/marketplace and
4
4
  * adapt them to the user's .sweny.yml providers.
5
5
  *
6
6
  * Pure functions for fetch/mismatch/adapt; file writes delegate to
@@ -13,67 +13,109 @@ import { DagRenderer } from "./renderer.js";
13
13
  import { validateParsed } from "../loader.js";
14
14
  import { loadConfigFile } from "./config-file.js";
15
15
  import { extractSkillsFromYaml, collectCredentialsForSkills, writeSwenyYmlIfMissing, appendMissingEnvKeys, writeWorkflowFile, } from "./new.js";
16
- export const MARKETPLACE_REPO = "swenyai/workflows";
17
- export const MARKETPLACE_RAW_BASE = `https://raw.githubusercontent.com/${MARKETPLACE_REPO}/main`;
18
- export async function fetchMarketplaceWorkflow(id) {
19
- const url = `${MARKETPLACE_RAW_BASE}/workflows/${id}.yml`;
20
- let res;
21
- try {
22
- res = await fetch(url);
23
- }
24
- catch {
25
- const err = new Error(`Could not reach github.com - check your connection`);
26
- err.kind = "network";
27
- throw err;
28
- }
29
- if (res.status === 404) {
30
- const err = new Error(`Workflow "${id}" not found in ${MARKETPLACE_REPO}.`);
31
- err.kind = "not-found";
32
- throw err;
33
- }
16
+ export const MARKETPLACE_REPO = "swenyai/marketplace";
17
+ export const MARKETPLACE_BRANCH = "main";
18
+ export const MARKETPLACE_RAW_BASE = `https://raw.githubusercontent.com/${MARKETPLACE_REPO}/${MARKETPLACE_BRANCH}`;
19
+ /** One recursive tree call lists every workflow file; the marketplace publishes no index of its own. */
20
+ export const MARKETPLACE_TREE_URL = `https://api.github.com/repos/${MARKETPLACE_REPO}/git/trees/${MARKETPLACE_BRANCH}?recursive=1`;
21
+ /** Directories under `workflows/` in the marketplace repo, in lookup order (official wins on an id clash). */
22
+ export const MARKETPLACE_SOURCES = ["official", "community"];
23
+ const WORKFLOW_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
24
+ const WORKFLOW_PATH_RE = new RegExp(`^workflows/(${MARKETPLACE_SOURCES.join("|")})/([A-Za-z0-9][A-Za-z0-9_-]*)\\.yml$`);
25
+ function fetchError(kind, message) {
26
+ const err = new Error(message);
27
+ err.kind = kind;
28
+ return err;
29
+ }
30
+ /** Throw a typed FetchError for any non-OK response (404 is left to the caller). */
31
+ function assertOk(res) {
32
+ if (res.ok)
33
+ return;
34
34
  if (res.status === 403 && res.headers.get("X-RateLimit-Remaining") === "0") {
35
+ const err = fetchError("rate-limit", `GitHub rate limit hit. Set GITHUB_TOKEN to raise the limit, or retry later.`);
35
36
  const reset = res.headers.get("X-RateLimit-Reset");
36
- const err = new Error(`GitHub rate limit hit. Set GITHUB_TOKEN to raise the limit, or retry later.`);
37
- err.kind = "rate-limit";
38
37
  if (reset)
39
38
  err.retryAfter = parseInt(reset, 10);
40
39
  throw err;
41
40
  }
42
- if (!res.ok) {
43
- const err = new Error(`Fetch failed with status ${res.status}`);
44
- err.kind = "unknown";
45
- throw err;
46
- }
47
- return { id, yaml: await res.text() };
41
+ throw fetchError("unknown", `Fetch failed with status ${res.status}`);
48
42
  }
49
- export async function fetchMarketplaceIndex(opts = {}) {
50
- const url = `${MARKETPLACE_RAW_BASE}/index.json`;
51
- let res;
43
+ async function httpGet(url, init) {
52
44
  try {
53
- res = await fetch(url, opts.timeoutMs ? { signal: AbortSignal.timeout(opts.timeoutMs) } : undefined);
45
+ return await (init ? fetch(url, init) : fetch(url));
54
46
  }
55
47
  catch {
56
- const err = new Error(`Could not reach github.com`);
57
- err.kind = "network";
58
- throw err;
48
+ throw fetchError("network", `Could not reach github.com - check your connection`);
59
49
  }
60
- if (res.status === 404) {
61
- const err = new Error(`Marketplace index not found`);
62
- err.kind = "not-found";
63
- throw err;
50
+ }
51
+ /** Fetch `workflows/official/<id>.yml`, then `workflows/community/<id>.yml`, from swenyai/marketplace. */
52
+ export async function fetchMarketplaceWorkflow(id) {
53
+ if (!WORKFLOW_ID_RE.test(id)) {
54
+ throw fetchError("not-found", `Workflow "${id}" not found in ${MARKETPLACE_REPO}.`);
64
55
  }
65
- if (!res.ok) {
66
- const err = new Error(`Fetch failed with status ${res.status}`);
67
- err.kind = "unknown";
68
- throw err;
56
+ for (const source of MARKETPLACE_SOURCES) {
57
+ const res = await httpGet(`${MARKETPLACE_RAW_BASE}/workflows/${source}/${id}.yml`);
58
+ if (res.status === 404)
59
+ continue;
60
+ assertOk(res);
61
+ return { id, yaml: await res.text() };
69
62
  }
70
- const raw = (await res.json());
71
- if (!Array.isArray(raw)) {
72
- const err = new Error(`Marketplace index is not an array`);
73
- err.kind = "bad-yaml";
74
- throw err;
63
+ throw fetchError("not-found", `Workflow "${id}" not found in ${MARKETPLACE_REPO}.`);
64
+ }
65
+ /**
66
+ * Build the browse list from the marketplace repo itself: one GitHub tree call
67
+ * to list `workflows/{official,community}/*.yml`, then each file's name,
68
+ * description and skills. Files that fail to fetch or parse are skipped.
69
+ */
70
+ export async function fetchMarketplaceIndex(opts = {}) {
71
+ const signal = opts.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
72
+ const headers = { Accept: "application/vnd.github+json" };
73
+ if (process.env.GITHUB_TOKEN)
74
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
75
+ const treeRes = await httpGet(MARKETPLACE_TREE_URL, { headers, ...(signal ? { signal } : {}) });
76
+ if (treeRes.status === 404)
77
+ throw fetchError("not-found", `Marketplace repo ${MARKETPLACE_REPO} not found`);
78
+ assertOk(treeRes);
79
+ const body = (await treeRes.json());
80
+ if (!body || !Array.isArray(body.tree)) {
81
+ throw fetchError("bad-yaml", `Marketplace tree listing is malformed`);
82
+ }
83
+ const found = [];
84
+ const seen = new Set();
85
+ for (const item of body.tree) {
86
+ if (item.type !== "blob" || typeof item.path !== "string")
87
+ continue;
88
+ const m = WORKFLOW_PATH_RE.exec(item.path);
89
+ if (m)
90
+ found.push({ source: m[1], id: m[2] });
75
91
  }
76
- return raw;
92
+ // Official first, then community; the first source to claim an id wins.
93
+ found.sort((a, b) => MARKETPLACE_SOURCES.indexOf(a.source) -
94
+ MARKETPLACE_SOURCES.indexOf(b.source));
95
+ const entries = await Promise.all(found.map(async ({ source, id }) => {
96
+ if (seen.has(id))
97
+ return null;
98
+ seen.add(id);
99
+ try {
100
+ const res = await fetch(`${MARKETPLACE_RAW_BASE}/workflows/${source}/${id}.yml`, signal ? { signal } : undefined);
101
+ if (!res.ok)
102
+ return null;
103
+ const yaml = await res.text();
104
+ const parsed = parseYaml(yaml);
105
+ if (!parsed || typeof parsed !== "object")
106
+ return null;
107
+ return {
108
+ id,
109
+ name: typeof parsed.name === "string" && parsed.name ? parsed.name : id,
110
+ description: typeof parsed.description === "string" ? parsed.description : "",
111
+ skills: extractSkillsFromYaml(yaml),
112
+ };
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }));
118
+ return entries.filter((e) => e !== null);
77
119
  }
78
120
  const CATEGORY_TO_CONFIG_KEY = {
79
121
  git: "source-control-provider",
@@ -177,7 +219,7 @@ export async function adaptWorkflowInteractive(workflow, options) {
177
219
  }
178
220
  }
179
221
  /**
180
- * Fetch a workflow from swenyai/workflows, adapt if provider mismatch is detected
222
+ * Fetch a workflow from swenyai/marketplace, adapt if provider mismatch is detected
181
223
  * AND an agent is available, then write files idempotently.
182
224
  *
183
225
  * When `overwrite` is false (default) and the workflow file already exists,
package/dist/cli/new.d.ts CHANGED
@@ -145,7 +145,7 @@ export declare function nonInteractiveUsage(): string;
145
145
  * Flow:
146
146
  * 1. Detect git remote (used to infer source-control provider)
147
147
  * 2. Pick one of five branches:
148
- * - `__marketplace` → browse published workflows at swenyai/workflows, then delegate to `installMarketplaceWorkflow`
148
+ * - `__marketplace` → browse published workflows at swenyai/marketplace, then delegate to `installMarketplaceWorkflow`
149
149
  * - A built-in `WORKFLOW_TEMPLATES` entry → write that workflow file
150
150
  * - `__custom` → LLM generates a workflow from a user description
151
151
  * - `__e2e` → delegate to the e2e browser-testing wizard in `./e2e.ts`
package/dist/cli/new.js CHANGED
@@ -523,7 +523,7 @@ function cancel() {
523
523
  * Flow:
524
524
  * 1. Detect git remote (used to infer source-control provider)
525
525
  * 2. Pick one of five branches:
526
- * - `__marketplace` → browse published workflows at swenyai/workflows, then delegate to `installMarketplaceWorkflow`
526
+ * - `__marketplace` → browse published workflows at swenyai/marketplace, then delegate to `installMarketplaceWorkflow`
527
527
  * - A built-in `WORKFLOW_TEMPLATES` entry → write that workflow file
528
528
  * - `__custom` → LLM generates a workflow from a user description
529
529
  * - `__e2e` → delegate to the e2e browser-testing wizard in `./e2e.ts`
@@ -669,7 +669,7 @@ export async function runNew(options) {
669
669
  {
670
670
  value: "__marketplace",
671
671
  label: "Browse marketplace",
672
- hint: "install a published workflow from swenyai/workflows",
672
+ hint: "install a published workflow from swenyai/marketplace",
673
673
  },
674
674
  ]
675
675
  : []),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.183",
3
+ "version": "0.1.185",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"