@reddb-io/red-skills 3.18.12 → 3.19.2
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/.agents/plugins/marketplace.json +63 -0
- package/.claude-plugin/marketplace.json +32 -0
- package/.gemini-plugin/marketplace.json +63 -0
- package/README.md +18 -18
- package/dist/code-nav.bundle.min.mjs +1 -1
- package/dist/opencode-host.bundle.min.mjs +165 -0
- package/dist/redskilled-mcp.bundle.min.mjs +372 -912
- package/dist/redskilled.bundle.min.mjs +301 -280
- package/dist/release.bundle.min.mjs +259 -0
- package/dist/rsp-core.bundle.min.mjs +199 -199
- package/dist/rsp.bundle.min.mjs +1 -1
- package/package.json +15 -3
- package/scripts/build-gemini-extension.mjs +180 -0
- package/scripts/generate-codex-manifests.mjs +291 -0
- package/scripts/generate-gemini-manifests.mjs +259 -0
- package/scripts/generate-pi-manifests.mjs +133 -0
- package/scripts/install-hermes-skills.mjs +194 -0
- package/scripts/install-opencode.sh +556 -0
- package/scripts/install-pi.sh +303 -0
- package/scripts/lib/manifest-core.mjs +106 -0
- package/scripts/validate-gemini-extension.mjs +194 -0
- package/dist/brain.bundle.min.mjs +0 -331
- package/dist/dev.bundle.min.mjs +0 -1372
- package/dist/memory.bundle.min.mjs +0 -7030
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
import{mkdirSync as ve,readFileSync as Gt,writeFileSync as qt}from"node:fs";import{dirname as Ht,resolve as D}from"node:path";function M(e){let t={app:e,version:Oe($("__RED_BUILD_VERSION__",()=>"3.19.2")??"0.0.0-dev"),gitSha:$("__RED_BUILD_GIT_SHA__",()=>"81d715b56dbed051a6edfc3eb7f3f659acebe871")??"unknown",buildTime:$("__RED_BUILD_TIME__",()=>"2026-08-18T01:06:20-03:00")??"unknown",bundleAsset:$("__RED_BUNDLE_ASSET__",()=>"opencode-host.bundle.min.mjs")??"unknown"},o=$("__REDDB_SDK_VERSION__",()=>""),n=$("__REDDB_BINARY_TAG__",()=>"");return o&&(t.reddbSdkVersion=o),n&&(t.reddbBinaryTag=n),t}function Oe(e){return e.startsWith("v")?e.slice(1):e}function L(e){return`${e.app} ${e.version} ${e.gitSha}`}function $(e,t){try{let o=t();return typeof o=="string"&&o.length>0?o:void 0}catch{return process.env[e.replace(/^__|__$/g,"")]}}var X={"=":"data",":=":{to:"data",typed:!0},":":"meta"},Se=/^[a-z][a-z0-9+.-]*:\/\//i;function Pe(e,t){return typeof t=="string"?{separator:e,to:t,typed:!1,prefix:!1}:{separator:e,to:t.to,typed:t.typed??!1,prefix:t.prefix??!1}}function Ee(e=X){return Object.entries(e).map(([t,o])=>Pe(t,o)).sort((t,o)=>o.separator.length-t.separator.length)}function Re(e){return/^-\d+(\.\d+)?$/.test(e)}function O(e){return!(!e||e.startsWith("-")&&!Re(e))}function R(e){if(e==="true")return!0;if(e==="false")return!1;if(e==="null")return null;let t=Number(e);return!Number.isNaN(t)&&Number.isFinite(t)&&e.trim()!==""?t:e}function Q(e){if(e==="true")return!0;if(e==="false")return!1;if(e==="null")return null;if(e.startsWith("[")&&e.endsWith("]")||e.startsWith("{")&&e.endsWith("}"))try{return JSON.parse(e)}catch{}let t=Number(e);return!Number.isNaN(t)&&Number.isFinite(t)&&e.trim()!==""?t:e.includes(",")?e.split(",").map(o=>Q(o.trim())):e}function x(e,t){switch(t){case"boolean":return Ce(e);case"number":return Ae(e);case"array":return e.split(",").map(o=>R(o.trim()));case"string":default:return e}}function Ce(e){let t=e.toLowerCase().trim();return t==="true"||t==="yes"||t==="1"||t==="on"}function Ae(e){if(e.trim()==="")throw new TypeError(`Cannot convert "${e}" to number`);let t=Number(e);if(Number.isNaN(t))throw new TypeError(`Cannot convert "${e}" to number`);return t}function w(e){return e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function Ie(e,t={}){return e.map(o=>{let n=t[o.type];return n?n(o.value):o.value}).join("")}function m(e,t){return{type:e,value:t}}function je(e){let t=[],o=Fe(e);if(t.push(m("section-header","Usage:"),m("text"," "),m("usage-text",o),m("newline",`
|
|
3
|
+
`),m("newline",`
|
|
4
|
+
`)),e.positional&&e.positional.length>0){t.push(m("section-header","Arguments:"),m("newline",`
|
|
5
|
+
`));for(let n of e.positional)if(n.description){let s=n.variadic?`${n.name}...`:n.name;t.push(m("indent"," "),m("positional-name",`<${s}>`)),n.required||t.push(m("positional-optional"," (optional)")),t.push(m("newline",`
|
|
6
|
+
`),m("indent"," "),m("option-description",n.description),m("newline",`
|
|
7
|
+
`))}t.push(m("newline",`
|
|
8
|
+
`))}if(e.options&&Object.keys(e.options).length>0){t.push(m("section-header","Options:"),m("newline",`
|
|
9
|
+
`));let n=Ne(e.options);t.push(...n)}return t}function Te(e,t){let o=je(e);return Ie(o,t)}function Fe(e){let t=["[options]"];if(e.positional)for(let o of e.positional)o.variadic?t.push(o.required?`<${o.name}...>`:`[${o.name}...]`):t.push(o.required?`<${o.name}>`:`[${o.name}]`);return t.join(" ")}function Ne(e,t=!1){let o=[],n=[],s=t?"global-option-flag":"option-flag",i=t?"global-option-type":"option-type",r=t?"global-option-default":"option-default",a=t?"global-option-description":"option-description";for(let[l,u]of Object.entries(e)){if(u.hidden)continue;let f=[],p=h=>{f.includes(h)||f.push(h)};u.short&&p(`-${u.short}`),p(`--${l}`);for(let h of u.aliases??[])h.length===1?p(`-${h}`):p(`--${h}`);let d=f.join(", "),y=u.type&&u.type!=="boolean"?`<${u.type}>`:void 0;n.push({flags:d,type:y,desc:u.description??"",default:u.default!==void 0?String(u.default):void 0})}let c=Math.max(...n.map(l=>l.flags.length+(l.type?l.type.length+1:0)),20);for(let l of n){let u=l.flags.length+(l.type?l.type.length+1:0),f=" ".repeat(c-u+2);o.push(m("indent"," ")),o.push(m(s,l.flags)),l.type&&(o.push(m("text"," ")),o.push(m(i,l.type))),o.push(m("text",f)),o.push(m(a,l.desc)),l.default!==void 0&&(o.push(m("text"," ")),o.push(m(r,`(default: ${l.default})`))),o.push(m("newline",`
|
|
10
|
+
`))}return o}function ee(e){let t=Ge(e.options??{},e.autoShort??!1),o=Ee(e.separators??X);return{schema:e,parse(n){return De(n,e,t,o)},help(){return Te(e,e.formatter)}}}function De(e,t,o,n){let s={positional:{},options:{},errors:[],rest:[]},i=new Set(n.map(u=>u.to));for(let u of i)s[u]={};let r=0,a=t.positional??[],c=0,l=!1;for(;c<e.length;){let u=e[c];if(t.stopEarly&&r>0){s.rest=e.slice(c);break}if(u==="--"&&!l){l=!0,c++;continue}if(l){if(r<a.length){let p=a[r];if(p.variadic){let d=[];for(;c<e.length;){let y=e[c];d.push(p.type?x(y,p.type):y),c++}s.positional[p.name]=d,r++}else{let d=p.type?x(u,p.type):u;s.positional[p.name]=d,r++,c++}}else t.allowUnknown!==!1&&(s.rest=s.rest??[],s.rest.push(u)),c++;continue}if(u.startsWith("--")){c=Ve(u,c,e,s,o,t);continue}if(u.startsWith("-")&&u.length>1&&!ze(u)){c=We(u,c,e,s,o,t);continue}let f=Ue(u,n);if(f){let{key:p,value:d,config:y}=f,h=s,_=h[y.to]??(h[y.to]={});_[p]=y.typed?Q(d):w(d),c++;continue}if(r<a.length){let p=a[r];if(p.variadic){let y=[];for(;c<e.length&&!e[c].startsWith("-");){let h=e[c];y.push(p.type?x(h,p.type):h),c++}s.positional[p.name]=y,r++;continue}let d=p.type?x(u,p.type):u;s.positional[p.name]=d,r++}else t.allowUnknown!==!1?s.rest.push(u):s.errors.push(`Unexpected positional argument: ${u}`);c++}return He(s,t,o),Be(s,t,a),Ke(s,t,o),s}function Ue(e,t){if(Se.test(e))return null;for(let o of t){if(o.prefix&&e.startsWith(o.separator)){let s=e.slice(o.separator.length),i=s.indexOf(":");if(i>0){let r=s.slice(0,i),a=s.slice(i+1);if(Me(r))return{key:r,value:a,config:o}}continue}let n=e.indexOf(o.separator);if(n>0){let s=e.slice(0,n),i=e.slice(n+o.separator.length);if(Le(s,i,o))return{key:s,value:i,config:o}}}return null}function Me(e){return e?/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(e):!1}function Le(e,t,o){return e?o.separator===":"?e.includes("/")||e.includes(".")||/^\d+$/.test(t)?!1:/^[A-Z][a-zA-Z0-9-]*$/.test(e):!((o.separator==="="||o.separator===":=")&&e.includes("/")):!1}function Ve(e,t,o,n,s,i){let r=e.slice(2);if(r.startsWith("no-")){let c=r.slice(3),l=s.get(c);if(l?.negatable!==!1&&l?.type==="boolean")return n.options[l.name]=!1,t+1}if(r.includes("=")){let c=r.indexOf("="),l=r.slice(0,c),u=r.slice(c+1);return C(l,u,t+1,n,s,i)}let a=s.get(r);if(a){if(a.type!=="boolean"){let c=o[t+1];return O(c)?C(a.name,c,t+2,n,s,i):(n.errors.push(`Option --${r} requires a value`),t+1)}return n.options[a.name]=!0,t+1}return i.strict?n.errors.push(`Unknown option: --${r}`):i.allowUnknown!==!1&&(n.options[r]=!0),t+1}function We(e,t,o,n,s,i){let r=e.slice(1);if(r.includes("=")){let a=r.indexOf("="),c=r.slice(0,a),l=r.slice(a+1),u=V(c,s);return u?C(u.name,l,t+1,n,s,i):(i.strict?n.errors.push(`Unknown option: -${c}`):i.allowUnknown!==!1&&(n.options[c]=R(w(l))),t+1)}if(r.length===1){let a=V(r,s);if(a){if(a.type!=="boolean"){let c=o[t+1];return O(c)?C(a.name,c,t+2,n,s,i):(n.errors.push(`Option -${r} requires a value`),t+1)}return n.options[a.name]=!0,t+1}if(i.strict)n.errors.push(`Unknown option: -${r}`);else if(i.allowUnknown!==!1){let c=o[t+1];if(O(c))return n.options[r]=R(w(c)),t+2;n.options[r]=!0}return t+1}for(let a of r){let c=V(a,s);c?c.type==="boolean"?n.options[c.name]=!0:n.errors.push(`Option -${a} requires a value`):i.strict?n.errors.push(`Unknown option: -${a}`):i.allowUnknown!==!1&&(n.options[a]=!0)}return t+1}function C(e,t,o,n,s,i){let r=s.get(e);if(r){if(r.choices){let c=r.type?x(w(t),r.type):w(t);if(!r.choices.includes(c))return n.errors.push(`Invalid value for '${e}': '${t}'. Allowed: ${r.choices.join(", ")}`),o}let a=r.type?x(w(t),r.type):w(t);if(r.type==="array"){let c=n.options[e];Array.isArray(c)?c.push(...a):n.options[e]=a}else n.options[e]=a}else i.strict?n.errors.push(`Unknown option: --${e}`):i.allowUnknown!==!1&&(n.options[e]=R(w(t)));return o}function Ge(e,t=!1){let o=new Map,n=Object.entries(e).sort(([i],[r])=>i.localeCompare(r)),s=new Set;for(let[,i]of n)i.short&&s.add(i.short);for(let[i,r]of n){let a=r.short??"";t&&!a&&(a=qe(i,s),a&&s.add(a));let c={name:i,short:a,aliases:r.aliases??[],type:r.type??"string",description:r.description??"",required:r.required??!1,hidden:r.hidden??!1,negatable:r.negatable??!0,default:r.default,choices:r.choices,env:r.env,validate:r.validate};o.set(i,c);for(let l of c.aliases)o.set(l,c)}return o}function qe(e,t){for(let o of e){if(!/[a-zA-Z]/.test(o))continue;let n=o.toLowerCase();if(!t.has(n))return n;let s=o.toUpperCase();if(!t.has(s))return s}return""}function V(e,t){for(let o of t.values())if(o.short===e||o.aliases.includes(e))return o}function He(e,t,o){for(let n of o.values())if(e.options[n.name]===void 0)if(n.env&&process.env[n.env]!==void 0){let s=process.env[n.env];e.options[n.name]=n.type?x(s,n.type):s}else n.default!==void 0&&(e.options[n.name]=n.default);for(let n of t.positional??[])e.positional[n.name]===void 0&&n.default!==void 0&&(e.positional[n.name]=n.default)}function Be(e,t,o){if(t.options)for(let[n,s]of Object.entries(t.options))s.required&&e.options[n]===void 0&&e.errors.push(`Missing required option: --${n}`);for(let n of o)n.required&&e.positional[n.name]===void 0&&e.errors.push(`Missing required argument: <${n.name}>`)}function Ke(e,t,o){for(let n of o.values()){let s=e.options[n.name];if(n.validate&&s!==void 0){let i=n.validate(s);i!==!0&&e.errors.push(i)}}for(let n of t.positional??[]){let s=e.positional[n.name];if(n.validate&&s!==void 0){let i=n.validate(s);i!==!0&&e.errors.push(i)}}}function ze(e){return/^-\d+(\.\d+)?$/.test(e)}var W=class extends Error{flag;known;constructor(t,o){super(`unknown flag '${t}'; expected one of: ${o.join(", ")}`),this.name="UnknownFlagError",this.flag=t,this.known=o}};function Je(e){let t={};for(let[o,n]of Object.entries(e))t[o]={type:n.kind==="boolean"?"boolean":"string",...o.length===1?{short:o}:{},...n.aliases===void 0?{}:{aliases:n.aliases}};return t}function Ze(e,t){let o=new Map;for(let[s,i]of Object.entries(t))if(!(i.kind!=="value"||i.type!=="array")){o.set(s,s);for(let r of i.aliases??[])o.set(r,s)}let n=new Map;for(let s=0;s<e.length;s+=1){let i=e[s];if(i==="--")break;let r=i.match(/^--([^=]+)(?:=(.*))?$/)??i.match(/^-([^=])(?:=(.*))?$/);if(r===null)continue;let a=o.get(r[1]);if(a===void 0)continue;let c=r[2];if(c===void 0&&O(e[s+1])&&(c=e[s+1],s+=1),c===void 0)continue;let l=n.get(a)??[];l.push(c),n.set(a,l)}return n}function te(e,t,o={}){let s=ee({options:Je(t),separators:{},allowUnknown:!0}).parse([...e]),i={},r=Ze(e,t),a=s.errors.find(c=>/^Option --?\S+ requires a value$/.test(c));if(a!==void 0)throw new Error(a.replace(/^Option /,""));if(s.errors.length>0)throw new Error(s.errors[0]);if(o.unknownFlags==="error"){let c=new Set(Object.keys(t));for(let l of Object.keys(s.options))if(!c.has(l))throw new W(l.length===1?`-${l}`:`--${l}`,Object.keys(t).map(u=>u.length===1?`-${u}`:`--${u}`))}for(let[c,l]of Object.entries(t)){let u=s.options[c];u!==void 0&&(l.kind==="boolean"?i[c]=u:l.type==="array"?i[c]=(r.get(c)??[]).map(f=>l.coerce(f)):i[c]=l.coerce(String(u)))}return{values:i,positionals:s.rest}}var S=class extends Error{token;known;constructor(t,o){super(`unknown command '${t}'; expected one of: ${o.join(", ")}`),this.name="UnknownCommandError",this.token=t,this.known=o}};function ne(e,t){let[o,...n]=e;if(o!==void 0){for(let[i,r]of Object.entries(t.commands))if(o===i||(r.aliases??[]).includes(o))return{command:i,args:n};if(t.errorOnUnknownCommand&&!o.startsWith("-"))throw new S(o,Object.keys(t.commands))}let s=t.keepArgvOnDefault??!0;return{command:t.default,args:s?[...e]:n}}var Ye={config:{kind:"value",aliases:["c"],coerce:e=>e},out:{kind:"value",aliases:["o"],coerce:e=>e},"out-dir":{kind:"value",aliases:["d"],coerce:e=>e},"plugins-root":{kind:"value",aliases:["p"],coerce:e=>e},plugin:{kind:"value",type:"array",coerce:e=>e},"slice-2":{kind:"boolean",aliases:["with-slice-2"]},copy:{kind:"boolean"},print:{kind:"boolean"},help:{kind:"boolean",aliases:["h"]},version:{kind:"boolean",aliases:["v"]},json:{kind:"boolean"}},v=class extends Error{constructor(t){super(t),this.name="OpencodeHostUsageError"}};function oe(e){return e[0]==="--version"||e[0]==="-v"}function se(e){let t;try{t=ne(e,{commands:{generate:{}},default:"generate",errorOnUnknownCommand:!0})}catch(i){throw i instanceof S?new v(i.message):i}let o;try{o=te(t.args,Ye,{unknownFlags:"error"})}catch(i){throw new v(i.message)}let n=o.positionals[0];if(n!==void 0)throw new v(`unexpected argument '${n}'`);let{values:s}=o;return{command:t.command,configPath:s.config??"./.red/config.yaml",outPath:s.out??"./opencode.json",outDir:s["out-dir"]??"./dist/opencode",pluginsRoot:s["plugins-root"]??"./plugins",showHelp:s.help??!1,showVersion:s.version??!1,versionJson:s.json??!1,printJson:s.print??!1,copySkills:s.copy??!1,pluginFilter:s.plugin??null,slice2:s["slice-2"]??!0}}function re(){return`opencode-host generate \u2014 emit the opencode-host runtime tree
|
|
11
|
+
|
|
12
|
+
Slice 1: opencode.json (provider block) at <out>
|
|
13
|
+
Slice 2: dist tree at <out-dir>/<plugin>/{opencode.json, .opencode/...}
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
opencode-host [generate] [--config <path>] [--out <path>] [--out-dir <path>] [--plugins-root <path>] [--plugin <name>] [--copy] [--print] [--no-slice-2]
|
|
17
|
+
opencode-host --version [--json]
|
|
18
|
+
opencode-host --help
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
-c, --config <path> path to .red/config.yaml (default: ./.red/config.yaml)
|
|
22
|
+
-o, --out <path> Slice 1: path to write opencode.json (default: ./opencode.json)
|
|
23
|
+
-d, --out-dir <path> Slice 2: dist root (default: ./dist/opencode)
|
|
24
|
+
-p, --plugins-root <path> path to the plugins/ tree (default: ./plugins)
|
|
25
|
+
--plugin <name> emit only this plugin (repeatable; Slice 2 only)
|
|
26
|
+
--copy copy SKILL.md instead of symlinking
|
|
27
|
+
--with-slice-2 emit the Slice 2 dist tree (default; kept for compatibility)
|
|
28
|
+
--no-slice-2 opt out of the Slice 2 dist tree
|
|
29
|
+
--print print Slice 1 JSON to stdout (Slice 2 not written)
|
|
30
|
+
-v, --version print the build version (--json for the build info)
|
|
31
|
+
--json with --version: print the structured build info
|
|
32
|
+
-h, --help show this help
|
|
33
|
+
|
|
34
|
+
Exit codes:
|
|
35
|
+
0 wrote (or printed) successfully
|
|
36
|
+
1 read/write failure or opt-in gate closed (plugins.dev.enabled: true missing)
|
|
37
|
+
2 usage error
|
|
38
|
+
|
|
39
|
+
Notes:
|
|
40
|
+
- The ADR 0067 strict opt-in gate applies: plugins.dev.enabled: true must be set.
|
|
41
|
+
- Slice 1 (--out) is unaffected by --plugin/--no-slice-2; it is the standalone
|
|
42
|
+
provider-block JSON file.
|
|
43
|
+
- Planner errors (bad name, missing frontmatter) are reported on stderr but
|
|
44
|
+
do NOT fail the build. The events the generator can map are emitted.
|
|
45
|
+
`}function Xe(e){let t=e.indexOf(" #");return(t>=0?e.slice(0,t):e).trim()}function G(e,t){let o=[];for(let n of e.split(`
|
|
46
|
+
`)){let s=n.replace(/\r$/,""),i=s.trim();if(!i||i.startsWith("#"))continue;let r=s.indexOf(":");if(r<0)continue;let a=s.length-s.trimStart().length,c=s.slice(0,r).trim();if(!c)continue;let l=s.slice(r+1).trim();for(;o.length&&o[o.length-1].indent>=a;)o.pop();if(o.push({indent:a,key:c}),l&&o.map(f=>f.key).join(".")===t)return Xe(l)}}function ie(e,t){return G(e,`plugins.${t}.enabled`)==="true"}var Qe=Object.freeze({openrouter:{npm:"@openrouter/ai-sdk-provider",name:"OpenRouter"},minimax:{npm:"@ai-sdk/openai-compatible",name:"MiniMax",options:{baseURL:"https://api.minimax.chat/v1"}},openai:{npm:"@ai-sdk/openai",name:"OpenAI"}}),et=Object.freeze([{env:"OPENAI_API_KEY",provider:"openai"},{env:"MINIMAX_API_KEY",provider:"minimax"},{env:"OPENROUTER_API_KEY",provider:"openrouter"}]);function q(e){for(let t of et){let o=e[t.env];if(typeof o=="string"&&o.length>0)return t.provider}}function tt(e,t="think"){let o=G(e,`plugins.dev.afk.models.opencode.${t}.model`);return typeof o=="string"&&o.length>0?o:void 0}function ce(e){return ie(e,"dev")}var nt="openrouter/anthropic/claude-3.5-sonnet";function A(e){let t=q(e.env)??"openrouter",n=e.modelOverride??tt(e.configText)??nt,s={};for(let[r,a]of Object.entries(Qe))s[r]=a;let i={};i[t]=s[t];for(let[r,a]of Object.entries(s))r!==t&&(i[r]=a);return{$schema:"https://opencode.ai/config.json",provider:i,model:n}}import{mkdirSync as F,symlinkSync as Ut,writeFileSync as J,copyFileSync as _e}from"node:fs";import{dirname as N,join as P,relative as Mt}from"node:path";import{existsSync as ot,readdirSync as st,readFileSync as rt,statSync as it}from"node:fs";import{basename as B,dirname as H,join as K,relative as ct}from"node:path";var at=/^[a-z0-9]+(-[a-z0-9]+)*$/,ae=1024,lt=new Set(["in-progress"]);function ut(e){return e.split(/[\\/]/)}function pt(e){if(!e.startsWith("---"))return{};let t=e.indexOf(`
|
|
47
|
+
---`,3);if(t<0)return{};let o=e.slice(3,t),n,s;for(let i of o.split(`
|
|
48
|
+
`)){let r=i.trim();if(!r||r.startsWith("#"))continue;let a=r.indexOf(":");if(a<0)continue;let c=r.slice(0,a).trim(),l=r.slice(a+1).trim();(l.startsWith('"')&&l.endsWith('"')||l.startsWith("'")&&l.endsWith("'"))&&(l=l.slice(1,-1)),c==="name"?n=l:c==="description"&&(s=l)}return{name:n,description:s}}function ft(e,t){let o=K(e,t,"skills");if(!ot(o))return[];let n=[],s=[o];for(;s.length>0;){let i=s.pop(),r;try{r=st(i)}catch{continue}for(let a of r){let c=K(i,a),l;try{l=it(c)}catch{continue}if(l.isDirectory()){if(lt.has(B(c)))continue;s.push(c)}else l.isFile()&&a==="SKILL.md"&&n.push(c)}}return n}function dt(e,t,o){let n=[],s=B(H(e)),i=B(H(H(e))),r=ct(K(t,o,"skills"),e),a=ut(r);if(a.length!==3||a[2]!=="SKILL.md")return n.push({path:e,name:s,code:"name-not-valid",message:`${r} is not in the expected <bucket>/<name>/SKILL.md layout`}),{errors:n};if(!at.test(s)||s.length>64)return n.push({path:e,name:s,code:"name-not-valid",message:`name "${s}" violates opencode's name rule: 1-64 chars, lowercase alphanumeric with single-hyphen separators (regex ^[a-z0-9]+(-[a-z0-9]+)*$)`}),{errors:n};let c;try{c=rt(e,"utf8")}catch(f){return n.push({path:e,name:s,code:"frontmatter-missing",message:`could not read ${e}: ${f.message}`}),{errors:n}}let{name:l,description:u}=pt(c);return l?l!==s&&n.push({path:e,name:s,code:"name-not-equal-to-dir",message:`frontmatter name "${l}" does not match directory name "${s}" \u2014 opencode will refuse to load this skill`}):n.push({path:e,name:s,code:"name-missing",message:"frontmatter is missing the required `name:` field"}),u?u.length>ae&&n.push({path:e,name:s,code:"description-missing-or-too-long",message:`frontmatter description is ${u.length} chars, opencode's budget is ${ae}`}):n.push({path:e,name:s,code:"description-missing-or-too-long",message:"frontmatter is missing the required `description:` field"}),n.length>0?{errors:n}:{plan:{source:e,target:`skills/${s}/SKILL.md`,name:s,bucket:i},errors:[]}}function gt(e,t){let o=[],n=[];for(let s of ft(e,t)){let{plan:i,errors:r}=dt(s,e,t);i&&o.push(i),n.push(...r)}return o.sort((s,i)=>s.target.localeCompare(i.target)),{plans:o,errors:n}}function le(e,t){return t.map(o=>({plugin:o,result:gt(e,o)}))}import{existsSync as mt,readFileSync as ht}from"node:fs";import{join as ue}from"node:path";var pe=Object.freeze({SessionStart:"config",PreToolUse:"tool.execute.before",PostToolUse:"tool.execute.after",Stop:"session.idle",PreCompact:"experimental.session.compacting",UserPromptSubmit:"chat.message"}),yt=Object.freeze({SessionStart:"plugin/session-start.ts",PreToolUse:"plugin/pre-tool-use.ts",PostToolUse:"plugin/post-tool-use.ts",Stop:"plugin/stop.ts",PreCompact:"plugin/pre-compact.ts",UserPromptSubmit:"plugin/user-prompt-submit.ts"}),_t=["codex.hooks.json","claude.hooks.json"];function wt(e,t){let o=ue(e,t,"hooks");return _t.map(n=>ue(o,n)).filter(n=>mt(n))}function kt(e,t){return e.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g,"${__pluginRoot}").replace(/\$\{CODEX_PLUGIN_ROOT\}/g,"${__pluginRoot}").replace(/\brsp-instructions --runner (claude|codex) --hook\b/g,"rsp-instructions --runner opencode --hook")}function xt(e){let t=e.hookGroups.some(s=>fe(s.command.replace(/^sh -c\s*'/,"").replace(/'$/,""))),o=e.hookGroups.map(s=>{let i=ge(s.matcher),r=s.command.replace(/^sh -c\s*'/,"").replace(/'$/,"");return fe(r)?` if (${i}.test(input.tool)) {
|
|
49
|
+
const __rewritten = await __runRspRewrite(${JSON.stringify(r)});
|
|
50
|
+
if (__rewritten !== null && output.args && typeof output.args === "object" && "command" in output.args) {
|
|
51
|
+
output.args = { ...output.args, command: __rewritten };
|
|
52
|
+
}
|
|
53
|
+
}`:` if (${i}.test(input.tool)) {
|
|
54
|
+
const __blocked = await __runHook(${JSON.stringify(r)});
|
|
55
|
+
if (__blocked) return;
|
|
56
|
+
}`}).join(`
|
|
57
|
+
`);return["// generated by @reddb-io/red-skills \u2014 slice 2 (hooks \u2192 events)","// ADR 0077: PreToolUse \u2192 tool.execute.before","// do not edit by hand \u2014 re-run the generator to refresh.","",'import type { Plugin } from "@opencode-ai/plugin";',"","export const PreToolUse: Plugin = async (context) => {"," return {",' "tool.execute.before": async (input: { tool: string; sessionID?: string; callID?: string }, output: { args: Record<string, unknown> }) => {'," const __encoder = new TextEncoder();"," const __decoder = new TextDecoder();"," const __pluginRoot = context.directory;"," const __cwd = context.worktree;"," const __payload = JSON.stringify({",' hook_event_name: "PreToolUse",'," tool_name: input.tool,"," tool_input: output.args ?? {},"," cwd: __cwd,"," workspace: { current_dir: __cwd },"," });",` const __shellQuote = (value: string): string => \`'\${value.replaceAll("'", "'\\\\''")}'\`;`," const __denyCommand = (reason: string, code: number): string => `printf '%s\\\\n' ${__shellQuote(reason)} >&2; exit ${code}`;"," const __runHook = async (inner: string): Promise<boolean> => {"," const proc = Bun.$`sh -c ${inner}`.env({ __pluginRoot, CLAUDE_PLUGIN_ROOT: __pluginRoot, CODEX_PLUGIN_ROOT: __pluginRoot, OPENCODE_PLUGIN_ROOT: __pluginRoot }).quiet().nothrow();"," try {"," const writer = proc.stdin.getWriter();"," await writer.write(__encoder.encode(__payload));"," await writer.close();"," } catch { /* hook exited before draining stdin (EPIPE) */ }"," const result = await proc;"," const stdout = __decoder.decode(result.stdout).trim();",' let structuredReason = "";'," if (stdout.length > 0) {"," try {"," const parsed = JSON.parse(stdout) as { decision?: string; reason?: string; hookSpecificOutput?: { permissionDecision?: string; permissionDecisionReason?: string } };",' if (parsed.decision === "block" || parsed.hookSpecificOutput?.permissionDecision === "deny") {'," structuredReason = parsed.hookSpecificOutput?.permissionDecisionReason || parsed.reason || `RedSkills PreToolUse hook denied ${input.tool}.`;"," }"," } catch { /* non-JSON stdout is ignored */ }"," }"," if (result.exitCode === 0 && structuredReason.length === 0) return false;"," const stderr = __decoder.decode(result.stderr).trim();"," const reason = structuredReason || (stderr.length > 0 ? stderr : `RedSkills PreToolUse hook denied ${input.tool}.`);",' if (output.args && typeof output.args === "object" && "command" in output.args) {'," output.args = { ...output.args, command: __denyCommand(reason, result.exitCode) };"," return true;"," }"," throw new Error(reason);"," };",...t?[" const __runRspRewrite = async (inner: string): Promise<string | null> => {"," const proc = Bun.$`sh -c ${inner}`.env({ __pluginRoot, CLAUDE_PLUGIN_ROOT: __pluginRoot, CODEX_PLUGIN_ROOT: __pluginRoot, OPENCODE_PLUGIN_ROOT: __pluginRoot }).quiet().nothrow();"," try {"," const writer = proc.stdin.getWriter();"," await writer.write(__encoder.encode(__payload));"," await writer.close();"," } catch { /* hook exited before draining stdin (EPIPE) */ }"," const result = await proc;"," if (result.exitCode !== 0) return null;"," const rewritten = __decoder.decode(result.stdout).trim();"," return rewritten.length > 0 ? rewritten : null;"," };"]:[],o," },"," };","};",""].join(`
|
|
58
|
+
`)}function vt(e){return["// generated by @reddb-io/red-skills \u2014 slice 2 (hooks \u2192 events)","// ADR 0077: SessionStart \u2192 config + chat system context","// do not edit by hand \u2014 re-run the generator to refresh.","",'import type { Plugin } from "@opencode-ai/plugin";',"","export const SessionStart: Plugin = async (context) => {"," let __ran = false;"," const __sessionContext: string[] = [];"," const __encoder = new TextEncoder();"," const __decoder = new TextDecoder();"," const __pluginRoot = context.directory;"," const __cwd = context.worktree;"," const __payload = JSON.stringify({",' hook_event_name: "SessionStart",'," cwd: __cwd,"," workspace: { current_dir: __cwd },"," });"," const __extractContext = (stdout: string): string | null => {"," const text = stdout.trim();",' if (!text || text === "{}") return null;'," try {"," const parsed = JSON.parse(text) as { systemMessage?: unknown; hookSpecificOutput?: { additionalContext?: unknown } };",' const direct = typeof parsed.systemMessage === "string" ? parsed.systemMessage : null;',' const claude = typeof parsed.hookSpecificOutput?.additionalContext === "string" ? parsed.hookSpecificOutput.additionalContext : null;'," return direct ?? claude;"," } catch {"," return text;"," }"," };"," const __runSessionStart = async (inner: string): Promise<void> => {"," try {"," const proc = Bun.$`sh -c ${inner}`.env({ __pluginRoot, CLAUDE_PLUGIN_ROOT: __pluginRoot, CODEX_PLUGIN_ROOT: __pluginRoot, OPENCODE_PLUGIN_ROOT: __pluginRoot }).quiet().nothrow();"," try {"," const writer = proc.stdin.getWriter();"," await writer.write(__encoder.encode(__payload));"," await writer.close();"," } catch { /* hook exited before draining stdin (EPIPE) */ }"," const result = await proc;"," const ctx = __extractContext(__decoder.decode(result.stdout));"," if (ctx && !__sessionContext.includes(ctx)) __sessionContext.push(ctx);"," } catch { /* best-effort */ }"," };"," const __runOnce = async (): Promise<void> => {"," if (__ran) return;"," __ran = true;",e.commands.map(o=>{let n=o.replace(/^sh -c\s*'/,"").replace(/'$/,"");return[` await __runSessionStart(${JSON.stringify(n)});`].join(`
|
|
59
|
+
`)}).join(`
|
|
60
|
+
`)," };"," return {"," config: async () => {"," await __runOnce();"," },",' "experimental.chat.system.transform": async (_input: { sessionID?: string }, output: { system: string[] }) => {'," await __runOnce();"," for (const ctx of __sessionContext) output.system.push(ctx);"," },"," };","};",""].join(`
|
|
61
|
+
`)}function I(e){return[" const __encoder = new TextEncoder();"," const __decoder = new TextDecoder();"," const __pluginRoot = context.directory;"," const __cwd = context.worktree;"," const __runHook = async (inner: string, payload: Record<string, unknown>): Promise<string | null> => {"," try {"," const proc = Bun.$`sh -c ${inner}`.env({ __pluginRoot, CLAUDE_PLUGIN_ROOT: __pluginRoot, CODEX_PLUGIN_ROOT: __pluginRoot, OPENCODE_PLUGIN_ROOT: __pluginRoot }).quiet().nothrow();"," try {"," const writer = proc.stdin.getWriter();"," await writer.write(__encoder.encode(JSON.stringify(payload)));"," await writer.close();"," } catch { /* hook exited before draining stdin (EPIPE) */ }"," const result = await proc;"," return __decoder.decode(result.stdout).trim();"," } catch {"," return null;"," }"," };"," const __extractContext = (stdout: string | null): string | null => {",' const text = (stdout ?? "").trim();',' if (!text || text === "{}") return null;'," try {"," const parsed = JSON.parse(text) as { systemMessage?: unknown; hookSpecificOutput?: { additionalContext?: unknown } };",' const direct = typeof parsed.systemMessage === "string" ? parsed.systemMessage : null;',' const claude = typeof parsed.hookSpecificOutput?.additionalContext === "string" ? parsed.hookSpecificOutput.additionalContext : null;'," return direct ?? claude;"," } catch {"," return text;"," }"," };",` const __sourceEvent = ${JSON.stringify(e)};`]}function j(e){return e.hookGroups.map(t=>{let o=t.command.replace(/^sh -c\s*'/,"").replace(/'$/,""),n=[` const __stdout = await __runHook(${JSON.stringify(o)}, ${e.payloadExpression});`,e.contextSink?` const __ctx = __extractContext(__stdout); if (__ctx) ${e.contextSink};`:""].filter(Boolean).join(`
|
|
62
|
+
`);return e.toolExpression?[` if (${ge(t.matcher)}.test(${e.toolExpression})) {`,n," }"].join(`
|
|
63
|
+
`):[" {",n," }"].join(`
|
|
64
|
+
`)}).join(`
|
|
65
|
+
`)}function $t(e){return["// generated by @reddb-io/red-skills \u2014 slice 2 (hooks \u2192 events)","// ADR 0077: PostToolUse \u2192 tool.execute.after","// do not edit by hand \u2014 re-run the generator to refresh.","",'import type { Plugin } from "@opencode-ai/plugin";',"","export const PostToolUse: Plugin = async (context) => {"," return {",' "tool.execute.after": async (input: { tool: string; sessionID?: string; callID?: string; args?: Record<string, unknown> }, output: { title?: string; output?: string; metadata?: unknown }) => {',...I("PostToolUse")," const __payload = {"," hook_event_name: __sourceEvent,"," session_id: input.sessionID,"," tool_name: input.tool,"," tool_input: input.args ?? {},"," tool_response: { title: output.title, output: output.output, metadata: output.metadata },"," cwd: __cwd,"," workspace: { current_dir: __cwd },"," };",j({hookGroups:e.hookGroups,toolExpression:"input.tool",payloadExpression:"__payload",contextSink:'output.output = [__ctx, output.output].filter(Boolean).join("\\n\\n")'})," },"," };","};",""].join(`
|
|
66
|
+
`)}function de(){return[" const __textParts = (value: unknown, out: string[] = []): string[] => {",' if (typeof value === "string") return out;',' if (!value || typeof value !== "object") return out;'," if (Array.isArray(value)) { for (const item of value) __textParts(item, out); return out; }"," const record = value as Record<string, unknown>;",' for (const key of ["text", "content", "message", "output"] as const) {',' if (typeof record[key] === "string") out.push(record[key] as string);'," }"," for (const child of Object.values(record)) __textParts(child, out);"," return out;"," };"," const __readSessionTranscript = async (sessionID?: string): Promise<string | undefined> => {"," if (!sessionID) return undefined;"," const sessionApi = (context.client as unknown as { session?: { messages?: (input: unknown) => Promise<unknown> } }).session;"," if (!sessionApi?.messages) return undefined;"," const attempts = [{ path: { id: sessionID } }, { path: { sessionID } }, { id: sessionID }, { sessionID }];"," for (const attempt of attempts) {"," try {"," const response = await sessionApi.messages(attempt);"," const data = (response as { data?: unknown })?.data ?? response;",' const text = __textParts(data).join("\\n").trim();'," if (text.length > 0) return text;"," return JSON.stringify(data);"," } catch { /* try the next SDK shape */ }"," }"," return undefined;"," };"]}function bt(e){return["// generated by @reddb-io/red-skills \u2014 slice 2 (hooks \u2192 events)","// ADR 0077: Stop \u2192 session.idle","// do not edit by hand \u2014 re-run the generator to refresh.","",'import type { Plugin } from "@opencode-ai/plugin";',"","export const Stop: Plugin = async (context) => {"," return {",' "session.idle": async (input: { sessionID?: string }) => {',...I("Stop"),...de()," const __transcript = await __readSessionTranscript(input.sessionID);"," const __payload = {"," hook_event_name: __sourceEvent,"," session_id: input.sessionID,"," transcript_text: __transcript,"," transcriptText: __transcript,"," cwd: __cwd,"," workspace: { current_dir: __cwd },"," };",j({hookGroups:e.hookGroups,payloadExpression:"__payload"})," },"," };","};",""].join(`
|
|
67
|
+
`)}function Ot(e){return["// generated by @reddb-io/red-skills \u2014 slice 2 (hooks \u2192 events)","// ADR 0077: PreCompact \u2192 experimental.session.compacting","// do not edit by hand \u2014 re-run the generator to refresh.","",'import type { Plugin } from "@opencode-ai/plugin";',"","export const PreCompact: Plugin = async (context) => {"," return {",' "experimental.session.compacting": async (input: { sessionID?: string }, output: { context: string[]; prompt?: string }) => {',...I("PreCompact"),...de()," const __transcript = await __readSessionTranscript(input.sessionID);"," const __payload = {"," hook_event_name: __sourceEvent,"," session_id: input.sessionID,"," transcript_text: __transcript,"," transcriptText: __transcript,"," cwd: __cwd,"," workspace: { current_dir: __cwd },"," };",j({hookGroups:e.hookGroups,payloadExpression:"__payload",contextSink:"output.context.push(__ctx)"})," },"," };","};",""].join(`
|
|
68
|
+
`)}function St(e){return["// generated by @reddb-io/red-skills \u2014 slice 2 (hooks \u2192 events)","// ADR 0077: UserPromptSubmit \u2192 chat.message","// do not edit by hand \u2014 re-run the generator to refresh.","",'import type { Plugin } from "@opencode-ai/plugin";',"","export const UserPromptSubmit: Plugin = async (context) => {"," return {",' "chat.message": async (input: { sessionID?: string; messageID?: string }, output: { message?: unknown; parts: unknown[] }) => {',...I("UserPromptSubmit")," const __payload = {"," hook_event_name: __sourceEvent,"," session_id: input.sessionID,"," message_id: input.messageID,"," prompt: output.message,"," cwd: __cwd,"," workspace: { current_dir: __cwd },"," };",j({hookGroups:e.hookGroups,payloadExpression:"__payload",contextSink:'output.parts.unshift({ type: "text", text: __ctx } as never)'})," },"," };","};",""].join(`
|
|
69
|
+
`)}function fe(e){return e.includes("hook claude-pre-exec")||e.includes("rsp-hook.sh")&&e.includes("claude-pre-exec")}function ge(e){if(e==="*"||e==="")return"/.*/";let t=e.split("|").map(n=>n.trim()).filter(Boolean);return t.length===0?"/.*/":`/^(${t.map(n=>n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("|")})$/i`}function me(e,t){let o=wt(e,t);if(o.length===0)return[];let n=new Map,s=[];for(let r of o){let a;try{a=JSON.parse(ht(r,"utf8"))}catch(c){s.push(`could not parse ${r}: ${c.message}`);continue}for(let[c,l]of Object.entries(a.hooks??{})){if(!pe[c]){s.push(`event "${c}" from ${r} has no known OpenCode plugin event equivalent (warn-and-continue)`);continue}let f=n.get(c)??[];for(let p of l){let d=p.matcher??"*";for(let y of p.hooks){if(y.type!=="command")continue;let h=kt(y.command,t);f.push({matcher:d,command:h,sourceFile:r})}}n.set(c,f)}}let i=[];for(let[r,a]of n){let c=pe[r],l=yt[r];!c||!l||(r==="SessionStart"?i.push({sourceEvent:r,opencodeEvent:c,target:l,source:vt({sourceFiles:a.map(u=>u.sourceFile),commands:a.map(u=>u.command)}),sourceFiles:a.map(u=>u.sourceFile),warnings:s}):r==="PreToolUse"?i.push({sourceEvent:r,opencodeEvent:c,target:l,source:xt({sourceFiles:a.map(u=>u.sourceFile),hookGroups:a}),sourceFiles:a.map(u=>u.sourceFile),warnings:s}):r==="PostToolUse"?i.push({sourceEvent:r,opencodeEvent:c,target:l,source:$t({sourceFiles:a.map(u=>u.sourceFile),hookGroups:a}),sourceFiles:a.map(u=>u.sourceFile),warnings:s}):r==="Stop"?i.push({sourceEvent:r,opencodeEvent:c,target:l,source:bt({sourceFiles:a.map(u=>u.sourceFile),hookGroups:a}),sourceFiles:a.map(u=>u.sourceFile),warnings:s}):r==="PreCompact"?i.push({sourceEvent:r,opencodeEvent:c,target:l,source:Ot({sourceFiles:a.map(u=>u.sourceFile),hookGroups:a}),sourceFiles:a.map(u=>u.sourceFile),warnings:s}):r==="UserPromptSubmit"&&i.push({sourceEvent:r,opencodeEvent:c,target:l,source:St({sourceFiles:a.map(u=>u.sourceFile),hookGroups:a}),sourceFiles:a.map(u=>u.sourceFile),warnings:s}))}return i}import{existsSync as z,readFileSync as Pt,statSync as Et}from"node:fs";import{homedir as he}from"node:os";import{join as b}from"node:path";function Rt(e,t){let o=b(e,t,".mcp.json");if(!z(o))return null;try{return JSON.parse(Pt(o,"utf8"))}catch{return null}}function Ct(e,t,o){let n=[b(e,t,o),b(e,t,"hooks",o.split("/").pop()??""),b(e,"..","..",".codex",".tmp","marketplaces","red-skills","plugins",t,o),b(he(),".codex","plugins","cache","red-skills",t,o)];for(let s of n.slice(0,2))if(z(s))return{path:s,candidate:s};for(let s of n.slice(2))if(z(s))return{path:s,candidate:s};return{path:null,candidate:n[0]??null}}function At(e,t=he()){for(let o of e.matchAll(/\$HOME\/([\w./-]+)/g)){let n=b(t,o[1]);try{if(Et(n).isFile())return n}catch{}}return null}function It(e,t,o,n){let s=[],i=n.command??"sh",r=n.args??[],a=n.env;if(i==="sh"&&r[0]==="-c"&&typeof r[1]=="string")return jt(e,t,o,r[1],a);let c={type:"local",command:[i,...r],enabled:!0};return a&&(c.environment=a),{entry:c,warnings:s}}function jt(e,t,o,n,s){let i=[],r=[/\$\{?CODEX_PLUGIN_ROOT\}?\/(?:\$\{?CLAUDE_PLUGIN_ROOT\}?\/)?([\w./-]+)/,/\$\{?CLAUDE_PLUGIN_ROOT\}?\/(?:\$\{?CODEX_PLUGIN_ROOT\}?\/)?([\w./-]+)/,/\$root\/([\w./-]+)/g],a=[];for(let p of r)if(p.flags&&p.flags.includes("g")){let d=n.matchAll(p);for(let y of d)a.push(y[1])}else{let d=n.match(p);d&&d[1]&&a.push(d[1])}let c=At(n);if(!c)for(let p of a){let{path:d}=Ct(e,t,p);if(d){c=d;break}}if(!c){i.push(`${o}: could not resolve any of the script paths referenced by the source hooks (${a.slice(0,3).join(", ")}) \u2014 the plugin checkout or a Codex cache must exist for this MCP to start`);let p={type:"local",command:["sh","-c",n],enabled:!0};return s&&(p.environment=s),{entry:p,warnings:i}}let l=c.endsWith(".mjs")?"node":c.endsWith(".sh")?"bash":"sh",u=n.match(/mcp(?=['"\s;|&]|$)/)?["mcp"]:[],f={type:"local",command:[l,c,...u],cwd:e,enabled:!0};return s&&(f.environment=s),{entry:f,warnings:i}}function T(e,t){let o=Rt(e,t);if(!o?.mcpServers)return[];let n=[];for(let[s,i]of Object.entries(o.mcpServers)){let{entry:r,warnings:a}=It(e,t,s,i);n.push({name:s,entry:r,warnings:a})}return n.sort((s,i)=>s.name.localeCompare(i.name)),n}import{existsSync as Tt}from"node:fs";import{join as Ft}from"node:path";function Nt(e){let t=[Ft(e,"dev","skills","engineering","afk","bin","afk.mjs")];for(let o of t)if(Tt(o))return{path:o};return{path:null}}function ye(e,t){if(t!=="dev")return[];let{path:o}=Nt(e),n=[];return o||n.push("dev: AFK launcher not found at plugins/dev/skills/engineering/afk/bin/afk.mjs \u2014 the session-status module will be a silent no-op until the launcher is built"),[{plugin:t,target:"plugin/session-status.ts",source:Dt,warnings:n}]}var Dt=`// generated by @reddb-io/red-skills \u2014 slice 4 (statusline + toasts)
|
|
70
|
+
// ADR 0080: session.idle / session.error / session.created /
|
|
71
|
+
// experimental.session.compacting \u2192 AFK statusline
|
|
72
|
+
// do not edit by hand \u2014 re-run the generator to refresh.
|
|
73
|
+
|
|
74
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
75
|
+
import { join } from "node:path";
|
|
76
|
+
|
|
77
|
+
const AFK_BIN = "skills/engineering/afk/bin/afk.mjs";
|
|
78
|
+
|
|
79
|
+
/** Strip ANSI escape codes from a string. The AFK statusline
|
|
80
|
+
* output is themed; the toast body should be plain text. */
|
|
81
|
+
function stripAnsi(text: string): string {
|
|
82
|
+
// eslint-disable-next-line no-control-regex
|
|
83
|
+
return text.replace(/\\u001b\\[[0-9;]*m/g, "").trim();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Run the AFK statusline and return the cleaned one-line output.
|
|
87
|
+
* Returns null on any failure \u2014 the plugin module is
|
|
88
|
+
* best-effort, not fail-closed. */
|
|
89
|
+
async function readAfkStatusline(directory: string): Promise<string | null> {
|
|
90
|
+
const launcher = join(directory, AFK_BIN);
|
|
91
|
+
try {
|
|
92
|
+
const out = await Bun.$\`node \${launcher} statusline \${directory}\`.text();
|
|
93
|
+
const cleaned = stripAnsi(out);
|
|
94
|
+
return cleaned.length > 0 ? cleaned : null;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const AFKStatusline: Plugin = async (ctx) => {
|
|
101
|
+
let lastStatusline: string | null = null;
|
|
102
|
+
const refresh = async (): Promise<string | null> => {
|
|
103
|
+
const s = await readAfkStatusline(ctx.directory);
|
|
104
|
+
if (s) lastStatusline = s;
|
|
105
|
+
return lastStatusline;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"session.idle": async () => {
|
|
110
|
+
const s = await refresh();
|
|
111
|
+
if (s) {
|
|
112
|
+
await ctx.client.tui.showToast({
|
|
113
|
+
body: { message: \`[afk] \${s}\`, variant: "info" },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
"session.error": async (input: { error?: { message?: string } }) => {
|
|
119
|
+
const msg = input?.error?.message ?? "unknown error";
|
|
120
|
+
await ctx.client.tui.showToast({
|
|
121
|
+
body: { message: \`[afk] session error: \${msg}\`, variant: "error" },
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
"session.created": async () => {
|
|
126
|
+
await ctx.client.tui.appendPrompt({
|
|
127
|
+
body: {
|
|
128
|
+
text: "Run /afk monitor to see the live worker state, or /afk dashboard for a 24h summary.",
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
"experimental.session.compacting": async (
|
|
134
|
+
_input: unknown,
|
|
135
|
+
output: { context: string[]; prompt?: string },
|
|
136
|
+
) => {
|
|
137
|
+
const s = await refresh();
|
|
138
|
+
if (!s) return;
|
|
139
|
+
const block = [
|
|
140
|
+
"## AFK live state",
|
|
141
|
+
s,
|
|
142
|
+
"If the user resumes this session, redskilled \`status { scope: worker }\` is the source of truth for current Worker state and liveness \u2014 re-read it before acting on any compaction summary; without the MCP, /afk monitor is the fallback.",
|
|
143
|
+
"",
|
|
144
|
+
].join("\\n");
|
|
145
|
+
output.context.push(block);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
`;function we(e){let t=le(e.pluginsRoot,e.plugins);return{byPlugin:e.plugins.map(n=>{let s=t.find(l=>l.plugin===n)?.result??{plans:[],errors:[]},i=me(e.pluginsRoot,n),r=T(e.pluginsRoot,n),a=ye(e.pluginsRoot,n),c=A({configText:e.configText,env:e.env});return{plugin:n,provider:c,skills:s,hooks:i,mcp:r,statusline:a}})}}function ke(e,t){for(let o of e.byPlugin){let{plugin:n,provider:s,skills:i,hooks:r,mcp:a,statusline:c}=o,l=P(t.outRoot,n);F(l,{recursive:!0});let u={...s};a.length>0&&(u.mcp=Lt(a)),J(P(l,"opencode.json"),JSON.stringify(u,null,2)+`
|
|
150
|
+
`,"utf8");for(let f of i.plans){let p=P(l,".opencode",f.target);if(F(N(p),{recursive:!0}),t.copySkills)_e(f.source,p);else{let d=Mt(N(p),f.source),h=!d.startsWith("..")||d.split("/").filter(_=>_==="..").length<=4?d:f.source;try{Ut(h,p)}catch{_e(f.source,p)}}}for(let f of r){let p=P(l,".opencode",f.target);F(N(p),{recursive:!0}),J(p,f.source,"utf8")}for(let f of c){let p=P(l,".opencode",f.target);F(N(p),{recursive:!0}),J(p,f.source,"utf8")}}}function Lt(e){let t={};for(let o of e)t[o.name]=o.entry;return t}import{existsSync as Z,readdirSync as Vt,statSync as Wt}from"node:fs";import{join as Y}from"node:path";function xe(e){if(!Z(e))return[];let t;try{t=Vt(e)}catch{return[]}let o=[];for(let n of t){let s=Y(e,n),i;try{i=Wt(s)}catch{continue}if(!i.isDirectory())continue;let r=Z(Y(s,".claude-plugin","plugin.json")),a=Z(Y(s,".codex-plugin","plugin.json"));(r||a)&&o.push({name:n,source:s})}return o.sort((n,s)=>n.name.localeCompare(s.name)),o}function Bt(e,t){let o=[],n=[];for(let r of t)for(let a of T(e,r)){o.push(a);for(let c of a.warnings)n.push(`[${r}] ${c}`)}let s=new Set;return{mcps:o.filter(r=>s.has(r.name)?!1:(s.add(r.name),!0)),warnings:n}}function Kt(e){let t={};for(let o of e)t[o.name]=o.entry;return t}function $e(e){let t=M("opencode-host");process.stdout.write(e?`${JSON.stringify(t)}
|
|
151
|
+
`:`${L(t)}
|
|
152
|
+
`)}function zt(e){if(oe(e)){$e(e.includes("--json"));return}let t;try{t=se(e)}catch(g){throw g instanceof v&&(process.stderr.write(`opencode-host: ${g.message}
|
|
153
|
+
`),process.exit(2)),g}if(t.showVersion){$e(t.versionJson);return}if(t.showHelp){process.stdout.write(re());return}let o=M("opencode-host"),n=L(o),s=D(t.configPath),i=D(t.pluginsRoot),r=D(t.outPath),a=D(t.outDir),c;try{c=Gt(s,"utf8")}catch(g){process.stderr.write(`opencode-host: could not read config at ${s}: ${g.message}
|
|
154
|
+
`),process.exit(1)}ce(c)||(process.stderr.write(`opencode-host: refusing to emit \u2014 plugins.dev.enabled is not true in .red/config.yaml (ADR 0067 strict opt-in). Run /red-setup to enable the dev plugin.
|
|
155
|
+
`),process.exit(1));let l=q(process.env);l===void 0&&process.stderr.write("opencode-host: no auth env-var set (OPENAI_API_KEY / MINIMAX_API_KEY / OPENROUTER_API_KEY). Run `/connect` inside opencode to pick a provider, or export one of the env vars.\n");let u=A({configText:c,env:process.env}),f=xe(i),p=Bt(i,f.map(g=>g.name)),d={...u};p.mcps.length>0&&(d.mcp=Kt(p.mcps));let h=["// generated by @reddb-io/red-skills",`// version: ${n}`,p.mcps.length>0?`// slice: 1+3 (provider block + ${p.mcps.length} MCP server(s))`:"// slice: 1 (provider block only)","// do not edit by hand \u2014 re-run the generator to refresh.",""].join(`
|
|
156
|
+
`)+JSON.stringify(d,null,2)+`
|
|
157
|
+
`;if(t.printJson){process.stdout.write(h);return}try{ve(Ht(r),{recursive:!0}),qt(r,h,"utf8")}catch(g){process.stderr.write(`opencode-host: could not write ${t.outPath}: ${g.message}
|
|
158
|
+
`),process.exit(1)}for(let g of p.warnings)process.stderr.write(`opencode-host: mcp warning \u2014 ${g}
|
|
159
|
+
`);if(!t.slice2){process.stdout.write(`opencode-host: wrote ${t.outPath} (slice 1+3; provider=${l??"openrouter"} via precedence, model=${u.model}, mcps=${p.mcps.length})
|
|
160
|
+
`);return}let _=t.pluginFilter&&t.pluginFilter.length>0?f.filter(g=>t.pluginFilter.includes(g.name)).map(g=>g.name):f.map(g=>g.name);_.length===0&&(process.stderr.write(`opencode-host: no plugins discovered under ${i} (expected directories like dev/, memory/, brain/)
|
|
161
|
+
`),process.exit(1));let E=we({pluginsRoot:i,plugins:_,configText:c,env:process.env});try{ve(a,{recursive:!0}),ke(E,{outRoot:a,pluginsRoot:i,plugins:_,configText:c,env:process.env,copySkills:t.copySkills,version:n})}catch(g){process.stderr.write(`opencode-host: could not write ${r}: ${g.message}
|
|
162
|
+
`),process.exit(1)}let U=0;for(let g of E.byPlugin){for(let k of g.skills.errors)process.stderr.write(`opencode-host: skill error in ${g.plugin}: ${k.message}
|
|
163
|
+
`),U++;for(let k of g.hooks)for(let be of k.warnings)process.stderr.write(`opencode-host: hook warning in ${g.plugin}: ${be}
|
|
164
|
+
`),U++}process.stdout.write(`opencode-host: wrote ${t.outPath} (slice 1 provider block) + ${a} (slice 2 dist tree; plugins=${_.length}, skills=${E.byPlugin.reduce((g,k)=>g+k.skills.plans.length,0)}, hooks=${E.byPlugin.reduce((g,k)=>g+k.hooks.length,0)}, warnings=${U}, provider=${l??"openrouter"} via precedence)
|
|
165
|
+
`)}zt(process.argv.slice(2));
|