orizu 0.5.17 → 0.5.19

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.
Files changed (41) hide show
  1. package/README.md +15 -0
  2. package/dist/archive-artifacts-cli.js +1 -3
  3. package/dist/artifact-pull.js +1 -3
  4. package/dist/diff-comments-cli.js +120 -32
  5. package/dist/error-response.js +17 -17
  6. package/dist/github-setup.js +3 -11
  7. package/dist/help.js +22 -2
  8. package/dist/index.js +43 -107
  9. package/dist/json-response.js +21 -0
  10. package/dist/markdown-report-input.js +67 -0
  11. package/dist/optimization-export-cli.js +30 -0
  12. package/dist/optimizations-list-cli.js +1 -3
  13. package/dist/prompt-draft-push.js +7 -5
  14. package/dist/prompt-length-wire.js +30 -0
  15. package/dist/prompt-report-cli.js +86 -0
  16. package/dist/prompt-summary-table.js +37 -0
  17. package/dist/scorer-draft-push.js +1 -22
  18. package/dist/scores-accept-cli.js +1 -3
  19. package/dist/zip-draft-push.js +1 -24
  20. package/package.json +1 -1
  21. package/src/archive-artifacts-cli.ts +1 -7
  22. package/src/artifact-pull.ts +1 -4
  23. package/src/diff-comments-cli.ts +177 -37
  24. package/src/error-response.ts +23 -21
  25. package/src/github-setup.ts +3 -12
  26. package/src/help.ts +22 -2
  27. package/src/index.ts +54 -132
  28. package/src/json-response.ts +37 -0
  29. package/src/markdown-report-input.ts +80 -0
  30. package/src/optimization-export-cli.ts +72 -0
  31. package/src/optimizations-list-cli.ts +1 -4
  32. package/src/prompt-draft-push.ts +8 -6
  33. package/src/prompt-length-wire.ts +65 -0
  34. package/src/prompt-report-cli.ts +135 -0
  35. package/src/prompt-summary-table.ts +57 -0
  36. package/src/scorer-draft-push.ts +1 -6
  37. package/src/scores-accept-cli.ts +1 -4
  38. package/src/zip-draft-push.ts +1 -6
  39. package/vendor/skills/orizu-cli/SKILL.md +1 -0
  40. package/vendor/skills/orizu-cli/references/cli-reference.md +15 -1
  41. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +19 -0
