@openspecui/core 1.0.0 → 1.0.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 +127 -57
- package/dist/index.mjs +529 -154
- package/package.json +2 -1
package/dist/index.d.mts
CHANGED
|
@@ -1284,16 +1284,40 @@ declare function createFileChangeObservable(watcher: OpenSpecWatcher): {
|
|
|
1284
1284
|
};
|
|
1285
1285
|
//#endregion
|
|
1286
1286
|
//#region src/config.d.ts
|
|
1287
|
+
type RunnerId = 'configured' | 'openspec' | 'npx' | 'bunx' | 'deno' | 'pnpm' | 'yarn';
|
|
1288
|
+
interface CliRunnerCandidate {
|
|
1289
|
+
id: RunnerId;
|
|
1290
|
+
source: string;
|
|
1291
|
+
commandParts: readonly string[];
|
|
1292
|
+
}
|
|
1293
|
+
interface CliRunnerAttempt {
|
|
1294
|
+
source: string;
|
|
1295
|
+
command: string;
|
|
1296
|
+
success: boolean;
|
|
1297
|
+
version?: string;
|
|
1298
|
+
error?: string;
|
|
1299
|
+
exitCode: number | null;
|
|
1300
|
+
}
|
|
1301
|
+
interface ResolvedCliRunner {
|
|
1302
|
+
source: string;
|
|
1303
|
+
command: string;
|
|
1304
|
+
commandParts: readonly string[];
|
|
1305
|
+
version?: string;
|
|
1306
|
+
attempts: readonly CliRunnerAttempt[];
|
|
1307
|
+
}
|
|
1287
1308
|
/**
|
|
1288
1309
|
* 解析 CLI 命令字符串为数组
|
|
1289
1310
|
*
|
|
1290
1311
|
* 支持两种格式:
|
|
1291
1312
|
* 1. JSON 数组:以 `[` 开头,如 `["npx", "@fission-ai/openspec"]`
|
|
1292
|
-
* 2.
|
|
1293
|
-
*
|
|
1294
|
-
* 注意:简单字符串解析不支持带引号的参数,如需复杂命令请使用 JSON 数组格式
|
|
1313
|
+
* 2. shell-like 字符串:支持引号与基础转义
|
|
1295
1314
|
*/
|
|
1296
1315
|
declare function parseCliCommand(command: string): string[];
|
|
1316
|
+
declare function buildCliRunnerCandidates(options: {
|
|
1317
|
+
configuredCommandParts?: readonly string[];
|
|
1318
|
+
userAgent?: string | null;
|
|
1319
|
+
}): readonly CliRunnerCandidate[];
|
|
1320
|
+
declare function createCleanCliEnv(baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
1297
1321
|
/** CLI 嗅探结果 */
|
|
1298
1322
|
interface CliSniffResult {
|
|
1299
1323
|
/** 是否存在全局 openspec 命令 */
|
|
@@ -1325,6 +1349,26 @@ declare function getDefaultCliCommand(): Promise<readonly string[]>;
|
|
|
1325
1349
|
* 获取默认 CLI 命令的字符串形式(用于 UI 显示)
|
|
1326
1350
|
*/
|
|
1327
1351
|
declare function getDefaultCliCommandString(): Promise<string>;
|
|
1352
|
+
declare const TerminalConfigSchema: z.ZodObject<{
|
|
1353
|
+
fontSize: z.ZodDefault<z.ZodNumber>;
|
|
1354
|
+
fontFamily: z.ZodDefault<z.ZodString>;
|
|
1355
|
+
cursorBlink: z.ZodDefault<z.ZodBoolean>;
|
|
1356
|
+
cursorStyle: z.ZodDefault<z.ZodEnum<["block", "underline", "bar"]>>;
|
|
1357
|
+
scrollback: z.ZodDefault<z.ZodNumber>;
|
|
1358
|
+
}, "strip", z.ZodTypeAny, {
|
|
1359
|
+
fontSize: number;
|
|
1360
|
+
fontFamily: string;
|
|
1361
|
+
cursorBlink: boolean;
|
|
1362
|
+
cursorStyle: "block" | "underline" | "bar";
|
|
1363
|
+
scrollback: number;
|
|
1364
|
+
}, {
|
|
1365
|
+
fontSize?: number | undefined;
|
|
1366
|
+
fontFamily?: string | undefined;
|
|
1367
|
+
cursorBlink?: boolean | undefined;
|
|
1368
|
+
cursorStyle?: "block" | "underline" | "bar" | undefined;
|
|
1369
|
+
scrollback?: number | undefined;
|
|
1370
|
+
}>;
|
|
1371
|
+
type TerminalConfig = z.infer<typeof TerminalConfigSchema>;
|
|
1328
1372
|
/**
|
|
1329
1373
|
* OpenSpecUI 配置 Schema
|
|
1330
1374
|
*
|
|
@@ -1335,36 +1379,73 @@ declare const OpenSpecUIConfigSchema: z.ZodObject<{
|
|
|
1335
1379
|
cli: z.ZodDefault<z.ZodObject<{
|
|
1336
1380
|
/** CLI 命令前缀 */
|
|
1337
1381
|
command: z.ZodOptional<z.ZodString>;
|
|
1382
|
+
/** CLI 命令参数 */
|
|
1383
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
1338
1384
|
}, "strip", z.ZodTypeAny, {
|
|
1339
1385
|
command?: string | undefined;
|
|
1386
|
+
args?: string[] | undefined;
|
|
1340
1387
|
}, {
|
|
1341
1388
|
command?: string | undefined;
|
|
1389
|
+
args?: string[] | undefined;
|
|
1342
1390
|
}>>;
|
|
1343
|
-
/**
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1391
|
+
/** 主题 */
|
|
1392
|
+
theme: z.ZodDefault<z.ZodEnum<["light", "dark", "system"]>>;
|
|
1393
|
+
/** 终端配置 */
|
|
1394
|
+
terminal: z.ZodDefault<z.ZodObject<{
|
|
1395
|
+
fontSize: z.ZodDefault<z.ZodNumber>;
|
|
1396
|
+
fontFamily: z.ZodDefault<z.ZodString>;
|
|
1397
|
+
cursorBlink: z.ZodDefault<z.ZodBoolean>;
|
|
1398
|
+
cursorStyle: z.ZodDefault<z.ZodEnum<["block", "underline", "bar"]>>;
|
|
1399
|
+
scrollback: z.ZodDefault<z.ZodNumber>;
|
|
1347
1400
|
}, "strip", z.ZodTypeAny, {
|
|
1348
|
-
|
|
1401
|
+
fontSize: number;
|
|
1402
|
+
fontFamily: string;
|
|
1403
|
+
cursorBlink: boolean;
|
|
1404
|
+
cursorStyle: "block" | "underline" | "bar";
|
|
1405
|
+
scrollback: number;
|
|
1349
1406
|
}, {
|
|
1350
|
-
|
|
1407
|
+
fontSize?: number | undefined;
|
|
1408
|
+
fontFamily?: string | undefined;
|
|
1409
|
+
cursorBlink?: boolean | undefined;
|
|
1410
|
+
cursorStyle?: "block" | "underline" | "bar" | undefined;
|
|
1411
|
+
scrollback?: number | undefined;
|
|
1351
1412
|
}>>;
|
|
1352
1413
|
}, "strip", z.ZodTypeAny, {
|
|
1353
1414
|
cli: {
|
|
1354
1415
|
command?: string | undefined;
|
|
1416
|
+
args?: string[] | undefined;
|
|
1355
1417
|
};
|
|
1356
|
-
|
|
1357
|
-
|
|
1418
|
+
theme: "light" | "dark" | "system";
|
|
1419
|
+
terminal: {
|
|
1420
|
+
fontSize: number;
|
|
1421
|
+
fontFamily: string;
|
|
1422
|
+
cursorBlink: boolean;
|
|
1423
|
+
cursorStyle: "block" | "underline" | "bar";
|
|
1424
|
+
scrollback: number;
|
|
1358
1425
|
};
|
|
1359
1426
|
}, {
|
|
1360
1427
|
cli?: {
|
|
1361
1428
|
command?: string | undefined;
|
|
1429
|
+
args?: string[] | undefined;
|
|
1362
1430
|
} | undefined;
|
|
1363
|
-
|
|
1364
|
-
|
|
1431
|
+
theme?: "light" | "dark" | "system" | undefined;
|
|
1432
|
+
terminal?: {
|
|
1433
|
+
fontSize?: number | undefined;
|
|
1434
|
+
fontFamily?: string | undefined;
|
|
1435
|
+
cursorBlink?: boolean | undefined;
|
|
1436
|
+
cursorStyle?: "block" | "underline" | "bar" | undefined;
|
|
1437
|
+
scrollback?: number | undefined;
|
|
1365
1438
|
} | undefined;
|
|
1366
1439
|
}>;
|
|
1367
1440
|
type OpenSpecUIConfig = z.infer<typeof OpenSpecUIConfigSchema>;
|
|
1441
|
+
type OpenSpecUIConfigUpdate = {
|
|
1442
|
+
cli?: {
|
|
1443
|
+
command?: string | null;
|
|
1444
|
+
args?: string[] | null;
|
|
1445
|
+
};
|
|
1446
|
+
theme?: OpenSpecUIConfig['theme'];
|
|
1447
|
+
terminal?: Partial<TerminalConfig>;
|
|
1448
|
+
};
|
|
1368
1449
|
/** 默认配置(静态,用于测试和类型) */
|
|
1369
1450
|
declare const DEFAULT_CONFIG: OpenSpecUIConfig;
|
|
1370
1451
|
/**
|
|
@@ -1375,6 +1456,9 @@ declare const DEFAULT_CONFIG: OpenSpecUIConfig;
|
|
|
1375
1456
|
*/
|
|
1376
1457
|
declare class ConfigManager {
|
|
1377
1458
|
private configPath;
|
|
1459
|
+
private projectDir;
|
|
1460
|
+
private resolvedRunner;
|
|
1461
|
+
private resolvingRunnerPromise;
|
|
1378
1462
|
constructor(projectDir: string);
|
|
1379
1463
|
/**
|
|
1380
1464
|
* 读取配置(响应式)
|
|
@@ -1388,23 +1472,41 @@ declare class ConfigManager {
|
|
|
1388
1472
|
*
|
|
1389
1473
|
* 会触发文件监听,自动更新订阅者。
|
|
1390
1474
|
*/
|
|
1391
|
-
writeConfig(config:
|
|
1475
|
+
writeConfig(config: OpenSpecUIConfigUpdate): Promise<void>;
|
|
1476
|
+
/**
|
|
1477
|
+
* 解析并缓存可用 CLI runner。
|
|
1478
|
+
*/
|
|
1479
|
+
private resolveCliRunner;
|
|
1480
|
+
private resolveCliRunnerUncached;
|
|
1392
1481
|
/**
|
|
1393
1482
|
* 获取 CLI 命令(数组形式)
|
|
1394
|
-
*
|
|
1395
|
-
* 优先级:配置文件 > 全局 openspec 命令 > npx fallback
|
|
1396
|
-
*
|
|
1397
|
-
* @returns CLI 命令数组,如 `['openspec']` 或 `['npx', '@fission-ai/openspec']`
|
|
1398
1483
|
*/
|
|
1399
1484
|
getCliCommand(): Promise<readonly string[]>;
|
|
1400
1485
|
/**
|
|
1401
1486
|
* 获取 CLI 命令的字符串形式(用于 UI 显示)
|
|
1402
1487
|
*/
|
|
1403
1488
|
getCliCommandString(): Promise<string>;
|
|
1489
|
+
/**
|
|
1490
|
+
* 获取 CLI 解析结果(用于诊断)
|
|
1491
|
+
*/
|
|
1492
|
+
getResolvedCliRunner(): Promise<ResolvedCliRunner>;
|
|
1493
|
+
/**
|
|
1494
|
+
* 清理 CLI 解析缓存(用于 ENOENT 自愈)
|
|
1495
|
+
*/
|
|
1496
|
+
invalidateResolvedCliRunner(): void;
|
|
1404
1497
|
/**
|
|
1405
1498
|
* 设置 CLI 命令
|
|
1406
1499
|
*/
|
|
1407
1500
|
setCliCommand(command: string): Promise<void>;
|
|
1501
|
+
private getConfiguredCommandParts;
|
|
1502
|
+
/**
|
|
1503
|
+
* 设置主题
|
|
1504
|
+
*/
|
|
1505
|
+
setTheme(theme: OpenSpecUIConfig['theme']): Promise<void>;
|
|
1506
|
+
/**
|
|
1507
|
+
* 设置终端配置(部分更新)
|
|
1508
|
+
*/
|
|
1509
|
+
setTerminalConfig(terminal: Partial<TerminalConfig>): Promise<void>;
|
|
1408
1510
|
}
|
|
1409
1511
|
//#endregion
|
|
1410
1512
|
//#region src/cli-executor.d.ts
|
|
@@ -1424,48 +1526,26 @@ interface CliStreamEvent {
|
|
|
1424
1526
|
/**
|
|
1425
1527
|
* CLI 执行器
|
|
1426
1528
|
*
|
|
1427
|
-
* 负责调用外部 openspec CLI
|
|
1428
|
-
*
|
|
1429
|
-
* - ['npx', '@fission-ai/openspec'] (默认)
|
|
1430
|
-
* - ['openspec'] (全局安装)
|
|
1431
|
-
* - 自定义数组或字符串
|
|
1432
|
-
*
|
|
1433
|
-
* 注意:所有命令都使用 shell: false 执行,避免 shell 注入风险
|
|
1529
|
+
* 负责调用外部 openspec CLI 命令,统一通过 ConfigManager 的 runner 解析结果执行。
|
|
1530
|
+
* 所有命令都使用 shell: false,避免 shell 注入风险。
|
|
1434
1531
|
*/
|
|
1435
1532
|
declare class CliExecutor {
|
|
1436
1533
|
private configManager;
|
|
1437
1534
|
private projectDir;
|
|
1438
1535
|
constructor(configManager: ConfigManager, projectDir: string);
|
|
1439
|
-
/**
|
|
1440
|
-
* 创建干净的环境变量,移除 pnpm 特有的配置
|
|
1441
|
-
* 避免 pnpm 环境变量污染 npx/npm 执行
|
|
1442
|
-
*/
|
|
1443
|
-
private getCleanEnv;
|
|
1444
|
-
/**
|
|
1445
|
-
* 构建完整命令数组
|
|
1446
|
-
*
|
|
1447
|
-
* @param args CLI 参数,如 ['init'] 或 ['archive', 'change-id']
|
|
1448
|
-
* @returns [command, ...commandArgs, ...args]
|
|
1449
|
-
*/
|
|
1450
1536
|
private buildCommandArray;
|
|
1537
|
+
private runCommandOnce;
|
|
1538
|
+
private executeInternal;
|
|
1451
1539
|
/**
|
|
1452
1540
|
* 执行 CLI 命令
|
|
1453
|
-
*
|
|
1454
|
-
* @param args CLI 参数,如 ['init'] 或 ['archive', 'change-id']
|
|
1455
|
-
* @returns 执行结果
|
|
1456
1541
|
*/
|
|
1457
1542
|
execute(args: string[]): Promise<CliResult>;
|
|
1458
1543
|
/**
|
|
1459
1544
|
* 执行 openspec init(非交互式)
|
|
1460
|
-
*
|
|
1461
|
-
* @param tools 工具列表,如 ['claude', 'cursor'] 或 'all' 或 'none'
|
|
1462
1545
|
*/
|
|
1463
1546
|
init(tools?: string[] | 'all' | 'none'): Promise<CliResult>;
|
|
1464
1547
|
/**
|
|
1465
1548
|
* 执行 openspec archive <changeId>(非交互式)
|
|
1466
|
-
*
|
|
1467
|
-
* @param changeId 要归档的 change ID
|
|
1468
|
-
* @param options 选项
|
|
1469
1549
|
*/
|
|
1470
1550
|
archive(changeId: string, options?: {
|
|
1471
1551
|
skipSpecs?: boolean;
|
|
@@ -1493,19 +1573,16 @@ declare class CliExecutor {
|
|
|
1493
1573
|
validateStream(type: 'spec' | 'change' | undefined, id: string | undefined, onEvent: (event: CliStreamEvent) => void): Promise<() => void>;
|
|
1494
1574
|
/**
|
|
1495
1575
|
* 检查 CLI 是否可用
|
|
1496
|
-
* @param timeout 超时时间(毫秒),默认 10 秒
|
|
1497
1576
|
*/
|
|
1498
1577
|
checkAvailability(timeout?: number): Promise<{
|
|
1499
1578
|
available: boolean;
|
|
1500
1579
|
version?: string;
|
|
1501
1580
|
error?: string;
|
|
1581
|
+
effectiveCommand?: string;
|
|
1582
|
+
tried?: string[];
|
|
1502
1583
|
}>;
|
|
1503
1584
|
/**
|
|
1504
1585
|
* 流式执行 CLI 命令
|
|
1505
|
-
*
|
|
1506
|
-
* @param args CLI 参数
|
|
1507
|
-
* @param onEvent 事件回调
|
|
1508
|
-
* @returns 取消函数
|
|
1509
1586
|
*/
|
|
1510
1587
|
executeStream(args: string[], onEvent: (event: CliStreamEvent) => void): Promise<() => void>;
|
|
1511
1588
|
/**
|
|
@@ -1521,13 +1598,6 @@ declare class CliExecutor {
|
|
|
1521
1598
|
}, onEvent: (event: CliStreamEvent) => void): Promise<() => void>;
|
|
1522
1599
|
/**
|
|
1523
1600
|
* 流式执行任意命令(数组形式)
|
|
1524
|
-
*
|
|
1525
|
-
* 用于执行不需要 openspec CLI 前缀的命令,如 npm install。
|
|
1526
|
-
* 使用 shell: false 避免 shell 注入风险。
|
|
1527
|
-
*
|
|
1528
|
-
* @param command 命令数组,如 ['npm', 'install', '-g', '@fission-ai/openspec']
|
|
1529
|
-
* @param onEvent 事件回调
|
|
1530
|
-
* @returns 取消函数
|
|
1531
1601
|
*/
|
|
1532
1602
|
executeCommandStream(command: readonly string[], onEvent: (event: CliStreamEvent) => void): () => void;
|
|
1533
1603
|
}
|
|
@@ -2621,4 +2691,4 @@ type PtyServerMessage = z.infer<typeof PtyServerMessageSchema>;
|
|
|
2621
2691
|
type PtySessionInfo = z.infer<typeof PtySessionInfoSchema>;
|
|
2622
2692
|
type PtyPlatform = z.infer<typeof PtyPlatformSchema>;
|
|
2623
2693
|
//#endregion
|
|
2624
|
-
export { type AIToolOption, AI_TOOLS, type ApplyInstructions, ApplyInstructionsSchema, type ApplyTask, ApplyTaskSchema, type ArchiveMeta, type ArtifactInstructions, ArtifactInstructionsSchema, type ArtifactStatus, ArtifactStatusSchema, type Change, type ChangeFile, ChangeFileSchema, type ChangeMeta, ChangeSchema, type ChangeStatus, ChangeStatusSchema, CliExecutor, type CliResult, type CliSniffResult, type CliStreamEvent, ConfigManager, DEFAULT_CONFIG, type Delta, type DeltaOperation, DeltaOperationType, DeltaSchema, type DeltaSpec, DeltaSpecSchema, type DependencyInfo, DependencyInfoSchema, type ExportSnapshot, type FileChangeEvent, type FileChangeType, MarkdownParser, OpenSpecAdapter, type OpenSpecUIConfig, OpenSpecUIConfigSchema, OpenSpecWatcher, OpsxKernel, type PathCallback, ProjectWatcher, 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 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 ToolConfig, type ValidationIssue, type ValidationResult, Validator, type WatchEvent, type WatchEventType, acquireWatcher, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, initWatcherPool, isGlobPattern, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };
|
|
2694
|
+
export { type AIToolOption, AI_TOOLS, type ApplyInstructions, ApplyInstructionsSchema, type ApplyTask, ApplyTaskSchema, type ArchiveMeta, type ArtifactInstructions, ArtifactInstructionsSchema, type ArtifactStatus, ArtifactStatusSchema, type Change, type ChangeFile, ChangeFileSchema, type ChangeMeta, ChangeSchema, type ChangeStatus, ChangeStatusSchema, CliExecutor, type CliResult, type CliRunnerAttempt, type CliSniffResult, type CliStreamEvent, ConfigManager, DEFAULT_CONFIG, type Delta, type DeltaOperation, DeltaOperationType, DeltaSchema, type DeltaSpec, DeltaSpecSchema, type DependencyInfo, DependencyInfoSchema, type ExportSnapshot, type FileChangeEvent, type FileChangeType, MarkdownParser, OpenSpecAdapter, type OpenSpecUIConfig, OpenSpecUIConfigSchema, type OpenSpecUIConfigUpdate, OpenSpecWatcher, OpsxKernel, type PathCallback, ProjectWatcher, 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 ToolConfig, type ValidationIssue, type ValidationResult, Validator, type WatchEvent, type WatchEventType, acquireWatcher, buildCliRunnerCandidates, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, initWatcherPool, isGlobPattern, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { readFile as readFile$1, readdir, stat } from "node:fs/promises";
|
|
5
|
-
import { dirname, join as join$1, matchesGlob, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { dirname as dirname$1, join as join$1, matchesGlob, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { existsSync, realpathSync, utimesSync } from "node:fs";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { watch } from "fs";
|
|
@@ -704,7 +704,7 @@ var ProjectWatcher = class {
|
|
|
704
704
|
const eventPath = event.path;
|
|
705
705
|
if (sub.watchChildren) return eventPath.startsWith(sub.path + "/") || eventPath === sub.path;
|
|
706
706
|
else {
|
|
707
|
-
const eventDir = dirname(eventPath);
|
|
707
|
+
const eventDir = dirname$1(eventPath);
|
|
708
708
|
return eventPath === sub.path || eventDir === sub.path;
|
|
709
709
|
}
|
|
710
710
|
}
|
|
@@ -1065,7 +1065,7 @@ async function reactiveReadFile(filepath) {
|
|
|
1065
1065
|
if (!state) {
|
|
1066
1066
|
state = new ReactiveState(await getValue());
|
|
1067
1067
|
stateCache$1.set(key, state);
|
|
1068
|
-
const release = acquireWatcher(dirname(normalizedPath), async () => {
|
|
1068
|
+
const release = acquireWatcher(dirname$1(normalizedPath), async () => {
|
|
1069
1069
|
const newValue = await getValue();
|
|
1070
1070
|
state.set(newValue);
|
|
1071
1071
|
}, { onError: () => {
|
|
@@ -1077,6 +1077,15 @@ async function reactiveReadFile(filepath) {
|
|
|
1077
1077
|
return state.get();
|
|
1078
1078
|
}
|
|
1079
1079
|
/**
|
|
1080
|
+
* 主动更新响应式文件缓存(用于写入后立即推送订阅)
|
|
1081
|
+
*
|
|
1082
|
+
* 仅当该文件已有缓存状态时生效;不会创建新的监听器。
|
|
1083
|
+
*/
|
|
1084
|
+
function updateReactiveFileCache(filepath, content) {
|
|
1085
|
+
const key = `file:${resolve(filepath)}`;
|
|
1086
|
+
stateCache$1.get(key)?.set(content);
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1080
1089
|
* 响应式读取目录内容
|
|
1081
1090
|
*
|
|
1082
1091
|
* 特性:
|
|
@@ -1144,7 +1153,7 @@ async function reactiveExists(path) {
|
|
|
1144
1153
|
if (!state) {
|
|
1145
1154
|
state = new ReactiveState(await getValue());
|
|
1146
1155
|
stateCache$1.set(key, state);
|
|
1147
|
-
const release = acquireWatcher(dirname(normalizedPath), async () => {
|
|
1156
|
+
const release = acquireWatcher(dirname$1(normalizedPath), async () => {
|
|
1148
1157
|
const newValue = await getValue();
|
|
1149
1158
|
state.set(newValue);
|
|
1150
1159
|
}, { onError: () => {
|
|
@@ -1185,7 +1194,7 @@ async function reactiveStat(path) {
|
|
|
1185
1194
|
return a.isDirectory === b.isDirectory && a.isFile === b.isFile && a.mtime === b.mtime && a.birthtime === b.birthtime;
|
|
1186
1195
|
} });
|
|
1187
1196
|
stateCache$1.set(key, state);
|
|
1188
|
-
const release = acquireWatcher(dirname(normalizedPath), async () => {
|
|
1197
|
+
const release = acquireWatcher(dirname$1(normalizedPath), async () => {
|
|
1189
1198
|
const newValue = await getValue();
|
|
1190
1199
|
state.set(newValue);
|
|
1191
1200
|
}, { onError: () => {
|
|
@@ -1870,20 +1879,124 @@ function createFileChangeObservable(watcher) {
|
|
|
1870
1879
|
//#endregion
|
|
1871
1880
|
//#region src/config.ts
|
|
1872
1881
|
const execAsync = promisify(exec);
|
|
1873
|
-
|
|
1874
|
-
const
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1882
|
+
const CLI_PROBE_TIMEOUT_MS = 2e4;
|
|
1883
|
+
const THEME_VALUES = [
|
|
1884
|
+
"light",
|
|
1885
|
+
"dark",
|
|
1886
|
+
"system"
|
|
1887
|
+
];
|
|
1888
|
+
const CURSOR_STYLE_VALUES = [
|
|
1889
|
+
"block",
|
|
1890
|
+
"underline",
|
|
1891
|
+
"bar"
|
|
1892
|
+
];
|
|
1893
|
+
const BASE_PACKAGE_MANAGER_RUNNERS = [
|
|
1894
|
+
{
|
|
1895
|
+
id: "npx",
|
|
1896
|
+
source: "npx",
|
|
1897
|
+
commandParts: [
|
|
1898
|
+
"npx",
|
|
1899
|
+
"-y",
|
|
1900
|
+
"@fission-ai/openspec"
|
|
1901
|
+
]
|
|
1902
|
+
},
|
|
1903
|
+
{
|
|
1904
|
+
id: "bunx",
|
|
1905
|
+
source: "bunx",
|
|
1906
|
+
commandParts: ["bunx", "@fission-ai/openspec"]
|
|
1907
|
+
},
|
|
1908
|
+
{
|
|
1909
|
+
id: "deno",
|
|
1910
|
+
source: "deno",
|
|
1911
|
+
commandParts: [
|
|
1912
|
+
"deno",
|
|
1913
|
+
"run",
|
|
1914
|
+
"-A",
|
|
1915
|
+
"npm:@fission-ai/openspec"
|
|
1916
|
+
]
|
|
1917
|
+
},
|
|
1918
|
+
{
|
|
1919
|
+
id: "pnpm",
|
|
1920
|
+
source: "pnpm",
|
|
1921
|
+
commandParts: [
|
|
1922
|
+
"pnpm",
|
|
1923
|
+
"dlx",
|
|
1924
|
+
"@fission-ai/openspec"
|
|
1925
|
+
]
|
|
1926
|
+
},
|
|
1927
|
+
{
|
|
1928
|
+
id: "yarn",
|
|
1929
|
+
source: "yarn",
|
|
1930
|
+
commandParts: [
|
|
1931
|
+
"yarn",
|
|
1932
|
+
"dlx",
|
|
1933
|
+
"@fission-ai/openspec"
|
|
1934
|
+
]
|
|
1935
|
+
}
|
|
1936
|
+
];
|
|
1937
|
+
function tokenizeCliCommand(input) {
|
|
1938
|
+
const tokens = [];
|
|
1939
|
+
let current = "";
|
|
1940
|
+
let quote = null;
|
|
1941
|
+
let tokenStarted = false;
|
|
1942
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
1943
|
+
const char = input[index];
|
|
1944
|
+
if (quote) {
|
|
1945
|
+
if (char === quote) {
|
|
1946
|
+
quote = null;
|
|
1947
|
+
tokenStarted = true;
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
if (char === "\\") {
|
|
1951
|
+
const next = input[index + 1];
|
|
1952
|
+
if (next && (next === quote || next === "\\")) {
|
|
1953
|
+
current += next;
|
|
1954
|
+
tokenStarted = true;
|
|
1955
|
+
index += 1;
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
current += char;
|
|
1960
|
+
tokenStarted = true;
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
if (char === "\"" || char === "'") {
|
|
1964
|
+
quote = char;
|
|
1965
|
+
tokenStarted = true;
|
|
1966
|
+
continue;
|
|
1967
|
+
}
|
|
1968
|
+
if (char === "\\") {
|
|
1969
|
+
const next = input[index + 1];
|
|
1970
|
+
if (next && /[\s"'\\]/.test(next)) {
|
|
1971
|
+
current += next;
|
|
1972
|
+
tokenStarted = true;
|
|
1973
|
+
index += 1;
|
|
1974
|
+
continue;
|
|
1975
|
+
}
|
|
1976
|
+
current += char;
|
|
1977
|
+
tokenStarted = true;
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
if (/\s/.test(char)) {
|
|
1981
|
+
if (tokenStarted) {
|
|
1982
|
+
tokens.push(current);
|
|
1983
|
+
current = "";
|
|
1984
|
+
tokenStarted = false;
|
|
1985
|
+
}
|
|
1986
|
+
continue;
|
|
1987
|
+
}
|
|
1988
|
+
current += char;
|
|
1989
|
+
tokenStarted = true;
|
|
1990
|
+
}
|
|
1991
|
+
if (tokenStarted) tokens.push(current);
|
|
1992
|
+
return tokens;
|
|
1993
|
+
}
|
|
1879
1994
|
/**
|
|
1880
1995
|
* 解析 CLI 命令字符串为数组
|
|
1881
1996
|
*
|
|
1882
1997
|
* 支持两种格式:
|
|
1883
1998
|
* 1. JSON 数组:以 `[` 开头,如 `["npx", "@fission-ai/openspec"]`
|
|
1884
|
-
* 2.
|
|
1885
|
-
*
|
|
1886
|
-
* 注意:简单字符串解析不支持带引号的参数,如需复杂命令请使用 JSON 数组格式
|
|
1999
|
+
* 2. shell-like 字符串:支持引号与基础转义
|
|
1887
2000
|
*/
|
|
1888
2001
|
function parseCliCommand(command) {
|
|
1889
2002
|
const trimmed = command.trim();
|
|
@@ -1894,7 +2007,144 @@ function parseCliCommand(command) {
|
|
|
1894
2007
|
} catch (err) {
|
|
1895
2008
|
throw new Error(`Failed to parse CLI command as JSON array: ${err instanceof Error ? err.message : err}`);
|
|
1896
2009
|
}
|
|
1897
|
-
|
|
2010
|
+
const tokens = tokenizeCliCommand(trimmed);
|
|
2011
|
+
if (tokens.length !== 1) return tokens;
|
|
2012
|
+
const firstChar = trimmed[0];
|
|
2013
|
+
const lastChar = trimmed[trimmed.length - 1];
|
|
2014
|
+
if (firstChar !== "\"" && firstChar !== "'" || firstChar !== lastChar) return tokens;
|
|
2015
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
2016
|
+
if (!inner) return tokens;
|
|
2017
|
+
const innerTokens = tokenizeCliCommand(inner.replace(/\\(["'])/g, "$1"));
|
|
2018
|
+
if (innerTokens.length > 1 && innerTokens.slice(1).some((token) => token.startsWith("-"))) return innerTokens;
|
|
2019
|
+
return tokens;
|
|
2020
|
+
}
|
|
2021
|
+
function commandToString(commandParts) {
|
|
2022
|
+
const formatToken = (token) => {
|
|
2023
|
+
if (!token) return "\"\"";
|
|
2024
|
+
if (!/[\s"'\\]/.test(token)) return token;
|
|
2025
|
+
return JSON.stringify(token);
|
|
2026
|
+
};
|
|
2027
|
+
return commandParts.map(formatToken).join(" ").trim();
|
|
2028
|
+
}
|
|
2029
|
+
function getRunnerPriorityFromUserAgent(userAgent) {
|
|
2030
|
+
if (!userAgent) return null;
|
|
2031
|
+
if (userAgent.startsWith("bun")) return "bunx";
|
|
2032
|
+
if (userAgent.startsWith("npm")) return "npx";
|
|
2033
|
+
if (userAgent.startsWith("deno")) return "deno";
|
|
2034
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
2035
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
2036
|
+
return null;
|
|
2037
|
+
}
|
|
2038
|
+
function buildCliRunnerCandidates(options) {
|
|
2039
|
+
const candidates = [];
|
|
2040
|
+
const configuredCommandParts = options.configuredCommandParts?.filter(Boolean) ?? [];
|
|
2041
|
+
if (configuredCommandParts.length > 0) candidates.push({
|
|
2042
|
+
id: "configured",
|
|
2043
|
+
source: "config.cli.command",
|
|
2044
|
+
commandParts: configuredCommandParts
|
|
2045
|
+
});
|
|
2046
|
+
candidates.push({
|
|
2047
|
+
id: "openspec",
|
|
2048
|
+
source: "openspec",
|
|
2049
|
+
commandParts: ["openspec"]
|
|
2050
|
+
});
|
|
2051
|
+
const packageRunners = [...BASE_PACKAGE_MANAGER_RUNNERS];
|
|
2052
|
+
const preferred = getRunnerPriorityFromUserAgent(options.userAgent);
|
|
2053
|
+
if (preferred) {
|
|
2054
|
+
const index = packageRunners.findIndex((item) => item.id === preferred);
|
|
2055
|
+
if (index > 0) {
|
|
2056
|
+
const [runner] = packageRunners.splice(index, 1);
|
|
2057
|
+
packageRunners.unshift(runner);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
return [...candidates, ...packageRunners];
|
|
2061
|
+
}
|
|
2062
|
+
function createCleanCliEnv(baseEnv = process.env) {
|
|
2063
|
+
const env = { ...baseEnv };
|
|
2064
|
+
for (const key of Object.keys(env)) if (key.startsWith("npm_config_") || key.startsWith("npm_package_") || key === "npm_execpath" || key === "npm_lifecycle_event" || key === "npm_lifecycle_script") delete env[key];
|
|
2065
|
+
return env;
|
|
2066
|
+
}
|
|
2067
|
+
async function probeCliRunner(candidate, cwd, env) {
|
|
2068
|
+
const [cmd, ...cmdArgs] = candidate.commandParts;
|
|
2069
|
+
return new Promise((resolve$1) => {
|
|
2070
|
+
let stdout = "";
|
|
2071
|
+
let stderr = "";
|
|
2072
|
+
let timedOut = false;
|
|
2073
|
+
const timer = setTimeout(() => {
|
|
2074
|
+
timedOut = true;
|
|
2075
|
+
child.kill();
|
|
2076
|
+
}, CLI_PROBE_TIMEOUT_MS);
|
|
2077
|
+
const child = spawn(cmd, [...cmdArgs, "--version"], {
|
|
2078
|
+
cwd,
|
|
2079
|
+
shell: false,
|
|
2080
|
+
env
|
|
2081
|
+
});
|
|
2082
|
+
child.stdout?.on("data", (data) => {
|
|
2083
|
+
stdout += data.toString();
|
|
2084
|
+
});
|
|
2085
|
+
child.stderr?.on("data", (data) => {
|
|
2086
|
+
stderr += data.toString();
|
|
2087
|
+
});
|
|
2088
|
+
child.on("error", (err) => {
|
|
2089
|
+
clearTimeout(timer);
|
|
2090
|
+
const code = err.code;
|
|
2091
|
+
const suffix = code ? ` (${code})` : "";
|
|
2092
|
+
resolve$1({
|
|
2093
|
+
source: candidate.source,
|
|
2094
|
+
command: commandToString(candidate.commandParts),
|
|
2095
|
+
success: false,
|
|
2096
|
+
error: `${err.message}${suffix}`,
|
|
2097
|
+
exitCode: null
|
|
2098
|
+
});
|
|
2099
|
+
});
|
|
2100
|
+
child.on("close", (exitCode) => {
|
|
2101
|
+
clearTimeout(timer);
|
|
2102
|
+
if (timedOut) {
|
|
2103
|
+
resolve$1({
|
|
2104
|
+
source: candidate.source,
|
|
2105
|
+
command: commandToString(candidate.commandParts),
|
|
2106
|
+
success: false,
|
|
2107
|
+
error: "CLI probe timed out",
|
|
2108
|
+
exitCode
|
|
2109
|
+
});
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (exitCode === 0) {
|
|
2113
|
+
const version = stdout.trim().split("\n")[0] || void 0;
|
|
2114
|
+
resolve$1({
|
|
2115
|
+
source: candidate.source,
|
|
2116
|
+
command: commandToString(candidate.commandParts),
|
|
2117
|
+
success: true,
|
|
2118
|
+
version,
|
|
2119
|
+
exitCode
|
|
2120
|
+
});
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
resolve$1({
|
|
2124
|
+
source: candidate.source,
|
|
2125
|
+
command: commandToString(candidate.commandParts),
|
|
2126
|
+
success: false,
|
|
2127
|
+
error: stderr.trim() || `Exit code ${exitCode ?? "null"}`,
|
|
2128
|
+
exitCode
|
|
2129
|
+
});
|
|
2130
|
+
});
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
async function resolveCliRunner(candidates, cwd, env) {
|
|
2134
|
+
const attempts = [];
|
|
2135
|
+
for (const candidate of candidates) {
|
|
2136
|
+
const attempt = await probeCliRunner(candidate, cwd, env);
|
|
2137
|
+
attempts.push(attempt);
|
|
2138
|
+
if (attempt.success) return {
|
|
2139
|
+
source: attempt.source,
|
|
2140
|
+
command: attempt.command,
|
|
2141
|
+
commandParts: candidate.commandParts,
|
|
2142
|
+
version: attempt.version,
|
|
2143
|
+
attempts
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
const details = attempts.map((attempt) => `- ${attempt.command}: ${attempt.error ?? "failed"}`).join("\n");
|
|
2147
|
+
throw new Error(`No available OpenSpec CLI runner.\n${details}`);
|
|
1898
2148
|
}
|
|
1899
2149
|
/**
|
|
1900
2150
|
* 比较两个语义化版本号
|
|
@@ -1921,7 +2171,7 @@ function compareVersions(a, b) {
|
|
|
1921
2171
|
*/
|
|
1922
2172
|
async function fetchLatestVersion() {
|
|
1923
2173
|
try {
|
|
1924
|
-
const { stdout } = await execAsync("npx @fission-ai/openspec --version", { timeout: 6e4 });
|
|
2174
|
+
const { stdout } = await execAsync("npx -y @fission-ai/openspec --version", { timeout: 6e4 });
|
|
1925
2175
|
return stdout.trim();
|
|
1926
2176
|
} catch {
|
|
1927
2177
|
return;
|
|
@@ -1951,7 +2201,6 @@ async function sniffGlobalCli() {
|
|
|
1951
2201
|
};
|
|
1952
2202
|
}
|
|
1953
2203
|
const version = localResult.stdout.trim();
|
|
1954
|
-
detectedCliCommand = GLOBAL_CLI_COMMAND;
|
|
1955
2204
|
return {
|
|
1956
2205
|
hasGlobal: true,
|
|
1957
2206
|
version,
|
|
@@ -1960,53 +2209,44 @@ async function sniffGlobalCli() {
|
|
|
1960
2209
|
};
|
|
1961
2210
|
}
|
|
1962
2211
|
/**
|
|
1963
|
-
* 检测全局安装的 openspec 命令
|
|
1964
|
-
* 优先使用全局命令,fallback 到 npx
|
|
1965
|
-
*
|
|
1966
|
-
* @returns CLI 命令数组
|
|
1967
|
-
*/
|
|
1968
|
-
async function detectCliCommand() {
|
|
1969
|
-
if (detectedCliCommand !== null) return detectedCliCommand;
|
|
1970
|
-
try {
|
|
1971
|
-
await execAsync(`${process.platform === "win32" ? "where" : "which"} openspec`);
|
|
1972
|
-
detectedCliCommand = GLOBAL_CLI_COMMAND;
|
|
1973
|
-
return detectedCliCommand;
|
|
1974
|
-
} catch {
|
|
1975
|
-
detectedCliCommand = FALLBACK_CLI_COMMAND;
|
|
1976
|
-
return detectedCliCommand;
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
/**
|
|
1980
2212
|
* 获取默认 CLI 命令(异步,带检测)
|
|
1981
2213
|
*
|
|
1982
2214
|
* @returns CLI 命令数组,如 `['openspec']` 或 `['npx', '@fission-ai/openspec']`
|
|
1983
2215
|
*/
|
|
1984
2216
|
async function getDefaultCliCommand() {
|
|
1985
|
-
return
|
|
2217
|
+
return (await resolveCliRunner(buildCliRunnerCandidates({ userAgent: process.env.npm_config_user_agent }).filter((candidate) => candidate.id !== "configured"), process.cwd(), createCleanCliEnv())).commandParts;
|
|
1986
2218
|
}
|
|
1987
2219
|
/**
|
|
1988
2220
|
* 获取默认 CLI 命令的字符串形式(用于 UI 显示)
|
|
1989
2221
|
*/
|
|
1990
2222
|
async function getDefaultCliCommandString() {
|
|
1991
|
-
return (await
|
|
2223
|
+
return commandToString(await getDefaultCliCommand());
|
|
1992
2224
|
}
|
|
2225
|
+
const TerminalConfigSchema = z.object({
|
|
2226
|
+
fontSize: z.number().min(8).max(32).default(13),
|
|
2227
|
+
fontFamily: z.string().default(""),
|
|
2228
|
+
cursorBlink: z.boolean().default(true),
|
|
2229
|
+
cursorStyle: z.enum(CURSOR_STYLE_VALUES).default("block"),
|
|
2230
|
+
scrollback: z.number().min(0).max(1e5).default(1e3)
|
|
2231
|
+
});
|
|
1993
2232
|
/**
|
|
1994
2233
|
* OpenSpecUI 配置 Schema
|
|
1995
2234
|
*
|
|
1996
2235
|
* 存储在 openspec/.openspecui.json 中,利用文件监听实现响应式更新
|
|
1997
2236
|
*/
|
|
1998
2237
|
const OpenSpecUIConfigSchema = z.object({
|
|
1999
|
-
cli: z.object({
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2238
|
+
cli: z.object({
|
|
2239
|
+
command: z.string().optional(),
|
|
2240
|
+
args: z.array(z.string()).optional()
|
|
2241
|
+
}).default({}),
|
|
2242
|
+
theme: z.enum(THEME_VALUES).default("system"),
|
|
2243
|
+
terminal: TerminalConfigSchema.default(TerminalConfigSchema.parse({}))
|
|
2005
2244
|
});
|
|
2006
2245
|
/** 默认配置(静态,用于测试和类型) */
|
|
2007
2246
|
const DEFAULT_CONFIG = {
|
|
2008
2247
|
cli: {},
|
|
2009
|
-
|
|
2248
|
+
theme: "system",
|
|
2249
|
+
terminal: TerminalConfigSchema.parse({})
|
|
2010
2250
|
};
|
|
2011
2251
|
/**
|
|
2012
2252
|
* 配置管理器
|
|
@@ -2016,7 +2256,11 @@ const DEFAULT_CONFIG = {
|
|
|
2016
2256
|
*/
|
|
2017
2257
|
var ConfigManager = class {
|
|
2018
2258
|
configPath;
|
|
2259
|
+
projectDir;
|
|
2260
|
+
resolvedRunner = null;
|
|
2261
|
+
resolvingRunnerPromise = null;
|
|
2019
2262
|
constructor(projectDir) {
|
|
2263
|
+
this.projectDir = projectDir;
|
|
2020
2264
|
this.configPath = join(projectDir, "openspec", ".openspecui.json");
|
|
2021
2265
|
}
|
|
2022
2266
|
/**
|
|
@@ -2046,43 +2290,145 @@ var ConfigManager = class {
|
|
|
2046
2290
|
*/
|
|
2047
2291
|
async writeConfig(config) {
|
|
2048
2292
|
const current = await this.readConfig();
|
|
2293
|
+
const nextCli = { ...current.cli };
|
|
2294
|
+
if (config.cli && Object.prototype.hasOwnProperty.call(config.cli, "command")) {
|
|
2295
|
+
const trimmed = config.cli.command?.trim();
|
|
2296
|
+
if (trimmed) nextCli.command = trimmed;
|
|
2297
|
+
else {
|
|
2298
|
+
delete nextCli.command;
|
|
2299
|
+
delete nextCli.args;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
if (config.cli && Object.prototype.hasOwnProperty.call(config.cli, "args")) {
|
|
2303
|
+
const args = (config.cli.args ?? []).map((arg) => arg.trim()).filter(Boolean);
|
|
2304
|
+
if (args.length > 0) nextCli.args = args;
|
|
2305
|
+
else delete nextCli.args;
|
|
2306
|
+
}
|
|
2307
|
+
if (!nextCli.command) delete nextCli.args;
|
|
2049
2308
|
const merged = {
|
|
2050
2309
|
...current,
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
...
|
|
2055
|
-
|
|
2056
|
-
ui: {
|
|
2057
|
-
...current.ui,
|
|
2058
|
-
...config.ui
|
|
2310
|
+
cli: nextCli,
|
|
2311
|
+
theme: config.theme ?? current.theme,
|
|
2312
|
+
terminal: {
|
|
2313
|
+
...current.terminal,
|
|
2314
|
+
...config.terminal
|
|
2059
2315
|
}
|
|
2060
2316
|
};
|
|
2061
|
-
|
|
2317
|
+
const serialized = JSON.stringify(merged, null, 2);
|
|
2318
|
+
await mkdir(dirname(this.configPath), { recursive: true });
|
|
2319
|
+
await writeFile(this.configPath, serialized, "utf-8");
|
|
2320
|
+
updateReactiveFileCache(this.configPath, serialized);
|
|
2321
|
+
this.invalidateResolvedCliRunner();
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* 解析并缓存可用 CLI runner。
|
|
2325
|
+
*/
|
|
2326
|
+
async resolveCliRunner() {
|
|
2327
|
+
if (this.resolvedRunner) return this.resolvedRunner;
|
|
2328
|
+
if (this.resolvingRunnerPromise) return this.resolvingRunnerPromise;
|
|
2329
|
+
this.resolvingRunnerPromise = this.resolveCliRunnerUncached().then((runner) => {
|
|
2330
|
+
this.resolvedRunner = runner;
|
|
2331
|
+
return runner;
|
|
2332
|
+
}).finally(() => {
|
|
2333
|
+
this.resolvingRunnerPromise = null;
|
|
2334
|
+
});
|
|
2335
|
+
return this.resolvingRunnerPromise;
|
|
2336
|
+
}
|
|
2337
|
+
async resolveCliRunnerUncached() {
|
|
2338
|
+
const config = await this.readConfig();
|
|
2339
|
+
const configuredCommandParts = this.getConfiguredCommandParts(config.cli);
|
|
2340
|
+
const hasConfiguredCommand = configuredCommandParts.length > 0;
|
|
2341
|
+
const resolved = await resolveCliRunner(hasConfiguredCommand ? [{
|
|
2342
|
+
id: "configured",
|
|
2343
|
+
source: "config.cli.command",
|
|
2344
|
+
commandParts: configuredCommandParts
|
|
2345
|
+
}] : buildCliRunnerCandidates({
|
|
2346
|
+
configuredCommandParts,
|
|
2347
|
+
userAgent: process.env.npm_config_user_agent
|
|
2348
|
+
}), this.projectDir, createCleanCliEnv());
|
|
2349
|
+
if (!hasConfiguredCommand) {
|
|
2350
|
+
const [resolvedCommand, ...resolvedArgs] = resolved.commandParts;
|
|
2351
|
+
const currentCommand = config.cli.command?.trim();
|
|
2352
|
+
const currentArgs = config.cli.args ?? [];
|
|
2353
|
+
if (currentCommand !== resolvedCommand || currentArgs.length !== resolvedArgs.length || currentArgs.some((arg, index) => arg !== resolvedArgs[index])) try {
|
|
2354
|
+
await this.writeConfig({ cli: {
|
|
2355
|
+
command: resolvedCommand,
|
|
2356
|
+
args: resolvedArgs
|
|
2357
|
+
} });
|
|
2358
|
+
} catch (err) {
|
|
2359
|
+
console.warn("Failed to persist auto-detected CLI command:", err);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return resolved;
|
|
2062
2363
|
}
|
|
2063
2364
|
/**
|
|
2064
2365
|
* 获取 CLI 命令(数组形式)
|
|
2065
|
-
*
|
|
2066
|
-
* 优先级:配置文件 > 全局 openspec 命令 > npx fallback
|
|
2067
|
-
*
|
|
2068
|
-
* @returns CLI 命令数组,如 `['openspec']` 或 `['npx', '@fission-ai/openspec']`
|
|
2069
2366
|
*/
|
|
2070
2367
|
async getCliCommand() {
|
|
2071
|
-
|
|
2072
|
-
if (config.cli.command) return parseCliCommand(config.cli.command);
|
|
2073
|
-
return getDefaultCliCommand();
|
|
2368
|
+
return (await this.resolveCliRunner()).commandParts;
|
|
2074
2369
|
}
|
|
2075
2370
|
/**
|
|
2076
2371
|
* 获取 CLI 命令的字符串形式(用于 UI 显示)
|
|
2077
2372
|
*/
|
|
2078
2373
|
async getCliCommandString() {
|
|
2079
|
-
return (await this.
|
|
2374
|
+
return (await this.resolveCliRunner()).command;
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* 获取 CLI 解析结果(用于诊断)
|
|
2378
|
+
*/
|
|
2379
|
+
async getResolvedCliRunner() {
|
|
2380
|
+
return this.resolveCliRunner();
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* 清理 CLI 解析缓存(用于 ENOENT 自愈)
|
|
2384
|
+
*/
|
|
2385
|
+
invalidateResolvedCliRunner() {
|
|
2386
|
+
this.resolvedRunner = null;
|
|
2387
|
+
this.resolvingRunnerPromise = null;
|
|
2080
2388
|
}
|
|
2081
2389
|
/**
|
|
2082
2390
|
* 设置 CLI 命令
|
|
2083
2391
|
*/
|
|
2084
2392
|
async setCliCommand(command) {
|
|
2085
|
-
|
|
2393
|
+
const trimmed = command.trim();
|
|
2394
|
+
if (!trimmed) {
|
|
2395
|
+
await this.writeConfig({ cli: {
|
|
2396
|
+
command: null,
|
|
2397
|
+
args: null
|
|
2398
|
+
} });
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
const commandParts = parseCliCommand(trimmed);
|
|
2402
|
+
if (commandParts.length === 0) {
|
|
2403
|
+
await this.writeConfig({ cli: {
|
|
2404
|
+
command: null,
|
|
2405
|
+
args: null
|
|
2406
|
+
} });
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
const [resolvedCommand, ...resolvedArgs] = commandParts;
|
|
2410
|
+
await this.writeConfig({ cli: {
|
|
2411
|
+
command: resolvedCommand,
|
|
2412
|
+
args: resolvedArgs
|
|
2413
|
+
} });
|
|
2414
|
+
}
|
|
2415
|
+
getConfiguredCommandParts(cli) {
|
|
2416
|
+
const command = cli.command?.trim();
|
|
2417
|
+
if (!command) return [];
|
|
2418
|
+
if (Array.isArray(cli.args) && cli.args.length > 0) return [command, ...cli.args];
|
|
2419
|
+
return parseCliCommand(command);
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* 设置主题
|
|
2423
|
+
*/
|
|
2424
|
+
async setTheme(theme) {
|
|
2425
|
+
await this.writeConfig({ theme });
|
|
2426
|
+
}
|
|
2427
|
+
/**
|
|
2428
|
+
* 设置终端配置(部分更新)
|
|
2429
|
+
*/
|
|
2430
|
+
async setTerminalConfig(terminal) {
|
|
2431
|
+
await this.writeConfig({ terminal });
|
|
2086
2432
|
}
|
|
2087
2433
|
};
|
|
2088
2434
|
|
|
@@ -2091,50 +2437,24 @@ var ConfigManager = class {
|
|
|
2091
2437
|
/**
|
|
2092
2438
|
* CLI 执行器
|
|
2093
2439
|
*
|
|
2094
|
-
* 负责调用外部 openspec CLI
|
|
2095
|
-
*
|
|
2096
|
-
* - ['npx', '@fission-ai/openspec'] (默认)
|
|
2097
|
-
* - ['openspec'] (全局安装)
|
|
2098
|
-
* - 自定义数组或字符串
|
|
2099
|
-
*
|
|
2100
|
-
* 注意:所有命令都使用 shell: false 执行,避免 shell 注入风险
|
|
2440
|
+
* 负责调用外部 openspec CLI 命令,统一通过 ConfigManager 的 runner 解析结果执行。
|
|
2441
|
+
* 所有命令都使用 shell: false,避免 shell 注入风险。
|
|
2101
2442
|
*/
|
|
2102
2443
|
var CliExecutor = class {
|
|
2103
2444
|
constructor(configManager, projectDir) {
|
|
2104
2445
|
this.configManager = configManager;
|
|
2105
2446
|
this.projectDir = projectDir;
|
|
2106
2447
|
}
|
|
2107
|
-
/**
|
|
2108
|
-
* 创建干净的环境变量,移除 pnpm 特有的配置
|
|
2109
|
-
* 避免 pnpm 环境变量污染 npx/npm 执行
|
|
2110
|
-
*/
|
|
2111
|
-
getCleanEnv() {
|
|
2112
|
-
const env = { ...process.env };
|
|
2113
|
-
for (const key of Object.keys(env)) if (key.startsWith("npm_config_") || key.startsWith("npm_package_") || key === "npm_execpath" || key === "npm_lifecycle_event" || key === "npm_lifecycle_script") delete env[key];
|
|
2114
|
-
return env;
|
|
2115
|
-
}
|
|
2116
|
-
/**
|
|
2117
|
-
* 构建完整命令数组
|
|
2118
|
-
*
|
|
2119
|
-
* @param args CLI 参数,如 ['init'] 或 ['archive', 'change-id']
|
|
2120
|
-
* @returns [command, ...commandArgs, ...args]
|
|
2121
|
-
*/
|
|
2122
2448
|
async buildCommandArray(args) {
|
|
2123
2449
|
return [...await this.configManager.getCliCommand(), ...args];
|
|
2124
2450
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
*
|
|
2128
|
-
* @param args CLI 参数,如 ['init'] 或 ['archive', 'change-id']
|
|
2129
|
-
* @returns 执行结果
|
|
2130
|
-
*/
|
|
2131
|
-
async execute(args) {
|
|
2132
|
-
const [cmd, ...cmdArgs] = await this.buildCommandArray(args);
|
|
2451
|
+
runCommandOnce(fullCommand) {
|
|
2452
|
+
const [cmd, ...cmdArgs] = fullCommand;
|
|
2133
2453
|
return new Promise((resolve$1) => {
|
|
2134
2454
|
const child = spawn(cmd, cmdArgs, {
|
|
2135
2455
|
cwd: this.projectDir,
|
|
2136
2456
|
shell: false,
|
|
2137
|
-
env:
|
|
2457
|
+
env: createCleanCliEnv()
|
|
2138
2458
|
});
|
|
2139
2459
|
let stdout = "";
|
|
2140
2460
|
let stderr = "";
|
|
@@ -2153,19 +2473,50 @@ var CliExecutor = class {
|
|
|
2153
2473
|
});
|
|
2154
2474
|
});
|
|
2155
2475
|
child.on("error", (err) => {
|
|
2476
|
+
const errorCode = err.code;
|
|
2477
|
+
const errorMessage = err.message + (errorCode ? ` (${errorCode})` : "");
|
|
2156
2478
|
resolve$1({
|
|
2157
2479
|
success: false,
|
|
2158
2480
|
stdout,
|
|
2159
|
-
stderr: stderr
|
|
2160
|
-
exitCode: null
|
|
2481
|
+
stderr: stderr ? `${stderr}\n${errorMessage}` : errorMessage,
|
|
2482
|
+
exitCode: null,
|
|
2483
|
+
errorCode
|
|
2161
2484
|
});
|
|
2162
2485
|
});
|
|
2163
2486
|
});
|
|
2164
2487
|
}
|
|
2488
|
+
async executeInternal(args, allowRetry) {
|
|
2489
|
+
let fullCommand;
|
|
2490
|
+
try {
|
|
2491
|
+
fullCommand = await this.buildCommandArray(args);
|
|
2492
|
+
} catch (err) {
|
|
2493
|
+
return {
|
|
2494
|
+
success: false,
|
|
2495
|
+
stdout: "",
|
|
2496
|
+
stderr: err instanceof Error ? err.message : String(err),
|
|
2497
|
+
exitCode: null
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
const result = await this.runCommandOnce(fullCommand);
|
|
2501
|
+
if (allowRetry && result.errorCode === "ENOENT") {
|
|
2502
|
+
this.configManager.invalidateResolvedCliRunner();
|
|
2503
|
+
return this.executeInternal(args, false);
|
|
2504
|
+
}
|
|
2505
|
+
return {
|
|
2506
|
+
success: result.success,
|
|
2507
|
+
stdout: result.stdout,
|
|
2508
|
+
stderr: result.stderr,
|
|
2509
|
+
exitCode: result.exitCode
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* 执行 CLI 命令
|
|
2514
|
+
*/
|
|
2515
|
+
async execute(args) {
|
|
2516
|
+
return this.executeInternal(args, true);
|
|
2517
|
+
}
|
|
2165
2518
|
/**
|
|
2166
2519
|
* 执行 openspec init(非交互式)
|
|
2167
|
-
*
|
|
2168
|
-
* @param tools 工具列表,如 ['claude', 'cursor'] 或 'all' 或 'none'
|
|
2169
2520
|
*/
|
|
2170
2521
|
async init(tools = "all") {
|
|
2171
2522
|
const toolsArg = Array.isArray(tools) ? tools.join(",") : tools;
|
|
@@ -2177,9 +2528,6 @@ var CliExecutor = class {
|
|
|
2177
2528
|
}
|
|
2178
2529
|
/**
|
|
2179
2530
|
* 执行 openspec archive <changeId>(非交互式)
|
|
2180
|
-
*
|
|
2181
|
-
* @param changeId 要归档的 change ID
|
|
2182
|
-
* @param options 选项
|
|
2183
2531
|
*/
|
|
2184
2532
|
async archive(changeId, options = {}) {
|
|
2185
2533
|
const args = [
|
|
@@ -2236,75 +2584,108 @@ var CliExecutor = class {
|
|
|
2236
2584
|
}
|
|
2237
2585
|
/**
|
|
2238
2586
|
* 检查 CLI 是否可用
|
|
2239
|
-
* @param timeout 超时时间(毫秒),默认 10 秒
|
|
2240
2587
|
*/
|
|
2241
2588
|
async checkAvailability(timeout = 1e4) {
|
|
2242
2589
|
try {
|
|
2243
|
-
const
|
|
2244
|
-
|
|
2590
|
+
const resolved = await Promise.race([this.configManager.getResolvedCliRunner(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("CLI runner resolve timed out")), timeout))]);
|
|
2591
|
+
const versionResult = await Promise.race([this.runCommandOnce([...resolved.commandParts, "--version"]), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("CLI check timed out")), timeout))]);
|
|
2592
|
+
if (versionResult.success) return {
|
|
2245
2593
|
available: true,
|
|
2246
|
-
version:
|
|
2594
|
+
version: versionResult.stdout.trim() || resolved.version,
|
|
2595
|
+
effectiveCommand: resolved.command,
|
|
2596
|
+
tried: resolved.attempts.map((attempt) => attempt.command)
|
|
2247
2597
|
};
|
|
2248
2598
|
return {
|
|
2249
2599
|
available: false,
|
|
2250
|
-
error:
|
|
2600
|
+
error: versionResult.stderr || "Unknown error",
|
|
2601
|
+
effectiveCommand: resolved.command,
|
|
2602
|
+
tried: resolved.attempts.map((attempt) => attempt.command)
|
|
2251
2603
|
};
|
|
2252
2604
|
} catch (err) {
|
|
2253
2605
|
return {
|
|
2254
2606
|
available: false,
|
|
2255
|
-
error: err instanceof Error ? err.message :
|
|
2607
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2256
2608
|
};
|
|
2257
2609
|
}
|
|
2258
2610
|
}
|
|
2259
2611
|
/**
|
|
2260
2612
|
* 流式执行 CLI 命令
|
|
2261
|
-
*
|
|
2262
|
-
* @param args CLI 参数
|
|
2263
|
-
* @param onEvent 事件回调
|
|
2264
|
-
* @returns 取消函数
|
|
2265
2613
|
*/
|
|
2266
2614
|
async executeStream(args, onEvent) {
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2615
|
+
let cancelled = false;
|
|
2616
|
+
let activeChild = null;
|
|
2617
|
+
const start = async (allowRetry) => {
|
|
2618
|
+
if (cancelled) return;
|
|
2619
|
+
let fullCommand;
|
|
2620
|
+
try {
|
|
2621
|
+
fullCommand = await this.buildCommandArray(args);
|
|
2622
|
+
} catch (err) {
|
|
2623
|
+
onEvent({
|
|
2624
|
+
type: "stderr",
|
|
2625
|
+
data: err instanceof Error ? err.message : String(err)
|
|
2626
|
+
});
|
|
2627
|
+
onEvent({
|
|
2628
|
+
type: "exit",
|
|
2629
|
+
exitCode: null
|
|
2630
|
+
});
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2279
2633
|
onEvent({
|
|
2280
|
-
type: "
|
|
2281
|
-
data:
|
|
2634
|
+
type: "command",
|
|
2635
|
+
data: fullCommand.join(" ")
|
|
2282
2636
|
});
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2637
|
+
const [cmd, ...cmdArgs] = fullCommand;
|
|
2638
|
+
const child = spawn(cmd, cmdArgs, {
|
|
2639
|
+
cwd: this.projectDir,
|
|
2640
|
+
shell: false,
|
|
2641
|
+
env: createCleanCliEnv()
|
|
2288
2642
|
});
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2643
|
+
activeChild = child;
|
|
2644
|
+
child.stdout?.on("data", (data) => {
|
|
2645
|
+
onEvent({
|
|
2646
|
+
type: "stdout",
|
|
2647
|
+
data: data.toString()
|
|
2648
|
+
});
|
|
2294
2649
|
});
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2650
|
+
child.stderr?.on("data", (data) => {
|
|
2651
|
+
onEvent({
|
|
2652
|
+
type: "stderr",
|
|
2653
|
+
data: data.toString()
|
|
2654
|
+
});
|
|
2300
2655
|
});
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2656
|
+
child.on("close", (exitCode) => {
|
|
2657
|
+
if (activeChild !== child) return;
|
|
2658
|
+
activeChild = null;
|
|
2659
|
+
onEvent({
|
|
2660
|
+
type: "exit",
|
|
2661
|
+
exitCode
|
|
2662
|
+
});
|
|
2304
2663
|
});
|
|
2305
|
-
|
|
2664
|
+
child.on("error", (err) => {
|
|
2665
|
+
if (activeChild !== child) return;
|
|
2666
|
+
activeChild = null;
|
|
2667
|
+
const code = err.code;
|
|
2668
|
+
const message = err.message + (code ? ` (${code})` : "");
|
|
2669
|
+
if (allowRetry && code === "ENOENT" && !cancelled) {
|
|
2670
|
+
this.configManager.invalidateResolvedCliRunner();
|
|
2671
|
+
start(false);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
onEvent({
|
|
2675
|
+
type: "stderr",
|
|
2676
|
+
data: message
|
|
2677
|
+
});
|
|
2678
|
+
onEvent({
|
|
2679
|
+
type: "exit",
|
|
2680
|
+
exitCode: null
|
|
2681
|
+
});
|
|
2682
|
+
});
|
|
2683
|
+
};
|
|
2684
|
+
await start(true);
|
|
2306
2685
|
return () => {
|
|
2307
|
-
|
|
2686
|
+
cancelled = true;
|
|
2687
|
+
activeChild?.kill();
|
|
2688
|
+
activeChild = null;
|
|
2308
2689
|
};
|
|
2309
2690
|
}
|
|
2310
2691
|
/**
|
|
@@ -2333,13 +2714,6 @@ var CliExecutor = class {
|
|
|
2333
2714
|
}
|
|
2334
2715
|
/**
|
|
2335
2716
|
* 流式执行任意命令(数组形式)
|
|
2336
|
-
*
|
|
2337
|
-
* 用于执行不需要 openspec CLI 前缀的命令,如 npm install。
|
|
2338
|
-
* 使用 shell: false 避免 shell 注入风险。
|
|
2339
|
-
*
|
|
2340
|
-
* @param command 命令数组,如 ['npm', 'install', '-g', '@fission-ai/openspec']
|
|
2341
|
-
* @param onEvent 事件回调
|
|
2342
|
-
* @returns 取消函数
|
|
2343
2717
|
*/
|
|
2344
2718
|
executeCommandStream(command, onEvent) {
|
|
2345
2719
|
const [cmd, ...cmdArgs] = command;
|
|
@@ -2350,7 +2724,7 @@ var CliExecutor = class {
|
|
|
2350
2724
|
const child = spawn(cmd, cmdArgs, {
|
|
2351
2725
|
cwd: this.projectDir,
|
|
2352
2726
|
shell: false,
|
|
2353
|
-
env:
|
|
2727
|
+
env: createCleanCliEnv()
|
|
2354
2728
|
});
|
|
2355
2729
|
child.stdout?.on("data", (data) => {
|
|
2356
2730
|
onEvent({
|
|
@@ -2371,9 +2745,10 @@ var CliExecutor = class {
|
|
|
2371
2745
|
});
|
|
2372
2746
|
});
|
|
2373
2747
|
child.on("error", (err) => {
|
|
2748
|
+
const code = err.code;
|
|
2374
2749
|
onEvent({
|
|
2375
2750
|
type: "stderr",
|
|
2376
|
-
data: err.message
|
|
2751
|
+
data: err.message + (code ? ` (${code})` : "")
|
|
2377
2752
|
});
|
|
2378
2753
|
onEvent({
|
|
2379
2754
|
type: "exit",
|
|
@@ -3478,4 +3853,4 @@ const PtyServerMessageSchema = z.discriminatedUnion("type", [
|
|
|
3478
3853
|
]);
|
|
3479
3854
|
|
|
3480
3855
|
//#endregion
|
|
3481
|
-
export { AI_TOOLS, ApplyInstructionsSchema, ApplyTaskSchema, ArtifactInstructionsSchema, ArtifactStatusSchema, ChangeFileSchema, ChangeSchema, ChangeStatusSchema, CliExecutor, ConfigManager, DEFAULT_CONFIG, DeltaOperationType, DeltaSchema, DeltaSpecSchema, DependencyInfoSchema, MarkdownParser, 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, Validator, acquireWatcher, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, initWatcherPool, isGlobPattern, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };
|
|
3856
|
+
export { AI_TOOLS, ApplyInstructionsSchema, ApplyTaskSchema, ArtifactInstructionsSchema, ArtifactStatusSchema, ChangeFileSchema, ChangeSchema, ChangeStatusSchema, CliExecutor, ConfigManager, DEFAULT_CONFIG, DeltaOperationType, DeltaSchema, DeltaSpecSchema, DependencyInfoSchema, MarkdownParser, 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, Validator, acquireWatcher, buildCliRunnerCandidates, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, initWatcherPool, isGlobPattern, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openspecui/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Core OpenSpec adapter and parser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsdown src/index.ts --format esm --dts",
|
|
33
|
+
"dev": "tsdown src/index.ts --format esm --dts --watch",
|
|
33
34
|
"test": "vitest run",
|
|
34
35
|
"test:watch": "vitest",
|
|
35
36
|
"typecheck": "tsc --noEmit"
|