@stackable-labs/cli-app-extension 1.92.0 → 1.93.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 +176 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,15 +2,15 @@
2
2
  import { createRequire } from 'module';
3
3
  import { program } from 'commander';
4
4
  import { render, useApp, Box, Text, useInput, useFocus, useFocusManager } from 'ink';
5
- import { readFile, writeFile, mkdir, readdir, rm } from 'fs/promises';
5
+ import { unlink, readFile, writeFile, mkdir, readdir, rm } from 'fs/promises';
6
6
  import { join, dirname } from 'path';
7
7
  import Spinner5 from 'ink-spinner';
8
8
  import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import TextInput from 'ink-text-input';
11
11
  import { SURFACE_TARGET } from '@stackable-labs/sdk-extension-contracts';
12
- import { clearAuthState, readAuthState, getToken, STANDALONE_CLIENT, deriveClientId, writeAuthState } from '@stackable-labs/utils-auth';
13
- import { execFile, spawn } from 'child_process';
12
+ import { homedir } from 'os';
13
+ import { execFile, spawn, execSync } from 'child_process';
14
14
  import { promisify } from 'util';
15
15
  import { installDependencies } from 'nypm';
16
16
  import { downloadTemplate } from 'giget';
@@ -1166,6 +1166,92 @@ var TemplateSelect = ({ onSubmit, onBack }) => {
1166
1166
  }
1167
1167
  );
1168
1168
  };
1169
+
1170
+ // ../../lib/contracts/src/base.ts
1171
+ var asClerkUserId = (value) => value;
1172
+ var asClerkOrgId = (value) => value;
1173
+
1174
+ // ../../lib/contracts/src/permissions.ts
1175
+ var SUPER_ROLE = {
1176
+ ADMIN: "org:super_admin"
1177
+ };
1178
+ var ORG_ROLE = {
1179
+ ADMIN: "org:admin",
1180
+ OWNER: "org:owner"};
1181
+ var EDITOR_ROLES = [
1182
+ SUPER_ROLE.ADMIN,
1183
+ ORG_ROLE.ADMIN,
1184
+ ORG_ROLE.OWNER
1185
+ ];
1186
+ [
1187
+ ...Object.values(SUPER_ROLE),
1188
+ ...Object.values(EDITOR_ROLES)
1189
+ ];
1190
+
1191
+ // ../../lib/utils-auth/src/constants.ts
1192
+ var STANDALONE_CLIENT_DATA = {
1193
+ CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
1194
+ MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
1195
+ };
1196
+ var STANDALONE_CLIENT = Object.fromEntries(
1197
+ Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.name])
1198
+ );
1199
+ var STANDALONE_CLIENT_AUTH_FILE = Object.fromEntries(
1200
+ Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.authFile])
1201
+ );
1202
+ Object.values(STANDALONE_CLIENT);
1203
+
1204
+ // ../../lib/utils-auth/src/index.ts
1205
+ var deriveClientId = async (clientName) => (await getDigest(clientName)).slice(0, 32);
1206
+ var AUTH_DIR = join(homedir(), ".stackable");
1207
+ join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.CLI);
1208
+ join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.MCP);
1209
+ var resolveAuthFile = (filename) => join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.CLI);
1210
+ var readAuthState = async (filename) => {
1211
+ try {
1212
+ const content = await readFile(resolveAuthFile(filename), "utf8");
1213
+ return JSON.parse(content);
1214
+ } catch {
1215
+ return null;
1216
+ }
1217
+ };
1218
+ var writeAuthState = async (state, filename) => {
1219
+ await mkdir(AUTH_DIR, { recursive: true, mode: 448 });
1220
+ await writeFile(resolveAuthFile(), JSON.stringify(state, null, 2), { mode: 384 });
1221
+ };
1222
+ var clearAuthState = async (filename) => {
1223
+ try {
1224
+ await unlink(resolveAuthFile(filename));
1225
+ } catch {
1226
+ }
1227
+ };
1228
+ var decodeJwtPayload = (token) => {
1229
+ try {
1230
+ const [, payload] = token.split(".");
1231
+ if (!payload) {
1232
+ return null;
1233
+ }
1234
+ const json = Buffer.from(payload, "base64url").toString("utf8");
1235
+ return JSON.parse(json);
1236
+ } catch {
1237
+ return null;
1238
+ }
1239
+ };
1240
+ var getToken = async (filename) => {
1241
+ const state = await readAuthState(filename);
1242
+ if (!state) {
1243
+ throw new Error("Not authenticated. Run `stackable-app-extension auth login` first.");
1244
+ }
1245
+ const payload = decodeJwtPayload(state.token);
1246
+ if (payload?.exp && typeof payload.exp === "number") {
1247
+ if (Date.now() >= payload.exp * 1e3) {
1248
+ throw new Error("Session expired. Run `stackable-app-extension auth login` to re-authenticate.");
1249
+ }
1250
+ }
1251
+ return state.token;
1252
+ };
1253
+
1254
+ // src/lib/api.ts
1169
1255
  var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
