@aitne-sh/aitne 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 (249) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +464 -0
  3. package/agent-assets/agent-profiles/_safety.md +26 -0
  4. package/agent-assets/agent-profiles/conversational.md +33 -0
  5. package/agent-assets/agent-profiles/docs-qa.md +24 -0
  6. package/agent-assets/agent-profiles/observer.md +28 -0
  7. package/agent-assets/agent-profiles/profile-importer.md +63 -0
  8. package/agent-assets/agent-profiles/proxy.md +28 -0
  9. package/agent-assets/agent-profiles/routine.md +16 -0
  10. package/agent-assets/agent-profiles/task.md +18 -0
  11. package/agent-assets/docs/concepts/agent-day.md +88 -0
  12. package/agent-assets/docs/concepts/auth-health.md +75 -0
  13. package/agent-assets/docs/concepts/backends-and-tiers.md +126 -0
  14. package/agent-assets/docs/concepts/costs-and-quotas.md +103 -0
  15. package/agent-assets/docs/concepts/delegated-mode.md +223 -0
  16. package/agent-assets/docs/concepts/memory-model.md +118 -0
  17. package/agent-assets/docs/concepts/observations.md +80 -0
  18. package/agent-assets/docs/concepts/process-keys.md +89 -0
  19. package/agent-assets/docs/concepts/routines.md +108 -0
  20. package/agent-assets/docs/concepts/safety-and-execution.md +109 -0
  21. package/agent-assets/docs/concepts/safety-model.md +279 -0
  22. package/agent-assets/docs/concepts/skills.md +100 -0
  23. package/agent-assets/docs/features/integrations/calendar.md +92 -0
  24. package/agent-assets/docs/features/integrations/git.md +95 -0
  25. package/agent-assets/docs/features/integrations/github.md +170 -0
  26. package/agent-assets/docs/features/integrations/mail.md +106 -0
  27. package/agent-assets/docs/features/integrations/notion.md +69 -0
  28. package/agent-assets/docs/features/integrations/obsidian.md +71 -0
  29. package/agent-assets/docs/features/lifestyle/git.md +178 -0
  30. package/agent-assets/docs/features/lifestyle/reading.md +93 -0
  31. package/agent-assets/docs/features/lifestyle/receipts.md +71 -0
  32. package/agent-assets/docs/features/lifestyle/travel-bookings.md +44 -0
  33. package/agent-assets/docs/features/lifestyle/travel-time.md +52 -0
  34. package/agent-assets/docs/features/memory-files/agent-journal.md +105 -0
  35. package/agent-assets/docs/features/memory-files/projects.md +56 -0
  36. package/agent-assets/docs/features/memory-files/roadmap.md +61 -0
  37. package/agent-assets/docs/features/memory-files/schedule.md +112 -0
  38. package/agent-assets/docs/features/memory-files/today.md +73 -0
  39. package/agent-assets/docs/features/memory-files/user-profile.md +81 -0
  40. package/agent-assets/docs/features/messaging/dashboard-chat.md +93 -0
  41. package/agent-assets/docs/features/messaging/discord.md +50 -0
  42. package/agent-assets/docs/features/messaging/overview.md +111 -0
  43. package/agent-assets/docs/features/messaging/pairing-and-magic-phrase.md +69 -0
  44. package/agent-assets/docs/features/messaging/slack.md +51 -0
  45. package/agent-assets/docs/features/messaging/telegram.md +63 -0
  46. package/agent-assets/docs/features/messaging/whatsapp.md +48 -0
  47. package/agent-assets/docs/features/operations/activity-and-conversations.md +105 -0
  48. package/agent-assets/docs/features/operations/approvals.md +58 -0
  49. package/agent-assets/docs/features/operations/backend-routing.md +62 -0
  50. package/agent-assets/docs/features/operations/cost-tracking.md +59 -0
  51. package/agent-assets/docs/features/operations/notifications.md +69 -0
  52. package/agent-assets/docs/features/operations/quiet-hours.md +106 -0
  53. package/agent-assets/docs/features/operations/schedule-approaching.md +60 -0
  54. package/agent-assets/docs/features/routines/custom-routines.md +101 -0
  55. package/agent-assets/docs/features/routines/evening-review.md +81 -0
  56. package/agent-assets/docs/features/routines/hourly-check.md +85 -0
  57. package/agent-assets/docs/features/routines/monthly-review.md +65 -0
  58. package/agent-assets/docs/features/routines/morning-routine.md +123 -0
  59. package/agent-assets/docs/features/routines/weekly-review.md +70 -0
  60. package/agent-assets/docs/getting-started/01-what-is-this.md +192 -0
  61. package/agent-assets/docs/getting-started/02-first-steps.md +80 -0
  62. package/agent-assets/docs/getting-started/03-what-can-this-do.md +110 -0
  63. package/agent-assets/docs/getting-started/04-first-day.md +287 -0
  64. package/agent-assets/docs/glossary.md +116 -0
  65. package/agent-assets/docs/guides/add-a-custom-routine.md +71 -0
  66. package/agent-assets/docs/guides/backup-and-restore.md +54 -0
  67. package/agent-assets/docs/guides/change-which-model-handles-x.md +47 -0
  68. package/agent-assets/docs/guides/connect-a-new-mail-account.md +59 -0
  69. package/agent-assets/docs/guides/import-knowledge-file.md +275 -0
  70. package/agent-assets/docs/guides/install-and-run.md +72 -0
  71. package/agent-assets/docs/guides/migrate-machines.md +52 -0
  72. package/agent-assets/docs/guides/pause-the-agent.md +65 -0
  73. package/agent-assets/docs/guides/reinstall-cleanly.md +52 -0
  74. package/agent-assets/docs/guides/setup-wizard.md +107 -0
  75. package/agent-assets/docs/guides/switch-default-backend.md +60 -0
  76. package/agent-assets/docs/reference/api.md +51 -0
  77. package/agent-assets/docs/reference/cli-commands.md +121 -0
  78. package/agent-assets/docs/reference/config.md +74 -0
  79. package/agent-assets/docs/reference/disallowed-tools.md +76 -0
  80. package/agent-assets/docs/reference/keyboard-shortcuts.md +39 -0
  81. package/agent-assets/docs/reference/process-keys.md +59 -0
  82. package/agent-assets/docs/reference/skills.md +50 -0
  83. package/agent-assets/docs/troubleshooting/auth-failed.md +57 -0
  84. package/agent-assets/docs/troubleshooting/dashboard-shows-degraded.md +55 -0
  85. package/agent-assets/docs/troubleshooting/fallback-keeps-firing.md +54 -0
  86. package/agent-assets/docs/troubleshooting/messaging-not-pairing.md +53 -0
  87. package/agent-assets/docs/troubleshooting/morning-routine-didnt-run.md +75 -0
  88. package/agent-assets/docs/troubleshooting/observation-not-detected.md +57 -0
  89. package/agent-assets/docs/troubleshooting/quota-exhausted.md +57 -0
  90. package/agent-assets/optimizer-skills/drift-analysis/SKILL.md +75 -0
  91. package/agent-assets/optimizer-skills/knowledge-map/SKILL.md +71 -0
  92. package/agent-assets/optimizer-skills/skill-curation/SKILL.md +108 -0
  93. package/agent-assets/project-doc-templates/git-repo.md +21 -0
  94. package/agent-assets/project-doc-templates/project.md +38 -0
  95. package/agent-assets/skills/attach/SKILL.md +104 -0
  96. package/agent-assets/skills/context/SKILL.md +257 -0
  97. package/agent-assets/skills/context/curation.json +37 -0
  98. package/agent-assets/skills/context/seeds/file-responsibilities.seed.json +13 -0
  99. package/agent-assets/skills/context/seeds/frontmatter-requirements.seed.json +40 -0
  100. package/agent-assets/skills/docs-search/SKILL.md +176 -0
  101. package/agent-assets/skills/external-services/SKILL.delegated.claude.md +369 -0
  102. package/agent-assets/skills/external-services/SKILL.delegated.codex.md +349 -0
  103. package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +347 -0
  104. package/agent-assets/skills/external-services/SKILL.md +371 -0
  105. package/agent-assets/skills/mail/SKILL.delegated.claude.md +284 -0
  106. package/agent-assets/skills/mail/SKILL.delegated.codex.md +261 -0
  107. package/agent-assets/skills/mail/SKILL.delegated.gemini.md +255 -0
  108. package/agent-assets/skills/mail/SKILL.md +313 -0
  109. package/agent-assets/skills/mail/references/errors.md +17 -0
  110. package/agent-assets/skills/mail/references/providers.md +40 -0
  111. package/agent-assets/skills/mail/references/query-grammar.md +24 -0
  112. package/agent-assets/skills/management-policy/SKILL.md +307 -0
  113. package/agent-assets/skills/management-policy/curation.json +13 -0
  114. package/agent-assets/skills/management-policy/seeds/policy-file-shape.seed.json +16 -0
  115. package/agent-assets/skills/management-task-modify/SKILL.md +202 -0
  116. package/agent-assets/skills/management-task-register/SKILL.md +330 -0
  117. package/agent-assets/skills/management-task-stop/SKILL.md +166 -0
  118. package/agent-assets/skills/notify/SKILL.md +196 -0
  119. package/agent-assets/skills/notion/SKILL.delegated.claude.md +254 -0
  120. package/agent-assets/skills/notion/SKILL.delegated.codex.md +195 -0
  121. package/agent-assets/skills/notion/SKILL.delegated.gemini.md +194 -0
  122. package/agent-assets/skills/notion/SKILL.md +86 -0
  123. package/agent-assets/skills/observations/SKILL.md +234 -0
  124. package/agent-assets/skills/observations/curation.json +13 -0
  125. package/agent-assets/skills/observations/seeds/source-namespacing.seed.json +20 -0
  126. package/agent-assets/skills/project-doc/SKILL.md +86 -0
  127. package/agent-assets/skills/project-doc/curation.json +21 -0
  128. package/agent-assets/skills/project-doc/seeds/project-shape.seed.json +25 -0
  129. package/agent-assets/skills/project-doc/seeds/slug-grammar.seed.json +20 -0
  130. package/agent-assets/skills/reading/SKILL.md +198 -0
  131. package/agent-assets/skills/reading/references/reading-taste.md +197 -0
  132. package/agent-assets/skills/receipts/SKILL.md +134 -0
  133. package/agent-assets/skills/roadmap/SKILL.md +276 -0
  134. package/agent-assets/skills/roadmap/curation.json +13 -0
  135. package/agent-assets/skills/roadmap/references/horizon-tags.md +40 -0
  136. package/agent-assets/skills/roadmap/references/preparation-timeline.md +47 -0
  137. package/agent-assets/skills/roadmap/seeds/entry-types.seed.json +16 -0
  138. package/agent-assets/skills/schedule/SKILL.md +228 -0
  139. package/agent-assets/skills/scheduled-managed-task/SKILL.md +392 -0
  140. package/agent-assets/skills/today/SKILL.md +198 -0
  141. package/agent-assets/skills/today/curation.json +21 -0
  142. package/agent-assets/skills/today/seeds/agent-notes-flavors.seed.json +17 -0
  143. package/agent-assets/skills/today/seeds/section-shape.seed.json +17 -0
  144. package/agent-assets/skills/travel/SKILL.md +132 -0
  145. package/agent-assets/skills/travel-time/SKILL.md +149 -0
  146. package/agent-assets/skills/user-interview/SKILL.md +323 -0
  147. package/agent-assets/skills/user-interview/references/sweep-and-fallback.md +94 -0
  148. package/agent-assets/skills/user-profile/SKILL.md +210 -0
  149. package/agent-assets/skills/user-profile/curation.json +29 -0
  150. package/agent-assets/skills/user-profile/seeds/learned-context-format.seed.json +14 -0
  151. package/agent-assets/skills/user-profile/seeds/routing-table.seed.json +53 -0
  152. package/agent-assets/skills/user-profile/seeds/topic-files.seed.json +27 -0
  153. package/agent-assets/task-flows/dashboard.docs_qa.md +43 -0
  154. package/agent-assets/task-flows/default.md +11 -0
  155. package/agent-assets/task-flows/git.branch.created.md +25 -0
  156. package/agent-assets/task-flows/git.lifecycle.poll.md +52 -0
  157. package/agent-assets/task-flows/git.local_ahead.stale.md +34 -0
  158. package/agent-assets/task-flows/git.merge_to_default.md +30 -0
  159. package/agent-assets/task-flows/git.project.refresh_architecture.md +100 -0
  160. package/agent-assets/task-flows/git.project.retemplate.md +73 -0
  161. package/agent-assets/task-flows/git.push.detected.md +32 -0
  162. package/agent-assets/task-flows/git.push.force_pushed.md +36 -0
  163. package/agent-assets/task-flows/git.tag.created.md +24 -0
  164. package/agent-assets/task-flows/github.assigned.md +43 -0
  165. package/agent-assets/task-flows/github.pull_request.review_requested.md +57 -0
  166. package/agent-assets/task-flows/github.security_alert.md +45 -0
  167. package/agent-assets/task-flows/github.workflow_run.failed.md +57 -0
  168. package/agent-assets/task-flows/knowledge.import.md +161 -0
  169. package/agent-assets/task-flows/message.received.dm.md +142 -0
  170. package/agent-assets/task-flows/message.received.dm_first.md +117 -0
  171. package/agent-assets/task-flows/message.received.md +14 -0
  172. package/agent-assets/task-flows/routine.custom.md +38 -0
  173. package/agent-assets/task-flows/routine.evening_review.md +323 -0
  174. package/agent-assets/task-flows/routine.hourly_check.delegated.claude.md +405 -0
  175. package/agent-assets/task-flows/routine.hourly_check.delegated.codex.md +400 -0
  176. package/agent-assets/task-flows/routine.hourly_check.delegated.gemini.md +404 -0
  177. package/agent-assets/task-flows/routine.hourly_check.md +184 -0
  178. package/agent-assets/task-flows/routine.hourly_check.triage.md +93 -0
  179. package/agent-assets/task-flows/routine.monthly_review.md +250 -0
  180. package/agent-assets/task-flows/routine.morning_routine.md +300 -0
  181. package/agent-assets/task-flows/routine.morning_routine_initial.md +184 -0
  182. package/agent-assets/task-flows/routine.roadmap_refresh.md +275 -0
  183. package/agent-assets/task-flows/routine.today_refresh.md +172 -0
  184. package/agent-assets/task-flows/routine.user_profile_sweep.md +242 -0
  185. package/agent-assets/task-flows/routine.weekly_review.md +247 -0
  186. package/agent-assets/task-flows/schedule.approaching.md +124 -0
  187. package/agent-assets/task-flows/scheduled.dm.md +391 -0
  188. package/agent-assets/task-flows/scheduled.task.md +141 -0
  189. package/agent-assets/task-flows/setup.initial.md +277 -0
  190. package/agent-assets/task-flows/setup.update.md +53 -0
  191. package/agent-assets/templates/README.md +85 -0
  192. package/agent-assets/templates/_index.md +39 -0
  193. package/agent-assets/templates/_manifest.json +103 -0
  194. package/agent-assets/templates/agent/journal.md +10 -0
  195. package/agent-assets/templates/agent/profile-questions.md +74 -0
  196. package/agent-assets/templates/context-index.md +42 -0
  197. package/agent-assets/templates/dossiers/_index.md +22 -0
  198. package/agent-assets/templates/dossiers/evening.md +23 -0
  199. package/agent-assets/templates/dossiers/hourly.md +23 -0
  200. package/agent-assets/templates/dossiers/monthly.md +23 -0
  201. package/agent-assets/templates/dossiers/morning.md +23 -0
  202. package/agent-assets/templates/dossiers/roadmap.md +23 -0
  203. package/agent-assets/templates/dossiers/weekly.md +23 -0
  204. package/agent-assets/templates/projects/_active.base +14 -0
  205. package/agent-assets/templates/projects/_index.md +29 -0
  206. package/agent-assets/templates/roadmap.md +15 -0
  207. package/agent-assets/templates/routines/_index.md +20 -0
  208. package/agent-assets/templates/routines/evening.md +22 -0
  209. package/agent-assets/templates/routines/hourly.md +30 -0
  210. package/agent-assets/templates/routines/monthly.md +25 -0
  211. package/agent-assets/templates/routines/morning.md +26 -0
  212. package/agent-assets/templates/routines/weekly.md +23 -0
  213. package/agent-assets/templates/rules/_index.md +19 -0
  214. package/agent-assets/templates/rules/journal-export.md +41 -0
  215. package/agent-assets/templates/rules/journal-format.md +61 -0
  216. package/agent-assets/templates/rules/management.md +48 -0
  217. package/agent-assets/templates/rules/mcp.md +40 -0
  218. package/agent-assets/templates/rules/policies/_index.md +22 -0
  219. package/agent-assets/templates/rules/redaction.md +30 -0
  220. package/agent-assets/templates/today.md +13 -0
  221. package/agent-assets/templates/user/_index.md +16 -0
  222. package/agent-assets/templates/user/expertise.md +7 -0
  223. package/agent-assets/templates/user/goals.md +7 -0
  224. package/agent-assets/templates/user/people.md +7 -0
  225. package/agent-assets/templates/user/personal.md +7 -0
  226. package/agent-assets/templates/user/profile.md +28 -0
  227. package/agent-assets/templates/user/work.md +7 -0
  228. package/bin/aitne.mjs +1096 -0
  229. package/package.json +78 -0
  230. package/personal-agent.mjs +39 -0
  231. package/scripts/browser.mjs +99 -0
  232. package/scripts/check-redaction-coverage.mjs +109 -0
  233. package/scripts/commands/audit.mjs +309 -0
  234. package/scripts/commands/doctor.mjs +437 -0
  235. package/scripts/commands/open.mjs +40 -0
  236. package/scripts/commands/setup.mjs +21 -0
  237. package/scripts/commands/uninstall.mjs +114 -0
  238. package/scripts/commands/update.mjs +96 -0
  239. package/scripts/commands/version.mjs +62 -0
  240. package/scripts/commands.md +0 -0
  241. package/scripts/lib/sqlite-loader.mjs +49 -0
  242. package/scripts/message-discipline-digest.mjs +535 -0
  243. package/scripts/poc/google-connector-inheritance/REPORT.md +197 -0
  244. package/scripts/poc/google-connector-inheritance/claude-sdk-probe.mjs +79 -0
  245. package/scripts/remint-roadmap-ids.mjs +257 -0
  246. package/scripts/rm-paths.mjs +22 -0
  247. package/scripts/run-node.mjs +223 -0
  248. package/scripts/smoke-obsidian-api.mjs +166 -0
  249. package/scripts/start.mjs +160 -0
