@stackable-labs/mcp-app-extension 0.23.0 → 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/dist/server.js CHANGED
@@ -1,8 +1,12 @@
1
- import { STANDALONE_CLIENT, MCP_AUTH_FILE, getToken, STANDALONE_CLIENT_AUTH_FILE } from './chunk-HOOVB46Z.js';
2
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { IDENTITY_EVENT, ACTIVITY_EVENT, SURFACE_TARGET, PERMISSIONS, CAPABILITY_PERMISSION_MAP, ALLOWED_ICONS, UI_TAGS, UI_TAG_ATTRIBUTES, tagToComponentName } from '@stackable-labs/sdk-extension-contracts';
2
+ import { IDENTITY_EVENT, ACTIVITY_EVENT, SURFACE_TARGET, TEMPLATE_FLAVORS, PERMISSIONS, CAPABILITY_PERMISSION_MAP, EVENT_HOOK_PERMISSION_MAP, ALLOWED_ICONS, UI_TAGS, UI_TAG_ATTRIBUTES, tagToComponentName } from '@stackable-labs/sdk-extension-contracts';
3
+ import { readFile } from 'fs/promises';
4
+ import { join } from 'path';
5
+ import { homedir } from 'os';
4
6
  import { z } from 'zod';
5
7
 
8
+ // src/server.ts
9
+
6
10
  // ../../sdk/extension/ai-docs/src/generated/template-content.ts
