@chorus-protocol/skill 0.4.0 → 0.5.0

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.
Files changed (2) hide show
  1. package/cli.mjs +193 -38
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -3,73 +3,228 @@
3
3
  import { readFileSync, mkdirSync, writeFileSync, existsSync, cpSync } from "fs";
4
4
  import { join, dirname } from "path";
5
5
  import { fileURLToPath } from "url";
6
+ import { homedir } from "os";
6
7
 
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  const pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8"));
9
10
 
10
11
  const LANGS = ["en", "zh-CN"];
11
12
  const DEFAULT_LANG = "en";
13
+ const TARGETS = ["local", "openclaw", "claude-user", "claude-project"];
12
14
 
13
15
  const args = process.argv.slice(2);
14
16
  const command = args[0];
15
17
 
18
+ function getFlag(name) {
19
+ const i = args.indexOf(name);
20
+ return i !== -1 && args[i + 1] ? args[i + 1] : null;
21
+ }
22
+
23
+ function resolveTargetDir(target) {
24
+ if (target === "openclaw") return join(homedir(), ".openclaw", "skills", "chorus");
25
+ if (target === "claude-user") return join(homedir(), ".claude", "skills", "chorus");
26
+ if (target === "claude-project") return join(process.cwd(), ".claude", "skills", "chorus");
27
+ return join(process.cwd(), "chorus");
28
+ }
29
+
30
+ function copySkillFiles(targetDir, lang) {
31
+ const templateDir = join(__dirname, "templates", lang);
32
+ const sharedDir = join(__dirname, "templates", "shared");
33
+ const protocolFile = lang === "en" ? "PROTOCOL.md" : `PROTOCOL.${lang}.md`;
34
+ const skillFile = lang === "en" ? "SKILL.md" : `SKILL.${lang}.md`;
35
+
36
+ mkdirSync(targetDir, { recursive: true });
37
+ mkdirSync(join(targetDir, "examples"), { recursive: true });
38
+
39
+ writeFileSync(join(targetDir, "PROTOCOL.md"), readFileSync(join(templateDir, protocolFile)));
40
+ writeFileSync(join(targetDir, "SKILL.md"), readFileSync(join(templateDir, skillFile)));
41
+ writeFileSync(join(targetDir, "TRANSPORT.md"), readFileSync(join(sharedDir, "TRANSPORT.md")));
42
+ writeFileSync(join(targetDir, "envelope.schema.json"), readFileSync(join(sharedDir, "envelope.schema.json")));
43
+ cpSync(join(sharedDir, "examples"), join(targetDir, "examples"), { recursive: true });
44
+ }
45
+
46
+ function registerOpenClaw() {
47
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
48
+ if (!existsSync(configPath)) {
49
+ console.error(`\nERROR: OpenClaw config not found at ${configPath}`);
50
+ console.error(`\nPossible causes:`);
51
+ console.error(` 1. OpenClaw is not installed — install it first: https://openclaw.com`);
52
+ console.error(` 2. OpenClaw config is in a non-standard location`);
53
+ console.error(`\nTroubleshooting: https://github.com/owensun6/chorus/blob/main/docs/distribution/openclaw-install.md#troubleshooting`);
54
+ return false;
55
+ }
56
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
57
+ if (!config.skills) config.skills = {};
58
+ if (!config.skills.entries) config.skills.entries = {};
59
+ config.skills.entries.chorus = { enabled: true };
60
+ writeFileSync(configPath, JSON.stringify(config, null, 4));
61
+ return true;
62
+ }
63
+
64
+ function unregisterOpenClaw() {
65
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
66
+ if (!existsSync(configPath)) return false;
67
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
68
+ if (config.skills?.entries?.chorus) {
69
+ delete config.skills.entries.chorus;
70
+ writeFileSync(configPath, JSON.stringify(config, null, 4));
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+
16
76
  if (command === "init") {
17
- const langFlag = args.indexOf("--lang");
18
- const lang = langFlag !== -1 && args[langFlag + 1] ? args[langFlag + 1] : DEFAULT_LANG;
77
+ const lang = getFlag("--lang") || DEFAULT_LANG;
78
+ const target = getFlag("--target") || "local";
19
79
 
20
80
  if (!LANGS.includes(lang)) {
21
81
  console.error(`Unsupported language: ${lang}. Available: ${LANGS.join(", ")}`);
22
82
  process.exit(1);
23
83
  }
24
84
 
25
- const targetDir = join(process.cwd(), "chorus");
85
+ if (!TARGETS.includes(target)) {
86
+ console.error(`Unsupported target: ${target}. Available: ${TARGETS.join(", ")}`);
87
+ process.exit(1);
88
+ }
89
+
90
+ const targetDir = resolveTargetDir(target);
26
91
 
27
92
  if (existsSync(targetDir)) {
28
- console.error(`Directory "chorus/" already exists. Remove it first or choose a different location.`);
93
+ console.error(`Directory "${targetDir}" already exists. Remove it first or use "chorus-skill uninstall --target ${target}".`);
29
94
  process.exit(1);
30
95
  }
31
96
 
32
- mkdirSync(targetDir, { recursive: true });
33
- mkdirSync(join(targetDir, "examples"), { recursive: true });
97
+ // For openclaw: verify registration is possible BEFORE writing files
98
+ if (target === "openclaw") {
99
+ const registered = registerOpenClaw();
100
+ if (!registered) {
101
+ console.error(`\nAborting — files were NOT written.`);
102
+ process.exit(1);
103
+ }
104
+ }
34
105
 
35
- const templateDir = join(__dirname, "templates", lang);
36
- const sharedDir = join(__dirname, "templates", "shared");
106
+ copySkillFiles(targetDir, lang);
37
107
 
38
- const protocolFile = lang === "en" ? "PROTOCOL.md" : `PROTOCOL.${lang}.md`;
39
- const skillFile = lang === "en" ? "SKILL.md" : `SKILL.${lang}.md`;
108
+ console.log(`✓ Files installed to ${targetDir} (${lang})`);
40
109
 
41
- writeFileSync(
42
- join(targetDir, "PROTOCOL.md"),
43
- readFileSync(join(templateDir, protocolFile))
44
- );
45
- writeFileSync(
46
- join(targetDir, "SKILL.md"),
47
- readFileSync(join(templateDir, skillFile))
48
- );
49
- writeFileSync(
50
- join(targetDir, "TRANSPORT.md"),
51
- readFileSync(join(sharedDir, "TRANSPORT.md"))
52
- );
53
- writeFileSync(
54
- join(targetDir, "envelope.schema.json"),
55
- readFileSync(join(sharedDir, "envelope.schema.json"))
56
- );
110
+ if (target === "openclaw") {
111
+ console.log(`✓ Registered in ~/.openclaw/openclaw.json`);
112
+ console.log(`\nNext: verify installation`);
113
+ console.log(` npx @chorus-protocol/skill verify --target openclaw`);
114
+ } else {
115
+ console.log(`\nFiles created:`);
116
+ console.log(` PROTOCOL.md — Protocol specification`);
117
+ console.log(` SKILL.md — Agent learning document`);
118
+ console.log(` TRANSPORT.md — Default transport profile (optional)`);
119
+ console.log(` envelope.schema.json`);
120
+ console.log(` examples/`);
121
+ console.log(`\nGive your agent SKILL.md to teach it the Chorus protocol.`);
122
+ }
57
123
 
58
- cpSync(join(sharedDir, "examples"), join(targetDir, "examples"), { recursive: true });
124
+ } else if (command === "uninstall") {
125
+ const target = getFlag("--target");
126
+
127
+ if (!target || !TARGETS.includes(target) || target === "local") {
128
+ console.error(`Usage: chorus-skill uninstall --target openclaw|claude-user|claude-project`);
129
+ process.exit(1);
130
+ }
131
+
132
+ const targetDir = resolveTargetDir(target);
133
+
134
+ if (!existsSync(targetDir)) {
135
+ console.error(`Not installed at ${targetDir}`);
136
+ process.exit(1);
137
+ }
138
+
139
+ const { rmSync } = await import("fs");
140
+ rmSync(targetDir, { recursive: true });
141
+ console.log(`Removed ${targetDir}`);
142
+
143
+ if (target === "openclaw") {
144
+ const removed = unregisterOpenClaw();
145
+ if (removed) {
146
+ console.log(` Unregistered from ~/.openclaw/openclaw.json`);
147
+ }
148
+ }
149
+
150
+ } else if (command === "verify") {
151
+ const envelopeJson = getFlag("--envelope");
152
+
153
+ if (envelopeJson) {
154
+ const REQUIRED_FIELDS = ["chorus_version", "sender_id", "original_text", "sender_culture"];
155
+ try {
156
+ const envelope = JSON.parse(envelopeJson);
157
+ const missing = REQUIRED_FIELDS.filter((k) => !envelope[k]);
158
+ if (missing.length === 0) {
159
+ console.log(`✓ Valid Chorus envelope (${REQUIRED_FIELDS.length}/${REQUIRED_FIELDS.length} required fields present)`);
160
+ } else {
161
+ console.error(`✗ Invalid envelope — missing fields: ${missing.join(", ")}`);
162
+ process.exit(1);
163
+ }
164
+ } catch {
165
+ console.error(`✗ Invalid JSON`);
166
+ process.exit(1);
167
+ }
168
+ } else {
169
+ const target = getFlag("--target") || "openclaw";
170
+
171
+ if (!TARGETS.includes(target)) {
172
+ console.error(`✗ Unknown target: ${target}. Available: ${TARGETS.join(", ")}`);
173
+ process.exit(1);
174
+ }
59
175
 
60
- console.log(`Chorus Skill package initialized in ./chorus/ (${lang})`);
61
- console.log(`\nFiles created:`);
62
- console.log(` chorus/PROTOCOL.md — Protocol specification`);
63
- console.log(` chorus/SKILL.md Agent learning document`);
64
- console.log(` chorus/TRANSPORT.md — Default transport profile (optional)`);
65
- console.log(` chorus/envelope.schema.json`);
66
- console.log(` chorus/examples/`);
67
- console.log(`\nGive your agent chorus/SKILL.md to teach it the Chorus protocol.`);
176
+ const targetDir = resolveTargetDir(target);
177
+ const skillPath = join(targetDir, "SKILL.md");
178
+
179
+ // Check 1: SKILL.md exists and is non-empty
180
+ if (!existsSync(skillPath)) {
181
+ console.error(`✗ SKILL.md not found at ${skillPath}`);
182
+ console.error(`\n Run: npx @chorus-protocol/skill init --target ${target}`);
183
+ process.exit(1);
184
+ }
185
+ const stat = (await import("fs")).statSync(skillPath);
186
+ if (stat.size === 0) {
187
+ console.error(`✗ SKILL.md exists but is empty at ${skillPath}`);
188
+ process.exit(1);
189
+ }
190
+ console.log(`✓ SKILL.md exists (${(stat.size / 1024).toFixed(1)} KB)`);
191
+
192
+ // Check 2: openclaw.json registration (only for openclaw target)
193
+ if (target === "openclaw") {
194
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
195
+ if (!existsSync(configPath)) {
196
+ console.error(`✗ openclaw.json not found at ${configPath}`);
197
+ process.exit(1);
198
+ }
199
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
200
+ if (config.skills?.entries?.chorus?.enabled === true) {
201
+ console.log(`✓ openclaw.json: chorus registered and enabled`);
202
+ } else {
203
+ console.error(`✗ openclaw.json: chorus not registered or not enabled`);
204
+ process.exit(1);
205
+ }
206
+ }
207
+
208
+ console.log(`\n✓ Installation verified.`);
209
+ }
68
210
  } else {
69
211
  console.log(`@chorus-protocol/skill v${pkg.version}`);
70
212
  console.log(`\nUsage:`);
71
- console.log(` chorus-skill init [--lang en|zh-CN] Initialize Chorus Skill package`);
213
+ console.log(` chorus-skill init --target openclaw [--lang en|zh-CN]`);
214
+ console.log(` chorus-skill verify --target openclaw`);
215
+ console.log(` chorus-skill verify --envelope '<json>'`);
216
+ console.log(` chorus-skill uninstall --target openclaw`);
72
217
  console.log(`\nExamples:`);
73
- console.log(` npx @chorus-protocol/skill init`);
74
- console.log(` npx @chorus-protocol/skill init --lang zh-CN`);
218
+ console.log(` npx @chorus-protocol/skill init --target openclaw # Install`);
219
+ console.log(` npx @chorus-protocol/skill verify --target openclaw # Verify installation`);
220
+ console.log(` npx @chorus-protocol/skill init --target openclaw --lang zh-CN`);
221
+
222
+ if (args.includes("--help-all")) {
223
+ console.log(`\nAlternative targets (advanced):`);
224
+ console.log(` --target local Copy to ./chorus (manual integration)`);
225
+ console.log(` --target claude-user Install to ~/.claude/skills/chorus`);
226
+ console.log(` --target claude-project Install to ./.claude/skills/chorus`);
227
+ } else {
228
+ console.log(`\nRun with --help-all to see alternative install targets.`);
229
+ }
75
230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chorus-protocol/skill",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Chorus Protocol — Link agents across platforms. Install the Chorus Skill package for your agent.",
5
5
  "bin": {
6
6
  "chorus-skill": "./cli.mjs"