1170
1256
  var DEFAULT_PUBLIC_API_URL = "https://api.stackablelabs.io/app-extension/latest";
1171
1257
  var getAdminApiBaseUrl = () => process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
@@ -1702,29 +1788,6 @@ export function createFetchApi(fetch: FetchFn) {
1702
1788
  }`
1703
1789
  }
1704
1790
  ];
1705
-
1706
- // ../../lib/contracts/src/base.ts
1707
- var asClerkUserId = (value) => value;
1708
- var asClerkOrgId = (value) => value;
1709
-
1710
- // ../../lib/contracts/src/permissions.ts
1711
- var SUPER_ROLE = {
1712
- ADMIN: "org:super_admin"
1713
- };
1714
- var ORG_ROLE = {
1715
- ADMIN: "org:admin",
1716
- OWNER: "org:owner"};
1717
- var EDITOR_ROLES = [
1718
- SUPER_ROLE.ADMIN,
1719
- ORG_ROLE.ADMIN,
1720
- ORG_ROLE.OWNER
1721
- ];
1722
- [
1723
- ...Object.values(SUPER_ROLE),
1724
- ...Object.values(EDITOR_ROLES)
1725
- ];
1726
-
1727
- // src/lib/devContext.ts
1728
1791
  var parseEnvFile = (content) => {
1729
1792
  const lines = content.split("\n");
1730
1793
  const env = {};
@@ -3292,11 +3355,79 @@ var downloadAndExtractAiDocs = async (targetDir, version2, filter) => {
3292
3355
  return { files: extractedFiles.sort() };
3293
3356
  };
3294
3357
  var downloadAndExtractMcpConfig = async (targetDir, version2) => downloadAndExtractAiDocs(targetDir, version2, (name) => MCP_FILE_PATTERN.test(name));
3358
+ var isClaudeCodeEnvironment = () => {
3359
+ if (process.env.CLAUDE_CODE) {
3360
+ return true;
3361
+ }
3362
+ try {
3363
+ execSync("which claude", { stdio: "ignore" });
3364
+ return true;
3365
+ } catch {
3366
+ return false;
3367
+ }
3368
+ };
3369
+ var PLUGIN_SETTINGS = {
3370
+ extraKnownMarketplaces: {
3371
+ "stackable-claude-plugins": {
3372
+ source: { source: "github", repo: "stackable-labs/claude-plugins" }
3373
+ }
3374
+ },
3375
+ enabledPlugins: {
3376
+ "stackable-extension-dev@stackable-claude-plugins": true
3377
+ }
3378
+ };
3379
+ var writePluginSettings = async (targetDir) => {
3380
+ const settingsDir = join(targetDir, ".claude");
3381
+ const settingsPath = join(settingsDir, "settings.json");
3382
+ await mkdir(settingsDir, { recursive: true });
3383
+ await writeFile(settingsPath, JSON.stringify(PLUGIN_SETTINGS, null, 2) + "\n");
3384
+ return [".claude/settings.json"];
3385
+ };
3386
+ var MODE_OPTIONS = [
3387
+ { value: "plugin", label: "Plugin (recommended)", description: "Install via Claude Code plugin \u2014 always up to date, no local files" },
3388
+ { value: "local", label: "Local files", description: "Extract AI editor configs into project (same as non-Claude editors)" }
3389
+ ];
3295
3390
  var AIScaffold = ({ version: version2 }) => {
3296
3391
  const { exit } = useApp();
3297
3392
  const [state, setState] = useState("validating");
3298
3393
  const [files, setFiles] = useState([]);
3299
3394
  const [errorMessage, setErrorMessage] = useState("");
3395
+ const [cursor, setCursor] = useState(0);
3396
+ const handleMode = async (mode) => {
3397
+ const projectDir = process.cwd();
3398
+ try {
3399
+ if (mode === "plugin") {
3400
+ const result = await writePluginSettings(projectDir);
3401
+ setFiles(result);
3402
+ } else {
3403
+ const result = await downloadAndExtractAiDocs(projectDir, version2);
3404
+ setFiles(result.files);
3405
+ }
3406
+ setState("done");
3407
+ } catch (err) {
3408
+ setErrorMessage(err instanceof Error ? err.message : String(err));
3409
+ setState("error");
3410
+ }
3411
+ exit();
3412
+ };
3413
+ useInput((_input, key) => {
3414
+ if (state !== "choosing") {
3415
+ return;
3416
+ }
3417
+ if (key.upArrow) {
3418
+ setCursor((c) => Math.max(0, c - 1));
3419
+ return;
3420
+ }
3421
+ if (key.downArrow) {
3422
+ setCursor((c) => Math.min(MODE_OPTIONS.length - 1, c + 1));
3423
+ return;
3424
+ }
3425
+ if (key.return) {
3426
+ const mode = MODE_OPTIONS[cursor].value;
3427
+ setState("downloading");
3428
+ void handleMode(mode);
3429
+ }
3430
+ });
3300
3431
  useEffect(() => {
3301
3432
  const run = async () => {
3302
3433
  const projectDir = process.cwd();
@@ -3307,6 +3438,10 @@ var AIScaffold = ({ version: version2 }) => {
3307
3438
  exit();
3308
3439
  return;
3309
3440
  }
3441
+ if (isClaudeCodeEnvironment()) {
3442
+ setState("choosing");
3443
+ return;
3444
+ }
3310
3445
  setState("downloading");
3311
3446
  try {
3312
3447
  const result = await downloadAndExtractAiDocs(projectDir, version2);
@@ -3327,6 +3462,21 @@ var AIScaffold = ({ version: version2 }) => {
3327
3462
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(Spinner5, { type: "dots" }) }),
3328
3463
  /* @__PURE__ */ jsx(Text, { children: "Checking project..." })
3329
3464
  ] }),
3465
+ state === "choosing" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
3466
+ /* @__PURE__ */ jsx(Text, { children: "Claude Code detected \u2014 choose how to set up AI assistance:" }),
3467
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, children: MODE_OPTIONS.map((opt, i) => {
3468
+ const isCursor = i === cursor;
3469
+ return /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
3470
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F" : " " }),
3471
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? "green" : void 0, children: isCursor ? "\u25C9" : "\u25CB" }),
3472
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3473
+ /* @__PURE__ */ jsx(Text, { bold: isCursor, children: opt.label }),
3474
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description })
3475
+ ] })
3476
+ ] }, opt.value);
3477
+ }) }),
3478
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 to navigate, Enter to select" })
3479
+ ] }),
3330
3480
  state === "downloading" && /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
3331
3481
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(Spinner5, { type: "dots" }) }),
3332
3482
  /* @__PURE__ */ jsxs(Text, { children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/cli-app-extension",
3
- "version": "1.92.0",
3
+ "version": "1.93.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {