@letta-ai/letta-code 0.27.17 → 0.27.19
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 +7 -0
- package/dist/app-server-client.js +11 -1
- package/dist/app-server-client.js.map +3 -3
- package/dist/types/app-server-client.d.ts +4 -1
- package/dist/types/app-server-client.d.ts.map +1 -1
- package/dist/types/types/protocol.d.ts +2 -1
- package/dist/types/types/protocol.d.ts.map +1 -1
- package/dist/types/types/protocol_v2.d.ts +17 -3
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/docs/examples/mods/README.md +60 -0
- package/docs/examples/mods/learning/memory-citations.env.json +165 -0
- package/docs/examples/mods/learning/uv-pip-install.env.json +199 -0
- package/docs/examples/mods/memory-citations.ts +347 -0
- package/letta.js +125665 -122383
- package/package.json +4 -2
- package/scripts/mod-learning/learn-mod.ts +306 -0
- package/skills/{context_doctor → context-doctor}/SKILL.md +1 -1
- package/skills/creating-mods/SKILL.md +10 -7
- package/skills/creating-mods/references/architecture.md +15 -4
- package/skills/creating-mods/references/commands.md +2 -1
- package/skills/creating-mods/references/events.md +141 -10
- package/skills/creating-mods/references/plan-mode.md +1 -1
- package/skills/creating-mods/references/ui.md +61 -23
- package/skills/customizing-statusline/SKILL.md +13 -14
- package/skills/customizing-statusline/references/api.md +74 -86
- package/skills/customizing-statusline/references/examples.md +79 -51
- package/skills/customizing-statusline/references/migration.md +38 -19
- package/skills/generating-mod-envs/SKILL.md +130 -0
- package/skills/generating-mod-envs/assets/mod-learning-env.template.json +57 -0
- package/skills/generating-mod-envs/scripts/validate-mod-env.ts +261 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mod UI recipes
|
|
2
2
|
|
|
3
|
-
UI capabilities are optional. Always guard UI work with `letta.capabilities.ui
|
|
3
|
+
UI capabilities are optional. Always guard UI work with `letta.capabilities.ui.panels` when writing portable mods.
|
|
4
4
|
|
|
5
5
|
For UI that belongs to a larger command/event mod, also read `architecture.md` for cleanup and composition patterns.
|
|
6
6
|
|
|
@@ -8,13 +8,9 @@ For UI that belongs to a larger command/event mod, also read `architecture.md` f
|
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
letta.capabilities.ui.panels
|
|
11
|
-
letta.capabilities.ui.statusValues
|
|
12
|
-
letta.capabilities.ui.customStatuslineRenderer
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
- `panels`:
|
|
16
|
-
- `statusValues`: small named status data that renderers or future surfaces can display.
|
|
17
|
-
- `customStatuslineRenderer`: TUI-only custom bottom-row renderer. Use `customizing-statusline` for statusline work.
|
|
13
|
+
- `panels`: text blocks placed around the input bar (the only mod UI surface). Desktop/listener disables panel UI.
|
|
18
14
|
|
|
19
15
|
## Panels
|
|
20
16
|
|
|
@@ -22,50 +18,92 @@ Panels are app/TUI-global today. Desktop/listener disables panel UI; future scop
|
|
|
22
18
|
|
|
23
19
|
```ts
|
|
24
20
|
if (letta.capabilities.ui.panels) {
|
|
21
|
+
let status = "Working…";
|
|
25
22
|
const panel = letta.ui.openPanel({
|
|
26
23
|
id: "my-mod",
|
|
27
|
-
content: ["Working…"],
|
|
28
24
|
order: 100,
|
|
25
|
+
render: ({ width }) => status,
|
|
29
26
|
});
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
status = "Done";
|
|
29
|
+
panel.update(); // re-invokes render with the current width
|
|
32
30
|
setTimeout(() => panel.close(), 5_000);
|
|
33
31
|
}
|
|
34
32
|
```
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
`render` returns the panel body: a string or string array (one entry per line). The host owns the region — it clips each line to `width` and caps total height — so the mod owns layout: use `width`, `row(left, right, width)`, and `columns(parts, width)` to align. The host re-invokes `render` whenever you call `panel.update()` and on terminal resize. Keep `render` cheap and side-effect-free; for longer text use a command `output` instead.
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
### Placement by `order`
|
|
37
|
+
|
|
38
|
+
`order` is a signed coordinate around the input:
|
|
39
|
+
|
|
40
|
+
- `order > 0` — above the input, higher nearer the top (default `100`).
|
|
41
|
+
- `order === 0` — the primary line just below the input, overriding the built-in `agent · model`. This is the statusline slot; use `customizing-statusline` for that work.
|
|
42
|
+
- `order < 0` — stacks below the primary line, `-1` closest.
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
A panel whose `render` is empty (`""`, `[]`, or only blank lines) is hidden.
|
|
45
|
+
|
|
46
|
+
### Render context
|
|
41
47
|
|
|
42
48
|
```ts
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
49
|
+
render(ctx: {
|
|
50
|
+
width: number;
|
|
51
|
+
agent: { id, name };
|
|
52
|
+
model: { id, displayName, provider, reasoningEffort };
|
|
53
|
+
row(left, right, width): string;
|
|
54
|
+
columns(parts: string[], width): string;
|
|
55
|
+
chalk: ChalkInstance;
|
|
56
|
+
}): string | string[]
|
|
46
57
|
```
|
|
47
58
|
|
|
48
|
-
|
|
59
|
+
`row`/`columns` are ANSI-aware, so chalk-colored segments align correctly.
|
|
60
|
+
|
|
61
|
+
Close panels when they are transient, and close/replace long-lived panels from the activation disposer if reload should remove them.
|
|
62
|
+
|
|
63
|
+
### Commands that open panels
|
|
64
|
+
|
|
65
|
+
If a command's `run()` opens a panel, guard the command **registration** on `letta.capabilities.ui.panels` — not just the `openPanel` call:
|
|
49
66
|
|
|
50
67
|
```ts
|
|
51
|
-
|
|
52
|
-
letta.
|
|
53
|
-
|
|
68
|
+
export default function activate(letta) {
|
|
69
|
+
if (!letta.capabilities.commands) return;
|
|
70
|
+
if (!letta.capabilities.ui.panels) return; // panel-only command: skip where panels are unsupported
|
|
71
|
+
|
|
72
|
+
return letta.commands.register({
|
|
73
|
+
id: "mycommand",
|
|
74
|
+
description: "…",
|
|
75
|
+
run() {
|
|
76
|
+
const panel = letta.ui.openPanel({ /* … */ });
|
|
77
|
+
// …
|
|
78
|
+
return { type: "handled" };
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
54
82
|
```
|
|
55
83
|
|
|
84
|
+
Guarding only the `openPanel` call is not enough. The desktop listener has `commands: true` but `ui.panels: false`, so a command registered there is advertised in the command picker while its panel work no-ops — the user sees a command that runs and does nothing. Gating the registration keeps panel-only commands out of hosts that can't render them.
|
|
85
|
+
|
|
56
86
|
## Timers and cleanup
|
|
57
87
|
|
|
58
88
|
```ts
|
|
59
89
|
export default function activate(letta) {
|
|
60
|
-
if (!letta.capabilities.ui.
|
|
90
|
+
if (!letta.capabilities.ui.panels) return;
|
|
91
|
+
|
|
92
|
+
let clock = new Date().toLocaleTimeString();
|
|
93
|
+
const panel = letta.ui.openPanel({
|
|
94
|
+
id: "clock",
|
|
95
|
+
order: 100,
|
|
96
|
+
render: ({ width, row }) => row("clock", clock, width),
|
|
97
|
+
});
|
|
61
98
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
99
|
+
const timer = setInterval(() => {
|
|
100
|
+
clock = new Date().toLocaleTimeString();
|
|
101
|
+
panel.update();
|
|
102
|
+
}, 30_000);
|
|
65
103
|
|
|
66
104
|
return () => {
|
|
67
105
|
clearInterval(timer);
|
|
68
|
-
|
|
106
|
+
panel.close();
|
|
69
107
|
};
|
|
70
108
|
}
|
|
71
109
|
```
|
|
@@ -11,28 +11,28 @@ Use this skill to create or update the global Letta Code statusline mod:
|
|
|
11
11
|
~/.letta/mods/statusline.tsx
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
The statusline is a
|
|
14
|
+
The statusline is a panel registered at `order: 0` — the primary line just below the input. It overrides the built-in `agent · model` line. Host UI can still temporarily preempt it for safety confirmations and transient hints.
|
|
15
15
|
|
|
16
16
|
## Statusline ownership model
|
|
17
17
|
|
|
18
18
|
```text
|
|
19
19
|
safety preemption
|
|
20
20
|
else transient host hint
|
|
21
|
-
else
|
|
21
|
+
else order-0 statusline panel
|
|
22
22
|
else built-in default statusline
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The order-0 panel owns the whole primary row. It renders text (not React) and owns its own layout via the `row`/`columns` helpers.
|
|
26
26
|
|
|
27
27
|
## Workflow
|
|
28
28
|
|
|
29
29
|
1. Check whether `~/.letta/mods/statusline.tsx` exists.
|
|
30
30
|
2. If it exists, read it before editing and preserve unrelated code.
|
|
31
|
-
3. If it does not exist,
|
|
31
|
+
3. If it does not exist, synthesize a focused starter for the user's request.
|
|
32
32
|
4. If the user asks to migrate, import a `.sh` file, or match a shell prompt, read `references/migration.md`.
|
|
33
33
|
5. If API details or concrete patterns are needed, read `references/api.md` and `references/examples.md`.
|
|
34
|
-
6. If the request combines statusline work with commands, tools, events, panels, or stateful mod behavior, also use `creating-mods` and its `references/architecture.md`.
|
|
35
|
-
7. Guard
|
|
34
|
+
6. If the request combines statusline work with commands, tools, events, other panels, or stateful mod behavior, also use `creating-mods` and its `references/architecture.md`.
|
|
35
|
+
7. Guard panel work with `letta.capabilities.ui.panels` when writing new files.
|
|
36
36
|
8. Edit `~/.letta/mods/statusline.tsx`.
|
|
37
37
|
9. Summarize the absolute file path changed and tell the user to run `/reload` unless the command can reload automatically.
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ If the user ran `/statusline` without a specific request:
|
|
|
42
42
|
|
|
43
43
|
- If a custom statusline file exists, summarize what it appears to do and ask what they want to change.
|
|
44
44
|
- If no custom file exists, explain that Letta is using the built-in default statusline and offer focused next steps:
|
|
45
|
-
1. start from
|
|
45
|
+
1. start from a simple `agent · model` statusline
|
|
46
46
|
2. add project info like git branch, worktree, or PR
|
|
47
47
|
3. migrate an existing legacy statusline `.sh` file
|
|
48
48
|
4. match shell prompt / PS1
|
|
@@ -56,16 +56,15 @@ Keep this conversational. Do not build a menu UI unless the product command expl
|
|
|
56
56
|
- Keep the mod single-file for MVP.
|
|
57
57
|
- Do not assume extra npm packages are available.
|
|
58
58
|
- Do not use relative multi-file imports yet.
|
|
59
|
-
- Keep
|
|
60
|
-
- Do async work in setup code, intervals, subscriptions,
|
|
61
|
-
-
|
|
62
|
-
- Guard
|
|
63
|
-
- Return a disposer that clears timers/subscriptions.
|
|
59
|
+
- Keep `render` synchronous and side-effect-free. Do not shell, fetch, await, or read files inside render.
|
|
60
|
+
- Do async work in setup code, intervals, or subscriptions, store the result in a closure variable, then call `panel.update()` to re-render.
|
|
61
|
+
- Register the statusline at `order: 0`. Compose left/right with `row(left, right, width)`; color with `chalk`.
|
|
62
|
+
- Guard panel work with `letta.capabilities.ui.panels` in new files.
|
|
63
|
+
- Return a disposer that clears timers/subscriptions and calls `panel.close()`.
|
|
64
64
|
- Preserve existing mod code unless the user asks to reset.
|
|
65
|
-
- Do not delete legacy command statusline files or settings unless the user explicitly asks.
|
|
66
65
|
|
|
67
66
|
## Useful references
|
|
68
67
|
|
|
69
|
-
- `references/api.md` -
|
|
68
|
+
- `references/api.md` - panel API, render context, lifecycle rules
|
|
70
69
|
- `references/examples.md` - common statusline patterns
|
|
71
70
|
- `references/migration.md` - legacy command `.sh` and PS1 migration
|
|
@@ -12,48 +12,74 @@ This is a trusted, user-owned global mod file. Project mods are intentionally un
|
|
|
12
12
|
|
|
13
13
|
## Activation
|
|
14
14
|
|
|
15
|
-
Export a default function or named `activate` function:
|
|
15
|
+
Export a default function or named `activate` function. The statusline is a panel at `order: 0`:
|
|
16
16
|
|
|
17
17
|
```tsx
|
|
18
18
|
export default function activate(letta) {
|
|
19
|
-
if (!letta.capabilities.ui.
|
|
20
|
-
|
|
21
|
-
letta.ui.
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if (!letta.capabilities.ui.panels) return;
|
|
20
|
+
|
|
21
|
+
const panel = letta.ui.openPanel({
|
|
22
|
+
id: "statusline",
|
|
23
|
+
order: 0, // primary line: overrides the built-in agent · model
|
|
24
|
+
render: ({ width, agent, model, row, chalk }) => {
|
|
25
|
+
const left = chalk.cyan(agent.name ?? "Letta");
|
|
26
|
+
const right = chalk.dim(model.displayName ?? "no model");
|
|
27
|
+
return row(left, right, width);
|
|
28
|
+
},
|
|
24
29
|
});
|
|
30
|
+
|
|
31
|
+
return () => panel.close();
|
|
25
32
|
}
|
|
26
33
|
```
|
|
27
34
|
|
|
28
35
|
## API
|
|
29
36
|
|
|
30
37
|
```ts
|
|
31
|
-
letta.capabilities.ui.
|
|
32
|
-
letta.capabilities.ui.customStatuslineRenderer: boolean
|
|
38
|
+
letta.capabilities.ui.panels: boolean
|
|
33
39
|
|
|
34
|
-
letta.ui.
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
letta.ui.openPanel(options: {
|
|
41
|
+
id: string;
|
|
42
|
+
order?: number; // default 100
|
|
43
|
+
render: (ctx) => string | string[];
|
|
44
|
+
}): { close(): void; update(opts?: { order?: number }): void }
|
|
45
|
+
|
|
46
|
+
letta.ui.closePanel(id: string): void
|
|
37
47
|
```
|
|
38
48
|
|
|
39
|
-
`
|
|
49
|
+
`openPanel` registers (or replaces, by `id`) a panel. `render` returns the panel body as a string or an array of strings (one per line). Call the returned handle's `update()` to re-render after state changes, and `close()` to remove it.
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
## Order placement
|
|
52
|
+
|
|
53
|
+
`order` is a signed coordinate around the input:
|
|
54
|
+
|
|
55
|
+
- `order > 0` — above the input. Higher numbers render nearer the top. Default when omitted is `100`.
|
|
56
|
+
- `order === 0` — the primary line just below the input. Overrides the built-in `agent · model`. Use this for the statusline.
|
|
57
|
+
- `order < 0` — stacks below the primary line. `-1` sits closest to it, more-negative lower.
|
|
58
|
+
|
|
59
|
+
A panel whose `render` returns empty (`""` or `[]`, or only blank lines) is hidden entirely — no blank row.
|
|
43
60
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
## Render context
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
render(ctx: {
|
|
65
|
+
width: number; // visible columns available to the panel
|
|
66
|
+
agent: { id, name }; // live at render time
|
|
67
|
+
model: { id, displayName, provider, reasoningEffort };
|
|
68
|
+
row(left, right, width): string; // left + right, right-aligned, ANSI-aware
|
|
69
|
+
columns(parts: string[], width): string; // spread parts evenly, ANSI-aware
|
|
70
|
+
chalk: ChalkInstance; // color helper
|
|
71
|
+
}): string | string[]
|
|
48
72
|
```
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
- `row`/`columns` measure visible width with ANSI stripped, so chalk-colored segments align correctly.
|
|
75
|
+
- The host clips each line to `width` and caps total height; the mod owns layout within that.
|
|
76
|
+
|
|
77
|
+
## Render rules
|
|
51
78
|
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
- Do
|
|
55
|
-
-
|
|
56
|
-
- Return `null` only when intentionally rendering nothing.
|
|
79
|
+
- `render` must be synchronous and side-effect-free.
|
|
80
|
+
- Do not run shell commands, network requests, file reads, or awaits inside `render`.
|
|
81
|
+
- Do async work in setup code or intervals, store the result in a closure variable, then call `panel.update()`.
|
|
82
|
+
- Return `""` (or `[]`) to render nothing.
|
|
57
83
|
|
|
58
84
|
## Async state pattern
|
|
59
85
|
|
|
@@ -66,92 +92,54 @@ import { promisify } from "node:util";
|
|
|
66
92
|
const execFileAsync = promisify(execFile);
|
|
67
93
|
|
|
68
94
|
export default function activate(letta) {
|
|
69
|
-
if (!letta.capabilities.ui.
|
|
95
|
+
if (!letta.capabilities.ui.panels) return;
|
|
96
|
+
|
|
97
|
+
let branch = "";
|
|
98
|
+
|
|
99
|
+
const panel = letta.ui.openPanel({
|
|
100
|
+
id: "statusline",
|
|
101
|
+
order: 0,
|
|
102
|
+
render: ({ width, agent, row, chalk }) => {
|
|
103
|
+
const left = branch ? chalk.green(`\u2442 ${branch}`) : (agent.name ?? "Letta");
|
|
104
|
+
return row(left, "", width);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
70
107
|
|
|
71
108
|
const update = async () => {
|
|
72
109
|
try {
|
|
73
110
|
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
74
111
|
cwd: process.cwd(),
|
|
75
112
|
});
|
|
76
|
-
|
|
77
|
-
letta.ui.setStatus("branch", stdout.trim());
|
|
78
|
-
}
|
|
113
|
+
branch = stdout.trim();
|
|
79
114
|
} catch {
|
|
80
|
-
|
|
81
|
-
letta.ui.clearStatus("branch");
|
|
82
|
-
}
|
|
115
|
+
branch = "";
|
|
83
116
|
}
|
|
117
|
+
panel.update();
|
|
84
118
|
};
|
|
85
119
|
|
|
86
|
-
letta.ui.setStatuslineRenderer((context) => {
|
|
87
|
-
const { Text } = context.components;
|
|
88
|
-
const branch = context.statuses.branch;
|
|
89
|
-
return <Text>{branch ? `branch ${branch}` : context.agent.name}</Text>;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
120
|
void update();
|
|
93
121
|
const timer = setInterval(update, 30_000);
|
|
94
122
|
|
|
95
123
|
return () => {
|
|
96
124
|
clearInterval(timer);
|
|
97
|
-
|
|
98
|
-
letta.ui.clearStatus("branch");
|
|
99
|
-
}
|
|
125
|
+
panel.close();
|
|
100
126
|
};
|
|
101
127
|
}
|
|
102
128
|
```
|
|
103
129
|
|
|
104
|
-
##
|
|
105
|
-
|
|
106
|
-
The app statusline render context source types live near:
|
|
107
|
-
|
|
108
|
-
```text
|
|
109
|
-
src/cli/display/statusline/types.ts
|
|
110
|
-
src/cli/display/statusline/context.ts
|
|
111
|
-
```
|
|
130
|
+
## Full-row layout
|
|
112
131
|
|
|
113
|
-
|
|
132
|
+
There is no host left/right API. Build left/right alignment inside `render` with `row`:
|
|
114
133
|
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
context.app.version
|
|
119
|
-
context.workspace.cwd
|
|
120
|
-
context.workspace.currentDir
|
|
121
|
-
context.workspace.projectDir
|
|
122
|
-
context.agent.name
|
|
123
|
-
context.agent.id
|
|
124
|
-
context.model.id
|
|
125
|
-
context.model.displayName
|
|
126
|
-
context.model.provider
|
|
127
|
-
context.model.reasoningEffort
|
|
128
|
-
context.permissionMode
|
|
129
|
-
context.terminalWidth
|
|
130
|
-
context.contextWindow.usedPercentage
|
|
131
|
-
context.contextWindow.remainingPercentage
|
|
132
|
-
context.cost.totalDurationMs
|
|
133
|
-
context.cost.totalCostUsd
|
|
134
|
-
context.reflection
|
|
135
|
-
context.memfs
|
|
136
|
-
context.backgroundAgents
|
|
137
|
-
context.rawPayload // compatibility payload for advanced cases
|
|
134
|
+
```tsx
|
|
135
|
+
render: ({ width, agent, model, row, chalk }) =>
|
|
136
|
+
row(chalk.dim("Press / for commands"), `${agent.name ?? "Letta"} \u00b7 ${model.displayName ?? ""}`, width),
|
|
138
137
|
```
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
## Full-row layout
|
|
143
|
-
|
|
144
|
-
New statuslines do not have a host left/right API. To create left/right visual alignment, do it inside the renderer:
|
|
139
|
+
Use `columns` for three or more evenly-spread segments:
|
|
145
140
|
|
|
146
141
|
```tsx
|
|
147
|
-
|
|
148
|
-
<Box flexDirection="row">
|
|
149
|
-
<Box flexGrow={1}>
|
|
150
|
-
<Text>left content</Text>
|
|
151
|
-
</Box>
|
|
152
|
-
<Text>right content</Text>
|
|
153
|
-
</Box>
|
|
154
|
-
);
|
|
142
|
+
render: ({ width, columns }) => columns(["left", "middle", "right"], width),
|
|
155
143
|
```
|
|
156
144
|
|
|
157
145
|
## Reload behavior
|
|
@@ -162,4 +150,4 @@ After editing `~/.letta/mods/statusline.tsx`, tell the user to run:
|
|
|
162
150
|
/reload
|
|
163
151
|
```
|
|
164
152
|
|
|
165
|
-
The runtime tracks mod loading
|
|
153
|
+
The runtime tracks mod loading so a custom statusline does not flash back to the built-in default during reload.
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
# Statusline Examples
|
|
2
2
|
|
|
3
|
-
Use these as patterns, not mandatory templates. Keep the final mod focused on the user's request.
|
|
3
|
+
Use these as patterns, not mandatory templates. Keep the final mod focused on the user's request. All register at `order: 0` (the primary line) and return text composed with `row`/`columns`/`chalk`.
|
|
4
4
|
|
|
5
5
|
## Agent and model
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
export default function activate(letta) {
|
|
9
|
-
if (!letta.capabilities.ui.
|
|
10
|
-
|
|
11
|
-
letta.ui.
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
if (!letta.capabilities.ui.panels) return;
|
|
10
|
+
|
|
11
|
+
const panel = letta.ui.openPanel({
|
|
12
|
+
id: "statusline",
|
|
13
|
+
order: 0,
|
|
14
|
+
render: ({ width, agent, model, row, chalk }) =>
|
|
15
|
+
row(
|
|
16
|
+
chalk.cyan(agent.name ?? "Letta"),
|
|
17
|
+
chalk.dim(model.displayName ?? "no model"),
|
|
18
|
+
width,
|
|
19
|
+
),
|
|
14
20
|
});
|
|
21
|
+
|
|
22
|
+
return () => panel.close();
|
|
15
23
|
}
|
|
16
24
|
```
|
|
17
25
|
|
|
@@ -24,28 +32,35 @@ import { promisify } from "node:util";
|
|
|
24
32
|
const execFileAsync = promisify(execFile);
|
|
25
33
|
|
|
26
34
|
export default function activate(letta) {
|
|
27
|
-
if (!letta.capabilities.ui.
|
|
35
|
+
if (!letta.capabilities.ui.panels) return;
|
|
36
|
+
|
|
37
|
+
let branch = "";
|
|
38
|
+
|
|
39
|
+
const panel = letta.ui.openPanel({
|
|
40
|
+
id: "statusline",
|
|
41
|
+
order: 0,
|
|
42
|
+
render: ({ width, agent, row, chalk }) =>
|
|
43
|
+
row(branch ? chalk.green(`git ${branch}`) : (agent.name ?? "Letta"), "", width),
|
|
44
|
+
});
|
|
28
45
|
|
|
29
46
|
const update = async () => {
|
|
30
47
|
try {
|
|
31
48
|
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
32
49
|
cwd: process.cwd(),
|
|
33
50
|
});
|
|
34
|
-
|
|
51
|
+
branch = stdout.trim();
|
|
35
52
|
} catch {
|
|
36
|
-
|
|
53
|
+
branch = "";
|
|
37
54
|
}
|
|
55
|
+
panel.update();
|
|
38
56
|
};
|
|
39
57
|
|
|
40
|
-
letta.ui.setStatuslineRenderer((context) => {
|
|
41
|
-
const { Text } = context.components;
|
|
42
|
-
const branch = context.statuses.branch;
|
|
43
|
-
return <Text>{branch ? `git ${branch}` : context.agent.name}</Text>;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
58
|
void update();
|
|
47
59
|
const timer = setInterval(update, 30_000);
|
|
48
|
-
return () =>
|
|
60
|
+
return () => {
|
|
61
|
+
clearInterval(timer);
|
|
62
|
+
panel.close();
|
|
63
|
+
};
|
|
49
64
|
}
|
|
50
65
|
```
|
|
51
66
|
|
|
@@ -53,21 +68,20 @@ export default function activate(letta) {
|
|
|
53
68
|
|
|
54
69
|
```tsx
|
|
55
70
|
export default function activate(letta) {
|
|
56
|
-
if (!letta.capabilities.ui.
|
|
57
|
-
|
|
58
|
-
letta.ui.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<Text>{context.agent.name ?? "Letta"} · {model}</Text>
|
|
68
|
-
</Box>
|
|
69
|
-
);
|
|
71
|
+
if (!letta.capabilities.ui.panels) return;
|
|
72
|
+
|
|
73
|
+
const panel = letta.ui.openPanel({
|
|
74
|
+
id: "statusline",
|
|
75
|
+
order: 0,
|
|
76
|
+
render: ({ width, agent, model, row, chalk }) =>
|
|
77
|
+
row(
|
|
78
|
+
chalk.dim("Press / for commands"),
|
|
79
|
+
`${agent.name ?? "Letta"} \u00b7 ${model.displayName ?? "no model"}`,
|
|
80
|
+
width,
|
|
81
|
+
),
|
|
70
82
|
});
|
|
83
|
+
|
|
84
|
+
return () => panel.close();
|
|
71
85
|
}
|
|
72
86
|
```
|
|
73
87
|
|
|
@@ -80,7 +94,15 @@ import { promisify } from "node:util";
|
|
|
80
94
|
const execFileAsync = promisify(execFile);
|
|
81
95
|
|
|
82
96
|
export default function activate(letta) {
|
|
83
|
-
if (!letta.capabilities.ui.
|
|
97
|
+
if (!letta.capabilities.ui.panels) return;
|
|
98
|
+
|
|
99
|
+
let pr = "";
|
|
100
|
+
|
|
101
|
+
const panel = letta.ui.openPanel({
|
|
102
|
+
id: "statusline",
|
|
103
|
+
order: 0,
|
|
104
|
+
render: ({ width, model, row }) => row(pr || (model.displayName ?? ""), "", width),
|
|
105
|
+
});
|
|
84
106
|
|
|
85
107
|
const update = async () => {
|
|
86
108
|
try {
|
|
@@ -89,21 +111,19 @@ export default function activate(letta) {
|
|
|
89
111
|
["pr", "view", "--json", "number,title", "--jq", "\"#\\(.number) \\(.title)\""],
|
|
90
112
|
{ cwd: process.cwd() },
|
|
91
113
|
);
|
|
92
|
-
|
|
93
|
-
pr ? letta.ui.setStatus("pr", pr) : letta.ui.clearStatus("pr");
|
|
114
|
+
pr = stdout.trim();
|
|
94
115
|
} catch {
|
|
95
|
-
|
|
116
|
+
pr = "";
|
|
96
117
|
}
|
|
118
|
+
panel.update();
|
|
97
119
|
};
|
|
98
120
|
|
|
99
|
-
letta.ui.setStatuslineRenderer((context) => {
|
|
100
|
-
const { Text } = context.components;
|
|
101
|
-
return <Text>{context.statuses.pr ?? context.model.displayName}</Text>;
|
|
102
|
-
});
|
|
103
|
-
|
|
104
121
|
void update();
|
|
105
122
|
const timer = setInterval(update, 60_000);
|
|
106
|
-
return () =>
|
|
123
|
+
return () => {
|
|
124
|
+
clearInterval(timer);
|
|
125
|
+
panel.close();
|
|
126
|
+
};
|
|
107
127
|
}
|
|
108
128
|
```
|
|
109
129
|
|
|
@@ -116,26 +136,34 @@ import { promisify } from "node:util";
|
|
|
116
136
|
const execFileAsync = promisify(execFile);
|
|
117
137
|
|
|
118
138
|
export default function activate(letta) {
|
|
119
|
-
if (!letta.capabilities.ui.
|
|
139
|
+
if (!letta.capabilities.ui.panels) return;
|
|
140
|
+
|
|
141
|
+
let music = "";
|
|
142
|
+
|
|
143
|
+
const panel = letta.ui.openPanel({
|
|
144
|
+
id: "statusline",
|
|
145
|
+
order: 0,
|
|
146
|
+
render: ({ width, agent, row, chalk }) =>
|
|
147
|
+
row(music ? chalk.magenta(music) : (agent.name ?? "Letta"), "", width),
|
|
148
|
+
});
|
|
120
149
|
|
|
121
150
|
const update = async () => {
|
|
122
151
|
try {
|
|
123
|
-
const script =
|
|
152
|
+
const script =
|
|
153
|
+
'tell application "Music" to if it is running then artist of current track & " - " & name of current track';
|
|
124
154
|
const { stdout } = await execFileAsync("osascript", ["-e", script]);
|
|
125
|
-
|
|
126
|
-
music ? letta.ui.setStatus("music", music) : letta.ui.clearStatus("music");
|
|
155
|
+
music = stdout.trim();
|
|
127
156
|
} catch {
|
|
128
|
-
|
|
157
|
+
music = "";
|
|
129
158
|
}
|
|
159
|
+
panel.update();
|
|
130
160
|
};
|
|
131
161
|
|
|
132
|
-
letta.ui.setStatuslineRenderer((context) => {
|
|
133
|
-
const { Text } = context.components;
|
|
134
|
-
return <Text>{context.statuses.music ?? context.agent.name}</Text>;
|
|
135
|
-
});
|
|
136
|
-
|
|
137
162
|
void update();
|
|
138
163
|
const timer = setInterval(update, 15_000);
|
|
139
|
-
return () =>
|
|
164
|
+
return () => {
|
|
165
|
+
clearInterval(timer);
|
|
166
|
+
panel.close();
|
|
167
|
+
};
|
|
140
168
|
}
|
|
141
169
|
```
|