@poncho-ai/sdk 1.8.0 → 1.9.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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +145 -0
- package/dist/index.d.ts +118 -1
- package/dist/index.js +190 -1
- package/package.json +1 -1
- package/src/api-types.ts +77 -0
- package/src/index.ts +32 -1
- package/src/logger.ts +199 -0
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/sdk@1.
|
|
2
|
+
> @poncho-ai/sdk@1.9.0 build /home/runner/work/poncho-ai/poncho-ai/packages/sdk
|
|
3
3
|
> tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v8.5.1
|
|
8
|
+
[34mCLI[39m Target: es2022
|
|
9
|
+
[34mESM[39m Build start
|
|
10
|
+
[32mESM[39m [1mdist/index.js [22m[32m17.24 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 23ms
|
|
12
|
+
[34mDTS[39m Build start
|
|
13
|
+
[32mDTS[39m ⚡️ Build success in 1448ms
|
|
14
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m27.76 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,150 @@
|
|
|
1
1
|
# @poncho-ai/sdk
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#100](https://github.com/cesr/poncho-ai/pull/100) [`ef4fe5d`](https://github.com/cesr/poncho-ai/commit/ef4fe5d1fd4bb31c201fd240f4524b64f01e3e6d) Thanks [@cesr](https://github.com/cesr)! - feat(logging): readable, scoped, level-aware dev server logs
|
|
8
|
+
|
|
9
|
+
`poncho dev` output is now formatted consistently across the CLI and
|
|
10
|
+
harness:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
20:23:45 ✓ poncho dev server ready at http://localhost:3000
|
|
14
|
+
20:23:45 • slack enabled at /api/messaging/slack
|
|
15
|
+
20:23:45 • cron scheduled 2 jobs: hourly_check, nightly_summary
|
|
16
|
+
20:24:15 → cron:hourly_check started
|
|
17
|
+
20:24:17 ✓ cron:hourly_check completed in 1.2s (3 chats)
|
|
18
|
+
20:25:00 ⚠ telegram approval not found: req-7f42a
|
|
19
|
+
20:25:01 ✗ poncho internal error: ECONNREFUSED
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Format: `HH:mm:ss <symbol> <scope> <message>`. Scopes (`poncho`, `cron`,
|
|
23
|
+
`reminder`, `messaging`, `slack`, `telegram`, `resend`, `subagent`,
|
|
24
|
+
`approval`, `browser`, `csrf`, `upload`, `serverless`, `self-fetch`,
|
|
25
|
+
`mcp`, `telemetry`, `cost`, `model`, `harness`, `event`, `tools`)
|
|
26
|
+
replace the previous mix of `[poncho]`, `[poncho][cost]`, `[cron]`,
|
|
27
|
+
`[messaging-runner]`, `[event] ...`, etc.
|
|
28
|
+
- New `createLogger(scope)` exported from `@poncho-ai/sdk` with
|
|
29
|
+
`.debug/.info/.warn/.error/.success/.ready/.item/.child(sub)` and
|
|
30
|
+
helpers `formatError`, `url`, `muted`, `num`.
|
|
31
|
+
- Honors `NO_COLOR` / `FORCE_COLOR` and `LOG_LEVEL=debug|info|warn|error|silent`.
|
|
32
|
+
Verbose telemetry/cost/event lines now log at `debug` and are silent
|
|
33
|
+
by default.
|
|
34
|
+
- `poncho dev` gains `-v`/`--verbose` (debug), `-q`/`--quiet` (warn+),
|
|
35
|
+
and `--log-level <level>` flags.
|
|
36
|
+
- Each scope tag is colored with a stable pastel hue (truecolor), with
|
|
37
|
+
256-color and 16-color fallbacks. Children (`cron:hourly_check`)
|
|
38
|
+
inherit their parent's color.
|
|
39
|
+
- TTY-aware: ANSI color is stripped when stdout is piped.
|
|
40
|
+
- Conversation-egress logging (`[poncho][egress] read: …`) is now opt-in
|
|
41
|
+
via `PONCHO_LOG_EGRESS=1` (matching the documented behavior; it had
|
|
42
|
+
been logging unconditionally).
|
|
43
|
+
- No behavior changes to which events are emitted — only formatting.
|
|
44
|
+
|
|
45
|
+
- [#100](https://github.com/cesr/poncho-ai/pull/100) [`ef4fe5d`](https://github.com/cesr/poncho-ai/commit/ef4fe5d1fd4bb31c201fd240f4524b64f01e3e6d) Thanks [@cesr](https://github.com/cesr)! - feat: Slack-style message threads
|
|
46
|
+
|
|
47
|
+
Users can now fork any persisted message into one or more threads. Each
|
|
48
|
+
thread is a new conversation whose initial history is a snapshot of the
|
|
49
|
+
parent up to and including the anchor message; replies in the thread
|
|
50
|
+
evolve independently of the parent. Multiple threads per parent message
|
|
51
|
+
are supported.
|
|
52
|
+
|
|
53
|
+
## Web UI
|
|
54
|
+
- Hover any message in the main pane to reveal a "Reply in thread" pill
|
|
55
|
+
positioned just below the bubble (offset varies by role). The pill is
|
|
56
|
+
invisible by default and only appears on hover; a delayed-hide bridges
|
|
57
|
+
the empty space between message and pill so the user can move the
|
|
58
|
+
mouse onto it without it flickering off.
|
|
59
|
+
- Once a thread exists on a message, the pill is replaced by an
|
|
60
|
+
always-visible badge (`"N replies · 5m ago"`, count bold + meta muted).
|
|
61
|
+
Multiple threads stack vertically under the message, each with its own
|
|
62
|
+
badge. Hovering a badge reveals an outside-positioned `×` delete with
|
|
63
|
+
the same two-step "× → sure?" confirmation as the sidebar
|
|
64
|
+
conversation-delete.
|
|
65
|
+
- Clicking a badge opens the thread in a right-side panel that mirrors
|
|
66
|
+
the existing browser-panel pattern: a flex sibling of `.main-chat`
|
|
67
|
+
with a 1px drag-resize handle. The panel has its own composer
|
|
68
|
+
(independent file uploads, paste-to-attach, attachment preview)
|
|
69
|
+
rendered alongside the main composer so users can keep typing in the
|
|
70
|
+
parent conversation. Vertical padding matches the main composer so
|
|
71
|
+
both chatboxes line up at the same baseline.
|
|
72
|
+
- The pinned parent message and replies inside the panel render through
|
|
73
|
+
the same DOM construction logic as the main pane (assistant avatar +
|
|
74
|
+
markdown, user bubble + file thumbnails). Reply submissions stream
|
|
75
|
+
token-by-token via SSE (parsed model:chunk events feed an optimistic
|
|
76
|
+
assistant placeholder; a thinking-indicator shows until the first
|
|
77
|
+
chunk lands).
|
|
78
|
+
- The open thread is reflected in the URL hash (`#thread=<id>`) so a
|
|
79
|
+
page reload restores the panel. Switching conversations or closing
|
|
80
|
+
the panel clears the hash and any sticky drag-resize widths.
|
|
81
|
+
|
|
82
|
+
## DB
|
|
83
|
+
- New `parent_message_id TEXT` column on `conversations` (migration 6)
|
|
84
|
+
plus a partial index on `(parent_conversation_id, parent_message_id)
|
|
85
|
+
WHERE parent_message_id IS NOT NULL`.
|
|
86
|
+
- The existing `parent_conversation_id` plumbing is reused; subagents
|
|
87
|
+
and threads coexist on that column, discriminated by whether
|
|
88
|
+
`parent_message_id` is set (subagents leave it `NULL`).
|
|
89
|
+
- `threadMeta` (snapshot length + cached parent-message summary)
|
|
90
|
+
round-trips inside the conversation `data` blob.
|
|
91
|
+
|
|
92
|
+
## API
|
|
93
|
+
- `GET /api/conversations/:id/threads` → `{ threads: ApiThreadSummary[] }`
|
|
94
|
+
- `POST /api/conversations/:id/threads { parentMessageId, title? }` →
|
|
95
|
+
201 `{ thread, conversationId }` |
|
|
96
|
+
404 `PARENT_MESSAGE_NOT_FOUND` |
|
|
97
|
+
409 `MESSAGE_ID_REQUIRED` (anchor lacks a stable id) |
|
|
98
|
+
409 `ANCHOR_IN_FLIGHT` (anchor is the streaming tail of a live run)
|
|
99
|
+
- The two `SUBAGENT_READ_ONLY` gates on `/messages` and `/continue` are
|
|
100
|
+
now keyed on `subagentMeta` rather than `parentConversationId` so
|
|
101
|
+
threads remain writable.
|
|
102
|
+
- New `ApiThreadSummary`, `ApiThreadListResponse`,
|
|
103
|
+
`ApiCreateThreadRequest`, `ApiCreateThreadResponse` types in
|
|
104
|
+
`@poncho-ai/sdk`. New `AgentClient.listThreads` /
|
|
105
|
+
`AgentClient.createThread` wrappers in `@poncho-ai/client`.
|
|
106
|
+
|
|
107
|
+
## Storage interface
|
|
108
|
+
- `ConversationStore.listThreads(parentConversationId)` and the
|
|
109
|
+
matching `StorageEngine.conversations.listThreads(...)`. External
|
|
110
|
+
implementers of these interfaces will need to add the method.
|
|
111
|
+
- `Conversation` / `ConversationCreateInit` / `ConversationSummary`
|
|
112
|
+
gained optional `parentMessageId` and `threadMeta` fields.
|
|
113
|
+
|
|
114
|
+
## Fork semantics
|
|
115
|
+
- Stable `metadata.id` on every persisted message: `randomUUID()` is
|
|
116
|
+
hoisted once per turn and reused for both the user message and the
|
|
117
|
+
in-flight assistant message across all persist sites (cli messaging
|
|
118
|
+
run, cli `/messages` handler, cron path). `buildAssistantMetadata`
|
|
119
|
+
takes an optional `{ id, timestamp }` opt-arg.
|
|
120
|
+
- No DB backfill of legacy id-less messages; the SPA hides the
|
|
121
|
+
"Reply in thread" affordance on rows whose `metadata.id` is missing.
|
|
122
|
+
- The visible-sequence used for the anchor lookup is reconstructed as
|
|
123
|
+
`[...compactedHistory, ...messages.filter(notCompactionSummary)]`,
|
|
124
|
+
so pre-compaction anchors are supported. For pre-compaction anchors,
|
|
125
|
+
`_harnessMessages` is reset to `undefined` so the harness rebuilds
|
|
126
|
+
canonical history from `messages` on the thread's first run.
|
|
127
|
+
- Forking on the actively-streaming tail message of a live run returns
|
|
128
|
+
409 `ANCHOR_IN_FLIGHT`; any prior, already-persisted message is
|
|
129
|
+
fork-able even while the parent is mid-run.
|
|
130
|
+
- Tool-result archive entries are filtered to only those referenced by
|
|
131
|
+
tool calls in the trimmed `_harnessMessages` (no whole-archive clones).
|
|
132
|
+
- All run-specific state is reset on the new thread:
|
|
133
|
+
`runtimeRunId`, `pendingApprovals`, `runStatus`,
|
|
134
|
+
`pendingSubagentResults`, `subagentCallbackCount`,
|
|
135
|
+
`runningCallbackSince`, `_continuationMessages`. `channelMeta` and
|
|
136
|
+
`subagentMeta` are explicitly NOT inherited so threads aren't bound
|
|
137
|
+
to the parent's Slack/Telegram thread and aren't subagent runs.
|
|
138
|
+
- Thread conversations stay out of the sidebar list (already-existing
|
|
139
|
+
`!c.parentConversationId` filter) and are cascade-deleted with their
|
|
140
|
+
parent.
|
|
141
|
+
|
|
142
|
+
## 1.8.1
|
|
143
|
+
|
|
144
|
+
### Patch Changes
|
|
145
|
+
|
|
146
|
+
- Add VfsAccess interface and expand ToolContext with optional `vfs` field for tenant-scoped virtual filesystem access in tool handlers.
|
|
147
|
+
|
|
3
148
|
## 1.8.0
|
|
4
149
|
|
|
5
150
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -528,6 +528,99 @@ declare const ONBOARDING_FIELDS: readonly [{
|
|
|
528
528
|
declare const FEATURE_DOMAIN_ORDER: readonly FeatureDomain[];
|
|
529
529
|
declare const fieldsForScope: (scope: OnboardingScope) => OnboardingField[];
|
|
530
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Shared API response types used by both the CLI server and the client SDK.
|
|
533
|
+
* Defining them here ensures compile-time drift detection between the two.
|
|
534
|
+
*/
|
|
535
|
+
interface ApiApprovalResponse {
|
|
536
|
+
ok: true;
|
|
537
|
+
approvalId: string;
|
|
538
|
+
approved: boolean;
|
|
539
|
+
batchComplete?: boolean;
|
|
540
|
+
}
|
|
541
|
+
interface ApiStopRunResponse {
|
|
542
|
+
ok: true;
|
|
543
|
+
stopped: boolean;
|
|
544
|
+
runId?: string;
|
|
545
|
+
}
|
|
546
|
+
interface ApiCompactResponse {
|
|
547
|
+
compacted: boolean;
|
|
548
|
+
messagesBefore: number;
|
|
549
|
+
messagesAfter: number;
|
|
550
|
+
warning?: string;
|
|
551
|
+
}
|
|
552
|
+
interface ApiSubagentSummary {
|
|
553
|
+
conversationId: string;
|
|
554
|
+
title: string;
|
|
555
|
+
task: string;
|
|
556
|
+
status: string;
|
|
557
|
+
messageCount: number;
|
|
558
|
+
hasPendingApprovals: boolean;
|
|
559
|
+
createdAt: string;
|
|
560
|
+
updatedAt: string;
|
|
561
|
+
}
|
|
562
|
+
interface ApiThreadSummary {
|
|
563
|
+
conversationId: string;
|
|
564
|
+
parentConversationId: string;
|
|
565
|
+
parentMessageId: string;
|
|
566
|
+
title: string;
|
|
567
|
+
parentMessageSummary?: string;
|
|
568
|
+
messageCount: number;
|
|
569
|
+
/** messageCount - snapshotLength: number of replies posted into the thread. */
|
|
570
|
+
replyCount: number;
|
|
571
|
+
snapshotLength: number;
|
|
572
|
+
createdAt: number;
|
|
573
|
+
updatedAt: number;
|
|
574
|
+
/** Same as updatedAt; named for the inline-row UI. */
|
|
575
|
+
lastReplyAt: number;
|
|
576
|
+
}
|
|
577
|
+
interface ApiThreadListResponse {
|
|
578
|
+
threads: ApiThreadSummary[];
|
|
579
|
+
}
|
|
580
|
+
interface ApiCreateThreadRequest {
|
|
581
|
+
parentMessageId: string;
|
|
582
|
+
title?: string;
|
|
583
|
+
}
|
|
584
|
+
interface ApiCreateThreadResponse {
|
|
585
|
+
thread: ApiThreadSummary;
|
|
586
|
+
conversationId: string;
|
|
587
|
+
}
|
|
588
|
+
interface ApiSecretEntry {
|
|
589
|
+
name: string;
|
|
590
|
+
label?: string;
|
|
591
|
+
isSet: boolean;
|
|
592
|
+
}
|
|
593
|
+
interface ApiSlashCommand {
|
|
594
|
+
command: string;
|
|
595
|
+
description: string;
|
|
596
|
+
type: "command" | "skill";
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
declare const LEVELS: {
|
|
600
|
+
readonly debug: 10;
|
|
601
|
+
readonly info: 20;
|
|
602
|
+
readonly warn: 30;
|
|
603
|
+
readonly error: 40;
|
|
604
|
+
readonly silent: 100;
|
|
605
|
+
};
|
|
606
|
+
type LevelName = keyof typeof LEVELS;
|
|
607
|
+
declare const setLogLevel: (level: LevelName) => void;
|
|
608
|
+
type Logger = {
|
|
609
|
+
debug: (msg: string, ...extras: unknown[]) => void;
|
|
610
|
+
info: (msg: string, ...extras: unknown[]) => void;
|
|
611
|
+
warn: (msg: string, ...extras: unknown[]) => void;
|
|
612
|
+
error: (msg: string, ...extras: unknown[]) => void;
|
|
613
|
+
success: (msg: string, ...extras: unknown[]) => void;
|
|
614
|
+
ready: (msg: string, ...extras: unknown[]) => void;
|
|
615
|
+
item: (msg: string, ...extras: unknown[]) => void;
|
|
616
|
+
child: (subscope: string) => Logger;
|
|
617
|
+
};
|
|
618
|
+
declare const createLogger: (scope: string) => Logger;
|
|
619
|
+
declare const formatError: (err: unknown) => string;
|
|
620
|
+
declare const url: (s: string) => string;
|
|
621
|
+
declare const muted: (s: string) => string;
|
|
622
|
+
declare const num: (s: string | number) => string;
|
|
623
|
+
|
|
531
624
|
type JsonSchema = {
|
|
532
625
|
type?: string;
|
|
533
626
|
description?: string;
|
|
@@ -570,6 +663,27 @@ interface Message {
|
|
|
570
663
|
}
|
|
571
664
|
/** Extract the text content from a message, regardless of content format. */
|
|
572
665
|
declare const getTextContent: (message: Message) => string;
|
|
666
|
+
/** Virtual filesystem scoped to the current tenant. Available when VFS is enabled. */
|
|
667
|
+
interface VfsAccess {
|
|
668
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
669
|
+
readText(path: string): Promise<string>;
|
|
670
|
+
writeFile(path: string, content: Uint8Array, mimeType?: string): Promise<void>;
|
|
671
|
+
writeText(path: string, content: string): Promise<void>;
|
|
672
|
+
exists(path: string): Promise<boolean>;
|
|
673
|
+
stat(path: string): Promise<{
|
|
674
|
+
size: number;
|
|
675
|
+
isDirectory: boolean;
|
|
676
|
+
mimeType?: string;
|
|
677
|
+
updatedAt: number;
|
|
678
|
+
}>;
|
|
679
|
+
readdir(path: string): Promise<string[]>;
|
|
680
|
+
mkdir(path: string, options?: {
|
|
681
|
+
recursive?: boolean;
|
|
682
|
+
}): Promise<void>;
|
|
683
|
+
rm(path: string, options?: {
|
|
684
|
+
recursive?: boolean;
|
|
685
|
+
}): Promise<void>;
|
|
686
|
+
}
|
|
573
687
|
interface ToolContext {
|
|
574
688
|
runId: string;
|
|
575
689
|
agentId: string;
|
|
@@ -580,6 +694,8 @@ interface ToolContext {
|
|
|
580
694
|
conversationId?: string;
|
|
581
695
|
/** The tenant ID when running in multi-tenant mode. */
|
|
582
696
|
tenantId?: string;
|
|
697
|
+
/** Virtual filesystem scoped to the current tenant. Available when VFS is enabled. */
|
|
698
|
+
vfs?: VfsAccess;
|
|
583
699
|
}
|
|
584
700
|
type ToolHandler<TInput extends Record<string, unknown>, TOutput> = (input: TInput, context: ToolContext) => Promise<TOutput> | TOutput;
|
|
585
701
|
interface ToolDefinition<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown> {
|
|
@@ -685,6 +801,7 @@ type AgentEvent = {
|
|
|
685
801
|
} | {
|
|
686
802
|
type: "tool:completed";
|
|
687
803
|
tool: string;
|
|
804
|
+
input?: unknown;
|
|
688
805
|
output: unknown;
|
|
689
806
|
duration: number;
|
|
690
807
|
outputTokenEstimate?: number;
|
|
@@ -771,4 +888,4 @@ type AgentEvent = {
|
|
|
771
888
|
reason: string;
|
|
772
889
|
};
|
|
773
890
|
|
|
774
|
-
export { type AgentEvent, type AgentFailure, type ContentPart, FEATURE_DOMAIN_ORDER, type FeatureDomain, type FileContentPart, type FileInput, type JsonSchema, type Message, ONBOARDING_FIELDS, type OnboardingField, type OnboardingFieldCondition, type OnboardingFieldKind, type OnboardingFieldTarget, type OnboardingOption, type OnboardingScope, type Role, type RunInput, type RunResult, type TextContentPart, type TokenUsage, type ToolContext, type ToolDefinition, type ToolHandler, defineTool, fieldsForScope, getTextContent };
|
|
891
|
+
export { type AgentEvent, type AgentFailure, type ApiApprovalResponse, type ApiCompactResponse, type ApiCreateThreadRequest, type ApiCreateThreadResponse, type ApiSecretEntry, type ApiSlashCommand, type ApiStopRunResponse, type ApiSubagentSummary, type ApiThreadListResponse, type ApiThreadSummary, type ContentPart, FEATURE_DOMAIN_ORDER, type FeatureDomain, type FileContentPart, type FileInput, type JsonSchema, type Logger, type Message, ONBOARDING_FIELDS, type OnboardingField, type OnboardingFieldCondition, type OnboardingFieldKind, type OnboardingFieldTarget, type OnboardingOption, type OnboardingScope, type Role, type RunInput, type RunResult, type TextContentPart, type TokenUsage, type ToolContext, type ToolDefinition, type ToolHandler, type VfsAccess, createLogger, defineTool, fieldsForScope, formatError, getTextContent, muted, num, setLogLevel, url };
|
package/dist/index.js
CHANGED
|
@@ -439,6 +439,189 @@ var fieldsForScope = (scope) => ONBOARDING_FIELDS.filter(
|
|
|
439
439
|
(field) => field.scopes.includes(scope)
|
|
440
440
|
);
|
|
441
441
|
|
|
442
|
+
// src/logger.ts
|
|
443
|
+
var LEVELS = { debug: 10, info: 20, warn: 30, error: 40, silent: 100 };
|
|
444
|
+
var parseLevel = (raw) => {
|
|
445
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
446
|
+
return v in LEVELS ? v : "info";
|
|
447
|
+
};
|
|
448
|
+
var colorEnabled = (() => {
|
|
449
|
+
if (process.env.NO_COLOR) return false;
|
|
450
|
+
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") return true;
|
|
451
|
+
return Boolean(process.stdout.isTTY);
|
|
452
|
+
})();
|
|
453
|
+
var wrap = (open, close = 0) => colorEnabled ? (s) => `\x1B[${open}m${s}\x1B[${close}m` : (s) => s;
|
|
454
|
+
var c = {
|
|
455
|
+
dim: wrap(2, 22),
|
|
456
|
+
bold: wrap(1, 22),
|
|
457
|
+
red: wrap(31),
|
|
458
|
+
green: wrap(32),
|
|
459
|
+
yellow: wrap(33),
|
|
460
|
+
blue: wrap(34),
|
|
461
|
+
magenta: wrap(35),
|
|
462
|
+
cyan: wrap(36),
|
|
463
|
+
gray: wrap(90)
|
|
464
|
+
};
|
|
465
|
+
var supportsTruecolor = (() => {
|
|
466
|
+
if (!colorEnabled) return false;
|
|
467
|
+
const t = process.env.COLORTERM;
|
|
468
|
+
return t === "truecolor" || t === "24bit";
|
|
469
|
+
})();
|
|
470
|
+
var supports256 = (() => {
|
|
471
|
+
if (!colorEnabled) return false;
|
|
472
|
+
if (supportsTruecolor) return true;
|
|
473
|
+
return Boolean(process.env.TERM && /256/.test(process.env.TERM));
|
|
474
|
+
})();
|
|
475
|
+
var PALETTE_256 = [
|
|
476
|
+
33,
|
|
477
|
+
39,
|
|
478
|
+
38,
|
|
479
|
+
75,
|
|
480
|
+
81,
|
|
481
|
+
45,
|
|
482
|
+
50,
|
|
483
|
+
49,
|
|
484
|
+
43,
|
|
485
|
+
36,
|
|
486
|
+
73,
|
|
487
|
+
80,
|
|
488
|
+
86,
|
|
489
|
+
76,
|
|
490
|
+
82,
|
|
491
|
+
113,
|
|
492
|
+
119,
|
|
493
|
+
148,
|
|
494
|
+
149,
|
|
495
|
+
178,
|
|
496
|
+
184,
|
|
497
|
+
220,
|
|
498
|
+
221,
|
|
499
|
+
208,
|
|
500
|
+
209,
|
|
501
|
+
214,
|
|
502
|
+
215,
|
|
503
|
+
202,
|
|
504
|
+
203,
|
|
505
|
+
198,
|
|
506
|
+
199,
|
|
507
|
+
200,
|
|
508
|
+
207,
|
|
509
|
+
206,
|
|
510
|
+
171,
|
|
511
|
+
165,
|
|
512
|
+
135,
|
|
513
|
+
141,
|
|
514
|
+
99,
|
|
515
|
+
105,
|
|
516
|
+
129,
|
|
517
|
+
134,
|
|
518
|
+
174
|
|
519
|
+
];
|
|
520
|
+
var PALETTE_16 = [31, 32, 33, 34, 35, 36, 91, 92, 93, 94, 95, 96];
|
|
521
|
+
var hashScope = (s) => {
|
|
522
|
+
let h = 5381;
|
|
523
|
+
for (let i = 0; i < s.length; i++) h = (h * 33 ^ s.charCodeAt(i)) >>> 0;
|
|
524
|
+
return h;
|
|
525
|
+
};
|
|
526
|
+
var hslToRgb = (h, s, l) => {
|
|
527
|
+
const a = s * Math.min(l, 1 - l);
|
|
528
|
+
const f = (n) => {
|
|
529
|
+
const k = (n + h / 30) % 12;
|
|
530
|
+
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
531
|
+
};
|
|
532
|
+
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
|
|
533
|
+
};
|
|
534
|
+
var scopeColorCache = /* @__PURE__ */ new Map();
|
|
535
|
+
var colorForScope = (scope) => {
|
|
536
|
+
if (!colorEnabled) return (s) => s;
|
|
537
|
+
const base = scope.split(":")[0];
|
|
538
|
+
const cached = scopeColorCache.get(base);
|
|
539
|
+
if (cached) return cached;
|
|
540
|
+
const hash = hashScope(base);
|
|
541
|
+
let fn;
|
|
542
|
+
if (supportsTruecolor) {
|
|
543
|
+
const hue = hash * 137 % 360;
|
|
544
|
+
const [r, g, b] = hslToRgb(hue, 0.4, 0.75);
|
|
545
|
+
fn = (s) => `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m`;
|
|
546
|
+
} else if (supports256) {
|
|
547
|
+
const code = PALETTE_256[hash % PALETTE_256.length];
|
|
548
|
+
fn = (s) => `\x1B[38;5;${code}m${s}\x1B[39m`;
|
|
549
|
+
} else {
|
|
550
|
+
const code = PALETTE_16[hash % PALETTE_16.length];
|
|
551
|
+
fn = (s) => `\x1B[${code}m${s}\x1B[39m`;
|
|
552
|
+
}
|
|
553
|
+
scopeColorCache.set(base, fn);
|
|
554
|
+
return fn;
|
|
555
|
+
};
|
|
556
|
+
var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
557
|
+
var formatTime = (d) => `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
558
|
+
var SCOPE_WIDTH = 10;
|
|
559
|
+
var padScope = (scope) => scope.length >= SCOPE_WIDTH ? scope : scope + " ".repeat(SCOPE_WIDTH - scope.length);
|
|
560
|
+
var SYMBOLS = {
|
|
561
|
+
debug: "\xB7",
|
|
562
|
+
info: "\u2192",
|
|
563
|
+
warn: "\u26A0",
|
|
564
|
+
error: "\u2717",
|
|
565
|
+
success: "\u2713",
|
|
566
|
+
ready: "\u2713",
|
|
567
|
+
neutral: "\u2022"
|
|
568
|
+
};
|
|
569
|
+
var SYMBOL_COLOR = {
|
|
570
|
+
debug: c.dim,
|
|
571
|
+
info: c.cyan,
|
|
572
|
+
warn: c.yellow,
|
|
573
|
+
error: c.red,
|
|
574
|
+
success: c.green,
|
|
575
|
+
ready: c.green,
|
|
576
|
+
neutral: c.gray
|
|
577
|
+
};
|
|
578
|
+
var VARIANT_LEVEL = {
|
|
579
|
+
debug: "debug",
|
|
580
|
+
info: "info",
|
|
581
|
+
neutral: "info",
|
|
582
|
+
success: "info",
|
|
583
|
+
ready: "info",
|
|
584
|
+
warn: "warn",
|
|
585
|
+
error: "error"
|
|
586
|
+
};
|
|
587
|
+
var currentLevel = parseLevel(process.env.LOG_LEVEL);
|
|
588
|
+
var setLogLevel = (level) => {
|
|
589
|
+
currentLevel = level;
|
|
590
|
+
};
|
|
591
|
+
var stringifyArg = (a) => {
|
|
592
|
+
if (a instanceof Error) return a.stack ?? a.message;
|
|
593
|
+
if (typeof a === "string") return a;
|
|
594
|
+
try {
|
|
595
|
+
return JSON.stringify(a);
|
|
596
|
+
} catch {
|
|
597
|
+
return String(a);
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
var write = (variant, scope, msg, extras) => {
|
|
601
|
+
if (LEVELS[VARIANT_LEVEL[variant]] < LEVELS[currentLevel]) return;
|
|
602
|
+
const stream = variant === "warn" || variant === "error" ? process.stderr : process.stdout;
|
|
603
|
+
const time = c.gray(formatTime(/* @__PURE__ */ new Date()));
|
|
604
|
+
const symbol = SYMBOL_COLOR[variant](SYMBOLS[variant]);
|
|
605
|
+
const scopeText = colorForScope(scope)(padScope(scope));
|
|
606
|
+
const tail = extras.length > 0 ? " " + extras.map(stringifyArg).join(" ") : "";
|
|
607
|
+
stream.write(`${time} ${symbol} ${scopeText} ${msg}${tail}
|
|
608
|
+
`);
|
|
609
|
+
};
|
|
610
|
+
var createLogger = (scope) => ({
|
|
611
|
+
debug: (msg, ...extras) => write("debug", scope, msg, extras),
|
|
612
|
+
info: (msg, ...extras) => write("info", scope, msg, extras),
|
|
613
|
+
warn: (msg, ...extras) => write("warn", scope, msg, extras),
|
|
614
|
+
error: (msg, ...extras) => write("error", scope, msg, extras),
|
|
615
|
+
success: (msg, ...extras) => write("success", scope, msg, extras),
|
|
616
|
+
ready: (msg, ...extras) => write("ready", scope, msg, extras),
|
|
617
|
+
item: (msg, ...extras) => write("neutral", scope, msg, extras),
|
|
618
|
+
child: (subscope) => createLogger(`${scope}:${subscope}`)
|
|
619
|
+
});
|
|
620
|
+
var formatError = (err) => err instanceof Error ? err.message : String(err);
|
|
621
|
+
var url = (s) => c.cyan(s);
|
|
622
|
+
var muted = (s) => c.dim(s);
|
|
623
|
+
var num = (s) => c.bold(String(s));
|
|
624
|
+
|
|
442
625
|
// src/index.ts
|
|
443
626
|
var getTextContent = (message) => {
|
|
444
627
|
if (typeof message.content === "string") return message.content;
|
|
@@ -448,7 +631,13 @@ var defineTool = (definition) => definition;
|
|
|
448
631
|
export {
|
|
449
632
|
FEATURE_DOMAIN_ORDER,
|
|
450
633
|
ONBOARDING_FIELDS,
|
|
634
|
+
createLogger,
|
|
451
635
|
defineTool,
|
|
452
636
|
fieldsForScope,
|
|
453
|
-
|
|
637
|
+
formatError,
|
|
638
|
+
getTextContent,
|
|
639
|
+
muted,
|
|
640
|
+
num,
|
|
641
|
+
setLogLevel,
|
|
642
|
+
url
|
|
454
643
|
};
|
package/package.json
CHANGED
package/src/api-types.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared API response types used by both the CLI server and the client SDK.
|
|
3
|
+
* Defining them here ensures compile-time drift detection between the two.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ApiApprovalResponse {
|
|
7
|
+
ok: true;
|
|
8
|
+
approvalId: string;
|
|
9
|
+
approved: boolean;
|
|
10
|
+
batchComplete?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ApiStopRunResponse {
|
|
14
|
+
ok: true;
|
|
15
|
+
stopped: boolean;
|
|
16
|
+
runId?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ApiCompactResponse {
|
|
20
|
+
compacted: boolean;
|
|
21
|
+
messagesBefore: number;
|
|
22
|
+
messagesAfter: number;
|
|
23
|
+
warning?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ApiSubagentSummary {
|
|
27
|
+
conversationId: string;
|
|
28
|
+
title: string;
|
|
29
|
+
task: string;
|
|
30
|
+
status: string;
|
|
31
|
+
messageCount: number;
|
|
32
|
+
hasPendingApprovals: boolean;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ApiThreadSummary {
|
|
38
|
+
conversationId: string;
|
|
39
|
+
parentConversationId: string;
|
|
40
|
+
parentMessageId: string;
|
|
41
|
+
title: string;
|
|
42
|
+
parentMessageSummary?: string;
|
|
43
|
+
messageCount: number;
|
|
44
|
+
/** messageCount - snapshotLength: number of replies posted into the thread. */
|
|
45
|
+
replyCount: number;
|
|
46
|
+
snapshotLength: number;
|
|
47
|
+
createdAt: number;
|
|
48
|
+
updatedAt: number;
|
|
49
|
+
/** Same as updatedAt; named for the inline-row UI. */
|
|
50
|
+
lastReplyAt: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ApiThreadListResponse {
|
|
54
|
+
threads: ApiThreadSummary[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ApiCreateThreadRequest {
|
|
58
|
+
parentMessageId: string;
|
|
59
|
+
title?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ApiCreateThreadResponse {
|
|
63
|
+
thread: ApiThreadSummary;
|
|
64
|
+
conversationId: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ApiSecretEntry {
|
|
68
|
+
name: string;
|
|
69
|
+
label?: string;
|
|
70
|
+
isSet: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ApiSlashCommand {
|
|
74
|
+
command: string;
|
|
75
|
+
description: string;
|
|
76
|
+
type: "command" | "skill";
|
|
77
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -50,6 +50,24 @@ export const getTextContent = (message: Message): string => {
|
|
|
50
50
|
.join("");
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
/** Virtual filesystem scoped to the current tenant. Available when VFS is enabled. */
|
|
54
|
+
export interface VfsAccess {
|
|
55
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
56
|
+
readText(path: string): Promise<string>;
|
|
57
|
+
writeFile(path: string, content: Uint8Array, mimeType?: string): Promise<void>;
|
|
58
|
+
writeText(path: string, content: string): Promise<void>;
|
|
59
|
+
exists(path: string): Promise<boolean>;
|
|
60
|
+
stat(path: string): Promise<{
|
|
61
|
+
size: number;
|
|
62
|
+
isDirectory: boolean;
|
|
63
|
+
mimeType?: string;
|
|
64
|
+
updatedAt: number;
|
|
65
|
+
}>;
|
|
66
|
+
readdir(path: string): Promise<string[]>;
|
|
67
|
+
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
68
|
+
rm(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
53
71
|
export interface ToolContext {
|
|
54
72
|
runId: string;
|
|
55
73
|
agentId: string;
|
|
@@ -60,6 +78,8 @@ export interface ToolContext {
|
|
|
60
78
|
conversationId?: string;
|
|
61
79
|
/** The tenant ID when running in multi-tenant mode. */
|
|
62
80
|
tenantId?: string;
|
|
81
|
+
/** Virtual filesystem scoped to the current tenant. Available when VFS is enabled. */
|
|
82
|
+
vfs?: VfsAccess;
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
export type ToolHandler<TInput extends Record<string, unknown>, TOutput> = (
|
|
@@ -89,6 +109,7 @@ export const defineTool = <
|
|
|
89
109
|
): ToolDefinition<TInput, TOutput> => definition;
|
|
90
110
|
|
|
91
111
|
export * from "./config-registry.js";
|
|
112
|
+
export * from "./api-types.js";
|
|
92
113
|
|
|
93
114
|
export interface FileInput {
|
|
94
115
|
/** base64 data, data: URI, or https:// URL */
|
|
@@ -154,7 +175,7 @@ export type AgentEvent =
|
|
|
154
175
|
| { type: "model:response"; usage: TokenUsage }
|
|
155
176
|
| { type: "tool:generating"; tool: string; toolCallId: string }
|
|
156
177
|
| { type: "tool:started"; tool: string; input: unknown }
|
|
157
|
-
| { type: "tool:completed"; tool: string; output: unknown; duration: number; outputTokenEstimate?: number }
|
|
178
|
+
| { type: "tool:completed"; tool: string; input?: unknown; output: unknown; duration: number; outputTokenEstimate?: number }
|
|
158
179
|
| { type: "tool:error"; tool: string; error: string; recoverable: boolean }
|
|
159
180
|
| {
|
|
160
181
|
type: "tool:approval:required";
|
|
@@ -205,3 +226,13 @@ export type AgentEvent =
|
|
|
205
226
|
}
|
|
206
227
|
| { type: "subagents:pending" }
|
|
207
228
|
| { type: "compaction:warning"; reason: string };
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
createLogger,
|
|
232
|
+
setLogLevel,
|
|
233
|
+
formatError,
|
|
234
|
+
url,
|
|
235
|
+
muted,
|
|
236
|
+
num,
|
|
237
|
+
type Logger,
|
|
238
|
+
} from "./logger.js";
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Tiny zero-dependency logger for the dev server.
|
|
2
|
+
//
|
|
3
|
+
// Format: `HH:mm:ss <symbol> <scope-padded> <message>`
|
|
4
|
+
// 10:23:45 ✓ poncho dev server ready at http://localhost:3000
|
|
5
|
+
// 10:25:01 ✗ poncho internal error: ...
|
|
6
|
+
//
|
|
7
|
+
// Honors `NO_COLOR` / `FORCE_COLOR` and `LOG_LEVEL` (debug|info|warn|error|silent).
|
|
8
|
+
|
|
9
|
+
const LEVELS = { debug: 10, info: 20, warn: 30, error: 40, silent: 100 } as const;
|
|
10
|
+
type LevelName = keyof typeof LEVELS;
|
|
11
|
+
|
|
12
|
+
const parseLevel = (raw: string | undefined): LevelName => {
|
|
13
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
14
|
+
return v in LEVELS ? (v as LevelName) : "info";
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const colorEnabled = ((): boolean => {
|
|
18
|
+
if (process.env.NO_COLOR) return false;
|
|
19
|
+
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") return true;
|
|
20
|
+
return Boolean(process.stdout.isTTY);
|
|
21
|
+
})();
|
|
22
|
+
|
|
23
|
+
const wrap = (open: number, close = 0): ((s: string) => string) =>
|
|
24
|
+
colorEnabled
|
|
25
|
+
? (s: string) => `\x1b[${open}m${s}\x1b[${close}m`
|
|
26
|
+
: (s: string) => s;
|
|
27
|
+
|
|
28
|
+
const c = {
|
|
29
|
+
dim: wrap(2, 22),
|
|
30
|
+
bold: wrap(1, 22),
|
|
31
|
+
red: wrap(31),
|
|
32
|
+
green: wrap(32),
|
|
33
|
+
yellow: wrap(33),
|
|
34
|
+
blue: wrap(34),
|
|
35
|
+
magenta: wrap(35),
|
|
36
|
+
cyan: wrap(36),
|
|
37
|
+
gray: wrap(90),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Per-scope colors. Each scope name gets a stable, distinct color derived
|
|
41
|
+
// from a hash of its base name (so children like `cron:hourly_check` share
|
|
42
|
+
// the parent's color). Truecolor → unique hue per scope (best). 256-color
|
|
43
|
+
// fallback → curated palette. 16-color fallback → small palette.
|
|
44
|
+
const supportsTruecolor = ((): boolean => {
|
|
45
|
+
if (!colorEnabled) return false;
|
|
46
|
+
const t = process.env.COLORTERM;
|
|
47
|
+
return t === "truecolor" || t === "24bit";
|
|
48
|
+
})();
|
|
49
|
+
|
|
50
|
+
const supports256 = ((): boolean => {
|
|
51
|
+
if (!colorEnabled) return false;
|
|
52
|
+
if (supportsTruecolor) return true;
|
|
53
|
+
return Boolean(process.env.TERM && /256/.test(process.env.TERM));
|
|
54
|
+
})();
|
|
55
|
+
|
|
56
|
+
const PALETTE_256 = [
|
|
57
|
+
33, 39, 38, 75, 81, 45, 50, 49, 43, 36, 73, 80, 86, 76, 82, 113,
|
|
58
|
+
119, 148, 149, 178, 184, 220, 221, 208, 209, 214, 215, 202, 203,
|
|
59
|
+
198, 199, 200, 207, 206, 171, 165, 135, 141, 99, 105, 129, 134, 174,
|
|
60
|
+
];
|
|
61
|
+
const PALETTE_16 = [31, 32, 33, 34, 35, 36, 91, 92, 93, 94, 95, 96];
|
|
62
|
+
|
|
63
|
+
const hashScope = (s: string): number => {
|
|
64
|
+
let h = 5381;
|
|
65
|
+
for (let i = 0; i < s.length; i++) h = ((h * 33) ^ s.charCodeAt(i)) >>> 0;
|
|
66
|
+
return h;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const hslToRgb = (h: number, s: number, l: number): [number, number, number] => {
|
|
70
|
+
const a = s * Math.min(l, 1 - l);
|
|
71
|
+
const f = (n: number): number => {
|
|
72
|
+
const k = (n + h / 30) % 12;
|
|
73
|
+
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
74
|
+
};
|
|
75
|
+
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const scopeColorCache = new Map<string, (s: string) => string>();
|
|
79
|
+
const colorForScope = (scope: string): ((s: string) => string) => {
|
|
80
|
+
if (!colorEnabled) return (s) => s;
|
|
81
|
+
const base = scope.split(":")[0]!;
|
|
82
|
+
const cached = scopeColorCache.get(base);
|
|
83
|
+
if (cached) return cached;
|
|
84
|
+
const hash = hashScope(base);
|
|
85
|
+
let fn: (s: string) => string;
|
|
86
|
+
if (supportsTruecolor) {
|
|
87
|
+
// Spread hues using the golden-angle multiplier for good visual distance
|
|
88
|
+
// between consecutive scopes. Mid-range S/L reads on dark and light bgs.
|
|
89
|
+
const hue = (hash * 137) % 360;
|
|
90
|
+
// Soft pastel: low saturation, high lightness — readable on dark and
|
|
91
|
+
// light backgrounds without being eye-grabbing.
|
|
92
|
+
const [r, g, b] = hslToRgb(hue, 0.40, 0.75);
|
|
93
|
+
fn = (s: string) => `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m`;
|
|
94
|
+
} else if (supports256) {
|
|
95
|
+
const code = PALETTE_256[hash % PALETTE_256.length];
|
|
96
|
+
fn = (s: string) => `\x1b[38;5;${code}m${s}\x1b[39m`;
|
|
97
|
+
} else {
|
|
98
|
+
const code = PALETTE_16[hash % PALETTE_16.length];
|
|
99
|
+
fn = (s: string) => `\x1b[${code}m${s}\x1b[39m`;
|
|
100
|
+
}
|
|
101
|
+
scopeColorCache.set(base, fn);
|
|
102
|
+
return fn;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const pad2 = (n: number): string => (n < 10 ? `0${n}` : String(n));
|
|
106
|
+
const formatTime = (d: Date): string =>
|
|
107
|
+
`${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
108
|
+
|
|
109
|
+
const SCOPE_WIDTH = 10;
|
|
110
|
+
const padScope = (scope: string): string =>
|
|
111
|
+
scope.length >= SCOPE_WIDTH ? scope : scope + " ".repeat(SCOPE_WIDTH - scope.length);
|
|
112
|
+
|
|
113
|
+
type Variant = "debug" | "info" | "warn" | "error" | "success" | "ready" | "neutral";
|
|
114
|
+
|
|
115
|
+
const SYMBOLS: Record<Variant, string> = {
|
|
116
|
+
debug: "·",
|
|
117
|
+
info: "→",
|
|
118
|
+
warn: "⚠",
|
|
119
|
+
error: "✗",
|
|
120
|
+
success: "✓",
|
|
121
|
+
ready: "✓",
|
|
122
|
+
neutral: "•",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const SYMBOL_COLOR: Record<Variant, (s: string) => string> = {
|
|
126
|
+
debug: c.dim,
|
|
127
|
+
info: c.cyan,
|
|
128
|
+
warn: c.yellow,
|
|
129
|
+
error: c.red,
|
|
130
|
+
success: c.green,
|
|
131
|
+
ready: c.green,
|
|
132
|
+
neutral: c.gray,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const VARIANT_LEVEL: Record<Variant, LevelName> = {
|
|
136
|
+
debug: "debug",
|
|
137
|
+
info: "info",
|
|
138
|
+
neutral: "info",
|
|
139
|
+
success: "info",
|
|
140
|
+
ready: "info",
|
|
141
|
+
warn: "warn",
|
|
142
|
+
error: "error",
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
let currentLevel: LevelName = parseLevel(process.env.LOG_LEVEL);
|
|
146
|
+
|
|
147
|
+
export const setLogLevel = (level: LevelName): void => {
|
|
148
|
+
currentLevel = level;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const stringifyArg = (a: unknown): string => {
|
|
152
|
+
if (a instanceof Error) return a.stack ?? a.message;
|
|
153
|
+
if (typeof a === "string") return a;
|
|
154
|
+
try {
|
|
155
|
+
return JSON.stringify(a);
|
|
156
|
+
} catch {
|
|
157
|
+
return String(a);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const write = (variant: Variant, scope: string, msg: string, extras: unknown[]): void => {
|
|
162
|
+
if (LEVELS[VARIANT_LEVEL[variant]] < LEVELS[currentLevel]) return;
|
|
163
|
+
const stream = variant === "warn" || variant === "error" ? process.stderr : process.stdout;
|
|
164
|
+
const time = c.gray(formatTime(new Date()));
|
|
165
|
+
const symbol = SYMBOL_COLOR[variant](SYMBOLS[variant]);
|
|
166
|
+
const scopeText = colorForScope(scope)(padScope(scope));
|
|
167
|
+
const tail = extras.length > 0 ? " " + extras.map(stringifyArg).join(" ") : "";
|
|
168
|
+
stream.write(`${time} ${symbol} ${scopeText} ${msg}${tail}\n`);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export type Logger = {
|
|
172
|
+
debug: (msg: string, ...extras: unknown[]) => void;
|
|
173
|
+
info: (msg: string, ...extras: unknown[]) => void;
|
|
174
|
+
warn: (msg: string, ...extras: unknown[]) => void;
|
|
175
|
+
error: (msg: string, ...extras: unknown[]) => void;
|
|
176
|
+
success: (msg: string, ...extras: unknown[]) => void;
|
|
177
|
+
ready: (msg: string, ...extras: unknown[]) => void;
|
|
178
|
+
item: (msg: string, ...extras: unknown[]) => void;
|
|
179
|
+
child: (subscope: string) => Logger;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const createLogger = (scope: string): Logger => ({
|
|
183
|
+
debug: (msg, ...extras) => write("debug", scope, msg, extras),
|
|
184
|
+
info: (msg, ...extras) => write("info", scope, msg, extras),
|
|
185
|
+
warn: (msg, ...extras) => write("warn", scope, msg, extras),
|
|
186
|
+
error: (msg, ...extras) => write("error", scope, msg, extras),
|
|
187
|
+
success: (msg, ...extras) => write("success", scope, msg, extras),
|
|
188
|
+
ready: (msg, ...extras) => write("ready", scope, msg, extras),
|
|
189
|
+
item: (msg, ...extras) => write("neutral", scope, msg, extras),
|
|
190
|
+
child: (subscope) => createLogger(`${scope}:${subscope}`),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export const formatError = (err: unknown): string =>
|
|
194
|
+
err instanceof Error ? err.message : String(err);
|
|
195
|
+
|
|
196
|
+
// Highlight helpers — use sparingly inside messages
|
|
197
|
+
export const url = (s: string): string => c.cyan(s);
|
|
198
|
+
export const muted = (s: string): string => c.dim(s);
|
|
199
|
+
export const num = (s: string | number): string => c.bold(String(s));
|
package/.turbo/turbo-lint.log
DELETED
package/.turbo/turbo-test.log
DELETED
|
File without changes
|