@sparelabs/sightline-extension-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/LICENSE +21 -0
- package/README.md +71 -0
- package/bin/sightline-ext.mjs +10 -0
- package/dist/args.d.ts +11 -0
- package/dist/args.js +53 -0
- package/dist/build.d.ts +56 -0
- package/dist/build.js +73 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +288 -0
- package/dist/core-api.d.ts +98 -0
- package/dist/core-api.js +166 -0
- package/dist/deploy.d.ts +13 -0
- package/dist/deploy.js +71 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -0
- package/dist/project.d.ts +99 -0
- package/dist/project.js +165 -0
- package/dist/template.d.ts +11 -0
- package/dist/template.js +221 -0
- package/package.json +53 -0
package/dist/template.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// `sightline-ext init`: the files of a new extension repository. A small
|
|
2
|
+
// "notes" extension: its own table, a read tool and a write tool, an audit
|
|
3
|
+
// event, and an /api route, so every part of the contract is shown once.
|
|
4
|
+
// The generated repository depends on the published packages only.
|
|
5
|
+
import { EXT_ID_RE } from "./project.js";
|
|
6
|
+
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
7
|
+
/** kebab-case id → the snake_case prefix of its tool names. */
|
|
8
|
+
export const toolPrefix = (id) => id.replace(/-/g, "_");
|
|
9
|
+
export function templateProblems(options) {
|
|
10
|
+
const problems = [];
|
|
11
|
+
if (!EXT_ID_RE.test(options.id) || options.id.length > 40)
|
|
12
|
+
problems.push(`"${options.id}" is not an extension id: kebab-case (a-z, 0-9, -), starting with a letter, at most 40 characters`);
|
|
13
|
+
if (options.publisher !== undefined && (!EXT_ID_RE.test(options.publisher) || options.publisher.length > 40))
|
|
14
|
+
problems.push(`--publisher must be kebab-case`);
|
|
15
|
+
return problems;
|
|
16
|
+
}
|
|
17
|
+
export function templateFiles(options) {
|
|
18
|
+
const problems = templateProblems(options);
|
|
19
|
+
if (problems.length)
|
|
20
|
+
throw new Error(problems.join("; "));
|
|
21
|
+
const { id, packagesVersion } = options;
|
|
22
|
+
const name = options.name ?? id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
23
|
+
const publisher = options.publisher ?? id;
|
|
24
|
+
const p = toolPrefix(id);
|
|
25
|
+
const range = `^${packagesVersion}`;
|
|
26
|
+
const manifest = {
|
|
27
|
+
manifestVersion: 2,
|
|
28
|
+
id,
|
|
29
|
+
name,
|
|
30
|
+
description: `${name}: notes your team keeps in Sightline.`,
|
|
31
|
+
publisher,
|
|
32
|
+
version: "0.1.0",
|
|
33
|
+
trust: "T2",
|
|
34
|
+
coreApi: "^0.1",
|
|
35
|
+
runtime: { kind: "hosted", entry: "dist/server.mjs", memoryMb: 256 },
|
|
36
|
+
permissions: [
|
|
37
|
+
{ key: `${id}.read`, label: "Read notes", default: "employees" },
|
|
38
|
+
{ key: `${id}.write`, label: "Write notes", default: "employees" },
|
|
39
|
+
],
|
|
40
|
+
tools: [
|
|
41
|
+
{
|
|
42
|
+
name: `${p}_list`,
|
|
43
|
+
access: "read",
|
|
44
|
+
description: "List the most recent notes, newest first.",
|
|
45
|
+
permission: `${id}.read`,
|
|
46
|
+
surfaces: ["chat", "mcp"],
|
|
47
|
+
input: {
|
|
48
|
+
type: "object",
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
properties: { limit: { type: "integer", minimum: 1, maximum: 50, description: "How many (default 10)." } },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: `${p}_add`,
|
|
55
|
+
access: "write",
|
|
56
|
+
description: "Add a note.",
|
|
57
|
+
permission: `${id}.write`,
|
|
58
|
+
surfaces: ["chat", "mcp"],
|
|
59
|
+
input: {
|
|
60
|
+
type: "object",
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
required: ["text"],
|
|
63
|
+
properties: { text: { type: "string", minLength: 1, maxLength: 2000, description: "The note." } },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
egress: { hosts: [], tenantHosts: false },
|
|
68
|
+
classification: "internal",
|
|
69
|
+
};
|
|
70
|
+
const server = `// The extension's backend. \`sightline-ext build\` bundles it into dist/server.mjs,
|
|
71
|
+
// which Sightline runs in its sandbox, one instance per workspace.
|
|
72
|
+
import { defineExtension, defineTool, ExtensionError, json, ok } from "@sparelabs/sightline-extension-sdk";
|
|
73
|
+
import manifest from "../sightline.extension.json" with { type: "json" };
|
|
74
|
+
|
|
75
|
+
// A type alias, not an interface: ctx.db rows must be indexable records.
|
|
76
|
+
type Note = {
|
|
77
|
+
id: string;
|
|
78
|
+
author: string | null;
|
|
79
|
+
text: string;
|
|
80
|
+
created_at: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default defineExtension({
|
|
84
|
+
manifest,
|
|
85
|
+
tools: {
|
|
86
|
+
${p}_list: defineTool<{ limit?: number }>({
|
|
87
|
+
validate: ({ limit }) =>
|
|
88
|
+
limit === undefined || (typeof limit === "number" && Number.isInteger(limit) && limit >= 1 && limit <= 50) ? [] : [{ path: "limit", message: "an integer from 1 to 50" }],
|
|
89
|
+
handler: async ({ limit = 10 }, ctx) => {
|
|
90
|
+
const notes = await ctx.db<Note>\`SELECT id, author, text, created_at FROM notes ORDER BY created_at DESC LIMIT \${limit}\`;
|
|
91
|
+
return json({ notes });
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
${p}_add: defineTool<{ text: string }>({
|
|
95
|
+
validate: ({ text }) => (typeof text === "string" && text.trim() !== "" && text.length <= 2000 ? [] : [{ path: "text", message: "1 to 2000 characters" }]),
|
|
96
|
+
handler: async ({ text }, ctx) => {
|
|
97
|
+
if (ctx.actor.type !== "user") throw new ExtensionError("permission_denied", "notes are written by a person");
|
|
98
|
+
const [note] = await ctx.db<{ id: string }>\`INSERT INTO notes (author, text) VALUES (\${ctx.actor.userId}, \${text.trim()}) RETURNING id\`;
|
|
99
|
+
await ctx.core.audit({ action: "${id}.note_added", target: { type: "note", id: note.id } });
|
|
100
|
+
return ok("Note added.");
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
// Your UI (if you add one) calls these through core: /functions/v1/ext-api/v1/x/${id}/api/*.
|
|
105
|
+
api: async (request, ctx) => {
|
|
106
|
+
if (request.method === "GET" && ctx.path === "/me") return Response.json(await ctx.core.me());
|
|
107
|
+
return new Response("not found", { status: 404 });
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
`;
|
|
111
|
+
const migration = `-- Your extension's own database, plain SQL. Add 0002_<name>.sql for the next
|
|
112
|
+
-- change; never edit a migration that has been deployed (core refuses it).
|
|
113
|
+
CREATE TABLE notes (
|
|
114
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
115
|
+
author uuid,
|
|
116
|
+
text text NOT NULL CHECK (length(text) BETWEEN 1 AND 2000),
|
|
117
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE INDEX notes_created_at_idx ON notes (created_at DESC);
|
|
121
|
+
`;
|
|
122
|
+
const pkg = {
|
|
123
|
+
name: id,
|
|
124
|
+
version: "0.1.0",
|
|
125
|
+
private: true,
|
|
126
|
+
type: "module",
|
|
127
|
+
scripts: {
|
|
128
|
+
build: "sightline-ext build",
|
|
129
|
+
dev: "sightline-ext dev",
|
|
130
|
+
test: "sightline-ext test",
|
|
131
|
+
deploy: "sightline-ext deploy",
|
|
132
|
+
typecheck: "tsc -p tsconfig.json",
|
|
133
|
+
},
|
|
134
|
+
dependencies: { "@sparelabs/sightline-extension-sdk": range },
|
|
135
|
+
devDependencies: { "@sparelabs/sightline-extension-cli": range, typescript: "^5.8.3" },
|
|
136
|
+
engines: { node: ">=20.19.0" },
|
|
137
|
+
};
|
|
138
|
+
const tsconfig = {
|
|
139
|
+
compilerOptions: {
|
|
140
|
+
target: "ES2022",
|
|
141
|
+
lib: ["ES2022", "DOM"],
|
|
142
|
+
module: "ESNext",
|
|
143
|
+
moduleResolution: "bundler",
|
|
144
|
+
resolveJsonModule: true,
|
|
145
|
+
strict: true,
|
|
146
|
+
noEmit: true,
|
|
147
|
+
skipLibCheck: true,
|
|
148
|
+
},
|
|
149
|
+
include: ["server"],
|
|
150
|
+
};
|
|
151
|
+
const config = {
|
|
152
|
+
contract: {
|
|
153
|
+
tool: { name: `${p}_list`, input: { limit: 5 }, as: "user:alice" },
|
|
154
|
+
},
|
|
155
|
+
settings: {},
|
|
156
|
+
};
|
|
157
|
+
const ci = `name: CI
|
|
158
|
+
|
|
159
|
+
on:
|
|
160
|
+
pull_request:
|
|
161
|
+
push:
|
|
162
|
+
branches: [main]
|
|
163
|
+
|
|
164
|
+
permissions:
|
|
165
|
+
contents: read
|
|
166
|
+
|
|
167
|
+
jobs:
|
|
168
|
+
test:
|
|
169
|
+
runs-on: ubuntu-latest
|
|
170
|
+
timeout-minutes: 15
|
|
171
|
+
steps:
|
|
172
|
+
- uses: actions/checkout@v4
|
|
173
|
+
- uses: actions/setup-node@v4
|
|
174
|
+
with:
|
|
175
|
+
node-version: 22
|
|
176
|
+
- uses: denoland/setup-deno@v2
|
|
177
|
+
with:
|
|
178
|
+
deno-version: v2.x
|
|
179
|
+
- run: npm ci
|
|
180
|
+
- run: npm run typecheck
|
|
181
|
+
# Bundles, validates the manifest against the published contract, runs the
|
|
182
|
+
# extension under Sightline's runtime flags with its own Postgres (Docker),
|
|
183
|
+
# and checks it against the HTTP contract.
|
|
184
|
+
- run: npm test
|
|
185
|
+
`;
|
|
186
|
+
const readme = `# ${name}
|
|
187
|
+
|
|
188
|
+
A hosted [Sightline](https://www.npmjs.com/package/@sparelabs/sightline-extension-cli) extension.
|
|
189
|
+
|
|
190
|
+
\`\`\`bash
|
|
191
|
+
npm install
|
|
192
|
+
npm run build # bundle dist/server.mjs and validate the manifest
|
|
193
|
+
npm run dev # run it locally against an emulated Sightline (needs Deno and Docker)
|
|
194
|
+
npm test # contract tests against the emulator
|
|
195
|
+
\`\`\`
|
|
196
|
+
|
|
197
|
+
Deploy to a workspace with a deploy token from a workspace admin:
|
|
198
|
+
|
|
199
|
+
\`\`\`bash
|
|
200
|
+
SIGHTLINE_URL=https://<your workspace> SIGHTLINE_DEPLOY_TOKEN=… npx sightline-ext deploy
|
|
201
|
+
\`\`\`
|
|
202
|
+
|
|
203
|
+
| Path | What |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| \`sightline.extension.json\` | The manifest: id, version, permissions, tools |
|
|
206
|
+
| \`server/main.ts\` | The backend (\`@sparelabs/sightline-extension-sdk\`) |
|
|
207
|
+
| \`migrations/NNNN_<name>.sql\` | Your database, in order; only ever add files |
|
|
208
|
+
| \`sightline-ext.json\` | Local only: what \`test\` calls, dev settings and secrets |
|
|
209
|
+
`;
|
|
210
|
+
return {
|
|
211
|
+
"package.json": json(pkg),
|
|
212
|
+
"sightline.extension.json": json(manifest),
|
|
213
|
+
"sightline-ext.json": json(config),
|
|
214
|
+
"server/main.ts": server,
|
|
215
|
+
"migrations/0001_notes.sql": migration,
|
|
216
|
+
"tsconfig.json": json(tsconfig),
|
|
217
|
+
".gitignore": "node_modules/\ndist/\n.sightline-devkit/\n",
|
|
218
|
+
".github/workflows/ci.yml": ci,
|
|
219
|
+
"README.md": readme,
|
|
220
|
+
};
|
|
221
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sparelabs/sightline-extension-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "sightline-ext: scaffold, run, build, test and deploy a hosted Sightline extension.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Spare Labs Inc.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/sparelabs/sightline.git",
|
|
10
|
+
"directory": "packages/extension-cli"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"bin": {
|
|
24
|
+
"sightline-ext": "bin/sightline-ext.mjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"bin",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node scripts/build.mjs",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@sparelabs/sightline-extension-devkit": "0.1.0",
|
|
38
|
+
"@sparelabs/sightline-extension-manifest": "0.1.0",
|
|
39
|
+
"@sparelabs/sightline-extension-testing": "0.1.0",
|
|
40
|
+
"esbuild": "^0.25.10"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.16.5",
|
|
44
|
+
"typescript": "^5.8.3"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.19.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public",
|
|
51
|
+
"registry": "https://registry.npmjs.org/"
|
|
52
|
+
}
|
|
53
|
+
}
|