{"id":22862404,"url":"https://github.com/dylibso/xtp-integration-exercise","last_synced_at":"2025-04-30T21:52:32.239Z","repository":{"id":253317138,"uuid":"841062093","full_name":"dylibso/xtp-integration-exercise","owner":"dylibso","description":"Hands on, 15 minute exercise to teach you XTP ","archived":false,"fork":false,"pushed_at":"2024-12-16T04:33:29.000Z","size":1469,"stargazers_count":7,"open_issues_count":4,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-30T20:13:11.277Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dylibso.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-11T14:28:05.000Z","updated_at":"2024-11-15T01:10:33.000Z","dependencies_parsed_at":"2024-08-15T23:22:13.531Z","dependency_job_id":null,"html_url":"https://github.com/dylibso/xtp-integration-exercise","commit_stats":null,"previous_names":["dylibso/xtp-integration-exercise"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-integration-exercise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-integration-exercise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-integration-exercise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-integration-exercise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dylibso","download_url":"https://codeload.github.com/dylibso/xtp-integration-exercise/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251789309,"owners_count":21644081,"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":"2024-12-13T10:13:27.604Z","updated_at":"2025-04-30T21:52:32.218Z","avatar_url":"https://github.com/dylibso.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# XTP Onboarding Exercise\n\n\u003e *Time to Complete*: 15 minutes\n\n## Overview\n\nIn this exercise, you'll take on the role of a messaging company providing a SaaS,\nmulti-tenant chat application, similar to Slack. Let’s call it Yak. It’s ideal for endless\nchatter and yakking away. Your platform is popular for its built-in slash command feature,\nand now your customers want the ability to add their own custom commands.\nTo meet this demand, you will add an extension system to your platform using XTP.\n\n## Prerequisites\n\n- [git](https://git-scm.com/downloads)\n- [npm \u0026 node.js](https://nodejs.org/en/download/package-manager)\n- [extism-js compiler](https://github.com/extism/js-pdk?tab=readme-ov-file#install-the-compiler)\n- An ice cold coffee or other beverage of choice\n\n## Video Walkthrough\n\nIf you're not able to run through this guide yourself yet, watch one of us go through the steps in this video:\n\n[![Watch the video](https://img.youtube.com/vi/GQZ1Wcy1cQM/maxresdefault.jpg)](https://youtu.be/GQZ1Wcy1cQM?t=914)\n\n\u003e *Note*: There have been some UI and flow changes to XTP since this video was recorded. Use this README as the definitive guide.\n\n## Step 1: Clone the Repository and Launch Yak\n\nClone [this repository](https://github.com/dylibso/xtp-integration-exercise) which contains the code for the Yak platform.\n\n```shell\ngit clone https://github.com/dylibso/xtp-integration-exercise.git\n```\n\nInstall the required dependencies and start the application:\n\n```bash\nnpm install\nnpm run dev\n```\n\nOpen up [http://localhost:3000](http://localhost:3000) in your browser:\n\nYou should see the following:\n\n![Yak home screen](docs/hello-yak.png)\n\n\u003e **Note**: If you are not seeing this, check the server logs\n\nYou are logged into your customer Acme Inc's instance and it's just you and the bot.\n\nType some messages into the chat and you'll see the bot does not respond. The bot only responds to\nslash commands. To try some built-in commands start your message with `/`. You'll see a popup with built-in commands. Try `/countvowels Hello World`. The bot should respond with `countvowels: 3`\n\nTry out the built-in commands by typing “/” into the message box. You should at least see goodies such as `countvowels`.\n\n![count vowels](docs/count-vowels.png)\n\n\u003e **Note**: If you are not seeing this or getting an error, check the server logs\n\nThese built-in commands are a real treat, but as we mentioned before, your users want more customization.\nSo let’s integrate XTP into Yak so customers like Acme Inc can augment their Yak instance with their own slash commands!\n\n## Step 3: Add a plugin system to Yak using XTP\n\nFirst, you'll need to make sure [you have an XTP account](https://xtp.dylibso.com/sign-up)\nand [you have created a team](https://xtp.dylibso.com/~/teams/new). You can name your team whatever you want.\n\nWith your new team in hand, let’s setup an [Extension Point](https://docs.xtp.dylibso.com/docs/overview#extension-point)\nfor the Host App (i.e., Yak).\n\n### Step 3a: Configure an Extension Point\n\nChoose your app, navigate to the `Extension Points` tab and select the `Add Extension Point` button. You should now see the following:\n\n![new extension point](docs/new-extension-point.png)\n\nLet's name the Extension Point `SlashCommand` to indicate that Yak customers, such as Acme Inc., will use it to deploy their own custom slash commands.\n\nNext we’ll need to define a schema that sets up the interface between the Host application (i.e., Yak) and any plugins pushed to this Extension Point.\n\nCopy and paste the following into the Schema box:\n\n```yaml\nversion: v1-draft\nexports:\n  handleMessage:\n    description: Called when a message is sent to this plugin.\n    input:\n      contentType: application/json\n      description: The message without the slash command\n      $ref: \"#/components/schemas/Message\"\n    output:\n      contentType: application/json\n      description: The message you will reply with as the bot\n      $ref: \"#/components/schemas/Message\"\ncomponents:\n  schemas:\n    MessageType:\n      description: Tells the application how to interpret this message.\n      enum:\n      - html\n      - text\n      - image\n    Message:\n      description: A message from the system\n      properties:\n        body:\n          type: string\n          description: The message body. Depends on the type\n        type:\n          $ref: \"#/components/schemas/MessageType\"\n        nick:\n          type: string\n          description: The nickname of the originator of the message\n          nullable: true\n```\n\nClick `Submit` and congrats you now have an Extension Point in Yak!\n\n\u003e **Note**: Don’t worry about the details of this schema just yet; there will be plenty of time for that later. If you're curious, you can find more information [here](https://docs.xtp.dylibso.com/docs/host-usage/xtp-schema).\n\n### Step 3b: Integrate XTP with Yak\n\nNow that XTP knows more details about how and where you’re adding extensibility to Yak\n(via the Extension Point configuration we just created), we’re ready to modify Yak itself with the ability to retrieve and execute plugins.\n\nSince the Yak server is written in JavaScript, we’ll do this by using the XTP JS SDK.\nThe SDK will equip Yak with the following capabilities:\n\n1. Retrieve plugins from the XTP plugin registry that have been deployed by Yak customers, such as Acme Inc.\n2. Execute those plugins in-process using an embedded plugin execution engine\n\n\u003e *Note*: That’s right, we’re going to execute 3rd party, arbitrary code (safely) right inside of Yak!\n\nTo make this easy, we've put all the command handling code into the file [commands.js](./commands.js).\nFirst let's start by created a client to connect to the XTP API. Paste this on the top of the file:\n\n```javascript\n// command.js\n\nimport createClient from '@dylibso/xtp'\n\nconst xtpClient = await createClient({\n  appId: '\u003capp-id\u003e',\n  token: String(process.env.XTP_TOKEN),\n  // typescript plug-ins need WASI, this is a minor detail that isn't important at the moment\n  useWasi: true\n})\n```\n\n\u003e **Note**: Get your app id by clicking on the `Detail` tab from your app view. Or pull it out of the url.\n\n\u003e **Note**: Generate a token from your user view here: [https://xtp.dylibso.com/tokens](https://xtp.dylibso.com/~/tokens).\n\u003e This is a secret. Store it in the environment variable XTP_TOKEN and restart the server.\n\u003e $ export XTP_TOKEN=\"\u003cmytoken\u003e\"\n\nNow that the client is initialized, there are two places we need to extend:\n\n1. Where the available command names are listed\n2. Where the messages are processed\n\nFor the first, let's extend `getCommands()`. This currently returns the list of built-in command names.\nWe will append on the result of `xtpClient.listAvailablePlugins()` which will return the commands installed at the\nlogged in guest account.\n\n```javascript\nconst GUEST_KEY = 'acme-corp'\nconst EXT_NAME = 'SlashCommand'\n\n// change this implementation to include guest commands\nexport async function getCommands() {\n  return Object.keys(BUILTIN_COMMANDS).concat(await xtpClient.listAvailablePlugins(\n    EXT_NAME,\n    GUEST_KEY,\n  ))\n}\n```\n\nNext we need to add the ability to execute these custom commands. To do that we must modify `commandHandler`:\n\n```javascript\n// add this helper function\nasync function runSlashCommand(commandName, message) {\n  // our Extension Point is:             `SlashCommand`\n  // our export that we want to call is: `handleMessage`\n  const pluginFunc = xtpClient.extensionPoints.SlashCommand.handleMessage\n\n  const result = await pluginFunc(\n    GUEST_KEY,\n    JSON.stringify(message), // The plug-in expects a json Message\n    {\n      // this is by default the name of our plugin,\n      // which is the name of the command\n      bindingName: commandName,\n      default: \"{}\"\n    }\n  )\n  return JSON.parse(result)\n}\n\n// modify commandHandler\nexport async function commandHandler(message) {\n// ...\n\n  const command = BUILTIN_COMMANDS[commandName]\n  if (command) {\n    botMessage = command(message)\n  } else { // add this else clause\n    // if we fail to find the command in the built-ins, let's check xtp\n    const pluginCommands = await xtpClient.listAvailablePlugins(\n      EXT_NAME,\n      GUEST_KEY,\n    )\n    if (pluginCommands.includes(commandName)) {\n      // running a plugin is no different than calling a normal function\n      // but it's sandboxed and language independent thanks to Wasm\n      botMessage = await runSlashCommand(commandName, message)\n    }\n  }\n\n// ...\n}\n```\n\n\u003e **Note**: Plugins are scoped by the Extension Point and a Guest Key, with the Guest Key identifying the plugin's creator, such as Acme Inc.\n\nFrom the Yak side we’re now all ready to go. See how easy that was?! Now let’s make sure things are working properly from the Guest side, by setting up a Guest account for testing purposes.\n\n\u003e **Note**: Before moving on, run `npm run dev` and make sure the application is running as normal. Leave it running for the next step.\n\n\n## Step 4: Add a Guest Account for Acme Inc.\n\nIn order for the Acme company to be able to push plugins to your newly established Extension Point, they’ll need an [XTP User Account](https://docs.xtp.dylibso.com/docs/overview#user-account) that has [Guest](https://docs.xtp.dylibso.com/docs/overview#guest) access to your [XTP Host App](https://docs.xtp.dylibso.com/docs/overview#host-app) (i,.e., Yak). \n\nNormally the guest would be one of your users and a third party, to make testing easier, we're going to invite ourselves as a guest.\n\nFirst you need to install the CLI in your terminal:\n\n```bash\ncurl https://static.dylibso.com/cli/install.sh | bash\n```\n\nStart by making sure that you're logged into the `xtp` CLI. Running the command\nbelow will open a browser window asking you to login.\n\n```shell\n$ xtp auth login\n```\n\n\nOnce you've approved the login, close the browser window. Now we can add ourselves\nas a guest. We're going to use `\"acme-corp\"` as our guest key since we are pretending to be Acme.\n\n\u003e **Note**: Normally you will invite guests via the API and they have their own account. For this demo we are just going to\nadd ourselves as a guest.\n\nIn order to add yourself as a guest.\n\n1. Navigate to your app in the dashboard\n2. Click the `Guests` tab\n3. Click the `Add yourself` button\n4. Click the auto-generated guest key and change it to `acme-corp`\n5. Hit \u003center\u003e\n\nYou are now authencated as the guest `acme-corp` in your app and can push plug-ins into this guest key.\n\n## Step 4: Deploy a Plugin\n\nNow let’s pretend you’re Acme Inc. (the \"Guest\" in XTP nomenclature) and deploy a plugin. \n\nLet's create a plugin called `loudify` that will reflect back the incoming message but all caps.\nTo generate a new plug-in project for a Slash command, use `plugin init`:\n\n```bash\nxtp plugin init\n```\n\n- Select the `SlashCommand` Extension Point\n- Select TypeScript as the language\n- Set a path for XTP to generate the plugin scaffolding (./loudify)\n- Navigate to the plugin directory/open it an in an IDE\n\nOpen `main.ts` and fill out this implementation for `handleMessageImpl`:\n\n```typescript\n// main.ts\n\nexport function handleMessageImpl(input: Message): Message {\n  return {\n    type: MessageType.text,\n    body: input.body.toUpperCase()\n  }\n}\n```\n\nThis will get called by Yak when it gets the `/loudify` command. So all we're doing returning a new message\nuppercasing the body of the input message.\n\nNow to install the code in Yak, from the plugin directory, run these two commands:\n\n```\n# Build and install the plugin\nxtp plugin push\n```\n\nIf all goes well, then you should be done; rebooting Yak is not required! Typing `/loud` in the\nmessage box should now show that `loudify` is a valid command. And sending a message like `/loudify hello world`\nshould respond with the loudified message.\n\n## Next Steps\n\nHopefully, this guided flow has given you a glimpse of XTP's capabilities and empowered you to add extensibility to your own applications. The possibilities are endless. For more information, explore the product documentation and join us on [Discord](https://extism.org/discord) if you encounter any issues or have ideas to enhance XTP's functionality.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdylibso%2Fxtp-integration-exercise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdylibso%2Fxtp-integration-exercise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdylibso%2Fxtp-integration-exercise/lists"}