@@ -0,0 +1,21 @@
1
+ export function sanitizeTerminalText(value) {
2
+ return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
3
+ }
4
+ export function sanitizeHumanInlineText(sanitizer, value) {
5
+ return sanitizer(value).replaceAll('\r', '').replaceAll('\n', '\\n');
6
+ }
7
+ export async function parseJsonResponse(response, context) {
8
+ const contentType = response.headers.get('content-type') || '';
9
+ const rawBody = await response.text();
10
+ if (!contentType.includes('application/json')) {
11
+ throw new Error(`${context} returned non-JSON response (status ${response.status}). ` +
12
+ `Body preview: ${sanitizeTerminalText(rawBody.slice(0, 180))}`);
13
+ }
14
+ try {
15
+ return JSON.parse(rawBody);
16
+ }
17
+ catch {
18
+ throw new Error(`${context} returned invalid JSON (status ${response.status}). ` +
19
+ `Body preview: ${sanitizeTerminalText(rawBody.slice(0, 180))}`);
20
+ }
21
+ }
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from 'fs';
2
+ import { basename } from 'path';
3
+ export const MARKDOWN_REPORT_MAX_BYTES = 2 * 1024 * 1024;
4
+ function getArg(args, name) {
5
+ const index = args.indexOf(name);
6
+ if (index === -1 || index + 1 >= args.length)
7
+ return null;
8
+ return args[index + 1];
9
+ }
10
+ function expandHomePath(path) {
11
+ if (!path.startsWith('~/'))
12
+ return path;
13
+ return `${process.env.HOME || ''}/${path.slice(2)}`;
14
+ }
15
+ function rejectDashPrefixedOptionValue(name, value) {
16
+ if (value?.startsWith('-')) {
17
+ throw new Error(`Invalid value for ${name}: option values cannot start with a dash`);
18
+ }
19
+ }
20
+ function readSourceFile(pathArg) {
21
+ const expandedPath = expandHomePath(pathArg);
22
+ try {
23
+ return readFileSync(expandedPath, 'utf8');
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
27
+ throw new Error(`File not found: ${expandedPath}`);
28
+ }
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ throw new Error(`Failed to read file '${expandedPath}': ${message}`);
31
+ }
32
+ }
33
+ export function readMarkdownReportInput(args, reportLabel) {
34
+ const report = getArg(args, '--report');
35
+ const reportFile = getArg(args, '--report-file');
36
+ rejectDashPrefixedOptionValue('--report', report);
37
+ rejectDashPrefixedOptionValue('--report-file', reportFile);
38
+ if (report && reportFile) {
39
+ throw new Error('Use either --report or --report-file, not both');
40
+ }
41
+ if (!report && !reportFile)
42
+ return null;
43
+ let markdown;
44
+ let sourceName;
45
+ if (reportFile) {
46
+ const expandedPath = expandHomePath(reportFile);
47
+ markdown = readSourceFile(reportFile);
48
+ sourceName = basename(expandedPath);
49
+ }
50
+ else if (report?.startsWith('@')) {
51
+ const path = report.slice(1);
52
+ const expandedPath = expandHomePath(path);
53
+ markdown = readSourceFile(path);
54
+ sourceName = basename(expandedPath);
55
+ }
56
+ else {
57
+ markdown = report || '';
58
+ sourceName = 'inline';
59
+ }
60
+ if (!markdown.trim()) {
61
+ throw new Error(`${reportLabel} report markdown must not be blank`);
62
+ }
63
+ if (Buffer.byteLength(markdown, 'utf8') > MARKDOWN_REPORT_MAX_BYTES) {
64
+ throw new Error(`${reportLabel} report exceeds ${MARKDOWN_REPORT_MAX_BYTES} bytes`);
65
+ }
66
+ return { markdown, sourceName };
67
+ }
@@ -0,0 +1,30 @@
1
+ import { parseJsonResponse, sanitizeHumanInlineText, } from './json-response.js';
2
+ export function printDiffCommentsSuppressionMarker(ctx, data) {
3
+ const reason = data.diffCommentsSuppressedReason;
4
+ if (typeof reason !== 'string' || reason.length === 0) {
5
+ return;
6
+ }
7
+ ctx.printLine(`Diff comments suppressed: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, reason)}`);
8
+ }
9
+ export async function exportOptimizationRunCommand(ctx) {
10
+ const runId = ctx.getPositionalArg(2) || ctx.getArg('--run-id');
11
+ const outPathArg = ctx.getArg('--out');
12
+ if (!runId) {
13
+ throw new Error('Usage: orizu optimizations export <run-id> [--out <path>] [--json]');
14
+ }
15
+ const response = await ctx.authedFetch(`/api/cli/optimization-runs/${encodeURIComponent(runId)}/export`);
16
+ if (!response.ok) {
17
+ throw new Error(`Failed to export optimization run: ${await response.text()}`);
18
+ }
19
+ const data = await parseJsonResponse(response, 'Optimization export');
20
+ if (ctx.hasJsonFlag() && !outPathArg) {
21
+ ctx.printJson(data);
22
+ return;
23
+ }
24
+ const filename = outPathArg
25
+ ? ctx.expandHomePath(outPathArg)
26
+ : `${runId}.optimization.json`;
27
+ ctx.writeTextFileEnsuringDir(filename, `${JSON.stringify(data, null, 2)}\n`);
28
+ ctx.printLine(`Saved optimization export to ${ctx.sanitizeTerminalText(filename)}`);
29
+ printDiffCommentsSuppressionMarker(ctx, data);
30
+ }
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { authedFetch } from './http.js';
14
14
  import { extractErrorMessage } from './error-response.js';
