wordspace 0.0.2 → 0.0.4

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.
Files changed (40) hide show
  1. package/dist/commands/add.d.ts +1 -0
  2. package/dist/commands/add.js +40 -0
  3. package/dist/commands/init.d.ts +1 -0
  4. package/dist/commands/init.js +44 -0
  5. package/dist/commands/search.d.ts +1 -0
  6. package/dist/commands/search.js +25 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +14 -3
  9. package/dist/lib/exec.d.ts +6 -0
  10. package/dist/lib/exec.js +21 -0
  11. package/dist/lib/log.d.ts +7 -0
  12. package/dist/lib/log.js +30 -0
  13. package/dist/lib/prompt.d.ts +9 -0
  14. package/dist/lib/prompt.js +59 -0
  15. package/dist/lib/workflows.d.ts +10 -0
  16. package/dist/lib/workflows.js +55 -0
  17. package/dist/steps/check-skills.d.ts +3 -0
  18. package/dist/steps/check-skills.js +8 -0
  19. package/dist/steps/create-dirs.d.ts +1 -0
  20. package/dist/steps/create-dirs.js +7 -0
  21. package/dist/steps/create-symlinks.d.ts +1 -0
  22. package/dist/steps/create-symlinks.js +30 -0
  23. package/dist/steps/fetch-workflows.d.ts +1 -0
  24. package/dist/steps/fetch-workflows.js +50 -0
  25. package/dist/steps/install-skills.d.ts +1 -0
  26. package/dist/steps/install-skills.js +10 -0
  27. package/dist/steps/setup-claude.d.ts +1 -0
  28. package/dist/steps/setup-claude.js +47 -0
  29. package/package.json +4 -1
  30. package/src/commands/init.ts +0 -48
  31. package/src/index.ts +0 -51
  32. package/src/lib/exec.ts +0 -30
  33. package/src/lib/log.ts +0 -38
  34. package/src/steps/check-skills.ts +0 -11
  35. package/src/steps/create-dirs.ts +0 -8
  36. package/src/steps/create-symlinks.ts +0 -39
  37. package/src/steps/fetch-workflows.ts +0 -92
  38. package/src/steps/install-skills.ts +0 -11
  39. package/src/steps/setup-claude.ts +0 -61
  40. package/tsconfig.json +0 -18
