@voltade/volty 0.1.24 → 0.1.26
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 +10 -0
- package/dist/volty.js +235 -97
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,16 @@ Open Claude Code anywhere and ask in plain language. The skill you installed in
|
|
|
48
48
|
|
|
49
49
|
Claude knows the safety rules: tests run on your workspace's Sandbox from a synthetic contact, whose replies are recorded in the thread but never sent, so nothing can reach a real contact. Claude cleans up after itself when it's done, and anything left over is removed after 24 hours.
|
|
50
50
|
|
|
51
|
+
## Working on a workspace's code in a folder
|
|
52
|
+
|
|
53
|
+
To edit a workspace's automations, pages, quick paths and inbox settings as files, start a folder with:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
volty init my-workspace
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
It pulls the workspace into the new folder and adds a `package.json`, a `.gitignore`, and a `tsconfig.json` that references `tsconfig.code.json` and `tsconfig.entry.json`, so your editor and Claude can check the code the way a push does with `npx tsc -b`, as the `@voltade/sdk` README explains. Run `npm install` in the folder, then open it in your editor. A file the folder already has is kept. `volty pull` brings in later changes from the workspace and `volty push` sends yours back.
|
|
60
|
+
|
|
51
61
|
## Upgrading
|
|
52
62
|
|
|
53
63
|
When a newer version exists, the tool prints a notice — Claude will see it and upgrade for you. To do it yourself:
|
package/dist/volty.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/volty.ts
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5
5
|
import { homedir as homedir3 } from "node:os";
|
|
6
|
-
import { join as
|
|
6
|
+
import { join as join7 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// src/api.ts
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
@@ -572,80 +572,99 @@ function renderResult(body, json) {
|
|
|
572
572
|
return text || "(no output)";
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
// src/
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
out.push("--expires-in-days", value.replace(/d$/, ""));
|
|
595
|
-
else if (flag === "cap")
|
|
596
|
-
out.push("--daily-cap", value);
|
|
597
|
-
else if (flag === "families")
|
|
598
|
-
for (const f of value.split(","))
|
|
599
|
-
out.push("--families", f.trim());
|
|
600
|
-
else
|
|
601
|
-
out.push(`--${flag}`, value);
|
|
575
|
+
// src/init.ts
|
|
576
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
577
|
+
import { join as join4 } from "node:path";
|
|
578
|
+
|
|
579
|
+
// ../shared/projects/checker-options.ts
|
|
580
|
+
var checkerOptions = {
|
|
581
|
+
code: {
|
|
582
|
+
target: "ES2022",
|
|
583
|
+
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
584
|
+
module: "ESNext",
|
|
585
|
+
moduleResolution: "bundler",
|
|
586
|
+
strict: true,
|
|
587
|
+
skipLibCheck: true,
|
|
588
|
+
esModuleInterop: true,
|
|
589
|
+
types: []
|
|
590
|
+
},
|
|
591
|
+
entry: {
|
|
592
|
+
strict: false,
|
|
593
|
+
jsx: "react-jsx"
|
|
602
594
|
}
|
|
603
|
-
|
|
604
|
-
}
|
|
595
|
+
};
|
|
605
596
|
|
|
606
|
-
//
|
|
607
|
-
var
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (!baseUrl)
|
|
597
|
+
// ../shared/projects/paths.ts
|
|
598
|
+
var SEGMENT = "[A-Za-z0-9_][A-Za-z0-9._-]*";
|
|
599
|
+
var PROJECT_SEGMENT = new RegExp(`^${SEGMENT}$`);
|
|
600
|
+
var PROJECT_PATH = new RegExp(`^(?:${SEGMENT}/)*${SEGMENT}$`);
|
|
601
|
+
var PUSH_EXCLUDES = [
|
|
602
|
+
"node_modules/",
|
|
603
|
+
"bun.lock",
|
|
604
|
+
"bun.lockb",
|
|
605
|
+
"package-lock.json",
|
|
606
|
+
"yarn.lock",
|
|
607
|
+
"pnpm-lock.yaml",
|
|
608
|
+
"*.tsbuildinfo"
|
|
609
|
+
];
|
|
610
|
+
function pushes(name, isDirectory) {
|
|
611
|
+
if (!PROJECT_SEGMENT.test(name))
|
|
622
612
|
return false;
|
|
623
|
-
|
|
624
|
-
try {
|
|
625
|
-
host = new URL(baseUrl).hostname;
|
|
626
|
-
} catch {
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
return host !== "localhost" && host !== "127.0.0.1" && host !== "::1" && host !== "[::1]";
|
|
630
|
-
}
|
|
631
|
-
var PROD_ORG_REFUSAL = "prod needs an explicit org: pass --org <id> or VOLTY_ORG=<id>; run 'volty org list --env prod' to see your orgs";
|
|
632
|
-
var STICKY_ORG_REFUSAL = "prod does not honour a sticky org: pass --org <id> or VOLTY_ORG=<id> on each command instead; run 'volty org list --env prod' to see your orgs";
|
|
633
|
-
function resolveTargetOrg({ env, baseUrl, override, sticky, command }) {
|
|
634
|
-
if (!isProdShaped(env, baseUrl))
|
|
635
|
-
return { org: override ?? sticky };
|
|
636
|
-
if (override)
|
|
637
|
-
return { org: override };
|
|
638
|
-
if (isOrgFreeCommand(command))
|
|
639
|
-
return { org: undefined };
|
|
640
|
-
return { refusal: PROD_ORG_REFUSAL };
|
|
613
|
+
return !PUSH_EXCLUDES.some((pattern) => pattern.endsWith("/") ? isDirectory && name === pattern.slice(0, -1) : pattern.startsWith("*") ? name.endsWith(pattern.slice(1)) : name === pattern);
|
|
641
614
|
}
|
|
642
615
|
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
616
|
+
// ../shared/projects/scaffold.ts
|
|
617
|
+
var SDK_VERSION_RANGE = "^0.1.0";
|
|
618
|
+
var SCAFFOLD_PATHS = [
|
|
619
|
+
"package.json",
|
|
620
|
+
"tsconfig.json",
|
|
621
|
+
"tsconfig.code.json",
|
|
622
|
+
"tsconfig.entry.json",
|
|
623
|
+
".gitignore"
|
|
624
|
+
];
|
|
625
|
+
var ENTRY_FILES = "pages/**/entry.tsx";
|
|
626
|
+
function options(name) {
|
|
627
|
+
return {
|
|
628
|
+
...checkerOptions.code,
|
|
629
|
+
composite: true,
|
|
630
|
+
noEmit: true,
|
|
631
|
+
tsBuildInfoFile: `./node_modules/.cache/volty/${name}.tsbuildinfo`,
|
|
632
|
+
jsx: checkerOptions.entry.jsx,
|
|
633
|
+
paths: { "~/*": ["./node_modules/@voltade/sdk/*"] }
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
var json = (value) => `${JSON.stringify(value, null, 2)}
|
|
637
|
+
`;
|
|
638
|
+
function scaffoldFiles() {
|
|
639
|
+
return {
|
|
640
|
+
"package.json": json({
|
|
641
|
+
name: "volty-project",
|
|
642
|
+
private: true,
|
|
643
|
+
type: "module",
|
|
644
|
+
devDependencies: {
|
|
645
|
+
"@types/react": "^19.0.0",
|
|
646
|
+
"@types/react-dom": "^19.0.0",
|
|
647
|
+
"@voltade/sdk": SDK_VERSION_RANGE,
|
|
648
|
+
typescript: "^5.9.0 || ^7.0.0"
|
|
649
|
+
}
|
|
650
|
+
}),
|
|
651
|
+
"tsconfig.json": json({
|
|
652
|
+
files: [],
|
|
653
|
+
references: [{ path: "./tsconfig.code.json" }, { path: "./tsconfig.entry.json" }]
|
|
654
|
+
}),
|
|
655
|
+
"tsconfig.code.json": json({
|
|
656
|
+
compilerOptions: { ...options("code"), allowUnreachableCode: false },
|
|
657
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
658
|
+
exclude: ["node_modules", ENTRY_FILES]
|
|
659
|
+
}),
|
|
660
|
+
"tsconfig.entry.json": json({
|
|
661
|
+
compilerOptions: { ...options("entry"), ...checkerOptions.entry, types: ["@voltade/sdk/globals"] },
|
|
662
|
+
include: [ENTRY_FILES]
|
|
663
|
+
}),
|
|
664
|
+
".gitignore": `${PUSH_EXCLUDES.join(`
|
|
665
|
+
`)}
|
|
666
|
+
`
|
|
667
|
+
};
|
|
649
668
|
}
|
|
650
669
|
|
|
651
670
|
// src/pull.ts
|
|
@@ -726,9 +745,108 @@ async function pull(ctx, opts) {
|
|
|
726
745
|
return { ...recorded, written, deleted, kept };
|
|
727
746
|
}
|
|
728
747
|
|
|
748
|
+
// src/init.ts
|
|
749
|
+
function scaffold(dir) {
|
|
750
|
+
const texts = scaffoldFiles();
|
|
751
|
+
return SCAFFOLD_PATHS.map((path) => {
|
|
752
|
+
const target = join4(dir, path);
|
|
753
|
+
if (existsSync5(target))
|
|
754
|
+
return { path, written: false };
|
|
755
|
+
writeFileSync5(target, texts[path]);
|
|
756
|
+
return { path, written: true };
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
async function init(ctx, opts) {
|
|
760
|
+
const pulled = readTargets(opts.dir) ? null : await pull(ctx, opts);
|
|
761
|
+
return { pulled, files: scaffold(opts.dir) };
|
|
762
|
+
}
|
|
763
|
+
function initLines(dir, files) {
|
|
764
|
+
return [
|
|
765
|
+
`${dir} is set up for an editor:`,
|
|
766
|
+
...files.map((f) => f.written ? ` wrote ${f.path}` : ` kept ${f.path}, which the folder already has`),
|
|
767
|
+
dir === "." ? "Next: run npm install, then open this folder in your editor." : `Next: cd ${dir}, run npm install, then open the folder in your editor.`
|
|
768
|
+
];
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/keys.ts
|
|
772
|
+
var VALUE_FLAGS = new Set(["name", "for", "for-user-id", "expires", "expires-in-days", "families", "cap"]);
|
|
773
|
+
function keysArgv(argv) {
|
|
774
|
+
const out = argv.slice(0, 2);
|
|
775
|
+
const rest = argv.slice(2);
|
|
776
|
+
for (let i = 0;i < rest.length; i++) {
|
|
777
|
+
const token = rest[i];
|
|
778
|
+
if (!token.startsWith("--")) {
|
|
779
|
+
out.push("--name", token);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const eq = token.indexOf("=");
|
|
783
|
+
const flag = eq > 0 ? token.slice(2, eq) : token.slice(2);
|
|
784
|
+
const value = eq > 0 ? token.slice(eq + 1) : VALUE_FLAGS.has(flag) ? rest[++i] : undefined;
|
|
785
|
+
if (value === undefined)
|
|
786
|
+
out.push(token);
|
|
787
|
+
else if (flag === "for")
|
|
788
|
+
out.push("--for-user-id", value);
|
|
789
|
+
else if (flag === "expires")
|
|
790
|
+
out.push("--expires-in-days", value.replace(/d$/, ""));
|
|
791
|
+
else if (flag === "cap")
|
|
792
|
+
out.push("--daily-cap", value);
|
|
793
|
+
else if (flag === "families")
|
|
794
|
+
for (const f of value.split(","))
|
|
795
|
+
out.push("--families", f.trim());
|
|
796
|
+
else
|
|
797
|
+
out.push(`--${flag}`, value);
|
|
798
|
+
}
|
|
799
|
+
return out;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/org-guard.ts
|
|
803
|
+
var ORG_NATIVE = new Set(["use", "current", "list"]);
|
|
804
|
+
function isOrgFreeCommand(command) {
|
|
805
|
+
const [cmd, sub] = command;
|
|
806
|
+
if (!cmd || cmd === "help" || cmd === "--help")
|
|
807
|
+
return true;
|
|
808
|
+
if (command.includes("--help"))
|
|
809
|
+
return true;
|
|
810
|
+
if (cmd === "auth" || cmd === "env" || cmd === "docs" || cmd === "skill")
|
|
811
|
+
return true;
|
|
812
|
+
return cmd === "org" && ORG_NATIVE.has(sub ?? "");
|
|
813
|
+
}
|
|
814
|
+
function isProdShaped(env, baseUrl) {
|
|
815
|
+
if (env === "prod")
|
|
816
|
+
return true;
|
|
817
|
+
if (!baseUrl)
|
|
818
|
+
return false;
|
|
819
|
+
let host;
|
|
820
|
+
try {
|
|
821
|
+
host = new URL(baseUrl).hostname;
|
|
822
|
+
} catch {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
return host !== "localhost" && host !== "127.0.0.1" && host !== "::1" && host !== "[::1]";
|
|
826
|
+
}
|
|
827
|
+
var PROD_ORG_REFUSAL = "prod needs an explicit org: pass --org <id> or VOLTY_ORG=<id>; run 'volty org list --env prod' to see your orgs";
|
|
828
|
+
var STICKY_ORG_REFUSAL = "prod does not honour a sticky org: pass --org <id> or VOLTY_ORG=<id> on each command instead; run 'volty org list --env prod' to see your orgs";
|
|
829
|
+
function resolveTargetOrg({ env, baseUrl, override, sticky, command }) {
|
|
830
|
+
if (!isProdShaped(env, baseUrl))
|
|
831
|
+
return { org: override ?? sticky };
|
|
832
|
+
if (override)
|
|
833
|
+
return { org: override };
|
|
834
|
+
if (isOrgFreeCommand(command))
|
|
835
|
+
return { org: undefined };
|
|
836
|
+
return { refusal: PROD_ORG_REFUSAL };
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/proposals.ts
|
|
840
|
+
function decideProposal(ctx, proposalId, decision, reason) {
|
|
841
|
+
return apiFetch(ctx, `/api/changes/proposals/${encodeURIComponent(proposalId)}/decide`, {
|
|
842
|
+
method: "POST",
|
|
843
|
+
body: { decision, ...reason ? { reason } : {} }
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
729
847
|
// src/push.ts
|
|
730
848
|
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
731
|
-
import { join as
|
|
849
|
+
import { join as join5 } from "node:path";
|
|
732
850
|
|
|
733
851
|
// ../shared/projects/limits.ts
|
|
734
852
|
var PROJECT_FILE_MAX_CHARS = 2000000;
|
|
@@ -748,10 +866,8 @@ function readProject(dir) {
|
|
|
748
866
|
const files = {};
|
|
749
867
|
let count = 0;
|
|
750
868
|
const walk = (rel) => {
|
|
751
|
-
for (const entry of readdirSync2(
|
|
752
|
-
if (entry.name
|
|
753
|
-
continue;
|
|
754
|
-
if (rel === "" && entry.name.startsWith("."))
|
|
869
|
+
for (const entry of readdirSync2(join5(dir, rel), { withFileTypes: true })) {
|
|
870
|
+
if (!pushes(entry.name, entry.isDirectory()))
|
|
755
871
|
continue;
|
|
756
872
|
const path = rel ? `${rel}/${entry.name}` : entry.name;
|
|
757
873
|
if (entry.isDirectory()) {
|
|
@@ -763,7 +879,7 @@ function readProject(dir) {
|
|
|
763
879
|
if (++count > PROJECT_FILES_MAX) {
|
|
764
880
|
throw new Error(`${dir} holds more than ${PROJECT_FILES_MAX.toLocaleString("en-US")} files, the most a push takes.`);
|
|
765
881
|
}
|
|
766
|
-
const text = readFileSync5(
|
|
882
|
+
const text = readFileSync5(join5(dir, path), "utf8");
|
|
767
883
|
if (text.length > PROJECT_FILE_MAX_CHARS) {
|
|
768
884
|
throw new Error(`${path} holds ${text.length.toLocaleString("en-US")} characters, over the ${PROJECT_FILE_MAX_CHARS.toLocaleString("en-US")} a push takes per file.`);
|
|
769
885
|
}
|
|
@@ -861,10 +977,11 @@ async function applyItem(ctx, item) {
|
|
|
861
977
|
}
|
|
862
978
|
return { result: { ...base, result: pending ? "pending" : "applied" }, stop: false };
|
|
863
979
|
}
|
|
980
|
+
var STORED = "stored with the version for the next pull";
|
|
864
981
|
function resultLine(r) {
|
|
865
982
|
switch (r.result) {
|
|
866
983
|
case "applied":
|
|
867
|
-
return `${r.name}: applied`;
|
|
984
|
+
return r.detail ? `${r.name}: applied, ${r.detail}` : `${r.name}: applied`;
|
|
868
985
|
case "pending":
|
|
869
986
|
return `${r.name}: pending approval, the running version stays live until an admin approves`;
|
|
870
987
|
case "held":
|
|
@@ -897,6 +1014,10 @@ async function pushTarget(ctx, opts) {
|
|
|
897
1014
|
results.push({ path: item.path, name: item.name, lines: item.lines, result: "skipped" });
|
|
898
1015
|
continue;
|
|
899
1016
|
}
|
|
1017
|
+
if (item.steps.length === 0 && item.stored) {
|
|
1018
|
+
results.push({ path: item.path, name: item.name, lines: item.lines, result: "applied", detail: STORED });
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
900
1021
|
if (item.steps.length === 0) {
|
|
901
1022
|
const detail = item.findings.find((f) => f.severity === "warn")?.message ?? "nothing to run for this kind yet";
|
|
902
1023
|
results.push({ path: item.path, name: item.name, lines: item.lines, result: "skipped", detail });
|
|
@@ -949,15 +1070,15 @@ async function pushTarget(ctx, opts) {
|
|
|
949
1070
|
}
|
|
950
1071
|
|
|
951
1072
|
// src/update.ts
|
|
952
|
-
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as
|
|
1073
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
953
1074
|
import { homedir as homedir2 } from "node:os";
|
|
954
|
-
import { dirname as dirname3, join as
|
|
955
|
-
var CACHE_PATH =
|
|
1075
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
1076
|
+
var CACHE_PATH = join6(homedir2(), ".config", "volty", "update-check.json");
|
|
956
1077
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
957
1078
|
var REGISTRY_LATEST = "https://registry.npmjs.org/@voltade/volty/latest";
|
|
958
1079
|
function currentVersion() {
|
|
959
1080
|
try {
|
|
960
|
-
const pkg = JSON.parse(readFileSync6(
|
|
1081
|
+
const pkg = JSON.parse(readFileSync6(join6(import.meta.dirname, "..", "package.json"), "utf8"));
|
|
961
1082
|
return pkg.version ?? null;
|
|
962
1083
|
} catch {
|
|
963
1084
|
return null;
|
|
@@ -983,7 +1104,7 @@ function readCache() {
|
|
|
983
1104
|
function writeCache(cache) {
|
|
984
1105
|
try {
|
|
985
1106
|
mkdirSync4(dirname3(CACHE_PATH), { recursive: true });
|
|
986
|
-
|
|
1107
|
+
writeFileSync6(CACHE_PATH, `${JSON.stringify(cache)}
|
|
987
1108
|
`);
|
|
988
1109
|
} catch {}
|
|
989
1110
|
}
|
|
@@ -1210,10 +1331,13 @@ var KEPT_NOTE = {
|
|
|
1210
1331
|
changed: ", and the workspace changed it too: delete it and pull again for the workspace copy",
|
|
1211
1332
|
removed: ", and the workspace no longer has it"
|
|
1212
1333
|
};
|
|
1213
|
-
|
|
1334
|
+
function folderArg(rest, command) {
|
|
1214
1335
|
const [dir = ".", ...extra] = rest;
|
|
1215
1336
|
if (extra.length > 0 || dir.startsWith("--"))
|
|
1216
|
-
fail(
|
|
1337
|
+
fail(`usage: volty ${command} [dir] [--workspace <id>]`);
|
|
1338
|
+
return dir;
|
|
1339
|
+
}
|
|
1340
|
+
async function workspaceOf(ctx) {
|
|
1217
1341
|
let workspaceId = ctx.organizationId;
|
|
1218
1342
|
if (!workspaceId) {
|
|
1219
1343
|
const res = await whoami(ctx);
|
|
@@ -1223,19 +1347,31 @@ async function handlePull(rest, ctx, env) {
|
|
|
1223
1347
|
}
|
|
1224
1348
|
if (!workspaceId)
|
|
1225
1349
|
fail("this key names no workspace: run `volty auth login`");
|
|
1226
|
-
|
|
1350
|
+
return workspaceId;
|
|
1351
|
+
}
|
|
1352
|
+
function pullLines(dir, out) {
|
|
1227
1353
|
if (out.unchanged && out.written.length + out.deleted.length + out.kept.length === 0) {
|
|
1228
|
-
|
|
1229
|
-
`);
|
|
1230
|
-
return;
|
|
1354
|
+
return [`${dir} is up to date with version ${out.versionId} of workspace ${out.workspaceId}.`];
|
|
1231
1355
|
}
|
|
1232
|
-
|
|
1356
|
+
return [
|
|
1233
1357
|
`Pulled workspace ${out.workspaceId} into ${dir}: version ${out.versionId}${out.unchanged ? " (unchanged)" : ""}.`,
|
|
1234
1358
|
...out.written.map((f) => ` wrote ${f}`),
|
|
1235
1359
|
...out.deleted.map((f) => ` deleted ${f}, which the workspace no longer has`),
|
|
1236
1360
|
...out.kept.map((f) => ` kept ${f.path}: edited here${KEPT_NOTE[f.workspace]}`),
|
|
1237
1361
|
"What the project does not hold is listed in .volty/observed."
|
|
1238
1362
|
];
|
|
1363
|
+
}
|
|
1364
|
+
async function handlePull(rest, ctx, env) {
|
|
1365
|
+
const dir = folderArg(rest, "pull");
|
|
1366
|
+
const out = await pull(ctx, { dir, workspaceId: await workspaceOf(ctx), target: env });
|
|
1367
|
+
process.stdout.write(`${pullLines(dir, out).join(`
|
|
1368
|
+
`)}
|
|
1369
|
+
`);
|
|
1370
|
+
}
|
|
1371
|
+
async function handleInit(rest, ctx, env) {
|
|
1372
|
+
const dir = folderArg(rest, "init");
|
|
1373
|
+
const out = await init(ctx, { dir, workspaceId: await workspaceOf(ctx), target: env });
|
|
1374
|
+
const lines = [...out.pulled ? pullLines(dir, out.pulled) : [], ...initLines(dir, out.files)];
|
|
1239
1375
|
process.stdout.write(`${lines.join(`
|
|
1240
1376
|
`)}
|
|
1241
1377
|
`);
|
|
@@ -1312,16 +1448,16 @@ function handleSkill(rest) {
|
|
|
1312
1448
|
if (rest[0] !== "install")
|
|
1313
1449
|
fail("usage: volty skill install");
|
|
1314
1450
|
const candidates = [
|
|
1315
|
-
|
|
1316
|
-
|
|
1451
|
+
join7(import.meta.dirname, "..", "skill", "volty-cli-ops", "SKILL.md"),
|
|
1452
|
+
join7(import.meta.dirname, "..", "..", ".claude", "skills", "volty-cli-ops", "SKILL.md")
|
|
1317
1453
|
];
|
|
1318
|
-
const source = candidates.find((p) =>
|
|
1454
|
+
const source = candidates.find((p) => existsSync6(p));
|
|
1319
1455
|
if (!source)
|
|
1320
1456
|
fail("bundled SKILL.md not found — reinstall the @voltade/volty package");
|
|
1321
|
-
const destDir =
|
|
1457
|
+
const destDir = join7(homedir3(), ".claude", "skills", "volty-cli-ops");
|
|
1322
1458
|
mkdirSync5(destDir, { recursive: true });
|
|
1323
|
-
|
|
1324
|
-
process.stdout.write(`Installed the volty-cli-ops skill → ${
|
|
1459
|
+
writeFileSync7(join7(destDir, "SKILL.md"), readFileSync7(source, "utf8"));
|
|
1460
|
+
process.stdout.write(`Installed the volty-cli-ops skill → ${join7(destDir, "SKILL.md")}
|
|
1325
1461
|
`);
|
|
1326
1462
|
}
|
|
1327
1463
|
async function main() {
|
|
@@ -1332,7 +1468,7 @@ async function main() {
|
|
|
1332
1468
|
argv = orgExtract.rest;
|
|
1333
1469
|
const cacheFlag = takeBool(argv, "--no-cache");
|
|
1334
1470
|
argv = cacheFlag.rest;
|
|
1335
|
-
const workspaceExtract = argv[0] === "pull" ? takeValue(argv, "--workspace") : { value: undefined, rest: argv };
|
|
1471
|
+
const workspaceExtract = argv[0] === "pull" || argv[0] === "init" ? takeValue(argv, "--workspace") : { value: undefined, rest: argv };
|
|
1336
1472
|
argv = workspaceExtract.rest;
|
|
1337
1473
|
const config = loadConfig();
|
|
1338
1474
|
if (argv[0] === "push") {
|
|
@@ -1389,6 +1525,8 @@ async function main() {
|
|
|
1389
1525
|
}
|
|
1390
1526
|
if (cmd === "pull")
|
|
1391
1527
|
return handlePull(rest, ctx, env);
|
|
1528
|
+
if (cmd === "init")
|
|
1529
|
+
return handleInit(rest, ctx, env);
|
|
1392
1530
|
if (cmd === "changes" && rest[0] === "decide")
|
|
1393
1531
|
return handleChanges(rest, ctx);
|
|
1394
1532
|
if (cmd === "keys")
|
package/package.json
CHANGED