@stackable-labs/cli-app-extension 1.19.0 → 1.20.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.
Files changed (2) hide show
  1. package/dist/index.js +319 -120
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -775,7 +775,7 @@ var fetchApps = async (token) => {
775
775
  iconUrl
776
776
  }));
777
777
  };
778
- var createExtensionRemote = async (appId, payload, token) => {
778
+ var createExtensionRemote = async (appId, token, payload) => {
779
779
  const baseUrl = getAdminApiBaseUrl();
780
780
  const res = await fetch(`${baseUrl}/app-extension/${appId}/extensions`, {
781
781
  method: "POST",
@@ -1726,17 +1726,25 @@ var App = ({ command, token, userId, orgId, initialName, initialExtensionId, opt
1726
1726
  };
1727
1727
  switch (step) {
1728
1728
  case "app": {
1729
- return /* @__PURE__ */ jsx13(AppSelect, { token, userId, orgId, onSubmit: handleAppSelect });
1729
+ return /* @__PURE__ */ jsx13(
1730
+ AppSelect,
1731
+ {
1732
+ token,
1733
+ orgId,
1734
+ userId,
1735
+ onSubmit: handleAppSelect
1736
+ }
1737
+ );
1730
1738
  }
1731
1739
  case "extensionSelect": {
1732
1740
  return /* @__PURE__ */ jsx13(
1733
1741
  ExtensionSelect,
1734
1742
  {
1735
1743
  appId: selectedApp.id,
1736
- token,
1737
1744
  command,
1738
- onSubmit: handleExtensionSelect,
1739
- onBack: goBack
1745
+ token,
1746
+ onBack: goBack,
1747
+ onSubmit: handleExtensionSelect
1740
1748
  }
1741
1749
  );
1742
1750
  }
@@ -1746,8 +1754,8 @@ var App = ({ command, token, userId, orgId, initialName, initialExtensionId, opt
1746
1754
  {
1747
1755
  availableTargets: selectedApp?.targets ?? [],
1748
1756
  preSelected: targets,
1749
- onSubmit: handleConfirmTargets,
1750
- onBack: goBack
1757
+ onBack: goBack,
1758
+ onSubmit: handleConfirmTargets
1751
1759
  }
1752
1760
  );
1753
1761
  }
@@ -1756,8 +1764,8 @@ var App = ({ command, token, userId, orgId, initialName, initialExtensionId, opt
1756
1764
  NamePrompt,
1757
1765
  {
1758
1766
  initialValue: name,
1759
- onSubmit: handleName,
1760
- onBack: goBack
1767
+ onBack: goBack,
1768
+ onSubmit: handleName
1761
1769
  }
1762
1770
  );
1763
1771
  }
@@ -1787,10 +1795,10 @@ var App = ({ command, token, userId, orgId, initialName, initialExtensionId, opt
1787
1795
  {
1788
1796
  command,
1789
1797
  name,
1790
- extensionPort,
1791
- previewPort,
1792
1798
  targets,
1793
1799
  outputDir,
1800
+ previewPort,
1801
+ extensionPort,
1794
1802
  extensionVersion: command !== "create" /* CREATE */ ? extensionVersion : void 0,
1795
1803
  bundleUrl: command === "update" /* UPDATE */ ? bundleUrl : void 0,
1796
1804
  enabled: command === "update" /* UPDATE */ ? enabled : void 0,
@@ -1811,9 +1819,9 @@ var App = ({ command, token, userId, orgId, initialName, initialExtensionId, opt
1811
1819
  {
1812
1820
  name,
1813
1821
  targets,
1814
- availableTargets: selectedApp?.targets ?? [],
1815
- bundleUrl,
1816
1822
  enabled,
1823
+ bundleUrl,
1824
+ availableTargets: selectedApp?.targets ?? [],
1817
1825
  onSubmit: (updated) => {
1818
1826
  setName(updated.name);
1819
1827
  setTargets(updated.targets);
@@ -1866,8 +1874,8 @@ var App = ({ command, token, userId, orgId, initialName, initialExtensionId, opt
1866
1874
  };
1867
1875
 
1868
1876
  // src/components/DevApp.tsx
1869
- import { Box as Box16, Text as Text16, useInput as useInput9 } from "ink";
1870
1877
  import { useRef, useState as useState10, useEffect as useEffect6, useCallback as useCallback2 } from "react";
1878
+ import { useInput as useInput9, Box as Box16, Text as Text16 } from "ink";
1871
1879
 
1872
1880
  // src/lib/tunnel.ts
1873
1881
  import { Tunnel } from "cloudflared";
@@ -1926,7 +1934,13 @@ var DevSetup = ({ initialContext, token, onReady }) => {
1926
1934
  if (step === "app") {
1927
1935
  return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
1928
1936
  /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { children: "Select the App for your extension:" }) }),
1929
- /* @__PURE__ */ jsx14(AppSelect, { token, onSubmit: handleAppSelect })
1937
+ /* @__PURE__ */ jsx14(
1938
+ AppSelect,
1939
+ {
1940
+ token,
1941
+ onSubmit: handleAppSelect
1942
+ }
1943
+ )
1930
1944
  ] });
1931
1945
  }
1932
1946
  return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
@@ -1934,8 +1948,8 @@ var DevSetup = ({ initialContext, token, onReady }) => {
1934
1948
  /* @__PURE__ */ jsx14(
1935
1949
  ExtensionSelect,
1936
1950
  {
1937
- appId: selectedApp?.id || initialContext.appId,
1938
1951
  token,
1952
+ appId: selectedApp?.id || initialContext.appId,
1939
1953
  onSubmit: handleExtensionSelect
1940
1954
  }
1941
1955
  )
@@ -1947,16 +1961,16 @@ import { Box as Box15, Text as Text15, useInput as useInput8 } from "ink";
1947
1961
  import { useEffect as useEffect5 } from "react";
1948
1962
  import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1949
1963
  var DevDashboard = ({
1964
+ previewTunnelUrl,
1965
+ tunnelUrl,
1950
1966
  userId,
1951
1967
  orgId,
1952
- extensionName,
1953
- extensionId,
1954
1968
  appId,
1955
1969
  appName,
1956
- tunnelUrl,
1957
- previewTunnelUrl,
1958
- previewPort,
1970
+ extensionId,
1971
+ extensionName,
1959
1972
  extensionPort,
1973
+ previewPort,
1960
1974
  onQuit
1961
1975
  }) => {
1962
1976
  useEffect5(() => {
@@ -2149,8 +2163,8 @@ var DevApp = ({ token, userId, orgId, options = {} }) => {
2149
2163
  return /* @__PURE__ */ jsx16(
2150
2164
  DevSetup,
2151
2165
  {
2152
- initialContext: devContext,
2153
2166
  token,
2167
+ initialContext: devContext,
2154
2168
  onReady: handleSetupReady
2155
2169
  }
2156
2170
  );
@@ -2161,16 +2175,16 @@ var DevApp = ({ token, userId, orgId, options = {} }) => {
2161
2175
  return /* @__PURE__ */ jsx16(
2162
2176
  DevDashboard,
2163
2177
  {
2164
- userId,
2178
+ previewTunnelUrl,
2179
+ tunnelUrl,
2165
2180
  orgId,
2166
- extensionName: devContext.extensionName,
2167
- extensionId: resolvedContext.extensionId,
2181
+ userId,
2168
2182
  appId: resolvedContext.appId,
2169
2183
  appName: resolvedContext.appName,
2184
+ extensionName: devContext.extensionName,
2185
+ extensionId: resolvedContext.extensionId,
2170
2186
  extensionPort: options.extensionPort ? parseInt(options.extensionPort, 10) : devContext.extensionPort,
2171
2187
  previewPort: options.previewPort ? parseInt(options.previewPort, 10) : devContext.previewPort,
2172
- tunnelUrl,
2173
- previewTunnelUrl,
2174
2188
  onQuit: handleQuit
2175
2189
  }
2176
2190
  );
@@ -2181,19 +2195,138 @@ var DevApp = ({ token, userId, orgId, options = {} }) => {
2181
2195
  return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { children: "Loading..." }) });
2182
2196
  };
2183
2197
 
2184
- // src/components/AuthLogin.tsx
2185
- import { createServer } from "http";
2198
+ // src/components/AIScaffold.tsx
2186
2199
  import { Box as Box17, Text as Text17, useApp as useApp2 } from "ink";
2187
2200
  import Spinner4 from "ink-spinner";
2188
- import open from "open";
2189
2201
  import { useState as useState11, useEffect as useEffect7 } from "react";
2190
2202
 
2203
+ // src/lib/aiDocs.ts
2204
+ import { existsSync, readFileSync } from "fs";
2205
+ import { join as join4, dirname } from "path";
2206
+ import { mkdir, writeFile as writeFile3 } from "fs/promises";
2207
+ import AdmZip from "adm-zip";
2208
+ var DEFAULT_STATIC_CDN_URL = "https://static.stackablelabs.io";
2209
+ var AI_DOCS_FILENAME = "extension-ai-docs.zip";
2210
+ var getStaticCdnBaseUrl = () => process.env.STATIC_CDN_BASE_URL ?? DEFAULT_STATIC_CDN_URL;
2211
+ var isValidManifest = (data) => {
2212
+ if (!data || typeof data !== "object") return false;
2213
+ const m = data;
2214
+ return typeof m.name === "string" && m.name.length > 0 && typeof m.version === "string" && Array.isArray(m.targets) && Array.isArray(m.permissions) && Array.isArray(m.allowedDomains);
2215
+ };
2216
+ var isExtensionProject = (dir) => {
2217
+ const manifestPath = join4(dir, "packages/extension/public/manifest.json");
2218
+ if (!existsSync(manifestPath)) {
2219
+ return { valid: false, reason: "No manifest.json found at packages/extension/public/manifest.json \u2014 is this an Extension project?" };
2220
+ }
2221
+ try {
2222
+ const raw = readFileSync(manifestPath, "utf8");
2223
+ const data = JSON.parse(raw);
2224
+ if (!isValidManifest(data)) {
2225
+ return { valid: false, reason: "Invalid manifest: missing required fields (name, version, targets, permissions, allowedDomains)" };
2226
+ }
2227
+ } catch {
2228
+ return { valid: false, reason: "Failed to parse manifest.json" };
2229
+ }
2230
+ return { valid: true };
2231
+ };
2232
+ var downloadAndExtractAiDocs = async (targetDir, version2) => {
2233
+ const baseUrl = getStaticCdnBaseUrl();
2234
+ const zipUrl = `${baseUrl}/ai-docs/${version2}/${AI_DOCS_FILENAME}`;
2235
+ const response = await fetch(zipUrl);
2236
+ if (!response.ok) {
2237
+ throw new Error(`Failed to download AI docs from ${zipUrl}: ${response.status} ${response.statusText}`);
2238
+ }
2239
+ const buffer = Buffer.from(await response.arrayBuffer());
2240
+ const zip = new AdmZip(buffer);
2241
+ const entries = zip.getEntries();
2242
+ const extractedFiles = [];
2243
+ for (const entry of entries) {
2244
+ if (entry.isDirectory) continue;
2245
+ const targetPath = join4(targetDir, entry.entryName);
2246
+ await mkdir(dirname(targetPath), { recursive: true });
2247
+ await writeFile3(targetPath, entry.getData());
2248
+ extractedFiles.push(entry.entryName);
2249
+ }
2250
+ return { files: extractedFiles.sort() };
2251
+ };
2252
+
2253
+ // src/components/AIScaffold.tsx
2254
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2255
+ var AIScaffold = ({ version: version2 }) => {
2256
+ const { exit } = useApp2();
2257
+ const [state, setState] = useState11("validating");
2258
+ const [files, setFiles] = useState11([]);
2259
+ const [errorMessage, setErrorMessage] = useState11("");
2260
+ useEffect7(() => {
2261
+ const run = async () => {
2262
+ const projectDir = process.cwd();
2263
+ const check = isExtensionProject(projectDir);
2264
+ if (!check.valid) {
2265
+ setErrorMessage(check.reason);
2266
+ setState("error");
2267
+ exit();
2268
+ return;
2269
+ }
2270
+ setState("downloading");
2271
+ try {
2272
+ const result = await downloadAndExtractAiDocs(projectDir, version2);
2273
+ setFiles(result.files);
2274
+ setState("done");
2275
+ } catch (err) {
2276
+ setErrorMessage(err instanceof Error ? err.message : String(err));
2277
+ setState("error");
2278
+ }
2279
+ exit();
2280
+ };
2281
+ run();
2282
+ }, []);
2283
+ return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
2284
+ /* @__PURE__ */ jsx17(Banner, {}),
2285
+ /* @__PURE__ */ jsxs16(StepShell, { title: "AI Editor Config", children: [
2286
+ state === "validating" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
2287
+ /* @__PURE__ */ jsx17(Text17, { color: "cyan", children: /* @__PURE__ */ jsx17(Spinner4, { type: "dots" }) }),
2288
+ /* @__PURE__ */ jsx17(Text17, { children: "Checking project..." })
2289
+ ] }),
2290
+ state === "downloading" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
2291
+ /* @__PURE__ */ jsx17(Text17, { color: "cyan", children: /* @__PURE__ */ jsx17(Spinner4, { type: "dots" }) }),
2292
+ /* @__PURE__ */ jsxs16(Text17, { children: [
2293
+ "Downloading AI editor configs (",
2294
+ version2,
2295
+ ")..."
2296
+ ] })
2297
+ ] }),
2298
+ state === "done" && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", gap: 1, children: [
2299
+ /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
2300
+ /* @__PURE__ */ jsx17(Text17, { color: "green", bold: true, children: "\u2714" }),
2301
+ /* @__PURE__ */ jsxs16(Text17, { bold: true, children: [
2302
+ "AI editor configs installed (",
2303
+ files.length,
2304
+ " files)"
2305
+ ] })
2306
+ ] }),
2307
+ /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", marginLeft: 2, children: files.map((f) => /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: f }, f)) })
2308
+ ] }),
2309
+ state === "error" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
2310
+ /* @__PURE__ */ jsx17(Text17, { color: "red", children: "\u2716" }),
2311
+ /* @__PURE__ */ jsx17(Text17, { children: errorMessage })
2312
+ ] })
2313
+ ] })
2314
+ ] });
2315
+ };
2316
+
2317
+ // src/components/AuthLogin.tsx
2318
+ import { createServer } from "http";
2319
+ import { Box as Box18, Text as Text18, useApp as useApp3 } from "ink";
2320
+ import Spinner5 from "ink-spinner";
2321
+ import open from "open";
2322
+ import { useState as useState12, useEffect as useEffect8 } from "react";
2323
+
2191
2324
  // src/lib/auth.ts
