@neetru/cli 2.12.12 → 2.12.15
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/CHANGELOG.md +575 -561
- package/dist/commands/ai.js +8 -8
- package/dist/commands/autocomplete.js +34 -34
- package/dist/commands/chat.d.ts +23 -0
- package/dist/commands/chat.js +157 -18
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/db.d.ts +31 -0
- package/dist/commands/db.js +148 -0
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.js +7 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/init.js +147 -147
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/ai/context.js +91 -91
- package/package.json +5 -5
- package/templates/auth/callback.ts +22 -22
- package/templates/auth/sign-in.tsx +41 -41
- package/templates/billing/checkout.ts +22 -22
- package/templates/billing/page.tsx +43 -43
- package/templates/support/ticket-form.tsx +68 -68
- package/templates/usage/track.ts +30 -30
- package/templates/users/profile.tsx +43 -43
- package/dist/commands/fn.d.ts +0 -6
- package/dist/commands/fn.js +0 -88
- package/dist/commands/fn.js.map +0 -1
- package/dist/commands/marketplace.d.ts +0 -36
- package/dist/commands/marketplace.js +0 -585
- package/dist/commands/marketplace.js.map +0 -1
package/dist/commands/init.js
CHANGED
|
@@ -316,106 +316,106 @@ async function scaffoldNextjs(dir, name, write) {
|
|
|
316
316
|
},
|
|
317
317
|
}, null, 2));
|
|
318
318
|
// next.config.mjs
|
|
319
|
-
await write(path.join(dir, 'next.config.mjs'), `/** @type {import('next').NextConfig} */
|
|
320
|
-
const nextConfig = {};
|
|
321
|
-
export default nextConfig;
|
|
319
|
+
await write(path.join(dir, 'next.config.mjs'), `/** @type {import('next').NextConfig} */
|
|
320
|
+
const nextConfig = {};
|
|
321
|
+
export default nextConfig;
|
|
322
322
|
`);
|
|
323
323
|
// Layout raiz
|
|
324
|
-
await write(path.join(dir, 'src', 'app', 'layout.tsx'), `import type { Metadata } from 'next';
|
|
325
|
-
|
|
326
|
-
export const metadata: Metadata = {
|
|
327
|
-
title: '${name}',
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
331
|
-
return (
|
|
332
|
-
<html lang="pt-BR">
|
|
333
|
-
<body>{children}</body>
|
|
334
|
-
</html>
|
|
335
|
-
);
|
|
336
|
-
}
|
|
324
|
+
await write(path.join(dir, 'src', 'app', 'layout.tsx'), `import type { Metadata } from 'next';
|
|
325
|
+
|
|
326
|
+
export const metadata: Metadata = {
|
|
327
|
+
title: '${name}',
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
331
|
+
return (
|
|
332
|
+
<html lang="pt-BR">
|
|
333
|
+
<body>{children}</body>
|
|
334
|
+
</html>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
337
|
`);
|
|
338
338
|
// Página inicial com auth gate via SDK 1.1
|
|
339
|
-
await write(path.join(dir, 'src', 'app', 'page.tsx'), `// Produto Neetru — gerado por neetru init
|
|
340
|
-
// Auth via @neetru/sdk client (createNeetruClient + auth namespace)
|
|
341
|
-
|
|
342
|
-
'use client';
|
|
343
|
-
|
|
344
|
-
import { createNeetruClient } from '@neetru/sdk';
|
|
345
|
-
import { useEffect, useState } from 'react';
|
|
346
|
-
import type { NeetruUser } from '@neetru/sdk';
|
|
347
|
-
|
|
348
|
-
// [C1] Falha rápido se NEXT_PUBLIC_NEETRU_ENV não estiver configurada —
|
|
349
|
-
// nunca defaulta silenciosamente pra 'dev' em produção.
|
|
350
|
-
const _neetruEnv = process.env.NEXT_PUBLIC_NEETRU_ENV;
|
|
351
|
-
if (!_neetruEnv) {
|
|
352
|
-
throw new Error(
|
|
353
|
-
'NEXT_PUBLIC_NEETRU_ENV não está definida. ' +
|
|
354
|
-
'Defina como "prod" no ambiente de produção e "dev" no ambiente de desenvolvimento. ' +
|
|
355
|
-
'Exemplo: NEXT_PUBLIC_NEETRU_ENV=prod',
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
const neetru = createNeetruClient({
|
|
359
|
-
apiKey: process.env.NEXT_PUBLIC_NEETRU_API_KEY,
|
|
360
|
-
env: _neetruEnv as 'dev' | 'prod',
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
export default function HomePage() {
|
|
364
|
-
const [user, setUser] = useState<NeetruUser | null>(null);
|
|
365
|
-
const [loading, setLoading] = useState(true);
|
|
366
|
-
|
|
367
|
-
useEffect(() => {
|
|
368
|
-
let unsub: (() => void) | undefined;
|
|
369
|
-
neetru.auth.getUser().then((u) => {
|
|
370
|
-
setUser(u);
|
|
371
|
-
setLoading(false);
|
|
372
|
-
});
|
|
373
|
-
unsub = neetru.auth.onAuthStateChanged((u) => setUser(u));
|
|
374
|
-
return () => unsub?.();
|
|
375
|
-
}, []);
|
|
376
|
-
|
|
377
|
-
if (loading) return <main><p>Carregando...</p></main>;
|
|
378
|
-
if (!user) {
|
|
379
|
-
return (
|
|
380
|
-
<main>
|
|
381
|
-
<h1>${name}</h1>
|
|
382
|
-
<button onClick={() => neetru.auth.signIn()}>Entrar com Neetru</button>
|
|
383
|
-
</main>
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
return (
|
|
387
|
-
<main>
|
|
388
|
-
<h1>Bem-vindo, {user.email}</h1>
|
|
389
|
-
<p>UID: {user.uid}</p>
|
|
390
|
-
<button onClick={() => neetru.auth.signOut()}>Sair</button>
|
|
391
|
-
</main>
|
|
392
|
-
);
|
|
393
|
-
}
|
|
339
|
+
await write(path.join(dir, 'src', 'app', 'page.tsx'), `// Produto Neetru — gerado por neetru init
|
|
340
|
+
// Auth via @neetru/sdk client (createNeetruClient + auth namespace)
|
|
341
|
+
|
|
342
|
+
'use client';
|
|
343
|
+
|
|
344
|
+
import { createNeetruClient } from '@neetru/sdk';
|
|
345
|
+
import { useEffect, useState } from 'react';
|
|
346
|
+
import type { NeetruUser } from '@neetru/sdk';
|
|
347
|
+
|
|
348
|
+
// [C1] Falha rápido se NEXT_PUBLIC_NEETRU_ENV não estiver configurada —
|
|
349
|
+
// nunca defaulta silenciosamente pra 'dev' em produção.
|
|
350
|
+
const _neetruEnv = process.env.NEXT_PUBLIC_NEETRU_ENV;
|
|
351
|
+
if (!_neetruEnv) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
'NEXT_PUBLIC_NEETRU_ENV não está definida. ' +
|
|
354
|
+
'Defina como "prod" no ambiente de produção e "dev" no ambiente de desenvolvimento. ' +
|
|
355
|
+
'Exemplo: NEXT_PUBLIC_NEETRU_ENV=prod',
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const neetru = createNeetruClient({
|
|
359
|
+
apiKey: process.env.NEXT_PUBLIC_NEETRU_API_KEY,
|
|
360
|
+
env: _neetruEnv as 'dev' | 'prod',
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
export default function HomePage() {
|
|
364
|
+
const [user, setUser] = useState<NeetruUser | null>(null);
|
|
365
|
+
const [loading, setLoading] = useState(true);
|
|
366
|
+
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
let unsub: (() => void) | undefined;
|
|
369
|
+
neetru.auth.getUser().then((u) => {
|
|
370
|
+
setUser(u);
|
|
371
|
+
setLoading(false);
|
|
372
|
+
});
|
|
373
|
+
unsub = neetru.auth.onAuthStateChanged((u) => setUser(u));
|
|
374
|
+
return () => unsub?.();
|
|
375
|
+
}, []);
|
|
376
|
+
|
|
377
|
+
if (loading) return <main><p>Carregando...</p></main>;
|
|
378
|
+
if (!user) {
|
|
379
|
+
return (
|
|
380
|
+
<main>
|
|
381
|
+
<h1>${name}</h1>
|
|
382
|
+
<button onClick={() => neetru.auth.signIn()}>Entrar com Neetru</button>
|
|
383
|
+
</main>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return (
|
|
387
|
+
<main>
|
|
388
|
+
<h1>Bem-vindo, {user.email}</h1>
|
|
389
|
+
<p>UID: {user.uid}</p>
|
|
390
|
+
<button onClick={() => neetru.auth.signOut()}>Sair</button>
|
|
391
|
+
</main>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
394
|
`);
|
|
395
395
|
// Middleware Next.js — gate de cookie de sessão em rotas privadas
|
|
396
|
-
await write(path.join(dir, 'src', 'middleware.ts'), `import { NextRequest, NextResponse } from 'next/server';
|
|
397
|
-
|
|
398
|
-
const PUBLIC_PATHS = ['/login', '/api/auth', '/_next', '/favicon.ico'];
|
|
399
|
-
|
|
400
|
-
export function middleware(request: NextRequest) {
|
|
401
|
-
const { pathname } = request.nextUrl;
|
|
402
|
-
if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
|
|
403
|
-
return NextResponse.next();
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const hasSession = request.cookies.has('__session');
|
|
407
|
-
if (!hasSession) {
|
|
408
|
-
const url = request.nextUrl.clone();
|
|
409
|
-
url.pathname = '/login';
|
|
410
|
-
return NextResponse.redirect(url);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return NextResponse.next();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export const config = {
|
|
417
|
-
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
418
|
-
};
|
|
396
|
+
await write(path.join(dir, 'src', 'middleware.ts'), `import { NextRequest, NextResponse } from 'next/server';
|
|
397
|
+
|
|
398
|
+
const PUBLIC_PATHS = ['/login', '/api/auth', '/_next', '/favicon.ico'];
|
|
399
|
+
|
|
400
|
+
export function middleware(request: NextRequest) {
|
|
401
|
+
const { pathname } = request.nextUrl;
|
|
402
|
+
if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
|
|
403
|
+
return NextResponse.next();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const hasSession = request.cookies.has('__session');
|
|
407
|
+
if (!hasSession) {
|
|
408
|
+
const url = request.nextUrl.clone();
|
|
409
|
+
url.pathname = '/login';
|
|
410
|
+
return NextResponse.redirect(url);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return NextResponse.next();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export const config = {
|
|
417
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
418
|
+
};
|
|
419
419
|
`);
|
|
420
420
|
// tsconfig.json
|
|
421
421
|
await write(path.join(dir, 'tsconfig.json'), JSON.stringify({
|
|
@@ -461,59 +461,59 @@ async function scaffoldNodeApi(dir, name, write) {
|
|
|
461
461
|
'@types/node': '^22.0.0',
|
|
462
462
|
},
|
|
463
463
|
}, null, 2));
|
|
464
|
-
await write(path.join(dir, 'src', 'index.ts'), `import Fastify from 'fastify';
|
|
465
|
-
import { createNeetruClient } from '@neetru/sdk';
|
|
466
|
-
|
|
467
|
-
// Padrão @neetru/sdk@1.1 — namespaces server-side: entitlements/usage/telemetry.
|
|
468
|
-
// [C1] Falha rápido se NEETRU_ENV não estiver configurada.
|
|
469
|
-
const _neetruEnv = process.env.NEETRU_ENV;
|
|
470
|
-
if (!_neetruEnv) {
|
|
471
|
-
throw new Error(
|
|
472
|
-
'NEETRU_ENV não está definida. ' +
|
|
473
|
-
'Defina como "prod" no ambiente de produção e "dev" no ambiente de desenvolvimento. ' +
|
|
474
|
-
'Exemplo: NEETRU_ENV=prod',
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
// [C2] NEETRU_API_KEY é obrigatória — falha rápido com mensagem clara.
|
|
478
|
-
const apiKey = process.env.NEETRU_API_KEY;
|
|
479
|
-
if (!apiKey) {
|
|
480
|
-
throw new Error(
|
|
481
|
-
'NEETRU_API_KEY é obrigatória. Configure a variável de ambiente antes de iniciar o servidor.',
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
const neetru = createNeetruClient({
|
|
485
|
-
apiKey,
|
|
486
|
-
env: _neetruEnv as 'dev' | 'prod',
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const PRODUCT_SLUG = process.env.NEETRU_PRODUCT_SLUG ?? '${name}';
|
|
490
|
-
|
|
491
|
-
const app = Fastify({ logger: true });
|
|
492
|
-
|
|
493
|
-
function requireEntitlement(feature: string) {
|
|
494
|
-
return async (
|
|
495
|
-
_request: import('fastify').FastifyRequest,
|
|
496
|
-
reply: import('fastify').FastifyReply,
|
|
497
|
-
) => {
|
|
498
|
-
const allowed = await neetru.entitlements.check(PRODUCT_SLUG, feature);
|
|
499
|
-
if (!allowed) {
|
|
500
|
-
return reply.code(403).send({ error: 'entitlement_denied', feature });
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
app.get('/health', async () => ({ status: 'ok' }));
|
|
506
|
-
|
|
507
|
-
app.get(
|
|
508
|
-
'/api/data',
|
|
509
|
-
{ preHandler: requireEntitlement('api-access') },
|
|
510
|
-
async () => {
|
|
511
|
-
await neetru.usage.report('api_call', 1, { productId: PRODUCT_SLUG });
|
|
512
|
-
return { data: [] };
|
|
513
|
-
},
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
await app.listen({ port: 3000, host: '0.0.0.0' });
|
|
464
|
+
await write(path.join(dir, 'src', 'index.ts'), `import Fastify from 'fastify';
|
|
465
|
+
import { createNeetruClient } from '@neetru/sdk';
|
|
466
|
+
|
|
467
|
+
// Padrão @neetru/sdk@1.1 — namespaces server-side: entitlements/usage/telemetry.
|
|
468
|
+
// [C1] Falha rápido se NEETRU_ENV não estiver configurada.
|
|
469
|
+
const _neetruEnv = process.env.NEETRU_ENV;
|
|
470
|
+
if (!_neetruEnv) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
'NEETRU_ENV não está definida. ' +
|
|
473
|
+
'Defina como "prod" no ambiente de produção e "dev" no ambiente de desenvolvimento. ' +
|
|
474
|
+
'Exemplo: NEETRU_ENV=prod',
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
// [C2] NEETRU_API_KEY é obrigatória — falha rápido com mensagem clara.
|
|
478
|
+
const apiKey = process.env.NEETRU_API_KEY;
|
|
479
|
+
if (!apiKey) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
'NEETRU_API_KEY é obrigatória. Configure a variável de ambiente antes de iniciar o servidor.',
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
const neetru = createNeetruClient({
|
|
485
|
+
apiKey,
|
|
486
|
+
env: _neetruEnv as 'dev' | 'prod',
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const PRODUCT_SLUG = process.env.NEETRU_PRODUCT_SLUG ?? '${name}';
|
|
490
|
+
|
|
491
|
+
const app = Fastify({ logger: true });
|
|
492
|
+
|
|
493
|
+
function requireEntitlement(feature: string) {
|
|
494
|
+
return async (
|
|
495
|
+
_request: import('fastify').FastifyRequest,
|
|
496
|
+
reply: import('fastify').FastifyReply,
|
|
497
|
+
) => {
|
|
498
|
+
const allowed = await neetru.entitlements.check(PRODUCT_SLUG, feature);
|
|
499
|
+
if (!allowed) {
|
|
500
|
+
return reply.code(403).send({ error: 'entitlement_denied', feature });
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
app.get('/health', async () => ({ status: 'ok' }));
|
|
506
|
+
|
|
507
|
+
app.get(
|
|
508
|
+
'/api/data',
|
|
509
|
+
{ preHandler: requireEntitlement('api-access') },
|
|
510
|
+
async () => {
|
|
511
|
+
await neetru.usage.report('api_call', 1, { productId: PRODUCT_SLUG });
|
|
512
|
+
return { data: [] };
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
await app.listen({ port: 3000, host: '0.0.0.0' });
|
|
517
517
|
`);
|
|
518
518
|
}
|
|
519
519
|
//# sourceMappingURL=init.js.map
|
package/dist/index.js
CHANGED
|
@@ -1607,6 +1607,32 @@ dbMigrationsCmd
|
|
|
1607
1607
|
const { runDbMigrationsCancel } = await import('./commands/db.js');
|
|
1608
1608
|
await runDbMigrationsCancel(migrationId, { json: !!opts.json });
|
|
1609
1609
|
});
|
|
1610
|
+
// ME2 (2026-06-15): detalha uma migração — status, classificação por-statement,
|
|
1611
|
+
// erro e redundantOf (onde/por quê parou). Antes só havia `list`.
|
|
1612
|
+
dbMigrationsCmd
|
|
1613
|
+
.command('show <migrationId>')
|
|
1614
|
+
.description('Detalhar uma migração — status, classificação por-statement, erro, redundantOf (GET /api/cli/v1/db/migrations/[id])')
|
|
1615
|
+
.option('--json', 'saída em JSON')
|
|
1616
|
+
.action(async (migrationId, opts) => {
|
|
1617
|
+
const { runDbMigrationsShow } = await import('./commands/db.js');
|
|
1618
|
+
await runDbMigrationsShow(migrationId, { json: !!opts.json });
|
|
1619
|
+
});
|
|
1620
|
+
// ME1 (2026-06-15): destrava migração partially_applied/failed REDUNDANTE.
|
|
1621
|
+
// Sem --force = prova por hash (admin|operator). Com --force = atestação (admin + MFA).
|
|
1622
|
+
dbMigrationsCmd
|
|
1623
|
+
.command('resolve <migrationId>')
|
|
1624
|
+
.description('Destravar migração partially_applied/failed redundante (POST /api/cli/v1/db/migrations/[id]/resolve)')
|
|
1625
|
+
.option('--force', 'atestar manualmente sem prova de redundância por hash (exige --mfa ou sessão MFA)')
|
|
1626
|
+
.option('--mfa <token>', 'código TOTP step-up MFA (necessário com --force)')
|
|
1627
|
+
.option('--json', 'saída em JSON')
|
|
1628
|
+
.action(async (migrationId, opts) => {
|
|
1629
|
+
const { runDbMigrationsResolve } = await import('./commands/db.js');
|
|
1630
|
+
await runDbMigrationsResolve(migrationId, {
|
|
1631
|
+
force: !!opts.force,
|
|
1632
|
+
mfa: opts.mfa,
|
|
1633
|
+
json: !!opts.json,
|
|
1634
|
+
});
|
|
1635
|
+
});
|
|
1610
1636
|
// D-3: neetru db migrate removido (legado Sprint 10 cortado).
|
|
1611
1637
|
// ── neetru db backups <dbId> ──────────────────────────────────────────
|
|
1612
1638
|
dbCmd
|