create-settlegrid-tool 1.0.0 → 1.0.1

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import prompts from "prompts";
5
5
  import pc3 from "picocolors";
6
- import path2 from "path";
6
+ import path4 from "path";
7
7
 
8
8
  // src/scaffold.ts
9
9
  import fs from "fs-extra";
@@ -128,7 +128,7 @@ startCommand = "npm start"
128
128
 
129
129
  // src/banner.ts
130
130
  import pc2 from "picocolors";
131
- function banner() {
131
+ function banner(version) {
132
132
  const emerald = pc2.green;
133
133
  const dim = pc2.dim;
134
134
  const art = `
@@ -138,14 +138,209 @@ ${emerald(" \\__ \\/ -_) _| _| / -_)| (_ | '_| / _` |")}
138
138
  ${emerald(" |___/\\___|\\__|\\__|_\\___| \\___|_| |_\\__,_|")}
139
139
  `;
140
140
  const tagline = dim(" The Settlement Layer for the AI Economy");
141
- const version = dim(" v1.0.0");
141
+ const versionLine = dim(` v${version}`);
142
142
  const separator = dim(" " + "-".repeat(44));
143
143
  return `${art}${tagline}
144
- ${version}
144
+ ${versionLine}
145
145
  ${separator}
146
146
  `;
147
147
  }
148
148
 
149
+ // src/args.ts
150
+ var TEMPLATE_EQ_PREFIX = "--template=";
151
+ function parseArgs(argv) {
152
+ const result = { help: false, version: false };
153
+ for (let i = 0; i < argv.length; i++) {
154
+ const arg = argv[i];
155
+ if (arg === "--help" || arg === "-h") {
156
+ result.help = true;
157
+ } else if (arg === "--version") {
158
+ result.version = true;
159
+ } else if (arg === "--template") {
160
+ const next = argv[i + 1];
161
+ if (next !== void 0 && !next.startsWith("-")) {
162
+ result.template = next;
163
+ i++;
164
+ } else {
165
+ result.template = "";
166
+ }
167
+ } else if (arg.startsWith(TEMPLATE_EQ_PREFIX)) {
168
+ result.template = arg.slice(TEMPLATE_EQ_PREFIX.length);
169
+ } else if (arg.startsWith("-")) {
170
+ } else if (result.directory === void 0) {
171
+ result.directory = arg;
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+
177
+ // src/version.ts
178
+ import { readFileSync } from "fs";
179
+ import * as path2 from "path";
180
+ import { fileURLToPath as fileURLToPath2 } from "url";
181
+ function readPackageVersion() {
182
+ try {
183
+ const here = path2.dirname(fileURLToPath2(import.meta.url));
184
+ const pkgPath = path2.resolve(here, "..", "package.json");
185
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
186
+ return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
187
+ } catch {
188
+ return "unknown";
189
+ }
190
+ }
191
+
192
+ // src/gallery.ts
193
+ import fs2 from "fs-extra";
194
+ var GALLERY_OWNER = "settlegrid";
195
+ var GALLERY_REPO_PREFIX = "settlegrid-";
196
+ var MAX_SLUG_LEN = 64;
197
+ var GALLERY_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
198
+ var ScaffoldError = class extends Error {
199
+ code;
200
+ constructor(code, message) {
201
+ super(message);
202
+ this.name = "ScaffoldError";
203
+ this.code = code;
204
+ }
205
+ };
206
+ function isValidGallerySlug(slug) {
207
+ return slug.length > 0 && slug.length <= MAX_SLUG_LEN && GALLERY_SLUG_RE.test(slug);
208
+ }
209
+ function gallerySource(slug) {
210
+ return `github:${GALLERY_OWNER}/${GALLERY_REPO_PREFIX}${slug}`;
211
+ }
212
+ function defaultGalleryDir(slug) {
213
+ return `${GALLERY_REPO_PREFIX}${slug}`;
214
+ }
215
+ function classifyGigetError(err) {
216
+ const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
217
+ if (msg.includes("404") || msg.includes("not found")) {
218
+ return "template_not_found";
219
+ }
220
+ if (msg.includes("eacces") || msg.includes("eperm") || msg.includes("erofs") || msg.includes("enospc") || msg.includes("eexist") || msg.includes("permission denied")) {
221
+ return "write_failed";
222
+ }
223
+ return "download_failed";
224
+ }
225
+ async function downloadGalleryTemplate(slug, targetDir) {
226
+ if (!isValidGallerySlug(slug)) {
227
+ throw new ScaffoldError(
228
+ "template_not_found",
229
+ slug.trim().length === 0 ? "Missing template slug. Usage: npx create-settlegrid-tool --template <slug>" : `Unknown or invalid template "${slug}". Slugs are lowercase letters, digits, and hyphens.`
230
+ );
231
+ }
232
+ if (await fs2.pathExists(targetDir)) {
233
+ const entries = await fs2.readdir(targetDir);
234
+ if (entries.length > 0) {
235
+ throw new ScaffoldError(
236
+ "write_failed",
237
+ `Directory "${targetDir}" already exists and is not empty.`
238
+ );
239
+ }
240
+ }
241
+ try {
242
+ const { downloadTemplate } = await import("giget");
243
+ await downloadTemplate(gallerySource(slug), {
244
+ dir: targetDir,
245
+ force: true
246
+ });
247
+ } catch (err) {
248
+ throw new ScaffoldError(
249
+ classifyGigetError(err),
250
+ err instanceof Error ? err.message : String(err)
251
+ );
252
+ }
253
+ }
254
+
255
+ // src/telemetry.ts
256
+ import { chmodSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
257
+ import { homedir } from "os";
258
+ import * as path3 from "path";
259
+ import { randomUUID } from "crypto";
260
+ var DEFAULT_PROXY_BASE = "https://settlegrid.ai";
261
+ var TELEMETRY_TIMEOUT_MS = 2e3;
262
+ function isOptedOut() {
263
+ if (isCi()) return true;
264
+ const raw = process.env.SETTLEGRID_TELEMETRY?.trim().toLowerCase();
265
+ if (!raw) return false;
266
+ return raw === "0" || raw === "false" || raw === "no" || raw === "off";
267
+ }
268
+ function isCi() {
269
+ return process.env.CI !== void 0 || process.env.CONTINUOUS_INTEGRATION !== void 0;
270
+ }
271
+ function getProxyBase() {
272
+ const raw = process.env.SETTLEGRID_API_URL?.trim();
273
+ const base = raw && raw.length > 0 ? raw : DEFAULT_PROXY_BASE;
274
+ return base.replace(/\/$/, "");
275
+ }
276
+ function telemetryIdPath() {
277
+ const home = homedir() || "/tmp";
278
+ return path3.join(home, ".settlegrid", "telemetry-id");
279
+ }
280
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
281
+ function getDistinctId() {
282
+ const envId = process.env.SETTLEGRID_POSTHOG_ID?.trim();
283
+ if (envId && UUID_RE.test(envId)) return envId;
284
+ const file = telemetryIdPath();
285
+ try {
286
+ const existing = readFileSync2(file, "utf8").trim();
287
+ if (UUID_RE.test(existing)) return existing;
288
+ } catch {
289
+ }
290
+ const id = randomUUID();
291
+ try {
292
+ mkdirSync(path3.dirname(file), { recursive: true, mode: 448 });
293
+ writeFileSync(file, id, { mode: 384 });
294
+ chmodSync(file, 384);
295
+ } catch {
296
+ }
297
+ return id;
298
+ }
299
+ var fetchOverride;
300
+ async function post(body) {
301
+ const fetchImpl = fetchOverride ?? globalThis.fetch;
302
+ if (typeof fetchImpl !== "function") {
303
+ return false;
304
+ }
305
+ const url = `${getProxyBase()}/api/telemetry/capture`;
306
+ const controller = new AbortController();
307
+ const timer = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
308
+ try {
309
+ const res = await fetchImpl(url, {
310
+ method: "POST",
311
+ headers: { "content-type": "application/json" },
312
+ body: JSON.stringify(body),
313
+ redirect: "error",
314
+ signal: controller.signal
315
+ });
316
+ return res.ok;
317
+ } catch {
318
+ return false;
319
+ } finally {
320
+ clearTimeout(timer);
321
+ }
322
+ }
323
+ async function capture(event, properties) {
324
+ if (isOptedOut()) return false;
325
+ return post({
326
+ event,
327
+ properties,
328
+ distinct_id: getDistinctId()
329
+ });
330
+ }
331
+ async function captureScaffoldSuccess(args) {
332
+ return capture("scaffold_success", {
333
+ template_slug: args.template_slug,
334
+ duration_ms: args.duration_ms
335
+ });
336
+ }
337
+ async function captureScaffoldFailed(args) {
338
+ return capture("scaffold_failed", {
339
+ template_slug: args.template_slug,
340
+ error_code: args.error_code
341
+ });
342
+ }
343
+
149
344
  // src/index.ts
150
345
  var CATEGORIES = [
151
346
  { title: "Data", value: "data" },
@@ -177,31 +372,85 @@ var DEPLOY_TARGETS = [
177
372
  function toSlug(name) {
178
373
  return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
179
374
  }
180
- async function main() {
181
- console.log(banner());
182
- const dirArg = process.argv[2];
183
- if (dirArg === "--help" || dirArg === "-h") {
184
- console.log(`
185
- ${pc3.bold("Usage:")} npx create-settlegrid-tool ${pc3.cyan("[directory]")}
375
+ function helpText() {
376
+ return `
377
+ ${pc3.bold("Usage:")}
378
+ ${pc3.cyan("npx create-settlegrid-tool")} ${pc3.dim("[directory]")}
379
+ ${pc3.cyan("npx create-settlegrid-tool --template")} ${pc3.dim("<slug> [directory]")}
380
+
381
+ ${pc3.dim("Create a monetized AI tool project with SettleGrid billing.")}
186
382
 
187
- ${pc3.dim("Creates a monetized AI tool project with SettleGrid billing.")}
383
+ ${pc3.bold("Modes:")}
384
+ ${pc3.dim("Interactive")} Run with no ${pc3.cyan("--template")} flag to pick a starter
385
+ archetype (blank, rest-api, openapi, mcp-server).
386
+ ${pc3.dim("Gallery")} Pass ${pc3.cyan("--template <slug>")} to scaffold one of the
387
+ published templates from ${pc3.underline("https://settlegrid.ai/templates")}.
188
388
 
189
389
  ${pc3.bold("Options:")}
190
- ${pc3.cyan("[directory]")} Project directory name (default: prompted)
191
- ${pc3.cyan("--help, -h")} Show this help message
192
- ${pc3.cyan("--version")} Show version
193
- `);
194
- process.exit(0);
195
- }
196
- if (dirArg === "--version") {
197
- console.log("1.0.0");
198
- process.exit(0);
390
+ ${pc3.cyan("--template <slug>")} Gallery template slug, e.g. ${pc3.dim("tmdb")} (non-interactive)
391
+ ${pc3.cyan("[directory]")} Target directory (default: ${pc3.dim("settlegrid-<slug>")} in
392
+ gallery mode, or prompted in interactive mode)
393
+ ${pc3.cyan("--help, -h")} Show this help message
394
+ ${pc3.cyan("--version")} Show version
395
+ `;
396
+ }
397
+ function mapInteractiveError(err) {
398
+ const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
399
+ if (msg.includes("already exists")) return "write_failed";
400
+ if (msg.includes("not found")) return "template_not_found";
401
+ return "unknown";
402
+ }
403
+ async function runGalleryMode(slug, directoryArg) {
404
+ const scaffoldStart = Date.now();
405
+ const trimmedSlug = slug.trim();
406
+ const telemetrySlug = isValidGallerySlug(trimmedSlug) ? trimmedSlug : "(invalid)";
407
+ const directory = directoryArg ?? defaultGalleryDir(trimmedSlug);
408
+ const targetDir = path4.resolve(process.cwd(), directory);
409
+ console.log(
410
+ pc3.dim(` Scaffolding "${trimmedSlug}" from the SettleGrid gallery...`)
411
+ );
412
+ console.log();
413
+ try {
414
+ await downloadGalleryTemplate(trimmedSlug, targetDir);
415
+ } catch (err) {
416
+ const code = err instanceof ScaffoldError ? err.code : "unknown";
417
+ const message = err instanceof Error ? err.message : String(err);
418
+ console.error(pc3.red(" Scaffold failed: ") + message);
419
+ if (code === "template_not_found") {
420
+ console.error(
421
+ pc3.dim(" Browse available templates at ") + pc3.cyan("https://settlegrid.ai/templates")
422
+ );
423
+ }
424
+ console.error();
425
+ process.exitCode = 1;
426
+ void captureScaffoldFailed({
427
+ template_slug: telemetrySlug,
428
+ error_code: code
429
+ });
430
+ return;
199
431
  }
432
+ console.log(pc3.green(pc3.bold(" Done!")) + " Template ready.\n");
433
+ console.log(` ${pc3.dim("$")} ${pc3.cyan(`cd ${directory}`)}`);
434
+ console.log(` ${pc3.dim("$")} ${pc3.cyan("npm install")}`);
435
+ console.log(
436
+ ` ${pc3.dim("$")} ${pc3.cyan("cp .env.example .env")} ${pc3.dim("# add your API keys")}`
437
+ );
438
+ console.log(` ${pc3.dim("$")} ${pc3.cyan("npm run dev")}
439
+ `);
440
+ console.log(
441
+ pc3.dim(" Register your tool to start earning: ") + pc3.cyan(pc3.underline("https://settlegrid.ai/dashboard/tools")) + "\n"
442
+ );
443
+ void captureScaffoldSuccess({
444
+ template_slug: telemetrySlug,
445
+ duration_ms: Date.now() - scaffoldStart
446
+ });
447
+ }
448
+ async function runInteractiveMode(directoryArg) {
200
449
  let cancelled = false;
201
450
  const response = await prompts(
202
451
  [
203
452
  {
204
- type: dirArg ? null : "text",
453
+ type: directoryArg ? null : "text",
205
454
  name: "directory",
206
455
  message: "Project directory",
207
456
  initial: "my-tool",
@@ -211,7 +460,7 @@ async function main() {
211
460
  type: "text",
212
461
  name: "toolName",
213
462
  message: "Tool name",
214
- initial: (prev) => prev || dirArg || "my-tool"
463
+ initial: (prev) => prev || directoryArg || "my-tool"
215
464
  },
216
465
  {
217
466
  type: "text",
@@ -261,9 +510,9 @@ async function main() {
261
510
  console.log(pc3.red("\nSetup cancelled."));
262
511
  process.exit(1);
263
512
  }
264
- const directory = dirArg || response.directory;
513
+ const directory = directoryArg || response.directory;
265
514
  const toolSlug = toSlug(directory);
266
- const targetDir = path2.resolve(process.cwd(), directory);
515
+ const targetDir = path4.resolve(process.cwd(), directory);
267
516
  const config = {
268
517
  directory,
269
518
  toolName: response.toolName || directory,
@@ -279,7 +528,19 @@ async function main() {
279
528
  console.log();
280
529
  console.log(pc3.dim(" Scaffolding project..."));
281
530
  console.log();
282
- await scaffold(config);
531
+ const scaffoldStart = Date.now();
532
+ try {
533
+ await scaffold(config);
534
+ } catch (err) {
535
+ const message = err instanceof Error ? err.message : String(err);
536
+ console.error(pc3.red(" Scaffold failed: ") + message + "\n");
537
+ process.exitCode = 1;
538
+ void captureScaffoldFailed({
539
+ template_slug: config.template,
540
+ error_code: mapInteractiveError(err)
541
+ });
542
+ return;
543
+ }
283
544
  console.log(pc3.green(pc3.bold(" Done!")) + " Your tool is ready.\n");
284
545
  console.log(` ${pc3.dim("$")} ${pc3.cyan(`cd ${directory}`)}`);
285
546
  console.log(` ${pc3.dim("$")} ${pc3.cyan("npm install")}`);
@@ -288,6 +549,28 @@ async function main() {
288
549
  console.log(
289
550
  pc3.dim(" Next: Register your tool at ") + pc3.cyan(pc3.underline("https://settlegrid.ai/dashboard/tools")) + "\n"
290
551
  );
552
+ void captureScaffoldSuccess({
553
+ template_slug: config.template,
554
+ duration_ms: Date.now() - scaffoldStart
555
+ });
556
+ }
557
+ async function main() {
558
+ const version = readPackageVersion();
559
+ const args = parseArgs(process.argv.slice(2));
560
+ console.log(banner(version));
561
+ if (args.help) {
562
+ console.log(helpText());
563
+ process.exit(0);
564
+ }
565
+ if (args.version) {
566
+ console.log(version);
567
+ process.exit(0);
568
+ }
569
+ if (args.template !== void 0) {
570
+ await runGalleryMode(args.template, args.directory);
571
+ return;
572
+ }
573
+ await runInteractiveMode(args.directory);
291
574
  }
292
575
  main().catch((err) => {
293
576
  console.error(pc3.red("Error:"), err instanceof Error ? err.message : err);
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/telemetry.ts
4
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { homedir } from "os";
6
+ import * as path from "path";
7
+ import { randomUUID } from "crypto";
8
+ var DEFAULT_PROXY_BASE = "https://settlegrid.ai";
9
+ var TELEMETRY_TIMEOUT_MS = 2e3;
10
+ function isOptedOut() {
11
+ if (isCi()) return true;
12
+ const raw = process.env.SETTLEGRID_TELEMETRY?.trim().toLowerCase();
13
+ if (!raw) return false;
14
+ return raw === "0" || raw === "false" || raw === "no" || raw === "off";
15
+ }
16
+ function isCi() {
17
+ return process.env.CI !== void 0 || process.env.CONTINUOUS_INTEGRATION !== void 0;
18
+ }
19
+ function getProxyBase() {
20
+ const raw = process.env.SETTLEGRID_API_URL?.trim();
21
+ const base = raw && raw.length > 0 ? raw : DEFAULT_PROXY_BASE;
22
+ return base.replace(/\/$/, "");
23
+ }
24
+ function telemetryIdPath() {
25
+ const home = homedir() || "/tmp";
26
+ return path.join(home, ".settlegrid", "telemetry-id");
27
+ }
28
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
29
+ function getDistinctId() {
30
+ const envId = process.env.SETTLEGRID_POSTHOG_ID?.trim();
31
+ if (envId && UUID_RE.test(envId)) return envId;
32
+ const file = telemetryIdPath();
33
+ try {
34
+ const existing = readFileSync(file, "utf8").trim();
35
+ if (UUID_RE.test(existing)) return existing;
36
+ } catch {
37
+ }
38
+ const id = randomUUID();
39
+ try {
40
+ mkdirSync(path.dirname(file), { recursive: true, mode: 448 });
41
+ writeFileSync(file, id, { mode: 384 });
42
+ chmodSync(file, 384);
43
+ } catch {
44
+ }
45
+ return id;
46
+ }
47
+ var fetchOverride;
48
+ async function post(body) {
49
+ const fetchImpl = fetchOverride ?? globalThis.fetch;
50
+ if (typeof fetchImpl !== "function") {
51
+ return false;
52
+ }
53
+ const url = `${getProxyBase()}/api/telemetry/capture`;
54
+ const controller = new AbortController();
55
+ const timer = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
56
+ try {
57
+ const res = await fetchImpl(url, {
58
+ method: "POST",
59
+ headers: { "content-type": "application/json" },
60
+ body: JSON.stringify(body),
61
+ redirect: "error",
62
+ signal: controller.signal
63
+ });
64
+ return res.ok;
65
+ } catch {
66
+ return false;
67
+ } finally {
68
+ clearTimeout(timer);
69
+ }
70
+ }
71
+ async function capture(event, properties) {
72
+ if (isOptedOut()) return false;
73
+ return post({
74
+ event,
75
+ properties,
76
+ distinct_id: getDistinctId()
77
+ });
78
+ }
79
+ async function captureCliInstallStarted(args) {
80
+ return capture("cli_install_started", {
81
+ cli_version: args.cli_version,
82
+ node_version: process.versions.node,
83
+ os: `${process.platform}-${process.arch}`
84
+ });
85
+ }
86
+
87
+ // src/version.ts
88
+ import { readFileSync as readFileSync2 } from "fs";
89
+ import * as path2 from "path";
90
+ import { fileURLToPath } from "url";
91
+ function readPackageVersion() {
92
+ try {
93
+ const here = path2.dirname(fileURLToPath(import.meta.url));
94
+ const pkgPath = path2.resolve(here, "..", "package.json");
95
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
96
+ return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
97
+ } catch {
98
+ return "unknown";
99
+ }
100
+ }
101
+
102
+ // src/postinstall.ts
103
+ var SAFETY_EXIT_MS = 2500;
104
+ (async () => {
105
+ const safety = setTimeout(() => process.exit(0), SAFETY_EXIT_MS);
106
+ safety.unref();
107
+ try {
108
+ await captureCliInstallStarted({ cli_version: readPackageVersion() });
109
+ } catch {
110
+ } finally {
111
+ clearTimeout(safety);
112
+ process.exit(0);
113
+ }
114
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-settlegrid-tool",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Create a monetized AI tool with SettleGrid in 30 seconds",
5
5
  "keywords": [
6
6
  "settlegrid",
@@ -47,10 +47,12 @@
47
47
  "test": "vitest run",
48
48
  "test:watch": "vitest",
49
49
  "lint": "tsc --noEmit",
50
+ "postinstall": "node ./dist/postinstall.js || true",
50
51
  "prepublishOnly": "npm run build"
51
52
  },
52
53
  "dependencies": {
53
54
  "fs-extra": "^11.2.0",
55
+ "giget": "^1.2.3",
54
56
  "picocolors": "^1.1.0",
55
57
  "prompts": "^2.4.2"
56
58
  },