wordspace 0.0.1 → 0.0.2

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/index.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { init } from "./commands/init.js";
3
+ import * as log from "./lib/log.js";
4
+ const VERSION = "0.1.0";
5
+ const HELP = `
6
+ Usage: wordspace <command> [options]
7
+
8
+ Commands:
9
+ init Bootstrap a new wordspace project
10
+
11
+ Options:
12
+ --force Re-run all steps even if already completed
13
+ --help Show this help message
14
+ --version Show version number
15
+ `.trim();
16
+ async function main() {
17
+ const args = process.argv.slice(2);
18
+ if (args.includes("--help") || args.includes("-h")) {
19
+ console.log(HELP);
20
+ process.exit(0);
21
+ }
22
+ if (args.includes("--version") || args.includes("-v")) {
23
+ console.log(VERSION);
24
+ process.exit(0);
25
+ }
26
+ const command = args.find((a) => !a.startsWith("-"));
27
+ const force = args.includes("--force");
28
+ if (command === "init") {
29
+ await init(force);
30
+ }
31
+ else if (!command) {
32
+ console.log(HELP);
33
+ process.exit(0);
34
+ }
35
+ else {
36
+ log.error(`Unknown command: ${command}`);
37
+ console.log(HELP);
38
+ process.exit(1);
39
+ }
40
+ }
41
+ main().catch((err) => {
42
+ log.error(err.message);
43
+ process.exit(1);
44
+ });
package/package.json CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "wordspace",
3
- "version": "0.0.1",
4
- "module": "index.ts",
3
+ "version": "0.0.2",
5
4
  "type": "module",
6
- "devDependencies": {
7
- "@types/bun": "latest"
5
+ "bin": {
6
+ "wordspace": "./dist/index.js"
7
+ },
8
+ "scripts": {
9
+ "build": "tsc"
8
10
  },
9
- "peerDependencies": {
10
- "typescript": "^5"
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "^5",
16
+ "@types/node": "^22"
11
17
  }
12
18
  }
@@ -0,0 +1,48 @@
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 ADDED
@@ -0,0 +1,51 @@
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
+ });
@@ -0,0 +1,30 @@
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 ADDED
@@ -0,0 +1,38 @@
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
+ }
@@ -0,0 +1,11 @@
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 };
@@ -0,0 +1,8 @@
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
+ }
@@ -0,0 +1,39 @@
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
+ }
@@ -0,0 +1,92 @@
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
+ }
@@ -0,0 +1,11 @@
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
+ }
@@ -0,0 +1,61 @@
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 CHANGED
@@ -1,29 +1,18 @@
1
1
  {
2
2
  "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
3
+ "target": "ES2022",
4
+ "module": "ESNext",
12
5
  "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "noEmit": true,
16
-
17
- // Best practices
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
18
9
  "strict": true,
10
+ "esModuleInterop": true,
19
11
  "skipLibCheck": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noUncheckedIndexedAccess": true,
22
- "noImplicitOverride": true,
23
-
24
- // Some stricter flags (disabled by default)
25
- "noUnusedLocals": false,
26
- "noUnusedParameters": false,
27
- "noPropertyAccessFromIndexSignature": false
28
- }
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true
15
+ },
16
+ "include": ["src"],
17
+ "exclude": ["dist", "node_modules"]
29
18
  }
package/CLAUDE.md DELETED
@@ -1,111 +0,0 @@
1
- ---
2
- description: Use Bun instead of Node.js, npm, pnpm, or vite.
3
- globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
4
- alwaysApply: false
5
- ---
6
-
7
- Default to using Bun instead of Node.js.
8
-
9
- - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
10
- - Use `bun test` instead of `jest` or `vitest`
11
- - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
12
- - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
13
- - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
14
- - Bun automatically loads .env, so don't use dotenv.
15
-
16
- ## APIs
17
-
18
- - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
19
- - `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
20
- - `Bun.redis` for Redis. Don't use `ioredis`.
21
- - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
22
- - `WebSocket` is built-in. Don't use `ws`.
23
- - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
24
- - Bun.$`ls` instead of execa.
25
-
26
- ## Testing
27
-
28
- Use `bun test` to run tests.
29
-
30
- ```ts#index.test.ts
31
- import { test, expect } from "bun:test";
32
-
33
- test("hello world", () => {
34
- expect(1).toBe(1);
35
- });
36
- ```
37
-
38
- ## Frontend
39
-
40
- Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
41
-
42
- Server:
43
-
44
- ```ts#index.ts
45
- import index from "./index.html"
46
-
47
- Bun.serve({
48
- routes: {
49
- "/": index,
50
- "/api/users/:id": {
51
- GET: (req) => {
52
- return new Response(JSON.stringify({ id: req.params.id }));
53
- },
54
- },
55
- },
56
- // optional websocket support
57
- websocket: {
58
- open: (ws) => {
59
- ws.send("Hello, world!");
60
- },
61
- message: (ws, message) => {
62
- ws.send(message);
63
- },
64
- close: (ws) => {
65
- // handle close
66
- }
67
- },
68
- development: {
69
- hmr: true,
70
- console: true,
71
- }
72
- })
73
- ```
74
-
75
- HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
76
-
77
- ```html#index.html
78
- <html>
79
- <body>
80
- <h1>Hello, world!</h1>
81
- <script type="module" src="./frontend.tsx"></script>
82
- </body>
83
- </html>
84
- ```
85
-
86
- With the following `frontend.tsx`:
87
-
88
- ```tsx#frontend.tsx
89
- import React from "react";
90
-
91
- // import .css files directly and it works
92
- import './index.css';
93
-
94
- import { createRoot } from "react-dom/client";
95
-
96
- const root = createRoot(document.body);
97
-
98
- export default function Frontend() {
99
- return <h1>Hello, world!</h1>;
100
- }
101
-
102
- root.render(<Frontend />);
103
- ```
104
-
105
- Then, run index.ts
106
-
107
- ```sh
108
- bun --hot ./index.ts
109
- ```
110
-
111
- For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
package/README.md DELETED
@@ -1,15 +0,0 @@
1
- # wordspace
2
-
3
- To install dependencies:
4
-
5
- ```bash
6
- bun install
7
- ```
8
-
9
- To run:
10
-
11
- ```bash
12
- bun run index.ts
13
- ```
14
-
15
- This project was created using `bun init` in bun v1.2.21. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/bun.lock DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "workspaces": {
4
- "": {
5
- "name": "wordspace",
6
- "devDependencies": {
7
- "@types/bun": "latest",
8
- },
9
- "peerDependencies": {
10
- "typescript": "^5",
11
- },
12
- },
13
- },
14
- "packages": {
15
- "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
16
-
17
- "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
18
-
19
- "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
20
-
21
- "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
22
-
23
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
24
- }
25
- }
package/index.ts DELETED
@@ -1 +0,0 @@
1
- console.log("Hello via Bun!");