openspecui 2.1.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +3 -8
- package/dist/index.mjs +1 -1
- package/dist/{open-DfwCb8mL.mjs → open-CLEEmeZr.mjs} +1 -1
- package/dist/{src-Nn_MJz41.mjs → src-DFu4IXMV.mjs} +320 -72
- package/package.json +1 -1
- package/web/assets/{BufferResource-CSbEbiLP.js → BufferResource-CYUkNl6e.js} +1 -1
- package/web/assets/{CanvasRenderer-CvpAMPzA.js → CanvasRenderer-B3FZcTbO.js} +1 -1
- package/web/assets/{Filter-BqwVCa6B.js → Filter-B6m1PS4e.js} +1 -1
- package/web/assets/{RenderTargetSystem-mD_Y2dUA.js → RenderTargetSystem-D-tQO-WR.js} +1 -1
- package/web/assets/{WebGLRenderer-DsvDwXyL.js → WebGLRenderer-CJA_VvyN.js} +1 -1
- package/web/assets/{WebGPURenderer-YcBYXAol.js → WebGPURenderer-CL8THZq8.js} +1 -1
- package/web/assets/{browserAll-MbWxSnea.js → browserAll-Bp7d2QAW.js} +1 -1
- package/web/assets/{ghostty-web-cM8zjOdb.js → ghostty-web-Dn3qPWne.js} +1 -1
- package/web/assets/{index-kjHuAdn9.js → index--lAEzTl3.js} +1 -1
- package/web/assets/{index-CskIUxS-.js → index-B_QlxcT9.js} +1 -1
- package/web/assets/{index-BCMJS8eq.js → index-CKG6_6g8.js} +1 -1
- package/web/assets/{index-DuZDcW_P.js → index-CXQzuBsO.js} +1 -1
- package/web/assets/{index-Cr4OFm9E.js → index-CeCKFMBL.js} +1 -1
- package/web/assets/{index-Cq4njHD4.js → index-Cy7P7qsp.js} +193 -193
- package/web/assets/{index-JHrsE4PU.js → index-D3_KWKkX.js} +1 -1
- package/web/assets/{index-KaodXPu0.js → index-D64umkMM.js} +1 -1
- package/web/assets/{index-TGQYcfbH.js → index-D7SV9jDS.js} +1 -1
- package/web/assets/index-D8ZU3Gye.css +1 -0
- package/web/assets/{index-DyZwNTgF.js → index-DF-P0r8_.js} +1 -1
- package/web/assets/{index-DijRYB99.js → index-DIk8Jt_d.js} +1 -1
- package/web/assets/{index-BMKX_bGe.js → index-DVQLid3N.js} +1 -1
- package/web/assets/{index-B0lABsCj.js → index-DbAAfBjU.js} +1 -1
- package/web/assets/{index-BY40EQxT.js → index-G43sk1RJ.js} +1 -1
- package/web/assets/{index-D0sXMPhu.js → index-QwKm9rik.js} +1 -1
- package/web/assets/{index-B1J3yPM2.js → index-oMDefHHq.js} +1 -1
- package/web/assets/{webworkerAll-BE_Ec3Rk.js → webworkerAll-iiZBXOCn.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-DHeR2UYq.css +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as SchemaInfoSchema, c as toOpsxDisplayPath, d as CliExecutor, f as ConfigManager, g as __toESM, h as __commonJS, i as SchemaDetailSchema, l as buildHostedLaunchUrl, m as OpenSpecAdapter, o as SchemaResolutionSchema, p as DEFAULT_CONFIG, r as require_dist, s as TemplatesSchema, t as startServer, u as resolveHostedAppBaseUrl } from "./src-
|
|
2
|
+
import { a as SchemaInfoSchema, c as toOpsxDisplayPath, d as CliExecutor, f as ConfigManager, g as __toESM, h as __commonJS, i as SchemaDetailSchema, l as buildHostedLaunchUrl, m as OpenSpecAdapter, o as SchemaResolutionSchema, p as DEFAULT_CONFIG, r as require_dist, s as TemplatesSchema, t as startServer, u as resolveHostedAppBaseUrl } from "./src-DFu4IXMV.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { createServer } from "node:net";
|
|
5
5
|
import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
|
|
@@ -4508,7 +4508,7 @@ var yargs_default = Yargs;
|
|
|
4508
4508
|
//#endregion
|
|
4509
4509
|
//#region package.json
|
|
4510
4510
|
var import_dist = require_dist();
|
|
4511
|
-
var version = "2.1.
|
|
4511
|
+
var version = "2.1.3";
|
|
4512
4512
|
|
|
4513
4513
|
//#endregion
|
|
4514
4514
|
//#region src/export.ts
|
|
@@ -4811,13 +4811,9 @@ async function generateSnapshot(projectDir) {
|
|
|
4811
4811
|
};
|
|
4812
4812
|
}));
|
|
4813
4813
|
let projectMd;
|
|
4814
|
-
let agentsMd;
|
|
4815
4814
|
try {
|
|
4816
4815
|
projectMd = await adapter.readProjectMd() ?? void 0;
|
|
4817
4816
|
} catch {}
|
|
4818
|
-
try {
|
|
4819
|
-
agentsMd = await adapter.readAgentsMd() ?? void 0;
|
|
4820
|
-
} catch {}
|
|
4821
4817
|
let configYaml;
|
|
4822
4818
|
let schemas = [];
|
|
4823
4819
|
const schemaDetails = {};
|
|
@@ -4920,7 +4916,6 @@ async function generateSnapshot(projectDir) {
|
|
|
4920
4916
|
changes,
|
|
4921
4917
|
archives,
|
|
4922
4918
|
projectMd,
|
|
4923
|
-
agentsMd,
|
|
4924
4919
|
opsx: {
|
|
4925
4920
|
configYaml,
|
|
4926
4921
|
schemas,
|
|
@@ -5462,7 +5457,7 @@ async function main() {
|
|
|
5462
5457
|
}
|
|
5463
5458
|
console.log("");
|
|
5464
5459
|
if (argv.open) {
|
|
5465
|
-
await (await import("./open-
|
|
5460
|
+
await (await import("./open-CLEEmeZr.mjs")).default(browserUrl);
|
|
5466
5461
|
console.log(useHostedApp ? "🌐 Hosted app opened" : "🌐 Browser opened");
|
|
5467
5462
|
}
|
|
5468
5463
|
console.log("");
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import fs, { constants } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs$1 from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import childProcess, { execFile } from "node:child_process";
|
|
6
7
|
import { promisify } from "node:util";
|
|
7
8
|
import process from "node:process";
|
|
8
9
|
import { Buffer } from "node:buffer";
|
|
9
|
-
import os from "node:os";
|
|
10
10
|
|
|
11
11
|
//#region ../../node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js
|
|
12
12
|
let isDockerCached;
|
|
@@ -14,6 +14,7 @@ import { EventEmitter } from "events";
|
|
|
14
14
|
import { watch } from "fs";
|
|
15
15
|
import { exec, spawn } from "child_process";
|
|
16
16
|
import { promisify } from "util";
|
|
17
|
+
import { homedir } from "node:os";
|
|
17
18
|
import { fileURLToPath } from "node:url";
|
|
18
19
|
import { EventEmitter as EventEmitter$1 } from "node:events";
|
|
19
20
|
import { execFile } from "node:child_process";
|
|
@@ -1669,6 +1670,23 @@ async function reactiveStat(path$1) {
|
|
|
1669
1670
|
}
|
|
1670
1671
|
return state.get();
|
|
1671
1672
|
}
|
|
1673
|
+
/**
|
|
1674
|
+
* 清除指定路径的缓存(用于测试)
|
|
1675
|
+
*/
|
|
1676
|
+
function clearCache(path$1) {
|
|
1677
|
+
if (path$1) {
|
|
1678
|
+
const normalizedPath = resolve$1(path$1);
|
|
1679
|
+
for (const [key, release] of releaseCache$1) if (key.includes(normalizedPath)) {
|
|
1680
|
+
release();
|
|
1681
|
+
releaseCache$1.delete(key);
|
|
1682
|
+
stateCache$1.delete(key);
|
|
1683
|
+
}
|
|
1684
|
+
} else {
|
|
1685
|
+
for (const release of releaseCache$1.values()) release();
|
|
1686
|
+
releaseCache$1.clear();
|
|
1687
|
+
stateCache$1.clear();
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1672
1690
|
|
|
1673
1691
|
//#endregion
|
|
1674
1692
|
//#region ../core/src/validator.ts
|
|
@@ -1869,23 +1887,11 @@ var OpenSpecAdapter = class {
|
|
|
1869
1887
|
return reactiveReadFile(join(this.openspecDir, "project.md"));
|
|
1870
1888
|
}
|
|
1871
1889
|
/**
|
|
1872
|
-
* Read AGENTS.md content (reactive)
|
|
1873
|
-
*/
|
|
1874
|
-
async readAgentsMd() {
|
|
1875
|
-
return reactiveReadFile(join(this.openspecDir, "AGENTS.md"));
|
|
1876
|
-
}
|
|
1877
|
-
/**
|
|
1878
1890
|
* Write project.md content
|
|
1879
1891
|
*/
|
|
1880
1892
|
async writeProjectMd(content) {
|
|
1881
1893
|
await writeFile(join(this.openspecDir, "project.md"), content, "utf-8");
|
|
1882
1894
|
}
|
|
1883
|
-
/**
|
|
1884
|
-
* Write AGENTS.md content
|
|
1885
|
-
*/
|
|
1886
|
-
async writeAgentsMd(content) {
|
|
1887
|
-
await writeFile(join(this.openspecDir, "AGENTS.md"), content, "utf-8");
|
|
1888
|
-
}
|
|
1889
1895
|
async readSpec(specId) {
|
|
1890
1896
|
try {
|
|
1891
1897
|
const content = await this.readSpecRaw(specId);
|
|
@@ -2056,24 +2062,6 @@ This project uses OpenSpec for spec-driven development.
|
|
|
2056
2062
|
- \`specs/\` - Source of truth specifications
|
|
2057
2063
|
- \`changes/\` - Active change proposals
|
|
2058
2064
|
- \`changes/archive/\` - Completed changes
|
|
2059
|
-
`, "utf-8");
|
|
2060
|
-
await writeFile(join(this.openspecDir, "AGENTS.md"), `# AI Agent Instructions
|
|
2061
|
-
|
|
2062
|
-
This project uses OpenSpec for spec-driven development.
|
|
2063
|
-
|
|
2064
|
-
## Available Commands
|
|
2065
|
-
- \`openspec list\` - List changes or specs
|
|
2066
|
-
- \`openspec view\` - Dashboard view
|
|
2067
|
-
- \`openspec show <name>\` - Show change or spec details
|
|
2068
|
-
- \`openspec validate <name>\` - Validate change or spec
|
|
2069
|
-
- \`openspec archive <change>\` - Archive completed change
|
|
2070
|
-
|
|
2071
|
-
## Workflow
|
|
2072
|
-
1. Create a change proposal in \`changes/<change-id>/proposal.md\`
|
|
2073
|
-
2. Define delta specs in \`changes/<change-id>/specs/\`
|
|
2074
|
-
3. Track tasks in \`changes/<change-id>/tasks.md\`
|
|
2075
|
-
4. Implement and mark tasks complete
|
|
2076
|
-
5. Archive when done: \`openspec archive <change-id>\`
|
|
2077
2065
|
`, "utf-8");
|
|
2078
2066
|
}
|
|
2079
2067
|
/**
|
|
@@ -5879,7 +5867,7 @@ var OpenSpecWatcher = class extends EventEmitter {
|
|
|
5879
5867
|
});
|
|
5880
5868
|
});
|
|
5881
5869
|
this.watchDir(this.openspecDir, (filename, eventType) => {
|
|
5882
|
-
if (filename === "project.md"
|
|
5870
|
+
if (filename === "project.md") this.emitDebounced(`project:${filename}`, {
|
|
5883
5871
|
type: "project",
|
|
5884
5872
|
action: eventType === "rename" ? "create" : "update",
|
|
5885
5873
|
path: join(this.openspecDir, filename),
|
|
@@ -6321,11 +6309,68 @@ const DEFAULT_CONFIG = {
|
|
|
6321
6309
|
terminal: TerminalConfigSchema.parse({}),
|
|
6322
6310
|
dashboard: DashboardConfigSchema.parse({})
|
|
6323
6311
|
};
|
|
6312
|
+
function areStringArraysEqual(left, right) {
|
|
6313
|
+
if (left === right) return true;
|
|
6314
|
+
if (!left || !right) return !left && !right;
|
|
6315
|
+
if (left.length !== right.length) return false;
|
|
6316
|
+
return left.every((value, index) => value === right[index]);
|
|
6317
|
+
}
|
|
6318
|
+
function pruneNullish(value) {
|
|
6319
|
+
if (value === null || value === void 0) return;
|
|
6320
|
+
if (Array.isArray(value)) return value.map((entry) => pruneNullish(entry)).filter((entry) => entry !== void 0);
|
|
6321
|
+
if (typeof value === "object") {
|
|
6322
|
+
const normalizedEntries = Object.entries(value).flatMap(([key, entryValue]) => {
|
|
6323
|
+
const nextValue = pruneNullish(entryValue);
|
|
6324
|
+
return nextValue === void 0 ? [] : [[key, nextValue]];
|
|
6325
|
+
});
|
|
6326
|
+
return Object.fromEntries(normalizedEntries);
|
|
6327
|
+
}
|
|
6328
|
+
return value;
|
|
6329
|
+
}
|
|
6330
|
+
function hasOwnEntries(value) {
|
|
6331
|
+
return Object.keys(value).length > 0;
|
|
6332
|
+
}
|
|
6333
|
+
function toPersistedConfig(config, options = {}) {
|
|
6334
|
+
const persisted = {};
|
|
6335
|
+
const command = config.cli.command?.trim();
|
|
6336
|
+
const args = (config.cli.args ?? []).map((arg) => arg.trim()).filter(Boolean);
|
|
6337
|
+
const cliCommandParts = command ? [command, ...args] : void 0;
|
|
6338
|
+
if (cliCommandParts && !areStringArraysEqual(cliCommandParts, options.defaultCliCommandParts)) {
|
|
6339
|
+
const persistedCommand = cliCommandParts[0];
|
|
6340
|
+
persisted.cli = args.length > 0 ? {
|
|
6341
|
+
command: persistedCommand,
|
|
6342
|
+
args
|
|
6343
|
+
} : { command: persistedCommand };
|
|
6344
|
+
}
|
|
6345
|
+
if (config.theme !== DEFAULT_CONFIG.theme) persisted.theme = config.theme;
|
|
6346
|
+
const codeEditor = {};
|
|
6347
|
+
if (config.codeEditor.theme !== DEFAULT_CONFIG.codeEditor.theme) codeEditor.theme = config.codeEditor.theme;
|
|
6348
|
+
if (hasOwnEntries(codeEditor)) persisted.codeEditor = codeEditor;
|
|
6349
|
+
if (config.appBaseUrl !== DEFAULT_CONFIG.appBaseUrl) persisted.appBaseUrl = config.appBaseUrl;
|
|
6350
|
+
const terminal = {};
|
|
6351
|
+
if (config.terminal.fontSize !== DEFAULT_CONFIG.terminal.fontSize) terminal.fontSize = config.terminal.fontSize;
|
|
6352
|
+
if (config.terminal.fontFamily !== DEFAULT_CONFIG.terminal.fontFamily) terminal.fontFamily = config.terminal.fontFamily;
|
|
6353
|
+
if (config.terminal.cursorBlink !== DEFAULT_CONFIG.terminal.cursorBlink) terminal.cursorBlink = config.terminal.cursorBlink;
|
|
6354
|
+
if (config.terminal.cursorStyle !== DEFAULT_CONFIG.terminal.cursorStyle) terminal.cursorStyle = config.terminal.cursorStyle;
|
|
6355
|
+
if (config.terminal.scrollback !== DEFAULT_CONFIG.terminal.scrollback) terminal.scrollback = config.terminal.scrollback;
|
|
6356
|
+
if (config.terminal.rendererEngine !== DEFAULT_CONFIG.terminal.rendererEngine) terminal.rendererEngine = config.terminal.rendererEngine;
|
|
6357
|
+
if (hasOwnEntries(terminal)) persisted.terminal = terminal;
|
|
6358
|
+
const dashboard = {};
|
|
6359
|
+
if (config.dashboard.trendPointLimit !== DEFAULT_CONFIG.dashboard.trendPointLimit) dashboard.trendPointLimit = config.dashboard.trendPointLimit;
|
|
6360
|
+
if (hasOwnEntries(dashboard)) persisted.dashboard = dashboard;
|
|
6361
|
+
return persisted;
|
|
6362
|
+
}
|
|
6363
|
+
function isPersistedConfigEmpty(config) {
|
|
6364
|
+
return !hasOwnEntries(config);
|
|
6365
|
+
}
|
|
6324
6366
|
/**
|
|
6325
6367
|
* 配置管理器
|
|
6326
6368
|
*
|
|
6327
6369
|
* 负责读写 openspec/.openspecui.json 配置文件。
|
|
6328
6370
|
* 读取操作使用 reactiveReadFile,支持响应式更新。
|
|
6371
|
+
*
|
|
6372
|
+
* `.openspecui.json` 是预期中的项目级 UI 配置文件,但只有显式偏离默认值的
|
|
6373
|
+
* override 才会落盘。仅启动 openspecui 或仅依赖默认配置时,不应触发文件写入。
|
|
6329
6374
|
*/
|
|
6330
6375
|
var ConfigManager = class {
|
|
6331
6376
|
configPath;
|
|
@@ -6336,18 +6381,11 @@ var ConfigManager = class {
|
|
|
6336
6381
|
this.projectDir = projectDir;
|
|
6337
6382
|
this.configPath = join(projectDir, "openspec", ".openspecui.json");
|
|
6338
6383
|
}
|
|
6339
|
-
|
|
6340
|
-
* 读取配置(响应式)
|
|
6341
|
-
*
|
|
6342
|
-
* 如果配置文件不存在,返回默认配置。
|
|
6343
|
-
* 如果配置文件格式错误,返回默认配置并打印警告。
|
|
6344
|
-
*/
|
|
6345
|
-
async readConfig() {
|
|
6346
|
-
const content = await reactiveReadFile(this.configPath);
|
|
6384
|
+
parseConfigContent(content) {
|
|
6347
6385
|
if (!content) return DEFAULT_CONFIG;
|
|
6348
6386
|
try {
|
|
6349
|
-
const
|
|
6350
|
-
const result = OpenSpecUIConfigSchema.safeParse(
|
|
6387
|
+
const normalized = pruneNullish(JSON.parse(content)) ?? {};
|
|
6388
|
+
const result = OpenSpecUIConfigSchema.safeParse(normalized);
|
|
6351
6389
|
if (result.success) return result.data;
|
|
6352
6390
|
console.warn("Invalid config format, using defaults:", result.error.message);
|
|
6353
6391
|
return DEFAULT_CONFIG;
|
|
@@ -6357,12 +6395,24 @@ var ConfigManager = class {
|
|
|
6357
6395
|
}
|
|
6358
6396
|
}
|
|
6359
6397
|
/**
|
|
6398
|
+
* 读取配置(响应式)
|
|
6399
|
+
*
|
|
6400
|
+
* 如果配置文件不存在,返回默认配置。
|
|
6401
|
+
* 如果配置文件格式错误,返回默认配置并打印警告。
|
|
6402
|
+
*/
|
|
6403
|
+
async readConfig() {
|
|
6404
|
+
const content = await reactiveReadFile(this.configPath);
|
|
6405
|
+
return this.parseConfigContent(content);
|
|
6406
|
+
}
|
|
6407
|
+
/**
|
|
6360
6408
|
* 写入配置
|
|
6361
6409
|
*
|
|
6362
6410
|
* 会触发文件监听,自动更新订阅者。
|
|
6363
6411
|
*/
|
|
6364
6412
|
async writeConfig(config) {
|
|
6365
|
-
const
|
|
6413
|
+
const currentContent = await reactiveReadFile(this.configPath);
|
|
6414
|
+
const fileExists = currentContent !== null;
|
|
6415
|
+
const current = this.parseConfigContent(currentContent);
|
|
6366
6416
|
const nextCli = { ...current.cli };
|
|
6367
6417
|
if (config.cli && Object.prototype.hasOwnProperty.call(config.cli, "command")) {
|
|
6368
6418
|
const trimmed = config.cli.command?.trim();
|
|
@@ -6378,7 +6428,7 @@ var ConfigManager = class {
|
|
|
6378
6428
|
else delete nextCli.args;
|
|
6379
6429
|
}
|
|
6380
6430
|
if (!nextCli.command) delete nextCli.args;
|
|
6381
|
-
const
|
|
6431
|
+
const persisted = toPersistedConfig({
|
|
6382
6432
|
...current,
|
|
6383
6433
|
cli: nextCli,
|
|
6384
6434
|
theme: config.theme ?? current.theme,
|
|
@@ -6395,13 +6445,25 @@ var ConfigManager = class {
|
|
|
6395
6445
|
...current.dashboard,
|
|
6396
6446
|
...config.dashboard
|
|
6397
6447
|
}
|
|
6398
|
-
};
|
|
6399
|
-
|
|
6448
|
+
});
|
|
6449
|
+
if (isPersistedConfigEmpty(persisted) && !fileExists) return;
|
|
6450
|
+
const serialized = isPersistedConfigEmpty(persisted) ? "{}" : JSON.stringify(persisted, null, 2);
|
|
6451
|
+
if (currentContent === serialized) return;
|
|
6400
6452
|
await mkdir(dirname(this.configPath), { recursive: true });
|
|
6401
6453
|
await writeFile(this.configPath, serialized, "utf-8");
|
|
6402
6454
|
updateReactiveFileCache(this.configPath, serialized);
|
|
6403
6455
|
this.invalidateResolvedCliRunner();
|
|
6404
6456
|
}
|
|
6457
|
+
async resolveDefaultCliCommandParts() {
|
|
6458
|
+
return (await resolveCliRunner(buildCliRunnerCandidates({ userAgent: process.env.npm_config_user_agent }).filter((candidate) => candidate.id !== "configured"), this.projectDir, createCleanCliEnv())).commandParts;
|
|
6459
|
+
}
|
|
6460
|
+
async isDefaultCliCommand(commandParts) {
|
|
6461
|
+
try {
|
|
6462
|
+
return areStringArraysEqual(commandParts, await this.resolveDefaultCliCommandParts());
|
|
6463
|
+
} catch {
|
|
6464
|
+
return false;
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6405
6467
|
/**
|
|
6406
6468
|
* 解析并缓存可用 CLI runner。
|
|
6407
6469
|
*/
|
|
@@ -6419,8 +6481,7 @@ var ConfigManager = class {
|
|
|
6419
6481
|
async resolveCliRunnerUncached() {
|
|
6420
6482
|
const config = await this.readConfig();
|
|
6421
6483
|
const configuredCommandParts = this.getConfiguredCommandParts(config.cli);
|
|
6422
|
-
|
|
6423
|
-
const resolved = await resolveCliRunner(hasConfiguredCommand ? [{
|
|
6484
|
+
return await resolveCliRunner(configuredCommandParts.length > 0 ? [{
|
|
6424
6485
|
id: "configured",
|
|
6425
6486
|
source: "config.cli.command",
|
|
6426
6487
|
commandParts: configuredCommandParts
|
|
@@ -6428,20 +6489,6 @@ var ConfigManager = class {
|
|
|
6428
6489
|
configuredCommandParts,
|
|
6429
6490
|
userAgent: process.env.npm_config_user_agent
|
|
6430
6491
|
}), this.projectDir, createCleanCliEnv());
|
|
6431
|
-
if (!hasConfiguredCommand) {
|
|
6432
|
-
const [resolvedCommand, ...resolvedArgs] = resolved.commandParts;
|
|
6433
|
-
const currentCommand = config.cli.command?.trim();
|
|
6434
|
-
const currentArgs = config.cli.args ?? [];
|
|
6435
|
-
if (currentCommand !== resolvedCommand || currentArgs.length !== resolvedArgs.length || currentArgs.some((arg, index) => arg !== resolvedArgs[index])) try {
|
|
6436
|
-
await this.writeConfig({ cli: {
|
|
6437
|
-
command: resolvedCommand,
|
|
6438
|
-
args: resolvedArgs
|
|
6439
|
-
} });
|
|
6440
|
-
} catch (err) {
|
|
6441
|
-
console.warn("Failed to persist auto-detected CLI command:", err);
|
|
6442
|
-
}
|
|
6443
|
-
}
|
|
6444
|
-
return resolved;
|
|
6445
6492
|
}
|
|
6446
6493
|
/**
|
|
6447
6494
|
* 获取 CLI 命令(数组形式)
|
|
@@ -6488,6 +6535,13 @@ var ConfigManager = class {
|
|
|
6488
6535
|
} });
|
|
6489
6536
|
return;
|
|
6490
6537
|
}
|
|
6538
|
+
if (await this.isDefaultCliCommand(commandParts)) {
|
|
6539
|
+
await this.writeConfig({ cli: {
|
|
6540
|
+
command: null,
|
|
6541
|
+
args: null
|
|
6542
|
+
} });
|
|
6543
|
+
return;
|
|
6544
|
+
}
|
|
6491
6545
|
const [resolvedCommand, ...resolvedArgs] = commandParts;
|
|
6492
6546
|
await this.writeConfig({ cli: {
|
|
6493
6547
|
command: resolvedCommand,
|
|
@@ -6800,9 +6854,36 @@ var CliExecutor = class {
|
|
|
6800
6854
|
}
|
|
6801
6855
|
/**
|
|
6802
6856
|
* 流式执行任意命令(数组形式)
|
|
6857
|
+
*
|
|
6858
|
+
* 字面量 `openspec` 会自动通过已解析的 CLI runner 执行,
|
|
6859
|
+
* 其它命令保持原始 spawn 行为。
|
|
6803
6860
|
*/
|
|
6804
6861
|
executeCommandStream(command, onEvent) {
|
|
6805
6862
|
const [cmd, ...cmdArgs] = command;
|
|
6863
|
+
if (cmd === "openspec") {
|
|
6864
|
+
let cancelResolved = null;
|
|
6865
|
+
let cancelled = false;
|
|
6866
|
+
this.executeStream([...cmdArgs], onEvent).then((cancel) => {
|
|
6867
|
+
if (cancelled) {
|
|
6868
|
+
cancel();
|
|
6869
|
+
return;
|
|
6870
|
+
}
|
|
6871
|
+
cancelResolved = cancel;
|
|
6872
|
+
}).catch((err) => {
|
|
6873
|
+
onEvent({
|
|
6874
|
+
type: "stderr",
|
|
6875
|
+
data: err instanceof Error ? err.message : String(err)
|
|
6876
|
+
});
|
|
6877
|
+
onEvent({
|
|
6878
|
+
type: "exit",
|
|
6879
|
+
exitCode: null
|
|
6880
|
+
});
|
|
6881
|
+
});
|
|
6882
|
+
return () => {
|
|
6883
|
+
cancelled = true;
|
|
6884
|
+
cancelResolved?.();
|
|
6885
|
+
};
|
|
6886
|
+
}
|
|
6806
6887
|
onEvent({
|
|
6807
6888
|
type: "command",
|
|
6808
6889
|
data: command.join(" ")
|
|
@@ -7044,12 +7125,6 @@ const AI_TOOLS = [
|
|
|
7044
7125
|
available: true,
|
|
7045
7126
|
successLabel: "Windsurf",
|
|
7046
7127
|
skillsDir: ".windsurf"
|
|
7047
|
-
},
|
|
7048
|
-
{
|
|
7049
|
-
name: "AGENTS.md (works with Amp, VS Code, …)",
|
|
7050
|
-
value: "agents",
|
|
7051
|
-
available: false,
|
|
7052
|
-
successLabel: "your AGENTS.md-compatible assistant"
|
|
7053
7128
|
}
|
|
7054
7129
|
];
|
|
7055
7130
|
/**
|
|
@@ -7059,11 +7134,23 @@ function getAvailableTools() {
|
|
|
7059
7134
|
return AI_TOOLS.filter((tool) => tool.available);
|
|
7060
7135
|
}
|
|
7061
7136
|
/**
|
|
7062
|
-
*
|
|
7137
|
+
* 获取所有工具
|
|
7063
7138
|
*/
|
|
7064
7139
|
function getAllTools() {
|
|
7065
7140
|
return AI_TOOLS;
|
|
7066
7141
|
}
|
|
7142
|
+
/**
|
|
7143
|
+
* 检测当前项目中已经存在的工具目录。
|
|
7144
|
+
*
|
|
7145
|
+
* 这里对齐 OpenSpec 官方 `getAvailableTools(projectPath)` 的语义:
|
|
7146
|
+
* 仅根据项目根目录下的工具目录是否存在来判断,不读取全局命令安装状态。
|
|
7147
|
+
*/
|
|
7148
|
+
async function getDetectedProjectTools(projectDir) {
|
|
7149
|
+
return (await Promise.all(AI_TOOLS.map(async (tool) => {
|
|
7150
|
+
if (!tool.skillsDir) return null;
|
|
7151
|
+
return await reactiveExists(join$1(projectDir, tool.skillsDir)) ? tool : null;
|
|
7152
|
+
}))).filter((tool) => tool !== null);
|
|
7153
|
+
}
|
|
7067
7154
|
/** 状态缓存:projectDir -> ReactiveState */
|
|
7068
7155
|
const stateCache = /* @__PURE__ */ new Map();
|
|
7069
7156
|
/** 监听器释放函数缓存 */
|
|
@@ -7126,6 +7213,143 @@ async function getConfiguredTools(projectDir) {
|
|
|
7126
7213
|
return state.get();
|
|
7127
7214
|
}
|
|
7128
7215
|
|
|
7216
|
+
//#endregion
|
|
7217
|
+
//#region ../core/src/tool-init-state.ts
|
|
7218
|
+
const TOOL_WORKFLOW_TO_SKILL_DIR = {
|
|
7219
|
+
propose: "openspec-propose",
|
|
7220
|
+
explore: "openspec-explore",
|
|
7221
|
+
new: "openspec-new-change",
|
|
7222
|
+
continue: "openspec-continue-change",
|
|
7223
|
+
apply: "openspec-apply-change",
|
|
7224
|
+
ff: "openspec-ff-change",
|
|
7225
|
+
sync: "openspec-sync-specs",
|
|
7226
|
+
archive: "openspec-archive-change",
|
|
7227
|
+
"bulk-archive": "openspec-bulk-archive-change",
|
|
7228
|
+
verify: "openspec-verify-change",
|
|
7229
|
+
onboard: "openspec-onboard"
|
|
7230
|
+
};
|
|
7231
|
+
const ALL_TOOL_WORKFLOWS = Object.keys(TOOL_WORKFLOW_TO_SKILL_DIR);
|
|
7232
|
+
function toKnownWorkflows(workflows) {
|
|
7233
|
+
return workflows.filter((workflow) => workflow in TOOL_WORKFLOW_TO_SKILL_DIR);
|
|
7234
|
+
}
|
|
7235
|
+
function resolveCodexHome() {
|
|
7236
|
+
const configuredHome = process.env.CODEX_HOME?.trim();
|
|
7237
|
+
return resolve$1(configuredHome ? configuredHome : join$1(homedir(), ".codex"));
|
|
7238
|
+
}
|
|
7239
|
+
function resolveToolCommandPath(projectDir, toolId, workflow) {
|
|
7240
|
+
switch (toolId) {
|
|
7241
|
+
case "amazon-q": return resolve$1(projectDir, ".amazonq", "prompts", `opsx-${workflow}.md`);
|
|
7242
|
+
case "antigravity": return resolve$1(projectDir, ".agent", "workflows", `opsx-${workflow}.md`);
|
|
7243
|
+
case "auggie": return resolve$1(projectDir, ".augment", "commands", `opsx-${workflow}.md`);
|
|
7244
|
+
case "claude": return resolve$1(projectDir, ".claude", "commands", "opsx", `${workflow}.md`);
|
|
7245
|
+
case "cline": return resolve$1(projectDir, ".clinerules", "workflows", `opsx-${workflow}.md`);
|
|
7246
|
+
case "codebuddy": return resolve$1(projectDir, ".codebuddy", "commands", "opsx", `${workflow}.md`);
|
|
7247
|
+
case "codex": return resolve$1(resolveCodexHome(), "prompts", `opsx-${workflow}.md`);
|
|
7248
|
+
case "continue": return resolve$1(projectDir, ".continue", "prompts", `opsx-${workflow}.prompt`);
|
|
7249
|
+
case "costrict": return resolve$1(projectDir, ".cospec", "openspec", "commands", `opsx-${workflow}.md`);
|
|
7250
|
+
case "crush": return resolve$1(projectDir, ".crush", "commands", "opsx", `${workflow}.md`);
|
|
7251
|
+
case "cursor": return resolve$1(projectDir, ".cursor", "commands", `opsx-${workflow}.md`);
|
|
7252
|
+
case "factory": return resolve$1(projectDir, ".factory", "commands", `opsx-${workflow}.md`);
|
|
7253
|
+
case "gemini": return resolve$1(projectDir, ".gemini", "commands", "opsx", `${workflow}.toml`);
|
|
7254
|
+
case "github-copilot": return resolve$1(projectDir, ".github", "prompts", `opsx-${workflow}.prompt.md`);
|
|
7255
|
+
case "iflow": return resolve$1(projectDir, ".iflow", "commands", `opsx-${workflow}.md`);
|
|
7256
|
+
case "kilocode": return resolve$1(projectDir, ".kilocode", "workflows", `opsx-${workflow}.md`);
|
|
7257
|
+
case "kiro": return resolve$1(projectDir, ".kiro", "prompts", `opsx-${workflow}.prompt.md`);
|
|
7258
|
+
case "opencode": return resolve$1(projectDir, ".opencode", "command", `opsx-${workflow}.md`);
|
|
7259
|
+
case "pi": return resolve$1(projectDir, ".pi", "prompts", `opsx-${workflow}.md`);
|
|
7260
|
+
case "qoder": return resolve$1(projectDir, ".qoder", "commands", "opsx", `${workflow}.md`);
|
|
7261
|
+
case "qwen": return resolve$1(projectDir, ".qwen", "commands", `opsx-${workflow}.toml`);
|
|
7262
|
+
case "roocode": return resolve$1(projectDir, ".roo", "commands", `opsx-${workflow}.md`);
|
|
7263
|
+
case "windsurf": return resolve$1(projectDir, ".windsurf", "workflows", `opsx-${workflow}.md`);
|
|
7264
|
+
default: return null;
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
function getSkillArtifacts(projectDir, skillsDir) {
|
|
7268
|
+
return ALL_TOOL_WORKFLOWS.map((workflow) => ({
|
|
7269
|
+
workflow,
|
|
7270
|
+
path: resolve$1(projectDir, skillsDir, "skills", TOOL_WORKFLOW_TO_SKILL_DIR[workflow], "SKILL.md")
|
|
7271
|
+
}));
|
|
7272
|
+
}
|
|
7273
|
+
function getCommandArtifacts(projectDir, toolId) {
|
|
7274
|
+
return ALL_TOOL_WORKFLOWS.flatMap((workflow) => {
|
|
7275
|
+
const path$1 = resolveToolCommandPath(projectDir, toolId, workflow);
|
|
7276
|
+
return path$1 ? [{
|
|
7277
|
+
workflow,
|
|
7278
|
+
path: path$1
|
|
7279
|
+
}] : [];
|
|
7280
|
+
});
|
|
7281
|
+
}
|
|
7282
|
+
function invalidateToolInitCaches(projectDir) {
|
|
7283
|
+
const cacheRoots = /* @__PURE__ */ new Set();
|
|
7284
|
+
for (const tool of AI_TOOLS) {
|
|
7285
|
+
if (tool.skillsDir) cacheRoots.add(resolve$1(projectDir, tool.skillsDir));
|
|
7286
|
+
for (const workflow of ALL_TOOL_WORKFLOWS) {
|
|
7287
|
+
const commandPath = resolveToolCommandPath(projectDir, tool.value, workflow);
|
|
7288
|
+
if (commandPath) cacheRoots.add(dirname$1(commandPath));
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
for (const root of cacheRoots) clearCache(root);
|
|
7292
|
+
}
|
|
7293
|
+
async function getExistingArtifactPaths(entries) {
|
|
7294
|
+
const presence = await Promise.all(entries.map(async (entry) => ({
|
|
7295
|
+
path: entry.path,
|
|
7296
|
+
exists: await reactiveExists(entry.path)
|
|
7297
|
+
})));
|
|
7298
|
+
return new Set(presence.filter((entry) => entry.exists).map((entry) => entry.path));
|
|
7299
|
+
}
|
|
7300
|
+
function countExisting(entries, existingPaths) {
|
|
7301
|
+
return entries.reduce((count, entry) => count + (existingPaths.has(entry.path) ? 1 : 0), 0);
|
|
7302
|
+
}
|
|
7303
|
+
function collectMissingWorkflows(entries, existingPaths) {
|
|
7304
|
+
return entries.filter((entry) => !existingPaths.has(entry.path)).map((entry) => entry.workflow);
|
|
7305
|
+
}
|
|
7306
|
+
function collectUnexpectedWorkflows(entries, desiredWorkflowSet, existingPaths) {
|
|
7307
|
+
return entries.filter((entry) => !desiredWorkflowSet.has(entry.workflow) && existingPaths.has(entry.path)).map((entry) => entry.workflow);
|
|
7308
|
+
}
|
|
7309
|
+
async function getToolInitStates(projectDir, options) {
|
|
7310
|
+
invalidateToolInitCaches(projectDir);
|
|
7311
|
+
const desiredWorkflows = toKnownWorkflows(options.workflows);
|
|
7312
|
+
const desiredWorkflowSet = new Set(desiredWorkflows);
|
|
7313
|
+
const shouldGenerateSkills = options.delivery !== "commands";
|
|
7314
|
+
const shouldGenerateCommands = options.delivery !== "skills";
|
|
7315
|
+
return Promise.all(AI_TOOLS.filter((tool) => tool.skillsDir).map(async (tool) => {
|
|
7316
|
+
const skillArtifacts = getSkillArtifacts(projectDir, tool.skillsDir);
|
|
7317
|
+
const commandArtifacts = getCommandArtifacts(projectDir, tool.value);
|
|
7318
|
+
const existingSkillPaths = await getExistingArtifactPaths(skillArtifacts);
|
|
7319
|
+
const existingCommandPaths = await getExistingArtifactPaths(commandArtifacts);
|
|
7320
|
+
const expectedSkillArtifacts = shouldGenerateSkills ? skillArtifacts.filter((entry) => desiredWorkflowSet.has(entry.workflow)) : [];
|
|
7321
|
+
const expectedCommandArtifacts = shouldGenerateCommands ? commandArtifacts.filter((entry) => desiredWorkflowSet.has(entry.workflow)) : [];
|
|
7322
|
+
const missingSkillWorkflows = collectMissingWorkflows(expectedSkillArtifacts, existingSkillPaths);
|
|
7323
|
+
const missingCommandWorkflows = collectMissingWorkflows(expectedCommandArtifacts, existingCommandPaths);
|
|
7324
|
+
const unexpectedSkillWorkflows = collectUnexpectedWorkflows(shouldGenerateSkills ? skillArtifacts : skillArtifacts, shouldGenerateSkills ? desiredWorkflowSet : /* @__PURE__ */ new Set(), existingSkillPaths);
|
|
7325
|
+
const unexpectedCommandWorkflows = collectUnexpectedWorkflows(shouldGenerateCommands ? commandArtifacts : commandArtifacts, shouldGenerateCommands ? desiredWorkflowSet : /* @__PURE__ */ new Set(), existingCommandPaths);
|
|
7326
|
+
const expectedSkillCount = expectedSkillArtifacts.length;
|
|
7327
|
+
const presentExpectedSkillCount = expectedSkillCount - missingSkillWorkflows.length;
|
|
7328
|
+
const detectedSkillCount = countExisting(skillArtifacts, existingSkillPaths);
|
|
7329
|
+
const expectedCommandCount = expectedCommandArtifacts.length;
|
|
7330
|
+
const presentExpectedCommandCount = expectedCommandCount - missingCommandWorkflows.length;
|
|
7331
|
+
const detectedCommandCount = countExisting(commandArtifacts, existingCommandPaths);
|
|
7332
|
+
const hasAnyArtifacts = detectedSkillCount + detectedCommandCount > 0;
|
|
7333
|
+
const isInitialized = missingSkillWorkflows.length === 0 && missingCommandWorkflows.length === 0 && unexpectedSkillWorkflows.length === 0 && unexpectedCommandWorkflows.length === 0;
|
|
7334
|
+
return {
|
|
7335
|
+
toolId: tool.value,
|
|
7336
|
+
toolName: tool.name,
|
|
7337
|
+
status: !hasAnyArtifacts ? "uninitialized" : isInitialized ? "initialized" : "partial",
|
|
7338
|
+
hasAnyArtifacts,
|
|
7339
|
+
expectedSkillCount,
|
|
7340
|
+
presentExpectedSkillCount,
|
|
7341
|
+
detectedSkillCount,
|
|
7342
|
+
expectedCommandCount,
|
|
7343
|
+
presentExpectedCommandCount,
|
|
7344
|
+
detectedCommandCount,
|
|
7345
|
+
missingSkillWorkflows,
|
|
7346
|
+
missingCommandWorkflows,
|
|
7347
|
+
unexpectedSkillWorkflows,
|
|
7348
|
+
unexpectedCommandWorkflows
|
|
7349
|
+
};
|
|
7350
|
+
}));
|
|
7351
|
+
}
|
|
7352
|
+
|
|
7129
7353
|
//#endregion
|
|
7130
7354
|
//#region ../core/src/dashboard-types.ts
|
|
7131
7355
|
const DASHBOARD_METRIC_KEYS = [
|
|
@@ -24846,9 +25070,30 @@ const cliRouter = router({
|
|
|
24846
25070
|
successLabel: tool.successLabel
|
|
24847
25071
|
}));
|
|
24848
25072
|
}),
|
|
25073
|
+
getDetectedProjectTools: publicProcedure.query(async ({ ctx }) => {
|
|
25074
|
+
return (await getDetectedProjectTools(ctx.projectDir)).map((tool) => ({
|
|
25075
|
+
name: tool.name,
|
|
25076
|
+
value: tool.value,
|
|
25077
|
+
available: tool.available,
|
|
25078
|
+
successLabel: tool.successLabel
|
|
25079
|
+
}));
|
|
25080
|
+
}),
|
|
24849
25081
|
getProfileState: publicProcedure.query(async ({ ctx }) => {
|
|
24850
25082
|
return fetchOpsxProfileState(ctx);
|
|
24851
25083
|
}),
|
|
25084
|
+
getToolInitStates: publicProcedure.input(objectType({
|
|
25085
|
+
delivery: enumType([
|
|
25086
|
+
"both",
|
|
25087
|
+
"skills",
|
|
25088
|
+
"commands"
|
|
25089
|
+
]),
|
|
25090
|
+
workflows: arrayType(stringType()).default([])
|
|
25091
|
+
})).query(async ({ ctx, input }) => {
|
|
25092
|
+
return getToolInitStates(ctx.projectDir, {
|
|
25093
|
+
delivery: input.delivery,
|
|
25094
|
+
workflows: input.workflows
|
|
25095
|
+
});
|
|
25096
|
+
}),
|
|
24852
25097
|
getGlobalConfigPath: publicProcedure.query(async ({ ctx }) => {
|
|
24853
25098
|
return { path: await resolveGlobalConfigPath(ctx) };
|
|
24854
25099
|
}),
|
|
@@ -25743,14 +25988,17 @@ async function startServer(config, setupApp) {
|
|
|
25743
25988
|
};
|
|
25744
25989
|
}
|
|
25745
25990
|
|
|
25991
|
+
//#endregion
|
|
25992
|
+
//#region src/web-assets.ts
|
|
25993
|
+
function getWebAssetsDirCandidates(runtimeDir) {
|
|
25994
|
+
return [join$1(runtimeDir, "..", "web"), join$1(runtimeDir, "..", "..", "web", "dist")];
|
|
25995
|
+
}
|
|
25996
|
+
|
|
25746
25997
|
//#endregion
|
|
25747
25998
|
//#region src/index.ts
|
|
25748
25999
|
const __dirname = dirname$1(fileURLToPath(import.meta.url));
|
|
25749
26000
|
function getWebAssetsDir() {
|
|
25750
|
-
const
|
|
25751
|
-
const prodPath = join$1(__dirname, "..", "web");
|
|
25752
|
-
if (existsSync(prodPath)) return prodPath;
|
|
25753
|
-
if (existsSync(devPath)) return devPath;
|
|
26001
|
+
for (const candidate of getWebAssetsDirCandidates(__dirname)) if (existsSync(candidate)) return candidate;
|
|
25754
26002
|
throw new Error("Web assets not found. Make sure to build the web package first.");
|
|
25755
26003
|
}
|
|
25756
26004
|
function setupStaticFiles(app) {
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{y as U,z as g,A as c,B as S,D as _,F as m,H as I,J as p}from"./index-
|
|
1
|
+
import{y as U,z as g,A as c,B as S,D as _,F as m,H as I,J as p}from"./index-Cy7P7qsp.js";const x={name:"local-uniform-bit",vertex:{header:`
|
|
2
2
|
|
|
3
3
|
struct LocalUniforms {
|
|
4
4
|
uTransformMatrix:mat3x3<f32>,
|