wordspace 0.0.8 → 0.0.10

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.
@@ -19,8 +19,7 @@ export async function init(force) {
19
19
  Your project is ready. Next steps:
20
20
 
21
21
  1. Open this directory in your editor
22
- 2. Start Claude Code: claude
23
- 3. Run a workflow: prose run workflows/<name>.prose
22
+ 2. Run a workflow: wordspace run workflows/<name>.prose
24
23
 
25
24
  Browse more workflows: wordspace search
26
25
  Add a workflow: wordspace add <name>
@@ -1 +1 @@
1
- export declare function run(target: string | undefined, force: boolean): Promise<void>;
1
+ export declare function run(target: string | undefined, force: boolean, harnessArg?: string): Promise<void>;
@@ -1,22 +1,83 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { parseGitHubUri, toRawUrl, fileName } from "../lib/github-uri.js";
4
4
  import { httpGet, getAuthHeaders } from "../lib/workflows.js";
5
- import { isClaudeInstalled, spawnClaude } from "../lib/spawn.js";
5
+ import { HARNESSES, detectInstalled, spawnHarness, } from "../lib/harness.js";
6
+ import { pickOne } from "../lib/prompt.js";
6
7
  import { ensureInit } from "../steps/auto-init.js";
7
8
  import * as log from "../lib/log.js";
8
- export async function run(target, force) {
9
+ /**
10
+ * Build the prompt string for a given harness.
11
+ *
12
+ * Skill-native harnesses (Claude Code) get the short `/open-prose run <path>` command.
13
+ * All others receive the .prose file content inline with execution instructions.
14
+ */
15
+ function buildPrompt(harness, prosePath, cwd) {
16
+ if (harness.skillNative) {
17
+ return `/open-prose run ${prosePath}`;
18
+ }
19
+ const fullPath = join(cwd, prosePath);
20
+ const content = readFileSync(fullPath, "utf-8");
21
+ return [
22
+ `You are executing a wordspace workflow defined in the file "${prosePath}".`,
23
+ `Follow the steps below exactly. Write all output files to the "output/" directory.`,
24
+ "",
25
+ "--- BEGIN WORKFLOW ---",
26
+ content,
27
+ "--- END WORKFLOW ---",
28
+ ].join("\n");
29
+ }
30
+ export async function run(target, force, harnessArg) {
9
31
  if (!target) {
10
32
  log.error("Usage: wordspace run <github:owner/repo/path.prose | local-path>");
11
33
  process.exit(1);
12
34
  }
13
- if (!isClaudeInstalled()) {
14
- log.error("claude CLI not found. Install Claude Code first: https://claude.ai/code");
15
- process.exit(1);
35
+ // --- Harness selection ---
36
+ let harness;
37
+ if (harnessArg) {
38
+ const match = HARNESSES.find((h) => h.bin === harnessArg || h.name.toLowerCase() === harnessArg.toLowerCase());
39
+ if (!match) {
40
+ log.error(`Unknown harness: ${harnessArg}`);
41
+ log.info("Available harnesses:");
42
+ for (const h of HARNESSES) {
43
+ log.info(` ${h.bin.padEnd(14)} ${h.name}`);
44
+ }
45
+ process.exit(1);
46
+ }
47
+ // Verify it's installed
48
+ const installed = detectInstalled();
49
+ if (!installed.some((h) => h.bin === match.bin)) {
50
+ log.error(`${match.name} is not installed.`);
51
+ log.info(`Install it: ${match.installUrl}`);
52
+ process.exit(1);
53
+ }
54
+ harness = match;
55
+ }
56
+ else {
57
+ const installed = detectInstalled();
58
+ if (installed.length === 0) {
59
+ log.error("No supported coding agent found.");
60
+ log.info("Install one of the following:");
61
+ for (const h of HARNESSES) {
62
+ log.info(` ${h.name} → ${h.installUrl}`);
63
+ }
64
+ process.exit(1);
65
+ }
66
+ else if (installed.length === 1) {
67
+ harness = installed[0];
68
+ }
69
+ else {
70
+ log.step("Pick a coding agent");
71
+ harness = await pickOne(installed, (h) => h.mode === "headless" ? `${h.name} (headless)` : h.name, "Select harness");
72
+ }
73
+ }
74
+ log.success(`Using ${harness.name}`);
75
+ if (harness.mode === "headless") {
76
+ log.warn("Headless mode — the agent will execute and exit without interaction.");
16
77
  }
17
78
  const cwd = process.cwd();
18
- // Auto-init if needed
19
- ensureInit(cwd);
79
+ // Auto-init if needed (harness-aware)
80
+ ensureInit(cwd, harness);
20
81
  let prosePath;
21
82
  if (target.startsWith("github:")) {
22
83
  // Remote: download from GitHub
@@ -46,10 +107,15 @@ export async function run(target, force) {
46
107
  }
47
108
  prosePath = target;
48
109
  }
49
- const prompt = `/prose run ${prosePath}`;
110
+ const prompt = buildPrompt(harness, prosePath, cwd);
50
111
  console.log();
51
- log.info(prompt);
112
+ if (harness.skillNative) {
113
+ log.info(prompt);
114
+ }
115
+ else {
116
+ log.info(`Running ${prosePath} via ${harness.name}...`);
117
+ }
52
118
  console.log();
53
- const exitCode = spawnClaude(prompt, cwd);
119
+ const exitCode = spawnHarness(harness, prompt, cwd);
54
120
  process.exit(exitCode);
55
121
  }
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { search } from "./commands/search.js";
4
4
  import { add } from "./commands/add.js";
5
5
  import { run } from "./commands/run.js";
6
6
  import * as log from "./lib/log.js";
7
- const VERSION = "0.0.8";
7
+ const VERSION = "0.0.10";
8
8
  const HELP = `
9
9
  Usage: wordspace <command> [options]
10
10
 
@@ -12,12 +12,13 @@ Commands:
12
12
  init Bootstrap a new wordspace project
13
13
  search [q] List available workflows (optionally filter by query)
14
14
  add <name> Download specific workflow(s) by name
15
- run <target> Run a .prose workflow via Claude Code
15
+ run <target> Run a .prose workflow via a coding agent
16
16
 
17
17
  Options:
18
- --force Re-run all steps / overwrite existing files
19
- --help Show this help message
20
- --version Show version number
18
+ --force Re-run all steps / overwrite existing files
19
+ --harness <name> Use a specific coding agent (e.g. claude, aider, goose)
20
+ --help Show this help message
21
+ --version Show version number
21
22
  `.trim();
22
23
  async function main() {
23
24
  const args = process.argv.slice(2);
@@ -29,7 +30,18 @@ async function main() {
29
30
  console.log(VERSION);
30
31
  process.exit(0);
31
32
  }
32
- const positional = args.filter((a) => !a.startsWith("-"));
33
+ const harnessIdx = args.indexOf("--harness");
34
+ const harnessArg = harnessIdx !== -1 ? args[harnessIdx + 1] : undefined;
35
+ // Filter out flags and their values from positional args
36
+ const positional = [];
37
+ for (let i = 0; i < args.length; i++) {
38
+ if (args[i] === "--harness") {
39
+ i++; // skip the value
40
+ }
41
+ else if (!args[i].startsWith("-")) {
42
+ positional.push(args[i]);
43
+ }
44
+ }
33
45
  const command = positional[0];
34
46
  const force = args.includes("--force");
35
47
  if (command === "init") {
@@ -42,7 +54,7 @@ async function main() {
42
54
  await add(positional.slice(1), force);
43
55
  }
44
56
  else if (command === "run") {
45
- await run(positional[1], force);
57
+ await run(positional[1], force, harnessArg);
46
58
  }
47
59
  else if (!command) {
48
60
  console.log(HELP);
@@ -0,0 +1,20 @@
1
+ export interface Harness {
2
+ /** Display name shown in the picker. */
3
+ name: string;
4
+ /** Binary name on PATH. */
5
+ bin: string;
6
+ /** Build the CLI args from the prompt string. */
7
+ args: (prompt: string) => string[];
8
+ /** URL or install command the user can use to install this harness. */
9
+ installUrl: string;
10
+ /** Whether the harness runs interactively or headless. */
11
+ mode: "interactive" | "headless";
12
+ /** Whether the harness natively understands /open-prose skill commands. */
13
+ skillNative: boolean;
14
+ }
15
+ /** All harnesses wordspace knows about, in display order. */
16
+ export declare const HARNESSES: Harness[];
17
+ /** Return only the harnesses whose binary is found on PATH. */
18
+ export declare function detectInstalled(): Harness[];
19
+ /** Spawn a harness interactively, handing over the terminal. */
20
+ export declare function spawnHarness(harness: Harness, prompt: string, cwd: string): number;
@@ -0,0 +1,104 @@
1
+ import { spawnSync, execSync } from "node:child_process";
2
+ /** All harnesses wordspace knows about, in display order. */
3
+ export const HARNESSES = [
4
+ {
5
+ name: "Claude Code",
6
+ bin: "claude",
7
+ args: (prompt) => [prompt],
8
+ installUrl: "npm i -g @anthropic-ai/claude-code",
9
+ mode: "interactive",
10
+ skillNative: true,
11
+ },
12
+ {
13
+ name: "Codex",
14
+ bin: "codex",
15
+ args: (prompt) => [prompt],
16
+ installUrl: "npm i -g @openai/codex",
17
+ mode: "interactive",
18
+ skillNative: false,
19
+ },
20
+ {
21
+ name: "Gemini CLI",
22
+ bin: "gemini",
23
+ args: (prompt) => [prompt],
24
+ installUrl: "npm i -g @google/gemini-cli",
25
+ mode: "interactive",
26
+ skillNative: false,
27
+ },
28
+ {
29
+ name: "Aider",
30
+ bin: "aider",
31
+ args: (prompt) => ["--message", prompt],
32
+ installUrl: "pip install aider-chat",
33
+ mode: "headless",
34
+ skillNative: false,
35
+ },
36
+ {
37
+ name: "Amp",
38
+ bin: "amp",
39
+ args: (prompt) => [prompt],
40
+ installUrl: "npm i -g @sourcegraph/amp",
41
+ mode: "interactive",
42
+ skillNative: false,
43
+ },
44
+ {
45
+ name: "OpenCode",
46
+ bin: "opencode",
47
+ args: (prompt) => ["-p", prompt],
48
+ installUrl: "https://opencode.ai",
49
+ mode: "interactive",
50
+ skillNative: false,
51
+ },
52
+ {
53
+ name: "Goose",
54
+ bin: "goose",
55
+ args: (prompt) => ["run", "-t", prompt],
56
+ installUrl: "brew install block-goose-cli",
57
+ mode: "headless",
58
+ skillNative: false,
59
+ },
60
+ {
61
+ name: "Cline",
62
+ bin: "cline",
63
+ args: (prompt) => [prompt],
64
+ installUrl: "npm i -g cline",
65
+ mode: "interactive",
66
+ skillNative: false,
67
+ },
68
+ {
69
+ name: "Kiro",
70
+ bin: "kiro",
71
+ args: (prompt) => [prompt],
72
+ installUrl: "curl -fsSL https://cli.kiro.dev/install | bash",
73
+ mode: "interactive",
74
+ skillNative: false,
75
+ },
76
+ {
77
+ name: "Cursor Agent",
78
+ bin: "cursor-agent",
79
+ args: (prompt) => ["chat", prompt],
80
+ installUrl: "https://cursor.com",
81
+ mode: "interactive",
82
+ skillNative: false,
83
+ },
84
+ ];
85
+ /** Return only the harnesses whose binary is found on PATH. */
86
+ export function detectInstalled() {
87
+ return HARNESSES.filter((h) => {
88
+ try {
89
+ execSync(`which ${h.bin}`, { stdio: "pipe" });
90
+ return true;
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ });
96
+ }
97
+ /** Spawn a harness interactively, handing over the terminal. */
98
+ export function spawnHarness(harness, prompt, cwd) {
99
+ const result = spawnSync(harness.bin, harness.args(prompt), {
100
+ cwd,
101
+ stdio: "inherit",
102
+ });
103
+ return result.status ?? 1;
104
+ }
@@ -7,3 +7,9 @@
7
7
  * In non-TTY environments (CI), auto-selects all items.
8
8
  */
9
9
  export declare function pickMany<T>(items: T[], label: (item: T, index: number) => string, prompt: string): Promise<T[]>;
10
+ /**
11
+ * Prompt the user to pick a single item from a numbered list.
12
+ *
13
+ * In non-TTY environments (CI), auto-selects the first item.
14
+ */
15
+ export declare function pickOne<T>(items: T[], label: (item: T, index: number) => string, prompt: string): Promise<T>;
@@ -22,6 +22,35 @@ export async function pickMany(items, label, prompt) {
22
22
  const answer = await ask(`${prompt} [all/none/1,2,3/1-3]: `);
23
23
  return parseSelection(answer, items);
24
24
  }
25
+ /**
26
+ * Prompt the user to pick a single item from a numbered list.
27
+ *
28
+ * In non-TTY environments (CI), auto-selects the first item.
29
+ */
30
+ export async function pickOne(items, label, prompt) {
31
+ if (items.length === 1)
32
+ return items[0];
33
+ // Non-TTY: auto-select first
34
+ if (!process.stdin.isTTY) {
35
+ return items[0];
36
+ }
37
+ // Print numbered list
38
+ for (let i = 0; i < items.length; i++) {
39
+ console.log(` ${String(i + 1).padStart(2)} ${label(items[i], i)}`);
40
+ }
41
+ console.log();
42
+ // Keep asking until we get a valid answer
43
+ while (true) {
44
+ const answer = await ask(`${prompt} [1-${items.length}]: `);
45
+ const n = parseInt(answer, 10);
46
+ if (!isNaN(n) && n >= 1 && n <= items.length) {
47
+ return items[n - 1];
48
+ }
49
+ // Default to first item on empty input
50
+ if (answer === "")
51
+ return items[0];
52
+ }
53
+ }
25
54
  function ask(prompt) {
26
55
  const rl = createInterface({ input: process.stdin, output: process.stdout });
27
56
  return new Promise((resolve) => {
@@ -1,2 +1,3 @@
1
+ import type { Harness } from "../lib/harness.js";
1
2
  /** Silently initialize the project if not already done. */
2
- export declare function ensureInit(cwd: string): void;
3
+ export declare function ensureInit(cwd: string, harness: Harness): void;
@@ -22,8 +22,11 @@ function installSkills(cwd) {
22
22
  }
23
23
  }
24
24
  /** Silently initialize the project if not already done. */
25
- export function ensureInit(cwd) {
26
- const hasSettings = existsSync(join(cwd, ".claude", "settings.local.json"));
25
+ export function ensureInit(cwd, harness) {
26
+ const isClaude = harness.bin === "claude";
27
+ const hasSettings = isClaude
28
+ ? existsSync(join(cwd, ".claude", "settings.local.json"))
29
+ : true; // non-Claude harnesses don't need Claude settings
27
30
  const hasOutput = existsSync(join(cwd, "output"));
28
31
  const skillsDir = join(cwd, ".agents", "skills");
29
32
  const hasSkills = existsSync(join(skillsDir, "open-prose")) &&
@@ -33,10 +36,12 @@ export function ensureInit(cwd) {
33
36
  if (hasSettings && hasOutput && hasSkills)
34
37
  return;
35
38
  log.info("Auto-initializing project...");
36
- if (!hasSettings || !hasOutput) {
37
- setupClaude(cwd);
39
+ if (!hasOutput) {
38
40
  createDirs(cwd);
39
41
  }
42
+ if (isClaude && !hasSettings) {
43
+ setupClaude(cwd);
44
+ }
40
45
  if (!hasSkills) {
41
46
  installSkills(cwd);
42
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wordspace",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -1,4 +0,0 @@
1
- /** Check if `claude` CLI is available on PATH. */
2
- export declare function isClaudeInstalled(): boolean;
3
- /** Spawn `claude` interactively, handing over the terminal. */
4
- export declare function spawnClaude(prompt: string, cwd: string): number;
package/dist/lib/spawn.js DELETED
@@ -1,19 +0,0 @@
1
- import { spawnSync, execSync } from "node:child_process";
2
- /** Check if `claude` CLI is available on PATH. */
3
- export function isClaudeInstalled() {
4
- try {
5
- execSync("which claude", { stdio: "pipe" });
6
- return true;
7
- }
8
- catch {
9
- return false;
10
- }
11
- }
12
- /** Spawn `claude` interactively, handing over the terminal. */
13
- export function spawnClaude(prompt, cwd) {
14
- const result = spawnSync("claude", [prompt], {
15
- cwd,
16
- stdio: "inherit",
17
- });
18
- return result.status ?? 1;
19
- }