@openspecui/server 1.6.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +137 -24
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -962,6 +962,12 @@ const t = initTRPC.context().create();
962
962
  const router = t.router;
963
963
  const publicProcedure = t.procedure;
964
964
  const execFileAsync = promisify(execFile);
965
+ const OPSX_CORE_PROFILE_WORKFLOWS = [
966
+ "propose",
967
+ "explore",
968
+ "apply",
969
+ "archive"
970
+ ];
965
971
  const dashboardGitTaskStatusEmitter = new EventEmitter$1();
966
972
  dashboardGitTaskStatusEmitter.setMaxListeners(200);
967
973
  const dashboardGitTaskStatus = {
@@ -1035,6 +1041,94 @@ function requireChangeId(changeId) {
1035
1041
  if (!changeId) throw new Error("change is required");
1036
1042
  return changeId;
1037
1043
  }
1044
+ function parseOpsxProfileListJson(stdout) {
1045
+ try {
1046
+ const parsed = JSON.parse(stdout);
1047
+ const profile = parsed.profile === "custom" ? "custom" : "core";
1048
+ return {
1049
+ profile,
1050
+ delivery: parsed.delivery === "skills" || parsed.delivery === "commands" ? parsed.delivery : "both",
1051
+ workflows: Array.isArray(parsed.workflows) ? parsed.workflows.filter((item) => typeof item === "string" && item.length > 0) : profile === "core" ? [...OPSX_CORE_PROFILE_WORKFLOWS] : []
1052
+ };
1053
+ } catch {
1054
+ return null;
1055
+ }
1056
+ }
1057
+ function parseOpsxConfigDrift(output) {
1058
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
1059
+ const warningLine = lines.find((line) => /global config.+not applied.+project/i.test(line)) ?? lines.find((line) => /out of sync/i.test(line)) ?? lines.find((line) => /run\s+`?openspec\s+update`?/i.test(line)) ?? null;
1060
+ return {
1061
+ drift: warningLine !== null,
1062
+ warningText: warningLine
1063
+ };
1064
+ }
1065
+ async function fetchOpsxProfileState(ctx) {
1066
+ const configListJson = await ctx.cliExecutor.execute([
1067
+ "config",
1068
+ "list",
1069
+ "--json"
1070
+ ]);
1071
+ if (!configListJson.success) return {
1072
+ available: false,
1073
+ profile: null,
1074
+ delivery: null,
1075
+ workflows: [],
1076
+ driftStatus: "unknown",
1077
+ warningText: null,
1078
+ error: configListJson.stderr || "Failed to load profile config."
1079
+ };
1080
+ const parsed = parseOpsxProfileListJson(configListJson.stdout);
1081
+ if (!parsed) return {
1082
+ available: false,
1083
+ profile: null,
1084
+ delivery: null,
1085
+ workflows: [],
1086
+ driftStatus: "unknown",
1087
+ warningText: null,
1088
+ error: "Invalid JSON from `openspec config list --json`."
1089
+ };
1090
+ const configListText = await ctx.cliExecutor.execute(["config", "list"]);
1091
+ if (!configListText.success) return {
1092
+ available: true,
1093
+ profile: parsed.profile,
1094
+ delivery: parsed.delivery,
1095
+ workflows: parsed.workflows,
1096
+ driftStatus: "unknown",
1097
+ warningText: null
1098
+ };
1099
+ const drift = parseOpsxConfigDrift(`${configListText.stdout}\n${configListText.stderr}`);
1100
+ return {
1101
+ available: true,
1102
+ profile: parsed.profile,
1103
+ delivery: parsed.delivery,
1104
+ workflows: parsed.workflows,
1105
+ driftStatus: drift.drift ? "drift" : "in-sync",
1106
+ warningText: drift.warningText
1107
+ };
1108
+ }
1109
+ async function resolveGlobalConfigPath(ctx) {
1110
+ const result = await ctx.cliExecutor.execute(["config", "path"]);
1111
+ if (!result.success) throw new Error(result.stderr || "Failed to resolve OpenSpec global config path.");
1112
+ const path = result.stdout.trim();
1113
+ if (!path) throw new Error("OpenSpec global config path is empty.");
1114
+ return path;
1115
+ }
1116
+ async function fetchGlobalConfigJson(ctx) {
1117
+ const result = await ctx.cliExecutor.execute([
1118
+ "config",
1119
+ "list",
1120
+ "--json"
1121
+ ]);
1122
+ if (!result.success) throw new Error(result.stderr || "Failed to load OpenSpec global config.");
1123
+ try {
1124
+ const parsed = JSON.parse(result.stdout);
1125
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenSpec global config must be a JSON object.");
1126
+ return parsed;
1127
+ } catch (error) {
1128
+ const message = error instanceof Error ? error.message : String(error);
1129
+ throw new Error(`Invalid JSON from \`openspec config list --json\`: ${message}`);
1130
+ }
1131
+ }
1038
1132
  function ensureEditableSource(source, label) {
1039
1133
  if (source === "package") throw new Error(`${label} is read-only (package source)`);
1040
1134
  }
@@ -1607,18 +1701,41 @@ const cliRouter = router({
1607
1701
  successLabel: tool.successLabel
1608
1702
  }));
1609
1703
  }),
