@keystone-os/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @keystone-os/cli
2
+
3
+ CLI for Keystone Studio Mini-Apps — **Sovereign OS 2026**. Scaffold, validate, and build with Ouroboros self-correction and Arweave cold path.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @keystone-os/cli
9
+ # or
10
+ npx @keystone-os/cli init
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ ### init [dir]
16
+
17
+ Scaffold a new Mini-App.
18
+
19
+ ```bash
20
+ keystone init my-app
21
+ npx @keystone-os/cli init my-app
22
+ ```
23
+
24
+ ### validate [dir] (Ouroboros Loop)
25
+
26
+ Validate against Glass Safety Standard. Use `--suggest` for self-correction hints.
27
+
28
+ ```bash
29
+ keystone validate --suggest
30
+ ```
31
+
32
+ - Direct `fetch()` → use `useFetch()` from SDK
33
+ - `localStorage` / `eval` / etc. → blocked
34
+ - **Pinned Import Maps** — enforces `?external=react,react-dom` for esm.sh
35
+
36
+ ### lockfile [dir]
37
+
38
+ Validate pinned import maps only.
39
+
40
+ ```bash
41
+ keystone lockfile
42
+ ```
43
+
44
+ ### build [dir]
45
+
46
+ Build Mini-App. Optional Arweave cold path for atomic rollbacks.
47
+
48
+ ```bash
49
+ keystone build
50
+ keystone build --anchor-arweave
51
+ keystone build -o ./dist
52
+ ```
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ "use strict";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // src/index.ts
28
+ var import_commander = require("commander");
29
+
30
+ // src/commands/init.ts
31
+ var fs = __toESM(require("fs"));
32
+ var path = __toESM(require("path"));
33
+ var STARTER_APP = `import { useVault, useFetch } from '@keystone-os/sdk';
34
+
35
+ export default function App() {
36
+ const { tokens, balances } = useVault();
37
+
38
+ return (
39
+ <div className="p-6 bg-zinc-900 text-white min-h-screen">
40
+ <h1 className="text-2xl font-bold text-emerald-400 mb-4">My Mini-App</h1>
41
+ <div className="space-y-2 font-mono">
42
+ {tokens.map((t) => (
43
+ <div key={t.symbol} className="flex justify-between border-b border-zinc-800 py-2">
44
+ <span>{t.symbol}</span>
45
+ <span>{t.balance.toLocaleString()}</span>
46
+ </div>
47
+ ))}
48
+ </div>
49
+ </div>
50
+ );
51
+ }
52
+ `;
53
+ var LOCKFILE = {
54
+ version: "1.0.0",
55
+ packages: {
56
+ react: {
57
+ url: "https://esm.sh/react@19.0.0",
58
+ types: "https://esm.sh/v135/@types/react@19.0.0/index.d.ts",
59
+ external: true
60
+ },
61
+ "react-dom": {
62
+ url: "https://esm.sh/react-dom@19.0.0",
63
+ types: "https://esm.sh/v135/@types/react-dom@19.0.0/index.d.ts",
64
+ external: true
65
+ },
66
+ "@keystone-os/sdk": {
67
+ url: "https://esm.sh/@keystone-os/sdk",
68
+ types: "https://esm.sh/@keystone-os/sdk",
69
+ external: false
70
+ }
71
+ }
72
+ };
73
+ function runInit(dir) {
74
+ const targetDir = path.resolve(process.cwd(), dir || ".");
75
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
76
+ throw new Error(`Directory ${targetDir} is not empty.`);
77
+ }
78
+ fs.mkdirSync(targetDir, { recursive: true });
79
+ fs.writeFileSync(path.join(targetDir, "App.tsx"), STARTER_APP);
80
+ fs.writeFileSync(
81
+ path.join(targetDir, "keystone.lock.json"),
82
+ JSON.stringify(LOCKFILE, null, 2)
83
+ );
84
+ fs.writeFileSync(
85
+ path.join(targetDir, "README.md"),
86
+ `# Keystone Mini-App
87
+
88
+ Built with \`@keystone-os/sdk\`. Open in Keystone Studio to run.
89
+ `
90
+ );
91
+ console.log(`Created Mini-App in ${targetDir}`);
92
+ console.log(" - App.tsx");
93
+ console.log(" - keystone.lock.json");
94
+ console.log(" - README.md");
95
+ }
96
+
97
+ // src/commands/validate.ts
98
+ var fs2 = __toESM(require("fs"));
99
+ var path2 = __toESM(require("path"));
100
+ var FORBIDDEN_PATTERNS = [
101
+ { pattern: /\bfetch\s*\(/g, msg: "Direct fetch() is blocked. Use useFetch() from '@keystone-os/sdk'." },
102
+ { pattern: /\blocalStorage\b/g, msg: "localStorage is blocked in sandbox." },
103
+ { pattern: /\bsessionStorage\b/g, msg: "sessionStorage is blocked in sandbox." },
104
+ { pattern: /\bdocument\.cookie\b/g, msg: "document.cookie is blocked in sandbox." },
105
+ { pattern: /\bwindow\.parent\.postMessage\b/g, msg: "window.parent.postMessage is reserved for SDK." },
106
+ { pattern: /\beval\s*\(/g, msg: "eval() is blocked by CSP." },
107
+ { pattern: /\bnew\s+Function\s*\(/g, msg: "new Function() is blocked by CSP." }
108
+ ];
109
+ function getSuggestion(error) {
110
+ if (error.message.includes("fetch()")) {
111
+ return `Replace fetch(url) with: const { data } = useFetch(url);`;
112
+ }
113
+ if (error.message.includes("localStorage")) {
114
+ return `Use useEncryptedSecret() from '@keystone-os/sdk' for persistent storage.`;
115
+ }
116
+ if (error.message.includes("sessionStorage")) {
117
+ return `Use in-memory state or useEncryptedSecret() from '@keystone-os/sdk'.`;
118
+ }
119
+ if (error.message.includes("document.cookie")) {
120
+ return `Use useSIWS() from '@keystone-os/sdk' for session/auth.`;
121
+ }
122
+ if (error.message.includes("postMessage")) {
123
+ return `Use AppEventBus.emit() from '@keystone-os/sdk' for host communication.`;
124
+ }
125
+ return void 0;
126
+ }
127
+ function runValidate(dir = ".", options) {
128
+ const targetDir = path2.resolve(process.cwd(), dir);
129
+ const errors = [];
130
+ const files = ["App.tsx", "app.tsx"];
131
+ for (const file of files) {
132
+ const filePath = path2.join(targetDir, file);
133
+ if (!fs2.existsSync(filePath)) continue;
134
+ const content = fs2.readFileSync(filePath, "utf-8");
135
+ for (const { pattern, msg } of FORBIDDEN_PATTERNS) {
136
+ const re = new RegExp(pattern.source, pattern.flags);
137
+ let m;
138
+ while ((m = re.exec(content)) !== null) {
139
+ const lineNum = content.slice(0, m.index).split("\n").length;
140
+ const err = { file, line: lineNum, message: msg };
141
+ if (options?.suggest) {
142
+ err.suggestion = getSuggestion(err);
143
+ }
144
+ errors.push(err);
145
+ }
146
+ }
147
+ const hasSdkImport = /from\s+['"]@keystone-os\/sdk['"]/.test(content);
148
+ const hasForbidden = FORBIDDEN_PATTERNS.some((p) => new RegExp(p.pattern.source).test(content));
149
+ if (!hasSdkImport && hasForbidden) {
150
+ const err = {
151
+ file,
152
+ line: 1,
153
+ message: "Use '@keystone-os/sdk' for fetch/vault/turnkey instead of raw APIs."
154
+ };
155
+ if (options?.suggest) {
156
+ err.suggestion = `Add: import { useFetch, useVault } from '@keystone-os/sdk';`;
157
+ }
158
+ errors.push(err);
159
+ }
160
+ }
161
+ const suggestions = options?.suggest ? errors.map((e) => e.suggestion).filter(Boolean) : void 0;
162
+ return { ok: errors.length === 0, errors, suggestions };
163
+ }
164
+
165
+ // src/commands/lockfile.ts
166
+ var fs3 = __toESM(require("fs"));
167
+ var path3 = __toESM(require("path"));
168
+ var ESM_SH_REGEX = /^https:\/\/esm\.sh\//;
169
+ var EXTERNAL_PARAM = "external=react,react-dom";
170
+ function validateLockfile(dir = ".") {
171
+ const targetDir = path3.resolve(process.cwd(), dir);
172
+ const lockPath = path3.join(targetDir, "keystone.lock.json");
173
+ const errors = [];
174
+ if (!fs3.existsSync(lockPath)) {
175
+ return { ok: true, errors: [] };
176
+ }
177
+ const raw = fs3.readFileSync(lockPath, "utf-8");
178
+ let data;
179
+ try {
180
+ data = JSON.parse(raw);
181
+ } catch {
182
+ return { ok: false, errors: [{ package: "lockfile", message: "Invalid JSON" }] };
183
+ }
184
+ const packages = data.packages ?? {};
185
+ const skipPackages = /* @__PURE__ */ new Set(["react", "react-dom", "react-dom/client", "@keystone-os/sdk"]);
186
+ for (const [name, pkg] of Object.entries(packages)) {
187
+ if (skipPackages.has(name)) continue;
188
+ const url = pkg.url;
189
+ if (!url || typeof url !== "string") continue;
190
+ if (url.startsWith("blob:") || url.startsWith("file:")) continue;
191
+ if (!ESM_SH_REGEX.test(url)) continue;
192
+ if (!url.includes("?")) {
193
+ errors.push({
194
+ package: name,
195
+ message: `esm.sh URL must include ?external=react,react-dom`,
196
+ fix: `${url}?external=react,react-dom`
197
+ });
198
+ } else if (!url.includes(EXTERNAL_PARAM) && !url.includes("external=")) {
199
+ const sep = url.includes("?") ? "&" : "?";
200
+ errors.push({
201
+ package: name,
202
+ message: `esm.sh URL must include external=react,react-dom`,
203
+ fix: `${url}${sep}external=react,react-dom`
204
+ });
205
+ }
206
+ }
207
+ return { ok: errors.length === 0, errors };
208
+ }
209
+
210
+ // src/commands/build.ts
211
+ var fs4 = __toESM(require("fs"));
212
+ var path4 = __toESM(require("path"));
213
+ async function runBuild(options = {}) {
214
+ const targetDir = path4.resolve(process.cwd(), options.dir ?? ".");
215
+ const outDir = options.outDir ?? path4.join(targetDir, "dist");
216
+ const appPath = path4.join(targetDir, "App.tsx");
217
+ if (!fs4.existsSync(appPath)) {
218
+ return { ok: false, error: "App.tsx not found" };
219
+ }
220
+ const raw = fs4.readFileSync(appPath, "utf-8");
221
+ let bundle = raw;
222
+ fs4.mkdirSync(outDir, { recursive: true });
223
+ const outputPath = path4.join(outDir, "app.bundle.js");
224
+ fs4.writeFileSync(outputPath, bundle);
225
+ const manifest = {
226
+ version: "1.0.0",
227
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
228
+ files: ["app.bundle.js"],
229
+ coldPath: null
230
+ };
231
+ if (options.anchorArweave) {
232
+ manifest.coldPath = `arweave://pending`;
233
+ }
234
+ const manifestPath = path4.join(outDir, "manifest.json");
235
+ fs4.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
236
+ return {
237
+ ok: true,
238
+ outputPath,
239
+ manifest: manifestPath,
240
+ arweaveTxId: options.anchorArweave ? void 0 : void 0
241
+ };
242
+ }
243
+
244
+ // src/index.ts
245
+ var program = new import_commander.Command();
246
+ program.name("keystone").description("CLI for Keystone Studio Mini-Apps \u2014 Sovereign OS 2026").version("0.2.0");
247
+ program.command("init [dir]").description("Scaffold a new Mini-App").action((dir) => {
248
+ try {
249
+ runInit(dir);
250
+ } catch (err) {
251
+ console.error(err instanceof Error ? err.message : err);
252
+ process.exit(1);
253
+ }
254
+ });
255
+ program.command("validate [dir]").description("Validate Mini-App against Glass Safety Standard (Ouroboros Loop)").option("--suggest", "Output suggested fixes for each error").action((dir = ".", opts) => {
256
+ const result = runValidate(dir, { suggest: opts.suggest });
257
+ if (result.ok) {
258
+ const lockResult = validateLockfile(dir);
259
+ if (lockResult.ok) {
260
+ console.log("Validation passed.");
261
+ } else {
262
+ for (const e of lockResult.errors) {
263
+ console.error(`[lockfile] ${e.package}: ${e.message}`);
264
+ if (e.fix) console.error(` Fix: ${e.fix}`);
265
+ }
266
+ process.exit(1);
267
+ }
268
+ } else {
269
+ for (const e of result.errors) {
270
+ console.error(`${e.file}:${e.line} \u2014 ${e.message}`);
271
+ if (opts.suggest && e.suggestion) {
272
+ console.error(` Suggestion: ${e.suggestion}`);
273
+ }
274
+ }
275
+ process.exit(1);
276
+ }
277
+ });
278
+ program.command("lockfile [dir]").description("Validate pinned import maps (?external=react,react-dom)").action((dir = ".") => {
279
+ const result = validateLockfile(dir);
280
+ if (result.ok) {
281
+ console.log("Lockfile valid.");
282
+ } else {
283
+ for (const e of result.errors) {
284
+ console.error(`${e.package}: ${e.message}`);
285
+ if (e.fix) console.error(` Fix: ${e.fix}`);
286
+ }
287
+ process.exit(1);
288
+ }
289
+ });
290
+ program.command("build [dir]").description("Build Mini-App (optional Arweave cold path)").option("--anchor-arweave", "Anchor build to Arweave for atomic rollbacks").option("-o, --out-dir <dir>", "Output directory").action(async (dir = ".", opts) => {
291
+ try {
292
+ const result = await runBuild({ dir, anchorArweave: opts.anchorArweave, outDir: opts.outDir });
293
+ if (result.ok) {
294
+ console.log("Build complete:", result.outputPath);
295
+ if (opts.anchorArweave) {
296
+ console.log("Cold path: manifest.json (Arweave upload requires arweave CLI)");
297
+ }
298
+ } else {
299
+ console.error(result.error);
300
+ process.exit(1);
301
+ }
302
+ } catch (err) {
303
+ console.error(err instanceof Error ? err.message : err);
304
+ process.exit(1);
305
+ }
306
+ });
307
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@keystone-os/cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for Keystone Studio Mini-Apps — init, validate",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "keystone": "./dist/index.js"
8
+ },
9
+ "files": ["dist"],
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "dev": "tsup src/index.ts --format cjs --dts --watch"
13
+ },
14
+ "dependencies": {
15
+ "commander": "^12.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^20.0.0",
19
+ "tsup": "^8.3.5",
20
+ "typescript": "^5.8.0"
21
+ },
22
+ "keywords": ["keystone", "web3", "treasury", "studio", "mini-app", "cli"],
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/stauniverse/keystone-treasury-os",
27
+ "directory": "packages/cli"
28
+ }
29
+ }