ndomo 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.
Files changed (247) hide show
  1. package/.bun-version +1 -0
  2. package/.dockerignore +79 -0
  3. package/.editorconfig +18 -0
  4. package/.env.example +19 -0
  5. package/.github/CODEOWNERS +8 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +2 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
  9. package/.github/dependabot.yml +36 -0
  10. package/.github/pull_request_template.md +24 -0
  11. package/.github/release.yml +30 -0
  12. package/.github/workflows/gitleaks.yml +28 -0
  13. package/.github/workflows/release-please.yml +27 -0
  14. package/.github/workflows/smoke.yml +29 -0
  15. package/.husky/commit-msg +1 -0
  16. package/CHANGELOG.md +114 -0
  17. package/Dockerfile +32 -0
  18. package/README.es.md +174 -0
  19. package/README.md +187 -0
  20. package/agents/chronicler.md +98 -0
  21. package/agents/ci-smith.md +136 -0
  22. package/agents/craftsman.md +341 -0
  23. package/agents/deploy-smith.md +138 -0
  24. package/agents/foreman.md +377 -0
  25. package/agents/go-smith.md +164 -0
  26. package/agents/guild.md +188 -0
  27. package/agents/inspector.md +83 -0
  28. package/agents/js-smith.md +127 -0
  29. package/agents/ops-scout.md +173 -0
  30. package/agents/painter.md +200 -0
  31. package/agents/python-smith.md +120 -0
  32. package/agents/ranger.md +307 -0
  33. package/agents/release-smith.md +165 -0
  34. package/agents/rust-smith.md +159 -0
  35. package/agents/sage.md +178 -0
  36. package/agents/scout.md +144 -0
  37. package/agents/scribe.md +156 -0
  38. package/agents/smith.md +201 -0
  39. package/agents/vue-smith.md +155 -0
  40. package/agents/warden.md +216 -0
  41. package/agents/zig-smith.md +156 -0
  42. package/bin/ndomo-analyses.ts +4 -0
  43. package/bin/ndomo-status.ts +4 -0
  44. package/biome.json +57 -0
  45. package/bun.lock +514 -0
  46. package/commitlint.config.js +3 -0
  47. package/config/ndomo.config.json +258 -0
  48. package/config/ndomo.schema.json +166 -0
  49. package/docs/agents.md +375 -0
  50. package/docs/bugs/plan-create-orphan-fk.md +131 -0
  51. package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
  52. package/docs/configuration.md +276 -0
  53. package/docs/database.md +364 -0
  54. package/docs/features/feature-flexible-builder-v1.md +724 -0
  55. package/docs/features/feature-flexible-builder-v2.md +882 -0
  56. package/docs/features/feature-flexible-builder.md +974 -0
  57. package/docs/http-server.md +244 -0
  58. package/docs/installation.md +259 -0
  59. package/docs/integrations.md +129 -0
  60. package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
  61. package/docs/operations/audit-v1.md +417 -0
  62. package/docs/operations/audit-v2.md +197 -0
  63. package/docs/operations/audit-v3.md +306 -0
  64. package/docs/operations/db-optimize-foundations.md +123 -0
  65. package/docs/operations/verify-gate-architecture.md +82 -0
  66. package/docs/workflows.md +448 -0
  67. package/opencode.json +5 -0
  68. package/package.json +65 -0
  69. package/release-please-config.json +11 -0
  70. package/scripts/dev-bust-cache.sh +164 -0
  71. package/scripts/install.sh +688 -0
  72. package/scripts/smoke-e2e.ts +704 -0
  73. package/scripts/smoke-hot.ts +417 -0
  74. package/scripts/smoke-http.sh +228 -0
  75. package/scripts/smoke-v4.ts +256 -0
  76. package/scripts/smoke-v5.ts +397 -0
  77. package/scripts/smoke.sh +9 -0
  78. package/scripts/uninstall.sh +224 -0
  79. package/skills/api-security-best-practices/SKILL.md +915 -0
  80. package/skills/bash-scripting/SKILL.md +201 -0
  81. package/skills/bun/SKILL.md +313 -0
  82. package/skills/cavecrew/SKILL.md +82 -0
  83. package/skills/caveman/SKILL.md +74 -0
  84. package/skills/caveman-review/README.md +33 -0
  85. package/skills/caveman-review/SKILL.md +55 -0
  86. package/skills/find-skills/SKILL.md +142 -0
  87. package/skills/frontend-design/LICENSE.txt +177 -0
  88. package/skills/frontend-design/SKILL.md +55 -0
  89. package/skills/golang-patterns/SKILL.md +674 -0
  90. package/skills/golang-security/SKILL.md +185 -0
  91. package/skills/golang-security/evals/evals.json +595 -0
  92. package/skills/golang-security/references/architecture.md +268 -0
  93. package/skills/golang-security/references/checklist.md +80 -0
  94. package/skills/golang-security/references/cookies.md +200 -0
  95. package/skills/golang-security/references/cryptography.md +424 -0
  96. package/skills/golang-security/references/filesystem.md +285 -0
  97. package/skills/golang-security/references/injection.md +315 -0
  98. package/skills/golang-security/references/logging.md +163 -0
  99. package/skills/golang-security/references/memory-safety.md +241 -0
  100. package/skills/golang-security/references/network.md +253 -0
  101. package/skills/golang-security/references/secrets.md +189 -0
  102. package/skills/golang-security/references/third-party.md +159 -0
  103. package/skills/golang-security/references/threat-modeling.md +189 -0
  104. package/skills/golang-testing/SKILL.md +720 -0
  105. package/skills/grill-me/SKILL.md +7 -0
  106. package/skills/javascript-testing-patterns/SKILL.md +537 -0
  107. package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
  108. package/skills/modern-javascript-patterns/SKILL.md +43 -0
  109. package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
  110. package/skills/modern-javascript-patterns/references/details.md +457 -0
  111. package/skills/python-anti-patterns/SKILL.md +349 -0
  112. package/skills/python-design-patterns/SKILL.md +85 -0
  113. package/skills/python-design-patterns/references/details.md +353 -0
  114. package/skills/python-error-handling/SKILL.md +193 -0
  115. package/skills/python-error-handling/references/details.md +171 -0
  116. package/skills/python-testing-patterns/SKILL.md +278 -0
  117. package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
  118. package/skills/python-testing-patterns/references/details.md +349 -0
  119. package/skills/rust-patterns/SKILL.md +500 -0
  120. package/skills/rust-testing/SKILL.md +501 -0
  121. package/skills/security-review/SKILL.md +504 -0
  122. package/skills/security-review/cloud-infrastructure-security.md +361 -0
  123. package/skills/vue-best-practices/SKILL.md +154 -0
  124. package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
  125. package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
  126. package/skills/vue-best-practices/references/component-async.md +97 -0
  127. package/skills/vue-best-practices/references/component-data-flow.md +307 -0
  128. package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
  129. package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
  130. package/skills/vue-best-practices/references/component-slots.md +216 -0
  131. package/skills/vue-best-practices/references/component-suspense.md +228 -0
  132. package/skills/vue-best-practices/references/component-teleport.md +108 -0
  133. package/skills/vue-best-practices/references/component-transition-group.md +128 -0
  134. package/skills/vue-best-practices/references/component-transition.md +125 -0
  135. package/skills/vue-best-practices/references/composables.md +290 -0
  136. package/skills/vue-best-practices/references/directives.md +162 -0
  137. package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
  138. package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
  139. package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
  140. package/skills/vue-best-practices/references/plugins.md +166 -0
  141. package/skills/vue-best-practices/references/reactivity.md +344 -0
  142. package/skills/vue-best-practices/references/render-functions.md +201 -0
  143. package/skills/vue-best-practices/references/sfc.md +310 -0
  144. package/skills/vue-best-practices/references/state-management.md +135 -0
  145. package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
  146. package/skills/vue-pinia-best-practices/SKILL.md +21 -0
  147. package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
  148. package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
  149. package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
  150. package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
  151. package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
  152. package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
  153. package/skills/zig-0.16/SKILL.md +840 -0
  154. package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
  155. package/src/cli/analyses.ts +280 -0
  156. package/src/cli/index.ts +108 -0
  157. package/src/cli/serve.ts +192 -0
  158. package/src/cli/smoke.ts +131 -0
  159. package/src/cli/status.test.ts +204 -0
  160. package/src/cli/status.ts +263 -0
  161. package/src/cli/vacuum.test.ts +82 -0
  162. package/src/cli/vacuum.ts +96 -0
  163. package/src/config/schema.test.ts +88 -0
  164. package/src/config/schema.ts +64 -0
  165. package/src/db/analyses-migration.test.ts +210 -0
  166. package/src/db/analyses.test.ts +466 -0
  167. package/src/db/analyses.ts +375 -0
  168. package/src/db/auto-checkpoint.ts +131 -0
  169. package/src/db/client.test.ts +129 -0
  170. package/src/db/client.ts +55 -0
  171. package/src/db/fts-escape.ts +20 -0
  172. package/src/db/incidents.test.ts +201 -0
  173. package/src/db/incidents.ts +93 -0
  174. package/src/db/index.ts +86 -0
  175. package/src/db/migrations-v13.test.ts +141 -0
  176. package/src/db/migrations-v8.test.ts +301 -0
  177. package/src/db/migrations.ts +147 -0
  178. package/src/db/plan-archive.test.ts +180 -0
  179. package/src/db/plan-archive.ts +274 -0
  180. package/src/db/plan-create.test.ts +276 -0
  181. package/src/db/plan-create.ts +78 -0
  182. package/src/db/plan-files.test.ts +289 -0
  183. package/src/db/plan-update-status.ts +287 -0
  184. package/src/db/plans.test.ts +490 -0
  185. package/src/db/plans.ts +534 -0
  186. package/src/db/resolve-project-dir.test.ts +143 -0
  187. package/src/db/resolve-project-dir.ts +75 -0
  188. package/src/db/rollbacks.test.ts +150 -0
  189. package/src/db/rollbacks.ts +67 -0
  190. package/src/db/schema.ts +907 -0
  191. package/src/db/sessions.test.ts +80 -0
  192. package/src/db/sessions.ts +135 -0
  193. package/src/db/shutdown.test.ts +147 -0
  194. package/src/db/shutdown.ts +45 -0
  195. package/src/db/tasks.test.ts +921 -0
  196. package/src/db/tasks.ts +747 -0
  197. package/src/db/types.ts +619 -0
  198. package/src/http/__tests__/auth.test.ts +196 -0
  199. package/src/http/__tests__/routes.test.ts +465 -0
  200. package/src/http/__tests__/sse.test.ts +317 -0
  201. package/src/http/auth.ts +72 -0
  202. package/src/http/middleware/cors.ts +53 -0
  203. package/src/http/middleware/security-headers.ts +21 -0
  204. package/src/http/routes/events.ts +112 -0
  205. package/src/http/routes/health.ts +51 -0
  206. package/src/http/routes/plans.ts +66 -0
  207. package/src/http/routes/sessions.ts +50 -0
  208. package/src/http/routes/tasks.ts +60 -0
  209. package/src/http/server.ts +95 -0
  210. package/src/http/sse.ts +116 -0
  211. package/src/index.ts +37 -0
  212. package/src/lib.ts +65 -0
  213. package/src/mem/scoped.ts +65 -0
  214. package/src/orchestrator/background.test.ts +268 -0
  215. package/src/orchestrator/background.ts +293 -0
  216. package/src/orchestrator/memory-hook.ts +182 -0
  217. package/src/orchestrator/reconciler.ts +123 -0
  218. package/src/orchestrator/scheduler.test.ts +300 -0
  219. package/src/orchestrator/scheduler.ts +243 -0
  220. package/src/plugin.test.ts +2574 -0
  221. package/src/plugin.ts +1690 -0
  222. package/src/sdk/client.ts +66 -0
  223. package/src/worktrees/manager.ts +236 -0
  224. package/src/worktrees/state.ts +87 -0
  225. package/tests/integration/ranger-flow.test.ts +257 -0
  226. package/tools/analysis_archive.ts +28 -0
  227. package/tools/analysis_create.ts +55 -0
  228. package/tools/analysis_get.ts +33 -0
  229. package/tools/analysis_link_plan.ts +44 -0
  230. package/tools/analysis_list.ts +48 -0
  231. package/tools/analysis_search.ts +36 -0
  232. package/tools/analysis_update.ts +44 -0
  233. package/tools/plan_approve.ts +31 -0
  234. package/tools/plan_create.ts +58 -0
  235. package/tools/plan_get.ts +40 -0
  236. package/tools/plan_list.ts +37 -0
  237. package/tools/plan_search.ts +34 -0
  238. package/tools/plan_update_status.ts +71 -0
  239. package/tools/session_checkpoint.ts +31 -0
  240. package/tools/session_end.ts +26 -0
  241. package/tools/session_start.ts +43 -0
  242. package/tools/task_create_batch.ts +70 -0
  243. package/tools/task_list.ts +35 -0
  244. package/tools/task_next_for_agent.ts +30 -0
  245. package/tools/task_search.ts +34 -0
  246. package/tools/task_update_status.ts +37 -0
  247. package/tsconfig.json +31 -0
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ # Verify Zig 0.16.x is available
3
+ set -e
4
+
5
+ ZIG_CMD="${ZIG_CMD:-zig}"
6
+
7
+ if ! command -v "$ZIG_CMD" &> /dev/null; then
8
+ echo "ERROR: zig not found. Set ZIG_CMD or add zig to PATH."
9
+ exit 1
10
+ fi
11
+
12
+ VERSION=$("$ZIG_CMD" version)
13
+ MAJOR=$(echo "$VERSION" | cut -d. -f1)
14
+ MINOR=$(echo "$VERSION" | cut -d. -f2)
15
+
16
+ if [[ "$MAJOR" -eq 0 && "$MINOR" -ge 16 ]] || [[ "$MAJOR" -ge 1 ]]; then
17
+ echo "OK: Zig $VERSION detected."
18
+ else
19
+ echo "WARNING: Zig $VERSION detected, this skill is for 0.16+."
20
+ exit 1
21
+ fi
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ndomo analyses CLI — list, get, search, archive analyses.
4
+ *
5
+ * Reads .ndomo/state.db from the project root (resolved same as status.ts).
6
+ * Supports list/get/search/archive subcommands with filters.
7
+ *
8
+ * Uses bun:sqlite (synchronous) — no async/await on DB ops.
9
+ */
10
+
11
+ import { Database } from "bun:sqlite";
12
+ import { existsSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import {
15
+ archiveAnalysis,
16
+ getAnalysis,
17
+ getAnalysisBySlug,
18
+ listAnalyses,
19
+ searchAnalyses,
20
+ } from "../db/analyses.ts";
21
+ import { runMigrations } from "../db/migrations.ts";
22
+ import type { Analysis } from "../db/types.ts";
23
+
24
+ const NDOMO_DIR = ".ndomo";
25
+ const DB_FILE = "state.db";
26
+
27
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
28
+
29
+ /** Resolve DB path — same logic as src/cli/status.ts. */
30
+ function resolveDbPath(): string | null {
31
+ const cwdPath = join(process.cwd(), NDOMO_DIR, DB_FILE);
32
+ if (existsSync(cwdPath)) return cwdPath;
33
+
34
+ let dir = process.cwd();
35
+ for (let i = 0; i < 5; i++) {
36
+ const parent = join(dir, "..");
37
+ if (parent === dir) break;
38
+ dir = parent;
39
+ const candidate = join(dir, NDOMO_DIR, DB_FILE);
40
+ if (existsSync(candidate)) return candidate;
41
+ }
42
+
43
+ return null;
44
+ }
45
+
46
+ /** Short ID — first 8 chars. */
47
+ function shortId(id: string): string {
48
+ return id.slice(0, 8);
49
+ }
50
+
51
+ /** Truncate string to maxLen with "..." suffix. */
52
+ function truncate(s: string, maxLen: number): string {
53
+ if (s.length <= maxLen) return s;
54
+ return `${s.slice(0, maxLen - 3)}...`;
55
+ }
56
+
57
+ /** Print analyses as table. */
58
+ function printTable(analyses: Analysis[]): void {
59
+ if (analyses.length === 0) {
60
+ console.log("no analyses found");
61
+ return;
62
+ }
63
+
64
+ console.log(
65
+ ` ${"id".padEnd(10)}${"slug".padEnd(26)}${"title".padEnd(32)}${"agent".padEnd(12)}${"sourcePlan".padEnd(12)}${"updatedAt".padEnd(22)}`,
66
+ );
67
+
68
+ for (const a of analyses) {
69
+ const id = shortId(a.id);
70
+ const slug = truncate(a.slug, 24);
71
+ const title = truncate(a.title, 30);
72
+ const agent = truncate(a.agent, 10);
73
+ const sourcePlan = a.sourcePlanId ? shortId(a.sourcePlanId) : "-";
74
+ const updatedAt = a.updatedAt ?? "-";
75
+
76
+ console.log(
77
+ ` ${id.padEnd(10)}${slug.padEnd(26)}${title.padEnd(32)}${agent.padEnd(12)}${sourcePlan.padEnd(12)}${updatedAt.padEnd(22)}`,
78
+ );
79
+ }
80
+ }
81
+
82
+ /** Print single analysis as JSON. */
83
+ function printJson(data: unknown): void {
84
+ console.log(JSON.stringify(data, null, 2));
85
+ }
86
+
87
+ /** Print help text. */
88
+ function printHelp(): void {
89
+ console.log(`Usage: ndomo analyses <subcommand> [options]
90
+
91
+ Subcommands:
92
+ list List analyses (default)
93
+ --agent <name> Filter by agent
94
+ --source-plan <id> Filter by source plan ID
95
+ --project <path> Filter by project path
96
+ --archived Include archived analyses
97
+ --limit <n> Max results (default 50)
98
+
99
+ get <id-or-slug> Get analysis by ID or slug
100
+ --project <path> Required when using slug
101
+
102
+ search <query> Full-text search over title+summary+findings
103
+ --limit <n> Max results (default 20)
104
+
105
+ archive <id> Soft-delete an analysis
106
+
107
+ help Show this help`);
108
+ }
109
+
110
+ // ─── Subcommand handlers ─────────────────────────────────────────────────────
111
+
112
+ function handleList(db: Database, args: string[]): void {
113
+ let agent: string | undefined;
114
+ let sourcePlanId: string | undefined;
115
+ let projectPath: string | undefined;
116
+ let archived: boolean | undefined;
117
+ let limit: number | undefined;
118
+
119
+ for (let i = 0; i < args.length; i++) {
120
+ const arg = args[i]!;
121
+ if (arg === "--agent" && i + 1 < args.length) {
122
+ agent = args[++i]!;
123
+ } else if (arg === "--source-plan" && i + 1 < args.length) {
124
+ sourcePlanId = args[++i]!;
125
+ } else if (arg === "--project" && i + 1 < args.length) {
126
+ projectPath = args[++i]!;
127
+ } else if (arg === "--archived") {
128
+ archived = true;
129
+ } else if (arg === "--limit" && i + 1 < args.length) {
130
+ const n = Number.parseInt(args[++i]!, 10);
131
+ if (Number.isNaN(n) || n < 1) {
132
+ console.error("error: --limit must be a positive integer");
133
+ process.exit(1);
134
+ }
135
+ limit = n;
136
+ }
137
+ }
138
+
139
+ const filters: Parameters<typeof listAnalyses>[1] = {};
140
+ if (agent !== undefined) filters.agent = agent;
141
+ if (sourcePlanId !== undefined) filters.sourcePlanId = sourcePlanId;
142
+ if (projectPath !== undefined) filters.projectPath = projectPath;
143
+ if (archived !== undefined) filters.archived = archived;
144
+ if (limit !== undefined) filters.limit = limit;
145
+
146
+ const results = listAnalyses(db, filters);
147
+ printTable(results);
148
+ }
149
+
150
+ function handleGet(db: Database, args: string[]): void {
151
+ if (args.length === 0) {
152
+ console.error("error: get requires an ID or slug argument");
153
+ process.exit(1);
154
+ }
155
+
156
+ const identifier = args[0]!;
157
+ let projectPath: string | undefined;
158
+
159
+ for (let i = 1; i < args.length; i++) {
160
+ if (args[i] === "--project" && i + 1 < args.length) {
161
+ projectPath = args[++i]!;
162
+ }
163
+ }
164
+
165
+ let analysis: Analysis | null = null;
166
+
167
+ // UUIDs have 8-4-4-4-12 format; slugs are shorter kebab-case
168
+ const isUuid = identifier.includes("-") && identifier.length >= 36;
169
+
170
+ if (isUuid) {
171
+ analysis = getAnalysis(db, identifier);
172
+ } else {
173
+ // Treat as slug — requires --project
174
+ if (projectPath === undefined) {
175
+ console.error("error: --project <path> is required when using slug");
176
+ process.exit(1);
177
+ }
178
+ analysis = getAnalysisBySlug(db, identifier, projectPath);
179
+ }
180
+
181
+ if (!analysis) {
182
+ console.error(`error: analysis '${identifier}' not found`);
183
+ process.exit(1);
184
+ }
185
+
186
+ printJson(analysis);
187
+ }
188
+
189
+ function handleSearch(db: Database, args: string[]): void {
190
+ if (args.length === 0) {
191
+ console.error("error: search requires a query argument");
192
+ process.exit(1);
193
+ }
194
+
195
+ const query = args[0]!;
196
+ let limit = 20;
197
+
198
+ for (let i = 1; i < args.length; i++) {
199
+ if (args[i] === "--limit" && i + 1 < args.length) {
200
+ const n = Number.parseInt(args[++i]!, 10);
201
+ if (Number.isNaN(n) || n < 1) {
202
+ console.error("error: --limit must be a positive integer");
203
+ process.exit(1);
204
+ }
205
+ limit = n;
206
+ }
207
+ }
208
+
209
+ const results = searchAnalyses(db, query, { limit });
210
+ printTable(results);
211
+ }
212
+
213
+ function handleArchive(db: Database, args: string[]): void {
214
+ if (args.length === 0) {
215
+ console.error("error: archive requires an ID argument");
216
+ process.exit(1);
217
+ }
218
+
219
+ const id = args[0]!;
220
+ try {
221
+ const archived = archiveAnalysis(db, id);
222
+ console.log(`archived analysis: ${shortId(archived.id)} (${archived.slug})`);
223
+ } catch (err) {
224
+ const message = err instanceof Error ? err.message : String(err);
225
+ console.error(`error: ${message}`);
226
+ process.exit(1);
227
+ }
228
+ }
229
+
230
+ // ─── Main ────────────────────────────────────────────────────────────────────
231
+
232
+ export function runAnalyses(args: string[]): void {
233
+ const subcommand = args[0];
234
+
235
+ // Help (before DB resolution)
236
+ if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
237
+ printHelp();
238
+ return;
239
+ }
240
+
241
+ const dbPath = resolveDbPath();
242
+ if (!dbPath) {
243
+ console.error("error: .ndomo/state.db not found — run from project root or parent dir");
244
+ process.exit(1);
245
+ }
246
+
247
+ const db = new Database(dbPath);
248
+ db.exec("PRAGMA foreign_keys = ON");
249
+ runMigrations(db);
250
+
251
+ try {
252
+ const rest = args.slice(1);
253
+
254
+ switch (subcommand) {
255
+ case "list":
256
+ handleList(db, rest);
257
+ break;
258
+ case "get":
259
+ handleGet(db, rest);
260
+ break;
261
+ case "search":
262
+ handleSearch(db, rest);
263
+ break;
264
+ case "archive":
265
+ handleArchive(db, rest);
266
+ break;
267
+ default:
268
+ console.error(`error: unknown subcommand '${subcommand}'`);
269
+ printHelp();
270
+ process.exit(1);
271
+ }
272
+ } finally {
273
+ db.close();
274
+ }
275
+ }
276
+
277
+ // Direct execution
278
+ if (import.meta.main) {
279
+ runAnalyses(process.argv.slice(2));
280
+ }
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ndomo CLI — unified entry point for all subcommands.
4
+ *
5
+ * Usage:
6
+ * bun run src/cli/index.ts <command> [options]
7
+ *
8
+ * Commands:
9
+ * status Show plans grouped by status with task counts
10
+ * serve Start the HTTP server
11
+ * vacuum Reclaim disk space from .ndomo/state.db
12
+ * smoke Run smoke tests
13
+ * help Show this help
14
+ *
15
+ * Each subcommand can also be run directly:
16
+ * bun run src/cli/status.ts --plans
17
+ * bun run src/cli/serve.ts --port 8080
18
+ */
19
+
20
+ const COMMANDS: Record<
21
+ string,
22
+ { description: string; run: (args: string[]) => void | Promise<void> }
23
+ > = {
24
+ status: {
25
+ description: "Show plans grouped by status with task counts",
26
+ run: async (args) => {
27
+ const { runStatus } = await import("./status.ts");
28
+ runStatus(args);
29
+ },
30
+ },
31
+ serve: {
32
+ description: "Start the HTTP server",
33
+ run: async (args) => {
34
+ const { runServe } = await import("./serve.ts");
35
+ await runServe(args);
36
+ },
37
+ },
38
+ vacuum: {
39
+ description: "Reclaim disk space from .ndomo/state.db",
40
+ run: async (args) => {
41
+ const { vacuumProject } = await import("./vacuum.ts");
42
+ const projectDir = args[0] ?? process.cwd();
43
+ const result = vacuumProject(projectDir);
44
+ const delta = result.sizeBefore - result.sizeAfter;
45
+ console.log(`[vacuum] incremental_vacuum: reclaimed ${result.pagesReclaimed} pages`);
46
+ console.log(`[vacuum] wal_checkpoint(TRUNCATE): ${JSON.stringify(result.checkpoint)}`);
47
+ console.log(
48
+ `[vacuum] file size: ${result.sizeBefore} → ${result.sizeAfter} bytes (${delta >= 0 ? "-" : "+"}${Math.abs(delta)} bytes)`,
49
+ );
50
+ },
51
+ },
52
+ smoke: {
53
+ description: "Run smoke tests",
54
+ run: async () => {
55
+ // smoke.ts runs on import — just import it
56
+ await import("./smoke.ts");
57
+ },
58
+ },
59
+ };
60
+
61
+ function printHelp(): void {
62
+ console.log(`ndomo CLI — multi-agent plugin
63
+
64
+ Usage:
65
+ bun run src/cli/index.ts <command> [options]
66
+
67
+ Commands:`);
68
+
69
+ for (const [name, cmd] of Object.entries(COMMANDS)) {
70
+ console.log(` ${name.padEnd(12)}${cmd.description}`);
71
+ }
72
+
73
+ console.log(`
74
+ Each subcommand can also be run directly:
75
+ bun run src/cli/status.ts --plans
76
+ bun run src/cli/serve.ts --port 8080
77
+ bun run src/cli/vacuum.ts`);
78
+ }
79
+
80
+ async function main(): Promise<void> {
81
+ const args = process.argv.slice(2);
82
+ const commandName = args[0];
83
+
84
+ if (!commandName || commandName === "--help" || commandName === "-h" || commandName === "help") {
85
+ printHelp();
86
+ process.exit(commandName ? 0 : 1);
87
+ }
88
+
89
+ const command = COMMANDS[commandName];
90
+ if (!command) {
91
+ console.error(
92
+ `error: unknown command "${commandName}". Run "ndomo help" for available commands.`,
93
+ );
94
+ process.exit(1);
95
+ }
96
+
97
+ try {
98
+ await command.run(args.slice(1));
99
+ } catch (err) {
100
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ // Direct execution
106
+ if (import.meta.main) {
107
+ await main();
108
+ }
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ndomo serve — start the HTTP server.
4
+ *
5
+ * Usage:
6
+ * bun run src/cli/serve.ts [options]
7
+ *
8
+ * Options:
9
+ * --port <n> Port number (default: from config, 4097)
10
+ * --no-auth Disable HTTP basic auth requirement
11
+ * --cors <origins> Comma-separated allowed origins (default: from config, "*")
12
+ * --force Start even when NDOMO_HTTP_ENABLED is not "true"
13
+ *
14
+ * Examples:
15
+ * bun run src/cli/serve.ts
16
+ * bun run src/cli/serve.ts --port 8080 --no-auth
17
+ * bun run src/cli/serve.ts --cors "https://app.example.com,https://admin.example.com"
18
+ * bun run src/cli/serve.ts --force --port 4098
19
+ *
20
+ * Graceful shutdown on SIGINT/SIGTERM.
21
+ * Exit codes: 0 = clean shutdown, 1 = startup failure.
22
+ */
23
+
24
+ import { Database } from "bun:sqlite";
25
+ import { existsSync } from "node:fs";
26
+ import { join } from "node:path";
27
+ import type { HttpConfig } from "../config/schema.ts";
28
+ import { loadHttpConfig } from "../config/schema.ts";
29
+ import { runMigrations } from "../db/migrations.ts";
30
+ import { type HttpServerHandle, startHttpServer } from "../http/server.ts";
31
+
32
+ const NDOMO_DIR = ".ndomo";
33
+ const DB_FILE = "state.db";
34
+
35
+ /** Resolve DB path — same logic as src/db/client.ts. */
36
+ function resolveDbPath(): string | null {
37
+ const cwdPath = join(process.cwd(), NDOMO_DIR, DB_FILE);
38
+ if (existsSync(cwdPath)) return cwdPath;
39
+
40
+ let dir = process.cwd();
41
+ for (let i = 0; i < 5; i++) {
42
+ const parent = join(dir, "..");
43
+ if (parent === dir) break;
44
+ dir = parent;
45
+ const candidate = join(dir, NDOMO_DIR, DB_FILE);
46
+ if (existsSync(candidate)) return candidate;
47
+ }
48
+
49
+ return null;
50
+ }
51
+
52
+ /** Parse CLI args into a partial config override. */
53
+ function parseArgs(args: string[]): {
54
+ port?: number;
55
+ authRequired?: boolean;
56
+ corsOrigins?: string[];
57
+ force: boolean;
58
+ } {
59
+ const result: { port?: number; authRequired?: boolean; corsOrigins?: string[]; force: boolean } =
60
+ {
61
+ force: false,
62
+ };
63
+
64
+ for (let i = 0; i < args.length; i++) {
65
+ const arg = args[i];
66
+ if (arg === "--port" && i + 1 < args.length) {
67
+ const portStr = args[++i];
68
+ if (!portStr) {
69
+ console.error("error: --port requires a value");
70
+ process.exit(1);
71
+ }
72
+ const port = Number(portStr);
73
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
74
+ console.error(`error: invalid port "${portStr}". Must be 1-65535.`);
75
+ process.exit(1);
76
+ }
77
+ result.port = port;
78
+ } else if (arg === "--no-auth") {
79
+ result.authRequired = false;
80
+ } else if (arg === "--cors" && i + 1 < args.length) {
81
+ const corsStr = args[++i];
82
+ if (!corsStr) {
83
+ console.error("error: --cors requires a value");
84
+ process.exit(1);
85
+ }
86
+ result.corsOrigins = corsStr.split(",").map((s) => s.trim());
87
+ } else if (arg === "--force") {
88
+ result.force = true;
89
+ } else if (arg === "--help" || arg === "-h") {
90
+ console.log(`ndomo serve — start the HTTP server
91
+
92
+ Options:
93
+ --port <n> Port number (default: from config, 4097)
94
+ --no-auth Disable HTTP basic auth requirement
95
+ --cors <origins> Comma-separated allowed origins (default: from config, "*")
96
+ --force Start even when NDOMO_HTTP_ENABLED is not "true"
97
+ --help, -h Show this help`);
98
+ process.exit(0);
99
+ }
100
+ }
101
+
102
+ return result;
103
+ }
104
+
105
+ /** Print startup banner. */
106
+ function printBanner(config: HttpConfig, dbPath: string): void {
107
+ console.log(`
108
+ ┌─────────────────────────────────────┐
109
+ │ ndomo HTTP server │
110
+ ├─────────────────────────────────────┤
111
+ │ port: ${String(config.port).padEnd(25)}│
112
+ │ auth: ${(config.auth.required ? "enabled" : "disabled").padEnd(25)}│
113
+ │ cors: ${config.cors.origins.join(", ").padEnd(25).slice(0, 25)}│
114
+ │ db: ${dbPath.slice(-25).padEnd(25)}│
115
+ │ pid: ${String(process.pid).padEnd(25)}│
116
+ └─────────────────────────────────────┘`);
117
+ }
118
+
119
+ /** Main entry point. */
120
+ export async function runServe(args: string[]): Promise<void> {
121
+ const opts = parseArgs(args);
122
+
123
+ // Resolve DB
124
+ const dbPath = resolveDbPath();
125
+ if (!dbPath) {
126
+ console.error("error: .ndomo/state.db not found — run from project root or parent dir");
127
+ process.exit(1);
128
+ }
129
+
130
+ // Open DB
131
+ const db = new Database(dbPath);
132
+ db.exec("PRAGMA foreign_keys = ON");
133
+ runMigrations(db);
134
+
135
+ // Load config + apply CLI overrides
136
+ const config = loadHttpConfig();
137
+
138
+ if (opts.port !== undefined) config.port = opts.port;
139
+ if (opts.authRequired !== undefined) config.auth.required = opts.authRequired;
140
+ if (opts.corsOrigins !== undefined) config.cors.origins = opts.corsOrigins;
141
+
142
+ // Feature gate: require NDOMO_HTTP_ENABLED=true unless --force
143
+ if (!config.enabled && !opts.force) {
144
+ console.error(
145
+ "error: HTTP server is disabled (NDOMO_HTTP_ENABLED is not 'true'). Use --force to override.",
146
+ );
147
+ db.close();
148
+ process.exit(1);
149
+ }
150
+
151
+ printBanner(config, dbPath);
152
+
153
+ // Start server
154
+ let serverHandle: HttpServerHandle;
155
+ try {
156
+ serverHandle = await startHttpServer({ db, httpConfig: config });
157
+ } catch (err) {
158
+ console.error(
159
+ `error: failed to start HTTP server: ${err instanceof Error ? err.message : String(err)}`,
160
+ );
161
+ db.close();
162
+ process.exit(1);
163
+ }
164
+
165
+ console.log(`✓ HTTP server listening on port ${serverHandle.port}`);
166
+
167
+ // Graceful shutdown
168
+ let shuttingDown = false;
169
+ const shutdown = async (signal: string) => {
170
+ if (shuttingDown) return;
171
+ shuttingDown = true;
172
+ console.log(`\n${signal} received — shutting down...`);
173
+ try {
174
+ await serverHandle.stop();
175
+ db.close();
176
+ console.log("✓ Server stopped cleanly.");
177
+ process.exit(0);
178
+ } catch (err) {
179
+ console.error(`error during shutdown: ${err instanceof Error ? err.message : String(err)}`);
180
+ db.close();
181
+ process.exit(1);
182
+ }
183
+ };
184
+
185
+ process.on("SIGINT", () => shutdown("SIGINT"));
186
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
187
+ }
188
+
189
+ // Direct execution
190
+ if (import.meta.main) {
191
+ runServe(process.argv.slice(2));
192
+ }