1704
+ getProfileState: publicProcedure.query(async ({ ctx }) => {
1705
+ return fetchOpsxProfileState(ctx);
1706
+ }),
1707
+ getGlobalConfigPath: publicProcedure.query(async ({ ctx }) => {
1708
+ return { path: await resolveGlobalConfigPath(ctx) };
1709
+ }),
1710
+ getGlobalConfig: publicProcedure.query(async ({ ctx }) => {
1711
+ return fetchGlobalConfigJson(ctx);
1712
+ }),
1713
+ setGlobalConfig: publicProcedure.input(z.object({ config: z.record(z.string(), z.unknown()) })).mutation(async ({ ctx, input }) => {
1714
+ const configPath = await resolveGlobalConfigPath(ctx);
1715
+ await mkdir(dirname(configPath), { recursive: true });
1716
+ await writeFile(configPath, `${JSON.stringify(input.config, null, 2)}\n`, "utf8");
1717
+ return { success: true };
1718
+ }),
1610
1719
  getConfiguredTools: publicProcedure.query(async ({ ctx }) => {
1611
1720
  return getConfiguredTools(ctx.projectDir);
1612
1721
  }),
1613
1722
  subscribeConfiguredTools: publicProcedure.subscription(({ ctx }) => {
1614
1723
  return createReactiveSubscription(() => getConfiguredTools(ctx.projectDir));
1615
1724
  }),
1616
- init: publicProcedure.input(z.object({ tools: z.union([
1617
- z.array(z.string()),
1618
- z.literal("all"),
1619
- z.literal("none")
1620
- ]).optional() }).optional()).mutation(async ({ ctx, input }) => {
1621
- return ctx.cliExecutor.init(input?.tools ?? "all");
1725
+ init: publicProcedure.input(z.object({
1726
+ tools: z.union([
1727
+ z.array(z.string()),
1728
+ z.literal("all"),
1729
+ z.literal("none")
1730
+ ]).optional(),
1731
+ profile: z.enum(["core", "custom"]).optional(),
1732
+ force: z.boolean().optional()
1733
+ }).optional()).mutation(async ({ ctx, input }) => {
1734
+ return ctx.cliExecutor.init({
1735
+ tools: input?.tools,
1736
+ profile: input?.profile,
1737
+ force: input?.force
1738
+ });
1622
1739
  }),
1623
1740
  archive: publicProcedure.input(z.object({
1624
1741
  changeId: z.string(),
@@ -1645,12 +1762,20 @@ const cliRouter = router({
1645
1762
  execute: publicProcedure.input(z.object({ args: z.array(z.string()) })).mutation(async ({ ctx, input }) => {
1646
1763
  return ctx.cliExecutor.execute(input.args);
1647
1764
  }),
1648
- initStream: publicProcedure.input(z.object({ tools: z.union([
1649
- z.array(z.string()),
1650
- z.literal("all"),
1651
- z.literal("none")
1652
- ]).optional() }).optional()).subscription(({ ctx, input }) => {
1653
- return createCliStreamObservable((onEvent) => ctx.cliExecutor.initStream(input?.tools ?? "all", onEvent));
1765
+ initStream: publicProcedure.input(z.object({
1766
+ tools: z.union([
1767
+ z.array(z.string()),
1768
+ z.literal("all"),
1769
+ z.literal("none")
1770
+ ]).optional(),
1771
+ profile: z.enum(["core", "custom"]).optional(),
1772
+ force: z.boolean().optional()
1773
+ }).optional()).subscription(({ ctx, input }) => {
1774
+ return createCliStreamObservable((onEvent) => ctx.cliExecutor.initStream({
1775
+ tools: input?.tools,
1776
+ profile: input?.profile,
1777
+ force: input?.force
1778
+ }, onEvent));
1654
1779
  }),
1655
1780
  archiveStream: publicProcedure.input(z.object({
1656
1781
  changeId: z.string(),
@@ -1884,18 +2009,6 @@ const opsxRouter = router({
1884
2009
  return ctx.kernel.getChangeIds();
1885
2010
  });
1886
2011
  }),
1887
- changeMetadata: publicProcedure.input(z.object({ changeId: z.string() })).query(async ({ ctx, input }) => {
1888
- await ctx.kernel.waitForWarmup();
1889
- await ctx.kernel.ensureChangeMetadata(input.changeId);
1890
- return ctx.kernel.getChangeMetadata(input.changeId);
1891
- }),
1892
- subscribeChangeMetadata: publicProcedure.input(z.object({ changeId: z.string() })).subscription(({ ctx, input }) => {
1893
- return createReactiveSubscription(async () => {
1894
- await ctx.kernel.waitForWarmup();
1895
- await ctx.kernel.ensureChangeMetadata(input.changeId);
1896
- return ctx.kernel.getChangeMetadata(input.changeId);
1897
- });
1898
- }),
1899
2012
  readArtifactOutput: publicProcedure.input(z.object({
1900
2013
  changeId: z.string(),
1901
2014
  outputPath: z.string()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openspecui/server",
3
- "version": "1.6.2",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
@@ -20,8 +20,8 @@
20
20
  "yaml": "^2.8.0",
21
21
  "yargs": "^18.0.0",
22
22
  "zod": "^3.24.1",
23
- "@openspecui/core": "1.6.2",
24
- "@openspecui/search": "1.1.0"
23
+ "@openspecui/search": "1.1.0",
24
+ "@openspecui/core": "2.0.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.10.2",