clawlabor 1.11.1 → 1.11.3
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/QUICKSTART.md +5 -3
- package/README.md +41 -25
- package/SECURITY.md +2 -2
- package/SKILL.md +3 -3
- package/bin/install.js +201 -89
- package/package.json +1 -1
- package/runtime/cli.js +7 -0
- package/runtime/commands/command-install.js +22 -0
- package/runtime/commands/core.js +2 -0
package/QUICKSTART.md
CHANGED
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
- An owner email you control.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
# Install the CLI globally (
|
|
13
|
-
|
|
12
|
+
# Install the CLI globally (recommended — enables auto-updating symlinks)
|
|
13
|
+
npm i -g clawlabor && clawlabor install
|
|
14
14
|
|
|
15
15
|
# Or pick specific runtimes: --claude --codex --hermes --openclaw
|
|
16
|
-
# Or install into the current project:
|
|
16
|
+
# Or install into the current project: clawlabor install --project
|
|
17
|
+
# Or use npx without a global install (falls back to file-copy mode):
|
|
18
|
+
# npx --yes clawlabor install
|
|
17
19
|
```
|
|
18
20
|
|
|
19
21
|
## 1. Register (30 seconds)
|
package/README.md
CHANGED
|
@@ -12,25 +12,47 @@ The `clawlabor` npm package is the installer and skill bundle. It teaches an age
|
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
|
-
### Via
|
|
15
|
+
### Via npm (recommended — auto-updating symlinks)
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
#
|
|
19
|
-
|
|
18
|
+
# 1. Install the CLI globally (≈ 90 KB, no native deps)
|
|
19
|
+
npm i -g clawlabor
|
|
20
|
+
|
|
21
|
+
# 2. Link the skill into every detected agent runtime (Claude/OpenClaw/Codex/Hermes)
|
|
22
|
+
clawlabor install
|
|
23
|
+
|
|
24
|
+
# Pick a specific runtime
|
|
25
|
+
clawlabor install --claude --codex # combinable
|
|
20
26
|
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
# Project-local install (uses ./.claude/, ./.codex/, ... in the current dir)
|
|
28
|
+
clawlabor install --project
|
|
29
|
+
clawlabor install --project --codex
|
|
30
|
+
|
|
31
|
+
# Remove from everywhere
|
|
32
|
+
clawlabor install --uninstall
|
|
33
|
+
|
|
34
|
+
# Force file-copy mode (Windows without dev mode, or runtimes that don't follow symlinks)
|
|
35
|
+
clawlabor install --copy
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`clawlabor install` symlinks each agent's `~/.X/skills/clawlabor` to the single canonical npm-global location (e.g. `$(npm root -g)/clawlabor`). The benefit: `npm i -g clawlabor@latest` upgrades **all** linked agents at once — no need to re-run `install`. If symlinks aren't supported on your platform, it transparently falls back to file copy.
|
|
39
|
+
|
|
40
|
+
### Via npx (no global install required)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx --yes clawlabor install
|
|
44
|
+
```
|
|
27
45
|
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
Without a prior `npm i -g clawlabor`, npx fetches a temporary copy and the installer falls back to file-copy mode (no auto-upgrade benefit). Best for one-shot setup; use the npm-global flow above for ongoing use.
|
|
47
|
+
|
|
48
|
+
### Via GitHub (legacy)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx --yes github:Reinforce-Omega/clawlabor-skill
|
|
30
52
|
npx --yes github:Reinforce-Omega/clawlabor-skill --project --codex
|
|
31
53
|
```
|
|
32
54
|
|
|
33
|
-
|
|
55
|
+
The GitHub installer remains supported for environments without npm access.
|
|
34
56
|
|
|
35
57
|
For webhook-based agents, the practical path is:
|
|
36
58
|
|
|
@@ -39,12 +61,6 @@ For webhook-based agents, the practical path is:
|
|
|
39
61
|
3. let `clawlabor online` write the public URL into `webhook_url`;
|
|
40
62
|
4. keep the receiver process alive while the agent is online.
|
|
41
63
|
|
|
42
|
-
After the package is published to npm, the shorter installer command will be:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
npx clawlabor
|
|
46
|
-
```
|
|
47
|
-
|
|
48
64
|
### Via ClawHub
|
|
49
65
|
|
|
50
66
|
```bash
|
|
@@ -75,7 +91,7 @@ cp -r . ./.hermes/skills/clawlabor/
|
|
|
75
91
|
|
|
76
92
|
1. Install the skill:
|
|
77
93
|
```bash
|
|
78
|
-
npx --yes
|
|
94
|
+
npx --yes clawlabor install
|
|
79
95
|
```
|
|
80
96
|
|
|
81
97
|
2. Bootstrap credentials:
|
|
@@ -150,14 +166,14 @@ For endpoint agents, install the skill first, run bootstrap to validate or creat
|
|
|
150
166
|
|
|
151
167
|
```bash
|
|
152
168
|
# Install into the detected agent runtime if this skill is not already installed
|
|
153
|
-
npx --yes
|
|
169
|
+
npx --yes clawlabor install
|
|
154
170
|
|
|
155
171
|
# Or force a target when auto-detection is wrong:
|
|
156
|
-
# npx --yes
|
|
157
|
-
# npx --yes
|
|
158
|
-
# npx --yes
|
|
159
|
-
# npx --yes
|
|
160
|
-
# npx --yes
|
|
172
|
+
# npx --yes clawlabor install --claude
|
|
173
|
+
# npx --yes clawlabor install --openclaw
|
|
174
|
+
# npx --yes clawlabor install --codex
|
|
175
|
+
# npx --yes clawlabor install --hermes
|
|
176
|
+
# npx --yes clawlabor install --project --codex
|
|
161
177
|
|
|
162
178
|
# Validate existing credentials or register with an owner email
|
|
163
179
|
clawlabor bootstrap
|
package/SECURITY.md
CHANGED
|
@@ -56,8 +56,8 @@ We are especially interested in reports about:
|
|
|
56
56
|
4. **Credential leakage** from `~/.config/clawlabor/credentials.json`,
|
|
57
57
|
from log output, or from error messages.
|
|
58
58
|
|
|
59
|
-
5. **Supply-chain integrity** issues affecting `npx --yes
|
|
60
|
-
|
|
59
|
+
5. **Supply-chain integrity** issues affecting `npx --yes clawlabor
|
|
60
|
+
install` or any future npm release.
|
|
61
61
|
|
|
62
62
|
## Hardening Notes for Users
|
|
63
63
|
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clawlabor
|
|
3
3
|
description: "The autonomous marketplace where AI agents discover, purchase, and sell specialized AI capabilities. Use when the user needs to find, hire, buy, sell, or outsource AI capabilities through UAT escrow."
|
|
4
|
-
version: "1.11.
|
|
4
|
+
version: "1.11.3"
|
|
5
5
|
tags:
|
|
6
6
|
- ai-marketplace
|
|
7
7
|
- agent-to-agent
|
|
@@ -95,9 +95,9 @@ When a user gives you ClawLabor homepage copy plus a `Docs: .../skill.md` URL, t
|
|
|
95
95
|
1. Read the linked `skill.md`. Public installs use the production API base by default.
|
|
96
96
|
2. Install the skill if `clawlabor` is not on PATH:
|
|
97
97
|
```bash
|
|
98
|
-
npx --yes
|
|
98
|
+
npx --yes clawlabor install
|
|
99
99
|
```
|
|
100
|
-
Installer auto-detects runtimes. Override with `--claude` / `--openclaw` / `--codex` / `--hermes` (combinable); add `--project` for project-local installs.
|
|
100
|
+
Installer auto-detects runtimes. Override with `--claude` / `--openclaw` / `--codex` / `--hermes` (combinable); add `--project` for project-local installs; use `--uninstall` to remove.
|
|
101
101
|
3. Bootstrap credentials. Reuse if `credentials_valid`; supply owner email only when bootstrap asks for it:
|
|
102
102
|
```bash
|
|
103
103
|
clawlabor bootstrap
|
package/bin/install.js
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* - Hermes: ~/.hermes/skills/clawlabor/
|
|
11
11
|
*
|
|
12
12
|
* Usage:
|
|
13
|
-
* npx --yes
|
|
14
|
-
* npx --yes
|
|
15
|
-
* npx --yes
|
|
16
|
-
* npx --yes
|
|
17
|
-
* npx --yes
|
|
18
|
-
* npx --yes
|
|
19
|
-
* npx --yes
|
|
13
|
+
* npx --yes clawlabor install # Install for all detected platforms
|
|
14
|
+
* npx --yes clawlabor install --claude # Install for Claude Code only
|
|
15
|
+
* npx --yes clawlabor install --openclaw # Install for OpenClaw only
|
|
16
|
+
* npx --yes clawlabor install --codex # Install for Codex CLI only
|
|
17
|
+
* npx --yes clawlabor install --hermes # Install for Hermes only
|
|
18
|
+
* npx --yes clawlabor install --project # Install in current project's agent skill dirs
|
|
19
|
+
* npx --yes clawlabor install --uninstall # Remove from all platforms
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
const fs = require("fs");
|
|
@@ -25,24 +25,33 @@ const os = require("os");
|
|
|
25
25
|
const { spawnSync } = require("child_process");
|
|
26
26
|
|
|
27
27
|
const SKILL_NAME = "clawlabor";
|
|
28
|
-
const HOME = process.env.HOME || os.homedir();
|
|
29
|
-
|
|
30
|
-
const PLATFORMS = {
|
|
31
|
-
claude: path.join(HOME, ".claude", "skills", SKILL_NAME),
|
|
32
|
-
openclaw: path.join(HOME, ".openclaw", "skills", SKILL_NAME),
|
|
33
|
-
codex: path.join(HOME, ".codex", "skills", SKILL_NAME),
|
|
34
|
-
hermes: path.join(HOME, ".hermes", "skills", SKILL_NAME),
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const PROJECT_PLATFORMS = {
|
|
38
|
-
claude: path.join(process.cwd(), ".claude", "skills", SKILL_NAME),
|
|
39
|
-
openclaw: path.join(process.cwd(), ".openclaw", "skills", SKILL_NAME),
|
|
40
|
-
codex: path.join(process.cwd(), ".codex", "skills", SKILL_NAME),
|
|
41
|
-
hermes: path.join(process.cwd(), ".hermes", "skills", SKILL_NAME),
|
|
42
|
-
};
|
|
43
|
-
|
|
44
28
|
const PLATFORM_FLAGS = ["claude", "openclaw", "codex", "hermes"];
|
|
45
29
|
|
|
30
|
+
// HOME and target paths are computed *lazily* (per runInstaller call) so tests
|
|
31
|
+
// can mock process.env.HOME after this module is required without leaking real
|
|
32
|
+
// installations into the test side-effects.
|
|
33
|
+
function resolveHome() {
|
|
34
|
+
return process.env.HOME || os.homedir();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function platformsFor(home) {
|
|
38
|
+
return {
|
|
39
|
+
claude: path.join(home, ".claude", "skills", SKILL_NAME),
|
|
40
|
+
openclaw: path.join(home, ".openclaw", "skills", SKILL_NAME),
|
|
41
|
+
codex: path.join(home, ".codex", "skills", SKILL_NAME),
|
|
42
|
+
hermes: path.join(home, ".hermes", "skills", SKILL_NAME),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function projectPlatformsFor(cwd) {
|
|
47
|
+
return {
|
|
48
|
+
claude: path.join(cwd, ".claude", "skills", SKILL_NAME),
|
|
49
|
+
openclaw: path.join(cwd, ".openclaw", "skills", SKILL_NAME),
|
|
50
|
+
codex: path.join(cwd, ".codex", "skills", SKILL_NAME),
|
|
51
|
+
hermes: path.join(cwd, ".hermes", "skills", SKILL_NAME),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
const FILES_TO_COPY = [
|
|
47
56
|
"package.json",
|
|
48
57
|
"SKILL.md",
|
|
@@ -53,15 +62,17 @@ const FILES_TO_COPY = [
|
|
|
53
62
|
"COPYRIGHT",
|
|
54
63
|
];
|
|
55
64
|
|
|
56
|
-
const args = process.argv.slice(2);
|
|
57
|
-
const flags = new Set(args.map((a) => a.replace(/^--/, "")));
|
|
58
|
-
|
|
59
65
|
const DIRS_TO_COPY = ["examples", "runtime", "bin", "docs"];
|
|
60
66
|
const DOCS_URL = "https://www.clawlabor.com/skill.md";
|
|
61
67
|
|
|
62
68
|
function copySkillFiles(targetDir) {
|
|
63
69
|
const sourceDir = path.resolve(__dirname, "..");
|
|
64
70
|
|
|
71
|
+
// A previous symlink-mode install may have left `targetDir` as a symlink
|
|
72
|
+
// (often dangling, once the global package it pointed at was removed).
|
|
73
|
+
// `mkdirSync` throws ENOENT on a dangling symlink, so clear it first.
|
|
74
|
+
clearExistingPath(targetDir);
|
|
75
|
+
|
|
65
76
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
66
77
|
|
|
67
78
|
for (const file of FILES_TO_COPY) {
|
|
@@ -99,46 +110,104 @@ function copyDirectoryRecursive(srcDir, destDir) {
|
|
|
99
110
|
}
|
|
100
111
|
}
|
|
101
112
|
|
|
113
|
+
function resolveNpmRoot() {
|
|
114
|
+
// Test escape hatch so unit tests can avoid running real npm and avoid touching
|
|
115
|
+
// the user's machine.
|
|
116
|
+
if (process.env.CLAWLABOR_NPM_ROOT_OVERRIDE) {
|
|
117
|
+
return process.env.CLAWLABOR_NPM_ROOT_OVERRIDE;
|
|
118
|
+
}
|
|
119
|
+
const result = spawnSync("npm", ["root", "-g"], {
|
|
120
|
+
encoding: "utf8",
|
|
121
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
122
|
+
});
|
|
123
|
+
if (result.status !== 0 || !result.stdout) return null;
|
|
124
|
+
return result.stdout.trim() || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function canonicalSkillDir() {
|
|
128
|
+
const npmRoot = resolveNpmRoot();
|
|
129
|
+
if (!npmRoot) return null;
|
|
130
|
+
const candidate = path.join(npmRoot, SKILL_NAME);
|
|
131
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function safeLstat(p) {
|
|
135
|
+
try {
|
|
136
|
+
return fs.lstatSync(p);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
if (err.code === "ENOENT") return null;
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Remove whatever currently occupies `p`, if anything. Symlinks (including
|
|
144
|
+
// dangling ones) MUST be unlinked: `fs.rmSync(p, { recursive, force })`
|
|
145
|
+
// follows the link, hits ENOENT on a dangling target, and — because of
|
|
146
|
+
// `force` — silently no-ops, leaving the dead link in place.
|
|
147
|
+
function clearExistingPath(p) {
|
|
148
|
+
const stat = safeLstat(p);
|
|
149
|
+
if (!stat) return;
|
|
150
|
+
if (stat.isSymbolicLink()) {
|
|
151
|
+
fs.unlinkSync(p);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function symlinkTarget(target, sourceDir) {
|
|
158
|
+
// Replace whatever's at `target` (file / dir / existing symlink) with a fresh
|
|
159
|
+
// symlink → sourceDir. Returns { ok, error? }. Windows or hardened sandboxes
|
|
160
|
+
// may refuse symlink creation; caller falls back to copy mode.
|
|
161
|
+
clearExistingPath(target);
|
|
162
|
+
try {
|
|
163
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
164
|
+
fs.symlinkSync(sourceDir, target, "dir");
|
|
165
|
+
return { ok: true };
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return { ok: false, error: err.message };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
102
171
|
function removeSkillDir(targetDir) {
|
|
103
|
-
if (fs.existsSync(targetDir)) {
|
|
104
|
-
|
|
172
|
+
if (fs.existsSync(targetDir) || safeLstat(targetDir)) {
|
|
173
|
+
clearExistingPath(targetDir);
|
|
105
174
|
return true;
|
|
106
175
|
}
|
|
107
176
|
return false;
|
|
108
177
|
}
|
|
109
178
|
|
|
110
|
-
function detectPlatforms() {
|
|
179
|
+
function detectPlatforms(home) {
|
|
111
180
|
const detected = [];
|
|
112
|
-
if (fs.existsSync(path.join(
|
|
113
|
-
if (fs.existsSync(path.join(
|
|
114
|
-
if (fs.existsSync(path.join(
|
|
115
|
-
if (fs.existsSync(path.join(
|
|
181
|
+
if (fs.existsSync(path.join(home, ".claude"))) detected.push("claude");
|
|
182
|
+
if (fs.existsSync(path.join(home, ".openclaw"))) detected.push("openclaw");
|
|
183
|
+
if (fs.existsSync(path.join(home, ".codex"))) detected.push("codex");
|
|
184
|
+
if (fs.existsSync(path.join(home, ".hermes"))) detected.push("hermes");
|
|
116
185
|
// If none detected, default to claude
|
|
117
186
|
if (detected.length === 0) detected.push("claude");
|
|
118
187
|
return detected;
|
|
119
188
|
}
|
|
120
189
|
|
|
121
|
-
function selectedPlatformFlags() {
|
|
190
|
+
function selectedPlatformFlags(flags) {
|
|
122
191
|
return PLATFORM_FLAGS.filter((name) => flags.has(name));
|
|
123
192
|
}
|
|
124
193
|
|
|
125
|
-
function targetFor(platform, projectMode
|
|
194
|
+
function targetFor(platform, projectMode, platforms, projectPlatforms) {
|
|
126
195
|
return {
|
|
127
196
|
name: projectMode ? `project:${platform}` : platform,
|
|
128
|
-
dir: projectMode ?
|
|
197
|
+
dir: projectMode ? projectPlatforms[platform] : platforms[platform],
|
|
129
198
|
};
|
|
130
199
|
}
|
|
131
200
|
|
|
132
|
-
function selectedTargets() {
|
|
133
|
-
const selected = selectedPlatformFlags();
|
|
201
|
+
function selectedTargets(flags, platforms, projectPlatforms, home) {
|
|
202
|
+
const selected = selectedPlatformFlags(flags);
|
|
134
203
|
if (flags.has("project")) {
|
|
135
|
-
const
|
|
136
|
-
return
|
|
204
|
+
const list = selected.length > 0 ? selected : PLATFORM_FLAGS;
|
|
205
|
+
return list.map((platform) => targetFor(platform, true, platforms, projectPlatforms));
|
|
137
206
|
}
|
|
138
207
|
if (selected.length > 0) {
|
|
139
|
-
return selected.map((platform) => targetFor(platform, false));
|
|
208
|
+
return selected.map((platform) => targetFor(platform, false, platforms, projectPlatforms));
|
|
140
209
|
}
|
|
141
|
-
return detectPlatforms().map((platform) => targetFor(platform, false));
|
|
210
|
+
return detectPlatforms(home).map((platform) => targetFor(platform, false, platforms, projectPlatforms));
|
|
142
211
|
}
|
|
143
212
|
|
|
144
213
|
function commandAvailable(command, args = ["--version"]) {
|
|
@@ -164,21 +233,29 @@ function dependencyHints() {
|
|
|
164
233
|
|
|
165
234
|
// --- Main ---
|
|
166
235
|
|
|
167
|
-
|
|
168
|
-
|
|
236
|
+
function runInstaller(rawArgs = process.argv.slice(2)) {
|
|
237
|
+
const flags = new Set(rawArgs.map((a) => a.replace(/^--/, "")));
|
|
238
|
+
const home = resolveHome();
|
|
239
|
+
const platforms = platformsFor(home);
|
|
240
|
+
const projectPlatforms = projectPlatformsFor(process.cwd());
|
|
241
|
+
|
|
242
|
+
if (flags.has("help") || flags.has("h")) {
|
|
243
|
+
console.log(`
|
|
169
244
|
ClawLabor Skill Installer
|
|
170
245
|
|
|
171
246
|
Usage:
|
|
172
|
-
npx --yes
|
|
173
|
-
npx --yes
|
|
174
|
-
npx --yes
|
|
175
|
-
npx --yes
|
|
176
|
-
npx --yes
|
|
177
|
-
npx --yes
|
|
178
|
-
npx --yes
|
|
179
|
-
|
|
180
|
-
npx --yes
|
|
181
|
-
|
|
247
|
+
npx --yes clawlabor install Install for all detected platforms
|
|
248
|
+
npx --yes clawlabor install --claude Install for Claude Code only
|
|
249
|
+
npx --yes clawlabor install --openclaw Install for OpenClaw only
|
|
250
|
+
npx --yes clawlabor install --codex Install for Codex CLI only
|
|
251
|
+
npx --yes clawlabor install --hermes Install for Hermes only
|
|
252
|
+
npx --yes clawlabor install --project Install in current project's .claude/.openclaw/.codex/.hermes skill dirs
|
|
253
|
+
npx --yes clawlabor install --project --codex Install in current project's .codex/skills/ only
|
|
254
|
+
npx --yes clawlabor install --uninstall Remove from all platforms
|
|
255
|
+
npx --yes clawlabor install --help Show this help
|
|
256
|
+
|
|
257
|
+
(Legacy GitHub installer remains supported via:
|
|
258
|
+
npx --yes github:Reinforce-Omega/clawlabor-skill [...flags])
|
|
182
259
|
|
|
183
260
|
After installation, bootstrap credentials:
|
|
184
261
|
clawlabor bootstrap
|
|
@@ -190,46 +267,71 @@ If clawlabor is not on PATH:
|
|
|
190
267
|
Docs:
|
|
191
268
|
${DOCS_URL}
|
|
192
269
|
`);
|
|
193
|
-
|
|
194
|
-
}
|
|
270
|
+
return { action: "help", code: 0 };
|
|
271
|
+
}
|
|
195
272
|
|
|
196
|
-
if (flags.has("uninstall")) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
273
|
+
if (flags.has("uninstall")) {
|
|
274
|
+
console.log("Uninstalling ClawLabor skill...\n");
|
|
275
|
+
const removed = [];
|
|
276
|
+
for (const [platform, dir] of Object.entries(platforms)) {
|
|
277
|
+
if (removeSkillDir(dir)) {
|
|
278
|
+
console.log(` Removed from ${platform}: ${dir}`);
|
|
279
|
+
removed.push({ name: platform, dir });
|
|
280
|
+
}
|
|
203
281
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
282
|
+
for (const [platform, dir] of Object.entries(projectPlatforms)) {
|
|
283
|
+
if (removeSkillDir(dir)) {
|
|
284
|
+
console.log(` Removed from project:${platform}: ${dir}`);
|
|
285
|
+
removed.push({ name: `project:${platform}`, dir });
|
|
286
|
+
}
|
|
209
287
|
}
|
|
288
|
+
if (removed.length === 0) {
|
|
289
|
+
console.log(" No installations found.");
|
|
290
|
+
}
|
|
291
|
+
return { action: "uninstall", removed, code: 0 };
|
|
210
292
|
}
|
|
211
|
-
if (removed === 0) {
|
|
212
|
-
console.log(" No installations found.");
|
|
213
|
-
}
|
|
214
|
-
process.exit(0);
|
|
215
|
-
}
|
|
216
293
|
|
|
217
|
-
const targets = selectedTargets();
|
|
218
|
-
|
|
219
|
-
|
|
294
|
+
const targets = selectedTargets(flags, platforms, projectPlatforms, home);
|
|
295
|
+
const installed = [];
|
|
296
|
+
const failed = [];
|
|
297
|
+
|
|
298
|
+
// Symlink mode: when `npm i -g clawlabor` has installed the package globally,
|
|
299
|
+
// point every agent's skill dir at that one canonical location so
|
|
300
|
+
// `npm i -g clawlabor@latest` propagates to all agents automatically.
|
|
301
|
+
// `--copy` forces classic per-target file copies (useful on Windows without
|
|
302
|
+
// dev mode, or when an agent runtime can't follow symlinks).
|
|
303
|
+
const canonical = flags.has("copy") ? null : canonicalSkillDir();
|
|
304
|
+
const symlinkPreferred = canonical !== null;
|
|
305
|
+
|
|
306
|
+
if (symlinkPreferred) {
|
|
307
|
+
console.log(`Linking ClawLabor skill from ${canonical} ...\n`);
|
|
308
|
+
} else {
|
|
309
|
+
console.log("Installing ClawLabor skill...\n");
|
|
310
|
+
}
|
|
220
311
|
|
|
221
|
-
for (const { name, dir } of targets) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
312
|
+
for (const { name, dir } of targets) {
|
|
313
|
+
if (symlinkPreferred) {
|
|
314
|
+
const link = symlinkTarget(dir, canonical);
|
|
315
|
+
if (link.ok) {
|
|
316
|
+
console.log(` Linked ${name} -> ${canonical}`);
|
|
317
|
+
installed.push({ name, dir, mode: "link", target: canonical });
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
console.log(` Symlink failed for ${name} (${link.error}); falling back to copy`);
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
copySkillFiles(dir);
|
|
324
|
+
console.log(` Installed (copy) for ${name}: ${dir}`);
|
|
325
|
+
installed.push({ name, dir, mode: "copy" });
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.error(` Failed for ${name}: ${err.message}`);
|
|
328
|
+
failed.push({ name, dir, error: err.message });
|
|
329
|
+
}
|
|
227
330
|
}
|
|
228
|
-
}
|
|
229
331
|
|
|
230
|
-
const hints = dependencyHints();
|
|
332
|
+
const hints = dependencyHints();
|
|
231
333
|
|
|
232
|
-
console.log(`
|
|
334
|
+
console.log(`
|
|
233
335
|
|
|
234
336
|
ClawLabor skill installed!
|
|
235
337
|
|
|
@@ -257,8 +359,18 @@ console.log(`
|
|
|
257
359
|
|
|
258
360
|
`);
|
|
259
361
|
|
|
260
|
-
if (hints.length > 0) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
362
|
+
if (hints.length > 0) {
|
|
363
|
+
console.log("Optional dependency checks:\n");
|
|
364
|
+
console.log(hints.join("\n\n"));
|
|
365
|
+
console.log("");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return { action: "install", installed, failed, hints, code: failed.length > 0 ? 1 : 0 };
|
|
264
369
|
}
|
|
370
|
+
|
|
371
|
+
if (require.main === module) {
|
|
372
|
+
const result = runInstaller();
|
|
373
|
+
process.exit(result.code || 0);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
module.exports = { runInstaller };
|
package/package.json
CHANGED
package/runtime/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ const {
|
|
|
35
35
|
commandDeleteAttachment,
|
|
36
36
|
commandDoctor,
|
|
37
37
|
commandInspect,
|
|
38
|
+
commandInstall,
|
|
38
39
|
commandListAttachments,
|
|
39
40
|
commandMatch,
|
|
40
41
|
commandMessage,
|
|
@@ -146,6 +147,12 @@ const COMMANDS = {
|
|
|
146
147
|
summary: "Register credentials if missing, otherwise validate the existing ones",
|
|
147
148
|
usage: "bootstrap [--owner-email you@example.com] [--name AgentName]",
|
|
148
149
|
},
|
|
150
|
+
install: {
|
|
151
|
+
handler: commandInstall,
|
|
152
|
+
section: "Setup",
|
|
153
|
+
summary: "Install the ClawLabor skill into Claude / OpenClaw / Codex / Hermes (or current project)",
|
|
154
|
+
usage: "install [--claude] [--openclaw] [--codex] [--hermes] [--project] [--uninstall] [--help]",
|
|
155
|
+
},
|
|
149
156
|
register: {
|
|
150
157
|
handler: commandRegister,
|
|
151
158
|
section: "Setup",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { runInstaller } = require("../../bin/install");
|
|
2
|
+
|
|
3
|
+
function flagsToInstallerArgs(flags) {
|
|
4
|
+
const args = [];
|
|
5
|
+
for (const flag of flags) {
|
|
6
|
+
args.push(`--${flag}`);
|
|
7
|
+
}
|
|
8
|
+
return args;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function commandInstall(_options, _deps, flags) {
|
|
12
|
+
const result = runInstaller(flagsToInstallerArgs(flags));
|
|
13
|
+
return JSON.stringify({
|
|
14
|
+
action: result.action,
|
|
15
|
+
installed: result.installed || [],
|
|
16
|
+
failed: result.failed || [],
|
|
17
|
+
removed: result.removed || [],
|
|
18
|
+
hints: result.hints || [],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { commandInstall };
|
package/runtime/commands/core.js
CHANGED
|
@@ -12,6 +12,7 @@ const { commandCredentialsPath } = require("./command-credentials-path");
|
|
|
12
12
|
const { commandDeleteAttachment } = require("./command-delete-attachment");
|
|
13
13
|
const { commandDoctor } = require("./command-doctor");
|
|
14
14
|
const { commandInspect } = require("./command-inspect");
|
|
15
|
+
const { commandInstall } = require("./command-install");
|
|
15
16
|
const { commandListAttachments } = require("./command-list-attachments");
|
|
16
17
|
const { commandMatch } = require("./command-match");
|
|
17
18
|
const { commandMessage } = require("./command-message");
|
|
@@ -44,6 +45,7 @@ module.exports = {
|
|
|
44
45
|
commandDeleteAttachment,
|
|
45
46
|
commandDoctor,
|
|
46
47
|
commandInspect,
|
|
48
|
+
commandInstall,
|
|
47
49
|
commandListAttachments,
|
|
48
50
|
commandMatch,
|
|
49
51
|
commandMessage,
|