@stravica-ai/digital-operator-pa 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 (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/bin/desk +9 -0
  4. package/bin/distill +2 -0
  5. package/bin/estate-sync +2 -0
  6. package/bin/launch +2 -0
  7. package/bin/memory-sync +2 -0
  8. package/bin/migrate-inspect +2 -0
  9. package/bin/pa +2 -0
  10. package/bin/pa-coordinator-hook +2 -0
  11. package/bin/pa-init +2 -0
  12. package/bin/research +2 -0
  13. package/bin/scheduler +2 -0
  14. package/bin/secrets +2 -0
  15. package/bin/slice +2 -0
  16. package/bin/smoke +9 -0
  17. package/bin/sync +2 -0
  18. package/dist/engineering-pa-cli/README.md +205 -0
  19. package/dist/engineering-pa-cli/bin/estate-sync +2 -0
  20. package/dist/engineering-pa-cli/bin/launch +2 -0
  21. package/dist/engineering-pa-cli/bin/pa +2 -0
  22. package/dist/engineering-pa-cli/bin/pa-coordinator-hook +12 -0
  23. package/dist/engineering-pa-cli/bin/research +2 -0
  24. package/dist/engineering-pa-cli/bin/scheduler +2 -0
  25. package/dist/engineering-pa-cli/bin/secrets +2 -0
  26. package/dist/engineering-pa-cli/bin/smoke +9 -0
  27. package/dist/engineering-pa-cli/bin/sync +2 -0
  28. package/dist/engineering-pa-cli/config/coordinator-allowlist.json +118 -0
  29. package/dist/engineering-pa-cli/docs/coordinator-hook.md +211 -0
  30. package/dist/engineering-pa-cli/docs/estate-sync.md +412 -0
  31. package/dist/engineering-pa-cli/docs/gsc.md +175 -0
  32. package/dist/engineering-pa-cli/docs/lint-linkedin.md +139 -0
  33. package/dist/engineering-pa-cli/docs/secrets.md +221 -0
  34. package/dist/engineering-pa-cli/docs/smoke.md +113 -0
  35. package/dist/engineering-pa-cli/docs/staging-alias.md +114 -0
  36. package/dist/engineering-pa-cli/index.mjs +17 -0
  37. package/dist/engineering-pa-cli/package.json +37 -0
  38. package/dist/engineering-pa-cli/src/autonomy/cli.mjs +329 -0
  39. package/dist/engineering-pa-cli/src/autonomy/envelope.mjs +301 -0
  40. package/dist/engineering-pa-cli/src/coordinator-hook/cli.mjs +140 -0
  41. package/dist/engineering-pa-cli/src/coordinator-hook/config.mjs +134 -0
  42. package/dist/engineering-pa-cli/src/coordinator-hook/message.mjs +47 -0
  43. package/dist/engineering-pa-cli/src/coordinator-hook/policy.mjs +211 -0
  44. package/dist/engineering-pa-cli/src/coordinator-hook/repo-root.mjs +44 -0
  45. package/dist/engineering-pa-cli/src/coordinator-hook/self.mjs +30 -0
  46. package/dist/engineering-pa-cli/src/environment/cli.mjs +287 -0
  47. package/dist/engineering-pa-cli/src/environment/probes.mjs +302 -0
  48. package/dist/engineering-pa-cli/src/gsc/auth.mjs +148 -0
  49. package/dist/engineering-pa-cli/src/gsc/cli.mjs +93 -0
  50. package/dist/engineering-pa-cli/src/gsc/cloudflare.mjs +318 -0
  51. package/dist/engineering-pa-cli/src/gsc/commands/add-property.mjs +70 -0
  52. package/dist/engineering-pa-cli/src/gsc/commands/list-properties.mjs +33 -0
  53. package/dist/engineering-pa-cli/src/gsc/commands/pull-analytics.mjs +130 -0
  54. package/dist/engineering-pa-cli/src/gsc/commands/submit-sitemap.mjs +41 -0
  55. package/dist/engineering-pa-cli/src/gsc/commands/verify-via-cf-dns.mjs +189 -0
  56. package/dist/engineering-pa-cli/src/knowledge/call-log.mjs +59 -0
  57. package/dist/engineering-pa-cli/src/knowledge/cli.mjs +242 -0
  58. package/dist/engineering-pa-cli/src/knowledge/eval.mjs +446 -0
  59. package/dist/engineering-pa-cli/src/knowledge/formatters.mjs +331 -0
  60. package/dist/engineering-pa-cli/src/knowledge/graph.mjs +710 -0
  61. package/dist/engineering-pa-cli/src/launch/README.md +214 -0
  62. package/dist/engineering-pa-cli/src/launch/cli.mjs +51 -0
  63. package/dist/engineering-pa-cli/src/launch/commands/launch.mjs +106 -0
  64. package/dist/engineering-pa-cli/src/launch/identity.mjs +43 -0
  65. package/dist/engineering-pa-cli/src/launch/planner.mjs +44 -0
  66. package/dist/engineering-pa-cli/src/launch/repo-root.mjs +53 -0
  67. package/dist/engineering-pa-cli/src/launch/runner.mjs +147 -0
  68. package/dist/engineering-pa-cli/src/launch/task-loader.mjs +85 -0
  69. package/dist/engineering-pa-cli/src/lint/cli.mjs +61 -0
  70. package/dist/engineering-pa-cli/src/lint/commands/linkedin.mjs +133 -0
  71. package/dist/engineering-pa-cli/src/lint/detectors.mjs +90 -0
  72. package/dist/engineering-pa-cli/src/lint/linter.mjs +69 -0
  73. package/dist/engineering-pa-cli/src/lint/reporter.mjs +32 -0
  74. package/dist/engineering-pa-cli/src/lint/rules.mjs +132 -0
  75. package/dist/engineering-pa-cli/src/pa/cli.mjs +369 -0
  76. package/dist/engineering-pa-cli/src/pa/doc-groups.mjs +164 -0
  77. package/dist/engineering-pa-cli/src/pa/io.mjs +80 -0
  78. package/dist/engineering-pa-cli/src/pa/project-cli.mjs +385 -0
  79. package/dist/engineering-pa-cli/src/pa/projects.mjs +352 -0
  80. package/dist/engineering-pa-cli/src/pa/repo-root.mjs +64 -0
  81. package/dist/engineering-pa-cli/src/pa/session.mjs +254 -0
  82. package/dist/engineering-pa-cli/src/pa/work-cli.mjs +164 -0
  83. package/dist/engineering-pa-cli/src/pa/work.mjs +196 -0
  84. package/dist/engineering-pa-cli/src/pa/workstreams.mjs +201 -0
  85. package/dist/engineering-pa-cli/src/research/README.md +360 -0
  86. package/dist/engineering-pa-cli/src/research/briefs/extractor.md +103 -0
  87. package/dist/engineering-pa-cli/src/research/briefs/searcher.md +113 -0
  88. package/dist/engineering-pa-cli/src/research/briefs/synthesizer.md +124 -0
  89. package/dist/engineering-pa-cli/src/research/briefs/verifier.md +86 -0
  90. package/dist/engineering-pa-cli/src/research/cli.mjs +141 -0
  91. package/dist/engineering-pa-cli/src/research/commands/gates.mjs +176 -0
  92. package/dist/engineering-pa-cli/src/research/commands/list.mjs +56 -0
  93. package/dist/engineering-pa-cli/src/research/commands/mark.mjs +97 -0
  94. package/dist/engineering-pa-cli/src/research/commands/paths.mjs +69 -0
  95. package/dist/engineering-pa-cli/src/research/commands/prune.mjs +86 -0
  96. package/dist/engineering-pa-cli/src/research/commands/run.mjs +94 -0
  97. package/dist/engineering-pa-cli/src/research/commands/show.mjs +43 -0
  98. package/dist/engineering-pa-cli/src/research/docs/operator-present-flow.md +120 -0
  99. package/dist/engineering-pa-cli/src/research/format.mjs +59 -0
  100. package/dist/engineering-pa-cli/src/research/promote.mjs +260 -0
  101. package/dist/engineering-pa-cli/src/research/schema/finding.schema.json +55 -0
  102. package/dist/engineering-pa-cli/src/research/schema/gates.schema.json +34 -0
  103. package/dist/engineering-pa-cli/src/research/schema/manifest.schema.json +59 -0
  104. package/dist/engineering-pa-cli/src/research/schema/plan.schema.json +41 -0
  105. package/dist/engineering-pa-cli/src/research/schema/sources.schema.json +31 -0
  106. package/dist/engineering-pa-cli/src/research/store/findings.mjs +61 -0
  107. package/dist/engineering-pa-cli/src/research/store/gates.mjs +73 -0
  108. package/dist/engineering-pa-cli/src/research/store/index.mjs +312 -0
  109. package/dist/engineering-pa-cli/src/research/store/manifest.mjs +56 -0
  110. package/dist/engineering-pa-cli/src/research/store/plan.mjs +59 -0
  111. package/dist/engineering-pa-cli/src/research/store/sources.mjs +44 -0
  112. package/dist/engineering-pa-cli/src/research/store/validate.mjs +92 -0
  113. package/dist/engineering-pa-cli/src/research/workflows/research-normal.js +573 -0
  114. package/dist/engineering-pa-cli/src/scheduler/cli.mjs +197 -0
  115. package/dist/engineering-pa-cli/src/secrets/cli.mjs +254 -0
  116. package/dist/engineering-pa-cli/src/secrets/commands/backup-template.mjs +67 -0
  117. package/dist/engineering-pa-cli/src/secrets/commands/backup.mjs +85 -0
  118. package/dist/engineering-pa-cli/src/secrets/commands/delete.mjs +58 -0
  119. package/dist/engineering-pa-cli/src/secrets/commands/find.mjs +36 -0
  120. package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +49 -0
  121. package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +69 -0
  122. package/dist/engineering-pa-cli/src/secrets/commands/list.mjs +37 -0
  123. package/dist/engineering-pa-cli/src/secrets/commands/rotate.mjs +46 -0
  124. package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +123 -0
  125. package/dist/engineering-pa-cli/src/secrets/commands/status.mjs +146 -0
  126. package/dist/engineering-pa-cli/src/secrets/commands/vaults.mjs +57 -0
  127. package/dist/engineering-pa-cli/src/secrets/commands/verify.mjs +143 -0
  128. package/dist/engineering-pa-cli/src/secrets/vault.mjs +766 -0
  129. package/dist/engineering-pa-cli/src/smoke/checks.mjs +199 -0
  130. package/dist/engineering-pa-cli/src/smoke/cli.mjs +108 -0
  131. package/dist/engineering-pa-cli/src/smoke/commands/run.mjs +237 -0
  132. package/dist/engineering-pa-cli/src/smoke/runner.mjs +191 -0
  133. package/dist/engineering-pa-cli/src/staging-alias/cli.mjs +91 -0
  134. package/dist/engineering-pa-cli/src/staging-alias/cloudflare.mjs +380 -0
  135. package/dist/engineering-pa-cli/src/staging-alias/commands/list.mjs +36 -0
  136. package/dist/engineering-pa-cli/src/staging-alias/commands/set.mjs +188 -0
  137. package/dist/engineering-pa-cli/src/staging-alias/commands/status.mjs +113 -0
  138. package/dist/engineering-pa-cli/src/staging-alias/commands/unset.mjs +95 -0
  139. package/dist/engineering-pa-cli/src/staging-alias/sites.mjs +105 -0
  140. package/dist/engineering-pa-cli/src/sync/cli.mjs +151 -0
  141. package/dist/engineering-pa-cli/src/sync/git.mjs +149 -0
  142. package/dist/engineering-pa-cli/src/sync/lock.mjs +30 -0
  143. package/dist/engineering-pa-cli/src/sync/regenerated-artifacts.mjs +114 -0
  144. package/dist/engineering-pa-cli/src/sync/registry-scope.mjs +260 -0
  145. package/dist/engineering-pa-cli/src/sync/run.mjs +531 -0
  146. package/dist/engineering-pa-cli/src/sync/runner.mjs +62 -0
  147. package/dist/engineering-pa-cli/src/sync/runs-digest.mjs +138 -0
  148. package/dist/engineering-pa-core/README.md +60 -0
  149. package/dist/engineering-pa-core/bin/desk +9 -0
  150. package/dist/engineering-pa-core/bin/distill +2 -0
  151. package/dist/engineering-pa-core/bin/memory-sync +2 -0
  152. package/dist/engineering-pa-core/bin/slice +2 -0
  153. package/dist/engineering-pa-core/docs/autonomy-envelope-tracker.md +111 -0
  154. package/dist/engineering-pa-core/docs/blueprints/README.md +45 -0
  155. package/dist/engineering-pa-core/docs/blueprints/docker-services/blueprint.md +168 -0
  156. package/dist/engineering-pa-core/docs/blueprints/git-identity/blueprint.md +116 -0
  157. package/dist/engineering-pa-core/docs/blueprints/ideas/custom-workspace-views.md +9 -0
  158. package/dist/engineering-pa-core/docs/blueprints/ideas/knowledge-domain-patterns.md +15 -0
  159. package/dist/engineering-pa-core/docs/blueprints/ideas/office-integration.md +11 -0
  160. package/dist/engineering-pa-core/docs/blueprints/ideas/team-scaling.md +9 -0
  161. package/dist/engineering-pa-core/docs/blueprints/ideas/work-personal-separation.md +9 -0
  162. package/dist/engineering-pa-core/docs/blueprints/scheduled-distillation/blueprint.md +129 -0
  163. package/dist/engineering-pa-core/docs/blueprints/tool-integration/blueprint.md +110 -0
  164. package/dist/engineering-pa-core/docs/commands/init.md +159 -0
  165. package/dist/engineering-pa-core/docs/commands/schedule.md +98 -0
  166. package/dist/engineering-pa-core/docs/commands/upgrade.md +67 -0
  167. package/dist/engineering-pa-core/docs/entity-location-map.md +93 -0
  168. package/dist/engineering-pa-core/docs/knowledge-scaffold.md +121 -0
  169. package/dist/engineering-pa-core/docs/lock.md +155 -0
  170. package/dist/engineering-pa-core/docs/memory-sync.md +28 -0
  171. package/dist/engineering-pa-core/docs/pa-operating-model.md +457 -0
  172. package/dist/engineering-pa-core/docs/scheduling.md +35 -0
  173. package/dist/engineering-pa-core/docs/standards/README.md +24 -0
  174. package/dist/engineering-pa-core/docs/standards/briefing-contract.md +161 -0
  175. package/dist/engineering-pa-core/docs/standards/tooling.md +240 -0
  176. package/dist/engineering-pa-core/docs/tools/distill.md +230 -0
  177. package/dist/engineering-pa-core/docs/tools/memory-sync.md +98 -0
  178. package/dist/engineering-pa-core/docs/tools/scheduler.md +243 -0
  179. package/dist/engineering-pa-core/docs/tools/slice.md +150 -0
  180. package/dist/engineering-pa-core/docs/upstream-issue-filing.md +88 -0
  181. package/dist/engineering-pa-core/docs/working-directory.md +29 -0
  182. package/dist/engineering-pa-core/docs/workspace-operations.md +46 -0
  183. package/dist/engineering-pa-core/index.mjs +21 -0
  184. package/dist/engineering-pa-core/knowledge/README.md +53 -0
  185. package/dist/engineering-pa-core/knowledge/canonical/.gitkeep +0 -0
  186. package/dist/engineering-pa-core/knowledge/content-voice/README.md +64 -0
  187. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-baseline.md +133 -0
  188. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-shape.md +58 -0
  189. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +33 -0
  190. package/dist/engineering-pa-core/knowledge/content-voice/content-pipeline-routing.md +58 -0
  191. package/dist/engineering-pa-core/knowledge/content-voice/new-tell-capture-procedure.md +40 -0
  192. package/dist/engineering-pa-core/knowledge/content-voice/voice-guide-shape.md +43 -0
  193. package/dist/engineering-pa-core/knowledge/delegation-dispatch/README.md +34 -0
  194. package/dist/engineering-pa-core/knowledge/delegation-dispatch/briefing-contract.md +77 -0
  195. package/dist/engineering-pa-core/knowledge/delegation-dispatch/delegation-modes.md +84 -0
  196. package/dist/engineering-pa-core/knowledge/delegation-dispatch/dispatch-envelope.md +238 -0
  197. package/dist/engineering-pa-core/knowledge/delegation-dispatch/task-groups-and-orchestrators.md +62 -0
  198. package/dist/engineering-pa-core/knowledge/delegation-dispatch/team-improvement-loop.md +48 -0
  199. package/dist/engineering-pa-core/knowledge/delegation-dispatch/tool-development-policy.md +48 -0
  200. package/dist/engineering-pa-core/knowledge/engineering-discipline/README.md +33 -0
  201. package/dist/engineering-pa-core/knowledge/engineering-discipline/blast-radius-and-safety.md +50 -0
  202. package/dist/engineering-pa-core/knowledge/engineering-discipline/investigate-root-causes.md +48 -0
  203. package/dist/engineering-pa-core/knowledge/engineering-discipline/role-ladder.md +57 -0
  204. package/dist/engineering-pa-core/knowledge/engineering-discipline/verify-runtime-behaviour.md +53 -0
  205. package/dist/engineering-pa-core/knowledge/engineering-discipline/worktree-isolation.md +50 -0
  206. package/dist/engineering-pa-core/knowledge/knowledge-memory/README.md +33 -0
  207. package/dist/engineering-pa-core/knowledge/knowledge-memory/claude-md-hygiene.md +47 -0
  208. package/dist/engineering-pa-core/knowledge/knowledge-memory/journal-and-self-improvement.md +53 -0
  209. package/dist/engineering-pa-core/knowledge/knowledge-memory/knowledge-retrieval-discipline.md +62 -0
  210. package/dist/engineering-pa-core/knowledge/knowledge-memory/librarian-synthesis-mandate.md +56 -0
  211. package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +58 -0
  212. package/dist/engineering-pa-core/knowledge/operator-interface/README.md +32 -0
  213. package/dist/engineering-pa-core/knowledge/operator-interface/communication-style.md +33 -0
  214. package/dist/engineering-pa-core/knowledge/operator-interface/escalation-and-correction.md +45 -0
  215. package/dist/engineering-pa-core/knowledge/operator-interface/push-back-discipline.md +42 -0
  216. package/dist/engineering-pa-core/knowledge/operator-interface/session-boot-and-end.md +56 -0
  217. package/dist/engineering-pa-core/knowledge/platform-tooling/README.md +37 -0
  218. package/dist/engineering-pa-core/knowledge/platform-tooling/desk-cli-reference.md +52 -0
  219. package/dist/engineering-pa-core/knowledge/platform-tooling/environment-awareness.md +91 -0
  220. package/dist/engineering-pa-core/knowledge/platform-tooling/mcp-servers-catalogue.md +52 -0
  221. package/dist/engineering-pa-core/knowledge/platform-tooling/pa-cli-reference.md +55 -0
  222. package/dist/engineering-pa-core/knowledge/platform-tooling/scheduling-and-launchd.md +56 -0
  223. package/dist/engineering-pa-core/knowledge/platform-tooling/tool-discovery-discipline.md +49 -0
  224. package/dist/engineering-pa-core/knowledge/platform-tooling/working-directory-vs-project-root.md +43 -0
  225. package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-server.md +51 -0
  226. package/dist/engineering-pa-core/knowledge/raw/.gitkeep +0 -0
  227. package/dist/engineering-pa-core/knowledge/scaffold/domains.json +5 -0
  228. package/dist/engineering-pa-core/knowledge/schema/.gitkeep +0 -0
  229. package/dist/engineering-pa-core/knowledge/work-management/README.md +33 -0
  230. package/dist/engineering-pa-core/knowledge/work-management/desk-cli.md +84 -0
  231. package/dist/engineering-pa-core/knowledge/work-management/progress-md.md +48 -0
  232. package/dist/engineering-pa-core/knowledge/work-management/projects.md +64 -0
  233. package/dist/engineering-pa-core/knowledge/work-management/spawn-log.md +48 -0
  234. package/dist/engineering-pa-core/knowledge/work-management/work-items.md +67 -0
  235. package/dist/engineering-pa-core/package.json +26 -0
  236. package/dist/engineering-pa-core/roles/agent-ops/definition.md +79 -0
  237. package/dist/engineering-pa-core/roles/dispatch-briefer/definition.md +195 -0
  238. package/dist/engineering-pa-core/roles/librarian/definition.md +176 -0
  239. package/dist/engineering-pa-core/roles/pa/definition.md +93 -0
  240. package/dist/engineering-pa-core/roles/system-admin/definition.md +106 -0
  241. package/dist/engineering-pa-core/roles/tool-developer/definition.md +116 -0
  242. package/dist/engineering-pa-core/roles/writer/definition.md +130 -0
  243. package/dist/engineering-pa-core/schema/autonomy-envelope.schema.json +61 -0
  244. package/dist/engineering-pa-core/schema/project.json +114 -0
  245. package/dist/engineering-pa-core/schema/schedule.json +63 -0
  246. package/dist/engineering-pa-core/schema/work-item.json +92 -0
  247. package/dist/engineering-pa-core/src/config/pa-package-name.mjs +30 -0
  248. package/dist/engineering-pa-core/src/desk/cli.mjs +95 -0
  249. package/dist/engineering-pa-core/src/desk/commands.mjs +398 -0
  250. package/dist/engineering-pa-core/src/desk/format.mjs +84 -0
  251. package/dist/engineering-pa-core/src/desk/repo.mjs +172 -0
  252. package/dist/engineering-pa-core/src/desk/team-scope.mjs +142 -0
  253. package/dist/engineering-pa-core/src/distill/cli.mjs +112 -0
  254. package/dist/engineering-pa-core/src/distill/prereqs.mjs +171 -0
  255. package/dist/engineering-pa-core/src/distill/prompt.mjs +74 -0
  256. package/dist/engineering-pa-core/src/distill/run.mjs +230 -0
  257. package/dist/engineering-pa-core/src/distill/runner.mjs +86 -0
  258. package/dist/engineering-pa-core/src/distill/staging.mjs +59 -0
  259. package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +154 -0
  260. package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +186 -0
  261. package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +225 -0
  262. package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +263 -0
  263. package/dist/engineering-pa-core/src/lock/lock.mjs +202 -0
  264. package/dist/engineering-pa-core/src/memory-sync/cli-support.mjs +12 -0
  265. package/dist/engineering-pa-core/src/memory-sync/cli.mjs +56 -0
  266. package/dist/engineering-pa-core/src/memory-sync/commands/pull.mjs +44 -0
  267. package/dist/engineering-pa-core/src/memory-sync/commands/push.mjs +46 -0
  268. package/dist/engineering-pa-core/src/memory-sync/mirror.mjs +114 -0
  269. package/dist/engineering-pa-core/src/memory-sync/repo.mjs +65 -0
  270. package/dist/engineering-pa-core/src/scheduler/detect.mjs +111 -0
  271. package/dist/engineering-pa-core/src/scheduler/host.mjs +38 -0
  272. package/dist/engineering-pa-core/src/scheduler/index.mjs +13 -0
  273. package/dist/engineering-pa-core/src/scheduler/install.mjs +854 -0
  274. package/dist/engineering-pa-core/src/scheduler/list.mjs +168 -0
  275. package/dist/engineering-pa-core/src/scheduler/remove.mjs +176 -0
  276. package/dist/engineering-pa-core/src/scheduler/verify.mjs +435 -0
  277. package/dist/engineering-pa-core/src/session-boot/autonomy-injection.mjs +84 -0
  278. package/dist/engineering-pa-core/src/slice/cli.mjs +139 -0
  279. package/dist/engineering-pa-core/src/slice/inputs.mjs +174 -0
  280. package/dist/engineering-pa-core/src/slice/prompt.mjs +94 -0
  281. package/dist/engineering-pa-core/src/slice/prompts/dave-boot.md +91 -0
  282. package/dist/engineering-pa-core/src/slice/run.mjs +334 -0
  283. package/dist/engineering-pa-core-extensions-atlassian/README.md +31 -0
  284. package/dist/engineering-pa-core-extensions-atlassian/index.mjs +9 -0
  285. package/dist/engineering-pa-core-extensions-atlassian/package.json +19 -0
  286. package/dist/engineering-pa-core-extensions-n8n/README.md +29 -0
  287. package/dist/engineering-pa-core-extensions-n8n/index.mjs +8 -0
  288. package/dist/engineering-pa-core-extensions-n8n/package.json +19 -0
  289. package/dist/engineering-pa-librarian/README.md +38 -0
  290. package/dist/engineering-pa-librarian/index.mjs +12 -0
  291. package/dist/engineering-pa-librarian/package.json +19 -0
  292. package/dist/engineering-pa-migrate-inspect/README.md +131 -0
  293. package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +2 -0
  294. package/dist/engineering-pa-migrate-inspect/package.json +21 -0
  295. package/dist/engineering-pa-migrate-inspect/src/age-key-migrate.mjs +83 -0
  296. package/dist/engineering-pa-migrate-inspect/src/cli.mjs +97 -0
  297. package/dist/engineering-pa-migrate-inspect/src/inspector.mjs +237 -0
  298. package/dist/engineering-pa-migrate-inspect/src/platforms/linux.mjs +87 -0
  299. package/dist/engineering-pa-migrate-inspect/src/platforms/macos.mjs +105 -0
  300. package/dist/engineering-pa-migrate-inspect/src/runner.mjs +87 -0
  301. package/dist/engineering-pa-runbook/README.md +103 -0
  302. package/dist/engineering-pa-runbook/package.json +18 -0
  303. package/dist/engineering-pa-runbook/reference/dex-template.runbook.json +66 -0
  304. package/dist/engineering-pa-runbook/reference/hq-dave.runbook.json +69 -0
  305. package/dist/engineering-pa-runbook/reference/smarthome01.runbook.json +92 -0
  306. package/dist/engineering-pa-runbook/schema/inspection.schema.json +113 -0
  307. package/dist/engineering-pa-runbook/schema/runbook.schema.json +67 -0
  308. package/dist/engineering-pa-runbook/src/validator.mjs +167 -0
  309. package/dist/engineering-pa-scaffold/README.md +164 -0
  310. package/dist/engineering-pa-scaffold/bin/pa-init +2 -0
  311. package/dist/engineering-pa-scaffold/index.mjs +42 -0
  312. package/dist/engineering-pa-scaffold/package.json +22 -0
  313. package/dist/engineering-pa-scaffold/src/cli.mjs +115 -0
  314. package/dist/engineering-pa-scaffold/src/commands/init.mjs +169 -0
  315. package/dist/engineering-pa-scaffold/src/generator.mjs +354 -0
  316. package/dist/engineering-pa-scaffold/src/prompts.mjs +125 -0
  317. package/dist/engineering-pa-scaffold/src/runner.mjs +142 -0
  318. package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +196 -0
  319. package/dist/engineering-pa-scaffold/templates/operator-README.md.template +44 -0
  320. package/dist/engineering-pa-types-engineering/README.md +35 -0
  321. package/dist/engineering-pa-types-engineering/docs/commands/product-build.md +120 -0
  322. package/dist/engineering-pa-types-engineering/index.mjs +9 -0
  323. package/dist/engineering-pa-types-engineering/package.json +19 -0
  324. package/dist/engineering-pa-types-engineering/roles/product-engineer/definition.md +650 -0
  325. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/build-spec.md +82 -0
  326. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/preflight-checklist.md +158 -0
  327. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/worker-brief.md +130 -0
  328. package/dist/engineering-pa-workspace/README.md +197 -0
  329. package/dist/engineering-pa-workspace/app/assets/md.js +187 -0
  330. package/dist/engineering-pa-workspace/app/assets/modal.js +120 -0
  331. package/dist/engineering-pa-workspace/app/assets/theme.css +132 -0
  332. package/dist/engineering-pa-workspace/app/assets/theme.js +159 -0
  333. package/dist/engineering-pa-workspace/app/banner.js +320 -0
  334. package/dist/engineering-pa-workspace/app/delegations.html +345 -0
  335. package/dist/engineering-pa-workspace/app/home.html +1060 -0
  336. package/dist/engineering-pa-workspace/app/index.html +361 -0
  337. package/dist/engineering-pa-workspace/app/knowledge-map.html +163 -0
  338. package/dist/engineering-pa-workspace/app/knowledge.html +304 -0
  339. package/dist/engineering-pa-workspace/app/nav.json +75 -0
  340. package/dist/engineering-pa-workspace/app/notes.html +199 -0
  341. package/dist/engineering-pa-workspace/app/onboarding.html +592 -0
  342. package/dist/engineering-pa-workspace/app/profile.html +243 -0
  343. package/dist/engineering-pa-workspace/app/project-detail.html +432 -0
  344. package/dist/engineering-pa-workspace/app/projects.html +297 -0
  345. package/dist/engineering-pa-workspace/app/repos.html +598 -0
  346. package/dist/engineering-pa-workspace/app/schedules.html +486 -0
  347. package/dist/engineering-pa-workspace/app/secrets.html +164 -0
  348. package/dist/engineering-pa-workspace/app/staging.html +319 -0
  349. package/dist/engineering-pa-workspace/app/tasks.html +465 -0
  350. package/dist/engineering-pa-workspace/app/vendor/chart.umd.min.js +21 -0
  351. package/dist/engineering-pa-workspace/app/viewer.html +116 -0
  352. package/dist/engineering-pa-workspace/docs/DAEMON.md +104 -0
  353. package/dist/engineering-pa-workspace/docs/page-blueprint.md +286 -0
  354. package/dist/engineering-pa-workspace/docs/status-mon.md +168 -0
  355. package/dist/engineering-pa-workspace/index.mjs +12 -0
  356. package/dist/engineering-pa-workspace/nav.json.example +8 -0
  357. package/dist/engineering-pa-workspace/package.json +27 -0
  358. package/dist/engineering-pa-workspace/src/api.mjs +2086 -0
  359. package/dist/engineering-pa-workspace/src/install-daemon.mjs +573 -0
  360. package/dist/engineering-pa-workspace/src/nav.mjs +190 -0
  361. package/dist/engineering-pa-workspace/src/secrets-api.mjs +175 -0
  362. package/dist/engineering-pa-workspace/src/server.mjs +516 -0
  363. package/dist/engineering-pa-workspace/src/status-mon/poll.mjs +220 -0
  364. package/dist/engineering-pa-workspace/src/status-mon/poller.mjs +113 -0
  365. package/dist/engineering-pa-workspace/src/status-mon/run.mjs +133 -0
  366. package/dist/engineering-pa-workspace/src/supervisor.mjs +264 -0
  367. package/dist/engineering-pa-workspace/src/ws.mjs +633 -0
  368. package/index.mjs +60 -0
  369. package/package.json +58 -0
  370. package/templates/banned-ai-tells.md +102 -0
  371. package/templates/purpose.md +105 -0
  372. package/templates/voice-guide.md +98 -0
@@ -0,0 +1,486 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Schedules — Workspace</title>
7
+ <script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
8
+ <link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
9
+ <script src="/core/tools/workspace/app/assets/theme.js" defer></script>
10
+ <style>
11
+ * { margin: 0; padding: 0; box-sizing: border-box; }
12
+ body { font-family: 'SF Mono','JetBrains Mono','Fira Code',monospace; background: var(--bg); color: var(--text); padding: 1.5rem; line-height: 1.6; }
13
+ h1 { color: var(--text-bright); font-size: 1.25rem; margin-bottom: 0.25rem; }
14
+ .subtitle { color: var(--text-dim); font-size: 0.78rem; margin-bottom: 1rem; display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; }
15
+ .refresh-btn { background: var(--bg-surface); border: 1px solid var(--border); color: var(--text); padding: 0.2rem 0.6rem; border-radius: 4px; font-size: 0.72rem; font-family: inherit; cursor: pointer; }
16
+ .refresh-btn:hover { border-color: var(--accent); color: var(--text-bright); }
17
+
18
+ /* Tabs */
19
+ .tabs { display: flex; gap: 0.4rem; margin-bottom: 1rem; border-bottom: 1px solid var(--border); }
20
+ .tab-btn { background: none; border: none; color: var(--text-dim); font-family: inherit; font-size: 0.82rem; padding: 0.5rem 0.9rem; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
21
+ .tab-btn:hover { color: var(--text-bright); }
22
+ .tab-btn.active { color: var(--text-bright); border-bottom-color: var(--accent); font-weight: 600; }
23
+ .tab-panel { display: none; }
24
+ .tab-panel.active { display: block; }
25
+
26
+ /* Shared loading state */
27
+ .loading { display: flex; align-items: center; justify-content: center; gap: 0.4rem; padding: 2rem; color: var(--text-dim); font-size: 0.82rem; letter-spacing: 0.04em; }
28
+ .loading .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); animation: loading-pulse 1.2s infinite ease-in-out; }
29
+ @keyframes loading-pulse { 0%,80%,100% { opacity: 0.25; } 40% { opacity: 1; } }
30
+
31
+ .card { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.1rem; margin-bottom: 1rem; min-width: 0; }
32
+ .card h2 { color: var(--purple); font-size: 0.92rem; margin-bottom: 0.6rem; display: flex; justify-content: space-between; align-items: center; }
33
+ .card h2 .count { color: var(--text-dim); font-weight: 400; font-size: 0.72rem; }
34
+
35
+ /* Agenda */
36
+ .agenda-bucket { margin-bottom: 1rem; }
37
+ .agenda-bucket:last-child { margin-bottom: 0; }
38
+ .agenda-title { color: var(--cyan); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.4rem; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 0.25rem; }
39
+ .agenda-title .date { color: var(--text-dim); font-weight: 400; letter-spacing: 0; text-transform: none; }
40
+ .agenda-title .bucket-count { color: var(--text-dim); font-weight: 400; letter-spacing: 0; text-transform: none; font-size: 0.66rem; }
41
+ .agenda-scroll { max-height: 500px; overflow-y: auto; padding-right: 0.25rem; }
42
+ .agenda-row { display: flex; align-items: center; gap: 0.7rem; padding: 0.35rem 0.4rem; border-radius: 4px; font-size: 0.77rem; }
43
+ .agenda-row:hover { background: var(--bg-highlight); }
44
+ .agenda-time { color: var(--accent); font-weight: 600; font-variant-numeric: tabular-nums; width: 4.5rem; flex-shrink: 0; }
45
+ .agenda-rel { color: var(--text-dim); font-size: 0.65rem; width: 3.5rem; flex-shrink: 0; }
46
+ .agenda-name { color: var(--text-bright); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
47
+ .agenda-cron { color: var(--text-dim); font-size: 0.66rem; background: var(--bg-highlight); padding: 0.08rem 0.35rem; border-radius: 3px; white-space: nowrap; }
48
+
49
+ /* Host-filter chips */
50
+ .host-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.75rem; }
51
+ .host-chip { background: var(--bg-highlight); border: 1px solid var(--border); color: var(--text-dim); font-family: inherit; font-size: 0.72rem; padding: 0.25rem 0.6rem; border-radius: 999px; cursor: pointer; display: inline-flex; align-items: center; gap: 0.3rem; }
52
+ .host-chip:hover { border-color: var(--accent); color: var(--text-bright); }
53
+ .host-chip.active { background: rgba(122,162,247,0.15); border-color: var(--accent); color: var(--text-bright); font-weight: 600; }
54
+ .host-chip .chip-count { color: var(--text-dim); font-weight: 400; }
55
+ .host-chip.active .chip-count { color: var(--accent); }
56
+ .host-chip .chip-tag { color: var(--green); font-weight: 400; font-style: italic; }
57
+
58
+ /* Active-schedules compact table */
59
+ .sched-table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
60
+ .sched-table th { text-align: left; color: var(--text-dim); font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.06em; padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--border); }
61
+ .sched-row { cursor: pointer; }
62
+ .sched-row td { padding: 0.45rem 0.5rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
63
+ .sched-row:hover { background: var(--bg-highlight); }
64
+ .sched-row .chevron { display: inline-block; width: 0.9em; color: var(--text-dim); transition: transform 0.15s ease; }
65
+ .sched-row.expanded .chevron { transform: rotate(90deg); }
66
+ .sched-name { color: var(--text-bright); font-weight: 600; }
67
+ .sched-detail-row td { padding: 0 0.5rem 0.8rem 1.6rem; border-bottom: 1px solid var(--border); }
68
+ .sched-detail { background: var(--bg-highlight); border-radius: 5px; padding: 0.7rem 0.85rem; font-size: 0.75rem; color: var(--text); }
69
+ .sched-detail-line { margin-bottom: 0.4rem; }
70
+ .sched-detail-line:last-child { margin-bottom: 0; }
71
+ .sched-detail-line strong { color: var(--text-dim); font-weight: 500; margin-right: 0.35rem; }
72
+ .sched-cmd { font-family: 'SF Mono','JetBrains Mono',monospace; color: var(--cyan); background: var(--bg-surface); padding: 0.1rem 0.4rem; border-radius: 3px; display: inline-block; }
73
+ .badge { display: inline-block; padding: 0.1rem 0.4rem; border-radius: 4px; font-size: 0.62rem; font-weight: 600; white-space: nowrap; }
74
+ .badge-active { background: rgba(158,206,106,0.15); color: var(--green); border: 1px solid rgba(158,206,106,0.3); }
75
+ .badge-inactive { background: rgba(86,95,137,0.15); color: var(--text-dim); border: 1px solid var(--border); }
76
+ .badge-cron { background: var(--bg-highlight); color: var(--accent); font-family: inherit; border: 1px solid var(--border); }
77
+ .badge-host { background: rgba(122,162,247,0.12); color: var(--accent); border: 1px solid rgba(122,162,247,0.3); }
78
+ .next-run { color: var(--text); font-variant-numeric: tabular-nums; }
79
+ .next-run .rel { color: var(--text-dim); font-size: 0.68rem; margin-left: 0.35rem; }
80
+ .no-next-run { color: var(--text-dim); font-style: italic; }
81
+
82
+ /* Live backend pane */
83
+ .live-status-title { color: var(--text-dim); font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.4rem; }
84
+ .live-status-content { background: var(--bg-highlight); border: 1px solid var(--border); border-radius: 5px; padding: 0.75rem; font-size: 0.72rem; white-space: pre-wrap; color: var(--text); max-height: 260px; overflow-y: auto; font-family: 'SF Mono','JetBrains Mono',monospace; }
85
+ .backend-label { color: var(--accent); font-weight: 600; }
86
+
87
+ /* Remote-host banner */
88
+ .remote-banner { background: rgba(224,175,104,0.08); border: 1px solid rgba(224,175,104,0.3); border-radius: 5px; padding: 0.6rem 0.8rem; color: var(--text); font-size: 0.74rem; margin-bottom: 0.6rem; line-height: 1.5; }
89
+ .scheduler-error { background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.35); border-radius: 5px; padding: 0.6rem 0.8rem; color: var(--red, #ef4444); font-size: 0.74rem; margin-bottom: 0.6rem; line-height: 1.5; }
90
+ .remote-banner strong { color: var(--yellow); }
91
+ .remote-banner code { background: var(--bg-highlight); color: var(--cyan); padding: 0.05rem 0.3rem; border-radius: 3px; font-size: 0.72rem; }
92
+
93
+ /* Empty */
94
+ .empty { color: var(--text-dim); font-size: 0.8rem; padding: 1rem; text-align: center; font-style: italic; }
95
+ </style>
96
+ <script src="/banner.js" defer></script>
97
+ </head>
98
+ <body>
99
+ <div id="global-banner"></div>
100
+ <h1>Schedules</h1>
101
+ <div class="subtitle">
102
+ <span>Managed recurring tasks.</span>
103
+ <button class="refresh-btn" onclick="load()">Refresh</button>
104
+ </div>
105
+ <div class="tabs">
106
+ <button class="tab-btn active" id="tab-btn-active" onclick="showTab('active')">Active schedules</button>
107
+ <button class="tab-btn" id="tab-btn-agenda" onclick="showTab('agenda')">Agenda</button>
108
+ </div>
109
+ <div id="content"><div class="loading"><span>Loading schedules</span><span class="dot"></span></div></div>
110
+ <script>
111
+ function esc(s) { var el = document.createElement('span'); el.textContent = s == null ? '' : String(s); return el.innerHTML; }
112
+
113
+ var DAY_LABEL = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
114
+ var MON_LABEL = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
115
+ var currentTab = 'active';
116
+ var lastData = null;
117
+ var expandedIds = {};
118
+ // Host-filter selection: 'all' | 'unspecified' | a normalized host key.
119
+ // Seeded to the current device on the FIRST render only — see the
120
+ // "Host filter" section below for the strict-default rationale.
121
+ var selectedHost = 'all';
122
+ var hostFilterInitialized = false;
123
+
124
+ function pad2(n) { return n < 10 ? '0' + n : '' + n; }
125
+
126
+ function formatTime(iso) {
127
+ var d = new Date(iso);
128
+ return pad2(d.getHours()) + ':' + pad2(d.getMinutes());
129
+ }
130
+
131
+ function formatDate(iso) {
132
+ var d = new Date(iso);
133
+ return DAY_LABEL[d.getDay()] + ' ' + d.getDate() + ' ' + MON_LABEL[d.getMonth()];
134
+ }
135
+
136
+ function formatDateTime(iso) {
137
+ return formatDate(iso) + ' ' + formatTime(iso);
138
+ }
139
+
140
+ function relTime(iso) {
141
+ var diff = Math.floor((new Date(iso).getTime() - Date.now()) / 1000);
142
+ if (diff < 0) return 'overdue';
143
+ if (diff < 60) return 'in <1m';
144
+ if (diff < 3600) return 'in ' + Math.floor(diff / 60) + 'm';
145
+ if (diff < 86400) return 'in ' + Math.floor(diff / 3600) + 'h';
146
+ return 'in ' + Math.floor(diff / 86400) + 'd';
147
+ }
148
+
149
+ // --- Host filter -----------------------------------------------------
150
+ //
151
+ // Schedule `host` values are short (`smarthome01`); the API's `hostname`
152
+ // field is the OS-reported FQDN of whatever machine is serving the page
153
+ // (`macbook.thefootonline.local`). Both need the same treatment before
154
+ // comparison — normalizeHost is the ONE place that does it. Nothing else
155
+ // in this file calls .split('.') / slices a hostname string directly.
156
+ //
157
+ // Default-filter rule (operator ruling): STRICT current-device default.
158
+ // If this device has zero schedules, the chip still shows (0) and stays
159
+ // selected — "true picture on load", never a silent fall-back to All.
160
+ // That default is applied once, on the first render() after page load;
161
+ // Refresh re-fetches data but does not reset a filter the operator has
162
+ // since chosen (resetting on every refresh would be surprising, not
163
+ // "strict").
164
+
165
+ function normalizeHost(h) {
166
+ if (!h || typeof h !== 'string') return '';
167
+ var lower = h.toLowerCase();
168
+ var dot = lower.indexOf('.');
169
+ return dot === -1 ? lower : lower.slice(0, dot);
170
+ }
171
+
172
+ // Normalised host key for a raw host value, or null for "no host set" —
173
+ // the null case is the "unspecified" bucket, kept distinct from any
174
+ // literal string key so it can never collide with a real host name.
175
+ function hostKeyOf(hostValue) {
176
+ return hostValue ? normalizeHost(hostValue) : null;
177
+ }
178
+
179
+ function hostBucketKey(s) { return hostKeyOf(s.host); }
180
+
181
+ // Builds the chip model from the full (unfiltered) schedule list plus the
182
+ // page-serving host. The current-device chip is ALWAYS included, even
183
+ // when its count is 0 — see the strict-default rule above.
184
+ function buildHostChips(schedules, hostnameFqdn) {
185
+ var currentHostKey = normalizeHost(hostnameFqdn);
186
+ var counts = {};
187
+ var unspecifiedCount = 0;
188
+ (schedules || []).forEach(function(s) {
189
+ var key = hostBucketKey(s);
190
+ if (key == null) { unspecifiedCount++; return; }
191
+ counts[key] = (counts[key] || 0) + 1;
192
+ });
193
+ var otherKeys = Object.keys(counts).filter(function(k) { return k !== currentHostKey; }).sort();
194
+ var orderedKeys = [currentHostKey].concat(otherKeys);
195
+ var chips = orderedKeys.map(function(key) {
196
+ return { key: key, count: counts[key] || 0, isCurrent: key === currentHostKey };
197
+ });
198
+ return {
199
+ currentHostKey: currentHostKey,
200
+ chips: chips,
201
+ unspecifiedCount: unspecifiedCount,
202
+ total: (schedules || []).length,
203
+ };
204
+ }
205
+
206
+ function filterByHost(schedules, selectedHost) {
207
+ if (selectedHost === 'all') return schedules;
208
+ return (schedules || []).filter(function(s) {
209
+ var key = hostBucketKey(s);
210
+ return selectedHost === 'unspecified' ? key == null : key === selectedHost;
211
+ });
212
+ }
213
+
214
+ function filterAgendaEntries(entries, selectedHost) {
215
+ if (selectedHost === 'all') return entries;
216
+ return (entries || []).filter(function(e) {
217
+ var key = hostKeyOf(e.host);
218
+ return selectedHost === 'unspecified' ? key == null : key === selectedHost;
219
+ });
220
+ }
221
+
222
+ function hostChipLabel(key) { return key === '' ? '(unknown)' : key; }
223
+
224
+ function renderHostChips(hostInfo, selectedHost) {
225
+ var html = '<div class="host-chips">';
226
+ html += '<button type="button" class="host-chip' + (selectedHost === 'all' ? ' active' : '') +
227
+ '" data-host-filter="all">All <span class="chip-count">' + hostInfo.total + '</span></button>';
228
+ hostInfo.chips.forEach(function(c) {
229
+ html += '<button type="button" class="host-chip' + (selectedHost === c.key ? ' active' : '') +
230
+ '" data-host-filter="' + esc(c.key) + '">' + esc(hostChipLabel(c.key)) +
231
+ (c.isCurrent ? ' <span class="chip-tag">· this machine</span>' : '') +
232
+ ' <span class="chip-count">' + c.count + '</span></button>';
233
+ });
234
+ if (hostInfo.unspecifiedCount > 0) {
235
+ html += '<button type="button" class="host-chip' + (selectedHost === 'unspecified' ? ' active' : '') +
236
+ '" data-host-filter="unspecified">unspecified <span class="chip-count">' + hostInfo.unspecifiedCount + '</span></button>';
237
+ }
238
+ html += '</div>';
239
+ return html;
240
+ }
241
+
242
+ // --- Tabs -----------------------------------------------------------
243
+
244
+ function showTab(name) {
245
+ currentTab = name;
246
+ document.getElementById('tab-btn-active').classList.toggle('active', name === 'active');
247
+ document.getElementById('tab-btn-agenda').classList.toggle('active', name === 'agenda');
248
+ var activePanel = document.getElementById('panel-active');
249
+ var agendaPanel = document.getElementById('panel-agenda');
250
+ if (activePanel) activePanel.classList.toggle('active', name === 'active');
251
+ if (agendaPanel) agendaPanel.classList.toggle('active', name === 'agenda');
252
+ }
253
+
254
+ // --- Agenda (Tab 2) --------------------------------------------------
255
+
256
+ function bucketTitle(label, iso, count) {
257
+ var dateStr = iso ? formatDate(iso) : '';
258
+ return '<div class="agenda-title"><span>' + esc(label) + (count != null ? ' <span class="bucket-count">· ' + count + ' entries</span>' : '') + '</span><span class="date">' + esc(dateStr) + '</span></div>';
259
+ }
260
+
261
+ function renderAgendaBucket(label, entries, fallbackDate) {
262
+ var html = '<div class="agenda-bucket">';
263
+ var firstIso = entries.length ? entries[0].nextRunAt : fallbackDate;
264
+ html += bucketTitle(label, firstIso, entries.length);
265
+ if (!entries.length) {
266
+ html += '<div class="empty" style="padding:0.5rem">Nothing scheduled.</div>';
267
+ } else {
268
+ // Each bucket is independently scrollable. Today can have 50+ entries
269
+ // for 15-minute jobs; bounding keeps the page usable.
270
+ html += '<div class="agenda-scroll">';
271
+ entries.forEach(function(e) {
272
+ html += '<div class="agenda-row">';
273
+ html += '<span class="agenda-time">' + esc(formatTime(e.nextRunAt)) + '</span>';
274
+ html += '<span class="agenda-rel">' + esc(relTime(e.nextRunAt)) + '</span>';
275
+ html += '<span class="agenda-name">' + esc(e.name) + '</span>';
276
+ html += '<span class="agenda-cron" title="' + esc(e.cron) + '">' + esc(e.cron) + '</span>';
277
+ html += '</div>';
278
+ });
279
+ html += '</div>';
280
+ }
281
+ html += '</div>';
282
+ return html;
283
+ }
284
+
285
+ function renderAgenda(agenda, selectedHost) {
286
+ var today = filterAgendaEntries(agenda && agenda.today ? agenda.today : [], selectedHost);
287
+ var tomorrow = filterAgendaEntries(agenda && agenda.tomorrow ? agenda.tomorrow : [], selectedHost);
288
+ var week = filterAgendaEntries(agenda && agenda.thisWeek ? agenda.thisWeek : [], selectedHost);
289
+
290
+ var now = new Date();
291
+ var start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
292
+ var tIso = start.toISOString();
293
+ var tmrStart = new Date(start.getTime() + 86400000);
294
+ var weekStart = new Date(start.getTime() + 2 * 86400000);
295
+
296
+ var html = '<div class="card"><h2>Agenda <span class="count">next 7 days</span></h2>';
297
+ html += renderAgendaBucket('Today', today, tIso);
298
+ html += renderAgendaBucket('Tomorrow', tomorrow, tmrStart.toISOString());
299
+ html += renderAgendaBucket('This week', week, weekStart.toISOString());
300
+ html += '</div>';
301
+ return html;
302
+ }
303
+
304
+ /**
305
+ * Parse the `liveStatus` string from the scheduler list.mjs output to detect
306
+ * whether there are any installed jobs on this host. The detect script
307
+ * returns backend-specific output but the common case is: empty string or a
308
+ * header-only response means no jobs. We treat any line containing an
309
+ * identifier-looking token (letters/digits/hyphens) beyond header whitespace
310
+ * as evidence of at least one job. Conservative: false-negatives are fine
311
+ * (we just fall back to the banner).
312
+ */
313
+ function hasInstalledJobs(liveStatus) {
314
+ if (!liveStatus || typeof liveStatus !== 'string') return false;
315
+ var lines = liveStatus.split(/\r?\n/).filter(function(l) { return l.trim().length > 0; });
316
+ if (!lines.length) return false;
317
+ return lines.length > 1;
318
+ }
319
+
320
+ // --- Active schedules (Tab 1) ----------------------------------------
321
+
322
+ function scheduleRowId(s, i) { return s.id || s._file || ('idx-' + i); }
323
+
324
+ function renderScheduleRow(s, i, backend) {
325
+ var rowId = scheduleRowId(s, i);
326
+ var enabled = s.enabled !== false;
327
+ var effectiveBackend = s.backend || backend || '—';
328
+ var nextCell;
329
+ if (s.nextRunAt) {
330
+ nextCell = '<span class="next-run">' + esc(formatDateTime(s.nextRunAt)) + '<span class="rel">' + esc(relTime(s.nextRunAt)) + '</span></span>';
331
+ } else {
332
+ nextCell = '<span class="no-next-run">' + (enabled ? 'unscheduled' : 'disabled') + '</span>';
333
+ }
334
+ var expanded = !!expandedIds[rowId];
335
+ // Row-id is carried via data-row-id, not an inline onclick — a JSON-
336
+ // stringified id embedded inside a double-quoted HTML attribute (which
337
+ // is itself double-quoted JS-string content) breaks on the first
338
+ // internal quote. A delegated click listener (registered once, below)
339
+ // reads the attribute instead, so no quoting/escaping foot-guns.
340
+ var html = '<tr class="sched-row' + (expanded ? ' expanded' : '') + '" data-row-id="' + esc(rowId) + '">';
341
+ html += '<td><span class="chevron">▶</span> <span class="sched-name">' + esc(s.name || s.id || s._file || 'Unnamed') + '</span></td>';
342
+ html += '<td>' + (s.schedule || s.cron ? '<span class="badge badge-cron">' + esc(s.schedule || s.cron) + '</span>' : '<span class="no-next-run">—</span>') + '</td>';
343
+ html += '<td>' + nextCell + '</td>';
344
+ html += '<td><span class="badge ' + (enabled ? 'badge-active' : 'badge-inactive') + '">' + (enabled ? 'active' : 'inactive') + '</span></td>';
345
+ // BUG FIXED HERE: this used to print the page-level `host` (the API's
346
+ // os.hostname() of the machine SERVING the page) for every row, so all
347
+ // schedules appeared to target wherever the operator happened to be
348
+ // viewing from. Each schedule carries its own target host — that's
349
+ // what belongs in its own row, mirroring the `s.backend || backend`
350
+ // fallback immediately above (but NOT falling back to the page host —
351
+ // that's exactly the bug being fixed).
352
+ html += '<td><span class="badge badge-host">' + esc(s.host || 'unspecified') + '</span></td>';
353
+ html += '<td><span class="badge badge-host">' + esc(effectiveBackend) + '</span></td>';
354
+ html += '</tr>';
355
+ html += '<tr class="sched-detail-row" id="detail-' + esc(rowId) + '" style="display:' + (expanded ? 'table-row' : 'none') + '">';
356
+ html += '<td colspan="6"><div class="sched-detail">';
357
+ html += '<div class="sched-detail-line"><strong>Description:</strong>' + esc(s.description || '(none)') + '</div>';
358
+ html += '<div class="sched-detail-line"><strong>Command:</strong><span class="sched-cmd">' + esc(s.command || s.cmd || '(none)') + '</span></div>';
359
+ html += '<div class="sched-detail-line"><strong>Working directory:</strong>' + esc(s.workingDirectory || '(default)') + '</div>';
360
+ if (s.agent) html += '<div class="sched-detail-line"><strong>Agent:</strong>' + esc(s.agent) + '</div>';
361
+ html += '</div></td>';
362
+ html += '</tr>';
363
+ return html;
364
+ }
365
+
366
+ function toggleScheduleRow(rowId) {
367
+ expandedIds[rowId] = !expandedIds[rowId];
368
+ var row = document.querySelector('.sched-row[data-row-id="' + CSS.escape(rowId) + '"]');
369
+ var detail = document.getElementById('detail-' + rowId);
370
+ if (row) row.classList.toggle('expanded', expandedIds[rowId]);
371
+ if (detail) detail.style.display = expandedIds[rowId] ? 'table-row' : 'none';
372
+ }
373
+
374
+ // Delegated click listener — registered once, survives every re-render
375
+ // (render() replaces #panel-active's innerHTML on every load()/refresh).
376
+ document.addEventListener('click', function(e) {
377
+ var chip = e.target.closest('[data-host-filter]');
378
+ if (chip) {
379
+ selectedHost = chip.getAttribute('data-host-filter');
380
+ // Re-render from the already-fetched data — chip switching is a
381
+ // client-side filter, not a new fetch (one round trip serves every
382
+ // chip; see the buildAgenda `host` field for why that's possible).
383
+ if (lastData) render(lastData);
384
+ return;
385
+ }
386
+ var row = e.target.closest('.sched-row');
387
+ if (row && row.dataset.rowId != null) toggleScheduleRow(row.dataset.rowId);
388
+ });
389
+
390
+ function renderActiveSchedules(schedules, liveStatus, backend, host, schedulerError, hostInfo, selectedHost) {
391
+ var filtered = filterByHost(schedules, selectedHost);
392
+ var countLabel = filtered.length === schedules.length
393
+ ? String(schedules.length)
394
+ : filtered.length + ' / ' + schedules.length;
395
+ var html = '<div class="card"><h2>Active schedules <span class="count">' + countLabel + '</span></h2>';
396
+ html += renderHostChips(hostInfo, selectedHost);
397
+ if (!schedules.length) {
398
+ html += '<div class="empty">No schedule definitions found in <code>schedules/</code>.</div>';
399
+ } else if (!filtered.length) {
400
+ // Distinct from the "no schedules at all" empty state above — schedules
401
+ // exist, the HOST FILTER is just excluding all of them. Without this,
402
+ // "true picture on load" (the strict current-device default) reads as
403
+ // a broken page on the common case of viewing from a laptop that
404
+ // doesn't run any of HQ's schedules itself.
405
+ html += '<div class="empty">No native schedules on ' + esc(hostChipLabel(selectedHost)) +
406
+ '. Use the chips above to see other hosts.</div>';
407
+ } else {
408
+ html += '<div style="overflow-x:auto"><table class="sched-table"><thead><tr>';
409
+ html += '<th>Name</th><th>Cron</th><th>Next run</th><th>Enabled</th><th>Host</th><th>Backend</th>';
410
+ html += '</tr></thead><tbody>';
411
+ filtered.forEach(function(s, i) { html += renderScheduleRow(s, i, backend); });
412
+ html += '</tbody></table></div>';
413
+ }
414
+ html += '</div>';
415
+
416
+ // OS scheduler section. States:
417
+ // 1. liveStatus has installed jobs → show them normally.
418
+ // 2. liveStatus empty but schedules exist → clarify banner (remote host).
419
+ // 3. schedulerError set → always surface it (detection/listing failed —
420
+ // "0 installed" without an explanation was the v1-path regression).
421
+ // 4. No schedules, no liveStatus, no error → don't render.
422
+ var installed = hasInstalledJobs(liveStatus);
423
+ var errorLine = schedulerError
424
+ ? '<div class="scheduler-error"><strong>Scheduler detection problem:</strong> ' + esc(schedulerError) + '</div>'
425
+ : '';
426
+ if (installed) {
427
+ html += '<div class="card"><h2>OS scheduler status</h2>';
428
+ html += errorLine;
429
+ html += '<div class="live-status-title">Backend: <span class="backend-label">' + esc(backend || 'unknown') + '</span>' + (host ? ' · Host: <span class="backend-label">' + esc(host) + '</span>' : '') + '</div>';
430
+ html += '<div class="live-status-content">' + esc(liveStatus) + '</div>';
431
+ html += '</div>';
432
+ } else if (schedulerError && schedules.length === 0) {
433
+ html += '<div class="card"><h2>OS scheduler status</h2>' + errorLine + '</div>';
434
+ } else if (schedules.length > 0) {
435
+ html += '<div class="card"><h2>OS scheduler status</h2>';
436
+ html += errorLine;
437
+ html += '<div class="remote-banner">';
438
+ html += '<strong>' + schedules.length + ' schedule' + (schedules.length === 1 ? '' : 's') + ' managed by HQ</strong> · ' +
439
+ '<strong>0 installed</strong> on this host' + (host ? ' (<code>' + esc(host) + '</code>)' : '') + '.' +
440
+ ' Schedules may be installed on a different host — HQ scheduled tasks typically target <code>smarthome01</code>. ' +
441
+ 'Run <code>desk schedule list</code> on the target host to see the live OS jobs.';
442
+ html += '</div>';
443
+ if (backend) {
444
+ html += '<div class="live-status-title">Detected backend here: <span class="backend-label">' + esc(backend) + '</span></div>';
445
+ }
446
+ html += '</div>';
447
+ }
448
+ return html;
449
+ }
450
+
451
+ // --- Top-level render --------------------------------------------------
452
+
453
+ function render(data) {
454
+ lastData = data;
455
+ var hostInfo = buildHostChips(data.schedules || [], data.hostname);
456
+ if (!hostFilterInitialized) {
457
+ // Strict default: the current device's chip, even at count 0 — set
458
+ // exactly once, on the first render after page load. Chip clicks and
459
+ // Refresh afterwards leave the operator's choice alone.
460
+ selectedHost = hostInfo.currentHostKey;
461
+ hostFilterInitialized = true;
462
+ }
463
+ document.getElementById('content').innerHTML =
464
+ '<div class="tab-panel active" id="panel-active"></div>' +
465
+ '<div class="tab-panel" id="panel-agenda"></div>';
466
+ document.getElementById('panel-active').innerHTML =
467
+ renderActiveSchedules(data.schedules || [], data.liveStatus, data.backend, data.hostname, data.schedulerError, hostInfo, selectedHost);
468
+ document.getElementById('panel-agenda').innerHTML =
469
+ renderAgenda(data.agenda || { today: [], tomorrow: [], thisWeek: [] }, selectedHost);
470
+ showTab(currentTab);
471
+ }
472
+
473
+ function load() {
474
+ document.getElementById('content').innerHTML = '<div class="loading"><span>Loading schedules</span><span class="dot"></span></div>';
475
+ fetch('/__api/schedules', { cache: 'no-store' })
476
+ .then(function(r) { return r.json(); })
477
+ .then(render)
478
+ .catch(function(e) {
479
+ document.getElementById('content').innerHTML = '<div class="empty">Unable to load schedules: ' + esc(e.message) + '</div>';
480
+ });
481
+ }
482
+
483
+ load();
484
+ </script>
485
+ </body>
486
+ </html>