@akanjs/cli 3.0.0-alpha.87 → 3.0.0-alpha.89
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/.build-stamp +1 -1
- package/{abstractCompactor-pwc6d6yk.js → abstractCompactor-cefxzggj.js} +2 -2
- package/{agent.command-180hh0ye.js → agent.command-r93wmq7m.js} +6 -6
- package/{application.command-v8dywdqh.js → application.command-5nqfqgdn.js} +9 -8
- package/{applicationBuildRunner-27wyznpv.js → applicationBuildRunner-b21m07fs.js} +4 -3
- package/{applicationReleasePackager-ktjt0erc.js → applicationReleasePackager-3wh58n2m.js} +2 -2
- package/buildBatch.proc.js +4 -4
- package/{capacitorApp-kwa67kzs.js → capacitorApp-gzcx6txb.js} +3 -3
- package/{cloud.command-12k4wy4x.js → cloud.command-g0pk0k60.js} +11 -11
- package/{context.command-dk2m2sjx.js → context.command-qd8kqrnh.js} +14 -14
- package/{dependencyScanner-c64m6fxp.js → dependencyScanner-shs82ynp.js} +2 -2
- package/devTui-2xf0meaq.js +552 -0
- package/devTuiView-7jy8f30g.js +9 -0
- package/{guideline.command-w6ddv13r.js → guideline.command-f38x8jhw.js} +4 -4
- package/guidelines/agentRule/agentRule.instruction.md +9 -1
- package/guidelines/conventions/conventions.instruction.md +30 -0
- package/guidelines/modelStore/modelStore.instruction.md +12 -0
- package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
- package/guidelines/runtimeRule/runtimeRule.instruction.md +37 -0
- package/incrementalBuilder.proc.js +8 -5
- package/{index-5y7qba1c.js → index-0emb9eat.js} +3 -3
- package/{index-cd5x18qh.js → index-3vvbv974.js} +4 -4
- package/{index-v0s24t8j.js → index-5xdqxbn9.js} +3 -3
- package/{index-k86zc21w.js → index-9bwhxrqf.js} +6 -6
- package/{index-ft1sfjz0.js → index-cezy7yb3.js} +66 -7
- package/{index-a6c5pc4p.js → index-d61rcfa2.js} +614 -62
- package/{index-gz0c482j.js → index-deekbtkj.js} +6 -6
- package/{index-saheq2bs.js → index-e82dyeqy.js} +1 -1
- package/{index-j9vnq8tr.js → index-h759vjfa.js} +3 -3
- package/{index-z3c9mmw4.js → index-jydtyzva.js} +2 -2
- package/{index-mfm35dhh.js → index-kcxmv48c.js} +1 -1
- package/{index-6c3ex9qw.js → index-kfnr6c19.js} +4 -4
- package/{index-1kzsn94d.js → index-mpmyqm1y.js} +1 -1
- package/{index-wet2ytnr.js → index-n85fer99.js} +2 -2
- package/{index-b2nt0w0k.js → index-pgsswppj.js} +1 -1
- package/{index-m9c7k0gm.js → index-pvb4vdwr.js} +1 -1
- package/{index-ayy5ecax.js → index-q0y2jq9j.js} +13 -1
- package/{index-k1n0h7dk.js → index-q8sn827n.js} +254 -126
- package/{index-dq6wq1tv.js → index-r50qen0z.js} +10 -10
- package/{index-5n0hf8r9.js → index-rntcdss1.js} +3 -3
- package/{index-a8h184wr.js → index-sjak5v5b.js} +6 -6
- package/{index-x23688ar.js → index-w8kmx1b6.js} +8 -4
- package/{index-qcracvc9.js → index-y74k5anj.js} +3 -3
- package/{index-xxe90kqy.js → index-zkvpaggp.js} +2 -2
- package/index.js +20 -20
- package/{library.command-wpedct6c.js → library.command-em103wtn.js} +4 -4
- package/{localRegistry.command-3cew8f75.js → localRegistry.command-gz8apn4y.js} +10 -10
- package/{module.command-8n4kct3e.js → module.command-451pztkc.js} +7 -7
- package/{package.command-fcbd0b1d.js → package.command-rfhyf38t.js} +5 -5
- package/package.json +2 -2
- package/{page.command-x4379grq.js → page.command-kt19da5g.js} +4 -4
- package/{primitive.command-8etmnqhq.js → primitive.command-pgy69ere.js} +8 -8
- package/{quality.command-hfx7z8yr.js → quality.command-gpe2vqb5.js} +3 -3
- package/{repair.command-j0fs5qkz.js → repair.command-2sbrz187.js} +6 -6
- package/{scalar.command-2emk89eh.js → scalar.command-jcf4e5sv.js} +6 -6
- package/{subspace.command-nr32nm35.js → subspace.command-kqzqtk0p.js} +6 -6
- package/{workflow.command-cvpmzwjg.js → workflow.command-m4z25a2p.js} +11 -11
- package/{workspace.command-rqnms515.js → workspace.command-dqgj9zbk.js} +22 -22
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
openBrowser
|
|
4
|
+
} from "./index-d61rcfa2.js";
|
|
5
|
+
|
|
6
|
+
// pkgs/@akanjs/cli/application/devTui.tsx
|
|
7
|
+
import { render } from "ink";
|
|
8
|
+
|
|
9
|
+
// pkgs/@akanjs/cli/ui/DevTuiApp.tsx
|
|
10
|
+
import { Box, Text, useInput } from "ink";
|
|
11
|
+
import { useEffect, useState } from "react";
|
|
12
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
13
|
+
var stateGlyph = {
|
|
14
|
+
starting: { glyph: "\u25D0", color: "yellow" },
|
|
15
|
+
ready: { glyph: "\u25CF", color: "green" },
|
|
16
|
+
restarting: { glyph: "\u25D0", color: "yellow" },
|
|
17
|
+
recovering: { glyph: "\u25D0", color: "yellow" },
|
|
18
|
+
suspended: { glyph: "\u25CC", color: "blue" },
|
|
19
|
+
failed: { glyph: "\u2717", color: "red" },
|
|
20
|
+
stopped: { glyph: "\u25CB", color: "gray" }
|
|
21
|
+
};
|
|
22
|
+
var Rail = ({ rows, selected, readyCount, appCount, width, height }) => /* @__PURE__ */ jsxDEV(Box, {
|
|
23
|
+
flexDirection: "column",
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
borderStyle: "round",
|
|
27
|
+
borderColor: "gray",
|
|
28
|
+
paddingX: 1,
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
31
|
+
dimColor: true,
|
|
32
|
+
children: [
|
|
33
|
+
"apps ",
|
|
34
|
+
readyCount,
|
|
35
|
+
"/",
|
|
36
|
+
appCount
|
|
37
|
+
]
|
|
38
|
+
}, undefined, true, undefined, this),
|
|
39
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
40
|
+
flexDirection: "column",
|
|
41
|
+
flexGrow: 1,
|
|
42
|
+
children: rows.map((row, idx) => {
|
|
43
|
+
const marker = row.state ? stateGlyph[row.state] : null;
|
|
44
|
+
return /* @__PURE__ */ jsxDEV(Text, {
|
|
45
|
+
inverse: idx === selected,
|
|
46
|
+
wrap: "truncate",
|
|
47
|
+
children: [
|
|
48
|
+
marker ? /* @__PURE__ */ jsxDEV(Text, {
|
|
49
|
+
color: marker.color,
|
|
50
|
+
children: marker.glyph
|
|
51
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
|
|
52
|
+
dimColor: true,
|
|
53
|
+
children: "\u25C7"
|
|
54
|
+
}, undefined, false, undefined, this),
|
|
55
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
56
|
+
dimColor: true,
|
|
57
|
+
children: " ".repeat(row.depth * 2 + 1)
|
|
58
|
+
}, undefined, false, undefined, this),
|
|
59
|
+
row.appIndex === null ? null : /* @__PURE__ */ jsxDEV(Text, {
|
|
60
|
+
dimColor: true,
|
|
61
|
+
children: `${row.appIndex} `
|
|
62
|
+
}, undefined, false, undefined, this),
|
|
63
|
+
row.label,
|
|
64
|
+
row.port === null ? null : /* @__PURE__ */ jsxDEV(Text, {
|
|
65
|
+
dimColor: true,
|
|
66
|
+
children: ` ${row.port}`
|
|
67
|
+
}, undefined, false, undefined, this)
|
|
68
|
+
]
|
|
69
|
+
}, `${row.app ?? "*"}/${row.source ?? "*"}`, true, undefined, this);
|
|
70
|
+
})
|
|
71
|
+
}, undefined, false, undefined, this)
|
|
72
|
+
]
|
|
73
|
+
}, undefined, true, undefined, this);
|
|
74
|
+
var LogPane = ({ lines, title, above, below, following, prefix, prefixWidth, width, height }) => /* @__PURE__ */ jsxDEV(Box, {
|
|
75
|
+
flexDirection: "column",
|
|
76
|
+
width,
|
|
77
|
+
height,
|
|
78
|
+
borderStyle: "round",
|
|
79
|
+
borderColor: following ? "gray" : "cyan",
|
|
80
|
+
paddingX: 1,
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
83
|
+
wrap: "truncate",
|
|
84
|
+
children: [
|
|
85
|
+
title,
|
|
86
|
+
following ? null : /* @__PURE__ */ jsxDEV(Text, {
|
|
87
|
+
color: "cyan",
|
|
88
|
+
children: ` \u25B2${above} \u25BC${below} paused`
|
|
89
|
+
}, undefined, false, undefined, this)
|
|
90
|
+
]
|
|
91
|
+
}, undefined, true, undefined, this),
|
|
92
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
93
|
+
flexDirection: "column",
|
|
94
|
+
flexGrow: 1,
|
|
95
|
+
children: lines.map((line) => /* @__PURE__ */ jsxDEV(Text, {
|
|
96
|
+
wrap: "truncate",
|
|
97
|
+
children: [
|
|
98
|
+
prefix === "none" ? null : /* @__PURE__ */ jsxDEV(Text, {
|
|
99
|
+
dimColor: true,
|
|
100
|
+
children: `${(prefix === "app" ? line.app : line.source).padEnd(prefixWidth)} \u2502 `
|
|
101
|
+
}, undefined, false, undefined, this),
|
|
102
|
+
line.text
|
|
103
|
+
]
|
|
104
|
+
}, line.seq, true, undefined, this))
|
|
105
|
+
}, undefined, false, undefined, this)
|
|
106
|
+
]
|
|
107
|
+
}, undefined, true, undefined, this);
|
|
108
|
+
var DevTuiApp = ({ actions }) => {
|
|
109
|
+
const [snapshot, setSnapshot] = useState(actions.snapshot);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
const unsubscribe = actions.subscribe(() => setSnapshot(actions.snapshot()));
|
|
112
|
+
setSnapshot(actions.snapshot());
|
|
113
|
+
return unsubscribe;
|
|
114
|
+
}, [actions]);
|
|
115
|
+
useInput((input, key) => {
|
|
116
|
+
if (snapshot.editingGrep) {
|
|
117
|
+
if (key.escape) {
|
|
118
|
+
actions.setGrep("");
|
|
119
|
+
actions.setEditingGrep(false);
|
|
120
|
+
} else if (key.return)
|
|
121
|
+
actions.setEditingGrep(false);
|
|
122
|
+
else if (key.backspace || key.delete)
|
|
123
|
+
actions.setGrep(snapshot.grep.slice(0, -1));
|
|
124
|
+
else if (input && !key.ctrl && !key.meta)
|
|
125
|
+
actions.setGrep(snapshot.grep + input);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const page = Math.max(1, snapshot.logRows - 1);
|
|
129
|
+
if (input === "q" || key.ctrl && input === "c")
|
|
130
|
+
actions.quit();
|
|
131
|
+
else if (key.upArrow)
|
|
132
|
+
actions.scroll(key.shift ? -page : -1);
|
|
133
|
+
else if (key.downArrow)
|
|
134
|
+
actions.scroll(key.shift ? page : 1);
|
|
135
|
+
else if (key.pageUp)
|
|
136
|
+
actions.scroll(-page);
|
|
137
|
+
else if (key.pageDown)
|
|
138
|
+
actions.scroll(page);
|
|
139
|
+
else if (input === "G" || input === "f")
|
|
140
|
+
actions.follow();
|
|
141
|
+
else if (key.tab)
|
|
142
|
+
actions.moveSelection(key.shift ? -1 : 1);
|
|
143
|
+
else if (input === "/")
|
|
144
|
+
actions.setEditingGrep(true);
|
|
145
|
+
else if (input === "e")
|
|
146
|
+
actions.toggleErrorsOnly();
|
|
147
|
+
else if (input === "c")
|
|
148
|
+
actions.clear();
|
|
149
|
+
else if (input === "o")
|
|
150
|
+
actions.openSelected();
|
|
151
|
+
else if (input === "r")
|
|
152
|
+
actions.restartSelected();
|
|
153
|
+
else if (/^[1-9]$/.test(input))
|
|
154
|
+
actions.selectApp(Number(input) - 1);
|
|
155
|
+
});
|
|
156
|
+
return /* @__PURE__ */ jsxDEV(Box, {
|
|
157
|
+
flexDirection: "column",
|
|
158
|
+
width: snapshot.columns,
|
|
159
|
+
height: snapshot.terminalRows,
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
162
|
+
flexDirection: "row",
|
|
163
|
+
height: snapshot.bodyHeight,
|
|
164
|
+
children: [
|
|
165
|
+
/* @__PURE__ */ jsxDEV(Rail, {
|
|
166
|
+
rows: snapshot.rows,
|
|
167
|
+
selected: snapshot.selected,
|
|
168
|
+
readyCount: snapshot.readyCount,
|
|
169
|
+
appCount: snapshot.appCount,
|
|
170
|
+
width: snapshot.railWidth,
|
|
171
|
+
height: snapshot.bodyHeight
|
|
172
|
+
}, undefined, false, undefined, this),
|
|
173
|
+
/* @__PURE__ */ jsxDEV(LogPane, {
|
|
174
|
+
lines: snapshot.lines,
|
|
175
|
+
title: snapshot.title,
|
|
176
|
+
above: snapshot.above,
|
|
177
|
+
below: snapshot.below,
|
|
178
|
+
following: snapshot.following,
|
|
179
|
+
prefix: snapshot.prefix,
|
|
180
|
+
prefixWidth: snapshot.prefixWidth,
|
|
181
|
+
width: Math.max(20, snapshot.columns - snapshot.railWidth),
|
|
182
|
+
height: snapshot.bodyHeight
|
|
183
|
+
}, undefined, false, undefined, this)
|
|
184
|
+
]
|
|
185
|
+
}, undefined, true, undefined, this),
|
|
186
|
+
snapshot.editingGrep ? /* @__PURE__ */ jsxDEV(Text, {
|
|
187
|
+
children: [
|
|
188
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
189
|
+
color: "cyan",
|
|
190
|
+
children: "grep "
|
|
191
|
+
}, undefined, false, undefined, this),
|
|
192
|
+
snapshot.grep,
|
|
193
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
194
|
+
inverse: true,
|
|
195
|
+
children: " "
|
|
196
|
+
}, undefined, false, undefined, this),
|
|
197
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
198
|
+
dimColor: true,
|
|
199
|
+
children: " enter to apply \xB7 esc to clear"
|
|
200
|
+
}, undefined, false, undefined, this)
|
|
201
|
+
]
|
|
202
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
|
|
203
|
+
dimColor: true,
|
|
204
|
+
wrap: "truncate",
|
|
205
|
+
children: `Tab next \xB7 1-9 app \xB7 \u2191\u2193 scroll (shift page) \xB7 G follow${snapshot.following ? "" : " (paused)"} \xB7 / grep${snapshot.grep ? ` (${snapshot.grep})` : ""} \xB7 e errors${snapshot.errorsOnly ? " on" : ""} \xB7 c clear \xB7 o open \xB7 r restart \xB7 q quit`
|
|
206
|
+
}, undefined, false, undefined, this)
|
|
207
|
+
]
|
|
208
|
+
}, undefined, true, undefined, this);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// pkgs/@akanjs/cli/application/devLogBuffer.ts
|
|
212
|
+
var ansi = new RegExp(`${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\[[\\s\\S]*?[@-~])`, "g");
|
|
213
|
+
var stripAnsi = (text) => text.replace(ansi, "");
|
|
214
|
+
var HOST_SOURCE = "host";
|
|
215
|
+
var childPrefix = /^\[child:(\d+) ([a-z-]+)\] \[(?:stdout|stderr)\] /;
|
|
216
|
+
var sourceOf = (text) => {
|
|
217
|
+
const match = childPrefix.exec(text);
|
|
218
|
+
return match ? `#${match[1]} ${match[2]}` : HOST_SOURCE;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
class DevLogBuffer {
|
|
222
|
+
static defaultLimit = 5000;
|
|
223
|
+
#limit;
|
|
224
|
+
#lines = [];
|
|
225
|
+
#partial = new Map;
|
|
226
|
+
#sources = new Map;
|
|
227
|
+
#seq = 0;
|
|
228
|
+
constructor({ limit = DevLogBuffer.defaultLimit } = {}) {
|
|
229
|
+
this.#limit = Math.max(1, limit);
|
|
230
|
+
}
|
|
231
|
+
get size() {
|
|
232
|
+
return this.#lines.length;
|
|
233
|
+
}
|
|
234
|
+
sourcesOf(app) {
|
|
235
|
+
return [...this.#sources.get(app) ?? []];
|
|
236
|
+
}
|
|
237
|
+
push(app, kind, chunk) {
|
|
238
|
+
const key = `${app}:${kind}`;
|
|
239
|
+
const parts = `${this.#partial.get(key) ?? ""}${chunk}`.split(`
|
|
240
|
+
`);
|
|
241
|
+
this.#partial.set(key, parts.pop() ?? "");
|
|
242
|
+
return this.#append(app, kind, parts);
|
|
243
|
+
}
|
|
244
|
+
flushPartials() {
|
|
245
|
+
const flushed = [];
|
|
246
|
+
for (const [key, remainder] of [...this.#partial]) {
|
|
247
|
+
this.#partial.set(key, "");
|
|
248
|
+
if (!remainder)
|
|
249
|
+
continue;
|
|
250
|
+
const split = key.lastIndexOf(":");
|
|
251
|
+
const app = key.slice(0, split);
|
|
252
|
+
const kind = key.slice(split + 1) === "stderr" ? "stderr" : "stdout";
|
|
253
|
+
flushed.push(...this.#append(app, kind, [remainder]));
|
|
254
|
+
}
|
|
255
|
+
return flushed;
|
|
256
|
+
}
|
|
257
|
+
select({ app = null, source = null, grep, errorsOnly = false } = {}) {
|
|
258
|
+
const needle = grep?.trim().toLowerCase();
|
|
259
|
+
return this.#lines.filter((line) => {
|
|
260
|
+
if (app !== null && line.app !== app)
|
|
261
|
+
return false;
|
|
262
|
+
if (source !== null && line.source !== source)
|
|
263
|
+
return false;
|
|
264
|
+
if (errorsOnly && line.kind !== "stderr")
|
|
265
|
+
return false;
|
|
266
|
+
if (needle && !stripAnsi(line.text).toLowerCase().includes(needle))
|
|
267
|
+
return false;
|
|
268
|
+
return true;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
clear(app = null) {
|
|
272
|
+
const kept = app === null ? [] : this.#lines.filter((line) => line.app !== app);
|
|
273
|
+
this.#lines.length = 0;
|
|
274
|
+
this.#lines.push(...kept);
|
|
275
|
+
}
|
|
276
|
+
#append(app, kind, texts) {
|
|
277
|
+
const known = this.#sources.get(app) ?? new Set;
|
|
278
|
+
this.#sources.set(app, known);
|
|
279
|
+
const added = texts.map((text) => {
|
|
280
|
+
const source = sourceOf(text);
|
|
281
|
+
known.add(source);
|
|
282
|
+
return { seq: ++this.#seq, app, source, kind, text };
|
|
283
|
+
});
|
|
284
|
+
this.#lines.push(...added);
|
|
285
|
+
if (this.#lines.length > this.#limit)
|
|
286
|
+
this.#lines.splice(0, this.#lines.length - this.#limit);
|
|
287
|
+
return added;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// pkgs/@akanjs/cli/application/devLogWindow.ts
|
|
292
|
+
var windowOf = (lines, rows, anchor) => {
|
|
293
|
+
const height = Math.max(1, Math.trunc(rows));
|
|
294
|
+
const end = anchor === null ? lines.length : endOf(lines, height, anchor);
|
|
295
|
+
const start = Math.max(0, end - height);
|
|
296
|
+
return {
|
|
297
|
+
lines: lines.slice(start, end),
|
|
298
|
+
above: start,
|
|
299
|
+
below: lines.length - end,
|
|
300
|
+
following: anchor === null
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
var scrollAnchor = (lines, rows, anchor, delta) => {
|
|
304
|
+
const height = Math.max(1, Math.trunc(rows));
|
|
305
|
+
if (lines.length <= height)
|
|
306
|
+
return null;
|
|
307
|
+
const end = anchor === null ? lines.length : endOf(lines, height, anchor);
|
|
308
|
+
const next = Math.min(lines.length, Math.max(height, end + Math.trunc(delta)));
|
|
309
|
+
if (next >= lines.length)
|
|
310
|
+
return null;
|
|
311
|
+
return lines[next - 1]?.seq ?? null;
|
|
312
|
+
};
|
|
313
|
+
var endOf = (lines, height, anchor) => {
|
|
314
|
+
for (let idx = lines.length - 1;idx >= 0; idx -= 1) {
|
|
315
|
+
const line = lines[idx];
|
|
316
|
+
if (line && line.seq <= anchor)
|
|
317
|
+
return idx + 1;
|
|
318
|
+
}
|
|
319
|
+
return Math.min(lines.length, height);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// pkgs/@akanjs/cli/application/devTui.tsx
|
|
323
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
324
|
+
|
|
325
|
+
class DevTui {
|
|
326
|
+
static frameMs = 34;
|
|
327
|
+
static footerRows = 1;
|
|
328
|
+
static paneChromeRows = 3;
|
|
329
|
+
#buffer = new DevLogBuffer;
|
|
330
|
+
#listeners = new Set;
|
|
331
|
+
#supervisor;
|
|
332
|
+
#instance;
|
|
333
|
+
#statuses = [];
|
|
334
|
+
#target = { app: null, source: null };
|
|
335
|
+
#anchor = null;
|
|
336
|
+
#grep = "";
|
|
337
|
+
#editingGrep = false;
|
|
338
|
+
#errorsOnly = false;
|
|
339
|
+
#frameTimer = null;
|
|
340
|
+
#cachedSnapshot = null;
|
|
341
|
+
constructor(supervisor) {
|
|
342
|
+
this.#supervisor = supervisor;
|
|
343
|
+
this.#statuses = supervisor.statuses;
|
|
344
|
+
process.stdout.on("resize", this.#onResize);
|
|
345
|
+
this.#instance = render(/* @__PURE__ */ jsxDEV2(DevTuiApp, {
|
|
346
|
+
actions: {
|
|
347
|
+
subscribe: this.#subscribe,
|
|
348
|
+
snapshot: this.#snapshot,
|
|
349
|
+
moveSelection: this.#moveSelection,
|
|
350
|
+
selectApp: this.#selectApp,
|
|
351
|
+
scroll: this.#scroll,
|
|
352
|
+
follow: this.#follow,
|
|
353
|
+
setGrep: this.#setGrep,
|
|
354
|
+
setEditingGrep: this.#setEditingGrep,
|
|
355
|
+
toggleErrorsOnly: this.#toggleErrorsOnly,
|
|
356
|
+
clear: this.#clear,
|
|
357
|
+
openSelected: this.#openSelected,
|
|
358
|
+
restartSelected: this.#restartSelected,
|
|
359
|
+
quit: this.#quit
|
|
360
|
+
}
|
|
361
|
+
}, undefined, false, undefined, this), { patchConsole: false });
|
|
362
|
+
}
|
|
363
|
+
onOutput = (app, kind, text) => {
|
|
364
|
+
this.#buffer.push(app, kind, text);
|
|
365
|
+
this.#scheduleFrame();
|
|
366
|
+
};
|
|
367
|
+
onStatus = (statuses) => {
|
|
368
|
+
this.#statuses = statuses;
|
|
369
|
+
this.#scheduleFrame();
|
|
370
|
+
};
|
|
371
|
+
onNote = (text, level) => {
|
|
372
|
+
this.#buffer.push("akan", level === "warn" ? "stderr" : "stdout", `${text}
|
|
373
|
+
`);
|
|
374
|
+
this.#scheduleFrame();
|
|
375
|
+
};
|
|
376
|
+
waitForExit = async () => {
|
|
377
|
+
await this.#instance.waitUntilExit();
|
|
378
|
+
};
|
|
379
|
+
close = () => {
|
|
380
|
+
if (this.#frameTimer)
|
|
381
|
+
clearTimeout(this.#frameTimer);
|
|
382
|
+
this.#frameTimer = null;
|
|
383
|
+
process.stdout.off("resize", this.#onResize);
|
|
384
|
+
this.#instance.unmount();
|
|
385
|
+
for (const line of this.#buffer.flushPartials())
|
|
386
|
+
process.stdout.write(`${line.app} \u2502 ${line.text}
|
|
387
|
+
`);
|
|
388
|
+
for (const status of this.#statuses)
|
|
389
|
+
process.stdout.write(`${status.name} ${status.url} \u2014 ${status.state}
|
|
390
|
+
`);
|
|
391
|
+
};
|
|
392
|
+
#subscribe = (listener) => {
|
|
393
|
+
this.#listeners.add(listener);
|
|
394
|
+
return () => {
|
|
395
|
+
this.#listeners.delete(listener);
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
#railRows() {
|
|
399
|
+
const rows = [
|
|
400
|
+
{ app: null, source: null, label: "all apps", state: null, port: null, depth: 0, appIndex: null }
|
|
401
|
+
];
|
|
402
|
+
this.#statuses.forEach((status, idx) => {
|
|
403
|
+
rows.push({
|
|
404
|
+
app: status.name,
|
|
405
|
+
source: null,
|
|
406
|
+
label: status.name,
|
|
407
|
+
state: status.state,
|
|
408
|
+
port: status.port,
|
|
409
|
+
depth: 0,
|
|
410
|
+
appIndex: idx < 9 ? idx + 1 : null
|
|
411
|
+
});
|
|
412
|
+
if (status.name !== this.#target.app)
|
|
413
|
+
return;
|
|
414
|
+
for (const source of this.#buffer.sourcesOf(status.name))
|
|
415
|
+
rows.push({ app: status.name, source, label: source, state: null, port: null, depth: 1, appIndex: null });
|
|
416
|
+
});
|
|
417
|
+
return rows;
|
|
418
|
+
}
|
|
419
|
+
#indexOfTarget(rows) {
|
|
420
|
+
const found = rows.findIndex((row) => row.app === this.#target.app && row.source === this.#target.source);
|
|
421
|
+
return found < 0 ? 0 : found;
|
|
422
|
+
}
|
|
423
|
+
#filtered(target) {
|
|
424
|
+
return this.#buffer.select({
|
|
425
|
+
app: target.app ?? null,
|
|
426
|
+
source: target.source ?? null,
|
|
427
|
+
grep: this.#grep,
|
|
428
|
+
errorsOnly: this.#errorsOnly
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
#snapshot = () => {
|
|
432
|
+
if (this.#cachedSnapshot)
|
|
433
|
+
return this.#cachedSnapshot;
|
|
434
|
+
const terminalRows = Math.max(8, process.stdout.rows ?? 24);
|
|
435
|
+
const columns = Math.max(40, process.stdout.columns ?? 80);
|
|
436
|
+
const bodyHeight = Math.max(DevTui.paneChromeRows + 1, terminalRows - DevTui.footerRows);
|
|
437
|
+
const logRows = Math.max(1, bodyHeight - DevTui.paneChromeRows);
|
|
438
|
+
const rows = this.#railRows();
|
|
439
|
+
const view = windowOf(this.#filtered(this.#target), logRows, this.#anchor);
|
|
440
|
+
const status = this.#statuses.find((candidate) => candidate.name === this.#target.app);
|
|
441
|
+
const railLabelWidth = rows.reduce((max, row) => Math.max(max, row.label.length + row.depth * 2), 0);
|
|
442
|
+
this.#cachedSnapshot = {
|
|
443
|
+
rows,
|
|
444
|
+
selected: this.#indexOfTarget(rows),
|
|
445
|
+
title: status ? `${status.name}${this.#target.source ? ` \xB7 ${this.#target.source}` : ""} \xB7 ${status.url} \xB7 ${status.state}${status.detail ? ` (${status.detail})` : ""}` : "all apps",
|
|
446
|
+
lines: view.lines,
|
|
447
|
+
above: view.above,
|
|
448
|
+
below: view.below,
|
|
449
|
+
following: view.following,
|
|
450
|
+
prefix: this.#target.app === null ? "app" : this.#target.source === null ? "source" : "none",
|
|
451
|
+
prefixWidth: this.#target.app === null ? this.#appWidth() : this.#sourceWidth(),
|
|
452
|
+
grep: this.#grep,
|
|
453
|
+
errorsOnly: this.#errorsOnly,
|
|
454
|
+
editingGrep: this.#editingGrep,
|
|
455
|
+
readyCount: this.#statuses.filter((candidate) => candidate.state === "ready").length,
|
|
456
|
+
appCount: this.#statuses.length,
|
|
457
|
+
logRows,
|
|
458
|
+
bodyHeight,
|
|
459
|
+
railWidth: Math.min(34, Math.max(20, railLabelWidth + 10)),
|
|
460
|
+
columns,
|
|
461
|
+
terminalRows
|
|
462
|
+
};
|
|
463
|
+
return this.#cachedSnapshot;
|
|
464
|
+
};
|
|
465
|
+
#appWidth() {
|
|
466
|
+
return this.#statuses.reduce((max, status) => Math.max(max, status.name.length), 0);
|
|
467
|
+
}
|
|
468
|
+
#sourceWidth() {
|
|
469
|
+
let width = HOST_SOURCE.length;
|
|
470
|
+
for (const status of this.#statuses)
|
|
471
|
+
for (const source of this.#buffer.sourcesOf(status.name))
|
|
472
|
+
width = Math.max(width, source.length);
|
|
473
|
+
return width;
|
|
474
|
+
}
|
|
475
|
+
#scheduleFrame() {
|
|
476
|
+
if (this.#frameTimer)
|
|
477
|
+
return;
|
|
478
|
+
this.#frameTimer = setTimeout(() => {
|
|
479
|
+
this.#frameTimer = null;
|
|
480
|
+
this.#renderNow();
|
|
481
|
+
}, DevTui.frameMs);
|
|
482
|
+
}
|
|
483
|
+
#renderNow() {
|
|
484
|
+
this.#cachedSnapshot = null;
|
|
485
|
+
for (const listener of this.#listeners)
|
|
486
|
+
listener();
|
|
487
|
+
}
|
|
488
|
+
#onResize = () => this.#renderNow();
|
|
489
|
+
#moveSelection = (delta) => {
|
|
490
|
+
const rows = this.#railRows();
|
|
491
|
+
const next = ((this.#indexOfTarget(rows) + delta) % rows.length + rows.length) % rows.length;
|
|
492
|
+
const row = rows[next];
|
|
493
|
+
if (row)
|
|
494
|
+
this.#setTarget({ app: row.app, source: row.source });
|
|
495
|
+
};
|
|
496
|
+
#selectApp = (index) => {
|
|
497
|
+
const status = this.#statuses[index];
|
|
498
|
+
if (status)
|
|
499
|
+
this.#setTarget({ app: status.name, source: null });
|
|
500
|
+
};
|
|
501
|
+
#setTarget(target) {
|
|
502
|
+
this.#target = { app: target.app ?? null, source: target.source ?? null };
|
|
503
|
+
this.#anchor = null;
|
|
504
|
+
this.#renderNow();
|
|
505
|
+
}
|
|
506
|
+
#scroll = (delta) => {
|
|
507
|
+
this.#anchor = scrollAnchor(this.#filtered(this.#target), this.#snapshot().logRows, this.#anchor, delta);
|
|
508
|
+
this.#renderNow();
|
|
509
|
+
};
|
|
510
|
+
#follow = () => {
|
|
511
|
+
this.#anchor = null;
|
|
512
|
+
this.#renderNow();
|
|
513
|
+
};
|
|
514
|
+
#setGrep = (grep) => {
|
|
515
|
+
this.#grep = grep;
|
|
516
|
+
this.#anchor = null;
|
|
517
|
+
this.#renderNow();
|
|
518
|
+
};
|
|
519
|
+
#setEditingGrep = (editing) => {
|
|
520
|
+
this.#editingGrep = editing;
|
|
521
|
+
this.#renderNow();
|
|
522
|
+
};
|
|
523
|
+
#toggleErrorsOnly = () => {
|
|
524
|
+
this.#errorsOnly = !this.#errorsOnly;
|
|
525
|
+
this.#anchor = null;
|
|
526
|
+
this.#renderNow();
|
|
527
|
+
};
|
|
528
|
+
#clear = () => {
|
|
529
|
+
this.#buffer.clear(this.#target.app ?? null);
|
|
530
|
+
this.#anchor = null;
|
|
531
|
+
this.#renderNow();
|
|
532
|
+
};
|
|
533
|
+
#openSelected = () => {
|
|
534
|
+
const status = this.#selectedStatus();
|
|
535
|
+
if (status)
|
|
536
|
+
openBrowser(status.url);
|
|
537
|
+
};
|
|
538
|
+
#restartSelected = () => {
|
|
539
|
+
const status = this.#selectedStatus();
|
|
540
|
+
if (status)
|
|
541
|
+
this.#supervisor.restart(status.name);
|
|
542
|
+
};
|
|
543
|
+
#selectedStatus() {
|
|
544
|
+
return this.#statuses.find((candidate) => candidate.name === this.#target.app);
|
|
545
|
+
}
|
|
546
|
+
#quit = () => {
|
|
547
|
+
this.#instance.unmount();
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
export {
|
|
551
|
+
DevTui
|
|
552
|
+
};
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
runner,
|
|
5
5
|
script,
|
|
6
6
|
command
|
|
7
|
-
} from "./index-
|
|
8
|
-
import"./index-k1n0h7dk.js";
|
|
7
|
+
} from "./index-cezy7yb3.js";
|
|
9
8
|
import {
|
|
10
9
|
FileSys
|
|
11
|
-
} from "./index-
|
|
10
|
+
} from "./index-q0y2jq9j.js";
|
|
11
|
+
import"./index-q8sn827n.js";
|
|
12
12
|
import {
|
|
13
13
|
getDirname2
|
|
14
14
|
} from "./index-6wwzs1bc.js";
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "./index-h7yw2z9n.js";
|
|
18
18
|
import {
|
|
19
19
|
AiSession
|
|
20
|
-
} from "./index-
|
|
20
|
+
} from "./index-n85fer99.js";
|
|
21
21
|
|
|
22
22
|
// pkgs/@akanjs/cli/guideline/guideline.script.ts
|
|
23
23
|
import { Logger } from "akanjs/common";
|
|
@@ -46,7 +46,7 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
46
46
|
already titles, and `defaultDraft` opens the composer with text in it without sending it. A panel driven by a
|
|
47
47
|
controlled `open` with **no** `onOpenChange` draws no close button at all rather than an inert one. Then the
|
|
48
48
|
slots: `AgentLauncher`, `AgentBubble`, `AgentComposer`, `AgentApproval`,
|
|
49
|
-
`AgentQuestion`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
|
|
49
|
+
`AgentQuestion`, `AgentQueued`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
|
|
50
50
|
`akanjs/ui` exports every default beside them (`DefaultBubble`, `DefaultComposer`, …) so a skin composes the
|
|
51
51
|
one it is replacing. `AgentCode` is where a highlighter binds — the fence's language reaches it — and a
|
|
52
52
|
replacement for `AgentBubble` carries its own `memo`, since the transcript re-renders on every delta. Only then
|
|
@@ -59,6 +59,14 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
59
59
|
it scrolls. The vertical arrows still walk what was sent, but only from the first or last line — anywhere else
|
|
60
60
|
the caret is the textarea's own. On a phone the panel is the whole screen (it is a card from `sm:` up) and it
|
|
61
61
|
lifts above the on-screen keyboard, which only `visualViewport` reports.
|
|
62
|
+
**Enter during a running turn parks the message and sends it the moment the turn ends** — the Queue button
|
|
63
|
+
beside Stop does the same, and the placeholder says so. One slot, not a list: a second send joins the first on
|
|
64
|
+
a new line, so the model is handed one user message. The card above the composer (`AgentQueued`) shows what is
|
|
65
|
+
waiting with two ways out — ✎ takes it back into the composer, ahead of whatever was typed since, and ✕ drops it.
|
|
66
|
+
**Stop hands a parked message back to the composer rather than opening the next turn with it**, so Stop means
|
|
67
|
+
stop; `/new` drops it along with the conversation it was written for, the way it drops staged files. A `/prompt`
|
|
68
|
+
parks like text and runs when its turn comes; the built-ins (`/new`, `/copy`, …) never park, and a question the
|
|
69
|
+
agent is waiting on still takes the composer as its answer.
|
|
62
70
|
- **The LLM is configured in `option.ts`, never through the environment.** `option.setLlm({ apiKey, model, host })`
|
|
63
71
|
— or `setLlm((options) => …)` to read the key out of the app's own env object, which is where a secret belongs —
|
|
64
72
|
fills whichever adaptor holds `LlmAdaptorRole`, reaching it as the `llmOption` use. The settings are the role's
|
|
@@ -302,6 +302,14 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
|
|
|
302
302
|
The RSC worker is always its own process (`--conditions react-server`). A solo process answers
|
|
303
303
|
`/_akan/app/health`, `/_akan/app/metrics` and `/_akan/bench/ping` in the gateway's own shape, and owns the
|
|
304
304
|
rotating log file; nothing supervises it but the orchestrator's probes.
|
|
305
|
+
- **Route prefixes move together or not at all.** `new AkanApp({ prefix, websocketPrefix })` in `main.ts` moves
|
|
306
|
+
the server's routes, the gateway's websocket upgrade and — through the SSR bootstrap script — the
|
|
307
|
+
`fetchClient` in every tab the server renders. A prebuilt CSR shell or a Capacitor bundle is never rendered by
|
|
308
|
+
a server, so `api: { prefix, websocketPrefix }` in `akan.config.ts` is what those follow; declare it there too
|
|
309
|
+
when the app ships either. Read the value with `getApiPrefix()` / `getWsPrefix()` from `akanjs/base` — never
|
|
310
|
+
write `/api` or `/ws` as a literal, and never try to hand it down as a React prop: `FetchClient` fixes its
|
|
311
|
+
origin before the first component renders. A blank prefix, a bare `/`, and one that shadows a basePath are all
|
|
312
|
+
refused.
|
|
305
313
|
- **`main.ts` imports `AkanApp` from `akanjs/server/akanApp`, not the barrel** — the barrel re-exports
|
|
306
314
|
`AkanServer`, whose graph the gateway never runs. Keep entrypoint imports at the leaf.
|
|
307
315
|
- **Every `Logger` call builds a `LogRecord`** carrying `traceId`, `endpoint` and `origin` alongside the message,
|
|
@@ -338,6 +346,15 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
|
|
|
338
346
|
- Avoid hooks. `useState` is for modal-open, tab, draft-input, and drag state only — never for server data. `useEffect` must be a genuine effect such as subscribe-with-cleanup or one-shot init. Prefer `Tab` over a `useState` mode switch. `.Template.tsx` files contain zero `useState`.
|
|
339
347
|
- Forms are entirely store-driven: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`, the setter passed by reference. Always use `Field.*`, never a bare `<input>` for a model field. Nested rows use `st.do.writeOnX("payments.3.name", v)` plus the generated `add<Field>OnX` / `sub<Field>OnX`. **Passing the setter by reference is also what makes the framework emit `data-akan-action` / `data-akan-state`** on the control — the annotation an in-page agent, an E2E selector, and an external browser agent all read. Wrapping it in an inline arrow (`onChange={(v) => st.do.setFieldOnX(v)}`) silently drops that: a closure the caller wrote says nothing about what it does. Never hand-write a `data-akan-*` attribute.
|
|
340
348
|
- Read with `st.use.*` and write with `st.do.*`. Client components do not call `fetch.*`.
|
|
349
|
+
- **An edit shell recovers its own form — never persist form values yourself.** `Load.Edit`, `Model.EditModal` and
|
|
350
|
+
`Model.New` save the whole `<model>Form` as the user types and offer it back on the next open, so a modal closed
|
|
351
|
+
by accident, a route change, or a killed app costs nothing. The scope is the record id for an edit and the seed
|
|
352
|
+
plus the route for a new form, both keyed to the signed-in user, so another record or another parent never loads
|
|
353
|
+
someone else's half-finished form. A new form's draft is applied on open with a "start over" chip; an edit whose
|
|
354
|
+
record moved in the meantime is *not* applied, and asks. Turn it off with `draft={false}`, or name the scope
|
|
355
|
+
yourself with `draft="<scope>"` when the context is in neither the id nor the seed. `field.secret` and
|
|
356
|
+
`field.hidden` values are never saved. The old per-field `cache` / `cacheKey` props are deprecated and store
|
|
357
|
+
nothing — they covered five control types, keyed on the translated label, and restored over server data.
|
|
341
358
|
- Static class strings stay plain strings. Reach for `cn` only for a conditional or to merge an incoming `className`, and merge the caller last: `cn("base classes", cond && "extra", className)`. `cn` comes from `akanjs/client` (token-aware tailwind-merge) and is the only class-combining function — no `clsx` (removed), no raw `twMerge` imports, no object syntax (`{ x: cond }` → `cond && "x"`).
|
|
342
359
|
- Multi-slot components take extra named props (`wrapperClassName`, `bodyClassName`), never a `classNames` object.
|
|
343
360
|
- Hoist enum→class lookups to a module-scope `as const` map typed `{ [key in cnst.XStatus["value"]]: string }`. Do not use `Record<...>`. Escalate the map to `webkit/` when a second module needs it.
|
|
@@ -837,6 +854,19 @@ when two shapes disagree.
|
|
|
837
854
|
## Application Test Commands
|
|
838
855
|
|
|
839
856
|
- After changing application source code, test the app with `bun run akan start <appName>`.
|
|
857
|
+
- **`akan start` takes several apps** — `akan start a,b`, `akan start a b`, `akan start all`, or no argument at
|
|
858
|
+
all for a checkbox that remembers the last pick.
|
|
859
|
+
- **It opens a full-screen view by default, at any app count.** A rail of apps with the processes inside each
|
|
860
|
+
one — `host` for the dev host, gateway and RSC worker, plus a row per replica the gateway forwards — and the
|
|
861
|
+
selected one's log beside it. `Tab` walks every row and `1`-`9` jump to that app (the rail shows each
|
|
862
|
+
app's digit), `↑↓` scroll (shift for a page, `G` back to following),
|
|
863
|
+
`/` greps, `e` shows stderr only, `c` clears, `o` opens the browser, `r` restarts that app, `q` quits.
|
|
864
|
+
**`--plain` prints prefixed interleaved lines instead**, and a pipe, a redirect or an unsized terminal
|
|
865
|
+
downgrades to that on its own.
|
|
866
|
+
- `--concurrency <n>` boots more than one app at a time; the default of 1 is what keeps two cold builder peaks
|
|
867
|
+
from overlapping. `--kill` frees the dev ports first — it resolves each port's listener, walks up to the top
|
|
868
|
+
of that akan dev tree and signals it, so another checkout's server or a stale orphan on the same port is
|
|
869
|
+
reclaimed. A holder that is not recognisably an akan process is reported and left alone.
|
|
840
870
|
- Test production build generation with `bun run akan build <appName>`.
|
|
841
871
|
- To test a built artifact locally, run it from the generated app directory with the required Akan runtime environment variables.
|
|
842
872
|
|
|
@@ -21,6 +21,18 @@ Generate client state coordination for a module. Store connects generated fetch
|
|
|
21
21
|
- Do not perform persistence logic in React components when a store action can own it.
|
|
22
22
|
- Do not call private or internal signal operations from UI state.
|
|
23
23
|
|
|
24
|
+
## Form Draft Recovery
|
|
25
|
+
- `<model>FormDraft`, `restore<Model>FormDraft`, `discard<Model>FormDraft` and `load<Model>FormDraft` are
|
|
26
|
+
generated. Do not hand-write a draft action, and do not write `<model>Form` to browser storage yourself.
|
|
27
|
+
- The draft key carries the signed-in user, derived by hashing the auth token's payload with `iat`, `exp`, `nbf`
|
|
28
|
+
and `jti` removed. **Do not put a claim that changes on every issue into the JWT.** One would rotate the key on
|
|
29
|
+
every silent token refresh, and the symptom is a form that never recovers anything: the draft was written under
|
|
30
|
+
a key nothing looks for again, and it lingers until the seven-day sweep.
|
|
31
|
+
- `field.secret` and `field.hidden` values are never written to a draft, so they come back empty. That is the
|
|
32
|
+
intended trade — a user retypes a password — and is not something to work around.
|
|
33
|
+
- A model whose form holds a large `field.visual` body can pass the 256KB per-draft cap, and a draft over it is
|
|
34
|
+
dropped with a warning rather than truncated.
|
|
35
|
+
|
|
24
36
|
## Review Checklist
|
|
25
37
|
- The instruction points to current docs pages, not removed docs routes.
|
|
26
38
|
- Generated examples use current Akan builder APIs and scanner-friendly filenames.
|