@workbench-ai/workbench 0.0.86 → 0.0.88

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 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkEA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AAuTD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,GAAE,KAGzD,GAAG,OAAO,CAAC,MAAM,CAAC,CAqMlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiEA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AAuTD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,GAAE,KAGzD,GAAG,OAAO,CAAC,MAAM,CAAC,CA2MlB"}
package/dist/index.js CHANGED
@@ -7,7 +7,6 @@ import { gzipSync } from "node:zlib";
7
7
  import { addWorkbenchRemote, addWorkbenchAgent, compareWorkbench, createWorkbenchVersionRuntimeSnapshot, createWorkbenchInspectionSnapshot, createWorkbenchAdapterAuthBundle, createWorkbenchReadOnlyInspectionSnapshot, diffWorkbenchVersions, evalWorkbenchSkill, improveWorkbenchSkill, initWorkbenchSkill, listWorkbenchAgents, listWorkbenchVersions, localWorkbenchAdapterAuthStore, parseWorkbenchAdapterAuthTarget, prepareWorkbenchCloudEvalRequest, prepareWorkbenchCloudImproveRequest, publishWorkbenchVersion, removeWorkbenchAgent, showWorkbenchRef, switchWorkbenchVersion, syncWorkbenchRemote, workbenchJobEvidenceForSnapshot, workbenchStatusSnapshot, WorkbenchCodedError, WorkbenchUserError, } from "@workbench-ai/workbench-core";
8
8
  import { normalizeWorkbenchSkillName } from "@workbench-ai/workbench-contract";
9
9
  import { emitError, emitResult } from "./output.js";
10
- import { fanOutSkill, manualFanOutCommand } from "./fanout.js";
11
10
  import { installedInventoryToJson, installSnapshotToStore, normalizeInstallSnapshotPath, readInstalledSkillsInventory, } from "./install-targets.js";
12
11
  import { startWorkbenchOpenServer } from "./open-server.js";
13
12
  const require = createRequire(import.meta.url);
@@ -449,14 +448,21 @@ export async function runCli(argv, io = {
449
448
  `next: workbench install ${preview.installHandle}`,
450
449
  ].join("\n"));
451
450
  }
452
- const remote = await ensurePublishRemote(parsed);
453
- const result = await publishWorkbenchVersion({
454
- ...core,
455
- version: optionalPositional(parsed, 1),
456
- remote,
457
- dryRun: parsed.flags["dry-run"] === true,
458
- visibility: parsePublishVisibilityFlags(parsed),
459
- });
451
+ let remote;
452
+ let result;
453
+ try {
454
+ remote = await ensurePublishRemote(parsed);
455
+ result = await publishWorkbenchVersion({
456
+ ...core,
457
+ version: optionalPositional(parsed, 1),
458
+ remote,
459
+ dryRun: parsed.flags["dry-run"] === true,
460
+ visibility: parsePublishVisibilityFlags(parsed),
461
+ });
462
+ }
463
+ catch (error) {
464
+ throw await publishErrorWithCliContext(error, parsed, remote);
465
+ }
460
466
  return emitResult("workbench.cli.publish.v1", {
461
467
  remote: result.remote,
462
468
  version: versionSummary(result.version),
@@ -907,10 +913,6 @@ async function handleInstall(parsed, io) {
907
913
  baseUrl: workbenchSource.baseUrl,
908
914
  },
909
915
  });
