@plasmickit/cli 0.0.39 → 0.0.41
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/src/agents.mjs +96 -0
- package/src/index.mjs +4 -3
- package/src/skill.mjs +45 -7
package/package.json
CHANGED
package/src/agents.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { access, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
// Global discovery locations are documented in ai/plasmic/README.md.
|
|
7
|
+
export function agentRegistry(home, env) {
|
|
8
|
+
const user = (relative) => path.join(home, relative);
|
|
9
|
+
const config = env.XDG_CONFIG_HOME?.trim() || user(".config");
|
|
10
|
+
const shared = user(".agents/skills");
|
|
11
|
+
const claude = env.CLAUDE_CONFIG_DIR?.trim() || user(".claude");
|
|
12
|
+
const codex = env.CODEX_HOME?.trim() || user(".codex");
|
|
13
|
+
const dsh = env.DSH_HOME?.trim() || user(".dsh");
|
|
14
|
+
const dshAgents = env.DSH_AGENTS_HOME?.trim() || user(".agents");
|
|
15
|
+
return [
|
|
16
|
+
{ id: "codex", name: "Codex", roots: [codex], skills: shared, commands: ["codex"], apps: ["Codex"] },
|
|
17
|
+
{ id: "claude-code", name: "Claude Code (CLI / Desktop Code)", roots: [claude], skills: path.join(claude, "skills"), commands: ["claude"], apps: ["Claude"] },
|
|
18
|
+
{ id: "deepseek-harness", name: "DeepSeek Harness", roots: [dsh], skills: path.join(dshAgents, "skills"), commands: ["dsh"] },
|
|
19
|
+
{ id: "cursor", name: "Cursor", roots: [user(".cursor")], skills: shared, commands: ["cursor", "cursor-agent"], apps: ["Cursor"] },
|
|
20
|
+
{ id: "gemini-cli", name: "Gemini CLI", roots: [user(".gemini/settings.json"), user(".gemini/skills")], skills: shared, commands: ["gemini"] },
|
|
21
|
+
{ id: "opencode", name: "OpenCode", roots: [path.join(config, "opencode")], skills: shared, commands: ["opencode"], apps: ["OpenCode"] },
|
|
22
|
+
{ id: "github-copilot", name: "GitHub Copilot", roots: [user(".copilot")], skills: shared, commands: ["copilot"], apps: ["GitHub Copilot"], extensions: ["github.copilot-", "github.copilot-chat-"] },
|
|
23
|
+
{ id: "antigravity", name: "Antigravity", roots: [user(".gemini/config"), user(".gemini/antigravity")], skills: user(".gemini/config/skills"), apps: ["Antigravity", "Antigravity IDE"] },
|
|
24
|
+
{ id: "antigravity-cli", name: "Antigravity CLI", roots: [user(".gemini/antigravity-cli")], skills: user(".gemini/antigravity-cli/skills"), commands: ["agy"] },
|
|
25
|
+
{ id: "windsurf", name: "Windsurf", roots: [user(".codeium/windsurf")], skills: user(".codeium/windsurf/skills"), commands: ["windsurf"], apps: ["Windsurf"] },
|
|
26
|
+
{ id: "cline", name: "Cline", roots: [user(".cline")], skills: shared, commands: ["cline"], extensions: ["saoudrizwan.claude-dev-"] },
|
|
27
|
+
{ id: "roo", name: "Roo Code", roots: [user(".roo")], skills: user(".roo/skills"), extensions: ["rooveterinaryinc.roo-cline-"] },
|
|
28
|
+
{ id: "continue", name: "Continue", roots: [user(".continue")], skills: user(".continue/skills"), commands: ["cn"], extensions: ["continue.continue-"] },
|
|
29
|
+
{ id: "kilo", name: "Kilo Code", roots: [user(".kilo")], skills: user(".kilo/skills"), commands: ["kilo"], extensions: ["kilocode.kilo-code-"] },
|
|
30
|
+
{ id: "kiro", name: "Kiro", roots: [user(".kiro")], skills: user(".kiro/skills"), commands: ["kiro-cli", "kiro"], apps: ["Kiro"] },
|
|
31
|
+
{ id: "trae", name: "Trae", roots: [user(".trae")], skills: user(".trae/skills"), apps: ["Trae"] },
|
|
32
|
+
{ id: "trae-cn", name: "Trae CN", roots: [user(".trae-cn")], skills: user(".trae-cn/skills"), apps: ["Trae CN"] },
|
|
33
|
+
{ id: "qwen-code", name: "Qwen Code", roots: [user(".qwen")], skills: user(".qwen/skills"), commands: ["qwen"] },
|
|
34
|
+
{ id: "amp", name: "Amp", roots: [path.join(config, "amp")], skills: path.join(config, "agents/skills"), commands: ["amp"] },
|
|
35
|
+
{ id: "goose", name: "Goose", roots: [path.join(config, "goose")], skills: path.join(config, "goose/skills"), commands: ["goose"], apps: ["Goose"] },
|
|
36
|
+
{ id: "droid", name: "Droid", roots: [user(".factory")], skills: shared, commands: ["droid"] },
|
|
37
|
+
{ id: "pi", name: "Pi", roots: [user(".pi/agent")], skills: shared, commands: ["pi"] },
|
|
38
|
+
{ id: "openclaw", name: "OpenClaw", roots: [user(".openclaw")], skills: user(".openclaw/skills"), commands: ["openclaw"] },
|
|
39
|
+
{ id: "zcode", name: "ZCode", roots: [user(".zcode")], skills: user(".zcode/skills"), apps: ["ZCode"] },
|
|
40
|
+
{ id: "zed", name: "Zed", roots: [path.join(config, "zed"), ...(env.APPDATA ? [path.join(env.APPDATA, "Zed")] : [])], skills: shared, commands: ["zed"], apps: ["Zed"] },
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function exists(filename) {
|
|
45
|
+
try { await stat(filename); return true; }
|
|
46
|
+
catch (error) { if (["ENOENT", "ENOTDIR"].includes(error.code)) return false; throw error; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function commandPath(command, env, platform) {
|
|
50
|
+
const extensions = platform === "win32" ? ["", ...(env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";")] : [""];
|
|
51
|
+
for (const directory of (env.PATH || env.Path || "").split(platform === "win32" ? ";" : ":").filter(Boolean)) {
|
|
52
|
+
for (const extension of extensions) {
|
|
53
|
+
const filename = path.join(directory.replace(/^"|"$/g, ""), command + extension);
|
|
54
|
+
try {
|
|
55
|
+
if (!(await stat(filename)).isFile()) continue;
|
|
56
|
+
await access(filename, platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
57
|
+
return filename;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (!["ENOENT", "ENOTDIR", "EACCES"].includes(error.code)) throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function applicationPaths(name, home, env, platform) {
|
|
66
|
+
if (platform === "darwin") return ["/Applications", path.join(home, "Applications")].map((root) => path.join(root, name + ".app"));
|
|
67
|
+
if (platform === "win32") return [env.APPDATA, env.ProgramData].filter(Boolean).map((root) => path.join(root, "Microsoft/Windows/Start Menu/Programs", name + ".lnk"));
|
|
68
|
+
const basename = name.toLowerCase().replaceAll(" ", "-") + ".desktop";
|
|
69
|
+
return [env.XDG_DATA_HOME || path.join(home, ".local/share"), ...(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":")].map((root) => path.join(root, "applications", basename));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function detectInstalledAgents({ home = homedir(), env = process.env, platform = process.platform } = {}) {
|
|
73
|
+
const extensionRoots = [".vscode", ".vscode-insiders", ".cursor", ".windsurf"].map((root) => path.join(home, root, "extensions"));
|
|
74
|
+
const extensions = [];
|
|
75
|
+
for (const root of extensionRoots) {
|
|
76
|
+
try { extensions.push(...(await readdir(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => path.join(root, entry.name))); }
|
|
77
|
+
catch (error) { if (!["ENOENT", "ENOTDIR"].includes(error.code)) throw error; }
|
|
78
|
+
}
|
|
79
|
+
const installed = [];
|
|
80
|
+
for (const agent of agentRegistry(home, env)) {
|
|
81
|
+
const evidence = [];
|
|
82
|
+
for (const root of agent.roots) if (await exists(root)) evidence.push({ kind: "config", path: root });
|
|
83
|
+
for (const command of agent.commands || []) {
|
|
84
|
+
const filename = await commandPath(command, env, platform);
|
|
85
|
+
if (filename) evidence.push({ kind: "cli", path: filename });
|
|
86
|
+
}
|
|
87
|
+
for (const name of agent.apps || []) {
|
|
88
|
+
for (const filename of applicationPaths(name, home, env, platform)) if (await exists(filename)) evidence.push({ kind: "app", path: filename });
|
|
89
|
+
}
|
|
90
|
+
for (const filename of extensions) {
|
|
91
|
+
if (agent.extensions?.some((prefix) => path.basename(filename).toLowerCase().startsWith(prefix))) evidence.push({ kind: "extension", path: filename });
|
|
92
|
+
}
|
|
93
|
+
if (evidence.length) installed.push({ id: agent.id, name: agent.name, skillsRoot: path.resolve(agent.skills), evidence });
|
|
94
|
+
}
|
|
95
|
+
return installed;
|
|
96
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { installSkill } from "./skill.mjs";
|
|
|
8
8
|
import { CLI_VERSION } from "./protocol.mjs";
|
|
9
9
|
|
|
10
10
|
const usage = `plasmickit commands:
|
|
11
|
-
skill install --
|
|
11
|
+
skill install [--dry-run]
|
|
12
12
|
version check [--feed HTTPS_URL]
|
|
13
13
|
references check|update|path [--feed HTTPS_URL] [--home CACHE_ROOT]
|
|
14
14
|
context resolve --mode prototype|codegen [--feed HTTPS_URL] [--home CACHE_ROOT]
|
|
@@ -16,7 +16,7 @@ const usage = `plasmickit commands:
|
|
|
16
16
|
`;
|
|
17
17
|
export async function main(argv) {
|
|
18
18
|
const { values, positionals } = parseArgs({ args: argv, allowPositionals: true, options: {
|
|
19
|
-
|
|
19
|
+
"dry-run": { type: "boolean" }, mode: { type: "string" }, feed: { type: "string" }, home: { type: "string" },
|
|
20
20
|
help: { type: "boolean" }, version: { type: "boolean" },
|
|
21
21
|
} });
|
|
22
22
|
if (values.version) { process.stdout.write(CLI_VERSION + "\n"); return; }
|
|
@@ -24,7 +24,7 @@ export async function main(argv) {
|
|
|
24
24
|
const [command, action] = positionals;
|
|
25
25
|
if (positionals.length !== 2) throw new Error("Expected a command and action; use --help");
|
|
26
26
|
let output;
|
|
27
|
-
if (command === "skill" && action === "install") output = await installSkill(values
|
|
27
|
+
if (command === "skill" && action === "install") output = await installSkill({ dryRun: values["dry-run"] });
|
|
28
28
|
else if (command === "version" && action === "check") output = await checkCliVersion(values);
|
|
29
29
|
else if (command === "references" && action === "check") output = await checkReferences(values);
|
|
30
30
|
else if (command === "references" && action === "update") {
|
|
@@ -35,6 +35,7 @@ export async function main(argv) {
|
|
|
35
35
|
else if (command === "context" && action === "resolve") output = await resolveContext(values.mode, values);
|
|
36
36
|
else throw new Error(`Unknown command: ${command} ${action}`);
|
|
37
37
|
process.stdout.write(JSON.stringify({ ok: true, ...output }, null, 2) + "\n");
|
|
38
|
+
if (output.ok === false) process.exitCode = 1;
|
|
38
39
|
}
|
|
39
40
|
if (process.argv[1] && realpathSync(path.resolve(process.argv[1])) === fileURLToPath(import.meta.url)) {
|
|
40
41
|
main(process.argv.slice(2)).catch((error) => {
|
package/src/skill.mjs
CHANGED
|
@@ -1,11 +1,49 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import { detectInstalledAgents } from "./agents.mjs";
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
async function physicalPath(filename) {
|
|
7
|
+
try { return await realpath(filename); }
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (!["ENOENT", "ENOTDIR"].includes(error.code)) throw error;
|
|
10
|
+
const parent = path.dirname(filename);
|
|
11
|
+
return parent === filename ? filename : path.join(await physicalPath(parent), path.basename(filename));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function installSkill({ dryRun = false, ...options } = {}) {
|
|
16
|
+
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." };
|
|
7
18
|
const content = await readFile(new URL("../skill/plasmic/SKILL.md", import.meta.url), "utf8");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
19
|
+
const destinations = new Map();
|
|
20
|
+
const installations = [];
|
|
21
|
+
for (const agent of detected) {
|
|
22
|
+
const target = path.join(agent.skillsRoot, "plasmic");
|
|
23
|
+
let physicalTarget;
|
|
24
|
+
try { physicalTarget = await physicalPath(target); }
|
|
25
|
+
catch (error) {
|
|
26
|
+
installations.push({ agents: [agent.id], target, skillPath: path.join(target, "SKILL.md"), status: "failed", error: error.message }); continue;
|
|
27
|
+
}
|
|
28
|
+
const key = (options.platform || process.platform) === "win32" ? physicalTarget.toLowerCase() : physicalTarget;
|
|
29
|
+
if (destinations.has(key)) destinations.get(key).agents.push(agent.id);
|
|
30
|
+
else destinations.set(key, { agents: [agent.id], target, skillPath: path.join(target, "SKILL.md") });
|
|
31
|
+
}
|
|
32
|
+
for (const destination of destinations.values()) {
|
|
33
|
+
if (dryRun) { installations.push({ ...destination, status: "planned" }); continue; }
|
|
34
|
+
try {
|
|
35
|
+
let previous;
|
|
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" });
|
|
46
|
+
} catch (error) { installations.push({ ...destination, status: "failed", error: error.message }); }
|
|
47
|
+
}
|
|
48
|
+
return { ok: installations.every((result) => result.status !== "failed"), mode: "bootstrap", dryRun, detected, installations };
|
|
11
49
|
}
|