clay-server 2.32.0-beta.7 → 2.32.0-beta.8

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.
@@ -3,8 +3,59 @@
3
3
  // Implements the YOKE interface using codex app-server protocol.
4
4
  // Bidirectional JSON-RPC over stdin/stdout enables interactive approval flows.
5
5
 
6
+ var path = require("path");
7
+ var fs = require("fs");
6
8
  var { CodexAppServer } = require("../codex-app-server");
7
9
 
10
+ // --- Claude skill discovery ---
11
+ // Finds Claude skills in ~/.claude/skills/ and <cwd>/.claude/skills/
12
+ // so Codex can recognize $<skill-name> in user input.
13
+ function discoverClaudeSkills(cwd) {
14
+ var skills = {};
15
+ var REAL_HOME;
16
+ try { REAL_HOME = require("../../config").REAL_HOME; } catch (e) { REAL_HOME = require("os").homedir(); }
17
+ var dirs = [
18
+ path.join(REAL_HOME, ".claude", "skills"),
19
+ path.join(cwd || "", ".claude", "skills"),
20
+ ];
21
+ for (var d = 0; d < dirs.length; d++) {
22
+ var base = dirs[d];
23
+ if (!base) continue;
24
+ var entries;
25
+ try { entries = fs.readdirSync(base, { withFileTypes: true }); } catch (e) { continue; }
26
+ for (var i = 0; i < entries.length; i++) {
27
+ var entry = entries[i];
28
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
29
+ var skillMd = path.join(base, entry.name, "SKILL.md");
30
+ try {
31
+ fs.accessSync(skillMd, fs.constants.R_OK);
32
+ // project skills override global skills
33
+ skills[entry.name] = skillMd;
34
+ } catch (e) {}
35
+ }
36
+ }
37
+ return skills;
38
+ }
39
+
40
+ // Parse user text for $<skill-name> references.
41
+ // Returns { text, skills: [{ name, path }] }
42
+ function parseSkillRefs(text, availableSkills) {
43
+ if (typeof text !== "string") return { text: text, skills: [] };
44
+ var skills = [];
45
+ var seen = {};
46
+ var re = /\$([a-zA-Z0-9_-]+)/g;
47
+ var match;
48
+ while ((match = re.exec(text)) !== null) {
49
+ var name = match[1];
50
+ if (seen[name]) continue;
51
+ if (availableSkills[name]) {
52
+ seen[name] = true;
53
+ skills.push({ name: name, path: availableSkills[name] });
54
+ }
55
+ }
56
+ return { text: text, skills: skills };
57
+ }
58
+
8
59
  // --- Event flattening ---
9
60
  // Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
10
61
  //
@@ -463,13 +514,15 @@ function createCodexQueryHandle(appServer, queryOpts) {
463
514
  var cmdParams = msg.params || {};
464
515
  if (canUseTool) {
465
516
  canUseTool("Bash", { command: cmdParams.command }, {}).then(function(decision) {
466
- appServer.respond(msg.id, isApproved(decision) ? "accept" : "decline");
517
+ var approved = isApproved(decision);
518
+ // Response must be wrapped in { decision: ... } object per app-server protocol
519
+ appServer.respond(msg.id, { decision: approved ? "accept" : "decline" });
467
520
  }).catch(function(err) {
468
521
  console.error("[yoke/codex] canUseTool error:", err.message);
469
- appServer.respond(msg.id, "decline");
522
+ appServer.respond(msg.id, { decision: "decline" });
470
523
  });
471
524
  } else {
472
- appServer.respond(msg.id, "accept");
525
+ appServer.respond(msg.id, { decision: "accept" });
473
526
  }
474
527
  return;
475
528
  }
@@ -480,13 +533,13 @@ function createCodexQueryHandle(appServer, queryOpts) {
480
533
  if (canUseTool) {
481
534
  var changeInfo = (fcParams.changes || []).map(function(c) { return c.kind + " " + c.path; }).join(", ");
482
535
  canUseTool("Edit", { changes: changeInfo, path: fcParams.path }, {}).then(function(decision) {
483
- appServer.respond(msg.id, isApproved(decision) ? "accept" : "decline");
536
+ appServer.respond(msg.id, { decision: isApproved(decision) ? "accept" : "decline" });
484
537
  }).catch(function(err) {
485
538
  console.error("[yoke/codex] canUseTool error:", err.message);
486
- appServer.respond(msg.id, "decline");
539
+ appServer.respond(msg.id, { decision: "decline" });
487
540
  });
488
541
  } else {
489
- appServer.respond(msg.id, "accept");
542
+ appServer.respond(msg.id, { decision: "accept" });
490
543
  }
491
544
  return;
492
545
  }
