@gram-ai/create-function 0.0.0-empty → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Speakeasy Development, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js ADDED
@@ -0,0 +1,156 @@
1
+ import { existsSync } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import process from "node:process";
5
+ import { $ } from "zx";
6
+ import { isCancel, log, taskLog } from "@clack/prompts";
7
+ import { parse } from "@bomb.sh/args";
8
+ import pkg from "../package.json" with { type: "json" };
9
+ import { confirmOrClack, selectOrClack, textOrClack, yn, } from "./prompts/helpers.js";
10
+ const packageNameRE = /^(@?[a-z0-9-_]+\/)?[a-z0-9-_]+$/;
11
+ const knownPackageManagers = new Set(["npm", "yarn", "pnpm", "bun", "deno"]);
12
+ async function init(argv) {
13
+ let packageManager = "npm";
14
+ let detectedPM = process.env["npm_config_user_agent"]?.split("/")[0] || "";
15
+ if (knownPackageManagers.has(detectedPM)) {
16
+ packageManager = detectedPM;
17
+ }
18
+ const args = parse(argv, {
19
+ alias: { y: "yes" },
20
+ string: ["template", "name", "dir", "git", "install"],
21
+ boolean: ["yes"],
22
+ });
23
+ const template = await selectOrClack({
24
+ message: "Pick a framework",
25
+ options: [
26
+ {
27
+ value: "gram",
28
+ label: "Gram",
29
+ hint: "Simple framework focused on getting you up and runnning with minimal code.",
30
+ },
31
+ {
32
+ value: "mcp",
33
+ label: "MCP",
34
+ hint: "Use the official @modelcontextprotocol/sdk package to build an MCP server and deploy it to Gram.",
35
+ },
36
+ ],
37
+ })(args.template);
38
+ if (isCancel(template)) {
39
+ log.info("Operation cancelled.");
40
+ return;
41
+ }
42
+ const nameArg = args.name?.trim();
43
+ const name = await textOrClack({
44
+ message: "What do you want to call your project?",
45
+ defaultValue: "gram-mcp-server",
46
+ validate: (value) => {
47
+ if (packageNameRE.test(value || "")) {
48
+ return undefined;
49
+ }
50
+ return [
51
+ "Package names can be scoped or unscoped and must only contain alphanumeric characters, dashes and underscores.",
52
+ "Examples:",
53
+ "my-mcp-server",
54
+ "@fancy-org/mcp-server",
55
+ ].join(" ");
56
+ },
57
+ })(nameArg);
58
+ if (isCancel(name)) {
59
+ log.info("Operation cancelled.");
60
+ return;
61
+ }
62
+ const rootDir = name.split("/").pop()?.trim() || "gram-func";
63
+ const dirArg = args.dir?.trim();
64
+ let dir = await textOrClack({
65
+ message: "Where do you want to create it?",
66
+ initialValue: rootDir,
67
+ defaultValue: rootDir,
68
+ validate: (value) => {
69
+ const trimmed = value?.trim() || "";
70
+ if (trimmed.length === 0) {
71
+ return "Directory name cannot be empty.";
72
+ }
73
+ if (existsSync(trimmed)) {
74
+ return `Directory ${trimmed} already exists. Please choose a different name.`;
75
+ }
76
+ return undefined;
77
+ },
78
+ })(dirArg);
79
+ if (isCancel(dir)) {
80
+ log.info("Operation cancelled.");
81
+ return;
82
+ }
83
+ dir = dir.trim();
84
+ const initGit = await confirmOrClack({
85
+ message: "Initialize a git repository?",
86
+ })(args.yes || yn(args.git ?? false));
87
+ if (isCancel(initGit)) {
88
+ log.info("Operation cancelled.");
89
+ return;
90
+ }
91
+ const installDeps = await confirmOrClack({
92
+ message: `Install dependencies with ${packageManager}?`,
93
+ })(args.yes || yn(args.install ?? false));
94
+ if (isCancel(installDeps)) {
95
+ log.info("Operation cancelled.");
96
+ return;
97
+ }
98
+ const tlog = taskLog({
99
+ title: "Setting up project",
100
+ });
101
+ tlog.message("Scaffolding");
102
+ const dirname = import.meta.dirname;
103
+ const templateDir = resolve(join(dirname, "..", `gram-template-${template}`));
104
+ await fs.cp(templateDir, dir, {
105
+ recursive: true,
106
+ filter: (src) => !src.includes("node_modules") &&
107
+ !src.includes(".git") &&
108
+ !src.includes("dist"),
109
+ });
110
+ let gramFuncsVersion = pkg.devDependencies["@gram-ai/functions"];
111
+ if (gramFuncsVersion == null || gramFuncsVersion.startsWith("workspace:")) {
112
+ // This templating package and `@gram-ai/functions` are versioned in
113
+ // lockstep so we can just use the matching version.
114
+ gramFuncsVersion = `^${pkg.version}`;
115
+ }
116
+ if (yn(process.env["GRAM_DEV"]) &&
117
+ existsSync(resolve(dirname, "..", "..", "functions"))) {
118
+ // For local development, use the local version of `@gram-ai/functions`
119
+ // if it exists.
120
+ const localPkgPath = resolve(dirname, "..", "..", "functions");
121
+ gramFuncsVersion = `file:${localPkgPath}`;
122
+ tlog.message(`Using local @gram-ai/functions from ${localPkgPath}`);
123
+ }
124
+ tlog.message("Updating package.json");
125
+ const pkgPath = await fs.readFile(join(dir, "package.json"), "utf-8");
126
+ const dstPkg = JSON.parse(pkgPath);
127
+ dstPkg.name = name;
128
+ const deps = dstPkg.dependencies;
129
+ if (deps?.["@gram-ai/functions"] != null) {
130
+ deps["@gram-ai/functions"] = gramFuncsVersion;
131
+ }
132
+ await fs.writeFile(join(dir, "package.json"), JSON.stringify(dstPkg, null, 2));
133
+ const contributingPath = join(dir, "CONTRIBUTING.md");
134
+ if (existsSync(contributingPath)) {
135
+ tlog.message("Creating symlinks for CONTRIBUTING.md");
136
+ await fs.symlink("CONTRIBUTING.md", join(dir, "AGENTS.md"));
137
+ await fs.symlink("CONTRIBUTING.md", join(dir, "CLAUDE.md"));
138
+ }
139
+ if (initGit) {
140
+ tlog.message("Initializing git repository");
141
+ await $ `git init ${dir}`;
142
+ }
143
+ if (installDeps) {
144
+ tlog.message(`Installing dependencies with ${packageManager}`);
145
+ await $ `cd ${dir} && ${packageManager} install`;
146
+ }
147
+ tlog.success(`All done! Run \`cd ${dir} && ${packageManager} run build\` to build your first Gram Function.`);
148
+ }
149
+ try {
150
+ await init(process.argv);
151
+ }
152
+ catch (err) {
153
+ log.error(`Unexpected error: ${err}`);
154
+ process.exit(1);
155
+ }
156
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type ConfirmOptions, type SelectOptions, type TextOptions } from "@clack/prompts";
2
+ export declare function yn(value: boolean | string | undefined): boolean;
3
+ export declare function textOrClack(options: TextOptions): (value: string | undefined) => Promise<string | symbol>;
4
+ export declare function selectOrClack<T>(options: SelectOptions<T>): (value: T | undefined) => Promise<T | symbol>;
5
+ export declare function confirmOrClack(options: ConfirmOptions): (value: boolean | undefined) => Promise<boolean | symbol>;
6
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/prompts/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AAGxB,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAI/D;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,GACnB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,CAEzD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC7B,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,CAE/C;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,cAAc,GACtB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,KAAK,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CAE3D"}
@@ -0,0 +1,19 @@
1
+ import { confirm, select, text, } from "@clack/prompts";
2
+ const y = new Set(["y", "yes", "true", "t", "1"]);
3
+ export function yn(value) {
4
+ if (value == null)
5
+ return false;
6
+ if (typeof value === "boolean")
7
+ return value;
8
+ return y.has(value.toLowerCase());
9
+ }
10
+ export function textOrClack(options) {
11
+ return async (value) => value || text(options);
12
+ }
13
+ export function selectOrClack(options) {
14
+ return async (value) => value || select(options);
15
+ }
16
+ export function confirmOrClack(options) {
17
+ return async (value) => value || confirm(options);
18
+ }
19
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/prompts/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,MAAM,EACN,IAAI,GAIL,MAAM,gBAAgB,CAAC;AAExB,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAClD,MAAM,UAAU,EAAE,CAAC,KAAmC;IACpD,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAAoB;IAEpB,OAAO,KAAK,EAAE,KAAyB,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,OAAyB;IAEzB,OAAO,KAAK,EAAE,KAAoB,EAAE,EAAE,CAAC,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAAuB;IAEvB,OAAO,KAAK,EAAE,KAA0B,EAAE,EAAE,CAAC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,9 @@
1
+ node_modules
2
+ dist
3
+ .env
4
+ .DS_Store
5
+ npm-debug.log*
6
+ yarn-debug.log*
7
+ yarn-error.log*
8
+ pnpm-debug.log*
9
+ *.tsbuildinfo
@@ -0,0 +1,399 @@
1
+ # Project Contribution Guide
2
+
3
+ - This is a Node.js project using TypeScript to build [Gram](https://gram.ai) Functions.
4
+ - The codebase assumes Node.js v22 or later is in use and TypeScript v5.9 or later is required.
5
+ - When working in this codebase prefer using the Web APIs and Node.js standard library where possible instead of third-party libraries.
6
+
7
+ ## Project Structure
8
+
9
+ - `src/` - Contains the source TypeScript code for the Gram Functions.
10
+ - `dist/` - Output directory for the built code (generated after running `npm run build`).
11
+ - `package.json` - Defines project metadata, dependencies, and scripts.
12
+ - `tsconfig.json` - TypeScript configuration file.
13
+
14
+ ## `package.json` scripts
15
+
16
+ - `build` - Bundles the Gram Functions code into a zip file for deployment and places it in the `dist/` directory.
17
+ - `lint` - Runs the TypeScript compiler in `noEmit` mode to check for type errors.
18
+
19
+ <details open>
20
+ <summary><strong>Guide: Using `@gram-ai/functions`</strong></summary>
21
+
22
+ ## Core Concepts
23
+
24
+ ### The Gram Instance
25
+
26
+ The `Gram` class is the main entry point for defining tools. You create an instance and chain `.tool()` calls to register multiple tools:
27
+
28
+ ```typescript
29
+ import { Gram } from "@gram-ai/functions";
30
+ import * as z from "zod/mini";
31
+
32
+ const g = new Gram()
33
+ .tool({
34
+ name: "add",
35
+ description: "Add two numbers",
36
+ inputSchema: { a: z.number(), b: z.number() },
37
+ async execute(ctx, input) {
38
+ return ctx.json({ sum: input.a + input.b });
39
+ },
40
+ })
41
+ .tool({
42
+ name: "multiply",
43
+ description: "Multiply two numbers",
44
+ inputSchema: { a: z.number(), b: z.number() },
45
+ async execute(ctx, input) {
46
+ return ctx.json({ product: input.a * input.b });
47
+ },
48
+ });
49
+
50
+ export const handleToolCall = g.handleToolCall;
51
+ ```
52
+
53
+ ### Tool Definition
54
+
55
+ Each tool requires:
56
+
57
+ - **name**: A unique identifier for the tool
58
+ - **description** (optional): Human-readable description of what the tool does
59
+ - **inputSchema**: A Zod schema object defining the expected input parameters
60
+ - **variables** (optional): Environment variables the tool needs
61
+ - **execute**: An async function that implements the tool logic
62
+
63
+ ### Tool Context
64
+
65
+ The `execute` function receives a `ctx` (context) object with helper methods:
66
+
67
+ #### `ctx.json(data)`
68
+
69
+ Returns a JSON response:
70
+
71
+ ```typescript
72
+ async execute(ctx, input) {
73
+ return ctx.json({ result: "success", value: 42 });
74
+ }
75
+ ```
76
+
77
+ #### `ctx.text(data)`
78
+
79
+ Returns a plain text response:
80
+
81
+ ```typescript
82
+ async execute(ctx, input) {
83
+ return ctx.text("Operation completed successfully");
84
+ }
85
+ ```
86
+
87
+ #### `ctx.html(data)`
88
+
89
+ Returns an HTML response:
90
+
91
+ ```typescript
92
+ async execute(ctx, input) {
93
+ return ctx.html("<h1>Hello, World!</h1>");
94
+ }
95
+ ```
96
+
97
+ #### `ctx.fail(data, options?)`
98
+
99
+ Throws an error response (never returns):
100
+
101
+ ```typescript
102
+ async execute(ctx, input) {
103
+ if (!input.value) {
104
+ ctx.fail({ error: "value is required" }, { status: 400 });
105
+ }
106
+ // ...
107
+ }
108
+ ```
109
+
110
+ #### `ctx.signal`
111
+
112
+ An `AbortSignal` for handling cancellation:
113
+
114
+ ```typescript
115
+ async execute(ctx, input) {
116
+ const response = await fetch(input.url, { signal: ctx.signal });
117
+ return ctx.json(await response.json());
118
+ }
119
+ ```
120
+
121
+ #### `ctx.vars`
122
+
123
+ Access to environment variables defined in the tool's `variables` property:
124
+
125
+ ```typescript
126
+ .tool({
127
+ name: "api_call",
128
+ inputSchema: { endpoint: z.string() },
129
+ variables: {
130
+ API_KEY: { description: "API key for authentication" }
131
+ },
132
+ async execute(ctx, input) {
133
+ const apiKey = ctx.vars.API_KEY;
134
+ // Use apiKey...
135
+ },
136
+ })
137
+ ```
138
+
139
+ ## Input Validation
140
+
141
+ Input schemas are defined using [Zod](https://zod.dev/):
142
+
143
+ ```typescript
144
+ import * as z from "zod/mini";
145
+
146
+ .tool({
147
+ name: "create_user",
148
+ inputSchema: {
149
+ email: z.string().check(z.email()),
150
+ age: z.number().check(z.min(18)),
151
+ name: z.optional(z.string()),
152
+ },
153
+ async execute(ctx, input) {
154
+ // input is fully typed based on the schema
155
+ return ctx.json({ userId: "123" });
156
+ },
157
+ })
158
+ ```
159
+
160
+ ### Lax Mode
161
+
162
+ By default, the framework strictly validates input. You can enable lax mode to allow unvalidated input to pass through:
163
+
164
+ ```typescript
165
+ const g = new Gram({ lax: true });
166
+ ```
167
+
168
+ ## Environment Variables
169
+
170
+ ### Runtime Environment
171
+
172
+ Pass environment variables are read from `process.env` by default, but you can override them when creating the `Gram` instance:
173
+
174
+ ```typescript
175
+ const g = new Gram({
176
+ env: {
177
+ API_KEY: "secret-key",
178
+ BASE_URL: "https://api.example.com",
179
+ },
180
+ });
181
+ ```
182
+
183
+ If not provided, the framework falls back to `process.env`.
184
+
185
+ ### Tool Variables
186
+
187
+ Declare which environment variables a tool needs:
188
+
189
+ ```typescript
190
+ .tool({
191
+ name: "weather",
192
+ inputSchema: { city: z.string() },
193
+ variables: {
194
+ WEATHER_API_KEY: {
195
+ description: "API key for weather service"
196
+ }
197
+ },
198
+ async execute(ctx, input) {
199
+ const apiKey = ctx.vars.WEATHER_API_KEY;
200
+ // Make API call...
201
+ },
202
+ })
203
+ ```
204
+
205
+ ## Response Types
206
+
207
+ The framework supports multiple response types. All response methods return Web API `Response` objects.
208
+
209
+ ### JSON Response
210
+
211
+ ```typescript
212
+ return ctx.json({
213
+ status: "success",
214
+ data: { id: 123, name: "Example" },
215
+ });
216
+ ```
217
+
218
+ ### Text Response
219
+
220
+ ```typescript
221
+ return ctx.text("Plain text response");
222
+ ```
223
+
224
+ ### HTML Response
225
+
226
+ ```typescript
227
+ return ctx.html(`
228
+ <!DOCTYPE html>
229
+ <html>
230
+ <body><h1>Hello</h1></body>
231
+ </html>
232
+ `);
233
+ ```
234
+
235
+ ### Custom Response
236
+
237
+ You can also return a plain `Response` object:
238
+
239
+ ```typescript
240
+ return new Response(data, {
241
+ status: 200,
242
+ headers: {
243
+ "Content-Type": "application/xml",
244
+ "X-Custom-Header": "value",
245
+ },
246
+ });
247
+ ```
248
+
249
+ ## Error Handling
250
+
251
+ ### Using `ctx.fail()`
252
+
253
+ Use `ctx.fail()` to throw error responses:
254
+
255
+ ```typescript
256
+ async execute(ctx, input) {
257
+ if (!input.userId) {
258
+ ctx.fail(
259
+ { error: "userId is required" },
260
+ { status: 400 }
261
+ );
262
+ }
263
+
264
+ const user = await fetchUser(input.userId);
265
+ if (!user) {
266
+ ctx.fail(
267
+ { error: "User not found" },
268
+ { status: 404 }
269
+ );
270
+ }
271
+
272
+ return ctx.json({ user });
273
+ }
274
+ ```
275
+
276
+ Errors automatically include a stack trace in the response.
277
+
278
+ ### Using `assert()`
279
+
280
+ The `assert` function provides a convenient way to validate conditions and throw error responses:
281
+
282
+ ```typescript
283
+ import { assert } from "@gram-ai/functions";
284
+
285
+ async execute(ctx, input) {
286
+ assert(input.userId, { error: "userId is required" }, { status: 400 });
287
+
288
+ const user = await fetchUser(input.userId);
289
+ assert(user, { error: "User not found" }, { status: 404 });
290
+
291
+ return ctx.json({ user });
292
+ }
293
+ ```
294
+
295
+ The `assert` function throws a `Response` object when the condition is false. The framework catches all thrown values, and if any happen to be a `Response` instance, they will be returned to the client.
296
+
297
+ Key points about `assert`:
298
+
299
+ - First parameter is the condition to check
300
+ - Second parameter is the error data (must include an `error` field)
301
+ - Third parameter is optional and can specify the status code (defaults to 500)
302
+ - Automatically includes a stack trace in the response
303
+ - Uses TypeScript's assertion type to narrow types when the assertion passes
304
+
305
+ ## Manifest Generation
306
+
307
+ Generate a manifest of all registered tools:
308
+
309
+ ```typescript
310
+ const g = new Gram()
311
+ .tool({
312
+ /* ... */
313
+ })
314
+ .tool({
315
+ /* ... */
316
+ });
317
+
318
+ const manifest = g.manifest();
319
+ // {
320
+ // version: "0.0.0",
321
+ // tools: [
322
+ // {
323
+ // name: "tool1",
324
+ // description: "...",
325
+ // inputSchema: "...", // JSON Schema string
326
+ // variables: { ... }
327
+ // },
328
+ // ...
329
+ // ]
330
+ // }
331
+ ```
332
+
333
+ ## Handling Tool Calls
334
+
335
+ Export the `handleToolCall` method to process incoming requests:
336
+
337
+ ```typescript
338
+ const g = new Gram()
339
+ .tool({
340
+ /* ... */
341
+ })
342
+ .tool({
343
+ /* ... */
344
+ });
345
+
346
+ export const handleToolCall = g.handleToolCall;
347
+ ```
348
+
349
+ You can also call tools programmatically:
350
+
351
+ ```typescript
352
+ const response = await g.handleToolCall({
353
+ name: "add",
354
+ input: { a: 5, b: 3 },
355
+ });
356
+
357
+ const data = await response.json();
358
+ console.log(data); // { sum: 8 }
359
+ ```
360
+
361
+ With abort signal support:
362
+
363
+ ```typescript
364
+ const controller = new AbortController();
365
+
366
+ const responsePromise = g.handleToolCall(
367
+ { name: "longRunning", input: {} },
368
+ { signal: controller.signal },
369
+ );
370
+
371
+ // Cancel after 5 seconds
372
+ setTimeout(() => controller.abort(), 5000);
373
+ ```
374
+
375
+ ## Type Safety
376
+
377
+ The framework provides full TypeScript type inference:
378
+
379
+ ```typescript
380
+ const g = new Gram().tool({
381
+ name: "greet",
382
+ inputSchema: { name: z.string() },
383
+ async execute(ctx, input) {
384
+ // input.name is typed as string
385
+ return ctx.json({ message: `Hello, ${input.name}` });
386
+ },
387
+ });
388
+
389
+ // Type-safe tool calls
390
+ const response = await g.handleToolCall({
391
+ name: "greet", // Only "greet" is valid
392
+ input: { name: "World" }, // input is typed correctly
393
+ });
394
+
395
+ // Response type is inferred
396
+ const data = await response.json(); // { message: string }
397
+ ```
398
+
399
+ </details>
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,35 @@
1
+ {
2
+ "type": "module",
3
+ "name": "gram-template-gram",
4
+ "version": "0.0.0",
5
+ "author": "",
6
+ "description": "",
7
+ "private": true,
8
+ "keywords": [
9
+ "mcp",
10
+ "getgram.ai"
11
+ ],
12
+ "license": "ISC",
13
+ "main": "dist/functions.js",
14
+ "files": [
15
+ "src",
16
+ "dist"
17
+ ],
18
+ "engine": {
19
+ "node": ">=22.18.0"
20
+ },
21
+ "scripts": {
22
+ "lint": "tsc --noEmit",
23
+ "build": "node ./src/build.ts",
24
+ "prebuild": "tsc -p tsconfig.json",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "dependencies": {
28
+ "@gram-ai/functions": "workspace:",
29
+ "zod": "^4"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5",
33
+ "@types/node": "22.x"
34
+ }
35
+ }
@@ -0,0 +1,14 @@
1
+ import { buildFunctions } from "@gram-ai/functions/build";
2
+ import { join } from "path";
3
+
4
+ async function build() {
5
+ await buildFunctions({
6
+ outDir: "dist",
7
+ entrypoint: join(import.meta.dirname, "functions.ts"),
8
+ export: "default",
9
+ });
10
+ }
11
+
12
+ if (import.meta.main) {
13
+ await build();
14
+ }
@@ -0,0 +1,13 @@
1
+ import { Gram } from "@gram-ai/functions";
2
+ import * as z from "zod/mini";
3
+
4
+ const gram = new Gram().tool({
5
+ name: "greet",
6
+ description: "Greet someone special",
7
+ inputSchema: { name: z.string() },
8
+ async execute(ctx, input) {
9
+ return ctx.json({ message: `Hello, ${input.name}!` });
10
+ },
11
+ });
12
+
13
+ export default gram;
@@ -0,0 +1,36 @@
1
+ {
2
+ "include": ["src"],
3
+ "exclude": ["node_modules"],
4
+ "compilerOptions": {
5
+ "noEmit": true,
6
+ "rewriteRelativeImportExtensions": true,
7
+
8
+ "module": "nodenext",
9
+ "target": "esnext",
10
+ "lib": ["esnext"],
11
+ "types": ["node"],
12
+ "resolveJsonModule": true,
13
+
14
+ "sourceMap": true,
15
+ "declaration": true,
16
+ "declarationMap": true,
17
+
18
+ "noUncheckedIndexedAccess": true,
19
+
20
+ "noImplicitReturns": true,
21
+ "noImplicitOverride": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noPropertyAccessFromIndexSignature": true,
26
+
27
+ "strict": true,
28
+ "erasableSyntaxOnly": true,
29
+ "jsx": "react-jsx",
30
+ "verbatimModuleSyntax": true,
31
+ "isolatedModules": true,
32
+ "noUncheckedSideEffectImports": true,
33
+ "moduleDetection": "force",
34
+ "skipLibCheck": true
35
+ }
36
+ }
@@ -0,0 +1,9 @@
1
+ node_modules
2
+ dist
3
+ .env
4
+ .DS_Store
5
+ npm-debug.log*
6
+ yarn-debug.log*
7
+ yarn-error.log*
8
+ pnpm-debug.log*
9
+ *.tsbuildinfo
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,35 @@
1
+ {
2
+ "type": "module",
3
+ "name": "gram-template-mcp",
4
+ "version": "0.0.0",
5
+ "author": "",
6
+ "description": "",
7
+ "private": true,
8
+ "keywords": [
9
+ "mcp",
10
+ "getgram.ai"
11
+ ],
12
+ "license": "ISC",
13
+ "main": "dist/functions.js",
14
+ "files": [
15
+ "src",
16
+ "dist"
17
+ ],
18
+ "engine": {
19
+ "node": ">=22.18.0"
20
+ },
21
+ "scripts": {
22
+ "build": "node ./src/build.ts",
23
+ "prebuild": "tsc -p tsconfig.json",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "dependencies": {
27
+ "@gram-ai/functions": "workspace:",
28
+ "@modelcontextprotocol/sdk": "^1.19.1",
29
+ "zod": "^3"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5",
33
+ "@types/node": "22.x"
34
+ }
35
+ }
@@ -0,0 +1,14 @@
1
+ import { buildMCPServer } from "@gram-ai/functions/build";
2
+
3
+ async function build() {
4
+ await buildMCPServer({
5
+ outDir: "dist",
6
+ functionEntrypoint: "./src/functions.ts",
7
+ serverExport: "server",
8
+ serverEntrypoint: "./src/mcp.ts",
9
+ });
10
+ }
11
+
12
+ if (import.meta.main) {
13
+ await build();
14
+ }
@@ -0,0 +1,28 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
3
+ import { server } from "./mcp.js";
4
+
5
+ const [serverTransport, clientTransport] = InMemoryTransport.createLinkedPair();
6
+
7
+ await server.connect(serverTransport);
8
+
9
+ const client = new Client({ name: "gram-functions", version: "0.0.0" });
10
+ await client.connect(clientTransport);
11
+
12
+ export async function handleToolCall(call: {
13
+ name: string;
14
+ input?: Record<string, unknown>;
15
+ _meta?: Record<string, unknown>;
16
+ }): Promise<Response> {
17
+ const response = await client.callTool({
18
+ name: call.name,
19
+ arguments: call.input,
20
+ _meta: call._meta,
21
+ });
22
+
23
+ const body = JSON.stringify(response);
24
+ return new Response(body, {
25
+ status: 200,
26
+ headers: { "Content-Type": "application/json; mcp=tools_call" },
27
+ });
28
+ }
@@ -0,0 +1,22 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+
4
+ export const server = new McpServer({
5
+ name: "demo-server",
6
+ version: "1.0.0",
7
+ });
8
+
9
+ server.registerTool(
10
+ "add",
11
+ {
12
+ title: "Addition Tool",
13
+ description: "Add two numbers",
14
+ inputSchema: { a: z.number(), b: z.number() },
15
+ },
16
+ async ({ a, b }) => {
17
+ const output = { result: a + b };
18
+ return {
19
+ content: [{ type: "text", text: JSON.stringify(output) }],
20
+ };
21
+ },
22
+ );
@@ -0,0 +1,36 @@
1
+ {
2
+ "include": ["src"],
3
+ "exclude": ["node_modules"],
4
+ "compilerOptions": {
5
+ "noEmit": true,
6
+ "rewriteRelativeImportExtensions": true,
7
+
8
+ "module": "nodenext",
9
+ "target": "esnext",
10
+ "lib": ["esnext"],
11
+ "types": ["node"],
12
+ "resolveJsonModule": true,
13
+
14
+ "sourceMap": true,
15
+ "declaration": true,
16
+ "declarationMap": true,
17
+
18
+ "noUncheckedIndexedAccess": true,
19
+
20
+ "noImplicitReturns": true,
21
+ "noImplicitOverride": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noPropertyAccessFromIndexSignature": true,
26
+
27
+ "strict": true,
28
+ "erasableSyntaxOnly": true,
29
+ "jsx": "react-jsx",
30
+ "verbatimModuleSyntax": true,
31
+ "isolatedModules": true,
32
+ "noUncheckedSideEffectImports": true,
33
+ "moduleDetection": "force",
34
+ "skipLibCheck": true
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,18 +1,46 @@
1
1
  {
2
+ "type": "module",
2
3
  "name": "@gram-ai/create-function",
3
- "version": "0.0.0-empty",
4
- "description": "",
5
- "main": "index.js",
4
+ "version": "0.1.0",
5
+ "description": "Build AI tools and deploy them to getgram.ai",
6
+ "keywords": [],
7
+ "homepage": "https://github.com/speakeasy-api/gram",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/speakeasy-api/gram.git",
11
+ "directory": "ts-framework/create-function"
12
+ },
13
+ "author": "Georges Haidar <georges@speakeasyapi.dev>",
14
+ "license": "MIT",
15
+ "main": "dist/index.js",
16
+ "bin": {
17
+ "create-function": "dist/main.js"
18
+ },
19
+ "engines": {
20
+ "node": ">=22.18.0"
21
+ },
6
22
  "publishConfig": {
7
23
  "access": "public"
8
24
  },
9
25
  "files": [
10
- "index.js"
26
+ "src",
27
+ "dist",
28
+ "gram-template-*/**"
11
29
  ],
12
- "scripts": {
13
- "test": "echo \"Error: no test specified\" && exit 1"
30
+ "dependencies": {
31
+ "@bomb.sh/args": "^0.3.1",
32
+ "@clack/prompts": "^1.0.0-alpha.6",
33
+ "zx": "8.8.4-lite"
14
34
  },
15
- "keywords": [],
16
- "author": "",
17
- "license": "ISC"
35
+ "devDependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.19.1",
37
+ "@types/node": "22.x",
38
+ "prettier": "^3.6.2",
39
+ "typescript": "5.9.2",
40
+ "zod": "^3.25.76",
41
+ "@gram-ai/functions": "^0.1.0"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc --noEmit false"
45
+ }
18
46
  }
package/src/main.ts ADDED
@@ -0,0 +1,189 @@
1
+ import { existsSync } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import process from "node:process";
5
+ import { $ } from "zx";
6
+ import { isCancel, log, taskLog } from "@clack/prompts";
7
+ import { parse } from "@bomb.sh/args";
8
+ import pkg from "../package.json" with { type: "json" };
9
+
10
+ import {
11
+ confirmOrClack,
12
+ selectOrClack,
13
+ textOrClack,
14
+ yn,
15
+ } from "./prompts/helpers.ts";
16
+
17
+ const packageNameRE = /^(@?[a-z0-9-_]+\/)?[a-z0-9-_]+$/;
18
+
19
+ const knownPackageManagers = new Set(["npm", "yarn", "pnpm", "bun", "deno"]);
20
+
21
+ async function init(argv: string[]): Promise<void> {
22
+ let packageManager = "npm";
23
+ let detectedPM = process.env["npm_config_user_agent"]?.split("/")[0] || "";
24
+ if (knownPackageManagers.has(detectedPM)) {
25
+ packageManager = detectedPM;
26
+ }
27
+
28
+ const args = parse(argv, {
29
+ alias: { y: "yes" },
30
+ string: ["template", "name", "dir", "git", "install"],
31
+ boolean: ["yes"],
32
+ });
33
+
34
+ const template = await selectOrClack<string>({
35
+ message: "Pick a framework",
36
+ options: [
37
+ {
38
+ value: "gram",
39
+ label: "Gram",
40
+ hint: "Simple framework focused on getting you up and runnning with minimal code.",
41
+ },
42
+ {
43
+ value: "mcp",
44
+ label: "MCP",
45
+ hint: "Use the official @modelcontextprotocol/sdk package to build an MCP server and deploy it to Gram.",
46
+ },
47
+ ],
48
+ })(args.template);
49
+ if (isCancel(template)) {
50
+ log.info("Operation cancelled.");
51
+ return;
52
+ }
53
+
54
+ const nameArg = args.name?.trim();
55
+ const name = await textOrClack({
56
+ message: "What do you want to call your project?",
57
+ defaultValue: "gram-mcp-server",
58
+ validate: (value) => {
59
+ if (packageNameRE.test(value || "")) {
60
+ return undefined;
61
+ }
62
+ return [
63
+ "Package names can be scoped or unscoped and must only contain alphanumeric characters, dashes and underscores.",
64
+ "Examples:",
65
+ "my-mcp-server",
66
+ "@fancy-org/mcp-server",
67
+ ].join(" ");
68
+ },
69
+ })(nameArg);
70
+ if (isCancel(name)) {
71
+ log.info("Operation cancelled.");
72
+ return;
73
+ }
74
+
75
+ const rootDir = name.split("/").pop()?.trim() || "gram-func";
76
+ const dirArg = args.dir?.trim();
77
+ let dir = await textOrClack({
78
+ message: "Where do you want to create it?",
79
+ initialValue: rootDir,
80
+ defaultValue: rootDir,
81
+ validate: (value) => {
82
+ const trimmed = value?.trim() || "";
83
+ if (trimmed.length === 0) {
84
+ return "Directory name cannot be empty.";
85
+ }
86
+
87
+ if (existsSync(trimmed)) {
88
+ return `Directory ${trimmed} already exists. Please choose a different name.`;
89
+ }
90
+
91
+ return undefined;
92
+ },
93
+ })(dirArg);
94
+ if (isCancel(dir)) {
95
+ log.info("Operation cancelled.");
96
+ return;
97
+ }
98
+ dir = dir.trim();
99
+
100
+ const initGit = await confirmOrClack({
101
+ message: "Initialize a git repository?",
102
+ })(args.yes || yn(args.git ?? false));
103
+ if (isCancel(initGit)) {
104
+ log.info("Operation cancelled.");
105
+ return;
106
+ }
107
+
108
+ const installDeps = await confirmOrClack({
109
+ message: `Install dependencies with ${packageManager}?`,
110
+ })(args.yes || yn(args.install ?? false));
111
+ if (isCancel(installDeps)) {
112
+ log.info("Operation cancelled.");
113
+ return;
114
+ }
115
+
116
+ const tlog = taskLog({
117
+ title: "Setting up project",
118
+ });
119
+
120
+ tlog.message("Scaffolding");
121
+ const dirname = import.meta.dirname;
122
+ const templateDir = resolve(join(dirname, "..", `gram-template-${template}`));
123
+ await fs.cp(templateDir, dir, {
124
+ recursive: true,
125
+ filter: (src) =>
126
+ !src.includes("node_modules") &&
127
+ !src.includes(".git") &&
128
+ !src.includes("dist"),
129
+ });
130
+
131
+ let gramFuncsVersion = pkg.devDependencies["@gram-ai/functions"];
132
+ if (gramFuncsVersion == null || gramFuncsVersion.startsWith("workspace:")) {
133
+ // This templating package and `@gram-ai/functions` are versioned in
134
+ // lockstep so we can just use the matching version.
135
+ gramFuncsVersion = `^${pkg.version}`;
136
+ }
137
+ if (
138
+ yn(process.env["GRAM_DEV"]) &&
139
+ existsSync(resolve(dirname, "..", "..", "functions"))
140
+ ) {
141
+ // For local development, use the local version of `@gram-ai/functions`
142
+ // if it exists.
143
+ const localPkgPath = resolve(dirname, "..", "..", "functions");
144
+ gramFuncsVersion = `file:${localPkgPath}`;
145
+ tlog.message(`Using local @gram-ai/functions from ${localPkgPath}`);
146
+ }
147
+
148
+ tlog.message("Updating package.json");
149
+ const pkgPath = await fs.readFile(join(dir, "package.json"), "utf-8");
150
+ const dstPkg = JSON.parse(pkgPath);
151
+ dstPkg.name = name;
152
+ const deps = dstPkg.dependencies;
153
+ if (deps?.["@gram-ai/functions"] != null) {
154
+ deps["@gram-ai/functions"] = gramFuncsVersion;
155
+ }
156
+
157
+ await fs.writeFile(
158
+ join(dir, "package.json"),
159
+ JSON.stringify(dstPkg, null, 2),
160
+ );
161
+
162
+ const contributingPath = join(dir, "CONTRIBUTING.md");
163
+ if (existsSync(contributingPath)) {
164
+ tlog.message("Creating symlinks for CONTRIBUTING.md");
165
+ await fs.symlink("CONTRIBUTING.md", join(dir, "AGENTS.md"));
166
+ await fs.symlink("CONTRIBUTING.md", join(dir, "CLAUDE.md"));
167
+ }
168
+
169
+ if (initGit) {
170
+ tlog.message("Initializing git repository");
171
+ await $`git init ${dir}`;
172
+ }
173
+
174
+ if (installDeps) {
175
+ tlog.message(`Installing dependencies with ${packageManager}`);
176
+ await $`cd ${dir} && ${packageManager} install`;
177
+ }
178
+
179
+ tlog.success(
180
+ `All done! Run \`cd ${dir} && ${packageManager} run build\` to build your first Gram Function.`,
181
+ );
182
+ }
183
+
184
+ try {
185
+ await init(process.argv);
186
+ } catch (err) {
187
+ log.error(`Unexpected error: ${err}`);
188
+ process.exit(1);
189
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ confirm,
3
+ select,
4
+ text,
5
+ type ConfirmOptions,
6
+ type SelectOptions,
7
+ type TextOptions,
8
+ } from "@clack/prompts";
9
+
10
+ const y = new Set(["y", "yes", "true", "t", "1"]);
11
+ export function yn(value: boolean | string | undefined): boolean {
12
+ if (value == null) return false;
13
+ if (typeof value === "boolean") return value;
14
+ return y.has(value.toLowerCase());
15
+ }
16
+
17
+ export function textOrClack(
18
+ options: TextOptions,
19
+ ): (value: string | undefined) => Promise<string | symbol> {
20
+ return async (value: string | undefined) => value || text(options);
21
+ }
22
+
23
+ export function selectOrClack<T>(
24
+ options: SelectOptions<T>,
25
+ ): (value: T | undefined) => Promise<T | symbol> {
26
+ return async (value: T | undefined) => value || select(options);
27
+ }
28
+
29
+ export function confirmOrClack(
30
+ options: ConfirmOptions,
31
+ ): (value: boolean | undefined) => Promise<boolean | symbol> {
32
+ return async (value: boolean | undefined) => value || confirm(options);
33
+ }
package/index.js DELETED
@@ -1,3 +0,0 @@
1
- export function greet() {
2
- return "Hello, World!";
3
- }