pi-revit 0.2.6 → 0.2.8
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/CHANGELOG.md +22 -0
- package/extensions/pi-revit/index.ts +70 -1
- package/package.json +1 -1
- package/scripts/setup-workspace.ps1 +80 -80
- package/scripts/uninstall.ps1 +146 -146
- package/src/Revit/Tools/SearchApiDocs.cs +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ Format follows [Keep a Changelog](https://keepachangelog.com/); version headers
|
|
|
7
7
|
Every published version gets an entry with **Added** / **Changed** / **Fixed** sections
|
|
8
8
|
describing what the user will notice — not internal refactors.
|
|
9
9
|
|
|
10
|
+
## [0.2.8] - 2026-07-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `search_api_docs`: overload-targeted queries no longer fail on comma spacing.
|
|
14
|
+
`Wall.Create(Document,Curve` (no space) and `Wall.Create( Document, Curve` now match the
|
|
15
|
+
same overloads as `Wall.Create(Document, Curve` — the query's spacing around commas and
|
|
16
|
+
parentheses is normalized to the rendered signature style before matching.
|
|
17
|
+
|
|
18
|
+
Requires redeploying the Revit add-in (`scripts\deploy.ps1` with Revit closed, then restart
|
|
19
|
+
Revit).
|
|
20
|
+
|
|
21
|
+
## [0.2.7] - 2026-07-21
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Update announcements: after a pi-revit update, the next pi session shows a one-time
|
|
25
|
+
"What's new in pi-revit" note listing the changelog entries for every version since the
|
|
26
|
+
last one announced — the same mechanism pi uses for its own updates. The last-announced
|
|
27
|
+
version is remembered in `%APPDATA%\pi-revit\state.json` (kept outside the package
|
|
28
|
+
folder so npm updates can't erase it). Fresh installs stay silent.
|
|
29
|
+
|
|
30
|
+
Extension-only change: no Revit add-in redeploy or Revit restart needed.
|
|
31
|
+
|
|
10
32
|
## [0.2.6] - 2026-07-20
|
|
11
33
|
|
|
12
34
|
### Fixed
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type, type TSchema } from "typebox";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { version as packageVersion } from "../../package.json";
|
|
7
8
|
|
|
8
9
|
interface BridgeInfo {
|
|
@@ -207,6 +208,73 @@ function versionMismatch(addinVersion: unknown): string | null {
|
|
|
207
208
|
return `pi-revit ${packageVersion} is installed, but the Revit add-in ${state} — the update is incomplete. Close Revit and run: npx.cmd -y pi-revit (or ask the agent to run scripts\\deploy.ps1 from the installed package, then restart Revit).`;
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
// ------------------------------------------------------------- what's new
|
|
212
|
+
|
|
213
|
+
/** Stable per-user state file. Deliberately NOT under node_modules: npm wipes
|
|
214
|
+
* the package folder on every update, which is exactly when the last-announced
|
|
215
|
+
* version must survive. */
|
|
216
|
+
function announcerStatePath(): string {
|
|
217
|
+
const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
218
|
+
return path.join(appData, "pi-revit", "state.json");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function compareVersions(a: string, b: string): number {
|
|
222
|
+
const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
223
|
+
const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
224
|
+
for (let i = 0; i < 3; i++) {
|
|
225
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
226
|
+
}
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Parses `## [x.y.z]` entries from the packaged CHANGELOG.md (the same header
|
|
231
|
+
* format pi's own changelog parser reads) and returns the ones newer than
|
|
232
|
+
* sinceVersion, newest first — so a 0.2.5 -> 0.2.9 jump shows all four. */
|
|
233
|
+
async function newChangelogEntries(sinceVersion: string): Promise<{ version: string; body: string }[]> {
|
|
234
|
+
const changelogPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "CHANGELOG.md");
|
|
235
|
+
const markdown = await readFile(changelogPath, "utf8");
|
|
236
|
+
const headers = [...markdown.matchAll(/^##\s+\[?(\d+\.\d+\.\d+)\]?[^\n]*$/gm)];
|
|
237
|
+
const entries: { version: string; body: string }[] = [];
|
|
238
|
+
for (let i = 0; i < headers.length; i++) {
|
|
239
|
+
const version = headers[i][1];
|
|
240
|
+
if (compareVersions(version, sinceVersion) <= 0) continue;
|
|
241
|
+
const start = (headers[i].index ?? 0) + headers[i][0].length;
|
|
242
|
+
const end = i + 1 < headers.length ? headers[i + 1].index : markdown.length;
|
|
243
|
+
entries.push({ version, body: markdown.slice(start, end).trim() });
|
|
244
|
+
}
|
|
245
|
+
return entries;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Shows the changelog entries between the last announced version and the
|
|
249
|
+
* current one, once per update, then records the current version. A fresh
|
|
250
|
+
* install records silently (nothing is "new" yet). Every failure is swallowed:
|
|
251
|
+
* the announcer must never break a session. */
|
|
252
|
+
async function announceUpdateOnce(notify: (message: string, level: "info") => void): Promise<void> {
|
|
253
|
+
try {
|
|
254
|
+
const statePath = announcerStatePath();
|
|
255
|
+
let lastVersion: string | null = null;
|
|
256
|
+
try {
|
|
257
|
+
const state = JSON.parse(await readFile(statePath, "utf8")) as { lastAnnouncedVersion?: string };
|
|
258
|
+
if (typeof state.lastAnnouncedVersion === "string") lastVersion = state.lastAnnouncedVersion;
|
|
259
|
+
} catch {
|
|
260
|
+
// First run: no state yet.
|
|
261
|
+
}
|
|
262
|
+
if (lastVersion === packageVersion) return;
|
|
263
|
+
|
|
264
|
+
if (lastVersion) {
|
|
265
|
+
const entries = await newChangelogEntries(lastVersion);
|
|
266
|
+
if (entries.length > 0) {
|
|
267
|
+
const text = entries.map((entry) => `pi-revit ${entry.version}\n${entry.body}`).join("\n\n");
|
|
268
|
+
notify(`What's new in pi-revit (updated from ${lastVersion}):\n\n${text}`, "info");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
272
|
+
await writeFile(statePath, JSON.stringify({ lastAnnouncedVersion: packageVersion }, null, 2), "utf8");
|
|
273
|
+
} catch {
|
|
274
|
+
// Never let the announcer break a session.
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
210
278
|
function registerPing(pi: ExtensionAPI) {
|
|
211
279
|
pi.registerTool({
|
|
212
280
|
name: "ping",
|
|
@@ -236,6 +304,7 @@ export default async function revitConnector(pi: ExtensionAPI) {
|
|
|
236
304
|
// where the user lands after running `pi update --extensions`. Bridge down at
|
|
237
305
|
// session start is the normal Revit-closed case: stay quiet.
|
|
238
306
|
pi.on("session_start", async (_event, ctx) => {
|
|
307
|
+
await announceUpdateOnce((message, level) => ctx.ui.notify(message, level));
|
|
239
308
|
try {
|
|
240
309
|
const payload = (await bridgeRequest("/ping", { method: "GET" }, undefined, 3_000)) as { addinVersion?: string };
|
|
241
310
|
const warning = versionMismatch(payload.addinVersion);
|
package/package.json
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
#Requires -Version 5.1
|
|
2
|
-
<#
|
|
3
|
-
Sets up the pi-revit user environment on this PC:
|
|
4
|
-
|
|
5
|
-
1. Workspace at Documents\pi-revit (AGENTS.md conventions, Models\ output tree,
|
|
6
|
-
double-click launcher). An existing AGENTS.md is never overwritten.
|
|
7
|
-
2. Global `pi-revit` command installed next to the `pi` command, so any terminal can run:
|
|
8
|
-
pi-revit -> Pi in the workspace
|
|
9
|
-
pi-revit -c -> continue the last session
|
|
10
|
-
Model output sorts itself: the Revit tools file each export under
|
|
11
|
-
Models\<model title>\ automatically, keyed by the document it came from.
|
|
12
|
-
|
|
13
|
-
Idempotent — safe to re-run. Usage:
|
|
14
|
-
scripts\setup-workspace.ps1
|
|
15
|
-
scripts\setup-workspace.ps1 -WorkspaceDir "D:\work\pi-revit"
|
|
16
|
-
#>
|
|
17
|
-
param(
|
|
18
|
-
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit')
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
$ErrorActionPreference = 'Stop'
|
|
22
|
-
$templates = Join-Path (Split-Path $PSScriptRoot -Parent) 'workspace'
|
|
23
|
-
|
|
24
|
-
# 1. Workspace folders. Models\ is where the Revit tools file per-model output.
|
|
25
|
-
New-Item -ItemType Directory -Force -Path (Join-Path $WorkspaceDir 'Models') | Out-Null
|
|
26
|
-
|
|
27
|
-
# Conventions / notes: copy only when missing so user edits survive re-runs.
|
|
28
|
-
$workspaceAgents = Join-Path $WorkspaceDir 'AGENTS.md'
|
|
29
|
-
if (-not (Test-Path $workspaceAgents)) {
|
|
30
|
-
Copy-Item (Join-Path $templates 'AGENTS.md') $workspaceAgents
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
# Launcher is code, not user data: always refresh.
|
|
34
|
-
Copy-Item (Join-Path $templates 'pi-revit-here.cmd') (Join-Path $WorkspaceDir 'pi-revit.cmd') -Force
|
|
35
|
-
|
|
36
|
-
Write-Host "Workspace ready: $WorkspaceDir" -ForegroundColor Green
|
|
37
|
-
|
|
38
|
-
# 2. Global pi-revit command next to pi (that directory is on PATH by definition).
|
|
39
|
-
# The template's workspace path is rewritten to honor -WorkspaceDir.
|
|
40
|
-
# Under `npx pi-revit`, npx prepends its ephemeral cache's node_modules\.bin to
|
|
41
|
-
# PATH, and that folder holds a pi shim pulled in via peerDependencies. A launcher
|
|
42
|
-
# written next to that shim lands in a folder that is neither on the user's own
|
|
43
|
-
# PATH nor long-lived, so skip pi entries inside this package's node_modules tree
|
|
44
|
-
# or any npx cache and use the user's permanent pi instead.
|
|
45
|
-
function Get-PermanentPiCommand {
|
|
46
|
-
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
47
|
-
# short names (
|
|
48
|
-
# mismatch would silently defeat the own-tree exclusion.
|
|
49
|
-
function Resolve-LongPath([string]$p) {
|
|
50
|
-
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
51
|
-
}
|
|
52
|
-
$ownTree = $null
|
|
53
|
-
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
54
|
-
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
55
|
-
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
56
|
-
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
57
|
-
if (-not $_.Source) { return $false }
|
|
58
|
-
$src = Resolve-LongPath $_.Source
|
|
59
|
-
($src -notmatch '\\_npx\\') -and
|
|
60
|
-
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
61
|
-
} | Select-Object -First 1
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
$pi = Get-PermanentPiCommand
|
|
65
|
-
if ($pi) {
|
|
66
|
-
$binDir = Split-Path $pi.Source -Parent
|
|
67
|
-
# Literal line swap (no regex): the workspace path may contain characters
|
|
68
|
-
# like $ that a -replace replacement string would misinterpret.
|
|
69
|
-
$globalCmd = Get-Content (Join-Path $templates 'pi-revit-global.cmd') | ForEach-Object {
|
|
70
|
-
if ($_ -like 'set "WORKSPACE=*') { 'set "WORKSPACE=' + $WorkspaceDir + '"' } else { $_ }
|
|
71
|
-
}
|
|
72
|
-
# cmd.exe parses .cmd files in the console's OEM code page: write the launcher
|
|
73
|
-
# in that encoding so workspace paths with non-ASCII characters (user names,
|
|
74
|
-
# localized folder names) stay intact.
|
|
75
|
-
Set-Content -Path (Join-Path $binDir 'pi-revit.cmd') -Value $globalCmd -Encoding Oem
|
|
76
|
-
Write-Host "Global command installed: $(Join-Path $binDir 'pi-revit.cmd')" -ForegroundColor Green
|
|
77
|
-
Write-Host 'Run pi-revit from any terminal (pi-revit -c continues the last session).'
|
|
78
|
-
} else {
|
|
79
|
-
Write-Warning 'No permanent pi command found on PATH. Install Pi first (npm install -g --ignore-scripts @earendil-works/pi-coding-agent), then run npx.cmd -y pi-revit again (or re-run this script) to get the global pi-revit command.'
|
|
80
|
-
}
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
Sets up the pi-revit user environment on this PC:
|
|
4
|
+
|
|
5
|
+
1. Workspace at Documents\pi-revit (AGENTS.md conventions, Models\ output tree,
|
|
6
|
+
double-click launcher). An existing AGENTS.md is never overwritten.
|
|
7
|
+
2. Global `pi-revit` command installed next to the `pi` command, so any terminal can run:
|
|
8
|
+
pi-revit -> Pi in the workspace
|
|
9
|
+
pi-revit -c -> continue the last session
|
|
10
|
+
Model output sorts itself: the Revit tools file each export under
|
|
11
|
+
Models\<model title>\ automatically, keyed by the document it came from.
|
|
12
|
+
|
|
13
|
+
Idempotent — safe to re-run. Usage:
|
|
14
|
+
scripts\setup-workspace.ps1
|
|
15
|
+
scripts\setup-workspace.ps1 -WorkspaceDir "D:\work\pi-revit"
|
|
16
|
+
#>
|
|
17
|
+
param(
|
|
18
|
+
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit')
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
$ErrorActionPreference = 'Stop'
|
|
22
|
+
$templates = Join-Path (Split-Path $PSScriptRoot -Parent) 'workspace'
|
|
23
|
+
|
|
24
|
+
# 1. Workspace folders. Models\ is where the Revit tools file per-model output.
|
|
25
|
+
New-Item -ItemType Directory -Force -Path (Join-Path $WorkspaceDir 'Models') | Out-Null
|
|
26
|
+
|
|
27
|
+
# Conventions / notes: copy only when missing so user edits survive re-runs.
|
|
28
|
+
$workspaceAgents = Join-Path $WorkspaceDir 'AGENTS.md'
|
|
29
|
+
if (-not (Test-Path $workspaceAgents)) {
|
|
30
|
+
Copy-Item (Join-Path $templates 'AGENTS.md') $workspaceAgents
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Launcher is code, not user data: always refresh.
|
|
34
|
+
Copy-Item (Join-Path $templates 'pi-revit-here.cmd') (Join-Path $WorkspaceDir 'pi-revit.cmd') -Force
|
|
35
|
+
|
|
36
|
+
Write-Host "Workspace ready: $WorkspaceDir" -ForegroundColor Green
|
|
37
|
+
|
|
38
|
+
# 2. Global pi-revit command next to pi (that directory is on PATH by definition).
|
|
39
|
+
# The template's workspace path is rewritten to honor -WorkspaceDir.
|
|
40
|
+
# Under `npx pi-revit`, npx prepends its ephemeral cache's node_modules\.bin to
|
|
41
|
+
# PATH, and that folder holds a pi shim pulled in via peerDependencies. A launcher
|
|
42
|
+
# written next to that shim lands in a folder that is neither on the user's own
|
|
43
|
+
# PATH nor long-lived, so skip pi entries inside this package's node_modules tree
|
|
44
|
+
# or any npx cache and use the user's permanent pi instead.
|
|
45
|
+
function Get-PermanentPiCommand {
|
|
46
|
+
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
47
|
+
# short names (SOMEUS~1.NAM) while $PSScriptRoot is normalized, and a form
|
|
48
|
+
# mismatch would silently defeat the own-tree exclusion.
|
|
49
|
+
function Resolve-LongPath([string]$p) {
|
|
50
|
+
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
51
|
+
}
|
|
52
|
+
$ownTree = $null
|
|
53
|
+
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
54
|
+
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
55
|
+
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
56
|
+
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
57
|
+
if (-not $_.Source) { return $false }
|
|
58
|
+
$src = Resolve-LongPath $_.Source
|
|
59
|
+
($src -notmatch '\\_npx\\') -and
|
|
60
|
+
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
61
|
+
} | Select-Object -First 1
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$pi = Get-PermanentPiCommand
|
|
65
|
+
if ($pi) {
|
|
66
|
+
$binDir = Split-Path $pi.Source -Parent
|
|
67
|
+
# Literal line swap (no regex): the workspace path may contain characters
|
|
68
|
+
# like $ that a -replace replacement string would misinterpret.
|
|
69
|
+
$globalCmd = Get-Content (Join-Path $templates 'pi-revit-global.cmd') | ForEach-Object {
|
|
70
|
+
if ($_ -like 'set "WORKSPACE=*') { 'set "WORKSPACE=' + $WorkspaceDir + '"' } else { $_ }
|
|
71
|
+
}
|
|
72
|
+
# cmd.exe parses .cmd files in the console's OEM code page: write the launcher
|
|
73
|
+
# in that encoding so workspace paths with non-ASCII characters (user names,
|
|
74
|
+
# localized folder names) stay intact.
|
|
75
|
+
Set-Content -Path (Join-Path $binDir 'pi-revit.cmd') -Value $globalCmd -Encoding Oem
|
|
76
|
+
Write-Host "Global command installed: $(Join-Path $binDir 'pi-revit.cmd')" -ForegroundColor Green
|
|
77
|
+
Write-Host 'Run pi-revit from any terminal (pi-revit -c continues the last session).'
|
|
78
|
+
} else {
|
|
79
|
+
Write-Warning 'No permanent pi command found on PATH. Install Pi first (npm install -g --ignore-scripts @earendil-works/pi-coding-agent), then run npx.cmd -y pi-revit again (or re-run this script) to get the global pi-revit command.'
|
|
80
|
+
}
|
package/scripts/uninstall.ps1
CHANGED
|
@@ -1,146 +1,146 @@
|
|
|
1
|
-
#Requires -Version 5.1
|
|
2
|
-
<#
|
|
3
|
-
Removes everything the pi-revit install scripts placed on this PC:
|
|
4
|
-
|
|
5
|
-
- Revit bridge add-in: %APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge.addin
|
|
6
|
-
%APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge\
|
|
7
|
-
- Global command: the pi-revit command next to the pi command
|
|
8
|
-
- Bridge runtime: %APPDATA%\RevitBridge\ (per-start connection token / bridge.json)
|
|
9
|
-
- Pi package: the pi-revit registration (pi remove), best-effort when pi is on PATH
|
|
10
|
-
|
|
11
|
-
The workspace at Documents\pi-revit holds YOUR data (AGENTS.md notes, per-project
|
|
12
|
-
session history) and is preserved by default. Pass -RemoveWorkspace to delete it too.
|
|
13
|
-
|
|
14
|
-
Pi itself (the global coding agent) is yours to manage and is never touched.
|
|
15
|
-
|
|
16
|
-
Close Revit before running. Idempotent — safe to re-run.
|
|
17
|
-
|
|
18
|
-
Usage:
|
|
19
|
-
scripts\uninstall.ps1
|
|
20
|
-
scripts\uninstall.ps1 -RevitVersion 2027
|
|
21
|
-
scripts\uninstall.ps1 -RemoveWorkspace
|
|
22
|
-
#>
|
|
23
|
-
param(
|
|
24
|
-
[string]$RevitVersion = '',
|
|
25
|
-
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit'),
|
|
26
|
-
[switch]$RemoveWorkspace
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
$ErrorActionPreference = 'Stop'
|
|
30
|
-
|
|
31
|
-
function Remove-IfExists {
|
|
32
|
-
param([string]$Path, [string]$Label)
|
|
33
|
-
if (Test-Path -LiteralPath $Path) {
|
|
34
|
-
Remove-Item -LiteralPath $Path -Recurse -Force
|
|
35
|
-
Write-Host "Removed $Label : $Path" -ForegroundColor Green
|
|
36
|
-
} else {
|
|
37
|
-
Write-Host "Not present, skipped $Label : $Path"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# 1. Revit add-in. With no -RevitVersion, sweep every Addins\<version> folder so the
|
|
42
|
-
# user need not remember which Revit they deployed to.
|
|
43
|
-
$addinsRoot = Join-Path $env:APPDATA 'Autodesk\Revit\Addins'
|
|
44
|
-
$versions = if ($RevitVersion) {
|
|
45
|
-
@($RevitVersion)
|
|
46
|
-
} elseif (Test-Path -LiteralPath $addinsRoot) {
|
|
47
|
-
Get-ChildItem -LiteralPath $addinsRoot -Directory | ForEach-Object { $_.Name }
|
|
48
|
-
} else {
|
|
49
|
-
@()
|
|
50
|
-
}
|
|
51
|
-
foreach ($v in $versions) {
|
|
52
|
-
$dir = Join-Path $addinsRoot $v
|
|
53
|
-
Remove-IfExists (Join-Path $dir 'RevitBridge.addin') "Revit $v add-in manifest"
|
|
54
|
-
Remove-IfExists (Join-Path $dir 'RevitBridge') "Revit $v add-in folder"
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# 2. Global pi-revit command next to the user's permanent pi. When this script runs
|
|
58
|
-
# from an npx cache or a node_modules copy, PATH may lead Get-Command to a pi shim
|
|
59
|
-
# inside that ephemeral tree first (see setup-workspace.ps1) — the launcher was
|
|
60
|
-
# never installed there, so resolve the permanent pi the same way setup does.
|
|
61
|
-
function Get-PermanentPiCommand {
|
|
62
|
-
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
63
|
-
# short names (
|
|
64
|
-
# mismatch would silently defeat the own-tree exclusion.
|
|
65
|
-
function Resolve-LongPath([string]$p) {
|
|
66
|
-
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
67
|
-
}
|
|
68
|
-
$ownTree = $null
|
|
69
|
-
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
70
|
-
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
71
|
-
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
72
|
-
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
73
|
-
if (-not $_.Source) { return $false }
|
|
74
|
-
$src = Resolve-LongPath $_.Source
|
|
75
|
-
($src -notmatch '\\_npx\\') -and
|
|
76
|
-
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
77
|
-
} | Select-Object -First 1
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
$pi = Get-Command pi -ErrorAction SilentlyContinue
|
|
81
|
-
$piPermanent = Get-PermanentPiCommand
|
|
82
|
-
if ($piPermanent) {
|
|
83
|
-
$binDir = Split-Path $piPermanent.Source -Parent
|
|
84
|
-
Remove-IfExists (Join-Path $binDir 'pi-revit.cmd') 'global pi-revit command'
|
|
85
|
-
} else {
|
|
86
|
-
Write-Host 'No permanent pi found on PATH; skipping the global pi-revit command.'
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# 3. Bridge runtime folder (connection token; recreated on each Revit start).
|
|
90
|
-
Remove-IfExists (Join-Path $env:APPDATA 'RevitBridge') 'bridge runtime folder'
|
|
91
|
-
|
|
92
|
-
# 4. Pi package registration. Pi keys a package by its install source: the npm
|
|
93
|
-
# installer (npx.cmd -y pi-revit) registers `npm:pi-revit`, while an install
|
|
94
|
-
# from a clone of this repo (`pi install ./`) registers the repo path resolved
|
|
95
|
-
# from the repo root. Try both forms; each succeeds only for the source that
|
|
96
|
-
# is actually registered, so a machine with both gets both removed.
|
|
97
|
-
if ($pi) {
|
|
98
|
-
$repoRoot = Split-Path $PSScriptRoot -Parent
|
|
99
|
-
# When this script runs from the npm-installed copy, `pi remove npm:pi-revit`
|
|
100
|
-
# deletes the very folder the script lives in — and Windows fails that delete
|
|
101
|
-
# (EBUSY) while any working directory sits inside it. Step our own process out
|
|
102
|
-
# if needed; the calling shell's location we can only warn about.
|
|
103
|
-
if ($repoRoot -match '\\node_modules\\' -and
|
|
104
|
-
(Get-Location).Path.StartsWith($repoRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
105
|
-
Set-Location $env:SystemRoot
|
|
106
|
-
Write-Warning "This PowerShell was started inside $repoRoot. If the package removal below fails, run 'cd \' in your shell and re-run the uninstall."
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
$removed = @()
|
|
110
|
-
# './' resolves against the repo root, so it runs from there — and first,
|
|
111
|
-
# while this folder is guaranteed to still exist. 'npm:pi-revit' then runs
|
|
112
|
-
# from a neutral directory so no working directory blocks the folder delete.
|
|
113
|
-
foreach ($source in @('./', 'npm:pi-revit')) {
|
|
114
|
-
Push-Location $(if ($source -eq './') { $repoRoot } else { $env:SystemRoot })
|
|
115
|
-
try {
|
|
116
|
-
$global:LASTEXITCODE = 0
|
|
117
|
-
& pi remove $source
|
|
118
|
-
if ($LASTEXITCODE -eq 0) {
|
|
119
|
-
$removed += $source
|
|
120
|
-
}
|
|
121
|
-
} finally {
|
|
122
|
-
Pop-Location
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if ($removed.Count -gt 0) {
|
|
126
|
-
Write-Host "Removed the pi-revit Pi package ($($removed -join ', '))." -ForegroundColor Green
|
|
127
|
-
} else {
|
|
128
|
-
Write-Warning "No pi-revit Pi package registration was found. If pi still lists it, run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source)."
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
Write-Warning "Could not auto-remove the Pi package; run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source). ($_)"
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
# 5. Workspace (user data) — opt-in only.
|
|
136
|
-
if ($RemoveWorkspace) {
|
|
137
|
-
Remove-IfExists $WorkspaceDir 'workspace'
|
|
138
|
-
} else {
|
|
139
|
-
Write-Host ''
|
|
140
|
-
Write-Host "Workspace preserved (your notes + session history): $WorkspaceDir" -ForegroundColor Yellow
|
|
141
|
-
Write-Host 'Pass -RemoveWorkspace to delete it too.'
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
Write-Host ''
|
|
145
|
-
Write-Host 'pi-revit uninstalled. Pi itself is untouched; remove it yourself if you want:' -ForegroundColor Green
|
|
146
|
-
Write-Host ' npm uninstall -g @earendil-works/pi-coding-agent'
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
Removes everything the pi-revit install scripts placed on this PC:
|
|
4
|
+
|
|
5
|
+
- Revit bridge add-in: %APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge.addin
|
|
6
|
+
%APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge\
|
|
7
|
+
- Global command: the pi-revit command next to the pi command
|
|
8
|
+
- Bridge runtime: %APPDATA%\RevitBridge\ (per-start connection token / bridge.json)
|
|
9
|
+
- Pi package: the pi-revit registration (pi remove), best-effort when pi is on PATH
|
|
10
|
+
|
|
11
|
+
The workspace at Documents\pi-revit holds YOUR data (AGENTS.md notes, per-project
|
|
12
|
+
session history) and is preserved by default. Pass -RemoveWorkspace to delete it too.
|
|
13
|
+
|
|
14
|
+
Pi itself (the global coding agent) is yours to manage and is never touched.
|
|
15
|
+
|
|
16
|
+
Close Revit before running. Idempotent — safe to re-run.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
scripts\uninstall.ps1
|
|
20
|
+
scripts\uninstall.ps1 -RevitVersion 2027
|
|
21
|
+
scripts\uninstall.ps1 -RemoveWorkspace
|
|
22
|
+
#>
|
|
23
|
+
param(
|
|
24
|
+
[string]$RevitVersion = '',
|
|
25
|
+
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit'),
|
|
26
|
+
[switch]$RemoveWorkspace
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
$ErrorActionPreference = 'Stop'
|
|
30
|
+
|
|
31
|
+
function Remove-IfExists {
|
|
32
|
+
param([string]$Path, [string]$Label)
|
|
33
|
+
if (Test-Path -LiteralPath $Path) {
|
|
34
|
+
Remove-Item -LiteralPath $Path -Recurse -Force
|
|
35
|
+
Write-Host "Removed $Label : $Path" -ForegroundColor Green
|
|
36
|
+
} else {
|
|
37
|
+
Write-Host "Not present, skipped $Label : $Path"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# 1. Revit add-in. With no -RevitVersion, sweep every Addins\<version> folder so the
|
|
42
|
+
# user need not remember which Revit they deployed to.
|
|
43
|
+
$addinsRoot = Join-Path $env:APPDATA 'Autodesk\Revit\Addins'
|
|
44
|
+
$versions = if ($RevitVersion) {
|
|
45
|
+
@($RevitVersion)
|
|
46
|
+
} elseif (Test-Path -LiteralPath $addinsRoot) {
|
|
47
|
+
Get-ChildItem -LiteralPath $addinsRoot -Directory | ForEach-Object { $_.Name }
|
|
48
|
+
} else {
|
|
49
|
+
@()
|
|
50
|
+
}
|
|
51
|
+
foreach ($v in $versions) {
|
|
52
|
+
$dir = Join-Path $addinsRoot $v
|
|
53
|
+
Remove-IfExists (Join-Path $dir 'RevitBridge.addin') "Revit $v add-in manifest"
|
|
54
|
+
Remove-IfExists (Join-Path $dir 'RevitBridge') "Revit $v add-in folder"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# 2. Global pi-revit command next to the user's permanent pi. When this script runs
|
|
58
|
+
# from an npx cache or a node_modules copy, PATH may lead Get-Command to a pi shim
|
|
59
|
+
# inside that ephemeral tree first (see setup-workspace.ps1) — the launcher was
|
|
60
|
+
# never installed there, so resolve the permanent pi the same way setup does.
|
|
61
|
+
function Get-PermanentPiCommand {
|
|
62
|
+
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
63
|
+
# short names (SOMEUS~1.NAM) while $PSScriptRoot is normalized, and a form
|
|
64
|
+
# mismatch would silently defeat the own-tree exclusion.
|
|
65
|
+
function Resolve-LongPath([string]$p) {
|
|
66
|
+
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
67
|
+
}
|
|
68
|
+
$ownTree = $null
|
|
69
|
+
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
70
|
+
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
71
|
+
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
72
|
+
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
73
|
+
if (-not $_.Source) { return $false }
|
|
74
|
+
$src = Resolve-LongPath $_.Source
|
|
75
|
+
($src -notmatch '\\_npx\\') -and
|
|
76
|
+
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
77
|
+
} | Select-Object -First 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
$pi = Get-Command pi -ErrorAction SilentlyContinue
|
|
81
|
+
$piPermanent = Get-PermanentPiCommand
|
|
82
|
+
if ($piPermanent) {
|
|
83
|
+
$binDir = Split-Path $piPermanent.Source -Parent
|
|
84
|
+
Remove-IfExists (Join-Path $binDir 'pi-revit.cmd') 'global pi-revit command'
|
|
85
|
+
} else {
|
|
86
|
+
Write-Host 'No permanent pi found on PATH; skipping the global pi-revit command.'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# 3. Bridge runtime folder (connection token; recreated on each Revit start).
|
|
90
|
+
Remove-IfExists (Join-Path $env:APPDATA 'RevitBridge') 'bridge runtime folder'
|
|
91
|
+
|
|
92
|
+
# 4. Pi package registration. Pi keys a package by its install source: the npm
|
|
93
|
+
# installer (npx.cmd -y pi-revit) registers `npm:pi-revit`, while an install
|
|
94
|
+
# from a clone of this repo (`pi install ./`) registers the repo path resolved
|
|
95
|
+
# from the repo root. Try both forms; each succeeds only for the source that
|
|
96
|
+
# is actually registered, so a machine with both gets both removed.
|
|
97
|
+
if ($pi) {
|
|
98
|
+
$repoRoot = Split-Path $PSScriptRoot -Parent
|
|
99
|
+
# When this script runs from the npm-installed copy, `pi remove npm:pi-revit`
|
|
100
|
+
# deletes the very folder the script lives in — and Windows fails that delete
|
|
101
|
+
# (EBUSY) while any working directory sits inside it. Step our own process out
|
|
102
|
+
# if needed; the calling shell's location we can only warn about.
|
|
103
|
+
if ($repoRoot -match '\\node_modules\\' -and
|
|
104
|
+
(Get-Location).Path.StartsWith($repoRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
105
|
+
Set-Location $env:SystemRoot
|
|
106
|
+
Write-Warning "This PowerShell was started inside $repoRoot. If the package removal below fails, run 'cd \' in your shell and re-run the uninstall."
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
$removed = @()
|
|
110
|
+
# './' resolves against the repo root, so it runs from there — and first,
|
|
111
|
+
# while this folder is guaranteed to still exist. 'npm:pi-revit' then runs
|
|
112
|
+
# from a neutral directory so no working directory blocks the folder delete.
|
|
113
|
+
foreach ($source in @('./', 'npm:pi-revit')) {
|
|
114
|
+
Push-Location $(if ($source -eq './') { $repoRoot } else { $env:SystemRoot })
|
|
115
|
+
try {
|
|
116
|
+
$global:LASTEXITCODE = 0
|
|
117
|
+
& pi remove $source
|
|
118
|
+
if ($LASTEXITCODE -eq 0) {
|
|
119
|
+
$removed += $source
|
|
120
|
+
}
|
|
121
|
+
} finally {
|
|
122
|
+
Pop-Location
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if ($removed.Count -gt 0) {
|
|
126
|
+
Write-Host "Removed the pi-revit Pi package ($($removed -join ', '))." -ForegroundColor Green
|
|
127
|
+
} else {
|
|
128
|
+
Write-Warning "No pi-revit Pi package registration was found. If pi still lists it, run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source)."
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
Write-Warning "Could not auto-remove the Pi package; run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source). ($_)"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# 5. Workspace (user data) — opt-in only.
|
|
136
|
+
if ($RemoveWorkspace) {
|
|
137
|
+
Remove-IfExists $WorkspaceDir 'workspace'
|
|
138
|
+
} else {
|
|
139
|
+
Write-Host ''
|
|
140
|
+
Write-Host "Workspace preserved (your notes + session history): $WorkspaceDir" -ForegroundColor Yellow
|
|
141
|
+
Write-Host 'Pass -RemoveWorkspace to delete it too.'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Write-Host ''
|
|
145
|
+
Write-Host 'pi-revit uninstalled. Pi itself is untouched; remove it yourself if you want:' -ForegroundColor Green
|
|
146
|
+
Write-Host ' npm uninstall -g @earendil-works/pi-coding-agent'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
using System.IO;
|
|
2
2
|
using System.Text;
|
|
3
|
+
using System.Text.RegularExpressions;
|
|
3
4
|
using System.Text.Json;
|
|
4
5
|
using System.Xml;
|
|
5
6
|
using System.Xml.Linq;
|
|
@@ -124,7 +125,13 @@ namespace RevitBridge.Tools
|
|
|
124
125
|
|
|
125
126
|
private static (List<ApiMember> Top, int Total) Search(DocIndex index, string query, char? kindFilter, int maxResults)
|
|
126
127
|
{
|
|
128
|
+
// Signatures are rendered exactly one way ("Name(Type, Type)"): normalize the
|
|
129
|
+
// query's spacing around commas and parentheses so 'Wall.Create(Document,Curve'
|
|
130
|
+
// and 'Wall.Create( Document, Curve' hit the same rendered text instead of
|
|
131
|
+
// failing on typography.
|
|
127
132
|
string q = query.ToLowerInvariant();
|
|
133
|
+
q = Regex.Replace(q, @"\s*,\s*", ", ");
|
|
134
|
+
q = Regex.Replace(q, @"\(\s+", "(");
|
|
128
135
|
string[] words = q.Split(WordSeparators, StringSplitOptions.RemoveEmptyEntries);
|
|
129
136
|
|
|
130
137
|
var scored = new List<(ApiMember Member, int Score)>();
|