@fnet/cli 0.2.9 → 0.2.11
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/dist/fservice/index.js +1 -1
- package/package.json +1 -1
- package/template/fnet/node/src/cli/index.js.njk +5 -315
- package/template/fnet/node/src/cli/index.js.v1.njk +319 -0
- package/template/fnet/node/src/cli/v2/core/args-parser.njk +10 -0
- package/template/fnet/node/src/cli/v2/core/imports.njk +33 -0
- package/template/fnet/node/src/cli/v2/core/run-wrapper.njk +25 -0
- package/template/fnet/node/src/cli/v2/index.js.njk +184 -0
- package/template/fnet/node/src/cli/v2/modes/default/extend.njk +11 -0
- package/template/fnet/node/src/cli/v2/modes/default/standard.njk +20 -0
- package/template/fnet/node/src/cli/v2/modes/http/builtin.njk +66 -0
- package/template/fnet/node/src/cli/v2/modes/http/imports.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/http/index.njk +12 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/imports.njk +33 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/index.njk +30 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/server-setup.njk +15 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/tool-handlers.njk +46 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/transport-http.njk +222 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/transport-stdio.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/pipeline/imports.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/pipeline/index.njk +113 -0
- package/template/fnet/node/src/cli/v2/modes/webhook/imports.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/webhook/index.njk +127 -0
- package/template/fnet/node/src/cli/v2/modes/websocket/imports.njk +15 -0
- package/template/fnet/node/src/cli/v2/modes/websocket/index.njk +104 -0
package/dist/fservice/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import e from"yargs";import{hideBin as t}from"yargs/helpers";import n from"chalk";import i from"node:fs";import r from"node:path";import o from"@fnet/object-from-schema";import s from"@fnet/prompt";import a from"node:os";import c from"yaml";import l from"@fnet/service";import d from"cli-table3";import{createRequire as m}from"module";function f(){return r.join(a.homedir(),".fnet","bin")}function u(){return r.join(a.homedir(),".fnet","metadata")}function p(){return r.join(u(),"binaries.json")}function g(){try{if("win32"===process.platform)return process.env.PSModulePath&&process.env.PSModulePath.includes("PowerShell")?"powershell-core":process.env.PSModulePath?"powershell":"cmd";const e=process.env.SHELL||"";return e.includes("bash")?"bash":e.includes("zsh")?"zsh":e.includes("fish")?"fish":e.includes("ksh")?"ksh":e.includes("csh")||e.includes("tcsh")?"csh":i.existsSync(r.join(a.homedir(),".bashrc"))?"bash":i.existsSync(r.join(a.homedir(),".zshrc"))?"zsh":i.existsSync(r.join(a.homedir(),".config","fish","config.fish"))?"fish":"unknown"}catch(e){return"unknown"}}function y(e){const t=a.homedir(),n=[];switch(e){case"bash":n.push({name:".bashrc",path:r.join(t,".bashrc")}),n.push({name:".bash_profile",path:r.join(t,".bash_profile")}),n.push({name:".profile",path:r.join(t,".profile")});break;case"zsh":n.push({name:".zshrc",path:r.join(t,".zshrc")}),n.push({name:".zprofile",path:r.join(t,".zprofile")});break;case"fish":n.push({name:"config.fish",path:r.join(t,".config","fish","config.fish")});break;case"ksh":n.push({name:".kshrc",path:r.join(t,".kshrc")}),n.push({name:".profile",path:r.join(t,".profile")});break;case"csh":n.push({name:".cshrc",path:r.join(t,".cshrc")}),n.push({name:".tcshrc",path:r.join(t,".tcshrc")});break;case"powershell":n.push({name:"Microsoft.PowerShell_profile.ps1",path:r.join(t,"Documents","WindowsPowerShell","Microsoft.PowerShell_profile.ps1")}),n.push({name:"profile.ps1",path:r.join(t,"Documents","WindowsPowerShell","profile.ps1")});break;case"powershell-core":n.push({name:"Microsoft.PowerShell_profile.ps1",path:r.join(t,"Documents","PowerShell","Microsoft.PowerShell_profile.ps1")}),n.push({name:"profile.ps1",path:r.join(t,"Documents","PowerShell","profile.ps1")});break;case"cmd":n.push({name:"fnet-path.bat",path:r.join(t,"fnet-path.bat")})}return n}function v(e,t){switch(e){case"bash":case"zsh":case"ksh":default:return`export PATH="${t}:$PATH"`;case"fish":return`set -gx PATH ${t} $PATH`;case"csh":return`setenv PATH ${t}:$PATH`;case"powershell":case"powershell-core":return`$env:PATH = "${t};" + $env:PATH`;case"cmd":return`@echo off\nSETX PATH "%PATH%;${t}"\necho Path updated successfully`}}function h(){return r.join(a.homedir(),".fnet","backups")}function S(){const e=(new Date).toISOString().replace(/:/g,"-").replace(/\..+/,""),t=r.join(h(),e);return i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),t}function b(e,t,o=null){try{if(!i.existsSync(e))return!1;const n=o||r.basename(e),s=r.join(t,n),a=r.dirname(s);return i.existsSync(a)||i.mkdirSync(a,{recursive:!0}),i.copyFileSync(e,s),!0}catch(t){return console.error(n.red(`Failed to backup file ${e}: ${t.message}`)),!1}}function w(e,t={}){const n={timestamp:(new Date).toISOString(),type:t.type||"manual",message:t.message||"",command:t.command||"",files:t.files||[],...t},o=r.join(e,"metadata.json");i.writeFileSync(o,JSON.stringify(n,null,2))}function $(e){const t=r.join(h(),"latest");try{i.existsSync(t)&&i.unlinkSync(t),"win32"===process.platform?i.writeFileSync(t+".txt",e):i.symlinkSync(e,t)}catch(e){console.warn(n.yellow(`Could not create latest symlink: ${e.message}`))}}var x={getBinDirectory:f,getMetadataDirectory:u,getMetadataFilePath:p,checkIfInPath:function(e){return(process.env.PATH||"").split(r.delimiter).includes(e)},detectUserShell:g,getShellConfigPath:function(){const e=y(g());for(const t of e)if(i.existsSync(t.path))return t.path;return e.length>0?e[0].path:null},getAllShellConfigPaths:y,createBinDirectoryStructure:async function(){const e=f(),t=u(),n=p();i.existsSync(e)||i.mkdirSync(e,{recursive:!0}),i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),i.existsSync(n)||i.writeFileSync(n,JSON.stringify({binaries:{},lastUpdated:(new Date).toISOString()},null,2))},getExportPathCommand:v,addBinToPath:async function(e,t,o,s={}){try{const{autoBackup:a=!0}=s,c=v(e,o);if("cmd"===e)return i.writeFileSync(t,c),console.log(n.yellow(`Created batch file at ${t}`)),console.log(n.yellow("Run this file to add the bin directory to your PATH")),!0;if("powershell"!==e&&"powershell-core"!==e||i.existsSync(r.dirname(t))||i.mkdirSync(r.dirname(t),{recursive:!0}),!i.existsSync(t)){const r="fish"===e?"# Fish shell configuration\n\n":"powershell"===e||"powershell-core"===e?"# PowerShell profile\n\n":"# Shell configuration\n\n";i.writeFileSync(t,r),console.log(n.green(`Created config file at ${t}`))}const l=i.readFileSync(t,"utf8");if(l.includes(o))return!0;if(a&&i.existsSync(t)){const e=S(),i=r.basename(t);b(t,r.join(e,"configs"),i)&&(w(e,{type:"auto",command:"addBinToPath",message:`Automatic backup before modifying ${i}`,files:[t]}),$(e),console.log(n.green(`✓ Backed up ${i} to ${e}`)))}const d=`${l.trim()}\n\n# Added by @fnet/cli\n${c}\n`;return i.writeFileSync(t,d),"powershell"!==e&&"powershell-core"!==e||(console.log(n.yellow("You may need to set the PowerShell execution policy to run scripts:")),console.log(n.green("Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned"))),!0}catch(e){return console.error(n.red(`Failed to add bin directory to PATH: ${e.message}`)),!1}},getBackupDirectory:h,createBackupDirectory:S,backupFile:b,createBackupMetadata:w,listBackups:function(){const e=h();if(!i.existsSync(e))return[];const t=[],n=i.readdirSync(e);for(const o of n){const n=r.join(e,o),s=i.statSync(n);if(s.isDirectory()&&"latest"!==o){const e=r.join(n,"metadata.json");let a={};if(i.existsSync(e))try{a=JSON.parse(i.readFileSync(e,"utf8"))}catch(e){}t.push({timestamp:o,path:n,created:s.mtime,...a})}}return t.sort(((e,t)=>new Date(t.created)-new Date(e.created))),t},updateLatestSymlink:$};function D(){return r.join(a.homedir(),".fnet","services")}function F(){return r.join(a.homedir(),".fnet","metadata")}function j(){return r.join(F(),"services.json")}function k(){const e=j();if(!i.existsSync(e))return{services:{},lastUpdated:(new Date).toISOString()};try{return JSON.parse(i.readFileSync(e,"utf8"))}catch(e){return console.warn(n.yellow(`Failed to parse service metadata file: ${e.message}`)),{services:{},lastUpdated:(new Date).toISOString()}}}function O(e){const t=j();e.lastUpdated=(new Date).toISOString(),i.writeFileSync(t,JSON.stringify(e,null,2))}function E(e){return r.join(D(),`${e}.yaml`)}function P(e){const t=E(e);if(!i.existsSync(t))return null;try{const e=i.readFileSync(t,"utf8");return c.parse(e)}catch(e){return console.warn(n.yellow(`Failed to parse service definition file: ${e.message}`)),null}}function M(e){const t=[];if(e.name||t.push("Service name is required"),e.binary||t.push("Binary name is required"),e.binary){const n=x.getBinDirectory(),o=r.join(n,e.binary);i.existsSync(o)||t.push(`Binary '${e.binary}' not found in bin directory`)}return{valid:0===t.length,errors:t}}var A={getServicesDirectory:D,getServiceMetadataDirectory:F,getServiceMetadataFilePath:j,createServiceDirectoryStructure:async function(){const e=D(),t=F(),n=j();i.existsSync(e)||i.mkdirSync(e,{recursive:!0}),i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),i.existsSync(n)||i.writeFileSync(n,JSON.stringify({services:{},lastUpdated:(new Date).toISOString()},null,2))},loadServiceMetadata:k,saveServiceMetadata:O,getServiceDefinitionPath:E,serviceDefinitionExists:function(e){const t=E(e);return i.existsSync(t)},loadServiceDefinition:P,saveServiceDefinition:function(e,t){const r=E(e);try{const e=c.stringify(t);return i.writeFileSync(r,e),!0}catch(e){return console.error(n.red(`Failed to save service definition: ${e.message}`)),!1}},deleteServiceDefinition:function(e){const t=E(e);if(!i.existsSync(t))return!1;try{return i.unlinkSync(t),!0}catch(e){return console.error(n.red(`Failed to delete service definition: ${e.message}`)),!1}},listServiceDefinitions:function(){const e=D();if(!i.existsSync(e))return[];try{return i.readdirSync(e).filter((e=>e.endsWith(".yaml"))).map((e=>e.replace(".yaml","")))}catch(e){return console.error(n.red(`Failed to list service definitions: ${e.message}`)),[]}},validateServiceDefinition:M,registerService:async function(e,t={}){const n=P(e);if(!n)throw new Error(`Service definition '${e}' not found`);const i=M(n);if(!i.valid)throw new Error(`Invalid service definition: ${i.errors.join(", ")}`);const o=x.getBinDirectory(),s=r.join(o,n.binary);try{await l({action:"register",name:n.name,description:n.description||`Service for ${n.binary}`,command:[s,...n.args||[]],env:n.env||{},wdir:n.workingDir,system:!1!==n.system,autoStart:!0===n.autoStart,restartOnFailure:!1!==n.restartOnFailure,user:n.user});const t=k();return t.services[n.name]={definition:e,binary:n.binary,registered:(new Date).toISOString(),status:"registered"},O(t),{success:!0,name:n.name,definition:e}}catch(e){throw new Error(`Failed to register service: ${e.message}`)}}};async function N(e){await A.createServiceDirectoryStructure();return{servicesDir:A.getServicesDirectory(),metadataDir:A.getServiceMetadataDirectory(),metadata:A.loadServiceMetadata(),args:e}}var T={getServiceDefinitionSchema:function(e=!0){let t=[];if(e)try{const e=x.getBinDirectory(),n=x.getMetadataFilePath();if(i.existsSync(n)){const e=JSON.parse(i.readFileSync(n,"utf8"));t=Object.keys(e.binaries).map((t=>({name:t,message:`${t} (${e.binaries[t].version||"unknown"})`})))}else i.existsSync(e)&&(t=i.readdirSync(e).map((e=>({name:e,message:e}))))}catch(e){console.warn(`Failed to get binary choices: ${e.message}`)}return{type:"object",required:["name","binary"],properties:{name:{type:"string",description:"Service name","x-prompt":{type:"input",message:"Enter service name:"}},binary:{type:"string",description:"Binary name in the bin directory","x-prompt":{type:"select",message:"Select binary:",choices:t}},description:{type:"string",description:"Service description","x-prompt":{type:"input",message:"Enter service description:"}},args:{type:"array",items:{type:"string"},description:"Command line arguments","x-prompt":{type:"input",message:"Enter command line arguments (space-separated):",result:e=>e?e.split(" "):[]}},env:{type:"object",additionalProperties:{type:"string"},description:"Environment variables","x-prompt":{type:"input",message:"Enter environment variables (KEY=VALUE format, one per line):",result:e=>e?e.split("\n").filter((e=>e.includes("="))).reduce(((e,t)=>{const[n,...i]=t.split("=");return e[n.trim()]=i.join("=").trim(),e}),{}):{}}},workingDir:{type:"string",description:"Working directory","x-prompt":{type:"input",message:"Enter working directory (optional):"}},autoStart:{type:"boolean",description:"Start on boot",default:!1,"x-prompt":{type:"confirm",message:"Start service on boot?",initial:!1}},restartOnFailure:{type:"boolean",description:"Restart on failure",default:!0,"x-prompt":{type:"confirm",message:"Restart service on failure?",initial:!0}},system:{type:"boolean",description:"System service",default:!0,"x-prompt":{type:"confirm",message:"Register as system service?",initial:!0}},user:{type:"string",description:"User to run the service as","x-prompt":{type:"input",message:"Enter user to run the service as (optional):"}},instances:{type:"integer",description:"Number of instances to run",default:1,minimum:1,"x-prompt":{type:"number",message:"Enter number of instances to run:",initial:1}},metadata:{type:"object",additionalProperties:!0,description:"Custom metadata"}}}}};var I={promptForSelection:async function(e){const{items:t,message:i,nameField:r="name",valueField:o="name",initialValue:a=null,allowAbort:c=!0}=e;if(!t||0===t.length)return console.log(n.yellow("No items available for selection.")),null;if(1===t.length&&!c){const e=t[0],i="string"==typeof e?e:e[o];return console.log(n.blue(`Only one option available: ${"string"==typeof e?e:e[r]}`)),i}let l=t.map((e=>"string"==typeof e?{name:e,value:e,message:e}:{name:e[o],value:e[o],message:e[r]||e[o]}));c&&l.push({name:"cancel",value:null,message:n.yellow("Cancel")});let d=null;if(a){const e=l.findIndex((e=>e.name===a));-1!==e&&(d=e)}const m="selectedItem",{[m]:f}=await s({type:"select",name:m,message:i,choices:l,initial:d});return"cancel"===f?null:f},promptForMultipleSelection:async function(e){const{items:t,message:i,nameField:r="name",valueField:o="name",initialValues:a=[],allowAbort:c=!0}=e;if(!t||0===t.length)return console.log(n.yellow("No items available for selection.")),null;let l=t.map((e=>"string"==typeof e?{name:e,value:e,message:e}:{name:e[o],value:e[o],message:e[r]||e[o]})),d=[];a&&a.length>0&&(d=l.map(((e,t)=>a.includes(e.name)?t:-1)).filter((e=>-1!==e)));const m="selectedItems",f=await s({type:"multiselect",name:m,message:i,choices:l,initial:d,hint:"(Use space to select, enter to confirm)",validate:e=>!(0===e.length&&!c)||"Please select at least one item"});return c&&0===f[m].length?null:f[m]}};const B={command:"definition <subcommand>",describe:"Manage service definitions",builder:e=>e.command({command:"create",describe:"Create a new service definition",builder:e=>e.option("name",{describe:"Service definition name",type:"string"}).option("output",{describe:"Output file path",type:"string",alias:"o"}),handler:C}).command({command:"list",describe:"List service definitions",builder:e=>e.option("format",{describe:"Output format",type:"string",choices:["json","text","table"],default:"table"}),handler:U}).command({command:"show [n]",describe:"Show service definition details",builder:e=>e.positional("name",{describe:"Service definition name",type:"string",demandOption:!1}).option("format",{describe:"Output format",type:"string",choices:["json","yaml"],default:"yaml"}),handler:R}).command({command:"edit [n]",describe:"Edit a service definition",builder:e=>e.positional("name",{describe:"Service definition name",type:"string",demandOption:!1}),handler:H}).command({command:"delete [n]",describe:"Delete a service definition",builder:e=>e.positional("name",{describe:"Service definition name",type:"string",demandOption:!1}).option("force",{describe:"Force deletion without confirmation",type:"boolean",default:!1,alias:"f"}),handler:z}).command({command:"validate [n]",describe:"Validate a service definition",builder:e=>e.positional("name",{describe:"Service definition name",type:"string",demandOption:!1}),handler:J}).demandCommand(1,"You need to specify a subcommand"),handler:()=>{}};async function C(e){try{await N(e);const t=T.getServiceDefinitionSchema(),i=await o({schema:t,format:"yaml"});let r;if(console.log("Result from fnetObjectFromSchema:",i),"string"==typeof i)try{r=(await import("yaml")).default.parse(i)}catch(e){console.warn(n.yellow(`Failed to parse YAML: ${e.message}`)),r={name:"service-"+Date.now()}}else r=i&&"object"==typeof i?i.data||i:{name:"service-"+Date.now()};console.log("Generated definition:",r);const a=e.name||(r&&r.name?r.name:"service-"+Date.now());if(A.serviceDefinitionExists(a)&&!e.force){const{confirmOverwrite:e}=await s({type:"confirm",name:"confirmOverwrite",message:`Service definition '${a}' already exists. Overwrite?`,initial:!1});if(!e)return void console.log(n.yellow("Operation cancelled."))}A.saveServiceDefinition(a,r)?(console.log(n.green(`Service definition '${a}' created successfully.`)),console.log(n.blue(`Location: ${A.getServiceDefinitionPath(a)}`))):console.error(n.red(`Failed to create service definition '${a}'.`))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function U(e){try{await N(e);const t=A.listServiceDefinitions();if(0===t.length)return void console.log(n.yellow("No service definitions found."));if("json"===e.format)console.log(JSON.stringify(t,null,2));else if("text"===e.format)t.forEach((e=>console.log(e)));else{console.log(n.bold("\nService Definitions:"));const e=(await Promise.resolve().then((function(){return Z}))).default,i=["NAME","BINARY","DESCRIPTION"],r=e.createTable(i,{chars:{mid:"","mid-mid":"","left-mid":"","right-mid":""}});for(const e of t){const t=A.loadServiceDefinition(e);t&&r.push([n.white(e),n.cyan(t.binary||"undefined"),t.description||""])}console.log(r.toString()),console.log(`Total: ${t.length} definition(s)`)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function R(e){try{await N(e);if(!e.name){const t=A.listServiceDefinitions();if(0===t.length)return void console.log(n.yellow("No service definitions found."));const i=await I.promptForSelection({items:t,message:"Select a service definition to show:",allowAbort:!0});if(null===i)return void console.log(n.yellow("Operation cancelled."));e.name=i}const t=A.loadServiceDefinition(e.name);if(t||(console.error(n.red(`Service definition '${e.name}' not found.`)),process.exit(1)),"json"===e.format)console.log(JSON.stringify(t,null,2));else{const e=(await import("yaml")).default;console.log(e.stringify(t))}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function H(e){try{await N(e);if(!e.name){const t=A.listServiceDefinitions();if(0===t.length)return void console.log(n.yellow("No service definitions found."));const i=await I.promptForSelection({items:t,message:"Select a service definition to edit:",allowAbort:!0});if(null===i)return void console.log(n.yellow("Operation cancelled."));e.name=i}const t=A.loadServiceDefinition(e.name);t||(console.error(n.red(`Service definition '${e.name}' not found.`)),process.exit(1));const i=T.getServiceDefinitionSchema(),r=(await o({schema:i,ref:t,format:"yaml"})).data;A.saveServiceDefinition(e.name,r)?console.log(n.green(`Service definition '${e.name}' updated successfully.`)):console.error(n.red(`Failed to update service definition '${e.name}'.`))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function z(e){try{await N(e);if(!e.name){const t=A.listServiceDefinitions();if(0===t.length)return void console.log(n.yellow("No service definitions found."));const i=await I.promptForSelection({items:t,message:"Select a service definition to delete:",allowAbort:!0});if(null===i)return void console.log(n.yellow("Operation cancelled."));e.name=i}if(A.serviceDefinitionExists(e.name)||(console.error(n.red(`Service definition '${e.name}' not found.`)),process.exit(1)),!e.force){const{confirmDelete:t}=await s({type:"confirm",name:"confirmDelete",message:`Are you sure you want to delete service definition '${e.name}'?`,initial:!1});if(!t)return void console.log(n.yellow("Operation cancelled."))}A.deleteServiceDefinition(e.name)?console.log(n.green(`Service definition '${e.name}' deleted successfully.`)):console.error(n.red(`Failed to delete service definition '${e.name}'.`))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function J(e){try{await N(e);if(!e.name){const t=A.listServiceDefinitions();if(0===t.length)return void console.log(n.yellow("No service definitions found."));const i=await I.promptForSelection({items:t,message:"Select a service definition to validate:",allowAbort:!0});if(null===i)return void console.log(n.yellow("Operation cancelled."));e.name=i}const t=A.loadServiceDefinition(e.name);t||(console.error(n.red(`Service definition '${e.name}' not found.`)),process.exit(1));const i=A.validateServiceDefinition(t);i.valid?console.log(n.green(`Service definition '${e.name}' is valid.`)):(console.error(n.red(`Service definition '${e.name}' is invalid:`)),i.errors.forEach((e=>{console.error(n.red(`- ${e}`))})),process.exit(1))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}const _={command:"register",describe:"Register a service definition as a system service",builder:e=>e.option("definition",{describe:"Service definition name",type:"string",demandOption:!0,alias:"d"}).option("start",{describe:"Start the service after registration",type:"boolean",default:!1}),handler:async e=>{try{await N(e);A.serviceDefinitionExists(e.definition)||(console.error(n.red(`Service definition '${e.definition}' not found.`)),process.exit(1)),console.log(n.blue(`Registering service from definition '${e.definition}'...`));const t=await A.registerService(e.definition);if(console.log(n.green(`Service '${t.name}' registered successfully.`)),e.start){console.log(n.blue(`Starting service '${t.name}'...`));const e=(await import("@fnet/service")).default;try{await e({action:"start",name:t.name}),console.log(n.green(`Service '${t.name}' started successfully.`));const i=A.loadServiceMetadata();i.services[t.name]&&(i.services[t.name].status="running",i.services[t.name].lastStarted=(new Date).toISOString(),A.saveServiceMetadata(i))}catch(e){console.error(n.red(`Failed to start service: ${e.message}`))}}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},W={command:"unregister",describe:"Unregister a service from the system",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}).option("keep-definition",{describe:"Keep the service definition",type:"boolean",default:!0}).option("force",{describe:"Force unregistration without confirmation",type:"boolean",default:!1,alias:"f"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();if(t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),!e.force){const{confirmUnregister:t}=await s({type:"confirm",name:"confirmUnregister",message:`Are you sure you want to unregister service '${e.name}'?`,initial:!1});if(!t)return void console.log(n.yellow("Operation cancelled."))}console.log(n.blue(`Unregistering service '${e.name}'...`));const i=(await import("@fnet/service")).default;try{const r=t.services[e.name].definition,o=A.loadServiceDefinition(r);if(!o)throw new Error(`Service definition '${r}' not found`);const s=!1!==o.system;try{await i({action:"stop",name:e.name,system:s}),console.log(n.blue(`Service '${e.name}' stopped.`))}catch(e){console.warn(n.yellow(`Warning: Failed to stop service: ${e.message}`))}if(await i({action:"unregister",name:e.name,system:s}),console.log(n.green(`Service '${e.name}' unregistered successfully.`)),delete t.services[e.name],A.saveServiceMetadata(t),!e.keepDefinition&&r&&A.serviceDefinitionExists(r)){A.deleteServiceDefinition(r)?console.log(n.green(`Service definition '${r}' deleted.`)):console.warn(n.yellow(`Warning: Failed to delete service definition '${r}'.`))}}catch(e){console.error(n.red(`Failed to unregister service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},L={command:"start",describe:"Start a registered service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Starting service '${e.name}'...`));const i=(await import("@fnet/service")).default;try{const r=t.services[e.name].definition,o=A.loadServiceDefinition(r);if(!o)throw new Error(`Service definition '${r}' not found`);await i({action:"start",name:e.name,system:!1!==o.system}),console.log(n.green(`Service '${e.name}' started successfully.`)),t.services[e.name].status="running",t.services[e.name].lastStarted=(new Date).toISOString(),A.saveServiceMetadata(t)}catch(e){console.error(n.red(`Failed to start service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},Y={command:"stop",describe:"Stop a running service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Stopping service '${e.name}'...`));const i=(await import("@fnet/service")).default;try{const r=t.services[e.name].definition,o=A.loadServiceDefinition(r);if(!o)throw new Error(`Service definition '${r}' not found`);await i({action:"stop",name:e.name,system:!1!==o.system}),console.log(n.green(`Service '${e.name}' stopped successfully.`)),t.services[e.name].status="stopped",t.services[e.name].lastStopped=(new Date).toISOString(),A.saveServiceMetadata(t)}catch(e){console.error(n.red(`Failed to stop service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},V={command:"restart",describe:"Restart a service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Restarting service '${e.name}'...`));const i=(await import("@fnet/service")).default;try{await i({action:"stop",name:e.name}),console.log(n.blue(`Service '${e.name}' stopped.`)),await i({action:"start",name:e.name}),console.log(n.green(`Service '${e.name}' restarted successfully.`)),t.services[e.name].status="running",t.services[e.name].lastRestarted=(new Date).toISOString(),A.saveServiceMetadata(t)}catch(e){console.error(n.red(`Failed to restart service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},q={command:"status",describe:"Check the status of a service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}).option("format",{describe:"Output format",type:"string",choices:["json","text","table"],default:"table"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Checking status of service '${e.name}'...`));const i=(await import("@fnet/service")).default;try{const r=t.services[e.name].definition,o=A.loadServiceDefinition(r);if(!o)throw new Error(`Service definition '${r}' not found`);const s=await i({action:"status",name:e.name,system:!1!==o.system});console.log(n.green(`Service '${e.name}' status: ${s}`)),t.services[e.name].status=s||"unknown",t.services[e.name].lastChecked=(new Date).toISOString(),A.saveServiceMetadata(t),"json"===e.format?console.log(JSON.stringify({name:e.name,status:s||"unknown",definition:t.services[e.name].definition,binary:t.services[e.name].binary},null,2)):"text"===e.format?(console.log(`Name: ${e.name}`),console.log(`Status: ${s||"unknown"}`),console.log(`Definition: ${t.services[e.name].definition}`),console.log(`Binary: ${t.services[e.name].binary}`)):(console.log(n.bold("\nService Status:")),console.log(n.bold("─".repeat(50))),console.log(`${n.bold("Name:")} ${e.name}`),console.log(`${n.bold("Status:")} ${function(e){switch(e){case"running":return n.green;case"stopped":return n.yellow;case"failed":return n.red;default:return n.gray}}(s)(s||"unknown")}`),console.log(`${n.bold("Definition:")} ${t.services[e.name].definition}`),console.log(`${n.bold("Binary:")} ${t.services[e.name].binary}`),console.log(n.bold("─".repeat(50))))}catch(e){console.error(n.red(`Failed to check service status: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}};function K(e,t={}){const i={chars:{top:"─","top-mid":"─","top-left":" ","top-right":" ",bottom:"─","bottom-mid":"─","bottom-left":" ","bottom-right":" ",left:" ","left-mid":" ",mid:"─","mid-mid":"─",right:" ","right-mid":" ",middle:" "},style:{head:[],border:[],compact:!0},wordWrap:!0,...t,head:e.map((e=>n.bold(e)))};return new d(i)}function G(e,t,n={}){const i=K(e,n);return Array.isArray(t)&&t.forEach((e=>{i.push(e)})),i.toString()}function X(e){switch(e){case"running":return n.green;case"stopped":return n.yellow;case"failed":return n.red;case"registered":return n.blue;default:return n.gray}}var Q={createTable:K,createTableWithData:G,getStatusColor:X},Z=Object.freeze({__proto__:null,createTable:K,createTableWithData:G,default:Q,getStatusColor:X});const ee={command:"list",describe:"List all registered services",builder:e=>e.option("binary",{describe:"Filter by binary name",type:"string",alias:"b"}).option("status",{describe:"Filter by status",type:"string",choices:["running","stopped","failed","unknown"],alias:"s"}).option("format",{describe:"Output format",type:"string",choices:["json","text","table"],default:"table"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();let i=Object.entries(t.services).map((([e,t])=>({name:e,...t})));if(e.binary&&(i=i.filter((t=>t.binary===e.binary))),e.status&&(i=i.filter((t=>t.status===e.status))),0===i.length)return void console.log(n.yellow("No services found."));if("json"===e.format)console.log(JSON.stringify(i,null,2));else if("text"===e.format)i.forEach((e=>{console.log(`${e.name} (${e.status||"unknown"})`)}));else{const e=["NAME","STATUS","BINARY","DEFINITION"],t=Q.createTable(e,{chars:{mid:"","mid-mid":"","left-mid":"","right-mid":""}});i.forEach((e=>{const i=Q.getStatusColor(e.status);t.push([n.white(e.name),i(e.status||"unknown"),n.cyan(e.binary),e.definition])})),console.log(n.bold("\nRegistered Services:")),console.log(t.toString()),console.log(`Total: ${i.length} service(s)`)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},te=m(import.meta.url),{version:ne}=te("../../package.json");e(t(process.argv)).scriptName("fservice").usage("Usage: $0 <command> [options]").version(ne).command(B).command(_).command(W).command(L).command(Y).command(V).command(q).command(ee).demandCommand(1,"You need to specify a command").strict().help().alias("h","help").alias("v","version").fail(((e,t,i)=>{t?(console.error(n.red(`Error: ${t.message}`)),console.error(t)):console.error(n.red(`Error: ${e}`)),console.error(n.yellow("\nUsage:"),i.help()),process.exit(1)})).parse();
|
|
2
|
+
import e from"yargs";import{hideBin as t}from"yargs/helpers";import n from"chalk";import r from"@fnet/object-from-schema";import s from"@fnet/prompt";import i from"node:fs";import o from"node:path";import a from"node:os";import c from"yaml";import l from"@fnet/service";import m from"cli-table3";import{createRequire as d}from"module";function f(){return o.join(a.homedir(),".fnet","bin")}function u(){return o.join(a.homedir(),".fnet","metadata")}function p(){return o.join(u(),"binaries.json")}function g(){try{if("win32"===process.platform)return process.env.PSModulePath&&process.env.PSModulePath.includes("PowerShell")?"powershell-core":process.env.PSModulePath?"powershell":"cmd";const e=process.env.SHELL||"";return e.includes("bash")?"bash":e.includes("zsh")?"zsh":e.includes("fish")?"fish":e.includes("ksh")?"ksh":e.includes("csh")||e.includes("tcsh")?"csh":i.existsSync(o.join(a.homedir(),".bashrc"))?"bash":i.existsSync(o.join(a.homedir(),".zshrc"))?"zsh":i.existsSync(o.join(a.homedir(),".config","fish","config.fish"))?"fish":"unknown"}catch(e){return"unknown"}}function y(e){const t=a.homedir(),n=[];switch(e){case"bash":n.push({name:".bashrc",path:o.join(t,".bashrc")}),n.push({name:".bash_profile",path:o.join(t,".bash_profile")}),n.push({name:".profile",path:o.join(t,".profile")});break;case"zsh":n.push({name:".zshrc",path:o.join(t,".zshrc")}),n.push({name:".zprofile",path:o.join(t,".zprofile")});break;case"fish":n.push({name:"config.fish",path:o.join(t,".config","fish","config.fish")});break;case"ksh":n.push({name:".kshrc",path:o.join(t,".kshrc")}),n.push({name:".profile",path:o.join(t,".profile")});break;case"csh":n.push({name:".cshrc",path:o.join(t,".cshrc")}),n.push({name:".tcshrc",path:o.join(t,".tcshrc")});break;case"powershell":n.push({name:"Microsoft.PowerShell_profile.ps1",path:o.join(t,"Documents","WindowsPowerShell","Microsoft.PowerShell_profile.ps1")}),n.push({name:"profile.ps1",path:o.join(t,"Documents","WindowsPowerShell","profile.ps1")});break;case"powershell-core":n.push({name:"Microsoft.PowerShell_profile.ps1",path:o.join(t,"Documents","PowerShell","Microsoft.PowerShell_profile.ps1")}),n.push({name:"profile.ps1",path:o.join(t,"Documents","PowerShell","profile.ps1")});break;case"cmd":n.push({name:"fnet-path.bat",path:o.join(t,"fnet-path.bat")})}return n}function v(e,t){switch(e){case"bash":case"zsh":case"ksh":default:return`export PATH="${t}:$PATH"`;case"fish":return`set -gx PATH ${t} $PATH`;case"csh":return`setenv PATH ${t}:$PATH`;case"powershell":case"powershell-core":return`$env:PATH = "${t};" + $env:PATH`;case"cmd":return`@echo off\nSETX PATH "%PATH%;${t}"\necho Path updated successfully`}}function h(){return o.join(a.homedir(),".fnet","backups")}function S(){const e=(new Date).toISOString().replace(/:/g,"-").replace(/\..+/,""),t=o.join(h(),e);return i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),t}function b(e,t,r=null){try{if(!i.existsSync(e))return!1;const n=r||o.basename(e),s=o.join(t,n),a=o.dirname(s);return i.existsSync(a)||i.mkdirSync(a,{recursive:!0}),i.copyFileSync(e,s),!0}catch(t){return console.error(n.red(`Failed to backup file ${e}: ${t.message}`)),!1}}function w(e,t={}){const n={timestamp:(new Date).toISOString(),type:t.type||"manual",message:t.message||"",command:t.command||"",files:t.files||[],...t},r=o.join(e,"metadata.json");i.writeFileSync(r,JSON.stringify(n,null,2))}function $(e){const t=o.join(h(),"latest");try{i.existsSync(t)&&i.unlinkSync(t),"win32"===process.platform?i.writeFileSync(t+".txt",e):i.symlinkSync(e,t)}catch(e){console.warn(n.yellow(`Could not create latest symlink: ${e.message}`))}}var x={getBinDirectory:f,getMetadataDirectory:u,getMetadataFilePath:p,checkIfInPath:function(e){return(process.env.PATH||"").split(o.delimiter).includes(e)},detectUserShell:g,getShellConfigPath:function(){const e=y(g());for(const t of e)if(i.existsSync(t.path))return t.path;return e.length>0?e[0].path:null},getAllShellConfigPaths:y,createBinDirectoryStructure:async function(){const e=f(),t=u(),n=p();i.existsSync(e)||i.mkdirSync(e,{recursive:!0}),i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),i.existsSync(n)||i.writeFileSync(n,JSON.stringify({binaries:{},lastUpdated:(new Date).toISOString()},null,2))},getExportPathCommand:v,addBinToPath:async function(e,t,r,s={}){try{const{autoBackup:a=!0}=s,c=v(e,r);if("cmd"===e)return i.writeFileSync(t,c),console.log(n.yellow(`Created batch file at ${t}`)),console.log(n.yellow("Run this file to add the bin directory to your PATH")),!0;if("powershell"!==e&&"powershell-core"!==e||i.existsSync(o.dirname(t))||i.mkdirSync(o.dirname(t),{recursive:!0}),!i.existsSync(t)){const r="fish"===e?"# Fish shell configuration\n\n":"powershell"===e||"powershell-core"===e?"# PowerShell profile\n\n":"# Shell configuration\n\n";i.writeFileSync(t,r),console.log(n.green(`Created config file at ${t}`))}const l=i.readFileSync(t,"utf8");if(l.includes(r))return!0;if(a&&i.existsSync(t)){const e=S(),r=o.basename(t);b(t,o.join(e,"configs"),r)&&(w(e,{type:"auto",command:"addBinToPath",message:`Automatic backup before modifying ${r}`,files:[t]}),$(e),console.log(n.green(`✓ Backed up ${r} to ${e}`)))}const m=`${l.trim()}\n\n# Added by @fnet/cli\n${c}\n`;return i.writeFileSync(t,m),"powershell"!==e&&"powershell-core"!==e||(console.log(n.yellow("You may need to set the PowerShell execution policy to run scripts:")),console.log(n.green("Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned"))),!0}catch(e){return console.error(n.red(`Failed to add bin directory to PATH: ${e.message}`)),!1}},getBackupDirectory:h,createBackupDirectory:S,backupFile:b,createBackupMetadata:w,listBackups:function(){const e=h();if(!i.existsSync(e))return[];const t=[],n=i.readdirSync(e);for(const r of n){const n=o.join(e,r),s=i.statSync(n);if(s.isDirectory()&&"latest"!==r){const e=o.join(n,"metadata.json");let a={};if(i.existsSync(e))try{a=JSON.parse(i.readFileSync(e,"utf8"))}catch(e){}t.push({timestamp:r,path:n,created:s.mtime,...a})}}return t.sort(((e,t)=>new Date(t.created)-new Date(e.created))),t},updateLatestSymlink:$};function M(){return o.join(a.homedir(),".fnet","services")}function F(){return o.join(a.homedir(),".fnet","metadata")}function j(){return o.join(F(),"services.json")}function k(){const e=j();if(!i.existsSync(e))return{services:{},lastUpdated:(new Date).toISOString()};try{return JSON.parse(i.readFileSync(e,"utf8"))}catch(e){return console.warn(n.yellow(`Failed to parse service metadata file: ${e.message}`)),{services:{},lastUpdated:(new Date).toISOString()}}}function O(e){const t=j();e.lastUpdated=(new Date).toISOString(),i.writeFileSync(t,JSON.stringify(e,null,2))}function D(e){return o.join(M(),`${e}.yaml`)}function E(e){const t=D(e);if(!i.existsSync(t))return null;try{const e=i.readFileSync(t,"utf8");return c.parse(e)}catch(e){return console.warn(n.yellow(`Failed to parse service definition file: ${e.message}`)),null}}function P(e){const t=[];if(e.name||t.push("Service name is required"),e.binary||t.push("Binary name is required"),e.binary){const n=x.getBinDirectory(),r=o.join(n,e.binary);i.existsSync(r)||t.push(`Binary '${e.binary}' not found in bin directory`)}return{valid:0===t.length,errors:t}}var A={getServicesDirectory:M,getServiceMetadataDirectory:F,getServiceMetadataFilePath:j,createServiceDirectoryStructure:async function(){const e=M(),t=F(),n=j();i.existsSync(e)||i.mkdirSync(e,{recursive:!0}),i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),i.existsSync(n)||i.writeFileSync(n,JSON.stringify({services:{},lastUpdated:(new Date).toISOString()},null,2))},loadServiceMetadata:k,saveServiceMetadata:O,getServiceManfifestPath:D,servicManifestExists:function(e){const t=D(e);return i.existsSync(t)},loadServiceManifest:E,saveServiceManifest:function(e,t){const r=D(e);try{const e=c.stringify(t);return i.writeFileSync(r,e),!0}catch(e){return console.error(n.red(`Failed to save service definition: ${e.message}`)),!1}},deleteServiceManifest:function(e){const t=D(e);if(!i.existsSync(t))return!1;try{return i.unlinkSync(t),!0}catch(e){return console.error(n.red(`Failed to delete service definition: ${e.message}`)),!1}},listServiceManifests:function(){const e=M();if(!i.existsSync(e))return[];try{return i.readdirSync(e).filter((e=>e.endsWith(".yaml"))).map((e=>e.replace(".yaml","")))}catch(e){return console.error(n.red(`Failed to list service definitions: ${e.message}`)),[]}},validateServiceManifest:P,registerService:async function(e,t={}){const n=E(e);if(!n)throw new Error(`Service definition '${e}' not found`);const r=P(n);if(!r.valid)throw new Error(`Invalid service definition: ${r.errors.join(", ")}`);const s=x.getBinDirectory(),i=o.join(s,n.binary);try{await l({action:"register",name:n.name,description:n.description||`Service for ${n.binary}`,command:[i,...n.args||[]],env:n.env||{},wdir:n.workingDir,system:!1!==n.system,autoStart:!0===n.autoStart,restartOnFailure:!1!==n.restartOnFailure,user:n.user});const t=k();return t.services[n.name]={definition:e,binary:n.binary,registered:(new Date).toISOString(),status:"registered"},O(t),{success:!0,name:n.name,definition:e}}catch(e){throw new Error(`Failed to register service: ${e.message}`)}}};async function N(e){await A.createServiceDirectoryStructure();return{servicesDir:A.getServicesDirectory(),metadataDir:A.getServiceMetadataDirectory(),metadata:A.loadServiceMetadata(),args:e}}var T={getServiceManifestSchema:function(e=!0){let t=[];if(e)try{const e=x.getBinDirectory(),n=x.getMetadataFilePath();if(i.existsSync(n)){const e=JSON.parse(i.readFileSync(n,"utf8"));t=Object.keys(e.binaries).map((t=>({name:t,message:`${t} (${e.binaries[t].version||"unknown"})`})))}else i.existsSync(e)&&(t=i.readdirSync(e).map((e=>({name:e,message:e}))))}catch(e){console.warn(`Failed to get binary choices: ${e.message}`)}return{type:"object",required:["name","binary"],properties:{name:{type:"string",description:"Service name","x-prompt":{type:"input",message:"Enter service name:"}},binary:{type:"string",description:"Binary name in the bin directory","x-prompt":{type:"select",message:"Select binary:",choices:t}},description:{type:"string",description:"Service description","x-prompt":{type:"input",message:"Enter service description:"}},args:{type:"array",items:{type:"string"},description:"Command line arguments","x-prompt":{type:"input",message:"Enter command line arguments (space-separated):",result:e=>e?e.split(" "):[]}},env:{type:"object",additionalProperties:{type:"string"},description:"Environment variables","x-prompt":{type:"input",message:"Enter environment variables (KEY=VALUE format, one per line):",result:e=>e?e.split("\n").filter((e=>e.includes("="))).reduce(((e,t)=>{const[n,...r]=t.split("=");return e[n.trim()]=r.join("=").trim(),e}),{}):{}}},workingDir:{type:"string",description:"Working directory","x-prompt":{type:"input",message:"Enter working directory (optional):"}},autoStart:{type:"boolean",description:"Start on boot",default:!1,"x-prompt":{type:"confirm",message:"Start service on boot?",initial:!1}},restartOnFailure:{type:"boolean",description:"Restart on failure",default:!0,"x-prompt":{type:"confirm",message:"Restart service on failure?",initial:!0}},system:{type:"boolean",description:"System service",default:!0,"x-prompt":{type:"confirm",message:"Register as system service?",initial:!0}},user:{type:"string",description:"User to run the service as","x-prompt":{type:"input",message:"Enter user to run the service as (optional):"}},instances:{type:"integer",description:"Number of instances to run",default:1,minimum:1,"x-prompt":{type:"number",message:"Enter number of instances to run:",initial:1}},metadata:{type:"object",additionalProperties:!0,description:"Custom metadata"}}}}};var I={promptForSelection:async function(e){const{items:t,message:r,nameField:i="name",valueField:o="name",initialValue:a=null,allowAbort:c=!0}=e;if(!t||0===t.length)return console.log(n.yellow("No items available for selection.")),null;if(1===t.length&&!c){const e=t[0],r="string"==typeof e?e:e[o];return console.log(n.blue(`Only one option available: ${"string"==typeof e?e:e[i]}`)),r}let l=t.map((e=>"string"==typeof e?{name:e,value:e,message:e}:{name:e[o],value:e[o],message:e[i]||e[o]}));c&&l.push({name:"cancel",value:null,message:n.yellow("Cancel")});let m=null;if(a){const e=l.findIndex((e=>e.name===a));-1!==e&&(m=e)}const d="selectedItem",{[d]:f}=await s({type:"select",name:d,message:r,choices:l,initial:m});return"cancel"===f?null:f},promptForMultipleSelection:async function(e){const{items:t,message:r,nameField:i="name",valueField:o="name",initialValues:a=[],allowAbort:c=!0}=e;if(!t||0===t.length)return console.log(n.yellow("No items available for selection.")),null;let l=t.map((e=>"string"==typeof e?{name:e,value:e,message:e}:{name:e[o],value:e[o],message:e[i]||e[o]})),m=[];a&&a.length>0&&(m=l.map(((e,t)=>a.includes(e.name)?t:-1)).filter((e=>-1!==e)));const d="selectedItems",f=await s({type:"multiselect",name:d,message:r,choices:l,initial:m,hint:"(Use space to select, enter to confirm)",validate:e=>!(0===e.length&&!c)||"Please select at least one item"});return c&&0===f[d].length?null:f[d]}};const B={command:"manifest <subcommand>",describe:"Manage service manifests",builder:e=>e.command({command:"create",describe:"Create a new service manifest",builder:e=>e.option("name",{describe:"Service manifest name",type:"string"}).option("output",{describe:"Output file path",type:"string",alias:"o"}),handler:C}).command({command:"list",describe:"List service manifests",builder:e=>e.option("format",{describe:"Output format",type:"string",choices:["json","text","table"],default:"table"}),handler:U}).command({command:"show [n]",describe:"Show service manifest details",builder:e=>e.positional("name",{describe:"Service manifest name",type:"string",demandOption:!1}).option("format",{describe:"Output format",type:"string",choices:["json","yaml"],default:"yaml"}),handler:R}).command({command:"edit [n]",describe:"Edit a service manifest",builder:e=>e.positional("name",{describe:"Service manifest name",type:"string",demandOption:!1}),handler:H}).command({command:"delete [n]",describe:"Delete a service manifest",builder:e=>e.positional("name",{describe:"Service manifest name",type:"string",demandOption:!1}).option("force",{describe:"Force deletion without confirmation",type:"boolean",default:!1,alias:"f"}),handler:z}).command({command:"validate [n]",describe:"Validate a service manifest",builder:e=>e.positional("name",{describe:"Service manifest name",type:"string",demandOption:!1}),handler:J}).demandCommand(1,"You need to specify a subcommand"),handler:()=>{}};async function C(e){try{await N(e);const t=T.getServiceManifestSchema(),i=await r({schema:t,format:"yaml"});let o;if(console.log("Result from fnetObjectFromSchema:",i),"string"==typeof i)try{o=(await import("yaml")).default.parse(i)}catch(e){console.warn(n.yellow(`Failed to parse YAML: ${e.message}`)),o={name:"service-"+Date.now()}}else o=i&&"object"==typeof i?i.data||i:{name:"service-"+Date.now()};console.log("Generated manifest:",o);const a=e.name||(o&&o.name?o.name:"service-"+Date.now());if(A.servicManifestExists(a)&&!e.force){const{confirmOverwrite:e}=await s({type:"confirm",name:"confirmOverwrite",message:`Service manifest '${a}' already exists. Overwrite?`,initial:!1});if(!e)return void console.log(n.yellow("Operation cancelled."))}A.saveServiceManifest(a,o)?(console.log(n.green(`Service manifest '${a}' created successfully.`)),console.log(n.blue(`Location: ${A.getServiceManfifestPath(a)}`))):console.error(n.red(`Failed to create service manifest '${a}'.`))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function U(e){try{await N(e);const t=A.listServiceManifests();if(0===t.length)return void console.log(n.yellow("No service manifests found."));if("json"===e.format)console.log(JSON.stringify(t,null,2));else if("text"===e.format)t.forEach((e=>console.log(e)));else{console.log(n.bold("\nService Definitions:"));const e=(await Promise.resolve().then((function(){return Z}))).default,r=["NAME","BINARY","DESCRIPTION"],s=e.createTable(r,{chars:{mid:"","mid-mid":"","left-mid":"","right-mid":""}});for(const e of t){const t=A.loadServiceManifest(e);t&&s.push([n.white(e),n.cyan(t.binary||"undefined"),t.description||""])}console.log(s.toString()),console.log(`Total: ${t.length} manifest(s)`)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function R(e){try{await N(e);if(!e.name){const t=A.listServiceManifests();if(0===t.length)return void console.log(n.yellow("No service manifests found."));const r=await I.promptForSelection({items:t,message:"Select a service manifest to show:",allowAbort:!0});if(null===r)return void console.log(n.yellow("Operation cancelled."));e.name=r}const t=A.loadServiceManifest(e.name);if(t||(console.error(n.red(`Service manifest '${e.name}' not found.`)),process.exit(1)),"json"===e.format)console.log(JSON.stringify(t,null,2));else{const e=(await import("yaml")).default;console.log(e.stringify(t))}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function H(e){try{await N(e);if(!e.name){const t=A.listServiceManifests();if(0===t.length)return void console.log(n.yellow("No service manifests found."));const r=await I.promptForSelection({items:t,message:"Select a service manifest to edit:",allowAbort:!0});if(null===r)return void console.log(n.yellow("Operation cancelled."));e.name=r}const t=A.loadServiceManifest(e.name);t||(console.error(n.red(`Service manifest '${e.name}' not found.`)),process.exit(1));const s=T.getServiceManifestSchema(),i=(await r({schema:s,ref:t,format:"yaml"})).data;A.saveServiceManifest(e.name,i)?console.log(n.green(`Service manifest '${e.name}' updated successfully.`)):console.error(n.red(`Failed to update service manifest '${e.name}'.`))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function z(e){try{await N(e);if(!e.name){const t=A.listServiceManifests();if(0===t.length)return void console.log(n.yellow("No service manifests found."));const r=await I.promptForSelection({items:t,message:"Select a service manifest to delete:",allowAbort:!0});if(null===r)return void console.log(n.yellow("Operation cancelled."));e.name=r}if(A.servicManifestExists(e.name)||(console.error(n.red(`Service manifest '${e.name}' not found.`)),process.exit(1)),!e.force){const{confirmDelete:t}=await s({type:"confirm",name:"confirmDelete",message:`Are you sure you want to delete service manifest '${e.name}'?`,initial:!1});if(!t)return void console.log(n.yellow("Operation cancelled."))}A.deleteServiceManifest(e.name)?console.log(n.green(`Service manifest '${e.name}' deleted successfully.`)):console.error(n.red(`Failed to delete service manifest '${e.name}'.`))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}async function J(e){try{await N(e);if(!e.name){const t=A.listServiceManifests();if(0===t.length)return void console.log(n.yellow("No service manifests found."));const r=await I.promptForSelection({items:t,message:"Select a service manifest to validate:",allowAbort:!0});if(null===r)return void console.log(n.yellow("Operation cancelled."));e.name=r}const t=A.loadServiceManifest(e.name);t||(console.error(n.red(`Service manifest '${e.name}' not found.`)),process.exit(1));const r=A.validateServiceManifest(t);r.valid?console.log(n.green(`Service manifest '${e.name}' is valid.`)):(console.error(n.red(`Service manifest '${e.name}' is invalid:`)),r.errors.forEach((e=>{console.error(n.red(`- ${e}`))})),process.exit(1))}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}const _={command:"register",describe:"Register a service manifest as a system service",builder:e=>e.option("manifest",{describe:"Service manifest name",type:"string",demandOption:!0,alias:"d"}).option("start",{describe:"Start the service after registration",type:"boolean",default:!1}),handler:async e=>{try{await N(e);A.servicManifestExists(e.manifest)||(console.error(n.red(`Service manifest '${e.manifest}' not found.`)),process.exit(1)),console.log(n.blue(`Registering service from manifest '${e.manifest}'...`));const t=await A.registerService(e.manifest);if(console.log(n.green(`Service '${t.name}' registered successfully.`)),e.start){console.log(n.blue(`Starting service '${t.name}'...`));const e=(await import("@fnet/service")).default;try{await e({action:"start",name:t.name}),console.log(n.green(`Service '${t.name}' started successfully.`));const r=A.loadServiceMetadata();r.services[t.name]&&(r.services[t.name].status="running",r.services[t.name].lastStarted=(new Date).toISOString(),A.saveServiceMetadata(r))}catch(e){console.error(n.red(`Failed to start service: ${e.message}`))}}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},W={command:"unregister",describe:"Unregister a service from the system",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}).option("keep-manifest",{describe:"Keep the service manifest",type:"boolean",default:!0}).option("force",{describe:"Force unregistration without confirmation",type:"boolean",default:!1,alias:"f"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();if(t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),!e.force){const{confirmUnregister:t}=await s({type:"confirm",name:"confirmUnregister",message:`Are you sure you want to unregister service '${e.name}'?`,initial:!1});if(!t)return void console.log(n.yellow("Operation cancelled."))}console.log(n.blue(`Unregistering service '${e.name}'...`));const r=(await import("@fnet/service")).default;try{const s=t.services[e.name].manifest,i=A.loadServiceManifest(s);if(!i)throw new Error(`Service manifest '${s}' not found`);const o=!1!==i.system;try{await r({action:"stop",name:e.name,system:o}),console.log(n.blue(`Service '${e.name}' stopped.`))}catch(e){console.warn(n.yellow(`Warning: Failed to stop service: ${e.message}`))}if(await r({action:"unregister",name:e.name,system:o}),console.log(n.green(`Service '${e.name}' unregistered successfully.`)),delete t.services[e.name],A.saveServiceMetadata(t),!e.keepDefinition&&s&&A.servicManifestExists(s)){A.deleteServiceManifest(s)?console.log(n.green(`Service manifest '${s}' deleted.`)):console.warn(n.yellow(`Warning: Failed to delete service manifest '${s}'.`))}}catch(e){console.error(n.red(`Failed to unregister service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},L={command:"start",describe:"Start a registered service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Starting service '${e.name}'...`));const r=(await import("@fnet/service")).default;try{const s=t.services[e.name].manifest,i=A.loadServiceManifest(s);if(!i)throw new Error(`Service manifest '${s}' not found`);await r({action:"start",name:e.name,system:!1!==i.system}),console.log(n.green(`Service '${e.name}' started successfully.`)),t.services[e.name].status="running",t.services[e.name].lastStarted=(new Date).toISOString(),A.saveServiceMetadata(t)}catch(e){console.error(n.red(`Failed to start service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},Y={command:"stop",describe:"Stop a running service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Stopping service '${e.name}'...`));const r=(await import("@fnet/service")).default;try{const s=t.services[e.name].manifest,i=A.loadServiceManifest(s);if(!i)throw new Error(`Service manifest '${s}' not found`);await r({action:"stop",name:e.name,system:!1!==i.system}),console.log(n.green(`Service '${e.name}' stopped successfully.`)),t.services[e.name].status="stopped",t.services[e.name].lastStopped=(new Date).toISOString(),A.saveServiceMetadata(t)}catch(e){console.error(n.red(`Failed to stop service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},V={command:"restart",describe:"Restart a service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Restarting service '${e.name}'...`));const r=(await import("@fnet/service")).default;try{await r({action:"stop",name:e.name}),console.log(n.blue(`Service '${e.name}' stopped.`)),await r({action:"start",name:e.name}),console.log(n.green(`Service '${e.name}' restarted successfully.`)),t.services[e.name].status="running",t.services[e.name].lastRestarted=(new Date).toISOString(),A.saveServiceMetadata(t)}catch(e){console.error(n.red(`Failed to restart service: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},q={command:"status",describe:"Check the status of a service",builder:e=>e.option("name",{describe:"Service name",type:"string",demandOption:!0,alias:"n"}).option("format",{describe:"Output format",type:"string",choices:["json","text","table"],default:"table"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();t.services[e.name]||(console.error(n.red(`Service '${e.name}' not found in metadata.`)),process.exit(1)),console.log(n.blue(`Checking status of service '${e.name}'...`));const r=(await import("@fnet/service")).default;try{const s=t.services[e.name].manifest,i=A.loadServiceManifest(s);if(!i)throw new Error(`Service manifest '${s}' not found`);const o=await r({action:"status",name:e.name,system:!1!==i.system});console.log(n.green(`Service '${e.name}' status: ${o}`)),t.services[e.name].status=o||"unknown",t.services[e.name].lastChecked=(new Date).toISOString(),A.saveServiceMetadata(t),"json"===e.format?console.log(JSON.stringify({name:e.name,status:o||"unknown",manifest:t.services[e.name].manifest,binary:t.services[e.name].binary},null,2)):"text"===e.format?(console.log(`Name: ${e.name}`),console.log(`Status: ${o||"unknown"}`),console.log(`Definition: ${t.services[e.name].manifest}`),console.log(`Binary: ${t.services[e.name].binary}`)):(console.log(n.bold("\nService Status:")),console.log(n.bold("─".repeat(50))),console.log(`${n.bold("Name:")} ${e.name}`),console.log(`${n.bold("Status:")} ${function(e){switch(e){case"running":return n.green;case"stopped":return n.yellow;case"failed":return n.red;default:return n.gray}}(o)(o||"unknown")}`),console.log(`${n.bold("Definition:")} ${t.services[e.name].manifest}`),console.log(`${n.bold("Binary:")} ${t.services[e.name].binary}`),console.log(n.bold("─".repeat(50))))}catch(e){console.error(n.red(`Failed to check service status: ${e.message}`)),process.exit(1)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}};function K(e,t={}){const r={chars:{top:"─","top-mid":"─","top-left":" ","top-right":" ",bottom:"─","bottom-mid":"─","bottom-left":" ","bottom-right":" ",left:" ","left-mid":" ",mid:"─","mid-mid":"─",right:" ","right-mid":" ",middle:" "},style:{head:[],border:[],compact:!0},wordWrap:!0,...t,head:e.map((e=>n.bold(e)))};return new m(r)}function G(e,t,n={}){const r=K(e,n);return Array.isArray(t)&&t.forEach((e=>{r.push(e)})),r.toString()}function X(e){switch(e){case"running":return n.green;case"stopped":return n.yellow;case"failed":return n.red;case"registered":return n.blue;default:return n.gray}}var Q={createTable:K,createTableWithData:G,getStatusColor:X},Z=Object.freeze({__proto__:null,createTable:K,createTableWithData:G,default:Q,getStatusColor:X});const ee={command:"list",describe:"List all registered services",builder:e=>e.option("binary",{describe:"Filter by binary name",type:"string",alias:"b"}).option("status",{describe:"Filter by status",type:"string",choices:["running","stopped","failed","unknown"],alias:"s"}).option("format",{describe:"Output format",type:"string",choices:["json","text","table"],default:"table"}),handler:async e=>{try{await N(e);const t=A.loadServiceMetadata();let r=Object.entries(t.services).map((([e,t])=>({name:e,...t})));if(e.binary&&(r=r.filter((t=>t.binary===e.binary))),e.status&&(r=r.filter((t=>t.status===e.status))),0===r.length)return void console.log(n.yellow("No services found."));if("json"===e.format)console.log(JSON.stringify(r,null,2));else if("text"===e.format)r.forEach((e=>{console.log(`${e.name} (${e.status||"unknown"})`)}));else{const e=["NAME","STATUS","BINARY","DEFINITION"],t=Q.createTable(e,{chars:{mid:"","mid-mid":"","left-mid":"","right-mid":""}});r.forEach((e=>{const r=Q.getStatusColor(e.status);t.push([n.white(e.name),r(e.status||"unknown"),n.cyan(e.binary),e.manifest])})),console.log(n.bold("\nRegistered Services:")),console.log(t.toString()),console.log(`Total: ${r.length} service(s)`)}}catch(e){console.error(n.red(`Error: ${e.message}`)),process.exit(1)}}},te=d(import.meta.url),{version:ne}=te("../../package.json");e(t(process.argv)).scriptName("fservice").usage("Usage: $0 <command> [options]").version(ne).command(B).command(_).command(W).command(L).command(Y).command(V).command(q).command(ee).demandCommand(1,"You need to specify a command").strict().help().alias("h","help").alias("v","version").fail(((e,t,r)=>{t?(console.error(n.red(`Error: ${t.message}`)),console.error(t)):console.error(n.red(`Error: ${e}`)),console.error(n.yellow("\nUsage:"),r.help()),process.exit(1)})).parse();
|
package/package.json
CHANGED
|
@@ -1,319 +1,9 @@
|
|
|
1
1
|
{% if atom.doc.features.cli.enabled===true %}
|
|
2
2
|
|
|
3
|
-
{
|
|
4
|
-
{%
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
|
-
import express from "express";
|
|
10
|
-
{% endmacro %}
|
|
3
|
+
{% if atom.doc.features.cli.template.version ==='v1' %}
|
|
4
|
+
{% include "./index.js.v1.njk" %}
|
|
5
|
+
{% else %}
|
|
6
|
+
{% include "./v2/index.js.njk" %}
|
|
7
|
+
{% endif %}
|
|
11
8
|
|
|
12
|
-
{% macro mcpModeCodeExtended(runFn) %}
|
|
13
|
-
if (cliMode === 'mcp') {
|
|
14
|
-
// MCP mode code
|
|
15
|
-
const server = new Server({
|
|
16
|
-
name: "{{atom.doc.features.cli.mcp.name or atom.doc.name}}",
|
|
17
|
-
version: "{{atom.doc.version or '0.0.1'}}"
|
|
18
|
-
}, {
|
|
19
|
-
capabilities: {
|
|
20
|
-
tools: {}
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Define available tools
|
|
25
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
26
|
-
return {
|
|
27
|
-
tools: [{
|
|
28
|
-
name: "{{atom.doc.features.cli.mcp.tool.name or atom.doc.name}}",
|
|
29
|
-
description: "{{atom.doc.features.cli.mcp.tool.description or atom.doc.description}}",
|
|
30
|
-
inputSchema: inputSchema,
|
|
31
|
-
}]
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Handle tool execution
|
|
36
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
37
|
-
if (request.params.name === "{{atom.doc.features.cli.mcp.tool.name or atom.doc.name}}") {
|
|
38
|
-
try {
|
|
39
|
-
const result = await {{ runFn }}(request.params.arguments, { Engine });
|
|
40
|
-
return {
|
|
41
|
-
content: [{
|
|
42
|
-
type: "text",
|
|
43
|
-
text: JSON.stringify(result)
|
|
44
|
-
}]
|
|
45
|
-
};
|
|
46
|
-
} catch (error) {
|
|
47
|
-
return {
|
|
48
|
-
content: [{
|
|
49
|
-
type: "text",
|
|
50
|
-
text: `Error: ${error.message}`
|
|
51
|
-
}],
|
|
52
|
-
isError: true
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
throw new Error("Tool not found");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Get transport type from arguments
|
|
60
|
-
const transportType = args['mcp-transport'] || args.mcp_transport || 'stdio';
|
|
61
|
-
let transport;
|
|
62
|
-
|
|
63
|
-
if (transportType === 'stdio') {
|
|
64
|
-
// Use stdio transport
|
|
65
|
-
transport = new StdioServerTransport();
|
|
66
|
-
} else if (transportType === 'sse') {
|
|
67
|
-
// Use SSE transport
|
|
68
|
-
const app = express();
|
|
69
|
-
app.use(express.json());
|
|
70
|
-
|
|
71
|
-
const port = args['cli-port'] || args.cli_port || 3000;
|
|
72
|
-
const server = app.listen(port, () => {
|
|
73
|
-
console.log(`MCP server started with SSE transport on port ${port}`);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
transport = new StreamableHTTPServerTransport({
|
|
77
|
-
sessionIdGenerator: () => Math.random().toString(36).substring(2, 15),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
app.post('/sse', async (req, res) => {
|
|
81
|
-
await transport.handleRequest(req, res, req.body);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
app.get('/sse', async (req, res) => {
|
|
85
|
-
await transport.handleRequest(req, res);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
app.delete('/sse', async (req, res) => {
|
|
89
|
-
await transport.handleRequest(req, res);
|
|
90
|
-
});
|
|
91
|
-
} else {
|
|
92
|
-
console.error(`Unknown MCP transport type: ${transportType}`);
|
|
93
|
-
console.error(`Supported types: stdio, sse`);
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await server.connect(transport);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
{% endmacro %}
|
|
101
|
-
|
|
102
|
-
{% macro mcpModeCodeEngine(engineVar) %}
|
|
103
|
-
if (cliMode === 'mcp') {
|
|
104
|
-
// MCP mode code
|
|
105
|
-
const server = new Server({
|
|
106
|
-
name: "{{atom.doc.features.cli.mcp.name or atom.doc.name}}",
|
|
107
|
-
version: "{{atom.doc.version or '0.0.1'}}"
|
|
108
|
-
}, {
|
|
109
|
-
capabilities: {
|
|
110
|
-
tools: {}
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Define available tools
|
|
115
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
116
|
-
return {
|
|
117
|
-
tools: [{
|
|
118
|
-
name: "{{atom.doc.features.cli.mcp.tool.name or atom.doc.name}}",
|
|
119
|
-
description: "{{atom.doc.features.cli.mcp.tool.description or atom.doc.description}}",
|
|
120
|
-
inputSchema: inputSchema,
|
|
121
|
-
}]
|
|
122
|
-
};
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Handle tool execution
|
|
126
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
127
|
-
if (request.params.name === "{{atom.doc.features.cli.mcp.tool.name or atom.doc.name}}") {
|
|
128
|
-
try {
|
|
129
|
-
const result = await {{ engineVar }}.run(request.params.arguments);
|
|
130
|
-
return {
|
|
131
|
-
content: [{
|
|
132
|
-
type: "text",
|
|
133
|
-
text: JSON.stringify(result)
|
|
134
|
-
}]
|
|
135
|
-
};
|
|
136
|
-
} catch (error) {
|
|
137
|
-
return {
|
|
138
|
-
content: [{
|
|
139
|
-
type: "text",
|
|
140
|
-
text: `Error: ${error.message}`
|
|
141
|
-
}],
|
|
142
|
-
isError: true
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
throw new Error("Tool not found");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Note: Direct access to workflow nodes is not implemented in this version
|
|
150
|
-
// In a future version, we could expose workflow nodes as separate MCP tools
|
|
151
|
-
|
|
152
|
-
// Get transport type from arguments
|
|
153
|
-
const transportType = args['mcp-transport'] || args.mcp_transport || 'stdio';
|
|
154
|
-
let transport;
|
|
155
|
-
|
|
156
|
-
if (transportType === 'stdio') {
|
|
157
|
-
// Use stdio transport
|
|
158
|
-
transport = new StdioServerTransport();
|
|
159
|
-
} else if (transportType === 'sse') {
|
|
160
|
-
// Use SSE transport
|
|
161
|
-
const app = express();
|
|
162
|
-
app.use(express.json());
|
|
163
|
-
|
|
164
|
-
const port = args['cli-port'] || args.cli_port || 3000;
|
|
165
|
-
const server = app.listen(port, () => {
|
|
166
|
-
console.log(`MCP server started with SSE transport on port ${port}`);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
transport = new StreamableHTTPServerTransport({
|
|
170
|
-
sessionIdGenerator: () => Math.random().toString(36).substring(2, 15),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
app.post('/sse', async (req, res) => {
|
|
174
|
-
await transport.handleRequest(req, res, req.body);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
app.get('/sse', async (req, res) => {
|
|
178
|
-
await transport.handleRequest(req, res);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
app.delete('/sse', async (req, res) => {
|
|
182
|
-
await transport.handleRequest(req, res);
|
|
183
|
-
});
|
|
184
|
-
} else {
|
|
185
|
-
console.error(`Unknown MCP transport type: ${transportType}`);
|
|
186
|
-
console.error(`Supported types: stdio, sse`);
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await server.connect(transport);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
{% endmacro %}
|
|
194
|
-
|
|
195
|
-
{% macro httpModeCodeExpress(runFn, engineParam) %}
|
|
196
|
-
if (cliMode === 'http') {
|
|
197
|
-
// HTTP mode code using Express
|
|
198
|
-
const app = express();
|
|
199
|
-
app.use(express.json());
|
|
200
|
-
|
|
201
|
-
app.post('/{{atom.doc.features.cli.http.path or atom.doc.name}}', async (req, res) => {
|
|
202
|
-
try {
|
|
203
|
-
const result = await {{ runFn }}(req.body{{ engineParam }});
|
|
204
|
-
res.json(result);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
res.status(500).json({ error: error.message });
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const port = args['cli-port'] || args.cli_port || 3000;
|
|
211
|
-
app.listen(port, () => {
|
|
212
|
-
console.log(`HTTP server started on port ${port}`);
|
|
213
|
-
});
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
{% endmacro %}
|
|
217
|
-
|
|
218
|
-
{% macro defaultModeExtended() %}
|
|
219
|
-
if (cliMode === 'default') {
|
|
220
|
-
// Default mode code
|
|
221
|
-
return await runExtended(await argv(), { Engine });
|
|
222
|
-
}
|
|
223
|
-
{% endmacro %}
|
|
224
|
-
|
|
225
|
-
{% macro defaultModeEngine(engineVar) %}
|
|
226
|
-
if (cliMode === 'default') {
|
|
227
|
-
// Default mode code
|
|
228
|
-
const result = await {{ engineVar }}.run(await argv());
|
|
229
|
-
|
|
230
|
-
if (typeof result !== 'undefined') {
|
|
231
|
-
const stdout_format = args['stdout-format'] || args.stdout_format || null;
|
|
232
|
-
|
|
233
|
-
if (stdout_format === 'json') console.log(JSON.stringify(result, null, 2));
|
|
234
|
-
else console.log(result);
|
|
235
|
-
}
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
{% endmacro %}
|
|
239
|
-
|
|
240
|
-
{% macro runWithCatch() %}
|
|
241
|
-
run()
|
|
242
|
-
.catch((error) => {
|
|
243
|
-
console.error(error.message);
|
|
244
|
-
process.exit(1);
|
|
245
|
-
});
|
|
246
|
-
{% endmacro %}
|
|
247
|
-
|
|
248
|
-
{% macro runWithThenCatch() %}
|
|
249
|
-
run()
|
|
250
|
-
.then(() => {
|
|
251
|
-
{# process.exit(0); #}
|
|
252
|
-
})
|
|
253
|
-
.catch((error) => {
|
|
254
|
-
console.error(error.message);
|
|
255
|
-
process.exit(1);
|
|
256
|
-
});
|
|
257
|
-
{% endmacro %}
|
|
258
|
-
|
|
259
|
-
{# Main template starts here #}
|
|
260
|
-
import argv from '../default/input.args.js';
|
|
261
|
-
import { schema as inputSchema } from '../default/validate_input.js';
|
|
262
|
-
import { default as Engine } from '../default/{{atom.doc.features.cli_default_entry_file or atom.doc.features.main_default_entry_file}}';
|
|
263
|
-
|
|
264
|
-
{% if atom.doc.features.cli.mcp.enabled===true %}
|
|
265
|
-
{{ importMcpDependencies() }}
|
|
266
|
-
{% elif atom.doc.features.cli.http.enabled===true %}
|
|
267
|
-
// Using express for HTTP mode
|
|
268
|
-
import express from 'express';
|
|
269
|
-
{% endif %}
|
|
270
|
-
|
|
271
|
-
{% if atom.doc.features.cli.extend===true %}
|
|
272
|
-
{# TYPE 1 #}
|
|
273
|
-
import { default as runExtended } from '../../../cli/index.js';
|
|
274
|
-
import argsParser from 'yargs-parser';
|
|
275
|
-
|
|
276
|
-
const run = async () => {
|
|
277
|
-
const args = argsParser(process.argv.slice(2));
|
|
278
|
-
const cliMode = args['cli-mode'] || args.cli_mode || 'default';
|
|
279
|
-
|
|
280
|
-
{{ defaultModeExtended() }}
|
|
281
|
-
|
|
282
|
-
{% if atom.doc.features.cli.mcp.enabled===true %}
|
|
283
|
-
{{ mcpModeCodeExtended('runExtended') }}
|
|
284
|
-
{% endif %}
|
|
285
|
-
|
|
286
|
-
{% if atom.doc.features.cli.http.enabled===true %}
|
|
287
|
-
{{ httpModeCodeExpress('runExtended', ', { Engine }') }}
|
|
288
|
-
{% endif %}
|
|
289
|
-
|
|
290
|
-
console.error(`Unknown CLI mode: ${cliMode}`);
|
|
291
|
-
process.exit(1);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
{{ runWithThenCatch() }}
|
|
295
|
-
{% else %}
|
|
296
|
-
{# TYPE 2 #}
|
|
297
|
-
import argsParser from 'yargs-parser';
|
|
298
|
-
const run = async () => {
|
|
299
|
-
const args =argsParser(process.argv.slice(2));
|
|
300
|
-
const cliMode = args['cli-mode'] || args.cli_mode || 'default';
|
|
301
|
-
const engine = new Engine();
|
|
302
|
-
|
|
303
|
-
{{ defaultModeEngine('engine') }}
|
|
304
|
-
|
|
305
|
-
{% if atom.doc.features.cli.mcp.enabled===true %}
|
|
306
|
-
{{ mcpModeCodeEngine('engine') }}
|
|
307
|
-
{% endif %}
|
|
308
|
-
|
|
309
|
-
{% if atom.doc.features.cli.http.enabled===true %}
|
|
310
|
-
{{ httpModeCodeExpress('engine.run', '') }}
|
|
311
|
-
{% endif %}
|
|
312
|
-
|
|
313
|
-
console.error(`Unknown CLI mode: ${cliMode}`);
|
|
314
|
-
process.exit(1);
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
{{ runWithCatch() }}
|
|
318
9
|
{% endif %}
|
|
319
|
-
{% endif %}
|