@fungi.computer/bonsai 0.0.1 → 0.1.0
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/CHANGELOG.md +26 -0
- package/backend/methods.ts +13 -0
- package/package.json +9 -9
- package/scripts/shed-terminal-proof.mjs +20 -20
- package/test/present.test.ts +27 -0
- package/vitest.config.ts +43 -1
- package/web/change-page.tsx +10 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @fungi.computer/bonsai
|
|
2
2
|
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Changes since 0.0.1:
|
|
8
|
+
|
|
9
|
+
- fix(agar): keep the result-row decoder out of Apps that do not present
|
|
10
|
+
- refactor(caps): one status bead, the gooey one, everywhere
|
|
11
|
+
- feat(shed): one Ghostty terminal for the light and full shells
|
|
12
|
+
- test(bonsai): cheaper real-git tests and a real-git vitest project
|
|
13
|
+
- feat(apps): describe finished Prints, Inkcap, Shed and Bonsai calls
|
|
14
|
+
- feat(oyster): public release 0.1.0
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- Updated dependencies
|
|
23
|
+
- @fungi.computer/agar@0.2.0
|
|
24
|
+
- @fungi.computer/caps@0.1.0
|
|
25
|
+
- @fungi.computer/gills@0.1.0
|
|
26
|
+
- @fungi.computer/oyster@0.2.0
|
|
27
|
+
- @fungi.computer/puffball@0.1.0
|
|
28
|
+
|
|
3
29
|
## 0.0.1
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/backend/methods.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resultRows } from "@fungi.computer/agar/method-presentation";
|
|
1
2
|
import { openChangesGill, recentCommitsGill } from "./gills.js";
|
|
2
3
|
import {
|
|
3
4
|
pickerOptionsInput,
|
|
@@ -339,6 +340,18 @@ const definition = defineAppBackend<BonsaiMethodContext>({
|
|
|
339
340
|
requires: [],
|
|
340
341
|
input: z.strictObject({}),
|
|
341
342
|
output: repoPage,
|
|
343
|
+
present: {
|
|
344
|
+
rows: resultRows,
|
|
345
|
+
verb: "listed",
|
|
346
|
+
row: ({ output }) => ({
|
|
347
|
+
id: "repos.list",
|
|
348
|
+
title: `Bonsai listed ${String(output.repos.length)} ${output.repos.length === 1 ? "repository" : "repositories"}`,
|
|
349
|
+
detail: output.repos
|
|
350
|
+
.slice(0, 5)
|
|
351
|
+
.map((repo) => repo.name)
|
|
352
|
+
.join(" · "),
|
|
353
|
+
}),
|
|
354
|
+
},
|
|
342
355
|
handler: (_input, context) => ({
|
|
343
356
|
repos: service.listRepos(context.bonsai),
|
|
344
357
|
}),
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/bonsai",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Bonsai: a git platform for many agents working at once, on Workers and Artifacts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@fungi.computer/agar": "0.
|
|
9
|
-
"@fungi.computer/caps": "0.0
|
|
8
|
+
"@fungi.computer/agar": "0.2.0",
|
|
9
|
+
"@fungi.computer/caps": "0.1.0",
|
|
10
10
|
"@fungi.computer/issues": "0.0.1",
|
|
11
|
-
"@fungi.computer/oyster": "0.
|
|
12
|
-
"@fungi.computer/puffball": "0.0
|
|
11
|
+
"@fungi.computer/oyster": "0.2.0",
|
|
12
|
+
"@fungi.computer/puffball": "0.1.0",
|
|
13
13
|
"@fungi.computer/stipe": "0.0.1",
|
|
14
14
|
"@pierre/diffs": "1.3.6",
|
|
15
15
|
"@pierre/trees": "1.0.0-beta.6",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@fungi.computer/cairn": "0.0.1",
|
|
28
28
|
"@fungi.computer/maosaico": "0.0.1",
|
|
29
29
|
"@visx/hierarchy": "3.12.0",
|
|
30
|
-
"@fungi.computer/gills": "0.0
|
|
30
|
+
"@fungi.computer/gills": "0.1.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"playwright": "1.62.1",
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"vitest": "4.1.11",
|
|
44
44
|
"wrangler": "4.135.0",
|
|
45
45
|
"tsx": "4.23.12",
|
|
46
|
-
"@fungi.computer/braid": "0.0
|
|
47
|
-
"@fungi.computer/gills": "0.0
|
|
46
|
+
"@fungi.computer/braid": "0.1.0",
|
|
47
|
+
"@fungi.computer/gills": "0.1.0",
|
|
48
48
|
"eslint": "10.9.1",
|
|
49
49
|
"oxlint": "1.78.0",
|
|
50
50
|
"jsdom": "29.0.1",
|
|
51
|
-
"@fungi.computer/computer": "0.0
|
|
51
|
+
"@fungi.computer/computer": "0.1.0"
|
|
52
52
|
},
|
|
53
53
|
"botanical": {
|
|
54
54
|
"role": "app",
|
|
@@ -377,16 +377,17 @@ try {
|
|
|
377
377
|
"The VM Computer fixture's sole Computer is named workspace",
|
|
378
378
|
);
|
|
379
379
|
const nonce = randomBytes(8).toString("hex");
|
|
380
|
-
// Shed
|
|
381
|
-
// typed line to the real PTY
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
380
|
+
// Shed is one Ghostty terminal whose light prompt is drawn on its
|
|
381
|
+
// canvas; `full` escalates one typed line to the real PTY in place, and
|
|
382
|
+
// every later command is typed into the same canvas. Typing goes to
|
|
383
|
+
// Ghostty's hidden key-capture textarea, which a click on the canvas
|
|
384
|
+
// focuses.
|
|
385
|
+
const canvas = shed.locator(".terminal-host canvas");
|
|
386
|
+
const typeLine = async (line) => {
|
|
387
|
+
await canvas.click();
|
|
388
|
+
await page.keyboard.type(line);
|
|
389
|
+
await page.keyboard.press("Enter");
|
|
390
|
+
};
|
|
390
391
|
const receivedBytes = () =>
|
|
391
392
|
Buffer.concat(received.filter((frame) => Buffer.isBuffer(frame)));
|
|
392
393
|
const sentBytesLength = () =>
|
|
@@ -404,14 +405,15 @@ try {
|
|
|
404
405
|
runCommandCalls.push(request.postDataJSON());
|
|
405
406
|
});
|
|
406
407
|
const beforeLightInput = sentBytesLength();
|
|
407
|
-
await
|
|
408
|
-
await prompt.fill("pwd");
|
|
409
|
-
await prompt.press("Enter");
|
|
408
|
+
await typeLine("pwd");
|
|
410
409
|
await expect
|
|
411
|
-
.poll(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
410
|
+
.poll(
|
|
411
|
+
() => runCommandCalls.some((call) => call.command.endsWith("; pwd")),
|
|
412
|
+
{
|
|
413
|
+
message: "A light command calls Shed's runCommand method",
|
|
414
|
+
timeout: 30_000,
|
|
415
|
+
},
|
|
416
|
+
)
|
|
415
417
|
.toBe(true);
|
|
416
418
|
assert.equal(
|
|
417
419
|
sentBytesLength(),
|
|
@@ -420,9 +422,7 @@ try {
|
|
|
420
422
|
);
|
|
421
423
|
|
|
422
424
|
const marker = `oyster-${nonce}-command`;
|
|
423
|
-
await
|
|
424
|
-
await prompt.fill(`full printf 'oyster-%s-command\\n' '${nonce}'`);
|
|
425
|
-
await prompt.press("Enter");
|
|
425
|
+
await typeLine(`full printf 'oyster-%s-command\\n' '${nonce}'`);
|
|
426
426
|
await expect
|
|
427
427
|
.poll(() => receivedBytes().includes(marker), {
|
|
428
428
|
message: "A real shell command returns through Shed's terminal",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import { appBackend } from "../backend/methods.js";
|
|
3
|
+
|
|
4
|
+
const repo = (name: string) => ({
|
|
5
|
+
name,
|
|
6
|
+
description: null,
|
|
7
|
+
parent: null,
|
|
8
|
+
owner: null,
|
|
9
|
+
createdAt: 1,
|
|
10
|
+
lastPushAt: null,
|
|
11
|
+
forks: [],
|
|
12
|
+
openChanges: 0,
|
|
13
|
+
activity: [],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("reads a repository listing as a count and the first few names", () => {
|
|
17
|
+
const present = appBackend.methods["repos.list"]?.present;
|
|
18
|
+
expect(present?.({}, { repos: [repo("storyboard")] })).toEqual({
|
|
19
|
+
id: "repos.list",
|
|
20
|
+
title: "Bonsai listed 1 repository",
|
|
21
|
+
detail: "storyboard",
|
|
22
|
+
});
|
|
23
|
+
expect(present?.({}, { repos: [repo("a"), repo("b")] })).toMatchObject({
|
|
24
|
+
title: "Bonsai listed 2 repositories",
|
|
25
|
+
detail: "a · b",
|
|
26
|
+
});
|
|
27
|
+
});
|
package/vitest.config.ts
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
import { defineConfig } from "vitest/config";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* These files drive real `git` subprocesses (init, http-backend pushes, clones,
|
|
5
|
+
* plumbing reads) through Agar's real-Git test kit. Each Git spawn costs a few
|
|
6
|
+
* milliseconds idle and tens under CI load, so a test that is about 1 s on an
|
|
7
|
+
* idle box crosses the 5 s default when the gate runs at several times
|
|
8
|
+
* oversubscription. They get their own project and timeout; the fast unit
|
|
9
|
+
* tests keep Vitest's default.
|
|
10
|
+
*/
|
|
11
|
+
const realGit = [
|
|
12
|
+
"test/background-runtime.test.ts",
|
|
13
|
+
"test/changes.test.ts",
|
|
14
|
+
"test/file-edits.test.ts",
|
|
15
|
+
"test/files-read-copy-calls.test.ts",
|
|
16
|
+
"test/graph-commit-read-copy-calls.test.ts",
|
|
17
|
+
"test/merge-background.test.ts",
|
|
18
|
+
"test/merge-conflict.test.ts",
|
|
19
|
+
"test/merge-repro.test.ts",
|
|
20
|
+
"test/read-copy-composer.test.ts",
|
|
21
|
+
"test/read-copy-lifetimes.test.ts",
|
|
22
|
+
"test/repository-deletion.test.ts",
|
|
23
|
+
];
|
|
24
|
+
|
|
3
25
|
export default defineConfig({
|
|
4
|
-
test: {
|
|
26
|
+
test: {
|
|
27
|
+
projects: [
|
|
28
|
+
{
|
|
29
|
+
test: {
|
|
30
|
+
name: "unit",
|
|
31
|
+
include: ["test/**/*.test.ts"],
|
|
32
|
+
exclude: realGit,
|
|
33
|
+
environment: "node",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
test: {
|
|
38
|
+
name: "real-git",
|
|
39
|
+
include: realGit,
|
|
40
|
+
environment: "node",
|
|
41
|
+
testTimeout: 30_000,
|
|
42
|
+
hookTimeout: 30_000,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
5
47
|
});
|
package/web/change-page.tsx
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "@fungi.computer/caps/components/data-list";
|
|
15
15
|
import { InlineCode } from "@fungi.computer/caps/components/inline-code";
|
|
16
16
|
import { Status } from "@fungi.computer/caps/components/status";
|
|
17
|
-
import { StatusLed } from "@fungi.computer/caps/components/field-guide";
|
|
18
17
|
import type { MergeResult, PolicyDecision } from "../shared/contract.js";
|
|
19
18
|
import {
|
|
20
19
|
useAgents,
|
|
@@ -569,14 +568,19 @@ export function ChangePage({ repo, number }: { repo: string; number: number }) {
|
|
|
569
568
|
>
|
|
570
569
|
<div className="min-w-0">
|
|
571
570
|
<div className="flex flex-wrap items-center gap-2">
|
|
572
|
-
<
|
|
573
|
-
|
|
571
|
+
<Status
|
|
572
|
+
size="compact"
|
|
574
573
|
tone={
|
|
575
574
|
check.status === "passed"
|
|
576
|
-
? "
|
|
575
|
+
? "success"
|
|
577
576
|
: check.status === "failed"
|
|
578
|
-
? "
|
|
579
|
-
: "
|
|
577
|
+
? "error"
|
|
578
|
+
: check.status === "running"
|
|
579
|
+
? "info"
|
|
580
|
+
: "neutral"
|
|
581
|
+
}
|
|
582
|
+
motion={
|
|
583
|
+
check.status === "running" ? "bounce" : "none"
|
|
580
584
|
}
|
|
581
585
|
label={check.status}
|
|
582
586
|
/>
|