okno 1.0.0-alpha.16 → 1.0.0-alpha.17
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/index.js +1 -1
- package/dist/init.d.ts +19 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +164 -67
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +53 -48
- package/dist/plugin-Cy62173B.js +1080 -0
- package/dist/vite/index.js +1 -1
- package/package.json +3 -2
- package/skills/connect-okno/SKILL.md +300 -0
- package/dist/plugin-DfOx2gkl.js +0 -1077
package/dist/index.js
CHANGED
package/dist/init.d.ts
CHANGED
|
@@ -13,8 +13,25 @@ export declare function detect(cwd: string): Detected;
|
|
|
13
13
|
* never reformat). Returns false (→ manual fallback) if there's no `plugins`
|
|
14
14
|
* array to extend, e.g. a bare `defineConfig({})`. Idempotent. */
|
|
15
15
|
export declare function addPlugin(configPath: string): boolean;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
export type ScaffoldMode = "starter" | "empty";
|
|
17
|
+
/**
|
|
18
|
+
* Scaffold okno/ if there's no okno/ yet. Two modes:
|
|
19
|
+
* - "starter": the site-wide globals every site needs — Meta (SEO/OG), Navbar,
|
|
20
|
+
* Footer — plus a Home page and a `navlink` block the nav/footer reuse. Ships
|
|
21
|
+
* ready to edit so the editor isn't an empty shell on first run.
|
|
22
|
+
* - "empty": just a Home page with a single field — a blank slate to model from
|
|
23
|
+
* scratch.
|
|
24
|
+
* The `connect-okno` skill explains why those are globals and how to extend the
|
|
25
|
+
* model. Returns false if okno/ already exists (left untouched).
|
|
26
|
+
*/
|
|
27
|
+
export declare function scaffold(cwd: string, mode?: ScaffoldMode): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Install the `connect-okno` skill into the project's `.claude/skills/` so the
|
|
30
|
+
* agent has the okno mental model (when to use collections vs blocks vs globals,
|
|
31
|
+
* the spread-to-edit pattern) on hand. Claude clients only. Returns the relative
|
|
32
|
+
* path written, or null if the bundled skill couldn't be found / already exists.
|
|
33
|
+
*/
|
|
34
|
+
export declare function writeSkill(cwd: string): string | null;
|
|
18
35
|
/** Register the okno MCP server for an AI client. */
|
|
19
36
|
export declare function writeMcpConfig(cwd: string, client: "claude" | "cursor"): string;
|
|
20
37
|
export declare function runInit(): Promise<void>;
|
package/dist/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAiBA,KAAK,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;AAEpE,UAAU,QAAQ;IACjB,SAAS,EAAE,SAAS,CAAA;IACpB,yEAAyE;IACzE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,oFAAoF;IACpF,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CACzB;AAiBD,kEAAkE;AAClE,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CA0B5C;AAED;;;mEAGmE;AACnE,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CA8BrD;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAA;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,YAAwB,GAAG,OAAO,CAmB7E;AA0GD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWrD;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAgB/E;AAED,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CA8F7C"}
|
package/dist/init.js
CHANGED
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
import { existsSync as
|
|
2
|
-
import { join as
|
|
3
|
-
|
|
1
|
+
import { existsSync as f, readFileSync as h, writeFileSync as l, mkdirSync as g } from "node:fs";
|
|
2
|
+
import { join as s, dirname as v } from "node:path";
|
|
3
|
+
import { fileURLToPath as S } from "node:url";
|
|
4
|
+
const u = {
|
|
4
5
|
astro: ["astro.config.mjs", "astro.config.ts", "astro.config.js"],
|
|
5
6
|
sveltekit: ["vite.config.ts", "vite.config.js"],
|
|
6
7
|
nuxt: ["nuxt.config.ts", "nuxt.config.js"],
|
|
7
8
|
vite: ["vite.config.ts", "vite.config.js", "vite.config.mjs"]
|
|
8
9
|
};
|
|
9
|
-
function
|
|
10
|
+
function p(n, e) {
|
|
10
11
|
for (const t of e) {
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
12
|
+
const o = s(n, t);
|
|
13
|
+
if (f(o)) return o;
|
|
13
14
|
}
|
|
14
15
|
return null;
|
|
15
16
|
}
|
|
16
|
-
function
|
|
17
|
-
const e =
|
|
18
|
-
if (
|
|
17
|
+
function b(n) {
|
|
18
|
+
const e = s(n, "package.json"), t = {};
|
|
19
|
+
if (f(e))
|
|
19
20
|
try {
|
|
20
|
-
const i = JSON.parse(
|
|
21
|
+
const i = JSON.parse(h(e, "utf-8"));
|
|
21
22
|
Object.assign(t, i.dependencies, i.devDependencies);
|
|
22
23
|
} catch {
|
|
23
24
|
}
|
|
24
|
-
const
|
|
25
|
-
return
|
|
25
|
+
const o = (i) => i in t;
|
|
26
|
+
return o("astro") || p(n, u.astro) ? { framework: "astro", configPath: p(n, u.astro), pluginsAt: "vite" } : o("nuxt") ? { framework: "nuxt", configPath: p(n, u.nuxt), pluginsAt: "vite" } : o("@sveltejs/kit") ? { framework: "sveltekit", configPath: p(n, u.vite), pluginsAt: "top" } : o("vite") ? { framework: "vite", configPath: p(n, u.vite), pluginsAt: "top" } : { framework: "unknown", configPath: null, pluginsAt: "top" };
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
-
let e =
|
|
28
|
+
function x(n) {
|
|
29
|
+
let e = h(n, "utf-8");
|
|
29
30
|
if (/from\s*["']okno\/vite["']/.test(e)) return !0;
|
|
30
31
|
const t = e.match(/\bplugins\s*:\s*\[/);
|
|
31
32
|
if (!t || t.index === void 0) return !1;
|
|
32
|
-
const
|
|
33
|
-
let
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
${
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
const
|
|
40
|
-
e = e.slice(0,
|
|
41
|
-
` +
|
|
33
|
+
const o = t.index + t[0].length, i = e.slice(o);
|
|
34
|
+
let m;
|
|
35
|
+
const a = i.match(/^[^\S\n]*\n(\s*)/);
|
|
36
|
+
a ? m = `
|
|
37
|
+
${a[1]}okno(),` : i.trimStart().startsWith("]") ? m = "okno()" : m = "okno(), ", e = e.slice(0, o) + m + e.slice(o);
|
|
38
|
+
const d = 'import { okno } from "okno/vite"', r = [...e.matchAll(/^import .*$/gm)];
|
|
39
|
+
if (r.length) {
|
|
40
|
+
const c = r[r.length - 1], k = c.index + c[0].length;
|
|
41
|
+
e = e.slice(0, k) + `
|
|
42
|
+
` + d + e.slice(k);
|
|
42
43
|
} else
|
|
43
|
-
e =
|
|
44
|
+
e = d + `
|
|
44
45
|
` + e;
|
|
45
|
-
return
|
|
46
|
+
return l(n, e), !0;
|
|
46
47
|
}
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
function A(n, e = "starter") {
|
|
49
|
+
const t = s(n, "okno"), o = s(t, "schema.ts");
|
|
50
|
+
return f(o) ? !1 : (g(s(t, "pages"), { recursive: !0 }), e === "empty" ? (l(o, C), l(s(t, "pages", "home.ts"), j), !0) : (g(s(t, "globals"), { recursive: !0 }), l(o, E), l(s(t, "pages", "home.ts"), T), l(s(t, "globals", "meta.ts"), w), l(s(t, "globals", "navbar.ts"), M), l(s(t, "globals", "footer.ts"), P), !0));
|
|
51
|
+
}
|
|
52
|
+
const C = `import type { Schema } from "okno"
|
|
52
53
|
|
|
53
54
|
export default {
|
|
54
55
|
pages: {
|
|
@@ -57,35 +58,111 @@ export default {
|
|
|
57
58
|
fields: {
|
|
58
59
|
title: { type: "string" },
|
|
59
60
|
},
|
|
61
|
+
url: "/",
|
|
60
62
|
},
|
|
61
63
|
},
|
|
62
64
|
} satisfies Schema
|
|
63
|
-
`
|
|
64
|
-
), m(
|
|
65
|
-
r(e, "pages", "home.ts"),
|
|
66
|
-
`export default {
|
|
65
|
+
`, j = `export default {
|
|
67
66
|
title: "Hello from okno",
|
|
68
67
|
}
|
|
69
|
-
`
|
|
70
|
-
|
|
68
|
+
`, E = `import type { Schema } from "okno"
|
|
69
|
+
|
|
70
|
+
export default {
|
|
71
|
+
pages: {
|
|
72
|
+
home: {
|
|
73
|
+
name: "Home",
|
|
74
|
+
fields: {
|
|
75
|
+
headline: { type: "string" },
|
|
76
|
+
subhead: { type: "string" },
|
|
77
|
+
body: { type: "richtext" },
|
|
78
|
+
image: { type: "media", accept: ["image"] },
|
|
79
|
+
},
|
|
80
|
+
url: "/",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
globals: {
|
|
84
|
+
meta: {
|
|
85
|
+
name: "Meta",
|
|
86
|
+
fields: {
|
|
87
|
+
title: { type: "string" },
|
|
88
|
+
description: { type: "string" },
|
|
89
|
+
shareImage: { type: "media", accept: ["image"], label: "Share image" },
|
|
90
|
+
favicon: { type: "media", accept: ["image"] },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
navbar: {
|
|
94
|
+
name: "Navbar",
|
|
95
|
+
fields: {
|
|
96
|
+
logo: { type: "string" },
|
|
97
|
+
links: { type: "block", block: "navlink", multiple: true },
|
|
98
|
+
cta: { type: "link", label: "Call to action" },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
footer: {
|
|
102
|
+
name: "Footer",
|
|
103
|
+
fields: {
|
|
104
|
+
text: { type: "string" },
|
|
105
|
+
links: { type: "block", block: "navlink", multiple: true },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
blocks: {
|
|
110
|
+
navlink: {
|
|
111
|
+
name: "Nav link",
|
|
112
|
+
fields: {
|
|
113
|
+
link: { type: "link" },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
} satisfies Schema
|
|
118
|
+
`, T = `export default {
|
|
119
|
+
headline: "Hello from okno",
|
|
120
|
+
subhead: "Edit this on the page — press “e” or visit ?okno.",
|
|
121
|
+
body: "<p>This is your home page. Everything here is content in your repo.</p>",
|
|
122
|
+
image: null,
|
|
123
|
+
}
|
|
124
|
+
`, w = `export default {
|
|
125
|
+
title: "My okno site",
|
|
126
|
+
description: "Built with okno — a Git-backed CMS with live editing.",
|
|
127
|
+
shareImage: null,
|
|
128
|
+
favicon: null,
|
|
129
|
+
}
|
|
130
|
+
`, M = `export default {
|
|
131
|
+
logo: "okno",
|
|
132
|
+
links: [
|
|
133
|
+
{ link: { text: "Home", url: "/", external: false } },
|
|
134
|
+
{ link: { text: "About", url: "/about", external: false } },
|
|
135
|
+
],
|
|
136
|
+
cta: { text: "Get started", url: "/", external: false },
|
|
137
|
+
}
|
|
138
|
+
`, P = `export default {
|
|
139
|
+
text: "© My okno site",
|
|
140
|
+
links: [{ link: { text: "Home", url: "/", external: false } }],
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
function R(n) {
|
|
144
|
+
const e = v(S(import.meta.url)), t = s(e, "..", "skills", "connect-okno", "SKILL.md");
|
|
145
|
+
if (!f(t)) return null;
|
|
146
|
+
const o = s(".claude", "skills", "connect-okno", "SKILL.md"), i = s(n, o);
|
|
147
|
+
return f(i) ? null : (g(v(i), { recursive: !0 }), l(i, h(t, "utf-8")), o);
|
|
71
148
|
}
|
|
72
|
-
function
|
|
73
|
-
const t = e === "claude" ? ".mcp.json" :
|
|
149
|
+
function H(n, e) {
|
|
150
|
+
const t = e === "claude" ? ".mcp.json" : s(".cursor", "mcp.json"), o = s(n, t);
|
|
74
151
|
let i = { mcpServers: {} };
|
|
75
|
-
if (
|
|
152
|
+
if (f(o))
|
|
76
153
|
try {
|
|
77
|
-
i = JSON.parse(
|
|
154
|
+
i = JSON.parse(h(o, "utf-8"));
|
|
78
155
|
} catch {
|
|
79
156
|
}
|
|
80
|
-
return i.mcpServers ??= {}, i.mcpServers.okno = { command: "npx", args: ["okno", "mcp"] },
|
|
157
|
+
return i.mcpServers ??= {}, i.mcpServers.okno = { command: "npx", args: ["okno", "mcp"] }, g(s(o, ".."), { recursive: !0 }), l(o, JSON.stringify(i, null, 2) + `
|
|
81
158
|
`), t;
|
|
82
159
|
}
|
|
83
|
-
async function
|
|
84
|
-
const
|
|
160
|
+
async function _() {
|
|
161
|
+
const n = process.cwd(), e = await import("@clack/prompts");
|
|
85
162
|
e.intro("okno init");
|
|
86
|
-
let t =
|
|
163
|
+
let t = b(n);
|
|
87
164
|
if (t.framework === "unknown") {
|
|
88
|
-
const
|
|
165
|
+
const r = await e.select({
|
|
89
166
|
message: "Which framework is this project?",
|
|
90
167
|
options: [
|
|
91
168
|
{ value: "astro", label: "Astro" },
|
|
@@ -94,11 +171,11 @@ async function A() {
|
|
|
94
171
|
{ value: "nuxt", label: "Nuxt" }
|
|
95
172
|
]
|
|
96
173
|
});
|
|
97
|
-
if (e.isCancel(
|
|
98
|
-
const
|
|
99
|
-
t = { framework:
|
|
174
|
+
if (e.isCancel(r)) return e.cancel("Cancelled.");
|
|
175
|
+
const c = r, k = c === "astro" || c === "nuxt" ? "vite" : "top", y = u[c] ?? u.vite;
|
|
176
|
+
t = { framework: c, configPath: p(n, y), pluginsAt: k };
|
|
100
177
|
}
|
|
101
|
-
const
|
|
178
|
+
const o = await e.select({
|
|
102
179
|
message: "Set up the okno MCP server for your AI editor?",
|
|
103
180
|
options: [
|
|
104
181
|
{ value: "claude", label: "Claude Code (.mcp.json)" },
|
|
@@ -107,31 +184,51 @@ async function A() {
|
|
|
107
184
|
],
|
|
108
185
|
initialValue: "claude"
|
|
109
186
|
});
|
|
110
|
-
if (e.isCancel(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
187
|
+
if (e.isCancel(o)) return e.cancel("Cancelled.");
|
|
188
|
+
let i = "starter";
|
|
189
|
+
if (!f(s(n, "okno", "schema.ts"))) {
|
|
190
|
+
const r = await e.select({
|
|
191
|
+
message: "Scaffold a Starter, or an empty schema?",
|
|
192
|
+
options: [
|
|
193
|
+
{ value: "starter", label: "Starter — Home + Meta/Navbar/Footer globals, ready to edit" },
|
|
194
|
+
{ value: "empty", label: "Empty — just a Home page, model it yourself" }
|
|
195
|
+
],
|
|
196
|
+
initialValue: "starter"
|
|
197
|
+
});
|
|
198
|
+
if (e.isCancel(r)) return e.cancel("Cancelled.");
|
|
199
|
+
i = r;
|
|
200
|
+
}
|
|
201
|
+
const a = [];
|
|
202
|
+
let d = !1;
|
|
203
|
+
if (t.configPath && (d = x(t.configPath)), d)
|
|
204
|
+
a.push(`✓ Added okno() to ${t.configPath.replace(n + "/", "")}`);
|
|
115
205
|
else {
|
|
116
|
-
const
|
|
206
|
+
const r = t.pluginsAt === "vite" ? `import { okno } from "okno/vite"
|
|
117
207
|
// inside defineConfig:
|
|
118
208
|
// vite: { plugins: [okno()] }` : `import { okno } from "okno/vite"
|
|
119
209
|
// inside defineConfig:
|
|
120
210
|
// plugins: [okno()]`;
|
|
121
|
-
|
|
122
|
-
${
|
|
211
|
+
a.push(`! Couldn't edit your config automatically — add okno() yourself:
|
|
212
|
+
${r}`);
|
|
213
|
+
}
|
|
214
|
+
if (A(n, i) ? a.push(
|
|
215
|
+
i === "starter" ? "✓ Scaffolded the okno Starter (Home + Meta/Navbar/Footer globals)" : "✓ Scaffolded an empty okno/ (Home page only)"
|
|
216
|
+
) : a.push("• okno/ already exists — left it alone"), o !== "skip") {
|
|
217
|
+
const r = H(n, o);
|
|
218
|
+
a.push(`✓ Registered the okno MCP server in ${r}`);
|
|
123
219
|
}
|
|
124
|
-
if (
|
|
125
|
-
const
|
|
126
|
-
|
|
220
|
+
if (o === "claude") {
|
|
221
|
+
const r = R(n);
|
|
222
|
+
r && a.push(`✓ Installed the connect-okno skill in ${r}`);
|
|
127
223
|
}
|
|
128
|
-
e.note(
|
|
224
|
+
e.note(a.join(`
|
|
129
225
|
`), "Done"), e.outro("Run your dev server and press “e” (or visit ?okno) to open the editor.");
|
|
130
226
|
}
|
|
131
227
|
export {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
228
|
+
x as addPlugin,
|
|
229
|
+
b as detect,
|
|
230
|
+
_ as runInit,
|
|
231
|
+
A as scaffold,
|
|
232
|
+
H as writeMcpConfig,
|
|
233
|
+
R as writeSkill
|
|
137
234
|
};
|
package/dist/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAwCA;kCACkC;AAClC,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAwCA;kCACkC;AAClC,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAmK5C"}
|
package/dist/mcp.js
CHANGED
|
@@ -1,54 +1,59 @@
|
|
|
1
|
-
import { existsSync as l, readdirSync as j, readFileSync as
|
|
2
|
-
import { join as i, dirname as O, relative as
|
|
1
|
+
import { existsSync as l, readdirSync as j, readFileSync as y, mkdirSync as D, writeFileSync as S, unlinkSync as C, realpathSync as N } from "node:fs";
|
|
2
|
+
import { join as i, dirname as O, relative as L, sep as E, resolve as T } from "node:path";
|
|
3
3
|
import { execFileSync as F } from "node:child_process";
|
|
4
|
-
import { p as b, e as
|
|
5
|
-
function
|
|
4
|
+
import { p as b, e as M, a as $ } from "./content-encode-DoojdsVU.js";
|
|
5
|
+
function I() {
|
|
6
6
|
const f = process.env.OKNO_DIR;
|
|
7
7
|
if (f) {
|
|
8
|
-
const o =
|
|
8
|
+
const o = T(process.cwd(), f);
|
|
9
9
|
return l(i(o, "schema.ts")) ? o : l(i(o, "okno", "schema.ts")) ? i(o, "okno") : o;
|
|
10
10
|
}
|
|
11
11
|
let h = process.cwd();
|
|
12
12
|
for (let o = 0; o < 25; o++) {
|
|
13
13
|
if (l(i(h, "okno", "schema.ts"))) return i(h, "okno");
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
h =
|
|
14
|
+
const s = O(h);
|
|
15
|
+
if (s === h) break;
|
|
16
|
+
h = s;
|
|
17
17
|
}
|
|
18
18
|
console.error("okno mcp: could not find an okno/ directory. Set OKNO_DIR to your project's okno/ path."), process.exit(1);
|
|
19
19
|
}
|
|
20
|
-
async function
|
|
21
|
-
const { McpServer: f } = await import("@modelcontextprotocol/sdk/server/mcp.js"), { StdioServerTransport: h } = await import("@modelcontextprotocol/sdk/server/stdio.js"), { z: o } = await import("zod"),
|
|
20
|
+
async function A() {
|
|
21
|
+
const { McpServer: f } = await import("@modelcontextprotocol/sdk/server/mcp.js"), { StdioServerTransport: h } = await import("@modelcontextprotocol/sdk/server/stdio.js"), { z: o } = await import("zod"), s = I();
|
|
22
22
|
function g(e) {
|
|
23
23
|
if (!e || e.includes("..") || e.includes("\0") || e.startsWith("/")) throw new Error(`Invalid path: ${e}`);
|
|
24
24
|
if (!e.endsWith(".ts")) throw new Error(`Path must be a .ts file: ${e}`);
|
|
25
|
-
const t = i(
|
|
26
|
-
if (t !==
|
|
25
|
+
const t = i(s, e);
|
|
26
|
+
if (t !== s && !t.startsWith(s + E)) throw new Error(`Path escapes okno dir: ${e}`);
|
|
27
27
|
return t;
|
|
28
28
|
}
|
|
29
|
-
const m = (e, t =
|
|
30
|
-
let
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
29
|
+
const m = (e, t = s) => F("git", e, { cwd: t, encoding: "utf-8" }).trim();
|
|
30
|
+
let u;
|
|
31
|
+
const _ = () => {
|
|
32
|
+
if (u !== void 0) return u;
|
|
33
33
|
try {
|
|
34
|
-
|
|
34
|
+
u = m(["config", "user.name"]) || "local";
|
|
35
35
|
} catch {
|
|
36
|
-
|
|
36
|
+
u = "local";
|
|
37
37
|
}
|
|
38
|
-
return
|
|
38
|
+
return u;
|
|
39
39
|
};
|
|
40
40
|
function w() {
|
|
41
|
-
const e = i(
|
|
41
|
+
const e = i(s, "schema.ts");
|
|
42
42
|
let t = `import type { Schema } from "okno"
|
|
43
43
|
|
|
44
|
-
`,
|
|
44
|
+
`, r = {};
|
|
45
45
|
if (l(e)) {
|
|
46
|
-
const n =
|
|
47
|
-
d > 0 && (t = n.slice(0, d)),
|
|
46
|
+
const n = y(e, "utf-8"), d = n.indexOf("export default");
|
|
47
|
+
d > 0 && (t = n.slice(0, d)), r = b(n) ?? {};
|
|
48
48
|
}
|
|
49
|
-
return { schema:
|
|
49
|
+
return { schema: r, header: t };
|
|
50
50
|
}
|
|
51
|
-
const c = (e) => ({ content: [{ type: "text", text: JSON.stringify(e, null, 2) }] }), a = new f(
|
|
51
|
+
const c = (e) => ({ content: [{ type: "text", text: JSON.stringify(e, null, 2) }] }), a = new f(
|
|
52
|
+
{ name: "okno", version: "0.2.0" },
|
|
53
|
+
{
|
|
54
|
+
instructions: "Okno is a Git-backed CMS: content is .ts files in the project's repo. Call get_schema first to learn the content model (pages, collections, globals, blocks). write_schema replaces the WHOLE schema — read it, merge, write it all back. Writes are drafts on the working branch; a human publishes — you cannot. For how to MODEL content (when to use a collection + reference vs a block vs a global, the spread-to-edit rendering pattern), use the `connect-okno` skill — it's installed at .claude/skills/connect-okno/SKILL.md (run `okno init` if missing)."
|
|
55
|
+
}
|
|
56
|
+
);
|
|
52
57
|
a.tool(
|
|
53
58
|
"get_schema",
|
|
54
59
|
"Get the Okno project's schema — pages, collections (with field definitions), globals, blocks, locales. Call this first to learn the content model before reading or writing.",
|
|
@@ -59,10 +64,10 @@ async function W() {
|
|
|
59
64
|
"List the entries in a collection. Returns each entry's slug and file path (relative to the okno dir).",
|
|
60
65
|
{ collection: o.string().describe("Collection slug, e.g. 'articles'") },
|
|
61
66
|
async ({ collection: e }) => {
|
|
62
|
-
const t = i(
|
|
67
|
+
const t = i(s, "collections", e);
|
|
63
68
|
if (!l(t)) return c([]);
|
|
64
|
-
const
|
|
65
|
-
return c(
|
|
69
|
+
const r = j(t, { withFileTypes: !0 }).filter((n) => n.isFile() && n.name.endsWith(".ts") && n.name !== "index.ts").map((n) => ({ slug: n.name.slice(0, -3), path: `collections/${e}/${n.name}` }));
|
|
70
|
+
return c(r);
|
|
66
71
|
}
|
|
67
72
|
), a.tool(
|
|
68
73
|
"read_entry",
|
|
@@ -71,7 +76,7 @@ async function W() {
|
|
|
71
76
|
async ({ path: e }) => {
|
|
72
77
|
const t = g(e);
|
|
73
78
|
if (!l(t)) throw new Error(`Not found: ${e}`);
|
|
74
|
-
return c(b(
|
|
79
|
+
return c(b(y(t, "utf-8")));
|
|
75
80
|
}
|
|
76
81
|
), a.tool(
|
|
77
82
|
"write_entry",
|
|
@@ -81,14 +86,14 @@ async function W() {
|
|
|
81
86
|
content: o.record(o.unknown()).describe("Entry fields as an object, matching the schema")
|
|
82
87
|
},
|
|
83
88
|
async ({ path: e, content: t }) => {
|
|
84
|
-
const
|
|
89
|
+
const r = g(e);
|
|
85
90
|
let n;
|
|
86
|
-
if (l(
|
|
87
|
-
const
|
|
88
|
-
|
|
91
|
+
if (l(r)) {
|
|
92
|
+
const p = b(y(r, "utf-8"));
|
|
93
|
+
p.created?.date && (n = p.created);
|
|
89
94
|
}
|
|
90
|
-
const d =
|
|
91
|
-
return D(O(
|
|
95
|
+
const d = M(t, { now: (/* @__PURE__ */ new Date()).toISOString(), author: _(), created: n });
|
|
96
|
+
return D(O(r), { recursive: !0 }), S(r, d), c({ ok: !0, path: e });
|
|
92
97
|
}
|
|
93
98
|
), a.tool(
|
|
94
99
|
"delete_entry",
|
|
@@ -96,7 +101,7 @@ async function W() {
|
|
|
96
101
|
{ path: o.string().describe("Entry path relative to the okno dir") },
|
|
97
102
|
async ({ path: e }) => {
|
|
98
103
|
const t = g(e);
|
|
99
|
-
return l(t) &&
|
|
104
|
+
return l(t) && C(t), c({ ok: !0, path: e });
|
|
100
105
|
}
|
|
101
106
|
), a.tool(
|
|
102
107
|
"write_schema",
|
|
@@ -104,32 +109,32 @@ async function W() {
|
|
|
104
109
|
{ schema: o.record(o.unknown()).describe("The complete schema object: { pages?, collections?, globals?, blocks?, locales? }") },
|
|
105
110
|
async ({ schema: e }) => {
|
|
106
111
|
const { header: t } = w();
|
|
107
|
-
return S(i(
|
|
112
|
+
return S(i(s, "schema.ts"), $(e, t)), c({ ok: !0 });
|
|
108
113
|
}
|
|
109
114
|
), a.tool(
|
|
110
115
|
"publish",
|
|
111
116
|
"Commit and push the okno content changes. Stages ONLY the okno/ paths (never the project's code), commits, and pushes the current branch. No-op if there's nothing to commit.",
|
|
112
117
|
{ message: o.string().optional().describe("Optional commit message (default: 'okno: content')") },
|
|
113
118
|
async ({ message: e }) => {
|
|
114
|
-
const t = m(["rev-parse", "--show-toplevel"]),
|
|
115
|
-
m(["add", "-A", "--",
|
|
119
|
+
const t = m(["rev-parse", "--show-toplevel"]), r = L(t, N(s)).split(E).join("/");
|
|
120
|
+
m(["add", "-A", "--", r], t);
|
|
116
121
|
const n = m(["diff", "--cached", "--name-only"], t);
|
|
117
122
|
let d = !1;
|
|
118
123
|
n && (m(["commit", "-m", e?.trim() || "okno: content"], t), d = !0);
|
|
119
|
-
let
|
|
124
|
+
let p = !1, v = null;
|
|
120
125
|
try {
|
|
121
|
-
m(["push"], t),
|
|
122
|
-
} catch (
|
|
123
|
-
v =
|
|
124
|
-
`)[0] : String(
|
|
126
|
+
m(["push"], t), p = !0;
|
|
127
|
+
} catch (k) {
|
|
128
|
+
v = k instanceof Error ? k.message.split(`
|
|
129
|
+
`)[0] : String(k);
|
|
125
130
|
}
|
|
126
|
-
return c({ ok: !0, committed: d, pushed:
|
|
131
|
+
return c({ ok: !0, committed: d, pushed: p, pushError: v, files: n ? n.split(`
|
|
127
132
|
`) : [] });
|
|
128
133
|
}
|
|
129
134
|
);
|
|
130
|
-
const
|
|
131
|
-
await a.connect(
|
|
135
|
+
const x = new h();
|
|
136
|
+
await a.connect(x), console.error(`okno mcp connected → ${s} (local filesystem)`);
|
|
132
137
|
}
|
|
133
138
|
export {
|
|
134
|
-
|
|
139
|
+
A as runMcp
|
|
135
140
|
};
|