portable-agent-layer 0.85.13 → 0.85.15
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/package.json +1 -1
- package/src/cli/doctor/environment.ts +31 -11
- package/src/cli/doctor/health.ts +31 -11
- package/src/cli/doctor/state.ts +7 -1
- package/src/cli/index.ts +29 -68
- package/src/cli/migrate.ts +3 -1
- package/src/hooks/lib/log.ts +16 -6
- package/src/targets/lib.ts +15 -3
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { dirname, resolve } from "node:path";
|
|
@@ -67,22 +68,47 @@ function playwrightPlatformOverride(host: BrowserHost): string {
|
|
|
67
68
|
return `PLAYWRIGHT_HOST_PLATFORM_OVERRIDE=ubuntu24.04-${host.arch === "arm64" ? "arm64" : "x64"} `;
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
function chromiumInstallCommand(host: BrowserHost): string {
|
|
72
|
+
const pinned = host.playwright ? `playwright@${host.playwright.version}` : "playwright";
|
|
73
|
+
return `${playwrightPlatformOverride(host)}bun x ${pinned} install chromium`;
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
export function playwrightFinding(host: BrowserHost): Finding {
|
|
71
77
|
if (hasChromium(host)) return passed("playwright", "Playwright Chromium installed");
|
|
72
|
-
const pinned = host.playwright ? `playwright@${host.playwright.version}` : "playwright";
|
|
73
|
-
const override = playwrightPlatformOverride(host);
|
|
74
78
|
return warning(
|
|
75
79
|
"playwright.missing",
|
|
76
80
|
"Playwright's Chromium is not installed — create-pdf, consulting-report and screenshots fail",
|
|
77
81
|
{
|
|
78
|
-
say:
|
|
82
|
+
say: playwrightPlatformOverride(host)
|
|
79
83
|
? "Install it as Ubuntu 24.04, which Playwright supports"
|
|
80
84
|
: "Install it",
|
|
81
|
-
command:
|
|
85
|
+
command: chromiumInstallCommand(host),
|
|
82
86
|
}
|
|
83
87
|
);
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
function thisBrowserHost(): BrowserHost {
|
|
91
|
+
return {
|
|
92
|
+
browsersPath: playwrightBrowsersPath(),
|
|
93
|
+
playwright: installedPlaywright(),
|
|
94
|
+
platform: process.platform,
|
|
95
|
+
arch: process.arch,
|
|
96
|
+
osRelease: readOsRelease(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function runInShell(command: string): boolean {
|
|
101
|
+
return spawnSync(command, { cwd: palPkg(), shell: true, stdio: "ignore" }).status === 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A failure is left to the doctor's report that follows, which names the fix. */
|
|
105
|
+
export function installChromium(
|
|
106
|
+
host: BrowserHost = thisBrowserHost(),
|
|
107
|
+
run: (command: string) => boolean = runInShell
|
|
108
|
+
): void {
|
|
109
|
+
if (!hasChromium(host)) run(chromiumInstallCommand(host));
|
|
110
|
+
}
|
|
111
|
+
|
|
86
112
|
interface PalInstall {
|
|
87
113
|
pal: string | null;
|
|
88
114
|
pkg: string;
|
|
@@ -136,13 +162,7 @@ export function environmentFindings(rtk: ToolCheck): Finding[] {
|
|
|
136
162
|
pkg: palPkg(),
|
|
137
163
|
repoMode: isRepoMode(),
|
|
138
164
|
}),
|
|
139
|
-
playwrightFinding(
|
|
140
|
-
browsersPath: playwrightBrowsersPath(),
|
|
141
|
-
playwright: installedPlaywright(),
|
|
142
|
-
platform: process.platform,
|
|
143
|
-
arch: process.arch,
|
|
144
|
-
osRelease: readOsRelease(),
|
|
145
|
-
}),
|
|
165
|
+
playwrightFinding(thisBrowserHost()),
|
|
146
166
|
rtkFinding(rtk),
|
|
147
167
|
...versionControlFindings(),
|
|
148
168
|
];
|
package/src/cli/doctor/health.ts
CHANGED
|
@@ -11,29 +11,49 @@ interface PendingMigration {
|
|
|
11
11
|
detail?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
interface ErrorContext {
|
|
15
|
+
now: number;
|
|
16
|
+
env: Record<string, string | undefined>;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
const CLAUDE_LOGIN_EXPIRED = /Failed to authenticate/;
|
|
20
|
+
const MINUTE_MS = 60_000;
|
|
15
21
|
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
function ago(at: number, now: number): string {
|
|
23
|
+
const minutes = Math.max(0, Math.round((now - at) / MINUTE_MS));
|
|
24
|
+
return minutes < 60 ? `${minutes}m ago` : `${Math.floor(minutes / 60)}h ago`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function claudeLoginExpiredFinding(
|
|
28
|
+
group: HookErrorGroup,
|
|
29
|
+
context: ErrorContext
|
|
30
|
+
): Finding {
|
|
31
|
+
const title = `Background calls to Claude could not log in — ${group.count} failed in the last 24h, last ${ago(group.lastAt, context.now)}: ${group.last}`;
|
|
32
|
+
if (context.env.CLAUDE_CODE_OAUTH_TOKEN)
|
|
33
|
+
return warning(`hook-errors.${group.source}`, title, {
|
|
34
|
+
say: "CLAUDE_CODE_OAUTH_TOKEN is set — if these failures predate it, they clear 24h after the last one; if not, the token is invalid, so create a new one",
|
|
22
35
|
command: "claude setup-token",
|
|
23
|
-
}
|
|
24
|
-
|
|
36
|
+
});
|
|
37
|
+
return failing(`hook-errors.${group.source}`, title, {
|
|
38
|
+
say: "Create a year-long token, then export it as CLAUDE_CODE_OAUTH_TOKEN in your shell profile",
|
|
39
|
+
command: "claude setup-token",
|
|
40
|
+
});
|
|
25
41
|
}
|
|
26
42
|
|
|
27
|
-
export function hookErrorFindings(
|
|
43
|
+
export function hookErrorFindings(
|
|
44
|
+
groups: HookErrorGroup[],
|
|
45
|
+
context: ErrorContext = { now: Date.now(), env: process.env }
|
|
46
|
+
): Finding[] {
|
|
28
47
|
if (groups.length === 0)
|
|
29
48
|
return [passed("hook-errors", "No hook errors in the last 24h")];
|
|
30
49
|
const log = resolve(palHome(), "debug", "debug.log");
|
|
31
50
|
return groups.map((group) => {
|
|
32
|
-
if (CLAUDE_LOGIN_EXPIRED.test(group.last))
|
|
51
|
+
if (CLAUDE_LOGIN_EXPIRED.test(group.last))
|
|
52
|
+
return claudeLoginExpiredFinding(group, context);
|
|
33
53
|
const times = group.count === 1 ? "once" : `${group.count} times`;
|
|
34
54
|
return warning(
|
|
35
55
|
`hook-errors.${group.source}`,
|
|
36
|
-
`${group.source} failed ${times} in the last 24h
|
|
56
|
+
`${group.source} failed ${times} in the last 24h, last ${ago(group.lastAt, context.now)}: ${group.last}`,
|
|
37
57
|
{ say: `Full trace in ${log}` }
|
|
38
58
|
);
|
|
39
59
|
});
|
package/src/cli/doctor/state.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { palHome, palPkg } from "../../hooks/lib/paths";
|
|
4
4
|
import { auditBindings, type BindingIssue } from "../../hooks/lib/projects";
|
|
5
5
|
import { telosStatus } from "../../hooks/lib/telos-topics";
|
|
6
|
+
import { retiredKeysIn } from "../migrate";
|
|
6
7
|
import { type Finding, failing, optional, passed, warning } from "./finding";
|
|
7
8
|
|
|
8
9
|
interface SchemaNode {
|
|
@@ -142,7 +143,12 @@ function settingsFindings(): Finding[] {
|
|
|
142
143
|
}),
|
|
143
144
|
];
|
|
144
145
|
const schema = readSettingsSchema();
|
|
145
|
-
const
|
|
146
|
+
const leftToMigration = new Set(
|
|
147
|
+
retiredKeysIn(settings as Record<string, Record<string, unknown>>)
|
|
148
|
+
);
|
|
149
|
+
const unknown = schema
|
|
150
|
+
? unknownSettingsKeys(settings, schema).filter((k) => !leftToMigration.has(k.path))
|
|
151
|
+
: [];
|
|
146
152
|
return [identityFinding(settings), unknownKeysFinding(unknown)];
|
|
147
153
|
}
|
|
148
154
|
|
package/src/cli/index.ts
CHANGED
|
@@ -50,10 +50,10 @@ import { inference, previewInferenceRoute } from "../hooks/lib/inference";
|
|
|
50
50
|
import { logDebug } from "../hooks/lib/log";
|
|
51
51
|
import { ensureRegistered, writeRegistryEntry } from "../hooks/lib/machine";
|
|
52
52
|
import { palHome, palPkg, paths, platform, toPath } from "../hooks/lib/paths";
|
|
53
|
-
import {
|
|
54
|
-
import { log } from "../targets/lib";
|
|
53
|
+
import { log, narrateSteps } from "../targets/lib";
|
|
55
54
|
import { builtinToolVerbs, runBuiltinTool } from "./builtin-tools";
|
|
56
55
|
import { type DoctorResult, detectAgents } from "./doctor/agents";
|
|
56
|
+
import { installChromium } from "./doctor/environment";
|
|
57
57
|
import { runDoctor } from "./doctor/run";
|
|
58
58
|
import { findSessionAgent, NO_SESSION_AGENT_MESSAGE } from "./session-agent";
|
|
59
59
|
|
|
@@ -155,7 +155,7 @@ async function runCli(command: string | undefined, args: string[]) {
|
|
|
155
155
|
break;
|
|
156
156
|
case "install":
|
|
157
157
|
banner();
|
|
158
|
-
await install(resolveTargets(args));
|
|
158
|
+
process.exit(await install(resolveTargets(args), args));
|
|
159
159
|
break;
|
|
160
160
|
case "uninstall":
|
|
161
161
|
await uninstall(args);
|
|
@@ -265,24 +265,10 @@ function banner() {
|
|
|
265
265
|
console.log("");
|
|
266
266
|
console.log(" ╔═══════════════════════════════════╗");
|
|
267
267
|
console.log(" ║ PAL — Portable Agent Layer ║");
|
|
268
|
-
console.log(" ║ Non-destructive · Modular ║");
|
|
269
268
|
console.log(" ╚═══════════════════════════════════╝");
|
|
270
269
|
console.log("");
|
|
271
270
|
}
|
|
272
271
|
|
|
273
|
-
/**
|
|
274
|
-
* Install asks for identity and stops. TELOS is a conversation, not a form: a
|
|
275
|
-
* user who has just installed a tool cannot yet say what they want from it, and
|
|
276
|
-
* answers given under that pressure are worse than none.
|
|
277
|
-
*/
|
|
278
|
-
function pointAtOnboarding(): void {
|
|
279
|
-
const unanswered = telosStatus().filter((topic) => topic.priority && !topic.answered);
|
|
280
|
-
if (unanswered.length === 0) return;
|
|
281
|
-
log.info(
|
|
282
|
-
`PAL knows nothing about you yet (${unanswered.length} topics open). When you have half an hour, ask your agent to onboard you.`
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
272
|
function showHelp() {
|
|
287
273
|
console.log(`
|
|
288
274
|
Usage:
|
|
@@ -290,8 +276,9 @@ function showHelp() {
|
|
|
290
276
|
pal cli <command> [options] Admin commands
|
|
291
277
|
|
|
292
278
|
Admin commands:
|
|
293
|
-
pal cli init [--claude] [--opencode] [--cursor] [--codex] Scaffold and install (default: all)
|
|
294
|
-
pal cli install [--claude] [--opencode] [--cursor] [--codex] Register hooks for targets
|
|
279
|
+
pal cli init [--claude] [--opencode] [--cursor] [--codex] Scaffold and install (default: all), then run the doctor
|
|
280
|
+
pal cli install [--claude] [--opencode] [--cursor] [--codex] Register hooks for targets, then run the doctor
|
|
281
|
+
Add --verbose to either for the step-by-step log
|
|
295
282
|
pal cli uninstall [--claude] [--opencode] [--cursor] [--codex] Remove hooks for targets
|
|
296
283
|
pal cli update Update PAL (git pull or npm update)
|
|
297
284
|
pal cli export [path] [--dry-run] Export state to zip
|
|
@@ -486,12 +473,10 @@ async function init(args: string[]) {
|
|
|
486
473
|
const { scaffoldTelos } = await import("../targets/lib");
|
|
487
474
|
|
|
488
475
|
banner();
|
|
476
|
+
narrateSteps(args.includes("--verbose"));
|
|
489
477
|
|
|
490
478
|
const health = detectAgents();
|
|
491
|
-
runDoctor([], health);
|
|
492
|
-
if (!health.hasAgent) {
|
|
493
|
-
process.exit(1);
|
|
494
|
-
}
|
|
479
|
+
if (!health.hasAgent) process.exit(runDoctor([], health));
|
|
495
480
|
|
|
496
481
|
const home = palHome();
|
|
497
482
|
log.info(`Creating PAL home at ${home}`);
|
|
@@ -506,7 +491,7 @@ async function init(args: string[]) {
|
|
|
506
491
|
|
|
507
492
|
// Auto-detect available targets
|
|
508
493
|
const targets = resolveTargets(args, health);
|
|
509
|
-
await install(targets);
|
|
494
|
+
process.exit(await install(targets, args));
|
|
510
495
|
}
|
|
511
496
|
|
|
512
497
|
/**
|
|
@@ -522,25 +507,25 @@ function runQuietly(cmd: string, args: string[], cwd: string): number | null {
|
|
|
522
507
|
return r.status;
|
|
523
508
|
}
|
|
524
509
|
|
|
525
|
-
|
|
526
|
-
|
|
510
|
+
function targetInstallers(): [keyof Targets, string, () => Promise<unknown>][] {
|
|
511
|
+
return [
|
|
512
|
+
["claude", "Claude Code", () => import("../targets/claude/install")],
|
|
513
|
+
["opencode", "opencode", () => import("../targets/opencode/install")],
|
|
514
|
+
["cursor", "Cursor", () => import("../targets/cursor/install")],
|
|
515
|
+
["copilot", "Copilot", () => import("../targets/copilot/install")],
|
|
516
|
+
["codex", "Codex", () => import("../targets/codex/install")],
|
|
517
|
+
];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function install(targets: Targets, args: string[]): Promise<number> {
|
|
521
|
+
narrateSteps(args.includes("--verbose"));
|
|
527
522
|
const pkg = palPkg();
|
|
528
523
|
if (runQuietly("bun", ["install", "--frozen-lockfile"], pkg) !== 0) {
|
|
529
524
|
log.warn("bun install failed — continuing anyway, but hooks may not work");
|
|
530
525
|
}
|
|
531
526
|
|
|
532
|
-
// Fetch the Chromium build Playwright uses for PDF rendering (create-pdf skill).
|
|
533
|
-
// Idempotent — skipped if already cached. Skipped entirely under PAL_SKIP_BROWSER_INSTALL=1
|
|
534
|
-
// (used by tests to avoid a ~150MB download on every run).
|
|
535
527
|
// Uses `bun x` (not `bunx`) for Windows compatibility — bunx resolves unreliably under cmd.exe.
|
|
536
|
-
if (process.env.PAL_SKIP_BROWSER_INSTALL !== "1")
|
|
537
|
-
const pw = runQuietly("bun", ["x", "playwright", "install", "chromium"], pkg);
|
|
538
|
-
if (pw !== 0) {
|
|
539
|
-
log.warn(
|
|
540
|
-
`playwright install chromium failed (exit ${pw}) — create-pdf and consulting-report skills won't work. Retry manually: bun x playwright install chromium`
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
528
|
+
if (process.env.PAL_SKIP_BROWSER_INSTALL !== "1") installChromium();
|
|
544
529
|
|
|
545
530
|
// Scaffold TELOS + PAL settings, then prompt for missing identity
|
|
546
531
|
const { scaffoldTelos, scaffoldPalSettings, copyPalDocs, generateSkillIndex } =
|
|
@@ -553,7 +538,6 @@ async function install(targets: Targets) {
|
|
|
553
538
|
await promptIdentity();
|
|
554
539
|
await promptAttribution();
|
|
555
540
|
await promptAutoUpdate();
|
|
556
|
-
pointAtOnboarding();
|
|
557
541
|
|
|
558
542
|
// Registers the label loadActor derives, so it travels on the next export.
|
|
559
543
|
const { ensureActorRegistered } = await import("../hooks/lib/actor");
|
|
@@ -566,34 +550,10 @@ async function install(targets: Targets) {
|
|
|
566
550
|
const palDocsCount = copyPalDocs();
|
|
567
551
|
regenerateIfNeeded();
|
|
568
552
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (targets.opencode) {
|
|
576
|
-
console.log("━━━ opencode ━━━");
|
|
577
|
-
await import("../targets/opencode/install");
|
|
578
|
-
console.log("");
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
if (targets.cursor) {
|
|
582
|
-
console.log("━━━ Cursor ━━━");
|
|
583
|
-
await import("../targets/cursor/install");
|
|
584
|
-
console.log("");
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (targets.copilot) {
|
|
588
|
-
console.log("━━━ Copilot ━━━");
|
|
589
|
-
await import("../targets/copilot/install");
|
|
590
|
-
console.log("");
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (targets.codex) {
|
|
594
|
-
console.log("━━━ Codex ━━━");
|
|
595
|
-
await import("../targets/codex/install");
|
|
596
|
-
console.log("");
|
|
553
|
+
for (const [target, label, installTarget] of targetInstallers()) {
|
|
554
|
+
if (!targets[target]) continue;
|
|
555
|
+
log.heading(label);
|
|
556
|
+
await installTarget();
|
|
597
557
|
}
|
|
598
558
|
|
|
599
559
|
// The rest of the shared work reads what the installers just wrote: the index
|
|
@@ -608,7 +568,8 @@ async function install(targets: Targets) {
|
|
|
608
568
|
|
|
609
569
|
await refreshControlRoom();
|
|
610
570
|
|
|
611
|
-
|
|
571
|
+
if (args.includes("--verbose")) console.log("");
|
|
572
|
+
return runDoctor(args);
|
|
612
573
|
}
|
|
613
574
|
|
|
614
575
|
/**
|
|
@@ -912,7 +873,7 @@ async function update() {
|
|
|
912
873
|
clearUpdateCache();
|
|
913
874
|
|
|
914
875
|
log.info("Reinstalling...");
|
|
915
|
-
await install(resolveTargets([]));
|
|
876
|
+
process.exit(await install(resolveTargets([]), []));
|
|
916
877
|
}
|
|
917
878
|
|
|
918
879
|
function cliDebug(args: string[]) {
|
package/src/cli/migrate.ts
CHANGED
|
@@ -671,7 +671,9 @@ function readSettingsObject(): Record<string, Record<string, unknown>> | null {
|
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
-
function retiredKeysIn(
|
|
674
|
+
export function retiredKeysIn(
|
|
675
|
+
settings: Record<string, Record<string, unknown>>
|
|
676
|
+
): string[] {
|
|
675
677
|
return Object.entries(RETIRED_SETTINGS_KEYS).flatMap(([section, keys]) => {
|
|
676
678
|
const value = settings[section];
|
|
677
679
|
if (typeof value !== "object" || value === null) return [];
|
package/src/hooks/lib/log.ts
CHANGED
|
@@ -83,6 +83,12 @@ export interface HookErrorGroup {
|
|
|
83
83
|
source: string;
|
|
84
84
|
count: number;
|
|
85
85
|
last: string;
|
|
86
|
+
lastAt: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface LoggedError {
|
|
90
|
+
at: number;
|
|
91
|
+
message: string;
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -114,13 +120,14 @@ function whatTheAgentSaid(message: string): string {
|
|
|
114
120
|
return output[1].trim() || output[2].trim() || message;
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
function groupBySource(errors:
|
|
123
|
+
function groupBySource(errors: LoggedError[]): HookErrorGroup[] {
|
|
118
124
|
const groups = new Map<string, HookErrorGroup>();
|
|
119
|
-
for (const
|
|
120
|
-
const source = /^([^:]+):/.exec(
|
|
121
|
-
const group = groups.get(source) ?? { source, count: 0, last: "" };
|
|
125
|
+
for (const { at, message } of errors) {
|
|
126
|
+
const source = /^([^:]+):/.exec(message)?.[1] ?? "unknown";
|
|
127
|
+
const group = groups.get(source) ?? { source, count: 0, last: "", lastAt: 0 };
|
|
122
128
|
group.count++;
|
|
123
|
-
group.last = whatTheAgentSaid(
|
|
129
|
+
group.last = whatTheAgentSaid(message.slice(source.length + 1).trim()).slice(0, 120);
|
|
130
|
+
group.lastAt = at;
|
|
124
131
|
groups.set(source, group);
|
|
125
132
|
}
|
|
126
133
|
return [...groups.values()].sort((a, b) => b.count - a.count);
|
|
@@ -131,7 +138,10 @@ export function recentHookErrors(now: number = Date.now()): HookErrorGroup[] {
|
|
|
131
138
|
const recent = readAllLogs()
|
|
132
139
|
.split("\n")
|
|
133
140
|
.filter((line) => line.includes("] ERROR ") && loggedAt(line) > now - DAY_MS)
|
|
134
|
-
.map((line) =>
|
|
141
|
+
.map((line) => ({
|
|
142
|
+
at: loggedAt(line),
|
|
143
|
+
message: line.replace(/^\[.*?\] ERROR /, ""),
|
|
144
|
+
}));
|
|
135
145
|
return groupBySource(recent);
|
|
136
146
|
} catch {
|
|
137
147
|
return [];
|
package/src/targets/lib.ts
CHANGED
|
@@ -27,9 +27,21 @@ function runningUnderTest(): boolean {
|
|
|
27
27
|
return process.env.PAL_TEST_SANDBOX === "1";
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
let narrating = true;
|
|
31
|
+
|
|
32
|
+
/** Install's step log is for --verbose; the doctor report that follows carries the result. */
|
|
33
|
+
export function narrateSteps(on: boolean): void {
|
|
34
|
+
narrating = on;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function narrate(line: string): void {
|
|
38
|
+
if (narrating) console.log(line);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
export const log = {
|
|
31
|
-
info: (msg: string) =>
|
|
32
|
-
success: (msg: string) =>
|
|
42
|
+
info: (msg: string) => narrate(`\x1b[34m[pal]\x1b[0m ${msg}`),
|
|
43
|
+
success: (msg: string) => narrate(`\x1b[32m[pal]\x1b[0m ${msg}`),
|
|
44
|
+
heading: (label: string) => narrate(`\n━━━ ${label} ━━━`),
|
|
33
45
|
warn: (msg: string) => console.log(`\x1b[33m[pal]\x1b[0m ${msg}`),
|
|
34
46
|
error: (msg: string) => console.error(`\x1b[31m[pal]\x1b[0m ${msg}`),
|
|
35
47
|
|
|
@@ -42,7 +54,7 @@ export const log = {
|
|
|
42
54
|
*/
|
|
43
55
|
detail: (msg: string) => {
|
|
44
56
|
if (runningUnderTest()) return;
|
|
45
|
-
|
|
57
|
+
narrate(`\x1b[34m[pal]\x1b[0m ${msg}`);
|
|
46
58
|
},
|
|
47
59
|
};
|
|
48
60
|
|