Follow this guide to get an early look at using Pieces OS in your own development environment. You can use this to get familiar with some key terms and endpoints: assets
, asset
, connect
, format
, formats
, and create
. There are other topics that are touched on, and further expansion on this starter project is coming soon.
See the NPM package here:
Read our Current Documentation:
- https://docs.pieces.app
- Developer docs coming soon
- Operating System Support
- Installing
- Running Your Project
- Create Connect() Function
- Connecting your Application
- Asset + /assets
- /assets/create
- /assets/snapshot
- /asset/update
- Refresh Your Snapshot
- /assets/delete
Currently, Pieces OS is utilized as the primary backend service with Pieces for Developers that powers all of the features that can be used there. Both programs are designed for full support by all operating systems, although our Linux Platform is available, it leans towards a 'heavily supported beta' and may experience incremental issues on specific flavors of linux.
You can choose to follow the following steps to configure your project by hand, or you can use npx create-react-app <your-app-name>
instead.
When developing on the Pieces platform, you need two primary things:
- Download the Pieces OS application
- Install the npm package
Pieces OS runs in the background of your computer and serves as a hub for all plugins and extensions developed by the team. In order to utilize your own Server locally and support all the functionality that powers things like Global Search, Copilot Chats, Asset Saving, context, and more.
Select the right version to download Pieces OS for your operating system:
- macOS - [Compatible with macOS 11 Big Sur or higher]
- Windows - [Compatible with Windows 10 version 1809 or higher]
- Linux - [Compatible with Ubuntu 18 or Higher]
You can also visit our user facing documentation to learn more about different features that are available now to give you an idea of some of the things that you can potentially do.
To install the current version of node
, simply run the command:
nvm install --lts
To check what version of node
that's currently running on your machine, run the command:
node --version
To install a specific version of node
, you can run the command:
nvm install [version-of-node]
To switch between different versions of node
, you can run the command:
nvm use [version-of-node]
npm
comes installed when you use nvm
to install node
.
Using npm:
npm install @pieces.app/pieces-os-client
Using pnpm:
pnpm add @pieces.app/pieces-os-client
After you install the package, you can import the library into your file(s) using require
:
const pieces = require('@pieces.app/pieces-os-client')
or you can import the package using import
as well:
import * as pieces from '@pieces.app/pieces-os-client'
Recommendation The order that npm packages are saved and added to your dependencies is important and will affect your installation flow. This slowed me down for quite a bit.
If you are having issues with your installation, it is likely due to a conflict in Typescript versions -
npm uninstall typescript
- then go back and perform all other npm installations before reinstalling typescript again.
Let's get started with the base of your new React project where we will learn about manipulating Pieces OS and creating our own assets locally on device.
{
"compilerOptions": {
"target": "es2020",
"module": "CommonJS",
"allowJs": true,
"checkJs": true,
"maxNodeModuleJsDepth": 1,
"forceConsistentCasingInFileNames": true,
"strict": false,
"skipLibCheck": true,
"jsx": "react"
},
"exclude": ["node_modules"]
}
npm install typescript
I also recommend installing ts-node
to get the proper commands running and to run individual test files directly from cmd line. Here is that:
npm install ts-node
Then getting the types package for node can also be super beneficially and in some cases needed to work in this environment:
npm install -D tslib @types/node
I ended up also installing a few react libraries to get a more visual experience while learning about the api itself.
Here are those npm commands for @types
:
npm install @types/react && npm install @types/react-dom
Along with the typing, you will need to install the full packages for react, react-dom, and react scripts to properly get started in this project.
npm install react && npm install react-dom && npm install react-scripts
And last but not least it's a good idea to add a few scripts into your package.json
to help with development:
{
"scripts": {
"dev": "ts-node index.tsx",
"clean": "rm -r node_modules && rm package-lock.json",
"start": "react-scripts start",
"build": "react-scripts build"
}
}
Don't Forget You can run any script that you add here via
npm run <your-script>
. For example to run "start" you would simple runnpm run start
Next you can go ahead and create a new directory called public
that will hold your index.html
file where your entry point exists. Create the file inside of public and save it there. You do not have to add anything to the index.html
file at this time, as we will come back to this later. If you would like, you can add the following as a placeholder for now:
<!DOCTYPE html>
<html lang="en">
</html>
Now that the initial .html
file has been created, you can start to work on your src directory and getting the rest of your core files added to the project.
Inside the src
directory, add two files:
singleton.ts
- single instance container for a few global functions and featuresindex.tsx
- where the core info is and where we are going to be spending most of the time during this project following the setup.
Once you open index.tsx
you should follow these steps to get your base Application window created:
-
Import the full react library at the top of your file, along with a single import from
react-dom
:import * as React from "react"; import { render } from 'react-dom';
-
Follow that with the full App() function and main run of the application:
function App(): React.JSX.Element { return ( <div> <h1>Hello Pieces Dev Community :)</h1> </div> )}
-
And then add these last two lines to target the
root
element by using the ID that is on it:const rootElement = document.getElementById("root"); const root = createRoot(rootElement);
Helpful tip When working in this environment I noticed some @babel errors during the build process (or running
npm run start
) and found this workaround that you can install via npm as well here.
Everything has been added. We are nearly there and will need to perform a final few checks before starting our dev project.
- Be sure that Pieces OS is running
- Double check that the port is
localhost:1000
- (optional) Run another npm install (because it never hurts)
Now that everything is ready to go, lets run this command here:
# remember that we added this to the scripts as: "start": "react-scripts start"
npm run start
And after a few seconds you should be able to see in your Chrome browser (or your primary browser) a blank window that looks like this:
You have now successfully set up your dev environment, and will be ready to test different endpoints inside of Pieces OS.
When Pieces OS is running in the background of your machine, it is communicating with other local applications that use Pieces software, and up until recently only supporting internally built tools.
As each plugin, extension, or application initializes they 'reach out' to Pieces OS and authenticate with the application themselves. There are a number of application formats that we support and provide for each of our applications. ==When developing on Pieces OS, you can use "LOCAL_DEV" to avoid any issues with other applications.==
The application
model describes what application a format or analytics event originated from. This is passed along when initializing your dev environment and creates a connection to Pieces OS.
To create the Application
object for your project, you will need to make sure that you have the following three things:
- Create a
tracked_application
json object to hold your post request data - Define your
url
for the/connect
function - Output using
console.log()
following yourconnect()
method is complete
We will go over the different usages in each of these steps one at a time to bring up any specificities that may need a deeper explanation.
Connecting your application here is as easy as a single POST request and can be done via the Response interface of the Fetch Api. Remember that you can name this whatever you would like to, just be sure to include the updated variable name in the options
down below.
When creating the
tracked_application
item, you will need to use a type that is not available inside the currentnpm_deployment
.This structure is the same as the tracked_application full example you see here below, and the only difference from the unavailable type
SeededTrackedApplication
and the available typeTrackedApplication
isid: number
.
First lets take a look at the tracked_application
object:
const tracked_application = {
name: Pieces.ApplicationNameEnum.Brave,
version: '0.0.1',
platform: Pieces.PlatformEnum.Macos,
}
- name: Brave
- platform: Depending on your current environment, you need to set the platform parameter to match your current operating system. Select between
.Macos
,.Windows
,.Linux
Imports Be sure to double-check that you have the following import added to the first few lines of your
index.tsx
file if you have not already:import * as Pieces from "@pieces.app/pieces-os-client";
When your program starts, it needs to connect to Pieces OS to gain access to any functional data and to exchange information on the localhost:1000
route. Now that you have your tracked_application
- lets get into the details.
Start by defining you connect function and add the initial /connect
route to your function as the url
variable and then attach the options
object. Include your tracked_application
object passed into a JSON.stringify()
method under the application
parameter like so:
async function connect(): Promise<Boolean> {
const url: string = 'http://localhost:1000/connect';
const options: {method: string, body: string} = {
method: 'POST',
body: JSON.stringify({ application: tracked_application}
),
}
}
Now let's add few more things to this file
_flag: Boolean
- for marking the success or failure of the try catch- try, catch
response
- for capturing the fetch response back from OS Serverdata
- for storing the data and logging ite
- error that is coming back if the response fails
This will just run the connect function and then log the response in your console, inside your browser:
connect().then( __ => console.log("Response back from /connect:", __ ));
Below this final line should be:
function App() ...
const rootElement ...
root.render(...)
Here is the entire connection function for you to double-check your work:
async function connect(): Promise<JSON> {
const url: string = 'http://localhost:1000/connect';
const options: {method: string, body: string} = {
method: 'POST',
body: JSON.stringify({ application: tracked_application}
),
}
let _output: Promise<JSON>;
try {
const response: Response = await fetch(url, options);
const data: Promise<JSON> = await response.json();
_output = data;
return _output;
} catch (e) {
console.error(e);
}
}
Now that everything has been correctly configured (fingers crossed) you can run your sample application and connect to Pieces OS for the first time.
Inside your terminal at the root directory of your project, use NPM to run one of the scripts that we added to the package.json file called "start":
npm run start
And you should have the same content in the main browser window as before once this completes. But if you open up your inspector using cmd+option+i
or ctrl+shift+c
you will see this inside of your console:
This includes both the full OS response object with all the data that you will need to get going with other endpoints, and your application is now connected and ready to go for the rest of your exploration and discovery!
Follow along with these next steps to learn about assets and formats, two things that are very important for managing any form of data with Pieces OS.
Asset is a very important models who's primary purpose is to manage the seeded data that comes in to the application, and is stored inside of Pieces OS. Each asset is a identifiable piece of saved data, or pre-seeded data.
Assets is equally important, but instead of containing a single asset with parameters storing data on it, Assets serves as the list of type: Asset
objects that are stored there. Also you will find the operations for adding, deleting, searching, and other functions that are related to referencing a number of different snippets to make a comparison. For instance:
If I want to create a snippet (lets call it
var
), I need to send this to the masterAssets
list, you would first createvar
itself with the proper formats and data added to thevar
object, then send the newly created SeededAsset over to the Assets list (assets/create). Then you will receive the asset back as the response from the server. Cool, right?
HEY! Read this. Traditionally,
Assets
is a linear list of flatAsset
objects stored in an array or list.You can use identifiers to get a specific asset from the asset list called a UUID. But you'll learn more about that later on.
Initially when creating your application, you will have no snippets saved to your project, will not be signed in, and you will have not completed onboarding. These are properties that you may see during this creation.
Check out
localhost:1000/assets
while Pieces OS is running to see the empty object that is there.
While creating an asset, there are some required parameters that you will need to be sure to include the proper format data.
Explaining Format
For each
Asset
object, each required parameter must be included, and the Asset must be seeded before it is sent to be created.
This seed data will become the asset. You can use this structure to provide data to Pieces OS, and will include fewer parameters than what you will get back in your response. Lets get started with the seeded asset formatting before we pass this over to /Assets
.
At the top level of this object you will see:
schema
metadata
application
(required)format
(required)discovered
available
Schema
, metadata
, discovered
and available
are all parameters that have extensive use cases, but for now we are going to focus on application
& format
- the two required formats for this object.
With each call you need to include your application object that you created earlier - and we can do this inside of the .then() following the return from connect()
which is defined here:
connect().then(__ => {
// parsing this value and restringifying it properly gives it its type.
full_context = __;
let _t = JSON.parse(JSON.stringify(full_context));
applicationData = _t.application;
console.log('Application Data: ', applicationData);
})
Now before continuing forward, we will need to prepare the create()
function to connect to the proper Assets/create
endpoint. Create slightly differs from connect, since previously our json object did not require any new data that was returned back from the server. In this case we will need to include the application data that was returned back from our initial call to /connect
.
The create()
function needs to accomplish a few things:
- Create a new asset using our simple
SeededAsset
configuration that we just created as theseed
object - Send a POST request to the new
http://localhost:1000/assets/create
with out data - Return the response back after this is completed
Here is what the create()
function looks like in its entirety:
function createAsset() {
let _seededAsset: SeededAsset = {
application: applicationData,
format: {
fragment: {
string: { raw: data },
},
},
metadata: {
name: name
}
}
// create your seed
let _seed: Pieces.Seed = {
asset: _seededAsset,
type: SeedTypeEnum.Asset
}
}
Now that we have the create function created, all that is left is to call create()
and log our new asset to the console!
You can add this final call to the end of the connect.then()
:
// make your api call.
new Pieces.AssetsApi().assetsCreateNewAsset({seed: _seed}).then(_a => {
console.log("well howdy", _a);
})
Once you receive your response back from Pieces OS, you will notice the drastic difference in the response back here. There is quite a long list of parameters that you can store alongside your assets to make them more powerful.
The response back will look similar to the following: https://jwaf.pieces.cloud/?p=24e242a85e
Now when you follow this guide, you will be receiving this data back from inside your console in the browser. But if you would like to view your data incrementally through the full browser window, you can navigate to http://localhost:1000/assets
to view a full list of snippets that have been saved.
As you move through your assets there are a number of things you may need your full assets list for without needing all of your data. (for example: the list view and carousel views in Pieces for Developers)
To get your assets snapshot, you can use this to list each asset:
new Pieces.AssetsApi().assetsSnapshot({}).then(_assetList => {
for (let i = 0; i < _assetList.iterable.length; i++) {
// this will log the asset to the console.
console.log(_assetsList[i]);
}
})
Each asset will have and ID on it that can be used to match a singular asset here. Very useful when trying to get a specific asset from your full assetsSnapshot.
Individual assets can be manipulated with a number of different properties and metadata. You can add titles, annotations, tags, links, anchors, and much more all through this single endpoint. To use it properly first use the assetSnapshot to get your asset using its ID property, and store your asset in a variable in your .then()
. You can then adjust any of the properties on this asset you have stored on _asset
, then pass into requestParameters
on the assetUpdate
endpoint.
Check out this code block with an example of how to rename an asset:
function renameAsset(_name: string, _id: String){
new Pieces.AssetsApi().assetsSnapshot({}).then(_assetList => {
for (let i = 0; i < _assetList.iterable.length; i++) {
if (_assetList.iterable[i].id == _id) {
let _asset = _assetList.iterable[i];
_asset.name = _name;
new Pieces.AssetApi().assetUpdate({asset: _asset}).then(_updated => {
console.log("updated:", _updated);
})
}
}
})
}
Then inside your UI you can connect the renameAsset() function to your button and text inputs. Note that in order to perform this operation you need both the id of the asset, and also the new name that you would like to update you asset to.
The code block below includes the state management for the text input, along with the button that initiates the asset update as well.
This is what that looks like:
function RenameAssetInput() {
const [name, setNameValue] = useState('');
const [id, setIdValue] = useState('');
const handleNameChange = (event) => {
setNameValue(event.target.value);
};
const handleIdChange = (event) => {
setIdValue(event.target.value);
};
return (
<>
<p>Name:</p>
<input value={name} style={{ width: '450px', verticalAlign: 'top' }} onChange={handleNameChange} />
<p>ID:</p>
<input value={id} style={{ width: '450px', verticalAlign: 'top' }} onChange={handleIdChange} />
<button style={{ marginTop: '10px', maxWidth: '200px' }} onClick={() => renameAsset(name, id)}>Rename Snippet</button>
</>
);
}
In order to get updates to your assetSnapshot as a whole, you may need to update you local list in order to reflect changes that come from Pieces OS and give information on the assets stored there. In order to perform a refresh you can use this code block here:
const [array, setArray] = useState([]);
const refresh = (_newAsset: LocalAsset) => {
setArray(prevArray => [...prevArray, _newAsset])
}
function refreshSnippetList() {
new Pieces.AssetsApi().assetsSnapshot({}).then((assets) => {
// loop through your assets.
for (let i = 0; i < assets.iterable.length; i++) {
let _local: LocalAsset = {
id: assets.iterable[i].id,
name: assets.iterable[i].name,
classification: assets.iterable[i].original.reference.classification.specific
}
refresh(_local);
}
})
}
I added this to the top level for reactivity inside the main App()
call. You can choose to place this in a different location if you are not in need of any reactive data.
Assets can be deleted from your Assets list entirely by passing them into the assetsDeleteAsset
endpoint. Just like the above example to rename a specific asset, you will need the ID of the asset that you are trying to remove. In order to get that you will need to use assetSnapshot in tandem with your delete endpoint:
new Pieces.AssetsApi().assetsSnapshot({}).then(_assetList => {
for (let i = 0; i < _assetList.iterable.length; i++) {
if (_assetList.iterable[i].id == _id) {
new Pieces.AssetsApi().assetsDeleteAsset({asset: _assetList.iterable[i].id }).then(_ => console.log("delete confirmed!"))
}
}
})
After a successful delete, you may have to reload your browser window in order to see the updated snippet list.
Recommendation
We use JSON Viewer internally when developing and recommend using some form of web based extension that assists with reading JSON DATA
This is a very simple guide on how to get up and running using the @pieces.app/pieces-os-client npm package and create a web environment that you can build on top of. Fork this repo to get started and learn about the depth of possibilities you have with Pieces OS.
More guides will be coming soon around:
- Creating a personal Copilot that understands your context
- Examples on
/search
endpoints - Improvements to the project structure + UI
- Testing custom implementations and combining endpoints
- ...more!