910
- const fanout = parsed.flags["dry-run"] === true
911
- ? skippedFanOut(result.directoryName, result.destination, result.result === "unchanged" ? "unchanged" : "dry-run")
912
- : await fanOutSkill(result.directoryName, { skillDir: result.destination });
913
- const next = installNextCommand(fanout);
914
916
  return emitResult("workbench.cli.install.v1", {
915
917
  source: sourceSummary,
916
918
  result: result.result,
@@ -921,14 +923,9 @@ async function handleInstall(parsed, io) {
921
923
  filesCopied: result.filesCopied,
922
924
  contentHash: result.contentHash,
923
925
  provenancePath: result.provenancePath,
924
- fanout: fanOutToJson(fanout),
925
- next: next,
926
+ next: null,
926
927
  ...(parsed.flags["dry-run"] === true ? { dryRun: true } : {}),
927
- }, parsed, io, () => [
928
- formatInstallOutcome(result, parsed.flags["dry-run"] === true),
929
- formatFanOut(fanout),
930
- ...(next ? [`next: ${next}`] : []),
931
- ].join("\n"));
928
+ }, parsed, io, () => formatInstallOutcome(result, parsed.flags["dry-run"] === true));
932
929
  }
933
930
  async function handleCloudEval(parsed, io) {
934
931
  const started = await startCloudExecution("eval", parsed, io);
@@ -1008,19 +1005,6 @@ async function handleCloudImprove(parsed, io) {
1008
1005
  ...(next ? [`next: ${next}`] : []),
1009
1006
  ].filter(Boolean).join("\n"));
1010
1007
  }
