@yagni-app/code-staging 0.2.1-staging.1038.1 → 0.2.1-staging.1039.1
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 +21 -0
- package/dist/cli.js +74 -1
- package/dist/extension/footer.d.ts +9 -1
- package/dist/extension/footer.js +23 -5
- package/dist/launch.js +5 -0
- package/dist/padding.d.ts +22 -0
- package/dist/padding.js +25 -0
- package/package.json +2 -2
package/dist/cli.d.ts
CHANGED
|
@@ -12,6 +12,27 @@
|
|
|
12
12
|
* plus a configured pi spawn. Everything that makes this "YAGNI Code" lives in
|
|
13
13
|
* pi-extension-yagni and the YAGNI backend.
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* Seed `editorPaddingX` into the per-profile pi `settings.json` so the prompt
|
|
17
|
+
* input is padded to match the chat/output area (`outputPad`) and the status
|
|
18
|
+
* bar. Fills the key in only when absent — an explicit user choice (including
|
|
19
|
+
* 0) is never overwritten. Best-effort: a missing/corrupt settings file or a
|
|
20
|
+
* write failure must never block the launch.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Seed `editorPaddingX` into the per-profile pi `settings.json` so the prompt
|
|
24
|
+
* input aligns with the chat/output area (`outputPad`) and the status bar.
|
|
25
|
+
*
|
|
26
|
+
* This writes to a user-owned file, so it is deliberately conservative:
|
|
27
|
+
* - fills the key in ONLY when absent — an explicit user choice (including 0)
|
|
28
|
+
* is never overwritten;
|
|
29
|
+
* - backs off on a missing-dir, corrupt, non-object, or symlinked settings
|
|
30
|
+
* file rather than risk clobbering anything;
|
|
31
|
+
* - writes ATOMICALLY (temp file + rename) and preserves the existing file's
|
|
32
|
+
* permissions, so a crash mid-write can never truncate the user's settings.
|
|
33
|
+
* Any failure is swallowed: seeding must never block or break a launch.
|
|
34
|
+
*/
|
|
35
|
+
export declare function seedEditorPadding(piAgentDir: string): void;
|
|
15
36
|
export declare const HELP_TEXT: string;
|
|
16
37
|
/** Parse `use <name> [--base-url <url>]` argv into its parts. */
|
|
17
38
|
export declare function parseUseArgs(args: string[]): {
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* pi-extension-yagni and the YAGNI backend.
|
|
14
14
|
*/
|
|
15
15
|
import { spawn } from "node:child_process";
|
|
16
|
-
import { mkdirSync, realpathSync } from "node:fs";
|
|
16
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
17
18
|
import { createInterface } from "node:readline/promises";
|
|
18
19
|
import { fileURLToPath } from "node:url";
|
|
19
20
|
import { PI_CONFIG_NAME } from "./branding.js";
|
|
@@ -27,6 +28,7 @@ import { runDoctor } from "./doctor.js";
|
|
|
27
28
|
import { installProcessCrashHandlers } from "./crashReport.js";
|
|
28
29
|
import { currentCliVersion, maybeNudgeAndRefresh, upgradeCommand } from "./upgrade.js";
|
|
29
30
|
import { maybeRefreshAtLaunch } from "./refresh.js";
|
|
31
|
+
import { PAD_X } from "./padding.js";
|
|
30
32
|
import { ensureShadowPiPackage } from "./piPackage.js";
|
|
31
33
|
import { resolveExtensionPath, resolvePiCliPath, resolvePiPackageDir } from "./paths.js";
|
|
32
34
|
import { credentialsFromProfile, getActiveProfileName, listProfiles, migrateLegacyCredentials, persistProfileTokenRotation, profilePath, readActiveProfile, useProfile, } from "./profiles.js";
|
|
@@ -43,6 +45,72 @@ async function confirmOnTty(question) {
|
|
|
43
45
|
rl.close();
|
|
44
46
|
}
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Seed `editorPaddingX` into the per-profile pi `settings.json` so the prompt
|
|
50
|
+
* input is padded to match the chat/output area (`outputPad`) and the status
|
|
51
|
+
* bar. Fills the key in only when absent — an explicit user choice (including
|
|
52
|
+
* 0) is never overwritten. Best-effort: a missing/corrupt settings file or a
|
|
53
|
+
* write failure must never block the launch.
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Seed `editorPaddingX` into the per-profile pi `settings.json` so the prompt
|
|
57
|
+
* input aligns with the chat/output area (`outputPad`) and the status bar.
|
|
58
|
+
*
|
|
59
|
+
* This writes to a user-owned file, so it is deliberately conservative:
|
|
60
|
+
* - fills the key in ONLY when absent — an explicit user choice (including 0)
|
|
61
|
+
* is never overwritten;
|
|
62
|
+
* - backs off on a missing-dir, corrupt, non-object, or symlinked settings
|
|
63
|
+
* file rather than risk clobbering anything;
|
|
64
|
+
* - writes ATOMICALLY (temp file + rename) and preserves the existing file's
|
|
65
|
+
* permissions, so a crash mid-write can never truncate the user's settings.
|
|
66
|
+
* Any failure is swallowed: seeding must never block or break a launch.
|
|
67
|
+
*/
|
|
68
|
+
export function seedEditorPadding(piAgentDir) {
|
|
69
|
+
try {
|
|
70
|
+
const settingsPath = join(piAgentDir, "settings.json");
|
|
71
|
+
let settings = {};
|
|
72
|
+
let existingMode;
|
|
73
|
+
if (existsSync(settingsPath)) {
|
|
74
|
+
// Refuse to follow a symlink: we must only ever write a regular, real
|
|
75
|
+
// settings file the user (or pi) owns.
|
|
76
|
+
if (lstatSync(settingsPath).isSymbolicLink())
|
|
77
|
+
return;
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return; // corrupt file: back off rather than clobber the user's settings
|
|
84
|
+
}
|
|
85
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
86
|
+
return; // non-object settings: leave it alone
|
|
87
|
+
}
|
|
88
|
+
settings = parsed;
|
|
89
|
+
existingMode = lstatSync(settingsPath).mode & 0o777;
|
|
90
|
+
}
|
|
91
|
+
if (settings.editorPaddingX !== undefined)
|
|
92
|
+
return; // user already chose
|
|
93
|
+
settings.editorPaddingX = PAD_X;
|
|
94
|
+
// Atomic write: serialize to a temp file in the same directory, then
|
|
95
|
+
// rename over the target. A crash leaves either the old file or the temp
|
|
96
|
+
// file, never a half-written settings.json.
|
|
97
|
+
const tmpPath = join(piAgentDir, `.settings.json.yagni-${process.pid}-${Date.now()}.tmp`);
|
|
98
|
+
try {
|
|
99
|
+
writeFileSync(tmpPath, `${JSON.stringify(settings, null, 2)}\n`, { mode: 0o600 });
|
|
100
|
+
renameSync(tmpPath, settingsPath);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
rmSync(tmpPath, { force: true }); // no-op once the rename succeeded
|
|
104
|
+
}
|
|
105
|
+
// Restore the original permissions if the file already had them (rename
|
|
106
|
+
// replaces the inode, which would otherwise reset to 0600).
|
|
107
|
+
if (existingMode !== undefined)
|
|
108
|
+
chmodSync(settingsPath, existingMode);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// never block launch on a settings-seed failure
|
|
112
|
+
}
|
|
113
|
+
}
|
|
46
114
|
async function runDefault(passthroughArgs) {
|
|
47
115
|
// Cache-backed update nudge (never a network wait), then a background cache
|
|
48
116
|
// refresh that completes while the session runs. Both fail soft.
|
|
@@ -72,6 +140,11 @@ async function runDefault(passthroughArgs) {
|
|
|
72
140
|
// instead of ~/.pi/agent, and prod state never bleeds into staging.
|
|
73
141
|
const piAgentDir = agentDir(profile.name);
|
|
74
142
|
mkdirSync(piAgentDir, { recursive: true, mode: 0o700 });
|
|
143
|
+
// Seed the editor's horizontal padding so the prompt input aligns with the
|
|
144
|
+
// chat/output area and the status bar. Only fills in when the user has not
|
|
145
|
+
// set it themselves — an explicit choice is never clobbered. Best-effort: a
|
|
146
|
+
// corrupt or unreadable settings.json must never block the launch.
|
|
147
|
+
seedEditorPadding(piAgentDir);
|
|
75
148
|
// Generate the shadow pi package so the terminal title/process name read
|
|
76
149
|
// "YAGNI Code" instead of "pi"/"π". Best-effort: if it can't be built we
|
|
77
150
|
// still launch (un-rebranded but hermetic), never blocking the agent.
|
|
@@ -40,6 +40,14 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import type { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
42
42
|
export declare const BRANCH_MAX_WIDTH = 60;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the status bar's left pad from the launcher's `YAGNI_PAD_X` env so it
|
|
45
|
+
* aligns with the editor input and the chat/output area on one shared column.
|
|
46
|
+
* The launcher seeds the same value as pi's `editorPaddingX`; this footer can't
|
|
47
|
+
* read pi settings, so the value crosses over env. Falls back to the output
|
|
48
|
+
* area's default (1) when unset/invalid. Clamped to pi's 0–3 editor range.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveFooterPadX(raw?: string | undefined): number;
|
|
43
51
|
/** Format token counts for compact footer display (mirrors pi's formatTokens). */
|
|
44
52
|
export declare function formatTokens(count: number): string;
|
|
45
53
|
/** Shorten cwd relative to home, like pi's built-in footer. */
|
|
@@ -85,7 +93,7 @@ export declare function renderFooterLines(input: {
|
|
|
85
93
|
usage: UsageTotals;
|
|
86
94
|
contextPercent: number | null;
|
|
87
95
|
statuses: string[];
|
|
88
|
-
}, theme: Pick<Theme, "fg">, width: number): string[];
|
|
96
|
+
}, theme: Pick<Theme, "fg">, width: number, padX?: number): string[];
|
|
89
97
|
/**
|
|
90
98
|
* Create a footer factory that captures the session `ctx` (for session data)
|
|
91
99
|
* and returns the component `setFooter` expects. Called from the
|
package/dist/extension/footer.js
CHANGED
|
@@ -46,6 +46,19 @@ export const BRANCH_MAX_WIDTH = 60;
|
|
|
46
46
|
const WORKTREE_MAX_WIDTH = 30;
|
|
47
47
|
/** Section separator: single space + middle dot + single space. */
|
|
48
48
|
const SEP = " · ";
|
|
49
|
+
/** Default horizontal pad when the launcher didn't forward one (matches outputPad=1). */
|
|
50
|
+
const DEFAULT_PAD_X = 1;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the status bar's left pad from the launcher's `YAGNI_PAD_X` env so it
|
|
53
|
+
* aligns with the editor input and the chat/output area on one shared column.
|
|
54
|
+
* The launcher seeds the same value as pi's `editorPaddingX`; this footer can't
|
|
55
|
+
* read pi settings, so the value crosses over env. Falls back to the output
|
|
56
|
+
* area's default (1) when unset/invalid. Clamped to pi's 0–3 editor range.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveFooterPadX(raw = process.env.YAGNI_PAD_X) {
|
|
59
|
+
const n = raw === undefined ? NaN : Number.parseInt(raw, 10);
|
|
60
|
+
return Number.isFinite(n) && n >= 0 && n <= 3 ? n : DEFAULT_PAD_X;
|
|
61
|
+
}
|
|
49
62
|
/** Format token counts for compact footer display (mirrors pi's formatTokens). */
|
|
50
63
|
export function formatTokens(count) {
|
|
51
64
|
if (count < 1000)
|
|
@@ -191,9 +204,14 @@ function contextColor(percent) {
|
|
|
191
204
|
return "dim";
|
|
192
205
|
}
|
|
193
206
|
/** Pure line-builder, exported for tests. All data injected; colors via theme. */
|
|
194
|
-
export function renderFooterLines(input, theme, width) {
|
|
207
|
+
export function renderFooterLines(input, theme, width, padX = 0) {
|
|
195
208
|
const dim = (s) => theme.fg("dim", s);
|
|
196
209
|
const sep = dim(SEP);
|
|
210
|
+
// Reserve the left pad so the status bar's text starts on the same column as
|
|
211
|
+
// the (padded) editor input and the chat/output area, instead of hugging the
|
|
212
|
+
// terminal edge. Truncation runs against the reduced content width.
|
|
213
|
+
const pad = " ".repeat(Math.max(0, Math.min(3, Math.floor(padX))));
|
|
214
|
+
const contentWidth = Math.max(1, width - pad.length);
|
|
197
215
|
// Line 1: folder · [worktree] · branch
|
|
198
216
|
const line1Parts = [theme.fg("accent", input.git.folder)];
|
|
199
217
|
if (input.git.inRepo) {
|
|
@@ -202,7 +220,7 @@ export function renderFooterLines(input, theme, width) {
|
|
|
202
220
|
if (input.git.branch)
|
|
203
221
|
line1Parts.push(theme.fg("border", truncateEnd(input.git.branch, BRANCH_MAX_WIDTH)));
|
|
204
222
|
}
|
|
205
|
-
const line1 = truncateToWidth(line1Parts.join(sep),
|
|
223
|
+
const line1 = pad + truncateToWidth(line1Parts.join(sep), contentWidth, dim("…"));
|
|
206
224
|
// Line 2: model · ↑in ↓out $cost · ctx%
|
|
207
225
|
const statParts = [];
|
|
208
226
|
if (input.usage.input)
|
|
@@ -217,12 +235,12 @@ export function renderFooterLines(input, theme, width) {
|
|
|
217
235
|
if (stats)
|
|
218
236
|
line2Parts.push(dim(stats));
|
|
219
237
|
line2Parts.push(theme.fg(contextColor(input.contextPercent), percentText));
|
|
220
|
-
const line2 = truncateToWidth(line2Parts.join(sep),
|
|
238
|
+
const line2 = pad + truncateToWidth(line2Parts.join(sep), contentWidth, dim("…"));
|
|
221
239
|
// Line 3: extension statuses (branding, todo counter, mode), joined by " · ".
|
|
222
240
|
const statuses = input.statuses.map((s) => s.replace(/[\r\n\t]/g, " ").trim()).filter(Boolean);
|
|
223
241
|
const lines = [line1, line2];
|
|
224
242
|
if (statuses.length > 0) {
|
|
225
|
-
lines.push(truncateToWidth(dim(statuses.join(SEP)),
|
|
243
|
+
lines.push(pad + truncateToWidth(dim(statuses.join(SEP)), contentWidth, dim("…")));
|
|
226
244
|
}
|
|
227
245
|
return lines;
|
|
228
246
|
}
|
|
@@ -258,7 +276,7 @@ export function createYagniFooterFactory(ctx) {
|
|
|
258
276
|
usage: collectUsage(ctx.sessionManager),
|
|
259
277
|
contextPercent: ctx.getContextUsage()?.percent ?? null,
|
|
260
278
|
statuses,
|
|
261
|
-
}, theme, width);
|
|
279
|
+
}, theme, width, resolveFooterPadX());
|
|
262
280
|
},
|
|
263
281
|
invalidate() {
|
|
264
282
|
gitCache = undefined;
|
package/dist/launch.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { agentDirEnvVar } from "./branding.js";
|
|
10
|
+
import { PAD_X_ENV, resolvePadX } from "./padding.js";
|
|
10
11
|
/**
|
|
11
12
|
* How close to expiry the token can be before launch warns. A coding session
|
|
12
13
|
* easily outlives a token, so warn early enough that re-logging in before a long
|
|
@@ -95,6 +96,10 @@ export function buildLaunch(creds, passthroughArgs, opts) {
|
|
|
95
96
|
// Match the e2b harness defaults: skip pi's update check + telemetry.
|
|
96
97
|
PI_SKIP_VERSION_CHECK: "1",
|
|
97
98
|
PI_TELEMETRY: "0",
|
|
99
|
+
// Forward the chosen horizontal pad so the extension's footer aligns with
|
|
100
|
+
// the editor (which the launcher pads by seeding editorPaddingX). The
|
|
101
|
+
// footer can't read pi's settings, so the value crosses over env.
|
|
102
|
+
[PAD_X_ENV]: String(resolvePadX()),
|
|
98
103
|
};
|
|
99
104
|
// Always load our extension. Default the provider to `yagni` unless the user
|
|
100
105
|
// explicitly chose one (so power users can still point pi elsewhere).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the CLI's horizontal padding, shared by the
|
|
3
|
+
* launcher (which seeds the editor setting) and — via the `YAGNI_PAD_X` env the
|
|
4
|
+
* launcher forwards — pi-extension-yagni's footer.
|
|
5
|
+
*
|
|
6
|
+
* The value must match pi's `outputPad` (the chat/output area's left+right
|
|
7
|
+
* padding, which defaults to 1) so the input editor, the output stream, and
|
|
8
|
+
* the status bar all align on the same column. Keeping it in one module makes
|
|
9
|
+
* the coherence structural rather than eyeballed: change this one number and
|
|
10
|
+
* the editor, footer, and seeded default move together.
|
|
11
|
+
*
|
|
12
|
+
* Separators (the ─ rules above/below the input) intentionally stay full-bleed
|
|
13
|
+
* and are NOT padded — matching Claude Code's frame.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PAD_X = 1;
|
|
16
|
+
/** Left (and right) padding string applied to the editor and footer. */
|
|
17
|
+
export declare const PAD_STR: string;
|
|
18
|
+
/** Env var the launcher sets so the extension's footer can match the editor pad. */
|
|
19
|
+
export declare const PAD_X_ENV = "YAGNI_PAD_X";
|
|
20
|
+
/** Parse a pad width from the env override, falling back to {@link PAD_X}. */
|
|
21
|
+
export declare function resolvePadX(raw?: string | undefined): number;
|
|
22
|
+
//# sourceMappingURL=padding.d.ts.map
|
package/dist/padding.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the CLI's horizontal padding, shared by the
|
|
3
|
+
* launcher (which seeds the editor setting) and — via the `YAGNI_PAD_X` env the
|
|
4
|
+
* launcher forwards — pi-extension-yagni's footer.
|
|
5
|
+
*
|
|
6
|
+
* The value must match pi's `outputPad` (the chat/output area's left+right
|
|
7
|
+
* padding, which defaults to 1) so the input editor, the output stream, and
|
|
8
|
+
* the status bar all align on the same column. Keeping it in one module makes
|
|
9
|
+
* the coherence structural rather than eyeballed: change this one number and
|
|
10
|
+
* the editor, footer, and seeded default move together.
|
|
11
|
+
*
|
|
12
|
+
* Separators (the ─ rules above/below the input) intentionally stay full-bleed
|
|
13
|
+
* and are NOT padded — matching Claude Code's frame.
|
|
14
|
+
*/
|
|
15
|
+
export const PAD_X = 1;
|
|
16
|
+
/** Left (and right) padding string applied to the editor and footer. */
|
|
17
|
+
export const PAD_STR = " ".repeat(PAD_X);
|
|
18
|
+
/** Env var the launcher sets so the extension's footer can match the editor pad. */
|
|
19
|
+
export const PAD_X_ENV = "YAGNI_PAD_X";
|
|
20
|
+
/** Parse a pad width from the env override, falling back to {@link PAD_X}. */
|
|
21
|
+
export function resolvePadX(raw = process.env[PAD_X_ENV]) {
|
|
22
|
+
const n = raw === undefined ? NaN : Number.parseInt(raw, 10);
|
|
23
|
+
return Number.isFinite(n) && n >= 0 && n <= 3 ? n : PAD_X;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=padding.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "0.2.1-staging.
|
|
3
|
+
"version": "0.2.1-staging.1039.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"@earendil-works/pi-tui": "0.83.0",
|
|
39
39
|
"typebox": "^1.1.38"
|
|
40
40
|
},
|
|
41
|
-
"yagniSourceSha": "
|
|
41
|
+
"yagniSourceSha": "82c8e819af017a1a2c5c93aea5b4f96690f267d3"
|
|
42
42
|
}
|