@pixel-point/toolcraft 0.0.8 → 0.0.11
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 +42 -9
- package/package.json +1 -1
- package/src/generate.mjs +42 -5
- package/src/generate.test.mjs +40 -0
- package/src/package-json.mjs +15 -0
- package/src/package-json.test.mjs +14 -1
- package/templates/runtime/contracts/component-contracts.test.ts +251 -47
- package/templates/runtime/contracts/component-contracts.ts +130 -57
- package/templates/runtime/contracts/decision-contracts.test.ts +38 -1
- package/templates/runtime/contracts/decision-contracts.ts +22 -9
- package/templates/runtime/export/export.test.ts +63 -0
- package/templates/runtime/export/export.ts +55 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/canvas-shell.test.tsx +58 -4
- package/templates/runtime/react/canvas-shell.tsx +31 -9
- package/templates/runtime/react/controls-panel.test.tsx +916 -108
- package/templates/runtime/react/controls-panel.tsx +136 -30
- package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
- package/templates/runtime/react/settings-transfer.test.ts +4 -0
- package/templates/runtime/react/settings-transfer.ts +6 -1
- package/templates/runtime/react/timeline-panel.test.tsx +14 -0
- package/templates/runtime/react/timeline-panel.tsx +44 -7
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
- package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
- package/templates/runtime/react/toolcraft-app.tsx +56 -37
- package/templates/runtime/schema/define-toolcraft.test.ts +266 -170
- package/templates/runtime/schema/define-toolcraft.ts +140 -246
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +44 -0
- package/templates/runtime/state/create-template-state.test.ts +156 -0
- package/templates/runtime/state/create-template-state.ts +38 -8
- package/templates/runtime/state/media-defaults.ts +105 -0
- package/templates/runtime/state/persistence.test.ts +58 -0
- package/templates/runtime/state/persistence.ts +105 -1
- package/templates/runtime/state/reducer.test.ts +280 -4
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/timeline-loop.test.ts +71 -0
- package/templates/runtime/state/timeline-loop.ts +35 -0
- package/templates/runtime/state/types.ts +27 -0
- package/templates/runtime/testing/performance.test.ts +810 -21
- package/templates/runtime/testing/performance.ts +823 -53
- package/templates/starter/AGENTS.md +24 -18
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +43 -10
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +55 -20
- package/templates/starter/docs/toolcraft/component-rules.md +75 -42
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +37 -11
- package/templates/starter/docs/toolcraft/schema-reference.md +171 -41
- package/templates/starter/docs/toolcraft/workflow.md +5 -2
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
- package/templates/starter/e2e/app-performance.spec.ts +55 -2
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +3 -0
- package/templates/starter/playwright.config.ts +1 -1
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +114 -13
- package/templates/starter/scripts/toolcraft-port.mjs +280 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +207 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +3412 -479
- package/templates/starter/src/app/starter-acceptance.ts +1453 -97
- package/templates/starter/src/app/starter-performance.test.ts +111 -7
- package/templates/starter/src/app/starter-performance.ts +5 -0
- package/templates/starter/src/app/starter-schema.test.ts +32 -7
- package/templates/starter/src/app/starter-schema.ts +6 -2
- package/templates/starter/vite.config.ts +58 -2
- package/templates/ui/components/control-layout/index.tsx +8 -3
- package/templates/ui/components/controls/actions/actions-control.tsx +56 -5
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
- package/templates/ui/components/controls/color/index.ts +4 -1
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +4 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +8 -25
- package/templates/ui/components/controls/slider/slider-value.ts +0 -1
- package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
- package/templates/ui/components/controls/vector/index.ts +1 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +109 -12
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
- package/templates/ui/components/panel/panel-section.tsx +29 -5
package/README.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# Toolcraft CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Create standalone Toolcraft apps from the command line.
|
|
4
|
+
|
|
5
|
+
Toolcraft is a Pixel Point starter kit and UI/runtime library for building
|
|
6
|
+
personal creative design tools with AI. It gives generated apps a ready canvas,
|
|
7
|
+
control panel, export flow, AI workflow instructions, and verification checks so
|
|
8
|
+
you can focus prompts on the visual idea instead of rebuilding common creative
|
|
9
|
+
tool plumbing.
|
|
10
|
+
|
|
11
|
+
You use Toolcraft when a custom tool is faster or clearer than forcing a broad
|
|
12
|
+
design app to fit one specific job: procedural graphics, image effects, branded
|
|
13
|
+
asset generators, animation controllers, shader experiments, or quick visual
|
|
14
|
+
tools for client work.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
4
17
|
|
|
5
18
|
```bash
|
|
6
19
|
npx @pixel-point/toolcraft create
|
|
@@ -12,14 +25,20 @@ After dependencies are installed, Toolcraft installs the required workflow skill
|
|
|
12
25
|
in a batch through the `skills` CLI. The skill installer uses the same agent,
|
|
13
26
|
scope, and installation prompts as `npx skills add`.
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
Example:
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
```bash
|
|
31
|
+
npx @pixel-point/toolcraft create my-ascii-tool
|
|
32
|
+
cd my-ascii-tool
|
|
33
|
+
pnpm dev
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then open the generated folder in Codex, Claude Code, Cursor, or another AI
|
|
37
|
+
coding agent and prompt for the creative output you want, for example:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
Build an app that applies an ASCII effect to an uploaded image.
|
|
41
|
+
```
|
|
23
42
|
|
|
24
43
|
Scripted usage:
|
|
25
44
|
|
|
@@ -50,5 +69,19 @@ Local tarball test, matching the published package layout:
|
|
|
50
69
|
cd cli
|
|
51
70
|
npm pack --pack-destination /tmp
|
|
52
71
|
cd ..
|
|
53
|
-
TOOLCRAFT_SKIP_INSTALL=1 TOOLCRAFT_SKIP_SKILLS=1 npm exec --package /tmp/pixel-point-toolcraft-0.0.
|
|
72
|
+
TOOLCRAFT_SKIP_INSTALL=1 TOOLCRAFT_SKIP_SKILLS=1 npm exec --package /tmp/pixel-point-toolcraft-0.0.10.tgz -- toolcraft create /tmp/toolcraft-pack-exec-test --name pack-exec-test --yes --force
|
|
54
73
|
```
|
|
74
|
+
|
|
75
|
+
## Learn more
|
|
76
|
+
|
|
77
|
+
- [How to craft personal design tools with AI](https://pixelpoint.io/blog/how-to-craft-personal-design-tools-with-toolcraft/)
|
|
78
|
+
- [Toolcraft YouTube tutorial](https://youtu.be/-QlmkGZLzFo)
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Toolcraft is distributed under the Toolcraft Designer License in `LICENSE.md`.
|
|
83
|
+
Designer client work is permitted under that license. Using AI coding assistants
|
|
84
|
+
or agents such as Codex, Claude, ChatGPT, Cursor, or similar tools to work on
|
|
85
|
+
generated apps is permitted. Platform, generator, AI software product,
|
|
86
|
+
app-builder, website-builder, template-marketplace, and resale uses require a
|
|
87
|
+
separate commercial license from Pixel Point.
|
package/package.json
CHANGED
package/src/generate.mjs
CHANGED
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
pathExists,
|
|
12
12
|
removeDirectory,
|
|
13
13
|
} from "./copy-recursive.mjs";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
createGeneratedPackageJson,
|
|
16
|
+
createGeneratedTsConfig,
|
|
17
|
+
normalizeProjectTitle,
|
|
18
|
+
} from "./package-json.mjs";
|
|
15
19
|
import { rewriteGeneratedText, rewriteTextFiles } from "./rewrite-imports.mjs";
|
|
16
20
|
|
|
17
21
|
const PACKAGE_ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
@@ -21,6 +25,17 @@ function writeJson(filePath, value) {
|
|
|
21
25
|
return fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
function escapeHtmlText(value) {
|
|
29
|
+
return String(value)
|
|
30
|
+
.replaceAll("&", "&")
|
|
31
|
+
.replaceAll("<", "<")
|
|
32
|
+
.replaceAll(">", ">");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function escapeHtmlAttribute(value) {
|
|
36
|
+
return escapeHtmlText(value).replaceAll('"', """);
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
async function readJson(filePath) {
|
|
25
40
|
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
26
41
|
}
|
|
@@ -100,6 +115,25 @@ async function restoreGeneratedGitignore(targetDir) {
|
|
|
100
115
|
await fs.rename(npmSafeGitignorePath, gitignorePath);
|
|
101
116
|
}
|
|
102
117
|
|
|
118
|
+
async function writeGeneratedProjectTitle(targetDir, title) {
|
|
119
|
+
const indexPath = path.join(targetDir, "index.html");
|
|
120
|
+
const source = await fs.readFile(indexPath, "utf8");
|
|
121
|
+
const escapedTitle = escapeHtmlText(title);
|
|
122
|
+
const escapedTitleAttribute = escapeHtmlAttribute(title);
|
|
123
|
+
const withTitle = source.replace(/<title>.*?<\/title>/, `<title>${escapedTitle}</title>`);
|
|
124
|
+
const identityMeta = `<meta name="toolcraft-app-title" content="${escapedTitleAttribute}" />`;
|
|
125
|
+
const identityMetaPattern = /<meta\b(?=[^>]*\bname=["']toolcraft-app-title["'])[^>]*>/i;
|
|
126
|
+
const nextSource = identityMetaPattern.test(withTitle)
|
|
127
|
+
? withTitle.replace(identityMetaPattern, identityMeta)
|
|
128
|
+
: withTitle.replace(/<head>/i, `<head>\n ${identityMeta}`);
|
|
129
|
+
|
|
130
|
+
if (!nextSource.includes(`<title>${escapedTitle}</title>`) || !nextSource.includes(identityMeta)) {
|
|
131
|
+
throw new Error(`Unable to update generated app title in ${indexPath}.`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await fs.writeFile(indexPath, nextSource);
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
async function removeToolcraftTestFiles(toolcraftRoot) {
|
|
104
138
|
async function visit(currentDir) {
|
|
105
139
|
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
@@ -190,6 +224,12 @@ export async function generateToolcraft(options = {}) {
|
|
|
190
224
|
await assertDirectory(sourcePaths.uiSrc, "UI package source");
|
|
191
225
|
await assertDirectory(sourcePaths.toolcraftSrc, "Toolcraft template runtime package source");
|
|
192
226
|
const starterPackageJson = await readJson(path.join(sourcePaths.starterDir, "package.json"));
|
|
227
|
+
const packageJson = createGeneratedPackageJson({
|
|
228
|
+
name: options.name ?? path.basename(targetDir),
|
|
229
|
+
starterPackageJson,
|
|
230
|
+
});
|
|
231
|
+
const projectTitle = normalizeProjectTitle(packageJson.name);
|
|
232
|
+
|
|
193
233
|
await ensureWritableTargetDirectory(targetDir, { force: options.force });
|
|
194
234
|
|
|
195
235
|
await copyDirectory(sourcePaths.starterDir, targetDir);
|
|
@@ -206,11 +246,8 @@ export async function generateToolcraft(options = {}) {
|
|
|
206
246
|
const changedFiles = await rewriteTextFiles(targetDir, (source) =>
|
|
207
247
|
rewriteGeneratedAppText(source),
|
|
208
248
|
);
|
|
249
|
+
await writeGeneratedProjectTitle(targetDir, projectTitle);
|
|
209
250
|
await writeToolcraftIntegrityManifest(toolcraftRoot);
|
|
210
|
-
const packageJson = createGeneratedPackageJson({
|
|
211
|
-
name: options.name ?? path.basename(targetDir),
|
|
212
|
-
starterPackageJson,
|
|
213
|
-
});
|
|
214
251
|
|
|
215
252
|
await writeJson(path.join(targetDir, "package.json"), packageJson);
|
|
216
253
|
await writeJson(path.join(targetDir, "tsconfig.json"), createGeneratedTsConfig());
|
package/src/generate.test.mjs
CHANGED
|
@@ -79,7 +79,15 @@ describe("generateToolcraft", () => {
|
|
|
79
79
|
assert.equal(packageJson.devDependencies["@playwright/test"], "^1.51.1");
|
|
80
80
|
assert.equal(packageJson.scripts["ai:check"], "node scripts/check-ai-skills.mjs");
|
|
81
81
|
assert.equal(packageJson.scripts.dev, "node scripts/run-vite-on-free-port.mjs dev");
|
|
82
|
+
assert.equal(
|
|
83
|
+
packageJson.scripts["dev:restart"],
|
|
84
|
+
"node scripts/run-vite-on-free-port.mjs dev --toolcraft-restart",
|
|
85
|
+
);
|
|
82
86
|
assert.equal(packageJson.scripts.preview, "node scripts/run-vite-on-free-port.mjs preview");
|
|
87
|
+
assert.equal(
|
|
88
|
+
packageJson.scripts["preview:restart"],
|
|
89
|
+
"node scripts/run-vite-on-free-port.mjs preview --toolcraft-restart",
|
|
90
|
+
);
|
|
83
91
|
assert.equal(packageJson.scripts["docs:check"], "node scripts/check-toolcraft-docs.mjs");
|
|
84
92
|
assert.equal(
|
|
85
93
|
packageJson.scripts.test,
|
|
@@ -93,11 +101,16 @@ describe("generateToolcraft", () => {
|
|
|
93
101
|
assert.equal(packageJson.scripts["verify:quick"], "pnpm ai:check && pnpm test");
|
|
94
102
|
assert.equal(packageJson.scripts["verify:ui"], "pnpm test:browser");
|
|
95
103
|
assert.equal(packageJson.scripts["verify:perf"], "pnpm test:browser:perf");
|
|
104
|
+
assert.equal(packageJson.scripts["verify:perf:playwright"], "pnpm test:browser:perf");
|
|
96
105
|
assert.equal(
|
|
97
106
|
packageJson.scripts["verify:final"],
|
|
98
107
|
"pnpm ai:check && pnpm test && pnpm build && pnpm test:browser",
|
|
99
108
|
);
|
|
100
109
|
|
|
110
|
+
const indexHtmlSource = await fs.readFile(path.join(targetDir, "index.html"), "utf8");
|
|
111
|
+
assert.match(indexHtmlSource, /<title>Generated App<\/title>/);
|
|
112
|
+
assert.match(indexHtmlSource, /<meta name="toolcraft-app-title" content="Generated App" \/>/);
|
|
113
|
+
|
|
101
114
|
assert.ok(await fs.stat(path.join(targetDir, "playwright.config.ts")));
|
|
102
115
|
const playwrightConfigSource = await fs.readFile(
|
|
103
116
|
path.join(targetDir, "playwright.config.ts"),
|
|
@@ -121,6 +134,7 @@ describe("generateToolcraft", () => {
|
|
|
121
134
|
assert.match(gitignoreSource, /dist/);
|
|
122
135
|
assert.match(gitignoreSource, /playwright-report/);
|
|
123
136
|
assert.match(gitignoreSource, /\.env\.\*/);
|
|
137
|
+
assert.match(gitignoreSource, /\.toolcraft/);
|
|
124
138
|
await assert.rejects(() => fs.stat(path.join(targetDir, "gitignore")), /ENOENT/);
|
|
125
139
|
assert.ok(await fs.stat(path.join(targetDir, "scripts/check-ai-skills.mjs")));
|
|
126
140
|
assert.ok(await fs.stat(path.join(targetDir, "scripts/toolcraft-port.mjs")));
|
|
@@ -146,6 +160,9 @@ describe("generateToolcraft", () => {
|
|
|
146
160
|
"utf8",
|
|
147
161
|
);
|
|
148
162
|
assert.match(appPerformanceSource, /defineToolcraftPerformance/);
|
|
163
|
+
assert.match(appPerformanceSource, /preferredRunner:\s*"agent-browser"/);
|
|
164
|
+
assert.match(appPerformanceSource, /fallbackRunner:\s*"playwright"/);
|
|
165
|
+
assert.match(appPerformanceSource, /agent-browser-unavailable/);
|
|
149
166
|
assert.doesNotMatch(appPerformanceSource, /function\s+validateToolcraftPerformanceCoverage/);
|
|
150
167
|
assert.doesNotMatch(
|
|
151
168
|
appPerformanceSource,
|
|
@@ -325,4 +342,27 @@ describe("generateToolcraft", () => {
|
|
|
325
342
|
/Target directory is not empty/,
|
|
326
343
|
);
|
|
327
344
|
});
|
|
345
|
+
|
|
346
|
+
it("writes the app identity marker when the generated title matches the template title", async () => {
|
|
347
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "toolcraft-cli-"));
|
|
348
|
+
tempRoots.push(tempRoot);
|
|
349
|
+
|
|
350
|
+
await generateToolcraft({
|
|
351
|
+
cwd: tempRoot,
|
|
352
|
+
force: true,
|
|
353
|
+
name: "toolcraft-app-template",
|
|
354
|
+
targetDir: "toolcraft-app-template",
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const indexHtmlSource = await fs.readFile(
|
|
358
|
+
path.join(tempRoot, "toolcraft-app-template", "index.html"),
|
|
359
|
+
"utf8",
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
assert.match(indexHtmlSource, /<title>Toolcraft App Template<\/title>/);
|
|
363
|
+
assert.match(
|
|
364
|
+
indexHtmlSource,
|
|
365
|
+
/<meta name="toolcraft-app-title" content="Toolcraft App Template" \/>/,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
328
368
|
});
|
package/src/package-json.mjs
CHANGED
|
@@ -9,6 +9,21 @@ export function sanitizePackageName(value) {
|
|
|
9
9
|
return sanitized || "toolcraft-app";
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export function normalizeProjectTitle(value) {
|
|
13
|
+
const words = String(value ?? "")
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/^@/, "")
|
|
16
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
17
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
|
|
20
|
+
const title = words
|
|
21
|
+
.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1).toLowerCase()}`)
|
|
22
|
+
.join(" ");
|
|
23
|
+
|
|
24
|
+
return title || "Toolcraft App";
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
function isWorkspaceDependency(name, specifier) {
|
|
13
28
|
return name.startsWith("@repo/") || String(specifier).startsWith("workspace:");
|
|
14
29
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createGeneratedPackageJson,
|
|
6
|
+
normalizeProjectTitle,
|
|
7
|
+
sanitizePackageName,
|
|
8
|
+
} from "./package-json.mjs";
|
|
5
9
|
|
|
6
10
|
describe("sanitizePackageName", () => {
|
|
7
11
|
it("normalizes package names for generated apps", () => {
|
|
@@ -11,6 +15,15 @@ describe("sanitizePackageName", () => {
|
|
|
11
15
|
});
|
|
12
16
|
});
|
|
13
17
|
|
|
18
|
+
describe("normalizeProjectTitle", () => {
|
|
19
|
+
it("creates a human-readable title from generated project names", () => {
|
|
20
|
+
assert.equal(normalizeProjectTitle("mesh-gradient"), "Mesh Gradient");
|
|
21
|
+
assert.equal(normalizeProjectTitle("mesh_gradient.app"), "Mesh Gradient App");
|
|
22
|
+
assert.equal(normalizeProjectTitle("@pixel-point/mesh-gradient"), "Pixel Point Mesh Gradient");
|
|
23
|
+
assert.equal(normalizeProjectTitle(""), "Toolcraft App");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
14
27
|
describe("createGeneratedPackageJson", () => {
|
|
15
28
|
it("uses the starter package manifest as source of truth", () => {
|
|
16
29
|
const packageJson = createGeneratedPackageJson({
|