scriptonia 0.2.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 +54 -0
- package/bin/scriptonia.mjs +322 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# scriptonia
|
|
2
|
+
|
|
3
|
+
Product memory for your coding agent. One command, browser sign-in, and your
|
|
4
|
+
agent reads what your customers actually asked for — with sources it can cite.
|
|
5
|
+
|
|
6
|
+
## Quick start
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npx scriptonia login
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That's it. A browser opens, you sign in with Google, and:
|
|
13
|
+
|
|
14
|
+
- your project is created (with a sample dataset so you can try it immediately),
|
|
15
|
+
- an API key is minted and stored for you (you never see or paste a key),
|
|
16
|
+
- the Claude Code skill is installed to `~/.claude/skills/scriptonia/`.
|
|
17
|
+
|
|
18
|
+
Then your agent — or you — can ask the product brain:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
scriptonia query "audit log filtering"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
| command | what it does |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `scriptonia login` | Browser sign-in. Stores creds in `~/.scriptonia/config.json`, installs the skill. |
|
|
29
|
+
| `scriptonia query "<topic>"` | Ask the product brain. Flags: `--window 7d\|30d\|90d\|all`, `--segment enterprise\|mid_market\|smb\|free`, `--json`. |
|
|
30
|
+
| `scriptonia link "<signal_id>"` | Resolve a signal id (from `query` sources) to its full text. |
|
|
31
|
+
| `scriptonia status` | Who you are / which project. |
|
|
32
|
+
| `scriptonia logout` | Forget credentials. |
|
|
33
|
+
| `scriptonia mcp` | Run as an MCP server over stdio (for MCP-native agents like Codex). |
|
|
34
|
+
|
|
35
|
+
## For MCP-native agents
|
|
36
|
+
|
|
37
|
+
Point your agent at:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "scriptonia", "mcp"]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It exposes `get_context`, `link_back`, and `flag_contradiction`, reading your
|
|
47
|
+
stored credentials — no env vars needed after `scriptonia login`.
|
|
48
|
+
|
|
49
|
+
## Self-hosting
|
|
50
|
+
|
|
51
|
+
By default the CLI talks to `https://scriptonia.dev`. Point it elsewhere with
|
|
52
|
+
`SCRIPTONIA_URL=https://your.host scriptonia login` (or `--url`).
|
|
53
|
+
|
|
54
|
+
Requires Node 18+.
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scriptonia — product memory for your coding agent.
|
|
4
|
+
*
|
|
5
|
+
* scriptonia login browser sign-in; stores creds; installs the skill
|
|
6
|
+
* scriptonia query "<topic>" ask the product brain (what the agent calls)
|
|
7
|
+
* scriptonia link "<signal_id>" resolve a signal to its full text
|
|
8
|
+
* scriptonia status who am I / which project
|
|
9
|
+
* scriptonia logout forget creds
|
|
10
|
+
* scriptonia mcp speak MCP over stdio (for MCP-native agents)
|
|
11
|
+
*
|
|
12
|
+
* After `login`, everything lives in ~/.scriptonia/config.json. No env vars,
|
|
13
|
+
* no key pasting. The installed Claude Code skill just runs `scriptonia query`.
|
|
14
|
+
*
|
|
15
|
+
* Pure ESM, zero build step — runs on Node 18+.
|
|
16
|
+
*/
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import http from "node:http";
|
|
19
|
+
import os from "node:os";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { spawn } from "node:child_process";
|
|
22
|
+
import { randomBytes } from "node:crypto";
|
|
23
|
+
|
|
24
|
+
const DEFAULT_URL = process.env.SCRIPTONIA_URL || "https://scriptonia.dev";
|
|
25
|
+
const CONFIG_DIR = path.join(os.homedir(), ".scriptonia");
|
|
26
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
27
|
+
|
|
28
|
+
const [, , cmd, ...rest] = process.argv;
|
|
29
|
+
|
|
30
|
+
const commands = { login, query, link, status, logout, init: reinstallSkill, mcp, help: async () => usage() };
|
|
31
|
+
const run = commands[cmd ?? "help"] ?? (async () => { usage(); process.exit(1); });
|
|
32
|
+
run(rest).catch((e) => {
|
|
33
|
+
console.error("error:", e instanceof Error ? e.message : String(e));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ─────────────────────────────────────────────────────────────
|
|
38
|
+
// login — the one command
|
|
39
|
+
// ─────────────────────────────────────────────────────────────
|
|
40
|
+
async function login(args) {
|
|
41
|
+
const flags = parseFlags(args);
|
|
42
|
+
const url = (flags.url ?? DEFAULT_URL).replace(/\/$/, "");
|
|
43
|
+
const state = randomBytes(16).toString("hex");
|
|
44
|
+
|
|
45
|
+
const result = await new Promise((resolve, reject) => {
|
|
46
|
+
let timer;
|
|
47
|
+
const done = (fn) => { clearTimeout(timer); fn(); };
|
|
48
|
+
const server = http.createServer((req, res) => {
|
|
49
|
+
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
50
|
+
if (u.pathname !== "/callback") { res.writeHead(404); res.end(); return; }
|
|
51
|
+
const token = u.searchParams.get("token");
|
|
52
|
+
const returnedState = u.searchParams.get("state");
|
|
53
|
+
const email = u.searchParams.get("email") ?? "";
|
|
54
|
+
const project = u.searchParams.get("project") ?? "";
|
|
55
|
+
if (!token || returnedState !== state) {
|
|
56
|
+
res.writeHead(400, { "content-type": "text/html" });
|
|
57
|
+
res.end(pageHtml("Login failed", "State mismatch — run <code>scriptonia login</code> again."));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
res.writeHead(200, { "content-type": "text/html" });
|
|
61
|
+
res.end(pageHtml("You're in ✓", `Signed in as <b>${escapeHtml(email)}</b> · project <b>${escapeHtml(project)}</b>.<br>Return to your terminal.`));
|
|
62
|
+
done(() => { server.close(); resolve({ url, token, email, project }); });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
server.listen(0, "127.0.0.1", () => {
|
|
66
|
+
const addr = server.address();
|
|
67
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
68
|
+
const authUrl =
|
|
69
|
+
`${url}/auth/cli?port=${port}&state=${state}` +
|
|
70
|
+
(flags.dev ? `&dev=1&email=${encodeURIComponent(flags.email ?? "you@example.com")}` : "");
|
|
71
|
+
console.log("\n Opening your browser to sign in…");
|
|
72
|
+
console.log(" If it doesn't open, visit:\n " + authUrl + "\n");
|
|
73
|
+
openBrowser(authUrl);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
timer = setTimeout(() => { server.close(); reject(new Error("timed out waiting for browser login (120s)")); }, 120_000);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
saveConfig(result);
|
|
80
|
+
installSkill();
|
|
81
|
+
|
|
82
|
+
console.log(`\n ✓ Signed in as ${result.email}`);
|
|
83
|
+
console.log(` ✓ Project "${result.project}" ready (with a sample dataset to try)`);
|
|
84
|
+
console.log(` ✓ Skill installed → ~/.claude/skills/scriptonia/SKILL.md`);
|
|
85
|
+
console.log(`\n Your agent now reads your customers. Try it:\n`);
|
|
86
|
+
console.log(` scriptonia query "audit log filtering"\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─────────────────────────────────────────────────────────────
|
|
90
|
+
// query — what the agent (and you) call
|
|
91
|
+
// ─────────────────────────────────────────────────────────────
|
|
92
|
+
async function query(args) {
|
|
93
|
+
const cfg = requireConfig();
|
|
94
|
+
const flags = parseFlags(args);
|
|
95
|
+
const q = args.filter((a) => !a.startsWith("--")).join(" ").trim();
|
|
96
|
+
if (!q) fail('usage: scriptonia query "<topic>" [--window 30d] [--segment enterprise] [--json]');
|
|
97
|
+
|
|
98
|
+
const res = await fetch(`${cfg.url}/api/query`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { authorization: `Bearer ${cfg.token}`, "content-type": "application/json" },
|
|
101
|
+
body: JSON.stringify({ query: q, window: flags.window, segment: flags.segment }),
|
|
102
|
+
});
|
|
103
|
+
const data = await res.json();
|
|
104
|
+
if (!res.ok) fail(`query failed (${res.status}): ${JSON.stringify(data)}`);
|
|
105
|
+
|
|
106
|
+
const wantJson = flags.json !== undefined || !process.stdout.isTTY;
|
|
107
|
+
if (wantJson) { process.stdout.write(JSON.stringify(data)); return; }
|
|
108
|
+
printPretty(data);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─────────────────────────────────────────────────────────────
|
|
112
|
+
// link — resolve a signal_id to its full body
|
|
113
|
+
// ─────────────────────────────────────────────────────────────
|
|
114
|
+
async function link(args) {
|
|
115
|
+
const cfg = requireConfig();
|
|
116
|
+
const id = args.find((a) => !a.startsWith("--"));
|
|
117
|
+
if (!id) fail('usage: scriptonia link "<signal_id>"');
|
|
118
|
+
const res = await fetch(`${cfg.url}/api/link_back?signal_id=${encodeURIComponent(id)}`, {
|
|
119
|
+
headers: { authorization: `Bearer ${cfg.token}` },
|
|
120
|
+
});
|
|
121
|
+
const data = await res.json();
|
|
122
|
+
if (!res.ok) fail(`link failed (${res.status}): ${JSON.stringify(data)}`);
|
|
123
|
+
process.stdout.write(JSON.stringify(data));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─────────────────────────────────────────────────────────────
|
|
127
|
+
// status / logout
|
|
128
|
+
// ─────────────────────────────────────────────────────────────
|
|
129
|
+
async function status() {
|
|
130
|
+
const cfg = loadConfig();
|
|
131
|
+
if (!cfg) { console.log("not logged in — run `scriptonia login`"); return; }
|
|
132
|
+
console.log(`signed in as ${cfg.email}`);
|
|
133
|
+
console.log(`project ${cfg.project}`);
|
|
134
|
+
console.log(`endpoint ${cfg.url}`);
|
|
135
|
+
console.log(`config ${CONFIG_PATH}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function logout() {
|
|
139
|
+
if (fs.existsSync(CONFIG_PATH)) fs.rmSync(CONFIG_PATH);
|
|
140
|
+
console.log("logged out.");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─────────────────────────────────────────────────────────────
|
|
144
|
+
// skill install
|
|
145
|
+
// ─────────────────────────────────────────────────────────────
|
|
146
|
+
async function reinstallSkill() {
|
|
147
|
+
requireConfig();
|
|
148
|
+
installSkill();
|
|
149
|
+
console.log("✓ skill reinstalled → ~/.claude/skills/scriptonia/SKILL.md");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function installSkill() {
|
|
153
|
+
const dir = path.join(os.homedir(), ".claude", "skills", "scriptonia");
|
|
154
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
155
|
+
fs.writeFileSync(path.join(dir, "SKILL.md"), skillMarkdown());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function skillMarkdown() {
|
|
159
|
+
return `---
|
|
160
|
+
name: scriptonia
|
|
161
|
+
description: >
|
|
162
|
+
BEFORE planning any user-facing change — a feature, a bug fix, a refactor with
|
|
163
|
+
UX impact, or a non-trivial default — run \`scriptonia query "<topic>"\`. It
|
|
164
|
+
returns what THIS project's customers actually asked for (with source ids you
|
|
165
|
+
can cite), what prior decisions your plan might contradict, and known edge
|
|
166
|
+
cases. Skipping it means shipping a plan that isn't grounded in customer signal.
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
# Scriptonia — Product Memory
|
|
170
|
+
|
|
171
|
+
Before writing a plan for anything user-facing, call:
|
|
172
|
+
|
|
173
|
+
\`\`\`sh
|
|
174
|
+
scriptonia query "<the topic you're about to build>"
|
|
175
|
+
\`\`\`
|
|
176
|
+
|
|
177
|
+
Optional flags: \`--window 7d|30d|90d|all\` (default 30d), \`--segment enterprise|mid_market|smb|free\`.
|
|
178
|
+
|
|
179
|
+
Output (JSON when piped) contains:
|
|
180
|
+
- **requirements[]** — each with \`source_ids\` and a \`context_url\`. Cite the
|
|
181
|
+
source_ids next to the matching step in your plan.
|
|
182
|
+
- **contradictions[]** — if non-empty, STOP and surface it to the human before
|
|
183
|
+
writing code. Each has a \`decision_ref\`.
|
|
184
|
+
- **edge_cases[]** — fold these into the plan.
|
|
185
|
+
- **sources[]** — quote a customer verbatim with \`scriptonia link "<signal_id>"\`.
|
|
186
|
+
|
|
187
|
+
Rule: every plan step for a user-facing change should cite at least one
|
|
188
|
+
source_id. If it can't, you're inventing demand — query first.
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─────────────────────────────────────────────────────────────
|
|
193
|
+
// mcp — MCP stdio server (for MCP-native agents)
|
|
194
|
+
// ─────────────────────────────────────────────────────────────
|
|
195
|
+
async function mcp() {
|
|
196
|
+
const cfg = requireConfig();
|
|
197
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
198
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
199
|
+
const { z } = await import("zod");
|
|
200
|
+
|
|
201
|
+
const server = new McpServer({ name: "scriptonia", version: "0.2.0" }, { capabilities: { tools: {} } });
|
|
202
|
+
const auth = () => ({ authorization: `Bearer ${cfg.token}`, "content-type": "application/json" });
|
|
203
|
+
|
|
204
|
+
server.registerTool(
|
|
205
|
+
"get_context",
|
|
206
|
+
{
|
|
207
|
+
description:
|
|
208
|
+
"Query the product brain: what customers said about a topic, prior decisions your plan may contradict, and known edge cases. Call BEFORE planning any user-facing change.",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
query: z.string(),
|
|
211
|
+
window: z.enum(["7d", "30d", "90d", "all"]).optional(),
|
|
212
|
+
segment: z.enum(["enterprise", "mid_market", "smb", "free", "unknown"]).optional(),
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
async ({ query: q, window, segment }) => {
|
|
216
|
+
const r = await fetch(`${cfg.url}/api/query`, { method: "POST", headers: auth(), body: JSON.stringify({ query: q, window, segment }) });
|
|
217
|
+
return { content: [{ type: "text", text: await r.text() }], isError: !r.ok };
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
server.registerTool(
|
|
221
|
+
"link_back",
|
|
222
|
+
{ description: "Resolve a signal_id from get_context.sources[] to its full body and author.", inputSchema: { signal_id: z.string() } },
|
|
223
|
+
async ({ signal_id }) => {
|
|
224
|
+
const r = await fetch(`${cfg.url}/api/link_back?signal_id=${encodeURIComponent(signal_id)}`, { headers: { authorization: `Bearer ${cfg.token}` } });
|
|
225
|
+
return { content: [{ type: "text", text: await r.text() }], isError: !r.ok };
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
server.registerTool(
|
|
229
|
+
"flag_contradiction",
|
|
230
|
+
{ description: "Report that your plan conflicts with a context. Persists for audit.", inputSchema: { plan_id: z.string(), context_id: z.string().optional(), note: z.string().optional() } },
|
|
231
|
+
async ({ plan_id, context_id, note }) => {
|
|
232
|
+
const r = await fetch(`${cfg.url}/api/flag_contradiction`, { method: "POST", headers: auth(), body: JSON.stringify({ plan_id, context_id, note }) });
|
|
233
|
+
return { content: [{ type: "text", text: await r.text() }], isError: !r.ok };
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
await server.connect(new StdioServerTransport());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─────────────────────────────────────────────────────────────
|
|
241
|
+
// helpers
|
|
242
|
+
// ─────────────────────────────────────────────────────────────
|
|
243
|
+
function loadConfig() {
|
|
244
|
+
try { return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8")); } catch { return null; }
|
|
245
|
+
}
|
|
246
|
+
function requireConfig() {
|
|
247
|
+
const cfg = loadConfig();
|
|
248
|
+
if (!cfg) fail("not logged in — run `scriptonia login`");
|
|
249
|
+
return cfg;
|
|
250
|
+
}
|
|
251
|
+
function saveConfig(cfg) {
|
|
252
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
253
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function parseFlags(args) {
|
|
257
|
+
const out = {};
|
|
258
|
+
for (let i = 0; i < args.length; i++) {
|
|
259
|
+
const a = args[i];
|
|
260
|
+
if (!a.startsWith("--")) continue;
|
|
261
|
+
const eq = a.indexOf("=");
|
|
262
|
+
if (eq > 0) out[a.slice(2, eq)] = a.slice(eq + 1);
|
|
263
|
+
else {
|
|
264
|
+
const next = args[i + 1];
|
|
265
|
+
if (next && !next.startsWith("--")) out[a.slice(2)] = args[++i];
|
|
266
|
+
else out[a.slice(2)] = "true";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function openBrowser(url) {
|
|
273
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
274
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
275
|
+
try { spawn(cmd, args, { stdio: "ignore", detached: true }).unref(); } catch { /* click the printed URL */ }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function printPretty(d) {
|
|
279
|
+
const c = { dim: "\x1b[2m", b: "\x1b[1m", v: "\x1b[35m", w: "\x1b[33m", g: "\x1b[32m", r: "\x1b[0m" };
|
|
280
|
+
console.log(`\n${c.b}${d.tldr ?? ""}${c.r}\n`);
|
|
281
|
+
for (const req of d.requirements ?? []) {
|
|
282
|
+
console.log(` ${c.g}●${c.r} ${c.b}${req.title}${c.r} ${c.dim}[${req.strength}]${c.r}`);
|
|
283
|
+
console.log(` ${req.detail}`);
|
|
284
|
+
console.log(` ${c.dim}sources: ${(req.source_ids ?? []).join(", ")}${c.r}`);
|
|
285
|
+
if (req.context_url) console.log(` ${c.v}${req.context_url}${c.r}`);
|
|
286
|
+
}
|
|
287
|
+
for (const con of d.contradictions ?? []) {
|
|
288
|
+
console.log(`\n ${c.w}⚠ CONTRADICTS${c.r} ${con.detail}`);
|
|
289
|
+
console.log(` ${c.dim}${con.decision_ref}${c.r}`);
|
|
290
|
+
}
|
|
291
|
+
if ((d.edge_cases ?? []).length) {
|
|
292
|
+
console.log(`\n ${c.dim}edge cases:${c.r}`);
|
|
293
|
+
for (const e of d.edge_cases) console.log(` · ${e}`);
|
|
294
|
+
}
|
|
295
|
+
console.log(`\n ${c.dim}${(d.sources ?? []).length} sources · ${d.meta?.ms ?? "?"}ms${c.r}\n`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function pageHtml(title, body) {
|
|
299
|
+
return `<!doctype html><meta charset=utf8><body style="font-family:ui-monospace,monospace;background:#08080A;color:#EDEDF0;display:grid;place-items:center;height:100vh;margin:0">
|
|
300
|
+
<div style="text-align:center;max-width:420px">
|
|
301
|
+
<div style="font-size:28px;letter-spacing:-.02em;margin-bottom:12px">${title}</div>
|
|
302
|
+
<div style="color:#9a9aa5;line-height:1.6">${body}</div>
|
|
303
|
+
</div></body>`;
|
|
304
|
+
}
|
|
305
|
+
function escapeHtml(s) {
|
|
306
|
+
return s.replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[ch]));
|
|
307
|
+
}
|
|
308
|
+
function fail(msg) { console.error(msg); process.exit(1); }
|
|
309
|
+
function usage() {
|
|
310
|
+
console.log(`scriptonia — product memory for your coding agent
|
|
311
|
+
|
|
312
|
+
scriptonia login sign in (browser) — installs the skill, no keys to copy
|
|
313
|
+
scriptonia query "<topic>" ask the product brain
|
|
314
|
+
scriptonia link "<id>" resolve a signal to its full text
|
|
315
|
+
scriptonia status show who you are
|
|
316
|
+
scriptonia logout forget credentials
|
|
317
|
+
scriptonia mcp run as an MCP server (stdio)
|
|
318
|
+
|
|
319
|
+
login flags: --dev --email you@co.com (local testing; skips Google)
|
|
320
|
+
query flags: --window 30d --segment enterprise --json
|
|
321
|
+
`);
|
|
322
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scriptonia",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Product memory for your coding agent. One command, browser sign-in, your agent reads your customers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"scriptonia": "bin/scriptonia.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
18
|
+
"zod": "^3.25.76"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"scriptonia",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"mcp",
|
|
24
|
+
"product-memory",
|
|
25
|
+
"customer-signal",
|
|
26
|
+
"agent",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://scriptonia.dev",
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|