@plasmickit/cli 0.4.0 → 0.5.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/package.json +1 -1
- package/skill/plasmic/SKILL.md +15 -7
- package/src/index.mjs +13 -2
- package/src/skill.mjs +60 -16
package/package.json
CHANGED
package/skill/plasmic/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plasmic
|
|
3
3
|
compatibility: Requires Node.js >=22.12, Python >=3.9 available as python3, and signed-in Plasmic Desktop MCP for design inspection or editing.
|
|
4
|
-
description:
|
|
4
|
+
description: Create, revise and review editable UI prototypes in Plasmic (原型设计/修改/验收), and generate, repair or verify their application code (代码生成/修复/还原验收). Use for prototype design when no other tool is specified, explicit Plasmic work, and follow-ups in established Plasmic design/code context. Excludes unrelated application coding and Plasmic platform development.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Plasmic
|
|
@@ -10,19 +10,27 @@ This is the entrypoint for resolving current resources and selecting a workflow.
|
|
|
10
10
|
|
|
11
11
|
## Resolve the workflow
|
|
12
12
|
|
|
13
|
-
Choose
|
|
13
|
+
Choose by the task's artifact and intent, not whether it is new work or uses MCP:
|
|
14
|
+
|
|
15
|
+
- `inspect`: explain or review the design/model itself without changing it.
|
|
16
|
+
- `prototype`: create, revise or repair an editable design and verify the resulting prototype.
|
|
17
|
+
- `codegen`: generate, update, repair or review application code against a Plasmic design, including read-only implementation acceptance.
|
|
18
|
+
|
|
19
|
+
For an editable prototype request with no chosen tool or existing design source, use Plasmic; honor another explicitly selected tool or established source. Follow-up work keeps its design/code context without repeating the skill name. Switch workflows when the target changes; read-only code review still uses codegen. Review does not authorize repairs. If the intended artifact is unclear, clarify it rather than choosing an editor or codebase by keyword alone.
|
|
14
20
|
|
|
15
21
|
If this skill contains `local-source.json`, read its absolute `resolver` path and run `node RESOLVER_PATH --mode MODE`. This selects current local development resources. A missing resolver/resource is an error; do not fall back to published or cached resources.
|
|
16
22
|
|
|
17
23
|
Otherwise run the matching published command:
|
|
18
24
|
|
|
19
25
|
```sh
|
|
20
|
-
npx -y @plasmickit/cli@latest context resolve --mode inspect
|
|
21
|
-
npx -y @plasmickit/cli@latest context resolve --mode prototype
|
|
22
|
-
npx -y @plasmickit/cli@latest context resolve --mode codegen
|
|
26
|
+
npx -y --prefer-online @plasmickit/cli@latest context resolve --mode inspect
|
|
27
|
+
npx -y --prefer-online @plasmickit/cli@latest context resolve --mode prototype
|
|
28
|
+
npx -y --prefer-online @plasmickit/cli@latest context resolve --mode codegen
|
|
23
29
|
```
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
Published `context resolve` first synchronizes existing installed skill entries from its CLI bundle, independently of resource updates. Local-source and symlinked entries are preserved. Report `skillUpdate` failures separately; they do not invalidate usable resources. Updated discovery instructions apply when the client reloads skills, not necessarily in this conversation.
|
|
32
|
+
|
|
33
|
+
Before work in a mode, resolve its resources and read every returned `mustRead` file, starting with `referencePath`; follow relevant links from `resourceRoot`. Within the same task, reuse known, already-read resources and retain pending checks; do not resolve again just because a new message arrived. Resolve/read a newly required mode or recover missing resource context before dependent work; reread affected design inputs when they change. Resource readiness or document reading alone does not establish workflow completion.
|
|
26
34
|
|
|
27
35
|
Both resolvers verify that Python 3.9+ is available as `python3` for reference readers and gates. The published resolver also checks the NAS release manifest and verifies resource hashes. Stop dependent work on failure; do not silently use stale references. If it requires a newer CLI before npm has that version, install its returned `cliUrl` and rerun. Relay meaningful preparation progress from stderr; stdout contains the result. Report local versus published resources accurately.
|
|
28
36
|
|
|
@@ -40,7 +48,7 @@ Select components before composing markup. Discover existing business components
|
|
|
40
48
|
|
|
41
49
|
For admin prototypes, read `references/admin-templates.md` and its catalog, inspect applicable live templates, then adapt the selected composition to the requirement. Read the relevant component and composition references before using them. A template or component must fit the current requirement and the project's actual library.
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
Keep plans and acceptance evidence outside the business UI. Prototype mutations require edit permission. Codegen follows the target application's architecture; an explicit official Plasmic CLI request follows the guide's official-CLI reference.
|
|
44
52
|
|
|
45
53
|
For prototype work, follow the workflow's execution-stage table. Derive observable acceptance cases from the current requirements before generation, prepare the current Page's detailed inputs, and retain actual gate/runtime results at each stage. Use existing preparation and verification commands; declarations, tool success and screenshots alone do not establish business acceptance.
|
|
46
54
|
|
package/src/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
import { formatProgress } from "./progress.mjs";
|
|
7
|
-
import { CLI_VERSION } from "./protocol.mjs";
|
|
7
|
+
import { CLI_VERSION, MODE_REFERENCES } from "./protocol.mjs";
|
|
8
8
|
import {
|
|
9
9
|
checkCliVersion,
|
|
10
10
|
checkReferences,
|
|
@@ -66,7 +66,17 @@ export async function main(argv) {
|
|
|
66
66
|
} else if (command === "references" && action === "path") {
|
|
67
67
|
output = await referencePath(values);
|
|
68
68
|
} else if (command === "context" && action === "resolve") {
|
|
69
|
-
|
|
69
|
+
if (!Object.hasOwn(MODE_REFERENCES, values.mode)) throw new Error("--mode must be inspect, prototype or codegen");
|
|
70
|
+
let skillUpdate;
|
|
71
|
+
try { skillUpdate = await installSkill({ existingOnly: true }); }
|
|
72
|
+
catch (error) { skillUpdate = { ok: false, error: error.message, installations: [] }; }
|
|
73
|
+
for (const entry of skillUpdate.installations) {
|
|
74
|
+
if (entry.status === "updated") process.stderr.write(`✓ Skill 入口已更新:${entry.skillPath};客户端重新加载技能后采用新入口\n`);
|
|
75
|
+
if (entry.status === "failed") process.stderr.write(`! Skill 入口更新失败:${entry.skillPath}:${entry.error}\n`);
|
|
76
|
+
}
|
|
77
|
+
if (skillUpdate.error) process.stderr.write(`! Skill 入口更新失败:${skillUpdate.error}\n`);
|
|
78
|
+
try { output = { ...await resolveContext(values.mode, values), skillUpdate }; }
|
|
79
|
+
catch (error) { error.skillUpdate = skillUpdate; throw error; }
|
|
70
80
|
} else {
|
|
71
81
|
throw new Error(`Unknown command: ${command} ${action}`);
|
|
72
82
|
}
|
|
@@ -85,6 +95,7 @@ if (
|
|
|
85
95
|
ok: false,
|
|
86
96
|
error: error.message,
|
|
87
97
|
...(error.stage ? { stage: error.stage } : {}),
|
|
98
|
+
...(error.skillUpdate ? { skillUpdate: error.skillUpdate } : {}),
|
|
88
99
|
...(error.cliUrl
|
|
89
100
|
? {
|
|
90
101
|
cliUrl: error.cliUrl,
|
package/src/skill.mjs
CHANGED
|
@@ -1,7 +1,59 @@
|
|
|
1
|
-
import { mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { lstat, mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
5
|
import { detectInstalledAgents } from "./agents.mjs";
|
|
6
|
+
import { CLI_VERSION, compareVersions } from "./protocol.mjs";
|
|
7
|
+
|
|
8
|
+
const versionMarker = /^<!-- plasmickit-cli-version: (.+) -->$/m;
|
|
9
|
+
|
|
10
|
+
async function optionalStat(filename) {
|
|
11
|
+
try { return await lstat(filename); }
|
|
12
|
+
catch (error) { if (error.code === "ENOENT") return undefined; throw error; }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function skipReason(target, existingOnly) {
|
|
16
|
+
if (await optionalStat(path.join(target, "local-source.json"))) return "local-source";
|
|
17
|
+
const file = await optionalStat(path.join(target, "SKILL.md"));
|
|
18
|
+
if ((await optionalStat(target))?.isSymbolicLink() || file?.isSymbolicLink()) return "symlink";
|
|
19
|
+
if (existingOnly && !file) return "not-installed";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function writeSkill(destination, content, existingOnly) {
|
|
23
|
+
const { target, skillPath } = destination;
|
|
24
|
+
const skipped = await skipReason(target, existingOnly);
|
|
25
|
+
if (skipped) return { status: "skipped", reason: skipped };
|
|
26
|
+
if (!existingOnly) await mkdir(target, { recursive: true });
|
|
27
|
+
const lock = path.join(target, ".plasmickit-skill-lock");
|
|
28
|
+
const deadline = Date.now() + 5000;
|
|
29
|
+
for (;;) {
|
|
30
|
+
try { await mkdir(lock); break; }
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error.code !== "EEXIST") throw error;
|
|
33
|
+
if (Date.now() >= deadline) throw new Error(`Skill update still locked: ${lock}. Retry after the other updater finishes; remove this lock only if no updater is running.`);
|
|
34
|
+
await delay(50);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
// Recheck under the shared directory lock: another CLI may have just updated it.
|
|
39
|
+
const skipped = await skipReason(target, existingOnly);
|
|
40
|
+
if (skipped) return { status: "skipped", reason: skipped };
|
|
41
|
+
let previous;
|
|
42
|
+
try { previous = await readFile(skillPath, "utf8"); }
|
|
43
|
+
catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
44
|
+
const installedVersion = previous?.match(versionMarker)?.[1];
|
|
45
|
+
if (installedVersion && compareVersions(installedVersion, CLI_VERSION) > 0) {
|
|
46
|
+
return { status: "skipped", reason: "newer-installed", installedVersion };
|
|
47
|
+
}
|
|
48
|
+
if (previous === content) return { status: "unchanged" };
|
|
49
|
+
const temporary = path.join(target, `.SKILL-${randomUUID()}.tmp`);
|
|
50
|
+
try {
|
|
51
|
+
await writeFile(temporary, content, { flag: "wx" });
|
|
52
|
+
await rename(temporary, skillPath);
|
|
53
|
+
} finally { await rm(temporary, { force: true }); }
|
|
54
|
+
return { status: previous === undefined ? "installed" : "updated" };
|
|
55
|
+
} finally { await rm(lock, { recursive: true, force: true }); }
|
|
56
|
+
}
|
|
5
57
|
|
|
6
58
|
async function physicalPath(filename) {
|
|
7
59
|
try { return await realpath(filename); }
|
|
@@ -12,10 +64,12 @@ async function physicalPath(filename) {
|
|
|
12
64
|
}
|
|
13
65
|
}
|
|
14
66
|
|
|
15
|
-
export async function installSkill({ dryRun = false, ...options } = {}) {
|
|
67
|
+
export async function installSkill({ dryRun = false, existingOnly = false, ...options } = {}) {
|
|
16
68
|
const detected = await detectInstalledAgents(options);
|
|
17
|
-
if (!detected.length) return { ok: false, mode: "bootstrap", detected, installations: [], error: "No supported local Agent CLI or app detected. Install or initialize a supported client, then rerun skill install." };
|
|
18
|
-
const
|
|
69
|
+
if (!detected.length && !existingOnly) return { ok: false, mode: "bootstrap", detected, installations: [], error: "No supported local Agent CLI or app detected. Install or initialize a supported client, then rerun skill install." };
|
|
70
|
+
const bundled = await readFile(new URL("../skill/plasmic/SKILL.md", import.meta.url), "utf8");
|
|
71
|
+
// Keep version and instructions in one atomic replacement, without a sidecar file.
|
|
72
|
+
const content = `${bundled.trimEnd()}\n\n<!-- plasmickit-cli-version: ${CLI_VERSION} -->\n`;
|
|
19
73
|
const destinations = new Map();
|
|
20
74
|
const installations = [];
|
|
21
75
|
for (const agent of detected) {
|
|
@@ -32,18 +86,8 @@ export async function installSkill({ dryRun = false, ...options } = {}) {
|
|
|
32
86
|
for (const destination of destinations.values()) {
|
|
33
87
|
if (dryRun) { installations.push({ ...destination, status: "planned" }); continue; }
|
|
34
88
|
try {
|
|
35
|
-
|
|
36
|
-
try { previous = await readFile(destination.skillPath, "utf8"); }
|
|
37
|
-
catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
38
|
-
if (previous === content) { installations.push({ ...destination, status: "unchanged" }); continue; }
|
|
39
|
-
await mkdir(destination.target, { recursive: true });
|
|
40
|
-
const temporary = path.join(destination.target, `.SKILL-${randomUUID()}.tmp`);
|
|
41
|
-
try {
|
|
42
|
-
await writeFile(temporary, content, { flag: "wx" });
|
|
43
|
-
await rename(temporary, destination.skillPath);
|
|
44
|
-
} finally { await rm(temporary, { force: true }); }
|
|
45
|
-
installations.push({ ...destination, status: "installed" });
|
|
89
|
+
installations.push({ ...destination, ...await writeSkill(destination, content, existingOnly) });
|
|
46
90
|
} catch (error) { installations.push({ ...destination, status: "failed", error: error.message }); }
|
|
47
91
|
}
|
|
48
|
-
return { ok: installations.every((result) => result.status !== "failed"), mode: "bootstrap", dryRun, detected, installations };
|
|
92
|
+
return { ok: installations.every((result) => result.status !== "failed"), mode: existingOnly ? "sync" : "bootstrap", cliVersion: CLI_VERSION, dryRun, detected, installations };
|
|
49
93
|
}
|