@openspecui/core 2.1.2 → 2.1.5

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.
@@ -0,0 +1,11 @@
1
+ //#region src/dashboard-display.d.ts
2
+ declare const DASHBOARD_RECENT_LIST_LIMIT = 10;
3
+ interface DashboardRecentListItem {
4
+ id: string;
5
+ updatedAt: number;
6
+ }
7
+ declare function compareDashboardItemsByUpdatedAt<T extends DashboardRecentListItem>(left: T, right: T): number;
8
+ declare function sortDashboardItemsByUpdatedAt<T extends DashboardRecentListItem>(items: readonly T[]): T[];
9
+ declare function selectRecentDashboardItems<T extends DashboardRecentListItem>(items: readonly T[], limit?: number): T[];
10
+ //#endregion
11
+ export { DASHBOARD_RECENT_LIST_LIMIT, DashboardRecentListItem, compareDashboardItemsByUpdatedAt, selectRecentDashboardItems, sortDashboardItemsByUpdatedAt };
@@ -0,0 +1,14 @@
1
+ //#region src/dashboard-display.ts
2
+ const DASHBOARD_RECENT_LIST_LIMIT = 10;
3
+ function compareDashboardItemsByUpdatedAt(left, right) {
4
+ return right.updatedAt - left.updatedAt || left.id.localeCompare(right.id);
5
+ }
6
+ function sortDashboardItemsByUpdatedAt(items) {
7
+ return [...items].sort(compareDashboardItemsByUpdatedAt);
8
+ }
9
+ function selectRecentDashboardItems(items, limit = DASHBOARD_RECENT_LIST_LIMIT) {
10
+ return sortDashboardItemsByUpdatedAt(items).slice(0, Math.max(0, Math.trunc(limit)));
11
+ }
12
+
13
+ //#endregion
14
+ export { DASHBOARD_RECENT_LIST_LIMIT, compareDashboardItemsByUpdatedAt, selectRecentDashboardItems, sortDashboardItemsByUpdatedAt };
@@ -1,2 +1,2 @@
1
- import { a as HostedBackendHealthResponse, c as buildHostedVersionManifestUrl, d as normalizeHostedAppBaseUrl, f as resolveHostedAppBaseUrl, i as HostedAppVersionManifest, l as isHostedAppVersionManifest, n as HostedAppChannelManifest, o as OFFICIAL_APP_BASE_URL, p as resolveHostedChannelForVersion, r as HostedAppCompatibilityEntry, s as buildHostedLaunchUrl, t as HostedAppChannelKind, u as isHostedBackendHealthResponse } from "./hosted-app-CY1XHUcf.mjs";
1
+ import { a as HostedBackendHealthResponse, c as buildHostedVersionManifestUrl, d as normalizeHostedAppBaseUrl, f as resolveHostedAppBaseUrl, i as HostedAppVersionManifest, l as isHostedAppVersionManifest, n as HostedAppChannelManifest, o as OFFICIAL_APP_BASE_URL, p as resolveHostedChannelForVersion, r as HostedAppCompatibilityEntry, s as buildHostedLaunchUrl, t as HostedAppChannelKind, u as isHostedBackendHealthResponse } from "./hosted-app-BP6Xje0B.mjs";
2
2
  export { HostedAppChannelKind, HostedAppChannelManifest, HostedAppCompatibilityEntry, HostedAppVersionManifest, HostedBackendHealthResponse, OFFICIAL_APP_BASE_URL, buildHostedLaunchUrl, buildHostedVersionManifestUrl, isHostedAppVersionManifest, isHostedBackendHealthResponse, normalizeHostedAppBaseUrl, resolveHostedAppBaseUrl, resolveHostedChannelForVersion };
@@ -1,3 +1,3 @@
1
- import { a as isHostedBackendHealthResponse, c as resolveHostedChannelForVersion, i as isHostedAppVersionManifest, n as buildHostedLaunchUrl, o as normalizeHostedAppBaseUrl, r as buildHostedVersionManifestUrl, s as resolveHostedAppBaseUrl, t as OFFICIAL_APP_BASE_URL } from "./hosted-app-Bn-MNDZ0.mjs";
1
+ import { a as isHostedBackendHealthResponse, c as resolveHostedChannelForVersion, i as isHostedAppVersionManifest, n as buildHostedLaunchUrl, o as normalizeHostedAppBaseUrl, r as buildHostedVersionManifestUrl, s as resolveHostedAppBaseUrl, t as OFFICIAL_APP_BASE_URL } from "./hosted-app-C1JDznip.mjs";
2
2
 
