@letta-ai/letta-code 0.27.16 → 0.27.18
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/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 +16 -2
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/letta.js +129194 -128465
- package/package.json +1 -1
- package/scripts/check-layer-boundaries.js +17 -0
- package/scripts/check-test-coverage.cjs +1 -0
- package/scripts/run-unit-tests.cjs +1 -0
- package/skills/creating-mods/SKILL.md +6 -6
- 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 +126 -9
- package/skills/creating-mods/references/plan-mode.md +1 -1
- package/skills/creating-mods/references/ui.md +40 -25
- 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
|
@@ -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
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Statusline Migration
|
|
2
2
|
|
|
3
|
-
Use this reference when migrating legacy command statuslines, standalone `.sh` statusline scripts, or shell PS1 prompts.
|
|
3
|
+
Use this reference when migrating legacy command statuslines, standalone `.sh` statusline scripts, or shell PS1 prompts into `~/.letta/mods/statusline.tsx`.
|
|
4
4
|
|
|
5
5
|
## Legacy Letta command statusline
|
|
6
6
|
|
|
@@ -40,10 +40,10 @@ When migrating:
|
|
|
40
40
|
|
|
41
41
|
- Preserve old config and referenced files unless the user explicitly asks to delete them.
|
|
42
42
|
- If `command` references a `.sh` file, read it before writing the new mod.
|
|
43
|
-
- Translate polling (`refreshIntervalMs`) to `setInterval`.
|
|
44
|
-
- Translate direct command output into cached
|
|
45
|
-
- If the command output used `\x1e` to split left/right output, convert it to
|
|
46
|
-
- Treat old prompt customization separately. The
|
|
43
|
+
- Translate polling (`refreshIntervalMs`) to `setInterval` + `panel.update()`.
|
|
44
|
+
- Translate direct command output into a cached closure variable plus synchronous rendering.
|
|
45
|
+
- If the command output used `\x1e` to split left/right output, convert it to `row(left, right, width)`; there is no separate left/right API.
|
|
46
|
+
- Treat old prompt customization separately. The statusline controls the primary row, not necessarily the input prompt.
|
|
47
47
|
|
|
48
48
|
Old model:
|
|
49
49
|
|
|
@@ -59,17 +59,36 @@ import { promisify } from "node:util";
|
|
|
59
59
|
|
|
60
60
|
const execFileAsync = promisify(execFile);
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
export default function activate(letta) {
|
|
63
|
+
if (!letta.capabilities.ui.panels) return;
|
|
64
|
+
|
|
65
|
+
let branch = "";
|
|
66
|
+
|
|
67
|
+
const panel = letta.ui.openPanel({
|
|
68
|
+
id: "statusline",
|
|
69
|
+
order: 0,
|
|
70
|
+
render: ({ width, row }) => row(branch, "", width),
|
|
65
71
|
});
|
|
66
|
-
letta.ui.setStatus("branch", stdout.trim());
|
|
67
|
-
};
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
const update = async () => {
|
|
74
|
+
try {
|
|
75
|
+
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
76
|
+
cwd: process.cwd(),
|
|
77
|
+
});
|
|
78
|
+
branch = stdout.trim();
|
|
79
|
+
} catch {
|
|
80
|
+
branch = "";
|
|
81
|
+
}
|
|
82
|
+
panel.update();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
void update();
|
|
86
|
+
const timer = setInterval(update, 30_000);
|
|
87
|
+
return () => {
|
|
88
|
+
clearInterval(timer);
|
|
89
|
+
panel.close();
|
|
90
|
+
};
|
|
91
|
+
}
|
|
73
92
|
```
|
|
74
93
|
|
|
75
94
|
## Standalone `.sh` file migration
|
|
@@ -79,11 +98,11 @@ If the user provides a `.sh` path:
|
|
|
79
98
|
1. Read the script.
|
|
80
99
|
2. Identify commands, expected stdin JSON, environment variables, and output shape.
|
|
81
100
|
3. Port shell commands to async setup/update code.
|
|
82
|
-
4. Store results
|
|
83
|
-
5. Render cached
|
|
84
|
-
6. Preserve graceful fallbacks for missing tools, not-a-git-repo, no PR, etc.
|
|
101
|
+
4. Store results in closure variables.
|
|
102
|
+
5. Render cached state synchronously and call `panel.update()` after each refresh.
|
|
103
|
+
6. Preserve graceful fallbacks for missing tools, not-a-git-repo, no PR, etc. (return `""` to hide the line).
|
|
85
104
|
|
|
86
|
-
|
|
105
|
+
Map the script's stdin JSON to the render context's semantic fields (`agent`, `model`, `width`); compute anything else in setup/update code.
|
|
87
106
|
|
|
88
107
|
## Shell PS1 import
|
|
89
108
|
|
|
@@ -127,4 +146,4 @@ If no PS1 is found and the user did not provide other instructions, ask for one
|
|
|
127
146
|
2. a description of what their prompt shows
|
|
128
147
|
3. the current prompt output as it appears in their terminal
|
|
129
148
|
|
|
130
|
-
Preserve colors where practical
|
|
149
|
+
Preserve colors where practical with `chalk`. If the PS1 is too dynamic to port exactly, ask whether to approximate it or port specific commands.
|