@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.
- package/dist/index.js +176 -26
- 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 {
|
|
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: [
|