workplane 0.1.2 → 0.3.0

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  # workplane
2
2
 
3
+ [![website](https://img.shields.io/badge/website-mindstaq.github.io%2Fworkplane-3dd6c6)](https://mindstaq.github.io/workplane/)
3
4
  [![npm version](https://img.shields.io/npm/v/workplane)](https://www.npmjs.com/package/workplane)
4
5
  [![status](https://img.shields.io/badge/status-beta-orange)](https://github.com/MindStaq/workplane)
5
6
  [![license](https://img.shields.io/npm/l/workplane)](https://github.com/MindStaq/workplane/blob/main/packages/workplane/LICENSE)
package/dist/cli.js CHANGED
@@ -1,4 +1,110 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../workplans/src/providers/anthropic.ts
13
+ var anthropic_exports = {};
14
+ __export(anthropic_exports, {
15
+ runAnthropicStep: () => runAnthropicStep
16
+ });
17
+ import Anthropic from "@anthropic-ai/sdk";
18
+ async function runAnthropicStep(opts) {
19
+ const client = new Anthropic({ apiKey: opts.apiKey ?? process.env.ANTHROPIC_API_KEY });
20
+ const message = await client.messages.create({
21
+ model: opts.model,
22
+ max_tokens: 4096,
23
+ messages: [{ role: "user", content: opts.prompt }]
24
+ });
25
+ const block = message.content[0];
26
+ return block.type === "text" ? block.text : "";
27
+ }
28
+ var init_anthropic = __esm({
29
+ "../workplans/src/providers/anthropic.ts"() {
30
+ "use strict";
31
+ }
32
+ });
33
+
34
+ // ../workplans/src/providers/openai.ts
35
+ var openai_exports = {};
36
+ __export(openai_exports, {
37
+ runOpenAIStep: () => runOpenAIStep
38
+ });
39
+ import OpenAI from "openai";
40
+ async function runOpenAIStep(opts) {
41
+ const client = new OpenAI({ apiKey: opts.apiKey ?? process.env.OPENAI_API_KEY });
42
+ const completion = await client.chat.completions.create({
43
+ model: opts.model,
44
+ messages: [{ role: "user", content: opts.prompt }]
45
+ });
46
+ return completion.choices[0]?.message.content ?? "";
47
+ }
48
+ var init_openai = __esm({
49
+ "../workplans/src/providers/openai.ts"() {
50
+ "use strict";
51
+ }
52
+ });
53
+
54
+ // ../workplans/src/providers/ollama.ts
55
+ var ollama_exports = {};
56
+ __export(ollama_exports, {
57
+ runOllamaStep: () => runOllamaStep
58
+ });
59
+ import { Ollama } from "ollama";
60
+ async function runOllamaStep(opts) {
61
+ const host = opts.host ?? process.env.OLLAMA_HOST ?? "http://localhost:11434";
62
+ const client = new Ollama({ host });
63
+ const response = await client.generate({ model: opts.model, prompt: opts.prompt });
64
+ return response.response;
65
+ }
66
+ var init_ollama = __esm({
67
+ "../workplans/src/providers/ollama.ts"() {
68
+ "use strict";
69
+ }
70
+ });
71
+
72
+ // ../workplans/src/providers/shell.ts
73
+ var shell_exports = {};
74
+ __export(shell_exports, {
75
+ runShellStep: () => runShellStep
76
+ });
77
+ import { exec } from "child_process";
78
+ import { promisify } from "util";
79
+ async function runShellStep(opts) {
80
+ const { stdout } = await execAsync(opts.command, {
81
+ cwd: opts.cwd,
82
+ maxBuffer: 10 * 1024 * 1024
83
+ });
84
+ return stdout;
85
+ }
86
+ var execAsync;
87
+ var init_shell = __esm({
88
+ "../workplans/src/providers/shell.ts"() {
89
+ "use strict";
90
+ execAsync = promisify(exec);
91
+ }
92
+ });
93
+
94
+ // ../workplans/src/providers/file.ts
95
+ var file_exports = {};
96
+ __export(file_exports, {
97
+ runFileStep: () => runFileStep
98
+ });
99
+ import { readFile } from "fs/promises";
100
+ async function runFileStep(opts) {
101
+ return readFile(opts.path, opts.encoding ?? "utf8");
102
+ }
103
+ var init_file = __esm({
104
+ "../workplans/src/providers/file.ts"() {
105
+ "use strict";
106
+ }
107
+ });
2
108
 
3
109
  // ../core/src/env.ts
4
110
  import { config as dotenvConfig } from "dotenv";
@@ -64,6 +170,292 @@ async function workplaneFetch(serverUrl2, path, options = {}) {
64
170
  return await response.json();
65
171
  }
66
172
 
173
+ // ../agent-skills/src/skills/code-review.ts
174
+ function codeReviewPlan(opts = {}) {
175
+ const { repoPath = ".", model = "claude-haiku-4-5-20251001", branch } = opts;
176
+ const diffCmd = branch ? `git diff ${branch}..HEAD` : "git diff HEAD~1";
177
+ return {
178
+ id: "code-review",
179
+ name: "Code Review",
180
+ steps: [
181
+ {
182
+ id: "diff",
183
+ name: "Git Diff",
184
+ adapter: "shell",
185
+ provider: "shell",
186
+ payload: { command: diffCmd, cwd: repoPath },
187
+ output: { dest: "next" }
188
+ },
189
+ {
190
+ id: "summarize",
191
+ name: "Summarize",
192
+ adapter: "ollama",
193
+ provider: "ollama",
194
+ model: "llama3",
195
+ payload: { prompt: "Summarize these code changes concisely:\n{{prevOutput}}" },
196
+ output: { dest: "next" }
197
+ },
198
+ {
199
+ id: "critique",
200
+ name: "Critique",
201
+ adapter: "anthropic",
202
+ provider: "anthropic",
203
+ model,
204
+ payload: { prompt: "Review this summary for correctness, security issues, and code quality:\n{{prevOutput}}" }
205
+ }
206
+ ]
207
+ };
208
+ }
209
+
210
+ // ../agent-skills/src/skills/summarize-file.ts
211
+ function summarizeFilePlan(opts) {
212
+ const { filePath, provider = "ollama", model = "llama3" } = opts;
213
+ return {
214
+ id: "summarize-file",
215
+ name: "Summarize File",
216
+ steps: [
217
+ {
218
+ id: "read",
219
+ name: "Read File",
220
+ adapter: "file",
221
+ provider: "file",
222
+ payload: { path: filePath },
223
+ output: { dest: "next" }
224
+ },
225
+ {
226
+ id: "summarize",
227
+ name: "Summarize",
228
+ adapter: provider,
229
+ provider,
230
+ model,
231
+ payload: { prompt: "Summarize the following file content concisely:\n{{prevOutput}}" }
232
+ }
233
+ ]
234
+ };
235
+ }
236
+
237
+ // ../agent-skills/src/skills/index.ts
238
+ var SkillRegistry = class {
239
+ skills = /* @__PURE__ */ new Map();
240
+ register(entry) {
241
+ this.skills.set(entry.name, entry);
242
+ }
243
+ get(name) {
244
+ return this.skills.get(name);
245
+ }
246
+ list() {
247
+ return Array.from(this.skills.values());
248
+ }
249
+ };
250
+ function createDefaultRegistry() {
251
+ const registry = new SkillRegistry();
252
+ registry.register({
253
+ name: "code-review",
254
+ description: "Git diff \u2192 local summarize (ollama) \u2192 frontier critique (anthropic)",
255
+ buildPlan: (opts) => codeReviewPlan({
256
+ repoPath: typeof opts.repo === "string" ? opts.repo : void 0,
257
+ model: typeof opts.model === "string" ? opts.model : void 0,
258
+ branch: typeof opts.branch === "string" ? opts.branch : void 0
259
+ })
260
+ });
261
+ registry.register({
262
+ name: "summarize-file",
263
+ description: "Read file \u2192 summarize with configurable provider (default: ollama)",
264
+ buildPlan: (opts) => {
265
+ const filePath = typeof opts.file === "string" ? opts.file : "";
266
+ if (!filePath) throw new Error("--file is required for summarize-file");
267
+ return summarizeFilePlan({
268
+ filePath,
269
+ provider: typeof opts.provider === "string" ? opts.provider : void 0,
270
+ model: typeof opts.model === "string" ? opts.model : void 0
271
+ });
272
+ }
273
+ });
274
+ return registry;
275
+ }
276
+ function listSkills(registry) {
277
+ for (const skill of registry.list()) {
278
+ process.stdout.write(` ${skill.name.padEnd(20)} ${skill.description}
279
+ `);
280
+ }
281
+ }
282
+
283
+ // ../workplans/src/template.ts
284
+ function applyTemplate(payload, prevOutput) {
285
+ return Object.fromEntries(
286
+ Object.entries(payload).map(([k, v]) => [
287
+ k,
288
+ typeof v === "string" ? v.replaceAll("{{prevOutput}}", prevOutput) : v
289
+ ])
290
+ );
291
+ }
292
+
293
+ // ../workplans/src/runner.ts
294
+ var INLINE_PROVIDERS = /* @__PURE__ */ new Set(["anthropic", "openai", "ollama", "shell", "file"]);
295
+ async function runInlineStep(step, prompt) {
296
+ const provider = step.provider;
297
+ if (provider === "anthropic") {
298
+ const { runAnthropicStep: runAnthropicStep2 } = await Promise.resolve().then(() => (init_anthropic(), anthropic_exports));
299
+ return runAnthropicStep2({ model: step.model ?? "claude-haiku-4-5-20251001", prompt });
300
+ }
301
+ if (provider === "openai") {
302
+ const { runOpenAIStep: runOpenAIStep2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
303
+ return runOpenAIStep2({ model: step.model ?? "gpt-4o-mini", prompt });
304
+ }
305
+ if (provider === "ollama") {
306
+ const { runOllamaStep: runOllamaStep2 } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
307
+ return runOllamaStep2({ model: step.model ?? "llama3", prompt });
308
+ }
309
+ if (provider === "shell") {
310
+ const { runShellStep: runShellStep2 } = await Promise.resolve().then(() => (init_shell(), shell_exports));
311
+ return runShellStep2({
312
+ command: String(step.payload.command ?? ""),
313
+ cwd: typeof step.payload.cwd === "string" ? step.payload.cwd : void 0
314
+ });
315
+ }
316
+ if (provider === "file") {
317
+ const { runFileStep: runFileStep2 } = await Promise.resolve().then(() => (init_file(), file_exports));
318
+ return runFileStep2({ path: String(step.payload.path ?? "") });
319
+ }
320
+ throw new Error(`unknown inline provider: ${provider}`);
321
+ }
322
+ var SequentialWorkplanRunner = class {
323
+ async run(plan, ctx) {
324
+ const results = [];
325
+ let prevOutput = "";
326
+ let planSucceeded = true;
327
+ for (const step of plan.steps) {
328
+ const payload = prevOutput ? applyTemplate(step.payload, prevOutput) : step.payload;
329
+ const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
330
+ const start = Date.now();
331
+ let output = "";
332
+ let exitCode = 0;
333
+ try {
334
+ if (step.provider && INLINE_PROVIDERS.has(step.provider)) {
335
+ output = await runInlineStep({ ...step, payload }, prompt);
336
+ } else {
337
+ const node = await ctx.resolveNode(step.requires ?? [], step.provider);
338
+ const result = await node.execute({ ...step, payload });
339
+ output = result.output;
340
+ exitCode = result.exitCode;
341
+ }
342
+ } catch (err) {
343
+ exitCode = 1;
344
+ output = err instanceof Error ? err.message : String(err);
345
+ planSucceeded = false;
346
+ const stepResult2 = {
347
+ stepId: step.id,
348
+ output,
349
+ exitCode,
350
+ durationMs: Date.now() - start
351
+ };
352
+ results.push(stepResult2);
353
+ ctx.emitResult(step.id, step.name, output);
354
+ if (!step.continueOnError) {
355
+ return {
356
+ planId: plan.id,
357
+ status: "step_failed",
358
+ steps: results,
359
+ succeeded: false
360
+ };
361
+ }
362
+ prevOutput = "";
363
+ continue;
364
+ }
365
+ const dest = step.output?.dest ?? "stdout";
366
+ const stepResult = {
367
+ stepId: step.id,
368
+ output,
369
+ exitCode,
370
+ durationMs: Date.now() - start
371
+ };
372
+ results.push(stepResult);
373
+ if (dest === "stdout" || dest === "artifact") {
374
+ ctx.emitResult(step.id, step.name, output);
375
+ prevOutput = "";
376
+ } else if (dest === "next") {
377
+ prevOutput = output;
378
+ }
379
+ }
380
+ return {
381
+ planId: plan.id,
382
+ status: planSucceeded ? "completed" : "failed",
383
+ steps: results,
384
+ succeeded: planSucceeded
385
+ };
386
+ }
387
+ };
388
+
389
+ // ../workplans/src/sinks/stdout.ts
390
+ function printStepResult(stepName, output) {
391
+ process.stdout.write(`=== step: ${stepName} ===
392
+ ${output}
393
+
394
+ `);
395
+ }
396
+
397
+ // ../workplans/src/context.ts
398
+ var WorkplaneNodeHandle = class {
399
+ constructor(serverUrl2, nodeToken) {
400
+ this.serverUrl = serverUrl2;
401
+ this.nodeToken = nodeToken;
402
+ }
403
+ serverUrl;
404
+ nodeToken;
405
+ async execute(step) {
406
+ const start = Date.now();
407
+ const taskRes = await fetch(`${this.serverUrl}/tasks`, {
408
+ method: "POST",
409
+ headers: {
410
+ "content-type": "application/json",
411
+ authorization: `Bearer ${this.nodeToken}`
412
+ },
413
+ body: JSON.stringify({
414
+ kind: "workplan.step",
415
+ adapter: step.adapter,
416
+ payload: step.payload,
417
+ requires: step.requires ?? []
418
+ })
419
+ });
420
+ if (!taskRes.ok) {
421
+ throw new Error(`failed to submit step ${step.id}: ${taskRes.status}`);
422
+ }
423
+ const task = await taskRes.json();
424
+ const output = await this.pollUntilDone(task.id);
425
+ return { stepId: step.id, output, exitCode: 0, durationMs: Date.now() - start };
426
+ }
427
+ async pollUntilDone(taskId) {
428
+ const terminalStatuses = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
429
+ for (; ; ) {
430
+ await new Promise((r) => setTimeout(r, 1e3));
431
+ const res = await fetch(`${this.serverUrl}/tasks/${taskId}`, {
432
+ headers: { authorization: `Bearer ${this.nodeToken}` }
433
+ });
434
+ const task = await res.json();
435
+ if (terminalStatuses.has(task.status)) {
436
+ if (task.status !== "succeeded") {
437
+ throw new Error(`step task ${taskId} ended with status: ${task.status}`);
438
+ }
439
+ return typeof task.payload?.output === "string" ? task.payload.output : "";
440
+ }
441
+ }
442
+ }
443
+ };
444
+ var LocalWorkplanContext = class {
445
+ serverUrl;
446
+ nodeToken;
447
+ constructor(opts = {}) {
448
+ this.serverUrl = opts.serverUrl ?? process.env.WORKPLANE_SERVER_URL ?? "http://localhost:8787";
449
+ this.nodeToken = opts.nodeToken ?? process.env.WORKPLANE_NODE_TOKEN ?? "";
450
+ }
451
+ async resolveNode(_requires, _provider) {
452
+ return new WorkplaneNodeHandle(this.serverUrl, this.nodeToken);
453
+ }
454
+ emitResult(_stepId, stepName, output) {
455
+ printStepResult(stepName, output);
456
+ }
457
+ };
458
+
67
459
  // ../cli/src/index.ts
68
460
  loadLocalEnv();
69
461
  var serverUrl = process.env.WORKPLANE_SERVER_URL ?? "http://localhost:8787";
@@ -111,13 +503,18 @@ function usage() {
111
503
  " task submit shell --command <cmd> [--repo <url>] [--branch <b>] [--cwd <path>]",
112
504
  " task submit aider --prompt <text> --repo <url> [--model <m>] [--test-command <cmd>]",
113
505
  " task submit inference --model <m> --prompt <text> [--requires ollama]",
114
- " task submit harness --harness <codex|claude-code> --repo <url> --prompt <text>",
506
+ " task submit harness --harness <codex|claude-code> --repo <url> --prompt <text> [--interactive]",
115
507
  " tasks [--status <status>]",
116
508
  " task show|logs|retry|cancel <taskId>",
117
509
  " runs [--task-id <id>] [--status <status>]",
118
510
  " run show <runId>",
511
+ " run input <runId> --stdin <text>",
512
+ " run input <runId> --signal <SIGTERM|SIGKILL|SIGINT>",
513
+ " run input <runId> --resize <COLSxROWS>",
119
514
  " logs <runId|taskId>",
120
- " artifacts <runId>"
515
+ " artifacts <runId>",
516
+ " skill list",
517
+ " skill run <name> [--key value ...]"
121
518
  ].join("\n")
122
519
  );
123
520
  }
@@ -201,6 +598,7 @@ async function submitHarness(args) {
201
598
  const model = valueArg(args, "--model");
202
599
  const branch = valueArg(args, "--branch");
203
600
  const testCommand = valueArg(args, "--test-command");
601
+ const interactive = args.includes("--interactive") || void 0;
204
602
  const requires = parseCsv(valueArg(args, "--requires") ?? `${harness},git`);
205
603
  const task = await httpJson("/tasks", {
206
604
  method: "POST",
@@ -209,7 +607,7 @@ async function submitHarness(args) {
209
607
  kind: "agent.run",
210
608
  adapter: harness,
211
609
  requires,
212
- payload: { prompt, repo, model, branch, testCommand }
610
+ payload: { prompt, repo, model, branch, testCommand, interactive }
213
611
  }
214
612
  });
215
613
  printJson(task);
@@ -304,6 +702,36 @@ async function main() {
304
702
  printJson(runs);
305
703
  return;
306
704
  }
705
+ if (command === "run" && subcommand === "input") {
706
+ const runId = rest[0];
707
+ if (!runId) {
708
+ throw new Error("run id is required");
709
+ }
710
+ const inputArgs = rest.slice(1);
711
+ const stdinData = valueArg(inputArgs, "--stdin");
712
+ const signal = valueArg(inputArgs, "--signal");
713
+ const resize = valueArg(inputArgs, "--resize");
714
+ let body;
715
+ if (stdinData !== void 0) {
716
+ body = { kind: "stdin", payload: { data: stdinData } };
717
+ } else if (signal !== void 0) {
718
+ if (signal !== "SIGTERM" && signal !== "SIGKILL" && signal !== "SIGINT") {
719
+ throw new Error("--signal must be SIGTERM, SIGKILL, or SIGINT");
720
+ }
721
+ body = { kind: "signal", payload: { signal } };
722
+ } else if (resize !== void 0) {
723
+ const match = /^(\d+)x(\d+)$/.exec(resize);
724
+ if (!match) {
725
+ throw new Error("--resize must be in COLSxROWS format, e.g. 220x50");
726
+ }
727
+ body = { kind: "resize", payload: { cols: Number(match[1]), rows: Number(match[2]) } };
728
+ } else {
729
+ throw new Error("one of --stdin, --signal, or --resize is required");
730
+ }
731
+ const result = await httpJson(`/runs/${runId}/input`, { method: "POST", operator: true, body });
732
+ printJson(result);
733
+ return;
734
+ }
307
735
  if (command === "run" && subcommand === "show") {
308
736
  const runId = rest[0];
309
737
  if (!runId) {
@@ -330,6 +758,41 @@ async function main() {
330
758
  printJson(artifacts);
331
759
  return;
332
760
  }
761
+ if (command === "skill" && subcommand === "list") {
762
+ const registry = createDefaultRegistry();
763
+ process.stdout.write("Available skills:\n");
764
+ listSkills(registry);
765
+ return;
766
+ }
767
+ if (command === "skill" && subcommand === "run") {
768
+ const skillName = rest[0];
769
+ if (!skillName) {
770
+ throw new Error("skill name is required");
771
+ }
772
+ const registry = createDefaultRegistry();
773
+ const skill = registry.get(skillName);
774
+ if (!skill) {
775
+ throw new Error(`unknown skill: ${skillName}. Run 'workplane skill list' to see available skills.`);
776
+ }
777
+ const optArgs = rest.slice(1);
778
+ const options = {};
779
+ for (let i = 0; i < optArgs.length - 1; i++) {
780
+ if (optArgs[i].startsWith("--")) {
781
+ options[optArgs[i].slice(2)] = optArgs[i + 1];
782
+ i++;
783
+ }
784
+ }
785
+ const plan = skill.buildPlan(options);
786
+ const runner = new SequentialWorkplanRunner();
787
+ const ctx = new LocalWorkplanContext();
788
+ const result = await runner.run(plan, ctx);
789
+ if (!result.succeeded) {
790
+ process.stderr.write(`skill '${skillName}' failed at step '${result.steps.at(-1)?.stepId ?? "unknown"}'
791
+ `);
792
+ process.exit(1);
793
+ }
794
+ return;
795
+ }
333
796
  usage();
334
797
  }
335
798
  main().catch((error) => {