3
3
  export { OFFICIAL_APP_BASE_URL, buildHostedLaunchUrl, buildHostedVersionManifestUrl, isHostedAppVersionManifest, isHostedBackendHealthResponse, normalizeHostedAppBaseUrl, resolveHostedAppBaseUrl, resolveHostedChannelForVersion };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as HostedBackendHealthResponse, c as buildHostedVersionManifestUrl, d as normalizeHostedAppBaseUrl, f as resolveHostedAppBaseUrl, i as HostedAppVersionManifest, l as isHostedAppVersionManifest, n as HostedAppChannelManifest, o as OFFICIAL_APP_BASE_URL, p as resolveHostedChannelForVersion, r as HostedAppCompatibilityEntry, s as buildHostedLaunchUrl, t as HostedAppChannelKind, u as isHostedBackendHealthResponse } from "./hosted-app-CY1XHUcf.mjs";
2
- import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-k65bc8bW.mjs";
1
+ import { a as HostedBackendHealthResponse, c as buildHostedVersionManifestUrl, d as normalizeHostedAppBaseUrl, f as resolveHostedAppBaseUrl, i as HostedAppVersionManifest, l as isHostedAppVersionManifest, n as HostedAppChannelManifest, o as OFFICIAL_APP_BASE_URL, p as resolveHostedChannelForVersion, r as HostedAppCompatibilityEntry, s as buildHostedLaunchUrl, t as HostedAppChannelKind, u as isHostedBackendHealthResponse } from "./hosted-app-BP6Xje0B.mjs";
2
+ import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-Cb-CVa6h.mjs";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { z } from "zod";
5
5
  import { EventEmitter } from "events";
@@ -306,11 +306,11 @@ declare const DeltaSpecSchema: z.ZodObject<{
306
306
  /** Raw markdown content of the delta spec */
307
307
  content: z.ZodString;
308
308
  }, "strip", z.ZodTypeAny, {
309
- specId: string;
310
309
  content: string;
311
- }, {
312
310
  specId: string;
311
+ }, {
313
312
  content: string;
313
+ specId: string;
314
314
  }>;
315
315
  type DeltaSpec = z.infer<typeof DeltaSpecSchema>;
316
316
  /**
@@ -488,11 +488,11 @@ declare const ChangeSchema: z.ZodObject<{
488
488
  /** Raw markdown content of the delta spec */
489
489
  content: z.ZodString;
490
490
  }, "strip", z.ZodTypeAny, {
491
- specId: string;
492
491
  content: string;
493
- }, {
494
492
  specId: string;
493
+ }, {
495
494
  content: string;
495
+ specId: string;
496
496
  }>, "many">>;
497
497
  /** Optional metadata */
498
498
  metadata: z.ZodOptional<z.ZodObject<{
@@ -549,8 +549,8 @@ declare const ChangeSchema: z.ZodObject<{
549
549
  } | undefined;
550
550
  design?: string | undefined;
551
551
  deltaSpecs?: {
552
- specId: string;
553
552
  content: string;
553
+ specId: string;
554
554
  }[] | undefined;
555
555
  }, {
556
556
  id: string;
@@ -596,8 +596,8 @@ declare const ChangeSchema: z.ZodObject<{
596
596
  } | undefined;
597
597
  design?: string | undefined;
598
598
  deltaSpecs?: {
599
- specId: string;
600
599
  content: string;
600
+ specId: string;
601
601
  }[] | undefined;
602
602
  }>;
603
603
  type Change = z.infer<typeof ChangeSchema>;
@@ -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>;
@@ -811,8 +803,8 @@ declare class OpenSpecAdapter {
811
803
  } | undefined;
812
804
  design?: string | undefined;
813
805
  deltaSpecs?: {
814
- specId: string;
815
806
  content: string;
807
+ specId: string;
816
808
  }[] | undefined;
817
809
  }[];
818
810
  archivedCount: number;
