{"id":27890297,"url":"https://github.com/tibcosoftware/cic-cli-core","last_synced_at":"2025-05-05T10:46:30.927Z","repository":{"id":40487414,"uuid":"378980505","full_name":"TIBCOSoftware/cic-cli-core","owner":"TIBCOSoftware","description":"CLI Core for TIBCO CLOUD™ CLI Plugins","archived":false,"fork":false,"pushed_at":"2023-07-19T03:01:15.000Z","size":21974,"stargazers_count":11,"open_issues_count":6,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-04-14T07:45:57.316Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TIBCOSoftware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-21T15:36:55.000Z","updated_at":"2022-11-09T13:52:14.000Z","dependencies_parsed_at":"2023-02-08T17:46:00.661Z","dependency_job_id":null,"html_url":"https://github.com/TIBCOSoftware/cic-cli-core","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TIBCOSoftware%2Fcic-cli-core","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TIBCOSoftware%2Fcic-cli-core/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TIBCOSoftware%2Fcic-cli-core/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TIBCOSoftware%2Fcic-cli-core/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TIBCOSoftware","download_url":"https://codeload.github.com/TIBCOSoftware/cic-cli-core/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252485722,"owners_count":21755822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-05T10:46:29.969Z","updated_at":"2025-05-05T10:46:30.917Z","avatar_url":"https://github.com/TIBCOSoftware.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CLI Core for TIBCO Cloud™\n\nIf you want to develop a CLI plugin for TIBCO Cloud™, use this package to get some features OOTB.\nUsing this package won't only give you features but will also help in maintaining a unified, consistent user experience for the end user.\n\n## Table of Contents\n\n1. [Get started](#get-started)\n2. [Command Class](#commands)\n    1. [BaseCommand](#basecommand)\n    2. [TCBaseCommand](#tcbasecommand)\n3. [Logging to the terminal](#logging-to-terminal)\n4. [Error Handling](#error-handling)\n5. [HTTP Requests](#http-requests)\n    1. [HTTPRequest](#httprequest)\n    2. [TCRequest](#tcrequest)\n6. [Plugin Configurations](#plugin-configurations)\n7. [UX](#ux)\n8. [API Documentation](#api-documentation)\n9. [Issue](#issues)\n10. [License](#license)\n\n## Get started\n\n- This CLI is based on oclif framework, you will first need to install [oclif](https://oclif.io/) framework on your machine.\n\n  ```bash\n  npm install -g oclif@1.18.0\n  ```\n\n- Install [TIBCO Cloud CLI](https://github.com/TIBCOSoftware/cic-cli-main) on your machine.\n\n- Generate the CLI plugin and install CLI core package.\n\n  ```\n  oclif plugin \u003cyour plugin name\u003e\n  cd \u003cyour plugin name\u003e\n  npm i @tibco-software/cic-cli-core\n  ```\n\n- Create topic (group) for your commands.\n  ```\n  oclif command \u003cYour topic name\u003e:hello\n  ```\n\n- Link your plugin to the TIBCO Cloud CLI.\n  ```\n  tibco plugins:link \u003cabsolute path of your plugin\u003e\n  ```\n\n- Test your hello command.\n\n  ```\n  tibco \u003cyour topic name\u003e:hello\n  ```\n\n\u003e **_NOTE:_** The TIBCO Cloud CLI works on oclif 1.18.x version. Please consider using the same version of oclif while developing the plugin.\n\n### How-to guide\n\n[Build Plugins on TIBCO Cloud CLI](https://www.walkthrough.so/pblc/niqkfEnGwRZM/build-plugins-for-tibco-cloud-tm-cli?sn=0) will guide you in detail to develop your first sample plugin.\n## Commands\n\n### BaseCommand\n\nExtend this class when you want to create any command which is executed on the machine locally or when you want to communicate with third party tools.\n\n```ts\nimport { flags } from '@oclif/command';\nimport { BaseCommand } from '@tibco-software/cic-cli-core';\n\nexport default class GenDirCommand extends BaseCommand {\n  static description = 'Creates directory';\n\n  static examples = [`tibco fs:gen-dir --path . mydir`];\n\n  static flags: flags.Input\u003cany\u003e \u0026 typeof BaseCommand.flags = {\n   \n   ...BaseCommand.flags,\n   \n   path: flags.string({\n      char: 'p',\n      description: 'Path other the cwd',\n      required: false,\n    }),\n  };\n\n  static args = [{ name: 'dirName' }];\n\n  async init() {\n    await super.init();\n    // Do any other initialization\n  }\n\n  async run() {\n    // Write your logic here\n\n    // to make any http requests to 3rd party tools\n    let httpReq = this.getHTTPRequest();\n\n    // get Plugin Config Object\n    let config = this.getPluginConfig();\n\n    // to get Plugin Config's property\n    config.get(\"property\");\n\n    // to set Plugin Config's property\n    config.set(\"count\", 2, { source: \"local\" });\n\n    // to delete Plugin Config's property\n    config.delete('count', { source: \"local\" });\n    \n  }\n\n  async catch(err: Error) {\n    // add any custom logic to handle errors from the command\n    // or simply return the parent class error handling\n    return super.catch(err);\n  }\n  async finally(err: Error) {\n    // called after run and catch regardless of whether or not the command errored\n    return super.finally(err);\n  }\n}\n```\n\n### TCBaseCommand\n\nExtend this class when you want to make API requests to TIBCO Cloud and manage profile configurations.\n\n\u003e **_NOTE:_** TCBaseCommand extends BaseCommand\n\n```ts\nimport { flags } from '@oclif/command';\nimport { TCBaseCommand } from '@tibco-software/cic-cli-core';\n\nexport default class ShowAppsCommand extends TCBaseCommand {\n  static description = 'Show Apps on TIBCO Cloud';\n\n  static examples = [`tibco tc:show-apps --all`];\n\n  static flags: flags.Input\u003cany\u003e \u0026 typeof TCBaseCommand.flags = {\n   \n   ...TCBaseCommand.flags,\n   \n   all: flags.boolean({\n      char: 'a',\n      description: 'show other owner apps',\n      required: false,\n    }),\n  };\n\n  async init() {\n    await super.init();\n    // Do any other initialization\n  }\n\n  async run() {\n    // Write your logic here\n\n    // to make any http requests to 3rd party tools\n    let httpReq = this.getHTTPRequest();\n\n    // to make any http requests to TIBCO Cloud\n    let tcReq = this.getTCRequest();\n\n    // to read Profile Config\n    let config = this.getProfileConfig();\n\n    // to save Profileconfig\n    this.saveProfileConfig(config);\n\n    // to reload Profile config\n    this.reloadProfileConfig();\n  }\n\n  async catch(err: Error) {\n    // add any custom logic to handle errors from the command\n    // or simply return the parent class error handling\n    return super.catch(err);\n  }\n  async finally(err: Error) {\n    // called after run and catch regardless of whether or not the command errored\n    return super.finally(err);\n  }\n}\n```\n\nAlternatively you can also import Requests module from package\n\n```ts\nimport { HTTPRequest, TCRequest } from '@tibco-software/cic-cli-core';\n\nlet req = new HTTPRequest('commandName', 'pluginName');\nlet tcReq = new TCRequest(profile, 'clientId', 'commandName', 'pluginName');\n```\n\n## Logging to terminal\n\nIf you are in Command class then simply use OOTB methods of logging.\n\n```ts\nasync run() {\n  this.log(\"Hello world\");\n  this.warn(\"File is getting replaced\");\n  this.debug(\"File not found, generating file\");\n  this.error(\"Failed to open app\");\n}\n```\n\nIn case, you are not in Command class then use Logger\n\n```ts\nimport { Logger, CLIBaseError } from '@tibco-software/cic-cli-core';\n\nLogger.log('Hello world');\nLogger.warn('File is getting replaced');\nLogger.debug('File not found, generating file');\nthrow new CLIBaseError('Failed to open an app');\n\n// for info on debugging see https://www.npmjs.com/package/debug\n// if you want to add your namespace to debugging\nlet debug = Logger.extendDebugger(\"interceptor\");\ndebug(\"My debugging namespace\");\n```\n\n## Error handling\n\nUse `CLIBaseError` class to throw errors. These errors are friendly and won't show a traceback unless debugging is enabled with env variable `DEBUG=*` or `CLI_NAME_DEBUG=1`.\n\nYou can extend `CLIBaseError` class to create more error classes.  \\\nFor E.g.: We created `HTTPError` class\n\n```ts\nexport class HTTPError extends CLIBaseError {\n  httpCode?: string | number;\n  httpHeaders?: { [index: string]: any };\n  httpResponse?: any;\n  constructor(message: string, httpCode?: string | number, httpResponse?: any, httpHeaders?: { [index: string]: any }) {\n    super(message);\n    this.httpCode = httpCode;\n    this.httpHeaders = httpHeaders;\n    this.httpResponse = httpResponse;\n    this.name = 'HTTPError';\n  }\n}\n```\n\n## HTTP Requests\n\nRequests module adds a wrapper on top of axios to simplify http requests\n\nIt will add some basic info to http requests -\n\n- add proxies if needed\n- User-Agent header\n- timeout (30s) - If the request takes longer than `timeout`, the request will be aborted.\n- connection (close)\n\nUse `HTTPRequest` class to make requests to any third party tools.\\\nUse `TCRequest` class while making request to TIBCO Cloud. `TCRequest` will take care of tokens, renew token when expired, add regions in endpoint based on the profile.\n\nCheck [axios options](https://github.com/axios/axios#request-config) for options parameter in all request methods\n\n### HTTPRequest\n\n#### doRequest\n\n```ts\nimport {HTTPRequest} from '@tibco-software/cic-cli-core';\n...\nlet req = new HTTPRequest('cmdName', 'pluginName');\n\n// doRequest(url, options?, data?)\nlet resp = await req.doRequest('https://api.mydomain.com/v1/apps/');  // default GET method\nlet resp2 = await req.doRequest('/v1/apps/scale', {baseURL: 'https://api.mydomain.com'}, {name:'myapp', count:2}); // default POST method if data parameter passed\nlet resp3 = await req.doRequest('https://api.mydomain.com/v1/apps/replace', {method: 'PUT'}, {}); // specify other methods from option's method property\n\nLogger.log(resp.body);\nLogger.log(resp.headers);\nLogger.log(resp.statusCode);\n```\n\n`doRequest` will throw error (instance of `HTTPError`) if response statusCode is in 4xx or 5xx.\n\n#### getAxiosClient\n\nUse this method if you want to make an http request instead of cli-core package making it for you\\\nIt will return an `axios` instance with some basic options added to it.\\\nFor `axios` instance methods checkout this part of [`axios methods`](https://www.npmjs.com/package/axios#instance-methods).\n\n```ts\nlet req = new HTTPRequest('cmdName', 'PluginName');\nlet client = req.getAxiosClient();\nlet axiosResp = await client.get('https://api.mydomain.com/v1/apps/', {});\nLogger.log(axiosResp.data);\nLogger.log(axiosResp.status);\nLogger.log(axiosResp.headers);\n```\n\n#### upload\n\nUse this method to upload files when required `Content-Type` is `multipart/form-data`.\\\nPass data parameter in `{key: value}` format and path of a file as value with a prefix `file://`. \\\nYou can also show progress bar on terminal by just passing last parameter `true` to the function. \\\nIt is recommended to show progress bar when file size more than 64KB.\n\n```ts\nlet req = new HTTPRequest('cmdName', 'PluginName');\nlet data = {\n  artifact: 'file:///Users/foo/Desktop/artifacts.json',\n  app: 'file:///Users/foo/Desktop/myapp.tgz',\n  name: 'MyApp',\n};\n\n// upload(url, data, axios Options, showProgressBar)\nlet resp = await req.upload('https://api.mydomain.com/v1/apps/new', data, {}, true);\n```\n\n#### download\n\nUse this method in case you need to download files.\\\nYou can also show progress bar on terminal by just passing last parameter `true` ot the function.\n\n```ts\nlet req = new HTTPRequest('cmdName', 'PluginName');\n\n// download(url, pathToStore, axios Options, showProgressBar)\nlet isDownloaded = await req.download('https://api.mydomain.com/v1/apps/pull', '/Users/foo/Desktop/', {}, true);\n\nif (isDownloaded === true) {\n  Logger.log('File downloaded successfully');\n}\n```\n\n### TCRequest\n\nThis module uses `HTTPRequest` to make calls to TIBCO Cloud.   \nIt takes care of tokens, renews them if expired before making a call. It also adds region to the endpoint based on current profile of the end user.  \n[`https://api.cloud.tibco.com`](https://api.cloud.tibco.com) is considered as a base URL when only path is passed to the url parameter of functions.\n\n#### doRequest\n\n```ts\nimport { TCRequest } from '@tibco-software/cic-cli-core';\nlet tcReq = new TCRequest(profile, 'clientId', 'commandName', 'pluginName');\n\n// doRequest(url, options, data?);\n// url =  https://api.cloud.tibco.com, method = GET, headers = { Authorization: Bearer \u003ctoken\u003e , ...}\nlet res = await tcReq.doRequest('/v1/apps/');\n\n// If profile has eu region\n// url = https://eu.api.cloud.tibco.com, method = GET, headers = { Authorization: Bearer \u003ctoken\u003e, ...}\nawait tcReq.doRequest('/v1/apps');\n\n// url = https://dev.tibco.com/userinfo, method = GET, headers = { Authorization: Bearer \u003ctoken\u003e, ...}\nawait tcReq.doRequest('/userinfo', { baseURL: 'https://dev.tibco.com' });\n\n// If profile has eu region\n// url = https://eu.dev.tibco.com/userinfo, method = GET, headers = { Authorization: Bearer \u003ctoken\u003e, ...}\nawait tcReq.doRequest('/userinfo', { baseURL: 'https://dev.tibco.com' });\n\n// doRequest(url, options, data?);\n// url =  https://api.cloud.tibco.com, method = POST, headers = { Authorization: Bearer \u003ctoken\u003e , ...}\nawait tcReq.doRequest('/v1/apps/', {} as AxiosRequestConfig, { otherOrgs: true });\n```\n\n#### getAxiosClient\n\nSame as HTTPRequest's getAxiosClient, this one adds region to url, token to the header and returns axios instance\n\n#### upload and download\n\nBoth methods are same as HTTPRequest's methods, these only adds region to url, token to the header and before making requests\n\n## Plugin Configurations\n\nYour plugin may also need configurations from user. To make those configurations persistent, you need to store them into the files. Below are few details on config file -\n\n- Configuration is stored in `.ini` file format.\n- It can be at **local level** in the users cwd or they can pass location explicitly using `--config` flag.\n- At **global level** this file is stored at `~/.config/@tibco-software/cic-cli-main/tibco-cli-config.ini`.\n- Local file will have higher precedence over global file.\n- These files will contain configurations for other plugins too. Check [here](https://github.com/TIBCOSoftware/cic-cli-main#configure-plugins) to understand the structure of file.\n\n`PluginConfig` Module manages all the operations needed for the configuratio file.\n\n**Example** to demonstrate behaviour of this module - \\\n`tibco tci:flogo:scale-app` \\\nAbove command scales flogo app in TCI and it needs `app-id` which should be fetched from a config file. \\\nConfig will look something like below -\n\n```ini\n\n[tci]\n\n[tci.flogo]\napp-id=1\n\n[tcam]\n...\n```\n\n```ts\n\nasync run() {\n    \n  let config = this.getPluginConfig();\n  \n  // if property exists locally then return value \n  // if property exits globally then return value\n  // else return undefined\n  let id = config.get(\"app-id\"); \n  \n  // if property exists locally then return local value else return undefined\n  let id = config.get(\"appi-id\", {source: 'local'});\n\n\n  // In above examples, we provided only property name and path from the root section was added by the config module.\n  // In below example since absoluePath option is true, you need to pass the complete path of the property from the beginning of the root section\n  // This is helpful when you want to fetch properties from other topics\n  let id = config.get(\"tci.flogo.appi-id\", {source: 'local', absolutePath: true});\n\n\n  // Update or add app-id property locally\n  // In case if config file does not have any sections and subsections for the property, config module will create them\n  config.set(\"app-id\", 2, {source:'local'});\n\n  // Unset the property locally\n  config.unset(\"app-id\",{source:'local'})\n\n}\n\n```\n\n\n## UX\n\nWith ux module you can create different prompts, spinner, tables (resized according to the terminal size) and open app from terminal.\n\n### prompts\n\n```ts\nimport { ux } from '@tibco-software/cic-cli-core';\n\n// Simple prompt\nawait ux.prompt('Enter your name ?');\n\n// Prompt for a password\nawait ux.prompt('Enter your password?', 'password');\n\n// Prompt choices\nawait ux.promptChoices('Total countries in the world', ['123', '195', '165', '187']);\n\n// Prompt for selecting multiple choices,\n// Choices can be selected by pressing space bar key and then Enter key after completing selection,\n// In case you want to show different names for choices on terminal then pass choices in this way [{name:\"\", value:\"\"}]\nawait ux.promptMultiSelectChoices('Select your favourite ice cream flavour', [\n  { name: 'Vanilla', value: 'vanilla' },\n  { name: 'Chocolate', value: 'chocolate' },\n  { name: 'Butter Pecan', value: 'butter-pecan' },\n  { name: 'Pistachio', value: 'pistachio' },\n  { name: 'Strawberry', value: 'strawberry' },\n  { name: 'Other', value: 'other' },\n]);\n\n// Prompt choices with search capability\nawait ux.promptChoicesWithSearch('Select you favourite colors', [\n  'Black',\n  'Green',\n  'Yellow',\n  'Red',\n  'White',\n  'Silver',\n  'Maroon',\n  'Grey',\n  'Orange',\n  'Brown',\n  'Blue',\n  'Cyan',\n  'Magenta',\n]);\n```\n\n![prompt demo](./media/ux-prompts.gif)\n\nIt is always better to configure flag for the prompts. This helps when user wants to automates their tasks using commands and can pass answers in the flags to the prompts.\\\nYou can directly pass flags as answer to prompt method and they won't prompt question to the terminal.\n\n```ts\n...\nstatic flags = {\n  name: flags.string({required: false})\n}\nasync run() {\n  const { flags } = this.parse(MyCommand);\n\n  // ux.prompt(question, input, answer)\n  await ux.prompt(\"Enter application name\", \"input\", flags.name);\n}\n```\n\n### spinner\n\n```ts\n async run() {\n    const spinner = await ux.spinner();\n    spinner.start(\"Downloading executable\");\n    await this.sleep(3000);\n    spinner.succeed(\"Downloaded\");\n\n    spinner.start(\"Installing application\");\n    await this.sleep(3000);\n    spinner.warn(\"No permission to register an app\");\n    await this.sleep(3000);\n    spinner.start(\"Retrying app installation\");\n    await this.sleep(3000);\n    spinner.fail(\"Failed\");\n  }\n\n  sleep(ms: number) {\n    return new Promise((resolve, reject) =\u003e {\n      setTimeout(resolve, ms);\n    });\n```\n\n![spinner demo](./media/ux-spinner.gif)\n\n### progress bar\n\nGive format of progress bar which will contain either inbuilt tokens or custom tokens.\nFor e.g: `:bar :percent :myCustomToken`\n\nInbuilt tokens\n| | | |\n|-----------|--------------------------------------|---|\n| :bar | the progress bar itself | |\n| :current | current tick number | |\n| :total | total ticks | |\n| :elapsed | time elapsed in seconds | |\n| :percent | completion percentage | |\n| :eta | estimated completion time in seconds | |\n| :rate | rate of ticks per second | |\n\n```ts\n...\nasync run() {\n  let bar = await ux.getProgressBar(\":bar :percent | :myCustomToken\", 100);\n  for (let i = 0; i \u003c 100; i++) {\n    await this.sleep(200);\n    bar.tick(1, { myCustomToken: `Printing custom token ${i}`});\n  }\n}\n\nsleep(ms: number) {\n  return new Promise((resolve, reject) =\u003e {\n    setTimeout(resolve, ms);\n  });\n}\n```\n\n![progress bar demo](./media/ux-progressbar.gif)\n\n### showTable\n\nTable gets adjusted as per the width of the terminal perhaps if terminal width is too narrow then content may overlap.\n\n```ts\nlet colors = [\n  {\n    color: \"red\",\n    value: \"#f00\",\n  },\n  {\n    color: \"green\",\n    value: \"#0f0\",\n  },\n  ...\n];\n\n// showTable(\u003cArray of objects\u003e, title?)\nawait ux.showTable(colors, 'Color codes');\n```\n\n \u003cimg src=\"./media/ux-tables.png\" style=\"padding-left:300\"/\u003e\n\n### open\n\nThis is just a wrapper method around package [open](https://www.npmjs.com/package/open)\n\n## API documentation\n\nTo see in details API documentation, check it out [here](./docs/README.md)\n\n## Issues\n\nIn case you find any issue, raise it here via the \"Issues\" tab of this GitHub repository.\n\n## License\n\n**BSD**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftibcosoftware%2Fcic-cli-core","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftibcosoftware%2Fcic-cli-core","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftibcosoftware%2Fcic-cli-core/lists"}