@zaip-ai/cli 1.3.5 → 1.3.7

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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +7 -0
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/services/install-agents-template.d.ts.map +1 -1
  6. package/dist/services/install-agents-template.js +18 -2
  7. package/dist/services/install-agents-template.js.map +1 -1
  8. package/dist/services/sqlite-bootstrap.d.ts +29 -0
  9. package/dist/services/sqlite-bootstrap.d.ts.map +1 -0
  10. package/dist/services/sqlite-bootstrap.js +141 -0
  11. package/dist/services/sqlite-bootstrap.js.map +1 -0
  12. package/dist/services/template-manifest.d.ts.map +1 -1
  13. package/dist/services/template-manifest.js +2 -0
  14. package/dist/services/template-manifest.js.map +1 -1
  15. package/package.json +2 -2
  16. package/templates/.agents/architecture/.gitkeep +0 -0
  17. package/templates/.agents/branding/.gitkeep +0 -0
  18. package/templates/.agents/dashboard/.zaip-managed.json +51 -39
  19. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-D1mpyYwh.js → CostCalculationGuidePage-DwVZOmMV.js} +1 -1
  20. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CKO47D5s.js → DashboardHelpDialog--czwAlcH.js} +723 -723
  21. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-BuSlCbyZ.js → PricingSettingsDialog-CyjMXkm8.js} +1 -1
  22. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-CUHFkAhZ.js → ProjectSettingsPage-B_Oj6Dlf.js} +1 -1
  23. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-BmASq2gW.js → TraceabilityWorkspace-CAqYVin1.js} +1 -1
  24. package/templates/.agents/dashboard/apps/frontend/dist/assets/{cost-monitor-Csj6LiAt.js → cost-monitor-C09KmlSw.js} +1 -1
  25. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-VyTTl_ph.js → icons-vendor-DXBOtWNI.js} +1 -1
  26. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CblPpexd.js +2 -0
  27. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CTC6vtln.js → react-vendor-DaNXQPyk.js} +1 -1
  28. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-9Mtvbwrg.js → traceability-I4AKqVDX.js} +1 -1
  29. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  30. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +17 -8
  31. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +8 -3
  32. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +19 -1
  33. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +8 -0
  34. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +7 -4
  35. package/templates/.agents/dashboard/modules/operations/src/react/OperationsFilteredStatusDialog.tsx +131 -0
  36. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +2 -133
  37. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +13 -7
  38. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +1 -1
  39. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +40 -0
  40. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.test.mjs +46 -0
  41. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +33 -26
  42. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +5 -8
  43. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +4 -6
  44. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-period-transition.spec.ts +37 -17
  45. package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +5 -3
  46. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +12 -5
  47. package/templates/.agents/growth/.gitkeep +0 -0
  48. package/templates/.agents/marketing/.gitkeep +0 -0
  49. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  50. package/templates/.agents/requirements/comparative-audit.md +1 -1
  51. package/templates/.agents/requirements/context-engine.md +1 -1
  52. package/templates/.agents/requirements/cost-observability.md +1 -1
  53. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  54. package/templates/.agents/requirements/data-export.md +1 -1
  55. package/templates/.agents/requirements/design-system.md +1 -1
  56. package/templates/.agents/requirements/execution-engine.md +1 -1
  57. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  58. package/templates/.agents/requirements/model-orchestration.md +1 -1
  59. package/templates/.agents/requirements/public-distribution.md +1 -1
  60. package/templates/.agents/requirements/software-architecture.md +1 -1
  61. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  62. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  63. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  64. package/templates/.agents/sales/.gitkeep +0 -0
  65. package/templates/README.md +722 -0
  66. package/templates/template-manifest.json +84 -85
  67. package/templates/.agents/architecture/software-architecture.md +0 -131
  68. package/templates/.agents/branding/brandbook.docx +0 -0
  69. package/templates/.agents/branding/brandbook.md +0 -60
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DIwDvCu5.js +0 -2
  71. package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +0 -169
  72. package/templates/.agents/growth/strategy.md +0 -27
  73. package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +0 -197
  74. package/templates/.agents/marketing/strategy.md +0 -29
  75. package/templates/.agents/sales/playbook.md +0 -32
  76. package/templates/.agents/sales/roteiro-comercial-zaip.md +0 -252