15
+ import { sanitizeTerminalText } from './json-response.js';
15
16
  // ALI-1175: best scores here derive from agent-reported score runs — nothing is
16
17
  // server-attested yet. Both the table footer AND the --json payload carry this
17
18
  // signal so no consumer, human or machine, mistakes the number for attested
@@ -25,9 +26,6 @@ function argValue(args, flag) {
25
26
  }
26
27
  return args[index + 1];
27
28
  }
28
- function sanitizeTerminalText(value) {
29
- return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
30
- }
31
29
  // Shared-parser behavior (artifact-pull.ts pattern): a non-JSON or malformed
32
30
  // body becomes a bounded, sanitized error — never a raw JSON.parse throw.
33
31
  async function parseJsonPayload(response, context) {
@@ -1,4 +1,5 @@
1
1
  import { authedFetch } from './http.js';
2
+ import { sanitizeTerminalText } from './json-response.js';
2
3
  import { appendRegistrationTagWarning } from './registration-tag-warning.js';
3
4
  /**
4
5
  * ADR-007 P4 (ALI-1074): the session-scoped, commit-first prompt push.
@@ -18,11 +19,6 @@ import { appendRegistrationTagWarning } from './registration-tag-warning.js';
18
19
  * Lives outside index.ts per the CLI line ratchet (ALI-976).
19
20
  */
20
21
  export const PROMPT_PRIMARY_GIT_FILENAME = 'prompt.md';
21
- // Mirrors index.ts `sanitizeTerminalText` (precedent: artifact-pull.ts keeps
22
- // its own copy to avoid importing the CLI entrypoint).
23
- function sanitizeTerminalText(value) {
24
- return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
25
- }
26
22
  /**
27
23
  * Build the git file set for a prompt-version draft: the primary text at the
28
24
  * canonical prompt.md plus every sidecar at its own relative path. A sidecar
@@ -74,6 +70,12 @@ export async function pushPromptDraft(input) {
74
70
  parentVersionId: input.parentVersionId,
75
71
  createdBy: manifest.provenance || { kind: 'human-edit' },
76
72
  files,
73
+ ...(input.report
74
+ ? {
75
+ reportMarkdown: input.report.markdown,
76
+ reportSourceName: input.report.sourceName,
77
+ }
78
+ : {}),
77
79
  }),
78
80
  });
79
81
  if (!response.ok) {
@@ -0,0 +1,30 @@
1
+ export const CLI_LENGTH_STATS_FIELDS = [
2
+ 'tokens',
3
+ 'lines',
4
+ 'chars',
5
+ 'words',
6
+ ];
7
+ export const CLI_LENGTH_DELTA_SPLIT_UNAVAILABLE_REASONS = [
8
+ 'diff_degraded_size_limit',
9
+ 'diff_degraded_cell_limit',
10
+ ];
11
+ export const CLI_LENGTH_MEASUREMENT_UNAVAILABLE_REASONS = [
12
+ 'length_measurement_size_limit',
13
+ 'length_measurement_failed',
14
+ 'body_resolution_failed',
15
+ 'measurement_cap_exceeded',
16
+ 'enrichment_failed',
17
+ 'bodies_unavailable_event_cap',
18
+ ];
19
+ export function formatFiniteLengthCount(value, signed = false) {
20
+ if (typeof value !== 'number' || !Number.isFinite(value))
21
+ return null;
22
+ const formatted = Math.abs(value).toLocaleString('en-US');
23
+ if (!signed)
24
+ return formatted;
25
+ if (value > 0)
26
+ return `+${formatted}`;
27
+ if (value < 0)
28
+ return `-${formatted}`;
29
+ return formatted;
30
+ }
@@ -0,0 +1,86 @@
1
+ import { extractErrorMessage } from './error-response.js';
2
+ import { authedFetch } from './http.js';
3
+ import { parseJsonResponse, sanitizeTerminalText } from './json-response.js';
4
+ import { readMarkdownReportInput } from './markdown-report-input.js';
5
+ const IDENTICAL_PROMPT_VERSION_ERROR = 'Prompt version is identical to the current prompt version.';
6
+ function getArg(args, name) {
7
+ const index = args.indexOf(name);
8
+ if (index === -1 || index + 1 >= args.length)
9
+ return null;
10
+ return args[index + 1];
11
+ }
12
+ function rejectDashPrefixedOptionValue(name, value) {
13
+ if (value?.startsWith('-')) {
14
+ throw new Error(`Invalid value for ${name}: option values cannot start with a dash`);
15
+ }
16
+ }
17
+ export async function promptReportCommand(args, context) {
18
+ const prompt = getArg(args, '--prompt');
19
+ const projectArg = getArg(args, '--project');
20
+ const version = getArg(args, '--version');
21
+ const versionId = getArg(args, '--version-id');
22
+ for (const [name, value] of [
23
+ ['--prompt', prompt],
24
+ ['--project', projectArg],
25
+ ['--version-id', versionId],
26
+ ]) {
27
+ rejectDashPrefixedOptionValue(name, value);
28
+ }
29
+ if (!prompt) {
30
+ throw new Error('Usage: orizu prompts report set --prompt <id-or-name> --project <team/project> ' +
31
+ '(--version <n> | --version-id <id>) ' +
32
+ '(--report <markdown|@file> | --report-file <path>) [--json]');
33
+ }
34
+ if (Boolean(version) === Boolean(versionId)) {
35
+ throw new Error('Exactly one of --version or --version-id is required');
36
+ }
37
+ if (version && (!/^[1-9]\d*$/.test(version) || !Number.isSafeInteger(Number(version)))) {
38
+ throw new Error('--version must be a positive integer');
39
+ }
40
+ const report = readMarkdownReportInput(args, 'Prompt');
41
+ if (!report) {
42
+ throw new Error('Usage: orizu prompts report set --prompt <id-or-name> --project <team/project> ' +
43
+ '(--version <n> | --version-id <id>) ' +
44
+ '(--report <markdown|@file> | --report-file <path>) [--json]');
45
+ }
46
+ const project = projectArg || await context.resolveProjectSlug(null);
47
+ const query = new URLSearchParams({ project });
48
+ if (version)
49
+ query.set('version', version);
50
+ else
51
+ query.set('versionId', versionId);
52
+ const fetcher = context.fetcher ?? authedFetch;
53
+ const response = await fetcher(`/api/cli/prompts/${encodeURIComponent(prompt)}/report?${query.toString()}`, {
54
+ method: 'PATCH',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({
57
+ reportMarkdown: report.markdown,
58
+ reportSourceName: report.sourceName,
59
+ }),
60
+ });
61
+ if (!response.ok) {
62
+ throw new Error(`Failed to update prompt report: ${await extractErrorMessage(response)}`);
63
+ }
64
+ const data = await parseJsonResponse(response, 'Prompt report update');
65
+ if (context.json) {
66
+ context.printJson(data);
67
+ return;
68
+ }
69
+ const versionLabel = data.version
70
+ ? data.version.versionNumber === null
71
+ ? data.version.id
72
+ : `v${data.version.versionNumber}`
73
+ : data.promptVersionId || versionId || `v${version}`;
74
+ const promptName = data.prompt?.name || prompt;
75
+ const status = data.version?.status || 'updated';
76
+ context.printLine(`Uploaded prompt report for ${sanitizeTerminalText(promptName)} ` +
77
+ `(${sanitizeTerminalText(versionLabel)}) [${sanitizeTerminalText(status)}]`);
78
+ }
79
+ export async function promptPushErrorMessage(response, kind, hasReport) {
80
+ const message = await extractErrorMessage(response);
81
+ if (hasReport && response.status === 409 && message === IDENTICAL_PROMPT_VERSION_ERROR) {
82
+ return (`${IDENTICAL_PROMPT_VERSION_ERROR} ` +
83
+ 'Attach the report to the existing version with `orizu prompts report set`.');
84
+ }
85
+ return `Failed to push ${kind}: ${message}`;
86
+ }
@@ -0,0 +1,37 @@
1
+ import { sanitizeTerminalText } from './json-response.js';
2
+ import { formatFiniteLengthCount, } from './prompt-length-wire.js';
3
+ export function printPromptSummaryTable(items, emptyMessage, printLine) {
4
+ if (items.length === 0) {
5
+ printLine(emptyMessage);
6
+ return;
7
+ }
8
+ const rows = items.map(item => ({
9
+ id: sanitizeTerminalText(item.id),
10
+ name: sanitizeTerminalText(item.name),
11
+ role: sanitizeTerminalText(item.role),
12
+ status: sanitizeTerminalText(item.status || 'active'),
13
+ tokens: item.lengthStats == null
14
+ ? '—'
15
+ : (() => {
16
+ const count = formatFiniteLengthCount(item.lengthStats.tokens);
17
+ return count === null ? '—' : `~${count}`;
18
+ })(),
19
+ lines: item.lengthStats == null
20
+ ? '—'
21
+ : formatFiniteLengthCount(item.lengthStats.lines) ?? '—',
22
+ chars: item.lengthStats == null
23
+ ? '—'
24
+ : formatFiniteLengthCount(item.lengthStats.chars) ?? '—',
25
+ words: item.lengthStats == null
26
+ ? '—'
27
+ : formatFiniteLengthCount(item.lengthStats.words) ?? '—',
28
+ }));
29
+ const headers = ['ID', 'NAME', 'ROLE', 'STATUS', 'TOKENS', 'LINES', 'CHARS', 'WORDS'];
30
+ const keys = ['id', 'name', 'role', 'status', 'tokens', 'lines', 'chars', 'words'];
31
+ const widths = keys.map((key, index) => Math.max(headers[index].length, ...rows.map(row => row[key].length)));
32
+ printLine(headers.map((header, index) => header.padEnd(widths[index])).join(' '));
33
+ printLine(widths.map(width => '-'.repeat(width)).join(' '));
34
+ rows.forEach(row => {
35
+ printLine(keys.map((key, index) => row[key].padEnd(widths[index])).join(' '));
36
+ });
37
+ }
@@ -1,27 +1,6 @@
1
1
  import { authedFetch } from './http.js';
2
+ import { sanitizeTerminalText } from './json-response.js';
2
3
  import { appendRegistrationTagWarning } from './registration-tag-warning.js';
3
- /**
4
- * ADR-007 P5 (ALI-1074): the session-scoped, commit-first scorer push.
5
- *
6
- * `orizu scorers register --session <id>` creates the version through
7
- * POST /api/cli/scorers/drafts: the server commits the scorer's canonical
8
- * `manifest.json` to the session's `orizu/session-*` branch (one Git Data API
9
- * commit) and then writes a DRAFT row pinning
10
- * repo_path/content_sha/commit_sha. The draft seals — and gets its version
11
- * number — when the session branch merges via `orizu session finish` +
12
- * promotion-manifest apply.
13
- *
14
- * The git filename is ALWAYS `manifest.json`: the server read side
15
- * (lib/artifact-git-read.ts SCORER_MANIFEST_FILENAMES) prefers that name.
16
- *
17
- * Lives outside index.ts per the CLI line ratchet (ALI-976); mirrors
18
- * prompt-draft-push.ts.
19
- */
20
- // Mirrors index.ts `sanitizeTerminalText` (precedent: artifact-pull.ts keeps
21
- // its own copy to avoid importing the CLI entrypoint).
22
- function sanitizeTerminalText(value) {
23
- return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
24
- }
25
4
  /** The full `orizu scorers register` command (moved out of index.ts for the
26
5
  * CLI line ratchet): --session takes the ADR-007 P5 commit-first draft path,
27
6
  * sessionless stays on the legacy create endpoint. */
@@ -11,9 +11,7 @@
11
11
  * own argument parsing and printing via injected io.
12
12
  */
13
13
  import { authedFetch } from './http.js';
14
- function sanitizeTerminalText(value) {
15
- return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
16
- }
14
+ import { sanitizeTerminalText } from './json-response.js';
17
15
  function argValue(args, flag) {
18
16
  const index = args.indexOf(flag);
19
17
  if (index === -1 || index + 1 >= args.length || args[index + 1].startsWith('--')) {
@@ -1,31 +1,8 @@
1
1
  import { basename } from 'path';
2
2
  import { zipDirectoryToBase64 } from './artifact-archive.js';
3
3
  import { authedFetch } from './http.js';
4
+ import { sanitizeTerminalText } from './json-response.js';
4
5
  import { appendRegistrationTagWarning } from './registration-tag-warning.js';
5
- /**
6
- * ADR-007 P5 (ALI-1074): the session-scoped, commit-first runner/optimizer
7
- * push.
8
- *
9
- * `orizu runners|optimizers push --session <id>` sends the SAME deterministic
10
- * zip as the legacy push to POST /api/cli/{runners|optimizers}/drafts: the
11
- * server unpacks the zip's file set and commits it to the session's
12
- * `orizu/session-*` branch (one Git Data API commit; binary entries travel
13
- * base64), keeps uploading the zip to storage (dual-write until ALI-1046),
14
- * and writes a DRAFT row pinning repo_path/content_sha/commit_sha. The draft
15
- * seals — and gets its version number — when the session branch merges via
16
- * `orizu session finish` + promotion-manifest apply.
17
- *
18
- * Labels cannot ride a draft push (`--label` with `--session` is an error at
19
- * the command layer): labels address sealed versions only.
20
- *
21
- * Lives outside index.ts per the CLI line ratchet (ALI-976); mirrors
22
- * prompt-draft-push.ts.
23
- */
24
- // Mirrors index.ts `sanitizeTerminalText` (precedent: artifact-pull.ts keeps
25
- // its own copy to avoid importing the CLI entrypoint).
26
- function sanitizeTerminalText(value) {
27
- return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
28
- }
29
6
  /** The full `orizu runners|optimizers push` command (moved out of index.ts
30
7
  * for the CLI line ratchet): --session takes the ADR-007 P5 commit-first
31
8
  * draft path, sessionless stays on the legacy zip-only endpoint. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.5.17",
3
+ "version": "0.5.19",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { authedFetch } from './http.js'
10
10
  import { extractErrorMessage } from './error-response.js'
11
+ import { sanitizeTerminalText } from './json-response.js'
11
12
 
12
13
  type ArchiveStatus = 'active' | 'archived' | 'all'
13
14
  type ArchiveAction = 'archive' | 'restore'
@@ -109,13 +110,6 @@ function argValue(args: readonly string[], flag: string): string | null {
109
110
  return args[index + 1]
110
111
  }
111
112
 
112
- function sanitizeTerminalText(value: unknown): string {
113
- return String(value).replace(
114
- /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g,
115
- ''
116
- )
117
- }
118
-
119
113
  function columnWidths(headers: string[], rows: string[][]): number[] {
120
114
  return headers.map((header, index) =>
121
115
  Math.max(header.length, ...rows.map(row => row[index]?.length || 0))
@@ -18,6 +18,7 @@ import { tmpdir } from 'os'
18
18
  import { dirname, join } from 'path'
19
19
 
20
20
  import { authedFetch } from './http.js'
21
+ import { sanitizeTerminalText } from './json-response.js'
21
22
 
22
23
  export type PullableArtifactKind = 'runner' | 'optimizer'
23
24
 
@@ -31,10 +32,6 @@ export interface PullArtifactIo {
31
32
  // Shared with the runners exec download path in index.ts.
32
33
  export const ARTIFACT_MAX_BYTES = 25 * 1024 * 1024
33
34
 
34
- function sanitizeTerminalText(value: unknown): string {
35
- return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '')
36
- }
37
-
38
35
  function expandHomePath(path: string): string {
39
36
  if (path.startsWith('~/')) {
40
37
  const home = process.env.HOME || ''