@@ -0,0 +1,437 @@
1
+ /**
2
+ * `aitne doctor` — diagnose install-time problems.
3
+ *
4
+ * Eight independent checks. Each returns a status (pass / warn / fail), a
5
+ * short detail line, and an optional hint that tells the user what to do.
6
+ * The single most common failure mode for new users is "I installed it but
7
+ * something is wrong" — doctor narrows that to "row N failed: do X."
8
+ *
9
+ * Doctor is read-only and offline. No daemon is started, no DB is mutated,
10
+ * no network call is made. Safe to run at any time.
11
+ */
12
+ import { execFileSync } from "node:child_process";
13
+ import fs from "node:fs";
14
+ import net from "node:net";
15
+ import path from "node:path";
16
+
17
+ export async function run(args, ctx) {
18
+ if (args.includes("--help") || args.includes("-h")) {
19
+ console.log(`Usage: aitne doctor [--json]
20
+
21
+ Run a series of install-health checks and report pass/warn/fail. Useful as
22
+ a first step when triaging "it doesn't work" — most first-install failures
23
+ are exactly one of these checks.
24
+
25
+ Flags:
26
+ --json Machine-readable output. Implies no terminal formatting.
27
+
28
+ Exit code:
29
+ 0 All checks pass (warnings are tolerated).
30
+ 1 At least one check failed.`);
31
+ return;
32
+ }
33
+
34
+ const checks = [
35
+ await checkNodeVersion(),
36
+ await checkPort("Daemon port", ctx.DAEMON_PORT, ctx.DAEMON_PID_FILE, ctx.helpers.getRunningPid),
37
+ await checkPort("Dashboard port", ctx.DASHBOARD_PORT, ctx.DASHBOARD_PID_FILE, ctx.helpers.getRunningPid),
38
+ await checkSecretStore(ctx),
39
+ await checkBackendCli(),
40
+ await checkDataDirWritable(ctx.DATA_DIR),
41
+ await checkBetterSqlite3(ctx.PROJECT_ROOT),
42
+ await checkAgentAssets(ctx.PROJECT_ROOT),
43
+ ...(await checkRepositoryGithubLinkDrift(ctx.DATA_DIR)),
44
+ ];
45
+
46
+ const passed = checks.filter((c) => c.status === "pass").length;
47
+ const warned = checks.filter((c) => c.status === "warn").length;
48
+ const failed = checks.filter((c) => c.status === "fail").length;
49
+
50
+ if (args.includes("--json")) {
51
+ process.stdout.write(JSON.stringify({ checks, passed, warned, failed }, null, 2) + "\n");
52
+ process.exit(failed > 0 ? 1 : 0);
53
+ }
54
+
55
+ console.log(`${ctx.APP_NAME} v${ctx.VERSION} doctor — ${checks.length} checks`);
56
+ console.log("");
57
+
58
+ // Right-pad label to widest so the detail column lines up.
59
+ const labelWidth = Math.max(...checks.map((c) => c.label.length));
60
+ for (const c of checks) {
61
+ const mark = c.status === "pass" ? "ok " : c.status === "warn" ? "warn" : "FAIL";
62
+ const label = c.label.padEnd(labelWidth);
63
+ console.log(` [${mark}] ${label} ${c.detail}`);
64
+ if (c.hint && c.status !== "pass") {
65
+ console.log(` ${" ".repeat(labelWidth)} hint: ${c.hint}`);
66
+ }
67
+ }
68
+ console.log("");
69
+ console.log(`${passed} ok · ${warned} warn · ${failed} fail`);
70
+ process.exit(failed > 0 ? 1 : 0);
71
+ }
72
+
73
+ // ─────────────────────────────────────────────────────────────────────────
74
+ // Individual checks. Each is an async function returning
75
+ // { status, label, detail, hint? }. Independent — never throw.
76
+ // ─────────────────────────────────────────────────────────────────────────
77
+
78
+ async function checkNodeVersion() {
79
+ const v = process.versions.node; // e.g. "22.10.0"
80
+ const major = parseInt(v.split(".")[0], 10) || 0;
81
+ if (major >= 22) {
82
+ return { status: "pass", label: "Node version", detail: `v${v} (>= 22)` };
83
+ }
84
+ return {
85
+ status: "fail",
86
+ label: "Node version",
87
+ detail: `v${v} — too old (need >= 22)`,
88
+ hint: "Install Node 22 LTS, then re-run. `corepack enable` is bundled with 22+.",
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Check whether a port is either (a) bindable, or (b) already held by our own
94
+ * PID file. If neither, we surface a fail — something else is on the port and
95
+ * the user needs to know.
96
+ *
97
+ * `getRunningPid` is passed in (rather than re-implemented inline) so the
98
+ * stale-PID handling stays consistent with `aitne start`'s view of "is our
99
+ * daemon up?". Inlining it would drift over time.
100
+ */
101
+ async function checkPort(label, port, pidFile, getRunningPid) {
102
+ // (b) — our own daemon already running.
103
+ const pid = getRunningPid(pidFile);
104
+ if (pid != null) {
105
+ return {
106
+ status: "pass",
107
+ label,
108
+ detail: `${port} held by ${label.split(" ")[0].toLowerCase()} PID ${pid}`,
109
+ };
110
+ }
111
+
112
+ // (a) — try to bind. If success, port is free.
113
+ const free = await new Promise((resolve) => {
114
+ const sock = net.createServer();
115
+ sock.unref();
116
+ sock.once("error", () => { resolve(false); });
117
+ sock.listen(port, "127.0.0.1", () => {
118
+ sock.close(() => resolve(true));
119
+ });
120
+ });
121
+
122
+ if (free) return { status: "pass", label, detail: `${port} free` };
123
+ return {
124
+ status: "fail",
125
+ label,
126
+ detail: `${port} in use by another process`,
127
+ hint: label.startsWith("Daemon")
128
+ ? `Set PA_API_PORT to an open port (e.g. PA_API_PORT=8322 aitne start), or stop the conflicting process.`
129
+ : `Set PA_DASHBOARD_PORT to an open port (e.g. PA_DASHBOARD_PORT=3001 aitne start), or stop the conflicting process.`,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * OS-specific secret-store probe. Per README §Platform support:
135
+ * - macOS: `security` CLI is in-box; just verify it executes.
136
+ * - Linux: `secret-tool` is preferred; the file fallback needs
137
+ * PA_MASTER_PASSWORD or a keyfile present.
138
+ * - Windows: PowerShell DPAPI is in-box; prefer `powershell.exe` and
139
+ * fall back to `pwsh.exe`.
140
+ *
141
+ * Failure here is "warn" (not "fail") because the daemon's
142
+ * file-store fallback is a documented graceful path on Linux.
143
+ */
144
+ async function checkSecretStore(ctx) {
145
+ const platform = process.platform;
146
+ if (platform === "darwin") {
147
+ try {
148
+ execFileSync("security", ["list-keychains", "-d", "user"], { stdio: "pipe", timeout: 3000 });
149
+ return { status: "pass", label: "Secret store", detail: "macOS Keychain reachable" };
150
+ } catch (err) {
151
+ return {
152
+ status: "fail",
153
+ label: "Secret store",
154
+ detail: `macOS \`security\` CLI failed: ${err?.message ?? "unknown"}`,
155
+ hint: "macOS ships `security`; this should never fail. Check $PATH.",
156
+ };
157
+ }
158
+ }
159
+ if (platform === "linux") {
160
+ try {
161
+ execFileSync("secret-tool", ["--version"], { stdio: "pipe", timeout: 3000 });
162
+ return { status: "pass", label: "Secret store", detail: "libsecret (`secret-tool`) reachable" };
163
+ } catch {
164
+ const hasMaster = !!process.env.PA_MASTER_PASSWORD;
165
+ const keyfile = path.join(ctx.DATA_DIR, "secrets", ".master-key");
166
+ const hasKeyfile = fs.existsSync(keyfile);
167
+ if (hasMaster || hasKeyfile) {
168
+ return {
169
+ status: "pass",
170
+ label: "Secret store",
171
+ detail: hasMaster ? "file store with PA_MASTER_PASSWORD" : "file store with keyfile",
172
+ };
173
+ }
174
+ return {
175
+ status: "warn",
176
+ label: "Secret store",
177
+ detail: "no `secret-tool`, no PA_MASTER_PASSWORD, no keyfile",
178
+ hint: "apt install libsecret-tools · or set PA_MASTER_PASSWORD before first run (README §Linux setup)",
179
+ };
180
+ }
181
+ }
182
+ if (platform === "win32") {
183
+ const psBinary = whichSync("powershell.exe") ? "powershell.exe" : "pwsh.exe";
184
+ try {
185
+ execFileSync(psBinary, [
186
+ "-NoProfile", "-Command",
187
+ "[System.Security.Cryptography.ProtectedData] | Out-Null; exit 0",
188
+ ], { stdio: "pipe", timeout: 5000 });
189
+ return { status: "pass", label: "Secret store", detail: `Windows DPAPI via ${psBinary} reachable` };
190
+ } catch (err) {
191
+ return {
192
+ status: "fail",
193
+ label: "Secret store",
194
+ detail: `PowerShell DPAPI probe failed: ${err?.message ?? "unknown"}`,
195
+ hint: "PowerShell ships with Windows; check $PATH or install PowerShell 7+ (`pwsh.exe`).",
196
+ };
197
+ }
198
+ }
199
+ return {
200
+ status: "warn",
201
+ label: "Secret store",
202
+ detail: `unknown platform ${platform}`,
203
+ hint: "Supported: darwin, linux, win32. File a bug if your platform is reasonable.",
204
+ };
205
+ }
206
+
207
+ async function checkBackendCli() {
208
+ const candidates = ["claude", "codex", "gemini"];
209
+ const found = [];
210
+ for (const name of candidates) {
211
+ if (whichSync(name)) found.push(name);
212
+ }
213
+ if (found.length > 0) {
214
+ return { status: "pass", label: "Backend CLI", detail: `${found.join(", ")} on PATH` };
215
+ }
216
+ return {
217
+ status: "warn",
218
+ label: "Backend CLI",
219
+ detail: "none of claude/codex/gemini found on PATH",
220
+ hint: "Install at least one (Claude Code recommended). The setup wizard can guide you.",
221
+ };
222
+ }
223
+
224
+ async function checkDataDirWritable(dataDir) {
225
+ try {
226
+ fs.mkdirSync(dataDir, { recursive: true });
227
+ const probe = path.join(dataDir, `.doctor-probe-${process.pid}`);
228
+ fs.writeFileSync(probe, "ok");
229
+ fs.unlinkSync(probe);
230
+ return { status: "pass", label: "Data dir writable", detail: dataDir };
231
+ } catch (err) {
232
+ return {
233
+ status: "fail",
234
+ label: "Data dir writable",
235
+ detail: `${dataDir}: ${err?.message ?? "unknown"}`,
236
+ hint: "Check permissions on the parent directory, or set PA_DATA_DIR to a writable path.",
237
+ };
238
+ }
239
+ }
240
+
241
+ async function checkBetterSqlite3(projectRoot) {
242
+ try {
243
+ const { loadBetterSqlite3 } = await import("../lib/sqlite-loader.mjs");
244
+ const Database = await loadBetterSqlite3(projectRoot);
245
+ // Open an in-memory DB to exercise the native binding fully.
246
+ const db = new Database(":memory:");
247
+ db.close();
248
+ return { status: "pass", label: "better-sqlite3", detail: "native binding loads" };
249
+ } catch (err) {
250
+ return {
251
+ status: "fail",
252
+ label: "better-sqlite3",
253
+ detail: `failed to load: ${err?.message ?? "unknown"}`,
254
+ hint: "Reinstall the package — your platform may have downloaded a corrupt prebuild. `pnpm rebuild better-sqlite3` from the dev repo.",
255
+ };
256
+ }
257
+ }
258
+
259
+ async function checkAgentAssets(projectRoot) {
260
+ const skillsDir = path.join(projectRoot, "agent-assets", "skills");
261
+ if (fs.existsSync(skillsDir) && fs.statSync(skillsDir).isDirectory()) {
262
+ let entries = 0;
263
+ try { entries = fs.readdirSync(skillsDir).length; } catch { /* permission edge */ }
264
+ return { status: "pass", label: "agent-assets", detail: `${entries} skill(s) at ${skillsDir}` };
265
+ }
266
+ return {
267
+ status: "fail",
268
+ label: "agent-assets",
269
+ detail: `missing: ${skillsDir}`,
270
+ hint: "The package looks corrupt. Reinstall: `npm install -g aitne@latest`.",
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Per-row drift check for unified repositories rows that pair a GitHub
276
+ * remote with a local clone. Resolves `git -C <local_path> remote get-url
277
+ * origin` and compares to the registered `<owner>/<repo>`. See
278
+ * `docs/design/appendices/unified-repositories.md` §11.1 for the lock.
279
+ *
280
+ * Returns one check row per drifted (or origin-less) repository, plus a
281
+ * single summary row when nothing drifts. The doctor is informational —
282
+ * fixes happen via /api/repositories or the dashboard.
283
+ */
284
+ async function checkRepositoryGithubLinkDrift(dataDir) {
285
+ const dbPath = path.join(dataDir, "data", "personal_agent.db");
286
+ if (!fs.existsSync(dbPath)) {
287
+ return [
288
+ {
289
+ status: "pass",
290
+ label: "Repository drift",
291
+ detail: "no DB yet (skipped — run aitne start first)",
292
+ },
293
+ ];
294
+ }
295
+
296
+ // Lazy-load better-sqlite3 from the daemon package so doctor stays
297
+ // dependency-light; if the native binding is missing we fall through
298
+ // to a pass with a hint (rather than fail the whole doctor pass).
299
+ let Database;
300
+ try {
301
+ const candidates = [
302
+ path.join(process.cwd(), "node_modules", "better-sqlite3", "lib", "index.js"),
303
+ path.join(process.cwd(), "packages", "daemon", "node_modules", "better-sqlite3", "lib", "index.js"),
304
+ ];
305
+ const found = candidates.find((p) => fs.existsSync(p));
306
+ if (!found) {
307
+ return [
308
+ {
309
+ status: "warn",
310
+ label: "Repository drift",
311
+ detail: "better-sqlite3 not resolvable from doctor — skipped",
312
+ hint: "Run `pnpm install` in the repo, then re-run aitne doctor.",
313
+ },
314
+ ];
315
+ }
316
+ Database = (await import(found)).default ?? (await import(found));
317
+ } catch (err) {
318
+ return [
319
+ {
320
+ status: "warn",
321
+ label: "Repository drift",
322
+ detail: `better-sqlite3 load failed: ${err?.message ?? "unknown"}`,
323
+ hint: "Re-run `pnpm install` to rebuild the native binding.",
324
+ },
325
+ ];
326
+ }
327
+
328
+ let rows;
329
+ try {
330
+ const db = new Database(dbPath, { readonly: true });
331
+ rows = db
332
+ .prepare(
333
+ `SELECT id, github_owner, github_repo, local_path, display_name
334
+ FROM repositories
335
+ WHERE github_owner IS NOT NULL
336
+ AND github_repo IS NOT NULL
337
+ AND local_path IS NOT NULL
338
+ AND local_only = 0`,
339
+ )
340
+ .all();
341
+ db.close();
342
+ } catch (err) {
343
+ return [
344
+ {
345
+ status: "warn",
346
+ label: "Repository drift",
347
+ detail: `DB read failed: ${err?.message ?? "unknown"}`,
348
+ hint: "Stop the daemon (aitne stop) and re-run, or check DB integrity.",
349
+ },
350
+ ];
351
+ }
352
+
353
+ if (rows.length === 0) {
354
+ return [
355
+ {
356
+ status: "pass",
357
+ label: "Repository drift",
358
+ detail: "no GitHub-paired rows with a local clone",
359
+ },
360
+ ];
361
+ }
362
+
363
+ const drifted = [];
364
+ for (const row of rows) {
365
+ const expected = `${row.github_owner}/${row.github_repo}`;
366
+ let originUrl = "";
367
+ try {
368
+ const out = execFileSync("git", ["-C", row.local_path, "remote", "get-url", "origin"], {
369
+ stdio: ["ignore", "pipe", "pipe"],
370
+ timeout: 3000,
371
+ });
372
+ originUrl = out.toString().trim();
373
+ } catch {
374
+ drifted.push({ row, expected, actual: null });
375
+ continue;
376
+ }
377
+ const actual = parseGithubOwnerRepo(originUrl);
378
+ if (!actual || actual.toLowerCase() !== expected.toLowerCase()) {
379
+ drifted.push({ row, expected, actual });
380
+ }
381
+ }
382
+
383
+ if (drifted.length === 0) {
384
+ return [
385
+ {
386
+ status: "pass",
387
+ label: "Repository drift",
388
+ detail: `${rows.length} paired row(s) — origin matches`,
389
+ },
390
+ ];
391
+ }
392
+
393
+ return drifted.map(({ row, expected, actual }) => ({
394
+ status: "warn",
395
+ label: `Repository drift`,
396
+ detail: `'${row.display_name ?? expected}' — registered ${expected}, origin ${actual ?? "(none)"}`,
397
+ hint: `clone: ${row.local_path} — re-link to actual / mark local-only / unlink local clone via the dashboard`,
398
+ }));
399
+ }
400
+
401
+ /**
402
+ * Parse `<owner>/<repo>` out of a GitHub remote URL. Supports
403
+ * - https://github.com/owner/repo(.git)?
404
+ * - git@github.com:owner/repo(.git)?
405
+ * - ssh://git@github.com/owner/repo(.git)?
406
+ * Returns null for non-GitHub remotes — the row's GitHub side is then
407
+ * treated as unknown for drift purposes.
408
+ */
409
+ function parseGithubOwnerRepo(url) {
410
+ if (!url) return null;
411
+ const trimmed = url.trim();
412
+ const patterns = [
413
+ /^https?:\/\/(?:[^/]+@)?github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/,
414
+ /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?\/?$/,
415
+ /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/,
416
+ ];
417
+ for (const re of patterns) {
418
+ const m = trimmed.match(re);
419
+ if (m) return `${m[1]}/${m[2]}`;
420
+ }
421
+ return null;
422
+ }
423
+
424
+ // ── helpers ──
425
+
426
+ /** Cross-platform `which` returning the resolved path or null. */
427
+ function whichSync(cmd) {
428
+ const isWin = process.platform === "win32";
429
+ const tool = isWin ? "where" : "which";
430
+ try {
431
+ const out = execFileSync(tool, [cmd], { stdio: ["ignore", "pipe", "ignore"], timeout: 2000 });
432
+ const first = out.toString().split(/\r?\n/)[0]?.trim();
433
+ return first || null;
434
+ } catch {
435
+ return null;
436
+ }
437
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `aitne open` — open the dashboard in the user's browser.
3
+ *
4
+ * If the daemon isn't running, auto-starts it (so a single command takes the
5
+ * user from "nothing running" to "dashboard tab open"). The auto-start path
6
+ * deliberately suppresses the browser-open step inside `cmdStart` (`--no-open`)
7
+ * because we open at the end of *this* command — otherwise the URL gets
8
+ * opened twice on cold-start.
9
+ */
10
+ export async function run(args, ctx) {
11
+ if (args.includes("--help") || args.includes("-h")) {
12
+ console.log(`Usage: aitne open [--setup]
13
+
14
+ Open the dashboard in the default browser. Auto-starts the daemon if
15
+ it isn't running.
16
+
17
+ Flags:
18
+ --setup Open the /setup wizard route instead of the root page.`);
19
+ return;
20
+ }
21
+
22
+ const goSetup = args.includes("--setup");
23
+ const url = goSetup
24
+ ? `http://localhost:${ctx.DASHBOARD_PORT}/setup`
25
+ : `http://localhost:${ctx.DASHBOARD_PORT}/`;
26
+
27
+ const daemonPid = ctx.helpers.getRunningPid(ctx.DAEMON_PID_FILE);
28
+ const dashPid = ctx.helpers.getRunningPid(ctx.DASHBOARD_PID_FILE);
29
+
30
+ if (!daemonPid || !dashPid) {
31
+ console.log(`${ctx.APP_NAME} is not running — starting now…`);
32
+ await ctx.helpers.cmdStart(["--no-open"]);
33
+ }
34
+
35
+ console.log(`Opening ${url}`);
36
+ const opened = await ctx.helpers.openBrowser(url);
37
+ if (!opened) {
38
+ console.log("(could not auto-open browser — paste the URL above manually)");
39
+ }
40
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `aitne setup` — re-run the setup wizard.
3
+ *
4
+ * Equivalent to `aitne open --setup`, but exists as its own verb so users
5
+ * who just want to (re)configure don't have to know about the `--setup`
6
+ * flag. Both paths converge on /setup once the daemon is up.
7
+ */
8
+ export async function run(args, ctx) {
9
+ if (args.includes("--help") || args.includes("-h")) {
10
+ console.log(`Usage: aitne setup
11
+
12
+ Open the dashboard /setup wizard, auto-starting ${ctx.APP_NAME} first if
13
+ needed. Use this to (re)configure backends, integrations, plans, or
14
+ execution mode after the initial install.`);
15
+ return;
16
+ }
17
+ // Delegate to open.mjs with --setup so the auto-start logic stays in one
18
+ // place.
19
+ const open = await import("./open.mjs");
20
+ return open.run(["--setup", ...args.filter((a) => a !== "--setup")], ctx);
21
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `aitne uninstall` — stop services and print the npm uninstall command.
3
+ *
4
+ * We do not run `npm uninstall -g aitne` ourselves: doing so from a process
5
+ * the user is currently executing (the bin lives inside the package being
6
+ * removed) is asking for partial writes and confusing error states. Instead,
7
+ * stop cleanly, optionally wipe the data dir on explicit confirmation, then
8
+ * print the command for the user to copy-paste.
9
+ *
10
+ * The data-dir wipe is gated behind a literal "WIPE" confirmation so a fat
11
+ * finger doesn't lose months of context/. Mirrors the safety pattern used by
12
+ * `aitne restart --clean-context`.
13
+ */
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+
17
+ export async function run(args, ctx) {
18
+ if (args.includes("--help") || args.includes("-h")) {
19
+ console.log(`Usage: aitne uninstall [--keep-data] [--wipe-data]
20
+
21
+ Stops the daemon and dashboard, then prints the npm command to remove the
22
+ binary. Offers to wipe the data directory.
23
+
24
+ Flags:
25
+ --keep-data Skip the data-wipe prompt; leave ~/.personal-agent intact.
26
+ --wipe-data Skip the confirmation prompt; wipe the data dir non-interactively.
27
+ (Both flags together is an error.)`);
28
+ return;
29
+ }
30
+
31
+ const keepData = args.includes("--keep-data");
32
+ const wipeData = args.includes("--wipe-data");
33
+ if (keepData && wipeData) {
34
+ console.error("--keep-data and --wipe-data are mutually exclusive.");
35
+ process.exit(1);
36
+ }
37
+
38
+ // Stop first — calling cmdStop here keeps the lifecycle logic in one place.
39
+ const daemonPid = ctx.helpers.getRunningPid(ctx.DAEMON_PID_FILE);
40
+ const dashPid = ctx.helpers.getRunningPid(ctx.DASHBOARD_PID_FILE);
41
+ if (daemonPid || dashPid) {
42
+ console.log(`Stopping ${ctx.APP_NAME}…`);
43
+ await ctx.helpers.cmdStop();
44
+ } else {
45
+ console.log(`${ctx.APP_NAME} is not running.`);
46
+ }
47
+
48
+ // Prompt (or skip) for data wipe.
49
+ const dataInfo = describeDataDir(ctx.DATA_DIR);
50
+ let willWipe = false;
51
+ if (!keepData) {
52
+ if (wipeData) {
53
+ willWipe = true;
54
+ } else if (dataInfo.exists) {
55
+ console.log("");
56
+ console.log(`Data directory: ${ctx.DATA_DIR} (${dataInfo.fileCount} files, ${dataInfo.sizeMb.toFixed(1)} MB)`);
57
+ console.log("Type WIPE to also delete the data directory, anything else to keep it:");
58
+ const reply = await readLineFromStdin();
59
+ willWipe = reply.trim() === "WIPE";
60
+ }
61
+ }
62
+
63
+ if (willWipe) {
64
+ if (dataInfo.exists) {
65
+ fs.rmSync(ctx.DATA_DIR, { recursive: true, force: true });
66
+ console.log(`Wiped → ${ctx.DATA_DIR}`);
67
+ } else {
68
+ console.log("Data directory does not exist; nothing to wipe.");
69
+ }
70
+ } else if (!keepData && dataInfo.exists) {
71
+ console.log(`Kept ${ctx.DATA_DIR} (${dataInfo.sizeMb.toFixed(1)} MB).`);
72
+ }
73
+
74
+ console.log("");
75
+ console.log("To remove the binary, run:");
76
+ console.log("");
77
+ console.log(" npm uninstall -g aitne");
78
+ console.log("");
79
+ console.log("(We don't run this for you — npm handles uninstalling a global");
80
+ console.log("package more reliably than a process executing from inside it.)");
81
+ }
82
+
83
+ function describeDataDir(dir) {
84
+ if (!fs.existsSync(dir)) return { exists: false, fileCount: 0, sizeMb: 0 };
85
+ let fileCount = 0;
86
+ let totalBytes = 0;
87
+ const walk = (d) => {
88
+ let entries;
89
+ try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
90
+ for (const e of entries) {
91
+ const full = path.join(d, e.name);
92
+ if (e.isDirectory()) walk(full);
93
+ else if (e.isFile()) {
94
+ fileCount++;
95
+ try { totalBytes += fs.statSync(full).size; } catch { /* race — ignore */ }
96
+ }
97
+ }
98
+ };
99
+ walk(dir);
100
+ return { exists: true, fileCount, sizeMb: totalBytes / (1024 * 1024) };
101
+ }
102
+
103
+ function readLineFromStdin() {
104
+ return new Promise((resolve) => {
105
+ process.stdin.resume();
106
+ process.stdin.setEncoding("utf-8");
107
+ const onData = (chunk) => {
108
+ process.stdin.pause();
109
+ process.stdin.removeListener("data", onData);
110
+ resolve(chunk);
111
+ };
112
+ process.stdin.on("data", onData);
113
+ });
114
+ }