wholestack 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/{chunk-7NZ77Q7Q.js → chunk-K6MSDQJT.js} +289 -147
- package/dist/cli.js +99 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,23 @@ on wholestack.ai.
|
|
|
43
43
|
| `-p, --print` | non-interactive, print result and exit |
|
|
44
44
|
| `--zeta-url <url>` | override the engine origin (defaults to https://wholestack.ai) |
|
|
45
45
|
|
|
46
|
+
## Projects — one spine with the web IDE
|
|
47
|
+
|
|
48
|
+
Everything you build (terminal, wholestack.ai/build, /demo) lands in the same
|
|
49
|
+
project list. The terminal can read and write the exact working tree the web
|
|
50
|
+
IDE edits:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
wholestack projects # list your platform projects
|
|
54
|
+
wholestack pull <id> [dir] # download a project's working tree
|
|
55
|
+
wholestack push [dir] # sync local edits back to the web IDE
|
|
56
|
+
wholestack open [buildId] # open a generated build in the web IDE
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`pull` links the directory via `.wholestack/project.json`, so a later `push`
|
|
60
|
+
(and `open` inside a delivered app) needs no arguments. Pushes skip
|
|
61
|
+
`node_modules`, build output, `.env*` and binaries.
|
|
62
|
+
|
|
46
63
|
## Security verification
|
|
47
64
|
|
|
48
65
|
Run ShipGate's gates on a generated contract:
|
|
@@ -455,16 +455,149 @@ function renderNewFileDiff(path, content, opts = {}) {
|
|
|
455
455
|
line();
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
// src/platform.ts
|
|
459
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
460
|
+
import { join, resolve, sep, dirname, normalize as pathNormalize } from "path";
|
|
461
|
+
import fg from "fast-glob";
|
|
462
|
+
function authHeaders() {
|
|
463
|
+
const key = process.env.ZETA_API_KEY?.trim();
|
|
464
|
+
if (!key) return null;
|
|
465
|
+
return { authorization: `Bearer ${key}`, "content-type": "application/json" };
|
|
466
|
+
}
|
|
467
|
+
var NO_KEY = "set ZETA_API_KEY (run `wholestack login`) first.";
|
|
468
|
+
async function apiJson(url, init) {
|
|
469
|
+
const headers = authHeaders();
|
|
470
|
+
if (!headers) return { ok: false, error: NO_KEY };
|
|
471
|
+
let resp;
|
|
472
|
+
try {
|
|
473
|
+
resp = await fetch(url, { ...init, headers: { ...headers, ...init?.headers ?? {} } });
|
|
474
|
+
} catch (e) {
|
|
475
|
+
return { ok: false, error: `cannot reach the platform: ${e.message}` };
|
|
476
|
+
}
|
|
477
|
+
const data = await resp.json().catch(() => null);
|
|
478
|
+
if (!resp.ok) {
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
status: resp.status,
|
|
482
|
+
error: data?.error ?? (resp.status === 401 ? "token rejected \u2014 run `wholestack login` again." : `platform returned ${resp.status}`)
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return { ok: true, data };
|
|
486
|
+
}
|
|
487
|
+
async function listProjects(apiUrl) {
|
|
488
|
+
const r = await apiJson(`${apiUrl}/api/projects`);
|
|
489
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
490
|
+
return { ok: true, projects: r.data.projects ?? [] };
|
|
491
|
+
}
|
|
492
|
+
async function pullWorkingTree(apiUrl, projectId) {
|
|
493
|
+
const r = await apiJson(
|
|
494
|
+
`${apiUrl}/api/projects/${encodeURIComponent(projectId)}/working-tree`
|
|
495
|
+
);
|
|
496
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
497
|
+
return { ok: true, name: r.data.project?.name, files: r.data.files ?? [] };
|
|
498
|
+
}
|
|
499
|
+
async function pushWorkingTree(apiUrl, projectId, files) {
|
|
500
|
+
const r = await apiJson(
|
|
501
|
+
`${apiUrl}/api/projects/${encodeURIComponent(projectId)}/working-tree`,
|
|
502
|
+
{ method: "PATCH", body: JSON.stringify({ files }) }
|
|
503
|
+
);
|
|
504
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
505
|
+
return { ok: true };
|
|
506
|
+
}
|
|
507
|
+
async function openBuildInEditor(apiUrl, buildId) {
|
|
508
|
+
const r = await apiJson(
|
|
509
|
+
`${apiUrl}/api/zeta/build/${encodeURIComponent(buildId)}/open-in-editor`,
|
|
510
|
+
{ method: "POST" }
|
|
511
|
+
);
|
|
512
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
513
|
+
return { ok: true, projectId: r.data.projectId, editorUrl: r.data.editorUrl };
|
|
514
|
+
}
|
|
515
|
+
var LINK_DIR = ".wholestack";
|
|
516
|
+
var LINK_FILE = "project.json";
|
|
517
|
+
function readProjectLink(dir) {
|
|
518
|
+
try {
|
|
519
|
+
const raw = readFileSync(join(dir, LINK_DIR, LINK_FILE), "utf8");
|
|
520
|
+
const json = JSON.parse(raw);
|
|
521
|
+
return json && typeof json === "object" ? json : null;
|
|
522
|
+
} catch {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function writeProjectLink(dir, link) {
|
|
527
|
+
try {
|
|
528
|
+
const prev = readProjectLink(dir) ?? {};
|
|
529
|
+
mkdirSync(join(dir, LINK_DIR), { recursive: true });
|
|
530
|
+
writeFileSync(
|
|
531
|
+
join(dir, LINK_DIR, LINK_FILE),
|
|
532
|
+
JSON.stringify({ ...prev, ...link }, null, 2) + "\n"
|
|
533
|
+
);
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function writeWorkingTree(destDir, files) {
|
|
538
|
+
const root = resolve(destDir);
|
|
539
|
+
let written = 0;
|
|
540
|
+
for (const f of files) {
|
|
541
|
+
if (!f?.path || typeof f.content !== "string") continue;
|
|
542
|
+
const rel = pathNormalize(f.path).replace(/^(\.\.(\/|\\|$))+/, "").replace(/^\/+/, "");
|
|
543
|
+
const full = resolve(root, rel);
|
|
544
|
+
if (full !== root && !full.startsWith(root + sep)) continue;
|
|
545
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
546
|
+
writeFileSync(full, f.content);
|
|
547
|
+
written += 1;
|
|
548
|
+
}
|
|
549
|
+
return written;
|
|
550
|
+
}
|
|
551
|
+
var PUSH_IGNORE = [
|
|
552
|
+
"**/node_modules/**",
|
|
553
|
+
"**/.next/**",
|
|
554
|
+
"**/.git/**",
|
|
555
|
+
"**/dist/**",
|
|
556
|
+
"**/.turbo/**",
|
|
557
|
+
"**/.wholestack/**",
|
|
558
|
+
"**/.zeta-g/**",
|
|
559
|
+
"**/*.lock",
|
|
560
|
+
"**/pnpm-lock.yaml",
|
|
561
|
+
"**/.env*"
|
|
562
|
+
];
|
|
563
|
+
var MAX_PUSH_FILES = 2e3;
|
|
564
|
+
var MAX_FILE_BYTES = 512 * 1024;
|
|
565
|
+
async function collectLocalFiles(dir) {
|
|
566
|
+
const paths = await fg("**/*", {
|
|
567
|
+
cwd: dir,
|
|
568
|
+
dot: true,
|
|
569
|
+
onlyFiles: true,
|
|
570
|
+
ignore: PUSH_IGNORE,
|
|
571
|
+
followSymbolicLinks: false
|
|
572
|
+
});
|
|
573
|
+
const files = [];
|
|
574
|
+
let skipped = 0;
|
|
575
|
+
for (const rel of paths.slice(0, MAX_PUSH_FILES)) {
|
|
576
|
+
try {
|
|
577
|
+
const buf = readFileSync(join(dir, rel));
|
|
578
|
+
if (buf.length > MAX_FILE_BYTES || buf.includes(0)) {
|
|
579
|
+
skipped += 1;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
files.push({ path: rel.split(sep).join("/"), content: buf.toString("utf8") });
|
|
583
|
+
} catch {
|
|
584
|
+
skipped += 1;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
skipped += Math.max(0, paths.length - MAX_PUSH_FILES);
|
|
588
|
+
return { files, skipped };
|
|
589
|
+
}
|
|
590
|
+
|
|
458
591
|
// src/prover.ts
|
|
459
592
|
import { spawn as spawn2 } from "child_process";
|
|
460
|
-
import { join as
|
|
461
|
-
import { existsSync as
|
|
593
|
+
import { join as join3 } from "path";
|
|
594
|
+
import { existsSync as existsSync3 } from "fs";
|
|
462
595
|
|
|
463
596
|
// src/zeta-engine.ts
|
|
464
597
|
import { spawn } from "child_process";
|
|
465
598
|
import { tmpdir } from "os";
|
|
466
|
-
import { join, dirname, normalize as
|
|
467
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
599
|
+
import { join as join2, dirname as dirname2, normalize as pathNormalize2, resolve as resolve2, sep as sep2 } from "path";
|
|
600
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
468
601
|
import { fileURLToPath } from "url";
|
|
469
602
|
function normalize(result) {
|
|
470
603
|
const files = Array.isArray(result.files) ? result.files : [];
|
|
@@ -651,25 +784,26 @@ async function deliverBuild(zetaApiUrl, buildId, destDir, onPhase) {
|
|
|
651
784
|
}
|
|
652
785
|
const data = await resp.json().catch(() => ({}));
|
|
653
786
|
const files = Array.isArray(data.files) ? data.files : [];
|
|
654
|
-
const root =
|
|
787
|
+
const root = resolve2(destDir);
|
|
655
788
|
let written = 0;
|
|
656
789
|
for (const f of files) {
|
|
657
790
|
if (!f?.path || typeof f.content !== "string") continue;
|
|
658
|
-
const rel =
|
|
659
|
-
const full =
|
|
660
|
-
if (full !== root && !full.startsWith(root +
|
|
661
|
-
|
|
662
|
-
|
|
791
|
+
const rel = pathNormalize2(f.path).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
792
|
+
const full = resolve2(root, rel);
|
|
793
|
+
if (full !== root && !full.startsWith(root + sep2)) continue;
|
|
794
|
+
mkdirSync2(dirname2(full), { recursive: true });
|
|
795
|
+
writeFileSync2(full, f.content);
|
|
663
796
|
written += 1;
|
|
664
797
|
onPhase?.(`wrote ${rel}`);
|
|
665
798
|
}
|
|
799
|
+
writeProjectLink(root, { buildId });
|
|
666
800
|
return { ok: true, written, dir: root };
|
|
667
801
|
}
|
|
668
802
|
function findRepoRoot(start) {
|
|
669
|
-
let dir = start ??
|
|
803
|
+
let dir = start ?? dirname2(fileURLToPath(import.meta.url));
|
|
670
804
|
for (let i = 0; i < 12; i++) {
|
|
671
|
-
if (
|
|
672
|
-
const up =
|
|
805
|
+
if (existsSync2(join2(dir, "scripts", "zeta-build-worker.mts"))) return dir;
|
|
806
|
+
const up = dirname2(dir);
|
|
673
807
|
if (up === dir) break;
|
|
674
808
|
dir = up;
|
|
675
809
|
}
|
|
@@ -689,9 +823,9 @@ async function localBuild(body, onPhase, repoRoot) {
|
|
|
689
823
|
error: "local build needs the ZETA monorepo (scripts/zeta-build-worker.mts not found). Run from inside the repo, or use the http engine (--zeta-url)."
|
|
690
824
|
};
|
|
691
825
|
}
|
|
692
|
-
const worker =
|
|
826
|
+
const worker = join2(root, "scripts", "zeta-build-worker.mts");
|
|
693
827
|
const buildId = `zeta-build-${Date.now().toString(36)}-${Math.floor(performance.now())}`;
|
|
694
|
-
const projectDir =
|
|
828
|
+
const projectDir = join2(tmpdir(), buildId);
|
|
695
829
|
const ideaB64 = Buffer.from(body.idea, "utf8").toString("base64");
|
|
696
830
|
const args = [
|
|
697
831
|
"--import",
|
|
@@ -753,11 +887,11 @@ var PROPERTY_KINDS = [
|
|
|
753
887
|
function resolveProver(repoRoot) {
|
|
754
888
|
const root = repoRoot ?? findRepoRoot();
|
|
755
889
|
if (!root) return null;
|
|
756
|
-
const base =
|
|
757
|
-
const dist =
|
|
758
|
-
if (
|
|
759
|
-
const src =
|
|
760
|
-
if (
|
|
890
|
+
const base = join3(root, "packages", "shipgate-contract-prover");
|
|
891
|
+
const dist = join3(base, "dist", "cli.js");
|
|
892
|
+
if (existsSync3(dist)) return { nodeArgs: [dist] };
|
|
893
|
+
const src = join3(base, "src", "cli.ts");
|
|
894
|
+
if (existsSync3(src)) return { nodeArgs: ["--import", "tsx", src] };
|
|
761
895
|
return null;
|
|
762
896
|
}
|
|
763
897
|
function runProver(proverArgs, opts) {
|
|
@@ -802,8 +936,8 @@ ${e.message}` });
|
|
|
802
936
|
// src/app-runner.ts
|
|
803
937
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
804
938
|
import { readFile } from "fs/promises";
|
|
805
|
-
import { existsSync as
|
|
806
|
-
import { join as
|
|
939
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
940
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
807
941
|
var running = [];
|
|
808
942
|
function killRunningApps() {
|
|
809
943
|
for (const a of running.splice(0)) {
|
|
@@ -816,8 +950,8 @@ function killRunningApps() {
|
|
|
816
950
|
var URL_RE = /(https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::\d+)?[^\s)]*)/i;
|
|
817
951
|
var FATAL_RE = /(Error:|error TS\d+|Cannot find module|EADDRINUSE|Failed to compile|SyntaxError|Module not found|exited with)/i;
|
|
818
952
|
async function detectRun(dir) {
|
|
819
|
-
const pkgPath =
|
|
820
|
-
if (!
|
|
953
|
+
const pkgPath = join4(dir, "package.json");
|
|
954
|
+
if (!existsSync4(pkgPath)) {
|
|
821
955
|
return { error: `no package.json in ${dir} \u2014 not a runnable app directory` };
|
|
822
956
|
}
|
|
823
957
|
let scripts = {};
|
|
@@ -827,7 +961,7 @@ async function detectRun(dir) {
|
|
|
827
961
|
} catch (e) {
|
|
828
962
|
return { error: `unreadable package.json: ${e.message}` };
|
|
829
963
|
}
|
|
830
|
-
const pm =
|
|
964
|
+
const pm = existsSync4(join4(dir, "pnpm-lock.yaml")) ? "pnpm" : existsSync4(join4(dir, "yarn.lock")) ? "yarn" : "npm";
|
|
831
965
|
for (const s of ["dev", "start", "serve"]) {
|
|
832
966
|
if (scripts[s]) return { command: pm, args: ["run", s], reason: `${pm} run ${s}` };
|
|
833
967
|
}
|
|
@@ -844,44 +978,44 @@ function pnpmAvailable() {
|
|
|
844
978
|
return _pnpmOnPath;
|
|
845
979
|
}
|
|
846
980
|
function installPmFor(dir) {
|
|
847
|
-
if (
|
|
848
|
-
if (
|
|
981
|
+
if (existsSync4(join4(dir, "yarn.lock"))) return "yarn";
|
|
982
|
+
if (existsSync4(join4(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
849
983
|
return pnpmAvailable() ? "pnpm" : "npm";
|
|
850
984
|
}
|
|
851
985
|
function ensureNpmrc(dir, lines) {
|
|
852
986
|
try {
|
|
853
|
-
const npmrc =
|
|
854
|
-
const prev =
|
|
987
|
+
const npmrc = join4(dir, ".npmrc");
|
|
988
|
+
const prev = existsSync4(npmrc) ? readFileSync2(npmrc, "utf8") : "";
|
|
855
989
|
const missing = lines.filter((l) => !prev.includes(l.split("=")[0]));
|
|
856
990
|
if (missing.length === 0) return;
|
|
857
|
-
const
|
|
858
|
-
|
|
991
|
+
const sep4 = prev && !prev.endsWith("\n") ? "\n" : "";
|
|
992
|
+
writeFileSync3(npmrc, prev + sep4 + missing.join("\n") + "\n");
|
|
859
993
|
} catch {
|
|
860
994
|
}
|
|
861
995
|
}
|
|
862
996
|
function isolateFromWorkspace(dir) {
|
|
863
|
-
if (
|
|
864
|
-
let cur =
|
|
997
|
+
if (existsSync4(join4(dir, "pnpm-workspace.yaml"))) return;
|
|
998
|
+
let cur = dirname3(dir);
|
|
865
999
|
let nested = false;
|
|
866
1000
|
for (let i = 0; i < 12; i++) {
|
|
867
|
-
if (
|
|
1001
|
+
if (existsSync4(join4(cur, "pnpm-workspace.yaml"))) {
|
|
868
1002
|
nested = true;
|
|
869
1003
|
break;
|
|
870
1004
|
}
|
|
871
|
-
const up =
|
|
1005
|
+
const up = dirname3(cur);
|
|
872
1006
|
if (up === cur) break;
|
|
873
1007
|
cur = up;
|
|
874
1008
|
}
|
|
875
1009
|
if (!nested) return;
|
|
876
1010
|
try {
|
|
877
|
-
|
|
1011
|
+
writeFileSync3(join4(dir, "pnpm-workspace.yaml"), "packages:\n - .\n");
|
|
878
1012
|
ensureNpmrc(dir, ["ignore-workspace-root-check=true"]);
|
|
879
1013
|
} catch {
|
|
880
1014
|
}
|
|
881
1015
|
}
|
|
882
1016
|
function installDeps(dir, onLog, signal) {
|
|
883
1017
|
const pm = installPmFor(dir);
|
|
884
|
-
if (pm === "pnpm" && !
|
|
1018
|
+
if (pm === "pnpm" && !existsSync4(join4(dir, "pnpm-lock.yaml"))) {
|
|
885
1019
|
ensureNpmrc(dir, ["shamefully-hoist=true", "prefer-offline=true"]);
|
|
886
1020
|
}
|
|
887
1021
|
const args = pm === "pnpm" ? (
|
|
@@ -891,7 +1025,7 @@ function installDeps(dir, onLog, signal) {
|
|
|
891
1025
|
["install", "--ignore-workspace", "--no-frozen-lockfile", "--prefer-offline", "--config.confirmModulesPurge=false"]
|
|
892
1026
|
) : pm === "yarn" ? ["install"] : ["install", "--legacy-peer-deps", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
893
1027
|
onLog?.(`installing dependencies (${pm})\u2026`);
|
|
894
|
-
return new Promise((
|
|
1028
|
+
return new Promise((resolve4) => {
|
|
895
1029
|
const child = spawn3(pm, args, { cwd: dir, shell: false, env: process.env });
|
|
896
1030
|
let log = "";
|
|
897
1031
|
const timer = setTimeout(() => child.kill("SIGKILL"), 8 * 6e4);
|
|
@@ -904,18 +1038,18 @@ function installDeps(dir, onLog, signal) {
|
|
|
904
1038
|
child.stderr.on("data", sink);
|
|
905
1039
|
child.on("error", (e) => {
|
|
906
1040
|
clearTimeout(timer);
|
|
907
|
-
|
|
1041
|
+
resolve4({ ok: false, log: log + `
|
|
908
1042
|
${e.message}` });
|
|
909
1043
|
});
|
|
910
1044
|
child.on("close", (code) => {
|
|
911
1045
|
clearTimeout(timer);
|
|
912
1046
|
signal?.removeEventListener("abort", onAbort);
|
|
913
|
-
|
|
1047
|
+
resolve4({ ok: code === 0, log: log.slice(-4e3) });
|
|
914
1048
|
});
|
|
915
1049
|
});
|
|
916
1050
|
}
|
|
917
1051
|
async function runApp(dir, opts = {}) {
|
|
918
|
-
if (!
|
|
1052
|
+
if (!existsSync4(join4(dir, "node_modules"))) {
|
|
919
1053
|
isolateFromWorkspace(dir);
|
|
920
1054
|
const inst = await installDeps(dir, void 0, opts.signal);
|
|
921
1055
|
if (!inst.ok) {
|
|
@@ -929,7 +1063,7 @@ async function runApp(dir, opts = {}) {
|
|
|
929
1063
|
let command;
|
|
930
1064
|
let cmdArgs;
|
|
931
1065
|
if (opts.script) {
|
|
932
|
-
const pm =
|
|
1066
|
+
const pm = existsSync4(join4(dir, "pnpm-lock.yaml")) ? "pnpm" : "npm";
|
|
933
1067
|
command = pm;
|
|
934
1068
|
cmdArgs = ["run", opts.script];
|
|
935
1069
|
} else {
|
|
@@ -940,7 +1074,7 @@ async function runApp(dir, opts = {}) {
|
|
|
940
1074
|
}
|
|
941
1075
|
const timeoutMs = opts.timeoutMs ?? 9e4;
|
|
942
1076
|
const child = spawn3(command, cmdArgs, { cwd: dir, shell: false, env: process.env });
|
|
943
|
-
return new Promise((
|
|
1077
|
+
return new Promise((resolve4) => {
|
|
944
1078
|
let log = "";
|
|
945
1079
|
let settled = false;
|
|
946
1080
|
const finish = (r) => {
|
|
@@ -948,7 +1082,7 @@ async function runApp(dir, opts = {}) {
|
|
|
948
1082
|
settled = true;
|
|
949
1083
|
clearTimeout(timer);
|
|
950
1084
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
951
|
-
|
|
1085
|
+
resolve4(r);
|
|
952
1086
|
};
|
|
953
1087
|
const timer = setTimeout(() => {
|
|
954
1088
|
child.kill("SIGTERM");
|
|
@@ -1091,13 +1225,13 @@ import { tool } from "ai";
|
|
|
1091
1225
|
import { z } from "zod";
|
|
1092
1226
|
import { spawn as spawn5 } from "child_process";
|
|
1093
1227
|
import { readFile as readFile2, writeFile, mkdir, readdir, stat } from "fs/promises";
|
|
1094
|
-
import { resolve as
|
|
1095
|
-
import
|
|
1228
|
+
import { resolve as resolve3, dirname as dirname4, relative, join as join5, sep as sep3 } from "path";
|
|
1229
|
+
import fg2 from "fast-glob";
|
|
1096
1230
|
var IGNORE = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/.next/**", "**/build/**", "**/.turbo/**"];
|
|
1097
1231
|
function inside(ctx, p) {
|
|
1098
|
-
const abs =
|
|
1232
|
+
const abs = resolve3(ctx.cwd, p);
|
|
1099
1233
|
const rel = relative(ctx.cwd, abs);
|
|
1100
|
-
if (rel === ".." || rel.startsWith(".." +
|
|
1234
|
+
if (rel === ".." || rel.startsWith(".." + sep3)) {
|
|
1101
1235
|
throw new Error(`path "${p}" escapes the workspace root`);
|
|
1102
1236
|
}
|
|
1103
1237
|
return abs;
|
|
@@ -1141,9 +1275,9 @@ ${CWD_MARK}:`);
|
|
|
1141
1275
|
const p = (nl >= 0 ? rest.slice(0, nl) : rest).trim();
|
|
1142
1276
|
out = out.slice(0, idx);
|
|
1143
1277
|
if (p) {
|
|
1144
|
-
const cand =
|
|
1278
|
+
const cand = resolve3(p);
|
|
1145
1279
|
const rel = relative(opts.root, cand);
|
|
1146
|
-
nextCwd = rel === "" || rel !== ".." && !rel.startsWith(".." +
|
|
1280
|
+
nextCwd = rel === "" || rel !== ".." && !rel.startsWith(".." + sep3) ? cand : opts.root;
|
|
1147
1281
|
}
|
|
1148
1282
|
}
|
|
1149
1283
|
res({ code: code ?? 1, out, cwd: nextCwd });
|
|
@@ -1180,7 +1314,7 @@ async function search(ctx, pattern, searchPath, globPat, ignoreCase, maxResults,
|
|
|
1180
1314
|
});
|
|
1181
1315
|
if (viaRg) return viaRg;
|
|
1182
1316
|
const re = new RegExp(pattern, ignoreCase ? "i" : "");
|
|
1183
|
-
const files = await
|
|
1317
|
+
const files = await fg2(fgPat, {
|
|
1184
1318
|
cwd: abs,
|
|
1185
1319
|
ignore: IGNORE,
|
|
1186
1320
|
onlyFiles: true,
|
|
@@ -1298,7 +1432,7 @@ function buildTools(ctx) {
|
|
|
1298
1432
|
}
|
|
1299
1433
|
}
|
|
1300
1434
|
if (keep !== false && viaHosted && result.ok && result.buildId && !result.paywalled) {
|
|
1301
|
-
const dest =
|
|
1435
|
+
const dest = join5(ctx.cwd, `zeta-${String(result.buildId).slice(-8)}`);
|
|
1302
1436
|
line(c.dim(" \u21B3 saving your code\u2026"));
|
|
1303
1437
|
const delivered = await deliverBuild(
|
|
1304
1438
|
ctx.zetaApiUrl,
|
|
@@ -1414,7 +1548,7 @@ function buildTools(ctx) {
|
|
|
1414
1548
|
ctx.checkpoints?.begin();
|
|
1415
1549
|
ctx.checkpoints?.capture(abs, before);
|
|
1416
1550
|
ctx.checkpoints?.commit(before == null ? `create ${path}` : `write ${path}`);
|
|
1417
|
-
await mkdir(
|
|
1551
|
+
await mkdir(dirname4(abs), { recursive: true });
|
|
1418
1552
|
await writeFile(abs, content, "utf8");
|
|
1419
1553
|
toolLine("write_file", c.dim(path));
|
|
1420
1554
|
return { ok: true, path, bytes: Buffer.byteLength(content) };
|
|
@@ -1546,7 +1680,7 @@ function buildTools(ctx) {
|
|
|
1546
1680
|
const names = await readdir(abs);
|
|
1547
1681
|
const entries = await Promise.all(
|
|
1548
1682
|
names.map(async (n) => {
|
|
1549
|
-
const s = await stat(
|
|
1683
|
+
const s = await stat(join5(abs, n)).catch(() => null);
|
|
1550
1684
|
return { name: n, dir: s?.isDirectory() ?? false };
|
|
1551
1685
|
})
|
|
1552
1686
|
);
|
|
@@ -1565,7 +1699,7 @@ function buildTools(ctx) {
|
|
|
1565
1699
|
execute: async ({ pattern, path }) => {
|
|
1566
1700
|
try {
|
|
1567
1701
|
const abs = inside(ctx, path);
|
|
1568
|
-
const files = await
|
|
1702
|
+
const files = await fg2(pattern, {
|
|
1569
1703
|
cwd: abs,
|
|
1570
1704
|
ignore: IGNORE,
|
|
1571
1705
|
onlyFiles: true,
|
|
@@ -1721,23 +1855,23 @@ function buildTools(ctx) {
|
|
|
1721
1855
|
}
|
|
1722
1856
|
|
|
1723
1857
|
// src/config.ts
|
|
1724
|
-
import { readFileSync as
|
|
1858
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5, chmodSync, renameSync } from "fs";
|
|
1725
1859
|
import { homedir } from "os";
|
|
1726
|
-
import { join as
|
|
1727
|
-
var LEGACY_DIR =
|
|
1728
|
-
var STATE_DIR =
|
|
1860
|
+
import { join as join6 } from "path";
|
|
1861
|
+
var LEGACY_DIR = join6(homedir(), ".zeta-g");
|
|
1862
|
+
var STATE_DIR = join6(homedir(), ".wholestack");
|
|
1729
1863
|
try {
|
|
1730
|
-
if (
|
|
1864
|
+
if (existsSync5(LEGACY_DIR) && !existsSync5(STATE_DIR)) renameSync(LEGACY_DIR, STATE_DIR);
|
|
1731
1865
|
} catch {
|
|
1732
1866
|
}
|
|
1733
1867
|
function stateDir() {
|
|
1734
1868
|
return STATE_DIR;
|
|
1735
1869
|
}
|
|
1736
1870
|
function projectDirs(cwd) {
|
|
1737
|
-
return [
|
|
1871
|
+
return [join6(cwd, ".wholestack"), join6(cwd, ".zeta-g")];
|
|
1738
1872
|
}
|
|
1739
1873
|
var CONFIG_DIR = STATE_DIR;
|
|
1740
|
-
var CONFIG_PATH =
|
|
1874
|
+
var CONFIG_PATH = join6(CONFIG_DIR, "config.json");
|
|
1741
1875
|
function parseDotenv(text) {
|
|
1742
1876
|
const out = {};
|
|
1743
1877
|
for (const raw of text.split("\n")) {
|
|
@@ -1760,16 +1894,16 @@ function fill(src) {
|
|
|
1760
1894
|
}
|
|
1761
1895
|
}
|
|
1762
1896
|
function loadStoredEnv() {
|
|
1763
|
-
const localEnv =
|
|
1764
|
-
if (
|
|
1897
|
+
const localEnv = join6(process.cwd(), ".env");
|
|
1898
|
+
if (existsSync5(localEnv)) {
|
|
1765
1899
|
try {
|
|
1766
|
-
fill(parseDotenv(
|
|
1900
|
+
fill(parseDotenv(readFileSync3(localEnv, "utf8")));
|
|
1767
1901
|
} catch {
|
|
1768
1902
|
}
|
|
1769
1903
|
}
|
|
1770
|
-
if (
|
|
1904
|
+
if (existsSync5(CONFIG_PATH)) {
|
|
1771
1905
|
try {
|
|
1772
|
-
const json = JSON.parse(
|
|
1906
|
+
const json = JSON.parse(readFileSync3(CONFIG_PATH, "utf8"));
|
|
1773
1907
|
const flat = {};
|
|
1774
1908
|
for (const [k, v] of Object.entries(json)) if (typeof v === "string") flat[k] = v;
|
|
1775
1909
|
fill(flat);
|
|
@@ -1778,25 +1912,25 @@ function loadStoredEnv() {
|
|
|
1778
1912
|
}
|
|
1779
1913
|
}
|
|
1780
1914
|
function saveKey(name, value) {
|
|
1781
|
-
|
|
1915
|
+
mkdirSync3(CONFIG_DIR, { recursive: true });
|
|
1782
1916
|
let current = {};
|
|
1783
|
-
if (
|
|
1917
|
+
if (existsSync5(CONFIG_PATH)) {
|
|
1784
1918
|
try {
|
|
1785
|
-
current = JSON.parse(
|
|
1919
|
+
current = JSON.parse(readFileSync3(CONFIG_PATH, "utf8"));
|
|
1786
1920
|
} catch {
|
|
1787
1921
|
current = {};
|
|
1788
1922
|
}
|
|
1789
1923
|
}
|
|
1790
1924
|
current[name] = value;
|
|
1791
|
-
|
|
1925
|
+
writeFileSync4(CONFIG_PATH, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
|
|
1792
1926
|
chmodSync(CONFIG_PATH, 384);
|
|
1793
1927
|
return CONFIG_PATH;
|
|
1794
1928
|
}
|
|
1795
1929
|
|
|
1796
1930
|
// src/hooks.ts
|
|
1797
1931
|
import { spawn as spawn6 } from "child_process";
|
|
1798
|
-
import { readFileSync as
|
|
1799
|
-
import { join as
|
|
1932
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
1933
|
+
import { join as join7 } from "path";
|
|
1800
1934
|
var HOOK_TIMEOUT_MS = 15e3;
|
|
1801
1935
|
function runOne(def, event, payload, cwd) {
|
|
1802
1936
|
return new Promise((res) => {
|
|
@@ -1882,12 +2016,12 @@ function mergeHookSets(...sets) {
|
|
|
1882
2016
|
return out;
|
|
1883
2017
|
}
|
|
1884
2018
|
function loadHookFiles(cwd) {
|
|
1885
|
-
const files = [
|
|
2019
|
+
const files = [join7(stateDir(), "hooks.json"), ...projectDirs(cwd).map((d) => join7(d, "hooks.json"))];
|
|
1886
2020
|
const sets = [];
|
|
1887
2021
|
for (const f of files) {
|
|
1888
|
-
if (!
|
|
2022
|
+
if (!existsSync6(f)) continue;
|
|
1889
2023
|
try {
|
|
1890
|
-
sets.push(JSON.parse(
|
|
2024
|
+
sets.push(JSON.parse(readFileSync4(f, "utf8")));
|
|
1891
2025
|
} catch {
|
|
1892
2026
|
}
|
|
1893
2027
|
}
|
|
@@ -3019,24 +3153,24 @@ import {
|
|
|
3019
3153
|
// src/markdown.ts
|
|
3020
3154
|
var useColor2 = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
3021
3155
|
var truecolor = /truecolor|24bit/i.test(process.env.COLORTERM ?? "");
|
|
3022
|
-
function
|
|
3156
|
+
function fg3(r, g, b, c256) {
|
|
3023
3157
|
if (!useColor2) return "";
|
|
3024
3158
|
return truecolor ? `\x1B[38;2;${r};${g};${b}m` : `\x1B[38;5;${c256}m`;
|
|
3025
3159
|
}
|
|
3026
3160
|
var RESET = useColor2 ? "\x1B[0m" : "";
|
|
3027
3161
|
var SY = {
|
|
3028
|
-
keyword:
|
|
3162
|
+
keyword: fg3(198, 120, 221, 176),
|
|
3029
3163
|
// violet
|
|
3030
|
-
string:
|
|
3164
|
+
string: fg3(152, 195, 121, 114),
|
|
3031
3165
|
// green
|
|
3032
|
-
number:
|
|
3166
|
+
number: fg3(209, 154, 102, 173),
|
|
3033
3167
|
// orange
|
|
3034
|
-
comment:
|
|
3168
|
+
comment: fg3(106, 115, 125, 244),
|
|
3035
3169
|
// gray
|
|
3036
|
-
fn:
|
|
3170
|
+
fn: fg3(97, 175, 239, 75),
|
|
3037
3171
|
// blue
|
|
3038
|
-
punct:
|
|
3039
|
-
type:
|
|
3172
|
+
punct: fg3(160, 168, 180, 247),
|
|
3173
|
+
type: fg3(229, 192, 123, 180)
|
|
3040
3174
|
// yellow
|
|
3041
3175
|
};
|
|
3042
3176
|
var KEYWORDS = /\b(const|let|var|function|return|if|else|for|while|switch|case|break|continue|new|class|extends|import|export|from|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|this|super|yield|static|public|private|protected|readonly|interface|type|enum|namespace|implements|as|void|null|undefined|true|false|def|elif|lambda|pass|with|fn|let|mut|pub|use|struct|impl|match|where|select|insert|update|delete|create|table|primary|key|foreign|references|contract|mapping|address|uint256|require|emit|modifier|memory|storage|payable)\b/g;
|
|
@@ -3085,7 +3219,7 @@ function highlightLine(src, _lang) {
|
|
|
3085
3219
|
function inline(s) {
|
|
3086
3220
|
if (!useColor2) return s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
3087
3221
|
let out = s;
|
|
3088
|
-
out = out.replace(/`([^`]+)`/g, (_m, code) =>
|
|
3222
|
+
out = out.replace(/`([^`]+)`/g, (_m, code) => fg3(229, 192, 123, 180) + code + RESET);
|
|
3089
3223
|
out = out.replace(/\*\*([^*]+)\*\*/g, (_m, t) => "\x1B[1m" + t + "\x1B[22m");
|
|
3090
3224
|
out = out.replace(/(^|[^*])\*([^*]+)\*/g, (_m, p, t) => p + "\x1B[3m" + t + "\x1B[23m");
|
|
3091
3225
|
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, t, u) => "\x1B[4m" + t + "\x1B[24m" + c.dim(` (${u})`));
|
|
@@ -3880,16 +4014,16 @@ function announcePlanMode() {
|
|
|
3880
4014
|
|
|
3881
4015
|
// src/memory.ts
|
|
3882
4016
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
3883
|
-
import { existsSync as
|
|
3884
|
-
import { join as
|
|
4017
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4018
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
3885
4019
|
var FILE_NAMES = ["ZETA.md", "AGENTS.md", "CLAUDE.md"];
|
|
3886
4020
|
var MAX_TOTAL = 14e3;
|
|
3887
4021
|
var MAX_PER_FILE = 8e3;
|
|
3888
4022
|
function repoRootFrom(start) {
|
|
3889
4023
|
let dir = start;
|
|
3890
4024
|
for (let i = 0; i < 24; i++) {
|
|
3891
|
-
if (
|
|
3892
|
-
const up =
|
|
4025
|
+
if (existsSync7(join8(dir, ".git"))) return dir;
|
|
4026
|
+
const up = dirname5(dir);
|
|
3893
4027
|
if (up === dir) break;
|
|
3894
4028
|
dir = up;
|
|
3895
4029
|
}
|
|
@@ -3906,22 +4040,22 @@ async function readBounded(path, limit) {
|
|
|
3906
4040
|
}
|
|
3907
4041
|
async function loadProjectMemory(cwd) {
|
|
3908
4042
|
const candidates = [];
|
|
3909
|
-
const globalFile =
|
|
3910
|
-
if (
|
|
4043
|
+
const globalFile = join8(stateDir(), "ZETA.md");
|
|
4044
|
+
if (existsSync7(globalFile)) candidates.push(globalFile);
|
|
3911
4045
|
const root = repoRootFrom(cwd);
|
|
3912
4046
|
const chain = [];
|
|
3913
4047
|
let dir = cwd;
|
|
3914
4048
|
for (let i = 0; i < 24; i++) {
|
|
3915
4049
|
chain.unshift(dir);
|
|
3916
4050
|
if (dir === root) break;
|
|
3917
|
-
const up =
|
|
4051
|
+
const up = dirname5(dir);
|
|
3918
4052
|
if (up === dir) break;
|
|
3919
4053
|
dir = up;
|
|
3920
4054
|
}
|
|
3921
4055
|
for (const d of chain) {
|
|
3922
4056
|
for (const name of FILE_NAMES) {
|
|
3923
|
-
const p =
|
|
3924
|
-
if (
|
|
4057
|
+
const p = join8(d, name);
|
|
4058
|
+
if (existsSync7(p) && !candidates.includes(p)) {
|
|
3925
4059
|
candidates.push(p);
|
|
3926
4060
|
break;
|
|
3927
4061
|
}
|
|
@@ -3948,20 +4082,20 @@ ${r.text.trim()}
|
|
|
3948
4082
|
// src/session.ts
|
|
3949
4083
|
import {
|
|
3950
4084
|
appendFileSync,
|
|
3951
|
-
mkdirSync as
|
|
3952
|
-
readFileSync as
|
|
4085
|
+
mkdirSync as mkdirSync4,
|
|
4086
|
+
readFileSync as readFileSync5,
|
|
3953
4087
|
readdirSync,
|
|
3954
|
-
existsSync as
|
|
4088
|
+
existsSync as existsSync8,
|
|
3955
4089
|
statSync
|
|
3956
4090
|
} from "fs";
|
|
3957
|
-
import { join as
|
|
4091
|
+
import { join as join9 } from "path";
|
|
3958
4092
|
import { randomUUID } from "crypto";
|
|
3959
|
-
var SESSIONS_DIR =
|
|
4093
|
+
var SESSIONS_DIR = join9(stateDir(), "sessions");
|
|
3960
4094
|
function ensureDir() {
|
|
3961
|
-
|
|
4095
|
+
mkdirSync4(SESSIONS_DIR, { recursive: true });
|
|
3962
4096
|
}
|
|
3963
4097
|
function fileFor(id) {
|
|
3964
|
-
return
|
|
4098
|
+
return join9(SESSIONS_DIR, `${id}.jsonl`);
|
|
3965
4099
|
}
|
|
3966
4100
|
function previewOf(message) {
|
|
3967
4101
|
if (message.role !== "user") return "";
|
|
@@ -3992,8 +4126,8 @@ var Session = class _Session {
|
|
|
3992
4126
|
/** Reopen an existing session and replay its messages. */
|
|
3993
4127
|
static resume(id) {
|
|
3994
4128
|
const path = fileFor(id);
|
|
3995
|
-
if (!
|
|
3996
|
-
const lines =
|
|
4129
|
+
if (!existsSync8(path)) return null;
|
|
4130
|
+
const lines = readFileSync5(path, "utf8").split("\n").filter(Boolean);
|
|
3997
4131
|
let meta = null;
|
|
3998
4132
|
const messages = [];
|
|
3999
4133
|
for (const l of lines) {
|
|
@@ -4016,16 +4150,16 @@ var Session = class _Session {
|
|
|
4016
4150
|
}
|
|
4017
4151
|
/** List sessions newest-first, with a preview of the first user message. */
|
|
4018
4152
|
static list(limit = 30, cwd) {
|
|
4019
|
-
if (!
|
|
4153
|
+
if (!existsSync8(SESSIONS_DIR)) return [];
|
|
4020
4154
|
const out = [];
|
|
4021
4155
|
for (const name of readdirSync(SESSIONS_DIR)) {
|
|
4022
4156
|
if (!name.endsWith(".jsonl")) continue;
|
|
4023
|
-
const path =
|
|
4157
|
+
const path = join9(SESSIONS_DIR, name);
|
|
4024
4158
|
let meta = null;
|
|
4025
4159
|
let preview = "";
|
|
4026
4160
|
let turns = 0;
|
|
4027
4161
|
try {
|
|
4028
|
-
const lines =
|
|
4162
|
+
const lines = readFileSync5(path, "utf8").split("\n").filter(Boolean);
|
|
4029
4163
|
for (const l of lines) {
|
|
4030
4164
|
const rec = JSON.parse(l);
|
|
4031
4165
|
if (rec.kind === "meta") meta = { id: rec.id, cwd: rec.cwd, model: rec.model, startedAt: rec.startedAt };
|
|
@@ -4058,8 +4192,8 @@ var Session = class _Session {
|
|
|
4058
4192
|
};
|
|
4059
4193
|
|
|
4060
4194
|
// src/commands.ts
|
|
4061
|
-
import { writeFileSync as
|
|
4062
|
-
import { join as
|
|
4195
|
+
import { writeFileSync as writeFileSync5, existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
4196
|
+
import { join as join10 } from "path";
|
|
4063
4197
|
|
|
4064
4198
|
// src/git.ts
|
|
4065
4199
|
import { execFileSync } from "child_process";
|
|
@@ -4385,13 +4519,13 @@ var BUILTINS = [
|
|
|
4385
4519
|
summary: "create a ZETA.md memory scaffold here",
|
|
4386
4520
|
source: "builtin",
|
|
4387
4521
|
run: (ctx) => {
|
|
4388
|
-
const path =
|
|
4389
|
-
if (
|
|
4522
|
+
const path = join10(ctx.cwd, "ZETA.md");
|
|
4523
|
+
if (existsSync9(path)) {
|
|
4390
4524
|
ctx.print(" " + c.yellow(`ZETA.md already exists at ${path}`));
|
|
4391
4525
|
return { type: "handled" };
|
|
4392
4526
|
}
|
|
4393
4527
|
try {
|
|
4394
|
-
|
|
4528
|
+
writeFileSync5(path, ZETA_MD_TEMPLATE, "utf8");
|
|
4395
4529
|
ctx.print(" " + c.green(`\u2713 wrote ${path}`) + c.dim(" \xB7 edit it, then /clear to reload"));
|
|
4396
4530
|
} catch (e) {
|
|
4397
4531
|
ctx.print(" " + c.red(e.message));
|
|
@@ -4588,12 +4722,12 @@ function parseCustom(name, body, source = "custom") {
|
|
|
4588
4722
|
};
|
|
4589
4723
|
}
|
|
4590
4724
|
function loadMdCommands(dir, source) {
|
|
4591
|
-
if (!
|
|
4725
|
+
if (!existsSync9(dir)) return [];
|
|
4592
4726
|
const cmds = [];
|
|
4593
4727
|
for (const file of readdirSync2(dir)) {
|
|
4594
4728
|
if (!file.endsWith(".md")) continue;
|
|
4595
4729
|
try {
|
|
4596
|
-
const body =
|
|
4730
|
+
const body = readFileSync6(join10(dir, file), "utf8");
|
|
4597
4731
|
cmds.push(parseCustom(file.replace(/\.md$/, ""), body, source));
|
|
4598
4732
|
} catch {
|
|
4599
4733
|
}
|
|
@@ -4629,7 +4763,7 @@ var CommandRegistry = class {
|
|
|
4629
4763
|
}
|
|
4630
4764
|
/** Load *.md commands from ~/.wholestack/commands and <cwd>/.wholestack/commands (or legacy .zeta-g/). */
|
|
4631
4765
|
loadCustom(cwd) {
|
|
4632
|
-
const dirs = [
|
|
4766
|
+
const dirs = [join10(stateDir(), "commands"), ...projectDirs(cwd).map((d) => join10(d, "commands"))];
|
|
4633
4767
|
for (const dir of dirs) {
|
|
4634
4768
|
for (const cmd of loadMdCommands(dir, "custom")) this.register(cmd);
|
|
4635
4769
|
}
|
|
@@ -4649,16 +4783,16 @@ var CommandRegistry = class {
|
|
|
4649
4783
|
};
|
|
4650
4784
|
|
|
4651
4785
|
// src/plugins.ts
|
|
4652
|
-
import { readFileSync as
|
|
4653
|
-
import { join as
|
|
4786
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
4787
|
+
import { join as join11 } from "path";
|
|
4654
4788
|
function pluginRoots(cwd) {
|
|
4655
|
-
return [
|
|
4789
|
+
return [join11(stateDir(), "plugins"), ...projectDirs(cwd).map((d) => join11(d, "plugins"))];
|
|
4656
4790
|
}
|
|
4657
4791
|
function resolveSystemPrompt(dir, value) {
|
|
4658
|
-
const asFile =
|
|
4659
|
-
if (value.length < 200 &&
|
|
4792
|
+
const asFile = join11(dir, value);
|
|
4793
|
+
if (value.length < 200 && existsSync10(asFile)) {
|
|
4660
4794
|
try {
|
|
4661
|
-
return
|
|
4795
|
+
return readFileSync7(asFile, "utf8").trim();
|
|
4662
4796
|
} catch {
|
|
4663
4797
|
return "";
|
|
4664
4798
|
}
|
|
@@ -4677,9 +4811,9 @@ function loadPlugins(cwd) {
|
|
|
4677
4811
|
const systemBlocks = [];
|
|
4678
4812
|
const hookSets = [];
|
|
4679
4813
|
for (const root of pluginRoots(cwd)) {
|
|
4680
|
-
if (!
|
|
4814
|
+
if (!existsSync10(root)) continue;
|
|
4681
4815
|
for (const entry of readdirSync3(root)) {
|
|
4682
|
-
const dir =
|
|
4816
|
+
const dir = join11(root, entry);
|
|
4683
4817
|
let info = null;
|
|
4684
4818
|
try {
|
|
4685
4819
|
info = statSync2(dir);
|
|
@@ -4687,10 +4821,10 @@ function loadPlugins(cwd) {
|
|
|
4687
4821
|
continue;
|
|
4688
4822
|
}
|
|
4689
4823
|
if (!info.isDirectory()) continue;
|
|
4690
|
-
const manifestPath =
|
|
4691
|
-
if (!
|
|
4824
|
+
const manifestPath = join11(dir, "zeta-plugin.json");
|
|
4825
|
+
if (!existsSync10(manifestPath)) continue;
|
|
4692
4826
|
try {
|
|
4693
|
-
const manifest = JSON.parse(
|
|
4827
|
+
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
4694
4828
|
const name = manifest.name ?? entry;
|
|
4695
4829
|
if (manifest.systemPrompt) {
|
|
4696
4830
|
const text = resolveSystemPrompt(dir, manifest.systemPrompt);
|
|
@@ -4698,7 +4832,7 @@ function loadPlugins(cwd) {
|
|
|
4698
4832
|
${text}
|
|
4699
4833
|
</plugin>`);
|
|
4700
4834
|
}
|
|
4701
|
-
const cmdDir =
|
|
4835
|
+
const cmdDir = join11(dir, manifest.commandsDir ?? "commands");
|
|
4702
4836
|
result.commands.push(...loadMdCommands(cmdDir, name));
|
|
4703
4837
|
if (manifest.mcpServers) Object.assign(result.mcpServers, manifest.mcpServers);
|
|
4704
4838
|
if (manifest.hooks) hookSets.push(tagHooks(manifest.hooks, name));
|
|
@@ -4726,14 +4860,14 @@ import { dynamicTool, jsonSchema } from "ai";
|
|
|
4726
4860
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4727
4861
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4728
4862
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4729
|
-
import { readFileSync as
|
|
4730
|
-
import { join as
|
|
4863
|
+
import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
|
|
4864
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
4731
4865
|
function isHttp(c2) {
|
|
4732
4866
|
return typeof c2.url === "string";
|
|
4733
4867
|
}
|
|
4734
4868
|
function readConfigFile(path) {
|
|
4735
4869
|
try {
|
|
4736
|
-
const json = JSON.parse(
|
|
4870
|
+
const json = JSON.parse(readFileSync8(path, "utf8"));
|
|
4737
4871
|
return json.mcpServers ?? {};
|
|
4738
4872
|
} catch {
|
|
4739
4873
|
return {};
|
|
@@ -4741,28 +4875,28 @@ function readConfigFile(path) {
|
|
|
4741
4875
|
}
|
|
4742
4876
|
function discoverConfigs(cwd) {
|
|
4743
4877
|
const merged = {};
|
|
4744
|
-
const global =
|
|
4745
|
-
if (
|
|
4878
|
+
const global = join12(stateDir(), "mcp.json");
|
|
4879
|
+
if (existsSync11(global)) Object.assign(merged, readConfigFile(global));
|
|
4746
4880
|
let dir = cwd;
|
|
4747
4881
|
for (let i = 0; i < 24; i++) {
|
|
4748
|
-
const p =
|
|
4749
|
-
if (
|
|
4882
|
+
const p = join12(dir, ".mcp.json");
|
|
4883
|
+
if (existsSync11(p)) {
|
|
4750
4884
|
Object.assign(merged, readConfigFile(p));
|
|
4751
4885
|
break;
|
|
4752
4886
|
}
|
|
4753
|
-
const up =
|
|
4887
|
+
const up = dirname6(dir);
|
|
4754
4888
|
if (up === dir) break;
|
|
4755
4889
|
dir = up;
|
|
4756
4890
|
}
|
|
4757
4891
|
return merged;
|
|
4758
4892
|
}
|
|
4759
4893
|
function withTimeout(p, ms, label) {
|
|
4760
|
-
return new Promise((
|
|
4894
|
+
return new Promise((resolve4, reject) => {
|
|
4761
4895
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
4762
4896
|
p.then(
|
|
4763
4897
|
(v) => {
|
|
4764
4898
|
clearTimeout(t);
|
|
4765
|
-
|
|
4899
|
+
resolve4(v);
|
|
4766
4900
|
},
|
|
4767
4901
|
(e) => {
|
|
4768
4902
|
clearTimeout(t);
|
|
@@ -4793,7 +4927,7 @@ async function loadMcpTools(opts) {
|
|
|
4793
4927
|
await Promise.all(
|
|
4794
4928
|
keys.map(async (key) => {
|
|
4795
4929
|
const cfg = configs[key];
|
|
4796
|
-
const client = new Client({ name: "wholestack", version: "0.5.
|
|
4930
|
+
const client = new Client({ name: "wholestack", version: "0.5.6" }, { capabilities: {} });
|
|
4797
4931
|
try {
|
|
4798
4932
|
const transport = isHttp(cfg) ? new StreamableHTTPClientTransport(new URL(cfg.url), {
|
|
4799
4933
|
requestInit: cfg.headers ? { headers: cfg.headers } : void 0
|
|
@@ -5003,13 +5137,13 @@ function buildWebTools() {
|
|
|
5003
5137
|
import { createInterface } from "readline/promises";
|
|
5004
5138
|
import { emitKeypressEvents } from "readline";
|
|
5005
5139
|
import { stdin, stdout } from "process";
|
|
5006
|
-
import { readFileSync as
|
|
5007
|
-
import { join as
|
|
5008
|
-
var HISTORY_FILE =
|
|
5140
|
+
import { readFileSync as readFileSync9, appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync12, readdirSync as readdirSync4 } from "fs";
|
|
5141
|
+
import { join as join13, dirname as dirname7, basename } from "path";
|
|
5142
|
+
var HISTORY_FILE = join13(stateDir(), "history");
|
|
5009
5143
|
var HISTORY_MAX = 1e3;
|
|
5010
5144
|
function loadHistory() {
|
|
5011
5145
|
try {
|
|
5012
|
-
return
|
|
5146
|
+
return readFileSync9(HISTORY_FILE, "utf8").split("\n").filter(Boolean).slice(-HISTORY_MAX);
|
|
5013
5147
|
} catch {
|
|
5014
5148
|
return [];
|
|
5015
5149
|
}
|
|
@@ -5045,12 +5179,12 @@ var InputController = class {
|
|
|
5045
5179
|
const at = line2.lastIndexOf("@");
|
|
5046
5180
|
if (at >= 0 && at >= line2.lastIndexOf(" ")) {
|
|
5047
5181
|
const partial = line2.slice(at + 1);
|
|
5048
|
-
const dir = partial.includes("/") ?
|
|
5182
|
+
const dir = partial.includes("/") ? dirname7(partial) : ".";
|
|
5049
5183
|
const base = basename(partial);
|
|
5050
5184
|
try {
|
|
5051
5185
|
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
5052
5186
|
const hits = entries.filter((e) => e.name.startsWith(base)).map((e) => {
|
|
5053
|
-
const full = dir === "." ? e.name :
|
|
5187
|
+
const full = dir === "." ? e.name : join13(dir, e.name);
|
|
5054
5188
|
return line2.slice(0, at + 1) + full + (e.isDirectory() ? "/" : "");
|
|
5055
5189
|
});
|
|
5056
5190
|
if (hits.length) return [hits, line2];
|
|
@@ -5155,7 +5289,7 @@ var InputController = class {
|
|
|
5155
5289
|
prevCaretR = caret.r;
|
|
5156
5290
|
};
|
|
5157
5291
|
let prevCaretR = 0;
|
|
5158
|
-
return new Promise((
|
|
5292
|
+
return new Promise((resolve4) => {
|
|
5159
5293
|
emitKeypressEvents(stdin);
|
|
5160
5294
|
const watching = this.activeWatch;
|
|
5161
5295
|
if (watching) this.stopWatch();
|
|
@@ -5179,7 +5313,7 @@ var InputController = class {
|
|
|
5179
5313
|
stdout.write("\x1B[" + Math.max(1, down) + "B\r\n");
|
|
5180
5314
|
this.rl.resume();
|
|
5181
5315
|
if (watching) this.startWatch(watching);
|
|
5182
|
-
|
|
5316
|
+
resolve4(val);
|
|
5183
5317
|
};
|
|
5184
5318
|
const replaceLine = (s) => {
|
|
5185
5319
|
buf = s;
|
|
@@ -5311,8 +5445,8 @@ var InputController = class {
|
|
|
5311
5445
|
}
|
|
5312
5446
|
saveHistory(entry) {
|
|
5313
5447
|
try {
|
|
5314
|
-
|
|
5315
|
-
if (!
|
|
5448
|
+
mkdirSync5(dirname7(HISTORY_FILE), { recursive: true });
|
|
5449
|
+
if (!existsSync12(HISTORY_FILE) || entry !== loadHistory().slice(-1)[0]) {
|
|
5316
5450
|
appendFileSync2(HISTORY_FILE, entry.replace(/\n/g, " ") + "\n", "utf8");
|
|
5317
5451
|
}
|
|
5318
5452
|
} catch {
|
|
@@ -5401,6 +5535,14 @@ export {
|
|
|
5401
5535
|
diffStat,
|
|
5402
5536
|
renderEditDiff,
|
|
5403
5537
|
renderNewFileDiff,
|
|
5538
|
+
listProjects,
|
|
5539
|
+
pullWorkingTree,
|
|
5540
|
+
pushWorkingTree,
|
|
5541
|
+
openBuildInEditor,
|
|
5542
|
+
readProjectLink,
|
|
5543
|
+
writeProjectLink,
|
|
5544
|
+
writeWorkingTree,
|
|
5545
|
+
collectLocalFiles,
|
|
5404
5546
|
PROPERTY_KINDS,
|
|
5405
5547
|
runProver,
|
|
5406
5548
|
killRunningApps,
|
package/dist/cli.js
CHANGED
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
buildTools,
|
|
13
13
|
buildWebTools,
|
|
14
14
|
c,
|
|
15
|
+
collectLocalFiles,
|
|
15
16
|
isPermissionMode,
|
|
16
17
|
killRunningApps,
|
|
17
18
|
line,
|
|
19
|
+
listProjects,
|
|
18
20
|
loadHookFiles,
|
|
19
21
|
loadMcpTools,
|
|
20
22
|
loadPlugins,
|
|
@@ -23,6 +25,10 @@ import {
|
|
|
23
25
|
mergeHookSets,
|
|
24
26
|
modelContextWindow,
|
|
25
27
|
modelLabel,
|
|
28
|
+
openBuildInEditor,
|
|
29
|
+
pullWorkingTree,
|
|
30
|
+
pushWorkingTree,
|
|
31
|
+
readProjectLink,
|
|
26
32
|
resolveModel,
|
|
27
33
|
resolveModelKey,
|
|
28
34
|
runProver,
|
|
@@ -32,8 +38,10 @@ import {
|
|
|
32
38
|
supportsThinking,
|
|
33
39
|
tasks,
|
|
34
40
|
userBox,
|
|
35
|
-
visionCapable
|
|
36
|
-
|
|
41
|
+
visionCapable,
|
|
42
|
+
writeProjectLink,
|
|
43
|
+
writeWorkingTree
|
|
44
|
+
} from "./chunk-K6MSDQJT.js";
|
|
37
45
|
|
|
38
46
|
// src/cli.ts
|
|
39
47
|
import { createInterface } from "readline/promises";
|
|
@@ -871,6 +879,12 @@ ${c.bold("Usage")}
|
|
|
871
879
|
wholestack "build me a todo app" run one prompt and exit
|
|
872
880
|
wholestack -m zeta-g1-max "..." drive it with the most capable brain
|
|
873
881
|
|
|
882
|
+
${c.bold("Projects")} ${c.dim("(one spine across terminal, dashboard and the web IDE)")}
|
|
883
|
+
wholestack projects list your platform projects
|
|
884
|
+
wholestack pull <id> [dir] download a project's working tree
|
|
885
|
+
wholestack push [dir] sync local edits back to the web IDE
|
|
886
|
+
wholestack open [buildId] open a generated build in the web IDE
|
|
887
|
+
|
|
874
888
|
${c.bold("Web3 security")} ${c.dim("(forge \xB7 slither \xB7 firewall \xB7 halmos \xB7 signed cert)")}
|
|
875
889
|
wholestack audit <dir> <Contract> full auto-detect security sweep
|
|
876
890
|
wholestack prove <dir> <Contract> --property <k> prove one invariant
|
|
@@ -1023,6 +1037,87 @@ async function runLogin(raw) {
|
|
|
1023
1037
|
line(c.dim(" now just run `wholestack`."));
|
|
1024
1038
|
return 0;
|
|
1025
1039
|
}
|
|
1040
|
+
async function runProjectSubcommand(raw) {
|
|
1041
|
+
const verb = raw[0];
|
|
1042
|
+
if (verb !== "projects" && verb !== "pull" && verb !== "push" && verb !== "open") return null;
|
|
1043
|
+
const base = webBaseUrl();
|
|
1044
|
+
if (verb === "projects") {
|
|
1045
|
+
const r2 = await listProjects(base);
|
|
1046
|
+
if (!r2.ok) {
|
|
1047
|
+
line(c.red(` ${r2.error}`));
|
|
1048
|
+
return 1;
|
|
1049
|
+
}
|
|
1050
|
+
if (!r2.projects?.length) {
|
|
1051
|
+
line(c.dim(' no projects yet \u2014 build something (`wholestack "an idea"`) or create one in the web IDE.'));
|
|
1052
|
+
return 0;
|
|
1053
|
+
}
|
|
1054
|
+
line();
|
|
1055
|
+
for (const p of r2.projects) {
|
|
1056
|
+
const when = p.updatedAt ? new Date(p.updatedAt).toISOString().slice(0, 10) : "";
|
|
1057
|
+
line(" " + c.cyan(p.id) + " " + c.bold(p.name) + (when ? c.dim(` \xB7 ${when}`) : ""));
|
|
1058
|
+
if (p.description) line(" " + c.dim(` ${p.description}`));
|
|
1059
|
+
}
|
|
1060
|
+
line();
|
|
1061
|
+
line(c.dim(" pull one with ") + c.cyan("wholestack pull <id>") + c.dim(" \xB7 open in the IDE at ") + c.cyan(`${base}/ide/studio?projectId=<id>`));
|
|
1062
|
+
return 0;
|
|
1063
|
+
}
|
|
1064
|
+
if (verb === "pull") {
|
|
1065
|
+
const projectId = raw[1];
|
|
1066
|
+
if (!projectId) {
|
|
1067
|
+
line(c.red(" usage: wholestack pull <projectId> [dir]"));
|
|
1068
|
+
return 1;
|
|
1069
|
+
}
|
|
1070
|
+
const r2 = await pullWorkingTree(base, projectId);
|
|
1071
|
+
if (!r2.ok) {
|
|
1072
|
+
line(c.red(` ${r2.error}`));
|
|
1073
|
+
return 1;
|
|
1074
|
+
}
|
|
1075
|
+
const dest = raw[2] ?? (r2.name ? r2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") : projectId);
|
|
1076
|
+
const written = writeWorkingTree(dest, r2.files ?? []);
|
|
1077
|
+
writeProjectLink(dest, { projectId });
|
|
1078
|
+
line(c.green(` \u2713 pulled ${written} files`) + c.dim(` \u2192 ${dest}/`));
|
|
1079
|
+
line(c.dim(" edit locally, then ") + c.cyan(`wholestack push ${dest === "." ? "" : dest}`).trimEnd() + c.dim(" to sync back to the web IDE."));
|
|
1080
|
+
return 0;
|
|
1081
|
+
}
|
|
1082
|
+
if (verb === "push") {
|
|
1083
|
+
const dir = raw[1] ?? ".";
|
|
1084
|
+
const link = readProjectLink(dir);
|
|
1085
|
+
const flagIdx = raw.indexOf("--project");
|
|
1086
|
+
const projectId = (flagIdx >= 0 ? raw[flagIdx + 1] : void 0) ?? link?.projectId;
|
|
1087
|
+
if (!projectId) {
|
|
1088
|
+
line(c.red(" no linked project \u2014 pull first, or pass --project <id>."));
|
|
1089
|
+
return 1;
|
|
1090
|
+
}
|
|
1091
|
+
const { files, skipped } = await collectLocalFiles(dir);
|
|
1092
|
+
if (!files.length) {
|
|
1093
|
+
line(c.red(" nothing to push (no readable text files found)."));
|
|
1094
|
+
return 1;
|
|
1095
|
+
}
|
|
1096
|
+
const r2 = await pushWorkingTree(base, projectId, files);
|
|
1097
|
+
if (!r2.ok) {
|
|
1098
|
+
line(c.red(` ${r2.error}`));
|
|
1099
|
+
return 1;
|
|
1100
|
+
}
|
|
1101
|
+
writeProjectLink(dir, { projectId });
|
|
1102
|
+
line(c.green(` \u2713 pushed ${files.length} files`) + c.dim(skipped ? ` (${skipped} skipped: binary/oversize)` : ""));
|
|
1103
|
+
line(c.dim(" open it: ") + c.cyan(`${base}/ide/studio?projectId=${projectId}`));
|
|
1104
|
+
return 0;
|
|
1105
|
+
}
|
|
1106
|
+
const buildId = raw[1] ?? readProjectLink(".")?.buildId;
|
|
1107
|
+
if (!buildId) {
|
|
1108
|
+
line(c.red(" usage: wholestack open <buildId> (or run it inside a delivered app dir)"));
|
|
1109
|
+
return 1;
|
|
1110
|
+
}
|
|
1111
|
+
const r = await openBuildInEditor(base, buildId);
|
|
1112
|
+
if (!r.ok) {
|
|
1113
|
+
line(c.red(` ${r.error}`));
|
|
1114
|
+
return 1;
|
|
1115
|
+
}
|
|
1116
|
+
if (r.projectId) writeProjectLink(".", { buildId, projectId: r.projectId });
|
|
1117
|
+
line(c.green(" \u2713 project ready in the web IDE"));
|
|
1118
|
+
line(c.dim(" ") + c.cyan(`${base}${r.editorUrl ?? `/ide/studio?projectId=${r.projectId}`}`));
|
|
1119
|
+
return 0;
|
|
1120
|
+
}
|
|
1026
1121
|
var EMPTY_PLUGINS = {
|
|
1027
1122
|
plugins: [],
|
|
1028
1123
|
systemText: "",
|
|
@@ -1051,6 +1146,8 @@ async function main() {
|
|
|
1051
1146
|
if (loggedIn !== null) exit(loggedIn);
|
|
1052
1147
|
const sub = await runSecuritySubcommand(rawArgs);
|
|
1053
1148
|
if (sub !== null) exit(sub);
|
|
1149
|
+
const proj = await runProjectSubcommand(rawArgs);
|
|
1150
|
+
if (proj !== null) exit(proj);
|
|
1054
1151
|
const args = parse(rawArgs);
|
|
1055
1152
|
if (args.version) {
|
|
1056
1153
|
line(`wholestack ${VERSION}`);
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wholestack",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Wholestack — a pro-grade conversational terminal agent for the Wholestack codegen engine. Talk to it in plain language: it writes ISL, generates full-stack or Solidity apps, and proves them with ShipGate. Browser login, membership-gated builds, slash commands, sessions, plan mode, diffs, MCP, plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|