@cortexkit/aft 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -46,11 +46,235 @@ var __export = (target, all) => {
46
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
47
  var __promiseAll = (args) => Promise.all(args);
48
48
 
49
- // src/lib/fs-util.ts
50
- import { existsSync, readdirSync, statSync } from "node:fs";
49
+ // src/lib/paths.ts
50
+ import { homedir, tmpdir } from "node:os";
51
51
  import { join } from "node:path";
52
+ function getAftBinaryCacheDir() {
53
+ if (process.env.AFT_CACHE_DIR) {
54
+ return join(process.env.AFT_CACHE_DIR, "bin");
55
+ }
56
+ if (process.platform === "win32") {
57
+ const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
58
+ const base2 = localAppData || join(homedir(), "AppData", "Local");
59
+ return join(base2, "aft", "bin");
60
+ }
61
+ const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
62
+ return join(base, "aft", "bin");
63
+ }
64
+ function getAftBinaryName() {
65
+ return process.platform === "win32" ? "aft.exe" : "aft";
66
+ }
67
+ function getAftLspPackagesDir() {
68
+ if (process.env.AFT_CACHE_DIR) {
69
+ return join(process.env.AFT_CACHE_DIR, "lsp-packages");
70
+ }
71
+ if (process.platform === "win32") {
72
+ const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
73
+ const base2 = localAppData || join(homedir(), "AppData", "Local");
74
+ return join(base2, "aft", "lsp-packages");
75
+ }
76
+ const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
77
+ return join(base, "aft", "lsp-packages");
78
+ }
79
+ function getAftLspBinariesDir() {
80
+ if (process.env.AFT_CACHE_DIR) {
81
+ return join(process.env.AFT_CACHE_DIR, "lsp-binaries");
82
+ }
83
+ if (process.platform === "win32") {
84
+ const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
85
+ const base2 = localAppData || join(homedir(), "AppData", "Local");
86
+ return join(base2, "aft", "lsp-binaries");
87
+ }
88
+ const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
89
+ return join(base, "aft", "lsp-binaries");
90
+ }
91
+ function homeDir() {
92
+ if (process.platform === "win32")
93
+ return process.env.USERPROFILE || process.env.HOME || homedir();
94
+ return process.env.HOME || homedir();
95
+ }
96
+ function dataHome() {
97
+ if (process.env.XDG_DATA_HOME)
98
+ return process.env.XDG_DATA_HOME;
99
+ if (process.platform === "win32") {
100
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join(homeDir(), "AppData", "Local");
101
+ }
102
+ return join(homeDir(), ".local", "share");
103
+ }
104
+ function getCortexKitStorageRoot() {
105
+ return join(dataHome(), "cortexkit", "aft");
106
+ }
107
+ function getTmpLogPath(filename) {
108
+ return join(tmpdir(), filename);
109
+ }
110
+ var init_paths = () => {};
111
+
112
+ // src/lib/binary-probe.ts
113
+ import { execSync, spawnSync } from "node:child_process";
114
+ import { existsSync } from "node:fs";
115
+ import { createRequire } from "node:module";
116
+ import { homedir as homedir2 } from "node:os";
117
+ import { join as join2 } from "node:path";
118
+ async function loadPluginVersion() {
119
+ try {
120
+ const bridgePackageName = "@cortexkit/aft-bridge";
121
+ const bridge = await import(bridgePackageName);
122
+ if (typeof bridge.PLUGIN_VERSION === "string" && bridge.PLUGIN_VERSION.length > 0) {
123
+ return bridge.PLUGIN_VERSION;
124
+ }
125
+ } catch {}
126
+ const require2 = createRequire(import.meta.url);
127
+ for (const relPath of [
128
+ "../../../aft-bridge/package.json",
129
+ "../../package.json",
130
+ "../package.json"
131
+ ]) {
132
+ try {
133
+ const pkg = require2(relPath);
134
+ if (typeof pkg.version === "string" && pkg.version.length > 0)
135
+ return pkg.version;
136
+ } catch {}
137
+ }
138
+ return "unknown";
139
+ }
140
+ function parseVersionOutput(output) {
141
+ for (const line of output.split(/\r?\n/)) {
142
+ const match = line.trim().match(VERSION_LINE);
143
+ if (match?.[1])
144
+ return match[1];
145
+ }
146
+ return null;
147
+ }
148
+ function majorMinor(version) {
149
+ if (!version)
150
+ return null;
151
+ const match = version.trim().match(/^v?(\d+)\.(\d+)\.\d+(?:[-+][0-9A-Za-z.-]+)?$/);
152
+ if (!match)
153
+ return null;
154
+ return `${match[1]}.${match[2]}`;
155
+ }
156
+ function versionMatchesExpected(candidate, expectedVersion) {
157
+ const candidateMajorMinor = majorMinor(candidate);
158
+ const expectedMajorMinor = majorMinor(expectedVersion);
159
+ return candidateMajorMinor !== null && candidateMajorMinor === expectedMajorMinor;
160
+ }
161
+ function probeBinaryVersion(preferredVersion) {
162
+ return probeAftBinary(preferredVersion).version;
163
+ }
164
+ function probeAftBinary(preferredVersion) {
165
+ const expectedVersion = preferredVersion ?? PLUGIN_VERSION;
166
+ const expectedMajorMinor = majorMinor(expectedVersion);
167
+ const candidates = [];
168
+ for (const candidate of aftBinaryCandidates(preferredVersion)) {
169
+ try {
170
+ if (!existsSync(candidate))
171
+ continue;
172
+ const result = spawnSync(candidate, ["--version"], {
173
+ stdio: ["ignore", "pipe", "pipe"],
174
+ encoding: "utf-8",
175
+ timeout: 5000,
176
+ env: process.env
177
+ });
178
+ const output = `${result.stdout ?? ""}
179
+ ${result.stderr ?? ""}`.trim();
180
+ if (result.error || result.status !== 0) {
181
+ candidates.push({
182
+ path: candidate,
183
+ status: "error",
184
+ version: null,
185
+ ...output ? { output } : {},
186
+ error: result.error?.message ?? `exit status ${result.status ?? "unknown"}`
187
+ });
188
+ continue;
189
+ }
190
+ const version = parseVersionOutput(output);
191
+ if (!version) {
192
+ candidates.push({ path: candidate, status: "invalid", version: null, output });
193
+ continue;
194
+ }
195
+ if (!versionMatchesExpected(version, expectedVersion)) {
196
+ candidates.push({ path: candidate, status: "unmatched", version, output });
197
+ continue;
198
+ }
199
+ candidates.push({ path: candidate, status: "matched", version, output });
200
+ return { version, path: candidate, expectedVersion, expectedMajorMinor, candidates };
201
+ } catch (error) {
202
+ candidates.push({
203
+ path: candidate,
204
+ status: "error",
205
+ version: null,
206
+ error: error instanceof Error ? error.message : String(error)
207
+ });
208
+ }
209
+ }
210
+ return { version: null, path: null, expectedVersion, expectedMajorMinor, candidates };
211
+ }
212
+ function pushCandidate(candidates, candidate) {
213
+ if (!candidate)
214
+ return;
215
+ if (!candidates.includes(candidate))
216
+ candidates.push(candidate);
217
+ }
218
+ function firstExisting(candidates) {
219
+ for (const candidate of candidates) {
220
+ try {
221
+ if (!existsSync(candidate))
222
+ continue;
223
+ return candidate;
224
+ } catch {}
225
+ }
226
+ return null;
227
+ }
228
+ function platformKey(platform = process.platform, arch = process.arch) {
229
+ const table = {
230
+ darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
231
+ linux: { arm64: "linux-arm64", x64: "linux-x64" },
232
+ win32: { x64: "win32-x64" }
233
+ };
234
+ return table[platform]?.[arch] ?? null;
235
+ }
236
+ function aftBinaryCandidates(preferredVersion) {
237
+ const candidates = [];
238
+ if (preferredVersion) {
239
+ const tag = preferredVersion.startsWith("v") ? preferredVersion : `v${preferredVersion}`;
240
+ pushCandidate(candidates, join2(getAftBinaryCacheDir(), tag, getAftBinaryName()));
241
+ }
242
+ const key = platformKey();
243
+ if (key) {
244
+ try {
245
+ const require2 = createRequire(import.meta.url);
246
+ pushCandidate(candidates, require2.resolve(`@cortexkit/aft-${key}/bin/${getAftBinaryName()}`));
247
+ } catch {}
248
+ }
249
+ try {
250
+ const lookup = process.platform === "win32" ? "where aft" : "which aft";
251
+ const resolved = execSync(lookup, {
252
+ stdio: "pipe",
253
+ encoding: "utf-8",
254
+ env: process.env
255
+ }).trim();
256
+ if (resolved) {
257
+ pushCandidate(candidates, resolved.split(/\r?\n/)[0]);
258
+ }
259
+ } catch {}
260
+ pushCandidate(candidates, join2(homedir2(), ".cargo", "bin", getAftBinaryName()));
261
+ return candidates;
262
+ }
263
+ function findAftBinary(preferredVersion) {
264
+ return firstExisting(aftBinaryCandidates(preferredVersion));
265
+ }
266
+ var PLUGIN_VERSION, VERSION_LINE;
267
+ var init_binary_probe = __esm(async () => {
268
+ init_paths();
269
+ PLUGIN_VERSION = await loadPluginVersion();
270
+ VERSION_LINE = /^(?:aft\s+)?v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/i;
271
+ });
272
+
273
+ // src/lib/fs-util.ts
274
+ import { existsSync as existsSync2, readdirSync, statSync } from "node:fs";
275
+ import { join as join3 } from "node:path";
52
276
  function dirSize(path) {
53
- if (!existsSync(path)) {
277
+ if (!existsSync2(path)) {
54
278
  return 0;
55
279
  }
56
280
  const stat = statSync(path);
@@ -62,7 +286,7 @@ function dirSize(path) {
62
286
  }
63
287
  let total = 0;
64
288
  for (const entry of readdirSync(path)) {
65
- total += dirSize(join(path, entry));
289
+ total += dirSize(join3(path, entry));
66
290
  }
67
291
  return total;
68
292
  }
@@ -7685,10 +7909,10 @@ var require_stringify = __commonJS((exports, module) => {
7685
7909
  replacer = null;
7686
7910
  indent = EMPTY;
7687
7911
  };
7688
- var join2 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
7912
+ var join4 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
7689
7913
  var join_content = (inside, value, gap) => {
7690
7914
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
7691
- return join2(comment, inside, gap);
7915
+ return join4(comment, inside, gap);
7692
7916
  };
7693
7917
  var stringify_string = (holder, key, value) => {
7694
7918
  const raw = get_raw_string_literal(holder, key);
@@ -7710,13 +7934,13 @@ var require_stringify = __commonJS((exports, module) => {
7710
7934
  if (i !== 0) {
7711
7935
  inside += COMMA;
7712
7936
  }
7713
- const before = join2(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
7937
+ const before = join4(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
7714
7938
  inside += before || LF + deeper_gap;
7715
7939
  inside += stringify(i, value, deeper_gap) || STR_NULL;
7716
7940
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
7717
7941
  after_comma = process_comments(value, AFTER(i), deeper_gap);
7718
7942
  }
7719
- inside += join2(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7943
+ inside += join4(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7720
7944
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
7721
7945
  };
7722
7946
  var object_stringify = (value, gap) => {
@@ -7737,13 +7961,13 @@ var require_stringify = __commonJS((exports, module) => {
7737
7961
  inside += COMMA;
7738
7962
  }
7739
7963
  first = false;
7740
- const before = join2(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
7964
+ const before = join4(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
7741
7965
  inside += before || LF + deeper_gap;
7742
7966
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
7743
7967
  after_comma = process_comments(value, AFTER(key), deeper_gap);
7744
7968
  };
7745
7969
  keys.forEach(iteratee);
7746
- inside += join2(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7970
+ inside += join4(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7747
7971
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
7748
7972
  };
7749
7973
  function stringify(key, holder, gap) {
@@ -7836,21 +8060,21 @@ var require_src2 = __commonJS((exports, module) => {
7836
8060
  });
7837
8061
 
7838
8062
  // src/lib/jsonc.ts
7839
- import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8063
+ import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7840
8064
  import { dirname } from "node:path";
7841
8065
  function detectJsoncFile(configDir, baseName) {
7842
8066
  const jsoncPath = `${configDir}/${baseName}.jsonc`;
7843
8067
  const jsonPath = `${configDir}/${baseName}.json`;
7844
- if (existsSync2(jsoncPath)) {
8068
+ if (existsSync3(jsoncPath)) {
7845
8069
  return { path: jsoncPath, format: "jsonc" };
7846
8070
  }
7847
- if (existsSync2(jsonPath)) {
8071
+ if (existsSync3(jsonPath)) {
7848
8072
  return { path: jsonPath, format: "json" };
7849
8073
  }
7850
8074
  return { path: jsonPath, format: "none" };
7851
8075
  }
7852
8076
  function readJsoncFile(path) {
7853
- if (!existsSync2(path)) {
8077
+ if (!existsSync3(path)) {
7854
8078
  return { value: null };
7855
8079
  }
7856
8080
  try {
@@ -7871,7 +8095,7 @@ function writeJsoncFile(path, value, format = "json") {
7871
8095
  `);
7872
8096
  }
7873
8097
  function ensureAftSchemaUrl(path, format) {
7874
- const existed = existsSync2(path);
8098
+ const existed = existsSync3(path);
7875
8099
  if (!existed) {
7876
8100
  const writeFormat = format === "jsonc" ? "jsonc" : "json";
7877
8101
  writeJsoncFile(path, { $schema: AFT_SCHEMA_URL }, writeFormat);
@@ -7906,73 +8130,10 @@ var init_jsonc = __esm(() => {
7906
8130
  import_comment_json = __toESM(require_src2(), 1);
7907
8131
  });
7908
8132
 
7909
- // src/lib/paths.ts
7910
- import { homedir, tmpdir } from "node:os";
7911
- import { join as join2 } from "node:path";
7912
- function getAftBinaryCacheDir() {
7913
- if (process.env.AFT_CACHE_DIR) {
7914
- return join2(process.env.AFT_CACHE_DIR, "bin");
7915
- }
7916
- if (process.platform === "win32") {
7917
- const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
7918
- const base2 = localAppData || join2(homedir(), "AppData", "Local");
7919
- return join2(base2, "aft", "bin");
7920
- }
7921
- const base = process.env.XDG_CACHE_HOME || join2(homedir(), ".cache");
7922
- return join2(base, "aft", "bin");
7923
- }
7924
- function getAftBinaryName() {
7925
- return process.platform === "win32" ? "aft.exe" : "aft";
7926
- }
7927
- function getAftLspPackagesDir() {
7928
- if (process.env.AFT_CACHE_DIR) {
7929
- return join2(process.env.AFT_CACHE_DIR, "lsp-packages");
7930
- }
7931
- if (process.platform === "win32") {
7932
- const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
7933
- const base2 = localAppData || join2(homedir(), "AppData", "Local");
7934
- return join2(base2, "aft", "lsp-packages");
7935
- }
7936
- const base = process.env.XDG_CACHE_HOME || join2(homedir(), ".cache");
7937
- return join2(base, "aft", "lsp-packages");
7938
- }
7939
- function getAftLspBinariesDir() {
7940
- if (process.env.AFT_CACHE_DIR) {
7941
- return join2(process.env.AFT_CACHE_DIR, "lsp-binaries");
7942
- }
7943
- if (process.platform === "win32") {
7944
- const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
7945
- const base2 = localAppData || join2(homedir(), "AppData", "Local");
7946
- return join2(base2, "aft", "lsp-binaries");
7947
- }
7948
- const base = process.env.XDG_CACHE_HOME || join2(homedir(), ".cache");
7949
- return join2(base, "aft", "lsp-binaries");
7950
- }
7951
- function homeDir() {
7952
- if (process.platform === "win32")
7953
- return process.env.USERPROFILE || process.env.HOME || homedir();
7954
- return process.env.HOME || homedir();
7955
- }
7956
- function dataHome() {
7957
- if (process.env.XDG_DATA_HOME)
7958
- return process.env.XDG_DATA_HOME;
7959
- if (process.platform === "win32") {
7960
- return process.env.LOCALAPPDATA || process.env.APPDATA || join2(homeDir(), "AppData", "Local");
7961
- }
7962
- return join2(homeDir(), ".local", "share");
7963
- }
7964
- function getCortexKitStorageRoot() {
7965
- return join2(dataHome(), "cortexkit", "aft");
7966
- }
7967
- function getTmpLogPath(filename) {
7968
- return join2(tmpdir(), filename);
7969
- }
7970
- var init_paths = () => {};
7971
-
7972
8133
  // src/lib/self-version.ts
7973
- import { createRequire } from "node:module";
8134
+ import { createRequire as createRequire2 } from "node:module";
7974
8135
  function getSelfVersion() {
7975
- const require2 = createRequire(import.meta.url);
8136
+ const require2 = createRequire2(import.meta.url);
7976
8137
  for (const relPath of ["../../package.json", "../package.json"]) {
7977
8138
  try {
7978
8139
  const version = require2(relPath).version;
@@ -7986,27 +8147,27 @@ function getSelfVersion() {
7986
8147
  var init_self_version = () => {};
7987
8148
 
7988
8149
  // src/adapters/opencode.ts
7989
- import { execSync } from "node:child_process";
7990
- import { existsSync as existsSync3, readFileSync as readFileSync2, rmSync, statSync as statSync2 } from "node:fs";
7991
- import { homedir as homedir2 } from "node:os";
7992
- import { dirname as dirname2, join as join3, parse, resolve } from "node:path";
8150
+ import { execSync as execSync2 } from "node:child_process";
8151
+ import { existsSync as existsSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2 } from "node:fs";
8152
+ import { homedir as homedir3 } from "node:os";
8153
+ import { dirname as dirname2, join as join4, parse, resolve } from "node:path";
7993
8154
  import { fileURLToPath } from "node:url";
7994
8155
  function getOpenCodeConfigDir() {
7995
8156
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
7996
8157
  if (envDir)
7997
8158
  return resolve(envDir);
7998
- const xdg = process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config");
7999
- return join3(xdg, "opencode");
8159
+ const xdg = process.env.XDG_CONFIG_HOME || join4(homedir3(), ".config");
8160
+ return join4(xdg, "opencode");
8000
8161
  }
8001
8162
  function getOpenCodeCacheDir() {
8002
8163
  const xdg = process.env.XDG_CACHE_HOME;
8003
8164
  if (xdg)
8004
- return join3(xdg, "opencode");
8165
+ return join4(xdg, "opencode");
8005
8166
  if (process.platform === "win32") {
8006
- const localAppData = process.env.LOCALAPPDATA ?? join3(homedir2(), "AppData", "Local");
8007
- return join3(localAppData, "opencode");
8167
+ const localAppData = process.env.LOCALAPPDATA ?? join4(homedir3(), "AppData", "Local");
8168
+ return join4(localAppData, "opencode");
8008
8169
  }
8009
- return join3(homedir2(), ".cache", "opencode");
8170
+ return join4(homedir3(), ".cache", "opencode");
8010
8171
  }
8011
8172
  function pathFromEntry(entry) {
8012
8173
  if (entry.startsWith("file://")) {
@@ -8025,13 +8186,13 @@ function pathPointsToOurPlugin(entry) {
8025
8186
  if (!fsPath)
8026
8187
  return false;
8027
8188
  try {
8028
- if (!existsSync3(fsPath))
8189
+ if (!existsSync4(fsPath))
8029
8190
  return false;
8030
8191
  let searchDir = statSync2(fsPath).isDirectory() ? fsPath : dirname2(fsPath);
8031
8192
  let pkgJsonPath = null;
8032
8193
  while (true) {
8033
- const candidate = join3(searchDir, "package.json");
8034
- if (existsSync3(candidate)) {
8194
+ const candidate = join4(searchDir, "package.json");
8195
+ if (existsSync4(candidate)) {
8035
8196
  pkgJsonPath = candidate;
8036
8197
  break;
8037
8198
  }
@@ -8063,7 +8224,7 @@ class OpenCodeAdapter {
8063
8224
  pluginEntryWithVersion = PLUGIN_ENTRY;
8064
8225
  isInstalled() {
8065
8226
  try {
8066
- execSync("opencode --version", { stdio: "ignore" });
8227
+ execSync2("opencode --version", { stdio: "ignore" });
8067
8228
  return true;
8068
8229
  } catch {
8069
8230
  return false;
@@ -8071,7 +8232,7 @@ class OpenCodeAdapter {
8071
8232
  }
8072
8233
  getHostVersion() {
8073
8234
  try {
8074
- return execSync("opencode --version", { encoding: "utf-8", stdio: "pipe" }).trim();
8235
+ return execSync2("opencode --version", { encoding: "utf-8", stdio: "pipe" }).trim();
8075
8236
  } catch {
8076
8237
  return null;
8077
8238
  }
@@ -8140,11 +8301,11 @@ class OpenCodeAdapter {
8140
8301
  };
8141
8302
  }
8142
8303
  getPluginCacheInfo() {
8143
- const path = join3(getOpenCodeCacheDir(), "packages", PLUGIN_ENTRY);
8304
+ const path = join4(getOpenCodeCacheDir(), "packages", PLUGIN_ENTRY);
8144
8305
  let cached;
8145
8306
  try {
8146
- const installedPkgPath = join3(path, "node_modules", "@cortexkit", "aft-opencode", "package.json");
8147
- if (existsSync3(installedPkgPath)) {
8307
+ const installedPkgPath = join4(path, "node_modules", "@cortexkit", "aft-opencode", "package.json");
8308
+ if (existsSync4(installedPkgPath)) {
8148
8309
  const pkg = JSON.parse(readFileSync2(installedPkgPath, "utf-8"));
8149
8310
  cached = typeof pkg.version === "string" ? pkg.version : undefined;
8150
8311
  }
@@ -8155,7 +8316,7 @@ class OpenCodeAdapter {
8155
8316
  path,
8156
8317
  cached,
8157
8318
  latest: getSelfVersion(),
8158
- exists: existsSync3(path)
8319
+ exists: existsSync4(path)
8159
8320
  };
8160
8321
  }
8161
8322
  getStorageDir() {
@@ -8204,11 +8365,11 @@ class OpenCodeAdapter {
8204
8365
  describeStorageSubtrees() {
8205
8366
  const storage = this.getStorageDir();
8206
8367
  return {
8207
- index: dirSize(join3(storage, "index")),
8208
- semantic: dirSize(join3(storage, "semantic")),
8209
- backups: dirSize(join3(storage, "backups")),
8210
- url_cache: dirSize(join3(storage, "url_cache")),
8211
- onnxruntime: dirSize(join3(storage, "onnxruntime"))
8368
+ index: dirSize(join4(storage, "index")),
8369
+ semantic: dirSize(join4(storage, "semantic")),
8370
+ backups: dirSize(join4(storage, "backups")),
8371
+ url_cache: dirSize(join4(storage, "url_cache")),
8372
+ onnxruntime: dirSize(join4(storage, "onnxruntime"))
8212
8373
  };
8213
8374
  }
8214
8375
  }
@@ -8222,18 +8383,18 @@ var init_opencode = __esm(() => {
8222
8383
  });
8223
8384
 
8224
8385
  // src/adapters/pi.ts
8225
- import { execSync as execSync2, spawnSync } from "node:child_process";
8226
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
8227
- import { homedir as homedir3 } from "node:os";
8228
- import { join as join4 } from "node:path";
8229
- function getPiAgentDir() {
8386
+ import { execSync as execSync3, spawnSync as spawnSync2 } from "node:child_process";
8387
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
8388
+ import { homedir as homedir4 } from "node:os";
8389
+ import { join as join5 } from "node:path";
8390
+ function getPiAgentDir() {
8230
8391
  const envHome = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
8231
- const home = envHome && envHome.length > 0 ? envHome : homedir3();
8232
- return join4(home, ".pi", "agent");
8392
+ const home = envHome && envHome.length > 0 ? envHome : homedir4();
8393
+ return join5(home, ".pi", "agent");
8233
8394
  }
8234
8395
  function readPiExtensionIndex() {
8235
- const settingsPath = join4(getPiAgentDir(), "settings.json");
8236
- if (existsSync4(settingsPath)) {
8396
+ const settingsPath = join5(getPiAgentDir(), "settings.json");
8397
+ if (existsSync5(settingsPath)) {
8237
8398
  try {
8238
8399
  const raw = readFileSync3(settingsPath, "utf-8");
8239
8400
  const trimmed = raw.replace(/^\uFEFF/, "");
@@ -8246,13 +8407,13 @@ function readPiExtensionIndex() {
8246
8407
  } catch {}
8247
8408
  }
8248
8409
  const candidates = [
8249
- join4(getPiAgentDir(), "extensions.json"),
8250
- join4(getPiAgentDir(), "extensions.jsonc"),
8251
- join4(getPiAgentDir(), "config.json"),
8252
- join4(getPiAgentDir(), "config.jsonc")
8410
+ join5(getPiAgentDir(), "extensions.json"),
8411
+ join5(getPiAgentDir(), "extensions.jsonc"),
8412
+ join5(getPiAgentDir(), "config.json"),
8413
+ join5(getPiAgentDir(), "config.jsonc")
8253
8414
  ];
8254
8415
  for (const path of candidates) {
8255
- if (!existsSync4(path))
8416
+ if (!existsSync5(path))
8256
8417
  continue;
8257
8418
  try {
8258
8419
  const { value } = readJsoncFile(path);
@@ -8285,15 +8446,15 @@ function piEntryMatchesAft(entry) {
8285
8446
  } else if (entry.startsWith("/")) {
8286
8447
  resolved = entry;
8287
8448
  } else if (entry.length > 0) {
8288
- resolved = join4(getPiAgentDir(), entry);
8449
+ resolved = join5(getPiAgentDir(), entry);
8289
8450
  }
8290
8451
  if (!resolved)
8291
8452
  return false;
8292
8453
  try {
8293
- if (!existsSync4(resolved))
8454
+ if (!existsSync5(resolved))
8294
8455
  return false;
8295
- const pkgPath = join4(resolved, "package.json");
8296
- if (!existsSync4(pkgPath))
8456
+ const pkgPath = join5(resolved, "package.json");
8457
+ if (!existsSync5(pkgPath))
8297
8458
  return false;
8298
8459
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
8299
8460
  return pkg.name === PLUGIN_NAME2;
@@ -8313,7 +8474,7 @@ class PiAdapter {
8313
8474
  pluginEntryWithVersion = PLUGIN_ENTRY2;
8314
8475
  isInstalled() {
8315
8476
  try {
8316
- execSync2("pi --version", { stdio: "ignore" });
8477
+ execSync3("pi --version", { stdio: "ignore" });
8317
8478
  return true;
8318
8479
  } catch {
8319
8480
  return false;
@@ -8321,7 +8482,7 @@ class PiAdapter {
8321
8482
  }
8322
8483
  getHostVersion() {
8323
8484
  try {
8324
- const result = spawnSync("pi", ["--version"], {
8485
+ const result = spawnSync2("pi", ["--version"], {
8325
8486
  stdio: ["ignore", "pipe", "pipe"],
8326
8487
  encoding: "utf-8"
8327
8488
  });
@@ -8342,7 +8503,7 @@ class PiAdapter {
8342
8503
  const aft = detectJsoncFile(configDir, "aft");
8343
8504
  return {
8344
8505
  configDir,
8345
- harnessConfig: index.path ?? join4(configDir, "extensions.json"),
8506
+ harnessConfig: index.path ?? join5(configDir, "extensions.json"),
8346
8507
  harnessConfigFormat: index.path ? "json" : "none",
8347
8508
  aftConfig: aft.path,
8348
8509
  aftConfigFormat: aft.format
@@ -8369,7 +8530,7 @@ class PiAdapter {
8369
8530
  };
8370
8531
  }
8371
8532
  try {
8372
- execSync2(`pi install ${PLUGIN_ENTRY2}`, { stdio: "inherit" });
8533
+ execSync3(`pi install ${PLUGIN_ENTRY2}`, { stdio: "inherit" });
8373
8534
  return {
8374
8535
  ok: true,
8375
8536
  action: "added",
@@ -8387,11 +8548,11 @@ class PiAdapter {
8387
8548
  }
8388
8549
  getPluginCacheInfo() {
8389
8550
  const candidates = [
8390
- join4(getPiAgentDir(), "node_modules", "@cortexkit", "aft-pi", "package.json"),
8391
- join4(getPiAgentDir(), "extensions", "node_modules", "@cortexkit", "aft-pi", "package.json")
8551
+ join5(getPiAgentDir(), "node_modules", "@cortexkit", "aft-pi", "package.json"),
8552
+ join5(getPiAgentDir(), "extensions", "node_modules", "@cortexkit", "aft-pi", "package.json")
8392
8553
  ];
8393
8554
  for (const candidate of candidates) {
8394
- if (!existsSync4(candidate))
8555
+ if (!existsSync5(candidate))
8395
8556
  continue;
8396
8557
  try {
8397
8558
  const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
@@ -8405,7 +8566,7 @@ class PiAdapter {
8405
8566
  } catch {}
8406
8567
  }
8407
8568
  return {
8408
- path: join4(getPiAgentDir(), "extensions"),
8569
+ path: join5(getPiAgentDir(), "extensions"),
8409
8570
  exists: false
8410
8571
  };
8411
8572
  }
@@ -8427,11 +8588,11 @@ class PiAdapter {
8427
8588
  describeStorageSubtrees() {
8428
8589
  const storage = this.getStorageDir();
8429
8590
  return {
8430
- index: dirSize(join4(storage, "index")),
8431
- semantic: dirSize(join4(storage, "semantic")),
8432
- backups: dirSize(join4(storage, "backups")),
8433
- url_cache: dirSize(join4(storage, "url_cache")),
8434
- onnxruntime: dirSize(join4(storage, "onnxruntime"))
8591
+ index: dirSize(join5(storage, "index")),
8592
+ semantic: dirSize(join5(storage, "semantic")),
8593
+ backups: dirSize(join5(storage, "backups")),
8594
+ url_cache: dirSize(join5(storage, "url_cache")),
8595
+ onnxruntime: dirSize(join5(storage, "onnxruntime"))
8435
8596
  };
8436
8597
  }
8437
8598
  }
@@ -8444,6 +8605,9 @@ var init_pi = __esm(() => {
8444
8605
  });
8445
8606
 
8446
8607
  // src/adapters/index.ts
8608
+ function getAllAdapters() {
8609
+ return ALL;
8610
+ }
8447
8611
  function getAdapter(kind) {
8448
8612
  const found = ALL.find((a) => a.kind === kind);
8449
8613
  if (!found)
@@ -9903,11 +10067,59 @@ async function resolveAdaptersForCommand(argv, options) {
9903
10067
  })), installed.map((a) => a.kind));
9904
10068
  return picks.map((kind) => getAdapter(kind));
9905
10069
  }
10070
+ function getAllRegistryAdapters() {
10071
+ return getAllAdapters();
10072
+ }
9906
10073
  var init_harness_select = __esm(() => {
9907
10074
  init_adapters();
9908
10075
  init_prompts();
9909
10076
  });
9910
10077
 
10078
+ // src/commands/version.ts
10079
+ var exports_version = {};
10080
+ __export(exports_version, {
10081
+ runVersion: () => runVersion
10082
+ });
10083
+ function runVersion() {
10084
+ const cliVersion = getSelfVersion();
10085
+ const binaryVersion = probeBinaryVersion();
10086
+ const lines = [];
10087
+ lines.push("");
10088
+ lines.push(" AFT versions");
10089
+ lines.push("");
10090
+ lines.push(` @cortexkit/aft (this CLI) v${cliVersion}`);
10091
+ lines.push(` aft binary ${binaryVersion ?? "not installed"}`);
10092
+ lines.push("");
10093
+ const adapters = getAllRegistryAdapters();
10094
+ const labelWidth = Math.max(...adapters.map((adapter) => adapter.displayName.length));
10095
+ for (const adapter of adapters) {
10096
+ const label = adapter.displayName.padEnd(labelWidth);
10097
+ if (!adapter.isInstalled()) {
10098
+ lines.push(` ${label} host not installed`);
10099
+ continue;
10100
+ }
10101
+ const host = adapter.getHostVersion() ?? "unknown";
10102
+ const registered = adapter.hasPluginEntry();
10103
+ let pluginPart;
10104
+ if (!registered) {
10105
+ pluginPart = "plugin not registered";
10106
+ } else {
10107
+ const cached = adapter.getPluginCacheInfo().cached;
10108
+ pluginPart = cached ? `plugin ${cached}` : "plugin registered (version unknown)";
10109
+ }
10110
+ lines.push(` ${label} host ${host} · ${pluginPart}`);
10111
+ }
10112
+ lines.push("");
10113
+ console.log(lines.join(`
10114
+ `));
10115
+ return 0;
10116
+ }
10117
+ var init_version = __esm(async () => {
10118
+ init_harness_select();
10119
+ init_self_version();
10120
+ await init_binary_probe();
10121
+ });
10122
+
9911
10123
  // src/commands/setup.ts
9912
10124
  var exports_setup = {};
9913
10125
  __export(exports_setup, {
@@ -10101,167 +10313,6 @@ ${stderrTrimmed}`);
10101
10313
  var MAX_NOISE_LINES_IN_ERROR = 5;
10102
10314
  var init_aft_bridge = () => {};
10103
10315
 
10104
- // src/lib/binary-probe.ts
10105
- import { execSync as execSync3, spawnSync as spawnSync2 } from "node:child_process";
10106
- import { existsSync as existsSync5 } from "node:fs";
10107
- import { createRequire as createRequire2 } from "node:module";
10108
- import { homedir as homedir4 } from "node:os";
10109
- import { join as join5 } from "node:path";
10110
- async function loadPluginVersion() {
10111
- try {
10112
- const bridgePackageName = "@cortexkit/aft-bridge";
10113
- const bridge = await import(bridgePackageName);
10114
- if (typeof bridge.PLUGIN_VERSION === "string" && bridge.PLUGIN_VERSION.length > 0) {
10115
- return bridge.PLUGIN_VERSION;
10116
- }
10117
- } catch {}
10118
- const require2 = createRequire2(import.meta.url);
10119
- for (const relPath of [
10120
- "../../../aft-bridge/package.json",
10121
- "../../package.json",
10122
- "../package.json"
10123
- ]) {
10124
- try {
10125
- const pkg = require2(relPath);
10126
- if (typeof pkg.version === "string" && pkg.version.length > 0)
10127
- return pkg.version;
10128
- } catch {}
10129
- }
10130
- return "unknown";
10131
- }
10132
- function parseVersionOutput(output) {
10133
- for (const line of output.split(/\r?\n/)) {
10134
- const match = line.trim().match(VERSION_LINE);
10135
- if (match?.[1])
10136
- return match[1];
10137
- }
10138
- return null;
10139
- }
10140
- function majorMinor(version) {
10141
- if (!version)
10142
- return null;
10143
- const match = version.trim().match(/^v?(\d+)\.(\d+)\.\d+(?:[-+][0-9A-Za-z.-]+)?$/);
10144
- if (!match)
10145
- return null;
10146
- return `${match[1]}.${match[2]}`;
10147
- }
10148
- function versionMatchesExpected(candidate, expectedVersion) {
10149
- const candidateMajorMinor = majorMinor(candidate);
10150
- const expectedMajorMinor = majorMinor(expectedVersion);
10151
- return candidateMajorMinor !== null && candidateMajorMinor === expectedMajorMinor;
10152
- }
10153
- function probeBinaryVersion(preferredVersion) {
10154
- return probeAftBinary(preferredVersion).version;
10155
- }
10156
- function probeAftBinary(preferredVersion) {
10157
- const expectedVersion = preferredVersion ?? PLUGIN_VERSION;
10158
- const expectedMajorMinor = majorMinor(expectedVersion);
10159
- const candidates = [];
10160
- for (const candidate of aftBinaryCandidates(preferredVersion)) {
10161
- try {
10162
- if (!existsSync5(candidate))
10163
- continue;
10164
- const result = spawnSync2(candidate, ["--version"], {
10165
- stdio: ["ignore", "pipe", "pipe"],
10166
- encoding: "utf-8",
10167
- timeout: 5000,
10168
- env: process.env
10169
- });
10170
- const output = `${result.stdout ?? ""}
10171
- ${result.stderr ?? ""}`.trim();
10172
- if (result.error || result.status !== 0) {
10173
- candidates.push({
10174
- path: candidate,
10175
- status: "error",
10176
- version: null,
10177
- ...output ? { output } : {},
10178
- error: result.error?.message ?? `exit status ${result.status ?? "unknown"}`
10179
- });
10180
- continue;
10181
- }
10182
- const version = parseVersionOutput(output);
10183
- if (!version) {
10184
- candidates.push({ path: candidate, status: "invalid", version: null, output });
10185
- continue;
10186
- }
10187
- if (!versionMatchesExpected(version, expectedVersion)) {
10188
- candidates.push({ path: candidate, status: "unmatched", version, output });
10189
- continue;
10190
- }
10191
- candidates.push({ path: candidate, status: "matched", version, output });
10192
- return { version, path: candidate, expectedVersion, expectedMajorMinor, candidates };
10193
- } catch (error) {
10194
- candidates.push({
10195
- path: candidate,
10196
- status: "error",
10197
- version: null,
10198
- error: error instanceof Error ? error.message : String(error)
10199
- });
10200
- }
10201
- }
10202
- return { version: null, path: null, expectedVersion, expectedMajorMinor, candidates };
10203
- }
10204
- function pushCandidate(candidates, candidate) {
10205
- if (!candidate)
10206
- return;
10207
- if (!candidates.includes(candidate))
10208
- candidates.push(candidate);
10209
- }
10210
- function firstExisting(candidates) {
10211
- for (const candidate of candidates) {
10212
- try {
10213
- if (!existsSync5(candidate))
10214
- continue;
10215
- return candidate;
10216
- } catch {}
10217
- }
10218
- return null;
10219
- }
10220
- function platformKey(platform = process.platform, arch = process.arch) {
10221
- const table = {
10222
- darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
10223
- linux: { arm64: "linux-arm64", x64: "linux-x64" },
10224
- win32: { x64: "win32-x64" }
10225
- };
10226
- return table[platform]?.[arch] ?? null;
10227
- }
10228
- function aftBinaryCandidates(preferredVersion) {
10229
- const candidates = [];
10230
- if (preferredVersion) {
10231
- const tag = preferredVersion.startsWith("v") ? preferredVersion : `v${preferredVersion}`;
10232
- pushCandidate(candidates, join5(getAftBinaryCacheDir(), tag, getAftBinaryName()));
10233
- }
10234
- const key = platformKey();
10235
- if (key) {
10236
- try {
10237
- const require2 = createRequire2(import.meta.url);
10238
- pushCandidate(candidates, require2.resolve(`@cortexkit/aft-${key}/bin/${getAftBinaryName()}`));
10239
- } catch {}
10240
- }
10241
- try {
10242
- const lookup = process.platform === "win32" ? "where aft" : "which aft";
10243
- const resolved = execSync3(lookup, {
10244
- stdio: "pipe",
10245
- encoding: "utf-8",
10246
- env: process.env
10247
- }).trim();
10248
- if (resolved) {
10249
- pushCandidate(candidates, resolved.split(/\r?\n/)[0]);
10250
- }
10251
- } catch {}
10252
- pushCandidate(candidates, join5(homedir4(), ".cargo", "bin", getAftBinaryName()));
10253
- return candidates;
10254
- }
10255
- function findAftBinary(preferredVersion) {
10256
- return firstExisting(aftBinaryCandidates(preferredVersion));
10257
- }
10258
- var PLUGIN_VERSION, VERSION_LINE;
10259
- var init_binary_probe = __esm(async () => {
10260
- init_paths();
10261
- PLUGIN_VERSION = await loadPluginVersion();
10262
- VERSION_LINE = /^(?:aft\s+)?v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/i;
10263
- });
10264
-
10265
10316
  // src/commands/lsp.ts
10266
10317
  var exports_lsp = {};
10267
10318
  __export(exports_lsp, {
@@ -10929,7 +10980,7 @@ var init_lsp_cache = __esm(() => {
10929
10980
 
10930
10981
  // src/lib/onnx.ts
10931
10982
  import { existsSync as existsSync10, readdirSync as readdirSync5, readlinkSync, realpathSync, statSync as statSync6 } from "node:fs";
10932
- import { isAbsolute, join as join9, resolve as resolve4, win32 } from "node:path";
10983
+ import { basename, isAbsolute, join as join9, resolve as resolve4, win32 } from "node:path";
10933
10984
  function getOnnxLibraryName() {
10934
10985
  if (process.platform === "darwin")
10935
10986
  return "libonnxruntime.dylib";
@@ -10992,10 +11043,29 @@ function findSystemOnnxRuntime() {
10992
11043
  searchPaths.push(...pathEntriesForPlatform());
10993
11044
  const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
10994
11045
  const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
10995
- searchPaths.push(join9(programFiles, "onnxruntime", "lib"), join9(programFiles, "Microsoft ONNX Runtime", "lib"), join9(programFiles, "Microsoft Machine Learning", "lib"), join9(programFilesX86, "onnxruntime", "lib"));
11046
+ searchPaths.push(join9(programFiles, "onnxruntime", "lib"), join9(programFiles, "Microsoft ONNX Runtime", "lib"), join9(programFiles, "Microsoft Machine Learning", "lib"), join9(programFilesX86, "onnxruntime", "lib"), ...(() => {
11047
+ const nugetPaths = [];
11048
+ const userProfile = process.env.USERPROFILE ?? "";
11049
+ if (!userProfile)
11050
+ return nugetPaths;
11051
+ const nugetPackageDir = join9(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
11052
+ if (!existsSync10(nugetPackageDir))
11053
+ return nugetPaths;
11054
+ try {
11055
+ for (const entry of readdirSync5(nugetPackageDir, { withFileTypes: true })) {
11056
+ if (!entry.isDirectory())
11057
+ continue;
11058
+ if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
11059
+ continue;
11060
+ nugetPaths.push(join9(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join9(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
11061
+ }
11062
+ } catch {}
11063
+ return nugetPaths;
11064
+ })());
10996
11065
  }
10997
11066
  const normalizeCase = process.platform === "win32" || process.platform === "darwin";
10998
11067
  const seen = new Set;
11068
+ const unknownVersionPaths = [];
10999
11069
  for (const dir of searchPaths) {
11000
11070
  let key = resolve4(dir).replace(/[/\\]+$/, "");
11001
11071
  if (normalizeCase)
@@ -11003,14 +11073,44 @@ function findSystemOnnxRuntime() {
11003
11073
  if (seen.has(key))
11004
11074
  continue;
11005
11075
  seen.add(key);
11006
- if (directoryContainsLibrary(dir, libName))
11007
- return dir;
11076
+ if (!directoryContainsLibrary(dir, libName))
11077
+ continue;
11078
+ const version = detectOrtVersion(dir);
11079
+ if (!version) {
11080
+ unknownVersionPaths.push(dir);
11081
+ continue;
11082
+ }
11083
+ if (!isOrtVersionCompatible(version))
11084
+ continue;
11085
+ return dir;
11008
11086
  }
11009
- return null;
11087
+ return unknownVersionPaths[0] ?? null;
11010
11088
  }
11011
11089
  function findCachedOnnxRuntime(storageDir) {
11012
11090
  const ortDir = join9(storageDir, "onnxruntime", ONNX_RUNTIME_VERSION);
11013
- return existsSync10(join9(ortDir, getOnnxLibraryName())) ? ortDir : null;
11091
+ const libName = getOnnxLibraryName();
11092
+ if (existsSync10(join9(ortDir, libName)))
11093
+ return ortDir;
11094
+ const libSubdir = join9(ortDir, "lib");
11095
+ if (existsSync10(join9(libSubdir, libName)))
11096
+ return libSubdir;
11097
+ return null;
11098
+ }
11099
+ function parseOrtVersionFromPath(value) {
11100
+ const name = basename(value);
11101
+ const semverish = name.match(/(?:^|[._-])(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)(?:\.(?:dylib|dll))?$/);
11102
+ if (semverish)
11103
+ return semverish[1].split(/[-+]/, 1)[0];
11104
+ return /\d+\.\d+\.\d+/.test(name) ? INVALID_ORT_VERSION : null;
11105
+ }
11106
+ function parseOrtVersionFromDirectoryPath(value) {
11107
+ const parts = value.split(/[\\/]+/).filter(Boolean).reverse();
11108
+ for (const part of parts) {
11109
+ const version = parseOrtVersionFromPath(part);
11110
+ if (version)
11111
+ return version;
11112
+ }
11113
+ return null;
11014
11114
  }
11015
11115
  function detectOrtVersion(libDir) {
11016
11116
  if (!existsSync10(libDir))
@@ -11018,28 +11118,32 @@ function detectOrtVersion(libDir) {
11018
11118
  const libName = getOnnxLibraryName();
11019
11119
  try {
11020
11120
  const entries = readdirSync5(libDir);
11121
+ const barePrefix = libName.replace(/\.(so|dylib|dll)$/, "");
11122
+ const expectedPrefix = process.platform === "win32" ? barePrefix.toLowerCase() : barePrefix;
11021
11123
  for (const entry of entries) {
11022
- if (!entry.startsWith(libName))
11124
+ const comparable = process.platform === "win32" ? entry.toLowerCase() : entry;
11125
+ if (!comparable.startsWith(expectedPrefix))
11023
11126
  continue;
11024
- const match = entry.match(/\.(\d+\.\d+\.\d+)$/);
11025
- if (match)
11026
- return match[1];
11127
+ const version = parseOrtVersionFromPath(entry);
11128
+ if (version)
11129
+ return version;
11027
11130
  }
11028
11131
  const base = join9(libDir, libName);
11029
11132
  if (existsSync10(base)) {
11030
11133
  try {
11031
11134
  const real = realpathSync(base);
11032
- const suffix = real.match(/\.(\d+\.\d+\.\d+)$/);
11033
- if (suffix)
11034
- return suffix[1];
11135
+ const version = parseOrtVersionFromPath(real) ?? parseOrtVersionFromDirectoryPath(real);
11136
+ if (version)
11137
+ return version;
11035
11138
  } catch {}
11036
11139
  try {
11037
11140
  const target = readlinkSync(base);
11038
- const suffix = target.match(/\.(\d+\.\d+\.\d+)$/);
11039
- if (suffix)
11040
- return suffix[1];
11141
+ const version = parseOrtVersionFromPath(target);
11142
+ if (version)
11143
+ return version;
11041
11144
  } catch {}
11042
11145
  }
11146
+ return parseOrtVersionFromDirectoryPath(libDir);
11043
11147
  } catch {}
11044
11148
  return null;
11045
11149
  }
@@ -11052,18 +11156,50 @@ function isOrtVersionCompatible(version) {
11052
11156
  return false;
11053
11157
  return minor >= REQUIRED_ORT_MIN_MINOR;
11054
11158
  }
11055
- var ONNX_RUNTIME_VERSION = "1.24.4", REQUIRED_ORT_MAJOR = 1, REQUIRED_ORT_MIN_MINOR = 20;
11159
+ var ONNX_RUNTIME_VERSION = "1.24.4", INVALID_ORT_VERSION = "<invalid>", REQUIRED_ORT_MAJOR = 1, REQUIRED_ORT_MIN_MINOR = 20;
11056
11160
  var init_onnx = () => {};
11057
11161
 
11058
11162
  // src/lib/sanitize.ts
11163
+ import { realpathSync as realpathSync2 } from "node:fs";
11059
11164
  import { homedir as homedir6, userInfo } from "node:os";
11060
11165
  function escapeRegex(value) {
11061
11166
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11062
11167
  }
11168
+ function safeRealpath(p2) {
11169
+ try {
11170
+ return realpathSync2(p2);
11171
+ } catch {
11172
+ return null;
11173
+ }
11174
+ }
11175
+ function isSensitiveKeyName(keyName) {
11176
+ return SENSITIVE_KEY_WORD.test(keyName) || SEGMENTED_KEY_WORD.test(keyName) || CAMEL_CASE_KEY_WORD.test(keyName);
11177
+ }
11178
+ function redactSecrets(content) {
11179
+ let sanitized = content;
11180
+ sanitized = sanitized.replace(/\b((?:Proxy-)?Authorization[^\S\r\n]*:[^\S\r\n]*(?:Bearer|Basic)[^\S\r\n]+)[A-Za-z0-9._~+/-]+=*/gi, `$1${SECRET_PLACEHOLDER}`);
11181
+ sanitized = sanitized.replace(/\bgithub_pat_[A-Za-z0-9_]+\b/g, SECRET_PLACEHOLDER);
11182
+ sanitized = sanitized.replace(/\bgh(?:p|o|s)_[A-Za-z0-9_]{16,}\b/g, SECRET_PLACEHOLDER);
11183
+ sanitized = sanitized.replace(/\bsk-(?:live-)?[A-Za-z0-9][A-Za-z0-9_-]{7,}\b/g, SECRET_PLACEHOLDER);
11184
+ sanitized = sanitized.replace(JWT_PATTERN, SECRET_PLACEHOLDER);
11185
+ sanitized = sanitized.replace(AWS_ACCESS_KEY_ID_PATTERN, SECRET_PLACEHOLDER);
11186
+ sanitized = sanitized.replace(quotedSensitiveKeyValuePattern, (match, prefix, _keyQuote, keyName, valueQuote) => isSensitiveKeyName(keyName) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
11187
+ sanitized = sanitized.replace(unquotedSensitiveKeyValuePattern, (match, prefix, keyName, valueQuote) => isSensitiveKeyName(keyName) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
11188
+ sanitized = sanitized.replace(bareSensitiveKeyValuePattern, (match, prefix, keyName) => isSensitiveKeyName(keyName) ? `${prefix}${SECRET_PLACEHOLDER}` : match);
11189
+ sanitized = sanitized.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^@\s/?#]+@/gi, `$1${URL_CREDENTIAL_PLACEHOLDER}@`);
11190
+ sanitized = sanitized.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "<EMAIL>");
11191
+ return sanitized;
11192
+ }
11063
11193
  function sanitizeContent(content) {
11064
11194
  const username = userInfo().username;
11065
11195
  const home = homedir6();
11066
- let sanitized = content;
11196
+ let sanitized = redactSecrets(content);
11197
+ const cwd = process.cwd();
11198
+ for (const candidate of new Set([cwd, safeRealpath(cwd)])) {
11199
+ if (candidate && candidate !== "/" && candidate !== home) {
11200
+ sanitized = sanitized.replace(new RegExp(escapeRegex(candidate), "g"), "<PROJECT>");
11201
+ }
11202
+ }
11067
11203
  if (home) {
11068
11204
  sanitized = sanitized.replace(new RegExp(escapeRegex(home), "g"), "~");
11069
11205
  }
@@ -11088,7 +11224,17 @@ function sanitizeValue(value) {
11088
11224
  }
11089
11225
  return value;
11090
11226
  }
11091
- var init_sanitize = () => {};
11227
+ var SECRET_PLACEHOLDER = "<REDACTED_SECRET>", URL_CREDENTIAL_PLACEHOLDER = "***", KEY_NAME = "[A-Za-z_][A-Za-z0-9_.-]*", SENSITIVE_KEY_WORD, SEGMENTED_KEY_WORD, CAMEL_CASE_KEY_WORD, JWT_PATTERN, AWS_ACCESS_KEY_ID_PATTERN, quotedSensitiveKeyValuePattern, unquotedSensitiveKeyValuePattern, bareSensitiveKeyValuePattern;
11228
+ var init_sanitize = __esm(() => {
11229
+ SENSITIVE_KEY_WORD = /(?:token|password|secret|api[_-]?key|passwd|pwd|credential)/i;
11230
+ SEGMENTED_KEY_WORD = /(?:^|[_.-])key(?:$|[_.-])/i;
11231
+ CAMEL_CASE_KEY_WORD = /[a-z0-9]Key(?:$|[A-Z_.-])/;
11232
+ JWT_PATTERN = /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
11233
+ AWS_ACCESS_KEY_ID_PATTERN = /\b(?:AKIA|ASIA|AGPA|AIDA|AROA)[A-Z0-9]{16}\b/g;
11234
+ quotedSensitiveKeyValuePattern = new RegExp(String.raw`((['"])(${KEY_NAME})\2[^\S\r\n]*:[^\S\r\n]*)(['"])([^'"\r\n]+)\4`, "gi");
11235
+ unquotedSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)(['"])([^'"\r\n]+)\3`, "gi");
11236
+ bareSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)([^\s,;&'"]+)`, "gi");
11237
+ });
11092
11238
 
11093
11239
  // src/lib/diagnostics.ts
11094
11240
  import {
@@ -11457,6 +11603,12 @@ function createGitHubIssue(repo, title, body) {
11457
11603
  var init_github = () => {};
11458
11604
 
11459
11605
  // src/lib/issue-body.ts
11606
+ function filterLogToSession(logText, sessionId) {
11607
+ const bareId = sessionId.replace(/^ses_/, "");
11608
+ const keepTokens = [`[ses_${bareId}]`, `[${bareId}]`];
11609
+ return logText.split(/\r?\n/).filter((line) => !SESSION_TAG_PATTERN.test(line) || keepTokens.some((token) => line.includes(token))).join(`
11610
+ `);
11611
+ }
11460
11612
  function isErrorLogLine(line) {
11461
11613
  return ERROR_LOG_PATTERNS.some((rx) => rx.test(line));
11462
11614
  }
@@ -11534,8 +11686,9 @@ function truncateToByteBudget(input, maxBytes) {
11534
11686
  }
11535
11687
  return buf.subarray(0, end).toString("utf8");
11536
11688
  }
11537
- var MAX_GITHUB_BODY_BYTES = 60000, ERROR_LOG_PATTERNS;
11689
+ var MAX_GITHUB_BODY_BYTES = 60000, SESSION_TAG_PATTERN, ERROR_LOG_PATTERNS;
11538
11690
  var init_issue_body = __esm(() => {
11691
+ SESSION_TAG_PATTERN = /\[ses_[^\]\s]+\]|\[[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]/;
11539
11692
  ERROR_LOG_PATTERNS = [
11540
11693
  /\bcrashed:/i,
11541
11694
  /\bfailed:/i,
@@ -11631,6 +11784,189 @@ var init_onnx_fix = __esm(() => {
11631
11784
  init_prompts();
11632
11785
  });
11633
11786
 
11787
+ // src/lib/sessions.ts
11788
+ import { existsSync as existsSync13, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync8 } from "node:fs";
11789
+ import { createRequire as createRequire3 } from "node:module";
11790
+ import { homedir as homedir7 } from "node:os";
11791
+ import { basename as basename2, join as join11 } from "node:path";
11792
+ function listRecentSessions(adapter) {
11793
+ try {
11794
+ if (adapter.kind === "opencode")
11795
+ return listRecentOpenCodeSessions();
11796
+ if (adapter.kind === "pi")
11797
+ return listRecentPiSessions();
11798
+ return [];
11799
+ } catch {
11800
+ return [];
11801
+ }
11802
+ }
11803
+ function mapOpenCodeSessionRows(rows) {
11804
+ return rows.map((row) => {
11805
+ if (typeof row.id !== "string" || row.id.length === 0)
11806
+ return null;
11807
+ if (typeof row.title !== "string" || row.title.length === 0)
11808
+ return null;
11809
+ const lastActivity = typeof row.time_updated === "number" ? row.time_updated : Number(row.time_updated);
11810
+ if (!Number.isFinite(lastActivity))
11811
+ return null;
11812
+ return {
11813
+ id: row.id,
11814
+ title: row.title,
11815
+ lastActivity
11816
+ };
11817
+ }).filter((session) => session !== null).sort((a, b) => b.lastActivity - a.lastActivity).slice(0, MAX_RECENT_SESSIONS);
11818
+ }
11819
+ function listRecentOpenCodeSessions() {
11820
+ const dbPath = join11(getXdgDataHome(), "opencode", "opencode.db");
11821
+ if (!existsSync13(dbPath))
11822
+ return [];
11823
+ let db = null;
11824
+ try {
11825
+ const require2 = createRequire3(import.meta.url);
11826
+ const sqlite = require2("node:sqlite");
11827
+ db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
11828
+ const rows = db.prepare("SELECT id, title, time_updated FROM session ORDER BY time_updated DESC LIMIT 5").all();
11829
+ return mapOpenCodeSessionRows(rows);
11830
+ } catch {
11831
+ return [];
11832
+ } finally {
11833
+ try {
11834
+ db?.close();
11835
+ } catch {}
11836
+ }
11837
+ }
11838
+ function getXdgDataHome() {
11839
+ const xdgDataHome = process.env.XDG_DATA_HOME;
11840
+ return xdgDataHome && xdgDataHome.length > 0 ? xdgDataHome : join11(homedir7(), ".local", "share");
11841
+ }
11842
+ function listRecentPiSessions() {
11843
+ return listPiSessionsFromDir(join11(getHomeDir(), ".pi", "agent", "sessions"));
11844
+ }
11845
+ function getHomeDir() {
11846
+ const envHome = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
11847
+ return envHome && envHome.length > 0 ? envHome : homedir7();
11848
+ }
11849
+ function listPiSessionsFromDir(sessionsDir) {
11850
+ try {
11851
+ if (!existsSync13(sessionsDir))
11852
+ return [];
11853
+ const files = collectJsonlFiles(sessionsDir).map((filePath) => {
11854
+ try {
11855
+ const stats = statSync8(filePath);
11856
+ return { filePath, mtimeMs: stats.mtimeMs };
11857
+ } catch {
11858
+ return null;
11859
+ }
11860
+ }).filter((entry) => entry !== null).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RECENT_SESSIONS * 4);
11861
+ const sessions = [];
11862
+ for (const file of files) {
11863
+ const parsed = parsePiSessionJsonl(readFileSync4(file.filePath, "utf8"), basename2(file.filePath));
11864
+ if (!parsed)
11865
+ continue;
11866
+ sessions.push({ ...parsed, lastActivity: file.mtimeMs });
11867
+ if (sessions.length >= MAX_RECENT_SESSIONS)
11868
+ break;
11869
+ }
11870
+ return sessions;
11871
+ } catch {
11872
+ return [];
11873
+ }
11874
+ }
11875
+ function collectJsonlFiles(root) {
11876
+ const files = [];
11877
+ const stack = [root];
11878
+ while (stack.length > 0) {
11879
+ const dir = stack.pop();
11880
+ if (!dir)
11881
+ continue;
11882
+ let entries;
11883
+ try {
11884
+ entries = readdirSync6(dir, { withFileTypes: true });
11885
+ } catch {
11886
+ continue;
11887
+ }
11888
+ for (const entry of entries) {
11889
+ const path = join11(dir, entry.name);
11890
+ if (entry.isDirectory()) {
11891
+ stack.push(path);
11892
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
11893
+ files.push(path);
11894
+ }
11895
+ }
11896
+ }
11897
+ return files;
11898
+ }
11899
+ function parsePiSessionJsonl(jsonl, fallbackFilename = "") {
11900
+ let id = extractUuidFromFilename(fallbackFilename);
11901
+ let title = null;
11902
+ for (const line of jsonl.split(/\r?\n/)) {
11903
+ const trimmed = line.trim();
11904
+ if (trimmed.length === 0)
11905
+ continue;
11906
+ let value;
11907
+ try {
11908
+ value = JSON.parse(trimmed);
11909
+ } catch {
11910
+ continue;
11911
+ }
11912
+ if (!value || typeof value !== "object")
11913
+ continue;
11914
+ const record = value;
11915
+ if (record.type === "session" && typeof record.id === "string" && record.id.length > 0) {
11916
+ id = record.id;
11917
+ }
11918
+ if (title === null) {
11919
+ const maybeTitle = extractPiUserMessageText(record);
11920
+ if (maybeTitle)
11921
+ title = truncateTitle(maybeTitle);
11922
+ }
11923
+ if (id && title)
11924
+ break;
11925
+ }
11926
+ if (!id)
11927
+ return null;
11928
+ return { id, title: title ?? id };
11929
+ }
11930
+ function extractPiUserMessageText(record) {
11931
+ if (record.type !== "message")
11932
+ return null;
11933
+ const message = record.message;
11934
+ if (!message || typeof message !== "object")
11935
+ return null;
11936
+ const messageRecord = message;
11937
+ if (messageRecord.role !== "user")
11938
+ return null;
11939
+ return extractTextFromContent(messageRecord.content);
11940
+ }
11941
+ function extractTextFromContent(content) {
11942
+ if (typeof content === "string")
11943
+ return content.trim() || null;
11944
+ if (!Array.isArray(content))
11945
+ return null;
11946
+ const parts = content.map((part) => {
11947
+ if (typeof part === "string")
11948
+ return part;
11949
+ if (!part || typeof part !== "object")
11950
+ return "";
11951
+ const partRecord = part;
11952
+ return partRecord.type === "text" && typeof partRecord.text === "string" ? partRecord.text : "";
11953
+ }).filter((text2) => text2.trim().length > 0);
11954
+ const joined = parts.join(" ").trim();
11955
+ return joined.length > 0 ? joined : null;
11956
+ }
11957
+ function extractUuidFromFilename(filename) {
11958
+ const match = filename.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i);
11959
+ return match?.[1] ?? null;
11960
+ }
11961
+ function truncateTitle(title, maxLength = 60) {
11962
+ const normalized = title.replace(/\s+/g, " ").trim();
11963
+ if (normalized.length <= maxLength)
11964
+ return normalized;
11965
+ return `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
11966
+ }
11967
+ var MAX_RECENT_SESSIONS = 5;
11968
+ var init_sessions = () => {};
11969
+
11634
11970
  // src/commands/doctor.ts
11635
11971
  var exports_doctor = {};
11636
11972
  __export(exports_doctor, {
@@ -11640,14 +11976,26 @@ __export(exports_doctor, {
11640
11976
  formatDoctorStorageStatus: () => formatDoctorStorageStatus,
11641
11977
  fixPluginEntries: () => fixPluginEntries,
11642
11978
  doctorSkewBinaryDownloadDecision: () => doctorSkewBinaryDownloadDecision,
11979
+ deriveIssueTitleFromBody: () => deriveIssueTitleFromBody,
11643
11980
  clearOldBinaries: () => clearOldBinaries,
11644
11981
  clearDoctorCaches: () => clearDoctorCaches,
11645
11982
  buildDoctorFixPlan: () => buildDoctorFixPlan,
11646
11983
  DOCTOR_FORCE_CLEAR_TARGETS: () => DOCTOR_FORCE_CLEAR_TARGETS,
11647
11984
  DOCTOR_CLEAR_TARGET_OPTIONS: () => DOCTOR_CLEAR_TARGET_OPTIONS
11648
11985
  });
11649
- import { existsSync as existsSync13, mkdirSync as mkdirSync2, rmSync as rmSync4, statSync as statSync8, writeFileSync as writeFileSync2 } from "node:fs";
11650
- import { join as join11 } from "node:path";
11986
+ import {
11987
+ chmodSync,
11988
+ existsSync as existsSync14,
11989
+ mkdirSync as mkdirSync2,
11990
+ mkdtempSync,
11991
+ readFileSync as readFileSync5,
11992
+ realpathSync as realpathSync3,
11993
+ rmSync as rmSync4,
11994
+ statSync as statSync9,
11995
+ writeFileSync as writeFileSync2
11996
+ } from "node:fs";
11997
+ import { tmpdir as tmpdir2 } from "node:os";
11998
+ import { join as join12 } from "node:path";
11651
11999
  async function runDoctor(options) {
11652
12000
  if (options.issue) {
11653
12001
  return runIssueFlow(options.argv);
@@ -11660,7 +12008,7 @@ async function runDoctor(options) {
11660
12008
  return runClearFlow(options.argv);
11661
12009
  }
11662
12010
  const adapters = await resolveAdaptersForCommand(options.argv, {
11663
- allowMulti: true,
12011
+ allowMulti: false,
11664
12012
  verb: "diagnose"
11665
12013
  });
11666
12014
  const report = await collectDiagnostics(adapters);
@@ -11691,6 +12039,10 @@ async function runDoctor(options) {
11691
12039
  O2.info(` aft config: ${h.aftConfig.exists ? h.configPaths.aftConfig : "(not set)"}`);
11692
12040
  if (h.aftConfig.parseError) {
11693
12041
  O2.error(` aft config parse error: ${h.aftConfig.parseError}`);
12042
+ } else if (h.aftConfig.exists) {
12043
+ const { value } = readJsoncFile(h.configPaths.aftConfig);
12044
+ const schemaSet = value?.$schema === AFT_SCHEMA_URL;
12045
+ O2.info(` aft config $schema: ${schemaSet ? "set" : "not set — run `aft doctor --fix` for editor autocomplete"}`);
11694
12046
  }
11695
12047
  O2.info(` storage: ${formatDoctorStorageStatus(h)}`);
11696
12048
  if (h.onnxRuntime.required) {
@@ -11797,7 +12149,7 @@ function clearOldBinaries() {
11797
12149
  errors: [],
11798
12150
  keptVersion: keepTag
11799
12151
  };
11800
- if (!existsSync13(info.path)) {
12152
+ if (!existsSync14(info.path)) {
11801
12153
  O2.info(`Binary cache: nothing to clear at ${info.path}`);
11802
12154
  return result;
11803
12155
  }
@@ -11807,10 +12159,10 @@ function clearOldBinaries() {
11807
12159
  return result;
11808
12160
  }
11809
12161
  for (const version of stale) {
11810
- const dir = join11(info.path, version);
12162
+ const dir = join12(info.path, version);
11811
12163
  let bytes = 0;
11812
12164
  try {
11813
- bytes = statSync8(dir).isDirectory() ? dirSize(dir) : 0;
12165
+ bytes = statSync9(dir).isDirectory() ? dirSize(dir) : 0;
11814
12166
  } catch {
11815
12167
  bytes = 0;
11816
12168
  }
@@ -11830,6 +12182,42 @@ function clearOldBinaries() {
11830
12182
  }
11831
12183
  return result;
11832
12184
  }
12185
+ function findSchemaFixTargets(adapters) {
12186
+ const targets = [];
12187
+ for (const adapter of adapters) {
12188
+ if (!adapter.isInstalled())
12189
+ continue;
12190
+ let aftConfig;
12191
+ let aftConfigFormat;
12192
+ try {
12193
+ ({ aftConfig, aftConfigFormat } = adapter.detectConfigPaths());
12194
+ } catch {
12195
+ continue;
12196
+ }
12197
+ const { value } = readJsoncFile(aftConfig);
12198
+ if (value?.$schema === AFT_SCHEMA_URL)
12199
+ continue;
12200
+ targets.push({ adapter, aftConfig, aftConfigFormat });
12201
+ }
12202
+ return targets;
12203
+ }
12204
+ function applySchemaFixes(targets) {
12205
+ let changed = 0;
12206
+ let errors = 0;
12207
+ for (const target of targets) {
12208
+ try {
12209
+ const result = ensureAftSchemaUrl(target.aftConfig, target.aftConfigFormat);
12210
+ if (result.action === "added" || result.action === "updated") {
12211
+ changed += 1;
12212
+ O2.success(`${target.adapter.displayName}: ${result.message}`);
12213
+ }
12214
+ } catch (error) {
12215
+ errors += 1;
12216
+ O2.warn(`${target.adapter.displayName}: could not set $schema on ${target.aftConfig}: ${error instanceof Error ? error.message : String(error)}`);
12217
+ }
12218
+ }
12219
+ return { changed, errors };
12220
+ }
11833
12221
  function buildDoctorFixPlan(adapters, report) {
11834
12222
  const items = [];
11835
12223
  const adaptersByKind = new Map(adapters.map((adapter) => [adapter.kind, adapter]));
@@ -11877,6 +12265,12 @@ function buildDoctorFixPlan(adapters, report) {
11877
12265
  });
11878
12266
  }
11879
12267
  }
12268
+ for (const target of findSchemaFixTargets(adapters)) {
12269
+ items.push({
12270
+ kind: "schema",
12271
+ message: `Will add the AFT config $schema URL to ${target.aftConfig} (editor autocomplete + validation)`
12272
+ });
12273
+ }
11880
12274
  return items;
11881
12275
  }
11882
12276
  function shouldSkipDoctorFixConfirmation(argv) {
@@ -11915,7 +12309,7 @@ function logUnmatchedBinaryCandidates(expectedVersion) {
11915
12309
  }
11916
12310
  async function runFixFlow(argv) {
11917
12311
  const adapters = await resolveAdaptersForCommand(argv, {
11918
- allowMulti: true,
12312
+ allowMulti: false,
11919
12313
  verb: "auto-fix issues for"
11920
12314
  });
11921
12315
  O2.info("Running diagnostics to identify auto-fixable issues…");
@@ -11937,6 +12331,7 @@ async function runFixFlow(argv) {
11937
12331
  }
11938
12332
  await fixPluginEntries(adapters);
11939
12333
  const storageSummary = ensureStorageDirsForRegisteredPlugins(adapters);
12334
+ const schemaSummary = applySchemaFixes(findSchemaFixTargets(adapters));
11940
12335
  let binaryDownloaded = false;
11941
12336
  let binaryDownloadSkipped = false;
11942
12337
  let binaryDownloadError = null;
@@ -11965,7 +12360,7 @@ async function runFixFlow(argv) {
11965
12360
  }
11966
12361
  }
11967
12362
  const onnxResult = await runOnnxFix(adapters, report, { yes: true });
11968
- if (onnxResult === null && !binaryDownloaded && !binaryDownloadSkipped && !binaryDownloadError && storageSummary.created === 0 && storageSummary.errors === 0) {
12363
+ if (onnxResult === null && !binaryDownloaded && !binaryDownloadSkipped && !binaryDownloadError && storageSummary.created === 0 && storageSummary.errors === 0 && schemaSummary.changed === 0 && schemaSummary.errors === 0) {
11969
12364
  O2.info("No auto-fixable issues detected.");
11970
12365
  wt("If you're still seeing 'Semantic Index: failed' in the TUI sidebar, run `aft doctor` (without --fix) for a full diagnostic dump.", "Tip");
11971
12366
  const afterReport2 = await collectDiagnostics(adapters);
@@ -11973,7 +12368,7 @@ async function runFixFlow(argv) {
11973
12368
  gt(stillHasProblems2 ? "Done — some issues remain." : "Done.");
11974
12369
  return stillHasProblems2 ? 1 : 0;
11975
12370
  }
11976
- const hadErrors = (onnxResult?.errors.length ?? 0) > 0 || binaryDownloadError !== null || storageSummary.errors > 0;
12371
+ const hadErrors = (onnxResult?.errors.length ?? 0) > 0 || binaryDownloadError !== null || storageSummary.errors > 0 || schemaSummary.errors > 0;
11977
12372
  const afterReport = await collectDiagnostics(adapters);
11978
12373
  const stillHasProblems = hasDoctorProblems(afterReport);
11979
12374
  gt(hadErrors ? "Done — some fixes failed." : stillHasProblems ? "Done — some issues remain." : "Done.");
@@ -12028,7 +12423,7 @@ function ensureStorageDirsForRegisteredPlugins(adapters) {
12028
12423
  if (!adapter.isInstalled() || !adapter.hasPluginEntry())
12029
12424
  continue;
12030
12425
  const storageDir = adapter.getStorageDir();
12031
- if (existsSync13(storageDir))
12426
+ if (existsSync14(storageDir))
12032
12427
  continue;
12033
12428
  mkdirSync2(storageDir, { recursive: true });
12034
12429
  summary.created += 1;
@@ -12100,31 +12495,124 @@ function formatStorageSizes(sizes) {
12100
12495
  const parts = Object.entries(sizes).filter(([, size]) => size > 0).map(([key, size]) => `${key}: ${formatBytes(size)}`);
12101
12496
  return parts.length > 0 ? parts.join(", ") : "empty";
12102
12497
  }
12498
+ function isInteractiveTerminal() {
12499
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
12500
+ }
12501
+ function issueDescriptionSummaryFromBody(body) {
12502
+ const lines = body.split(/\r?\n/);
12503
+ const descriptionStart = lines.findIndex((line) => line.trim() === "## Description");
12504
+ if (descriptionStart !== -1) {
12505
+ const parts = [];
12506
+ for (let i = descriptionStart + 1;i < lines.length; i += 1) {
12507
+ const trimmed = lines[i].trim();
12508
+ if (trimmed.startsWith("## "))
12509
+ break;
12510
+ if (!trimmed)
12511
+ continue;
12512
+ parts.push(trimmed);
12513
+ if (parts.join(" ").length >= 72)
12514
+ break;
12515
+ }
12516
+ const summary = parts.join(" ").replace(/\s+/g, " ").trim();
12517
+ if (summary)
12518
+ return summary;
12519
+ }
12520
+ return lines.map((line) => line.trim()).find((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("```")) ?? "diagnostic report";
12521
+ }
12522
+ function deriveIssueTitleFromBody(body) {
12523
+ const summary = issueDescriptionSummaryFromBody(sanitizeContent(body));
12524
+ return sanitizeContent(`AFT issue: ${summary.slice(0, 72)}`);
12525
+ }
12526
+ function writeIssueReviewFile(body) {
12527
+ let reviewDir = null;
12528
+ try {
12529
+ reviewDir = mkdtempSync(join12(tmpdir2(), "aft-issue-"));
12530
+ if (process.platform !== "win32") {
12531
+ chmodSync(reviewDir, 448);
12532
+ }
12533
+ const outPath = join12(reviewDir, "issue.md");
12534
+ writeFileSync2(outPath, `${body}
12535
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
12536
+ return { path: outPath, realPath: realpathSync3(outPath) };
12537
+ } catch (err) {
12538
+ if (reviewDir) {
12539
+ try {
12540
+ rmSync4(reviewDir, { recursive: true, force: true });
12541
+ } catch {}
12542
+ }
12543
+ O2.error(`Failed to write sanitized issue report: ${err instanceof Error ? err.message : String(err)}`);
12544
+ return null;
12545
+ }
12546
+ }
12547
+ function readReviewedIssueFile(reviewFile) {
12548
+ try {
12549
+ const realPath = realpathSync3(reviewFile.path);
12550
+ if (realPath !== reviewFile.realPath) {
12551
+ O2.error(`Review file path changed before filing; refusing to read ${reviewFile.path}.`);
12552
+ return null;
12553
+ }
12554
+ return readFileSync5(reviewFile.path, "utf8");
12555
+ } catch (err) {
12556
+ O2.error(`Failed to read reviewed issue report: ${err instanceof Error ? err.message : String(err)}`);
12557
+ return null;
12558
+ }
12559
+ }
12560
+ async function promptForIssueSession(adapter) {
12561
+ const sessions = listRecentSessions(adapter);
12562
+ if (sessions.length === 0)
12563
+ return null;
12564
+ const allLogsValue = "__all__";
12565
+ const selected = await selectOne("Is this issue about a specific session?", [
12566
+ { label: "General — not session-specific (include all logs)", value: allLogsValue },
12567
+ ...sessions.map((session) => ({
12568
+ label: truncateTitle(session.title),
12569
+ value: session.id,
12570
+ hint: shortSessionId(session.id)
12571
+ }))
12572
+ ]);
12573
+ if (selected === allLogsValue)
12574
+ return null;
12575
+ return sessions.find((session) => session.id === selected) ?? null;
12576
+ }
12577
+ function shortSessionId(id) {
12578
+ const bareId = id.replace(/^ses_/, "");
12579
+ return bareId.length <= 12 ? bareId : bareId.slice(0, 12);
12580
+ }
12103
12581
  async function runIssueFlow(argv) {
12104
12582
  mt("AFT doctor --issue");
12583
+ if (!isInteractiveTerminal()) {
12584
+ wt("Non-interactive terminal — not collecting or filing automatically. Run `aft doctor --issue` from an interactive terminal so you can describe and review the report before filing.", "Manual filing");
12585
+ gt("Done.");
12586
+ return 0;
12587
+ }
12105
12588
  const adapters = await resolveAdaptersForCommand(argv, {
12106
- allowMulti: true,
12589
+ allowMulti: false,
12107
12590
  verb: "include in the issue"
12108
12591
  });
12109
12592
  const description = await text("Describe the problem you're running into:", {
12110
12593
  placeholder: "What happened? What did you expect? Steps to reproduce…",
12111
12594
  validate: (value) => value.trim().length === 0 ? "Please enter a short description." : undefined
12112
12595
  });
12596
+ const selectedSession = await promptForIssueSession(adapters[0]);
12597
+ const selectedBareSessionId = selectedSession?.id.replace(/^ses_/, "") ?? null;
12113
12598
  const report = await collectDiagnostics(adapters);
12114
12599
  const logSections = adapters.map((adapter) => {
12115
12600
  const path = adapter.getLogFile();
12116
12601
  const tail = tailLogFile(path, 200);
12602
+ const scopedTail = selectedBareSessionId ? filterLogToSession(tail, selectedBareSessionId) : tail;
12117
12603
  return `#### ${adapter.displayName} log (${path})
12118
12604
 
12119
12605
  \`\`\`
12120
- ${tail || "<no log output>"}
12606
+ ${scopedTail || "<no log output>"}
12121
12607
  \`\`\`
12122
12608
  `;
12123
12609
  }).join(`
12124
12610
  `);
12125
12611
  const errorScanWindow = adapters.map((adapter) => {
12126
12612
  const path = adapter.getLogFile();
12127
- return sanitizeContent(tailLogFile(path, 4000));
12613
+ const tail = tailLogFile(path, 4000);
12614
+ const scopedTail = selectedBareSessionId ? filterLogToSession(tail, selectedBareSessionId) : tail;
12615
+ return sanitizeContent(scopedTail);
12128
12616
  }).join(`
12129
12617
  `);
12130
12618
  const recentErrorLines = extractRecentErrors(errorScanWindow, 20);
@@ -12140,6 +12628,7 @@ ${tail || "<no log output>"}
12140
12628
  `- AFT binary: ${report.binaryVersion ?? "unknown"}`,
12141
12629
  `- OS: ${report.platform} ${report.arch}`,
12142
12630
  `- Node: ${report.nodeVersion}`,
12631
+ ...selectedSession ? [`- Session: ses_${selectedBareSessionId} (${truncateTitle(selectedSession.title)})`] : [],
12143
12632
  "",
12144
12633
  "## Diagnostics",
12145
12634
  renderDiagnosticsMarkdown(report),
@@ -12153,14 +12642,35 @@ ${tail || "<no log output>"}
12153
12642
  ].join(`
12154
12643
  `);
12155
12644
  const body = capBodyToGithubLimit(sanitizeContent(rawBody));
12156
- const title = sanitizeContent(`AFT issue: ${description.slice(0, 72)}`);
12157
- const outPath = join11(process.cwd(), `aft-issue-${Date.now()}.md`);
12158
- writeFileSync2(outPath, `${body}
12159
- `);
12645
+ const reviewFile = writeIssueReviewFile(body);
12646
+ if (!reviewFile) {
12647
+ gt("Done — could not write the issue report.");
12648
+ return 1;
12649
+ }
12650
+ const outPath = reviewFile.path;
12160
12651
  O2.success(`Wrote sanitized issue body to ${outPath}`);
12652
+ wt(`Open and review the report before filing:
12653
+ ${outPath}
12654
+
12655
+ Home paths and your username have been stripped, but it still contains log lines and file paths from your project. Edit the file to remove anything you don't want public — your edits are used when you confirm below.`, "Review before filing");
12656
+ const proceed = await confirm("Have you reviewed the report above? File it as a GitHub issue now?", false);
12657
+ if (!proceed) {
12658
+ wt(`No issue filed. When ready, file manually at
12659
+ https://github.com/cortexkit/aft/issues/new and paste the contents of ${outPath}.`, "Skipped");
12660
+ gt("Done.");
12661
+ return 0;
12662
+ }
12663
+ const reviewedBody = readReviewedIssueFile(reviewFile);
12664
+ if (reviewedBody === null) {
12665
+ wt("No issue filed. Please review the report path above and file manually if needed.", "Skipped");
12666
+ gt("Done.");
12667
+ return 1;
12668
+ }
12669
+ const finalBody = capBodyToGithubLimit(sanitizeContent(reviewedBody));
12670
+ const finalTitle = deriveIssueTitleFromBody(finalBody);
12161
12671
  if (isGhInstalled()) {
12162
12672
  O2.info("Opening GitHub issue via `gh`…");
12163
- const result = createGitHubIssue("cortexkit/aft", title, body);
12673
+ const result = createGitHubIssue("cortexkit/aft", finalTitle, finalBody);
12164
12674
  if (result.url) {
12165
12675
  O2.success(`Issue filed: ${result.url}`);
12166
12676
  openBrowser(result.url);
@@ -12169,7 +12679,7 @@ ${tail || "<no log output>"}
12169
12679
  }
12170
12680
  O2.warn(`gh failed: ${result.stderr ?? "unknown error"}. Falling back to browser.`);
12171
12681
  }
12172
- const fallback = `https://github.com/cortexkit/aft/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
12682
+ const fallback = `https://github.com/cortexkit/aft/issues/new?title=${encodeURIComponent(finalTitle)}&body=${encodeURIComponent(finalBody)}`;
12173
12683
  O2.info("Opening GitHub issue form in your browser…");
12174
12684
  openBrowser(fallback);
12175
12685
  wt(`If the browser didn't open, the sanitized body is at ${outPath}. Copy it into a new issue at https://github.com/cortexkit/aft/issues/new.`, "Fallback");
@@ -12183,11 +12693,13 @@ var init_doctor = __esm(async () => {
12183
12693
  init_github();
12184
12694
  init_harness_select();
12185
12695
  init_issue_body();
12696
+ init_jsonc();
12186
12697
  init_lsp_cache();
12187
12698
  init_onnx_fix();
12188
12699
  init_prompts();
12189
12700
  init_sanitize();
12190
12701
  init_self_version();
12702
+ init_sessions();
12191
12703
  await __promiseAll([
12192
12704
  init_binary_probe(),
12193
12705
  init_diagnostics()
@@ -12218,6 +12730,7 @@ function printHelp() {
12218
12730
  console.log(" -------");
12219
12731
  console.log("");
12220
12732
  console.log(" Commands:");
12733
+ console.log(" --version Show CLI, binary, and per-harness plugin versions");
12221
12734
  console.log(" setup Interactive setup wizard");
12222
12735
  console.log(" doctor Check and fix configuration issues");
12223
12736
  console.log(" doctor lsp <file> Inspect LSP setup for one file");
@@ -12239,6 +12752,10 @@ function printHelp() {
12239
12752
  console.log("");
12240
12753
  }
12241
12754
  async function main() {
12755
+ if (command === "--version" || command === "-v" || command === "-V" || command === "version") {
12756
+ const { runVersion: runVersion2 } = await init_version().then(() => exports_version);
12757
+ return runVersion2();
12758
+ }
12242
12759
  if (command === "setup") {
12243
12760
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), exports_setup));
12244
12761
  return runSetup2(args);