@@ -0,0 +1,131 @@
1
+ import type { DashboardTaskProjection, MacroProjection } from "../contracts.js";
2
+ import type { OverviewStatusKind, OverviewStatusPage } from "./operations-page-contract.js";
3
+ import { type OperationsLocale, riskLabel } from "./status-labels.js";
4
+
5
+ export function OperationsFilteredStatusDialog({
6
+ eyebrow,
7
+ label,
8
+ locale,
9
+ onClose,
10
+ onOpenTask,
11
+ onOpenDocument,
12
+ items,
13
+ state,
14
+ kind = "task",
15
+ onLoadMore,
16
+ page,
17
+ }: {
18
+ eyebrow: string;
19
+ label: string;
20
+ locale: OperationsLocale;
21
+ onClose: () => void;
22
+ onOpenTask: (taskId: string) => void;
23
+ onOpenDocument: (kind: "prd" | "spec", id: string) => void;
24
+ items: readonly (DashboardTaskProjection | MacroProjection)[];
25
+ state?: { loading: boolean; error: string | null } | undefined;
26
+ kind?: OverviewStatusKind;
27
+ onLoadMore?: ((offset: number) => void) | undefined;
28
+ page?: OverviewStatusPage | undefined;
29
+ }) {
30
+ const total = page?.total ?? items.length;
31
+ const taskLabel = statusTypeLabel(kind, total, locale);
32
+ return (
33
+ <dialog
34
+ className="operations-status-dialog-backdrop"
35
+ onMouseDown={(event) => {
36
+ if (event.currentTarget === event.target) onClose();
37
+ }}
38
+ open
39
+ >
40
+ <section
41
+ aria-label={`${label} — ${total} ${taskLabel}`}
42
+ aria-modal="true"
43
+ className="operations-status-dialog"
44
+ role="dialog"
45
+ >
46
+ <header>
47
+ <div>
48
+ <span>{eyebrow}</span>
49
+ <h2>{label}</h2>
50
+ <p>
51
+ {total} {taskLabel}
52
+ </p>
53
+ </div>
54
+ <button
55
+ aria-label={locale === "pt-BR" ? "Fechar" : "Close"}
56
+ onClick={onClose}
57
+ type="button"
58
+ >
59
+ ×
60
+ </button>
61
+ </header>
62
+ <div className="operations-status-task-list">
63
+ {(page?.loading ?? state?.loading) && items.length === 0 ? (
64
+ <p aria-busy="true" aria-live="polite" className="operations-empty" role="status">
65
+ {locale === "pt-BR" ? "Carregando itens\u2026" : "Loading items\u2026"}
66
+ </p>
67
+ ) : null}
68
+ {(page?.error ?? state?.error) ? (
69
+ <p aria-live="assertive" className="operations-empty" role="alert">
70
+ {page?.error ?? state?.error}
71
+ </p>
72
+ ) : null}
73
+ {items.map((item) => (
74
+ <button
75
+ key={item.id}
76
+ onClick={() =>
77
+ "change_id" in item ? onOpenTask(item.id) : onOpenDocument(item.kind, item.id)
78
+ }
79
+ type="button"
80
+ >
81
+ <span className="operations-status-task-main">
82
+ <strong>{item.title}</strong>
83
+ <small>{item.id}</small>
84
+ </span>
85
+ <span
86
+ className={
87
+ "change_id" in item
88
+ ? `operations-risk is-${item.risk}`
89
+ : "operations-work-kind is-macro"
90
+ }
91
+ >
92
+ {"change_id" in item ? riskLabel(item.risk, locale) : item.kind.toUpperCase()}
93
+ </span>
94
+ <span className="operations-status-task-action">
95
+ {locale === "pt-BR" ? "Ver detalhes" : "View details"} →
96
+ </span>
97
+ </button>
98
+ ))}
99
+ {items.length === 0 && !(page?.loading ?? state?.loading) ? (
100
+ <p className="operations-empty">
101
+ {locale === "pt-BR"
102
+ ? `Nenhum${kind === "task" ? "a" : ""} ${statusTypeLabel(kind, 1, locale)} neste status.`
103
+ : `No ${statusTypeLabel(kind, 2, locale)} in this status.`}
104
+ </p>
105
+ ) : null}
106
+ {onLoadMore && items.length < (page?.total ?? 0) ? (
107
+ <button disabled={page?.loading} onClick={() => onLoadMore(items.length)} type="button">
108
+ {page?.loading
109
+ ? locale === "pt-BR"
110
+ ? "Carregando…"
111
+ : "Loading…"
112
+ : locale === "pt-BR"
113
+ ? "Carregar mais"
114
+ : "Load more"}
115
+ </button>
116
+ ) : null}
117
+ </div>
118
+ </section>
119
+ </dialog>
120
+ );
121
+ }
122
+
123
+ function statusTypeLabel(
124
+ kind: OverviewStatusKind,
125
+ count: number,
126
+ locale: OperationsLocale,
127
+ ): string {
128
+ if (kind === "prd") return count === 1 ? "PRD" : "PRDs";
129
+ if (kind === "spec") return count === 1 ? "SPEC" : "SPECs";
130
+ return locale === "pt-BR" ? (count === 1 ? "tarefa" : "tarefas") : count === 1 ? "task" : "tasks";
131
+ }
@@ -6,12 +6,9 @@ import {
6
6
  formatPercent,
7
7
  } from "@zaip-ai/dashboard-ui";
