yam-flow 2.4.0 → 2.5.1
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/AGENTS.md +6 -4
- package/CHANGELOG.md +54 -0
- package/COMMANDS.md +13 -0
- package/README.md +53 -1
- package/ROADMAP.md +5 -1
- package/dist/bin/yam.js +104 -17
- package/dist/lib/bounded-input.d.ts +12 -0
- package/dist/lib/bounded-input.js +48 -0
- package/dist/lib/external-updates.d.ts +103 -0
- package/dist/lib/external-updates.js +1054 -0
- package/dist/lib/skill-installation.js +23 -5
- package/dist/lib/stable-order.d.ts +1 -0
- package/dist/lib/stable-order.js +5 -0
- package/dist/lib/ueye-artifacts.js +3 -2
- package/package.json +6 -3
- package/src/bin/yam.ts +109 -13
- package/src/lib/bounded-input.ts +59 -0
- package/src/lib/external-updates.ts +1220 -0
- package/src/lib/skill-installation.ts +24 -5
- package/src/lib/stable-order.ts +4 -0
- package/src/lib/ueye-artifacts.ts +3 -2
- package/yam.manifest.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -24,18 +24,20 @@
|
|
|
24
24
|
|
|
25
25
|
## Latest Update Trigger
|
|
26
26
|
|
|
27
|
-
When the user says exactly `yam 최신 업데이트 사항 반영해줘` in a chat for this project, treat that phrase as explicit authorization to
|
|
27
|
+
When the user says exactly `yam 최신 업데이트 사항 반영해줘` in a chat for this project, treat that phrase as explicit authorization to check and apply reviewed updates for yam, Scrapling, and Insane Search.
|
|
28
28
|
|
|
29
29
|
Run these commands sequentially from the yam project directory:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
|
|
33
|
-
yam
|
|
32
|
+
yam update check --json
|
|
33
|
+
yam update apply --all --json
|
|
34
34
|
yam version
|
|
35
35
|
yam status
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
- Request any environment approval required for network access or writes outside the workspace.
|
|
39
39
|
- Stop at the first failed command, report the blocker, and do not claim that later steps ran.
|
|
40
|
-
-
|
|
40
|
+
- Treat that exact phrase as one-time authorization for this run only; do not create an unattended updater, hook, or automation.
|
|
41
|
+
- Never edit `.codex/plugins/cache` directly. If Insane Search cannot be updated in place through the official Codex CLI, report `manual_plugin_update_required` and stop.
|
|
42
|
+
- After all four commands pass, summarize every component receipt, report the installed yam version and skill status, then tell the user that Codex can be restarted to reload updated skills/plugins.
|
|
41
43
|
- Do not restart Codex automatically.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to yam-flow are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.5.1 - 2026-07-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added bounded stdin readers with a 1 MiB hook limit and a 4 MiB general CLI limit. Malformed or oversized hook payloads now return a valid fail-open response without echoing or acting on rejected input.
|
|
10
|
+
- Added a final read-only `yam doctor --json` verification to yam updates and automatic rollbacks. The updater accepts success only when the command exits cleanly and returns the expected healthy Doctor contract.
|
|
11
|
+
- Added a release-time secret guard over the actual npm dry-run packlist. It scans regular UTF-8 text files for high-signal private-key and provider credential patterns while reporting only pattern, path, and line metadata.
|
|
12
|
+
|
|
13
|
+
### Improved
|
|
14
|
+
|
|
15
|
+
- Install and Ueye integrity manifests now use locale-independent ordinal ordering. Existing yam 2.5.0-and-earlier receipts retain compatibility with their stored legacy order while new digests are deterministic across locales.
|
|
16
|
+
|
|
17
|
+
### Security
|
|
18
|
+
|
|
19
|
+
- Rejected stdin payloads are byte-bounded, are not included in diagnostics, and cannot supply hook context or trigger workspace mutation.
|
|
20
|
+
- `verify:self`, `prepack`, `prepublishOnly`, and `release:check` now inherit the npm packlist secret guard; the inner pack inspection disables lifecycle scripts to avoid recursive execution.
|
|
21
|
+
- Updater Doctor output recorded in receipts remains redacted and bounded, and an unhealthy rollback is not reported as an automatic recovery.
|
|
22
|
+
|
|
23
|
+
### Verification
|
|
24
|
+
|
|
25
|
+
- TypeScript typecheck and build
|
|
26
|
+
- Bounded hook latency/read-only smoke and packaged CLI smoke
|
|
27
|
+
- Locale-independent install/Ueye manifest regression smokes
|
|
28
|
+
- External update/rollback Doctor smoke
|
|
29
|
+
- Npm packlist secret-pattern and redaction smoke
|
|
30
|
+
|
|
31
|
+
## v2.5.0 - 2026-07-30
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Added read-only `yam update check [--json]` for yam, Scrapling, and Insane Search with official registry/manifest versions and source-revision metadata.
|
|
36
|
+
- Added explicitly authorized component/all update application with per-component receipts, a concurrent-run lock, exact stable versions, verification evidence, and rollback guidance.
|
|
37
|
+
- Added isolated Scrapling version environments with `pip check`, HTTP extraction, browser extraction, and atomic executable switching; the previous environment remains available for rollback.
|
|
38
|
+
- Added deterministic temp-only updater smoke coverage, including receipt-failure symlink rollback, manual Codex plugin fallback, strict no-remove/cache behavior, and yam-last `--all` ordering.
|
|
39
|
+
|
|
40
|
+
### Improved
|
|
41
|
+
|
|
42
|
+
- The build now preserves executable mode on `dist/bin/yam.js`, and dist freshness fails if a global npm install would expose a non-executable `yam` target.
|
|
43
|
+
|
|
44
|
+
### Security
|
|
45
|
+
|
|
46
|
+
- Insane Search updates pin the official manifest to the exact observed Git revision and use only official Codex CLI commands. A missing safe in-place path returns `manual_plugin_update_required`; an unreadable post-upgrade state fails closed before `add`; yam never removes the plugin first or writes `.codex/plugins/cache` directly.
|
|
47
|
+
- External commands use argument arrays without a shell, version/component allowlists, redacted captured output, and fail-closed apply locking.
|
|
48
|
+
- Existing Scrapling links require a matching yam ownership marker, and an unwritable receipt stops `--all` before any later component mutation.
|
|
49
|
+
|
|
50
|
+
### Verification
|
|
51
|
+
|
|
52
|
+
- `npm run external-updates-smoke`
|
|
53
|
+
- `npm run typecheck`
|
|
54
|
+
- `npm run verify:self`
|
|
55
|
+
- `npm run cli-smoke`
|
|
56
|
+
- `npm run dist:freshness`
|
|
57
|
+
- `npm run package-boundary:check`
|
|
58
|
+
|
|
5
59
|
## v2.4.0 - 2026-07-27
|
|
6
60
|
|
|
7
61
|
### Added
|
package/COMMANDS.md
CHANGED
|
@@ -143,6 +143,19 @@ yam measure quick --files 3 --commands 1 --report-lines 5 --seconds 40
|
|
|
143
143
|
yam template tuning
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
## External Updates
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
yam update check
|
|
150
|
+
yam update check --json
|
|
151
|
+
yam update apply --component scrapling --json
|
|
152
|
+
yam update apply --component insane-search --json
|
|
153
|
+
yam update apply --component yam --json
|
|
154
|
+
yam update apply --all --json
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`yam update check` is read-only. Every `apply` needs explicit authorization, uses a concurrent-run lock, records a component receipt, and stops on failed/manual results. `--all` runs yam last. Insane Search is never remove-first and `.codex/plugins/cache` is never edited directly.
|
|
158
|
+
|
|
146
159
|
## Tool Doctor / Proof / Safety
|
|
147
160
|
|
|
148
161
|
Read-only readiness and evidence helpers. These do not install, deploy, query databases, or run verification by themselves.
|
package/README.md
CHANGED
|
@@ -99,6 +99,8 @@ yam hook status --global
|
|
|
99
99
|
|
|
100
100
|
The profile installs `UserPromptSubmit` and `Stop` handlers. At `Stop`, yam checks the latest assistant message with the same read-only Study Note Guard used by `yam study-note check`. If changed artifacts lack the required role, execution point, before/after, expected behavior, syntax/structure, verification, limits, or relevant architecture hygiene, Codex receives one correction prompt. A second failed check warns but does not loop forever. If Git change scope is unavailable, the guard reports partial truth and keeps a manual-inspection warning visible instead of claiming the project is clean. The hook never writes the Study Note or runs verification for the agent.
|
|
101
101
|
|
|
102
|
+
Hook stdin is limited to 1 MiB. Malformed JSON or an oversized payload produces a valid fail-open response without echoing the rejected content, deriving hook context from it, or mutating the workspace. Other CLI commands that read piped text use a 4 MiB limit and fail closed when it is exceeded.
|
|
103
|
+
|
|
102
104
|
`yam hook status` reports stale or missing command targets as `broken` and exits nonzero. Run `yam hook enable <profile> --global` again to create a timestamped backup, preserve unrelated hooks, and migrate that profile to the current installed path and event coverage. Restart Codex or open a new task after changing hooks.
|
|
103
105
|
|
|
104
106
|
## Install
|
|
@@ -120,7 +122,7 @@ yam install --replace-user-skill quick
|
|
|
120
122
|
|
|
121
123
|
Repeat `--replace-user-skill <name>` for each reviewed conflict. Unproven legacy, retired, and Codex mirror directories are preserved rather than deleted by name.
|
|
122
124
|
|
|
123
|
-
A successful install writes `~/.agents/skills/.yam-flow-install-receipt.json`. The receipt records the package version, source identity, install timestamp, destination, managed skill inventory, and per-file hashes. `yam status` independently recomputes source and installed hashes, so a stale package install, local file change, unexpected file, or modified receipt is reported as drift.
|
|
125
|
+
A successful install writes `~/.agents/skills/.yam-flow-install-receipt.json`. The receipt records the package version, source identity, install timestamp, destination, managed skill inventory, and per-file hashes. Integrity paths and digests use locale-independent ordinal ordering; receipts written by yam 2.5.0 and earlier are also checked against their stored legacy order during upgrade. `yam status` independently recomputes source and installed hashes, so a stale package install, local file change, unexpected file, or modified receipt is reported as drift.
|
|
124
126
|
|
|
125
127
|
The receipt is local integrity provenance, not a cryptographic signature or a security boundary against another process running as the same OS user. An actor that can rewrite both the receipt and every recorded file can forge local ownership evidence.
|
|
126
128
|
|
|
@@ -130,6 +132,54 @@ Manual copying can still make a skill loadable, but it intentionally remains unv
|
|
|
130
132
|
|
|
131
133
|
Restart Codex after installing so skills reload.
|
|
132
134
|
|
|
135
|
+
## External Updates
|
|
136
|
+
|
|
137
|
+
Update checks are read-only and never authorize a mutation by themselves:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
yam update check
|
|
141
|
+
yam update check --json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Apply requires an explicit component or `--all`:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
yam update apply --component scrapling
|
|
148
|
+
yam update apply --component insane-search
|
|
149
|
+
yam update apply --component yam
|
|
150
|
+
yam update apply --all --json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`--all` processes Scrapling, then Insane Search, then yam so the running updater replaces itself last. It stops at the first failed or manual-only component. A yam update is successful only after the installed version and status checks plus a final read-only `yam doctor --json` return the expected healthy contract. Doctor failure triggers the existing rollback path, and rollback is considered automatic only when its own version, status, and Doctor checks pass. Each attempt writes a component receipt under `~/.local/state/yam/update-receipts/` with the old/new version, source revision, checks, side effects, outcome, and rollback guidance. A lock prevents concurrent apply runs.
|
|
154
|
+
|
|
155
|
+
Scrapling updates install an exact stable PyPI version into a new versioned virtual environment. `pip check`, ordinary HTTP extraction, and headless browser extraction must pass before the executable symlink changes atomically; the previous environment is retained. An existing environment must have a matching `.yam-scrapling-install.json` ownership marker before yam will replace its link. Insane Search uses only the official `codex plugin` commands. yam never edits `.codex/plugins/cache`, never removes the plugin first, and returns `manual_plugin_update_required` when the installed Codex CLI cannot update it safely in place.
|
|
156
|
+
|
|
157
|
+
There is no unattended updater. The exact project trigger in `AGENTS.md` grants one-time authorization for a checked `--all` run, and Codex is never restarted automatically.
|
|
158
|
+
|
|
159
|
+
### Scrapling Usage
|
|
160
|
+
|
|
161
|
+
The isolated executable can be used directly from the shell:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
scrapling --version
|
|
165
|
+
scrapling extract get https://example.com example.txt --css-selector h1
|
|
166
|
+
scrapling extract stealthy-fetch https://example.com example-browser.txt --css-selector h1 --headless
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
From Python inside the installed environment:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from scrapling.fetchers import Fetcher, StealthyFetcher
|
|
173
|
+
|
|
174
|
+
page = Fetcher.get("https://example.com")
|
|
175
|
+
print(page.css("h1::text").get())
|
|
176
|
+
|
|
177
|
+
browser_page = StealthyFetcher.fetch("https://example.com", headless=True)
|
|
178
|
+
print(browser_page.css("h1::text").get())
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Use `Fetcher` for ordinary HTTP pages and `StealthyFetcher` only when a real browser or stronger anti-bot behavior is needed; the browser path is heavier.
|
|
182
|
+
|
|
133
183
|
## Uninstall
|
|
134
184
|
|
|
135
185
|
```bash
|
|
@@ -207,6 +257,8 @@ yam status
|
|
|
207
257
|
|
|
208
258
|
After `yam status` reports all skills and the install receipt as `ok`, restart Codex so the refreshed skills reload.
|
|
209
259
|
|
|
260
|
+
Before packaging or publishing, `verify:self` inspects the exact file list returned by `npm pack --dry-run --json --ignore-scripts`. The release gate scans regular UTF-8 text files up to 2 MiB for high-signal private-key, npm, GitHub, OpenAI, Anthropic, and AWS credential patterns. Findings expose only the pattern id, package-relative path, and line number; matched text is never printed. Binary, symlink, non-regular, unreadable, outside-root, and oversized entries are reported as skipped rather than silently represented as scanned.
|
|
261
|
+
|
|
210
262
|
Publishing still requires confirming the final npm package name and account access.
|
|
211
263
|
|
|
212
264
|
## Trust Kernel
|
package/ROADMAP.md
CHANGED
|
@@ -97,6 +97,7 @@ Tasks:
|
|
|
97
97
|
- Add symlink-friendly local install.
|
|
98
98
|
- Add version command. Done.
|
|
99
99
|
- Add staged transactional install, rollback, receipt, and drift-aware status. Done.
|
|
100
|
+
- Add checked component updates for yam, Scrapling, and Insane Search with explicit authorization, receipts, and rollback guidance. Done.
|
|
100
101
|
- Consider npm packaging only after route behavior stabilizes.
|
|
101
102
|
|
|
102
103
|
### 8. Scout / Research Workflow
|
|
@@ -171,7 +172,7 @@ Tasks:
|
|
|
171
172
|
|
|
172
173
|
- Add `.yam/memory/records/*.json` storage. Done.
|
|
173
174
|
- Add `yam memory init/add/list/summary/resolve`. Done.
|
|
174
|
-
- Add lightweight secret-pattern blocking. Done.
|
|
175
|
+
- Add lightweight secret-pattern blocking, including an actual npm dry-run packlist release guard with redacted findings. Done.
|
|
175
176
|
- Teach context reuse to prefer `summary.md`, not full record reads. Done.
|
|
176
177
|
- Keep memory opt-in only. Done.
|
|
177
178
|
|
|
@@ -307,6 +308,7 @@ Tasks:
|
|
|
307
308
|
- Keep `yam-lite` advisory-only through `UserPromptSubmit`. Done.
|
|
308
309
|
- Add Study Note `UserPromptSubmit` reminder plus one-pass `Stop` completion gate. Done.
|
|
309
310
|
- Detect broken hook targets and migrate stale paths without replacing unrelated hooks. Done.
|
|
311
|
+
- Bound hook/general stdin and fail open safely for rejected hook payloads. Done.
|
|
310
312
|
- Add `references/tool-trust-layer.md`. Done.
|
|
311
313
|
- Add read-only `yam tools doctor`. Done.
|
|
312
314
|
- Add `yam proof` summary command. Done.
|
|
@@ -317,6 +319,8 @@ Tasks:
|
|
|
317
319
|
- Add shallow `.sql` destructive keyword scan. Done.
|
|
318
320
|
- Add `yam proof write`. Done.
|
|
319
321
|
- Prepare npm/npx package metadata and dry-run packaging. Done.
|
|
322
|
+
- Make integrity-critical install and Ueye manifest ordering locale-independent. Done.
|
|
323
|
+
- Require a final read-only Doctor contract for yam update and rollback success. Done.
|
|
320
324
|
- Add small route-scoped trust kernel: truth caps, fake/real policy, runtime truth matrix, and completion proof object. Done.
|
|
321
325
|
|
|
322
326
|
### 16. Deferred Triggers
|
package/dist/bin/yam.js
CHANGED
|
@@ -11,6 +11,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { TRUTH_STATUSES, buildLoopReport, buildMediaGenerationProof, buildMissionCompletionGate, buildMissionPatchEnvelope, buildMissionSubagentReceipt, buildRollbackHint, buildRuntimeBackendEvidence, buildStudyNote, buildUeyeDesignCompletionGate, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
|
|
12
12
|
import { archiveUeyeRevision, inspectImageFile, upsertUeyeAsset, verifyUeyeAssetManifest, verifyUeyeRevisionHistory } from '../lib/ueye-artifacts.js';
|
|
13
13
|
import { inspectSkillInstallation, installSkillSetTransactional, uninstallSkillSetSafely } from '../lib/skill-installation.js';
|
|
14
|
+
import { EXTERNAL_UPDATE_COMPONENTS, applyExternalUpdates, checkExternalUpdates } from '../lib/external-updates.js';
|
|
15
|
+
import { BoundedInputError, GENERAL_STDIN_MAX_BYTES, HOOK_STDIN_MAX_BYTES, readBoundedStdinJson, readBoundedStdinText } from '../lib/bounded-input.js';
|
|
14
16
|
function errorMessage(error) {
|
|
15
17
|
return error instanceof Error ? error.message : String(error);
|
|
16
18
|
}
|
|
@@ -146,6 +148,9 @@ Usage:
|
|
|
146
148
|
yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
|
|
147
149
|
yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
|
|
148
150
|
yam mission gate [--expected-thread id] [--receipt file] [--json]
|
|
151
|
+
yam update check [--json]
|
|
152
|
+
yam update apply --component yam|scrapling|insane-search [--json]
|
|
153
|
+
yam update apply --all [--json]
|
|
149
154
|
yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
|
|
150
155
|
yam release report [--json]
|
|
151
156
|
yam safety [text...]
|
|
@@ -2004,7 +2009,20 @@ async function hookRun(args = []) {
|
|
|
2004
2009
|
console.log(JSON.stringify({ continue: true }));
|
|
2005
2010
|
return;
|
|
2006
2011
|
}
|
|
2007
|
-
|
|
2012
|
+
let input;
|
|
2013
|
+
try {
|
|
2014
|
+
input = await readStdinJson();
|
|
2015
|
+
}
|
|
2016
|
+
catch (error) {
|
|
2017
|
+
if (!(error instanceof BoundedInputError))
|
|
2018
|
+
throw error;
|
|
2019
|
+
const code = error.code === 'invalid_json' ? 'invalid_hook_json' : error.code;
|
|
2020
|
+
console.log(JSON.stringify({
|
|
2021
|
+
continue: true,
|
|
2022
|
+
systemMessage: `yam-${profile} hook input rejected: ${code}; continuing without using or echoing the payload.`
|
|
2023
|
+
}));
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2008
2026
|
const event = input?.hook_event_name || input?.hookEventName || input?.event || 'UserPromptSubmit';
|
|
2009
2027
|
const cwd = String(input?.cwd || process.cwd());
|
|
2010
2028
|
if (profile === 'study-note' && event === 'Stop') {
|
|
@@ -2025,18 +2043,7 @@ async function hookRun(args = []) {
|
|
|
2025
2043
|
console.log(JSON.stringify(output));
|
|
2026
2044
|
}
|
|
2027
2045
|
async function readStdinJson() {
|
|
2028
|
-
|
|
2029
|
-
for await (const chunk of process.stdin)
|
|
2030
|
-
chunks.push(Buffer.from(chunk));
|
|
2031
|
-
const text = Buffer.concat(chunks).toString('utf8').trim();
|
|
2032
|
-
if (!text)
|
|
2033
|
-
return {};
|
|
2034
|
-
try {
|
|
2035
|
-
return JSON.parse(text);
|
|
2036
|
-
}
|
|
2037
|
-
catch {
|
|
2038
|
-
return {};
|
|
2039
|
-
}
|
|
2046
|
+
return readBoundedStdinJson(process.stdin, HOOK_STDIN_MAX_BYTES);
|
|
2040
2047
|
}
|
|
2041
2048
|
function extractPrompt(input = {}) {
|
|
2042
2049
|
const candidates = [
|
|
@@ -2667,10 +2674,7 @@ function redactSensitiveText(text = '') {
|
|
|
2667
2674
|
async function readStdinTextIfAvailable() {
|
|
2668
2675
|
if (process.stdin.isTTY)
|
|
2669
2676
|
return '';
|
|
2670
|
-
|
|
2671
|
-
for await (const chunk of process.stdin)
|
|
2672
|
-
chunks.push(Buffer.from(chunk));
|
|
2673
|
-
return Buffer.concat(chunks).toString('utf8').trim();
|
|
2677
|
+
return (await readBoundedStdinText(process.stdin, GENERAL_STDIN_MAX_BYTES)).trim();
|
|
2674
2678
|
}
|
|
2675
2679
|
async function loop(args = []) {
|
|
2676
2680
|
const subcommand = args[0] || 'help';
|
|
@@ -3556,6 +3560,86 @@ async function mission(args = []) {
|
|
|
3556
3560
|
console.error(`unknown mission command: ${subcommand}`);
|
|
3557
3561
|
return missionUsage();
|
|
3558
3562
|
}
|
|
3563
|
+
async function updateCommand(args = []) {
|
|
3564
|
+
const subcommand = args[0] || 'help';
|
|
3565
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
3566
|
+
return updateUsage();
|
|
3567
|
+
if (subcommand === 'check') {
|
|
3568
|
+
const rest = args.slice(1);
|
|
3569
|
+
const invalid = rest.filter((arg) => arg !== '--json');
|
|
3570
|
+
if (invalid.length || rest.filter((arg) => arg === '--json').length > 1) {
|
|
3571
|
+
throw new Error(`invalid update check option: ${invalid[0] || '--json repeated'}`);
|
|
3572
|
+
}
|
|
3573
|
+
const report = await checkExternalUpdates(VERSION);
|
|
3574
|
+
printJsonOrHuman(report, rest.includes('--json'), 'External update check');
|
|
3575
|
+
if (!report.success)
|
|
3576
|
+
process.exitCode = 1;
|
|
3577
|
+
return;
|
|
3578
|
+
}
|
|
3579
|
+
if (subcommand !== 'apply') {
|
|
3580
|
+
throw new Error(`unknown update command: ${subcommand}`);
|
|
3581
|
+
}
|
|
3582
|
+
const parsed = parseUpdateApplyArgs(args.slice(1));
|
|
3583
|
+
const report = await applyExternalUpdates(VERSION, {
|
|
3584
|
+
component: parsed.component,
|
|
3585
|
+
all: parsed.all
|
|
3586
|
+
});
|
|
3587
|
+
printJsonOrHuman(report, parsed.json, 'External update apply');
|
|
3588
|
+
if (!report.success)
|
|
3589
|
+
process.exitCode = 1;
|
|
3590
|
+
}
|
|
3591
|
+
function parseUpdateApplyArgs(args) {
|
|
3592
|
+
let component;
|
|
3593
|
+
let all = false;
|
|
3594
|
+
let json = false;
|
|
3595
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
3596
|
+
const arg = String(args[index] || '');
|
|
3597
|
+
if (arg === '--json') {
|
|
3598
|
+
if (json)
|
|
3599
|
+
throw new Error('--json may be supplied only once');
|
|
3600
|
+
json = true;
|
|
3601
|
+
continue;
|
|
3602
|
+
}
|
|
3603
|
+
if (arg === '--all') {
|
|
3604
|
+
if (all)
|
|
3605
|
+
throw new Error('--all may be supplied only once');
|
|
3606
|
+
all = true;
|
|
3607
|
+
continue;
|
|
3608
|
+
}
|
|
3609
|
+
if (arg === '--component' || arg.startsWith('--component=')) {
|
|
3610
|
+
if (component)
|
|
3611
|
+
throw new Error('--component may be supplied only once');
|
|
3612
|
+
const value = arg === '--component' ? String(args[index + 1] || '') : arg.slice('--component='.length);
|
|
3613
|
+
if (arg === '--component')
|
|
3614
|
+
index += 1;
|
|
3615
|
+
if (!EXTERNAL_UPDATE_COMPONENTS.includes(value)) {
|
|
3616
|
+
throw new Error('component must be one of: yam, scrapling, insane-search');
|
|
3617
|
+
}
|
|
3618
|
+
component = value;
|
|
3619
|
+
continue;
|
|
3620
|
+
}
|
|
3621
|
+
throw new Error(`invalid update apply option: ${arg}`);
|
|
3622
|
+
}
|
|
3623
|
+
if ((all && component) || (!all && !component)) {
|
|
3624
|
+
throw new Error('choose exactly one of --component yam|scrapling|insane-search or --all');
|
|
3625
|
+
}
|
|
3626
|
+
return { component, all, json };
|
|
3627
|
+
}
|
|
3628
|
+
function updateUsage() {
|
|
3629
|
+
console.log(`yam update
|
|
3630
|
+
|
|
3631
|
+
Usage:
|
|
3632
|
+
yam update check [--json]
|
|
3633
|
+
yam update apply --component yam|scrapling|insane-search [--json]
|
|
3634
|
+
yam update apply --all [--json]
|
|
3635
|
+
|
|
3636
|
+
Safety:
|
|
3637
|
+
check is read-only. apply requires an explicit component or --all.
|
|
3638
|
+
--all updates Scrapling and Insane Search before yam, then stops at the first failed/manual component.
|
|
3639
|
+
Insane Search is updated only through the official Codex CLI; plugin caches are never edited directly.
|
|
3640
|
+
Every apply attempt records component checks, outcome, side effects, and rollback guidance.
|
|
3641
|
+
`);
|
|
3642
|
+
}
|
|
3559
3643
|
function missionUsage() {
|
|
3560
3644
|
console.log(`yam mission
|
|
3561
3645
|
|
|
@@ -5313,6 +5397,7 @@ function showRequestedHelp(args) {
|
|
|
5313
5397
|
media: mediaUsage,
|
|
5314
5398
|
runtime: runtimeUsage,
|
|
5315
5399
|
mission: missionUsage,
|
|
5400
|
+
update: updateUsage,
|
|
5316
5401
|
benchmark: benchmarkUsage,
|
|
5317
5402
|
memory: memoryUsage
|
|
5318
5403
|
};
|
|
@@ -5360,6 +5445,8 @@ async function main() {
|
|
|
5360
5445
|
return runtime(process.argv.slice(3));
|
|
5361
5446
|
if (command === 'mission')
|
|
5362
5447
|
return mission(process.argv.slice(3));
|
|
5448
|
+
if (command === 'update')
|
|
5449
|
+
return updateCommand(process.argv.slice(3));
|
|
5363
5450
|
if (command === 'benchmark')
|
|
5364
5451
|
return benchmark(process.argv.slice(3));
|
|
5365
5452
|
if (command === 'release')
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const HOOK_STDIN_MAX_BYTES: number;
|
|
2
|
+
export declare const GENERAL_STDIN_MAX_BYTES: number;
|
|
3
|
+
export type BoundedInputErrorCode = 'input_too_large' | 'invalid_json';
|
|
4
|
+
export declare class BoundedInputError extends Error {
|
|
5
|
+
readonly code: BoundedInputErrorCode;
|
|
6
|
+
readonly maxBytes?: number;
|
|
7
|
+
constructor(code: BoundedInputErrorCode, options?: {
|
|
8
|
+
maxBytes?: number;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare function readBoundedStdinText(input: NodeJS.ReadableStream & AsyncIterable<string | Buffer>, maxBytes: number): Promise<string>;
|
|
12
|
+
export declare function readBoundedStdinJson(input: NodeJS.ReadableStream & AsyncIterable<string | Buffer>, maxBytes: number): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const HOOK_STDIN_MAX_BYTES = 1024 * 1024;
|
|
2
|
+
export const GENERAL_STDIN_MAX_BYTES = 4 * 1024 * 1024;
|
|
3
|
+
export class BoundedInputError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
maxBytes;
|
|
6
|
+
constructor(code, options = {}) {
|
|
7
|
+
const detail = code === 'input_too_large' && options.maxBytes
|
|
8
|
+
? `stdin input exceeds the ${options.maxBytes}-byte limit`
|
|
9
|
+
: 'stdin input is not a valid JSON object';
|
|
10
|
+
super(`[${code}] ${detail}`);
|
|
11
|
+
this.name = 'BoundedInputError';
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.maxBytes = options.maxBytes;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function readBoundedStdinText(input, maxBytes) {
|
|
17
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
|
|
18
|
+
throw new Error('stdin byte limit must be a positive safe integer');
|
|
19
|
+
}
|
|
20
|
+
const chunks = [];
|
|
21
|
+
let totalBytes = 0;
|
|
22
|
+
for await (const chunk of input) {
|
|
23
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
24
|
+
totalBytes += bytes.byteLength;
|
|
25
|
+
if (totalBytes > maxBytes) {
|
|
26
|
+
throw new BoundedInputError('input_too_large', { maxBytes });
|
|
27
|
+
}
|
|
28
|
+
chunks.push(bytes);
|
|
29
|
+
}
|
|
30
|
+
return Buffer.concat(chunks, totalBytes).toString('utf8');
|
|
31
|
+
}
|
|
32
|
+
export async function readBoundedStdinJson(input, maxBytes) {
|
|
33
|
+
const text = (await readBoundedStdinText(input, maxBytes)).trim();
|
|
34
|
+
if (!text)
|
|
35
|
+
return {};
|
|
36
|
+
try {
|
|
37
|
+
const value = JSON.parse(text);
|
|
38
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
39
|
+
throw new BoundedInputError('invalid_json');
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof BoundedInputError)
|
|
45
|
+
throw error;
|
|
46
|
+
throw new BoundedInputError('invalid_json');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export declare const EXTERNAL_UPDATE_COMPONENTS: readonly ["yam", "scrapling", "insane-search"];
|
|
2
|
+
export type ExternalUpdateComponent = typeof EXTERNAL_UPDATE_COMPONENTS[number];
|
|
3
|
+
type JsonRecord = Record<string, any>;
|
|
4
|
+
export interface ExternalCommandResult {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
status: number | null;
|
|
7
|
+
stdout: string;
|
|
8
|
+
stderr: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ExternalCommandOptions {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ExternalUpdatePaths {
|
|
16
|
+
stateDir: string;
|
|
17
|
+
receiptDir: string;
|
|
18
|
+
lockFile: string;
|
|
19
|
+
scraplingRoot: string;
|
|
20
|
+
scraplingBin: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ExternalUpdateDependencies {
|
|
23
|
+
run?: (command: string, args: string[], options?: ExternalCommandOptions) => Promise<ExternalCommandResult> | ExternalCommandResult;
|
|
24
|
+
fetchJson?: (url: string) => Promise<any>;
|
|
25
|
+
now?: () => Date;
|
|
26
|
+
homeDir?: string;
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
paths?: Partial<ExternalUpdatePaths>;
|
|
29
|
+
removeCandidate?: (candidate: string, root: string) => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export interface ExternalComponentCheck {
|
|
32
|
+
component: ExternalUpdateComponent;
|
|
33
|
+
installed_version: string;
|
|
34
|
+
latest_version: string;
|
|
35
|
+
update_available: boolean;
|
|
36
|
+
status: 'up_to_date' | 'update_available' | 'not_installed' | 'check_failed';
|
|
37
|
+
source: string;
|
|
38
|
+
source_revision?: {
|
|
39
|
+
local: string;
|
|
40
|
+
remote: string;
|
|
41
|
+
drift: boolean;
|
|
42
|
+
note: string;
|
|
43
|
+
};
|
|
44
|
+
executable?: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ExternalComponentReceipt {
|
|
48
|
+
schema: 'yam.external-update-receipt.v1';
|
|
49
|
+
generated_at: string;
|
|
50
|
+
component: ExternalUpdateComponent;
|
|
51
|
+
outcome: 'up_to_date' | 'updated' | 'failed' | 'manual_plugin_update_required';
|
|
52
|
+
previous_version: string;
|
|
53
|
+
requested_version: string;
|
|
54
|
+
installed_version: string;
|
|
55
|
+
source: string;
|
|
56
|
+
source_revision: JsonRecord | null;
|
|
57
|
+
checks: Array<{
|
|
58
|
+
id: string;
|
|
59
|
+
status: 'passed' | 'failed' | 'skipped';
|
|
60
|
+
note: string;
|
|
61
|
+
}>;
|
|
62
|
+
rollback_hint: {
|
|
63
|
+
automatic: boolean;
|
|
64
|
+
previous_target: string;
|
|
65
|
+
guidance: string;
|
|
66
|
+
};
|
|
67
|
+
side_effects: string[];
|
|
68
|
+
error: string;
|
|
69
|
+
truth_status: 'verified' | 'partial' | 'blocked';
|
|
70
|
+
persistence?: 'written' | 'failed';
|
|
71
|
+
receipt_path?: string;
|
|
72
|
+
receipt_error?: string;
|
|
73
|
+
}
|
|
74
|
+
export declare function checkExternalUpdates(currentYamVersion: string, dependencies?: ExternalUpdateDependencies): Promise<{
|
|
75
|
+
schema: string;
|
|
76
|
+
generated_at: string;
|
|
77
|
+
mutation_authorized: boolean;
|
|
78
|
+
components: [ExternalComponentCheck, ExternalComponentCheck, ExternalComponentCheck];
|
|
79
|
+
update_available: boolean;
|
|
80
|
+
success: boolean;
|
|
81
|
+
truth_status: string;
|
|
82
|
+
next_action: string;
|
|
83
|
+
}>;
|
|
84
|
+
export declare function applyExternalUpdates(currentYamVersion: string, selection: {
|
|
85
|
+
component?: ExternalUpdateComponent;
|
|
86
|
+
all?: boolean;
|
|
87
|
+
}, dependencies?: ExternalUpdateDependencies): Promise<{
|
|
88
|
+
schema: string;
|
|
89
|
+
generated_at: string;
|
|
90
|
+
mutation_authorized: boolean;
|
|
91
|
+
requested_components: ("yam" | "scrapling" | "insane-search")[];
|
|
92
|
+
applied_components: ("yam" | "scrapling" | "insane-search")[];
|
|
93
|
+
receipts: ExternalComponentReceipt[];
|
|
94
|
+
stopped_early: boolean;
|
|
95
|
+
lock: {
|
|
96
|
+
path: string;
|
|
97
|
+
released: boolean;
|
|
98
|
+
};
|
|
99
|
+
success: boolean;
|
|
100
|
+
truth_status: string;
|
|
101
|
+
next_action: string;
|
|
102
|
+
}>;
|
|
103
|
+
export {};
|