@framers/agentos-ext-widget-generator 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Framers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @module WidgetFileManager
3
+ *
4
+ * Manages the local widgets directory for generated HTML widgets. Provides
5
+ * methods to save widget HTML to disk, list existing widgets, delete files,
6
+ * resolve full paths, and construct view/download URLs.
7
+ *
8
+ * Files are stored under `{workspaceDir}/widgets/` with timestamped,
9
+ * slugified filenames to avoid collisions and ensure filesystem safety.
10
+ */
11
+ /**
12
+ * Metadata returned when listing files in the widgets directory.
13
+ */
14
+ export interface WidgetFileEntry {
15
+ /** The file's base name (e.g. `2026-03-28T12-00-00-000Z-my-widget.html`). */
16
+ filename: string;
17
+ /** File size in bytes. */
18
+ sizeBytes: number;
19
+ /** ISO 8601 timestamp of when the file was created (from filesystem). */
20
+ createdAt: string;
21
+ }
22
+ /**
23
+ * Result returned after successfully saving a widget to disk.
24
+ */
25
+ export interface WidgetSaveResult {
26
+ /** Absolute path to the saved file. */
27
+ filePath: string;
28
+ /** The generated filename (basename only). */
29
+ filename: string;
30
+ }
31
+ /**
32
+ * Manages the widgets directory for generated HTML widgets. Handles saving,
33
+ * listing, deleting, and resolving paths for widget files.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const manager = new WidgetFileManager('/home/agent/workspace', 3777);
38
+ * const { filePath, filename } = await manager.save('<html>...</html>', 'My Chart');
39
+ * const url = manager.getWidgetUrl(filename);
40
+ * ```
41
+ */
42
+ export declare class WidgetFileManager {
43
+ /** Absolute path to the widgets directory. */
44
+ private readonly widgetsDir;
45
+ /** Port used for constructing view and download URLs. */
46
+ private readonly serverPort;
47
+ /**
48
+ * Create a new WidgetFileManager instance.
49
+ *
50
+ * @param workspaceDir - Root workspace directory for the agent. The
51
+ * widgets directory will be created as a subdirectory named `widgets`.
52
+ * @param serverPort - Optional port number for constructing localhost
53
+ * view and download URLs. Defaults to `3777`.
54
+ */
55
+ constructor(workspaceDir: string, serverPort?: number);
56
+ /**
57
+ * Save an HTML widget to the widgets directory.
58
+ *
59
+ * Creates the widgets directory if it does not already exist. The filename
60
+ * is generated from the current ISO timestamp and a slugified version of
61
+ * the widget title, ensuring uniqueness and filesystem safety.
62
+ *
63
+ * @param html - The complete HTML content to write.
64
+ * @param title - The widget title, used to derive the filename slug.
65
+ * @returns An object containing the absolute `filePath` and `filename`.
66
+ */
67
+ save(html: string, title: string): Promise<WidgetSaveResult>;
68
+ /**
69
+ * List all widget files in the widgets directory with their metadata.
70
+ *
71
+ * Returns an empty array if the widgets directory does not exist or
72
+ * contains no files. Non-file entries (directories, symlinks) are
73
+ * silently skipped.
74
+ *
75
+ * @returns An array of {@link WidgetFileEntry} objects sorted by filename
76
+ * (newest first due to the timestamp prefix convention).
77
+ */
78
+ list(): Promise<WidgetFileEntry[]>;
79
+ /**
80
+ * Remove a widget file from the widgets directory.
81
+ *
82
+ * @param filename - The basename of the file to delete.
83
+ * @returns `true` if the file was successfully deleted, `false` if it
84
+ * did not exist or could not be removed.
85
+ */
86
+ remove(filename: string): Promise<boolean>;
87
+ /**
88
+ * Resolve a filename to its absolute path in the widgets directory.
89
+ *
90
+ * @param filename - The basename of the file to resolve.
91
+ * @returns The absolute file path if the file exists, or `null` if not found.
92
+ */
93
+ resolve(filename: string): string | null;
94
+ /**
95
+ * Construct a view URL for the given widget filename.
96
+ *
97
+ * Uses the configured server port to build a localhost URL. In
98
+ * production deployments the URL scheme would be replaced by a
99
+ * reverse-proxy or CDN URL.
100
+ *
101
+ * @param filename - The basename of the widget file.
102
+ * @returns A fully-qualified HTTP URL pointing to the widget.
103
+ */
104
+ getWidgetUrl(filename: string): string;
105
+ /**
106
+ * Construct a download URL for the given widget filename.
107
+ *
108
+ * Returns the same URL as {@link getWidgetUrl} since the widget is
109
+ * a self-contained HTML file that can be both viewed and downloaded.
110
+ *
111
+ * @param filename - The basename of the widget file.
112
+ * @returns A fully-qualified HTTP URL pointing to the file.
113
+ */
114
+ getDownloadUrl(filename: string): string;
115
+ /**
116
+ * Convert a title string into a URL- and filesystem-safe slug.
117
+ *
118
+ * Transforms the input to lowercase, replaces non-alphanumeric
119
+ * characters with hyphens, collapses consecutive hyphens, and trims
120
+ * leading/trailing hyphens.
121
+ *
122
+ * @param title - The raw title string to slugify.
123
+ * @returns A clean, lowercase slug suitable for filenames.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * slugify('3D Solar System!') // '3d-solar-system'
128
+ * ```
129
+ */
130
+ private slugify;
131
+ }
132
+ //# sourceMappingURL=WidgetFileManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetFileManager.d.ts","sourceRoot":"","sources":["../src/WidgetFileManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IAEjB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAElB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IAEjB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC;;;;;;;OAOG;gBACS,YAAY,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;IAKrD;;;;;;;;;;OAUG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAelE;;;;;;;;;OASG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAgCxC;;;;;;OAMG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAehD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKxC;;;;;;;;;OASG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAItC;;;;;;;;OAQG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIxC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,OAAO;CAOhB"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @module WidgetFileManager
3
+ *
4
+ * Manages the local widgets directory for generated HTML widgets. Provides
5
+ * methods to save widget HTML to disk, list existing widgets, delete files,
6
+ * resolve full paths, and construct view/download URLs.
7
+ *
8
+ * Files are stored under `{workspaceDir}/widgets/` with timestamped,
9
+ * slugified filenames to avoid collisions and ensure filesystem safety.
10
+ */
11
+ import { mkdir, writeFile, readdir, stat, unlink } from 'node:fs/promises';
12
+ import { existsSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ /**
15
+ * Manages the widgets directory for generated HTML widgets. Handles saving,
16
+ * listing, deleting, and resolving paths for widget files.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const manager = new WidgetFileManager('/home/agent/workspace', 3777);
21
+ * const { filePath, filename } = await manager.save('<html>...</html>', 'My Chart');
22
+ * const url = manager.getWidgetUrl(filename);
23
+ * ```
24
+ */
25
+ export class WidgetFileManager {
26
+ /** Absolute path to the widgets directory. */
27
+ widgetsDir;
28
+ /** Port used for constructing view and download URLs. */
29
+ serverPort;
30
+ /**
31
+ * Create a new WidgetFileManager instance.
32
+ *
33
+ * @param workspaceDir - Root workspace directory for the agent. The
34
+ * widgets directory will be created as a subdirectory named `widgets`.
35
+ * @param serverPort - Optional port number for constructing localhost
36
+ * view and download URLs. Defaults to `3777`.
37
+ */
38
+ constructor(workspaceDir, serverPort) {
39
+ this.widgetsDir = join(workspaceDir, 'widgets');
40
+ this.serverPort = serverPort ?? 3777;
41
+ }
42
+ /**
43
+ * Save an HTML widget to the widgets directory.
44
+ *
45
+ * Creates the widgets directory if it does not already exist. The filename
46
+ * is generated from the current ISO timestamp and a slugified version of
47
+ * the widget title, ensuring uniqueness and filesystem safety.
48
+ *
49
+ * @param html - The complete HTML content to write.
50
+ * @param title - The widget title, used to derive the filename slug.
51
+ * @returns An object containing the absolute `filePath` and `filename`.
52
+ */
53
+ async save(html, title) {
54
+ if (!existsSync(this.widgetsDir)) {
55
+ await mkdir(this.widgetsDir, { recursive: true });
56
+ }
57
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
58
+ const slug = this.slugify(title);
59
+ const filename = `${timestamp}-${slug}.html`;
60
+ const filePath = join(this.widgetsDir, filename);
61
+ await writeFile(filePath, html, 'utf-8');
62
+ return { filePath, filename };
63
+ }
64
+ /**
65
+ * List all widget files in the widgets directory with their metadata.
66
+ *
67
+ * Returns an empty array if the widgets directory does not exist or
68
+ * contains no files. Non-file entries (directories, symlinks) are
69
+ * silently skipped.
70
+ *
71
+ * @returns An array of {@link WidgetFileEntry} objects sorted by filename
72
+ * (newest first due to the timestamp prefix convention).
73
+ */
74
+ async list() {
75
+ if (!existsSync(this.widgetsDir)) {
76
+ return [];
77
+ }
78
+ const entries = await readdir(this.widgetsDir);
79
+ const results = [];
80
+ for (const entry of entries) {
81
+ const fullPath = join(this.widgetsDir, entry);
82
+ try {
83
+ const fileStat = await stat(fullPath);
84
+ if (!fileStat.isFile()) {
85
+ continue;
86
+ }
87
+ results.push({
88
+ filename: entry,
89
+ sizeBytes: fileStat.size,
90
+ createdAt: fileStat.birthtime.toISOString(),
91
+ });
92
+ }
93
+ catch {
94
+ // Skip files that can't be stat'd (e.g. permission errors)
95
+ continue;
96
+ }
97
+ }
98
+ return results;
99
+ }
100
+ /**
101
+ * Remove a widget file from the widgets directory.
102
+ *
103
+ * @param filename - The basename of the file to delete.
104
+ * @returns `true` if the file was successfully deleted, `false` if it
105
+ * did not exist or could not be removed.
106
+ */
107
+ async remove(filename) {
108
+ const fullPath = join(this.widgetsDir, filename);
109
+ if (!existsSync(fullPath)) {
110
+ return false;
111
+ }
112
+ try {
113
+ await unlink(fullPath);
114
+ return true;
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ }
120
+ /**
121
+ * Resolve a filename to its absolute path in the widgets directory.
122
+ *
123
+ * @param filename - The basename of the file to resolve.
124
+ * @returns The absolute file path if the file exists, or `null` if not found.
125
+ */
126
+ resolve(filename) {
127
+ const fullPath = join(this.widgetsDir, filename);
128
+ return existsSync(fullPath) ? fullPath : null;
129
+ }
130
+ /**
131
+ * Construct a view URL for the given widget filename.
132
+ *
133
+ * Uses the configured server port to build a localhost URL. In
134
+ * production deployments the URL scheme would be replaced by a
135
+ * reverse-proxy or CDN URL.
136
+ *
137
+ * @param filename - The basename of the widget file.
138
+ * @returns A fully-qualified HTTP URL pointing to the widget.
139
+ */
140
+ getWidgetUrl(filename) {
141
+ return `http://localhost:${this.serverPort}/widgets/${filename}`;
142
+ }
143
+ /**
144
+ * Construct a download URL for the given widget filename.
145
+ *
146
+ * Returns the same URL as {@link getWidgetUrl} since the widget is
147
+ * a self-contained HTML file that can be both viewed and downloaded.
148
+ *
149
+ * @param filename - The basename of the widget file.
150
+ * @returns A fully-qualified HTTP URL pointing to the file.
151
+ */
152
+ getDownloadUrl(filename) {
153
+ return `http://localhost:${this.serverPort}/widgets/${filename}`;
154
+ }
155
+ /**
156
+ * Convert a title string into a URL- and filesystem-safe slug.
157
+ *
158
+ * Transforms the input to lowercase, replaces non-alphanumeric
159
+ * characters with hyphens, collapses consecutive hyphens, and trims
160
+ * leading/trailing hyphens.
161
+ *
162
+ * @param title - The raw title string to slugify.
163
+ * @returns A clean, lowercase slug suitable for filenames.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * slugify('3D Solar System!') // '3d-solar-system'
168
+ * ```
169
+ */
170
+ slugify(title) {
171
+ return title
172
+ .toLowerCase()
173
+ .replace(/[^a-z0-9]+/g, '-')
174
+ .replace(/-+/g, '-')
175
+ .replace(/^-|-$/g, '');
176
+ }
177
+ }
178
+ //# sourceMappingURL=WidgetFileManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetFileManager.js","sourceRoot":"","sources":["../src/WidgetFileManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @module WidgetWrapper
3
+ *
4
+ * Safety wrapper that ensures every generated widget has a well-formed
5
+ * HTML structure, sensible defaults, and a client-side error boundary.
6
+ *
7
+ * The wrapper is additive: it inspects the incoming HTML with
8
+ * case-insensitive matching and only injects elements that are missing.
9
+ * This prevents double-adding when the agent already includes a
10
+ * doctype, charset meta, viewport meta, or error handler.
11
+ */
12
+ /**
13
+ * Wraps raw HTML content with structural defaults and an error boundary.
14
+ *
15
+ * Guarantees that every widget has:
16
+ * - A `<!DOCTYPE html>` declaration
17
+ * - A `<meta charset="utf-8">` tag
18
+ * - A responsive viewport meta tag
19
+ * - A minimal CSS reset (margin/padding/box-sizing + system-ui font)
20
+ * - A `window.onerror` error boundary that surfaces runtime errors visually
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const wrapper = new WidgetWrapper();
25
+ * const safe = wrapper.wrap('<div>Hello world</div>');
26
+ * // safe now contains a full HTML document with all defaults injected
27
+ * ```
28
+ */
29
+ export declare class WidgetWrapper {
30
+ /**
31
+ * Apply the safety wrapper to the given HTML string.
32
+ *
33
+ * Performs case-insensitive checks against the source HTML and only
34
+ * injects elements that are not already present. The injection order
35
+ * is: doctype, charset, viewport, CSS reset, error boundary.
36
+ *
37
+ * @param html - Raw HTML content to wrap.
38
+ * @returns The wrapped HTML with all missing defaults injected.
39
+ */
40
+ wrap(html: string): string;
41
+ /**
42
+ * Inject content into the `<head>` section of the HTML document.
43
+ *
44
+ * If a `<head>` tag exists, the content is inserted right after it.
45
+ * Otherwise, it is prepended to the document (after the doctype, if
46
+ * present).
47
+ *
48
+ * @param html - The HTML document string.
49
+ * @param content - The HTML fragment to inject.
50
+ * @returns The modified HTML with the content injected.
51
+ */
52
+ private injectIntoHead;
53
+ /**
54
+ * Inject content just before the closing `</body>` tag.
55
+ *
56
+ * If no `</body>` tag exists, the content is appended at the end
57
+ * of the document.
58
+ *
59
+ * @param html - The HTML document string.
60
+ * @param content - The HTML fragment to inject.
61
+ * @returns The modified HTML with the content injected.
62
+ */
63
+ private injectBeforeBodyClose;
64
+ }
65
+ //# sourceMappingURL=WidgetWrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetWrapper.d.ts","sourceRoot":"","sources":["../src/WidgetWrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAa;IACxB;;;;;;;;;OASG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IA6C1B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,cAAc;IAkBtB;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB;CAS9B"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @module WidgetWrapper
3
+ *
4
+ * Safety wrapper that ensures every generated widget has a well-formed
5
+ * HTML structure, sensible defaults, and a client-side error boundary.
6
+ *
7
+ * The wrapper is additive: it inspects the incoming HTML with
8
+ * case-insensitive matching and only injects elements that are missing.
9
+ * This prevents double-adding when the agent already includes a
10
+ * doctype, charset meta, viewport meta, or error handler.
11
+ */
12
+ /**
13
+ * Wraps raw HTML content with structural defaults and an error boundary.
14
+ *
15
+ * Guarantees that every widget has:
16
+ * - A `<!DOCTYPE html>` declaration
17
+ * - A `<meta charset="utf-8">` tag
18
+ * - A responsive viewport meta tag
19
+ * - A minimal CSS reset (margin/padding/box-sizing + system-ui font)
20
+ * - A `window.onerror` error boundary that surfaces runtime errors visually
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const wrapper = new WidgetWrapper();
25
+ * const safe = wrapper.wrap('<div>Hello world</div>');
26
+ * // safe now contains a full HTML document with all defaults injected
27
+ * ```
28
+ */
29
+ export class WidgetWrapper {
30
+ /**
31
+ * Apply the safety wrapper to the given HTML string.
32
+ *
33
+ * Performs case-insensitive checks against the source HTML and only
34
+ * injects elements that are not already present. The injection order
35
+ * is: doctype, charset, viewport, CSS reset, error boundary.
36
+ *
37
+ * @param html - Raw HTML content to wrap.
38
+ * @returns The wrapped HTML with all missing defaults injected.
39
+ */
40
+ wrap(html) {
41
+ const lower = html.toLowerCase();
42
+ let result = html;
43
+ // 1. Add <!DOCTYPE html> if missing
44
+ if (!lower.includes('<!doctype html>')) {
45
+ result = '<!DOCTYPE html>\n' + result;
46
+ }
47
+ // 2. Add <meta charset="utf-8"> if missing
48
+ if (!lower.includes('charset')) {
49
+ result = this.injectIntoHead(result, '<meta charset="utf-8">');
50
+ }
51
+ // 3. Add viewport meta if missing
52
+ if (!lower.includes('viewport')) {
53
+ result = this.injectIntoHead(result, '<meta name="viewport" content="width=device-width, initial-scale=1">');
54
+ }
55
+ // 4. Prepend CSS reset
56
+ if (!lower.includes('box-sizing: border-box')) {
57
+ const resetCss = '<style>* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: system-ui, sans-serif; }</style>';
58
+ result = this.injectIntoHead(result, resetCss);
59
+ }
60
+ // 5. Append error boundary script
61
+ if (!lower.includes('window.onerror')) {
62
+ const errorScript = `<script>
63
+ window.onerror = function(msg, url, line) {
64
+ var el = document.createElement('div');
65
+ el.style.cssText = 'position:fixed;top:0;left:0;right:0;padding:16px;background:#fee2e2;color:#991b1b;font:14px system-ui;z-index:99999';
66
+ el.textContent = 'Widget error: ' + msg + ' (line ' + line + ')';
67
+ document.body.prepend(el);
68
+ };
69
+ </script>`;
70
+ result = this.injectBeforeBodyClose(result, errorScript);
71
+ }
72
+ return result;
73
+ }
74
+ /**
75
+ * Inject content into the `<head>` section of the HTML document.
76
+ *
77
+ * If a `<head>` tag exists, the content is inserted right after it.
78
+ * Otherwise, it is prepended to the document (after the doctype, if
79
+ * present).
80
+ *
81
+ * @param html - The HTML document string.
82
+ * @param content - The HTML fragment to inject.
83
+ * @returns The modified HTML with the content injected.
84
+ */
85
+ injectIntoHead(html, content) {
86
+ const headIndex = html.toLowerCase().indexOf('<head>');
87
+ if (headIndex !== -1) {
88
+ const insertPos = headIndex + '<head>'.length;
89
+ return html.slice(0, insertPos) + '\n' + content + html.slice(insertPos);
90
+ }
91
+ // No <head> tag — insert after doctype if present, otherwise at the top
92
+ const doctypeEnd = html.toLowerCase().indexOf('<!doctype html>');
93
+ if (doctypeEnd !== -1) {
94
+ const insertPos = doctypeEnd + '<!doctype html>'.length;
95
+ return html.slice(0, insertPos) + '\n' + content + html.slice(insertPos);
96
+ }
97
+ return content + '\n' + html;
98
+ }
99
+ /**
100
+ * Inject content just before the closing `</body>` tag.
101
+ *
102
+ * If no `</body>` tag exists, the content is appended at the end
103
+ * of the document.
104
+ *
105
+ * @param html - The HTML document string.
106
+ * @param content - The HTML fragment to inject.
107
+ * @returns The modified HTML with the content injected.
108
+ */
109
+ injectBeforeBodyClose(html, content) {
110
+ const bodyCloseIndex = html.toLowerCase().indexOf('</body>');
111
+ if (bodyCloseIndex !== -1) {
112
+ return html.slice(0, bodyCloseIndex) + content + '\n' + html.slice(bodyCloseIndex);
113
+ }
114
+ return html + '\n' + content;
115
+ }
116
+ }
117
+ //# sourceMappingURL=WidgetWrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetWrapper.js","sourceRoot":"","sources":["../src/WidgetWrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,aAAa;IACxB;;;;;;;;;OASG;IACH,IAAI,CAAC,IAAY;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,IAAI,MAAM,GAAG,IAAI,CAAC;QAElB,oCAAoC;QACpC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACvC,MAAM,GAAG,mBAAmB,GAAG,MAAM,CAAC;QACxC,CAAC;QAED,2CAA2C;QAC3C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;QACjE,CAAC;QAED,kCAAkC;QAClC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAChC,MAAM,GAAG,IAAI,CAAC,cAAc,CAC1B,MAAM,EACN,sEAAsE,CACvE,CAAC;QACJ,CAAC;QAED,uBAAuB;QACvB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC;YAC9C,MAAM,QAAQ,GACZ,kHAAkH,CAAC;YACrH,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACjD,CAAC;QAED,kCAAkC;QAClC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACtC,MAAM,WAAW,GAAG;;;;;;;UAOhB,CAAC;YACL,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;;;;OAUG;IACK,cAAc,CAAC,IAAY,EAAE,OAAe;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEvD,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;YACrB,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC;YAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC3E,CAAC;QAED,wEAAwE;QACxE,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACjE,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;YACtB,MAAM,SAAS,GAAG,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC;YACxD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC3E,CAAC;QAED,OAAO,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC;IAC/B,CAAC;IAED;;;;;;;;;OASG;IACK,qBAAqB,CAAC,IAAY,EAAE,OAAe;QACzD,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAE7D,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACrF,CAAC;QAED,OAAO,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @module index
3
+ *
4
+ * Widget Generator Extension Pack — generates self-contained interactive
5
+ * HTML/CSS/JS widgets with safety wrapping and file management.
6
+ *
7
+ * Entry point for the extension; follows the standard AgentOS extension
8
+ * pack factory pattern (see {@link createExtensionPack}). The factory
9
+ * wires up the {@link WidgetWrapper} for HTML safety, the
10
+ * {@link WidgetFileManager} for file persistence, and registers the
11
+ * `generate_widget` tool.
12
+ */
13
+ import { GenerateWidgetTool } from './tools/generateWidget.js';
14
+ /**
15
+ * Options accepted by the Widget Generator extension pack factory.
16
+ */
17
+ export interface WidgetGeneratorExtensionOptions {
18
+ /** Override the default priority used when registering the tool. */
19
+ priority?: number;
20
+ /** Override the agent workspace directory (defaults to `process.cwd()`). */
21
+ workspaceDir?: string;
22
+ /** Override the server port used for widget URLs (defaults to `3777`). */
23
+ serverPort?: number;
24
+ }
25
+ /**
26
+ * Factory function called by the AgentOS extension loader. Returns a pack
27
+ * descriptor containing the `generate_widget` tool.
28
+ *
29
+ * The factory:
30
+ *
31
+ * 1. Resolves the workspace directory and server port from context options.
32
+ * 2. Creates the {@link WidgetWrapper} for HTML safety wrapping.
33
+ * 3. Creates the {@link WidgetFileManager} for file I/O.
34
+ * 4. Wires both into the {@link GenerateWidgetTool}.
35
+ * 5. Returns the tool as an extension pack descriptor with lifecycle hooks.
36
+ *
37
+ * @param context - Extension activation context provided by the AgentOS runtime.
38
+ * @returns An extension pack with tool descriptors and lifecycle hooks.
39
+ */
40
+ export declare function createExtensionPack(context: any): {
41
+ name: string;
42
+ version: string;
43
+ descriptors: {
44
+ id: string;
45
+ kind: "tool";
46
+ priority: number;
47
+ payload: GenerateWidgetTool;
48
+ }[];
49
+ onActivate: () => Promise<any>;
50
+ onDeactivate: () => Promise<any>;
51
+ };
52
+ export default createExtensionPack;
53
+ export { WidgetWrapper } from './WidgetWrapper.js';
54
+ export { WidgetFileManager } from './WidgetFileManager.js';
55
+ export { GenerateWidgetTool } from './tools/generateWidget.js';
56
+ export type { GenerateWidgetInput, GenerateWidgetOutput } from './types.js';
57
+ export type { WidgetFileEntry, WidgetSaveResult } from './WidgetFileManager.js';
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,GAAG;;;;;;;;;;;EAiC/C;AAED,eAAe,mBAAmB,CAAC;AAGnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @module index
3
+ *
4
+ * Widget Generator Extension Pack — generates self-contained interactive
5
+ * HTML/CSS/JS widgets with safety wrapping and file management.
6
+ *
7
+ * Entry point for the extension; follows the standard AgentOS extension
8
+ * pack factory pattern (see {@link createExtensionPack}). The factory
9
+ * wires up the {@link WidgetWrapper} for HTML safety, the
10
+ * {@link WidgetFileManager} for file persistence, and registers the
11
+ * `generate_widget` tool.
12
+ */
13
+ import { WidgetWrapper } from './WidgetWrapper.js';
14
+ import { WidgetFileManager } from './WidgetFileManager.js';
15
+ import { GenerateWidgetTool } from './tools/generateWidget.js';
16
+ /**
17
+ * Factory function called by the AgentOS extension loader. Returns a pack
18
+ * descriptor containing the `generate_widget` tool.
19
+ *
20
+ * The factory:
21
+ *
22
+ * 1. Resolves the workspace directory and server port from context options.
23
+ * 2. Creates the {@link WidgetWrapper} for HTML safety wrapping.
24
+ * 3. Creates the {@link WidgetFileManager} for file I/O.
25
+ * 4. Wires both into the {@link GenerateWidgetTool}.
26
+ * 5. Returns the tool as an extension pack descriptor with lifecycle hooks.
27
+ *
28
+ * @param context - Extension activation context provided by the AgentOS runtime.
29
+ * @returns An extension pack with tool descriptors and lifecycle hooks.
30
+ */
31
+ export function createExtensionPack(context) {
32
+ const options = (context.options || {});
33
+ // Resolve configuration
34
+ const workspaceDir = options.workspaceDir ?? process.cwd();
35
+ const serverPort = options.serverPort ?? 3777;
36
+ const priority = options.priority ?? 50;
37
+ // Create dependencies
38
+ const wrapper = new WidgetWrapper();
39
+ const fileManager = new WidgetFileManager(workspaceDir, serverPort);
40
+ // Create the tool with all dependencies injected
41
+ const tool = new GenerateWidgetTool(wrapper, fileManager);
42
+ return {
43
+ name: '@framers/agentos-ext-widget-generator',
44
+ version: '1.0.0',
45
+ descriptors: [
46
+ {
47
+ // IMPORTANT: ToolExecutor uses descriptor id as the lookup key for tool calls.
48
+ // Keep it aligned with `tool.name`.
49
+ id: tool.name,
50
+ kind: 'tool',
51
+ priority,
52
+ payload: tool,
53
+ },
54
+ ],
55
+ onActivate: async () => context.logger?.info('Interactive Widgets activated'),
56
+ onDeactivate: async () => context.logger?.info('Interactive Widgets deactivated'),
57
+ };
58
+ }
59
+ export default createExtensionPack;
60
+ // Re-export classes for direct consumption
61
+ export { WidgetWrapper } from './WidgetWrapper.js';
62
+ export { WidgetFileManager } from './WidgetFileManager.js';
63
+ export { GenerateWidgetTool } from './tools/generateWidget.js';
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAgB/D;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAY;IAC9C,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAoC,CAAC;IAE3E,wBAAwB;IACxB,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;IAExC,sBAAsB;IACtB,MAAM,OAAO,GAAG,IAAI,aAAa,EAAE,CAAC;IACpC,MAAM,WAAW,GAAG,IAAI,iBAAiB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAEpE,iDAAiD;IACjD,MAAM,IAAI,GAAG,IAAI,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAE1D,OAAO;QACL,IAAI,EAAE,uCAAuC;QAC7C,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE;YACX;gBACE,+EAA+E;gBAC/E,oCAAoC;gBACpC,EAAE,EAAE,IAAI,CAAC,IAAI;gBACb,IAAI,EAAE,MAAe;gBACrB,QAAQ;gBACR,OAAO,EAAE,IAAI;aACd;SACF;QACD,UAAU,EAAE,KAAK,IAAI,EAAE,CACrB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,+BAA+B,CAAC;QACvD,YAAY,EAAE,KAAK,IAAI,EAAE,CACvB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,iCAAiC,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,eAAe,mBAAmB,CAAC;AAEnC,2CAA2C;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}