@nuniapp/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/README.md +41 -0
- package/dist/index.mjs +393 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @nuniapp/cli
|
|
2
|
+
|
|
3
|
+
Sets up [Nuni](https://nuni.praveenjuge.com) in any project.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx @nuniapp/cli init # new project ID + install steps for your framework
|
|
7
|
+
npx @nuniapp/cli init --json # the same, machine-readable (for agents)
|
|
8
|
+
npx @nuniapp/cli prompt # the ready-made prompt for your coding agent
|
|
9
|
+
npx @nuniapp/cli id # just a new project ID
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`init` does not edit your files. It detects your framework (Next.js, React, Vue, Nuxt, SvelteKit, Astro, Angular, plain HTML and more) and package manager, and prints exactly what to add and where.
|
|
13
|
+
|
|
14
|
+
## Agent prompt
|
|
15
|
+
|
|
16
|
+
<!-- prompt:start -->
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
Add Nuni (pinned comments on the live site) to this project.
|
|
20
|
+
|
|
21
|
+
Get a project ID by running: npx @nuniapp/cli@latest init (it prints the ID and the exact snippet for this framework).
|
|
22
|
+
|
|
23
|
+
Steps:
|
|
24
|
+
1. Detect the framework and package manager.
|
|
25
|
+
2. If the app uses React (Next.js, Vite, Remix, React Router, TanStack Start, etc.):
|
|
26
|
+
- Install @nuniapp/react.
|
|
27
|
+
- Render <Nuni project="PROJECT_ID" /> once, globally, in the root layout or app entry (for Next.js App Router: app/layout.tsx, inside <body>). It is already a client component.
|
|
28
|
+
3. Otherwise, if there is a bundler (Vue, Svelte, Astro, Angular, etc.):
|
|
29
|
+
- Install @nuniapp/widget.
|
|
30
|
+
- Call init({ project: "PROJECT_ID" }) once on the client, in the app entry or root layout. import { init } from "@nuniapp/widget".
|
|
31
|
+
4. If there is no bundler (plain HTML, WordPress, Webflow): add this before </body> on every page:
|
|
32
|
+
<script src="https://cdn.jsdelivr.net/npm/@nuniapp/widget@0/dist/nuni.global.js" data-project="PROJECT_ID" defer></script>
|
|
33
|
+
5. Commit the project ID directly in the code. It is public, not a secret. Do not put it in an env var.
|
|
34
|
+
6. Do not add Nuni to server-only code, and do not load it more than once.
|
|
35
|
+
7. If the site sends a Content-Security-Policy, allow connect-src https://*.convex.cloud wss://*.convex.cloud https://*.convex.site (and script-src cdn.jsdelivr.net for the script tag).
|
|
36
|
+
8. Start the dev server, open the site, and confirm the Nuni button appears in the bottom-right corner.
|
|
37
|
+
|
|
38
|
+
Docs: https://nuni.praveenjuge.com/quickstart
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
<!-- prompt:end -->
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
5
|
+
//#region ../shared/src/id.ts
|
|
6
|
+
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
7
|
+
const ID_LENGTH = 22;
|
|
8
|
+
const PREFIX = "nuni_";
|
|
9
|
+
const PROJECT_ID_PATTERN = new RegExp(`^${PREFIX}[${ALPHABET}]{${ID_LENGTH}}$`);
|
|
10
|
+
/** Random base58 string using the platform CSPRNG (browser, Node, Convex). */
|
|
11
|
+
function randomBase58(length) {
|
|
12
|
+
const bytes = new Uint8Array(length);
|
|
13
|
+
crypto.getRandomValues(bytes);
|
|
14
|
+
let out = "";
|
|
15
|
+
for (const byte of bytes) {
|
|
16
|
+
let b = byte;
|
|
17
|
+
while (b >= 232) {
|
|
18
|
+
const retry = /* @__PURE__ */ new Uint8Array(1);
|
|
19
|
+
crypto.getRandomValues(retry);
|
|
20
|
+
b = retry[0];
|
|
21
|
+
}
|
|
22
|
+
out += ALPHABET[b % 58];
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/** Public project identifier, safe to commit into source code. */
|
|
27
|
+
function generateProjectId() {
|
|
28
|
+
return PREFIX + randomBase58(ID_LENGTH);
|
|
29
|
+
}
|
|
30
|
+
function isProjectId(value) {
|
|
31
|
+
return typeof value === "string" && PROJECT_ID_PATTERN.test(value);
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region ../shared/src/prompt.ts
|
|
35
|
+
const PACKAGES = {
|
|
36
|
+
widget: "@nuniapp/widget",
|
|
37
|
+
react: "@nuniapp/react",
|
|
38
|
+
cli: "@nuniapp/cli"
|
|
39
|
+
};
|
|
40
|
+
const CDN_URL = "https://cdn.jsdelivr.net/npm/@nuniapp/widget@0/dist/nuni.global.js";
|
|
41
|
+
const DOCS_URL = "https://nuni.praveenjuge.com";
|
|
42
|
+
/**
|
|
43
|
+
* The prompt a developer pastes into Codex, Claude Code, Cursor, etc.
|
|
44
|
+
* Single source of truth for the docs site, CLI and READMEs.
|
|
45
|
+
*/
|
|
46
|
+
function buildAgentPrompt(options = {}) {
|
|
47
|
+
return `Add Nuni (pinned comments on the live site) to this project.
|
|
48
|
+
|
|
49
|
+
${options.projectId ? `Use this Nuni project ID: ${options.projectId}` : `Get a project ID by running: npx ${PACKAGES.cli}@latest init (it prints the ID and the exact snippet for this framework).`}
|
|
50
|
+
|
|
51
|
+
Steps:
|
|
52
|
+
1. Detect the framework and package manager.
|
|
53
|
+
2. If the app uses React (Next.js, Vite, Remix, React Router, TanStack Start, etc.):
|
|
54
|
+
- Install ${PACKAGES.react}.
|
|
55
|
+
- Render <Nuni project="PROJECT_ID" /> once, globally, in the root layout or app entry (for Next.js App Router: app/layout.tsx, inside <body>). It is already a client component.
|
|
56
|
+
3. Otherwise, if there is a bundler (Vue, Svelte, Astro, Angular, etc.):
|
|
57
|
+
- Install ${PACKAGES.widget}.
|
|
58
|
+
- Call init({ project: "PROJECT_ID" }) once on the client, in the app entry or root layout. import { init } from "${PACKAGES.widget}".
|
|
59
|
+
4. If there is no bundler (plain HTML, WordPress, Webflow): add this before </body> on every page:
|
|
60
|
+
<script src="${CDN_URL}" data-project="PROJECT_ID" defer><\/script>
|
|
61
|
+
5. Commit the project ID directly in the code. It is public, not a secret. Do not put it in an env var.
|
|
62
|
+
6. Do not add Nuni to server-only code, and do not load it more than once.
|
|
63
|
+
7. If the site sends a Content-Security-Policy, allow connect-src https://*.convex.cloud wss://*.convex.cloud https://*.convex.site (and script-src cdn.jsdelivr.net for the script tag).
|
|
64
|
+
8. Start the dev server, open the site, and confirm the Nuni button appears in the bottom-right corner.
|
|
65
|
+
|
|
66
|
+
Docs: ${DOCS_URL}/quickstart`;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/detect.ts
|
|
70
|
+
function readPackage(cwd) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function first(cwd, candidates, fallback) {
|
|
78
|
+
return candidates.find((c) => existsSync(join(cwd, c))) ?? fallback;
|
|
79
|
+
}
|
|
80
|
+
function detectPackageManager(cwd) {
|
|
81
|
+
if (existsSync(join(cwd, "bun.lock")) || existsSync(join(cwd, "bun.lockb"))) return "bun";
|
|
82
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
83
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
84
|
+
return "npm";
|
|
85
|
+
}
|
|
86
|
+
function installCommand(pm, pkg) {
|
|
87
|
+
switch (pm) {
|
|
88
|
+
case "bun": return `bun add ${pkg}`;
|
|
89
|
+
case "pnpm": return `pnpm add ${pkg}`;
|
|
90
|
+
case "yarn": return `yarn add ${pkg}`;
|
|
91
|
+
default: return `npm install ${pkg}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function detectFramework(cwd) {
|
|
95
|
+
const pkg = readPackage(cwd);
|
|
96
|
+
if (!pkg) return {
|
|
97
|
+
id: "html",
|
|
98
|
+
name: "Static HTML",
|
|
99
|
+
kind: "script",
|
|
100
|
+
file: existsSync(join(cwd, "index.html")) ? "index.html" : readdirSync(cwd).find((f) => f.endsWith(".html")) ?? "index.html"
|
|
101
|
+
};
|
|
102
|
+
const deps = {
|
|
103
|
+
...pkg.dependencies,
|
|
104
|
+
...pkg.devDependencies
|
|
105
|
+
};
|
|
106
|
+
const has = (name) => name in deps;
|
|
107
|
+
if (has("next")) {
|
|
108
|
+
const app = first(cwd, [
|
|
109
|
+
"app/layout.tsx",
|
|
110
|
+
"src/app/layout.tsx",
|
|
111
|
+
"app/layout.jsx",
|
|
112
|
+
"src/app/layout.jsx",
|
|
113
|
+
"app/layout.js",
|
|
114
|
+
"src/app/layout.js"
|
|
115
|
+
], "");
|
|
116
|
+
if (app) return {
|
|
117
|
+
id: "next-app",
|
|
118
|
+
name: "Next.js (App Router)",
|
|
119
|
+
kind: "react",
|
|
120
|
+
file: app
|
|
121
|
+
};
|
|
122
|
+
const pages = first(cwd, [
|
|
123
|
+
"pages/_app.tsx",
|
|
124
|
+
"src/pages/_app.tsx",
|
|
125
|
+
"pages/_app.jsx",
|
|
126
|
+
"src/pages/_app.jsx",
|
|
127
|
+
"pages/_app.js"
|
|
128
|
+
], "");
|
|
129
|
+
if (pages) return {
|
|
130
|
+
id: "next-pages",
|
|
131
|
+
name: "Next.js (Pages Router)",
|
|
132
|
+
kind: "react",
|
|
133
|
+
file: pages
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
id: "next-app",
|
|
137
|
+
name: "Next.js",
|
|
138
|
+
kind: "react",
|
|
139
|
+
file: "app/layout.tsx"
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (has("@tanstack/react-start")) return {
|
|
143
|
+
id: "tanstack-start",
|
|
144
|
+
name: "TanStack Start",
|
|
145
|
+
kind: "react",
|
|
146
|
+
file: first(cwd, ["src/routes/__root.tsx", "app/routes/__root.tsx"], "src/routes/__root.tsx")
|
|
147
|
+
};
|
|
148
|
+
if (has("@remix-run/react") || has("react-router") || has("@react-router/dev")) return {
|
|
149
|
+
id: "react-router",
|
|
150
|
+
name: "React Router / Remix",
|
|
151
|
+
kind: "react",
|
|
152
|
+
file: first(cwd, ["app/root.tsx", "app/root.jsx"], "app/root.tsx")
|
|
153
|
+
};
|
|
154
|
+
if (has("gatsby")) return {
|
|
155
|
+
id: "gatsby",
|
|
156
|
+
name: "Gatsby",
|
|
157
|
+
kind: "react",
|
|
158
|
+
file: first(cwd, ["gatsby-browser.tsx", "gatsby-browser.js"], "gatsby-browser.tsx")
|
|
159
|
+
};
|
|
160
|
+
if (has("nuxt")) return {
|
|
161
|
+
id: "nuxt",
|
|
162
|
+
name: "Nuxt",
|
|
163
|
+
kind: "vanilla",
|
|
164
|
+
file: "plugins/nuni.client.ts"
|
|
165
|
+
};
|
|
166
|
+
if (has("@sveltejs/kit")) return {
|
|
167
|
+
id: "sveltekit",
|
|
168
|
+
name: "SvelteKit",
|
|
169
|
+
kind: "vanilla",
|
|
170
|
+
file: "src/routes/+layout.svelte"
|
|
171
|
+
};
|
|
172
|
+
if (has("astro")) return {
|
|
173
|
+
id: "astro",
|
|
174
|
+
name: "Astro",
|
|
175
|
+
kind: "vanilla",
|
|
176
|
+
file: first(cwd, ["src/layouts/Layout.astro", "src/layouts/BaseLayout.astro"], "src/layouts/Layout.astro")
|
|
177
|
+
};
|
|
178
|
+
if (has("@angular/core")) return {
|
|
179
|
+
id: "angular",
|
|
180
|
+
name: "Angular",
|
|
181
|
+
kind: "vanilla",
|
|
182
|
+
file: "src/main.ts"
|
|
183
|
+
};
|
|
184
|
+
if (has("vue")) return {
|
|
185
|
+
id: "vue",
|
|
186
|
+
name: "Vue",
|
|
187
|
+
kind: "vanilla",
|
|
188
|
+
file: first(cwd, ["src/main.ts", "src/main.js"], "src/main.ts")
|
|
189
|
+
};
|
|
190
|
+
if (has("svelte")) return {
|
|
191
|
+
id: "svelte",
|
|
192
|
+
name: "Svelte",
|
|
193
|
+
kind: "vanilla",
|
|
194
|
+
file: first(cwd, ["src/main.ts", "src/main.js"], "src/main.ts")
|
|
195
|
+
};
|
|
196
|
+
if (has("react")) return {
|
|
197
|
+
id: "react",
|
|
198
|
+
name: "React",
|
|
199
|
+
kind: "react",
|
|
200
|
+
file: first(cwd, [
|
|
201
|
+
"src/main.tsx",
|
|
202
|
+
"src/main.jsx",
|
|
203
|
+
"src/index.tsx",
|
|
204
|
+
"src/index.jsx",
|
|
205
|
+
"src/App.tsx"
|
|
206
|
+
], "src/main.tsx")
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
id: "vanilla",
|
|
210
|
+
name: "JavaScript app",
|
|
211
|
+
kind: "vanilla",
|
|
212
|
+
file: first(cwd, [
|
|
213
|
+
"src/main.ts",
|
|
214
|
+
"src/main.js",
|
|
215
|
+
"src/index.ts",
|
|
216
|
+
"src/index.js"
|
|
217
|
+
], "src/main.ts")
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/snippets.ts
|
|
222
|
+
function packageFor(framework) {
|
|
223
|
+
if (framework.kind === "react") return PACKAGES.react;
|
|
224
|
+
if (framework.kind === "vanilla") return PACKAGES.widget;
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
function snippetFor(framework, projectId) {
|
|
228
|
+
const react = `import { Nuni } from "${PACKAGES.react}"`;
|
|
229
|
+
switch (framework.id) {
|
|
230
|
+
case "next-app": return `${react}
|
|
231
|
+
|
|
232
|
+
// Inside <body>, once:
|
|
233
|
+
<Nuni project="${projectId}" />`;
|
|
234
|
+
case "next-pages": return `${react}
|
|
235
|
+
|
|
236
|
+
export default function App({ Component, pageProps }) {
|
|
237
|
+
return (
|
|
238
|
+
<>
|
|
239
|
+
<Component {...pageProps} />
|
|
240
|
+
<Nuni project="${projectId}" />
|
|
241
|
+
</>
|
|
242
|
+
)
|
|
243
|
+
}`;
|
|
244
|
+
case "gatsby": return `${react}
|
|
245
|
+
|
|
246
|
+
export const wrapRootElement = ({ element }) => (
|
|
247
|
+
<>
|
|
248
|
+
{element}
|
|
249
|
+
<Nuni project="${projectId}" />
|
|
250
|
+
</>
|
|
251
|
+
)`;
|
|
252
|
+
case "nuxt": return `import { init } from "${PACKAGES.widget}"
|
|
253
|
+
|
|
254
|
+
export default defineNuxtPlugin(() => {
|
|
255
|
+
init({ project: "${projectId}" })
|
|
256
|
+
})`;
|
|
257
|
+
case "sveltekit": return `<script>
|
|
258
|
+
import { onMount } from "svelte"
|
|
259
|
+
import { init } from "${PACKAGES.widget}"
|
|
260
|
+
|
|
261
|
+
onMount(() => init({ project: "${projectId}" }).destroy)
|
|
262
|
+
<\/script>
|
|
263
|
+
|
|
264
|
+
<slot />`;
|
|
265
|
+
case "astro": return `<!-- In your base layout, before </body> -->
|
|
266
|
+
<script>
|
|
267
|
+
import { init } from "${PACKAGES.widget}"
|
|
268
|
+
init({ project: "${projectId}" })
|
|
269
|
+
<\/script>`;
|
|
270
|
+
case "html": return `<!-- Before </body> on every page -->
|
|
271
|
+
<script src="${CDN_URL}" data-project="${projectId}" defer><\/script>`;
|
|
272
|
+
default:
|
|
273
|
+
if (framework.kind === "react") return `${react}
|
|
274
|
+
|
|
275
|
+
// Render once next to your app's root component:
|
|
276
|
+
<Nuni project="${projectId}" />`;
|
|
277
|
+
return `import { init } from "${PACKAGES.widget}"
|
|
278
|
+
|
|
279
|
+
// Once, on the client:
|
|
280
|
+
init({ project: "${projectId}" })`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/cli.ts
|
|
285
|
+
const VERSION = "0.1.0";
|
|
286
|
+
const HELP = `nuni ${VERSION}: pinned comments on your live site
|
|
287
|
+
|
|
288
|
+
Usage
|
|
289
|
+
npx @nuniapp/cli init [--id <project-id>] [--cwd <dir>] [--json]
|
|
290
|
+
npx @nuniapp/cli prompt [--id <project-id>]
|
|
291
|
+
npx @nuniapp/cli id
|
|
292
|
+
|
|
293
|
+
Commands
|
|
294
|
+
init Generate a project ID and print the install steps for this project
|
|
295
|
+
prompt Print the ready-made prompt for Codex, Claude Code, Cursor, etc.
|
|
296
|
+
id Print a new project ID
|
|
297
|
+
|
|
298
|
+
Docs: ${DOCS_URL}`;
|
|
299
|
+
const bold = (s) => process.stdout.isTTY ? `\x1b[1m${s}\x1b[22m` : s;
|
|
300
|
+
const dim = (s) => process.stdout.isTTY ? `\x1b[2m${s}\x1b[22m` : s;
|
|
301
|
+
function run(argv) {
|
|
302
|
+
const { values, positionals } = parseArgs({
|
|
303
|
+
args: argv,
|
|
304
|
+
allowPositionals: true,
|
|
305
|
+
options: {
|
|
306
|
+
id: { type: "string" },
|
|
307
|
+
cwd: { type: "string" },
|
|
308
|
+
json: {
|
|
309
|
+
type: "boolean",
|
|
310
|
+
default: false
|
|
311
|
+
},
|
|
312
|
+
help: {
|
|
313
|
+
type: "boolean",
|
|
314
|
+
short: "h",
|
|
315
|
+
default: false
|
|
316
|
+
},
|
|
317
|
+
version: {
|
|
318
|
+
type: "boolean",
|
|
319
|
+
short: "v",
|
|
320
|
+
default: false
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
const command = positionals[0] ?? "init";
|
|
325
|
+
if (values.version) {
|
|
326
|
+
console.log(VERSION);
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
if (values.help || command === "help") {
|
|
330
|
+
console.log(HELP);
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
if (values.id && !isProjectId(values.id)) {
|
|
334
|
+
console.error(`"${values.id}" is not a valid Nuni project ID (expected nuni_ followed by 22 characters).`);
|
|
335
|
+
return 1;
|
|
336
|
+
}
|
|
337
|
+
const projectId = values.id ?? generateProjectId();
|
|
338
|
+
if (command === "id") {
|
|
339
|
+
console.log(projectId);
|
|
340
|
+
return 0;
|
|
341
|
+
}
|
|
342
|
+
if (command === "prompt") {
|
|
343
|
+
console.log(buildAgentPrompt({ projectId }));
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
if (command !== "init") {
|
|
347
|
+
console.error(`Unknown command "${command}".\n\n${HELP}`);
|
|
348
|
+
return 1;
|
|
349
|
+
}
|
|
350
|
+
const cwd = resolve(values.cwd ?? process.cwd());
|
|
351
|
+
const framework = detectFramework(cwd);
|
|
352
|
+
const pm = detectPackageManager(cwd);
|
|
353
|
+
const pkg = packageFor(framework);
|
|
354
|
+
const install = pkg ? installCommand(pm, pkg) : null;
|
|
355
|
+
const snippet = snippetFor(framework, projectId);
|
|
356
|
+
if (values.json) {
|
|
357
|
+
console.log(JSON.stringify({
|
|
358
|
+
projectId,
|
|
359
|
+
framework: framework.name,
|
|
360
|
+
frameworkId: framework.id,
|
|
361
|
+
file: framework.file,
|
|
362
|
+
package: pkg,
|
|
363
|
+
install,
|
|
364
|
+
snippet
|
|
365
|
+
}, null, 2));
|
|
366
|
+
return 0;
|
|
367
|
+
}
|
|
368
|
+
console.log(`
|
|
369
|
+
${bold("Nuni")} ${dim(`v${VERSION}`)}
|
|
370
|
+
|
|
371
|
+
${bold("Project ID")} ${projectId}
|
|
372
|
+
${dim("Public, safe to commit. Keep using this same ID on every environment.")}
|
|
373
|
+
|
|
374
|
+
${bold("Detected")} ${framework.name}${pkg ? ` (${pm})` : ""}
|
|
375
|
+
${install ? `\n${bold("1. Install")}\n ${install}\n` : ""}
|
|
376
|
+
${bold(`${install ? "2" : "1"}. Add it once, globally`)} ${dim(`(${framework.file})`)}
|
|
377
|
+
|
|
378
|
+
${snippet.split("\n").map((line) => ` ${line}`).join("\n")}
|
|
379
|
+
|
|
380
|
+
${bold(`${install ? "3" : "2"}. Open the site`)}
|
|
381
|
+
Start the dev server. A Nuni button appears in the bottom-right corner.
|
|
382
|
+
Press C, click anything, leave a comment. No account needed.
|
|
383
|
+
|
|
384
|
+
${bold("Own this site?")} Open the Nuni panel and choose "Claim Nuni" to sign in with GitHub.
|
|
385
|
+
${dim(`Docs: ${DOCS_URL}`)}
|
|
386
|
+
`);
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/index.ts
|
|
391
|
+
process.exitCode = run(process.argv.slice(2));
|
|
392
|
+
//#endregion
|
|
393
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nuniapp/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Set up Nuni in any project: generates a project ID and prints the install snippet.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://nuni.praveenjuge.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/praveenjuge/nuni.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"nuni": "./dist/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"provenance": true
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsdown",
|
|
29
|
+
"dev": "tsdown --watch",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"typecheck": "tsc",
|
|
32
|
+
"test": "vitest run"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@nuni/eslint-config": "workspace:*",
|
|
36
|
+
"@nuni/shared": "workspace:*",
|
|
37
|
+
"@nuni/tsconfig": "workspace:*",
|
|
38
|
+
"@types/node": "^22",
|
|
39
|
+
"eslint": "^10.11.0",
|
|
40
|
+
"tsdown": "^0.23.0",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vitest": "^5.0.2"
|
|
43
|
+
}
|
|
44
|
+
}
|