lsd-pi 1.1.2 → 1.1.3
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/README.md +2 -1
- package/dist/bedrock-auth.d.ts +25 -0
- package/dist/bedrock-auth.js +59 -0
- package/dist/headless.js +8 -3
- package/dist/loader.js +1 -0
- package/dist/onboarding-llm.d.ts +37 -0
- package/dist/onboarding-llm.js +64 -0
- package/dist/onboarding.d.ts +2 -14
- package/dist/onboarding.js +146 -71
- package/dist/pi-migration.js +1 -0
- package/dist/resources/extensions/memory/auto-extract.js +21 -3
- package/dist/resources/extensions/memory/dream.js +703 -0
- package/dist/resources/extensions/memory/extension-manifest.json +2 -2
- package/dist/resources/extensions/memory/index.js +115 -8
- package/dist/resources/extensions/slash-commands/extension-manifest.json +10 -10
- package/dist/resources/extensions/slash-commands/index.js +0 -4
- package/dist/resources/extensions/slash-commands/plan.js +181 -45
- package/dist/resources/extensions/subagent/agents.js +14 -1
- package/dist/resources/extensions/subagent/configured-model.js +3 -2
- package/dist/resources/extensions/subagent/index.js +34 -28
- package/dist/resources/extensions/subagent/launch-helpers.js +24 -0
- package/dist/resources/extensions/subagent/model-resolution.js +41 -3
- package/dist/resources/extensions/usage/extension-manifest.json +11 -0
- package/dist/resources/extensions/usage/index.js +346 -0
- package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/SKILL.md +6 -6
- package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/references/custom-tools.md +1 -1
- package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extension-lifecycle.md +2 -2
- package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensioncontext-reference.md +1 -1
- package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/references/key-rules-gotchas.md +4 -4
- package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/packaging-distribution.md +6 -6
- package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/create-extension.md +3 -3
- package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/debug-extension.md +5 -5
- package/dist/resources/skills/teams-debug/SKILL.md +5 -6
- package/dist/resources/skills/teams-document/SKILL.md +1 -2
- package/dist/resources/skills/teams-plan/SKILL.md +3 -4
- package/dist/resources/skills/teams-run/SKILL.md +3 -4
- package/dist/resources/skills/teams-verify/SKILL.md +4 -5
- package/dist/startup-model-validation.js +1 -0
- package/dist/welcome-screen.js +13 -11
- package/dist/wizard.js +12 -0
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +688 -409
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +761 -488
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +40 -18
- package/packages/pi-ai/src/models.generated.ts +759 -486
- package/packages/pi-coding-agent/dist/cli/config-selector.js +1 -1
- package/packages/pi-coding-agent/dist/cli/config-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +1 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +6 -30
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +44 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +9 -5
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +3 -2
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +1 -1
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +15 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +25 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +10 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +31 -22
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +18 -5
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +139 -20
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/config-selector.ts +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +5 -28
- package/packages/pi-coding-agent/src/core/settings-manager.ts +52 -1
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +18 -5
- package/packages/pi-coding-agent/src/core/skills.ts +3 -2
- package/packages/pi-coding-agent/src/index.ts +1 -1
- package/packages/pi-coding-agent/src/main.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +12 -13
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +46 -20
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +171 -20
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -0
- package/packages/pi-tui/dist/components/editor.d.ts +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +23 -0
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +23 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts +18 -5
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +139 -20
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +4 -0
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/memory/auto-extract.ts +23 -3
- package/src/resources/extensions/memory/dream.ts +814 -0
- package/src/resources/extensions/memory/extension-manifest.json +2 -2
- package/src/resources/extensions/memory/index.ts +134 -13
- package/src/resources/extensions/memory/tests/auto-extract.test.ts +10 -2
- package/src/resources/extensions/memory/tests/dream.test.ts +142 -0
- package/src/resources/extensions/slash-commands/extension-manifest.json +10 -10
- package/src/resources/extensions/slash-commands/index.ts +3 -7
- package/src/resources/extensions/slash-commands/plan.ts +192 -46
- package/src/resources/extensions/subagent/agents.ts +11 -1
- package/src/resources/extensions/subagent/configured-model.ts +3 -2
- package/src/resources/extensions/subagent/index.ts +38 -30
- package/src/resources/extensions/subagent/launch-helpers.ts +30 -0
- package/src/resources/extensions/subagent/model-resolution.ts +40 -3
- package/src/resources/extensions/usage/extension-manifest.json +11 -0
- package/src/resources/extensions/usage/index.ts +441 -0
- package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/SKILL.md +6 -6
- package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/references/custom-tools.md +1 -1
- package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extension-lifecycle.md +2 -2
- package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensioncontext-reference.md +1 -1
- package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/references/key-rules-gotchas.md +4 -4
- package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/packaging-distribution.md +6 -6
- package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/create-extension.md +3 -3
- package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/debug-extension.md +5 -5
- package/src/resources/skills/teams-debug/SKILL.md +5 -6
- package/src/resources/skills/teams-document/SKILL.md +1 -2
- package/src/resources/skills/teams-plan/SKILL.md +3 -4
- package/src/resources/skills/teams-run/SKILL.md +3 -4
- package/src/resources/skills/teams-verify/SKILL.md +4 -5
- package/dist/resources/extensions/slash-commands/create-extension.js +0 -264
- package/dist/resources/extensions/slash-commands/create-slash-command.js +0 -208
- package/src/resources/extensions/slash-commands/create-extension.ts +0 -297
- package/src/resources/extensions/slash-commands/create-slash-command.ts +0 -234
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/compaction-session-control.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-commands.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-rendering.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-ui.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/events-reference.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensionapi-reference.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/mode-behavior.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/model-provider-management.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/remote-execution-overrides.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/state-management.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/system-prompt-modification.md +0 -0
- /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/add-capability.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/compaction-session-control.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-commands.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-rendering.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-ui.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/events-reference.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensionapi-reference.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/mode-behavior.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/model-provider-management.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/remote-execution-overrides.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/state-management.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/system-prompt-modification.md +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/templates/extension-skeleton.ts +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/templates/stateful-tool-skeleton.ts +0 -0
- /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/add-capability.md +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Uses JSON mode to capture structured output from subagents.
|
|
13
13
|
*/
|
|
14
|
-
import { spawn,
|
|
14
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
15
15
|
import * as crypto from "node:crypto";
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as os from "node:os";
|
|
@@ -22,18 +22,19 @@ import { Container, Markdown, Spacer, Text } from "@gsd/pi-tui";
|
|
|
22
22
|
import { Type } from "@sinclair/typebox";
|
|
23
23
|
import { formatTokenCount } from "../shared/mod.js";
|
|
24
24
|
import { discoverAgents } from "./agents.js";
|
|
25
|
+
import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
|
|
25
26
|
import { createIsolation, mergeDeltaPatches, readIsolationMode, } from "./isolation.js";
|
|
26
27
|
import { registerWorker, updateWorker } from "./worker-registry.js";
|
|
27
28
|
import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
|
|
28
29
|
import { resolveConfiguredSubagentModel } from "./configured-model.js";
|
|
29
|
-
import { resolveSubagentModel } from "./model-resolution.js";
|
|
30
|
+
import { normalizeSubagentModel, resolveSubagentModel, } from "./model-resolution.js";
|
|
30
31
|
import { loadEffectivePreferences } from "../shared/preferences.js";
|
|
31
32
|
import { CmuxClient } from "../cmux/index.js";
|
|
32
33
|
const MAX_PARALLEL_TASKS = 8;
|
|
33
34
|
const MAX_CONCURRENCY = 4;
|
|
34
35
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
35
36
|
const liveSubagentProcesses = new Set();
|
|
36
|
-
async function stopLiveSubagents() {
|
|
37
|
+
export async function stopLiveSubagents() {
|
|
37
38
|
const active = Array.from(liveSubagentProcesses);
|
|
38
39
|
if (active.length === 0)
|
|
39
40
|
return;
|
|
@@ -203,23 +204,14 @@ function readBudgetSubagentModelFromSettings() {
|
|
|
203
204
|
return undefined;
|
|
204
205
|
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
205
206
|
const parsed = JSON.parse(raw);
|
|
206
|
-
return typeof parsed.budgetSubagentModel === "string"
|
|
207
|
+
return typeof parsed.budgetSubagentModel === "string"
|
|
208
|
+
? normalizeSubagentModel(parsed.budgetSubagentModel)
|
|
209
|
+
: undefined;
|
|
207
210
|
}
|
|
208
211
|
catch {
|
|
209
212
|
return undefined;
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
|
-
function buildSubagentProcessArgs(agent, task, tmpPromptPath, model) {
|
|
213
|
-
const args = ["--mode", "json", "-p", "--no-session"];
|
|
214
|
-
if (model)
|
|
215
|
-
args.push("--model", model);
|
|
216
|
-
if (agent.tools && agent.tools.length > 0)
|
|
217
|
-
args.push("--tools", agent.tools.join(","));
|
|
218
|
-
if (tmpPromptPath)
|
|
219
|
-
args.push("--append-system-prompt", tmpPromptPath);
|
|
220
|
-
args.push(`Task: ${task}`);
|
|
221
|
-
return args;
|
|
222
|
-
}
|
|
223
215
|
function resolveSubagentCliPath(defaultCwd) {
|
|
224
216
|
const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
|
|
225
217
|
.map((value) => value?.trim())
|
|
@@ -235,7 +227,7 @@ function resolveSubagentCliPath(defaultCwd) {
|
|
|
235
227
|
}
|
|
236
228
|
for (const binName of ["lsd", "gsd"]) {
|
|
237
229
|
try {
|
|
238
|
-
const resolved =
|
|
230
|
+
const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
|
|
239
231
|
if (resolved)
|
|
240
232
|
return resolved;
|
|
241
233
|
}
|
|
@@ -247,22 +239,22 @@ function resolveSubagentCliPath(defaultCwd) {
|
|
|
247
239
|
}
|
|
248
240
|
function processSubagentEventLine(line, currentResult, emitUpdate, proc) {
|
|
249
241
|
if (!line.trim())
|
|
250
|
-
return;
|
|
242
|
+
return false;
|
|
251
243
|
let event;
|
|
252
244
|
try {
|
|
253
245
|
event = JSON.parse(line);
|
|
254
246
|
}
|
|
255
247
|
catch {
|
|
256
|
-
return;
|
|
248
|
+
return false;
|
|
257
249
|
}
|
|
258
250
|
if (proc && isSubagentPermissionRequest(event)) {
|
|
259
251
|
void handleSubagentPermissionRequest(event, proc, {
|
|
260
252
|
requestFileChangeApproval,
|
|
261
253
|
requestClassifierDecision,
|
|
262
254
|
});
|
|
263
|
-
return;
|
|
255
|
+
return false;
|
|
264
256
|
}
|
|
265
|
-
if (event.type === "message_end" && event.message) {
|
|
257
|
+
if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
|
|
266
258
|
const msg = event.message;
|
|
267
259
|
currentResult.messages.push(msg);
|
|
268
260
|
if (msg.role === "assistant") {
|
|
@@ -276,7 +268,7 @@ function processSubagentEventLine(line, currentResult, emitUpdate, proc) {
|
|
|
276
268
|
currentResult.usage.cost += usage.cost?.total || 0;
|
|
277
269
|
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
278
270
|
}
|
|
279
|
-
if (msg.model)
|
|
271
|
+
if (msg.model && (!currentResult.model || msg.model.includes("/")))
|
|
280
272
|
currentResult.model = msg.model;
|
|
281
273
|
if (msg.stopReason)
|
|
282
274
|
currentResult.stopReason = msg.stopReason;
|
|
@@ -289,6 +281,7 @@ function processSubagentEventLine(line, currentResult, emitUpdate, proc) {
|
|
|
289
281
|
currentResult.messages.push(event.message);
|
|
290
282
|
emitUpdate();
|
|
291
283
|
}
|
|
284
|
+
return event.type === "agent_end";
|
|
292
285
|
}
|
|
293
286
|
async function waitForFile(filePath, signal, timeoutMs = 30 * 60 * 1000) {
|
|
294
287
|
const started = Date.now();
|
|
@@ -350,8 +343,8 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, mo
|
|
|
350
343
|
const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel);
|
|
351
344
|
let wasAborted = false;
|
|
352
345
|
const exitCode = await new Promise((resolve) => {
|
|
353
|
-
const bundledPaths = (
|
|
354
|
-
const extensionArgs = bundledPaths.flatMap(p => ["--extension", p]);
|
|
346
|
+
const bundledPaths = getBundledExtensionPathsFromEnv();
|
|
347
|
+
const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
|
|
355
348
|
const cliPath = resolveSubagentCliPath(cwd ?? defaultCwd);
|
|
356
349
|
if (!cliPath) {
|
|
357
350
|
currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
|
|
@@ -359,23 +352,36 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, mo
|
|
|
359
352
|
return;
|
|
360
353
|
}
|
|
361
354
|
const proc = spawn(process.execPath, [cliPath, ...extensionArgs, ...args], { cwd: cwd ?? defaultCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
355
|
+
proc.stdin.end();
|
|
362
356
|
liveSubagentProcesses.add(proc);
|
|
363
357
|
let buffer = "";
|
|
358
|
+
let completionSeen = false;
|
|
364
359
|
proc.stdout.on("data", (data) => {
|
|
365
360
|
buffer += data.toString();
|
|
366
361
|
const lines = buffer.split("\n");
|
|
367
362
|
buffer = lines.pop() || "";
|
|
368
|
-
for (const line of lines)
|
|
369
|
-
processSubagentEventLine(line, currentResult, emitUpdate, proc)
|
|
363
|
+
for (const line of lines) {
|
|
364
|
+
if (processSubagentEventLine(line, currentResult, emitUpdate, proc)) {
|
|
365
|
+
completionSeen = true;
|
|
366
|
+
try {
|
|
367
|
+
proc.kill("SIGTERM");
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
/* ignore */
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
370
374
|
});
|
|
371
375
|
proc.stderr.on("data", (data) => {
|
|
372
376
|
currentResult.stderr += data.toString();
|
|
373
377
|
});
|
|
374
378
|
proc.on("close", (code) => {
|
|
375
379
|
liveSubagentProcesses.delete(proc);
|
|
376
|
-
if (buffer.trim())
|
|
377
|
-
processSubagentEventLine(buffer, currentResult, emitUpdate, proc);
|
|
378
|
-
|
|
380
|
+
if (buffer.trim()) {
|
|
381
|
+
const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc);
|
|
382
|
+
completionSeen = completionSeen || completedOnFlush;
|
|
383
|
+
}
|
|
384
|
+
resolve(completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0));
|
|
379
385
|
});
|
|
380
386
|
proc.on("error", () => {
|
|
381
387
|
liveSubagentProcesses.delete(proc);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
export function getBundledExtensionPathsFromEnv(env = process.env) {
|
|
3
|
+
const rawPaths = [env.GSD_BUNDLED_EXTENSION_PATHS, env.LSD_BUNDLED_EXTENSION_PATHS]
|
|
4
|
+
.map((value) => value?.trim())
|
|
5
|
+
.filter((value) => Boolean(value));
|
|
6
|
+
const unique = new Set();
|
|
7
|
+
for (const raw of rawPaths) {
|
|
8
|
+
for (const entry of raw.split(path.delimiter).map((value) => value.trim()).filter(Boolean)) {
|
|
9
|
+
unique.add(entry);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return Array.from(unique);
|
|
13
|
+
}
|
|
14
|
+
export function buildSubagentProcessArgs(agent, task, tmpPromptPath, model) {
|
|
15
|
+
const args = ["--mode", "json", "-p", "--no-session"];
|
|
16
|
+
if (model)
|
|
17
|
+
args.push("--model", model);
|
|
18
|
+
if (agent.tools && agent.tools.length > 0)
|
|
19
|
+
args.push("--tools", agent.tools.join(","));
|
|
20
|
+
if (tmpPromptPath)
|
|
21
|
+
args.push("--append-system-prompt", tmpPromptPath);
|
|
22
|
+
args.push(`Task: ${task}`);
|
|
23
|
+
return args;
|
|
24
|
+
}
|
|
@@ -1,12 +1,50 @@
|
|
|
1
|
+
const BARE_MODEL_PROVIDER_RULES = [
|
|
2
|
+
{ provider: "anthropic", matches: (modelId) => modelId.startsWith("claude-") },
|
|
3
|
+
{ provider: "google", matches: (modelId) => modelId.startsWith("gemini-") },
|
|
4
|
+
{
|
|
5
|
+
provider: "openai",
|
|
6
|
+
matches: (modelId) => /^(gpt-|o[134]-|omni-|text-embedding-)/.test(modelId),
|
|
7
|
+
},
|
|
8
|
+
{ provider: "xai", matches: (modelId) => modelId.startsWith("grok-") },
|
|
9
|
+
{
|
|
10
|
+
provider: "mistral",
|
|
11
|
+
matches: (modelId) => /^(mistral-|ministral-|codestral-)/.test(modelId),
|
|
12
|
+
},
|
|
13
|
+
{ provider: "groq", matches: (modelId) => modelId.startsWith("llama-") || modelId.startsWith("mixtral-") },
|
|
14
|
+
];
|
|
15
|
+
export function inferProviderForBareModel(modelId) {
|
|
16
|
+
const normalizedModelId = modelId.trim().toLowerCase();
|
|
17
|
+
if (!normalizedModelId)
|
|
18
|
+
return undefined;
|
|
19
|
+
return BARE_MODEL_PROVIDER_RULES.find((rule) => rule.matches(normalizedModelId))?.provider;
|
|
20
|
+
}
|
|
21
|
+
export function isQualifiedSubagentModel(model) {
|
|
22
|
+
const trimmed = model.trim();
|
|
23
|
+
if (!trimmed || trimmed.includes(" "))
|
|
24
|
+
return false;
|
|
25
|
+
const parts = trimmed.split("/");
|
|
26
|
+
return parts.length === 2 && parts.every(Boolean);
|
|
27
|
+
}
|
|
28
|
+
export function normalizeSubagentModel(model) {
|
|
29
|
+
const trimmed = model?.trim();
|
|
30
|
+
if (!trimmed || trimmed.startsWith("$"))
|
|
31
|
+
return undefined;
|
|
32
|
+
if (isQualifiedSubagentModel(trimmed))
|
|
33
|
+
return trimmed;
|
|
34
|
+
if (trimmed.includes("/"))
|
|
35
|
+
return undefined;
|
|
36
|
+
const inferredProvider = inferProviderForBareModel(trimmed);
|
|
37
|
+
return inferredProvider ? `${inferredProvider}/${trimmed}` : undefined;
|
|
38
|
+
}
|
|
1
39
|
export function resolveSubagentModel(agent, options = {}) {
|
|
2
|
-
const overrideModel = options.overrideModel
|
|
40
|
+
const overrideModel = normalizeSubagentModel(options.overrideModel);
|
|
3
41
|
if (overrideModel)
|
|
4
42
|
return overrideModel;
|
|
5
|
-
const agentModel = agent.model
|
|
43
|
+
const agentModel = normalizeSubagentModel(agent.model);
|
|
6
44
|
if (agentModel)
|
|
7
45
|
return agentModel;
|
|
8
46
|
if (options.parentModel?.provider && options.parentModel?.id) {
|
|
9
|
-
return `${options.parentModel.provider}/${options.parentModel.id}
|
|
47
|
+
return normalizeSubagentModel(`${options.parentModel.provider}/${options.parentModel.id}`);
|
|
10
48
|
}
|
|
11
49
|
return undefined;
|
|
12
50
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "usage",
|
|
3
|
+
"name": "Usage",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Show built-in LSD token and cost usage reports aggregated from session history",
|
|
6
|
+
"tier": "bundled",
|
|
7
|
+
"requires": { "platform": ">=2.29.0" },
|
|
8
|
+
"provides": {
|
|
9
|
+
"commands": ["usage"]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
function getSessionsRoot() {
|
|
5
|
+
const appRoot = process.env.LSD_HOME || join(homedir(), ".lsd");
|
|
6
|
+
return join(appRoot, "sessions");
|
|
7
|
+
}
|
|
8
|
+
function getCurrentProjectSessionsDir(cwd) {
|
|
9
|
+
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
10
|
+
return join(getSessionsRoot(), safePath);
|
|
11
|
+
}
|
|
12
|
+
function startOfLocalDay(date) {
|
|
13
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
14
|
+
}
|
|
15
|
+
function endOfLocalDay(date) {
|
|
16
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).getTime();
|
|
17
|
+
}
|
|
18
|
+
function formatInt(value) {
|
|
19
|
+
return Math.round(value).toLocaleString();
|
|
20
|
+
}
|
|
21
|
+
function formatCost(value) {
|
|
22
|
+
return `$${value.toFixed(4)}`;
|
|
23
|
+
}
|
|
24
|
+
function parseRangeToken(token) {
|
|
25
|
+
const trimmed = token?.trim();
|
|
26
|
+
if (!trimmed || trimmed === "today") {
|
|
27
|
+
const now = new Date();
|
|
28
|
+
return {
|
|
29
|
+
label: "today",
|
|
30
|
+
startMs: startOfLocalDay(now),
|
|
31
|
+
endMs: endOfLocalDay(now),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const rollingMatch = trimmed.match(/^(\d+)d$/i);
|
|
35
|
+
if (rollingMatch) {
|
|
36
|
+
const days = Math.max(1, Number.parseInt(rollingMatch[1] ?? "1", 10));
|
|
37
|
+
const now = new Date();
|
|
38
|
+
return {
|
|
39
|
+
label: `${days}d`,
|
|
40
|
+
startMs: startOfLocalDay(new Date(now.getFullYear(), now.getMonth(), now.getDate() - (days - 1))),
|
|
41
|
+
endMs: endOfLocalDay(now),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const dateMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
45
|
+
if (dateMatch) {
|
|
46
|
+
const year = Number.parseInt(dateMatch[1] ?? "0", 10);
|
|
47
|
+
const month = Number.parseInt(dateMatch[2] ?? "1", 10) - 1;
|
|
48
|
+
const day = Number.parseInt(dateMatch[3] ?? "1", 10);
|
|
49
|
+
const date = new Date(year, month, day);
|
|
50
|
+
if (!Number.isNaN(date.getTime())) {
|
|
51
|
+
return {
|
|
52
|
+
label: trimmed,
|
|
53
|
+
startMs: startOfLocalDay(date),
|
|
54
|
+
endMs: endOfLocalDay(date),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Invalid usage range "${trimmed}". Use: today, 7d, or YYYY-MM-DD.`);
|
|
59
|
+
}
|
|
60
|
+
function parseArgs(rawArgs) {
|
|
61
|
+
const tokens = rawArgs.trim().split(/\s+/).filter(Boolean);
|
|
62
|
+
let rangeToken;
|
|
63
|
+
let scope = "all-projects";
|
|
64
|
+
let groupBy = "model";
|
|
65
|
+
let json = false;
|
|
66
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
67
|
+
const token = tokens[i];
|
|
68
|
+
if (token === "--project-current") {
|
|
69
|
+
scope = "current-project";
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (token === "--all-projects") {
|
|
73
|
+
scope = "all-projects";
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (token === "--json") {
|
|
77
|
+
json = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (token === "--by") {
|
|
81
|
+
const next = tokens[i + 1];
|
|
82
|
+
if (next !== "model" && next !== "project" && next !== "project-model") {
|
|
83
|
+
throw new Error('Invalid --by value. Use: model, project, or project-model.');
|
|
84
|
+
}
|
|
85
|
+
groupBy = next;
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (token.startsWith("--")) {
|
|
90
|
+
throw new Error(`Unknown flag: ${token}`);
|
|
91
|
+
}
|
|
92
|
+
if (!rangeToken) {
|
|
93
|
+
rangeToken = token;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Unexpected argument: ${token}`);
|
|
97
|
+
}
|
|
98
|
+
const range = parseRangeToken(rangeToken);
|
|
99
|
+
return {
|
|
100
|
+
...range,
|
|
101
|
+
scope,
|
|
102
|
+
groupBy,
|
|
103
|
+
json,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function walkJsonlFiles(dir) {
|
|
107
|
+
if (!existsSync(dir))
|
|
108
|
+
return [];
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
111
|
+
const fullPath = join(dir, entry.name);
|
|
112
|
+
if (entry.isDirectory()) {
|
|
113
|
+
out.push(...walkJsonlFiles(fullPath));
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (entry.isFile() && fullPath.endsWith(".jsonl")) {
|
|
117
|
+
out.push(fullPath);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
function normalizeProjectLabel(cwd, fallbackPath) {
|
|
123
|
+
if (cwd && cwd.trim())
|
|
124
|
+
return cwd;
|
|
125
|
+
return basename(fallbackPath);
|
|
126
|
+
}
|
|
127
|
+
function makeModelLabel(message) {
|
|
128
|
+
if (message.provider && message.model)
|
|
129
|
+
return `${message.provider}/${message.model}`;
|
|
130
|
+
if (message.model)
|
|
131
|
+
return message.model;
|
|
132
|
+
return "unknown";
|
|
133
|
+
}
|
|
134
|
+
function makeGroupKey(groupBy, project, model) {
|
|
135
|
+
switch (groupBy) {
|
|
136
|
+
case "project":
|
|
137
|
+
return project;
|
|
138
|
+
case "project-model":
|
|
139
|
+
return `${project} :: ${model}`;
|
|
140
|
+
case "model":
|
|
141
|
+
default:
|
|
142
|
+
return model;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function collectUsage(sessionFiles, startMs, endMs, scope, groupBy) {
|
|
146
|
+
const rows = new Map();
|
|
147
|
+
let filesScanned = 0;
|
|
148
|
+
let matchedMessages = 0;
|
|
149
|
+
for (const file of sessionFiles) {
|
|
150
|
+
filesScanned++;
|
|
151
|
+
let projectLabel = basename(file);
|
|
152
|
+
let headerResolved = false;
|
|
153
|
+
const raw = readFileSync(file, "utf-8");
|
|
154
|
+
for (const line of raw.split("\n")) {
|
|
155
|
+
if (!line.trim())
|
|
156
|
+
continue;
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(line);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!headerResolved) {
|
|
165
|
+
const header = parsed;
|
|
166
|
+
if (header.type === "session") {
|
|
167
|
+
projectLabel = normalizeProjectLabel(header.cwd, file);
|
|
168
|
+
headerResolved = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const message = parsed?.type === "message" ? parsed.message : undefined;
|
|
172
|
+
if (!message || message.role !== "assistant")
|
|
173
|
+
continue;
|
|
174
|
+
const timestamp = Number(message.timestamp ?? 0);
|
|
175
|
+
if (!timestamp || timestamp < startMs || timestamp >= endMs)
|
|
176
|
+
continue;
|
|
177
|
+
matchedMessages++;
|
|
178
|
+
const model = makeModelLabel(message);
|
|
179
|
+
const key = makeGroupKey(groupBy, projectLabel, model);
|
|
180
|
+
const usage = message.usage ?? {};
|
|
181
|
+
const input = Number(usage.input ?? 0);
|
|
182
|
+
const output = Number(usage.output ?? 0);
|
|
183
|
+
const cacheRead = Number(usage.cacheRead ?? 0);
|
|
184
|
+
const cacheWrite = Number(usage.cacheWrite ?? 0);
|
|
185
|
+
const cost = Number(usage.cost?.total ?? 0);
|
|
186
|
+
const total = input + output + cacheRead + cacheWrite;
|
|
187
|
+
const existing = rows.get(key) ?? {
|
|
188
|
+
key,
|
|
189
|
+
project: groupBy === "model" ? "—" : projectLabel,
|
|
190
|
+
model: groupBy === "project" ? "—" : model,
|
|
191
|
+
messages: 0,
|
|
192
|
+
input: 0,
|
|
193
|
+
output: 0,
|
|
194
|
+
cacheRead: 0,
|
|
195
|
+
cacheWrite: 0,
|
|
196
|
+
total: 0,
|
|
197
|
+
cost: 0,
|
|
198
|
+
};
|
|
199
|
+
existing.messages += 1;
|
|
200
|
+
existing.input += input;
|
|
201
|
+
existing.output += output;
|
|
202
|
+
existing.cacheRead += cacheRead;
|
|
203
|
+
existing.cacheWrite += cacheWrite;
|
|
204
|
+
existing.total += total;
|
|
205
|
+
existing.cost += cost;
|
|
206
|
+
rows.set(key, existing);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const orderedRows = [...rows.values()].sort((left, right) => {
|
|
210
|
+
if (right.total !== left.total)
|
|
211
|
+
return right.total - left.total;
|
|
212
|
+
return left.key.localeCompare(right.key);
|
|
213
|
+
});
|
|
214
|
+
const totals = orderedRows.reduce((acc, row) => {
|
|
215
|
+
acc.messages += row.messages;
|
|
216
|
+
acc.input += row.input;
|
|
217
|
+
acc.output += row.output;
|
|
218
|
+
acc.cacheRead += row.cacheRead;
|
|
219
|
+
acc.cacheWrite += row.cacheWrite;
|
|
220
|
+
acc.total += row.total;
|
|
221
|
+
acc.cost += row.cost;
|
|
222
|
+
return acc;
|
|
223
|
+
}, { messages: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, cost: 0 });
|
|
224
|
+
return {
|
|
225
|
+
label: "",
|
|
226
|
+
scope,
|
|
227
|
+
groupBy,
|
|
228
|
+
sessionsRoot: getSessionsRoot(),
|
|
229
|
+
filesScanned,
|
|
230
|
+
matchedMessages,
|
|
231
|
+
rows: orderedRows,
|
|
232
|
+
totals,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function renderTable(report) {
|
|
236
|
+
const firstColumnHeader = report.groupBy === "project"
|
|
237
|
+
? "project"
|
|
238
|
+
: report.groupBy === "project-model"
|
|
239
|
+
? "project :: model"
|
|
240
|
+
: "model";
|
|
241
|
+
const displayRows = report.rows.map((row) => ({
|
|
242
|
+
label: row.key,
|
|
243
|
+
msgs: String(row.messages),
|
|
244
|
+
input: formatInt(row.input),
|
|
245
|
+
output: formatInt(row.output),
|
|
246
|
+
read: formatInt(row.cacheRead),
|
|
247
|
+
write: formatInt(row.cacheWrite),
|
|
248
|
+
total: formatInt(row.total),
|
|
249
|
+
cost: formatCost(row.cost),
|
|
250
|
+
}));
|
|
251
|
+
const widths = {
|
|
252
|
+
label: Math.max(firstColumnHeader.length, ...displayRows.map((row) => row.label.length), 5),
|
|
253
|
+
msgs: Math.max(4, ...displayRows.map((row) => row.msgs.length), String(report.totals.messages).length),
|
|
254
|
+
input: Math.max(5, ...displayRows.map((row) => row.input.length), formatInt(report.totals.input).length),
|
|
255
|
+
output: Math.max(6, ...displayRows.map((row) => row.output.length), formatInt(report.totals.output).length),
|
|
256
|
+
read: Math.max(4, ...displayRows.map((row) => row.read.length), formatInt(report.totals.cacheRead).length),
|
|
257
|
+
write: Math.max(5, ...displayRows.map((row) => row.write.length), formatInt(report.totals.cacheWrite).length),
|
|
258
|
+
total: Math.max(5, ...displayRows.map((row) => row.total.length), formatInt(report.totals.total).length),
|
|
259
|
+
cost: Math.max(7, ...displayRows.map((row) => row.cost.length), formatCost(report.totals.cost).length),
|
|
260
|
+
};
|
|
261
|
+
const header = [
|
|
262
|
+
firstColumnHeader.padEnd(widths.label),
|
|
263
|
+
"msgs".padStart(widths.msgs),
|
|
264
|
+
"input".padStart(widths.input),
|
|
265
|
+
"output".padStart(widths.output),
|
|
266
|
+
"read".padStart(widths.read),
|
|
267
|
+
"write".padStart(widths.write),
|
|
268
|
+
"total".padStart(widths.total),
|
|
269
|
+
"cost".padStart(widths.cost),
|
|
270
|
+
].join(" ");
|
|
271
|
+
const divider = "-".repeat(header.length);
|
|
272
|
+
const body = displayRows.map((row) => [
|
|
273
|
+
row.label.padEnd(widths.label),
|
|
274
|
+
row.msgs.padStart(widths.msgs),
|
|
275
|
+
row.input.padStart(widths.input),
|
|
276
|
+
row.output.padStart(widths.output),
|
|
277
|
+
row.read.padStart(widths.read),
|
|
278
|
+
row.write.padStart(widths.write),
|
|
279
|
+
row.total.padStart(widths.total),
|
|
280
|
+
row.cost.padStart(widths.cost),
|
|
281
|
+
].join(" "));
|
|
282
|
+
const totalsLine = [
|
|
283
|
+
"TOTAL".padEnd(widths.label),
|
|
284
|
+
String(report.totals.messages).padStart(widths.msgs),
|
|
285
|
+
formatInt(report.totals.input).padStart(widths.input),
|
|
286
|
+
formatInt(report.totals.output).padStart(widths.output),
|
|
287
|
+
formatInt(report.totals.cacheRead).padStart(widths.read),
|
|
288
|
+
formatInt(report.totals.cacheWrite).padStart(widths.write),
|
|
289
|
+
formatInt(report.totals.total).padStart(widths.total),
|
|
290
|
+
formatCost(report.totals.cost).padStart(widths.cost),
|
|
291
|
+
].join(" ");
|
|
292
|
+
return [header, divider, ...body, divider, totalsLine].join("\n");
|
|
293
|
+
}
|
|
294
|
+
function renderReport(report) {
|
|
295
|
+
const scopeLabel = report.scope === "all-projects" ? "all projects" : "current project";
|
|
296
|
+
const summary = [
|
|
297
|
+
`Usage report: ${report.label}`,
|
|
298
|
+
`Scope: ${scopeLabel}`,
|
|
299
|
+
`Grouped by: ${report.groupBy}`,
|
|
300
|
+
`Sessions root: ${report.sessionsRoot}`,
|
|
301
|
+
`Session files scanned: ${report.filesScanned}`,
|
|
302
|
+
`Assistant messages matched: ${report.matchedMessages}`,
|
|
303
|
+
].join("\n");
|
|
304
|
+
if (report.rows.length === 0) {
|
|
305
|
+
return `${summary}\n\nNo assistant usage found for that range.`;
|
|
306
|
+
}
|
|
307
|
+
return `${summary}\n\n\
|
|
308
|
+
\`\`\`text\n${renderTable(report)}\n\`\`\``;
|
|
309
|
+
}
|
|
310
|
+
function buildUsageReport(ctx, args) {
|
|
311
|
+
const sessionFiles = args.scope === "current-project"
|
|
312
|
+
? walkJsonlFiles(getCurrentProjectSessionsDir(ctx.cwd))
|
|
313
|
+
: walkJsonlFiles(getSessionsRoot());
|
|
314
|
+
const report = collectUsage(sessionFiles, args.startMs, args.endMs, args.scope, args.groupBy);
|
|
315
|
+
report.label = args.label;
|
|
316
|
+
return report;
|
|
317
|
+
}
|
|
318
|
+
export default function usageExtension(pi) {
|
|
319
|
+
pi.registerCommand("usage", {
|
|
320
|
+
description: "Show token and cost usage from LSD sessions (today by model by default)",
|
|
321
|
+
async handler(rawArgs, ctx) {
|
|
322
|
+
try {
|
|
323
|
+
const parsed = parseArgs(rawArgs);
|
|
324
|
+
const report = buildUsageReport(ctx, parsed);
|
|
325
|
+
const content = parsed.json
|
|
326
|
+
? `\`\`\`json\n${JSON.stringify(report, null, 2)}\n\`\`\``
|
|
327
|
+
: renderReport(report);
|
|
328
|
+
pi.sendMessage({
|
|
329
|
+
customType: "usage-report",
|
|
330
|
+
content,
|
|
331
|
+
display: true,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
336
|
+
ctx.ui.notify(`${message}\nUsage: /usage [today|7d|YYYY-MM-DD] [--project-current|--all-projects] [--by model|project|project-model] [--json]`, "error");
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
export const __testing = {
|
|
342
|
+
parseArgs,
|
|
343
|
+
parseRangeToken,
|
|
344
|
+
renderTable,
|
|
345
|
+
collectUsage,
|
|
346
|
+
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: create-
|
|
3
|
-
description: Create, debug, and iterate on
|
|
2
|
+
name: create-lsd-extension
|
|
3
|
+
description: Create, debug, and iterate on LSD extensions (TypeScript modules that add tools, commands, event hooks, custom UI, and providers to LSD). Use when asked to build an extension, add a tool the LLM can call, register a slash command, hook into LSD events, create custom TUI components, or modify LSD behavior. Triggers on "create extension", "build extension", "add a tool", "register command", "hook into lsd", "custom tool", "lsd plugin", "lsd extension".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<essential_principles>
|
|
7
7
|
|
|
8
|
-
**Extensions are TypeScript modules** that hook into
|
|
8
|
+
**Extensions are TypeScript modules** that hook into LSD's runtime (built on pi). They export a default function receiving `ExtensionAPI` and use it to subscribe to events, register tools/commands/shortcuts, and interact with the session.
|
|
9
9
|
|
|
10
|
-
**
|
|
10
|
+
**LSD extension paths (community/user-installed extensions):**
|
|
11
11
|
- Global: `~/.pi/agent/extensions/*.ts` or `~/.pi/agent/extensions/*/index.ts`
|
|
12
12
|
- Project-local: `.gsd/extensions/*.ts` or `.gsd/extensions/*/index.ts`
|
|
13
13
|
|
|
14
|
-
Note: `~/.gsd/agent/extensions/` is reserved for bundled extensions synced from the
|
|
14
|
+
Note: `~/.gsd/agent/extensions/` is reserved for bundled extensions synced from the lsd-pi package. Community extensions placed there are silently ignored by the loader.
|
|
15
15
|
|
|
16
16
|
**The three primitives:**
|
|
17
17
|
1. **Events** — Listen and react (`pi.on("event", handler)`). Can block tool calls, modify messages, inject context.
|
|
@@ -79,7 +79,7 @@ All domain knowledge in `references/`:
|
|
|
79
79
|
<success_criteria>
|
|
80
80
|
Extension is complete when:
|
|
81
81
|
- TypeScript compiles without errors (jiti handles this at runtime)
|
|
82
|
-
- Extension loads on
|
|
82
|
+
- Extension loads on LSD startup or `/reload` without errors
|
|
83
83
|
- Tools appear in the LLM's system prompt and are callable
|
|
84
84
|
- Commands respond to `/command` input
|
|
85
85
|
- Event hooks fire at the expected lifecycle points
|
|
@@ -3,10 +3,10 @@ The extension lifecycle from load to shutdown, including the full event flow.
|
|
|
3
3
|
</overview>
|
|
4
4
|
|
|
5
5
|
<loading>
|
|
6
|
-
Extensions load when
|
|
6
|
+
Extensions load when LSD starts (or on `/reload`). The default export function runs synchronously — subscribe to events and register tools/commands during this call.
|
|
7
7
|
|
|
8
8
|
```
|
|
9
|
-
|
|
9
|
+
LSD starts
|
|
10
10
|
└─► Extension default function runs
|
|
11
11
|
├── pi.on("event", handler) ← Subscribe
|
|
12
12
|
├── pi.registerTool({...}) ← Register tools
|
|
@@ -21,7 +21,7 @@ ctx.ui.setStatus("my-ext", "● Active"); // Footer status
|
|
|
21
21
|
ctx.ui.setStatus("my-ext", undefined); // Clear
|
|
22
22
|
ctx.ui.setWidget("my-id", ["Line 1", "Line 2"]); // Widget above editor
|
|
23
23
|
ctx.ui.setWidget("my-id", ["Below!"], { placement: "belowEditor" });
|
|
24
|
-
ctx.ui.setTitle("
|
|
24
|
+
ctx.ui.setTitle("lsd - my project"); // Terminal title
|
|
25
25
|
ctx.ui.setEditorText("Prefill"); // Set editor content
|
|
26
26
|
ctx.ui.setWorkingMessage("Analyzing..."); // Working message during streaming
|
|
27
27
|
ctx.ui.setToolsExpanded(true); // Expand tool output
|