@openspecui/core 2.1.2 → 2.1.3

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.d.mts CHANGED
@@ -696,18 +696,10 @@ declare class OpenSpecAdapter {
696
696
  * Read project.md content (reactive)
697
697
  */
698
698
  readProjectMd(): Promise<string | null>;
699
- /**
700
- * Read AGENTS.md content (reactive)
701
- */
702
- readAgentsMd(): Promise<string | null>;
703
699
  /**
704
700
  * Write project.md content
705
701
  */
706
702
  writeProjectMd(content: string): Promise<void>;
707
- /**
708
- * Write AGENTS.md content
709
- */
710
- writeAgentsMd(content: string): Promise<void>;
711
703
  readSpec(specId: string): Promise<Spec | null>;
712
704
  readSpecRaw(specId: string): Promise<string | null>;
713
705
  readChange(changeId: string): Promise<Change | null>;
@@ -1538,6 +1530,9 @@ declare const DEFAULT_CONFIG: OpenSpecUIConfig;
1538
1530
  *
1539
1531
  * 负责读写 openspec/.openspecui.json 配置文件。
1540
1532
  * 读取操作使用 reactiveReadFile,支持响应式更新。
1533
+ *
1534
+ * `.openspecui.json` 是预期中的项目级 UI 配置文件,但只有显式偏离默认值的
1535
+ * override 才会落盘。仅启动 openspecui 或仅依赖默认配置时,不应触发文件写入。
1541
1536
  */
1542
1537
  declare class ConfigManager {
1543
1538
  private configPath;
@@ -1545,6 +1540,7 @@ declare class ConfigManager {
1545
1540
  private resolvedRunner;
1546
1541
  private resolvingRunnerPromise;
1547
1542
  constructor(projectDir: string);
1543
+ private parseConfigContent;
1548
1544
  /**
1549
1545
  * 读取配置(响应式)
1550
1546
  *
@@ -1558,6 +1554,8 @@ declare class ConfigManager {
1558
1554
  * 会触发文件监听,自动更新订阅者。
1559
1555
  */
1560
1556
  writeConfig(config: OpenSpecUIConfigUpdate): Promise<void>;
1557
+ private resolveDefaultCliCommandParts;
1558
+ private isDefaultCliCommand;
1561
1559
  /**
1562
1560
  * 解析并缓存可用 CLI runner。
1563
1561
  */
@@ -1691,6 +1689,9 @@ declare class CliExecutor {
1691
1689
  }, onEvent: (event: CliStreamEvent) => void): Promise<() => void>;
1692
1690
  /**
1693
1691
  * 流式执行任意命令(数组形式)
1692
+ *
1693
+ * 字面量 `openspec` 会自动通过已解析的 CLI runner 执行,
1694
+ * 其它命令保持原始 spawn 行为。
1694
1695
  */
1695
1696
  executeCommandStream(command: readonly string[], onEvent: (event: CliStreamEvent) => void): () => void;
1696
1697
  }
@@ -1711,7 +1712,7 @@ interface AIToolOption {
1711
1712
  name: string;
1712
1713
  /** 工具 ID(用于 CLI 参数) */
1713
1714
  value: string;
1714
- /** 是否可用(available: false 的工具不会出现在主列表中) */
1715
+ /** 是否可由 openspec init 直接管理 */
1715
1716
  available: boolean;
1716
1717
  /** 成功消息中使用的标签 */
1717
1718
  successLabel?: string;
@@ -1736,11 +1737,18 @@ declare function getAvailableTools(): ToolConfig[];
1736
1737
  */
1737
1738
  declare function getAvailableToolIds(): string[];
1738
1739
  /**
1739
- * 获取所有工具(包括 available: false 的)
1740
+ * 获取所有工具
1740
1741
  */
1741
1742
  declare function getAllTools(): ToolConfig[];
1742
1743
  /**
1743
- * 获取所有工具 ID 列表(包括 available: false 的)
1744
+ * 检测当前项目中已经存在的工具目录。
1745
+ *
1746
+ * 这里对齐 OpenSpec 官方 `getAvailableTools(projectPath)` 的语义:
1747
+ * 仅根据项目根目录下的工具目录是否存在来判断,不读取全局命令安装状态。
1748
+ */
1749
+ declare function getDetectedProjectTools(projectDir: string): Promise<ToolConfig[]>;
1750
+ /**
1751
+ * 获取所有工具 ID 列表
1744
1752
  */
1745
1753
  declare function getAllToolIds(): string[];
1746
1754
  /**
@@ -1756,6 +1764,44 @@ declare function getConfiguredTools(projectDir: string): Promise<string[]>;
1756
1764
  */
1757
1765
  declare function isToolConfigured(projectDir: string, toolId: string): Promise<boolean>;
1758
1766
  //#endregion
1767
+ //#region src/tool-init-state.d.ts
1768
+ declare const TOOL_WORKFLOW_TO_SKILL_DIR: {
1769
+ readonly propose: "openspec-propose";
1770
+ readonly explore: "openspec-explore";
1771
+ readonly new: "openspec-new-change";
1772
+ readonly continue: "openspec-continue-change";
1773
+ readonly apply: "openspec-apply-change";
1774
+ readonly ff: "openspec-ff-change";
1775
+ readonly sync: "openspec-sync-specs";
1776
+ readonly archive: "openspec-archive-change";
1777
+ readonly 'bulk-archive': "openspec-bulk-archive-change";
1778
+ readonly verify: "openspec-verify-change";
1779
+ readonly onboard: "openspec-onboard";
1780
+ };
1781
+ type ToolWorkflowId = keyof typeof TOOL_WORKFLOW_TO_SKILL_DIR;
1782
+ type ToolInitDelivery = 'both' | 'skills' | 'commands';
1783
+ type ToolInitStatus = 'uninitialized' | 'partial' | 'initialized';
1784
+ interface ToolInitState {
1785
+ toolId: string;
1786
+ toolName: string;
1787
+ status: ToolInitStatus;
1788
+ hasAnyArtifacts: boolean;
1789
+ expectedSkillCount: number;
1790
+ presentExpectedSkillCount: number;
1791
+ detectedSkillCount: number;
1792
+ expectedCommandCount: number;
1793
+ presentExpectedCommandCount: number;
1794
+ detectedCommandCount: number;
1795
+ missingSkillWorkflows: ToolWorkflowId[];
1796
+ missingCommandWorkflows: ToolWorkflowId[];
1797
+ unexpectedSkillWorkflows: ToolWorkflowId[];
1798
+ unexpectedCommandWorkflows: ToolWorkflowId[];
1799
+ }
1800
+ declare function getToolInitStates(projectDir: string, options: {
1801
+ delivery: ToolInitDelivery;
1802
+ workflows: readonly string[];
1803
+ }): Promise<ToolInitState[]>;
1804
+ //#endregion
1759
1805
  //#region src/dashboard-types.d.ts
1760
1806
  declare const DASHBOARD_METRIC_KEYS: readonly ["specifications", "requirements", "activeChanges", "inProgressChanges", "completedChanges", "taskCompletionPercent"];
1761
1807
  type DashboardMetricKey = (typeof DASHBOARD_METRIC_KEYS)[number];
@@ -2346,8 +2392,6 @@ interface ExportSnapshot {
2346
2392
  }>;
2347
2393
  /** Project.md content */
2348
2394
  projectMd?: string;
2349
- /** AGENTS.md content */
2350
- agentsMd?: string;
2351
2395
  /** OPSX configuration data (for Config view) */
2352
2396
  opsx?: {
2353
2397
  configYaml?: string;
@@ -2962,4 +3006,4 @@ type PtyServerMessage = z.infer<typeof PtyServerMessageSchema>;
2962
3006
  type PtySessionInfo = z.infer<typeof PtySessionInfoSchema>;
2963
3007
  type PtyPlatform = z.infer<typeof PtyPlatformSchema>;
2964
3008
  //#endregion
2965
- export { type AIToolOption, AI_TOOLS, type ApplyInstructions, ApplyInstructionsSchema, type ApplyTask, ApplyTaskSchema, type ArchiveMeta, type ArtifactInstructions, ArtifactInstructionsSchema, type ArtifactStatus, ArtifactStatusSchema, CODE_EDITOR_THEME_VALUES, type Change, type ChangeFile, ChangeFileSchema, type ChangeMeta, ChangeSchema, type ChangeStatus, ChangeStatusSchema, CliExecutor, type CliResult, type CliRunnerAttempt, type CliSniffResult, type CliStreamEvent, type CodeEditorTheme, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DEFAULT_CONFIG, type DashboardCardAvailability, type DashboardConfig, DashboardConfigSchema, type DashboardGitCommitEntry, type DashboardGitDiffStats, type DashboardGitEntry, type DashboardGitSnapshot, type DashboardGitUncommittedEntry, type DashboardGitWorktree, type DashboardMetricKey, type DashboardOverview, type DashboardSummary, type DashboardTrendKind, type DashboardTrendMeta, type DashboardTrendPoint, type DashboardTriColorTrendPoint, type Delta, type DeltaOperation, DeltaOperationType, DeltaSchema, type DeltaSpec, DeltaSpecSchema, type DependencyInfo, DependencyInfoSchema, type ExportSnapshot, type FileChangeEvent, type FileChangeType, type HostedAppChannelKind, type HostedAppChannelManifest, type HostedAppCompatibilityEntry, type HostedAppVersionManifest, type HostedBackendHealthResponse, MarkdownParser, OFFICIAL_APP_BASE_URL, OpenSpecAdapter, type OpenSpecUIConfig, OpenSpecUIConfigSchema, type OpenSpecUIConfigUpdate, OpenSpecWatcher, OpsxKernel, type PathCallback, ProjectWatcher, type ProjectWatcherReinitializeReason, type ProjectWatcherRuntimeStatus, PtyAttachMessageSchema, PtyBufferResponseSchema, type PtyClientMessage, PtyClientMessageSchema, PtyCloseMessageSchema, PtyCreateMessageSchema, PtyCreatedResponseSchema, PtyErrorCodeSchema, PtyErrorResponseSchema, PtyExitResponseSchema, PtyInputMessageSchema, PtyListMessageSchema, PtyListResponseSchema, PtyOutputResponseSchema, type PtyPlatform, PtyPlatformSchema, PtyResizeMessageSchema, type PtyServerMessage, PtyServerMessageSchema, type PtySessionInfo, PtyTitleResponseSchema, ReactiveContext, ReactiveState, type ReactiveStateOptions, type Requirement, RequirementSchema, type ResolvedCliRunner, type SchemaArtifact, SchemaArtifactSchema, type SchemaDetail, SchemaDetailSchema, type SchemaInfo, SchemaInfoSchema, type SchemaResolution, SchemaResolutionSchema, type Spec, type SpecMeta, SpecSchema, type Task, TaskSchema, type TemplateContentMap, type TemplatesMap, TemplatesSchema, type TerminalConfig, TerminalConfigSchema, type TerminalRendererEngine, TerminalRendererEngineSchema, type ToolConfig, VIRTUAL_PROJECT_DIRNAME, type ValidationIssue, type ValidationResult, Validator, type WatchEvent, type WatchEventType, type WatcherRuntimeStatus, acquireWatcher, buildCliRunnerCandidates, buildHostedLaunchUrl, buildHostedVersionManifestUrl, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, getWatcherRuntimeStatus, initWatcherPool, isGlobPattern, isHostedAppVersionManifest, isHostedBackendHealthResponse, isTerminalRendererEngine, isToolConfigured, isWatcherPoolInitialized, normalizeHostedAppBaseUrl, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, resolveHostedAppBaseUrl, resolveHostedChannelForVersion, sniffGlobalCli, toOpsxDisplayPath };
3009
+ export { type AIToolOption, AI_TOOLS, type ApplyInstructions, ApplyInstructionsSchema, type ApplyTask, ApplyTaskSchema, type ArchiveMeta, type ArtifactInstructions, ArtifactInstructionsSchema, type ArtifactStatus, ArtifactStatusSchema, CODE_EDITOR_THEME_VALUES, type Change, type ChangeFile, ChangeFileSchema, type ChangeMeta, ChangeSchema, type ChangeStatus, ChangeStatusSchema, CliExecutor, type CliResult, type CliRunnerAttempt, type CliSniffResult, type CliStreamEvent, type CodeEditorTheme, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DEFAULT_CONFIG, type DashboardCardAvailability, type DashboardConfig, DashboardConfigSchema, type DashboardGitCommitEntry, type DashboardGitDiffStats, type DashboardGitEntry, type DashboardGitSnapshot, type DashboardGitUncommittedEntry, type DashboardGitWorktree, type DashboardMetricKey, type DashboardOverview, type DashboardSummary, type DashboardTrendKind, type DashboardTrendMeta, type DashboardTrendPoint, type DashboardTriColorTrendPoint, type Delta, type DeltaOperation, DeltaOperationType, DeltaSchema, type DeltaSpec, DeltaSpecSchema, type DependencyInfo, DependencyInfoSchema, type ExportSnapshot, type FileChangeEvent, type FileChangeType, type HostedAppChannelKind, type HostedAppChannelManifest, type HostedAppCompatibilityEntry, type HostedAppVersionManifest, type HostedBackendHealthResponse, MarkdownParser, OFFICIAL_APP_BASE_URL, OpenSpecAdapter, type OpenSpecUIConfig, OpenSpecUIConfigSchema, type OpenSpecUIConfigUpdate, OpenSpecWatcher, OpsxKernel, type PathCallback, ProjectWatcher, type ProjectWatcherReinitializeReason, type ProjectWatcherRuntimeStatus, PtyAttachMessageSchema, PtyBufferResponseSchema, type PtyClientMessage, PtyClientMessageSchema, PtyCloseMessageSchema, PtyCreateMessageSchema, PtyCreatedResponseSchema, PtyErrorCodeSchema, PtyErrorResponseSchema, PtyExitResponseSchema, PtyInputMessageSchema, PtyListMessageSchema, PtyListResponseSchema, PtyOutputResponseSchema, type PtyPlatform, PtyPlatformSchema, PtyResizeMessageSchema, type PtyServerMessage, PtyServerMessageSchema, type PtySessionInfo, PtyTitleResponseSchema, ReactiveContext, ReactiveState, type ReactiveStateOptions, type Requirement, RequirementSchema, type ResolvedCliRunner, type SchemaArtifact, SchemaArtifactSchema, type SchemaDetail, SchemaDetailSchema, type SchemaInfo, SchemaInfoSchema, type SchemaResolution, SchemaResolutionSchema, type Spec, type SpecMeta, SpecSchema, TOOL_WORKFLOW_TO_SKILL_DIR, type Task, TaskSchema, type TemplateContentMap, type TemplatesMap, TemplatesSchema, type TerminalConfig, TerminalConfigSchema, type TerminalRendererEngine, TerminalRendererEngineSchema, type ToolConfig, type ToolInitDelivery, type ToolInitState, type ToolInitStatus, type ToolWorkflowId, VIRTUAL_PROJECT_DIRNAME, type ValidationIssue, type ValidationResult, Validator, type WatchEvent, type WatchEventType, type WatcherRuntimeStatus, acquireWatcher, buildCliRunnerCandidates, buildHostedLaunchUrl, buildHostedVersionManifestUrl, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getDetectedProjectTools, getProjectWatcher, getToolById, getToolInitStates, getWatchedProjectDir, getWatcherRuntimeStatus, initWatcherPool, isGlobPattern, isHostedAppVersionManifest, isHostedBackendHealthResponse, isTerminalRendererEngine, isToolConfigured, isWatcherPoolInitialized, normalizeHostedAppBaseUrl, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, resolveHostedAppBaseUrl, resolveHostedChannelForVersion, sniffGlobalCli, toOpsxDisplayPath };
package/dist/index.mjs CHANGED
@@ -11,6 +11,7 @@ import { EventEmitter } from "events";
11
11
  import { watch } from "fs";
12
12
  import { exec, spawn } from "child_process";
13
13
  import { promisify } from "util";
14
+ import { homedir } from "node:os";
14
15
  import { parse } from "yaml";
15
16
 
16
17
  //#region src/parser.ts
@@ -1417,23 +1418,11 @@ var OpenSpecAdapter = class {
1417
1418
  return reactiveReadFile(join(this.openspecDir, "project.md"));
1418
1419
  }
1419
1420
  /**
1420
- * Read AGENTS.md content (reactive)
1421
- */
1422
- async readAgentsMd() {
1423
- return reactiveReadFile(join(this.openspecDir, "AGENTS.md"));
1424
- }
1425
- /**
1426
1421
  * Write project.md content
1427
1422
  */
1428
1423
  async writeProjectMd(content) {
1429
1424
  await writeFile(join(this.openspecDir, "project.md"), content, "utf-8");
1430
1425
  }
1431
- /**
1432
- * Write AGENTS.md content
1433
- */
1434
- async writeAgentsMd(content) {
1435
- await writeFile(join(this.openspecDir, "AGENTS.md"), content, "utf-8");
1436
- }
1437
1426
  async readSpec(specId) {
1438
1427
  try {
1439
1428
  const content = await this.readSpecRaw(specId);
@@ -1604,24 +1593,6 @@ This project uses OpenSpec for spec-driven development.
1604
1593
  - \`specs/\` - Source of truth specifications
1605
1594
  - \`changes/\` - Active change proposals
1606
1595
  - \`changes/archive/\` - Completed changes
1607
- `, "utf-8");
1608
- await writeFile(join(this.openspecDir, "AGENTS.md"), `# AI Agent Instructions
1609
-
1610
- This project uses OpenSpec for spec-driven development.
1611
-
1612
- ## Available Commands
1613
- - \`openspec list\` - List changes or specs
1614
- - \`openspec view\` - Dashboard view
1615
- - \`openspec show <name>\` - Show change or spec details
1616
- - \`openspec validate <name>\` - Validate change or spec
1617
- - \`openspec archive <change>\` - Archive completed change
1618
-
1619
- ## Workflow
1620
- 1. Create a change proposal in \`changes/<change-id>/proposal.md\`
1621
- 2. Define delta specs in \`changes/<change-id>/specs/\`
1622
- 3. Track tasks in \`changes/<change-id>/tasks.md\`
1623
- 4. Implement and mark tasks complete
1624
- 5. Archive when done: \`openspec archive <change-id>\`
1625
1596
  `, "utf-8");
1626
1597
  }
1627
1598
  /**
@@ -1880,7 +1851,7 @@ var OpenSpecWatcher = class extends EventEmitter {
1880
1851
  });
1881
1852
  });
1882
1853
  this.watchDir(this.openspecDir, (filename, eventType) => {
1883
- if (filename === "project.md" || filename === "AGENTS.md") this.emitDebounced(`project:${filename}`, {
1854
+ if (filename === "project.md") this.emitDebounced(`project:${filename}`, {
1884
1855
  type: "project",
1885
1856
  action: eventType === "rename" ? "create" : "update",
1886
1857
  path: join(this.openspecDir, filename),
@@ -2344,11 +2315,68 @@ const DEFAULT_CONFIG = {
2344
2315
  terminal: TerminalConfigSchema.parse({}),
2345
2316
  dashboard: DashboardConfigSchema.parse({})
2346
2317
  };
2318
+ function areStringArraysEqual(left, right) {
2319
+ if (left === right) return true;
2320
+ if (!left || !right) return !left && !right;
2321
+ if (left.length !== right.length) return false;
2322
+ return left.every((value, index) => value === right[index]);
2323
+ }
2324
+ function pruneNullish(value) {
2325
+ if (value === null || value === void 0) return;
2326
+ if (Array.isArray(value)) return value.map((entry) => pruneNullish(entry)).filter((entry) => entry !== void 0);
2327
+ if (typeof value === "object") {
2328
+ const normalizedEntries = Object.entries(value).flatMap(([key, entryValue]) => {
2329
+ const nextValue = pruneNullish(entryValue);
2330
+ return nextValue === void 0 ? [] : [[key, nextValue]];
2331
+ });
2332
+ return Object.fromEntries(normalizedEntries);
2333
+ }
2334
+ return value;
2335
+ }
2336
+ function hasOwnEntries(value) {
2337
+ return Object.keys(value).length > 0;
2338
+ }
2339
+ function toPersistedConfig(config, options = {}) {
2340
+ const persisted = {};
2341
+ const command = config.cli.command?.trim();
2342
+ const args = (config.cli.args ?? []).map((arg) => arg.trim()).filter(Boolean);
2343
+ const cliCommandParts = command ? [command, ...args] : void 0;
2344
+ if (cliCommandParts && !areStringArraysEqual(cliCommandParts, options.defaultCliCommandParts)) {
2345
+ const persistedCommand = cliCommandParts[0];
2346
+ persisted.cli = args.length > 0 ? {
2347
+ command: persistedCommand,
2348
+ args
2349
+ } : { command: persistedCommand };
2350
+ }
2351
+ if (config.theme !== DEFAULT_CONFIG.theme) persisted.theme = config.theme;
2352
+ const codeEditor = {};
2353
+ if (config.codeEditor.theme !== DEFAULT_CONFIG.codeEditor.theme) codeEditor.theme = config.codeEditor.theme;
2354
+ if (hasOwnEntries(codeEditor)) persisted.codeEditor = codeEditor;
2355
+ if (config.appBaseUrl !== DEFAULT_CONFIG.appBaseUrl) persisted.appBaseUrl = config.appBaseUrl;
2356
+ const terminal = {};
2357
+ if (config.terminal.fontSize !== DEFAULT_CONFIG.terminal.fontSize) terminal.fontSize = config.terminal.fontSize;
2358
+ if (config.terminal.fontFamily !== DEFAULT_CONFIG.terminal.fontFamily) terminal.fontFamily = config.terminal.fontFamily;
2359
+ if (config.terminal.cursorBlink !== DEFAULT_CONFIG.terminal.cursorBlink) terminal.cursorBlink = config.terminal.cursorBlink;
2360
+ if (config.terminal.cursorStyle !== DEFAULT_CONFIG.terminal.cursorStyle) terminal.cursorStyle = config.terminal.cursorStyle;
2361
+ if (config.terminal.scrollback !== DEFAULT_CONFIG.terminal.scrollback) terminal.scrollback = config.terminal.scrollback;
2362
+ if (config.terminal.rendererEngine !== DEFAULT_CONFIG.terminal.rendererEngine) terminal.rendererEngine = config.terminal.rendererEngine;
2363
+ if (hasOwnEntries(terminal)) persisted.terminal = terminal;
2364
+ const dashboard = {};
2365
+ if (config.dashboard.trendPointLimit !== DEFAULT_CONFIG.dashboard.trendPointLimit) dashboard.trendPointLimit = config.dashboard.trendPointLimit;
2366
+ if (hasOwnEntries(dashboard)) persisted.dashboard = dashboard;
2367
+ return persisted;
2368
+ }
2369
+ function isPersistedConfigEmpty(config) {
2370
+ return !hasOwnEntries(config);
2371
+ }
2347
2372
  /**
2348
2373
  * 配置管理器
2349
2374
  *
2350
2375
  * 负责读写 openspec/.openspecui.json 配置文件。
2351
2376
  * 读取操作使用 reactiveReadFile,支持响应式更新。
2377
+ *
2378
+ * `.openspecui.json` 是预期中的项目级 UI 配置文件,但只有显式偏离默认值的
2379
+ * override 才会落盘。仅启动 openspecui 或仅依赖默认配置时,不应触发文件写入。
2352
2380
  */
2353
2381
  var ConfigManager = class {
2354
2382
  configPath;
@@ -2359,18 +2387,11 @@ var ConfigManager = class {
2359
2387
  this.projectDir = projectDir;
2360
2388
  this.configPath = join(projectDir, "openspec", ".openspecui.json");
2361
2389
  }
2362
- /**
2363
- * 读取配置(响应式)
2364
- *
2365
- * 如果配置文件不存在,返回默认配置。
2366
- * 如果配置文件格式错误,返回默认配置并打印警告。
2367
- */
2368
- async readConfig() {
2369
- const content = await reactiveReadFile(this.configPath);
2390
+ parseConfigContent(content) {
2370
2391
  if (!content) return DEFAULT_CONFIG;
2371
2392
  try {
2372
- const parsed = JSON.parse(content);
2373
- const result = OpenSpecUIConfigSchema.safeParse(parsed);
2393
+ const normalized = pruneNullish(JSON.parse(content)) ?? {};
2394
+ const result = OpenSpecUIConfigSchema.safeParse(normalized);
2374
2395
  if (result.success) return result.data;
2375
2396
  console.warn("Invalid config format, using defaults:", result.error.message);
2376
2397
  return DEFAULT_CONFIG;
@@ -2380,12 +2401,24 @@ var ConfigManager = class {
2380
2401
  }
2381
2402
  }
2382
2403
  /**
2404
+ * 读取配置(响应式)
2405
+ *
2406
+ * 如果配置文件不存在,返回默认配置。
2407
+ * 如果配置文件格式错误,返回默认配置并打印警告。
2408
+ */
2409
+ async readConfig() {
2410
+ const content = await reactiveReadFile(this.configPath);
2411
+ return this.parseConfigContent(content);
2412
+ }
2413
+ /**
2383
2414
  * 写入配置
2384
2415
  *
2385
2416
  * 会触发文件监听,自动更新订阅者。
2386
2417
  */
2387
2418
  async writeConfig(config) {
2388
- const current = await this.readConfig();
2419
+ const currentContent = await reactiveReadFile(this.configPath);
2420
+ const fileExists = currentContent !== null;
2421
+ const current = this.parseConfigContent(currentContent);
2389
2422
  const nextCli = { ...current.cli };
2390
2423
  if (config.cli && Object.prototype.hasOwnProperty.call(config.cli, "command")) {
2391
2424
  const trimmed = config.cli.command?.trim();
@@ -2401,7 +2434,7 @@ var ConfigManager = class {
2401
2434
  else delete nextCli.args;
2402
2435
  }
2403
2436
  if (!nextCli.command) delete nextCli.args;
2404
- const merged = {
2437
+ const persisted = toPersistedConfig({
2405
2438
  ...current,
2406
2439
  cli: nextCli,
2407
2440
  theme: config.theme ?? current.theme,
@@ -2418,13 +2451,25 @@ var ConfigManager = class {
2418
2451
  ...current.dashboard,
2419
2452
  ...config.dashboard
2420
2453
  }
2421
- };
2422
- const serialized = JSON.stringify(merged, null, 2);
2454
+ });
2455
+ if (isPersistedConfigEmpty(persisted) && !fileExists) return;
2456
+ const serialized = isPersistedConfigEmpty(persisted) ? "{}" : JSON.stringify(persisted, null, 2);
2457
+ if (currentContent === serialized) return;
2423
2458
  await mkdir(dirname(this.configPath), { recursive: true });
2424
2459
  await writeFile(this.configPath, serialized, "utf-8");
2425
2460
  updateReactiveFileCache(this.configPath, serialized);
2426
2461
  this.invalidateResolvedCliRunner();
2427
2462
  }
2463
+ async resolveDefaultCliCommandParts() {
2464
+ return (await resolveCliRunner(buildCliRunnerCandidates({ userAgent: process.env.npm_config_user_agent }).filter((candidate) => candidate.id !== "configured"), this.projectDir, createCleanCliEnv())).commandParts;
2465
+ }
2466
+ async isDefaultCliCommand(commandParts) {
2467
+ try {
2468
+ return areStringArraysEqual(commandParts, await this.resolveDefaultCliCommandParts());
2469
+ } catch {
2470
+ return false;
2471
+ }
2472
+ }
2428
2473
  /**
2429
2474
  * 解析并缓存可用 CLI runner。
2430
2475
  */
@@ -2442,8 +2487,7 @@ var ConfigManager = class {
2442
2487
  async resolveCliRunnerUncached() {
2443
2488
  const config = await this.readConfig();
2444
2489
  const configuredCommandParts = this.getConfiguredCommandParts(config.cli);
2445
- const hasConfiguredCommand = configuredCommandParts.length > 0;
2446
- const resolved = await resolveCliRunner(hasConfiguredCommand ? [{
2490
+ return await resolveCliRunner(configuredCommandParts.length > 0 ? [{
2447
2491
  id: "configured",
2448
2492
  source: "config.cli.command",
2449
2493
  commandParts: configuredCommandParts
@@ -2451,20 +2495,6 @@ var ConfigManager = class {
2451
2495
  configuredCommandParts,
2452
2496
  userAgent: process.env.npm_config_user_agent
2453
2497
  }), this.projectDir, createCleanCliEnv());
2454
- if (!hasConfiguredCommand) {
2455
- const [resolvedCommand, ...resolvedArgs] = resolved.commandParts;
2456
- const currentCommand = config.cli.command?.trim();
2457
- const currentArgs = config.cli.args ?? [];
2458
- if (currentCommand !== resolvedCommand || currentArgs.length !== resolvedArgs.length || currentArgs.some((arg, index) => arg !== resolvedArgs[index])) try {
2459
- await this.writeConfig({ cli: {
2460
- command: resolvedCommand,
2461
- args: resolvedArgs
2462
- } });
2463
- } catch (err) {
2464
- console.warn("Failed to persist auto-detected CLI command:", err);
2465
- }
2466
- }
2467
- return resolved;
2468
2498
  }
2469
2499
  /**
2470
2500
  * 获取 CLI 命令(数组形式)
@@ -2511,6 +2541,13 @@ var ConfigManager = class {
2511
2541
  } });
2512
2542
  return;
2513
2543
  }
2544
+ if (await this.isDefaultCliCommand(commandParts)) {
2545
+ await this.writeConfig({ cli: {
2546
+ command: null,
2547
+ args: null
2548
+ } });
2549
+ return;
2550
+ }
2514
2551
  const [resolvedCommand, ...resolvedArgs] = commandParts;
2515
2552
  await this.writeConfig({ cli: {
2516
2553
  command: resolvedCommand,
@@ -2823,9 +2860,36 @@ var CliExecutor = class {
2823
2860
  }
2824
2861
  /**
2825
2862
  * 流式执行任意命令(数组形式)
2863
+ *
2864
+ * 字面量 `openspec` 会自动通过已解析的 CLI runner 执行,
2865
+ * 其它命令保持原始 spawn 行为。
2826
2866
  */
2827
2867
  executeCommandStream(command, onEvent) {
2828
2868
  const [cmd, ...cmdArgs] = command;
2869
+ if (cmd === "openspec") {
2870
+ let cancelResolved = null;
2871
+ let cancelled = false;
2872
+ this.executeStream([...cmdArgs], onEvent).then((cancel) => {
2873
+ if (cancelled) {
2874
+ cancel();
2875
+ return;
2876
+ }
2877
+ cancelResolved = cancel;
2878
+ }).catch((err) => {
2879
+ onEvent({
2880
+ type: "stderr",
2881
+ data: err instanceof Error ? err.message : String(err)
2882
+ });
2883
+ onEvent({
2884
+ type: "exit",
2885
+ exitCode: null
2886
+ });
2887
+ });
2888
+ return () => {
2889
+ cancelled = true;
2890
+ cancelResolved?.();
2891
+ };
2892
+ }
2829
2893
  onEvent({
2830
2894
  type: "command",
2831
2895
  data: command.join(" ")
@@ -3067,12 +3131,6 @@ const AI_TOOLS = [
3067
3131
  available: true,
3068
3132
  successLabel: "Windsurf",
3069
3133
  skillsDir: ".windsurf"
3070
- },
3071
- {
3072
- name: "AGENTS.md (works with Amp, VS Code, …)",
3073
- value: "agents",
3074
- available: false,
3075
- successLabel: "your AGENTS.md-compatible assistant"
3076
3134
  }
3077
3135
  ];
3078
3136
  /**
@@ -3088,13 +3146,25 @@ function getAvailableToolIds() {
3088
3146
  return getAvailableTools().map((tool) => tool.value);
3089
3147
  }
3090
3148
  /**
3091
- * 获取所有工具(包括 available: false 的)
3149
+ * 获取所有工具
3092
3150
  */
3093
3151
  function getAllTools() {
3094
3152
  return AI_TOOLS;
3095
3153
  }
3096
3154
  /**
3097
- * 获取所有工具 ID 列表(包括 available: false 的)
3155
+ * 检测当前项目中已经存在的工具目录。
3156
+ *
3157
+ * 这里对齐 OpenSpec 官方 `getAvailableTools(projectPath)` 的语义:
3158
+ * 仅根据项目根目录下的工具目录是否存在来判断,不读取全局命令安装状态。
3159
+ */
3160
+ async function getDetectedProjectTools(projectDir) {
3161
+ return (await Promise.all(AI_TOOLS.map(async (tool) => {
3162
+ if (!tool.skillsDir) return null;
3163
+ return await reactiveExists(join$1(projectDir, tool.skillsDir)) ? tool : null;
3164
+ }))).filter((tool) => tool !== null);
3165
+ }
3166
+ /**
3167
+ * 获取所有工具 ID 列表
3098
3168
  */
3099
3169
  function getAllToolIds() {
3100
3170
  return AI_TOOLS.map((tool) => tool.value);
@@ -3173,6 +3243,143 @@ async function isToolConfigured(projectDir, toolId) {
3173
3243
  return (await getConfiguredTools(projectDir)).includes(toolId);
3174
3244
  }
3175
3245
 
3246
+ //#endregion
3247
+ //#region src/tool-init-state.ts
3248
+ const TOOL_WORKFLOW_TO_SKILL_DIR = {
3249
+ propose: "openspec-propose",
3250
+ explore: "openspec-explore",
3251
+ new: "openspec-new-change",
3252
+ continue: "openspec-continue-change",
3253
+ apply: "openspec-apply-change",
3254
+ ff: "openspec-ff-change",
3255
+ sync: "openspec-sync-specs",
3256
+ archive: "openspec-archive-change",
3257
+ "bulk-archive": "openspec-bulk-archive-change",
3258
+ verify: "openspec-verify-change",
3259
+ onboard: "openspec-onboard"
3260
+ };
3261
+ const ALL_TOOL_WORKFLOWS = Object.keys(TOOL_WORKFLOW_TO_SKILL_DIR);
3262
+ function toKnownWorkflows(workflows) {
3263
+ return workflows.filter((workflow) => workflow in TOOL_WORKFLOW_TO_SKILL_DIR);
3264
+ }
3265
+ function resolveCodexHome() {
3266
+ const configuredHome = process.env.CODEX_HOME?.trim();
3267
+ return resolve(configuredHome ? configuredHome : join$1(homedir(), ".codex"));
3268
+ }
3269
+ function resolveToolCommandPath(projectDir, toolId, workflow) {
3270
+ switch (toolId) {
3271
+ case "amazon-q": return resolve(projectDir, ".amazonq", "prompts", `opsx-${workflow}.md`);
3272
+ case "antigravity": return resolve(projectDir, ".agent", "workflows", `opsx-${workflow}.md`);
3273
+ case "auggie": return resolve(projectDir, ".augment", "commands", `opsx-${workflow}.md`);
3274
+ case "claude": return resolve(projectDir, ".claude", "commands", "opsx", `${workflow}.md`);
3275
+ case "cline": return resolve(projectDir, ".clinerules", "workflows", `opsx-${workflow}.md`);
3276
+ case "codebuddy": return resolve(projectDir, ".codebuddy", "commands", "opsx", `${workflow}.md`);
3277
+ case "codex": return resolve(resolveCodexHome(), "prompts", `opsx-${workflow}.md`);
3278
+ case "continue": return resolve(projectDir, ".continue", "prompts", `opsx-${workflow}.prompt`);
3279
+ case "costrict": return resolve(projectDir, ".cospec", "openspec", "commands", `opsx-${workflow}.md`);
3280
+ case "crush": return resolve(projectDir, ".crush", "commands", "opsx", `${workflow}.md`);
3281
+ case "cursor": return resolve(projectDir, ".cursor", "commands", `opsx-${workflow}.md`);
3282
+ case "factory": return resolve(projectDir, ".factory", "commands", `opsx-${workflow}.md`);
3283
+ case "gemini": return resolve(projectDir, ".gemini", "commands", "opsx", `${workflow}.toml`);
3284
+ case "github-copilot": return resolve(projectDir, ".github", "prompts", `opsx-${workflow}.prompt.md`);
3285
+ case "iflow": return resolve(projectDir, ".iflow", "commands", `opsx-${workflow}.md`);
3286
+ case "kilocode": return resolve(projectDir, ".kilocode", "workflows", `opsx-${workflow}.md`);
3287
+ case "kiro": return resolve(projectDir, ".kiro", "prompts", `opsx-${workflow}.prompt.md`);
3288
+ case "opencode": return resolve(projectDir, ".opencode", "command", `opsx-${workflow}.md`);
3289
+ case "pi": return resolve(projectDir, ".pi", "prompts", `opsx-${workflow}.md`);
3290
+ case "qoder": return resolve(projectDir, ".qoder", "commands", "opsx", `${workflow}.md`);
3291
+ case "qwen": return resolve(projectDir, ".qwen", "commands", `opsx-${workflow}.toml`);
3292
+ case "roocode": return resolve(projectDir, ".roo", "commands", `opsx-${workflow}.md`);
3293
+ case "windsurf": return resolve(projectDir, ".windsurf", "workflows", `opsx-${workflow}.md`);
3294
+ default: return null;
3295
+ }
3296
+ }
3297
+ function getSkillArtifacts(projectDir, skillsDir) {
3298
+ return ALL_TOOL_WORKFLOWS.map((workflow) => ({
3299
+ workflow,
3300
+ path: resolve(projectDir, skillsDir, "skills", TOOL_WORKFLOW_TO_SKILL_DIR[workflow], "SKILL.md")
3301
+ }));
3302
+ }
3303
+ function getCommandArtifacts(projectDir, toolId) {
3304
+ return ALL_TOOL_WORKFLOWS.flatMap((workflow) => {
3305
+ const path = resolveToolCommandPath(projectDir, toolId, workflow);
3306
+ return path ? [{
3307
+ workflow,
3308
+ path
3309
+ }] : [];
3310
+ });
3311
+ }
3312
+ function invalidateToolInitCaches(projectDir) {
3313
+ const cacheRoots = /* @__PURE__ */ new Set();
3314
+ for (const tool of AI_TOOLS) {
3315
+ if (tool.skillsDir) cacheRoots.add(resolve(projectDir, tool.skillsDir));
3316
+ for (const workflow of ALL_TOOL_WORKFLOWS) {
3317
+ const commandPath = resolveToolCommandPath(projectDir, tool.value, workflow);
3318
+ if (commandPath) cacheRoots.add(dirname$1(commandPath));
3319
+ }
3320
+ }
3321
+ for (const root of cacheRoots) clearCache(root);
3322
+ }
3323
+ async function getExistingArtifactPaths(entries) {
3324
+ const presence = await Promise.all(entries.map(async (entry) => ({
3325
+ path: entry.path,
3326
+ exists: await reactiveExists(entry.path)
3327
+ })));
3328
+ return new Set(presence.filter((entry) => entry.exists).map((entry) => entry.path));
3329
+ }
3330
+ function countExisting(entries, existingPaths) {
3331
+ return entries.reduce((count, entry) => count + (existingPaths.has(entry.path) ? 1 : 0), 0);
3332
+ }
3333
+ function collectMissingWorkflows(entries, existingPaths) {
3334
+ return entries.filter((entry) => !existingPaths.has(entry.path)).map((entry) => entry.workflow);
3335
+ }
3336
+ function collectUnexpectedWorkflows(entries, desiredWorkflowSet, existingPaths) {
3337
+ return entries.filter((entry) => !desiredWorkflowSet.has(entry.workflow) && existingPaths.has(entry.path)).map((entry) => entry.workflow);
3338
+ }
3339
+ async function getToolInitStates(projectDir, options) {
3340
+ invalidateToolInitCaches(projectDir);
3341
+ const desiredWorkflows = toKnownWorkflows(options.workflows);
3342
+ const desiredWorkflowSet = new Set(desiredWorkflows);
3343
+ const shouldGenerateSkills = options.delivery !== "commands";
3344
+ const shouldGenerateCommands = options.delivery !== "skills";
3345
+ return Promise.all(AI_TOOLS.filter((tool) => tool.skillsDir).map(async (tool) => {
3346
+ const skillArtifacts = getSkillArtifacts(projectDir, tool.skillsDir);
3347
+ const commandArtifacts = getCommandArtifacts(projectDir, tool.value);
3348
+ const existingSkillPaths = await getExistingArtifactPaths(skillArtifacts);
3349
+ const existingCommandPaths = await getExistingArtifactPaths(commandArtifacts);
3350
+ const expectedSkillArtifacts = shouldGenerateSkills ? skillArtifacts.filter((entry) => desiredWorkflowSet.has(entry.workflow)) : [];
3351
+ const expectedCommandArtifacts = shouldGenerateCommands ? commandArtifacts.filter((entry) => desiredWorkflowSet.has(entry.workflow)) : [];
3352
+ const missingSkillWorkflows = collectMissingWorkflows(expectedSkillArtifacts, existingSkillPaths);
3353
+ const missingCommandWorkflows = collectMissingWorkflows(expectedCommandArtifacts, existingCommandPaths);
3354
+ const unexpectedSkillWorkflows = collectUnexpectedWorkflows(shouldGenerateSkills ? skillArtifacts : skillArtifacts, shouldGenerateSkills ? desiredWorkflowSet : /* @__PURE__ */ new Set(), existingSkillPaths);
3355
+ const unexpectedCommandWorkflows = collectUnexpectedWorkflows(shouldGenerateCommands ? commandArtifacts : commandArtifacts, shouldGenerateCommands ? desiredWorkflowSet : /* @__PURE__ */ new Set(), existingCommandPaths);
3356
+ const expectedSkillCount = expectedSkillArtifacts.length;
3357
+ const presentExpectedSkillCount = expectedSkillCount - missingSkillWorkflows.length;
3358
+ const detectedSkillCount = countExisting(skillArtifacts, existingSkillPaths);
3359
+ const expectedCommandCount = expectedCommandArtifacts.length;
3360
+ const presentExpectedCommandCount = expectedCommandCount - missingCommandWorkflows.length;
3361
+ const detectedCommandCount = countExisting(commandArtifacts, existingCommandPaths);
3362
+ const hasAnyArtifacts = detectedSkillCount + detectedCommandCount > 0;
3363
+ const isInitialized = missingSkillWorkflows.length === 0 && missingCommandWorkflows.length === 0 && unexpectedSkillWorkflows.length === 0 && unexpectedCommandWorkflows.length === 0;
3364
+ return {
3365
+ toolId: tool.value,
3366
+ toolName: tool.name,
3367
+ status: !hasAnyArtifacts ? "uninitialized" : isInitialized ? "initialized" : "partial",
3368
+ hasAnyArtifacts,
3369
+ expectedSkillCount,
3370
+ presentExpectedSkillCount,
3371
+ detectedSkillCount,
3372
+ expectedCommandCount,
3373
+ presentExpectedCommandCount,
3374
+ detectedCommandCount,
3375
+ missingSkillWorkflows,
3376
+ missingCommandWorkflows,
3377
+ unexpectedSkillWorkflows,
3378
+ unexpectedCommandWorkflows
3379
+ };
3380
+ }));
3381
+ }
3382
+
3176
3383
  //#endregion
3177
3384
  //#region src/dashboard-types.ts
3178
3385
  const DASHBOARD_METRIC_KEYS = [
@@ -4079,4 +4286,4 @@ const PtyServerMessageSchema = z.discriminatedUnion("type", [
4079
4286
  ]);
4080
4287
 
4081
4288
  //#endregion
4082
- export { AI_TOOLS, ApplyInstructionsSchema, ApplyTaskSchema, ArtifactInstructionsSchema, ArtifactStatusSchema, CODE_EDITOR_THEME_VALUES, ChangeFileSchema, ChangeSchema, ChangeStatusSchema, CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DEFAULT_CONFIG, DashboardConfigSchema, DeltaOperationType, DeltaSchema, DeltaSpecSchema, DependencyInfoSchema, MarkdownParser, OFFICIAL_APP_BASE_URL, OpenSpecAdapter, OpenSpecUIConfigSchema, OpenSpecWatcher, OpsxKernel, ProjectWatcher, PtyAttachMessageSchema, PtyBufferResponseSchema, PtyClientMessageSchema, PtyCloseMessageSchema, PtyCreateMessageSchema, PtyCreatedResponseSchema, PtyErrorCodeSchema, PtyErrorResponseSchema, PtyExitResponseSchema, PtyInputMessageSchema, PtyListMessageSchema, PtyListResponseSchema, PtyOutputResponseSchema, PtyPlatformSchema, PtyResizeMessageSchema, PtyServerMessageSchema, PtyTitleResponseSchema, ReactiveContext, ReactiveState, RequirementSchema, SchemaArtifactSchema, SchemaDetailSchema, SchemaInfoSchema, SchemaResolutionSchema, SpecSchema, TaskSchema, TemplatesSchema, TerminalConfigSchema, TerminalRendererEngineSchema, VIRTUAL_PROJECT_DIRNAME, Validator, acquireWatcher, buildCliRunnerCandidates, buildHostedLaunchUrl, buildHostedVersionManifestUrl, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, getWatcherRuntimeStatus, initWatcherPool, isGlobPattern, isHostedAppVersionManifest, isHostedBackendHealthResponse, isTerminalRendererEngine, isToolConfigured, isWatcherPoolInitialized, normalizeHostedAppBaseUrl, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, resolveHostedAppBaseUrl, resolveHostedChannelForVersion, sniffGlobalCli, toOpsxDisplayPath };
4289
+ export { AI_TOOLS, ApplyInstructionsSchema, ApplyTaskSchema, ArtifactInstructionsSchema, ArtifactStatusSchema, CODE_EDITOR_THEME_VALUES, ChangeFileSchema, ChangeSchema, ChangeStatusSchema, CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DEFAULT_CONFIG, DashboardConfigSchema, DeltaOperationType, DeltaSchema, DeltaSpecSchema, DependencyInfoSchema, MarkdownParser, OFFICIAL_APP_BASE_URL, OpenSpecAdapter, OpenSpecUIConfigSchema, OpenSpecWatcher, OpsxKernel, ProjectWatcher, PtyAttachMessageSchema, PtyBufferResponseSchema, PtyClientMessageSchema, PtyCloseMessageSchema, PtyCreateMessageSchema, PtyCreatedResponseSchema, PtyErrorCodeSchema, PtyErrorResponseSchema, PtyExitResponseSchema, PtyInputMessageSchema, PtyListMessageSchema, PtyListResponseSchema, PtyOutputResponseSchema, PtyPlatformSchema, PtyResizeMessageSchema, PtyServerMessageSchema, PtyTitleResponseSchema, ReactiveContext, ReactiveState, RequirementSchema, SchemaArtifactSchema, SchemaDetailSchema, SchemaInfoSchema, SchemaResolutionSchema, SpecSchema, TOOL_WORKFLOW_TO_SKILL_DIR, TaskSchema, TemplatesSchema, TerminalConfigSchema, TerminalRendererEngineSchema, VIRTUAL_PROJECT_DIRNAME, Validator, acquireWatcher, buildCliRunnerCandidates, buildHostedLaunchUrl, buildHostedVersionManifestUrl, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getDetectedProjectTools, getProjectWatcher, getToolById, getToolInitStates, getWatchedProjectDir, getWatcherRuntimeStatus, initWatcherPool, isGlobPattern, isHostedAppVersionManifest, isHostedBackendHealthResponse, isTerminalRendererEngine, isToolConfigured, isWatcherPoolInitialized, normalizeHostedAppBaseUrl, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, resolveHostedAppBaseUrl, resolveHostedChannelForVersion, sniffGlobalCli, toOpsxDisplayPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openspecui/core",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "Core OpenSpec adapter and parser",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",