@netnodeag/kraftwerk 0.38.1 → 0.39.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/dist/cli/init.js +1 -0
- package/dist/cli/kraftwerk.js +2 -0
- package/dist/cli/vibeables.d.ts +2 -0
- package/dist/cli/vibeables.js +89 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +29 -10
- package/dist/inspector/chat/sessions.d.ts +11 -0
- package/dist/inspector/chat/sessions.js +86 -10
- package/dist/inspector/chat/types.d.ts +2 -0
- package/dist/inspector/git.js +5 -3
- package/dist/inspector/mtime.d.ts +3 -0
- package/dist/inspector/mtime.js +31 -0
- package/dist/inspector/repos.d.ts +2 -0
- package/dist/inspector/repos.js +5 -1
- package/dist/inspector/server.js +90 -1
- package/dist/inspector/settings.d.ts +3 -1
- package/dist/inspector/settings.js +19 -8
- package/dist/inspector/vibeables.d.ts +152 -0
- package/dist/inspector/vibeables.js +509 -0
- package/inspector/dist/assets/apl-gfMMiTNf.js +1 -0
- package/inspector/dist/assets/asciiarmor-C_eisQCp.js +1 -0
- package/inspector/dist/assets/asn1-DCl_8MCW.js +1 -0
- package/inspector/dist/assets/asterisk-BEn0Cwpx.js +1 -0
- package/inspector/dist/assets/brainfuck-D5EjA2JK.js +1 -0
- package/inspector/dist/assets/clike-D_6L3tnO.js +1 -0
- package/inspector/dist/assets/clojure-BflJGmDX.js +1 -0
- package/inspector/dist/assets/cmake-84nbDH0I.js +1 -0
- package/inspector/dist/assets/cobol-DHLQUbN7.js +1 -0
- package/inspector/dist/assets/coffeescript-De_zI4J0.js +1 -0
- package/inspector/dist/assets/commonlisp-DgAKBe0r.js +1 -0
- package/inspector/dist/assets/crystal-Bsdphhtm.js +1 -0
- package/inspector/dist/assets/css-CnaYoQib.js +1 -0
- package/inspector/dist/assets/cypher-p9eYesGn.js +1 -0
- package/inspector/dist/assets/d-DWkQyNnU.js +1 -0
- package/inspector/dist/assets/diff-ChtP43wD.js +1 -0
- package/inspector/dist/assets/dist-3Nzkrylv.js +1 -0
- package/inspector/dist/assets/dist-5wk4daHl.js +1 -0
- package/inspector/dist/assets/dist-BCM6ehYa.js +1 -0
- package/inspector/dist/assets/dist-BECiZEqH.js +1 -0
- package/inspector/dist/assets/dist-BbhR2mEU.js +1 -0
- package/inspector/dist/assets/dist-CF7An11X.js +1 -0
- package/inspector/dist/assets/dist-CI-2-8Tz.js +23 -0
- package/inspector/dist/assets/dist-CPlSMtdY.js +1 -0
- package/inspector/dist/assets/dist-CXS6W3P9.js +9 -0
- package/inspector/dist/assets/dist-Ceimhh7q.js +1 -0
- package/inspector/dist/assets/dist-D3tgdU3w.js +1 -0
- package/inspector/dist/assets/dist-D8nmL5S9.js +1 -0
- package/inspector/dist/assets/dist-DNBDudvB.js +1 -0
- package/inspector/dist/assets/dist-DTW4FhJ_.js +1 -0
- package/inspector/dist/assets/dist-DXIk6Ebu.js +1 -0
- package/inspector/dist/assets/dist-DaG9QYNU.js +1 -0
- package/inspector/dist/assets/dist-Dd-u7fXY.js +1 -0
- package/inspector/dist/assets/dist-DwyxsFyN.js +1 -0
- package/inspector/dist/assets/dist-H3rerbMg.js +13 -0
- package/inspector/dist/assets/dist-ZMc9InB9.js +1 -0
- package/inspector/dist/assets/dist-nc_mc1w0.js +1 -0
- package/inspector/dist/assets/dist-rE2oRtJp.js +11 -0
- package/inspector/dist/assets/dist-yFKhRrHN.js +2 -0
- package/inspector/dist/assets/dockerfile-DJkHQgkl.js +1 -0
- package/inspector/dist/assets/dtd-DTF72YNO.js +1 -0
- package/inspector/dist/assets/dylan-BCDoL_-p.js +1 -0
- package/inspector/dist/assets/ebnf-D4c0_ac3.js +1 -0
- package/inspector/dist/assets/ecl-BVIeWmwY.js +1 -0
- package/inspector/dist/assets/editor-6yCJBeDg.js +120 -0
- package/inspector/dist/assets/editor-B1Jq9YIq.css +1 -0
- package/inspector/dist/assets/eiffel-BEjRio4Q.js +1 -0
- package/inspector/dist/assets/elm-Cshzl8qu.js +1 -0
- package/inspector/dist/assets/erlang-BV0c4Hdn.js +1 -0
- package/inspector/dist/assets/factor-LKH5gN8L.js +1 -0
- package/inspector/dist/assets/fcl-ClOhbFR7.js +1 -0
- package/inspector/dist/assets/forth-D6RzAl64.js +1 -0
- package/inspector/dist/assets/fortran-Bt6PBEDR.js +1 -0
- package/inspector/dist/assets/gas-BHEdbvp9.js +1 -0
- package/inspector/dist/assets/gherkin-oBAE_ms0.js +1 -0
- package/inspector/dist/assets/groovy-CUpZ7dEl.js +1 -0
- package/inspector/dist/assets/haskell-BRsoo5mP.js +1 -0
- package/inspector/dist/assets/haxe-DjVOVvNP.js +1 -0
- package/inspector/dist/assets/http-CLVgA2GD.js +1 -0
- package/inspector/dist/assets/idl-BUZw3wgd.js +1 -0
- package/inspector/dist/assets/index-Dom87GNV.css +1 -0
- package/inspector/dist/assets/index-DurTvcNI.js +218 -0
- package/inspector/dist/assets/javascript-Bu8mWwtz.js +1 -0
- package/inspector/dist/assets/julia-CQfgDRfP.js +1 -0
- package/inspector/dist/assets/livescript-BXxG0Yva.js +1 -0
- package/inspector/dist/assets/lua-DPCEkfD_.js +1 -0
- package/inspector/dist/assets/material-symbols-rounded-DzDTtRam.woff2 +0 -0
- package/inspector/dist/assets/mathematica-u8YMmbU0.js +1 -0
- package/inspector/dist/assets/mbox-BEMVcqjs.js +1 -0
- package/inspector/dist/assets/{mirc-CjQqDB4T.js → mirc-C9zpzAZU.js} +1 -1
- package/inspector/dist/assets/mllike-B45xgp2S.js +1 -0
- package/inspector/dist/assets/modelica-CqHI_q-D.js +1 -0
- package/inspector/dist/assets/mscgen-qgSLujhx.js +1 -0
- package/inspector/dist/assets/mumps-BSzj6Xyz.js +1 -0
- package/inspector/dist/assets/{nginx-DdIZxoE0.js → nginx-8H7HVH4w.js} +1 -1
- package/inspector/dist/assets/nsis-viPvXFiq.js +1 -0
- package/inspector/dist/assets/ntriples-B9h1VB_g.js +1 -0
- package/inspector/dist/assets/octave-D2Q8cUp1.js +1 -0
- package/inspector/dist/assets/oz-BwTct_5T.js +1 -0
- package/inspector/dist/assets/pascal-Dp_KdFgX.js +1 -0
- package/inspector/dist/assets/perl-BkSkiI9m.js +1 -0
- package/inspector/dist/assets/pig-AF4Hwhju.js +1 -0
- package/inspector/dist/assets/powershell-BcIto6Sf.js +1 -0
- package/inspector/dist/assets/properties-C357ku5U.js +1 -0
- package/inspector/dist/assets/protobuf-D1ij_kNL.js +1 -0
- package/inspector/dist/assets/pug-3K-JMW4g.js +1 -0
- package/inspector/dist/assets/puppet-BZyAS7I4.js +1 -0
- package/inspector/dist/assets/python-B_1Jd8HM.js +1 -0
- package/inspector/dist/assets/q-B9NhS7L_.js +1 -0
- package/inspector/dist/assets/r-CxDmU1I4.js +1 -0
- package/inspector/dist/assets/rpm-gpDK5sbt.js +1 -0
- package/inspector/dist/assets/ruby-ChfNwlCY.js +1 -0
- package/inspector/dist/assets/{sas-B4kiWyti.js → sas-C2OyD2Y6.js} +1 -1
- package/inspector/dist/assets/scheme-D9qYN0V5.js +1 -0
- package/inspector/dist/assets/shell-DwuoZtxw.js +1 -0
- package/inspector/dist/assets/sieve-tLWDfEkZ.js +1 -0
- package/inspector/dist/assets/simple-mode-DXiLM1-G.js +1 -0
- package/inspector/dist/assets/smalltalk-BLp5-jwC.js +1 -0
- package/inspector/dist/assets/solr-B78wSQJQ.js +1 -0
- package/inspector/dist/assets/sparql-Dz-mlCAC.js +1 -0
- package/inspector/dist/assets/spreadsheet-CTIncYxj.js +1 -0
- package/inspector/dist/assets/sql-DuQMFDvy.js +1 -0
- package/inspector/dist/assets/stex-DNzd62Q_.js +1 -0
- package/inspector/dist/assets/stylus-BOVgKnng.js +1 -0
- package/inspector/dist/assets/swift-DKB6_1j6.js +1 -0
- package/inspector/dist/assets/tcl-BGfruO1u.js +1 -0
- package/inspector/dist/assets/textile-C4jVoHG1.js +1 -0
- package/inspector/dist/assets/tiddlywiki-VlUiK86J.js +1 -0
- package/inspector/dist/assets/tiki-AK6FwT8q.js +1 -0
- package/inspector/dist/assets/toml-BPTmHmyx.js +1 -0
- package/inspector/dist/assets/troff-C2dAgh7P.js +1 -0
- package/inspector/dist/assets/ttcn-PPSTk1ow.js +1 -0
- package/inspector/dist/assets/ttcn-cfg-2b4Gb5sE.js +1 -0
- package/inspector/dist/assets/turtle-Cx3rYX2g.js +1 -0
- package/inspector/dist/assets/vb-BthdM_4N.js +1 -0
- package/inspector/dist/assets/vbscript-D9f6rSsU.js +1 -0
- package/inspector/dist/assets/velocity-CqftRsjg.js +1 -0
- package/inspector/dist/assets/verilog-CjI9Y6RI.js +1 -0
- package/inspector/dist/assets/vhdl-kS471soi.js +1 -0
- package/inspector/dist/assets/webidl-D5jiJvOU.js +1 -0
- package/inspector/dist/assets/xquery-BKJx4mf9.js +1 -0
- package/inspector/dist/assets/yacas-CXXgtw0p.js +1 -0
- package/inspector/dist/assets/z80-Co3PbHdE.js +1 -0
- package/inspector/dist/index.html +2 -2
- package/package.json +6 -6
- package/inspector/dist/assets/apl-B4CMkyY2.js +0 -1
- package/inspector/dist/assets/asciiarmor-Df11BRmG.js +0 -1
- package/inspector/dist/assets/asn1-EdZsLKOL.js +0 -1
- package/inspector/dist/assets/asterisk-B-8jnY81.js +0 -1
- package/inspector/dist/assets/brainfuck-C4LP7Hcl.js +0 -1
- package/inspector/dist/assets/clike-B9uivgTg.js +0 -1
- package/inspector/dist/assets/clojure-BMjYHr_A.js +0 -1
- package/inspector/dist/assets/cmake-BQqOBYOt.js +0 -1
- package/inspector/dist/assets/cobol-CWcv1MsR.js +0 -1
- package/inspector/dist/assets/coffeescript-S37ZYGWr.js +0 -1
- package/inspector/dist/assets/commonlisp-DBKNyK5s.js +0 -1
- package/inspector/dist/assets/crystal-SjHAIU92.js +0 -1
- package/inspector/dist/assets/css-BnMrqG3P.js +0 -1
- package/inspector/dist/assets/cypher-C_CwsFkJ.js +0 -1
- package/inspector/dist/assets/d-pRatUO7H.js +0 -1
- package/inspector/dist/assets/diff-DbItnlRl.js +0 -1
- package/inspector/dist/assets/dockerfile-BKs6k2Af.js +0 -1
- package/inspector/dist/assets/dtd-DF_7sFjM.js +0 -1
- package/inspector/dist/assets/dylan-DwRh75JA.js +0 -1
- package/inspector/dist/assets/ebnf-CDyGwa7X.js +0 -1
- package/inspector/dist/assets/ecl-Cabwm37j.js +0 -1
- package/inspector/dist/assets/editor-9rr2cQaW.css +0 -1
- package/inspector/dist/assets/editor-CONb__vl.js +0 -144
- package/inspector/dist/assets/eiffel-CnydiIhH.js +0 -1
- package/inspector/dist/assets/elm-vLlmbW-K.js +0 -1
- package/inspector/dist/assets/erlang-BNw1qcRV.js +0 -1
- package/inspector/dist/assets/factor-kuTfRLto.js +0 -1
- package/inspector/dist/assets/fcl-Kvtd6kyn.js +0 -1
- package/inspector/dist/assets/forth-Ffai-XNe.js +0 -1
- package/inspector/dist/assets/fortran-DYz_wnZ1.js +0 -1
- package/inspector/dist/assets/gas-Bneqetm1.js +0 -1
- package/inspector/dist/assets/gherkin-heZmZLOM.js +0 -1
- package/inspector/dist/assets/groovy-D9Dt4D0W.js +0 -1
- package/inspector/dist/assets/haskell-Cw1EW3IL.js +0 -1
- package/inspector/dist/assets/haxe-H-WmDvRZ.js +0 -1
- package/inspector/dist/assets/http-DBlCnlav.js +0 -1
- package/inspector/dist/assets/idl-BEugSyMb.js +0 -1
- package/inspector/dist/assets/index-6VtYKf_K.js +0 -1
- package/inspector/dist/assets/index-B_BJEGG5.js +0 -1
- package/inspector/dist/assets/index-BkznuDSS.js +0 -1
- package/inspector/dist/assets/index-BrGHSw5I.js +0 -1
- package/inspector/dist/assets/index-BuiRW-03.js +0 -1
- package/inspector/dist/assets/index-C1tY1uK0.js +0 -1
- package/inspector/dist/assets/index-CDY-uFno.js +0 -7
- package/inspector/dist/assets/index-CcGsEtey.js +0 -1
- package/inspector/dist/assets/index-CntcQxRr.js +0 -1
- package/inspector/dist/assets/index-CpDzWqX0.js +0 -3
- package/inspector/dist/assets/index-Cw6eNCH7.js +0 -1
- package/inspector/dist/assets/index-D1_Glu0y.css +0 -1
- package/inspector/dist/assets/index-DMWUqEQi.js +0 -217
- package/inspector/dist/assets/index-DfK1f9gz.js +0 -1
- package/inspector/dist/assets/index-IosqBxL0.js +0 -1
- package/inspector/dist/assets/index-iRFYcgNN.js +0 -1
- package/inspector/dist/assets/index-p5w5E19N.js +0 -1
- package/inspector/dist/assets/index-rAZqRLhj.js +0 -2
- package/inspector/dist/assets/index-yGDq5Egm.js +0 -1
- package/inspector/dist/assets/javascript-iXu5QeM3.js +0 -1
- package/inspector/dist/assets/julia-DuME0IfC.js +0 -1
- package/inspector/dist/assets/livescript-BwQOo05w.js +0 -1
- package/inspector/dist/assets/lua-VAEuO923.js +0 -1
- package/inspector/dist/assets/material-symbols-rounded-DxXFL64Y.woff2 +0 -0
- package/inspector/dist/assets/mathematica-DTrFuWx2.js +0 -1
- package/inspector/dist/assets/mbox-CNhZ1qSd.js +0 -1
- package/inspector/dist/assets/mllike-CXdrOF99.js +0 -1
- package/inspector/dist/assets/modelica-Dc1JOy9r.js +0 -1
- package/inspector/dist/assets/mscgen-BA5vi2Kp.js +0 -1
- package/inspector/dist/assets/mumps-BT43cFF4.js +0 -1
- package/inspector/dist/assets/nsis-LY0VT_u5.js +0 -1
- package/inspector/dist/assets/ntriples-BfvgReVJ.js +0 -1
- package/inspector/dist/assets/octave-Ck1zUtKM.js +0 -1
- package/inspector/dist/assets/oz-BzwKVEFT.js +0 -1
- package/inspector/dist/assets/pascal--L3eBynH.js +0 -1
- package/inspector/dist/assets/perl-CdXCOZ3F.js +0 -1
- package/inspector/dist/assets/pig-CevX1Tat.js +0 -1
- package/inspector/dist/assets/powershell-CFHJl5sT.js +0 -1
- package/inspector/dist/assets/properties-C78fOPTZ.js +0 -1
- package/inspector/dist/assets/protobuf-ChK-085T.js +0 -1
- package/inspector/dist/assets/pug-DeIclll2.js +0 -1
- package/inspector/dist/assets/puppet-DMA9R1ak.js +0 -1
- package/inspector/dist/assets/python-BuPzkPfP.js +0 -1
- package/inspector/dist/assets/q-pXgVlZs6.js +0 -1
- package/inspector/dist/assets/r-B6wPVr8A.js +0 -1
- package/inspector/dist/assets/rpm-CTu-6PCP.js +0 -1
- package/inspector/dist/assets/ruby-BK5aLJev.js +0 -1
- package/inspector/dist/assets/scheme-C41bIUwD.js +0 -1
- package/inspector/dist/assets/shell-CjFT_Tl9.js +0 -1
- package/inspector/dist/assets/sieve-C3Gn_uJK.js +0 -1
- package/inspector/dist/assets/simple-mode-GW_nhZxv.js +0 -1
- package/inspector/dist/assets/smalltalk-CnHTOXQT.js +0 -1
- package/inspector/dist/assets/solr-DehyRSwq.js +0 -1
- package/inspector/dist/assets/sparql-DkYu6x3z.js +0 -1
- package/inspector/dist/assets/spreadsheet-BCZA_wO0.js +0 -1
- package/inspector/dist/assets/sql-D0XecflT.js +0 -1
- package/inspector/dist/assets/stex-C3f8Ysf7.js +0 -1
- package/inspector/dist/assets/stylus-B533Al4x.js +0 -1
- package/inspector/dist/assets/swift-BzpIVaGY.js +0 -1
- package/inspector/dist/assets/tcl-DVfN8rqt.js +0 -1
- package/inspector/dist/assets/textile-CnDTJFAw.js +0 -1
- package/inspector/dist/assets/tiddlywiki-DO-Gjzrf.js +0 -1
- package/inspector/dist/assets/tiki-DGYXhP31.js +0 -1
- package/inspector/dist/assets/toml-Bm5Em-hy.js +0 -1
- package/inspector/dist/assets/troff-wAsdV37c.js +0 -1
- package/inspector/dist/assets/ttcn-CfJYG6tj.js +0 -1
- package/inspector/dist/assets/ttcn-cfg-B9xdYoR4.js +0 -1
- package/inspector/dist/assets/turtle-B1tBg_DP.js +0 -1
- package/inspector/dist/assets/vb-CmGdzxic.js +0 -1
- package/inspector/dist/assets/vbscript-BuJXcnF6.js +0 -1
- package/inspector/dist/assets/velocity-D8B20fx6.js +0 -1
- package/inspector/dist/assets/verilog-C6RDOZhf.js +0 -1
- package/inspector/dist/assets/vhdl-lSbBsy5d.js +0 -1
- package/inspector/dist/assets/webidl-ZXfAyPTL.js +0 -1
- package/inspector/dist/assets/xquery-DzFWVndE.js +0 -1
- package/inspector/dist/assets/yacas-BJ4BC0dw.js +0 -1
- package/inspector/dist/assets/z80-Hz9HOZM7.js +0 -1
package/dist/cli/init.js
CHANGED
|
@@ -24,6 +24,7 @@ agents: ${DATA_DIR}/agents # agent definitions
|
|
|
24
24
|
skills: ${DATA_DIR}/skills # workspace skills (shared instruction packages)
|
|
25
25
|
# repos: # git repositories the agents work on (uncomment both lines to enable)
|
|
26
26
|
# root: ${DATA_DIR}/repos # clones land here (git-ignored); a bare \`repos:\` uses repos/ instead
|
|
27
|
+
# vibeables: # small apps built live in a chat with a preview pane (uncomment to enable; one folder per app under ${DATA_DIR}/vibeables, versioned with the workspace)
|
|
27
28
|
`;
|
|
28
29
|
const WORKFLOW_TEMPLATE = `# yaml-language-server: $schema=${SCHEMA_URL}
|
|
29
30
|
name: hello
|
package/dist/cli/kraftwerk.js
CHANGED
|
@@ -14,6 +14,7 @@ import { runInit } from "./init.js";
|
|
|
14
14
|
import { registerKnowledgeCommands } from "./knowledge.js";
|
|
15
15
|
import { registerProjectCommands } from "./projects.js";
|
|
16
16
|
import { registerRepoCommands } from "./repos.js";
|
|
17
|
+
import { registerVibeableCommands } from "./vibeables.js";
|
|
17
18
|
import { registerRoutineCommands } from "./routines.js";
|
|
18
19
|
import { listRuns, showRun } from "./runs.js";
|
|
19
20
|
import { runUi } from "./ui.js";
|
|
@@ -262,6 +263,7 @@ registerKnowledgeCommands(program);
|
|
|
262
263
|
registerRoutineCommands(program);
|
|
263
264
|
registerProjectCommands(program);
|
|
264
265
|
registerRepoCommands(program);
|
|
266
|
+
registerVibeableCommands(program);
|
|
265
267
|
program
|
|
266
268
|
.command("ui")
|
|
267
269
|
.description("Start the inspector web UI for this project's runs and workflows")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import { createVibeable, deleteVibeable, listVibeables } from "../inspector/vibeables.js";
|
|
4
|
+
import { fmtAgo } from "./projects.js";
|
|
5
|
+
import { initContext as prepare } from "./routines.js";
|
|
6
|
+
/**
|
|
7
|
+
* `kraftwerk vibeables` — the small apps built live in a chat, one folder
|
|
8
|
+
* each under the vibeables root (`vibeables.root` in kraftwerk.yml). The
|
|
9
|
+
* same module the inspector uses, so an app created here shows up in every
|
|
10
|
+
* chat's picker at once:
|
|
11
|
+
*
|
|
12
|
+
* kraftwerk vibeables list: name, mode, state
|
|
13
|
+
* kraftwerk vibeables create <name> new app from the starter (index.html, vibeable.yml)
|
|
14
|
+
* kraftwerk vibeables remove <name> delete the folder (its history stays in the workspace git)
|
|
15
|
+
*/
|
|
16
|
+
const die = (msg) => {
|
|
17
|
+
console.error(chalk.red(msg));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
};
|
|
20
|
+
export function registerVibeableCommands(program) {
|
|
21
|
+
const vibeables = program
|
|
22
|
+
.command("vibeables")
|
|
23
|
+
.description("Small apps built live in a chat: list, create one from the starter, remove");
|
|
24
|
+
vibeables
|
|
25
|
+
.command("list", { isDefault: true })
|
|
26
|
+
.description("List the apps under the vibeables root")
|
|
27
|
+
.option("--json", "Machine-readable output")
|
|
28
|
+
.action(async (opts) => {
|
|
29
|
+
await prepare();
|
|
30
|
+
const view = await listVibeables();
|
|
31
|
+
if (opts.json) {
|
|
32
|
+
console.log(JSON.stringify(view, null, 2));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!view.enabled)
|
|
36
|
+
die(view.error ?? "vibeables are off");
|
|
37
|
+
if (view.vibeables.length === 0) {
|
|
38
|
+
console.log(chalk.dim(`No vibeables under ${view.root} yet — \`kraftwerk vibeables create <name>\` starts one.`));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const table = new Table({
|
|
42
|
+
head: ["name", "mode", "state", "changed"].map((h) => chalk.bold(h)),
|
|
43
|
+
wordWrap: true,
|
|
44
|
+
colWidths: [24, 30, 22, 14],
|
|
45
|
+
});
|
|
46
|
+
for (const v of view.vibeables) {
|
|
47
|
+
table.push([
|
|
48
|
+
chalk.cyan(v.slug),
|
|
49
|
+
v.dev ? `dev: ${v.dev}` : "static",
|
|
50
|
+
v.configError ? chalk.red(v.configError) : v.hasIndex ? chalk.dim("ready") : chalk.yellow("no index.html yet"),
|
|
51
|
+
chalk.dim(fmtAgo(v.updatedAt)),
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
console.log(table.toString());
|
|
55
|
+
console.log(chalk.dim(view.root ?? ""));
|
|
56
|
+
});
|
|
57
|
+
vibeables
|
|
58
|
+
.command("create")
|
|
59
|
+
.description("Create an app folder from the starter (index.html, style.css, app.js, vibeable.yml)")
|
|
60
|
+
.argument("<name>", "Folder name under the root")
|
|
61
|
+
.option("--json", "Print the new entry as JSON")
|
|
62
|
+
.action(async (name, opts) => {
|
|
63
|
+
await prepare();
|
|
64
|
+
try {
|
|
65
|
+
const v = await createVibeable(name);
|
|
66
|
+
if (opts.json)
|
|
67
|
+
console.log(JSON.stringify(v, null, 2));
|
|
68
|
+
else
|
|
69
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(v.slug)} → ${v.path} ${chalk.dim("(open it from any chat with the vibeable button)")}`);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
die(err.message);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
vibeables
|
|
76
|
+
.command("remove")
|
|
77
|
+
.description("Delete an app folder; its history stays in the workspace git")
|
|
78
|
+
.argument("<name>", "Folder name under the root")
|
|
79
|
+
.action(async (name) => {
|
|
80
|
+
await prepare();
|
|
81
|
+
try {
|
|
82
|
+
await deleteVibeable(name);
|
|
83
|
+
console.log(`${chalk.green("✔")} removed ${chalk.cyan(name)}`);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
die(err.message);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
* autosync: pull # off | pull
|
|
29
29
|
* repos: # git repositories the agents work on (absent = off, bare key = on)
|
|
30
30
|
* root: kraftwerk-data/repos # where clones land, relative to the file. Default: repos
|
|
31
|
+
* vibeables: # small apps built live in a chat, rendered in the inspector (absent = off, bare key = on)
|
|
32
|
+
* root: apps # one folder per app, part of the workspace. Default: kraftwerk-data/vibeables
|
|
31
33
|
*/
|
|
32
34
|
/** Stable, versionless URL of the workflow JSON schema (editor validation). */
|
|
33
35
|
export declare const SCHEMA_URL = "https://raw.githubusercontent.com/NETNODEAG/kraftwerk/main/kraftwerk/schema/workflow.schema.json";
|
|
@@ -71,6 +73,22 @@ export declare const REPOS_DEFAULT_ROOT = "repos";
|
|
|
71
73
|
* settings save and the module itself cannot disagree.
|
|
72
74
|
*/
|
|
73
75
|
export declare function reposRootFor(project: Project): string | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Vibeables: small applications the user builds live with an agent — a chat
|
|
78
|
+
* with a preview pane. Each is one folder under the root, plain files the
|
|
79
|
+
* inspector serves (or a dev command it runs). The root is part of the
|
|
80
|
+
* workspace and synced by the workspace git like agents and knowledge.
|
|
81
|
+
*/
|
|
82
|
+
export interface VibeablesConfig {
|
|
83
|
+
/** false keeps the block but turns the feature off. Default: true. */
|
|
84
|
+
enabled?: boolean;
|
|
85
|
+
/** Where the apps live, relative to the project root. Default: kraftwerk-data/vibeables */
|
|
86
|
+
root?: string;
|
|
87
|
+
}
|
|
88
|
+
/** Where vibeables live when `vibeables.root` is not set. */
|
|
89
|
+
export declare const VIBEABLES_DEFAULT_ROOT = "kraftwerk-data/vibeables";
|
|
90
|
+
/** Absolute vibeables root when the feature is on, undefined otherwise. */
|
|
91
|
+
export declare function vibeablesRootFor(project: Project): string | undefined;
|
|
74
92
|
/** A directory as a .gitignore entry: relative, forward slashes, no trailing slash; undefined outside the root. */
|
|
75
93
|
export declare function ignoreEntryFor(projectRoot: string, dir: string): string | undefined;
|
|
76
94
|
/** True when .gitignore text already covers the entry (with or without a leading or trailing slash). */
|
|
@@ -109,6 +127,8 @@ export interface ProjectConfig {
|
|
|
109
127
|
git?: GitConfig;
|
|
110
128
|
/** Repositories the agents work on. Absent = off. */
|
|
111
129
|
repos?: ReposConfig;
|
|
130
|
+
/** Vibeables: apps built live in a chat. Absent = off. */
|
|
131
|
+
vibeables?: VibeablesConfig;
|
|
112
132
|
}
|
|
113
133
|
export interface Project {
|
|
114
134
|
/** Absolute project root the CLI operates on. */
|
package/dist/config.js
CHANGED
|
@@ -32,6 +32,8 @@ import { parse } from "yaml";
|
|
|
32
32
|
* autosync: pull # off | pull
|
|
33
33
|
* repos: # git repositories the agents work on (absent = off, bare key = on)
|
|
34
34
|
* root: kraftwerk-data/repos # where clones land, relative to the file. Default: repos
|
|
35
|
+
* vibeables: # small apps built live in a chat, rendered in the inspector (absent = off, bare key = on)
|
|
36
|
+
* root: apps # one folder per app, part of the workspace. Default: kraftwerk-data/vibeables
|
|
35
37
|
*/
|
|
36
38
|
/** Stable, versionless URL of the workflow JSON schema (editor validation). */
|
|
37
39
|
export const SCHEMA_URL = "https://raw.githubusercontent.com/NETNODEAG/kraftwerk/main/kraftwerk/schema/workflow.schema.json";
|
|
@@ -52,6 +54,15 @@ export function reposRootFor(project) {
|
|
|
52
54
|
return undefined;
|
|
53
55
|
return path.resolve(project.root, r.root ?? REPOS_DEFAULT_ROOT);
|
|
54
56
|
}
|
|
57
|
+
/** Where vibeables live when `vibeables.root` is not set. */
|
|
58
|
+
export const VIBEABLES_DEFAULT_ROOT = "kraftwerk-data/vibeables";
|
|
59
|
+
/** Absolute vibeables root when the feature is on, undefined otherwise. */
|
|
60
|
+
export function vibeablesRootFor(project) {
|
|
61
|
+
const v = project.config.vibeables;
|
|
62
|
+
if (!v || v.enabled === false)
|
|
63
|
+
return undefined;
|
|
64
|
+
return path.resolve(project.root, v.root ?? VIBEABLES_DEFAULT_ROOT);
|
|
65
|
+
}
|
|
55
66
|
/** A directory as a .gitignore entry: relative, forward slashes, no trailing slash; undefined outside the root. */
|
|
56
67
|
export function ignoreEntryFor(projectRoot, dir) {
|
|
57
68
|
const rel = path.relative(projectRoot, path.resolve(projectRoot, dir)).split(path.sep).join("/");
|
|
@@ -127,7 +138,7 @@ export async function resolveProject(cwd) {
|
|
|
127
138
|
const root = gitFallback ?? start;
|
|
128
139
|
return { root, config: {}, outputDir: path.join(root, "output") };
|
|
129
140
|
}
|
|
130
|
-
const KNOWN_KEYS = ["name", "icon", "color", "port", "workflows", "output", "knowledge", "agents", "skills", "switcher", "git", "repos"];
|
|
141
|
+
const KNOWN_KEYS = ["name", "icon", "color", "port", "workflows", "output", "knowledge", "agents", "skills", "switcher", "git", "repos", "vibeables"];
|
|
131
142
|
async function loadConfig(configPath) {
|
|
132
143
|
let raw;
|
|
133
144
|
try {
|
|
@@ -171,7 +182,12 @@ async function loadConfig(configPath) {
|
|
|
171
182
|
else if (key === "repos") {
|
|
172
183
|
if (config[key] === null)
|
|
173
184
|
config[key] = {};
|
|
174
|
-
|
|
185
|
+
validateRootBlock(configPath, "repos", config[key]);
|
|
186
|
+
}
|
|
187
|
+
else if (key === "vibeables") {
|
|
188
|
+
if (config[key] === null)
|
|
189
|
+
config[key] = {};
|
|
190
|
+
validateRootBlock(configPath, "vibeables", config[key]);
|
|
175
191
|
}
|
|
176
192
|
else if (typeof config[key] !== "string") {
|
|
177
193
|
throw new Error(`${path.basename(configPath)}: ${key} must be a string`);
|
|
@@ -232,26 +248,29 @@ function validateGit(configPath, value) {
|
|
|
232
248
|
throw new Error(`${file}: git.autosync must be "off" or "pull"`);
|
|
233
249
|
}
|
|
234
250
|
}
|
|
235
|
-
|
|
251
|
+
/**
|
|
252
|
+
* `repos` and `vibeables` share one shape: { enabled?, root? }. The root
|
|
253
|
+
* must never be the project itself or anything outside it — it is what
|
|
254
|
+
* remove/delete operate under, and what the workspace git may stage.
|
|
255
|
+
*/
|
|
256
|
+
function validateRootBlock(configPath, block, value) {
|
|
236
257
|
const file = path.basename(configPath);
|
|
237
258
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
238
|
-
throw new Error(`${file}:
|
|
259
|
+
throw new Error(`${file}: ${block} must be a mapping (enabled, root)`);
|
|
239
260
|
}
|
|
240
261
|
const r = value;
|
|
241
262
|
for (const key of Object.keys(r)) {
|
|
242
263
|
if (!["enabled", "root"].includes(key)) {
|
|
243
|
-
throw new Error(`${file}:
|
|
264
|
+
throw new Error(`${file}: ${block}.${key} is unknown (allowed: enabled, root)`);
|
|
244
265
|
}
|
|
245
266
|
}
|
|
246
267
|
if (r.enabled !== undefined && typeof r.enabled !== "boolean") {
|
|
247
|
-
throw new Error(`${file}:
|
|
268
|
+
throw new Error(`${file}: ${block}.enabled must be true or false`);
|
|
248
269
|
}
|
|
249
270
|
if (r.root !== undefined && (typeof r.root !== "string" || !r.root.trim())) {
|
|
250
|
-
throw new Error(`${file}:
|
|
271
|
+
throw new Error(`${file}: ${block}.root must be a non-empty string`);
|
|
251
272
|
}
|
|
252
|
-
// The root is what `repos remove --force` deletes under, so it must never
|
|
253
|
-
// be the project itself or anything outside it.
|
|
254
273
|
if (typeof r.root === "string" && !ignoreEntryFor(path.dirname(configPath), r.root.trim())) {
|
|
255
|
-
throw new Error(`${file}:
|
|
274
|
+
throw new Error(`${file}: ${block}.root must be a directory inside the project (not ".", ".." or an absolute path outside it)`);
|
|
256
275
|
}
|
|
257
276
|
}
|
|
@@ -18,6 +18,17 @@ export declare function getChat(id: string): Promise<{
|
|
|
18
18
|
export declare function postMessage(id: string, text: string): Promise<{
|
|
19
19
|
error?: string;
|
|
20
20
|
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Open a vibeable in the chat (or close it with null). The agent's cwd
|
|
23
|
+
* becomes the app folder, so the running subprocess is dropped and the next
|
|
24
|
+
* message spawns one there with fresh context. Refused while a turn runs:
|
|
25
|
+
* the agent would keep editing in the old folder.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setChatVibeable(id: string, slug: string | null): Promise<{
|
|
28
|
+
error?: string;
|
|
29
|
+
status?: number;
|
|
30
|
+
meta?: ChatMeta;
|
|
31
|
+
}>;
|
|
21
32
|
export declare function resolvePermission(id: string, requestId: string, optionId: string | null): Promise<{
|
|
22
33
|
error?: string;
|
|
23
34
|
}>;
|
|
@@ -7,6 +7,7 @@ import { listSkills, readSkill } from "../skills.js";
|
|
|
7
7
|
import { listWorkflows } from "../workflows.js";
|
|
8
8
|
import { getAgent, listAgents } from "../agents.js";
|
|
9
9
|
import { listRepos } from "../repos.js";
|
|
10
|
+
import { resolveVibeable, vibeableStatus, VIBEABLE_CONFIG_FILE } from "../vibeables.js";
|
|
10
11
|
import { startAcpBackend } from "./acp.js";
|
|
11
12
|
import { startPiBackend } from "./pi.js";
|
|
12
13
|
import { appendEvent, listChatMetas, newChatId, readEvents, readMeta, safeChatDir, writeMeta, } from "./store.js";
|
|
@@ -168,6 +169,43 @@ async function reposContext() {
|
|
|
168
169
|
`\`npx kraftwerk repos\` lists them, \`update <name>\` fetches and fast-forwards a clean clone, \`remove <name>\` ` +
|
|
169
170
|
`deletes one. A repository the user mentions that is not listed is not cloned yet — offer to add it.`);
|
|
170
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* "## Vibeable" block: the chat has an app folder open in the preview pane.
|
|
174
|
+
* The agent works inside that folder, and everything it saves is what the
|
|
175
|
+
* user sees next — so the block spells out how the preview runs and what
|
|
176
|
+
* the sandbox rules out.
|
|
177
|
+
*/
|
|
178
|
+
async function vibeableContext(slug) {
|
|
179
|
+
const st = await vibeableStatus(slug).catch(() => null);
|
|
180
|
+
if (!st) {
|
|
181
|
+
return `## Vibeable\nThis chat had the vibeable "${slug}" open in its preview pane, but its folder is gone. Tell the user; do not recreate it unasked.`;
|
|
182
|
+
}
|
|
183
|
+
const dev = st.dev;
|
|
184
|
+
const mode = dev?.running
|
|
185
|
+
? `A dev server is running (\`${dev.command}\`, port ${dev.port}${dev.ready ? "" : ", not answering yet"}); the pane shows it.`
|
|
186
|
+
: st.config.dev
|
|
187
|
+
? `${VIBEABLE_CONFIG_FILE} declares \`dev: ${st.config.dev}\`; the dev server is not running, so the pane shows the static folder until the user starts it with the play button.`
|
|
188
|
+
: `No dev command: the pane shows the folder${st.config.dir ? ` ${st.config.dir}/` : ""} served as-is.`;
|
|
189
|
+
return (`## Vibeable: ${slug}\n` +
|
|
190
|
+
`You are building a small piece of software live with the user. Your working directory is its folder: ${st.path}. ` +
|
|
191
|
+
`The user sees it rendered in a preview pane next to this chat, and every file you save reloads that preview within a second — ` +
|
|
192
|
+
`work in small visible steps, and describe what changed instead of pasting code. Think ad-hoc software, not a website: a dashboard, ` +
|
|
193
|
+
`a slideshow, a calculator, a tracker with a backend — whatever the user asks for.\n\n` +
|
|
194
|
+
`Current state: ${mode}${st.configError ? ` (${st.configError} — fix it)` : ""}\n\n` +
|
|
195
|
+
`How the preview runs:\n` +
|
|
196
|
+
`- Static mode (default): the inspector serves the folder directly at /vibeables/${slug}/ — index.html is the entry; plain HTML, CSS and ` +
|
|
197
|
+
`JavaScript with no build step. Use relative URLs (./app.js, not /app.js). ES modules and CDN imports (https://esm.sh/<pkg>) work. ` +
|
|
198
|
+
`The static preview is a sandboxed document: no localStorage, no cookies, no calls into the inspector's own /api. Keep state in memory, ` +
|
|
199
|
+
`or move to dev mode when it must persist.\n` +
|
|
200
|
+
`- Dev mode: when the app needs a build tool, a backend or a database, write ${VIBEABLE_CONFIG_FILE} with \`dev: <command>\` ` +
|
|
201
|
+
`(e.g. \`npm run dev\`, \`node server.js\`, \`python3 -m http.server $PORT\`). kraftwerk starts it inside the folder with PORT in the ` +
|
|
202
|
+
`environment and the pane embeds that server; the command must listen on $PORT (or declare \`port:\` when it cannot). Optional ` +
|
|
203
|
+
`\`dir:\` picks the folder for static mode (e.g. dist). The app brings its own backend and storage — a small node server writing JSON ` +
|
|
204
|
+
`files, SQLite, an external API — nothing of that is provided by kraftwerk.\n\n` +
|
|
205
|
+
`Rules: the folder is part of this workspace and versioned with it — do not run git yourself, the user reviews and commits from the ` +
|
|
206
|
+
`inspector's Git screen; keep secrets out of the folder (.env is git-ignored, node_modules too); leave ${VIBEABLE_CONFIG_FILE} valid YAML; ` +
|
|
207
|
+
`when you switch the app to dev mode, say so and ask the user to press play in the pane. Reply briefly — the preview shows the result.`);
|
|
208
|
+
}
|
|
171
209
|
/** Base context per scope; scopeContext() appends the shared skills block. */
|
|
172
210
|
async function baseScopeContext(scope, agent) {
|
|
173
211
|
if (scope.kind === "agent") {
|
|
@@ -301,19 +339,22 @@ const RENDERING_BLOCK = `## Chat rendering\n` +
|
|
|
301
339
|
`with , or link any artifact the same way. ` +
|
|
302
340
|
`Prefer these relative /api/... URLs over file paths when showing results: they render directly ` +
|
|
303
341
|
`in this chat and keep working wherever the inspector is reachable.`;
|
|
304
|
-
async function scopeContext(
|
|
342
|
+
async function scopeContext(meta) {
|
|
343
|
+
const { scope, agent } = meta;
|
|
305
344
|
// The repositories block reads every clone from git, so it runs alongside
|
|
306
345
|
// the rest instead of adding its spawns to the first prompt's latency.
|
|
307
|
-
const [base, repos, skills] = await Promise.all([
|
|
346
|
+
const [base, repos, vibe, skills] = await Promise.all([
|
|
308
347
|
baseScopeContext(scope, agent),
|
|
309
348
|
scope.kind === "agent" || scope.kind === "kraftwerk" ? reposContext() : Promise.resolve(""),
|
|
349
|
+
meta.vibeable ? vibeableContext(meta.vibeable) : Promise.resolve(""),
|
|
310
350
|
availableSkills(scope),
|
|
311
351
|
]);
|
|
312
|
-
return [base, repos, RENDERING_BLOCK, skillsBlock(skills)].filter(Boolean).join("\n\n");
|
|
352
|
+
return [base, repos, vibe, RENDERING_BLOCK, skillsBlock(skills)].filter(Boolean).join("\n\n");
|
|
313
353
|
}
|
|
314
354
|
/* ---------- backend lifecycle ---------- */
|
|
315
355
|
/** Agent sessions carry the agent's model/effort; resolved live so edits apply to new backends. */
|
|
316
|
-
async function backendTuning(
|
|
356
|
+
async function backendTuning(meta) {
|
|
357
|
+
const { scope, agent } = meta;
|
|
317
358
|
const tuning = {};
|
|
318
359
|
if (scope.kind === "agent") {
|
|
319
360
|
const def = await getAgent(scope.slug).catch(() => null);
|
|
@@ -327,7 +368,8 @@ async function backendTuning(scope, agent) {
|
|
|
327
368
|
}
|
|
328
369
|
// Run chats live in the run folder — grant claude the project root so
|
|
329
370
|
// project-level skills and files stay reachable.
|
|
330
|
-
|
|
371
|
+
// A vibeable session works inside the app folder for the same reason.
|
|
372
|
+
if ((scope.kind === "run" || meta.vibeable) && agent === "claude")
|
|
331
373
|
tuning.addDirs = [getProjectRoot()];
|
|
332
374
|
// pi loads skills only when told: hand it every visible skill folder.
|
|
333
375
|
if (agent === "pi") {
|
|
@@ -365,7 +407,7 @@ async function ensureBackend(state) {
|
|
|
365
407
|
},
|
|
366
408
|
};
|
|
367
409
|
const { agent, cwd } = state.meta;
|
|
368
|
-
const tuning = await backendTuning(state.meta
|
|
410
|
+
const tuning = await backendTuning(state.meta);
|
|
369
411
|
state.backend =
|
|
370
412
|
agent === "pi"
|
|
371
413
|
? startPiBackend(cwd, hooks, tuning)
|
|
@@ -376,8 +418,10 @@ async function ensureBackend(state) {
|
|
|
376
418
|
return state.backend;
|
|
377
419
|
}
|
|
378
420
|
/* ---------- public API (used by server.ts) ---------- */
|
|
421
|
+
/** Where a chat's agent runs when no vibeable is open: the run folder for run chats, else the project root. */
|
|
422
|
+
const defaultCwd = (scope) => (scope.kind === "run" ? safeRunDir(scope.runId) : getProjectRoot());
|
|
379
423
|
export async function createChat(opts) {
|
|
380
|
-
const cwd =
|
|
424
|
+
const cwd = defaultCwd(opts.scope);
|
|
381
425
|
const now = new Date().toISOString();
|
|
382
426
|
const meta = {
|
|
383
427
|
id: newChatId(),
|
|
@@ -432,9 +476,7 @@ export async function postMessage(id, text) {
|
|
|
432
476
|
void (async () => {
|
|
433
477
|
try {
|
|
434
478
|
const backend = await ensureBackend(state);
|
|
435
|
-
const context = state.needsContext
|
|
436
|
-
? await scopeContext(state.meta.scope, state.meta.agent)
|
|
437
|
-
: "";
|
|
479
|
+
const context = state.needsContext ? await scopeContext(state.meta) : "";
|
|
438
480
|
state.needsContext = false;
|
|
439
481
|
// "/<skill> args" expands to the skill's instructions for the agent;
|
|
440
482
|
// the thread keeps the short form the user typed.
|
|
@@ -461,6 +503,40 @@ export async function postMessage(id, text) {
|
|
|
461
503
|
})();
|
|
462
504
|
return {};
|
|
463
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Open a vibeable in the chat (or close it with null). The agent's cwd
|
|
508
|
+
* becomes the app folder, so the running subprocess is dropped and the next
|
|
509
|
+
* message spawns one there with fresh context. Refused while a turn runs:
|
|
510
|
+
* the agent would keep editing in the old folder.
|
|
511
|
+
*/
|
|
512
|
+
export async function setChatVibeable(id, slug) {
|
|
513
|
+
const state = await loadState(id);
|
|
514
|
+
if (!state)
|
|
515
|
+
return { error: "not found", status: 404 };
|
|
516
|
+
if (state.busy)
|
|
517
|
+
return { error: "agent is still working — wait for the turn to finish", status: 409 };
|
|
518
|
+
if (slug) {
|
|
519
|
+
let dir;
|
|
520
|
+
try {
|
|
521
|
+
dir = (await resolveVibeable(slug)).dir;
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
const msg = err.message;
|
|
525
|
+
return { error: msg, status: /^no vibeable/.test(msg) ? 404 : /are off/.test(msg) ? 409 : 400 };
|
|
526
|
+
}
|
|
527
|
+
state.meta.vibeable = slug;
|
|
528
|
+
state.meta.cwd = dir;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
delete state.meta.vibeable;
|
|
532
|
+
state.meta.cwd = defaultCwd(state.meta.scope);
|
|
533
|
+
}
|
|
534
|
+
state.meta.updatedAt = new Date().toISOString();
|
|
535
|
+
await writeMeta(state.meta);
|
|
536
|
+
dropBackend(state);
|
|
537
|
+
state.needsContext = true;
|
|
538
|
+
return { meta: state.meta };
|
|
539
|
+
}
|
|
464
540
|
export async function resolvePermission(id, requestId, optionId) {
|
|
465
541
|
const state = await loadState(id);
|
|
466
542
|
if (!state)
|
package/dist/inspector/git.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { reposRootFor, resolveProject } from "../config.js";
|
|
4
|
+
import { reposRootFor, resolveProject, vibeablesRootFor } from "../config.js";
|
|
5
5
|
import { ENV_FILE } from "../runner/docker.js";
|
|
6
6
|
import { getOutputDir, getProjectRoot } from "./context.js";
|
|
7
7
|
/**
|
|
@@ -190,7 +190,7 @@ async function checkedOut(repo) {
|
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
192
192
|
* The roots that may be committed, as repo-relative paths: the workflows,
|
|
193
|
-
* knowledge, agents and
|
|
193
|
+
* knowledge, agents, skills and vibeables roots this project declares, plus
|
|
194
194
|
* kraftwerk.yml. The output directory is excluded even when it lives inside
|
|
195
195
|
* one of them.
|
|
196
196
|
*/
|
|
@@ -217,6 +217,8 @@ function scopeFor({ project, repoRoot }) {
|
|
|
217
217
|
path.resolve(project.root, project.config.knowledge ?? "knowledge"),
|
|
218
218
|
path.resolve(project.root, project.config.agents ?? "agents"),
|
|
219
219
|
path.resolve(project.root, project.config.skills ?? "skills"),
|
|
220
|
+
// Vibeables are the workspace's own apps: versioned with it, unlike clones.
|
|
221
|
+
vibeablesRootFor(project),
|
|
220
222
|
project.configPath,
|
|
221
223
|
];
|
|
222
224
|
const mapped = roots.filter((r) => !!r).map(rel).filter((r) => !!r);
|
|
@@ -393,7 +395,7 @@ export async function gitStatus(fresh = false) {
|
|
|
393
395
|
const blocked = all.filter((f) => !f.syncable).sort(byPath);
|
|
394
396
|
const blockedHidden = Math.max(0, blocked.length - MAX_BLOCKED);
|
|
395
397
|
const scopeError = scope.wholeRepo
|
|
396
|
-
? "A configured root points at the repository root, which would put the whole repo in scope. Narrow workflows/knowledge/agents/skills in kraftwerk.yml."
|
|
398
|
+
? "A configured root points at the repository root, which would put the whole repo in scope. Narrow workflows/knowledge/agents/skills/vibeables in kraftwerk.yml."
|
|
397
399
|
: undefined;
|
|
398
400
|
const value = {
|
|
399
401
|
...base,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Newest mtime under a folder, for "changed 5m ago" on lists of folders. A
|
|
5
|
+
* directory's own mtime only moves when an entry is added or removed, not
|
|
6
|
+
* when a file inside is edited, so the walk looks at files. Bounded: git
|
|
7
|
+
* internals, dependencies and dot entries are skipped, depth is capped, and
|
|
8
|
+
* a huge tree stops early — the number is a hint, not an audit.
|
|
9
|
+
*/
|
|
10
|
+
const SKIP = /^(\.git|node_modules|\.cache|\.vite|\.next|vendor|dist|build|target)$/;
|
|
11
|
+
export async function newestMtime(dir, depth = 0, budget = { files: 2_000 }) {
|
|
12
|
+
let newest = 0;
|
|
13
|
+
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
14
|
+
for (const e of entries) {
|
|
15
|
+
if (budget.files-- <= 0)
|
|
16
|
+
break;
|
|
17
|
+
if (SKIP.test(e.name) || e.name.startsWith("."))
|
|
18
|
+
continue;
|
|
19
|
+
const abs = path.join(dir, e.name);
|
|
20
|
+
if (e.isDirectory()) {
|
|
21
|
+
if (depth < 4)
|
|
22
|
+
newest = Math.max(newest, await newestMtime(abs, depth + 1, budget));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const st = await fs.stat(abs).catch(() => null);
|
|
26
|
+
if (st)
|
|
27
|
+
newest = Math.max(newest, st.mtimeMs);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return newest;
|
|
31
|
+
}
|
|
@@ -37,6 +37,8 @@ export interface RepoInfo {
|
|
|
37
37
|
ahead?: number;
|
|
38
38
|
/** Commits on the upstream not here. Absent when the branch tracks nothing. */
|
|
39
39
|
behind?: number;
|
|
40
|
+
/** Newest change in the working tree (.git, node_modules and dot entries excluded); lists sort by it. */
|
|
41
|
+
updatedAt?: string;
|
|
40
42
|
/** Set when git could not be read for this folder. */
|
|
41
43
|
error?: string;
|
|
42
44
|
}
|
package/dist/inspector/repos.js
CHANGED
|
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { gitignoreHas, ignoreEntryFor, reposRootFor, resolveProject } from "../config.js";
|
|
4
4
|
import { getProjectRoot } from "./context.js";
|
|
5
|
+
import { newestMtime } from "./mtime.js";
|
|
5
6
|
import { git, gitNet, sshCommandFor } from "./git.js";
|
|
6
7
|
/**
|
|
7
8
|
* A clone is the one call here that can legitimately take long: a large
|
|
@@ -61,11 +62,14 @@ export async function openRepos() {
|
|
|
61
62
|
async function inspect(root, slug) {
|
|
62
63
|
const dir = path.join(root, slug);
|
|
63
64
|
const info = { slug, path: dir, dirty: 0 };
|
|
64
|
-
const [status, log, origin] = await Promise.all([
|
|
65
|
+
const [status, log, origin, newest] = await Promise.all([
|
|
65
66
|
git(["status", "--porcelain=v2", "--branch", "--untracked-files=normal"], dir),
|
|
66
67
|
git(["log", "-1", "--format=%h%x00%s%x00%cI"], dir),
|
|
67
68
|
git(["remote", "get-url", "origin"], dir),
|
|
69
|
+
newestMtime(dir),
|
|
68
70
|
]);
|
|
71
|
+
if (newest)
|
|
72
|
+
info.updatedAt = new Date(newest).toISOString();
|
|
69
73
|
if (!status.ok)
|
|
70
74
|
return { ...info, error: status.stderr || "not a git repository" };
|
|
71
75
|
if (origin.ok)
|