@sweny-ai/core 0.1.192 → 0.1.194

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 ADDED
@@ -0,0 +1,57 @@
1
+ # @sweny-ai/core
2
+
3
+ **Workflows for coding agents. One set of rules, a receipt for every run.**
4
+
5
+ SWEny runs a DAG of coding-agent steps from a YAML file, on your laptop or in CI. The agent does the work. SWEny decides what it can touch, checks what it hands back, and records what it did.
6
+
7
+ ## Start
8
+
9
+ ```bash
10
+ npx @sweny-ai/core new # pick a named workflow, e.g. explain-repo (no skill credentials)
11
+ npx @sweny-ai/core check # preflight: is your agent logged in?
12
+ npx @sweny-ai/core workflow run .sweny/workflows/explain-repo.yml
13
+ ```
14
+
15
+ Install once with `npm install -g @sweny-ai/core` and the binary is `sweny`. Want to watch it first? `sweny try` replays a recorded demo with no login and no network (sample data, not a real run).
16
+
17
+ ## Credentials
18
+
19
+ Requires Node 20+ and a login or API key for the agent that runs your nodes. Claude Code is the default; pick another with `--agent`.
20
+
21
+ | Agent | Credentials it needs |
22
+ |-------|----------------------|
23
+ | Claude Code (default) | `CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`), or `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`, or a Claude Code login (`claude`, then `/login`), or Bedrock / Vertex routing. |
24
+ | Codex (`--agent codex`) | `CODEX_API_KEY`, `OPENAI_API_KEY` or `CODEX_ACCESS_TOKEN`, or a `codex login`. |
25
+ | pi (`--agent pi`) | The API key of the model provider pi calls (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, ...). |
26
+ | ACP agent (`--agent "acp:<command>"`) | Whatever that agent needs; pass its key var through `SWENY_ENV_PASSTHROUGH`. |
27
+
28
+ For Claude Code and Codex, `sweny check` and `sweny workflow run` stop with the fix when no credential or login is found, before any node runs. Skill credentials (`GITHUB_TOKEN` and so on) are separate and only needed by workflows that use those skills.
29
+
30
+ ## What you get
31
+
32
+ Every run ends with a receipt: nodes, tool calls, duration, tokens, cost, and the policy the agent ran under. An example:
33
+
34
+ ```text
35
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
36
+ ┆ ✓ passed run 3f9a2c ┆
37
+ ┆ explain-repo ┆
38
+ ┆ ┆
39
+ ┆ nodes 2/2 ┆
40
+ ┆ tool calls 12 ┆
41
+ ┆ duration 28s ┆
42
+ ┆ tokens 30k ┆
43
+ ┆ cost $0.13 ┆
44
+ ┆ policy env scoped, sandbox on ┆
45
+ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
46
+ ┆ [ ENV SCOPED · SANDBOXED ] ┆
47
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
48
+ ```
49
+
50
+ Then `sweny runs` lists your history, and `sweny runs diff` shows what changed in the answers between two runs. The [GitHub Action](https://docs.sweny.ai/action/) posts the same receipt on a PR, and recurring [packs](https://docs.sweny.ai/workflows/packs/) (weekly digest, dependency drift, PR risk review) run it on a schedule.
51
+
52
+ ## Links
53
+
54
+ - [Quick start](https://docs.sweny.ai/getting-started/quick-start/)
55
+ - [CLI reference](https://docs.sweny.ai/cli/commands/)
56
+ - [Agents and policies](https://docs.sweny.ai/getting-started/agents-and-policies/)
57
+ - [Source and issues](https://github.com/swenyai/sweny)
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The "what next" note after a workspace's first successful real run (#534).
3
+ *
4
+ * `sweny try` ends with one command to run next; a real run used to end in
5
+ * silence, at the exact moment a user has just seen value. This prints one
6
+ * short block (history, compare, schedule) once per workspace.
7
+ *
8
+ * Once means once per workspace for this user: the marker lives in sweny's
9
+ * per-user state dir (`<state>/first-success/<workspace hash>`), never in the
10
+ * repo, so it is not committed and an agent in the workspace cannot reset it.
11
+ * The marker is claimed atomically (`wx`), so two runs finishing together
12
+ * print the note once.
13
+ *
14
+ * Silent when: CI, a failed run, `--json`, `--stream`, `--dry-run`, or the
15
+ * marker already exists. A marker that cannot be written means no note: a
16
+ * note that might repeat is worse than none. Every other output (a TTY, a
17
+ * pipe, NO_COLOR) gets the same words; only the paint and glyphs change, and
18
+ * the last line is always plain text.
19
+ */
20
+ import { type Paint } from "./style.js";
21
+ type Env = Record<string, string | undefined>;
22
+ /** How to schedule a workflow you wrote: the Action's cron and dispatch triggers. */
23
+ export declare const SCHEDULING_DOCS_URL = "https://docs.sweny.ai/action/scheduling/";
24
+ /** The two command steps, in order. Commands are real subcommands (a spec asserts it). */
25
+ export declare const WHAT_NEXT_STEPS: ReadonlyArray<{
26
+ command: string;
27
+ what: string;
28
+ }>;
29
+ /** The third step, and the plain closing line. */
30
+ export declare const WHAT_NEXT_SCHEDULE = "Run it on a schedule: https://docs.sweny.ai/action/scheduling/";
31
+ /** CI: the `CI` convention, or GitHub Actions itself. */
32
+ export declare function inCi(env?: Env): boolean;
33
+ /**
34
+ * The note: a heading, one aligned row per step, then a plain closing line
35
+ * that says how to schedule it. Same words with or without color or Unicode.
36
+ */
37
+ export declare function formatWhatNext(paint?: Paint, unicode?: boolean): string;
38
+ /** `<state>/first-success/<workspace hash>`: one empty file per workspace. */
39
+ export declare function firstSuccessMarker(cwd: string, env?: Env): string;
40
+ /**
41
+ * Claim the workspace's marker. True only for the caller that created it.
42
+ * False when it already exists or cannot be written (read-only home, no
43
+ * state dir): the note then stays silent rather than risk repeating.
44
+ */
45
+ export declare function claimFirstSuccess(cwd: string, env?: Env): boolean;
46
+ export interface WhatNextOptions {
47
+ /** Where the note goes: the run's stdout. */
48
+ stream: {
49
+ write(s: string): unknown;
50
+ isTTY?: boolean;
51
+ };
52
+ /** The run succeeded (no failed node, not crashed). Anything else prints nothing. */
53
+ ok: boolean;
54
+ /** `--json`, `--stream` or `--dry-run`: stdout is data, or nothing real ran. */
55
+ quiet?: boolean;
56
+ env?: Env;
57
+ cwd?: string;
58
+ /** Override color. Default: `colorEnabled(stream, env)`. */
59
+ color?: boolean;
60
+ /** Override Unicode. Default: `supportsUnicode(env)`. */
61
+ unicode?: boolean;
62
+ }
63
+ /** Print the note if this is the workspace's first successful real run. Returns whether it printed. */
64
+ export declare function writeWhatNext(opts: WhatNextOptions): boolean;
65
+ export {};
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The "what next" note after a workspace's first successful real run (#534).
3
+ *
4
+ * `sweny try` ends with one command to run next; a real run used to end in
5
+ * silence, at the exact moment a user has just seen value. This prints one
6
+ * short block (history, compare, schedule) once per workspace.
7
+ *
8
+ * Once means once per workspace for this user: the marker lives in sweny's
9
+ * per-user state dir (`<state>/first-success/<workspace hash>`), never in the
10
+ * repo, so it is not committed and an agent in the workspace cannot reset it.
11
+ * The marker is claimed atomically (`wx`), so two runs finishing together
12
+ * print the note once.
13
+ *
14
+ * Silent when: CI, a failed run, `--json`, `--stream`, `--dry-run`, or the
15
+ * marker already exists. A marker that cannot be written means no note: a
16
+ * note that might repeat is worse than none. Every other output (a TTY, a
17
+ * pipe, NO_COLOR) gets the same words; only the paint and glyphs change, and
18
+ * the last line is always plain text.
19
+ */
20
+ import fs from "node:fs";
21
+ import path from "node:path";
22
+ import { swenyStateDir, workspaceScope } from "../journal.js";
23
+ import { createPaint } from "./style.js";
24
+ import { colorEnabled, glyphsFor, supportsUnicode } from "./terminal.js";
25
+ /** How to schedule a workflow you wrote: the Action's cron and dispatch triggers. */
26
+ export const SCHEDULING_DOCS_URL = "https://docs.sweny.ai/action/scheduling/";
27
+ /** The two command steps, in order. Commands are real subcommands (a spec asserts it). */
28
+ export const WHAT_NEXT_STEPS = [
29
+ { command: "sweny runs", what: "see this run and the ones before it" },
30
+ { command: "sweny runs diff", what: "run it again, then see what changed in the answers" },
31
+ ];
32
+ /** The third step, and the plain closing line. */
33
+ export const WHAT_NEXT_SCHEDULE = `Run it on a schedule: ${SCHEDULING_DOCS_URL}`;
34
+ function truthy(v) {
35
+ return v !== undefined && v !== "" && v !== "0" && v.toLowerCase() !== "false";
36
+ }
37
+ /** CI: the `CI` convention, or GitHub Actions itself. */
38
+ export function inCi(env = process.env) {
39
+ return truthy(env.CI) || env.GITHUB_ACTIONS === "true";
40
+ }
41
+ /**
42
+ * The note: a heading, one aligned row per step, then a plain closing line
43
+ * that says how to schedule it. Same words with or without color or Unicode.
44
+ */
45
+ export function formatWhatNext(paint = createPaint(false), unicode = true) {
46
+ const g = glyphsFor(unicode);
47
+ const width = Math.max(...WHAT_NEXT_STEPS.map((s) => s.command.length));
48
+ const rows = WHAT_NEXT_STEPS.map((s) => ` ${paint.strong(s.command.padEnd(width))} ${paint.muted(s.what)}`);
49
+ return [` ${paint.brand(g.brand)} ${paint.strong("Next")}`, ...rows, ` ${WHAT_NEXT_SCHEDULE}`].join("\n");
50
+ }
51
+ /** `<state>/first-success/<workspace hash>`: one empty file per workspace. */
52
+ export function firstSuccessMarker(cwd, env = process.env) {
53
+ return path.join(swenyStateDir(env), "first-success", workspaceScope(cwd));
54
+ }
55
+ /**
56
+ * Claim the workspace's marker. True only for the caller that created it.
57
+ * False when it already exists or cannot be written (read-only home, no
58
+ * state dir): the note then stays silent rather than risk repeating.
59
+ */
60
+ export function claimFirstSuccess(cwd, env = process.env) {
61
+ const file = firstSuccessMarker(cwd, env);
62
+ try {
63
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
64
+ fs.closeSync(fs.openSync(file, "wx", 0o600));
65
+ return true;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ /** Print the note if this is the workspace's first successful real run. Returns whether it printed. */
72
+ export function writeWhatNext(opts) {
73
+ const env = opts.env ?? process.env;
74
+ if (!opts.ok || opts.quiet || inCi(env))
75
+ return false;
76
+ if (!claimFirstSuccess(opts.cwd ?? process.cwd(), env))
77
+ return false;
78
+ const paint = createPaint(opts.color ?? colorEnabled(opts.stream, env));
79
+ opts.stream.write(`${formatWhatNext(paint, opts.unicode ?? supportsUnicode(env))}\n\n`);
80
+ return true;
81
+ }
package/dist/cli/main.js CHANGED
@@ -45,6 +45,7 @@ import { createStreamObserver } from "./stream-observer.js";
45
45
  import { createRunLogger, summarizeRun, writeStepSummary, WORKFLOW_RUN_DESCRIPTION, WORKFLOW_RUN_OPTIONS, } from "./run-output.js";
46
46
  import { writeRunComment } from "./comment-output.js";
47
47
  import { writeReceipt } from "./ticket.js";
48
+ import { writeWhatNext } from "./first-success.js";
48
49
  import { createNodeProgress, toolCallsText } from "./progress.js";
49
50
  import { applyNoColor, createPaint } from "./style.js";
50
51
  import { colorEnabled, glyphsFor, richOutput, spinnerFramesFor, supportsUnicode } from "./terminal.js";
@@ -1139,6 +1140,12 @@ resume) {
1139
1140
  await writeReceipt(receipt, { workflow: workflow.id, runId, stream: process.stdout });
1140
1141
  if (sinceLast)
1141
1142
  console.log(createPaint(colorEnabled(process.stdout)).muted(` ${formatSinceLast(sinceLast)}`));
1143
+ // First successful real run in this workspace: one "what next" note (#534).
1144
+ writeWhatNext({
1145
+ stream: process.stdout,
1146
+ ok: true,
1147
+ quiet: Boolean(options.stream) || config.dryRun,
1148
+ });
1142
1149
  process.exit(0);
1143
1150
  }
1144
1151
  catch (err) {
package/dist/cli/new.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { type WorkflowTemplate } from "./templates.js";
11
11
  import { type Skill } from "../types.js";
12
+ import { SCHEDULING_DOCS_URL } from "./first-success.js";
12
13
  export interface Credential {
13
14
  key: string;
14
15
  hint?: string;
@@ -171,3 +172,11 @@ export declare function runNew(options?: {
171
172
  /** Internal: the id came from the marketplace picker, skip the built-in shortcut. */
172
173
  forceMarketplace?: boolean;
173
174
  }): Promise<void>;
175
+ export { SCHEDULING_DOCS_URL };
176
+ /**
177
+ * The closing line (#534). A pack already shows its own trigger step, so it
178
+ * closes as before. Any other workflow that was written (a starter or a
179
+ * described one) says how to schedule THIS workflow; it never names another
180
+ * pack, because no template metadata says which pack would suit it.
181
+ */
182
+ export declare function outroMessage(hasExistingConfig: boolean, offerSchedule: boolean): string;
package/dist/cli/new.js CHANGED
@@ -20,6 +20,7 @@ import { configuredSkills } from "../skills/custom-loader.js";
20
20
  import { builtinSkills } from "../skills/index.js";
21
21
  import { DagRenderer } from "./renderer.js";
22
22
  import { runE2eInit } from "./e2e.js";
23
+ import { SCHEDULING_DOCS_URL } from "./first-success.js";
23
24
  // ── Skill → Credential extras ─────────────────────────────────────────
24
25
  /**
25
26
  * Supplementary metadata (URLs, hints, defaults) for env vars.
@@ -853,7 +854,18 @@ export async function runNew(options) {
853
854
  steps.push(`${stepNum++}. Create your first workflow:`, ` ${cli} new`);
854
855
  }
855
856
  p.note(steps.join("\n"), "Next steps");
856
- p.outro(hasExistingConfig ? "Workflow added!" : "You're all set!");
857
+ p.outro(outroMessage(hasExistingConfig, Boolean(template) && !template?.pack));
858
+ }
859
+ export { SCHEDULING_DOCS_URL };
860
+ /**
861
+ * The closing line (#534). A pack already shows its own trigger step, so it
862
+ * closes as before. Any other workflow that was written (a starter or a
863
+ * described one) says how to schedule THIS workflow; it never names another
864
+ * pack, because no template metadata says which pack would suit it.
865
+ */
866
+ export function outroMessage(hasExistingConfig, offerSchedule) {
867
+ const done = hasExistingConfig ? "Workflow added!" : "You're all set!";
868
+ return offerSchedule ? `${done} Run it on a schedule: ${SCHEDULING_DOCS_URL}` : done;
857
869
  }
858
870
  // ── Custom workflow branch ────────────────────────────────────────────
859
871
  /**
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.192",
3
+ "version": "0.1.194",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"
7
7
  },
8
- "description": "Skill library, DAG workflow orchestration, and E2E browser testing powered by Claude",
8
+ "description": "Workflows for coding agents. One set of rules, a receipt for every run.",
9
9
  "license": "MIT",
10
10
  "engines": {
11
11
  "node": ">=22"
@@ -51,7 +51,8 @@
51
51
  }
52
52
  },
53
53
  "files": [
54
- "dist"
54
+ "dist",
55
+ "README.md"
55
56
  ],
56
57
  "scripts": {
57
58
  "build": "rm -rf dist && tsc -p tsconfig.build.json && cp src/workflows/*.yml dist/workflows/ && cp src/cli/try-fixture.json dist/cli/ && node scripts/build-workflow-browser.mjs && node scripts/write-public-schema.mjs",
@@ -93,13 +94,16 @@
93
94
  "directory": "packages/core"
94
95
  },
95
96
  "keywords": [
97
+ "ai-agents",
98
+ "coding-agents",
99
+ "claude-code",
100
+ "codex",
101
+ "acp",
96
102
  "workflow",
97
103
  "dag",
98
- "skills",
99
- "claude",
100
- "ai",
104
+ "github-action",
105
+ "sandbox",
101
106
  "automation",
102
- "devops",
103
107
  "sweny"
104
108
  ]
105
109
  }