@eide/foir-cli 0.1.45 → 0.1.47

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/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { config } from "dotenv";
5
- import { resolve as resolve5, dirname as dirname5 } from "path";
5
+ import { resolve as resolve6, dirname as dirname5 } from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
  import { createRequire } from "module";
8
8
  import { Command } from "commander";
@@ -343,13 +343,13 @@ function withErrorHandler(optsFn, fn) {
343
343
  // src/commands/login.ts
344
344
  async function findAvailablePort(start, end) {
345
345
  for (let port = start; port <= end; port++) {
346
- const available = await new Promise((resolve6) => {
346
+ const available = await new Promise((resolve7) => {
347
347
  const server = http.createServer();
348
348
  server.listen(port, () => {
349
349
  server.close();
350
- resolve6(true);
350
+ resolve7(true);
351
351
  });
352
- server.on("error", () => resolve6(false));
352
+ server.on("error", () => resolve7(false));
353
353
  });
354
354
  if (available) return port;
355
355
  }
@@ -387,7 +387,7 @@ async function loginAction(globalOpts) {
387
387
  const state = crypto.randomBytes(16).toString("hex");
388
388
  const port = await findAvailablePort(9876, 9900);
389
389
  const redirectUri = `http://localhost:${port}/callback`;
390
- const authCode = await new Promise((resolve6, reject) => {
390
+ const authCode = await new Promise((resolve7, reject) => {
391
391
  const server = http.createServer((req, res) => {
392
392
  const url = new URL(req.url, `http://localhost:${port}`);
393
393
  if (url.pathname === "/callback") {
@@ -420,7 +420,7 @@ async function loginAction(globalOpts) {
420
420
  `<html><head><meta http-equiv="refresh" content="2;url=${mainUrl}"></head><body style="font-family:system-ui;text-align:center;padding:50px"><h1>Authentication successful!</h1><p>You can close this window.</p></body></html>`
421
421
  );
422
422
  server.close();
423
- resolve6(code);
423
+ resolve7(code);
424
424
  }
425
425
  });
426
426
  server.listen(port);
@@ -497,7 +497,7 @@ var CLI_API_KEY_SCOPES = [
497
497
  "records:publish",
498
498
  "files:read",
499
499
  "files:write",
500
- "extensions:read",
500
+ "configs:read",
501
501
  "operations:read",
502
502
  "operations:execute"
503
503
  ];
@@ -921,7 +921,7 @@ function registerMediaCommands(program2, globalOpts) {
921
921
  );
922
922
  }
923
923
 
924
- // src/commands/create-extension.ts
924
+ // src/commands/create-config.ts
925
925
  import chalk4 from "chalk";
926
926
  import inquirer2 from "inquirer";
927
927
 
@@ -977,14 +977,14 @@ function getManagerInfo(manager) {
977
977
  }
978
978
 
979
979
  // src/scaffold/scaffold.ts
980
- async function scaffold(projectName, extensionType, apiUrl) {
980
+ async function scaffold(projectName, configType, apiUrl) {
981
981
  const projectDir = path2.resolve(process.cwd(), projectName);
982
982
  if (fs4.existsSync(projectDir)) {
983
983
  throw new Error(
984
984
  `Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`
985
985
  );
986
986
  }
987
- const files = getFiles(projectName, extensionType, apiUrl);
987
+ const files = getFiles(projectName, configType, apiUrl);
988
988
  for (const [filePath, content] of Object.entries(files)) {
989
989
  const fullPath = path2.join(projectDir, filePath);
990
990
  const dir = path2.dirname(fullPath);
@@ -1009,11 +1009,12 @@ async function scaffold(projectName, extensionType, apiUrl) {
1009
1009
  console.log(` ${pm.name === "npm" ? "npm run" : pm.name} dev`);
1010
1010
  console.log();
1011
1011
  }
1012
- function getFiles(projectName, extensionType, apiUrl) {
1012
+ function getFiles(projectName, configType, apiUrl) {
1013
1013
  return {
1014
1014
  // Root
1015
1015
  "package.json": getRootPackageJson(projectName),
1016
- "extension.manifest.json": getManifest(projectName, extensionType),
1016
+ // Config manifest
1017
+ "foir.config.ts": getFoirConfig(projectName, configType),
1017
1018
  // UI (Vite SPA)
1018
1019
  "ui/package.json": getUiPackageJson(projectName),
1019
1020
  "ui/tsconfig.json": getUiTsconfig(),
@@ -1022,7 +1023,7 @@ function getFiles(projectName, extensionType, apiUrl) {
1022
1023
  "ui/.env.example": getUiEnvExample(apiUrl),
1023
1024
  "ui/.gitignore": getUiGitignore(),
1024
1025
  "ui/src/main.tsx": getUiMain(),
1025
- "ui/src/App.tsx": getUiApp(extensionType),
1026
+ "ui/src/App.tsx": getUiApp(configType),
1026
1027
  "ui/src/index.css": getUiCss(),
1027
1028
  "ui/src/vite-env.d.ts": '/// <reference types="vite/client" />\n',
1028
1029
  // API (Hono)
@@ -1032,8 +1033,7 @@ function getFiles(projectName, extensionType, apiUrl) {
1032
1033
  "api/.gitignore": "node_modules\ndist\n.env\n.env.local\n",
1033
1034
  "api/src/index.ts": getApiIndex(),
1034
1035
  "api/src/routes/webhooks.ts": getApiWebhooks(),
1035
- "api/src/routes/health.ts": getApiHealth(),
1036
- "api/src/lib/platform.ts": getApiPlatform()
1036
+ "api/src/routes/health.ts": getApiHealth()
1037
1037
  };
1038
1038
  }
1039
1039
  function getRootPackageJson(name) {
@@ -1046,20 +1046,31 @@ function getRootPackageJson(name) {
1046
1046
  build: "pnpm --filter ./ui build && pnpm --filter ./api build"
1047
1047
  },
1048
1048
  devDependencies: {
1049
- concurrently: "^9.0.0"
1049
+ concurrently: "^9.0.0",
1050
+ "@eide/foir-cli": "^0.1.0"
1050
1051
  }
1051
1052
  };
1052
1053
  return JSON.stringify(pkg, null, 2) + "\n";
1053
1054
  }
1054
- function getManifest(name, extensionType) {
1055
- const manifest = {
1056
- name: name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
1057
- version: "0.1.0",
1058
- type: extensionType,
1059
- description: `${extensionType} extension`,
1060
- entityTypes: []
1061
- };
1062
- return JSON.stringify(manifest, null, 2) + "\n";
1055
+ function getFoirConfig(name, configType) {
1056
+ const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1057
+ return `import { defineConfig } from '@eide/foir-cli/configs';
1058
+
1059
+ export default defineConfig({
1060
+ key: '${name}',
1061
+ name: '${displayName}',
1062
+ configType: '${configType}',
1063
+
1064
+ // Uncomment and configure as needed:
1065
+ // models: [],
1066
+ // operations: [],
1067
+ // schedules: [],
1068
+ // hooks: [],
1069
+ // segments: [],
1070
+ // authProviders: [],
1071
+ // placements: [],
1072
+ });
1073
+ `;
1063
1074
  }
1064
1075
  function getUiPackageJson(name) {
1065
1076
  const pkg = {
@@ -1073,7 +1084,7 @@ function getUiPackageJson(name) {
1073
1084
  preview: "vite preview"
1074
1085
  },
1075
1086
  dependencies: {
1076
- "@eide/extension-sdk": "^0.1.0",
1087
+ "@eide/foir-editor-sdk": "^0.1.0",
1077
1088
  react: "^19.0.0",
1078
1089
  "react-dom": "^19.0.0"
1079
1090
  },
@@ -1170,13 +1181,13 @@ dist
1170
1181
  function getUiMain() {
1171
1182
  return `import { StrictMode } from 'react';
1172
1183
  import { createRoot } from 'react-dom/client';
1173
- import { ExtensionProvider, useExtension } from '@eide/extension-sdk';
1184
+ import { EditorProvider, useEditor } from '@eide/foir-editor-sdk';
1174
1185
  import { useEffect } from 'react';
1175
1186
  import { App } from './App';
1176
1187
  import './index.css';
1177
1188
 
1178
1189
  function ThemeSync({ children }: { children: React.ReactNode }) {
1179
- const { theme } = useExtension();
1190
+ const { theme } = useEditor();
1180
1191
 
1181
1192
  useEffect(() => {
1182
1193
  const root = document.documentElement;
@@ -1189,17 +1200,17 @@ function ThemeSync({ children }: { children: React.ReactNode }) {
1189
1200
 
1190
1201
  createRoot(document.getElementById('root')!).render(
1191
1202
  <StrictMode>
1192
- <ExtensionProvider>
1203
+ <EditorProvider>
1193
1204
  <ThemeSync>
1194
1205
  <App />
1195
1206
  </ThemeSync>
1196
- </ExtensionProvider>
1207
+ </EditorProvider>
1197
1208
  </StrictMode>
1198
1209
  );
1199
1210
  `;
1200
1211
  }
1201
- function getUiApp(extensionType) {
1202
- switch (extensionType) {
1212
+ function getUiApp(configType) {
1213
+ switch (configType) {
1203
1214
  case "custom-editor":
1204
1215
  return getCustomEditorApp();
1205
1216
  case "widget":
@@ -1209,10 +1220,10 @@ function getUiApp(extensionType) {
1209
1220
  }
1210
1221
  }
1211
1222
  function getCustomEditorApp() {
1212
- return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
1223
+ return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
1213
1224
 
1214
1225
  export function App() {
1215
- const { isReady, init, updateField, setDirty } = useExtension();
1226
+ const { isReady, init, updateField, setDirty } = useEditor();
1216
1227
  const containerRef = useAutoResize({ minHeight: 600 });
1217
1228
 
1218
1229
  if (!isReady) return null;
@@ -1220,7 +1231,7 @@ export function App() {
1220
1231
  return (
1221
1232
  <div ref={containerRef} className="p-6 space-y-4">
1222
1233
  <h1 className="text-lg font-semibold">
1223
- Editing: {init?.entityModelKey}
1234
+ Editing: {init?.modelKey}
1224
1235
  </h1>
1225
1236
  <p className="text-sm text-gray-500">
1226
1237
  Record: {init?.recordId}
@@ -1232,10 +1243,10 @@ export function App() {
1232
1243
  `;
1233
1244
  }
1234
1245
  function getWidgetApp() {
1235
- return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
1246
+ return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
1236
1247
 
1237
1248
  export function App() {
1238
- const { isReady, init, client } = useExtension();
1249
+ const { isReady, init, client } = useEditor();
1239
1250
  const containerRef = useAutoResize({ minHeight: 300 });
1240
1251
 
1241
1252
  if (!isReady) return null;
@@ -1244,7 +1255,7 @@ export function App() {
1244
1255
  <div ref={containerRef} className="p-6 space-y-4">
1245
1256
  <h2 className="text-lg font-semibold">Dashboard Widget</h2>
1246
1257
  <p className="text-sm text-gray-500">
1247
- Connected to: {init?.entityModelKey}
1258
+ Connected to: {init?.modelKey}
1248
1259
  </p>
1249
1260
  {/* Add your widget content here */}
1250
1261
  </div>
@@ -1253,19 +1264,19 @@ export function App() {
1253
1264
  `;
1254
1265
  }
1255
1266
  function getWorkflowApp() {
1256
- return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
1267
+ return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
1257
1268
 
1258
1269
  export function App() {
1259
- const { isReady, init, client, requestSave } = useExtension();
1270
+ const { isReady, init, client, requestSave } = useEditor();
1260
1271
  const containerRef = useAutoResize({ minHeight: 400 });
1261
1272
 
1262
1273
  if (!isReady) return null;
1263
1274
 
1264
1275
  return (
1265
1276
  <div ref={containerRef} className="p-6 space-y-4">
1266
- <h1 className="text-lg font-semibold">Workflow Extension</h1>
1277
+ <h1 className="text-lg font-semibold">Workflow Config</h1>
1267
1278
  <p className="text-sm text-gray-500">
1268
- Processing: {init?.entityModelKey} / {init?.recordId}
1279
+ Processing: {init?.modelKey} / {init?.recordId}
1269
1280
  </p>
1270
1281
  {/* Add your workflow steps here */}
1271
1282
  </div>
@@ -1305,7 +1316,7 @@ function getApiPackageJson(name) {
1305
1316
  start: "node dist/index.js"
1306
1317
  },
1307
1318
  dependencies: {
1308
- "@eide/extension-sdk": "^0.1.0",
1319
+ "@eide/foir-editor-sdk": "^0.1.0",
1309
1320
  hono: "^4.0.0",
1310
1321
  "@hono/node-server": "^1.0.0"
1311
1322
  },
@@ -1341,8 +1352,8 @@ function getApiEnvExample(apiUrl) {
1341
1352
  PLATFORM_BASE_URL=${baseUrl}
1342
1353
  PLATFORM_API_KEY=sk_your_api_key_here
1343
1354
 
1344
- # Extension
1345
- EXTENSION_KEY=${"{your-extension-key}"}
1355
+ # Config
1356
+ CONFIG_KEY=${"{your-config-key}"}
1346
1357
  WEBHOOK_SECRET=your_webhook_secret_here
1347
1358
 
1348
1359
  # Server
@@ -1363,25 +1374,25 @@ app.route('/', health);
1363
1374
 
1364
1375
  const port = parseInt(process.env.PORT || '3002', 10);
1365
1376
 
1366
- console.log(\`Extension API running on http://localhost:\${port}\`);
1377
+ console.log(\`Config API running on http://localhost:\${port}\`);
1367
1378
 
1368
1379
  serve({ fetch: app.fetch, port });
1369
1380
  `;
1370
1381
  }
1371
1382
  function getApiWebhooks() {
1372
1383
  return `import { Hono } from 'hono';
1373
- import { verifyWebhookSignature } from '@eide/extension-sdk/server';
1384
+ import { verifyWebhookSignature } from '@eide/foir-editor-sdk/server';
1374
1385
 
1375
1386
  const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || '';
1376
1387
 
1377
1388
  export const webhooks = new Hono();
1378
1389
 
1379
1390
  /**
1380
- * Receive entity lifecycle events from the platform.
1391
+ * Receive record lifecycle events from the platform.
1381
1392
  */
1382
- webhooks.post('/entity-changed', async (c) => {
1393
+ webhooks.post('/record-changed', async (c) => {
1383
1394
  const body = await c.req.text();
1384
- const signature = c.req.header('x-eide-signature') ?? '';
1395
+ const signature = c.req.header('x-foir-signature') ?? '';
1385
1396
 
1386
1397
  const valid = await verifyWebhookSignature(body, signature, WEBHOOK_SECRET);
1387
1398
  if (!valid) {
@@ -1389,7 +1400,7 @@ webhooks.post('/entity-changed', async (c) => {
1389
1400
  }
1390
1401
 
1391
1402
  const payload = JSON.parse(body);
1392
- console.log('[Webhook] Entity changed:', payload.event, payload.entityId);
1403
+ console.log('[Webhook] Record changed:', payload.event, payload.recordId);
1393
1404
 
1394
1405
  // TODO: Handle the event (sync, transform, notify, etc.)
1395
1406
 
@@ -1407,36 +1418,14 @@ health.get('/health', (c) => {
1407
1418
  });
1408
1419
  `;
1409
1420
  }
1410
- function getApiPlatform() {
1411
- return `import { createExtensionClient } from '@eide/extension-sdk/server';
1412
-
1413
- /**
1414
- * Pre-configured platform client for this extension.
1415
- *
1416
- * Uses env vars for configuration:
1417
- * - PLATFORM_BASE_URL: Platform API base URL
1418
- * - PLATFORM_API_KEY: Project-scoped API key (sk_*)
1419
- * - EXTENSION_KEY: This extension's extension key
1420
- */
1421
- export const platform = createExtensionClient({
1422
- baseUrl: process.env.PLATFORM_BASE_URL || 'http://localhost:4000',
1423
- apiKey: process.env.PLATFORM_API_KEY || '',
1424
- extensionKey: process.env.EXTENSION_KEY || '',
1425
- });
1426
- `;
1427
- }
1428
1421
 
1429
- // src/commands/create-extension.ts
1430
- var EXTENSION_TYPES = [
1431
- "custom-editor",
1432
- "workflow",
1433
- "widget"
1434
- ];
1435
- function isValidExtensionType(value) {
1436
- return EXTENSION_TYPES.includes(value);
1422
+ // src/commands/create-config.ts
1423
+ var CONFIG_TYPES = ["custom-editor", "workflow", "widget"];
1424
+ function isValidConfigType(value) {
1425
+ return CONFIG_TYPES.includes(value);
1437
1426
  }
1438
- function registerCreateExtensionCommand(program2, globalOpts) {
1439
- program2.command("create-extension [name]").description("Scaffold a new Foir extension").option("--type <type>", "Extension type: custom-editor, workflow, widget").option(
1427
+ function registerCreateConfigCommand(program2, globalOpts) {
1428
+ program2.command("create-config [name]").description("Scaffold a new Foir config").option("--type <type>", "Config type: custom-editor, workflow, widget").option(
1440
1429
  "--api-url <url>",
1441
1430
  "Platform API URL",
1442
1431
  "http://localhost:4000/graphql"
@@ -1445,205 +1434,50 @@ function registerCreateExtensionCommand(program2, globalOpts) {
1445
1434
  globalOpts,
1446
1435
  async (name, cmdOpts) => {
1447
1436
  console.log();
1448
- console.log(chalk4.bold(" Create Foir Extension"));
1449
- console.log(chalk4.gray(" ---------------------"));
1437
+ console.log(chalk4.bold(" Create Foir Config"));
1438
+ console.log(chalk4.gray(" ------------------"));
1450
1439
  console.log();
1451
- let extensionName = name;
1452
- if (!extensionName) {
1440
+ let configName = name;
1441
+ if (!configName) {
1453
1442
  const { inputName } = await inquirer2.prompt([
1454
1443
  {
1455
1444
  type: "input",
1456
1445
  name: "inputName",
1457
- message: "Extension name:",
1458
- default: "my-extension"
1446
+ message: "Config name:",
1447
+ default: "my-config"
1459
1448
  }
1460
1449
  ]);
1461
- extensionName = inputName;
1450
+ configName = inputName;
1462
1451
  }
1463
- let extensionType;
1464
- if (cmdOpts?.type && isValidExtensionType(cmdOpts.type)) {
1465
- extensionType = cmdOpts.type;
1452
+ let configType;
1453
+ if (cmdOpts?.type && isValidConfigType(cmdOpts.type)) {
1454
+ configType = cmdOpts.type;
1466
1455
  } else {
1467
1456
  const { selectedType } = await inquirer2.prompt([
1468
1457
  {
1469
1458
  type: "list",
1470
1459
  name: "selectedType",
1471
- message: "Extension type:",
1472
- choices: EXTENSION_TYPES,
1460
+ message: "Config type:",
1461
+ choices: CONFIG_TYPES,
1473
1462
  default: "custom-editor"
1474
1463
  }
1475
1464
  ]);
1476
- extensionType = selectedType;
1465
+ configType = selectedType;
1477
1466
  }
1478
1467
  const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:4000/graphql";
1479
1468
  console.log();
1480
1469
  console.log(
1481
- ` Scaffolding ${chalk4.cyan(`"${extensionName}"`)} (${extensionType})...`
1470
+ ` Scaffolding ${chalk4.cyan(`"${configName}"`)} (${configType})...`
1482
1471
  );
1483
1472
  console.log();
1484
- await scaffold(extensionName, extensionType, apiUrl);
1473
+ await scaffold(configName, configType, apiUrl);
1485
1474
  }
1486
1475
  )
1487
1476
  );
1488
1477
  }
1489
1478
 
1490
1479
  // src/graphql/generated.ts
1491
- var GlobalSearchDocument = {
1492
- kind: "Document",
1493
- definitions: [
1494
- {
1495
- kind: "OperationDefinition",
1496
- operation: "query",
1497
- name: { kind: "Name", value: "GlobalSearch" },
1498
- variableDefinitions: [
1499
- {
1500
- kind: "VariableDefinition",
1501
- variable: {
1502
- kind: "Variable",
1503
- name: { kind: "Name", value: "query" }
1504
- },
1505
- type: {
1506
- kind: "NonNullType",
1507
- type: {
1508
- kind: "NamedType",
1509
- name: { kind: "Name", value: "String" }
1510
- }
1511
- }
1512
- },
1513
- {
1514
- kind: "VariableDefinition",
1515
- variable: {
1516
- kind: "Variable",
1517
- name: { kind: "Name", value: "limit" }
1518
- },
1519
- type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1520
- },
1521
- {
1522
- kind: "VariableDefinition",
1523
- variable: {
1524
- kind: "Variable",
1525
- name: { kind: "Name", value: "modelKeys" }
1526
- },
1527
- type: {
1528
- kind: "ListType",
1529
- type: {
1530
- kind: "NonNullType",
1531
- type: {
1532
- kind: "NamedType",
1533
- name: { kind: "Name", value: "String" }
1534
- }
1535
- }
1536
- }
1537
- },
1538
- {
1539
- kind: "VariableDefinition",
1540
- variable: {
1541
- kind: "Variable",
1542
- name: { kind: "Name", value: "includeMedia" }
1543
- },
1544
- type: { kind: "NamedType", name: { kind: "Name", value: "Boolean" } }
1545
- }
1546
- ],
1547
- selectionSet: {
1548
- kind: "SelectionSet",
1549
- selections: [
1550
- {
1551
- kind: "Field",
1552
- name: { kind: "Name", value: "globalSearch" },
1553
- arguments: [
1554
- {
1555
- kind: "Argument",
1556
- name: { kind: "Name", value: "query" },
1557
- value: {
1558
- kind: "Variable",
1559
- name: { kind: "Name", value: "query" }
1560
- }
1561
- },
1562
- {
1563
- kind: "Argument",
1564
- name: { kind: "Name", value: "limit" },
1565
- value: {
1566
- kind: "Variable",
1567
- name: { kind: "Name", value: "limit" }
1568
- }
1569
- },
1570
- {
1571
- kind: "Argument",
1572
- name: { kind: "Name", value: "modelKeys" },
1573
- value: {
1574
- kind: "Variable",
1575
- name: { kind: "Name", value: "modelKeys" }
1576
- }
1577
- },
1578
- {
1579
- kind: "Argument",
1580
- name: { kind: "Name", value: "includeMedia" },
1581
- value: {
1582
- kind: "Variable",
1583
- name: { kind: "Name", value: "includeMedia" }
1584
- }
1585
- }
1586
- ],
1587
- selectionSet: {
1588
- kind: "SelectionSet",
1589
- selections: [
1590
- {
1591
- kind: "Field",
1592
- name: { kind: "Name", value: "records" },
1593
- selectionSet: {
1594
- kind: "SelectionSet",
1595
- selections: [
1596
- { kind: "Field", name: { kind: "Name", value: "id" } },
1597
- {
1598
- kind: "Field",
1599
- name: { kind: "Name", value: "modelKey" }
1600
- },
1601
- { kind: "Field", name: { kind: "Name", value: "title" } },
1602
- {
1603
- kind: "Field",
1604
- name: { kind: "Name", value: "naturalKey" }
1605
- },
1606
- {
1607
- kind: "Field",
1608
- name: { kind: "Name", value: "subtitle" }
1609
- },
1610
- {
1611
- kind: "Field",
1612
- name: { kind: "Name", value: "updatedAt" }
1613
- }
1614
- ]
1615
- }
1616
- },
1617
- {
1618
- kind: "Field",
1619
- name: { kind: "Name", value: "media" },
1620
- selectionSet: {
1621
- kind: "SelectionSet",
1622
- selections: [
1623
- { kind: "Field", name: { kind: "Name", value: "id" } },
1624
- {
1625
- kind: "Field",
1626
- name: { kind: "Name", value: "fileName" }
1627
- },
1628
- {
1629
- kind: "Field",
1630
- name: { kind: "Name", value: "altText" }
1631
- },
1632
- {
1633
- kind: "Field",
1634
- name: { kind: "Name", value: "fileUrl" }
1635
- }
1636
- ]
1637
- }
1638
- }
1639
- ]
1640
- }
1641
- }
1642
- ]
1643
- }
1644
- }
1645
- ]
1646
- };
1480
+ var GlobalSearchDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GlobalSearch" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "query" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "modelKeys" } }, "type": { "kind": "ListType", "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "includeMedia" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Boolean" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "globalSearch" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "query" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "query" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "modelKeys" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "modelKeys" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeMedia" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "includeMedia" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "records" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "modelKey" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } }, { "kind": "Field", "name": { "kind": "Name", "value": "naturalKey" } }, { "kind": "Field", "name": { "kind": "Name", "value": "subtitle" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "media" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fileName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "altText" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fileUrl" } }] } }] } }] } }] };
1647
1481
 
1648
1482
  // src/commands/search.ts
1649
1483
  function registerSearchCommands(program2, globalOpts) {
@@ -1916,11 +1750,10 @@ Edit the files, then run:
1916
1750
  );
1917
1751
  }
1918
1752
 
1919
- // src/commands/profiles.ts
1753
+ // src/commands/push.ts
1920
1754
  import chalk6 from "chalk";
1921
-
1922
- // src/lib/input.ts
1923
- import inquirer4 from "inquirer";
1755
+ import { existsSync as existsSync4 } from "fs";
1756
+ import { resolve as resolve4 } from "path";
1924
1757
 
1925
1758
  // src/lib/config-loader.ts
1926
1759
  import { readFile } from "fs/promises";
@@ -1945,7 +1778,194 @@ async function loadConfig(filePath) {
1945
1778
  );
1946
1779
  }
1947
1780
 
1781
+ // src/commands/push.ts
1782
+ var CONFIG_FILE_NAMES = [
1783
+ "foir.config.ts",
1784
+ "foir.config.js",
1785
+ "foir.config.mjs",
1786
+ "foir.config.json"
1787
+ ];
1788
+ var APPLY_CONFIG_MUTATION = (
1789
+ /* GraphQL */
1790
+ `
1791
+ mutation ApplyConfig($input: ApplyConfigInput!) {
1792
+ applyConfig(input: $input) {
1793
+ configId
1794
+ configKey
1795
+ credentials {
1796
+ platformApiKey
1797
+ platformEditorKey
1798
+ webhookSecret
1799
+ }
1800
+ modelsCreated
1801
+ modelsUpdated
1802
+ operationsCreated
1803
+ operationsUpdated
1804
+ segmentsCreated
1805
+ segmentsUpdated
1806
+ schedulesCreated
1807
+ schedulesUpdated
1808
+ hooksCreated
1809
+ hooksUpdated
1810
+ isUpdate
1811
+ }
1812
+ }
1813
+ `
1814
+ );
1815
+ function discoverConfigFile() {
1816
+ for (const name of CONFIG_FILE_NAMES) {
1817
+ const path3 = resolve4(process.cwd(), name);
1818
+ if (existsSync4(path3)) return path3;
1819
+ }
1820
+ return null;
1821
+ }
1822
+ function registerPushCommand(program2, globalOpts) {
1823
+ program2.command("push").description("Push foir.config.ts to the platform").option("--config <path>", "Path to config file (default: auto-discover)").option("--force", "Force reinstall (delete and recreate)", false).action(
1824
+ withErrorHandler(
1825
+ globalOpts,
1826
+ async (opts) => {
1827
+ const configPath = opts.config ? resolve4(opts.config) : discoverConfigFile();
1828
+ if (!configPath) {
1829
+ throw new Error(
1830
+ "No config file found. Create a foir.config.ts or use --config <path>."
1831
+ );
1832
+ }
1833
+ if (!existsSync4(configPath)) {
1834
+ throw new Error(`Config file not found: ${configPath}`);
1835
+ }
1836
+ console.log(chalk6.dim(`Loading ${configPath}...`));
1837
+ const config2 = await loadConfig(configPath);
1838
+ if (!config2?.key || !config2?.name) {
1839
+ throw new Error(
1840
+ 'Config must have at least "key" and "name" fields.'
1841
+ );
1842
+ }
1843
+ if (opts.force) {
1844
+ config2.force = true;
1845
+ }
1846
+ const client = await createClient(globalOpts());
1847
+ console.log(
1848
+ chalk6.dim(`Pushing config "${config2.key}" to platform...`)
1849
+ );
1850
+ const data = await client.request(
1851
+ APPLY_CONFIG_MUTATION,
1852
+ { input: config2 }
1853
+ );
1854
+ const result = data.applyConfig;
1855
+ console.log();
1856
+ if (result.isUpdate) {
1857
+ console.log(chalk6.green("Config updated successfully."));
1858
+ } else {
1859
+ console.log(chalk6.green("Config applied successfully."));
1860
+ }
1861
+ console.log();
1862
+ console.log(` Config ID: ${chalk6.cyan(result.configId)}`);
1863
+ console.log(` Config Key: ${chalk6.cyan(result.configKey)}`);
1864
+ console.log();
1865
+ const stats = [
1866
+ ["Models", result.modelsCreated, result.modelsUpdated],
1867
+ ["Operations", result.operationsCreated, result.operationsUpdated],
1868
+ ["Segments", result.segmentsCreated, result.segmentsUpdated],
1869
+ ["Schedules", result.schedulesCreated, result.schedulesUpdated],
1870
+ ["Hooks", result.hooksCreated, result.hooksUpdated]
1871
+ ].filter(([, c, u]) => c > 0 || u > 0);
1872
+ if (stats.length > 0) {
1873
+ for (const [label, created, updated] of stats) {
1874
+ const parts = [];
1875
+ if (created > 0)
1876
+ parts.push(chalk6.green(`${created} created`));
1877
+ if (updated > 0)
1878
+ parts.push(chalk6.yellow(`${updated} updated`));
1879
+ console.log(` ${label}: ${parts.join(", ")}`);
1880
+ }
1881
+ console.log();
1882
+ }
1883
+ if (result.credentials) {
1884
+ console.log(chalk6.bold.yellow("Credentials (save these now):"));
1885
+ console.log();
1886
+ console.log(
1887
+ ` PLATFORM_API_KEY: ${chalk6.cyan(result.credentials.platformApiKey)}`
1888
+ );
1889
+ console.log(
1890
+ ` PLATFORM_EDITOR_KEY: ${chalk6.cyan(result.credentials.platformEditorKey)}`
1891
+ );
1892
+ console.log(
1893
+ ` WEBHOOK_SECRET: ${chalk6.cyan(result.credentials.webhookSecret)}`
1894
+ );
1895
+ console.log();
1896
+ console.log(
1897
+ chalk6.dim(
1898
+ "These credentials are only shown once. Store them securely."
1899
+ )
1900
+ );
1901
+ }
1902
+ }
1903
+ )
1904
+ );
1905
+ }
1906
+
1907
+ // src/commands/remove.ts
1908
+ import chalk7 from "chalk";
1909
+ import inquirer4 from "inquirer";
1910
+ var GET_CONFIG_QUERY = (
1911
+ /* GraphQL */
1912
+ `
1913
+ query GetConfigByKey($key: String!) {
1914
+ configByKey(key: $key) {
1915
+ id
1916
+ key
1917
+ name
1918
+ configType
1919
+ }
1920
+ }
1921
+ `
1922
+ );
1923
+ var UNREGISTER_MUTATION = (
1924
+ /* GraphQL */
1925
+ `
1926
+ mutation UnregisterConfig($id: ID!) {
1927
+ unregisterConfig(id: $id)
1928
+ }
1929
+ `
1930
+ );
1931
+ function registerRemoveCommand(program2, globalOpts) {
1932
+ program2.command("remove <key>").description("Remove a config and all its provisioned resources").option("--force", "Skip confirmation prompt", false).action(
1933
+ withErrorHandler(
1934
+ globalOpts,
1935
+ async (key, opts) => {
1936
+ const client = await createClient(globalOpts());
1937
+ const { configByKey: config2 } = await client.request(GET_CONFIG_QUERY, { key });
1938
+ if (!config2) {
1939
+ throw new Error(`Config not found: ${key}`);
1940
+ }
1941
+ if (!opts.force) {
1942
+ const { confirmed } = await inquirer4.prompt([
1943
+ {
1944
+ type: "confirm",
1945
+ name: "confirmed",
1946
+ message: `Remove config "${config2.name}" (${config2.key})? This will delete all its models, operations, hooks, and schedules.`,
1947
+ default: false
1948
+ }
1949
+ ]);
1950
+ if (!confirmed) {
1951
+ console.log(chalk7.dim("Cancelled."));
1952
+ return;
1953
+ }
1954
+ }
1955
+ await client.request(UNREGISTER_MUTATION, { id: config2.id });
1956
+ console.log(
1957
+ chalk7.green(`Removed config "${config2.name}" (${config2.key}).`)
1958
+ );
1959
+ }
1960
+ )
1961
+ );
1962
+ }
1963
+
1964
+ // src/commands/profiles.ts
1965
+ import chalk8 from "chalk";
1966
+
1948
1967
  // src/lib/input.ts
1968
+ import inquirer5 from "inquirer";
1949
1969
  async function parseInputData(opts) {
1950
1970
  if (opts.data) {
1951
1971
  return JSON.parse(opts.data);
@@ -1974,7 +1994,7 @@ function isUUID(value) {
1974
1994
  }
1975
1995
  async function confirmAction(message, opts) {
1976
1996
  if (opts?.confirm) return true;
1977
- const { confirmed } = await inquirer4.prompt([
1997
+ const { confirmed } = await inquirer5.prompt([
1978
1998
  {
1979
1999
  type: "confirm",
1980
2000
  name: "confirmed",
@@ -2145,7 +2165,7 @@ function registerProfilesCommand(program2, globalOpts) {
2145
2165
  if (opts.json || opts.jsonl) {
2146
2166
  formatOutput({ deleted: name }, opts);
2147
2167
  } else {
2148
- console.log(chalk6.green(`Deleted profile "${name}".`));
2168
+ console.log(chalk8.green(`Deleted profile "${name}".`));
2149
2169
  }
2150
2170
  }
2151
2171
  )
@@ -2154,9 +2174,9 @@ function registerProfilesCommand(program2, globalOpts) {
2154
2174
 
2155
2175
  // src/commands/register-commands.ts
2156
2176
  import { readFileSync, readdirSync } from "fs";
2157
- import { resolve as resolve4, dirname as dirname4 } from "path";
2177
+ import { resolve as resolve5, dirname as dirname4 } from "path";
2158
2178
  import { fileURLToPath } from "url";
2159
- import chalk7 from "chalk";
2179
+ import chalk9 from "chalk";
2160
2180
 
2161
2181
  // ../command-registry/src/command-map.ts
2162
2182
  var COMMANDS = [
@@ -2782,65 +2802,65 @@ var COMMANDS = [
2782
2802
  // EXTENSIONS
2783
2803
  // =========================================================================
2784
2804
  {
2785
- group: "extensions",
2805
+ group: "configs",
2786
2806
  name: "list",
2787
- description: "List extensions",
2788
- operation: "extensions",
2807
+ description: "List configs",
2808
+ operation: "configs",
2789
2809
  operationType: "query",
2790
2810
  columns: [
2791
2811
  { key: "id", header: "ID", width: 28 },
2792
2812
  { key: "key", header: "Key", width: 20 },
2793
2813
  { key: "name", header: "Name", width: 20 },
2794
- { key: "extensionType", header: "Type", width: 12 },
2814
+ { key: "configType", header: "Type", width: 12 },
2795
2815
  { key: "enabled", header: "Enabled", width: 8, format: "boolean" },
2796
2816
  { key: "syncStatus", header: "Sync", width: 10 }
2797
2817
  ]
2798
2818
  },
2799
2819
  {
2800
- group: "extensions",
2820
+ group: "configs",
2801
2821
  name: "get",
2802
- description: "Get an extension",
2803
- operation: "extension",
2822
+ description: "Get a config",
2823
+ operation: "config",
2804
2824
  operationType: "query",
2805
2825
  positionalArgs: [{ name: "id", graphqlArg: "id" }],
2806
- alternateGet: { operation: "extensionByKey", argName: "key" }
2826
+ alternateGet: { operation: "configByKey", argName: "key" }
2807
2827
  },
2808
2828
  {
2809
- group: "extensions",
2829
+ group: "configs",
2810
2830
  name: "register",
2811
- description: "Register an extension",
2812
- operation: "registerExtension",
2831
+ description: "Register a config",
2832
+ operation: "registerConfig",
2813
2833
  operationType: "mutation",
2814
2834
  acceptsInput: true,
2815
- successMessage: "Registered extension"
2835
+ successMessage: "Registered config"
2816
2836
  },
2817
2837
  {
2818
- group: "extensions",
2819
- name: "install",
2820
- description: "Install an extension",
2821
- operation: "installExtension",
2838
+ group: "configs",
2839
+ name: "apply",
2840
+ description: "Apply a config",
2841
+ operation: "applyConfig",
2822
2842
  operationType: "mutation",
2823
2843
  acceptsInput: true,
2824
- successMessage: "Installed extension"
2844
+ successMessage: "Applied config"
2825
2845
  },
2826
2846
  {
2827
- group: "extensions",
2847
+ group: "configs",
2828
2848
  name: "uninstall",
2829
- description: "Unregister an extension",
2830
- operation: "unregisterExtension",
2849
+ description: "Unregister a config",
2850
+ operation: "unregisterConfig",
2831
2851
  operationType: "mutation",
2832
2852
  positionalArgs: [{ name: "id", graphqlArg: "id" }],
2833
2853
  requiresConfirmation: true,
2834
2854
  scalarResult: true,
2835
- successMessage: "Unregistered extension"
2855
+ successMessage: "Unregistered config"
2836
2856
  },
2837
2857
  {
2838
- group: "extensions",
2858
+ group: "configs",
2839
2859
  name: "sync",
2840
- description: "Trigger extension sync",
2841
- operation: "triggerExtensionSync",
2860
+ description: "Trigger config sync",
2861
+ operation: "triggerConfigSync",
2842
2862
  operationType: "mutation",
2843
- positionalArgs: [{ name: "extensionId", graphqlArg: "extensionId" }],
2863
+ positionalArgs: [{ name: "configId", graphqlArg: "configId" }],
2844
2864
  successMessage: "Triggered sync"
2845
2865
  },
2846
2866
  // =========================================================================
@@ -3757,11 +3777,11 @@ function createSchemaEngine(sdl) {
3757
3777
  var __filename = fileURLToPath(import.meta.url);
3758
3778
  var __dirname = dirname4(__filename);
3759
3779
  function loadSchemaSDL() {
3760
- const bundledPath = resolve4(__dirname, "schema.graphql");
3780
+ const bundledPath = resolve5(__dirname, "schema.graphql");
3761
3781
  try {
3762
3782
  return readFileSync(bundledPath, "utf-8");
3763
3783
  } catch {
3764
- const monorepoPath = resolve4(
3784
+ const monorepoPath = resolve5(
3765
3785
  __dirname,
3766
3786
  "../../../graphql-core/schema.graphql"
3767
3787
  );
@@ -3917,11 +3937,11 @@ function registerDynamicCommands(program2, globalOpts) {
3917
3937
  );
3918
3938
  Object.assign(variables, coerced);
3919
3939
  if (flags.dir && entry.acceptsInput) {
3920
- const dirPath = resolve4(String(flags.dir));
3940
+ const dirPath = resolve5(String(flags.dir));
3921
3941
  const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
3922
3942
  if (files.length === 0) {
3923
3943
  console.error(
3924
- chalk7.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
3944
+ chalk9.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
3925
3945
  );
3926
3946
  return;
3927
3947
  }
@@ -3929,7 +3949,7 @@ function registerDynamicCommands(program2, globalOpts) {
3929
3949
  let updated = 0;
3930
3950
  let failed = 0;
3931
3951
  for (const file of files) {
3932
- const filePath = resolve4(dirPath, file);
3952
+ const filePath = resolve5(dirPath, file);
3933
3953
  const fileData = await parseInputData({ file: filePath });
3934
3954
  const argName = entry.inputArgName ?? "input";
3935
3955
  const fileVars = { ...variables, [argName]: fileData };
@@ -3966,19 +3986,19 @@ function registerDynamicCommands(program2, globalOpts) {
3966
3986
  } catch (updateErr) {
3967
3987
  failed++;
3968
3988
  const msg2 = updateErr instanceof Error ? updateErr.message : String(updateErr);
3969
- console.error(chalk7.red(`\u2717 ${label}:`), msg2);
3989
+ console.error(chalk9.red(`\u2717 ${label}:`), msg2);
3970
3990
  continue;
3971
3991
  }
3972
3992
  }
3973
3993
  failed++;
3974
3994
  const msg = err instanceof Error ? err.message : String(err);
3975
- console.error(chalk7.red(`\u2717 ${label}:`), msg);
3995
+ console.error(chalk9.red(`\u2717 ${label}:`), msg);
3976
3996
  }
3977
3997
  }
3978
3998
  if (!(opts.json || opts.jsonl || opts.quiet)) {
3979
3999
  console.log("");
3980
4000
  console.log(
3981
- chalk7.bold(
4001
+ chalk9.bold(
3982
4002
  `Done: ${created} created${updated ? `, ${updated} updated` : ""}${failed ? `, ${failed} failed` : ""}`
3983
4003
  )
3984
4004
  );
@@ -4154,7 +4174,7 @@ function registerDynamicCommands(program2, globalOpts) {
4154
4174
  }
4155
4175
  } else if (!(opts.json || opts.jsonl || opts.quiet)) {
4156
4176
  console.error(
4157
- chalk7.yellow(
4177
+ chalk9.yellow(
4158
4178
  "\u26A0 Could not auto-publish: no version found in response"
4159
4179
  )
4160
4180
  );
@@ -4178,7 +4198,7 @@ function autoColumns(items) {
4178
4198
  // src/cli.ts
4179
4199
  var __filename2 = fileURLToPath2(import.meta.url);
4180
4200
  var __dirname2 = dirname5(__filename2);
4181
- config({ path: resolve5(__dirname2, "../.env.local") });
4201
+ config({ path: resolve6(__dirname2, "../.env.local") });
4182
4202
  var require2 = createRequire(import.meta.url);
4183
4203
  var { version } = require2("../package.json");
4184
4204
  var program = new Command();
@@ -4200,7 +4220,9 @@ registerWhoamiCommand(program, getGlobalOpts);
4200
4220
  registerProfilesCommand(program, getGlobalOpts);
4201
4221
  registerMediaCommands(program, getGlobalOpts);
4202
4222
  registerSearchCommands(program, getGlobalOpts);
4203
- registerCreateExtensionCommand(program, getGlobalOpts);
4223
+ registerPushCommand(program, getGlobalOpts);
4224
+ registerRemoveCommand(program, getGlobalOpts);
4225
+ registerCreateConfigCommand(program, getGlobalOpts);
4204
4226
  registerInitCommands(program, getGlobalOpts);
4205
4227
  registerDynamicCommands(program, getGlobalOpts);
4206
4228
  program.parse();