@web-applets/sdk 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -2,34 +2,24 @@
2
2
 
3
3
  > An open spec & SDK for creating apps that agents can use.
4
4
 
5
- 🔗 [Applets Repo](https://github.com/unternet-co/community-applets) | 🔗 [Mailing List](https://groups.google.com/a/unternet.co/g/community) | 🔗 [Applets Chat Demo](https://github.com/unternet-co/applets-chat)
5
+ 💌 [Mailing List](https://groups.google.com/a/unternet.co/g/community)
6
6
 
7
7
  ## What is it?
8
8
 
9
- Web Applets is an open specification for building software that both humans and AI can understand and use together. Instead of forcing AI to operate traditional point-and-click apps built for humans, Web Applets creates a new kind of software designed for human-AI collaboration. Think of them a bit like artifacts, but they do stuff!
9
+ **Web Applets is an open specification for building software that both humans and AI can understand and use together.** Instead of forcing AI to operate traditional point-and-click apps built for humans, Web Applets creates a new kind of software designed for human-AI collaboration.
10
10
 
11
- ![Demo of a web applets chatbot](./docs/assets/applets-chat-demo.gif)
12
-
13
- Web Applets are modular pieces of web software that:
14
-
15
- - **Can be used directly by humans with rich, graphical interfaces**
16
- - **Can be understood and operated by AI through a clear protocol**
17
- - **Run locally in your environment, not on distant servers**
18
- - **Share context and state with their environment**
19
- - **Can be freely combined and composed**
11
+ Think of them a bit like Claude artifacts, but they _do stuff_ & _work anywhere_!
20
12
 
21
- Think of any web software you use today - maps, documents, shopping, calendars - and imagine if instead of visiting these as separate websites, you could pull them into your own environment where both you and AI could use them together seamlessly.
13
+ ![Demo of a web applets chatbot](./docs/assets/applets-chat-demo.gif)
22
14
 
23
- ## Key Features
15
+ Think of any web software you use today - maps, documents, shopping, calendars - and imagine if instead of visiting these as separate websites, you could pull them into your own environment where both you and AI could use them together seamlessly. Web applets can do that!
24
16
 
25
- - **Built on Web Standards:** Create applets using familiar web technologies (HTML, CSS, JavaScript)
17
+ - **Built on Web Standards:** Create applets using familiar web technologies (HTML, CSS, JavaScript, React, Vue, etc.)
26
18
  - **AI-Native Protocol:** Applets expose their state and actions in a way AI can understand and use
27
19
  - **Rich Interfaces:** Full support for complex graphical UIs, not just text
28
20
  - **Local-First:** Runs in your environment, keeping your data under your control
29
21
  - **Composable:** Applets can work together, sharing context and state
30
- - **Open Standard:** Designed for interoperability, not platform lock-in
31
-
32
- Web Applets aims to do for AI-enabled software what the web did for documents - create an open platform where anyone can build, share, and connect applications. We believe the future of software should be built on open collaboration, not tight integration with closed platforms.
22
+ - **Open Standard:** Designed for interoperability across clients, not platform lock-in
33
23
 
34
24
  ## Example
35
25
 
@@ -75,6 +65,17 @@ context.ondata = () => {
75
65
 
76
66
  Done! If you load this up in the inspector and introduce yourself, it will respond by greeting you.
77
67
 
68
+ To use this applet, we need to load it in our host web app using the SDK. Assuming the applet lives in our public directory, here's what that might look like:
69
+
70
+ ```js
71
+ const applet = await applets.load('/helloworld.applet');
72
+ applet.onstateupdated = (state) => console.log(state);
73
+ applet.dispatchAction('set_name', { name: 'Web Applets' });
74
+ // { name: 'Web Applets' }
75
+ ```
76
+
77
+ For a live example you can download and play with now, check out the [applets chat demo](https://github.com/unternet-co/applets-chat).
78
+
78
79
  ## Getting started
79
80
 
80
81
  Create a new web app with the applets SDK installed. You can do this quickly using our CLI:
@@ -93,7 +94,7 @@ Inside the generated folder, you'll find a basic web app setup:
93
94
 
94
95
  Now if you run `npx @web-applets/inspector`, you should be able to test out your new applet directly. This applet will now work in any environment where the SDK is installed.
95
96
 
96
- ![A screenshot showing the 'playground' editing UI, with a web applets showing 'Hello, Web Applets'](docs/assets/web-applets-playground.png)
97
+ ![A screenshot showing the 'playground' editing UI, with a web applets showing 'Hello, Web Applets'](docs/assets/web-applets-inspector.png)
97
98
 
98
99
  ## Integrating Web Applets into your client
99
100
 
@@ -136,7 +137,7 @@ applet.data = { name: 'Ada Lovelace' };
136
137
 
137
138
  This is a community project, and we're open to community members discussing the project direction, and submitting code!
138
139
 
139
- To join the conversation, visit the Applets mailing list at [groups.google.com/a/unternet.co/g/community](https://groups.google.com/a/unternet.co/g/community). You can also find more about the company that's kicking off this work at [unternet.co](https://unternet.co)
140
+ To join the conversation, visit the Applets mailing list [here](https://groups.google.com/a/unternet.co/g/community). You can also find more about the company that's kicking off this work at [unternet.co](https://unternet.co)
140
141
 
141
142
  ## License
142
143
 
@@ -1,4 +1,4 @@
1
- import { AppletAction, AppletMessage, ActionParams, AppletManifest, AppletDataEvent, AppletResizeEvent, AppletActionsEvent, AppletMessageRelay, AppletReadyEvent } from './shared';
1
+ import { AppletAction, ActionParams, AppletManifest, AppletDataEvent, AppletResizeEvent, AppletActionsEvent, AppletMessageRelay, AppletReadyEvent } from './shared';
2
2
  export declare function load(url: string, container?: HTMLIFrameElement): Promise<Applet>;
3
3
  export interface AppletOptions {
4
4
  manifest: AppletManifest;
@@ -10,7 +10,7 @@ export declare class Applet<T = any> extends EventTarget {
10
10
  actions: AppletAction[];
11
11
  container: HTMLIFrameElement;
12
12
  constructor(manifest: AppletManifest, targetWindow: Window);
13
- dispatchAction(actionId: string, params?: ActionParams): Promise<AppletMessage>;
13
+ dispatchAction(actionId: string, params?: ActionParams): Promise<import("./shared").AppletMessage>;
14
14
  get data(): T;
15
15
  set data(data: T);
16
16
  get manifest(): AppletManifest;
@@ -1,15 +1,14 @@
1
1
  export interface AppletManifest {
2
2
  name?: string;
3
3
  short_name?: string;
4
- icons: AppletIcons;
4
+ icons: ManifestIcon[];
5
5
  description?: string;
6
- icon?: string;
7
6
  display?: string;
8
7
  start_url?: string;
9
8
  unsafe?: boolean;
10
9
  actions?: AppletAction[];
11
10
  }
12
- export interface AppletIcons {
11
+ export interface ManifestIcon {
13
12
  src: string;
14
13
  purpose?: string;
15
14
  sizes?: string;
@@ -91,7 +90,7 @@ export declare class AppletActionMessage extends AppletMessage {
91
90
  export declare class AppletInitMessage extends AppletMessage {
92
91
  constructor();
93
92
  }
94
- export type AppletMessageType = 'action' | 'actions' | 'data' | 'init' | 'ready' | 'resolve' | 'resize';
93
+ export type AppletMessageType = 'action' | 'actions' | 'data' | 'init' | 'ready' | 'style' | 'resolve' | 'resize';
95
94
  export type AppletMessageCallback = (message: AppletMessage) => Promise<void> | void;
96
95
  export declare class AppletDataEvent extends Event {
97
96
  data: any;
@@ -9,10 +9,14 @@ export async function loadManifest(pageUrl) {
9
9
  const parser = new DOMParser();
10
10
  const doc = parser.parseFromString(html, 'text/html');
11
11
  const linkElem = doc.querySelector('link[rel="manifest"]');
12
- const manifestUrl = parseUrl(linkElem.href);
12
+ const href = linkElem.getAttribute('href');
13
+ const manifestUrl = parseUrl(href, pageUrl);
13
14
  const manifestRequest = await fetch(manifestUrl);
14
15
  manifest = await manifestRequest.json();
15
- // TODO: Add verification this is a valid manifest
16
+ manifest.icons = manifest.icons.map((icon) => {
17
+ icon.src = parseUrl(icon.src, pageUrl);
18
+ return icon;
19
+ });
16
20
  }
17
21
  catch (e) {
18
22
  return;
package/dist/utils.js CHANGED
@@ -1,8 +1,24 @@
1
1
  // Adds http/https to URLs, and prepends with window location if relative
2
2
  export function parseUrl(url, base) {
3
- if (url)
4
- url = URL.parse(url, base ?? window.location.href).href;
5
- return trimTrailingSlash(url);
3
+ if (!url)
4
+ return '';
5
+ try {
6
+ // If the base URL is provided, ensure it has a trailing slash for proper path resolution
7
+ if (base) {
8
+ // Don't add trailing slash if the base already ends with a file extension
9
+ if (!base.match(/\.[a-zA-Z0-9]+$/)) {
10
+ base = base.endsWith('/') ? base : base + '/';
11
+ }
12
+ }
13
+ // Use URL constructor to properly resolve relative paths
14
+ const resolvedUrl = new URL(url, base ?? window.location.href);
15
+ return trimTrailingSlash(resolvedUrl.href);
16
+ }
17
+ catch (e) {
18
+ // Return original URL if parsing fails
19
+ console.warn('Failed to parse URL:', e);
20
+ return url;
21
+ }
6
22
  }
7
23
  function trimTrailingSlash(url) {
8
24
  if (url.endsWith('/')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web-applets/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The Web Applets SDK, for creating & hosting Web Applets.",
5
5
  "author": "Rupert Manfredi <rupert@unternet.co>",
6
6
  "license": "MIT",
@@ -1,31 +0,0 @@
1
- import { AppletAction, AppletMessage, ActionParams, AppletManifest, AppletDataEvent, AppletResizeEvent, AppletActionsEvent, AppletMessageRelay } from './shared';
2
- interface LoadOpts {
3
- unsafe?: boolean;
4
- }
5
- declare function load(url: string, container?: HTMLIFrameElement, opts?: LoadOpts): Promise<Applet>;
6
- interface AppletOptions {
7
- manifest: AppletManifest;
8
- container: HTMLIFrameElement;
9
- }
10
- declare class Applet<T = any> extends EventTarget {
11
- #private;
12
- messageRelay: AppletMessageRelay;
13
- url: string;
14
- actions: AppletAction[];
15
- container: HTMLIFrameElement;
16
- type: string;
17
- constructor(options: AppletOptions);
18
- initializeListeners(): void;
19
- get data(): T;
20
- set data(data: T);
21
- get manifest(): AppletManifest;
22
- onresize(event: AppletResizeEvent): void;
23
- onactions(event: AppletActionsEvent): void;
24
- ondata(event: AppletDataEvent): void;
25
- disconnect(): void;
26
- dispatchAction(actionId: string, params: ActionParams): Promise<AppletMessage>;
27
- }
28
- export declare const applets: {
29
- load: typeof load;
30
- };
31
- export { Applet };
package/dist/core/host.js DELETED
@@ -1,134 +0,0 @@
1
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
- if (kind === "m") throw new TypeError("Private method is not writable");
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _Applet_manifest, _Applet_data;
13
- import { AppletMessage, AppletDataMessage, AppletInitMessage, AppletDataEvent, AppletResizeEvent, AppletActionsEvent, AppletMessageRelay, } from './shared';
14
- import { parseUrl } from '../lib/utils';
15
- // Container for initializing applets without an explicit container
16
- const hiddenContainer = document.createElement('iframe');
17
- hiddenContainer.style.display = 'none';
18
- document.body.appendChild(hiddenContainer);
19
- const defaultOpts = {
20
- unsafe: false,
21
- };
22
- // Load an applet object from a URL
23
- async function load(url, container, opts) {
24
- const _opts = Object.assign(defaultOpts, opts ?? {});
25
- if (!container)
26
- container = hiddenContainer;
27
- url = parseUrl(url);
28
- const manifest = await loadManifest(`${url}`);
29
- // If unsafe enabled, allow same origin sandbox
30
- // This is required for e.g. YouTube embeds
31
- if (_opts.unsafe && manifest.unsafe) {
32
- container.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
33
- }
34
- else {
35
- container.setAttribute('sandbox', 'allow-scripts allow-forms');
36
- }
37
- // Load the applet
38
- const applet = new Applet({
39
- manifest,
40
- container,
41
- });
42
- return new Promise((resolve) => {
43
- applet.messageRelay.on('ready', () => {
44
- resolve(applet);
45
- });
46
- });
47
- }
48
- class Applet extends EventTarget {
49
- constructor(options) {
50
- super();
51
- this.actions = [];
52
- _Applet_manifest.set(this, void 0);
53
- this.type = 'host';
54
- _Applet_data.set(this, void 0);
55
- this.container = options.container;
56
- this.container.src = options.manifest.start_url;
57
- this.messageRelay = new AppletMessageRelay(this.container.contentWindow);
58
- __classPrivateFieldSet(this, _Applet_manifest, options.manifest, "f");
59
- this.initializeListeners();
60
- this.messageRelay.on('ready', () => {
61
- this.messageRelay.send(new AppletInitMessage({ manifest: options.manifest }));
62
- });
63
- }
64
- initializeListeners() {
65
- this.messageRelay.on('data', (message) => {
66
- __classPrivateFieldSet(this, _Applet_data, message.data, "f");
67
- const dataEvent = new AppletDataEvent({ data: message.data });
68
- if (typeof this.ondata === 'function')
69
- this.ondata(dataEvent);
70
- this.dispatchEvent(dataEvent);
71
- });
72
- this.messageRelay.on('resize', (message) => {
73
- const resizeEvent = new AppletResizeEvent({
74
- dimensions: message.dimensions,
75
- });
76
- if (typeof this.onresize === 'function')
77
- this.onresize(resizeEvent);
78
- this.dispatchEvent(resizeEvent);
79
- });
80
- this.messageRelay.on('actions', (message) => {
81
- this.actions = message.actions;
82
- const actionsEvent = new AppletActionsEvent({ actions: message.actions });
83
- if (typeof this.onactions === 'function')
84
- this.onactions(actionsEvent);
85
- this.dispatchEvent(actionsEvent);
86
- });
87
- }
88
- get data() {
89
- return __classPrivateFieldGet(this, _Applet_data, "f");
90
- }
91
- set data(data) {
92
- __classPrivateFieldSet(this, _Applet_data, data, "f");
93
- this.messageRelay.send(new AppletDataMessage({ data }));
94
- }
95
- get manifest() {
96
- return __classPrivateFieldGet(this, _Applet_manifest, "f");
97
- }
98
- onresize(event) { }
99
- onactions(event) { }
100
- ondata(event) { }
101
- disconnect() {
102
- this.container.src = 'about:blank';
103
- }
104
- async dispatchAction(actionId, params) {
105
- const actionMessage = new AppletMessage('action', {
106
- actionId,
107
- params,
108
- });
109
- return await this.messageRelay.send(actionMessage);
110
- }
111
- }
112
- _Applet_manifest = new WeakMap(), _Applet_data = new WeakMap();
113
- // Loads a manifest and parses the JSON
114
- async function loadManifest(baseUrl) {
115
- baseUrl = parseUrl(baseUrl);
116
- let manifest;
117
- try {
118
- const request = await fetch(`${baseUrl}/manifest.json`);
119
- manifest = await request.json();
120
- // TODO: Add verification this is a valid manifest
121
- }
122
- catch (e) {
123
- console.error(e.message);
124
- }
125
- manifest.start_url = manifest.start_url
126
- ? parseUrl(manifest.start_url, baseUrl)
127
- : baseUrl;
128
- return manifest;
129
- }
130
- // Exports
131
- export const applets = {
132
- load,
133
- };
134
- export { Applet };
@@ -1,17 +0,0 @@
1
- import { AppletAction } from '../core/shared';
2
- export declare function parseUrl(url: string, base?: string): string;
3
- export declare function createOpenAISchemaForAction(action: AppletAction): {
4
- strict: boolean;
5
- name: string;
6
- schema: {
7
- type: string;
8
- required: string[];
9
- properties: {
10
- id: {
11
- type: string;
12
- };
13
- params: import("../core/shared").JSONSchemaProperties;
14
- };
15
- additionalProperties: boolean;
16
- };
17
- };
package/dist/lib/utils.js DELETED
@@ -1,37 +0,0 @@
1
- // Adds http/https to URLs, and prepends with window location if relative
2
- export function parseUrl(url, base) {
3
- if (['http', 'https'].includes(url.split('://')[0])) {
4
- return url;
5
- }
6
- let path = trimSlashes(url);
7
- url = `${base || window.location.origin}/${path}`;
8
- return url;
9
- }
10
- function trimSlashes(str) {
11
- return str.replace(/^\/+|\/+$/g, '');
12
- }
13
- export function createOpenAISchemaForAction(action) {
14
- return {
15
- strict: true,
16
- name: 'action_schema',
17
- schema: {
18
- type: 'object',
19
- required: Object.keys(action),
20
- properties: {
21
- id: { type: 'string' },
22
- params: action.params,
23
- },
24
- additionalProperties: false,
25
- },
26
- };
27
- }
28
- // export async function loadAppletManifest(url: string): Promise<AppletManifest> {
29
- // url = parseUrl(url);
30
- // const request = await fetch(`${url}/manifest.json`);
31
- // const appletManifest = await request.json();
32
- // if (appletManifest.type !== 'applet') {
33
- // throw new Error("URL doesn't point to a valid applet manifest.");
34
- // }
35
- // appletManifest.entrypoint = parseUrl(appletManifest.entrypoint, url);
36
- // return appletManifest;
37
- // }