@sweny-ai/core 0.1.201 → 0.1.203
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/cli/check.d.ts +39 -7
- package/dist/cli/check.js +61 -121
- package/dist/cli/main.js +7 -22
- package/dist/cli/new.js +2 -5
- package/dist/cli/output.d.ts +13 -1
- package/dist/cli/output.js +41 -20
- package/dist/harness/acp.d.ts +9 -1
- package/dist/harness/acp.js +33 -7
- package/dist/harness/claude-code.d.ts +8 -1
- package/dist/harness/claude-code.js +25 -5
- package/dist/harness/codex.d.ts +22 -4
- package/dist/harness/codex.js +96 -21
- package/dist/harness/diagnose.d.ts +71 -0
- package/dist/harness/diagnose.js +201 -0
- package/dist/harness/index.d.ts +1 -1
- package/dist/harness/pi.d.ts +13 -1
- package/dist/harness/pi.js +61 -6
- package/dist/harness/types.d.ts +25 -0
- package/dist/journal.d.ts +53 -0
- package/dist/journal.js +40 -8
- package/package.json +1 -1
package/dist/cli/check.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { CliConfig } from "./config.js";
|
|
2
|
+
import { type AgentHarness } from "../harness/index.js";
|
|
3
|
+
export { resolveCheckAuthMode, redactUrl, checkAnthropicGateway } from "../harness/diagnose.js";
|
|
4
|
+
export type { CheckAuthMode } from "../harness/diagnose.js";
|
|
2
5
|
export interface CheckResult {
|
|
3
6
|
name: string;
|
|
4
7
|
status: "ok" | "fail" | "skip";
|
|
@@ -15,16 +18,45 @@ export interface CheckOptions {
|
|
|
15
18
|
* (no workflows found), the legacy config-driven checks run.
|
|
16
19
|
*/
|
|
17
20
|
scope?: ReadonlySet<string>;
|
|
18
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Not read by the agent lines since #532: each harness probes its own stored
|
|
23
|
+
* login. Kept so existing callers compile; {@link validateCheckInputs} takes
|
|
24
|
+
* its own copy.
|
|
25
|
+
*/
|
|
19
26
|
claudeCodeLogin?: boolean;
|
|
27
|
+
/** The agent harness to diagnose (test seam). Default: `createHarness(codingAgentProvider)`. */
|
|
28
|
+
harness?: Pick<AgentHarness, "diagnose">;
|
|
29
|
+
/** Leave out the agent lines ({@link runCheck} renders them itself). */
|
|
30
|
+
skipAgent?: boolean;
|
|
31
|
+
/** Env the agent credentials are read from (default: `process.env`). */
|
|
32
|
+
env?: Record<string, string | undefined>;
|
|
20
33
|
}
|
|
21
34
|
export declare function checkProviderConnectivity(config: CliConfig, opts?: CheckOptions): Promise<CheckResult[]>;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
/**
|
|
36
|
+
* The agent lines (#532): whatever the harness's `diagnose()` checks (CLI and
|
|
37
|
+
* version, credentials, a stored login, a cheap upstream probe), rendered
|
|
38
|
+
* like every other line. The harness owns the checks; nothing here branches
|
|
39
|
+
* on the agent.
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkAgent(config: CliConfig, opts?: Pick<CheckOptions, "harness" | "env">): Promise<CheckResult[]>;
|
|
42
|
+
export interface CheckReport {
|
|
43
|
+
/** Every line to render: the agent's first, then the providers'. */
|
|
44
|
+
results: CheckResult[];
|
|
45
|
+
/** Config errors (not the agent's): when set, no provider is probed. */
|
|
46
|
+
errors: string[];
|
|
47
|
+
/** True when `sweny check` must exit non-zero. */
|
|
48
|
+
failed: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `sweny check` end to end (#532). The agent's lines always render, even when
|
|
52
|
+
* its run gate (`preflight()`, the check `sweny workflow run` makes before any
|
|
53
|
+
* node) fails, so a broken setup sees its diagnosis. When the gate fails and
|
|
54
|
+
* no agent line already says why, its reason is added as a failed line. Config
|
|
55
|
+
* errors skip the provider probes but not the agent lines.
|
|
56
|
+
*/
|
|
57
|
+
export declare function runCheck(config: CliConfig, opts?: Omit<CheckOptions, "harness" | "skipAgent"> & {
|
|
58
|
+
harness?: Pick<AgentHarness, "diagnose" | "preflight">;
|
|
59
|
+
}): Promise<CheckReport>;
|
|
28
60
|
export declare function checkDatadog(creds: Record<string, string>): Promise<CheckResult>;
|
|
29
61
|
/**
|
|
30
62
|
* Detect a local Claude Code login the way `sweny workflow run` benefits from
|
package/dist/cli/check.js
CHANGED
|
@@ -4,53 +4,19 @@ import { parse as parseYaml } from "yaml";
|
|
|
4
4
|
import { validateInputs } from "./config.js";
|
|
5
5
|
import { findSkillEnvGaps } from "./skill-env.js";
|
|
6
6
|
import { configuredSkills } from "../skills/custom-loader.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import { consoleLogger } from "../types.js";
|
|
8
|
+
import { createHarness, isSupportedAgent, unsupportedAgentError } from "../harness/index.js";
|
|
9
|
+
import { CHECK_TIMEOUT_MS, networkErrorMessage } from "../harness/diagnose.js";
|
|
10
|
+
// The Claude probes moved behind the harness seam (#532); these names stay importable from here.
|
|
11
|
+
export { resolveCheckAuthMode, redactUrl, checkAnthropicGateway } from "../harness/diagnose.js";
|
|
12
12
|
export async function checkProviderConnectivity(config, opts = {}) {
|
|
13
13
|
const results = [];
|
|
14
14
|
const scope = opts.scope;
|
|
15
15
|
const inScope = (skillId) => !scope || scope.has(skillId);
|
|
16
16
|
const githubChecked = { done: false }; // avoid double-checking same token
|
|
17
17
|
// ── Coding agent ────────────────────────────────────────────────────────────
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
const base = config.anthropicBaseUrl;
|
|
21
|
-
if (mode === "none" && opts.claudeCodeLogin) {
|
|
22
|
-
results.push({
|
|
23
|
-
name: "Anthropic (claude agent)",
|
|
24
|
-
status: "ok",
|
|
25
|
-
detail: "Claude Code login detected",
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
else if (mode === "none") {
|
|
29
|
-
results.push({ name: "Anthropic (claude agent)", status: "skip", detail: "No credential configured" });
|
|
30
|
-
}
|
|
31
|
-
else if (base) {
|
|
32
|
-
// Gateway: probe the gateway, never real Anthropic with a gateway-bound key.
|
|
33
|
-
results.push(await checkAnthropicGateway(base, config, mode));
|
|
34
|
-
}
|
|
35
|
-
else if (mode === "api-key") {
|
|
36
|
-
results.push(await checkAnthropic(config.anthropicApiKey));
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
// oauth or auth-token, no base URL: nothing cheap to probe, report the mode.
|
|
40
|
-
results.push({
|
|
41
|
-
name: "Anthropic (claude agent)",
|
|
42
|
-
status: "skip",
|
|
43
|
-
detail: `auth mode: ${mode}, skipping upstream API check`,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
results.push({
|
|
49
|
-
name: `Coding agent (${config.codingAgentProvider})`,
|
|
50
|
-
status: "skip",
|
|
51
|
-
detail: "Connectivity check not implemented for this provider",
|
|
52
|
-
});
|
|
53
|
-
}
|
|
18
|
+
if (!opts.skipAgent)
|
|
19
|
+
results.push(...(await checkAgent(config, opts)));
|
|
54
20
|
// ── Observability ────────────────────────────────────────────────────────────
|
|
55
21
|
for (const provider of config.observabilityProviders) {
|
|
56
22
|
if (!inScope(provider))
|
|
@@ -131,82 +97,69 @@ export async function checkProviderConnectivity(config, opts = {}) {
|
|
|
131
97
|
return results;
|
|
132
98
|
}
|
|
133
99
|
// ── Provider check functions ─────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The agent lines (#532): whatever the harness's `diagnose()` checks (CLI and
|
|
102
|
+
* version, credentials, a stored login, a cheap upstream probe), rendered
|
|
103
|
+
* like every other line. The harness owns the checks; nothing here branches
|
|
104
|
+
* on the agent.
|
|
105
|
+
*/
|
|
106
|
+
export async function checkAgent(config, opts = {}) {
|
|
107
|
+
const agent = config.codingAgentProvider;
|
|
108
|
+
const name = `Coding agent (${agent})`;
|
|
109
|
+
if (!isSupportedAgent(agent))
|
|
110
|
+
return [{ name, status: "fail", detail: unsupportedAgentError(agent) }];
|
|
111
|
+
let harness;
|
|
136
112
|
try {
|
|
137
|
-
|
|
138
|
-
headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
|
|
139
|
-
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS),
|
|
140
|
-
});
|
|
141
|
-
if (res.status === 200) {
|
|
142
|
-
return { name, status: "ok", detail: "API key is valid" };
|
|
143
|
-
}
|
|
144
|
-
if (res.status === 401) {
|
|
145
|
-
return {
|
|
146
|
-
name,
|
|
147
|
-
status: "fail",
|
|
148
|
-
detail: "401 Unauthorized - check ANTHROPIC_API_KEY at https://console.anthropic.com",
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
return { name, status: "fail", detail: `Unexpected HTTP ${res.status}` };
|
|
113
|
+
harness = opts.harness ?? createHarness(agent, { logger: consoleLogger });
|
|
152
114
|
}
|
|
153
115
|
catch (err) {
|
|
154
|
-
return { name, status: "fail", detail:
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
export function resolveCheckAuthMode(config) {
|
|
158
|
-
const hasKey = !!config.anthropicApiKey;
|
|
159
|
-
const hasBearer = !!config.anthropicAuthToken;
|
|
160
|
-
const hasOauth = !!config.claudeOauthToken;
|
|
161
|
-
if (config.swenyAuth === "oauth")
|
|
162
|
-
return hasOauth ? "oauth" : "none";
|
|
163
|
-
if (config.swenyAuth === "api-key")
|
|
164
|
-
return hasKey ? "api-key" : hasBearer ? "auth-token" : "none";
|
|
165
|
-
// auto: OAuth wins when present (today's protective behavior)
|
|
166
|
-
if (hasOauth)
|
|
167
|
-
return "oauth";
|
|
168
|
-
if (hasKey)
|
|
169
|
-
return "api-key";
|
|
170
|
-
if (hasBearer)
|
|
171
|
-
return "auth-token";
|
|
172
|
-
return "none";
|
|
173
|
-
}
|
|
174
|
-
/** Redact a URL to scheme + host so userinfo / query (which can carry a key) never logs. */
|
|
175
|
-
export function redactUrl(raw) {
|
|
176
|
-
try {
|
|
177
|
-
const u = new URL(raw);
|
|
178
|
-
return `${u.protocol}//${u.host}`;
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
return "(invalid URL)";
|
|
116
|
+
return [{ name, status: "fail", detail: err instanceof Error ? err.message : String(err) }];
|
|
182
117
|
}
|
|
118
|
+
if (!harness.diagnose)
|
|
119
|
+
return [{ name, status: "skip", detail: "This agent has no preflight checks" }];
|
|
120
|
+
return harness.diagnose({ env: agentCheckEnv(config, opts.env ?? process.env), timeoutMs: CHECK_TIMEOUT_MS });
|
|
183
121
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return { name, status: "ok", detail: `gateway reachable (${safeBase}), auth mode: ${mode}` };
|
|
122
|
+
/** validateInputs' generic agent-auth lines; the harness's own lines replace them (#339, #532). */
|
|
123
|
+
const AGENT_AUTH_ERROR_PREFIXES = ["Missing: ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN", "Missing: CODEX_API_KEY"];
|
|
124
|
+
/**
|
|
125
|
+
* `sweny check` end to end (#532). The agent's lines always render, even when
|
|
126
|
+
* its run gate (`preflight()`, the check `sweny workflow run` makes before any
|
|
127
|
+
* node) fails, so a broken setup sees its diagnosis. When the gate fails and
|
|
128
|
+
* no agent line already says why, its reason is added as a failed line. Config
|
|
129
|
+
* errors skip the provider probes but not the agent lines.
|
|
130
|
+
*/
|
|
131
|
+
export async function runCheck(config, opts = {}) {
|
|
132
|
+
const agent = config.codingAgentProvider;
|
|
133
|
+
const errors = validateCheckInputs(config, { scope: opts.scope, env: opts.env }).filter((e) => !AGENT_AUTH_ERROR_PREFIXES.some((p) => e.startsWith(p)) && e !== unsupportedAgentError(agent));
|
|
134
|
+
let harness = opts.harness;
|
|
135
|
+
if (!harness && isSupportedAgent(agent)) {
|
|
136
|
+
try {
|
|
137
|
+
harness = createHarness(agent, { logger: consoleLogger });
|
|
201
138
|
}
|
|
202
|
-
|
|
203
|
-
|
|
139
|
+
catch {
|
|
140
|
+
// checkAgent reports the construction error as the agent's line.
|
|
204
141
|
}
|
|
205
|
-
return { name, status: "fail", detail: `Unexpected HTTP ${res.status} from gateway ${safeBase}` };
|
|
206
142
|
}
|
|
207
|
-
|
|
208
|
-
|
|
143
|
+
const results = await checkAgent(config, { harness, env: opts.env });
|
|
144
|
+
if (harness && !results.some((r) => r.status === "fail")) {
|
|
145
|
+
const pre = await harness.preflight();
|
|
146
|
+
if (!pre.ok)
|
|
147
|
+
results.push({ name: `Agent (${agent})`, status: "fail", detail: pre.reason });
|
|
209
148
|
}
|
|
149
|
+
if (errors.length === 0)
|
|
150
|
+
results.push(...(await checkProviderConnectivity(config, { ...opts, skipAgent: true })));
|
|
151
|
+
return { results, errors, failed: errors.length > 0 || results.some((r) => r.status === "fail") };
|
|
152
|
+
}
|
|
153
|
+
/** Env for the agent checks: the process env, with the agent settings `.sweny.yml` can also carry. */
|
|
154
|
+
function agentCheckEnv(config, env) {
|
|
155
|
+
return {
|
|
156
|
+
...env,
|
|
157
|
+
ANTHROPIC_API_KEY: config.anthropicApiKey,
|
|
158
|
+
ANTHROPIC_AUTH_TOKEN: config.anthropicAuthToken,
|
|
159
|
+
CLAUDE_CODE_OAUTH_TOKEN: config.claudeOauthToken,
|
|
160
|
+
ANTHROPIC_BASE_URL: config.anthropicBaseUrl,
|
|
161
|
+
SWENY_AUTH: config.swenyAuth,
|
|
162
|
+
};
|
|
210
163
|
}
|
|
211
164
|
export async function checkDatadog(creds) {
|
|
212
165
|
const name = "Observability (datadog)";
|
|
@@ -330,19 +283,6 @@ async function checkGitHub(token, name) {
|
|
|
330
283
|
return { name, status: "fail", detail: networkErrorMessage(err) };
|
|
331
284
|
}
|
|
332
285
|
}
|
|
333
|
-
function networkErrorMessage(err) {
|
|
334
|
-
// AbortSignal.timeout fires a DOMException with name "TimeoutError"; an
|
|
335
|
-
// explicit abort surfaces as "AbortError". Either means we gave up waiting.
|
|
336
|
-
const errName = err instanceof Error ? err.name : "";
|
|
337
|
-
if (errName === "TimeoutError" || errName === "AbortError") {
|
|
338
|
-
return `Connection timed out after ${CHECK_TIMEOUT_MS}ms - host unreachable or too slow`;
|
|
339
|
-
}
|
|
340
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
341
|
-
if (/ENOTFOUND|ETIMEDOUT|ECONNREFUSED/i.test(msg)) {
|
|
342
|
-
return `Network error - check your internet connection (${msg})`;
|
|
343
|
-
}
|
|
344
|
-
return msg;
|
|
345
|
-
}
|
|
346
286
|
// ── Workflow-scoped checks ───────────────────────────────────────────────────
|
|
347
287
|
/**
|
|
348
288
|
* Detect a local Claude Code login the way `sweny workflow run` benefits from
|
package/dist/cli/main.js
CHANGED
|
@@ -51,7 +51,7 @@ import { applyNoColor, createPaint } from "./style.js";
|
|
|
51
51
|
import { colorEnabled, glyphsFor, richOutput, spinnerFramesFor, supportsUnicode } from "./terminal.js";
|
|
52
52
|
import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs, parsePositiveInt, } from "./config.js";
|
|
53
53
|
import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, writeResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
|
|
54
|
-
import {
|
|
54
|
+
import { discoverWorkflowSkillIds, runCheck } from "./check.js";
|
|
55
55
|
import { registerSetupCommand } from "./setup.js";
|
|
56
56
|
import { registerPublishCommand } from "./publish.js";
|
|
57
57
|
import { registerSkillCommand } from "./skill.js";
|
|
@@ -119,8 +119,6 @@ program
|
|
|
119
119
|
// ── sweny try ─────────────────────────────────────────────────────────
|
|
120
120
|
registerTryCommand(program);
|
|
121
121
|
// ── sweny check ───────────────────────────────────────────────────────
|
|
122
|
-
/** validateInputs' generic agent-auth lines, replaced by the harness preflight reason (#339). */
|
|
123
|
-
const AGENT_AUTH_ERROR_PREFIXES = ["Missing: ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN", "Missing: CODEX_API_KEY"];
|
|
124
122
|
program
|
|
125
123
|
.command("check")
|
|
126
124
|
.description("Verify provider credentials and connectivity")
|
|
@@ -129,26 +127,13 @@ program
|
|
|
129
127
|
const config = parseCliInputs({}, fileConfig);
|
|
130
128
|
const workflows = discoverWorkflowSkillIds(process.cwd());
|
|
131
129
|
const scope = workflows.found ? workflows.skillIds : undefined;
|
|
132
|
-
const claudeCodeLogin = detectClaudeCodeLogin();
|
|
133
|
-
let errors = validateCheckInputs(config, { scope, claudeCodeLogin });
|
|
134
|
-
// #339: the agent's own preflight (install and login), named by --agent.
|
|
135
|
-
// Its reason replaces the generic agent-auth line from validateInputs.
|
|
136
|
-
const agent = config.codingAgentProvider;
|
|
137
|
-
const pre = isSupportedAgent(agent) ? await createHarness(agent, { logger: consoleLogger }).preflight() : undefined;
|
|
138
|
-
if (pre && !pre.ok) {
|
|
139
|
-
errors = [pre.reason, ...errors.filter((e) => !AGENT_AUTH_ERROR_PREFIXES.some((p) => e.startsWith(p)))];
|
|
140
|
-
}
|
|
141
|
-
if (errors.length > 0) {
|
|
142
|
-
console.error(formatValidationErrors(errors));
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
130
|
console.log(chalk.dim("\n Checking provider connectivity…\n"));
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
process.exit(
|
|
131
|
+
// #532: the agent's diagnosis always renders, even when its run gate fails.
|
|
132
|
+
const report = await runCheck(config, { scope });
|
|
133
|
+
console.log(formatCheckResults(report.results, { color: colorEnabled(process.stdout), unicode: supportsUnicode() }));
|
|
134
|
+
if (report.errors.length > 0)
|
|
135
|
+
console.error(formatValidationErrors(report.errors));
|
|
136
|
+
process.exit(report.failed ? 1 : 0);
|
|
152
137
|
});
|
|
153
138
|
registerSetupCommand(program);
|
|
154
139
|
registerPublishCommand(program);
|
package/dist/cli/new.js
CHANGED
|
@@ -38,7 +38,6 @@ export const SKILL_EXTRAS = {
|
|
|
38
38
|
BETTERSTACK_API_TOKEN: { url: "https://betterstack.com/docs/logs/api" },
|
|
39
39
|
NR_API_KEY: { url: "https://one.newrelic.com/api-keys" },
|
|
40
40
|
LINEAR_API_KEY: { url: "https://linear.app/settings/api" },
|
|
41
|
-
LINEAR_TEAM_ID: { hint: "Settings > Teams > copy ID from URL" },
|
|
42
41
|
JIRA_BASE_URL: { hint: "e.g. https://your-org.atlassian.net" },
|
|
43
42
|
JIRA_EMAIL: { hint: "your Atlassian account email" },
|
|
44
43
|
JIRA_API_TOKEN: { url: "https://id.atlassian.com/manage-profile/security/api-tokens" },
|
|
@@ -77,10 +76,8 @@ export const SKILL_CREDENTIALS = {
|
|
|
77
76
|
],
|
|
78
77
|
betterstack: [{ key: "BETTERSTACK_API_TOKEN", url: "https://betterstack.com/docs/logs/api" }],
|
|
79
78
|
newrelic: [{ key: "NR_API_KEY", url: "https://one.newrelic.com/api-keys" }],
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
{ key: "LINEAR_TEAM_ID", hint: "Settings > Teams > copy ID from URL" },
|
|
83
|
-
],
|
|
79
|
+
// No LINEAR_TEAM_ID: only `sweny setup linear` reads it, and it takes --team-id (#532).
|
|
80
|
+
linear: [{ key: "LINEAR_API_KEY", url: "https://linear.app/settings/api" }],
|
|
84
81
|
jira: [
|
|
85
82
|
{ key: "JIRA_BASE_URL", hint: "e.g. https://your-org.atlassian.net" },
|
|
86
83
|
{ key: "JIRA_EMAIL", hint: "your Atlassian account email" },
|
package/dist/cli/output.d.ts
CHANGED
|
@@ -23,7 +23,19 @@ export declare function formatDagResultHuman(results: Map<string, NodeResult>, d
|
|
|
23
23
|
export declare function formatValidationErrors(errors: string[]): string;
|
|
24
24
|
export declare function extractCredentialHint(err: unknown): string | null;
|
|
25
25
|
export declare function formatCrashError(error: unknown): string;
|
|
26
|
-
export
|
|
26
|
+
export interface CheckFormatOptions {
|
|
27
|
+
/** ANSI color (`colorEnabled(stdout)`: off under NO_COLOR, a pipe, TERM=dumb). Default off. */
|
|
28
|
+
color?: boolean;
|
|
29
|
+
/** Unicode glyphs and box lines (`supportsUnicode()`); false draws ASCII. Default on. */
|
|
30
|
+
unicode?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `sweny check`'s box: one line per check (glyph, name, detail below), then a
|
|
34
|
+
* summary. Painted through {@link createPaint} and the theme `GLYPHS`,
|
|
35
|
+
* so `NO_COLOR` and ASCII terminals get the same text without color or with
|
|
36
|
+
* ASCII glyphs and box lines.
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatCheckResults(results: CheckResult[], opts?: CheckFormatOptions): string;
|
|
27
39
|
/**
|
|
28
40
|
* Node results as JSON, redacted with the run journal's redactor (secret
|
|
29
41
|
* values, secret-named fields, known token shapes). Structure and keys stay.
|
package/dist/cli/output.js
CHANGED
|
@@ -2,6 +2,8 @@ import chalk from "chalk";
|
|
|
2
2
|
import { redact } from "../journal.js";
|
|
3
3
|
import { toMermaidBlock } from "../mermaid.js";
|
|
4
4
|
import { PALETTE, ROLE_COLORS } from "../theme.js";
|
|
5
|
+
import { createPaint } from "./style.js";
|
|
6
|
+
import { glyphsFor, toAsciiDrawing } from "./terminal.js";
|
|
5
7
|
// ── Color palette (tokens from theme.ts; one color per meaning) ─
|
|
6
8
|
export const c = {
|
|
7
9
|
brand: chalk.hex(ROLE_COLORS.brand),
|
|
@@ -376,33 +378,52 @@ export function formatCrashError(error) {
|
|
|
376
378
|
];
|
|
377
379
|
return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(body), boxBottom(), ""].join("\n");
|
|
378
380
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
381
|
+
/**
|
|
382
|
+
* `sweny check`'s box: one line per check (glyph, name, detail below), then a
|
|
383
|
+
* summary. Painted through {@link createPaint} and the theme `GLYPHS`,
|
|
384
|
+
* so `NO_COLOR` and ASCII terminals get the same text without color or with
|
|
385
|
+
* ASCII glyphs and box lines.
|
|
386
|
+
*/
|
|
387
|
+
export function formatCheckResults(results, opts = {}) {
|
|
388
|
+
const paint = createPaint(opts.color ?? false);
|
|
389
|
+
const unicode = opts.unicode ?? true;
|
|
390
|
+
const g = glyphsFor(unicode);
|
|
391
|
+
const inner = BOX_WIDTH - 4;
|
|
392
|
+
const bar = paint.muted("\u2502");
|
|
393
|
+
const edge = (l, r) => paint.muted(` ${l}${"\u2500".repeat(BOX_WIDTH)}${r}`);
|
|
394
|
+
const blank = ` ${bar}${" ".repeat(BOX_WIDTH)}${bar}`;
|
|
395
|
+
// A line that fits keeps its color; a longer one wraps as plain text.
|
|
396
|
+
const row = (text) => (visLen(text) <= inner ? [text] : wrapText(text, inner)).map((l) => ` ${bar} ${padLine(l, inner)} ${bar}`);
|
|
397
|
+
const section = (rows) => [blank, ...rows, blank];
|
|
398
|
+
const body = results.flatMap((r) => {
|
|
399
|
+
const icon = r.status === "ok"
|
|
400
|
+
? paint.success(g.success)
|
|
401
|
+
: r.status === "fail"
|
|
402
|
+
? paint.error(g.failure)
|
|
403
|
+
: paint.muted(g.skipped);
|
|
404
|
+
const tone = r.status === "fail" ? paint.error : paint.muted;
|
|
405
|
+
// The detail sits under the name, indented past the glyph, each wrapped line painted on its own.
|
|
406
|
+
const detail = wrapText(r.detail, inner - 5).flatMap((l) => row(` ${tone(l)}`));
|
|
407
|
+
return [...row(`${icon} ${r.name}`), ...detail];
|
|
388
408
|
});
|
|
389
409
|
const hasFailure = results.some((r) => r.status === "fail");
|
|
390
410
|
const summary = hasFailure
|
|
391
|
-
?
|
|
411
|
+
? paint.error("One or more checks failed. Fix the issues above before running a workflow.")
|
|
392
412
|
: results.every((r) => r.status === "skip")
|
|
393
|
-
?
|
|
394
|
-
:
|
|
395
|
-
|
|
413
|
+
? paint.muted("All providers set to file mode: no network checks performed.")
|
|
414
|
+
: paint.success("All checks passed.");
|
|
415
|
+
const out = [
|
|
396
416
|
"",
|
|
397
|
-
|
|
398
|
-
...
|
|
399
|
-
|
|
400
|
-
...
|
|
401
|
-
|
|
402
|
-
...
|
|
403
|
-
|
|
417
|
+
edge("\u256D", "\u256E"),
|
|
418
|
+
...section(row(paint.strong("Provider Connectivity Check"))),
|
|
419
|
+
edge("\u251C", "\u2524"),
|
|
420
|
+
...section(body),
|
|
421
|
+
edge("\u251C", "\u2524"),
|
|
422
|
+
...section(row(summary)),
|
|
423
|
+
edge("\u2570", "\u256F"),
|
|
404
424
|
"",
|
|
405
425
|
].join("\n");
|
|
426
|
+
return unicode ? out : toAsciiDrawing(out);
|
|
406
427
|
}
|
|
407
428
|
// ── JSON output ─────────────────────────────────────────────────
|
|
408
429
|
/**
|
package/dist/harness/acp.d.ts
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
*/
|
|
47
47
|
import type { Logger, McpServerConfig, ToolContext } from "../types.js";
|
|
48
48
|
import { type HarnessEnvOptions, type SandboxMode } from "../agent-env.js";
|
|
49
|
-
import type { AgentHarness, HarnessCapabilities, HarnessCompleteRequest, HarnessId, HarnessInfo, HarnessRunRequest, HarnessRunResult, ToolClass } from "./types.js";
|
|
49
|
+
import type { AgentHarness, HarnessCapabilities, HarnessCheck, HarnessCompleteRequest, HarnessDiagnoseOptions, HarnessId, HarnessInfo, HarnessRunRequest, HarnessRunResult, ToolClass } from "./types.js";
|
|
50
50
|
import { ACP_CAPABILITIES } from "./capabilities.js";
|
|
51
51
|
import { type HarnessPolicyMode } from "./policy.js";
|
|
52
52
|
import { type SandboxWrapper } from "./sandbox-wrapper.js";
|
|
@@ -163,6 +163,14 @@ export declare class AcpHarness implements AgentHarness {
|
|
|
163
163
|
ok: false;
|
|
164
164
|
reason: string;
|
|
165
165
|
}>;
|
|
166
|
+
private notFoundReason;
|
|
167
|
+
/**
|
|
168
|
+
* `sweny check` (#532): the command resolves (the way {@link preflight}
|
|
169
|
+
* does), and the operator-named auth vars are set. The agent is not started
|
|
170
|
+
* (same reason as `preflight`), so its version and its own login are
|
|
171
|
+
* reported on the first run. No credential value is printed.
|
|
172
|
+
*/
|
|
173
|
+
diagnose(opts?: HarnessDiagnoseOptions): Promise<HarnessCheck[]>;
|
|
166
174
|
/**
|
|
167
175
|
* Env for the agent process: scoped like Claude Code's, with only the auth
|
|
168
176
|
* vars the operator named. Skill credentials are dropped either way
|
package/dist/harness/acp.js
CHANGED
|
@@ -59,6 +59,7 @@ import { defaultSandboxWrapper, prepareAgentSpawn, wrappersFrom, } from "./sandb
|
|
|
59
59
|
import { TOKEN_ENV } from "./tool-bridge/protocol.js";
|
|
60
60
|
import { translateDenyNames } from "./codex.js";
|
|
61
61
|
import { RPC_ERROR, RpcError, RpcPeer, splitCommandLine } from "./acp-rpc.js";
|
|
62
|
+
import { diagnoseEnv } from "./diagnose.js";
|
|
62
63
|
export { ACP_CAPABILITIES };
|
|
63
64
|
/** The ACP major version sweny speaks (`InitializeRequest.protocolVersion`). */
|
|
64
65
|
export const ACP_PROTOCOL_VERSION = 1;
|
|
@@ -239,15 +240,40 @@ export class AcpHarness {
|
|
|
239
240
|
*/
|
|
240
241
|
async preflight() {
|
|
241
242
|
const found = resolveExecutable(this.agent.command, overlaidProcessEnv(this.envOverlay, this.authVars), this.cwd);
|
|
242
|
-
if (!found)
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
ok: false,
|
|
246
|
-
reason: `ACP agent command "${this.agent.command}" was not found on PATH (or is not executable). Install the agent and check that \`${example}\` starts an ACP server.`,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
243
|
+
if (!found)
|
|
244
|
+
return { ok: false, reason: this.notFoundReason() };
|
|
249
245
|
return { ok: true, version: this.agentVersion };
|
|
250
246
|
}
|
|
247
|
+
notFoundReason() {
|
|
248
|
+
const example = [this.agent.command, ...this.agent.args].join(" ");
|
|
249
|
+
return `ACP agent command "${this.agent.command}" was not found on PATH (or is not executable). Install the agent and check that \`${example}\` starts an ACP server.`;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* `sweny check` (#532): the command resolves (the way {@link preflight}
|
|
253
|
+
* does), and the operator-named auth vars are set. The agent is not started
|
|
254
|
+
* (same reason as `preflight`), so its version and its own login are
|
|
255
|
+
* reported on the first run. No credential value is printed.
|
|
256
|
+
*/
|
|
257
|
+
async diagnose(opts = {}) {
|
|
258
|
+
const env = diagnoseEnv(opts.env, this.envOverlay, this.authVars);
|
|
259
|
+
const cliName = `Agent CLI (${this.id})`;
|
|
260
|
+
const found = resolveExecutable(this.agent.command, env, this.cwd);
|
|
261
|
+
const cli = found
|
|
262
|
+
? { name: cliName, status: "ok", detail: `${found} (the agent reports its version when a run starts)` }
|
|
263
|
+
: { name: cliName, status: "fail", detail: this.notFoundReason() };
|
|
264
|
+
const authName = `Agent auth (${this.id})`;
|
|
265
|
+
const missing = this.authVars.filter((v) => !env[v]?.trim());
|
|
266
|
+
const auth = this.authVars.length === 0
|
|
267
|
+
? {
|
|
268
|
+
name: authName,
|
|
269
|
+
status: "skip",
|
|
270
|
+
detail: "ACP agents handle their own login; the agent reports a missing one when a run starts",
|
|
271
|
+
}
|
|
272
|
+
: missing.length > 0
|
|
273
|
+
? { name: authName, status: "fail", detail: `Missing: ${missing.join(", ")}` }
|
|
274
|
+
: { name: authName, status: "ok", detail: `set: ${this.authVars.join(", ")}` };
|
|
275
|
+
return [cli, auth];
|
|
276
|
+
}
|
|
251
277
|
/**
|
|
252
278
|
* Env for the agent process: scoped like Claude Code's, with only the auth
|
|
253
279
|
* vars the operator named. Skill credentials are dropped either way
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { Claude, Tool, ToolContext, NodeUsage, Logger, McpServerConfig } from "../types.js";
|
|
12
12
|
import { type HarnessEnvOptions, type SandboxMode } from "../agent-env.js";
|
|
13
|
-
import type { AgentHarness, HarnessCapabilities, HarnessCompleteRequest, HarnessInfo, HarnessRunRequest, HarnessRunResult, NodePolicy, ToolClass } from "./types.js";
|
|
13
|
+
import type { AgentHarness, HarnessCapabilities, HarnessCheck, HarnessCompleteRequest, HarnessDiagnoseOptions, HarnessInfo, HarnessRunRequest, HarnessRunResult, NodePolicy, ToolClass } from "./types.js";
|
|
14
14
|
import { type HarnessPolicyMode } from "./policy.js";
|
|
15
15
|
import { CLAUDE_CODE_CAPABILITIES } from "./capabilities.js";
|
|
16
16
|
import { buildEvaluatePrompt } from "./prompts.js";
|
|
@@ -216,6 +216,13 @@ export declare class ClaudeCodeHarness implements Claude, AgentHarness {
|
|
|
216
216
|
ok: false;
|
|
217
217
|
reason: string;
|
|
218
218
|
}>;
|
|
219
|
+
/**
|
|
220
|
+
* `sweny check` (#532): the Agent SDK sweny ships with, then the credential
|
|
221
|
+
* line `sweny check` always printed (a stored login, an API key or gateway
|
|
222
|
+
* probe that lists models, or the auth mode). The env is the one
|
|
223
|
+
* {@link preflight} reads: the caller's `env` / `envRemove` applied.
|
|
224
|
+
*/
|
|
225
|
+
diagnose(opts?: HarnessDiagnoseOptions): Promise<HarnessCheck[]>;
|
|
219
226
|
/**
|
|
220
227
|
* Run a node. Runs {@link policyGate} (always `degraded: []` for Claude
|
|
221
228
|
* Code), compiles the policy to native options, and tags the result with the
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { query, createSdkMcpServer, tool as sdkTool } from "@anthropic-ai/claude-agent-sdk";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
|
-
import { realpathSync } from "node:fs";
|
|
13
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
14
15
|
import { consoleLogger } from "../types.js";
|
|
15
16
|
import { AGENT_AUTH_VARS, agentEnvOverlay, applyEnvOverlay, finishAgentEnv, overlaidProcessEnv, reportWithheldEnv, parseList, resolveEnvScope, scopeAgentEnv, withPushBlocked, mcpWithheld, resolveAgentSandbox, } from "../agent-env.js";
|
|
16
17
|
import { gitCredentialGap, policyGate } from "./policy.js";
|
|
@@ -22,7 +23,8 @@ import { startToolBridge } from "./tool-bridge/server.js";
|
|
|
22
23
|
import { parseToolResultContent, summarizeToolError, tryParseJSON } from "./parse.js";
|
|
23
24
|
import { makeAbort } from "./abort.js";
|
|
24
25
|
import { runStateRoot } from "../journal.js";
|
|
25
|
-
import { claudeCodeAuth } from "./auth.js";
|
|
26
|
+
import { claudeCodeAuth, detectClaudeCodeLogin } from "./auth.js";
|
|
27
|
+
import { diagnoseClaudeCode, diagnoseEnv } from "./diagnose.js";
|
|
26
28
|
export { buildEvaluatePrompt, CLAUDE_CODE_CAPABILITIES };
|
|
27
29
|
export { parseToolResultContent, summarizeToolError, makeAbort };
|
|
28
30
|
let cachedSdkVersion;
|
|
@@ -31,9 +33,11 @@ function claudeSdkVersion() {
|
|
|
31
33
|
if (cachedSdkVersion !== undefined)
|
|
32
34
|
return cachedSdkVersion;
|
|
33
35
|
try {
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
36
|
+
// The SDK's `exports` map has no "./package.json", so read it next to the entry file (the package root).
|
|
37
|
+
const entry = createRequire(import.meta.url).resolve("@anthropic-ai/claude-agent-sdk");
|
|
38
|
+
const pkg = JSON.parse(readFileSync(path.join(path.dirname(entry), "package.json"), "utf-8"));
|
|
39
|
+
cachedSdkVersion =
|
|
40
|
+
pkg.name === "@anthropic-ai/claude-agent-sdk" && typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
37
41
|
}
|
|
38
42
|
catch {
|
|
39
43
|
cachedSdkVersion = "unknown";
|
|
@@ -333,6 +337,22 @@ export class ClaudeCodeHarness {
|
|
|
333
337
|
return { ok: false, reason: auth.reason };
|
|
334
338
|
return { ok: true, version: claudeSdkVersion() };
|
|
335
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* `sweny check` (#532): the Agent SDK sweny ships with, then the credential
|
|
342
|
+
* line `sweny check` always printed (a stored login, an API key or gateway
|
|
343
|
+
* probe that lists models, or the auth mode). The env is the one
|
|
344
|
+
* {@link preflight} reads: the caller's `env` / `envRemove` applied.
|
|
345
|
+
*/
|
|
346
|
+
async diagnose(opts = {}) {
|
|
347
|
+
const env = diagnoseEnv(opts.env, this.envOverlay, AGENT_AUTH_VARS);
|
|
348
|
+
const sdk = {
|
|
349
|
+
name: "Agent CLI (claude)",
|
|
350
|
+
status: "ok",
|
|
351
|
+
detail: `Claude Agent SDK ${claudeSdkVersion()} (installed with sweny)`,
|
|
352
|
+
};
|
|
353
|
+
const hasLogin = opts.hasLogin ?? (() => detectClaudeCodeLogin({ env }));
|
|
354
|
+
return [sdk, ...(await diagnoseClaudeCode({ env, hasLogin, timeoutMs: opts.timeoutMs }))];
|
|
355
|
+
}
|
|
336
356
|
/**
|
|
337
357
|
* Run a node. Runs {@link policyGate} (always `degraded: []` for Claude
|
|
338
358
|
* Code), compiles the policy to native options, and tags the result with the
|