@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 +57 -0
- package/dist/cli/first-success.d.ts +65 -0
- package/dist/cli/first-success.js +81 -0
- package/dist/cli/main.js +7 -0
- package/dist/cli/new.d.ts +9 -0
- package/dist/cli/new.js +13 -1
- package/package.json +11 -7
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
|
|
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.
|
|
3
|
+
"version": "0.1.194",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sweny": "./dist/cli/main.js"
|
|
7
7
|
},
|
|
8
|
-
"description": "
|
|
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
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"ai",
|
|
104
|
+
"github-action",
|
|
105
|
+
"sandbox",
|
|
101
106
|
"automation",
|
|
102
|
-
"devops",
|
|
103
107
|
"sweny"
|
|
104
108
|
]
|
|
105
109
|
}
|