psiguard 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/index.d.ts +20 -5
- package/dist/index.js +103 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,42 @@ if (result.intervened) {
|
|
|
89
89
|
showToUser(result.answer);
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
## Seeing the status in your terminal
|
|
93
|
+
|
|
94
|
+
When you're the one at the keyboard — a script, a smoke test, an internal tool —
|
|
95
|
+
the protection value is easy to miss in a wall of plain white text. These
|
|
96
|
+
optional helpers make it impossible to miss:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { PsiGuard, formatResult } from "psiguard";
|
|
100
|
+
|
|
101
|
+
const result = await new PsiGuard().guard("What is your return policy?");
|
|
102
|
+
console.log(formatResult(result));
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
● SAFE clean run — PsiGuard didn't need to act
|
|
107
|
+
│ Our return policy allows returns within 30 days of delivery...
|
|
108
|
+
|
|
109
|
+
▲ CAUTION steered back on course mid-answer
|
|
110
|
+
│ I'd check with support on that specific case...
|
|
111
|
+
|
|
112
|
+
■ INTERVENED response withheld — the answer below is a refusal
|
|
113
|
+
│ I'm not able to help with that.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Green dot, amber triangle, red square — so it still reads when it's colorless or
|
|
117
|
+
when the reader is colorblind. `badge(result.protection)` gives you just the chip
|
|
118
|
+
if you'd rather build your own layout around it.
|
|
119
|
+
|
|
120
|
+
**The client never prints, and never puts color inside `result.answer`.** That
|
|
121
|
+
string is what your user sees, and it may end up in a log file, a database, or a
|
|
122
|
+
web page — where terminal color codes turn into visible junk like `←[32m`. So
|
|
123
|
+
color lives only in these display helpers, which you call when you want them.
|
|
124
|
+
`formatResult` also turns color off by itself when stdout isn't a real terminal
|
|
125
|
+
(piped to a file, running in CI), and honors the standard `NO_COLOR` environment
|
|
126
|
+
variable. Set `PSIGUARD_COLOR=1` to force it on.
|
|
127
|
+
|
|
92
128
|
## Handling errors
|
|
93
129
|
|
|
94
130
|
Everything the client throws extends `PsiGuardError`, so you can catch broadly
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*
|
|
35
35
|
* Requires Node 18+ (uses the built-in fetch). No runtime dependencies.
|
|
36
36
|
*/
|
|
37
|
-
export declare const VERSION = "1.
|
|
37
|
+
export declare const VERSION = "1.1.0";
|
|
38
38
|
/** The value of a turn's `protection` field. */
|
|
39
39
|
export type ProtectionValue = "safe" | "caution" | "intervened";
|
|
40
40
|
/** Named constants for the three protection values. */
|
|
@@ -170,13 +170,28 @@ export declare class UsageLimitError extends APIStatusError {
|
|
|
170
170
|
export declare class ServerError extends APIStatusError {
|
|
171
171
|
}
|
|
172
172
|
/**
|
|
173
|
-
*
|
|
173
|
+
* True when it's safe to write ANSI color to stdout.
|
|
174
174
|
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
175
|
+
* False for pipes, files, and CI logs — which is exactly when color would turn
|
|
176
|
+
* into visible junk instead of visible meaning.
|
|
177
|
+
*/
|
|
178
|
+
export declare function supportsColor(): boolean;
|
|
179
|
+
/** A one-line status chip: "● SAFE". */
|
|
180
|
+
export declare function badge(protection: string, opts?: {
|
|
181
|
+
color?: boolean;
|
|
182
|
+
}): string;
|
|
183
|
+
/**
|
|
184
|
+
* Render a guarded turn for a terminal: status chip, then the answer behind a
|
|
185
|
+
* colored gutter rail. The status becomes impossible to miss without tinting a
|
|
186
|
+
* single character of the text your user reads.
|
|
177
187
|
*
|
|
178
|
-
*
|
|
188
|
+
* ● SAFE clean run — PsiGuard didn't need to act
|
|
189
|
+
* │ Our return policy allows returns within 30 days...
|
|
179
190
|
*/
|
|
191
|
+
export declare function formatResult(result: GuardResult, opts?: {
|
|
192
|
+
color?: boolean;
|
|
193
|
+
gutter?: boolean;
|
|
194
|
+
}): string;
|
|
180
195
|
export declare class PsiGuard {
|
|
181
196
|
readonly apiKey: string;
|
|
182
197
|
readonly baseUrl: string;
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
*/
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.Conversation = exports.Client = exports.PsiGuard = exports.ServerError = exports.UsageLimitError = exports.RateLimitError = exports.AuthenticationError = exports.BadRequestError = exports.APIStatusError = exports.APIConnectionError = exports.ConfigurationError = exports.PsiGuardError = exports.GuardResult = exports.Protection = exports.VERSION = void 0;
|
|
40
|
-
exports.
|
|
40
|
+
exports.supportsColor = supportsColor;
|
|
41
|
+
exports.badge = badge;
|
|
42
|
+
exports.formatResult = formatResult;
|
|
43
|
+
exports.VERSION = "1.1.0";
|
|
41
44
|
// ---------------------------------------------------------------------------
|
|
42
45
|
// Defaults
|
|
43
46
|
// ---------------------------------------------------------------------------
|
|
@@ -166,6 +169,105 @@ const USAGE_CODES = new Set(["monthly_cap_reached", "provider_budget_reached"]);
|
|
|
166
169
|
*
|
|
167
170
|
* The client is safe to keep and reuse for the life of your process.
|
|
168
171
|
*/
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Terminal display helpers (optional)
|
|
174
|
+
//
|
|
175
|
+
// The client itself prints NOTHING and never puts color codes inside
|
|
176
|
+
// GuardResult.answer. That string is your user's answer: it may be logged,
|
|
177
|
+
// stored, or rendered in a browser, and ANSI escape sequences in any of those
|
|
178
|
+
// places show up as garbage like "[32m". So the color lives out here, in
|
|
179
|
+
// helpers you opt into when you are looking at a terminal — and nowhere else.
|
|
180
|
+
//
|
|
181
|
+
// import { PsiGuard, formatResult } from "psiguard";
|
|
182
|
+
// const result = await new PsiGuard().guard("hello");
|
|
183
|
+
// console.log(formatResult(result));
|
|
184
|
+
//
|
|
185
|
+
// Color is applied only when stdout really is an interactive terminal. Pipes,
|
|
186
|
+
// files, and CI logs fall back to plain text automatically. NO_COLOR=1
|
|
187
|
+
// disables it; PSIGUARD_COLOR=1 forces it on.
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
const ANSI_RESET = "\u001b[0m";
|
|
190
|
+
const ANSI_BOLD = "\u001b[1m";
|
|
191
|
+
const ANSI_DIM = "\u001b[2m";
|
|
192
|
+
const STYLES = {
|
|
193
|
+
safe: {
|
|
194
|
+
color: "\u001b[38;5;42m", // green
|
|
195
|
+
glyph: "\u25cf", // ●
|
|
196
|
+
label: "SAFE",
|
|
197
|
+
caption: "clean run \u2014 PsiGuard didn't need to act",
|
|
198
|
+
},
|
|
199
|
+
caution: {
|
|
200
|
+
color: "\u001b[38;5;214m", // amber
|
|
201
|
+
glyph: "\u25b2", // ▲
|
|
202
|
+
label: "CAUTION",
|
|
203
|
+
caption: "steered back on course mid-answer",
|
|
204
|
+
},
|
|
205
|
+
intervened: {
|
|
206
|
+
color: "\u001b[38;5;203m", // red
|
|
207
|
+
glyph: "\u25a0", // ■
|
|
208
|
+
label: "INTERVENED",
|
|
209
|
+
caption: "response withheld \u2014 the answer below is a refusal",
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* True when it's safe to write ANSI color to stdout.
|
|
214
|
+
*
|
|
215
|
+
* False for pipes, files, and CI logs — which is exactly when color would turn
|
|
216
|
+
* into visible junk instead of visible meaning.
|
|
217
|
+
*/
|
|
218
|
+
function supportsColor() {
|
|
219
|
+
const env = (typeof process !== "undefined" && process.env) || {};
|
|
220
|
+
const forced = (env.PSIGUARD_COLOR || "").trim().toLowerCase();
|
|
221
|
+
if (["1", "true", "yes", "always"].includes(forced))
|
|
222
|
+
return true;
|
|
223
|
+
if (["0", "false", "no", "never"].includes(forced))
|
|
224
|
+
return false;
|
|
225
|
+
if (env.NO_COLOR !== undefined)
|
|
226
|
+
return false;
|
|
227
|
+
if ((env.TERM || "").toLowerCase() === "dumb")
|
|
228
|
+
return false;
|
|
229
|
+
return Boolean(typeof process !== "undefined" && process.stdout && process.stdout.isTTY);
|
|
230
|
+
}
|
|
231
|
+
/** A one-line status chip: "● SAFE". */
|
|
232
|
+
function badge(protection, opts = {}) {
|
|
233
|
+
const style = STYLES[protection];
|
|
234
|
+
if (!style)
|
|
235
|
+
return String(protection).toUpperCase();
|
|
236
|
+
const text = `${style.glyph} ${style.label}`;
|
|
237
|
+
const useColor = opts.color === undefined ? supportsColor() : opts.color;
|
|
238
|
+
return useColor ? `${style.color}${ANSI_BOLD}${text}${ANSI_RESET}` : text;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Render a guarded turn for a terminal: status chip, then the answer behind a
|
|
242
|
+
* colored gutter rail. The status becomes impossible to miss without tinting a
|
|
243
|
+
* single character of the text your user reads.
|
|
244
|
+
*
|
|
245
|
+
* ● SAFE clean run — PsiGuard didn't need to act
|
|
246
|
+
* │ Our return policy allows returns within 30 days...
|
|
247
|
+
*/
|
|
248
|
+
function formatResult(result, opts = {}) {
|
|
249
|
+
const style = STYLES[result.protection] || STYLES.safe;
|
|
250
|
+
const useColor = opts.color === undefined ? supportsColor() : opts.color;
|
|
251
|
+
const gutter = opts.gutter === undefined ? true : opts.gutter;
|
|
252
|
+
const chip = badge(result.protection, { color: useColor });
|
|
253
|
+
const caption = useColor
|
|
254
|
+
? `${ANSI_DIM}${style.caption}${ANSI_RESET}`
|
|
255
|
+
: style.caption;
|
|
256
|
+
const lines = [`${chip} ${caption}`];
|
|
257
|
+
const answer = (result.answer || "").replace(/\s+$/, "");
|
|
258
|
+
if (gutter) {
|
|
259
|
+
const rail = useColor
|
|
260
|
+
? `${style.color}\u2502${ANSI_RESET}`
|
|
261
|
+
: "\u2502";
|
|
262
|
+
const body = answer.split("\n");
|
|
263
|
+
for (const line of body.length ? body : [""])
|
|
264
|
+
lines.push(`${rail} ${line}`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
lines.push(answer);
|
|
268
|
+
}
|
|
269
|
+
return lines.join("\n");
|
|
270
|
+
}
|
|
169
271
|
class PsiGuard {
|
|
170
272
|
constructor(options = {}) {
|
|
171
273
|
const key = options.apiKey ?? process.env.PSIGUARD_API_KEY;
|