1011
- function skippedFanOut(name, destination, reason) {
1012
- return {
1013
- status: "skipped",
1014
- command: manualFanOutCommand(destination, name),
1015
- linkedAgents: [],
1016
- reason,
1017
- };
1018
- }
1019
- function installNextCommand(fanout) {
1020
- return fanout.status === "failed" || (fanout.status === "skipped" && fanout.reason !== "dry-run" && fanout.reason !== "unchanged")
1021
- ? fanout.command
1022
- : null;
1023
- }
1024
1008
  function formatInstallOutcome(result, dryRun) {
1025
1009
  if (dryRun) {
1026
1010
  if (result.result === "unchanged") {
@@ -1048,34 +1032,6 @@ function formatInstallOutcome(result, dryRun) {
1048
1032
  function formatFileCount(count) {
1049
1033
  return `${count} ${count === 1 ? "file" : "files"}`;
1050
1034
  }
1051
- function fanOutToJson(fanout) {
1052
- return {
1053
- status: fanout.status,
1054
- command: fanout.command,
1055
- linkedAgents: fanout.linkedAgents,
1056
- ...(fanout.additionalAgents ? { additionalAgents: fanout.additionalAgents } : {}),
1057
- ...(fanout.reason ? { reason: fanout.reason } : {}),
1058
- ...(fanout.exitCode !== undefined ? { exitCode: fanout.exitCode } : {}),
1059
- };
1060
- }
1061
- function formatFanOut(fanout) {
1062
- if (fanout.status === "skipped") {
1063
- if (fanout.reason === "dry-run") {
1064
- return "fanout: planned";
1065
- }
1066
- if (fanout.reason === "unchanged") {
1067
- return "fanout: unchanged";
1068
- }
1069
- return `fanout skipped: ${fanout.reason ?? "not available"}`;
1070
- }
1071
- if (fanout.status === "failed") {
1072
- return `fanout failed: ${fanout.reason ?? "unknown failure"}`;
1073
- }
1074
- if (fanout.linkedAgents.length === 0) {
1075
- return "fanout: completed";
1076
- }
1077
- return "fanout: completed";
1078
- }
1079
1035
  async function latestInstallVersion(record) {
1080
1036
  const handle = normalizedOwnerSkillHandle(record.handle);
1081
1037
  if (!handle) {
@@ -2689,6 +2645,33 @@ function installHandleFromCloudRemote(remote) {
2689
2645
  }
2690
2646
  return `${source.owner}/${source.skill}`;
2691
2647
  }
2648
+ async function publishErrorWithCliContext(error, parsed, remoteName) {
2649
+ if (!(error instanceof WorkbenchCodedError) || error.code !== "auth_required") {
2650
+ return error;
2651
+ }
2652
+ if (error.message.startsWith("workbench publish")) {
2653
+ return error;
2654
+ }
2655
+ return new WorkbenchCodedError("auth_required", "workbench publish requires Workbench Cloud auth.", {
2656
+ remediation: await publishAuthRemediation(parsed, remoteName, error.remediation),
2657
+ ...(error.subject ? { subject: error.subject } : {}),
2658
+ exitCode: error.exitCode,
2659
+ });
2660
+ }
2661
+ async function publishAuthRemediation(parsed, remoteName, fallback) {
2662
+ const root = path.resolve(dirFlag(parsed) ?? process.cwd());
2663
+ try {
2664
+ const snapshot = await createWorkbenchReadOnlyInspectionSnapshot({ dir: root });
2665
+ const remote = remoteName
2666
+ ? snapshot.remotes.find((entry) => entry.name === remoteName)
2667
+ : cloudRemoteLinkTargetFromRemotes(snapshot.remotes).existing;
2668
+ const source = remote ? parseWorkbenchInstallSource(remote.url) : undefined;
2669
+ return workbenchLoginRemediation(source?.baseUrl);
2670
+ }
2671
+ catch {
2672
+ return fallback ?? "Run workbench login.";
2673
+ }
2674
+ }
2692
2675
  function parseOwnerSkillHandle(input) {
2693
2676
  const handle = normalizedOwnerSkillHandle(input);
2694
2677
  if (!handle) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-ai/workbench",
3
- "version": "0.0.86",
3
+ "version": "0.0.88",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/workbench-ai/workbench.git",
@@ -20,12 +20,11 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "skills": "1.5.11",
24
23
  "yaml": "^2.8.2",
25
- "@workbench-ai/workbench-built-in-adapters": "0.0.86",
26
- "@workbench-ai/workbench-contract": "0.0.86",
27
- "@workbench-ai/workbench-core": "0.0.86",
28
- "@workbench-ai/workbench-protocol": "0.0.86"
24
+ "@workbench-ai/workbench-contract": "0.0.88",
25
+ "@workbench-ai/workbench-built-in-adapters": "0.0.88",
26
+ "@workbench-ai/workbench-protocol": "0.0.88",
27
+ "@workbench-ai/workbench-core": "0.0.88"
29
28
  },
30
29
  "devDependencies": {
31
30
  "@tailwindcss/postcss": "^4.2.2",
@@ -36,7 +35,7 @@
36
35
  "react-dom": "^19.2.0",
37
36
  "typescript": "^5.9.2",
38
37
  "vitest": "^3.2.4",
39
- "@workbench-ai/workbench-ui": "0.0.86"
38
+ "@workbench-ai/workbench-ui": "0.0.88"
40
39
  },
41
40
  "scripts": {
42
41
  "build": "rm -rf dist && tsc -p tsconfig.json && chmod 755 dist/workbench.js && node ./scripts/build-dev-open-assets.mjs",
package/dist/fanout.d.ts DELETED
@@ -1,13 +0,0 @@
1
- export interface FanOutResult {
2
- status: "completed" | "skipped" | "failed";
3
- command: string;
4
- linkedAgents: string[];
5
- additionalAgents?: number;
6
- reason?: string;
7
- exitCode?: number | null;
8
- }
9
- export declare function fanOutSkill(name: string, options: {
10
- skillDir: string;
11
- }): Promise<FanOutResult>;
12
- export declare function manualFanOutCommand(skillDir: string, name: string): string;
13
- //# sourceMappingURL=fanout.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAoEpG;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAO1E"}
package/dist/fanout.js DELETED
@@ -1,223 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { promises as fs } from "node:fs";
3
- import { createRequire } from "node:module";
4
- import os from "node:os";
5
- import path from "node:path";
6
- const require = createRequire(import.meta.url);
7
- const FANOUT_TIMEOUT_MS = 30_000;
8
- export async function fanOutSkill(name, options) {
9
- const resolved = await resolveSkillsBin().catch((error) => ({
10
- binPath: null,
11
- reason: error instanceof Error ? error.message : "Unable to resolve skills package.",
12
- }));
13
- const command = manualFanOutCommand(options.skillDir, name);
14
- if (!resolved.binPath) {
15
- return {
16
- status: "skipped",
17
- command,
18
- linkedAgents: [],
19
- reason: resolved.reason,
20
- };
21
- }
22
- const stagingRoot = await fs.mkdtemp(path.join(os.tmpdir(), "workbench-skills-fanout-"));
23
- const stagedSkillDir = path.join(stagingRoot, name);
24
- try {
25
- await fs.cp(options.skillDir, stagedSkillDir, {
26
- recursive: true,
27
- force: true,
28
- dereference: true,
29
- });
30
- const args = [
31
- resolved.binPath,
32
- "add",
33
- stagedSkillDir,
34
- "--global",
35
- "--yes",
36
- ];
37
- const child = await runNode(args, minimalChildEnv(process.env), FANOUT_TIMEOUT_MS);
38
- const { linkedAgents, additionalAgents } = parseFanOutAgents(`${child.stdout}\n${child.stderr}`);
39
- if (child.timedOut) {
40
- return {
41
- status: "failed",
42
- command,
43
- linkedAgents,
44
- ...(additionalAgents > 0 ? { additionalAgents } : {}),
45
- reason: `skills fan-out timed out after ${FANOUT_TIMEOUT_MS}ms.`,
46
- exitCode: child.exitCode,
47
- };
48
- }
49
- if (child.exitCode !== 0) {
50
- return {
51
- status: "failed",
52
- command,
53
- linkedAgents,
54
- ...(additionalAgents > 0 ? { additionalAgents } : {}),
55
- reason: conciseFailureReason(`${child.stdout}\n${child.stderr}`) ?? `skills fan-out exited ${child.exitCode}.`,
56
- exitCode: child.exitCode,
57
- };
58
- }
59
- return {
60
- status: "completed",
61
- command,
62
- linkedAgents,
63
- ...(additionalAgents > 0 ? { additionalAgents } : {}),
64
- exitCode: child.exitCode,
65
- };
66
- }
67
- catch (error) {
68
- return {
69
- status: "failed",
70
- command,
71
- linkedAgents: [],
72
- reason: error instanceof Error ? error.message : "skills fan-out failed.",
73
- };
74
- }
75
- finally {
76
- await fs.rm(stagingRoot, { recursive: true, force: true });
77
- }
78
- }
79
- export function manualFanOutCommand(skillDir, name) {
80
- const quotedName = quoteShellArg(name);
81
- return [
82
- "tmpdir=$(mktemp -d)",
83
- `cp -R ${quoteShellArg(skillDir)} "$tmpdir"/${quotedName}`,
84
- `skills add "$tmpdir"/${quotedName} --global --yes`,
85
- ].join(" && ");
86
- }
87
- function quoteShellArg(value) {
88
- return /^[A-Za-z0-9_./:=@+-]+$/u.test(value)
89
- ? value
90
- : `'${value.replace(/'/gu, "'\\''")}'`;
91
- }
92
- async function resolveSkillsBin() {
93
- const packageJsonPath = require.resolve("skills/package.json");
94
- const packageRoot = path.dirname(packageJsonPath);
95
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
96
- const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.skills;
97
- if (!bin) {
98
- throw new Error("The skills package does not declare a skills bin.");
99
- }
100
- return {
101
- binPath: path.resolve(packageRoot, bin),
102
- };
103
- }
104
- function runNode(args, env, timeoutMs) {
105
- return new Promise((resolve) => {
106
- const child = spawn(process.execPath, args, {
107
- env,
108
- stdio: ["ignore", "pipe", "pipe"],
109
- windowsHide: true,
110
- });
111
- let stdout = "";
112
- let stderr = "";
113
- let timedOut = false;
114
- const timer = setTimeout(() => {
115
- timedOut = true;
116
- child.kill("SIGTERM");
117
- }, timeoutMs);
118
- child.stdout?.setEncoding("utf8");
119
- child.stderr?.setEncoding("utf8");
120
- child.stdout?.on("data", (chunk) => {
121
- stdout += chunk;
122
- });
123
- child.stderr?.on("data", (chunk) => {
124
- stderr += chunk;
125
- });
126
- child.on("error", (error) => {
127
- clearTimeout(timer);
128
- resolve({
129
- exitCode: 1,
130
- stdout,
131
- stderr: stderr || error.message,
132
- timedOut,
133
- });
134
- });
135
- child.on("close", (exitCode) => {
136
- clearTimeout(timer);
137
- resolve({
138
- exitCode,
139
- stdout,
140
- stderr,
141
- timedOut,
142
- });
143
- });
144
- });
145
- }
146
- // The skills CLI changes behavior when it detects it is running inside an
147
- // agent (via @vercel/detect-agent reading agent-specific environment
148
- // variables). Fan-out must always run in machine mode, so the child gets a
149
- // minimal allowlisted environment instead of a hand-maintained blacklist of
150
- // every agent's marker variables.
151
- function minimalChildEnv(env) {
152
- const allowed = [
153
- "PATH",
154
- "HOME",
155
- "USER",
156
- "SHELL",
157
- "TMPDIR",
158
- "TMP",
159
- "TEMP",
160
- "LANG",
161
- "NODE_EXTRA_CA_CERTS",
162
- "HTTP_PROXY",
163
- "HTTPS_PROXY",
164
- "NO_PROXY",
165
- "http_proxy",
166
- "https_proxy",
167
- "no_proxy",
168
- "SYSTEMROOT",
169
- "SystemRoot",
170
- "COMSPEC",
171
- "APPDATA",
172
- "LOCALAPPDATA",
173
- "USERPROFILE",
174
- ];
175
- const next = {};
176
- for (const key of Object.keys(env)) {
177
- if (allowed.includes(key) || key.startsWith("XDG_") || key.startsWith("LC_")) {
178
- next[key] = env[key];
179
- }
180
- }
181
- return next;
182
- }
183
- function parseFanOutAgents(output) {
184
- const cleaned = stripAnsi(output);
185
- const linked = new Set();
186
- let additionalAgents = 0;
187
- for (const line of cleaned.split(/\r?\n/u)) {
188
- const normalized = line.replace(/[\u2502\u25c7\u25cf\u25a0\u2713\u2717]/gu, " ").trim();
189
- const match = /^(?:universal|symlinked|copied|copy\s*\u2192|symlink\s*\u2192):\s*(.+)$/u.exec(normalized);
190
- if (!match) {
191
- continue;
192
- }
193
- for (const rawAgent of match[1].split(",").map((entry) => entry.trim())) {
194
- const moreMatch = /\+(\d+) more$/u.exec(rawAgent);
195
- if (moreMatch) {
196
- additionalAgents += Number(moreMatch[1]);
197
- }
198
- const agent = rawAgent.replace(/\s*\+\d+ more$/u, "").trim();
199
- if (agent) {
200
- linked.add(agent);
201
- }
202
- }
203
- }
204
- return {
205
- linkedAgents: [...linked].sort((left, right) => left.localeCompare(right)),
206
- additionalAgents,
207
- };
208
- }
209
- function conciseFailureReason(output) {
210
- const lines = stripAnsi(output)
211
- .split(/\r?\n/u)
212
- .map((line) => line.replace(/[\u2500-\u257f\u25a0\u25cf\u25c7\u2713\u2717]/gu, " ").trim())
213
- .filter((line) => line.length > 0 &&
214
- !/^skills\s*$/iu.test(line) &&
215
- !/^Tip:/u.test(line) &&
216
- !/^(Source|Local path|Found \d+ skill|Available skills|Installed \d+ skill|Done!|Review skills)/u.test(line) &&
217
- !/^- /u.test(line));
218
- const reason = lines.at(-1);
219
- return reason ? (reason.length > 200 ? `${reason.slice(0, 197)}...` : reason) : null;
220
- }
221
- function stripAnsi(value) {
222
- return value.replace(/\x1B\[[0-?]*[ -/]*[@-~]/gu, "");
223
- }