@agentrhq/webcmd 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/cli-manifest.json +11 -11
- package/clis/producthunt/browse.js +9 -2
- package/clis/producthunt/browser-commands.test.js +66 -0
- package/clis/producthunt/hot.js +3 -3
- package/clis/producthunt/utils.js +27 -0
- package/clis/producthunt/utils.test.js +8 -0
- package/clis/spotify/spotify.js +11 -11
- package/dist/src/browser/daemon-client.d.ts +1 -0
- package/dist/src/browser/daemon-client.js +25 -1
- package/dist/src/browser/daemon-client.test.js +86 -1
- package/dist/src/browser/daemon-transport.d.ts +2 -0
- package/dist/src/browser/protocol.d.ts +12 -1
- package/dist/src/browser/runtime/local-cloak/actions.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/actions.js +9 -9
- package/dist/src/browser/runtime/local-cloak/provider.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/provider.js +6 -3
- package/dist/src/browser/runtime/local-cloak/provider.test.js +1 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +5 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.js +95 -19
- package/dist/src/browser/runtime/local-cloak/session-manager.test.js +235 -0
- package/dist/src/browser/runtime/provider.d.ts +1 -0
- package/dist/src/cli.js +36 -12
- package/dist/src/cli.test.js +5 -0
- package/dist/src/daemon/server.js +65 -24
- package/dist/src/daemon/server.test.js +211 -2
- package/dist/src/docs-sync-review-cli.test.js +1 -1
- package/dist/src/errors.d.ts +5 -0
- package/dist/src/errors.js +23 -0
- package/dist/src/errors.test.js +58 -1
- package/dist/src/execution.js +57 -6
- package/dist/src/execution.test.js +426 -2
- package/dist/src/hosted/availability.test.js +12 -1
- package/dist/src/hosted/client.js +3 -1
- package/dist/src/hosted/client.test.js +62 -0
- package/dist/src/hosted/main-lifecycle.test.js +66 -5
- package/dist/src/hosted/types.d.ts +1 -0
- package/dist/src/main.js +4 -0
- package/dist/src/package-bin-process.d.ts +13 -0
- package/dist/src/package-bin-process.js +24 -0
- package/dist/src/package-bin-process.test.d.ts +1 -0
- package/dist/src/package-bin-process.test.js +22 -0
- package/dist/src/session-lease.d.ts +82 -0
- package/dist/src/session-lease.js +163 -0
- package/dist/src/session-lease.test.d.ts +1 -0
- package/dist/src/session-lease.test.js +217 -0
- package/dist/src/skills.d.ts +8 -4
- package/dist/src/skills.js +30 -1
- package/dist/src/skills.test.js +40 -12
- package/hosted-contract.json +45 -34
- package/package.json +3 -2
- package/scripts/check-package-bin.mjs +7 -6
- package/scripts/collect-ci-diagnostics.ps1 +274 -0
- package/scripts/docs-sync-review.ts +1 -1
- package/skills/webcmd-adapter-author/SKILL.md +1 -1
- package/skills/webcmd-adapter-author/references/adapter-template.md +2 -6
|
@@ -5,6 +5,10 @@ import fs from 'node:fs';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import {
|
|
9
|
+
formatPackageBinSpawnFailure,
|
|
10
|
+
packageBinSpawnOptions,
|
|
11
|
+
} from '../dist/src/package-bin-process.js';
|
|
8
12
|
|
|
9
13
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
14
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
@@ -20,14 +24,11 @@ function run(command, args, opts = {}) {
|
|
|
20
24
|
cwd: ROOT,
|
|
21
25
|
encoding: 'utf8',
|
|
22
26
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
27
|
+
...packageBinSpawnOptions(process.platform, command),
|
|
23
28
|
...opts,
|
|
24
29
|
});
|
|
25
|
-
if (result.status !== 0) {
|
|
26
|
-
fail(
|
|
27
|
-
`${command} ${args.join(' ')} exited ${result.status}`,
|
|
28
|
-
result.stdout.trim(),
|
|
29
|
-
result.stderr.trim(),
|
|
30
|
-
].filter(Boolean).join('\n'));
|
|
30
|
+
if (result.error || result.status !== 0) {
|
|
31
|
+
fail(formatPackageBinSpawnFailure(command, args, result));
|
|
31
32
|
}
|
|
32
33
|
return result;
|
|
33
34
|
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[switch]$SelfTest
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = 'Continue'
|
|
7
|
+
|
|
8
|
+
function Protect-SensitiveDiagnosticText {
|
|
9
|
+
param(
|
|
10
|
+
[AllowEmptyString()][string]$Text
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
$sensitiveLinePattern = '(?i)(?:^|[^a-z0-9])(?:auth(?:[-_]?token)?|authorization|proxy[-_]?authorization|bearer|token|api[-_]?key|secret|password|credentials?|set[-_]?cookie|cookie|cdp(?:[-_\s]?(?:endpoint|url))?|devtools(?:[-_\s]?url)?|live[-_\s]?url|browser[-_\s]?url|websocket[-_\s]?url|ws[-_\s]?url|page[-_\s]?(?:content|text|html)|innerhtml|outerhtml|snapshot)(?:$|[^a-z0-9])|document\.|<html|<body|\b(?:html|body|content)\b\s*"?\s*:'
|
|
14
|
+
$schemeUrlPattern = '(?i)\b[a-z][a-z0-9+.-]*://[^\s"''<>]+'
|
|
15
|
+
$sanitized = foreach ($line in ($Text -split "`r?`n")) {
|
|
16
|
+
if ($line -match $sensitiveLinePattern) {
|
|
17
|
+
'[REDACTED sensitive diagnostic line]'
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
$line -replace $schemeUrlPattern, '[REDACTED_URL]'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return ($sanitized -join [Environment]::NewLine)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function Write-SanitizedText {
|
|
27
|
+
param(
|
|
28
|
+
[Parameter(Mandatory = $true)][string]$Path,
|
|
29
|
+
[AllowEmptyString()][string]$Text
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
Protect-SensitiveDiagnosticText -Text $Text | Set-Content -LiteralPath $Path -Encoding utf8
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Write-SanitizedJson {
|
|
36
|
+
param(
|
|
37
|
+
[Parameter(Mandatory = $true)][string]$Path,
|
|
38
|
+
[Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()]$Value
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Write-SanitizedText -Path $Path -Text (ConvertTo-Json -InputObject $Value -Depth 20)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function Get-DaemonLogMetadata {
|
|
45
|
+
param(
|
|
46
|
+
[Parameter(Mandatory = $true)][AllowNull()]$Response
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
$knownLevels = @('debug', 'info', 'warn', 'error')
|
|
50
|
+
$logs = if ($null -ne $Response -and $null -ne $Response.logs) { @($Response.logs) } else { @() }
|
|
51
|
+
$countsByKnownLevel = [ordered]@{}
|
|
52
|
+
foreach ($knownLevel in $knownLevels) {
|
|
53
|
+
$countsByKnownLevel[$knownLevel] = 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
$unrecognizedLevelCount = 0
|
|
57
|
+
$timestamps = @()
|
|
58
|
+
$maximumTimestamp = [DateTimeOffset]::UtcNow.AddDays(1).ToUnixTimeMilliseconds()
|
|
59
|
+
foreach ($entry in $logs) {
|
|
60
|
+
$level = if ($null -ne $entry.level) { ([string]$entry.level).ToLowerInvariant() } else { '' }
|
|
61
|
+
if ($knownLevels -contains $level) {
|
|
62
|
+
$countsByKnownLevel[$level] += 1
|
|
63
|
+
} else {
|
|
64
|
+
$unrecognizedLevelCount += 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
$timestamp = 0L
|
|
68
|
+
if ($null -ne $entry.ts -and [Int64]::TryParse(([string]$entry.ts), [ref]$timestamp) -and $timestamp -ge 0 -and $timestamp -le $maximumTimestamp) {
|
|
69
|
+
$timestamps += $timestamp
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
$earliestTimestampUtc = $null
|
|
74
|
+
$latestTimestampUtc = $null
|
|
75
|
+
if ($timestamps.Count -gt 0) {
|
|
76
|
+
$earliestTimestamp = [Int64](($timestamps | Measure-Object -Minimum).Minimum)
|
|
77
|
+
$latestTimestamp = [Int64](($timestamps | Measure-Object -Maximum).Maximum)
|
|
78
|
+
$earliestTimestampUtc = [DateTimeOffset]::FromUnixTimeMilliseconds($earliestTimestamp).UtcDateTime.ToString('o')
|
|
79
|
+
$latestTimestampUtc = [DateTimeOffset]::FromUnixTimeMilliseconds($latestTimestamp).UtcDateTime.ToString('o')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return [pscustomobject][ordered]@{
|
|
83
|
+
totalCount = $logs.Count
|
|
84
|
+
countsByKnownLevel = [pscustomobject]$countsByKnownLevel
|
|
85
|
+
unrecognizedLevelCount = $unrecognizedLevelCount
|
|
86
|
+
earliestTimestampUtc = $earliestTimestampUtc
|
|
87
|
+
latestTimestampUtc = $latestTimestampUtc
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Test-CloakDiagnosticLogName {
|
|
92
|
+
param(
|
|
93
|
+
[Parameter(Mandatory = $true)][string]$Name
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return $Name.EndsWith('.log', [StringComparison]::OrdinalIgnoreCase) -or
|
|
97
|
+
$Name.Equals('LOG', [StringComparison]::OrdinalIgnoreCase) -or
|
|
98
|
+
$Name.Equals('LOG.old', [StringComparison]::OrdinalIgnoreCase)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function Get-CloakLogFileMetadata {
|
|
102
|
+
param(
|
|
103
|
+
[Parameter(Mandatory = $true)][string]$Root,
|
|
104
|
+
[Parameter(Mandatory = $true)]$File
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return [pscustomobject][ordered]@{
|
|
108
|
+
relativePath = [System.IO.Path]::GetRelativePath($Root, $File.FullName)
|
|
109
|
+
bytes = $File.Length
|
|
110
|
+
lastWriteUtc = $File.LastWriteTimeUtc.ToString('o')
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Assert-DiagnosticsSelfTest {
|
|
115
|
+
param(
|
|
116
|
+
[Parameter(Mandatory = $true)][bool]$Condition,
|
|
117
|
+
[Parameter(Mandatory = $true)][string]$Message
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if (-not $Condition) {
|
|
121
|
+
throw "Diagnostics self-test failed: $Message"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function Invoke-DiagnosticsSelfTest {
|
|
126
|
+
$sensitiveSamples = @(
|
|
127
|
+
'auth: sensitive-value',
|
|
128
|
+
'authToken: sensitive-value',
|
|
129
|
+
'auth_token: sensitive-value',
|
|
130
|
+
'authorization: Bearer sensitive-value',
|
|
131
|
+
'bearer sensitive-value',
|
|
132
|
+
'token: sensitive-value',
|
|
133
|
+
'api-key: sensitive-value',
|
|
134
|
+
'api_key: sensitive-value',
|
|
135
|
+
'apiKey: sensitive-value',
|
|
136
|
+
'secret: sensitive-value',
|
|
137
|
+
'password: sensitive-value',
|
|
138
|
+
'credentials: sensitive-value',
|
|
139
|
+
'cookie: sensitive-value',
|
|
140
|
+
'cdpUrl: ws://127.0.0.1/devtools/sensitive-value',
|
|
141
|
+
'cdp_url: ws://127.0.0.1/devtools/sensitive-value',
|
|
142
|
+
'CDP endpoint: ws://127.0.0.1/devtools/sensitive-value',
|
|
143
|
+
'liveUrl: https://example.test/sensitive-value',
|
|
144
|
+
'live_url: https://example.test/sensitive-value',
|
|
145
|
+
'live URL: https://example.test/sensitive-value',
|
|
146
|
+
'scheme only https://example.test/sensitive-value'
|
|
147
|
+
)
|
|
148
|
+
foreach ($sample in $sensitiveSamples) {
|
|
149
|
+
$protected = Protect-SensitiveDiagnosticText -Text $sample
|
|
150
|
+
Assert-DiagnosticsSelfTest -Condition (-not $protected.Contains('sensitive-value')) -Message "sensitive sample survived: $sample"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
$unlabeledPageText = 'Customer account balance is 1234 and the recovery phrase is violet quartz'
|
|
154
|
+
$syntheticLogs = [pscustomobject]@{
|
|
155
|
+
ok = $true
|
|
156
|
+
logs = @(
|
|
157
|
+
[pscustomobject]@{ level = 'warn'; msg = $unlabeledPageText; text = 'private text'; value = 'private value'; data = @{ private = $true }; ts = 1704067200000 },
|
|
158
|
+
[pscustomobject]@{ level = 'info'; msg = 'https://example.test/private'; ts = 1704153600000 },
|
|
159
|
+
[pscustomobject]@{ level = 'custom'; msg = 'secret but unlabeled'; ts = -1 }
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
$metadata = Get-DaemonLogMetadata -Response $syntheticLogs
|
|
163
|
+
$metadataJson = ConvertTo-Json -InputObject $metadata -Depth 5
|
|
164
|
+
Assert-DiagnosticsSelfTest -Condition ($metadata.totalCount -eq 3) -Message 'daemon log total count is incorrect'
|
|
165
|
+
Assert-DiagnosticsSelfTest -Condition ($metadata.countsByKnownLevel.warn -eq 1 -and $metadata.countsByKnownLevel.info -eq 1) -Message 'known daemon log level counts are incorrect'
|
|
166
|
+
Assert-DiagnosticsSelfTest -Condition ($metadata.unrecognizedLevelCount -eq 1) -Message 'unrecognized daemon log level count is incorrect'
|
|
167
|
+
Assert-DiagnosticsSelfTest -Condition ($metadata.earliestTimestampUtc -eq '2024-01-01T00:00:00.0000000Z') -Message 'earliest bounded timestamp is incorrect'
|
|
168
|
+
Assert-DiagnosticsSelfTest -Condition ($metadata.latestTimestampUtc -eq '2024-01-02T00:00:00.0000000Z') -Message 'latest bounded timestamp is incorrect'
|
|
169
|
+
Assert-DiagnosticsSelfTest -Condition (-not $metadataJson.Contains($unlabeledPageText)) -Message 'unlabeled page text survived daemon metadata projection'
|
|
170
|
+
Assert-DiagnosticsSelfTest -Condition ($metadataJson -notmatch '(?i)"(?:msg|text|value|data)"\s*:') -Message 'free-form daemon fields survived metadata projection'
|
|
171
|
+
Assert-DiagnosticsSelfTest -Condition (($metadata.PSObject.Properties.Name -join ',') -eq 'totalCount,countsByKnownLevel,unrecognizedLevelCount,earliestTimestampUtc,latestTimestampUtc') -Message 'daemon metadata contains a non-allowlisted field'
|
|
172
|
+
|
|
173
|
+
$diagnosticLogNames = @('browser.log', 'debug.log', 'LOG', 'LOG.old')
|
|
174
|
+
foreach ($name in $diagnosticLogNames) {
|
|
175
|
+
Assert-DiagnosticsSelfTest -Condition (Test-CloakDiagnosticLogName -Name $name) -Message "known diagnostic log name was rejected: $name"
|
|
176
|
+
}
|
|
177
|
+
foreach ($name in @('Login Data', 'catalog.json', 'debug.txt', 'LOG.bak')) {
|
|
178
|
+
Assert-DiagnosticsSelfTest -Condition (-not (Test-CloakDiagnosticLogName -Name $name)) -Message "non-log profile file was accepted: $name"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
$syntheticFile = [pscustomobject]@{
|
|
182
|
+
FullName = Join-Path ([System.IO.Path]::GetTempPath()) 'debug.log'
|
|
183
|
+
Length = 42
|
|
184
|
+
LastWriteTimeUtc = [DateTime]::Parse('2024-01-03T00:00:00Z').ToUniversalTime()
|
|
185
|
+
SensitiveContent = 'cookie=sensitive-value'
|
|
186
|
+
}
|
|
187
|
+
$fileMetadata = Get-CloakLogFileMetadata -Root ([System.IO.Path]::GetTempPath()) -File $syntheticFile
|
|
188
|
+
$fileMetadataJson = ConvertTo-Json -InputObject $fileMetadata -Depth 3
|
|
189
|
+
Assert-DiagnosticsSelfTest -Condition (($fileMetadata.PSObject.Properties.Name -join ',') -eq 'relativePath,bytes,lastWriteUtc') -Message 'Cloak file metadata contains a non-allowlisted field'
|
|
190
|
+
Assert-DiagnosticsSelfTest -Condition (-not $fileMetadataJson.Contains('sensitive-value')) -Message 'Cloak file contents survived metadata projection'
|
|
191
|
+
|
|
192
|
+
Write-Host 'Diagnostics self-test passed.'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if ($SelfTest) {
|
|
196
|
+
$ErrorActionPreference = 'Stop'
|
|
197
|
+
Invoke-DiagnosticsSelfTest
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
$artifactRoot = Join-Path $PWD 'artifacts/windows-cloak'
|
|
202
|
+
New-Item -ItemType Directory -Path $artifactRoot -Force | Out-Null
|
|
203
|
+
|
|
204
|
+
$runnerMetadata = [ordered]@{
|
|
205
|
+
collectedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
|
|
206
|
+
nodeVersion = (& node --version 2>&1 | Out-String).Trim()
|
|
207
|
+
npmVersion = (& npm --version 2>&1 | Out-String).Trim()
|
|
208
|
+
runner = [ordered]@{
|
|
209
|
+
os = $env:RUNNER_OS
|
|
210
|
+
architecture = $env:RUNNER_ARCH
|
|
211
|
+
imageOs = $env:ImageOS
|
|
212
|
+
imageVersion = $env:ImageVersion
|
|
213
|
+
machineName = $env:COMPUTERNAME
|
|
214
|
+
}
|
|
215
|
+
github = [ordered]@{
|
|
216
|
+
runId = $env:GITHUB_RUN_ID
|
|
217
|
+
runAttempt = $env:GITHUB_RUN_ATTEMPT
|
|
218
|
+
sha = $env:GITHUB_SHA
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
$windows = Get-CimInstance Win32_OperatingSystem -ErrorAction Stop
|
|
224
|
+
$runnerMetadata['windows'] = [ordered]@{
|
|
225
|
+
caption = $windows.Caption
|
|
226
|
+
version = $windows.Version
|
|
227
|
+
buildNumber = $windows.BuildNumber
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
$runnerMetadata['windows'] = @{ error = 'Windows metadata unavailable' }
|
|
231
|
+
}
|
|
232
|
+
Write-SanitizedJson -Path (Join-Path $artifactRoot 'runner-metadata.json') -Value $runnerMetadata
|
|
233
|
+
|
|
234
|
+
$headers = @{ 'X-Webcmd' = '1' }
|
|
235
|
+
try {
|
|
236
|
+
$daemonStatus = Invoke-RestMethod -Uri 'http://127.0.0.1:9777/status' -Headers $headers -TimeoutSec 5
|
|
237
|
+
Write-SanitizedJson -Path (Join-Path $artifactRoot 'daemon-status.json') -Value $daemonStatus
|
|
238
|
+
} catch {
|
|
239
|
+
Write-SanitizedText -Path (Join-Path $artifactRoot 'daemon-status.txt') -Text 'Daemon status unavailable.'
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
$daemonLogs = Invoke-RestMethod -Uri 'http://127.0.0.1:9777/logs' -Headers $headers -TimeoutSec 5
|
|
244
|
+
$daemonLogMetadata = Get-DaemonLogMetadata -Response $daemonLogs
|
|
245
|
+
Write-SanitizedJson -Path (Join-Path $artifactRoot 'daemon-log-metadata.json') -Value $daemonLogMetadata
|
|
246
|
+
} catch {
|
|
247
|
+
Write-SanitizedText -Path (Join-Path $artifactRoot 'daemon-log-metadata.txt') -Text 'Daemon log metadata unavailable.'
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
$processMetadata = Get-Process -ErrorAction SilentlyContinue |
|
|
251
|
+
Where-Object { $_.ProcessName -match '(?i)^(node|chrome|chromium|cloak)' } |
|
|
252
|
+
ForEach-Object {
|
|
253
|
+
[ordered]@{
|
|
254
|
+
name = $_.ProcessName
|
|
255
|
+
id = $_.Id
|
|
256
|
+
startedAtUtc = if ($_.StartTime) { $_.StartTime.ToUniversalTime().ToString('o') } else { $null }
|
|
257
|
+
workingSetBytes = $_.WorkingSet64
|
|
258
|
+
cpuSeconds = $_.CPU
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
Write-SanitizedJson -Path (Join-Path $artifactRoot 'process-metadata.json') -Value @($processMetadata)
|
|
262
|
+
|
|
263
|
+
$cloakRoot = if ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.webcmd/cloak' } else { $null }
|
|
264
|
+
$cloakLogMetadata = @()
|
|
265
|
+
if ($cloakRoot -and (Test-Path -LiteralPath $cloakRoot)) {
|
|
266
|
+
$cloakLogMetadata = Get-ChildItem -LiteralPath $cloakRoot -Recurse -File -ErrorAction SilentlyContinue |
|
|
267
|
+
Where-Object { Test-CloakDiagnosticLogName -Name $_.Name } |
|
|
268
|
+
ForEach-Object {
|
|
269
|
+
Get-CloakLogFileMetadata -Root $cloakRoot -File $_
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
Write-SanitizedJson -Path (Join-Path $artifactRoot 'cloak-log-metadata.json') -Value @($cloakLogMetadata)
|
|
273
|
+
|
|
274
|
+
Write-Host "Sanitized diagnostics written to $artifactRoot"
|
|
@@ -223,7 +223,7 @@ export async function generateGeminiReview(
|
|
|
223
223
|
responseMimeType: 'application/json',
|
|
224
224
|
responseJsonSchema: REVIEW_JSON_SCHEMA,
|
|
225
225
|
temperature: 0.1,
|
|
226
|
-
abortSignal: AbortSignal.timeout(
|
|
226
|
+
abortSignal: AbortSignal.timeout(180_000),
|
|
227
227
|
},
|
|
228
228
|
});
|
|
229
229
|
const text = response.text?.trim();
|
|
@@ -175,7 +175,7 @@ Check these off step by step:
|
|
|
175
175
|
[ ] Order: identifier columns -> business numbers -> metadata.
|
|
176
176
|
|
|
177
177
|
[ ] 9. Write the adapter (`adapter-template.md`):
|
|
178
|
-
[ ] `webcmd browser init <site>/<name
|
|
178
|
+
[ ] `webcmd browser init <site>/<name>`, then set `strategy: Strategy.<strategy>` in the generated file
|
|
179
179
|
[ ] Find the closest same-site or same-type adapter and copy it.
|
|
180
180
|
[ ] Edit name, URL, and field mapping.
|
|
181
181
|
|
|
@@ -7,14 +7,10 @@ Use this after recon, endpoint verification, field decoding, output design, and
|
|
|
7
7
|
For private iteration:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
webcmd browser init <site>/<name>
|
|
10
|
+
webcmd browser init <site>/<name>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```text
|
|
16
|
-
~/.webcmd/clis/<site>/<name>.js
|
|
17
|
-
```
|
|
13
|
+
This scaffolds `~/.webcmd/clis/<site>/<name>.js` with a `Strategy.PUBLIC` placeholder — `init` takes no flags, so set the real `strategy:` value (and the other `TODO` fields) by hand once the file exists.
|
|
18
14
|
|
|
19
15
|
Promote a community CLI to the main repo as a plugin:
|
|
20
16
|
|