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.
- package/dist/commands/init.js +1 -2
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.js +77 -11
- package/dist/index.js +19 -7
- package/dist/lib/harness.d.ts +20 -0
- package/dist/lib/harness.js +104 -0
- package/dist/lib/prompt.d.ts +6 -0
- package/dist/lib/prompt.js +29 -0
- package/dist/steps/auto-init.d.ts +2 -1
- package/dist/steps/auto-init.js +9 -4
- package/package.json +1 -1
- package/dist/lib/spawn.d.ts +0 -4
- package/dist/lib/spawn.js +0 -19
package/dist/commands/init.js
CHANGED
|
@@ -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.
|
|
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>
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -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>;
|
package/dist/commands/run.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
110
|
+
const prompt = buildPrompt(harness, prosePath, cwd);
|
|
50
111
|
console.log();
|
|
51
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
15
|
+
run <target> Run a .prose workflow via a coding agent
|
|
16
16
|
|
|
17
17
|
Options:
|
|
18
|
-
--force
|
|
19
|
-
--
|
|
20
|
-
--
|
|
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
|
|
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
|
+
}
|
package/dist/lib/prompt.d.ts
CHANGED
|
@@ -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>;
|
package/dist/lib/prompt.js
CHANGED
|
@@ -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) => {
|
package/dist/steps/auto-init.js
CHANGED
|
@@ -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
|
|
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 (!
|
|
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
package/dist/lib/spawn.d.ts
DELETED
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
|
-
}
|