doe-sdk 0.1.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/README.md +509 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +118 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +25 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +100 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/login.d.ts +52 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +571 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/publish.d.ts +27 -0
- package/dist/cli/publish.d.ts.map +1 -0
- package/dist/cli/publish.js +531 -0
- package/dist/cli/publish.js.map +1 -0
- package/dist/cli/scaffold.d.ts +18 -0
- package/dist/cli/scaffold.d.ts.map +1 -0
- package/dist/cli/scaffold.js +252 -0
- package/dist/cli/scaffold.js.map +1 -0
- package/dist/cli/ui.d.ts +57 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +339 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli/validate.d.ts +28 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +1270 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/compat/legacy-adapter.d.ts +198 -0
- package/dist/compat/legacy-adapter.d.ts.map +1 -0
- package/dist/compat/legacy-adapter.js +318 -0
- package/dist/compat/legacy-adapter.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/client.d.ts +370 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +470 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/types/api.d.ts +564 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +11 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/manifest.d.ts +412 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/manifest.js +42 -0
- package/dist/types/manifest.js.map +1 -0
- package/dist/types/marketplace-categories.d.ts +9 -0
- package/dist/types/marketplace-categories.d.ts.map +1 -0
- package/dist/types/marketplace-categories.js +16 -0
- package/dist/types/marketplace-categories.js.map +1 -0
- package/dist/types/permissions.d.ts +86 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/permissions.js +295 -0
- package/dist/types/permissions.js.map +1 -0
- package/dist/types/theme/index.d.ts +7 -0
- package/dist/types/theme/index.d.ts.map +1 -0
- package/dist/types/theme/index.js +7 -0
- package/dist/types/theme/index.js.map +1 -0
- package/dist/types/theme/schema.d.ts +1205 -0
- package/dist/types/theme/schema.d.ts.map +1 -0
- package/dist/types/theme/schema.js +325 -0
- package/dist/types/theme/schema.js.map +1 -0
- package/dist/types/theme/types.d.ts +648 -0
- package/dist/types/theme/types.d.ts.map +1 -0
- package/dist/types/theme/types.js +8 -0
- package/dist/types/theme/types.js.map +1 -0
- package/package.json +75 -0
- package/templates/extension/README.md +254 -0
- package/templates/extension/icon.png +0 -0
- package/templates/extension/index.html +42 -0
- package/templates/extension/manifest.json +57 -0
- package/templates/extension/package.json +24 -0
- package/templates/extension/src/App.tsx +252 -0
- package/templates/extension/src/components/OnboardingComplete.tsx +190 -0
- package/templates/extension/src/components/OnboardingProgress.tsx +82 -0
- package/templates/extension/src/components/OnboardingWelcome.tsx +166 -0
- package/templates/extension/src/components/StepContainer.tsx +217 -0
- package/templates/extension/src/components/playground/CanvasTab.tsx +24 -0
- package/templates/extension/src/components/playground/ConfigTab.tsx +24 -0
- package/templates/extension/src/components/playground/EventsTab.tsx +24 -0
- package/templates/extension/src/components/playground/InfoTab.tsx +89 -0
- package/templates/extension/src/components/playground/NetworkTab.tsx +24 -0
- package/templates/extension/src/components/playground/PlaygroundContainer.tsx +184 -0
- package/templates/extension/src/components/playground/ResultDisplay.tsx +30 -0
- package/templates/extension/src/components/playground/StorageTab.tsx +24 -0
- package/templates/extension/src/components/playground/UITab.tsx +24 -0
- package/templates/extension/src/components/shared/CanvasControls.tsx +130 -0
- package/templates/extension/src/components/shared/ConfigControls.tsx +154 -0
- package/templates/extension/src/components/shared/EventsControls.tsx +232 -0
- package/templates/extension/src/components/shared/InfoControls.tsx +281 -0
- package/templates/extension/src/components/shared/NetworkControls.tsx +328 -0
- package/templates/extension/src/components/shared/StorageControls.tsx +203 -0
- package/templates/extension/src/components/shared/UIControls.tsx +199 -0
- package/templates/extension/src/components/shared/index.ts +15 -0
- package/templates/extension/src/components/steps/CanvasStep.tsx +67 -0
- package/templates/extension/src/components/steps/ClipboardStep.tsx +167 -0
- package/templates/extension/src/components/steps/ConfigStep.tsx +63 -0
- package/templates/extension/src/components/steps/EventsStep.tsx +69 -0
- package/templates/extension/src/components/steps/InfoStep.tsx +70 -0
- package/templates/extension/src/components/steps/NetworkStep.tsx +70 -0
- package/templates/extension/src/components/steps/StorageStep.tsx +61 -0
- package/templates/extension/src/components/steps/UIStep.tsx +70 -0
- package/templates/extension/src/hooks/useDoe.ts +93 -0
- package/templates/extension/src/hooks/useOnboarding.ts +264 -0
- package/templates/extension/src/hooks/usePointerHandlers.ts +105 -0
- package/templates/extension/src/main.tsx +18 -0
- package/templates/extension/src/styles.ts +265 -0
- package/templates/extension/tsconfig.json +28 -0
- package/templates/extension/vite.config.ts +32 -0
- package/templates/theme/README.md +132 -0
- package/templates/theme/manifest.json +19 -0
- package/templates/theme/package.json +16 -0
- package/templates/theme/styles/.gitkeep +2 -0
- package/templates/theme/themes/theme.json +32 -0
- package/templates/theme/vite-plugin-doe-theme.ts +53 -0
- package/templates/theme/vite.config.ts +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# doe-sdk
|
|
2
|
+
|
|
3
|
+
SDK for building DOE extensions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D doe-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Create a New Extension
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx doe-sdk create my-extension
|
|
17
|
+
cd my-extension
|
|
18
|
+
pnpm install
|
|
19
|
+
pnpm dev
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Project Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
my-extension/
|
|
26
|
+
├── manifest.json # Extension metadata and permissions
|
|
27
|
+
├── src/
|
|
28
|
+
│ ├── main.ts # Extension logic
|
|
29
|
+
│ └── index.html # UI panel (optional)
|
|
30
|
+
├── dist/ # Built output
|
|
31
|
+
├── package.json
|
|
32
|
+
└── tsconfig.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## CLI Commands
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Create new extension (extension starter template)
|
|
39
|
+
npx doe-sdk create <name>
|
|
40
|
+
|
|
41
|
+
# Validate manifest and code
|
|
42
|
+
npx doe-sdk validate
|
|
43
|
+
|
|
44
|
+
# Login to developer account
|
|
45
|
+
npx doe-sdk login
|
|
46
|
+
|
|
47
|
+
# Publish to marketplace (requires build first)
|
|
48
|
+
npx doe-sdk publish
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Note:** Build and dev server are handled by the template's tooling (Vite):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd my-extension
|
|
55
|
+
pnpm dev # Vite dev server with hot reload
|
|
56
|
+
pnpm build # Vite production build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Manifest Configuration
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"id": "@your-username/my-extension",
|
|
64
|
+
"version": "1.0.0",
|
|
65
|
+
"name": "My Extension",
|
|
66
|
+
"description": "A helpful description",
|
|
67
|
+
"author": "Your Name",
|
|
68
|
+
"tier": "sandboxed",
|
|
69
|
+
"permissions": ["canvas:read", "storage:read", "storage:write"],
|
|
70
|
+
"main": "dist/main.js",
|
|
71
|
+
"ui": "dist/index.html"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Tiers
|
|
76
|
+
|
|
77
|
+
| Tier | Description |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `sandboxed` | Runs in iframe, message-passing only (default) |
|
|
80
|
+
| `trusted` | Same-origin iframe, network permissions |
|
|
81
|
+
| `privileged` | Full access (DOE team only) |
|
|
82
|
+
|
|
83
|
+
### Permissions Reference
|
|
84
|
+
|
|
85
|
+
#### Sandboxed Tier (Default)
|
|
86
|
+
|
|
87
|
+
| Permission | Description |
|
|
88
|
+
|------------|-------------|
|
|
89
|
+
| `canvas:read` | Read shapes created by this extension |
|
|
90
|
+
| `canvas:write` | Create/modify/delete shapes |
|
|
91
|
+
| `storage:read` | Read extension storage |
|
|
92
|
+
| `storage:write` | Write extension storage |
|
|
93
|
+
| `clipboard:write` | Write to clipboard |
|
|
94
|
+
| `ui:show-panel` | Display extension panel |
|
|
95
|
+
| `config:read` | Read extension configuration |
|
|
96
|
+
| `config:write` | Write extension configuration |
|
|
97
|
+
| `profile:read-basic` | Read user display name and avatar |
|
|
98
|
+
| `workspace:read-basic` | Read workspace ID and name |
|
|
99
|
+
| `theme:read` | Read theme tokens and subscribe to changes |
|
|
100
|
+
| `theme:write` | Inject custom styles |
|
|
101
|
+
|
|
102
|
+
#### Trusted Tier (Additional)
|
|
103
|
+
|
|
104
|
+
| Permission | Description | Sensitive |
|
|
105
|
+
|------------|-------------|-----------|
|
|
106
|
+
| `canvas:read:all` | Read ALL shapes on canvas (requires consent) | Yes |
|
|
107
|
+
| `clipboard:read` | Read clipboard content | Yes |
|
|
108
|
+
| `network:fetch` | Make HTTP requests (requires `hostPermissions`) | Yes |
|
|
109
|
+
|
|
110
|
+
## Extension API
|
|
111
|
+
|
|
112
|
+
Extensions receive an injected API at `window.__DOE_EXTENSION_API__`:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import type { DOEExtensionAPI } from "doe-sdk";
|
|
116
|
+
|
|
117
|
+
const api = window.__DOE_EXTENSION_API__!;
|
|
118
|
+
|
|
119
|
+
// Read canvas shapes
|
|
120
|
+
const shapes = await api.canvas.getShapes();
|
|
121
|
+
|
|
122
|
+
// Store data
|
|
123
|
+
await api.storage.set("key", { data: "value" });
|
|
124
|
+
|
|
125
|
+
// Subscribe to events
|
|
126
|
+
api.events.onSelectionChange((payload) => {
|
|
127
|
+
console.log("Selected:", payload.shapeIds);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Show right-side panel
|
|
131
|
+
await api.ui.showPanel({ title: "My Extension", width: 400 });
|
|
132
|
+
|
|
133
|
+
// Show centered modal
|
|
134
|
+
await api.ui.showModal({ title: "Settings", width: 500, height: 400 });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Canvas API
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Read operations (requires canvas:read)
|
|
141
|
+
const shapes = await api.canvas.getShapes();
|
|
142
|
+
const shape = await api.canvas.getShape("shape-id");
|
|
143
|
+
const selected = await api.canvas.getSelectedShapes();
|
|
144
|
+
const viewport = await api.canvas.getViewport();
|
|
145
|
+
const info = await api.canvas.getCanvasInfo();
|
|
146
|
+
|
|
147
|
+
// Write operations (requires canvas:write)
|
|
148
|
+
const newShape = await api.canvas.createShape({
|
|
149
|
+
type: "note",
|
|
150
|
+
x: 100,
|
|
151
|
+
y: 100,
|
|
152
|
+
props: { text: "Hello!", color: "yellow" }
|
|
153
|
+
});
|
|
154
|
+
await api.canvas.updateShape("shape-id", { props: { text: "Updated" } });
|
|
155
|
+
await api.canvas.deleteShape("shape-id");
|
|
156
|
+
await api.canvas.selectShapes(["shape-1", "shape-2"]);
|
|
157
|
+
await api.canvas.panTo(500, 500);
|
|
158
|
+
await api.canvas.zoomTo(1.5);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Storage API
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// Requires storage:read and storage:write
|
|
165
|
+
await api.storage.set("myKey", { data: "value" });
|
|
166
|
+
const value = await api.storage.get<{ data: string }>("myKey");
|
|
167
|
+
const keys = await api.storage.keys();
|
|
168
|
+
await api.storage.delete("myKey");
|
|
169
|
+
await api.storage.clear();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Config API
|
|
173
|
+
|
|
174
|
+
Read and write extension configuration. Supports secure storage for secrets.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Requires config:read and config:write
|
|
178
|
+
const config = await api.config.getConfig();
|
|
179
|
+
const apiKey = await api.config.get<string>("apiKey");
|
|
180
|
+
await api.config.set("theme", "dark");
|
|
181
|
+
|
|
182
|
+
// Open the extension's settings UI
|
|
183
|
+
await api.config.openSettings();
|
|
184
|
+
|
|
185
|
+
// Subscribe to configuration changes
|
|
186
|
+
const unsubscribe = api.config.onConfigChange((key, value) => {
|
|
187
|
+
console.log(`Config ${key} changed to:`, value);
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Configuration schema in manifest.json:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"configurationSchema": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"properties": {
|
|
198
|
+
"apiKey": {
|
|
199
|
+
"type": "string",
|
|
200
|
+
"title": "API Key",
|
|
201
|
+
"description": "Your API key for the service",
|
|
202
|
+
"format": "password"
|
|
203
|
+
},
|
|
204
|
+
"refreshInterval": {
|
|
205
|
+
"type": "integer",
|
|
206
|
+
"title": "Refresh Interval",
|
|
207
|
+
"description": "Minutes between refreshes",
|
|
208
|
+
"default": 30,
|
|
209
|
+
"minimum": 1,
|
|
210
|
+
"maximum": 60
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"required": ["apiKey"]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Fields with `format: "password"` are securely stored in the OS keychain.
|
|
219
|
+
|
|
220
|
+
## Theme API
|
|
221
|
+
|
|
222
|
+
Extensions can access theme information and inject custom styles:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
const api = window.__DOE_EXTENSION_API__!;
|
|
226
|
+
|
|
227
|
+
// Get current theme info
|
|
228
|
+
const themeId = await api.theme.getActiveThemeId();
|
|
229
|
+
const themeType = await api.theme.getThemeType(); // "light" or "dark"
|
|
230
|
+
|
|
231
|
+
// Get theme tokens
|
|
232
|
+
const accent = await api.theme.getToken("colors.accent");
|
|
233
|
+
const tokens = await api.theme.getTokens(["colors.background", "colors.text"]);
|
|
234
|
+
|
|
235
|
+
// Get CSS variable name for a token
|
|
236
|
+
const cssVar = await api.theme.getCSSVariable("colors.accent"); // "--doe-accent"
|
|
237
|
+
|
|
238
|
+
// List all installed themes
|
|
239
|
+
const themes = await api.theme.list();
|
|
240
|
+
|
|
241
|
+
// Subscribe to theme changes
|
|
242
|
+
const unsubscribe = api.theme.onThemeChange((event) => {
|
|
243
|
+
console.log("Theme changed:", event.themeId, event.themeType);
|
|
244
|
+
// Update your UI accordingly
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Inject custom styles (requires theme:write permission)
|
|
248
|
+
const injection = await api.theme.injectStyles(`
|
|
249
|
+
.my-extension-panel {
|
|
250
|
+
background: var(--doe-surface);
|
|
251
|
+
color: var(--doe-text);
|
|
252
|
+
border: 1px solid var(--doe-border);
|
|
253
|
+
}
|
|
254
|
+
`);
|
|
255
|
+
|
|
256
|
+
// Later, remove the injected styles
|
|
257
|
+
await injection.remove();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Available Theme Tokens
|
|
261
|
+
|
|
262
|
+
| Token Path | CSS Variable | Description |
|
|
263
|
+
|------------|--------------|-------------|
|
|
264
|
+
| `colors.background` | `--doe-bg` | App background |
|
|
265
|
+
| `colors.surface` | `--doe-surface` | Panel/card background |
|
|
266
|
+
| `colors.text` | `--doe-text` | Primary text |
|
|
267
|
+
| `colors.textMuted` | `--doe-text-muted` | Secondary text |
|
|
268
|
+
| `colors.accent` | `--doe-accent` | Accent/brand color |
|
|
269
|
+
| `colors.border` | `--doe-border` | Borders and dividers |
|
|
270
|
+
| `colors.error` | `--doe-error` | Error states |
|
|
271
|
+
| `colors.warning` | `--doe-warning` | Warning states |
|
|
272
|
+
| `colors.success` | `--doe-success` | Success states |
|
|
273
|
+
| `typography.fontFamily` | `--doe-font-family` | Primary font |
|
|
274
|
+
| `typography.fontFamilyMono` | `--doe-font-family-mono` | Monospace font |
|
|
275
|
+
| `shapes.borderRadiusSm` | `--doe-radius-sm` | Small radius |
|
|
276
|
+
| `shapes.borderRadiusMd` | `--doe-radius-md` | Medium radius |
|
|
277
|
+
| `effects.shadowSm` | `--doe-shadow-sm` | Small shadow |
|
|
278
|
+
| `effects.shadowMd` | `--doe-shadow-md` | Medium shadow |
|
|
279
|
+
|
|
280
|
+
## Creating Theme Extensions
|
|
281
|
+
|
|
282
|
+
Extensions can contribute custom themes via the `contributes` field:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"id": "@your-username/my-theme",
|
|
287
|
+
"version": "1.0.0",
|
|
288
|
+
"name": "My Custom Theme",
|
|
289
|
+
"description": "A beautiful custom theme",
|
|
290
|
+
"author": "Your Name",
|
|
291
|
+
"tier": "sandboxed",
|
|
292
|
+
"permissions": [],
|
|
293
|
+
"contributes": {
|
|
294
|
+
"themes": [
|
|
295
|
+
{
|
|
296
|
+
"path": "./themes/my-theme.json",
|
|
297
|
+
"label": "My Theme"
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Theme JSON file structure:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"id": "my-theme",
|
|
309
|
+
"name": "My Theme",
|
|
310
|
+
"version": "1.0.0",
|
|
311
|
+
"author": "Your Name",
|
|
312
|
+
"type": "dark",
|
|
313
|
+
"colors": {
|
|
314
|
+
"background": "#1a1a2e",
|
|
315
|
+
"surface": "#252541",
|
|
316
|
+
"text": "#eaeaea",
|
|
317
|
+
"textMuted": "#a0a0a0",
|
|
318
|
+
"accent": "#e94560",
|
|
319
|
+
"border": "#3d3d5c"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
See the [Theming Guide](../../docs/THEMING.md) for detailed documentation.
|
|
325
|
+
|
|
326
|
+
### Events API
|
|
327
|
+
|
|
328
|
+
Subscribe to application events for reactive extensions.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// No permission required for basic events
|
|
332
|
+
const unsubscribe1 = api.events.onSelectionChange((payload) => {
|
|
333
|
+
console.log("Selected shapes:", payload.shapeIds);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const unsubscribe2 = api.events.onCanvasChange((payload) => {
|
|
337
|
+
console.log(`Canvas ${payload.type}:`, payload.shapeIds);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const unsubscribe3 = api.events.onThemeChange((payload) => {
|
|
341
|
+
console.log("Theme:", payload.mode); // "light" or "dark"
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Requires config:read permission
|
|
345
|
+
const unsubscribe4 = api.events.onConfigChange((payload) => {
|
|
346
|
+
console.log(`Config ${payload.key} changed to:`, payload.value);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Clean up when done
|
|
350
|
+
unsubscribe1();
|
|
351
|
+
unsubscribe2();
|
|
352
|
+
unsubscribe3();
|
|
353
|
+
unsubscribe4();
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### UI API
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// Show right-side panel (requires ui:show-panel)
|
|
360
|
+
await api.ui.showPanel({
|
|
361
|
+
title: "My Extension",
|
|
362
|
+
width: 400,
|
|
363
|
+
});
|
|
364
|
+
await api.ui.hidePanel();
|
|
365
|
+
|
|
366
|
+
// Show centered modal dialog (requires ui:show-panel)
|
|
367
|
+
await api.ui.showModal({
|
|
368
|
+
title: "My Modal",
|
|
369
|
+
width: 500,
|
|
370
|
+
height: 400,
|
|
371
|
+
});
|
|
372
|
+
await api.ui.hideModal();
|
|
373
|
+
|
|
374
|
+
// Show toast notification
|
|
375
|
+
await api.ui.showToast("Operation complete!", {
|
|
376
|
+
duration: 3000,
|
|
377
|
+
type: "success" // "info" | "success" | "warning" | "error"
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Confirmation dialog
|
|
381
|
+
const confirmed = await api.ui.confirm("Delete this item?", {
|
|
382
|
+
title: "Confirm Delete",
|
|
383
|
+
confirmText: "Delete",
|
|
384
|
+
cancelText: "Cancel"
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Get current theme (no permission required)
|
|
388
|
+
const theme = await api.ui.getTheme();
|
|
389
|
+
console.log(theme.mode); // "light" or "dark"
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Network API
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// Requires network:fetch permission + hostPermissions in manifest
|
|
396
|
+
const response = await api.network!.fetch("https://api.example.com/data", {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: { "Content-Type": "application/json" },
|
|
399
|
+
body: JSON.stringify({ query: "test" }),
|
|
400
|
+
timeout: 5000
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
if (response.ok) {
|
|
404
|
+
const data = await response.json();
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Manifest must include hostPermissions:
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"permissions": ["network:fetch"],
|
|
413
|
+
"hostPermissions": ["https://api.example.com/*"]
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Workspace API
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// Get workspace info (requires workspace:read-basic)
|
|
421
|
+
const workspace = await api.workspace.getInfo();
|
|
422
|
+
console.log(workspace.id, workspace.name);
|
|
423
|
+
|
|
424
|
+
// Get current user (requires profile:read-basic)
|
|
425
|
+
const user = await api.workspace.getCurrentUser();
|
|
426
|
+
console.log(user?.displayName, user?.avatarUrl);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Clipboard API
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// Write to clipboard (requires clipboard:write)
|
|
433
|
+
await api.clipboard!.writeText("Copied text");
|
|
434
|
+
|
|
435
|
+
// Read from clipboard (requires clipboard:read, trusted tier)
|
|
436
|
+
const text = await api.clipboard!.readText();
|
|
437
|
+
const hasContent = await api.clipboard!.hasContent();
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Extension Template
|
|
441
|
+
|
|
442
|
+
The default template is the extension starter with Vite:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
npx doe-sdk create my-extension
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
This creates a project with:
|
|
449
|
+
- Extension starter setup with TypeScript
|
|
450
|
+
- Vite for bundling
|
|
451
|
+
- `useDoe()` hook for API access
|
|
452
|
+
- Todo list example that works standalone and in DOE
|
|
453
|
+
|
|
454
|
+
### Using the useDoe Hook
|
|
455
|
+
|
|
456
|
+
```tsx
|
|
457
|
+
import { useDoe } from "./hooks/useDoe";
|
|
458
|
+
|
|
459
|
+
function MyComponent() {
|
|
460
|
+
const { api, isReady, error } = useDoe();
|
|
461
|
+
|
|
462
|
+
if (error) return <div>Standalone Mode</div>;
|
|
463
|
+
if (!isReady) return <div>Connecting...</div>;
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
<button onClick={() => api.ui.showToast({ message: "Hello!" })}>
|
|
467
|
+
Show Toast
|
|
468
|
+
</button>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The extension works in both modes:
|
|
474
|
+
- **Standalone**: For development (`pnpm dev`)
|
|
475
|
+
- **In DOE**: Full API access when loaded as extension
|
|
476
|
+
|
|
477
|
+
## TypeScript Support
|
|
478
|
+
|
|
479
|
+
Full TypeScript support with type definitions:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import type {
|
|
483
|
+
DOEExtensionAPI,
|
|
484
|
+
ExtensionManifest,
|
|
485
|
+
Shape,
|
|
486
|
+
CanvasReadAPI,
|
|
487
|
+
ConfigAPI,
|
|
488
|
+
EventsAPI,
|
|
489
|
+
UIAPI,
|
|
490
|
+
PanelOptions,
|
|
491
|
+
ModalOptions,
|
|
492
|
+
SelectionChangePayload,
|
|
493
|
+
CanvasChangePayload,
|
|
494
|
+
ThemeChangePayload,
|
|
495
|
+
ConfigChangePayload,
|
|
496
|
+
UserBasicInfo,
|
|
497
|
+
WorkspaceInfo,
|
|
498
|
+
} from "doe-sdk";
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## Documentation
|
|
502
|
+
|
|
503
|
+
- [AI Prompt Reference](./docs/AI_PROMPT.md) - Comprehensive API reference for AI assistants
|
|
504
|
+
- [Migration Guide](./docs/MIGRATION.md) - Upgrading from previous versions
|
|
505
|
+
- [Permissions Reference](./docs/permissions.json) - Full permission definitions
|
|
506
|
+
|
|
507
|
+
## License
|
|
508
|
+
|
|
509
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* DOE Extension CLI (Minimal)
|
|
4
|
+
*
|
|
5
|
+
* Command-line interface for creating and publishing DOE extensions.
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* - create <name> - Scaffold a new extension project
|
|
9
|
+
* - validate - Validate manifest and code
|
|
10
|
+
* - login - Login to developer account
|
|
11
|
+
* - logout - Logout from developer account
|
|
12
|
+
* - publish - Submit extension to marketplace
|
|
13
|
+
*
|
|
14
|
+
* Dev/Build: Delegated to template's own tooling (Vite)
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* DOE Extension CLI (Minimal)
|
|
4
|
+
*
|
|
5
|
+
* Command-line interface for creating and publishing DOE extensions.
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* - create <name> - Scaffold a new extension project
|
|
9
|
+
* - validate - Validate manifest and code
|
|
10
|
+
* - login - Login to developer account
|
|
11
|
+
* - logout - Logout from developer account
|
|
12
|
+
* - publish - Submit extension to marketplace
|
|
13
|
+
*
|
|
14
|
+
* Dev/Build: Delegated to template's own tooling (Vite)
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from "commander";
|
|
17
|
+
import { SDK_VERSION } from "../index.js";
|
|
18
|
+
const program = new Command();
|
|
19
|
+
program
|
|
20
|
+
.name("doe-sdk")
|
|
21
|
+
.description("DOE Extension SDK CLI")
|
|
22
|
+
.version(SDK_VERSION);
|
|
23
|
+
// Init command - interactive project creation (recommended)
|
|
24
|
+
program
|
|
25
|
+
.command("init")
|
|
26
|
+
.alias("new")
|
|
27
|
+
.description("Create a new extension or theme (interactive)")
|
|
28
|
+
.option("--type <type>", "Pre-select project type: extension or theme")
|
|
29
|
+
.option("--minimal", "Use minimal output without ASCII art")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
const { init } = await import("./init.js");
|
|
32
|
+
const initOptions = {};
|
|
33
|
+
if (options.type === "extension" || options.type === "theme") {
|
|
34
|
+
initOptions.type = options.type;
|
|
35
|
+
}
|
|
36
|
+
if (options.minimal !== undefined) {
|
|
37
|
+
initOptions.minimal = options.minimal;
|
|
38
|
+
}
|
|
39
|
+
await init(initOptions);
|
|
40
|
+
});
|
|
41
|
+
// Create command - quick scaffold with name
|
|
42
|
+
program
|
|
43
|
+
.command("create <name>")
|
|
44
|
+
.description("Scaffold a new extension or theme project")
|
|
45
|
+
.option("-t, --template <template>", "Template to use (extension, theme)")
|
|
46
|
+
.option("--type <type>", "Project type: extension or theme")
|
|
47
|
+
.option("-d, --directory <dir>", "Output directory")
|
|
48
|
+
.action(async (name, options) => {
|
|
49
|
+
const { quickCreate } = await import("./init.js");
|
|
50
|
+
const { scaffold } = await import("./scaffold.js");
|
|
51
|
+
// If template explicitly provided, use scaffold directly
|
|
52
|
+
if (options.template) {
|
|
53
|
+
const scaffoldOptions = { template: options.template };
|
|
54
|
+
if (options.directory) {
|
|
55
|
+
scaffoldOptions.directory = options.directory;
|
|
56
|
+
}
|
|
57
|
+
await scaffold(name, scaffoldOptions);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Otherwise use interactive quick create
|
|
61
|
+
const type = options.type === "extension" || options.type === "theme" ? options.type : undefined;
|
|
62
|
+
await quickCreate(name, type);
|
|
63
|
+
});
|
|
64
|
+
// Validate command - validate extension
|
|
65
|
+
program
|
|
66
|
+
.command("validate")
|
|
67
|
+
.description("Validate extension manifest and code")
|
|
68
|
+
.option("--strict", "Enable strict validation", false)
|
|
69
|
+
.option("--fix", "Auto-fix issues (removes unused permissions)", false)
|
|
70
|
+
.action(async (options) => {
|
|
71
|
+
const { validate } = await import("./validate.js");
|
|
72
|
+
await validate(options);
|
|
73
|
+
});
|
|
74
|
+
// Login command - authenticate developer
|
|
75
|
+
program
|
|
76
|
+
.command("login")
|
|
77
|
+
.description("Login to your DOE developer account")
|
|
78
|
+
.action(async () => {
|
|
79
|
+
const { login } = await import("./login.js");
|
|
80
|
+
await login();
|
|
81
|
+
});
|
|
82
|
+
// Logout command - remove stored credentials
|
|
83
|
+
program
|
|
84
|
+
.command("logout")
|
|
85
|
+
.description("Logout from your DOE developer account")
|
|
86
|
+
.action(async () => {
|
|
87
|
+
const { logout } = await import("./login.js");
|
|
88
|
+
await logout();
|
|
89
|
+
});
|
|
90
|
+
// Whoami command - show authentication status
|
|
91
|
+
program
|
|
92
|
+
.command("whoami")
|
|
93
|
+
.description("Show current authentication status")
|
|
94
|
+
.action(async () => {
|
|
95
|
+
const { whoami } = await import("./login.js");
|
|
96
|
+
await whoami();
|
|
97
|
+
});
|
|
98
|
+
// Publish command - submit to marketplace
|
|
99
|
+
program
|
|
100
|
+
.command("publish")
|
|
101
|
+
.description("Submit extension to the marketplace")
|
|
102
|
+
.option("--check-security", "Run security checks before publishing", true)
|
|
103
|
+
.option("--dry-run", "Validate without submitting", false)
|
|
104
|
+
.action(async (options) => {
|
|
105
|
+
const { publish } = await import("./publish.js");
|
|
106
|
+
await publish(options);
|
|
107
|
+
});
|
|
108
|
+
// Status command - check submission status
|
|
109
|
+
program
|
|
110
|
+
.command("status [submissionId]")
|
|
111
|
+
.description("Check the status of a submission")
|
|
112
|
+
.action(async (submissionId) => {
|
|
113
|
+
const { checkStatus } = await import("./publish.js");
|
|
114
|
+
await checkStatus(submissionId);
|
|
115
|
+
});
|
|
116
|
+
// Parse arguments
|
|
117
|
+
program.parse();
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Command - Interactive project scaffolding
|
|
3
|
+
*
|
|
4
|
+
* Linear/Notion style CLI experience for creating new extensions or themes.
|
|
5
|
+
* Features subtle animations, clean typography, and minimal design.
|
|
6
|
+
*/
|
|
7
|
+
interface InitOptions {
|
|
8
|
+
/** Skip interactive prompts if name provided */
|
|
9
|
+
name?: string | undefined;
|
|
10
|
+
/** Pre-select project type */
|
|
11
|
+
type?: "extension" | "theme" | undefined;
|
|
12
|
+
/** Use minimal output (no ASCII art) */
|
|
13
|
+
minimal?: boolean | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Interactive init command
|
|
17
|
+
*/
|
|
18
|
+
export declare function init(options?: InitOptions): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Quick create without full interactive mode
|
|
21
|
+
* Used when name is provided via CLI argument
|
|
22
|
+
*/
|
|
23
|
+
export declare function quickCreate(name: string, type?: "extension" | "theme"): Promise<void>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,UAAU,WAAW;IACnB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,8BAA8B;IAC9B,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;IACzC,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiEnE;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,GAC3B,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|