@@ -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];
@@ -1860,14 +1906,14 @@ declare const ArtifactStatusSchema: z.ZodObject<{
1860
1906
  missingDeps: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1861
1907
  relativePath: z.ZodOptional<z.ZodString>;
1862
1908
  }, "strip", z.ZodTypeAny, {
1863
- id: string;
1864
1909
  status: "done" | "ready" | "blocked";
1910
+ id: string;
1865
1911
  outputPath: string;
1866
1912
  missingDeps?: string[] | undefined;
1867
1913
  relativePath?: string | undefined;
1868
1914
  }, {
1869
- id: string;
1870
1915
  status: "done" | "ready" | "blocked";
1916
+ id: string;
1871
1917
  outputPath: string;
1872
1918
  missingDeps?: string[] | undefined;
1873
1919
  relativePath?: string | undefined;
@@ -1885,14 +1931,14 @@ declare const ChangeStatusSchema: z.ZodObject<{
1885
1931
  missingDeps: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1886
1932
  relativePath: z.ZodOptional<z.ZodString>;
1887
1933
  }, "strip", z.ZodTypeAny, {
1888
- id: string;
1889
1934
  status: "done" | "ready" | "blocked";
1935
+ id: string;
1890
1936
  outputPath: string;
1891
1937
  missingDeps?: string[] | undefined;
1892
1938
  relativePath?: string | undefined;
1893
1939
  }, {
1894
- id: string;
1895
1940
  status: "done" | "ready" | "blocked";
1941
+ id: string;
1896
1942
  outputPath: string;
1897
1943
  missingDeps?: string[] | undefined;
1898
1944
  relativePath?: string | undefined;
@@ -1903,8 +1949,8 @@ declare const ChangeStatusSchema: z.ZodObject<{
1903
1949
  isComplete: boolean;
1904
1950
  applyRequires: string[];
1905
1951
  artifacts: {
1906
- id: string;
1907
1952
  status: "done" | "ready" | "blocked";
1953
+ id: string;
1908
1954
  outputPath: string;
1909
1955
  missingDeps?: string[] | undefined;
1910
1956
  relativePath?: string | undefined;
@@ -1915,8 +1961,8 @@ declare const ChangeStatusSchema: z.ZodObject<{
1915
1961
  isComplete: boolean;
1916
1962
  applyRequires: string[];
1917
1963
  artifacts: {
1918
- id: string;
1919
1964
  status: "done" | "ready" | "blocked";
1965
+ id: string;
1920
1966
  outputPath: string;
1921
1967
  missingDeps?: string[] | undefined;
1922
1968
  relativePath?: string | undefined;
@@ -1929,13 +1975,13 @@ declare const DependencyInfoSchema: z.ZodObject<{
1929
1975
  path: z.ZodString;
1930
1976
  description: z.ZodString;
1931
1977
  }, "strip", z.ZodTypeAny, {
1932
- id: string;
1933
1978
  path: string;
1979
+ id: string;
1934
1980
  description: string;
1935
1981
  done: boolean;
1936
1982
  }, {
1937
- id: string;
1938
1983
  path: string;
1984
+ id: string;
1939
1985
  description: string;
1940
1986
  done: boolean;
1941
1987
  }>;
@@ -2043,13 +2089,13 @@ declare const ArtifactInstructionsSchema: z.ZodObject<{
2043
2089
  path: z.ZodString;
2044
2090
  description: z.ZodString;
2045
2091
  }, "strip", z.ZodTypeAny, {
2046
- id: string;
2047
2092
  path: string;
2093
+ id: string;
2048
2094
  description: string;
2049
2095
  done: boolean;
2050
2096
  }, {
2051
- id: string;
2052
2097
  path: string;
2098
+ id: string;
2053
2099
  description: string;
2054
2100
  done: boolean;
2055
2101
  }>, "many">;
@@ -2063,8 +2109,8 @@ declare const ArtifactInstructionsSchema: z.ZodObject<{
2063
2109
  artifactId: string;
2064
2110
  template: string;
2065
2111
  dependencies: {
2066
- id: string;
2067
2112
  path: string;
2113
+ id: string;
2068
2114
  description: string;
2069
2115
  done: boolean;
2070
2116
  }[];
@@ -2081,8 +2127,8 @@ declare const ArtifactInstructionsSchema: z.ZodObject<{
2081
2127
  artifactId: string;
2082
2128
  template: string;
2083
2129
  dependencies: {
2084
- id: string;
2085
2130
  path: string;
2131
+ id: string;
2086
2132
  description: string;
2087
2133
  done: boolean;
2088
2134
  }[];
@@ -2128,8 +2174,8 @@ declare const SchemaResolutionSchema: z.ZodObject<{
2128
2174
  displayPath?: string | undefined;
2129
2175
  }>, "many">;
2130
2176
  }, "strip", z.ZodTypeAny, {
2131
- name: string;
2132
2177
  path: string;
2178
+ name: string;
2133
2179
  source: "project" | "user" | "package";
2134
2180
  shadows: {
2135
2181
  path: string;
@@ -2138,8 +2184,8 @@ declare const SchemaResolutionSchema: z.ZodObject<{
2138
2184
  }[];
2139
2185
  displayPath?: string | undefined;
2140
2186
  }, {
2141
- name: string;
2142
2187
  path: string;
2188
+ name: string;
2143
2189
  source: "project" | "user" | "package";
2144
2190
  shadows: {
2145
2191
  path: string;
@@ -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;
@@ -2809,14 +2853,14 @@ declare const PtyErrorResponseSchema: z.ZodObject<{
2809
2853
  message: z.ZodString;
2810
2854
  sessionId: z.ZodOptional<z.ZodString>;
2811
2855
  }, "strip", z.ZodTypeAny, {
2856
+ type: "error";
2812
2857
  code: "INVALID_JSON" | "INVALID_MESSAGE" | "SESSION_NOT_FOUND" | "PTY_CREATE_FAILED";
2813
2858
  message: string;
2814
- type: "error";
2815
2859
  sessionId?: string | undefined;
2816
2860
  }, {
2861
+ type: "error";
2817
2862
  code: "INVALID_JSON" | "INVALID_MESSAGE" | "SESSION_NOT_FOUND" | "PTY_CREATE_FAILED";
2818
2863
  message: string;
2819
- type: "error";
2820
2864
  sessionId?: string | undefined;
2821
2865
  }>;
2822
2866
  declare const PtyServerMessageSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
@@ -2947,14 +2991,14 @@ declare const PtyServerMessageSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObje
2947
2991
  message: z.ZodString;
2948
2992
  sessionId: z.ZodOptional<z.ZodString>;
2949
2993
  }, "strip", z.ZodTypeAny, {
2994
+ type: "error";
2950
2995
  code: "INVALID_JSON" | "INVALID_MESSAGE" | "SESSION_NOT_FOUND" | "PTY_CREATE_FAILED";
2951
2996
  message: string;
2952
- type: "error";
2953
2997
  sessionId?: string | undefined;
2954
2998
  }, {
2999
+ type: "error";
2955
3000
  code: "INVALID_JSON" | "INVALID_MESSAGE" | "SESSION_NOT_FOUND" | "PTY_CREATE_FAILED";
2956
3001
  message: string;
2957
- type: "error";
2958
3002
  sessionId?: string | undefined;
2959
3003
  }>]>;
2960
3004
  type PtyClientMessage = z.infer<typeof PtyClientMessageSchema>;
@@ -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
@@ -1,5 +1,5 @@
1
- import { a as isHostedBackendHealthResponse, c as resolveHostedChannelForVersion, i as isHostedAppVersionManifest, n as buildHostedLaunchUrl, o as normalizeHostedAppBaseUrl, r as buildHostedVersionManifestUrl, s as resolveHostedAppBaseUrl, t as OFFICIAL_APP_BASE_URL } from "./hosted-app-Bn-MNDZ0.mjs";
2
- import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-D-YbNUvQ.mjs";
1
+ import { a as isHostedBackendHealthResponse, c as resolveHostedChannelForVersion, i as isHostedAppVersionManifest, n as buildHostedLaunchUrl, o as normalizeHostedAppBaseUrl, r as buildHostedVersionManifestUrl, s as resolveHostedAppBaseUrl, t as OFFICIAL_APP_BASE_URL } from "./hosted-app-C1JDznip.mjs";
2
+ import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-DDK7QA5y.mjs";
3
3
  import { mkdir, readFile, rename, writeFile } from "fs/promises";
4
4
  import { dirname, join } from "path";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -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 };
@@ -1,2 +1,2 @@
1
- import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-k65bc8bW.mjs";
1
+ import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-Cb-CVa6h.mjs";
2
2
  export { VIRTUAL_PROJECT_DIRNAME, toOpsxDisplayPath };
@@ -1,3 +1,3 @@
1
- import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-D-YbNUvQ.mjs";
1
+ import { n as toOpsxDisplayPath, t as VIRTUAL_PROJECT_DIRNAME } from "./opsx-display-path-DDK7QA5y.mjs";
2
2
 
3
3
  export { VIRTUAL_PROJECT_DIRNAME, 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.5",
4
4
  "description": "Core OpenSpec adapter and parser",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -10,6 +10,10 @@
10
10
  "import": "./dist/index.mjs",
11
11
  "types": "./dist/index.d.mts"
12
12
  },
13
+ "./dashboard-display": {
14
+ "import": "./dist/dashboard-display.mjs",
15
+ "types": "./dist/dashboard-display.d.mts"
16
+ },
13
17
  "./hosted-app": {
14
18
  "import": "./dist/hosted-app.mjs",
15
19
  "types": "./dist/hosted-app.d.mts"
@@ -23,8 +27,8 @@
23
27
  "dist"
24
28
  ],
25
29
  "scripts": {
26
- "build": "tsdown src/index.ts src/opsx-display-path.ts src/hosted-app.ts --format esm --dts",
27
- "dev": "tsdown src/index.ts src/opsx-display-path.ts src/hosted-app.ts --format esm --dts --watch",
30
+ "build": "tsdown src/index.ts src/dashboard-display.ts src/opsx-display-path.ts src/hosted-app.ts --format esm --dts",
31
+ "dev": "tsdown src/index.ts src/dashboard-display.ts src/opsx-display-path.ts src/hosted-app.ts --format esm --dts --watch",
28
32
  "test": "vitest run",
29
33
  "test:watch": "vitest",
30
34
  "typecheck": "tsc --noEmit"