2192
- import { readFile as readFile3, writeFile as writeFile3, mkdir, unlink } from "fs/promises";
2193
- import { join as join4 } from "path";
2325
+ import { readFile as readFile3, writeFile as writeFile4, mkdir as mkdir2, unlink } from "fs/promises";
2326
+ import { join as join5 } from "path";
2194
2327
  import { homedir } from "os";
2195
- var AUTH_DIR = join4(homedir(), ".stackable");
2196
- var AUTH_FILE = join4(AUTH_DIR, "auth.json");
2328
+ var AUTH_DIR = join5(homedir(), ".stackable");
2329
+ var AUTH_FILE = join5(AUTH_DIR, "auth.json");
2197
2330
  var readAuthState = async () => {
2198
2331
  try {
2199
2332
  const content = await readFile3(AUTH_FILE, "utf8");
@@ -2203,8 +2336,8 @@ var readAuthState = async () => {
2203
2336
  }
2204
2337
  };
2205
2338
  var writeAuthState = async (state) => {
2206
- await mkdir(AUTH_DIR, { recursive: true, mode: 448 });
2207
- await writeFile3(AUTH_FILE, JSON.stringify(state, null, 2), { mode: 384 });
2339
+ await mkdir2(AUTH_DIR, { recursive: true, mode: 448 });
2340
+ await writeFile4(AUTH_FILE, JSON.stringify(state, null, 2), { mode: 384 });
2208
2341
  };
2209
2342
  var clearAuthState = async () => {
2210
2343
  try {
@@ -2215,7 +2348,9 @@ var clearAuthState = async () => {
2215
2348
  var decodeJwtPayload = (token) => {
2216
2349
  try {
2217
2350
  const [, payload] = token.split(".");
2218
- if (!payload) return null;
2351
+ if (!payload) {
2352
+ return null;
2353
+ }
2219
2354
  const json = Buffer.from(payload, "base64url").toString("utf8");
2220
2355
  return JSON.parse(json);
2221
2356
  } catch {
@@ -2237,7 +2372,7 @@ var getToken = async () => {
2237
2372
  };
2238
2373
 
2239
2374
  // src/components/AuthLogin.tsx
2240
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2375
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
2241
2376
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
2242
2377
  var callbackPage = (heading, sub, redirectUrl) => `<!DOCTYPE html>
2243
2378
  <html><head><meta charset="utf-8"><title>Stackable CLI</title>
@@ -2247,13 +2382,13 @@ var callbackPage = (heading, sub, redirectUrl) => `<!DOCTYPE html>
2247
2382
  ${redirectUrl ? `<script>(function(){var s=3,el=document.getElementById('h');function t(){if(s<=0){location.href='${redirectUrl}';return}el.textContent='Redirecting in '+s+'s\u2026';s--;setTimeout(t,1000)}t()})()</script>` : ""}
2248
2383
  </body></html>`;
2249
2384
  var AuthLogin = ({ dashboardUrl }) => {
2250
- const { exit } = useApp2();
2251
- const [state, setState] = useState11("waiting");
2252
- const [loginUrl, setLoginUrl] = useState11("");
2253
- const [userIdLabel, setUserIdLabel] = useState11("");
2254
- const [orgIdLabel, setOrgIdLabel] = useState11("");
2255
- const [errorMessage, setErrorMessage] = useState11("");
2256
- useEffect7(() => {
2385
+ const { exit } = useApp3();
2386
+ const [state, setState] = useState12("waiting");
2387
+ const [loginUrl, setLoginUrl] = useState12("");
2388
+ const [userIdLabel, setUserIdLabel] = useState12("");
2389
+ const [orgIdLabel, setOrgIdLabel] = useState12("");
2390
+ const [errorMessage, setErrorMessage] = useState12("");
2391
+ useEffect8(() => {
2257
2392
  let server;
2258
2393
  let timeout;
2259
2394
  const run = async () => {
@@ -2328,109 +2463,109 @@ var AuthLogin = ({ dashboardUrl }) => {
2328
2463
  server?.close();
2329
2464
  };
2330
2465
  }, []);
2331
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
2332
- /* @__PURE__ */ jsx17(Banner, {}),
2333
- /* @__PURE__ */ jsxs16(StepShell, { title: "Authenticate with Stackable", children: [
2334
- state === "waiting" && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", gap: 1, children: [
2335
- /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
2336
- /* @__PURE__ */ jsx17(Text17, { color: "cyan", children: /* @__PURE__ */ jsx17(Spinner4, { type: "dots" }) }),
2337
- /* @__PURE__ */ jsx17(Text17, { children: "Waiting for browser authentication..." })
2466
+ return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
2467
+ /* @__PURE__ */ jsx18(Banner, {}),
2468
+ /* @__PURE__ */ jsxs17(StepShell, { title: "Authenticate with Stackable", children: [
2469
+ state === "waiting" && /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", gap: 1, children: [
2470
+ /* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
2471
+ /* @__PURE__ */ jsx18(Text18, { color: "cyan", children: /* @__PURE__ */ jsx18(Spinner5, { type: "dots" }) }),
2472
+ /* @__PURE__ */ jsx18(Text18, { children: "Waiting for browser authentication..." })
2338
2473
  ] }),
2339
- loginUrl && /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
2474
+ loginUrl && /* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
2340
2475
  " ",
2341
2476
  loginUrl
2342
2477
  ] })
2343
2478
  ] }),
2344
- state === "success" && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", gap: 1, children: [
2345
- /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
2346
- /* @__PURE__ */ jsxs16(Box17, { gap: 2, children: [
2347
- /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "User:" }),
2348
- /* @__PURE__ */ jsx17(Text17, { color: "cyan", children: userIdLabel })
2479
+ state === "success" && /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", gap: 1, children: [
2480
+ /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
2481
+ /* @__PURE__ */ jsxs17(Box18, { gap: 2, children: [
2482
+ /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "User:" }),
2483
+ /* @__PURE__ */ jsx18(Text18, { color: "cyan", children: userIdLabel })
2349
2484
  ] }),
2350
- /* @__PURE__ */ jsxs16(Box17, { gap: 2, children: [
2351
- /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "Org: " }),
2352
- /* @__PURE__ */ jsx17(Text17, { color: "cyan", children: orgIdLabel })
2485
+ /* @__PURE__ */ jsxs17(Box18, { gap: 2, children: [
2486
+ /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "Org: " }),
2487
+ /* @__PURE__ */ jsx18(Text18, { color: "cyan", children: orgIdLabel })
2353
2488
  ] })
2354
2489
  ] }),
2355
- /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
2356
- /* @__PURE__ */ jsx17(Text17, { color: "green", bold: true, children: "\u2714" }),
2357
- /* @__PURE__ */ jsx17(Text17, { bold: true, children: "Authenticated" })
2490
+ /* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
2491
+ /* @__PURE__ */ jsx18(Text18, { color: "green", bold: true, children: "\u2714" }),
2492
+ /* @__PURE__ */ jsx18(Text18, { bold: true, children: "Authenticated" })
2358
2493
  ] })
2359
2494
  ] }),
2360
- state === "error" && /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
2361
- /* @__PURE__ */ jsx17(Text17, { color: "red", children: "\u2716" }),
2362
- /* @__PURE__ */ jsx17(Text17, { children: errorMessage })
2495
+ state === "error" && /* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
2496
+ /* @__PURE__ */ jsx18(Text18, { color: "red", children: "\u2716" }),
2497
+ /* @__PURE__ */ jsx18(Text18, { children: errorMessage })
2363
2498
  ] })
2364
2499
  ] })
2365
2500
  ] });
2366
2501
  };
2367
2502
 
2368
2503
  // src/components/AuthLogout.tsx
2369
- import { useEffect as useEffect8 } from "react";
2370
- import { Box as Box18, Text as Text18, useApp as useApp3 } from "ink";
2371
- import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
2504
+ import { useEffect as useEffect9 } from "react";
2505
+ import { Box as Box19, Text as Text19, useApp as useApp4 } from "ink";
2506
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
2372
2507
  var AuthLogout = () => {
2373
- const { exit } = useApp3();
2374
- useEffect8(() => {
2508
+ const { exit } = useApp4();
2509
+ useEffect9(() => {
2375
2510
  exit();
2376
2511
  }, [exit]);
2377
- return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
2378
- /* @__PURE__ */ jsx18(Banner, {}),
2379
- /* @__PURE__ */ jsx18(StepShell, { title: "Authenticate with Stackable", children: /* @__PURE__ */ jsxs17(Box18, { gap: 1, children: [
2380
- /* @__PURE__ */ jsx18(Text18, { color: "green", bold: true, children: "\u2714" }),
2381
- /* @__PURE__ */ jsx18(Text18, { bold: true, children: "Logged out" })
2512
+ return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
2513
+ /* @__PURE__ */ jsx19(Banner, {}),
2514
+ /* @__PURE__ */ jsx19(StepShell, { title: "Authenticate with Stackable", children: /* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
2515
+ /* @__PURE__ */ jsx19(Text19, { color: "green", bold: true, children: "\u2714" }),
2516
+ /* @__PURE__ */ jsx19(Text19, { bold: true, children: "Logged out" })
2382
2517
  ] }) })
2383
2518
  ] });
2384
2519
  };
2385
2520
 
2386
2521
  // src/components/AuthStatus.tsx
2387
- import { useEffect as useEffect9 } from "react";
2388
- import { Box as Box19, Text as Text19, useApp as useApp4 } from "ink";
2389
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
2522
+ import { useEffect as useEffect10 } from "react";
2523
+ import { useApp as useApp5, Box as Box20, Text as Text20 } from "ink";
2524
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2390
2525
  var AuthStatus = ({ state, userId, orgId, expiry }) => {
2391
- const { exit } = useApp4();
2392
- useEffect9(() => {
2526
+ const { exit } = useApp5();
2527
+ useEffect10(() => {
2393
2528
  exit();
2394
2529
  }, [exit]);
2395
- return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
2396
- /* @__PURE__ */ jsx19(Banner, {}),
2397
- /* @__PURE__ */ jsxs18(StepShell, { title: "Authenticate with Stackable", children: [
2398
- state === "authenticated" && /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", gap: 1, children: [
2399
- /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
2400
- /* @__PURE__ */ jsxs18(Box19, { gap: 2, children: [
2401
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "User:" }),
2402
- /* @__PURE__ */ jsx19(Text19, { color: "cyan", children: userId })
2530
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
2531
+ /* @__PURE__ */ jsx20(Banner, {}),
2532
+ /* @__PURE__ */ jsxs19(StepShell, { title: "Authenticate with Stackable", children: [
2533
+ state === "authenticated" && /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", gap: 1, children: [
2534
+ /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
2535
+ /* @__PURE__ */ jsxs19(Box20, { gap: 2, children: [
2536
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "User:" }),
2537
+ /* @__PURE__ */ jsx20(Text20, { color: "cyan", children: userId })
2403
2538
  ] }),
2404
- /* @__PURE__ */ jsxs18(Box19, { gap: 2, children: [
2405
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Org: " }),
2406
- /* @__PURE__ */ jsx19(Text19, { color: "cyan", children: orgId })
2539
+ /* @__PURE__ */ jsxs19(Box20, { gap: 2, children: [
2540
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Org: " }),
2541
+ /* @__PURE__ */ jsx20(Text20, { color: "cyan", children: orgId })
2407
2542
  ] }),
2408
- expiry && /* @__PURE__ */ jsxs18(Box19, { gap: 2, children: [
2409
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Exp: " }),
2410
- /* @__PURE__ */ jsx19(Text19, { color: "cyan", children: expiry.toLocaleDateString() })
2543
+ expiry && /* @__PURE__ */ jsxs19(Box20, { gap: 2, children: [
2544
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Exp: " }),
2545
+ /* @__PURE__ */ jsx20(Text20, { color: "cyan", children: expiry.toLocaleDateString() })
2411
2546
  ] })
2412
2547
  ] }),
2413
- /* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
2414
- /* @__PURE__ */ jsx19(Text19, { color: "green", bold: true, children: "\u2714" }),
2415
- /* @__PURE__ */ jsx19(Text19, { bold: true, children: "Authenticated" })
2548
+ /* @__PURE__ */ jsxs19(Box20, { gap: 1, children: [
2549
+ /* @__PURE__ */ jsx20(Text20, { color: "green", bold: true, children: "\u2714" }),
2550
+ /* @__PURE__ */ jsx20(Text20, { bold: true, children: "Authenticated" })
2416
2551
  ] })
2417
2552
  ] }),
2418
- state === "expired" && /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", gap: 1, children: [
2419
- /* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
2420
- /* @__PURE__ */ jsx19(Text19, { color: "red", children: "\u2716" }),
2421
- /* @__PURE__ */ jsxs18(Text19, { children: [
2553
+ state === "expired" && /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", gap: 1, children: [
2554
+ /* @__PURE__ */ jsxs19(Box20, { gap: 1, children: [
2555
+ /* @__PURE__ */ jsx20(Text20, { color: "red", children: "\u2716" }),
2556
+ /* @__PURE__ */ jsxs19(Text20, { children: [
2422
2557
  "Session expired",
2423
2558
  expiry ? ` (${expiry.toLocaleDateString()})` : ""
2424
2559
  ] })
2425
2560
  ] }),
2426
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Run `stackable-app-extension auth login` to re-authenticate." })
2561
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Run `stackable-app-extension auth login` to re-authenticate." })
2427
2562
  ] }),
2428
- state === "not-logged-in" && /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", gap: 1, children: [
2429
- /* @__PURE__ */ jsxs18(Box19, { gap: 1, children: [
2430
- /* @__PURE__ */ jsx19(Text19, { color: "red", children: "\u2716" }),
2431
- /* @__PURE__ */ jsx19(Text19, { children: "Not logged in" })
2563
+ state === "not-logged-in" && /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", gap: 1, children: [
2564
+ /* @__PURE__ */ jsxs19(Box20, { gap: 1, children: [
2565
+ /* @__PURE__ */ jsx20(Text20, { color: "red", children: "\u2716" }),
2566
+ /* @__PURE__ */ jsx20(Text20, { children: "Not logged in" })
2432
2567
  ] }),
2433
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "Run `stackable-app-extension auth login`" })
2568
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Run `stackable-app-extension auth login`" })
2434
2569
  ] })
2435
2570
  ] })
2436
2571
  ] });
@@ -2484,7 +2619,7 @@ var checkForUpdate = (currentVersion) => {
2484
2619
  };
2485
2620
 
2486
2621
  // src/index.tsx
2487
- import { jsx as jsx20 } from "react/jsx-runtime";
2622
+ import { jsx as jsx21 } from "react/jsx-runtime";
2488
2623
  var require2 = createRequire(import.meta.url);
2489
2624
  var { version } = require2("../package.json");
2490
2625
  checkForUpdate(version);
@@ -2496,7 +2631,9 @@ var ensureToken = async () => {
2496
2631
  } catch (err) {
2497
2632
  const message = err instanceof Error ? err.message : String(err);
2498
2633
  const isExpired = message.toLowerCase().includes("expired");
2499
- render(/* @__PURE__ */ jsx20(AuthStatus, { state: isExpired ? "expired" : "not-logged-in" }));
2634
+ render(
2635
+ /* @__PURE__ */ jsx21(AuthStatus, { state: isExpired ? "expired" : "not-logged-in" })
2636
+ );
2500
2637
  return null;
2501
2638
  }
2502
2639
  };
@@ -2507,7 +2644,19 @@ program.command("create" /* CREATE */).description("Create a new Extension proje
2507
2644
  return;
2508
2645
  }
2509
2646
  const { token, userId, orgId } = auth2;
2510
- render(/* @__PURE__ */ jsx20(App, { command: "create" /* CREATE */, initialName: name, options, token, userId, orgId }));
2647
+ render(
2648
+ /* @__PURE__ */ jsx21(
2649
+ App,
2650
+ {
2651
+ command: "create" /* CREATE */,
2652
+ initialName: name,
2653
+ options,
2654
+ token,
2655
+ orgId,
2656
+ userId
2657
+ }
2658
+ )
2659
+ );
2511
2660
  });
2512
2661
  program.command("scaffold" /* SCAFFOLD */).description("Scaffold a local project from an existing Extension").option("--extension-port <port>", "Extension dev server port (default: 6543)").option("--preview-port <port>", "Preview dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action(async (options) => {
2513
2662
  const auth2 = await ensureToken();
@@ -2515,7 +2664,18 @@ program.command("scaffold" /* SCAFFOLD */).description("Scaffold a local project
2515
2664
  return;
2516
2665
  }
2517
2666
  const { token, userId, orgId } = auth2;
2518
- render(/* @__PURE__ */ jsx20(App, { command: "scaffold" /* SCAFFOLD */, options, token, userId, orgId }));
2667
+ render(
2668
+ /* @__PURE__ */ jsx21(
2669
+ App,
2670
+ {
2671
+ command: "scaffold" /* SCAFFOLD */,
2672
+ options,
2673
+ token,
2674
+ orgId,
2675
+ userId
2676
+ }
2677
+ )
2678
+ );
2519
2679
  });
2520
2680
  program.command("update" /* UPDATE */).description("Update an existing Extension").argument("[extensionId]", "Extension ID to update").option("--app-id <id>", "Skip App selection").option("--name <name>", "New Extension name").option("--targets <targets>", "Comma-separated target slots (validated against app)").option("--bundle-url <url>", "New bundle URL").option("--enabled <bool>", "Enable/disable Extension").option("--set-version <version>", "Explicit version (skips auto-compute)").action(async (extensionId, options) => {
2521
2681
  const auth2 = await ensureToken();
@@ -2523,7 +2683,19 @@ program.command("update" /* UPDATE */).description("Update an existing Extension
2523
2683
  return;
2524
2684
  }
2525
2685
  const { token, userId, orgId } = auth2;
2526
- render(/* @__PURE__ */ jsx20(App, { command: "update" /* UPDATE */, initialExtensionId: extensionId, options, token, userId, orgId }));
2686
+ render(
2687
+ /* @__PURE__ */ jsx21(
2688
+ App,
2689
+ {
2690
+ command: "update" /* UPDATE */,
2691
+ initialExtensionId: extensionId,
2692
+ options,
2693
+ token,
2694
+ userId,
2695
+ orgId
2696
+ }
2697
+ )
2698
+ );
2527
2699
  });
2528
2700
  program.command("dev" /* DEV */).description("Start dev servers with a public tunnel").option("--dir <path>", "Project root (default: cwd)").option("--extension-port <port>", "Override Extension port").option("--preview-port <port>", "Override Preview port").option("--no-tunnel", "Skip tunnel, just run vite dev").action(async (options) => {
2529
2701
  const auth2 = await ensureToken();
@@ -2531,30 +2703,57 @@ program.command("dev" /* DEV */).description("Start dev servers with a public tu
2531
2703
  return;
2532
2704
  }
2533
2705
  const { token, userId, orgId } = auth2;
2534
- render(/* @__PURE__ */ jsx20(DevApp, { options, token, userId, orgId }), { exitOnCtrlC: false });
2706
+ render(
2707
+ /* @__PURE__ */ jsx21(
2708
+ DevApp,
2709
+ {
2710
+ options,
2711
+ token,
2712
+ userId,
2713
+ orgId
2714
+ }
2715
+ ),
2716
+ { exitOnCtrlC: false }
2717
+ );
2535
2718
  });
2536
2719
  var DASHBOARD_URL = process.env.ADMIN_DASHBOARD_URL ?? "https://admin.stackablelabs.io";
2537
2720
  var auth = program.command("auth").description("Manage CLI authentication");
2538
2721
  auth.command("login").description("Authenticate with Stackable via browser").action(async () => {
2539
- render(/* @__PURE__ */ jsx20(AuthLogin, { dashboardUrl: DASHBOARD_URL }));
2722
+ render(/* @__PURE__ */ jsx21(AuthLogin, { dashboardUrl: DASHBOARD_URL }));
2540
2723
  });
2541
2724
  auth.command("logout").description("Clear stored CLI credentials").action(async () => {
2542
2725
  await clearAuthState();
2543
- render(/* @__PURE__ */ jsx20(AuthLogout, {}));
2726
+ render(/* @__PURE__ */ jsx21(AuthLogout, {}));
2544
2727
  });
2545
2728
  auth.command("status").description("Show current authentication status").action(async () => {
2546
2729
  const state = await readAuthState();
2547
2730
  if (!state) {
2548
- render(/* @__PURE__ */ jsx20(AuthStatus, { state: "not-logged-in" }));
2731
+ render(
2732
+ /* @__PURE__ */ jsx21(AuthStatus, { state: "not-logged-in" })
2733
+ );
2549
2734
  return;
2550
2735
  }
2551
2736
  const [, payloadB64] = state.token.split(".");
2552
2737
  const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
2553
2738
  const expiry = payload.exp ? new Date(payload.exp * 1e3) : null;
2554
2739
  if (expiry && Date.now() >= expiry.getTime()) {
2555
- render(/* @__PURE__ */ jsx20(AuthStatus, { state: "expired", expiry }));
2740
+ render(/* @__PURE__ */ jsx21(AuthStatus, { state: "expired", expiry }));
2556
2741
  return;
2557
2742
  }
2558
- render(/* @__PURE__ */ jsx20(AuthStatus, { state: "authenticated", userId: state.userId, orgId: state.orgId, expiry }));
2743
+ render(
2744
+ /* @__PURE__ */ jsx21(
2745
+ AuthStatus,
2746
+ {
2747
+ state: "authenticated",
2748
+ userId: state.userId,
2749
+ orgId: state.orgId,
2750
+ expiry
2751
+ }
2752
+ )
2753
+ );
2754
+ });
2755
+ var ai = program.command("ai").description("AI editor configuration tools");
2756
+ ai.command("scaffold").description("Download AI editor config files into your Extension project").option("--version <version>", 'AI docs version (semver or "latest")', "latest").action(async (options) => {
2757
+ render(/* @__PURE__ */ jsx21(AIScaffold, { version: options.version }));
2559
2758
  });
2560
2759
  program.parse(process.argv.filter((arg) => arg !== "--"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/cli-app-extension",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {
@@ -12,6 +12,8 @@
12
12
  "LICENSE"
13
13
  ],
14
14
  "dependencies": {
15
+ "@stackable-labs/lib-contracts": "workspace:*",
16
+ "adm-zip": "0.x",
15
17
  "cloudflared": "0.x",
16
18
  "commander": "12.x",
17
19
  "giget": "3.x",