7
11
  var PATTERN_SECTIONS = [
8
12
  {
@@ -530,6 +534,8 @@ const result = await capabilities.data.fetch('https://api.example.com/orders', {
530
534
  - For optional secret fields, the entire header is omitted if the value is not configured
531
535
  - Declare secret fields in your \`manifest.json\` \`settingsSchema\` with \`"secret": true\`
532
536
 
537
+ > See [Instance Settings](./instance-settings) for the full schema-declaration + storage-mode story, including which field types accept \`secret: true\`.
538
+
533
539
  ## context.read \u2014 Read Host Context
534
540
  Read host-provided context (customer ID, email, extension settings, etc.).
535
541
  - **Permission required:** \`context:read\`
@@ -1148,6 +1154,202 @@ const context = await capabilities.context.read()
1148
1154
  \`\`\`
1149
1155
  `;
1150
1156
  };
1157
+
1158
+ // ../../sdk/extension/ai-docs/src/generators/instance-settings.ts
1159
+ var SECRET_ALLOWED = ["text", "textarea", "email"];
1160
+ var generateInstanceSettings = () => {
1161
+ const fm = frontmatter({
1162
+ root: false,
1163
+ targets: ["*"],
1164
+ description: "Instance Settings: declaring per-Instance configuration in your extension manifest, the install-time admin form, and reading both regular and secure values from extension code.",
1165
+ globs: ["packages/extension/src/**/*.tsx", "packages/extension/src/**/*.ts", "packages/extension/public/manifest.json"]
1166
+ });
1167
+ return `${fm}
1168
+
1169
+ # Instance Settings
1170
+
1171
+ Instance Settings are the per-Instance configuration values your extension needs in
1172
+ order to do its job \u2014 API keys, account identifiers, environment toggles, feature
1173
+ flags. You declare them once in your extension's \`manifest.json\`; whoever installs
1174
+ your extension fills them in from the admin dashboard at install time; your code
1175
+ reads the values at runtime.
1176
+
1177
+ There are two flavors:
1178
+
1179
+ - **Regular** values live in plaintext and are readable from your extension code.
1180
+ Use them for non-sensitive configuration the UI needs to branch on.
1181
+ - **Secure** values are encrypted at rest and **never** leave the server. Use them
1182
+ for credentials. Your code references them as a placeholder; the proxy
1183
+ substitutes the real value server-side when it makes the outbound request.
1184
+
1185
+ ## 1. Declare your settings in \`manifest.json\`
1186
+
1187
+ Add a \`settingsSchema\` array to \`packages/extension/public/manifest.json\`. Each
1188
+ entry describes one input on the install-time admin form:
1189
+
1190
+ \`\`\`json
1191
+ {
1192
+ "name": "My Extension",
1193
+ "version": "1.0.0",
1194
+ "targets": ["slot.content"],
1195
+ "permissions": ["context:read", "data:fetch"],
1196
+ "allowedDomains": ["api.example.com"],
1197
+ "settingsSchema": [
1198
+ {
1199
+ "identifier": "apiKey",
1200
+ "label": "API Key",
1201
+ "type": "text",
1202
+ "secret": true,
1203
+ "required": true,
1204
+ "description": "Your account API key. Stored encrypted; injected server-side into outbound request headers."
1205
+ },
1206
+ {
1207
+ "identifier": "environmentType",
1208
+ "label": "Environment",
1209
+ "type": "select",
1210
+ "options": [
1211
+ { "label": "Production", "value": "prod" },
1212
+ { "label": "Sandbox", "value": "sandbox" }
1213
+ ]
1214
+ }
1215
+ ]
1216
+ }
1217
+ \`\`\`
1218
+
1219
+ That's the entire authoring step. The next time someone installs (or re-syncs)
1220
+ your extension, the admin dashboard renders a form from this schema on the
1221
+ Instance settings page. Regular fields show inline values; secret fields show a
1222
+ masked input that accepts a value once and displays \`\u2022\u2022\u2022\u2022\` after save (the
1223
+ cleartext value is never returned by the API).
1224
+
1225
+ ### Field types
1226
+
1227
+ | Type | Renders as | Accepts \`secret: true\` |
1228
+ |------|------------|--------------------------|
1229
+ | \`text\` | Single-line text input | Yes |
1230
+ | \`textarea\` | Multi-line text input | Yes |
1231
+ | \`email\` | Email input with format validation | Yes |
1232
+ | \`number\` | Numeric input (\`min\` / \`max\` / \`step\`) | No |
1233
+ | \`select\` | Dropdown (use \`allowMultiple\` for multi-select) | No |
1234
+ | \`radio\` | Radio button group | No |
1235
+ | \`toggle\` | On/off switch | No |
1236
+ | \`tags\` | Tag list / multi-string entry | No |
1237
+
1238
+ The \`secret: true\` flag is only valid on \`${SECRET_ALLOWED.join("`, `")}\` types.
1239
+
1240
+ All fields share these optional properties: \`required\`, \`placeholder\`,
1241
+ \`description\` (help text shown below the input), and \`dependsOn\` (conditional
1242
+ visibility \u2014 show this field only when another field has a matching value).
1243
+
1244
+ ## 2. The install-time admin form
1245
+
1246
+ When someone installs your extension into one of their Instances, the admin
1247
+ dashboard reads your \`settingsSchema\` and generates a form. Each field becomes
1248
+ an input; \`required\` fields block save; \`description\` text becomes inline help;
1249
+ \`dependsOn\` rules show or hide fields based on other field values.
1250
+
1251
+ Each Instance gets its own copy of these values, so the same extension can run
1252
+ side-by-side on multiple Instances with completely different credentials and
1253
+ configuration. The installer can edit values later from the same Instance
1254
+ settings page \u2014 your code always sees the current values.
1255
+
1256
+ ## 3. Read regular values: \`useSettings()\` (or \`useContextData()\`)
1257
+
1258
+ Regular (non-secret) values are exposed to your extension code via the
1259
+ \`useSettings()\` hook, keyed by each field's \`identifier\`. This requires the
1260
+ \`context:read\` permission.
1261
+
1262
+ \`\`\`tsx
1263
+ import { Surface, ui, useSettings } from '@stackable-labs/sdk-extension-react'
1264
+
1265
+ export const Content = () => {
1266
+ const settings = useSettings()
1267
+ const environmentType = (settings.environmentType as string) || 'prod'
1268
+
1269
+ return (
1270
+ <Surface id="slot.content">
1271
+ <ui.Card>
1272
+ <ui.CardContent>
1273
+ <ui.Text>Looking up orders\u2026</ui.Text>
1274
+ {environmentType === 'sandbox' && (
1275
+ <ui.Text className="text-xs opacity-50">Sandbox Mode</ui.Text>
1276
+ )}
1277
+ </ui.CardContent>
1278
+ </ui.Card>
1279
+ </Surface>
1280
+ )
1281
+ }
1282
+ \`\`\`
1283
+
1284
+ If you need settings alongside other context data (customer, locale, etc.), they
1285
+ also come back from \`useContextData()\`:
1286
+
1287
+ \`\`\`tsx
1288
+ import { useContextData } from '@stackable-labs/sdk-extension-react'
1289
+
1290
+ const { loading, settings, customerId } = useContextData()
1291
+ \`\`\`
1292
+
1293
+ > **Note:** Secret fields are **never** present in \`useSettings()\` or on
1294
+ > \`ctx.settings\`. Reading \`settings.apiKey\` for a \`secret: true\` field returns
1295
+ > \`undefined\`, even when a value is configured. If you find yourself reaching
1296
+ > for a secret value in extension code, you almost certainly want the
1297
+ > \`data.fetch\` placeholder pattern below instead.
1298
+
1299
+ ## 4. Use secure values: \`{{settings.<identifier>}}\` in \`data.fetch\`
1300
+
1301
+ Secure values are decrypted only by the proxy Lambda that handles \`data.fetch\` \u2014
1302
+ at substitution time, per-request, then discarded. Your code references them as
1303
+ template placeholders in **header values**:
1304
+
1305
+ \`\`\`tsx
1306
+ import { useCapabilities, useSettings } from '@stackable-labs/sdk-extension-react'
1307
+
1308
+ const capabilities = useCapabilities()
1309
+ const settings = useSettings()
1310
+ const environmentType = (settings.environmentType as string) || 'prod'
1311
+
1312
+ const result = await capabilities.data.fetch('https://api.example.com/orders', {
1313
+ method: 'GET',
1314
+ headers: {
1315
+ // environmentType is a regular value \u2014 interpolate it normally
1316
+ 'X-Environment': environmentType,
1317
+ // apiKey is secret \u2014 the proxy substitutes it server-side; the cleartext
1318
+ // value never enters this code path
1319
+ 'X-Api-Key': '{{settings.apiKey}}',
1320
+ },
1321
+ })
1322
+ if (!result.ok) throw new Error(\`Request failed: \${result.status}\`)
1323
+ \`\`\`
1324
+
1325
+ Placeholders are only resolved in **header values** \u2014 not URLs, not header
1326
+ names, not the request body. For \`required: true\` secret fields the proxy
1327
+ returns \`400\` when the value is not configured; for optional secret fields
1328
+ the entire header is omitted.
1329
+
1330
+ ## Regular vs secure at a glance
1331
+
1332
+ | | Regular | Secure (\`secret: true\`) |
1333
+ |--|---------|--------------------------|
1334
+ | **Stored** | Plaintext | Encrypted with a per-Instance derived key |
1335
+ | **Readable from extension code?** | Yes \u2014 \`useSettings().<identifier>\` | **Never** \u2014 secrets are not serialized to the browser or sandbox |
1336
+ | **How to use the value** | Read at render time and use freely | Reference as a \`{{settings.<identifier>}}\` placeholder in \`data.fetch\` headers \u2014 the proxy substitutes server-side |
1337
+ | **Returned by admin API after save?** | Yes (the value is shown back in the form) | No (the form shows \`\u2022\u2022\u2022\u2022\`; the cleartext value is unrecoverable) |
1338
+
1339
+ ## When in doubt, mark it \`secret: true\`
1340
+
1341
+ The cost is negligible \u2014 one encryption per write, one decryption per outbound
1342
+ request. The blast radius of an accidental log line, screenshot, or sandbox
1343
+ exfiltration drops to zero. Secrets on one Instance also can't be used to
1344
+ decrypt secrets on any other Instance, even when both Instances run the same
1345
+ extension \u2014 encryption keys are derived per-Instance.
1346
+
1347
+ If a value is sensitive at all (API keys, OAuth tokens, signing secrets, webhook
1348
+ shared secrets, personal access tokens), declare the field \`secret: true\` and
1349
+ reach for it via the \`data.fetch\` placeholder pattern. Save \`useSettings()\` for
1350
+ the values your UI legitimately needs to branch on.
1351
+ `;
1352
+ };
1151
1353
  var generatePermissions = () => {
1152
1354
  const fm = frontmatter({
1153
1355
  root: false,
@@ -1538,28 +1740,42 @@ export const Header = () => {
1538
1740
  - **Use ScrollArea** \u2014 wrap content surfaces in \`<ui.ScrollArea>\` for overflow handling
1539
1741
  `;
1540
1742
  };
1541
-
1542
- // ../../sdk/extension/ai-docs/src/cli-commands.ts
1543
1743
  var DLX = "pnpm --config.dlx-cache-max-age=0 dlx";
1544
1744
  var CLI = {
1545
1745
  /** Scaffold a new extension project */
1546
- create: (name = "<project-name>") => `${DLX} @stackable-labs/create-extension ${name}`,
1746
+ create: (name = "<extension-name>") => `${DLX} @stackable-labs/create-extension ${name}`,
1547
1747
  /** Start dev servers with hot reload */
1548
1748
  dev: `${DLX} @stackable-labs/cli-app-extension@latest dev`,
1549
- /** Deploy the extension */
1749
+ /** Deploy the extension (future) */
1550
1750
  deploy: `${DLX} @stackable-labs/cli-app-extension@latest deploy`,
1551
- /** Validate the extension for common errors */
1751
+ /** Validate the extension for common errors (coming soon) */
1552
1752
  validate: `${DLX} @stackable-labs/cli-app-extension@latest validate`,
1553
1753
  /** Scaffold from an existing extension */
1554
1754
  scaffold: `${DLX} @stackable-labs/cli-app-extension@latest scaffold`,
1555
1755
  /** Update an existing extension */
1556
- update: `${DLX} @stackable-labs/cli-app-extension@latest update`
1756
+ update: `${DLX} @stackable-labs/cli-app-extension@latest update`,
1757
+ /** Manage CLI authentication (browser-based OAuth) */
1758
+ auth: {
1759
+ login: `${DLX} @stackable-labs/cli-app-extension@latest auth login`,
1760
+ logout: `${DLX} @stackable-labs/cli-app-extension@latest auth logout`,
1761
+ status: `${DLX} @stackable-labs/cli-app-extension@latest auth status`
1762
+ },
1763
+ /** AI editor configuration tools (Skills + MCP) */
1764
+ ai: {
1765
+ /** Download AI editor config files (Stackable Skills) into your project */
1766
+ scaffold: `${DLX} @stackable-labs/cli-app-extension@latest ai scaffold`,
1767
+ /** Add Stackable MCP server config to your project */
1768
+ mcp: `${DLX} @stackable-labs/cli-app-extension@latest ai mcp`
1769
+ }
1557
1770
  };
1558
- var TEMPLATE_FLAVORS = [
1559
- { name: "minimal", label: "Minimal", description: "Bare minimum \u2014 single surface, hello-world component" },
1560
- { name: "starter", label: "Starter", description: "Common patterns \u2014 store, api helpers, menu (default)" },
1561
- { name: "kitchen-sink", label: "Kitchen Sink", description: "Everything \u2014 every component, capability, surface, and hook" }
1562
- ];
1771
+ var TEMPLATE_FLAVOR_META = {
1772
+ minimal: { label: "Minimal", description: "Bare minimum \u2014 single surface, hello-world component" },
1773
+ starter: { label: "Starter", description: "Common patterns \u2014 store, api helpers, menu (default)" },
1774
+ "kitchen-sink": { label: "Kitchen Sink", description: "Everything \u2014 every component, capability, surface, and hook" }
1775
+ };
1776
+ var TEMPLATE_FLAVOR_DETAILS = TEMPLATE_FLAVORS.map(
1777
+ (name) => ({ name, ...TEMPLATE_FLAVOR_META[name] })
1778
+ );
1563
1779
 
1564
1780
  // ../../sdk/extension/ai-docs/src/generators/quick-start.ts
1565
1781
  var generateQuickStart = () => {
@@ -1575,6 +1791,21 @@ var generateQuickStart = () => {
1575
1791
 
1576
1792
  Create, develop, and deploy your first Stackable extension in minutes.
1577
1793
 
1794
+ ## Choosing your path
1795
+
1796
+ Stackable supports two authoring paths. Both produce the same kind of extension and ship to the same marketplace.
1797
+
1798
+ | | AI Extension Studio | CLI (this guide) |
1799
+ |---|---|---|
1800
+ | **Best for** | Prototyping, learning, quick iterations | Production extensions, team workflows |
1801
+ | **Code structure** | Single file | Multi-file (surfaces/, components/, lib/) |
1802
+ | **Preview** | Built-in live preview | Local dev server + Cloudflare tunnel |
1803
+ | **AI assistance** | Sidekick chat + smart insertion | Skills, MCP server, and Claude Code plugin (see [AI-Accelerated Development](/docs/reference/ai-accelerated-development)) |
1804
+ | **Version control** | Auto-saved to cloud | Git-based |
1805
+ | **Deployment** | Link to extension + deploy | CLI deploy command |
1806
+
1807
+ Both produce the same output \u2014 a Stackable extension that runs in the host application via the same Remote DOM pipeline. **This guide covers the [CLI](/docs/reference/cli-reference) path.** For Studio, see [AI Extension Studio](/docs/reference/extension-studio).
1808
+
1578
1809
  ## Prerequisites
1579
1810
 
1580
1811
  - **Node.js** 22 or later
@@ -1713,21 +1944,11 @@ export const Content = () => {
1713
1944
  }
1714
1945
  \`\`\`
1715
1946
 
1716
- ## 6. Validate & Deploy
1947
+ ## 6. Submit for review
1717
1948
 
1718
- Before deploying, validate your extension:
1949
+ When your extension is ready, open it in the [Stackable admin dashboard](https://admin.stackablelabs.com), fill in the marketplace listing (icon, screenshots, description, tagline, support links), and submit for review. Most reviews complete within a few business days.
1719
1950
 
1720
- \`\`\`bash
1721
- ${CLI.validate}
1722
- \`\`\`
1723
-
1724
- This checks manifest structure, permission usage, surface targets, and import patterns.
1725
-
1726
- When ready, deploy:
1727
-
1728
- \`\`\`bash
1729
- ${CLI.deploy}
1730
- \`\`\`
1951
+ > *CLI \`validate\` and \`deploy\` commands are on the roadmap (see [CLI Reference](/docs/reference/cli-reference#validate-coming-soon)). Today, manifests are validated server-side during submission and via the \`validate_manifest\` MCP tool.*
1731
1952
 
1732
1953
  ## Next Steps
1733
1954
 
@@ -1746,7 +1967,7 @@ var generateCliReference = () => {
1746
1967
  description: "CLI commands for creating, developing, and deploying Stackable extensions",
1747
1968
  globs: ["packages/extension/src/**/*.tsx", "packages/extension/src/**/*.ts"]
1748
1969
  });
1749
- const templateRows = TEMPLATE_FLAVORS.map((t) => `| \`${t.name}\` | ${t.description} |`).join("\n");
1970
+ const templateRows = TEMPLATE_FLAVOR_DETAILS.map((t) => `| \`${t.name}\` | ${t.description} |`).join("\n");
1750
1971
  return `${fm}
1751
1972
 
1752
1973
  # CLI Reference
@@ -1765,7 +1986,7 @@ ${CLI.create()}
1765
1986
 
1766
1987
  | Argument | Description |
1767
1988
  |----------|-------------|
1768
- | \`project-name\` | Directory name for the new project |
1989
+ | \`extension-name\` | Name for your extension (saved as the manifest's \`name\`; kebab-cased to derive the directory and extension ID) |
1769
1990
 
1770
1991
  **Options:**
1771
1992
 
@@ -1826,9 +2047,11 @@ Append this to your deployed host app URL to load your local extension instead
1826
2047
  of the production bundle. The override is browser-session only \u2014 no DB changes,
1827
2048
  no shared state. Each developer gets isolated overrides.
1828
2049
 
1829
- ## validate
2050
+ ## validate *(coming soon)*
1830
2051
 
1831
- Check your extension for common errors before deploying:
2052
+ > *NOTE: manifests are validated server-side during the marketplace submission flow, in the AI Studio, or can be validated using the \`validate_manifest\` MCP tool \u2014 see [AI-Accelerated Development](/docs/reference/ai-accelerated-development#live-mcp-server).*
2053
+
2054
+ When shipped, the \`validate\` command will check your extension for common errors locally before submission:
1832
2055
 
1833
2056
  \`\`\`bash
1834
2057
  ${CLI.validate}
@@ -1849,23 +2072,21 @@ ${CLI.validate}
1849
2072
  - \`0\` \u2014 all checks pass
1850
2073
  - \`1\` \u2014 errors found (must fix before deploying)
1851
2074
 
1852
- ## deploy
2075
+ ## deploy *(future)*
2076
+
2077
+ > *This command is on the roadmap. Currently build, deployment and hosting are the responsibility of the extension developer with your hosted Bundle URL being updated via the admin dashboard or via CLI.*
1853
2078
 
1854
- Package and deploy the extension:
2079
+ When shipped, the \`deploy\` command will package and ship your extension straight from the terminal:
1855
2080
 
1856
2081
  \`\`\`bash
1857
2082
  ${CLI.deploy}
1858
2083
  \`\`\`
1859
2084
 
1860
- **What it does:**
1861
- 1. Runs validation checks (same as validate)
1862
- 2. Builds the extension for production
1863
- 3. Packages the build output
1864
- 4. Uploads to the Stackable extension registry
1865
-
1866
- **Prerequisites:**
1867
- - All validation checks must pass
1868
- - You must be authenticated (follow the prompts if not)
2085
+ **Planned behavior:**
2086
+ 1. Run validation checks (same as \`validate\`)
2087
+ 2. Build the extension for production
2088
+ 3. Package the build output
2089
+ 4. Upload to the Stackable extension registry
1869
2090
 
1870
2091
  ## scaffold
1871
2092
 
@@ -1902,6 +2123,62 @@ ${CLI.update} [extensionId]
1902
2123
  | \`--bundle-url <url>\` | New bundle URL |
1903
2124
  | \`--enabled <bool>\` | Enable/disable extension |
1904
2125
  | \`--dir <path>\` | Project root (default: cwd) |
2126
+
2127
+ ## auth
2128
+
2129
+ Manage CLI authentication. Subcommands open a browser-based OAuth flow against the Stackable platform and store the resulting credentials locally.
2130
+
2131
+ **Subcommands:**
2132
+
2133
+ | Subcommand | Description |
2134
+ |------------|-------------|
2135
+ | \`login\` | Authenticate with Stackable via browser-based OAuth |
2136
+ | \`logout\` | Clear stored CLI credentials |
2137
+ | \`status\` | Show current authentication status (signed-in user, org, token expiry) |
2138
+
2139
+ **Examples:**
2140
+
2141
+ \`\`\`bash
2142
+ # First-time setup
2143
+ ${CLI.auth.login}
2144
+
2145
+ # Check who's signed in
2146
+ ${CLI.auth.status}
2147
+
2148
+ # Sign out
2149
+ ${CLI.auth.logout}
2150
+ \`\`\`
2151
+
2152
+ ## ai
2153
+
2154
+ Manage AI editor configuration in your Extension project. Subcommands write Stackable's [AI tooling](/docs/reference/ai-accelerated-development) (Skills + MCP server config) into your project.
2155
+
2156
+ **Subcommands:**
2157
+
2158
+ | Subcommand | Description |
2159
+ |------------|-------------|
2160
+ | \`scaffold\` | Download Stackable Skills into your project's \`.claude/\`, \`.cursor/\`, \`.windsurf/\`, etc. |
2161
+ | \`mcp\` | Add Stackable MCP server config to your project so any MCP-compatible AI client can connect |
2162
+
2163
+ **Options:**
2164
+
2165
+ | Flag | Description | Applies to |
2166
+ |------|-------------|------------|
2167
+ | \`--version <version>\` | AI docs version (semver or \`latest\`, default: \`latest\`) | both |
2168
+ | \`--dir <path>\` | Project root directory (default: cwd) | \`mcp\` only |
2169
+
2170
+ **Examples:**
2171
+
2172
+ \`\`\`bash
2173
+ # Add the latest Stackable Skills to your project
2174
+ ${CLI.ai.scaffold}
2175
+
2176
+ # Pin a specific version
2177
+ ${CLI.ai.scaffold} --version 0.2.0
2178
+
2179
+ # Configure your project to talk to the Stackable MCP server
2180
+ ${CLI.ai.mcp}
2181
+ \`\`\`
1905
2182
  `;
1906
2183
  };
1907
2184
 
@@ -2060,29 +2337,29 @@ var generateExtensionStudio = () => {
2060
2337
  const fm = frontmatter({
2061
2338
  root: false,
2062
2339
  targets: ["*"],
2063
- description: "Extension Studio: in-browser builder with AI assistant, component palette, and live preview",
2340
+ description: "AI Extension Studio: in-browser builder with AI assistant, component palette, and live preview",
2064
2341
  globs: ["packages/extension/src/**/*.tsx", "packages/extension/src/**/*.ts"]
2065
2342
  });
2066
2343
  return `${fm}
2067
2344
 
2068
- # Extension Studio
2345
+ # AI Extension Studio
2069
2346
 
2070
- Extension Studio is an in-browser development environment for building Stackable
2347
+ Stackable's AI Extension Studio is an in-browser development environment for building Stackable
2071
2348
  extensions without local tooling. It runs inside the admin dashboard and provides
2072
2349
  a code editor, live preview, component palette, and an AI assistant \u2014 everything
2073
2350
  needed to create, iterate on, and deploy extensions from the browser.
2074
2351
 
2075
2352
  ## Layout
2076
2353
 
2077
- Studio is organized as a 3-pane workspace:
2354
+ "Studio" is organized as a 3-pane workspace:
2078
2355
 
2079
2356
  | Pane | Position | Purpose |
2080
2357
  |------|----------|---------|
2081
2358
  | **Shelf** | Left (collapsible) | Component palette, surface picker, capability list |
2082
- | **Stage** | Center | Code editor (CodeMirror) or live preview \u2014 toggle between modes |
2359
+ | **Stage** | Center | Code editor or live preview \u2014 toggle between modes |
2083
2360
  | **Sidekick** | Right (collapsible, resizable) | AI chat assistant for code generation and SDK guidance |
2084
2361
 
2085
- ## The Shelf
2362
+ ## The "Shelf"
2086
2363
 
2087
2364
  The Shelf is a collapsible sidebar with three sections:
2088
2365
 
@@ -2107,7 +2384,7 @@ The SDK capabilities your extension can use: \`data.query\`, \`data.fetch\`,
2107
2384
  \`events:identity\`, \`events:messaging\`, and \`events:activity\`. Clicking a
2108
2385
  capability adds the permission to your manifest and AI-inserts the hook usage.
2109
2386
 
2110
- ## The Stage
2387
+ ## The "Stage"
2111
2388
 
2112
2389
  The center pane switches between two modes:
2113
2390
 
@@ -2122,7 +2399,7 @@ Changes in Code mode recompile automatically via in-browser esbuild and update
2122
2399
  the preview. The toolbar shows the current status: Ready, Saving, Compiling,
2123
2400
  Thinking (AI), or Error.
2124
2401
 
2125
- ## The Sidekick
2402
+ ## The "Sidekick"
2126
2403
 
2127
2404
  The Sidekick is an AI chat assistant that understands the Stackable Extension SDK.
2128
2405
  It can:
@@ -2162,18 +2439,114 @@ Studio toolbar.
2162
2439
 
2163
2440
  ## Studio vs CLI
2164
2441
 
2165
- | | Studio | CLI |
2442
+ Comparing approaches? See [Choosing your path](/docs/guides/quick-start#choosing-your-path) in the Quick Start guide for the full Studio vs CLI comparison.
2443
+
2444
+ Both workflows produce the same output \u2014 a Stackable extension that runs in the host application via the same Remote DOM pipeline. Start in Studio to prototype, then scaffold to CLI when you need the full development workflow.
2445
+ `;
2446
+ };
2447
+
2448
+ // ../../sdk/extension/ai-docs/src/generators/ai-accelerated-development.ts
2449
+ var generateAIAcceleratedDevelopment = () => {
2450
+ const fm = frontmatter({
2451
+ root: false,
2452
+ targets: ["*"],
2453
+ description: "AI-Accelerated Development: AI Extension Studio, Agent Skills, the live MCP server, and the Claude Code plugin \u2014 and how to choose between them.",
2454
+ globs: ["*"]
2455
+ });
2456
+ return `${fm}
2457
+
2458
+ # AI-Accelerated Development
2459
+
2460
+ Stackable's AI-native messaging experience platform makes building extensions really simple - and incredibly fast. Four complementary surfaces (Studio, Agent Skills, MCP Server, and Claude Code Plugin) take you from possibility to production in minutes.
2461
+
2462
+ | Surface | Best for | Install |
2166
2463
  |---|---|---|
2167
- | **Best for** | Prototyping, learning, quick iterations | Production extensions, team workflows |
2168
- | **Code structure** | Single file | Multi-file (surfaces/, components/, lib/) |
2169
- | **Preview** | Built-in live preview | Local dev server + Cloudflare tunnel |
2170
- | **AI assistance** | Sidekick chat + smart insertion | Your own AI editor (with SDK skills) |
2171
- | **Version control** | Auto-saved to cloud | Git-based |
2172
- | **Deployment** | Link to extension + deploy | CLI deploy command |
2464
+ | **AI Extension Studio** | First extensions, prototyping, in-browser builds | Open the admin dashboard |
2465
+ | **Agent Skills** | Any AI coding assistant | \`pnpm dlx skills add stackable-labs/skills\` (auto-bundled in scaffolds) |
2466
+ | **Live MCP Server** | AI clients that speak MCP | Add to your client's MCP config |
2467
+ | **Claude Code Plugin** | Claude Code users (bundles Skills + MCP) | \`/plugin marketplace add stackable-labs/claude-plugins\` |
2468
+
2469
+ ---
2470
+
2471
+ ## AI Extension Studio
2472
+
2473
+ AI Extension Studio is more than just another AI App builder ([Lovable](https://lovable.dev), [v0](https://v0.dev), [Bolt](https://bolt.new), [Replit](https://replit.com), etc.). It's an in-browser companion trained specifically to conceptualize and build experiences for the Stackable framework that are secure, look great, and are **production ready in minutes!**
2474
+
2475
+ The 3-pane workspace (component palette, live preview, agentic chat) takes you from idea to shipped code in moments. The agentic chat ("Sidekick") writes and modifies code, updates your manifest, looks up SDK reference on demand, and suggests next steps as you work. No installs, no Node version to manage, no tunnel \u2014 open the admin dashboard and you're building.
2476
+
2477
+ **Best for:** first extensions, prototyping, non-developers, and quick experiments.
2478
+
2479
+ **When to move to the CLI:** Studio produces a simple single-file project. When you need multi-file structure, custom dependencies, your own build pipeline, or git-based version control, you can transition seamlessly. Either download your Studio project as a ZIP from the export menu, or pull it directly via the CLI. Either path produces a fully-structured local project ready for production build and deployment.
2480
+
2481
+ For full Studio details, see [AI Extension Studio](/docs/reference/extension-studio).
2482
+
2483
+ ## AI Agentic Skills
2484
+
2485
+ [Agent Skills](https://agentskills.io) is an open standard for publishing machine-readable knowledge that AI coding assistants can pull on demand instead of crawling your docs. Stackable publishes the core SDK concepts (surfaces, capabilities, components, hooks, patterns, recipes, and more) as Agent Skills, so any compatible assistant has just-in-time access to exactly the surface area it needs.
2486
+
2487
+ ### How to install
2488
+
2489
+ - **Bundled automatically when you scaffold** \u2014 \`@stackable-labs/cli-app-extension create\` writes the Stackable Skills into your project's \`.claude/\`, \`.cursor/\`, \`.windsurf/\`, and equivalent directories. No extra setup.
2490
+ - **Standalone install** \u2014 for an existing project, or to update to the latest skills, run:
2491
+ \`\`\`bash
2492
+ pnpm dlx skills add stackable-labs/skills
2493
+ \`\`\`
2494
+ This is powered by [Vercel's open \`skills\` CLI](https://github.com/vercel-labs/skills).
2495
+
2496
+ ### Compatible assistants
2497
+
2498
+ Agent Skills work with any tool that supports the format \u2014 Claude Code, Cursor, Windsurf, Codex, VS Code (with MCP-aware extensions), Continue, Zed, and 40+ others listed on agentskills.io. Stackable's Claude Code plugin (below) bundles + auto-updates these skills for Claude Code users.
2499
+
2500
+ ## Live MCP Server
2501
+
2502
+ The Stackable platform exposes a hosted MCP (Model Context Protocol) server that any compatible AI client can connect to for live access to your account's apps, extensions, and platform metadata.
2503
+
2504
+ **Endpoint:** \`https://api-use1.stackablelabs.io/mcp/app-extension\`
2505
+
2506
+ ### Available tools (snapshot)
2507
+
2508
+ | Tool | What it does |
2509
+ |---|---|
2510
+ | \`list_apps\` | List apps in your account |
2511
+ | \`list_extensions\` | List extensions for a given app |
2512
+ | \`list_instances\` | List deployed instances of an extension |
2513
+ | \`list_skills\` | List available SDK skills |
2514
+ | \`lookup_skill\` | Fetch a specific SDK skill's content on demand |
2515
+ | \`get_extension\` | Fetch full details for a single extension |
2516
+ | \`validate_manifest\` | Validate an extension manifest against the platform |
2517
+ | \`validate_permissions\` | Validate manifest permissions |
2518
+
2519
+ ### Compatible clients
2520
+
2521
+ [Claude Desktop](https://claude.ai/download), [VS Code](https://code.visualstudio.com), [Cursor](https://cursor.com), [Codex](https://developers.openai.com/codex), [Antigravity](https://antigravity.google), [Windsurf](https://windsurf.com), and any other MCP-compatible AI tool. Each client has its own way to register MCP servers \u2014 see your client's docs for the exact config syntax.
2522
+
2523
+ Authentication uses standard OAuth2 \u2014 your client will prompt you to sign in on first use.
2524
+
2525
+ ## Claude Code Plugin
2526
+
2527
+ For Claude Code users, the Stackable Claude Code plugin bundles the Skills and pre-configures the MCP server in one install.
2528
+
2529
+ **Install:**
2530
+ \`\`\`bash
2531
+ # Add marketplace
2532
+ /plugin marketplace add stackable-labs/claude-plugins
2533
+
2534
+ # Install plugin
2535
+ /plugin install stackable-extension-dev@stackable-claude-plugins
2536
+ \`\`\`
2537
+
2538
+ **What it includes:**
2539
+ - Latest Stackable Agent Skills (auto-updated)
2540
+ - The Stackable MCP server pre-configured
2541
+ - Slash commands and workflows tailored for extension development
2542
+
2543
+ After install, Claude Code can scaffold, validate, and inspect your Stackable extensions directly from your editor without any extra config.
2173
2544
 
2174
- Both workflows produce the same output \u2014 a Stackable extension that runs in the
2175
- host application via the same Remote DOM pipeline. Start in Studio to prototype,
2176
- then scaffold to CLI when you need the full development workflow.
2545
+ ## Choosing your tools
2546
+
2547
+ - **Just starting?** Open [AI Extension Studio](/docs/reference/extension-studio) in the admin dashboard. No installs, no decisions \u2014 get started on your first extension right in the browser.
2548
+ - **Want more control, or preparing for release?** Use the [CLI](/docs/guides/quick-start) for a full local TypeScript project: multi-file structure, your own dependencies, git-based version control, and your own build pipeline.
2549
+ - **Building from scratch with your own AI workflow?** Wire up your editor with [Agent Skills](#ai-agentic-skills), the [Live MCP Server](#live-mcp-server), or both \u2014 or use the [Claude Code Plugin](#claude-code-plugin) for Claude Code users, which auto-bundles Skills and pre-configures the MCP server in one install.
2177
2550
  `;
2178
2551
  };
2179
2552
  var generateProjectStructure = () => {
@@ -3012,12 +3385,23 @@ var generateValidateExtensionCommand = () => {
3012
3385
  description: "Validate this extension for common errors before deploying (manifest, permissions, surfaces, imports)",
3013
3386
  targets: ["*"]
3014
3387
  });
3388
+ const eventHookBullets = Object.keys(EVENT_HOOK_PERMISSION_MAP).map((hook) => `- \`${hook}\` \u2192 needs \`${EVENT_HOOK_PERMISSION_MAP[hook]}\` permission (also check manifest \`events\` array has matching entries)`).join("\n");
3015
3389
  return `${fm}
3016
3390
 
3017
3391
  # Validate Extension
3018
3392
 
3019
3393
  Check this extension for common errors before deploying. Run through each check
3020
- and report all issues found.
3394
+ and ensure no issues found before you submit.
3395
+
3396
+ > **Tip \u2014 let the platform validate for you.** The hosted Stackable MCP server
3397
+ > exposes a \`validate_manifest\` tool that runs the same server-side checks the
3398
+ > marketplace submission flow uses. If your AI client is connected to the
3399
+ > Stackable MCP server (see [AI-Accelerated Development](/docs/reference/ai-accelerated-development#live-mcp-server)),
3400
+ > just ask it to validate \`packages/extension/public/manifest.json\` for you \u2014
3401
+ > it returns structured errors and warnings without you having to walk the
3402
+ > checklist below by hand. The manual steps remain useful for code-level checks
3403
+ > the manifest validator can't see (permission usage, surface wiring, sandbox
3404
+ > compliance).
3021
3405
 
3022
3406
  ## 1. Manifest validation
3023
3407
  Read \`packages/extension/public/manifest.json\` and verify:
@@ -3037,9 +3421,7 @@ Scan all \`.tsx\` files in \`packages/extension/src/\` for capability usage:
3037
3421
  - \`capabilities.actions.toast\` \u2192 needs \`actions:toast\` permission
3038
3422
  - \`capabilities.actions.invoke\` \u2192 needs \`actions:invoke\` permission
3039
3423
  - \`useExtendIdentity\` \u2192 needs \`extend:identity\` permission
3040
- - \`useIdentityEvent\` \u2192 needs \`events:identity\` permission (also check manifest \`events\` array has matching entries)
3041
- - \`useMessagingEvent\` \u2192 needs \`events:messaging\` permission (also check manifest \`events\` array has matching entries)
3042
- - \`useActivityEvent\` \u2192 needs \`events:activity\` permission (also check manifest \`events\` array has matching entries)
3424
+ ${eventHookBullets}
3043
3425
 
3044
3426
  Report:
3045
3427
  - **Missing permissions:** capabilities used in code but not declared in manifest
@@ -3059,6 +3441,202 @@ Verify that:
3059
3441
  ## 5. Summary
3060
3442
  Print a summary: total issues found, categorized by severity (error vs warning).
3061
3443
  Errors must be fixed before deploying. Warnings are recommendations.
3444
+
3445
+ ## Next: deploy
3446
+
3447
+ Once validation passes, build the production bundle, host it, and register the
3448
+ Bundle URL with Stackable. See the [Deploy guide](/docs/guides/deploy).
3449
+ `;
3450
+ };
3451
+
3452
+ // ../../sdk/extension/ai-docs/src/commands/deploy-extension.ts
3453
+ var generateDeployExtensionCommand = () => {
3454
+ const fm = frontmatter({
3455
+ description: "Build, host, and register the production Bundle URL for this Stackable extension",
3456
+ targets: ["*"]
3457
+ });
3458
+ return `${fm}
3459
+
3460
+ # Deploy
3461
+
3462
+ Stackable hosts the marketplace, the proxy, and the runtime \u2014 but **you host
3463
+ your extension's bundle/runtime**. Deployment is three steps: build for production,
3464
+ upload to a host of your choice, then point your extension's **Bundle URL** at
3465
+ the result.
3466
+
3467
+ ## 1. Build the production bundle
3468
+
3469
+ From the project root:
3470
+
3471
+ \`\`\`bash
3472
+ pnpm build
3473
+ \`\`\`
3474
+
3475
+ This runs Vite in production mode and writes the static bundle to
3476
+ \`packages/extension/dist/\` \u2014 a small set of JS/CSS files plus your
3477
+ \`manifest.json\`. That's the artifact you ship.
3478
+
3479
+ > **Tip:** Validate the build before you upload it. Run through the
3480
+ > [Validate guide](/docs/guides/validate) (or ask your AI client to call the
3481
+ > \`validate_manifest\` MCP tool) so manifest typos and missing permissions are
3482
+ > caught before they reach the marketplace.
3483
+
3484
+ ## 2. Upload to a hosting provider
3485
+
3486
+ Stackable doesn't care where the bundle lives \u2014 only that it's served over
3487
+ HTTPS with permissive CORS so the host runtime can fetch it. Anything that
3488
+ serves static files works. The most common options:
3489
+
3490
+ | Provider | Notes |
3491
+ |----------|-------|
3492
+ | **Netlify** | Drop \`packages/extension/dist/\` into a site, or wire up Git auto-deploys. CORS is already permissive. |
3493
+ | **Vercel** | Same idea \u2014 link the repo and let Vercel build/deploy on push. Set the build output to \`packages/extension/dist\`. |
3494
+ | **Cloudflare Pages** | Connect the repo, set the output directory, deploys land on the edge globally. |
3495
+ | **AWS S3 + CloudFront** | Sync \`dist/\` to an S3 bucket, front it with a CloudFront distribution. Make sure the CloudFront response headers include \`Access-Control-Allow-Origin: *\` (or your host origin). |
3496
+ | **Your own CDN / object storage** | Anything that returns \`200 OK\` with the right \`Content-Type\` and CORS headers will work. |
3497
+
3498
+ Whichever you pick, the only requirements are:
3499
+
3500
+ - Bundle is reachable over **HTTPS**
3501
+ - CORS allows the host application's origin (most providers do this by default; S3+CloudFront is the one that usually needs explicit configuration)
3502
+ - The URL is **stable** \u2014 every Instance running your extension fetches from this URL on load
3503
+
3504
+ > **Need help with setup or deployment?** Reach out at [developers@stackablelabs.com](mailto:developers@stackablelabs.com) or open an issue / discussion on [GitHub](https://github.com/stackable-labs).
3505
+
3506
+ ## 3. Register your Bundle URL with Stackable
3507
+
3508
+ Once your extension bundle is deployed/live, tell Stackable where to find it. You have two options:
3509
+
3510
+ - **Admin dashboard** \u2014 open your extension at [admin.stackablelabs.com](https://admin.stackablelabs.com), edit the **Bundle URL** field, and save. Existing Instances pick up the new bundle the next time they load.
3511
+ - **CLI** \u2014 from the project directory:
3512
+
3513
+ \`\`\`bash
3514
+ ${CLI.update} --bundle-url https://your-host.example.com/manifest.json
3515
+ \`\`\`
3516
+
3517
+ Or, with an explicit extension ID (handy for CI/CD):
3518
+
3519
+ \`\`\`bash
3520
+ ${CLI.update} ext_xxx --bundle-url https://your-host.example.com/manifest.json
3521
+ \`\`\`
3522
+
3523
+ Point the URL at your bundle's \`manifest.json\` \u2014 that's the entrypoint the
3524
+ runtime fetches first; everything else is loaded relative to it.
3525
+
3526
+ ## 4. Roll out and iterate
3527
+
3528
+ That's the entire deploy loop. Subsequent updates follow the same path: bump
3529
+ \`version\` in \`manifest.json\`, run \`pnpm build\`, re-upload, and (only if the
3530
+ URL changed) re-register. Most teams wire steps 1\u20133 into a single CI/CD job
3531
+ that runs on every merge to \`main\`.
3532
+
3533
+ ## Next: list it on the Marketplace
3534
+
3535
+ Deploying makes your extension **runnable** at a stable URL. To make it
3536
+ **installable** by other organizations, you also need to fill in the
3537
+ marketplace listing (icon, screenshots, description, visibility) and submit
3538
+ for review. See [Marketplace Listing](/docs/reference/marketplace-listing).
3539
+ `;
3540
+ };
3541
+
3542
+ // ../../sdk/extension/ai-docs/src/commands/marketplace-listing.ts
3543
+ var generateMarketplaceListing = () => {
3544
+ const fm = frontmatter({
3545
+ description: "Fill in your marketplace listing, choose visibility, and submit your Stackable extension for review and approval",
3546
+ targets: ["*"]
3547
+ });
3548
+ return `${fm}
3549
+
3550
+ # Listing & Submission
3551
+
3552
+ Once your extension is built, hosted, and registered with a Bundle URL (see
3553
+ [Deploy](/docs/guides/deploy)), the last step is to make it discoverable and
3554
+ installable. That happens entirely in the [admin dashboard](https://admin.stackablelabs.com)
3555
+ \u2014 you fill in your marketplace listing, pick a visibility mode, and submit for
3556
+ review.
3557
+
3558
+ ## 1. Fill in the listing fields
3559
+
3560
+ Open your extension in the admin dashboard and complete the **Listing** tab.
3561
+ Every field below is part of the marketplace metadata that prospective
3562
+ installers see when browsing or evaluating your extension.
3563
+
3564
+ | Field | Required | What it's for |
3565
+ |-------|----------|---------------|
3566
+ | **Icon** | Yes | Square image rendered on browse cards and the detail page. Square aspect ratio, transparent background recommended. |
3567
+ | **Short description** | Yes | One-line summary used on browse cards and search results. Keep it under ~120 characters. |
3568
+ | **Long description** | Yes | Markdown body for the extension detail page. Cover what the extension does, the problem it solves, and any setup requirements. Screenshots and links allowed. |
3569
+ | **Screenshots** | Recommended | Image gallery shown on the detail page. Pick views that demonstrate the extension actually working in a host application. |
3570
+ | **Categories** | Yes | One or more curated categories from the platform taxonomy (e.g., commerce, support, analytics). Drives marketplace browse filters. |
3571
+ | **Tags** | Optional | Freeform searchable tags. Used for keyword search across the marketplace. |
3572
+ | **Publisher** | Auto | Pulled from your organization profile \u2014 the org name shown as the listing's publisher. Update your org profile in the dashboard if this needs to change. |
3573
+
3574
+ > **Tip:** The icon and screenshots are what make a listing feel real. A
3575
+ > placeholder icon and no screenshots is the single biggest reason listings
3576
+ > get a "needs more info" review response.
3577
+
3578
+ ## 2. Choose a visibility mode
3579
+
3580
+ Every listing has a **visibility** setting that controls who can install it:
3581
+
3582
+ | Visibility | Who can find and install it |
3583
+ |------------|------------------------------|
3584
+ | **Public** | Anyone browsing the marketplace. Goes through full marketplace review. Best for extensions you want available to everyone. |
3585
+ | **Protected** | Only organizations you explicitly add to the **allowed orgs** list. The listing is hidden from public browse; people you've allowed see it in their marketplace as if it were public. Best for private/internal extensions, paid customers, or staged rollouts. |
3586
+
3587
+ Visibility can be changed later from the same Listing tab. Switching from
3588
+ **Protected** \u2192 **Public** triggers a fresh marketplace review.
3589
+
3590
+ ## 3. Submit for review
3591
+
3592
+ Click **Submit for review**. The platform runs a two-stage review:
3593
+
3594
+ ### Stage 1 \u2014 Automated Bundle scan
3595
+
3596
+ Runs immediately on submit. The platform fetches your bundle from the
3597
+ registered Bundle URL and runs deterministic checks:
3598
+
3599
+ - Bundle reachable, valid \`manifest.json\`, sane bundle size
3600
+ - Declared permissions match observed capability usage
3601
+ - Declared targets match the surfaces your code actually registers
3602
+ - Declared \`allowedDomains\` match the endpoints \`data.fetch\` actually hits
3603
+ - No use of forbidden browser globals (\`document\`, \`window.location\`, etc.)
3604
+
3605
+ Findings come back categorized as **error**, **warning**, or **info**. Errors
3606
+ must be fixed before the listing can move forward. Warnings don't block
3607
+ submission but factor into the review.
3608
+
3609
+ ### Stage 2 \u2014 Advanced review + Stackable approval
3610
+
3611
+ If Stage 1 passes, the platform runs an advanced review of your bundle \u2014
3612
+ framework analysis, permission surface, network behavior, and any
3613
+ security-relevant findings get scored and assessed against your listing
3614
+ content. From there, Stackable will either:
3615
+
3616
+ - **Approve** \u2014 your listing moves to **Published** and becomes installable.
3617
+ - **Request changes** \u2014 you'll see structured feedback on the listing page. Address it, re-deploy if needed, and resubmit.
3618
+
3619
+ Most reviews complete within a few business days. You'll get an email when
3620
+ the status changes; the listing's review status is also visible in the admin
3621
+ dashboard at all times.
3622
+
3623
+ ## Review status lifecycle
3624
+
3625
+ | Status | Meaning |
3626
+ |--------|---------|
3627
+ | **Draft** | Listing is being edited; not submitted. |
3628
+ | **Pending review** | Submitted; waiting on Stage 1 + Stage 2 + Stackable approval. |
3629
+ | **Rejected** | Stackable requested changes. Address the feedback and resubmit. |
3630
+ | **Published** | Live in the marketplace. Installable per your visibility setting. |
3631
+ | **Suspended** | Pulled from the marketplace by Stackable (security issue, ToS violation, etc.). Contact support. |
3632
+
3633
+ ## After publishing
3634
+
3635
+ Re-deploys to the same Bundle URL don't require re-submission \u2014 your existing
3636
+ Instances pick up the new bundle automatically. **Listing edits** (description,
3637
+ screenshots, categories, visibility) and **major version bumps** do go through
3638
+ review again. Patch updates that don't change permissions, targets, or
3639
+ \`allowedDomains\` typically clear review quickly.
3062
3640
  `;
3063
3641
  };
3064
3642
 
@@ -3152,6 +3730,12 @@ var SKILLS = [
3152
3730
  type: "knowledge",
3153
3731
  content: () => generateStylingAndTheming()
3154
3732
  },
3733
+ {
3734
+ id: "instance-settings",
3735
+ description: "Instance Settings: declaring settingsSchema in your extension manifest, the install-time admin form, and reading regular values via useSettings() vs secure values via {{settings.x}} placeholders in data.fetch. Use when adding per-Instance configuration to an extension.",
3736
+ type: "knowledge",
3737
+ content: () => generateInstanceSettings()
3738
+ },
3155
3739
  // ── Docs-only knowledge skills ──────────────────────────────
3156
3740
  {
3157
3741
  id: "quick-start",
@@ -3181,6 +3765,13 @@ var SKILLS = [
3181
3765
  scopes: ["docs"],
3182
3766
  content: () => generateExtensionStudio()
3183
3767
  },
3768
+ {
3769
+ id: "ai-accelerated-development",
3770
+ description: "AI-Accelerated Development: AI Extension Studio, Agent Skills, the live MCP server, and the Claude Code plugin \u2014 and how to choose between them.",
3771
+ type: "knowledge",
3772
+ scopes: ["docs"],
3773
+ content: () => generateAIAcceleratedDevelopment()
3774
+ },
3184
3775
  // ── Cookbook skills (docs-only) ────────────────────────────────
3185
3776
  {
3186
3777
  id: "cookbook-structural",
@@ -3203,6 +3794,13 @@ var SKILLS = [
3203
3794
  scopes: ["docs"],
3204
3795
  content: () => generateCookbookEvents()
3205
3796
  },
3797
+ {
3798
+ id: "marketplace-listing",
3799
+ description: "Marketplace listing reference: the listing fields (icon, screenshots, description, categories), Public vs Protected visibility, and the two-stage review process (bundle scan + security review + approval). Use when publishing a deployed extension to the marketplace.",
3800
+ type: "knowledge",
3801
+ scopes: ["docs"],
3802
+ content: () => generateMarketplaceListing()
3803
+ },
3206
3804
  // ── Action skills ─────────────────────────────────────────────
3207
3805
  {
3208
3806
  id: "add-surface",
@@ -3227,6 +3825,15 @@ var SKILLS = [
3227
3825
  description: "Validate this extension for common errors before deploying: manifest, permissions, surfaces, imports. Use before publishing or when debugging issues.",
3228
3826
  type: "action",
3229
3827
  content: () => generateValidateExtensionCommand()
3828
+ },
3829
+ {
3830
+ id: "deploy",
3831
+ description: "Build the production bundle, host it on Netlify / Vercel / Cloudflare Pages / S3+CloudFront, and register your Bundle URL with Stackable via the admin dashboard or CLI. Use when shipping an extension after validation passes.",
3832
+ type: "action",
3833
+ // TODO: T3 #24 — remove `scopes: ['docs']` once `cli deploy` is implemented so Studio's
3834
+ // AI assistant in the future should be able invoke the deploy flow (not just describe it).
3835
+ scopes: ["docs"],
3836
+ content: () => generateDeployExtensionCommand()
3230
3837
  }
3231
3838
  ];
3232
3839
 
@@ -3828,6 +4435,64 @@ pair(EXAMPLE_SNIPPETS, EXAMPLE_SNIPPETS2);
3828
4435
  pair(CAPABILITY_SNIPPETS, CAPABILITY_SNIPPETS2);
3829
4436
  pair(EVENT_SNIPPETS, EVENT_SNIPPETS2);
3830
4437
 
4438
+ // ../../lib/contracts/src/custom.ts
4439
+ var CUSTOM_ROLE = {
4440
+ SUPER_ADMIN: "super_admin"
4441
+ };
4442
+ new Set(Object.values(CUSTOM_ROLE));
4443
+
4444
+ // ../../lib/utils-auth/src/constants.ts
4445
+ var STANDALONE_CLIENT_DATA = {
4446
+ CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
4447
+ MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
4448
+ };
4449
+ var STANDALONE_CLIENT = Object.fromEntries(
4450
+ Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.name])
4451
+ );
4452
+ var STANDALONE_CLIENT_AUTH_FILE = Object.fromEntries(
4453
+ Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.authFile])
4454
+ );
4455
+ Object.values(STANDALONE_CLIENT);
4456
+
4457
+ // ../../lib/utils-auth/src/index.ts
4458
+ var AUTH_DIR = join(homedir(), ".stackable");
4459
+ join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.CLI);
4460
+ var MCP_AUTH_FILE = join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.MCP);
4461
+ var resolveAuthFile = (filename) => join(AUTH_DIR, filename ?? STANDALONE_CLIENT_AUTH_FILE.CLI);
4462
+ var readAuthState = async (filename) => {
4463
+ try {
4464
+ const content = await readFile(resolveAuthFile(filename), "utf8");
4465
+ return JSON.parse(content);
4466
+ } catch {
4467
+ return null;
4468
+ }
4469
+ };
4470
+ var decodeJwtPayload = (token) => {
4471
+ try {
4472
+ const [, payload] = token.split(".");
4473
+ if (!payload) {
4474
+ return null;
4475
+ }
4476
+ const json = Buffer.from(payload, "base64url").toString("utf8");
4477
+ return JSON.parse(json);
4478
+ } catch {
4479
+ return null;
4480
+ }
4481
+ };
4482
+ var getToken = async (filename) => {
4483
+ const state = await readAuthState(filename);
4484
+ if (!state) {
4485
+ throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
4486
+ }
4487
+ const payload = decodeJwtPayload(state.token);
4488
+ if (payload?.exp && typeof payload.exp === "number") {
4489
+ if (Date.now() >= payload.exp * 1e3) {
4490
+ throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
4491
+ }
4492
+ }
4493
+ return state.token;
4494
+ };
4495
+
3831
4496
  // package.json
3832
4497
  var package_default = {
3833
4498
  version: "0.0.0"};
@@ -3851,9 +4516,13 @@ var createMcpServer = (options = {}) => {
3851
4516
  return await getToken(STANDALONE_CLIENT_AUTH_FILE.MCP);
3852
4517
  } catch {
3853
4518
  }
3854
- const { performOAuthFlow } = await import('./auth-DWIWIJNN.js');
4519
+ if (!options.performOAuthFlow) {
4520
+ throw new Error(
4521
+ "No auth token available. Provide authContext (server) or performOAuthFlow (CLI)."
4522
+ );
4523
+ }
3855
4524
  const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL;
3856
- return performOAuthFlow(`${MCP_API_BASE_URL}/.well-known/oauth-authorization-server`);
4525
+ return options.performOAuthFlow(`${MCP_API_BASE_URL}/.well-known/oauth-authorization-server`);
3857
4526
  };
3858
4527
  const authHeaders = (token) => ({
3859
4528
  authorization: `Bearer ${token}`,
@@ -3990,12 +4659,7 @@ ${errors.map((e) => `- ${e}`).join("\n")}`);
3990
4659
  usedPermissions.add(permission);
3991
4660
  }
3992
4661
  }
3993
- const eventHookMap = {
3994
- useIdentityEvent: "events:identity",
3995
- useMessagingEvent: "events:messaging",
3996
- useActivityEvent: "events:activity"
3997
- };
3998
- for (const [hook, permission] of Object.entries(eventHookMap)) {
4662
+ for (const [hook, permission] of Object.entries(EVENT_HOOK_PERMISSION_MAP)) {
3999
4663
  if (allSource.includes(hook)) {
4000
4664
  usedPermissions.add(permission);
4001
4665
  }