wotann 0.5.92 → 0.5.94
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.
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
* escalation, agent config persistence, hardcoded secrets, and invisible text.
|
|
8
8
|
*/
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
|
-
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync
|
|
10
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
11
11
|
import { extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
12
|
-
export const TRUSTED_SKILL_REPOS = new Set([
|
|
12
|
+
export const TRUSTED_SKILL_REPOS = new Set([
|
|
13
|
+
"openai/skills",
|
|
14
|
+
"anthropics/skills",
|
|
15
|
+
"huggingface/skills",
|
|
16
|
+
]);
|
|
13
17
|
export const INSTALL_POLICY = {
|
|
14
18
|
builtin: ["allow", "allow", "allow"],
|
|
15
19
|
trusted: ["allow", "allow", "block"],
|
|
@@ -405,7 +409,14 @@ export function contentHash(path) {
|
|
|
405
409
|
return `sha256:${h.digest("hex").slice(0, 16)}`;
|
|
406
410
|
}
|
|
407
411
|
function p(pattern, patternId, severity, category, description, recommendation) {
|
|
408
|
-
return {
|
|
412
|
+
return {
|
|
413
|
+
regex: new RegExp(pattern, "i"),
|
|
414
|
+
patternId,
|
|
415
|
+
severity,
|
|
416
|
+
category,
|
|
417
|
+
description,
|
|
418
|
+
recommendation,
|
|
419
|
+
};
|
|
409
420
|
}
|
|
410
421
|
function buildScanResult(input) {
|
|
411
422
|
const issues = deduplicateIssues(input.findings);
|
|
@@ -489,7 +500,17 @@ function checkStructure(skillDir) {
|
|
|
489
500
|
return findings;
|
|
490
501
|
}
|
|
491
502
|
function structuralIssue(patternId, severity, category, file, match, description, recommendation) {
|
|
492
|
-
return {
|
|
503
|
+
return {
|
|
504
|
+
pattern: category,
|
|
505
|
+
patternId,
|
|
506
|
+
category,
|
|
507
|
+
file,
|
|
508
|
+
line: 0,
|
|
509
|
+
match,
|
|
510
|
+
severity,
|
|
511
|
+
description,
|
|
512
|
+
recommendation,
|
|
513
|
+
};
|
|
493
514
|
}
|
|
494
515
|
function listFiles(root) {
|
|
495
516
|
return listEntries(root).filter((entry) => {
|
|
@@ -524,7 +545,7 @@ function isInside(child, parent) {
|
|
|
524
545
|
function worstSeverity(issues) {
|
|
525
546
|
if (issues.length === 0)
|
|
526
547
|
return "info";
|
|
527
|
-
return issues.reduce((worst, issue) =>
|
|
548
|
+
return issues.reduce((worst, issue) => SEVERITY_ORDER[issue.severity] < SEVERITY_ORDER[worst] ? issue.severity : worst, "info");
|
|
528
549
|
}
|
|
529
550
|
function compareIssueSeverity(a, b) {
|
|
530
551
|
return SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];
|
|
@@ -38,6 +38,20 @@ export interface RawModeStream {
|
|
|
38
38
|
* state is preserved when it works) inside a try/catch. If it throws
|
|
39
39
|
* here, the resolver falls through to `/dev/tty` or refuses cleanly
|
|
40
40
|
* instead of mounting Ink into the silent loop.
|
|
41
|
+
*
|
|
42
|
+
* Node 25 `_handle` exception (added 2026-05-28 — empirical evidence
|
|
43
|
+
* from user diagnostic): on Node v25.x, the probe call throws
|
|
44
|
+
* "Cannot read properties of undefined (reading '_handle')"
|
|
45
|
+
* because the libuv TTY binding (`this._handle`) isn't initialized at
|
|
46
|
+
* probe time — Node 25 lazily binds it on first I/O. The same
|
|
47
|
+
* `setRawMode` call AT INK'S RENDER COMMIT succeeds because by then
|
|
48
|
+
* Ink has read from the stream and the binding is live. Treating
|
|
49
|
+
* this specific error as "passes" (optimistic) lets the mount
|
|
50
|
+
* proceed; the `mount-interactive-ink` Ink-throw backstop catches
|
|
51
|
+
* any genuine failure at render time and writes the same actionable
|
|
52
|
+
* guidance. False-positive cost: a runtime error message instead of
|
|
53
|
+
* a guard refusal — same end UX. True-positive value: working TUI
|
|
54
|
+
* under Node 25, which is the user's current default.
|
|
41
55
|
*/
|
|
42
56
|
export declare function isRawModeCapable(stream: unknown): boolean;
|
|
43
57
|
export interface ResolveStdinOptions {
|
|
@@ -46,10 +60,26 @@ export interface ResolveStdinOptions {
|
|
|
46
60
|
}
|
|
47
61
|
/**
|
|
48
62
|
* Resolve an input stream Ink can drive `useInput()` with:
|
|
49
|
-
* 1. `
|
|
50
|
-
* 2. else
|
|
63
|
+
* 1. controlling terminal (`/dev/tty`) if raw-capable — preferred.
|
|
64
|
+
* 2. else `stdin` if it is raw-capable — fallback for CI / Windows /
|
|
65
|
+
* detached processes that have no controlling terminal.
|
|
51
66
|
* 3. else `null` — caller MUST NOT mount the interactive Ink app
|
|
52
67
|
* (it would hang); print guidance + exit instead.
|
|
68
|
+
*
|
|
69
|
+
* Why /dev/tty FIRST (changed 2026-05-28 from stdin-first):
|
|
70
|
+
* Under `npx wotann`, `npm exec`, `sudo`, and other launchers, the
|
|
71
|
+
* subprocess's `process.stdin` can pass the static shape check
|
|
72
|
+
* (`isTTY === true`, `setRawMode` is a function) yet behave oddly
|
|
73
|
+
* when Ink actually drives it — the probe's no-op `setRawMode(false)`
|
|
74
|
+
* succeeds while Ink's `setRawMode(true)` then fails, the stream is in
|
|
75
|
+
* flowing mode because some boot import attached a `data` listener,
|
|
76
|
+
* the launcher transformed the descriptor, etc. The controlling
|
|
77
|
+
* terminal is the canonical "user's keyboard" source and is
|
|
78
|
+
* unaffected by any of that — the same pattern `less`, `vim`, `sudo`,
|
|
79
|
+
* and `git rebase -i` use for stdin-needs-tty scenarios. The fallback
|
|
80
|
+
* to `stdin` covers the no-`/dev/tty` cases (CI containers, Windows
|
|
81
|
+
* without a console, detached processes).
|
|
82
|
+
*
|
|
53
83
|
* Never throws.
|
|
54
84
|
*/
|
|
55
85
|
export declare function resolveInteractiveStdin(stdin?: unknown, opts?: ResolveStdinOptions): NodeJS.ReadStream | null;
|
|
@@ -77,10 +107,14 @@ export interface StdinDiagnosis {
|
|
|
77
107
|
readonly ttyProbeResult?: string;
|
|
78
108
|
}
|
|
79
109
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
110
|
+
* Records the full state of BOTH potential input sources — /dev/tty
|
|
111
|
+
* (the preferred source after the 2026-05-28 reorder) and `stdin`
|
|
112
|
+
* (the fallback). Always walks both regardless of whether the
|
|
113
|
+
* resolver would have short-circuited, because the diagnostic is most
|
|
114
|
+
* useful when something looks wrong with one source: the user / next
|
|
115
|
+
* agent gets the full picture, not "stdin worked, didn't look at tty".
|
|
116
|
+
*
|
|
117
|
+
* Never throws.
|
|
84
118
|
*/
|
|
85
119
|
export declare function diagnoseStdin(stdin?: unknown, opts?: ResolveStdinOptions): StdinDiagnosis;
|
|
86
120
|
/**
|
|
@@ -35,6 +35,20 @@ import { ReadStream } from "node:tty";
|
|
|
35
35
|
* state is preserved when it works) inside a try/catch. If it throws
|
|
36
36
|
* here, the resolver falls through to `/dev/tty` or refuses cleanly
|
|
37
37
|
* instead of mounting Ink into the silent loop.
|
|
38
|
+
*
|
|
39
|
+
* Node 25 `_handle` exception (added 2026-05-28 — empirical evidence
|
|
40
|
+
* from user diagnostic): on Node v25.x, the probe call throws
|
|
41
|
+
* "Cannot read properties of undefined (reading '_handle')"
|
|
42
|
+
* because the libuv TTY binding (`this._handle`) isn't initialized at
|
|
43
|
+
* probe time — Node 25 lazily binds it on first I/O. The same
|
|
44
|
+
* `setRawMode` call AT INK'S RENDER COMMIT succeeds because by then
|
|
45
|
+
* Ink has read from the stream and the binding is live. Treating
|
|
46
|
+
* this specific error as "passes" (optimistic) lets the mount
|
|
47
|
+
* proceed; the `mount-interactive-ink` Ink-throw backstop catches
|
|
48
|
+
* any genuine failure at render time and writes the same actionable
|
|
49
|
+
* guidance. False-positive cost: a runtime error message instead of
|
|
50
|
+
* a guard refusal — same end UX. True-positive value: working TUI
|
|
51
|
+
* under Node 25, which is the user's current default.
|
|
38
52
|
*/
|
|
39
53
|
export function isRawModeCapable(stream) {
|
|
40
54
|
if (stream === null || typeof stream !== "object")
|
|
@@ -48,10 +62,22 @@ export function isRawModeCapable(stream) {
|
|
|
48
62
|
setRawMode(currentIsRaw);
|
|
49
63
|
return true;
|
|
50
64
|
}
|
|
51
|
-
catch {
|
|
52
|
-
return
|
|
65
|
+
catch (e) {
|
|
66
|
+
return isNode25HandleInitDefer(e);
|
|
53
67
|
}
|
|
54
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Detect the Node 25+ "_handle is undefined at probe time" pattern.
|
|
71
|
+
* Matches both the literal `_handle` reference and the broader libuv
|
|
72
|
+
* binding init-order race that surfaces under the same wording. Kept
|
|
73
|
+
* separate from `isRawModeCapable` so the rationale lives next to
|
|
74
|
+
* the matcher and the same logic can drive `probeStdinDetail`.
|
|
75
|
+
*/
|
|
76
|
+
function isNode25HandleInitDefer(e) {
|
|
77
|
+
if (!(e instanceof Error))
|
|
78
|
+
return false;
|
|
79
|
+
return e.message.includes("_handle");
|
|
80
|
+
}
|
|
55
81
|
/**
|
|
56
82
|
* Open the controlling terminal as a raw-capable input stream, or
|
|
57
83
|
* `null` if there is none (Windows without a console, detached
|
|
@@ -72,24 +98,42 @@ function defaultOpenControllingTty() {
|
|
|
72
98
|
}
|
|
73
99
|
/**
|
|
74
100
|
* Resolve an input stream Ink can drive `useInput()` with:
|
|
75
|
-
* 1. `
|
|
76
|
-
* 2. else
|
|
101
|
+
* 1. controlling terminal (`/dev/tty`) if raw-capable — preferred.
|
|
102
|
+
* 2. else `stdin` if it is raw-capable — fallback for CI / Windows /
|
|
103
|
+
* detached processes that have no controlling terminal.
|
|
77
104
|
* 3. else `null` — caller MUST NOT mount the interactive Ink app
|
|
78
105
|
* (it would hang); print guidance + exit instead.
|
|
106
|
+
*
|
|
107
|
+
* Why /dev/tty FIRST (changed 2026-05-28 from stdin-first):
|
|
108
|
+
* Under `npx wotann`, `npm exec`, `sudo`, and other launchers, the
|
|
109
|
+
* subprocess's `process.stdin` can pass the static shape check
|
|
110
|
+
* (`isTTY === true`, `setRawMode` is a function) yet behave oddly
|
|
111
|
+
* when Ink actually drives it — the probe's no-op `setRawMode(false)`
|
|
112
|
+
* succeeds while Ink's `setRawMode(true)` then fails, the stream is in
|
|
113
|
+
* flowing mode because some boot import attached a `data` listener,
|
|
114
|
+
* the launcher transformed the descriptor, etc. The controlling
|
|
115
|
+
* terminal is the canonical "user's keyboard" source and is
|
|
116
|
+
* unaffected by any of that — the same pattern `less`, `vim`, `sudo`,
|
|
117
|
+
* and `git rebase -i` use for stdin-needs-tty scenarios. The fallback
|
|
118
|
+
* to `stdin` covers the no-`/dev/tty` cases (CI containers, Windows
|
|
119
|
+
* without a console, detached processes).
|
|
120
|
+
*
|
|
79
121
|
* Never throws.
|
|
80
122
|
*/
|
|
81
123
|
export function resolveInteractiveStdin(stdin = process.stdin, opts = {}) {
|
|
82
|
-
if (isRawModeCapable(stdin))
|
|
83
|
-
return stdin;
|
|
84
124
|
const open = opts.openControllingTty ?? defaultOpenControllingTty;
|
|
85
125
|
let tty = null;
|
|
86
126
|
try {
|
|
87
127
|
tty = open();
|
|
88
128
|
}
|
|
89
129
|
catch {
|
|
90
|
-
|
|
130
|
+
tty = null;
|
|
91
131
|
}
|
|
92
|
-
|
|
132
|
+
if (tty !== null && isRawModeCapable(tty))
|
|
133
|
+
return tty;
|
|
134
|
+
if (isRawModeCapable(stdin))
|
|
135
|
+
return stdin;
|
|
136
|
+
return null;
|
|
93
137
|
}
|
|
94
138
|
/**
|
|
95
139
|
* Same observation pattern as `isRawModeCapable` but captures the
|
|
@@ -123,21 +167,16 @@ function probeStdinDetail(stream) {
|
|
|
123
167
|
}
|
|
124
168
|
}
|
|
125
169
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
170
|
+
* Records the full state of BOTH potential input sources — /dev/tty
|
|
171
|
+
* (the preferred source after the 2026-05-28 reorder) and `stdin`
|
|
172
|
+
* (the fallback). Always walks both regardless of whether the
|
|
173
|
+
* resolver would have short-circuited, because the diagnostic is most
|
|
174
|
+
* useful when something looks wrong with one source: the user / next
|
|
175
|
+
* agent gets the full picture, not "stdin worked, didn't look at tty".
|
|
176
|
+
*
|
|
177
|
+
* Never throws.
|
|
130
178
|
*/
|
|
131
179
|
export function diagnoseStdin(stdin = process.stdin, opts = {}) {
|
|
132
|
-
const stdinProbe = probeStdinDetail(stdin);
|
|
133
|
-
if (stdinProbe.probeResult === "ok") {
|
|
134
|
-
return {
|
|
135
|
-
stdinIsTTY: stdinProbe.isTTY,
|
|
136
|
-
stdinHasSetRawMode: stdinProbe.hasSetRawMode,
|
|
137
|
-
stdinProbeResult: stdinProbe.probeResult,
|
|
138
|
-
ttyOpenResult: "not-attempted",
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
180
|
const open = opts.openControllingTty ?? defaultOpenControllingTty;
|
|
142
181
|
let tty = null;
|
|
143
182
|
let ttyOpenResult;
|
|
@@ -148,6 +187,7 @@ export function diagnoseStdin(stdin = process.stdin, opts = {}) {
|
|
|
148
187
|
catch (e) {
|
|
149
188
|
ttyOpenResult = e instanceof Error ? e.message : String(e);
|
|
150
189
|
}
|
|
190
|
+
const stdinProbe = probeStdinDetail(stdin);
|
|
151
191
|
if (tty === null) {
|
|
152
192
|
return {
|
|
153
193
|
stdinIsTTY: stdinProbe.isTTY,
|