8
8
  import { type ReactNode, useState } from "react";
9
- import type {
10
- DashboardTaskProjection,
11
- MacroProjection,
12
- OperationsProjection,
13
- } from "../contracts.js";
9
+ import type { MacroProjection, OperationsProjection } from "../contracts.js";
14
10
  import { allPhaseMetricTotals, executionMetricTotals } from "./execution-metrics.js";
11
+ import { OperationsFilteredStatusDialog as FilteredStatusDialog } from "./OperationsFilteredStatusDialog.js";
15
12
  import type { EntityMetric } from "./OperationsOverview.helpers.js";
16
13
  import {
17
14
  aggregateMacroMetrics,
@@ -915,134 +912,6 @@ export function OperationsOverview({
915
912
  );
916
913
  }
917
914
 
918
- function FilteredStatusDialog({
919
- eyebrow,
920
- label,
921
- locale,
922
- onClose,
923
- onOpenTask,
924
- onOpenDocument,
925
- items,
926
- state,
927
- kind = "task",
928
- onLoadMore,
929
- page,
930
- }: {
931
- eyebrow: string;
932
- label: string;
933
- locale: OperationsLocale;
934
- onClose: () => void;
935
- onOpenTask: (taskId: string) => void;
936
- onOpenDocument: (kind: "prd" | "spec", id: string) => void;
937
- items: readonly (DashboardTaskProjection | MacroProjection)[];
938
- state?: { loading: boolean; error: string | null } | undefined;
939
- kind?: OverviewStatusKind;
940
- onLoadMore?: ((offset: number) => void) | undefined;
941
- page?: OverviewStatusPage | undefined;
942
- }) {
943
- const total = page?.total ?? items.length;
944
- const taskLabel = statusTypeLabel(kind, total, locale);
945
- return (
946
- <dialog
947
- className="operations-status-dialog-backdrop"
948
- onMouseDown={(event) => {
949
- if (event.currentTarget === event.target) onClose();
950
- }}
951
- open
952
- >
953
- <section
954
- aria-label={`${label} — ${total} ${taskLabel}`}
955
- aria-modal="true"
956
- className="operations-status-dialog"
957
- role="dialog"
958
- >
959
- <header>
960
- <div>
961
- <span>{eyebrow}</span>
962
- <h2>{label}</h2>
963
- <p>
964
- {total} {taskLabel}
965
- </p>
966
- </div>
967
- <button
968
- aria-label={locale === "pt-BR" ? "Fechar" : "Close"}
969
- onClick={onClose}
970
- type="button"
971
- >
972
- ×
973
- </button>
974
- </header>
975
- <div className="operations-status-task-list">
976
- {(page?.loading ?? state?.loading) && items.length === 0 ? (
977
- <p aria-busy="true" aria-live="polite" className="operations-empty" role="status">
978
- {locale === "pt-BR" ? "Carregando itens\u2026" : "Loading items\u2026"}
979
- </p>
980
- ) : null}
981
- {(page?.error ?? state?.error) ? (
982
- <p aria-live="assertive" className="operations-empty" role="alert">
983
- {page?.error ?? state?.error}
984
- </p>
985
- ) : null}
986
- {items.map((item) => (
987
- <button
988
- key={item.id}
989
- onClick={() =>
990
- "change_id" in item ? onOpenTask(item.id) : onOpenDocument(item.kind, item.id)
991
- }
992
- type="button"
993
- >
994
- <span className="operations-status-task-main">
995
- <strong>{item.title}</strong>
996
- <small>{item.id}</small>
997
- </span>
998
- <span
999
- className={
1000
- "change_id" in item
1001
- ? `operations-risk is-${item.risk}`
1002
- : "operations-work-kind is-macro"
1003
- }
1004
- >
1005
- {"change_id" in item ? riskLabel(item.risk, locale) : item.kind.toUpperCase()}
1006
- </span>
1007
- <span className="operations-status-task-action">
1008
- {locale === "pt-BR" ? "Ver detalhes" : "View details"} →
1009
- </span>
1010
- </button>
1011
- ))}
1012
- {items.length === 0 && !(page?.loading ?? state?.loading) ? (
1013
- <p className="operations-empty">
1014
- {locale === "pt-BR"
1015
- ? `Nenhum${kind === "task" ? "a" : ""} ${statusTypeLabel(kind, 1, locale)} neste status.`
1016
- : `No ${statusTypeLabel(kind, 2, locale)} in this status.`}
1017
- </p>
1018
- ) : null}
1019
- {onLoadMore && items.length < (page?.total ?? 0) ? (
1020
- <button disabled={page?.loading} onClick={() => onLoadMore(items.length)} type="button">
1021
- {page?.loading
1022
- ? locale === "pt-BR"
1023
- ? "Carregando…"
1024
- : "Loading…"
1025
- : locale === "pt-BR"
1026
- ? "Carregar mais"
1027
- : "Load more"}
1028
- </button>
1029
- ) : null}
1030
- </div>
1031
- </section>
1032
- </dialog>
1033
- );
1034
- }
1035
-
1036
- function statusTypeLabel(
1037
- kind: OverviewStatusKind,
1038
- count: number,
1039
- locale: OperationsLocale,
1040
- ): string {
1041
- if (kind === "prd") return count === 1 ? "PRD" : "PRDs";
1042
- if (kind === "spec") return count === 1 ? "SPEC" : "SPECs";
1043
- return locale === "pt-BR" ? (count === 1 ? "tarefa" : "tarefas") : count === 1 ? "task" : "tasks";
1044
- }
1045
-
1046
915
  function PanelHeader({ title, hint, action }: { title: string; hint: string; action?: ReactNode }) {
1047
916
  return (
1048
917
  <header className="operations-panel-header">
@@ -2,6 +2,10 @@ import { readFileSync } from "node:fs";
2
2
  import { describe, expect, it } from "vitest";
3
3
 
4
4
  const source = readFileSync(new URL("./OperationsOverview.tsx", import.meta.url), "utf8");
5
+ const dialogSource = readFileSync(
6
+ new URL("./OperationsFilteredStatusDialog.tsx", import.meta.url),
7
+ "utf8",
8
+ );
5
9
 
6
10
  describe("OperationsOverview status cards", () => {
7
11
  it("opens a filtered dialog for Tasks, SPECs, and PRDs", () => {
@@ -12,16 +16,18 @@ describe("OperationsOverview status cards", () => {
12
16
  });
13
17
 
14
18
  it("uses selected-type labels, closes before document navigation, and paginates", () => {
15
- expect(source).toContain("onOpenDocument(item.kind, item.id)");
19
+ expect(dialogSource).toContain("onOpenDocument(item.kind, item.id)");
16
20
  expect(source).toContain("setSelectedStatus(null);");
17
21
  expect(source).toContain('"Status das tarefas"');
18
22
  expect(source).toContain('"Status das SPECs"');
19
23
  expect(source).toContain('"Status dos PRDs"');
20
- expect(source).toContain("const total = page?.total ?? items.length;");
21
- expect(source).toContain("aria-label={`${label} — ${total} ${taskLabel}`}");
22
- expect(source).toContain("onLoadMore(items.length)");
23
- expect(source).toContain('aria-busy="true"');
24
- expect(source).toContain('role="alert"');
25
- expect(source).toContain('Nenhum${kind === "task" ? "a" : ""}');
24
+ expect(dialogSource).toContain("const total = page?.total ?? items.length;");
25
+ expect(dialogSource).toContain(
26
+ "aria-label={`" + "$" + "{label} — $" + "{total} $" + "{taskLabel}`}",
27
+ );
28
+ expect(dialogSource).toContain("onLoadMore(items.length)");
29
+ expect(dialogSource).toContain('aria-busy="true"');
30
+ expect(dialogSource).toContain('role="alert"');
31
+ expect(dialogSource).toContain("Nenhum$" + '{kind === "task" ? "a" : ""}');
26
32
  });
27
33
  });
@@ -582,7 +582,7 @@ describe("dashboard backend", () => {
582
582
  tasks: expect.any(Array),
583
583
  runs: expect.any(Array),
584
584
  });
585
- });
585
+ }, 60_000);
586
586
  it("serves only allowlisted canonical agent portraits", async () => {
587
587
  const app = createDashboardApp(workspaceRoot, "test-session");
588
588
  const agentIds = [
@@ -0,0 +1,40 @@
1
+ import { access, mkdir } from "node:fs/promises";
2
+ import { relative, resolve } from "node:path";
3
+ import process from "node:process";
4
+
5
+ async function exists(path) {
6
+ return access(path).then(
7
+ () => true,
8
+ () => false,
9
+ );
10
+ }
11
+
12
+ /**
13
+ * A fresh npm installation has no lifecycle history yet. The SQLite read
14
+ * model supports that empty state, so its canonical source root is created
15
+ * instead of requiring the development workspace to be installed again.
16
+ */
17
+ export async function ensureDashboardRecordsDirectory(root) {
18
+ await mkdir(resolve(root, ".agents/records"), { recursive: true });
19
+ }
20
+
21
+ export async function assertDashboardPrerequisites(workspaceRoot) {
22
+ const [major, minor] = process.versions.node.split(".").map(Number);
23
+ if (!Number.isInteger(major) || major < 22 || (major === 22 && minor < 16))
24
+ throw new Error(
25
+ `O Dashboard SQLite requer Node.js 22.16.0 ou superior; a versão encontrada foi ${process.versions.node}. Atualize o Node.js e tente novamente.`,
26
+ );
27
+ try {
28
+ await import("node:sqlite");
29
+ } catch {
30
+ throw new Error(
31
+ "O Node.js instalado não disponibiliza node:sqlite. Reinstale o Node.js 22.16.0 ou superior com suporte a SQLite.",
32
+ );
33
+ }
34
+ await ensureDashboardRecordsDirectory(workspaceRoot);
35
+ const required = resolve(workspaceRoot, ".agents/config");
36
+ if (!(await exists(required)))
37
+ throw new Error(
38
+ `Pré-requisito do Dashboard SQLite ausente: ${relative(workspaceRoot, required) || required}. Execute npx @zaip-ai/cli@latest init na raiz do projeto e tente novamente.`,
39
+ );
40
+ }
@@ -0,0 +1,46 @@
1
+ import { access, mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import {
6
+ assertDashboardPrerequisites,
7
+ ensureDashboardRecordsDirectory,
8
+ } from "./start-dashboard-prerequisites.mjs";
9
+
10
+ const temporaryDirectories = [];
11
+
12
+ afterEach(async () => {
13
+ await Promise.all(
14
+ temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true, force: true })),
15
+ );
16
+ });
17
+
18
+ describe("dashboard SQLite prerequisites", () => {
19
+ it("creates the empty records root required by a fresh npm installation", async () => {
20
+ const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-empty-records-"));
21
+ temporaryDirectories.push(root);
22
+
23
+ await expect(access(join(root, ".agents", "records"))).rejects.toMatchObject({
24
+ code: "ENOENT",
25
+ });
26
+ await ensureDashboardRecordsDirectory(root);
27
+ await expect(access(join(root, ".agents", "records"))).resolves.toBeUndefined();
28
+ });
29
+
30
+ it("accepts a zeroed workspace after creating its records root", async () => {
31
+ const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-prerequisites-"));
32
+ temporaryDirectories.push(root);
33
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
34
+
35
+ await expect(assertDashboardPrerequisites(root)).resolves.toBeUndefined();
36
+ await expect(access(join(root, ".agents", "records"))).resolves.toBeUndefined();
37
+ });
38
+
39
+ it("directs incomplete consumers to init instead of a development install", async () => {
40
+ const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-missing-config-"));
41
+ temporaryDirectories.push(root);
42
+ await expect(assertDashboardPrerequisites(root)).rejects.toThrow(
43
+ "npx @zaip-ai/cli@latest init",
44
+ );
45
+ });
46
+ });
@@ -25,6 +25,7 @@ import {
25
25
  presentLaunchFailure,
26
26
  writeLauncherStatus,
27
27
  } from "./start-dashboard-lifecycle.mjs";
28
+ import { assertDashboardPrerequisites } from "./start-dashboard-prerequisites.mjs";
28
29
  import {
29
30
  isProcessAlive,
30
31
  stopOwnedDashboardProcess,
@@ -171,30 +172,7 @@ export function formatLauncherError(value, maximumLength = 3_600) {
171
172
  const lastHalf = maximumLength - firstHalf;
172
173
  return `${message.slice(0, firstHalf)}\n\n… detalhes reduzidos; consulte o log temporário …\n\n${message.slice(-lastHalf)}`;
173
174
  }
174
- export async function assertDashboardPrerequisites() {
175
- const major = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
176
- if (!Number.isInteger(major) || major < 22)
177
- throw new Error(
178
- `O Dashboard SQLite requer Node.js 22 ou superior; a versão encontrada foi ${process.versions.node}. Instale o Node.js 22 e tente novamente.`,
179
- );
180
- try {
181
- await import("node:sqlite");
182
- } catch {
183
- throw new Error(
184
- "O Node.js instalado não disponibiliza node:sqlite. Reinstale uma versão suportada do Node.js 22 ou superior.",
185
- );
186
- }
187
- for (const required of [
188
- resolve(workspaceRoot, ".agents/records"),
189
- resolve(workspaceRoot, ".agents/config"),
190
- resolve(workspaceRoot, "node_modules/esbuild"),
191
- ]) {
192
- if (!(await exists(required)))
193
- throw new Error(
194
- `Pré-requisito do Dashboard SQLite ausente: ${relative(workspaceRoot, required) || required}. Execute corepack pnpm install na raiz do ZAIP-AIOS e tente novamente.`,
195
- );
196
- }
197
- }
175
+
198
176
  export async function readDashboardSession(path = sessionPath) {
199
177
  try {
200
178
  return JSON.parse(await readFile(path, "utf8"));
@@ -457,6 +435,32 @@ export async function refreshDashboardBuildTargets({
457
435
  return { artifacts: Object.values(targets).flatMap((target) => target.artifacts), builtTargets };
458
436
  }
459
437
  async function ensureFreshDashboardBuild(onProgress = () => {}) {
438
+ // npm consumers receive a ready-to-run server and frontend bundle. They do
439
+ // not receive development-only esbuild, so never turn the first dashboard
440
+ // click into a pnpm-install requirement when that packaged runtime exists.
441
+ const buildTool = resolve(workspaceRoot, "node_modules/esbuild");
442
+ if (!(await exists(buildTool))) {
443
+ const packagedArtifacts = [installedServer, resolve(frontendRoot, "index.html")];
444
+ if (
445
+ await Promise.all(packagedArtifacts.map((path) => exists(path))).then((entries) =>
446
+ entries.every(Boolean),
447
+ )
448
+ ) {
449
+ buildProgress(
450
+ onProgress,
451
+ 39,
452
+ "Usando runtime empacotado",
453
+ "O pacote npm já inclui o dashboard pronto; nenhuma instalação de desenvolvimento é necessária.",
454
+ );
455
+ const signature = await Promise.all(
456
+ [...packagedArtifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
457
+ const details = await stat(path);
458
+ return `${path}:${details.size}:${details.mtimeMs}`;
459
+ }),
460
+ );
461
+ return createHash("sha256").update(signature.join("|")).digest("hex");
462
+ }
463
+ }
460
464
  const { artifacts } = await refreshDashboardBuildTargets({ onProgress });
461
465
  const artifactFingerprint = await Promise.all(
462
466
  [...artifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
@@ -626,7 +630,10 @@ async function redirectLaunchPage(launchPage, target) {
626
630
  launchPage.closeAfterHandoffGrace();
627
631
  }
628
632
  async function assertRuntimeReady() {
629
- const serverPath = (await exists(sourceServer)) ? sourceServer : installedServer;
633
+ const serverPath =
634
+ (await exists(resolve(workspaceRoot, "node_modules/esbuild"))) && (await exists(sourceServer))
635
+ ? sourceServer
636
+ : installedServer;
630
637
  if (!(await exists(serverPath))) {
631
638
  throw new Error(
632
639
  "Dashboard server bundle is missing; run the ZAIP release build or reinstall ZAIP-AIOS",
@@ -854,7 +861,7 @@ async function main() {
854
861
  detail:
855
862
  "Verificando Node.js 22 e o suporte a node:sqlite; isto não instala um banco de dados.",
856
863
  });
857
- await assertDashboardPrerequisites();
864
+ await assertDashboardPrerequisites(workspaceRoot);
858
865
  launchPage.setProgress({
859
866
  percent: 18,
860
867
  phase: "Preparando a sessão do dashboard",
@@ -5,7 +5,6 @@ import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { afterEach, describe, expect, it } from "vitest";
7
7
  import {
8
- assertDashboardPrerequisites,
9
8
  browserLaunchCommands,
10
9
  findAvailablePort,
11
10
  findAvailablePortWithFallback,
@@ -56,17 +55,15 @@ describe("dashboard executable session", () => {
56
55
  expect(stripTerminalControlCodes(colored)).toBe("Missing export");
57
56
  });
58
57
 
59
- it("shows preparation before validating a Node runtime that can import node:sqlite", async () => {
58
+ it("shows preparation before validating dashboard prerequisites", async () => {
60
59
  const source = await readFile(new URL("./start-dashboard.mjs", import.meta.url), "utf8");
61
60
 
62
- expect(source).toContain("export async function assertDashboardPrerequisites()");
63
- expect(source).toContain('await import("node:sqlite")');
64
- expect(source).not.toContain('builtinModules.includes("node:sqlite")');
65
- expect(source).toContain("Node.js 22 ou superior");
61
+ expect(source).toContain('from "./start-dashboard-prerequisites.mjs";');
62
+ expect(source).toContain("await assertDashboardPrerequisites(workspaceRoot);");
63
+ expect(source).toContain("Usando runtime empacotado");
66
64
  expect(source.indexOf("await openBrowser(dashboardUrl(launchPort, token))")).toBeLessThan(
67
- source.indexOf("await assertDashboardPrerequisites();"),
65
+ source.indexOf("await assertDashboardPrerequisites(workspaceRoot);"),
68
66
  );
69
- await expect(assertDashboardPrerequisites()).resolves.toBeUndefined();
70
67
  });
71
68
 
72
69
  it("keeps the beginning and end of an oversized launcher error", () => {
@@ -50,7 +50,7 @@ test("releases an aborted Cost group load and keeps Operation reachable", async
50
50
  });
51
51
 
52
52
  await openDashboard(page);
53
- await expectGlobalMonthPreset(page);
53
+ await expectGlobalAllTimePreset(page);
54
54
  const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
55
55
  await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
56
56
  const costTabs = page.locator(".cost-tabs");
@@ -87,7 +87,7 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
87
87
  });
88
88
 
89
89
  await openDashboard(page);
90
- await expectGlobalMonthPreset(page);
90
+ await expectGlobalAllTimePreset(page);
91
91
  const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
92
92
  await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
93
93
  const costTabs = page.locator(".cost-tabs");
@@ -124,10 +124,8 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
124
124
  expect(failedApiResponses).toEqual([]);
125
125
  });
126
126
 
127
- async function expectGlobalMonthPreset(page: import("@playwright/test").Page) {
128
- await expect(
129
- page.locator(".dashboard-global-controls").getByLabel("Período"),
130
- ).toHaveValue("this-month");
127
+ async function expectGlobalAllTimePreset(page: import("@playwright/test").Page) {
128
+ await expect(page.locator(".dashboard-global-controls").getByLabel("Período")).toHaveValue("all");
131
129
  }
132
130
 
133
131
  async function openDashboard(page: import("@playwright/test").Page) {