@@ -0,0 +1 @@
1
+ export declare function add(names: string[], force: boolean): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as log from "../lib/log.js";
4
+ import { listWorkflows, downloadWorkflow } from "../lib/workflows.js";
5
+ export async function add(names, force) {
6
+ if (names.length === 0) {
7
+ log.error("Specify at least one workflow name. Run `wordspace search` to browse.");
8
+ process.exit(1);
9
+ }
10
+ let available;
11
+ try {
12
+ available = await listWorkflows();
13
+ }
14
+ catch (err) {
15
+ log.error(`Could not fetch workflows: ${err.message}`);
16
+ process.exit(1);
17
+ }
18
+ const workflowsDir = join(process.cwd(), "workflows");
19
+ mkdirSync(workflowsDir, { recursive: true });
20
+ let downloaded = 0;
21
+ for (const raw of names) {
22
+ const name = raw.endsWith(".prose") ? raw : `${raw}.prose`;
23
+ const entry = available.find((w) => w.name === name);
24
+ if (!entry) {
25
+ log.warn(`Workflow not found: ${name}`);
26
+ continue;
27
+ }
28
+ try {
29
+ const ok = await downloadWorkflow(entry, workflowsDir, force);
30
+ if (ok)
31
+ downloaded++;
32
+ }
33
+ catch (err) {
34
+ log.warn(`Failed to download ${name}: ${err.message}`);
35
+ }
36
+ }
37
+ if (downloaded > 0) {
38
+ log.success(`Downloaded ${downloaded} workflow(s) to workflows/`);
39
+ }
40
+ }
@@ -0,0 +1 @@
1
+ export declare function init(force: boolean): Promise<void>;
@@ -0,0 +1,44 @@
1
+ import { checkSkills } from "../steps/check-skills.js";
2
+ import { installSkills } from "../steps/install-skills.js";
3
+ import { fetchWorkflows } from "../steps/fetch-workflows.js";
4
+ import { createSymlinks } from "../steps/create-symlinks.js";
5
+ import { setupClaude } from "../steps/setup-claude.js";
6
+ import { createDirs } from "../steps/create-dirs.js";
7
+ import * as log from "../lib/log.js";
8
+ export async function init(force) {
9
+ const cwd = process.cwd();
10
+ log.banner();
11
+ // Step 1: Check skills
12
+ log.step("1/6 Skills");
13
+ const hasSkills = checkSkills(cwd);
14
+ if (hasSkills && !force) {
15
+ log.skip("All skills already installed");
16
+ }
17
+ else {
18
+ installSkills(cwd);
19
+ }
20
+ // Step 2: Fetch workflows
21
+ log.step("2/6 Workflows");
22
+ await fetchWorkflows(cwd, force);
23
+ // Step 3: Create symlinks
24
+ log.step("3/6 Symlinks");
25
+ createSymlinks(cwd, force);
26
+ // Step 4: Setup Claude settings
27
+ log.step("4/6 Claude settings");
28
+ setupClaude(cwd);
29
+ // Step 5: Create directories
30
+ log.step("5/6 Directories");
31
+ createDirs(cwd);
32
+ // Step 6: Done
33
+ log.step("6/6 Done");
34
+ console.log(`
35
+ Your project is ready. Next steps:
36
+
37
+ 1. Open this directory in your editor
38
+ 2. Start Claude Code: claude
39
+ 3. Run a workflow: prose run workflows/<name>.prose
40
+
41
+ Browse more workflows: wordspace search
42
+ Add a workflow: wordspace add <name>
43
+ `);
44
+ }
@@ -0,0 +1 @@
1
+ export declare function search(query?: string): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import * as log from "../lib/log.js";
2
+ import { listWorkflows } from "../lib/workflows.js";
3
+ export async function search(query) {
4
+ let workflows;
5
+ try {
6
+ workflows = await listWorkflows();
7
+ }
8
+ catch (err) {
9
+ log.error(`Could not fetch workflows: ${err.message}`);
10
+ process.exit(1);
11
+ }
12
+ if (query) {
13
+ const lower = query.toLowerCase();
14
+ workflows = workflows.filter((w) => w.name.toLowerCase().includes(lower));
15
+ }
16
+ if (workflows.length === 0) {
17
+ log.info(query ? `No workflows matching "${query}"` : "No workflows found");
18
+ return;
19
+ }
20
+ log.info(`${workflows.length} workflow(s) available${query ? ` matching "${query}"` : ""}:\n`);
21
+ for (const w of workflows) {
22
+ console.log(` ${w.name.replace(/\.prose$/, "")}`);
23
+ }
24
+ console.log(`\nRun ${`wordspace add <name>`} to download a workflow.`);
25
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js CHANGED
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { init } from "./commands/init.js";
3
+ import { search } from "./commands/search.js";
4
+ import { add } from "./commands/add.js";
3
5
  import * as log from "./lib/log.js";
4
- const VERSION = "0.1.0";
6
+ const VERSION = "0.0.4";
5
7
  const HELP = `
6
8
  Usage: wordspace <command> [options]
7
9
 
8
10
  Commands:
9
11
  init Bootstrap a new wordspace project
12
+ search [q] List available workflows (optionally filter by query)
13
+ add <name> Download specific workflow(s) by name
10
14
 
11
15
  Options:
12
- --force Re-run all steps even if already completed
16
+ --force Re-run all steps / overwrite existing files
13
17
  --help Show this help message
14
18
  --version Show version number
15
19
  `.trim();
@@ -23,11 +27,18 @@ async function main() {
23
27
  console.log(VERSION);
24
28
  process.exit(0);
25
29
  }
26
- const command = args.find((a) => !a.startsWith("-"));
30
+ const positional = args.filter((a) => !a.startsWith("-"));
31
+ const command = positional[0];
27
32
  const force = args.includes("--force");
28
33
  if (command === "init") {
29
34
  await init(force);
30
35
  }
36
+ else if (command === "search") {
37
+ await search(positional[1]);
38
+ }
39
+ else if (command === "add") {
40
+ await add(positional.slice(1), force);
41
+ }
31
42
  else if (!command) {
32
43
  console.log(HELP);
33
44
  process.exit(0);
@@ -0,0 +1,6 @@
1
+ export interface ExecOptions {
2
+ cwd?: string;
3
+ timeout?: number;
4
+ silent?: boolean;
5
+ }
6
+ export declare function exec(cmd: string, opts?: ExecOptions): string;
@@ -0,0 +1,21 @@
1
+ import { execSync } from "node:child_process";
2
+ import * as log from "./log.js";
3
+ export function exec(cmd, opts = {}) {
4
+ const { cwd = process.cwd(), timeout = 60_000, silent = false } = opts;
5
+ try {
6
+ const result = execSync(cmd, {
7
+ cwd,
8
+ timeout,
9
+ stdio: silent ? "pipe" : ["pipe", "pipe", "pipe"],
10
+ encoding: "utf-8",
11
+ });
12
+ return result.trim();
13
+ }
14
+ catch (err) {
15
+ const e = err;
16
+ const msg = e.stderr?.trim() || e.message || "Command failed";
17
+ log.error(`Command failed: ${cmd}`);
18
+ log.error(msg);
19
+ throw new Error(`exec failed: ${cmd}`);
20
+ }
21
+ }
@@ -0,0 +1,7 @@
1
+ export declare function info(msg: string): void;
2
+ export declare function success(msg: string): void;
3
+ export declare function warn(msg: string): void;
4
+ export declare function error(msg: string): void;
5
+ export declare function step(msg: string): void;
6
+ export declare function skip(msg: string): void;
7
+ export declare function banner(): void;
@@ -0,0 +1,30 @@
1
+ const noColor = !!process.env["NO_COLOR"];
2
+ const code = (n) => (noColor ? "" : `\x1b[${n}m`);
3
+ const reset = code(0);
4
+ const bold = code(1);
5
+ const dim = code(2);
6
+ const green = code(32);
7
+ const yellow = code(33);
8
+ const red = code(31);
9
+ const cyan = code(36);
10
+ export function info(msg) {
11
+ console.log(`${cyan}i${reset} ${msg}`);
12
+ }
13
+ export function success(msg) {
14
+ console.log(`${green}✓${reset} ${msg}`);
15
+ }
16
+ export function warn(msg) {
17
+ console.log(`${yellow}!${reset} ${msg}`);
18
+ }
19
+ export function error(msg) {
20
+ console.error(`${red}✗${reset} ${msg}`);
21
+ }
22
+ export function step(msg) {
23
+ console.log(`\n${bold}${msg}${reset}`);
24
+ }
25
+ export function skip(msg) {
26
+ console.log(`${dim}–${reset} ${dim}${msg}${reset}`);
27
+ }
28
+ export function banner() {
29
+ console.log(`\n${bold}wordspace init${reset}\n`);
30
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Prompt the user to pick items from a numbered list.
3
+ *
4
+ * Accepts: "all", "none", comma-separated numbers, ranges ("1-3"),
5
+ * or a mix ("1,3-5,7").
6
+ *
7
+ * In non-TTY environments (CI), auto-selects all items.
8
+ */
9
+ export declare function pickMany<T>(items: T[], label: (item: T, index: number) => string, prompt: string): Promise<T[]>;
@@ -0,0 +1,59 @@
1
+ import { createInterface } from "node:readline";
2
+ /**
3
+ * Prompt the user to pick items from a numbered list.
4
+ *
5
+ * Accepts: "all", "none", comma-separated numbers, ranges ("1-3"),
6
+ * or a mix ("1,3-5,7").
7
+ *
8
+ * In non-TTY environments (CI), auto-selects all items.
9
+ */
10
+ export async function pickMany(items, label, prompt) {
11
+ if (items.length === 0)
12
+ return [];
13
+ // Non-TTY: auto-select all
14
+ if (!process.stdin.isTTY) {
15
+ return items;
16
+ }
17
+ // Print numbered list
18
+ for (let i = 0; i < items.length; i++) {
19
+ console.log(` ${String(i + 1).padStart(2)} ${label(items[i], i)}`);
20
+ }
21
+ console.log();
22
+ const answer = await ask(`${prompt} [all/none/1,2,3/1-3]: `);
23
+ return parseSelection(answer, items);
24
+ }
25
+ function ask(prompt) {
26
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
27
+ return new Promise((resolve) => {
28
+ rl.question(prompt, (answer) => {
29
+ rl.close();
30
+ resolve(answer.trim());
31
+ });
32
+ });
33
+ }
34
+ function parseSelection(input, items) {
35
+ const lower = input.toLowerCase();
36
+ if (lower === "all" || lower === "")
37
+ return items;
38
+ if (lower === "none" || lower === "0")
39
+ return [];
40
+ const indices = new Set();
41
+ for (const part of input.split(",")) {
42
+ const trimmed = part.trim();
43
+ const rangeMatch = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
44
+ if (rangeMatch) {
45
+ const start = parseInt(rangeMatch[1], 10);
46
+ const end = parseInt(rangeMatch[2], 10);
47
+ for (let i = start; i <= end; i++) {
48
+ if (i >= 1 && i <= items.length)
49
+ indices.add(i - 1);
50
+ }
51
+ }
52
+ else {
53
+ const n = parseInt(trimmed, 10);
54
+ if (!isNaN(n) && n >= 1 && n <= items.length)
55
+ indices.add(n - 1);
56
+ }
57
+ }
58
+ return [...indices].sort((a, b) => a - b).map((i) => items[i]);
59
+ }
@@ -0,0 +1,10 @@
1
+ export interface WorkflowEntry {
2
+ name: string;
3
+ download_url: string;
4
+ }
5
+ export declare function httpGet(url: string, headers?: Record<string, string>): Promise<string>;
6
+ export declare function getAuthHeaders(): Record<string, string>;
7
+ /** Fetch the list of .prose workflow files available on GitHub. */
8
+ export declare function listWorkflows(): Promise<WorkflowEntry[]>;
9
+ /** Download a single workflow file into the target directory. */
10
+ export declare function downloadWorkflow(entry: WorkflowEntry, workflowsDir: string, force: boolean): Promise<boolean>;
@@ -0,0 +1,55 @@
1
+ import { get as httpsGet } from "node:https";
2
+ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import * as log from "./log.js";
5
+ const CONTENTS_URL = "https://api.github.com/repos/frames-engineering/wordspace-demos/contents/workflows";
6
+ export function httpGet(url, headers = {}) {
7
+ return new Promise((resolve, reject) => {
8
+ const allHeaders = {
9
+ "User-Agent": "wordspace-cli",
10
+ ...headers,
11
+ };
12
+ httpsGet(url, { headers: allHeaders }, (res) => {
13
+ if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
14
+ httpGet(res.headers.location, headers).then(resolve, reject);
15
+ return;
16
+ }
17
+ if (res.statusCode !== 200) {
18
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
19
+ return;
20
+ }
21
+ let data = "";
22
+ res.on("data", (chunk) => (data += chunk.toString()));
23
+ res.on("end", () => resolve(data));
24
+ res.on("error", reject);
25
+ }).on("error", reject);
26
+ });
27
+ }
28
+ export function getAuthHeaders() {
29
+ const token = process.env["GITHUB_TOKEN"] || process.env["GH_TOKEN"];
30
+ if (token) {
31
+ return { Authorization: `Bearer ${token}` };
32
+ }
33
+ return {};
34
+ }
35
+ /** Fetch the list of .prose workflow files available on GitHub. */
36
+ export async function listWorkflows() {
37
+ const headers = getAuthHeaders();
38
+ const body = await httpGet(CONTENTS_URL, headers);
39
+ const entries = JSON.parse(body);
40
+ return entries.filter((e) => e.name.endsWith(".prose"));
41
+ }
42
+ /** Download a single workflow file into the target directory. */
43
+ export async function downloadWorkflow(entry, workflowsDir, force) {
44
+ const dest = join(workflowsDir, entry.name);
45
+ if (existsSync(dest) && !force) {
46
+ log.skip(`${entry.name} (exists)`);
47
+ return false;
48
+ }
49
+ const headers = getAuthHeaders();
50
+ const content = await httpGet(entry.download_url, headers);
51
+ mkdirSync(workflowsDir, { recursive: true });
52
+ writeFileSync(dest, content, "utf-8");
53
+ log.success(entry.name);
54
+ return true;
55
+ }
@@ -0,0 +1,3 @@
1
+ declare const SKILLS: readonly ["open-prose", "agentwallet", "registry", "websh"];
2
+ export declare function checkSkills(cwd: string): boolean;
3
+ export { SKILLS };
@@ -0,0 +1,8 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const SKILLS = ["open-prose", "agentwallet", "registry", "websh"];
4
+ export function checkSkills(cwd) {
5
+ const base = join(cwd, ".agents", "skills");
6
+ return SKILLS.every((s) => existsSync(join(base, s)));
7
+ }
8
+ export { SKILLS };
@@ -0,0 +1 @@
1
+ export declare function createDirs(cwd: string): void;
@@ -0,0 +1,7 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as log from "../lib/log.js";
4
+ export function createDirs(cwd) {
5
+ mkdirSync(join(cwd, "output"), { recursive: true });
6
+ log.success("Created output/ directory");
7
+ }
@@ -0,0 +1 @@
1
+ export declare function createSymlinks(cwd: string, force: boolean): void;
@@ -0,0 +1,30 @@
1
+ import { mkdirSync, symlinkSync, readlinkSync, lstatSync, unlinkSync, } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { SKILLS } from "./check-skills.js";
4
+ import * as log from "../lib/log.js";
5
+ export function createSymlinks(cwd, force) {
6
+ const skillsDir = join(cwd, "skills");
7
+ mkdirSync(skillsDir, { recursive: true });
8
+ for (const skill of SKILLS) {
9
+ const linkPath = join(skillsDir, skill);
10
+ const target = join("..", ".agents", "skills", skill);
11
+ // Check if symlink already exists and points to the right target
12
+ try {
13
+ const stat = lstatSync(linkPath);
14
+ if (stat.isSymbolicLink()) {
15
+ const existing = readlinkSync(linkPath);
16
+ if (existing === target && !force) {
17
+ log.skip(`skills/${skill} (exists)`);
18
+ continue;
19
+ }
20
+ // Remove stale or forced symlink
21
+ unlinkSync(linkPath);
22
+ }
23
+ }
24
+ catch {
25
+ // Does not exist — will create
26
+ }
27
+ symlinkSync(target, linkPath);
28
+ log.success(`skills/${skill} -> ${target}`);
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ export declare function fetchWorkflows(cwd: string, force: boolean): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { mkdirSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as log from "../lib/log.js";
4
+ import { listWorkflows, downloadWorkflow } from "../lib/workflows.js";
5
+ import { pickMany } from "../lib/prompt.js";
6
+ export async function fetchWorkflows(cwd, force) {
7
+ const workflowsDir = join(cwd, "workflows");
8
+ mkdirSync(workflowsDir, { recursive: true });
9
+ let available;
10
+ try {
11
+ available = await listWorkflows();
12
+ }
13
+ catch (err) {
14
+ log.warn(`Could not fetch workflow list from GitHub: ${err.message}`);
15
+ log.warn("Skipping workflow download (skills are the critical part).");
16
+ return;
17
+ }
18
+ if (available.length === 0) {
19
+ log.warn("No .prose files found in remote repository");
20
+ return;
21
+ }
22
+ // Filter out already-installed unless --force
23
+ const candidates = force
24
+ ? available
25
+ : available.filter((e) => !existsSync(join(workflowsDir, e.name)));
26
+ if (candidates.length === 0) {
27
+ log.skip("All workflows already present");
28
+ return;
29
+ }
30
+ log.info(`${available.length} workflow(s) available, ${candidates.length} new:`);
31
+ const selected = await pickMany(candidates, (w) => w.name.replace(/\.prose$/, ""), "Which workflows do you want?");
32
+ if (selected.length === 0) {
33
+ log.skip("No workflows selected");
34
+ return;
35
+ }
36
+ let downloaded = 0;
37
+ for (const entry of selected) {
38
+ try {
39
+ const ok = await downloadWorkflow(entry, workflowsDir, force);
40
+ if (ok)
41
+ downloaded++;
42
+ }
43
+ catch (err) {
44
+ log.warn(`Failed to download ${entry.name}: ${err.message}`);
45
+ }
46
+ }
47
+ if (downloaded > 0) {
48
+ log.success(`Downloaded ${downloaded} workflow(s) to workflows/`);
49
+ }
50
+ }
@@ -0,0 +1 @@
1
+ export declare function installSkills(cwd: string): void;
@@ -0,0 +1,10 @@
1
+ import { exec } from "../lib/exec.js";
2
+ import * as log from "../lib/log.js";
3
+ export function installSkills(cwd) {
4
+ log.info("Installing skills via npx skills add...");
5
+ exec("npx -y skills add frames-engineering/skills -y", {
6
+ cwd,
7
+ timeout: 120_000,
8
+ });
9
+ log.success("Skills installed to .agents/skills/");
10
+ }
@@ -0,0 +1 @@
1
+ export declare function setupClaude(cwd: string): void;
@@ -0,0 +1,47 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as log from "../lib/log.js";
4
+ const BASE_PERMISSIONS = [
5
+ "Bash(curl:*)",
6
+ "Bash(python3:*)",
7
+ "WebFetch(domain:registry.mcpay.tech)",
8
+ "WebFetch(domain:frames.ag)",
9
+ "WebSearch",
10
+ ];
11
+ export function setupClaude(cwd) {
12
+ const claudeDir = join(cwd, ".claude");
13
+ mkdirSync(claudeDir, { recursive: true });
14
+ const settingsPath = join(claudeDir, "settings.local.json");
15
+ let settings = {};
16
+ if (existsSync(settingsPath)) {
17
+ try {
18
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
19
+ }
20
+ catch {
21
+ log.warn("Could not parse existing settings.local.json, creating fresh");
22
+ settings = {};
23
+ }
24
+ }
25
+ if (!settings.permissions) {
26
+ settings.permissions = {};
27
+ }
28
+ if (!Array.isArray(settings.permissions.allow)) {
29
+ settings.permissions.allow = [];
30
+ }
31
+ // Merge base permissions (deduplicate)
32
+ const existing = new Set(settings.permissions.allow);
33
+ let added = 0;
34
+ for (const perm of BASE_PERMISSIONS) {
35
+ if (!existing.has(perm)) {
36
+ settings.permissions.allow.push(perm);
37
+ added++;
38
+ }
39
+ }
40
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
41
+ if (added > 0) {
42
+ log.success(`Added ${added} permission(s) to .claude/settings.local.json`);
43
+ }
44
+ else {
45
+ log.skip("All base permissions already present");
46
+ }
47
+ }
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "wordspace",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
5
8
  "bin": {
6
9
  "wordspace": "./dist/index.js"
7
10
  },
@@ -1,48 +0,0 @@
1
- import { checkSkills } from "../steps/check-skills.js";
2
- import { installSkills } from "../steps/install-skills.js";
3
- import { fetchWorkflows } from "../steps/fetch-workflows.js";
4
- import { createSymlinks } from "../steps/create-symlinks.js";
5
- import { setupClaude } from "../steps/setup-claude.js";
6
- import { createDirs } from "../steps/create-dirs.js";
7
- import * as log from "../lib/log.js";
8
-
9
- export async function init(force: boolean) {
10
- const cwd = process.cwd();
11
-
12
- log.banner();
13
-
14
- // Step 1: Check skills
15
- log.step("1/6 Skills");
16
- const hasSkills = checkSkills(cwd);
17
- if (hasSkills && !force) {
18
- log.skip("All skills already installed");
19
- } else {
20
- installSkills(cwd);
21
- }
22
-
23
- // Step 2: Fetch workflows
24
- log.step("2/6 Workflows");
25
- await fetchWorkflows(cwd, force);
26
-
27
- // Step 3: Create symlinks
28
- log.step("3/6 Symlinks");
29
- createSymlinks(cwd, force);
30
-
31
- // Step 4: Setup Claude settings
32
- log.step("4/6 Claude settings");
33
- setupClaude(cwd);
34
-
35
- // Step 5: Create directories
36
- log.step("5/6 Directories");
37
- createDirs(cwd);
38
-
39
- // Step 6: Done
40
- log.step("6/6 Done");
41
- console.log(`
42
- Your project is ready. Next steps:
43
-
44
- 1. Open this directory in your editor
45
- 2. Start Claude Code: claude
46
- 3. Run a workflow: prose run workflows/<name>.prose
47
- `);
48
- }
package/src/index.ts DELETED
@@ -1,51 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { init } from "./commands/init.js";
4
- import * as log from "./lib/log.js";
5
-
6
- const VERSION = "0.1.0";
7
-
8
- const HELP = `
9
- Usage: wordspace <command> [options]
10
-
11
- Commands:
12
- init Bootstrap a new wordspace project
13
-
14
- Options:
15
- --force Re-run all steps even if already completed
16
- --help Show this help message
17
- --version Show version number
18
- `.trim();
19
-
20
- async function main() {
21
- const args = process.argv.slice(2);
22
-
23
- if (args.includes("--help") || args.includes("-h")) {
24
- console.log(HELP);
25
- process.exit(0);
26
- }
27
-
28
- if (args.includes("--version") || args.includes("-v")) {
29
- console.log(VERSION);
30
- process.exit(0);
31
- }
32
-
33
- const command = args.find((a) => !a.startsWith("-"));
34
- const force = args.includes("--force");
35
-
36
- if (command === "init") {
37
- await init(force);
38
- } else if (!command) {
39
- console.log(HELP);
40
- process.exit(0);
41
- } else {
42
- log.error(`Unknown command: ${command}`);
43
- console.log(HELP);
44
- process.exit(1);
45
- }
46
- }
47
-
48
- main().catch((err: Error) => {
49
- log.error(err.message);
50
- process.exit(1);
51
- });
package/src/lib/exec.ts DELETED
@@ -1,30 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import * as log from "./log.js";
3
-
4
- export interface ExecOptions {
5
- cwd?: string;
6
- timeout?: number;
7
- silent?: boolean;
8
- }
9
-
10
- export function exec(
11
- cmd: string,
12
- opts: ExecOptions = {},
13
- ): string {
14
- const { cwd = process.cwd(), timeout = 60_000, silent = false } = opts;
15
- try {
16
- const result = execSync(cmd, {
17
- cwd,
18
- timeout,
19
- stdio: silent ? "pipe" : ["pipe", "pipe", "pipe"],
20
- encoding: "utf-8",
21
- });
22
- return result.trim();
23
- } catch (err: unknown) {
24
- const e = err as { stderr?: string; message?: string };
25
- const msg = e.stderr?.trim() || e.message || "Command failed";
26
- log.error(`Command failed: ${cmd}`);
27
- log.error(msg);
28
- throw new Error(`exec failed: ${cmd}`);
29
- }
30
- }
package/src/lib/log.ts DELETED
@@ -1,38 +0,0 @@
1
- const noColor = !!process.env["NO_COLOR"];
2
-
3
- const code = (n: number) => (noColor ? "" : `\x1b[${n}m`);
4
- const reset = code(0);
5
- const bold = code(1);
6
- const dim = code(2);
7
- const green = code(32);
8
- const yellow = code(33);
9
- const red = code(31);
10
- const cyan = code(36);
11
-
12
- export function info(msg: string) {
13
- console.log(`${cyan}i${reset} ${msg}`);
14
- }
15
-
16
- export function success(msg: string) {
17
- console.log(`${green}✓${reset} ${msg}`);
18
- }
19
-
20
- export function warn(msg: string) {
21
- console.log(`${yellow}!${reset} ${msg}`);
22
- }
23
-
24
- export function error(msg: string) {
25
- console.error(`${red}✗${reset} ${msg}`);
26
- }
27
-
28
- export function step(msg: string) {
29
- console.log(`\n${bold}${msg}${reset}`);
30
- }
31
-
32
- export function skip(msg: string) {
33
- console.log(`${dim}–${reset} ${dim}${msg}${reset}`);
34
- }
35
-
36
- export function banner() {
37
- console.log(`\n${bold}wordspace init${reset}\n`);
38
- }
@@ -1,11 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- const SKILLS = ["open-prose", "agentwallet", "registry", "websh"] as const;
5
-
6
- export function checkSkills(cwd: string): boolean {
7
- const base = join(cwd, ".agents", "skills");
8
- return SKILLS.every((s) => existsSync(join(base, s)));
9
- }
10
-
11
- export { SKILLS };
@@ -1,8 +0,0 @@
1
- import { mkdirSync } from "node:fs";
2
- import { join } from "node:path";
3
- import * as log from "../lib/log.js";
4
-
5
- export function createDirs(cwd: string) {
6
- mkdirSync(join(cwd, "output"), { recursive: true });
7
- log.success("Created output/ directory");
8
- }
@@ -1,39 +0,0 @@
1
- import {
2
- mkdirSync,
3
- symlinkSync,
4
- readlinkSync,
5
- lstatSync,
6
- unlinkSync,
7
- } from "node:fs";
8
- import { join } from "node:path";
9
- import { SKILLS } from "./check-skills.js";
10
- import * as log from "../lib/log.js";
11
-
12
- export function createSymlinks(cwd: string, force: boolean) {
13
- const skillsDir = join(cwd, "skills");
14
- mkdirSync(skillsDir, { recursive: true });
15
-
16
- for (const skill of SKILLS) {
17
- const linkPath = join(skillsDir, skill);
18
- const target = join("..", ".agents", "skills", skill);
19
-
20
- // Check if symlink already exists and points to the right target
21
- try {
22
- const stat = lstatSync(linkPath);
23
- if (stat.isSymbolicLink()) {
24
- const existing = readlinkSync(linkPath);
25
- if (existing === target && !force) {
26
- log.skip(`skills/${skill} (exists)`);
27
- continue;
28
- }
29
- // Remove stale or forced symlink
30
- unlinkSync(linkPath);
31
- }
32
- } catch {
33
- // Does not exist — will create
34
- }
35
-
36
- symlinkSync(target, linkPath);
37
- log.success(`skills/${skill} -> ${target}`);
38
- }
39
- }
@@ -1,92 +0,0 @@
1
- import { get as httpsGet } from "node:https";
2
- import { mkdirSync, writeFileSync, existsSync } from "node:fs";
3
- import { join } from "node:path";
4
- import * as log from "../lib/log.js";
5
-
6
- const CONTENTS_URL =
7
- "https://api.github.com/repos/frames-engineering/wordspace-demos/contents/workflows";
8
-
9
- interface GitHubEntry {
10
- name: string;
11
- download_url: string;
12
- }
13
-
14
- function httpGet(url: string, headers: Record<string, string> = {}): Promise<string> {
15
- return new Promise((resolve, reject) => {
16
- const allHeaders: Record<string, string> = {
17
- "User-Agent": "wordspace-cli",
18
- ...headers,
19
- };
20
- httpsGet(url, { headers: allHeaders }, (res) => {
21
- // Follow redirects
22
- if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
23
- httpGet(res.headers.location, headers).then(resolve, reject);
24
- return;
25
- }
26
- if (res.statusCode !== 200) {
27
- reject(new Error(`HTTP ${res.statusCode} for ${url}`));
28
- return;
29
- }
30
- let data = "";
31
- res.on("data", (chunk: Buffer) => (data += chunk.toString()));
32
- res.on("end", () => resolve(data));
33
- res.on("error", reject);
34
- }).on("error", reject);
35
- });
36
- }
37
-
38
- function getAuthHeaders(): Record<string, string> {
39
- const token = process.env["GITHUB_TOKEN"] || process.env["GH_TOKEN"];
40
- if (token) {
41
- return { Authorization: `Bearer ${token}` };
42
- }
43
- return {};
44
- }
45
-
46
- export async function fetchWorkflows(cwd: string, force: boolean) {
47
- const workflowsDir = join(cwd, "workflows");
48
- mkdirSync(workflowsDir, { recursive: true });
49
-
50
- const headers = getAuthHeaders();
51
-
52
- let entries: GitHubEntry[];
53
- try {
54
- const body = await httpGet(CONTENTS_URL, headers);
55
- entries = JSON.parse(body) as GitHubEntry[];
56
- } catch (err) {
57
- log.warn(
58
- `Could not fetch workflow list from GitHub: ${(err as Error).message}`,
59
- );
60
- log.warn("Skipping workflow download (skills are the critical part).");
61
- return;
62
- }
63
-
64
- const proseFiles = entries.filter((e) => e.name.endsWith(".prose"));
65
- if (proseFiles.length === 0) {
66
- log.warn("No .prose files found in workflows/");
67
- return;
68
- }
69
-
70
- let downloaded = 0;
71
- for (const file of proseFiles) {
72
- const dest = join(workflowsDir, file.name);
73
- if (existsSync(dest) && !force) {
74
- log.skip(`${file.name} (exists)`);
75
- continue;
76
- }
77
- try {
78
- const content = await httpGet(file.download_url, headers);
79
- writeFileSync(dest, content, "utf-8");
80
- log.success(file.name);
81
- downloaded++;
82
- } catch (err) {
83
- log.warn(`Failed to download ${file.name}: ${(err as Error).message}`);
84
- }
85
- }
86
-
87
- if (downloaded > 0) {
88
- log.success(`Downloaded ${downloaded} workflow(s) to workflows/`);
89
- } else {
90
- log.skip("All workflows already present");
91
- }
92
- }
@@ -1,11 +0,0 @@
1
- import { exec } from "../lib/exec.js";
2
- import * as log from "../lib/log.js";
3
-
4
- export function installSkills(cwd: string) {
5
- log.info("Installing skills via npx skills add...");
6
- exec("npx -y skills add frames-engineering/skills -y", {
7
- cwd,
8
- timeout: 120_000,
9
- });
10
- log.success("Skills installed to .agents/skills/");
11
- }
@@ -1,61 +0,0 @@
1
- import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
2
- import { join } from "node:path";
3
- import * as log from "../lib/log.js";
4
-
5
- const BASE_PERMISSIONS = [
6
- "Bash(curl:*)",
7
- "Bash(python3:*)",
8
- "WebFetch(domain:registry.mcpay.tech)",
9
- "WebFetch(domain:frames.ag)",
10
- "WebSearch",
11
- ];
12
-
13
- interface ClaudeSettings {
14
- permissions?: {
15
- allow?: string[];
16
- [key: string]: unknown;
17
- };
18
- [key: string]: unknown;
19
- }
20
-
21
- export function setupClaude(cwd: string) {
22
- const claudeDir = join(cwd, ".claude");
23
- mkdirSync(claudeDir, { recursive: true });
24
-
25
- const settingsPath = join(claudeDir, "settings.local.json");
26
-
27
- let settings: ClaudeSettings = {};
28
- if (existsSync(settingsPath)) {
29
- try {
30
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
31
- } catch {
32
- log.warn("Could not parse existing settings.local.json, creating fresh");
33
- settings = {};
34
- }
35
- }
36
-
37
- if (!settings.permissions) {
38
- settings.permissions = {};
39
- }
40
- if (!Array.isArray(settings.permissions.allow)) {
41
- settings.permissions.allow = [];
42
- }
43
-
44
- // Merge base permissions (deduplicate)
45
- const existing = new Set(settings.permissions.allow);
46
- let added = 0;
47
- for (const perm of BASE_PERMISSIONS) {
48
- if (!existing.has(perm)) {
49
- settings.permissions.allow.push(perm);
50
- added++;
51
- }
52
- }
53
-
54
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
55
-
56
- if (added > 0) {
57
- log.success(`Added ${added} permission(s) to .claude/settings.local.json`);
58
- } else {
59
- log.skip("All base permissions already present");
60
- }
61
- }
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "declaration": true,
7
- "outDir": "dist",
8
- "rootDir": "src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "resolveJsonModule": true,
14
- "isolatedModules": true
15
- },
16
- "include": ["src"],
17
- "exclude": ["dist", "node_modules"]
18
- }