@yagni-app/code-staging 1.0.0-staging.1175.1 → 1.0.0-staging.1177.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.js +53 -4
- package/dist/outputFormat.d.ts +83 -0
- package/dist/outputFormat.js +207 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -27,6 +27,7 @@ import { login } from "./login.js";
|
|
|
27
27
|
import { logout } from "./logout.js";
|
|
28
28
|
import { tokenCommand } from "./token.js";
|
|
29
29
|
import { buildLaunch } from "./launch.js";
|
|
30
|
+
import { parseOutputFormat, parseJsonEvents, buildResultObject, readGuardianEvents, } from "./outputFormat.js";
|
|
30
31
|
import { runDoctor } from "./doctor.js";
|
|
31
32
|
import { installProcessCrashHandlers } from "./crashReport.js";
|
|
32
33
|
import { currentCliVersion, maybeNudgeAndRefresh, upgradeCommand } from "./upgrade.js";
|
|
@@ -159,6 +160,10 @@ export function seedHideThinkingBlock(piAgentDir) {
|
|
|
159
160
|
return seedSetting(piAgentDir, "hideThinkingBlock", true);
|
|
160
161
|
}
|
|
161
162
|
async function runDefault(passthroughArgs) {
|
|
163
|
+
// Parse --output-format out of argv before passing to pi (pi doesn't know
|
|
164
|
+
// about it). The format determines how we handle pi's stdout: text = inherit,
|
|
165
|
+
// stream-json = inherit with --mode json, json = pipe + post-process.
|
|
166
|
+
const { format: outputFormat, remainingArgs } = parseOutputFormat(passthroughArgs);
|
|
162
167
|
// Cache-backed update nudge (never a network wait), then a background cache
|
|
163
168
|
// refresh that completes while the session runs. Both fail soft.
|
|
164
169
|
await maybeNudgeAndRefresh({ current: cliVersion() });
|
|
@@ -237,7 +242,7 @@ async function runDefault(passthroughArgs) {
|
|
|
237
242
|
// re-login in another terminal is picked up here with no stale cached token.
|
|
238
243
|
let plan;
|
|
239
244
|
try {
|
|
240
|
-
plan = buildLaunch(creds,
|
|
245
|
+
plan = buildLaunch(creds, remainingArgs, {
|
|
241
246
|
extensionPath: resolveExtensionPath(),
|
|
242
247
|
agentDir: piAgentDir,
|
|
243
248
|
piPackageDir: shadowPiDir,
|
|
@@ -260,18 +265,57 @@ async function runDefault(passthroughArgs) {
|
|
|
260
265
|
process.stderr.write(`${warning}\n`);
|
|
261
266
|
}
|
|
262
267
|
const { env, argv } = plan;
|
|
268
|
+
// For json/stream-json output, inject --mode json so pi emits NDJSON events.
|
|
269
|
+
// Don't override if the user already chose --mode (mirrors userChoseProvider/
|
|
270
|
+
// userChoseModel in buildLaunch). Appended at the end — pi's flag parser
|
|
271
|
+
// handles --mode anywhere in argv.
|
|
272
|
+
const userChoseMode = remainingArgs.some((a) => a === "--mode" || a.startsWith("--mode="));
|
|
273
|
+
const childArgv = outputFormat === "text" || userChoseMode
|
|
274
|
+
? argv
|
|
275
|
+
: [...argv, "--mode", "json"];
|
|
276
|
+
// text + stream-json: inherit stdout (passthrough). json: pipe stdout so we
|
|
277
|
+
// can post-process the NDJSON into a single result object.
|
|
278
|
+
const stdio = outputFormat === "json"
|
|
279
|
+
? ["inherit", "pipe", "inherit"]
|
|
280
|
+
: "inherit";
|
|
263
281
|
const piCli = resolvePiCliPath();
|
|
282
|
+
const startMs = Date.now();
|
|
264
283
|
return await new Promise((resolve) => {
|
|
265
|
-
const child = spawn(process.execPath, [piCli, ...
|
|
266
|
-
stdio:
|
|
284
|
+
const child = spawn(process.execPath, [piCli, ...childArgv], {
|
|
285
|
+
stdio: stdio,
|
|
267
286
|
env,
|
|
268
287
|
});
|
|
288
|
+
// Collect pi's stdout when piping for --output-format json.
|
|
289
|
+
let stdoutChunks = "";
|
|
290
|
+
if (outputFormat === "json" && child.stdout) {
|
|
291
|
+
child.stdout.setEncoding("utf8");
|
|
292
|
+
child.stdout.on("data", (chunk) => {
|
|
293
|
+
stdoutChunks += chunk;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
269
296
|
// Forward termination signals to pi instead of dying around it (see
|
|
270
297
|
// signalForward.ts for the policy: first signal graceful, second tree-kill).
|
|
271
298
|
installSignalForwarding(child);
|
|
272
299
|
// 128+n for a signal death (bash parity), so a cancelled/killed run never
|
|
273
300
|
// reads as success to scripts or CI.
|
|
274
|
-
child.on("exit", (code, signal) =>
|
|
301
|
+
child.on("exit", (code, signal) => {
|
|
302
|
+
const exitCode = exitCodeFor(code, signal);
|
|
303
|
+
if (outputFormat === "json") {
|
|
304
|
+
const durationMs = Date.now() - startMs;
|
|
305
|
+
const events = parseJsonEvents(stdoutChunks);
|
|
306
|
+
const yagniSessionId = env.YAGNI_SESSION_ID ?? "";
|
|
307
|
+
const guardianEvents = readGuardianEvents(yagniSessionId);
|
|
308
|
+
const verbose = remainingArgs.includes("--verbose");
|
|
309
|
+
const result = buildResultObject({
|
|
310
|
+
events,
|
|
311
|
+
guardianEvents,
|
|
312
|
+
durationMs,
|
|
313
|
+
verbose,
|
|
314
|
+
});
|
|
315
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
316
|
+
}
|
|
317
|
+
resolve(exitCode);
|
|
318
|
+
});
|
|
275
319
|
child.on("error", (err) => {
|
|
276
320
|
process.stderr.write(`Failed to start YAGNI Code: ${err.message}\n`);
|
|
277
321
|
resolve(1);
|
|
@@ -286,6 +330,8 @@ export const HELP_TEXT = [
|
|
|
286
330
|
" yagni -c Continue the most recent session.",
|
|
287
331
|
" yagni -r Browse and resume a previous session.",
|
|
288
332
|
' yagni -p "prompt" Print one response and exit (reads piped stdin too).',
|
|
333
|
+
' yagni -p "prompt" Use --output-format json for a machine-readable',
|
|
334
|
+
' --output-format json result object with tools, cost, guardian reviews.',
|
|
289
335
|
" yagni login Authorize the active environment (device-code flow).",
|
|
290
336
|
" yagni logout Revoke and clear the active environment's token.",
|
|
291
337
|
" yagni doctor Check that everything is ready (green/red checklist).",
|
|
@@ -307,6 +353,9 @@ export const HELP_TEXT = [
|
|
|
307
353
|
" --model <tier> Model tier (fixed to advanced).",
|
|
308
354
|
" --thinking <level> off | minimal | low | medium | high | xhigh | max",
|
|
309
355
|
" --session <id> Open a specific session; --fork <id> branches one.",
|
|
356
|
+
" --output-format <fmt> Output format: text (default), json, stream-json.",
|
|
357
|
+
" json emits a single result object; stream-json",
|
|
358
|
+
" emits NDJSON events (same as --mode json).",
|
|
310
359
|
" --mode json Emit machine-readable events (for scripts and CI).",
|
|
311
360
|
"",
|
|
312
361
|
"In a session:",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--output-format` support for headless/print mode (YAG-593).
|
|
3
|
+
*
|
|
4
|
+
* The launcher parses `--output-format` out of argv (pi doesn't know about it),
|
|
5
|
+
* maps it to pi's `--mode json` internally, and post-processes pi's NDJSON
|
|
6
|
+
* event stream into a single JSON result object — mirroring Claude Code's
|
|
7
|
+
* `--output-format json` shape.
|
|
8
|
+
*/
|
|
9
|
+
export type OutputFormat = "text" | "json" | "stream-json";
|
|
10
|
+
/**
|
|
11
|
+
* Strip `--output-format <value>` (or `--output-format=<value>`) from argv.
|
|
12
|
+
* Returns the format and the remaining args (with the flag removed).
|
|
13
|
+
* Unknown values fall back to "text" with a stderr warning.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseOutputFormat(argv: string[], writeErr?: (line: string) => void): {
|
|
16
|
+
format: OutputFormat;
|
|
17
|
+
remainingArgs: string[];
|
|
18
|
+
};
|
|
19
|
+
export interface ToolUsed {
|
|
20
|
+
name: string;
|
|
21
|
+
is_error: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface GuardianReview {
|
|
24
|
+
outcome: string;
|
|
25
|
+
duration_ms?: number;
|
|
26
|
+
tier?: string;
|
|
27
|
+
/** True when the command was blocked (ask in headless or deny). */
|
|
28
|
+
blocked?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface ResultObject {
|
|
31
|
+
type: "result";
|
|
32
|
+
subtype: "success" | "error_during_execution";
|
|
33
|
+
result: string;
|
|
34
|
+
is_error: boolean;
|
|
35
|
+
duration_ms: number;
|
|
36
|
+
num_turns: number;
|
|
37
|
+
session_id: string;
|
|
38
|
+
total_cost_usd: number;
|
|
39
|
+
usage: {
|
|
40
|
+
input_tokens: number;
|
|
41
|
+
output_tokens: number;
|
|
42
|
+
cache_read_tokens: number;
|
|
43
|
+
total_tokens: number;
|
|
44
|
+
};
|
|
45
|
+
tools_used: ToolUsed[];
|
|
46
|
+
guardian_reviews: GuardianReview[];
|
|
47
|
+
stop_reason: string | null;
|
|
48
|
+
}
|
|
49
|
+
interface ParsedEvent {
|
|
50
|
+
type: string;
|
|
51
|
+
[key: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse raw NDJSON text (pi's --mode json stdout) into an array of event objects.
|
|
55
|
+
* Skips blank lines. Fail-soft: unparseable lines are skipped.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseJsonEvents(raw: string): ParsedEvent[];
|
|
58
|
+
/**
|
|
59
|
+
* Build the single JSON result object from collected pi events and Guardian
|
|
60
|
+
* events. Pure — no I/O.
|
|
61
|
+
*
|
|
62
|
+
* @param events NDJSON event objects from pi's --mode json stdout
|
|
63
|
+
* @param guardian Guardian review entries from the error sink
|
|
64
|
+
* @param durationMs Wall-clock time measured by the launcher
|
|
65
|
+
* @param verbose When true, returns the raw events array instead of a single object
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildResultObject(opts: {
|
|
68
|
+
events: ParsedEvent[];
|
|
69
|
+
guardianEvents?: GuardianReview[];
|
|
70
|
+
durationMs: number;
|
|
71
|
+
verbose?: boolean;
|
|
72
|
+
}): ResultObject | ParsedEvent[];
|
|
73
|
+
/**
|
|
74
|
+
* Read Guardian review events from the error sink JSONL, filtered by
|
|
75
|
+
* YAGNI_SESSION_ID. The error sink is written by the extension during the
|
|
76
|
+
* session; this reads it post-run. Fail-soft: returns [] on any error.
|
|
77
|
+
*/
|
|
78
|
+
export declare function readGuardianEvents(yagniSessionId: string, opts?: {
|
|
79
|
+
homeDir?: string;
|
|
80
|
+
now?: Date;
|
|
81
|
+
}): GuardianReview[];
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=outputFormat.d.ts.map
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--output-format` support for headless/print mode (YAG-593).
|
|
3
|
+
*
|
|
4
|
+
* The launcher parses `--output-format` out of argv (pi doesn't know about it),
|
|
5
|
+
* maps it to pi's `--mode json` internally, and post-processes pi's NDJSON
|
|
6
|
+
* event stream into a single JSON result object — mirroring Claude Code's
|
|
7
|
+
* `--output-format json` shape.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { DISTRIBUTION } from "./distribution.js";
|
|
13
|
+
/**
|
|
14
|
+
* Strip `--output-format <value>` (or `--output-format=<value>`) from argv.
|
|
15
|
+
* Returns the format and the remaining args (with the flag removed).
|
|
16
|
+
* Unknown values fall back to "text" with a stderr warning.
|
|
17
|
+
*/
|
|
18
|
+
export function parseOutputFormat(argv, writeErr = (l) => void process.stderr.write(`${l}\n`)) {
|
|
19
|
+
const remaining = [];
|
|
20
|
+
let format = "text";
|
|
21
|
+
for (let i = 0; i < argv.length; i++) {
|
|
22
|
+
const arg = argv[i];
|
|
23
|
+
if (arg === "--output-format" && i + 1 < argv.length) {
|
|
24
|
+
const value = argv[++i];
|
|
25
|
+
if (value === "text" || value === "json" || value === "stream-json") {
|
|
26
|
+
format = value;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
writeErr(`Unknown --output-format value "${value}", falling back to text.`);
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (arg.startsWith("--output-format=")) {
|
|
34
|
+
const value = arg.slice("--output-format=".length);
|
|
35
|
+
if (value === "text" || value === "json" || value === "stream-json") {
|
|
36
|
+
format = value;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
writeErr(`Unknown --output-format value "${value}", falling back to text.`);
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
remaining.push(arg);
|
|
44
|
+
}
|
|
45
|
+
return { format, remainingArgs: remaining };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse raw NDJSON text (pi's --mode json stdout) into an array of event objects.
|
|
49
|
+
* Skips blank lines. Fail-soft: unparseable lines are skipped.
|
|
50
|
+
*/
|
|
51
|
+
export function parseJsonEvents(raw) {
|
|
52
|
+
const events = [];
|
|
53
|
+
for (const line of raw.split("\n")) {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
if (!trimmed)
|
|
56
|
+
continue;
|
|
57
|
+
try {
|
|
58
|
+
events.push(JSON.parse(trimmed));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// skip unparseable lines
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return events;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build the single JSON result object from collected pi events and Guardian
|
|
68
|
+
* events. Pure — no I/O.
|
|
69
|
+
*
|
|
70
|
+
* @param events NDJSON event objects from pi's --mode json stdout
|
|
71
|
+
* @param guardian Guardian review entries from the error sink
|
|
72
|
+
* @param durationMs Wall-clock time measured by the launcher
|
|
73
|
+
* @param verbose When true, returns the raw events array instead of a single object
|
|
74
|
+
*/
|
|
75
|
+
export function buildResultObject(opts) {
|
|
76
|
+
const { events, durationMs } = opts;
|
|
77
|
+
const guardianReviews = opts.guardianEvents ?? [];
|
|
78
|
+
if (opts.verbose) {
|
|
79
|
+
return events;
|
|
80
|
+
}
|
|
81
|
+
// Session ID from the "session" header event
|
|
82
|
+
const sessionEvent = events.find((e) => e.type === "session");
|
|
83
|
+
const sessionId = typeof sessionEvent?.id === "string" ? sessionEvent.id : "";
|
|
84
|
+
// Count turns
|
|
85
|
+
const numTurns = events.filter((e) => e.type === "turn_start").length;
|
|
86
|
+
// Find the final assistant message from agent_end
|
|
87
|
+
const agentEnd = events.find((e) => e.type === "agent_end");
|
|
88
|
+
const messages = agentEnd?.messages ?? [];
|
|
89
|
+
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
|
|
90
|
+
let resultText = "";
|
|
91
|
+
let stopReason = null;
|
|
92
|
+
if (lastAssistant) {
|
|
93
|
+
const textContent = lastAssistant.content?.find((c) => c.type === "text");
|
|
94
|
+
if (textContent?.text) {
|
|
95
|
+
resultText = textContent.text;
|
|
96
|
+
}
|
|
97
|
+
// stopReason is on the assistant message — check message_end events as fallback
|
|
98
|
+
}
|
|
99
|
+
// Get stopReason from the last assistant message_end event
|
|
100
|
+
const assistantMessageEnds = events.filter((e) => e.type === "message_end" && typeof e.message === "object");
|
|
101
|
+
const lastAssistantEnd = [...assistantMessageEnds]
|
|
102
|
+
.reverse()
|
|
103
|
+
.find((e) => e.message?.role === "assistant");
|
|
104
|
+
if (lastAssistantEnd?.message?.stopReason) {
|
|
105
|
+
stopReason = lastAssistantEnd.message.stopReason;
|
|
106
|
+
}
|
|
107
|
+
// Also try to get result text from message_end if agent_end was missing
|
|
108
|
+
if (!resultText && lastAssistantEnd?.message?.content) {
|
|
109
|
+
const textContent = lastAssistantEnd.message.content.find((c) => c.type === "text");
|
|
110
|
+
if (textContent?.text) {
|
|
111
|
+
resultText = textContent.text;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Aggregate usage and cost across all assistant message_end events
|
|
115
|
+
let inputTokens = 0;
|
|
116
|
+
let outputTokens = 0;
|
|
117
|
+
let cacheReadTokens = 0;
|
|
118
|
+
let totalTokens = 0;
|
|
119
|
+
let totalCostUsd = 0;
|
|
120
|
+
for (const e of assistantMessageEnds) {
|
|
121
|
+
const usage = e.message?.usage;
|
|
122
|
+
if (usage) {
|
|
123
|
+
inputTokens += usage.input ?? 0;
|
|
124
|
+
outputTokens += usage.output ?? 0;
|
|
125
|
+
cacheReadTokens += usage.cacheRead ?? 0;
|
|
126
|
+
totalTokens += usage.totalTokens ?? 0;
|
|
127
|
+
totalCostUsd += usage.cost?.total ?? 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Extract tools used from tool_execution_end events
|
|
131
|
+
const toolsUsed = events
|
|
132
|
+
.filter((e) => e.type === "tool_execution_end")
|
|
133
|
+
.map((e) => ({
|
|
134
|
+
name: typeof e.toolName === "string" ? e.toolName : "unknown",
|
|
135
|
+
is_error: e.isError === true,
|
|
136
|
+
}));
|
|
137
|
+
// Determine subtype
|
|
138
|
+
const isError = stopReason === "error" ||
|
|
139
|
+
stopReason === "aborted" ||
|
|
140
|
+
(!agentEnd && events.length === 0);
|
|
141
|
+
const subtype = isError
|
|
142
|
+
? "error_during_execution"
|
|
143
|
+
: "success";
|
|
144
|
+
return {
|
|
145
|
+
type: "result",
|
|
146
|
+
subtype,
|
|
147
|
+
result: resultText,
|
|
148
|
+
is_error: isError,
|
|
149
|
+
duration_ms: durationMs,
|
|
150
|
+
num_turns: numTurns,
|
|
151
|
+
session_id: sessionId,
|
|
152
|
+
total_cost_usd: Math.round(totalCostUsd * 1e6) / 1e6,
|
|
153
|
+
usage: {
|
|
154
|
+
input_tokens: inputTokens,
|
|
155
|
+
output_tokens: outputTokens,
|
|
156
|
+
cache_read_tokens: cacheReadTokens,
|
|
157
|
+
total_tokens: totalTokens,
|
|
158
|
+
},
|
|
159
|
+
tools_used: toolsUsed,
|
|
160
|
+
guardian_reviews: guardianReviews,
|
|
161
|
+
stop_reason: stopReason,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// --- Guardian events from the error sink ---
|
|
165
|
+
/**
|
|
166
|
+
* Read Guardian review events from the error sink JSONL, filtered by
|
|
167
|
+
* YAGNI_SESSION_ID. The error sink is written by the extension during the
|
|
168
|
+
* session; this reads it post-run. Fail-soft: returns [] on any error.
|
|
169
|
+
*/
|
|
170
|
+
export function readGuardianEvents(yagniSessionId, opts = {}) {
|
|
171
|
+
const now = opts.now ?? new Date();
|
|
172
|
+
const home = opts.homeDir ?? join(homedir(), DISTRIBUTION.stateDirName);
|
|
173
|
+
const dayStamp = now.toISOString().slice(0, 10);
|
|
174
|
+
const logPath = join(home, "logs", `errors-${dayStamp}.jsonl`);
|
|
175
|
+
try {
|
|
176
|
+
const data = readFileSync(logPath, "utf8");
|
|
177
|
+
const reviews = [];
|
|
178
|
+
for (const line of data.split("\n")) {
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (!trimmed)
|
|
181
|
+
continue;
|
|
182
|
+
try {
|
|
183
|
+
const obj = JSON.parse(trimmed);
|
|
184
|
+
if (obj.source === "guardian" && obj.sessionId === yagniSessionId) {
|
|
185
|
+
reviews.push({
|
|
186
|
+
outcome: obj.outcome ?? "unknown",
|
|
187
|
+
...(obj.durationMs !== undefined ? { duration_ms: obj.durationMs } : {}),
|
|
188
|
+
...(obj.tier !== undefined ? { tier: obj.tier } : {}),
|
|
189
|
+
// "ask" and "deny" both block the command in headless
|
|
190
|
+
...(obj.outcome === "ask" || obj.outcome === "deny"
|
|
191
|
+
? { blocked: true }
|
|
192
|
+
: {}),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// skip unparseable lines
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return reviews;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// file missing, unreadable, etc.
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=outputFormat.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.0.0-staging.
|
|
3
|
+
"version": "1.0.0-staging.1177.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)",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"turndown": "^7.2.4",
|
|
41
41
|
"typebox": "^1.3.15"
|
|
42
42
|
},
|
|
43
|
-
"yagniSourceSha": "
|
|
43
|
+
"yagniSourceSha": "92209df02c3660ed50b91a72f3197d47c2003e1d"
|
|
44
44
|
}
|