@yagni-app/code 0.2.1 → 0.3.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/dist/cli.d.ts +30 -0
- package/dist/cli.js +135 -3
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/extension/advisor.d.ts +4 -4
- package/dist/extension/advisor.js +6 -7
- package/dist/extension/approvedPrefixes.d.ts +92 -0
- package/dist/extension/approvedPrefixes.js +252 -0
- package/dist/extension/askAdvisorTool.d.ts +2 -2
- package/dist/extension/askAdvisorTool.js +5 -5
- package/dist/extension/askYagniTool.js +49 -0
- package/dist/extension/branding.d.ts +24 -3
- package/dist/extension/branding.js +71 -10
- package/dist/extension/chipEditor.d.ts +30 -9
- package/dist/extension/chipEditor.js +173 -59
- package/dist/extension/claudeRules.d.ts +0 -2
- package/dist/extension/claudeRules.js +0 -8
- package/dist/extension/cmux/dispatcher.d.ts +25 -0
- package/dist/extension/cmux/dispatcher.js +266 -0
- package/dist/extension/cmux/hooks.d.ts +12 -0
- package/dist/extension/cmux/hooks.js +192 -0
- package/dist/extension/cmux/index.d.ts +3 -0
- package/dist/extension/cmux/index.js +155 -0
- package/dist/extension/cmux/naming.d.ts +5 -0
- package/dist/extension/cmux/naming.js +23 -0
- package/dist/extension/cmux/state.d.ts +33 -0
- package/dist/extension/cmux/state.js +142 -0
- package/dist/extension/config.d.ts +32 -1
- package/dist/extension/config.js +36 -4
- package/dist/extension/costHud.d.ts +16 -22
- package/dist/extension/costHud.js +8 -47
- package/dist/extension/crashReport.js +1 -3
- package/dist/extension/execPolicy.d.ts +119 -0
- package/dist/extension/execPolicy.js +805 -0
- package/dist/extension/footer.d.ts +111 -0
- package/dist/extension/footer.js +294 -0
- package/dist/extension/guardian.d.ts +129 -0
- package/dist/extension/guardian.js +213 -0
- package/dist/extension/index.d.ts +15 -4
- package/dist/extension/index.js +250 -24
- package/dist/extension/permission.d.ts +123 -10
- package/dist/extension/permission.js +586 -40
- package/dist/extension/pipeline/childRegistry.d.ts +41 -0
- package/dist/extension/pipeline/childRegistry.js +118 -0
- package/dist/extension/pipeline/finish.js +5 -1
- package/dist/extension/pipeline/goCommand.d.ts +1 -1
- package/dist/extension/pipeline/goCommand.js +35 -6
- package/dist/extension/pipeline/goStatusCommands.d.ts +10 -0
- package/dist/extension/pipeline/goStatusCommands.js +61 -1
- package/dist/extension/pipeline/personas.js +25 -0
- package/dist/extension/pipeline/runRegistry.d.ts +14 -0
- package/dist/extension/pipeline/runRegistry.js +35 -0
- package/dist/extension/pipeline/runner.js +4 -0
- package/dist/extension/pipeline/verify.d.ts +4 -0
- package/dist/extension/pipeline/verify.js +48 -26
- package/dist/extension/redact.d.ts +20 -0
- package/dist/extension/redact.js +64 -0
- package/dist/extension/rerouteNotice.d.ts +3 -4
- package/dist/extension/rerouteNotice.js +20 -11
- package/dist/extension/subagentRender.d.ts +129 -0
- package/dist/extension/subagentRender.js +441 -0
- package/dist/extension/subagents.d.ts +4 -7
- package/dist/extension/subagents.js +103 -33
- package/dist/extension/ticketTools.d.ts +37 -0
- package/dist/extension/ticketTools.js +117 -0
- package/dist/extension/tokenProvider.js +46 -5
- package/dist/launch.d.ts +7 -0
- package/dist/launch.js +24 -12
- package/dist/padding.d.ts +22 -0
- package/dist/padding.js +25 -0
- package/dist/promptEnrichment.d.ts +40 -0
- package/dist/promptEnrichment.js +85 -0
- package/dist/signalForward.d.ts +60 -0
- package/dist/signalForward.js +130 -0
- package/package.json +5 -5
- package/dist/extension/boostCommand.d.ts +0 -144
- package/dist/extension/boostCommand.js +0 -263
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `ask_advisor` — the peak-tier escalation available to
|
|
2
|
+
* `ask_advisor` — the peak-tier escalation available to Advanced sessions (YAG-380).
|
|
3
3
|
*
|
|
4
|
-
* Cheap driver, expensive consultant:
|
|
4
|
+
* Cheap driver, expensive consultant: an Advanced session drives on `advanced` and
|
|
5
5
|
* calls this when it hits a judgment call worth the strongest model. The consult
|
|
6
6
|
* spawns a child pi at `peak` through the same `runStage` seam `/go` uses, and
|
|
7
7
|
* returns the advice as plain TEXT the driver acts on — text we own, so
|
|
@@ -79,7 +79,7 @@ export function makeAskAdvisorTool(opts) {
|
|
|
79
79
|
label: "Ask the advisor",
|
|
80
80
|
description: "Escalate ONE hard judgment call to the peak-tier advisor — the strongest " +
|
|
81
81
|
"model available, which reads the code itself and returns a recommendation. " +
|
|
82
|
-
"Available only on the
|
|
82
|
+
"Available only on the Advanced tier, and capped per session, so use it for " +
|
|
83
83
|
"calls that are genuinely worth it: an architectural fork with no obvious " +
|
|
84
84
|
"right answer, a subtle correctness question you cannot settle by reading, " +
|
|
85
85
|
"a change whose blast radius you are unsure of, or a second opinion before " +
|
|
@@ -87,7 +87,7 @@ export function makeAskAdvisorTool(opts) {
|
|
|
87
87
|
"lookups (use ask_yagni), for anything you can settle by reading the code, " +
|
|
88
88
|
"or to review work you have already finished. Pass a sharp question plus the " +
|
|
89
89
|
"relevant excerpts — never a conversation transcript.",
|
|
90
|
-
promptSnippet: "ask_advisor: escalate one hard judgment call to the peak-tier advisor (
|
|
90
|
+
promptSnippet: "ask_advisor: escalate one hard judgment call to the peak-tier advisor (Advanced sessions, capped).",
|
|
91
91
|
promptGuidelines: [
|
|
92
92
|
"Call ask_advisor only for a genuine judgment fork — an architectural choice, a subtle correctness question, or a second opinion before an approach you would have to unwind. Reading the code is cheaper; do that first.",
|
|
93
93
|
"Ask ONE specific question per consult, and include the excerpts that matter. The advisor reads the repo itself, so point it at the right place rather than pasting everything.",
|
|
@@ -159,7 +159,7 @@ export function makeAskAdvisorTool(opts) {
|
|
|
159
159
|
*/
|
|
160
160
|
export function registerAdviseCommand(pi, tool) {
|
|
161
161
|
pi.registerCommand("advise", {
|
|
162
|
-
description: "Escalate one hard call to the peak-tier advisor (
|
|
162
|
+
description: "Escalate one hard call to the peak-tier advisor (Advanced sessions only, capped per session).",
|
|
163
163
|
handler: async (args, ctx) => {
|
|
164
164
|
const notify = (message, type) => {
|
|
165
165
|
if (ctx.hasUI)
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
+
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import { collectRepoDocs } from "./repoDocs.js";
|
|
3
4
|
import { friendlyFetchError, METERED_POST_FETCH_POLICY, resilientFetch } from "./resilientFetch.js";
|
|
5
|
+
import { markdownOrPlain } from "./subagentRender.js";
|
|
4
6
|
const parameters = Type.Object({
|
|
5
7
|
question: Type.String(),
|
|
6
8
|
context: Type.Optional(Type.String()),
|
|
7
9
|
});
|
|
10
|
+
/** Collapsed answer preview length, in lines. */
|
|
11
|
+
const ANSWER_PREVIEW_LINES = 4;
|
|
12
|
+
/** Collapse whitespace and clip to `max`, appending an ellipsis when cut. */
|
|
13
|
+
function clipLine(text, max) {
|
|
14
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
15
|
+
if (collapsed.length <= max)
|
|
16
|
+
return collapsed;
|
|
17
|
+
return `${collapsed.slice(0, max - 1)}…`;
|
|
18
|
+
}
|
|
19
|
+
function citationCount(n) {
|
|
20
|
+
return n === 1 ? "1 citation" : `${n} citations`;
|
|
21
|
+
}
|
|
8
22
|
/**
|
|
9
23
|
* Build the `ask_yagni` tool definition.
|
|
10
24
|
*
|
|
@@ -28,6 +42,41 @@ export function makeAskYagniTool(opts) {
|
|
|
28
42
|
"When you use an answer, quote or reference its citations so the user can verify the source.",
|
|
29
43
|
],
|
|
30
44
|
parameters,
|
|
45
|
+
renderCall(args, theme) {
|
|
46
|
+
const t = theme;
|
|
47
|
+
let text = `${t.fg("toolTitle", t.bold("ask_yagni"))} ${t.fg("dim", clipLine(args?.question ?? "…", 100))}`;
|
|
48
|
+
if (args?.context)
|
|
49
|
+
text += t.fg("muted", " (+context)");
|
|
50
|
+
return new Text(text, 0, 0);
|
|
51
|
+
},
|
|
52
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
53
|
+
const t = theme;
|
|
54
|
+
const answer = result.content.find((c) => c.type === "text")?.text ?? "";
|
|
55
|
+
const citations = result.details?.citations ?? [];
|
|
56
|
+
if (isPartial)
|
|
57
|
+
return new Text(t.fg("muted", answer || "Asking YAGNI…"), 0, 0);
|
|
58
|
+
if (expanded) {
|
|
59
|
+
const container = new Container();
|
|
60
|
+
container.addChild(markdownOrPlain(answer || "(no answer)", t));
|
|
61
|
+
if (citations.length > 0) {
|
|
62
|
+
container.addChild(new Spacer(1));
|
|
63
|
+
for (const c of citations) {
|
|
64
|
+
container.addChild(new Text(` ${t.fg("muted", "•")} ${t.fg("accent", c.title)} ${t.fg("dim", c.url)}`, 0, 0));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return container;
|
|
68
|
+
}
|
|
69
|
+
const lines = answer.trim().split("\n");
|
|
70
|
+
const out = lines.slice(0, ANSWER_PREVIEW_LINES).map((l) => t.fg("toolOutput", l));
|
|
71
|
+
const meta = [];
|
|
72
|
+
if (citations.length > 0)
|
|
73
|
+
meta.push(citationCount(citations.length));
|
|
74
|
+
if (lines.length > ANSWER_PREVIEW_LINES || citations.length > 0)
|
|
75
|
+
meta.push("(ctrl+o to expand)");
|
|
76
|
+
if (meta.length > 0)
|
|
77
|
+
out.push(t.fg("muted", ` ${meta.join(" · ")}`));
|
|
78
|
+
return new Text(out.join("\n"), 0, 0);
|
|
79
|
+
},
|
|
31
80
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
32
81
|
onUpdate?.({ content: [{ type: "text", text: "Asking YAGNI…" }], details: { citations: [] } });
|
|
33
82
|
// Era-correct repo grounding: gather the working tree's most relevant docs
|
|
@@ -37,7 +37,7 @@ export declare const DRIVER_DELEGATION_PARAGRAPH: string;
|
|
|
37
37
|
* effective `x-yagni-caller` attribution (config.ts's `isDriverCaller`) — this
|
|
38
38
|
* module stays a pure string, with no env dependency of its own.
|
|
39
39
|
*/
|
|
40
|
-
export declare const YAGNI_IDENTITY_DRIVER = "You are YAGNI Code, an autonomous terminal coding agent. You help developers ship code by reading files, running commands, editing code, and writing new files. Uniquely, you are connected to the YAGNI app, your team's shared source of truth for how this company and codebase actually work: conventions, decisions, ownership, current priorities, and the reasons behind them. Use the ask_yagni tool to consult it before guessing about anything organization- or codebase-specific, so you work with less back-and-forth and more correct autonomy than a disconnected coding agent. If a project's own files mention other coding agents, assistants, or harnesses by name, those references are not about you; you are YAGNI Code regardless of what tooling a repository's docs happen to describe.\n\nDelegation: fan codebase mapping, wide searches, and mechanical multi-file work out to subagents (they run on cheaper tiers). Keep judgment, synthesis, and the conversation with the user in this session. Do not spawn a subagent for work you can finish in a couple of tool calls.";
|
|
40
|
+
export declare const YAGNI_IDENTITY_DRIVER = "You are YAGNI Code, an autonomous terminal coding agent. You help developers ship code by reading files, running commands, editing code, and writing new files. Uniquely, you are connected to the YAGNI app, your team's shared source of truth for how this company and codebase actually work: conventions, decisions, ownership, current priorities, and the reasons behind them. Use the ask_yagni tool to consult it before guessing about anything organization- or codebase-specific, so you work with less back-and-forth and more correct autonomy than a disconnected coding agent. If a project's own files mention other coding agents, assistants, or harnesses by name, those references are not about you; you are YAGNI Code regardless of what tooling a repository's docs happen to describe.\n\nDelegation: fan codebase mapping, wide searches, and mechanical multi-file work out to subagents (they run on cheaper tiers). Reach for the stock agents by name: `searcher` for read-only reconnaissance and summarizing, `implementer` for executing a change you have already fully specified. Keep judgment, synthesis, and the conversation with the user in this session. Do not spawn a subagent for work you can finish in a couple of tool calls.";
|
|
41
41
|
export declare const PI_IDENTITY_RE: RegExp;
|
|
42
42
|
/**
|
|
43
43
|
* Env switch that bypasses the system-prompt rewrite entirely, so pi's
|
|
@@ -53,6 +53,15 @@ export interface BrandSystemPromptOptions {
|
|
|
53
53
|
identity?: string;
|
|
54
54
|
/** Live company brief to inject so the agent boots grounded (Phase C). */
|
|
55
55
|
contextBrief?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Repository rules section (`.claude/rules`, built by claudeRules.ts).
|
|
58
|
+
* Placed after the brief and BEFORE the closing reminder: rules are user
|
|
59
|
+
* content that may legitimately name other coding agents — exactly the text
|
|
60
|
+
* the reminder inoculates against — so the reminder must stay the most
|
|
61
|
+
* recent instruction. Appended after the scrub step, so rules content is
|
|
62
|
+
* never brand-rewritten (same exemption as <project_context>).
|
|
63
|
+
*/
|
|
64
|
+
rulesSection?: string | null;
|
|
56
65
|
}
|
|
57
66
|
/**
|
|
58
67
|
* Rebrand pi's assembled system prompt as YAGNI Code's, and optionally inject a
|
|
@@ -61,7 +70,9 @@ export interface BrandSystemPromptOptions {
|
|
|
61
70
|
* Steps: (1) drop pi's self-referential documentation block, (2) replace the
|
|
62
71
|
* identity sentence (with a loose fallback, else prepend), (3) scrub residual
|
|
63
72
|
* brand tokens outside user content, (4) append the company brief + a proactive
|
|
64
|
-
* ask_yagni directive
|
|
73
|
+
* ask_yagni directive, (5) append the repository-rules section verbatim, (6)
|
|
74
|
+
* close with the identity reminder, which must stay last (recency). Idempotent:
|
|
75
|
+
* re-branding keeps a single identity, brief, rules section, and reminder.
|
|
65
76
|
*/
|
|
66
77
|
export declare function brandSystemPrompt(original: string, opts?: BrandSystemPromptOptions): string;
|
|
67
78
|
/** The minimal theme surface the masthead needs (a subset of pi's Theme). */
|
|
@@ -69,11 +80,21 @@ export interface MastheadTheme {
|
|
|
69
80
|
bold(s: string): string;
|
|
70
81
|
fg(color: string, s: string): string;
|
|
71
82
|
}
|
|
83
|
+
export interface MastheadOptions {
|
|
84
|
+
/** CLI version (e.g. "0.2.1") shown next to the title; omitted when absent. */
|
|
85
|
+
version?: string;
|
|
86
|
+
/** Working directory shown on the third line (already home-collapsed). */
|
|
87
|
+
cwd?: string;
|
|
88
|
+
}
|
|
72
89
|
/**
|
|
73
90
|
* Build the YAGNI Code startup masthead string, rendered into a header that
|
|
74
91
|
* REPLACES pi's built-in startup banner (which otherwise shows "pi v<version>"
|
|
75
92
|
* and a "Pi can explain its own features…" onboarding line). Kept here as a
|
|
76
93
|
* pure string builder so its content is unit-testable without a terminal.
|
|
94
|
+
*
|
|
95
|
+
* Layout: the YAGNI art on the left, and to its right a three-line block —
|
|
96
|
+
* bold white "YAGNI Code" + version, the tagline, and the cwd. Art rows are
|
|
97
|
+
* padded to a uniform width so the right column stays straight.
|
|
77
98
|
*/
|
|
78
|
-
export declare function buildMastheadString(theme: MastheadTheme): string;
|
|
99
|
+
export declare function buildMastheadString(theme: MastheadTheme, opts?: MastheadOptions): string;
|
|
79
100
|
//# sourceMappingURL=branding.d.ts.map
|
|
@@ -40,9 +40,12 @@ export const YAGNI_IDENTITY = "You are YAGNI Code, an autonomous terminal coding
|
|
|
40
40
|
* exist.
|
|
41
41
|
*/
|
|
42
42
|
export const DRIVER_DELEGATION_PARAGRAPH = "Delegation: fan codebase mapping, wide searches, and mechanical multi-file " +
|
|
43
|
-
"work out to subagents (they run on cheaper tiers).
|
|
44
|
-
"
|
|
45
|
-
"for
|
|
43
|
+
"work out to subagents (they run on cheaper tiers). Reach for the stock " +
|
|
44
|
+
"agents by name: `searcher` for read-only reconnaissance and summarizing, " +
|
|
45
|
+
"`implementer` for executing a change you have already fully specified. " +
|
|
46
|
+
"Keep judgment, synthesis, and the conversation with the user in this " +
|
|
47
|
+
"session. Do not spawn a subagent for work you can finish in a couple of " +
|
|
48
|
+
"tool calls.";
|
|
46
49
|
/**
|
|
47
50
|
* The identity used for the interactive DRIVER session ONLY: {@link
|
|
48
51
|
* YAGNI_IDENTITY} plus {@link DRIVER_DELEGATION_PARAGRAPH}. The caller (index.ts)
|
|
@@ -51,7 +54,7 @@ export const DRIVER_DELEGATION_PARAGRAPH = "Delegation: fan codebase mapping, wi
|
|
|
51
54
|
* module stays a pure string, with no env dependency of its own.
|
|
52
55
|
*/
|
|
53
56
|
export const YAGNI_IDENTITY_DRIVER = `${YAGNI_IDENTITY}\n\n${DRIVER_DELEGATION_PARAGRAPH}`;
|
|
54
|
-
// pi 0.
|
|
57
|
+
// pi 0.84.1's exact identity sentence (dist/core/system-prompt.js). Exported as
|
|
55
58
|
// the identity anchor the CLI's pi-contract tripwire test reads back from pi's
|
|
56
59
|
// built system prompt, so a pi bump that reworded the opener (silently defeating
|
|
57
60
|
// our rebrand) fails CI loudly. Export-only: no behavior change here.
|
|
@@ -100,7 +103,9 @@ export function brandingDisabled(env) {
|
|
|
100
103
|
* Steps: (1) drop pi's self-referential documentation block, (2) replace the
|
|
101
104
|
* identity sentence (with a loose fallback, else prepend), (3) scrub residual
|
|
102
105
|
* brand tokens outside user content, (4) append the company brief + a proactive
|
|
103
|
-
* ask_yagni directive
|
|
106
|
+
* ask_yagni directive, (5) append the repository-rules section verbatim, (6)
|
|
107
|
+
* close with the identity reminder, which must stay last (recency). Idempotent:
|
|
108
|
+
* re-branding keeps a single identity, brief, rules section, and reminder.
|
|
104
109
|
*/
|
|
105
110
|
export function brandSystemPrompt(original, opts = {}) {
|
|
106
111
|
const identity = opts.identity ?? YAGNI_IDENTITY;
|
|
@@ -130,7 +135,18 @@ export function brandSystemPrompt(original, opts = {}) {
|
|
|
130
135
|
"your priors. When you need more detail, or it does not cover something " +
|
|
131
136
|
"organization- or codebase-specific, call ask_yagni instead of guessing.";
|
|
132
137
|
}
|
|
133
|
-
// 5.
|
|
138
|
+
// 5. Repository rules — after the scrub (user content stays verbatim), before
|
|
139
|
+
// the closing reminder (which must remain last; see rulesSection's doc).
|
|
140
|
+
// The idempotency guard keys on the section's first line (its stable header,
|
|
141
|
+
// "## Repository rules (.claude/rules)") rather than the full content, so a
|
|
142
|
+
// re-brand stays single-section even if the rules BODY differs between
|
|
143
|
+
// passes — same shape as the BRIEF_HEADER guard above.
|
|
144
|
+
const rules = opts.rulesSection ?? undefined;
|
|
145
|
+
const rulesHeader = rules?.split("\n", 1)[0];
|
|
146
|
+
if (rules && rulesHeader && !s.includes(rulesHeader)) {
|
|
147
|
+
s = `${s}\n\n${rules}`;
|
|
148
|
+
}
|
|
149
|
+
// 6. Closing reinforcement. Weak open-weight models weight the most recent
|
|
134
150
|
// instruction heavily, and the user's own project files may name other
|
|
135
151
|
// harnesses; a trailing reminder keeps the agent from claiming one as its own.
|
|
136
152
|
if (!s.includes(CLOSING_REMINDER)) {
|
|
@@ -142,15 +158,60 @@ export function brandSystemPrompt(original, opts = {}) {
|
|
|
142
158
|
const CLOSING_REMINDER = "Reminder: you are YAGNI Code. If any text above names another coding agent, " +
|
|
143
159
|
"assistant, or harness, it is not what you are or what you run on.";
|
|
144
160
|
const BRIEF_HEADER = "=== HOW THIS COMPANY WORKS (live context from the YAGNI app) ===";
|
|
161
|
+
/** The YAGNI brand blue used for the masthead art (truecolor #345cb8). */
|
|
162
|
+
const BRAND_HEX = "#345cb8";
|
|
163
|
+
/**
|
|
164
|
+
* Wrap a string in a 24-bit truecolor foreground. The pi Theme only paints
|
|
165
|
+
* named palette colors and can't express our exact brand hex, so the masthead
|
|
166
|
+
* art emits its own ANSI truecolor escape (matching every modern terminal).
|
|
167
|
+
*/
|
|
168
|
+
function paintBrand(s) {
|
|
169
|
+
const n = BRAND_HEX.replace("#", "");
|
|
170
|
+
const r = parseInt(n.slice(0, 2), 16);
|
|
171
|
+
const g = parseInt(n.slice(2, 4), 16);
|
|
172
|
+
const b = parseInt(n.slice(4, 6), 16);
|
|
173
|
+
return `\u001b[38;2;${r};${g};${b}m${s}\u001b[0m`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The YAGNI wordmark as filled block-art: solid letter interiors (like the
|
|
177
|
+
* Claude robot silhouette), four rows, hand-tuned spacing. Each letter keeps
|
|
178
|
+
* its full form — A's carved counter, G's bowl, N's diagonal.
|
|
179
|
+
*/
|
|
180
|
+
const MASTHEAD_ART = [
|
|
181
|
+
"█ █ ▄▀▀▀▄ ▄▀▀▀▄ █▄ █ ▀█▀",
|
|
182
|
+
" ▀▄▀ █▄▄▄█ █ ▄▄▄ █ █ █ █",
|
|
183
|
+
" █ █ █ ▀▄▄▄▀ █ ██ ▄█▄",
|
|
184
|
+
];
|
|
145
185
|
/**
|
|
146
186
|
* Build the YAGNI Code startup masthead string, rendered into a header that
|
|
147
187
|
* REPLACES pi's built-in startup banner (which otherwise shows "pi v<version>"
|
|
148
188
|
* and a "Pi can explain its own features…" onboarding line). Kept here as a
|
|
149
189
|
* pure string builder so its content is unit-testable without a terminal.
|
|
190
|
+
*
|
|
191
|
+
* Layout: the YAGNI art on the left, and to its right a three-line block —
|
|
192
|
+
* bold white "YAGNI Code" + version, the tagline, and the cwd. Art rows are
|
|
193
|
+
* padded to a uniform width so the right column stays straight.
|
|
150
194
|
*/
|
|
151
|
-
export function buildMastheadString(theme) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
195
|
+
export function buildMastheadString(theme, opts = {}) {
|
|
196
|
+
const title = theme.bold(theme.fg("text", "YAGNI Code"));
|
|
197
|
+
const version = opts.version?.trim();
|
|
198
|
+
const line1 = version ? `${title} ${theme.fg("dim", `v${version}`)}` : title;
|
|
199
|
+
const right = [
|
|
200
|
+
line1,
|
|
201
|
+
theme.fg("dim", "a terminal coding agent that knows your company"),
|
|
202
|
+
...(opts.cwd ? [theme.fg("dim", opts.cwd)] : []),
|
|
203
|
+
];
|
|
204
|
+
const artW = Math.max(...MASTHEAD_ART.map((l) => [...l].length));
|
|
205
|
+
const gap = " ";
|
|
206
|
+
const rows = Math.max(MASTHEAD_ART.length, right.length);
|
|
207
|
+
const lines = [];
|
|
208
|
+
for (let i = 0; i < rows; i++) {
|
|
209
|
+
const rawArt = MASTHEAD_ART[i] ?? "";
|
|
210
|
+
const pad = " ".repeat(Math.max(0, artW - [...rawArt].length));
|
|
211
|
+
const left = rawArt ? paintBrand(rawArt) + pad : " ".repeat(artW);
|
|
212
|
+
const r = right[i] ?? "";
|
|
213
|
+
lines.push(r ? `${left}${gap}${r}` : left);
|
|
214
|
+
}
|
|
215
|
+
return lines.join("\n");
|
|
155
216
|
}
|
|
156
217
|
//# sourceMappingURL=branding.js.map
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* literal text is the source of truth, so undo/cursor/history and the
|
|
18
18
|
* chip→model mapping all share one string and can't desync.
|
|
19
19
|
*
|
|
20
|
-
* Validated against pi 0.
|
|
20
|
+
* Validated against pi 0.84.1 (spike, 5/5): the subclass owns `onPasteImage`
|
|
21
21
|
* because `setCustomEditorComponent` only fills it when unset, and jiti aliases
|
|
22
22
|
* the extension's pi imports to the same module instance pi uses.
|
|
23
23
|
*/
|
|
@@ -48,21 +48,33 @@ export interface ImageAttachment {
|
|
|
48
48
|
*/
|
|
49
49
|
export declare function unwrapBracketedPaste(data: string): string | null;
|
|
50
50
|
/**
|
|
51
|
-
* Recognize a pasted
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
51
|
+
* Recognize a pasted payload that is entirely image-file paths and decode
|
|
52
|
+
* them. This covers Finder/desktop drag-drop and Ghostty/cmux's Cmd+V
|
|
53
|
+
* (which writes the clipboard image to a `clipboard-*.png` temp file and
|
|
54
|
+
* pastes its path) — Claude Code parity: dragging a screenshot in becomes
|
|
55
|
+
* `[Image #N]`, not a literal path.
|
|
56
|
+
*
|
|
57
|
+
* Guardrails against converting something the user meant as text: the WHOLE
|
|
58
|
+
* paste must be path tokens, every token must be an absolute path to a real
|
|
59
|
+
* file with an image extension AND genuine image magic bytes, and the
|
|
60
|
+
* conversion is visible — each image becomes a chip in the prompt, so nothing
|
|
61
|
+
* is ever attached silently. Returns null when the paste is not such a list
|
|
62
|
+
* (caller passes it through as ordinary text).
|
|
57
63
|
*/
|
|
58
|
-
export declare function
|
|
64
|
+
export declare function readPastedImagePaths(pastedText: string): ClipboardImage[] | null;
|
|
59
65
|
/**
|
|
60
66
|
* Read an image from the system clipboard without forking pi. Best-effort and
|
|
61
|
-
* cross-platform
|
|
67
|
+
* cross-platform using ONLY tools the OS ships with: osascript (macOS),
|
|
62
68
|
* wl-paste (Wayland), xclip (X11), PowerShell (Windows/WSL). Returns null when
|
|
63
69
|
* the clipboard holds no image (caller then pastes text instead).
|
|
64
70
|
*/
|
|
65
71
|
export declare const defaultClipboardImageReader: ClipboardImageReader;
|
|
72
|
+
/**
|
|
73
|
+
* Parse osascript's clipboard dump — `«data PNGf89504E47…»` — into raw bytes.
|
|
74
|
+
* The 4-char tag after `«data ` is the pasteboard flavor; the hex that follows
|
|
75
|
+
* is the image. Returns null when the output holds no such dump.
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseOsascriptImageHex(out: string): Uint8Array | null;
|
|
66
78
|
/**
|
|
67
79
|
* A CustomEditor that turns clipboard image paste into numbered `[Image #N]`
|
|
68
80
|
* chips. The image bytes live here, keyed by chip number; the editor text holds
|
|
@@ -72,6 +84,15 @@ export declare class ChipEditor extends CustomEditor {
|
|
|
72
84
|
private stashed;
|
|
73
85
|
private chipCounter;
|
|
74
86
|
private readImage;
|
|
87
|
+
/**
|
|
88
|
+
* Fired when Ctrl+C lands on an EMPTY prompt. pi's own binding is
|
|
89
|
+
* clear-editor (Esc is the interrupt key), but with nothing typed that is a
|
|
90
|
+
* no-op, and every peer harness (Claude Code, Codex, Gemini, Aider) treats a
|
|
91
|
+
* single Ctrl+C as "stop what's happening". The hook lets the extension wire
|
|
92
|
+
* exactly that; the key still falls through to pi afterwards, so clear and
|
|
93
|
+
* the 500ms double-press exit window are untouched.
|
|
94
|
+
*/
|
|
95
|
+
onEmptyCtrlC?: () => void;
|
|
75
96
|
constructor(tui: TUI, theme: EditorTheme, keybindings: ConstructorParameters<typeof CustomEditor>[2], readImage?: ClipboardImageReader, options?: EditorOptions);
|
|
76
97
|
/**
|
|
77
98
|
* Paste-image must fire on Cmd+V too (Claude Code parity on macOS). Claude
|