@@ -584,6 +637,26 @@ function createCodexQueryHandle(appServer, queryOpts) {
584
637
  input = currentMessage;
585
638
  }
586
639
 
640
+ // Detect $<skill-name> references (Claude skills) and inject skill input items
641
+ var availableSkills = discoverClaudeSkills(queryOpts.cwd);
642
+ var skillItemsToInject = [];
643
+ var injected = {};
644
+ for (var ii = 0; ii < input.length; ii++) {
645
+ if (input[ii].type === "text" && input[ii].text) {
646
+ var parsed = parseSkillRefs(input[ii].text, availableSkills);
647
+ for (var si = 0; si < parsed.skills.length; si++) {
648
+ if (!injected[parsed.skills[si].name]) {
649
+ injected[parsed.skills[si].name] = true;
650
+ skillItemsToInject.push({ type: "skill", name: parsed.skills[si].name, path: parsed.skills[si].path });
651
+ }
652
+ }
653
+ }
654
+ }
655
+ if (skillItemsToInject.length > 0) {
656
+ console.log("[yoke/codex] injecting Claude skills:", skillItemsToInject.map(function(s) { return s.name; }).join(", "));
657
+ input = input.concat(skillItemsToInject);
658
+ }
659
+
587
660
  await appServer.send("turn/start", {
588
661
  threadId: state.threadId,
589
662
  input: input,
@@ -850,13 +923,51 @@ function createCodexAdapter(opts) {
850
923
  "gpt-5.2",
851
924
  ];
852
925
 
926
+ // Discover skills: built-in Codex skills + Claude skills
927
+ var skillNames = [];
928
+ try {
929
+ var REAL_HOME;
930
+ try { REAL_HOME = require("../../config").REAL_HOME; } catch (e) { REAL_HOME = require("os").homedir(); }
931
+ var claudeSkillsDir = require("path").join(REAL_HOME, ".claude", "skills");
932
+ var extraRoots = _cwd ? [{ cwd: _cwd, extraUserRoots: [claudeSkillsDir] }] : [];
933
+ var skillsResult = await _appServer.send("skills/list", {
934
+ cwds: _cwd ? [_cwd] : [],
935
+ forceReload: true,
936
+ perCwdExtraUserRoots: extraRoots,
937
+ }, 10000).catch(function(e) {
938
+ console.error("[codex] skills/list failed:", e.message);
939
+ return null;
940
+ });
941
+ // Response shape: { data: [{ cwd, skills: [{ name, ... }] }] }
942
+ if (skillsResult && skillsResult.data) {
943
+ for (var di = 0; di < skillsResult.data.length; di++) {
944
+ var entry = skillsResult.data[di];
945
+ if (!entry.skills) continue;
946
+ for (var sk = 0; sk < entry.skills.length; sk++) {
947
+ if (entry.skills[sk].name && skillNames.indexOf(entry.skills[sk].name) === -1) {
948
+ skillNames.push(entry.skills[sk].name);
949
+ }
950
+ }
951
+ }
952
+ }
953
+ // Also discover Claude skills directly as fallback
954
+ var claudeSkills = discoverClaudeSkills(_cwd);
955
+ var claudeSkillNames = Object.keys(claudeSkills);
956
+ for (var csn = 0; csn < claudeSkillNames.length; csn++) {
957
+ if (skillNames.indexOf(claudeSkillNames[csn]) === -1) skillNames.push(claudeSkillNames[csn]);
958
+ }
959
+ console.log("[codex] Discovered skills:", skillNames.length, "(" + skillNames.slice(0, 5).join(", ") + (skillNames.length > 5 ? "..." : "") + ")");
960
+ } catch (e) {
961
+ console.error("[codex] Failed to discover skills:", e.message);
962
+ }
963
+
853
964
  _initPromise = null;
854
965
 
855
966
  return {
856
967
  models: _cachedModels,
857
968
  defaultModel: "gpt-5.4",
858
- skills: [],
859
- slashCommands: [],
969
+ skills: skillNames,
970
+ slashCommands: skillNames,
860
971
  fastModeState: null,
861
972
  capabilities: {
862
973
  thinking: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.32.0-beta.7",
3
+ "version": "2.32.0-beta.8",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",