@murphai/murph 0.1.1

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 (301) hide show
  1. package/CHANGELOG.md +2009 -0
  2. package/LICENSE +674 -0
  3. package/README.md +97 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/assistant/automation/run-loop.d.ts +21 -0
  6. package/dist/assistant/automation/run-loop.d.ts.map +1 -0
  7. package/dist/assistant/automation/run-loop.js +31 -0
  8. package/dist/assistant/automation/run-loop.js.map +1 -0
  9. package/dist/assistant/automation.d.ts +10 -0
  10. package/dist/assistant/automation.d.ts.map +1 -0
  11. package/dist/assistant/automation.js +5 -0
  12. package/dist/assistant/automation.js.map +1 -0
  13. package/dist/assistant/cron.d.ts +19 -0
  14. package/dist/assistant/cron.d.ts.map +1 -0
  15. package/dist/assistant/cron.js +59 -0
  16. package/dist/assistant/cron.js.map +1 -0
  17. package/dist/assistant/doctor-security.d.ts +15 -0
  18. package/dist/assistant/doctor-security.d.ts.map +1 -0
  19. package/dist/assistant/doctor-security.js +172 -0
  20. package/dist/assistant/doctor-security.js.map +1 -0
  21. package/dist/assistant/doctor.d.ts +5 -0
  22. package/dist/assistant/doctor.d.ts.map +1 -0
  23. package/dist/assistant/doctor.js +527 -0
  24. package/dist/assistant/doctor.js.map +1 -0
  25. package/dist/assistant/outbox.d.ts +19 -0
  26. package/dist/assistant/outbox.d.ts.map +1 -0
  27. package/dist/assistant/outbox.js +28 -0
  28. package/dist/assistant/outbox.js.map +1 -0
  29. package/dist/assistant/provider-catalog.d.ts +61 -0
  30. package/dist/assistant/provider-catalog.d.ts.map +1 -0
  31. package/dist/assistant/provider-catalog.js +205 -0
  32. package/dist/assistant/provider-catalog.js.map +1 -0
  33. package/dist/assistant/service.d.ts +85 -0
  34. package/dist/assistant/service.d.ts.map +1 -0
  35. package/dist/assistant/service.js +26 -0
  36. package/dist/assistant/service.js.map +1 -0
  37. package/dist/assistant/status.d.ts +9 -0
  38. package/dist/assistant/status.d.ts.map +1 -0
  39. package/dist/assistant/status.js +16 -0
  40. package/dist/assistant/status.js.map +1 -0
  41. package/dist/assistant/stop.d.ts +20 -0
  42. package/dist/assistant/stop.d.ts.map +1 -0
  43. package/dist/assistant/stop.js +142 -0
  44. package/dist/assistant/stop.js.map +1 -0
  45. package/dist/assistant/store.d.ts +6 -0
  46. package/dist/assistant/store.d.ts.map +1 -0
  47. package/dist/assistant/store.js +21 -0
  48. package/dist/assistant/store.js.map +1 -0
  49. package/dist/assistant/ui/ink.d.ts +247 -0
  50. package/dist/assistant/ui/ink.d.ts.map +1 -0
  51. package/dist/assistant/ui/ink.js +2417 -0
  52. package/dist/assistant/ui/ink.js.map +1 -0
  53. package/dist/assistant/ui/theme.d.ts +64 -0
  54. package/dist/assistant/ui/theme.d.ts.map +1 -0
  55. package/dist/assistant/ui/theme.js +180 -0
  56. package/dist/assistant/ui/theme.js.map +1 -0
  57. package/dist/assistant/ui/view-model.d.ts +89 -0
  58. package/dist/assistant/ui/view-model.d.ts.map +1 -0
  59. package/dist/assistant/ui/view-model.js +298 -0
  60. package/dist/assistant/ui/view-model.js.map +1 -0
  61. package/dist/assistant-chat-ink.d.ts +2 -0
  62. package/dist/assistant-chat-ink.d.ts.map +1 -0
  63. package/dist/assistant-chat-ink.js +2 -0
  64. package/dist/assistant-chat-ink.js.map +1 -0
  65. package/dist/assistant-daemon-client.d.ts +81 -0
  66. package/dist/assistant-daemon-client.d.ts.map +1 -0
  67. package/dist/assistant-daemon-client.js +473 -0
  68. package/dist/assistant-daemon-client.js.map +1 -0
  69. package/dist/assistant-runtime.d.ts +25 -0
  70. package/dist/assistant-runtime.d.ts.map +1 -0
  71. package/dist/assistant-runtime.js +17 -0
  72. package/dist/assistant-runtime.js.map +1 -0
  73. package/dist/bin.d.ts +3 -0
  74. package/dist/bin.d.ts.map +1 -0
  75. package/dist/bin.js +7 -0
  76. package/dist/bin.js.map +1 -0
  77. package/dist/cli-entry.d.ts +10 -0
  78. package/dist/cli-entry.d.ts.map +1 -0
  79. package/dist/cli-entry.js +127 -0
  80. package/dist/cli-entry.js.map +1 -0
  81. package/dist/commands/assistant.d.ts +5 -0
  82. package/dist/commands/assistant.d.ts.map +1 -0
  83. package/dist/commands/assistant.js +1663 -0
  84. package/dist/commands/assistant.js.map +1 -0
  85. package/dist/commands/audit-command-helpers.d.ts +15 -0
  86. package/dist/commands/audit-command-helpers.d.ts.map +1 -0
  87. package/dist/commands/audit-command-helpers.js +24 -0
  88. package/dist/commands/audit-command-helpers.js.map +1 -0
  89. package/dist/commands/audit.d.ts +4 -0
  90. package/dist/commands/audit.d.ts.map +1 -0
  91. package/dist/commands/audit.js +107 -0
  92. package/dist/commands/audit.js.map +1 -0
  93. package/dist/commands/device.d.ts +4 -0
  94. package/dist/commands/device.d.ts.map +1 -0
  95. package/dist/commands/device.js +177 -0
  96. package/dist/commands/device.js.map +1 -0
  97. package/dist/commands/document.d.ts +4 -0
  98. package/dist/commands/document.d.ts.map +1 -0
  99. package/dist/commands/document.js +117 -0
  100. package/dist/commands/document.js.map +1 -0
  101. package/dist/commands/event.d.ts +4 -0
  102. package/dist/commands/event.d.ts.map +1 -0
  103. package/dist/commands/event.js +136 -0
  104. package/dist/commands/event.js.map +1 -0
  105. package/dist/commands/experiment.d.ts +4 -0
  106. package/dist/commands/experiment.d.ts.map +1 -0
  107. package/dist/commands/experiment.js +140 -0
  108. package/dist/commands/experiment.js.map +1 -0
  109. package/dist/commands/export-intake-read-helpers.d.ts +150 -0
  110. package/dist/commands/export-intake-read-helpers.d.ts.map +1 -0
  111. package/dist/commands/export-intake-read-helpers.js +328 -0
  112. package/dist/commands/export-intake-read-helpers.js.map +1 -0
  113. package/dist/commands/export.d.ts +4 -0
  114. package/dist/commands/export.d.ts.map +1 -0
  115. package/dist/commands/export.js +179 -0
  116. package/dist/commands/export.js.map +1 -0
  117. package/dist/commands/food.d.ts +4 -0
  118. package/dist/commands/food.d.ts.map +1 -0
  119. package/dist/commands/food.js +190 -0
  120. package/dist/commands/food.js.map +1 -0
  121. package/dist/commands/health-command-factory.d.ts +230 -0
  122. package/dist/commands/health-command-factory.d.ts.map +1 -0
  123. package/dist/commands/health-command-factory.js +551 -0
  124. package/dist/commands/health-command-factory.js.map +1 -0
  125. package/dist/commands/health-entity-command-registry.d.ts +27 -0
  126. package/dist/commands/health-entity-command-registry.d.ts.map +1 -0
  127. package/dist/commands/health-entity-command-registry.js +84 -0
  128. package/dist/commands/health-entity-command-registry.js.map +1 -0
  129. package/dist/commands/inbox.d.ts +5 -0
  130. package/dist/commands/inbox.d.ts.map +1 -0
  131. package/dist/commands/inbox.js +841 -0
  132. package/dist/commands/inbox.js.map +1 -0
  133. package/dist/commands/intake.d.ts +4 -0
  134. package/dist/commands/intake.d.ts.map +1 -0
  135. package/dist/commands/intake.js +175 -0
  136. package/dist/commands/intake.js.map +1 -0
  137. package/dist/commands/intervention.d.ts +4 -0
  138. package/dist/commands/intervention.d.ts.map +1 -0
  139. package/dist/commands/intervention.js +122 -0
  140. package/dist/commands/intervention.js.map +1 -0
  141. package/dist/commands/journal.d.ts +12 -0
  142. package/dist/commands/journal.d.ts.map +1 -0
  143. package/dist/commands/journal.js +186 -0
  144. package/dist/commands/journal.js.map +1 -0
  145. package/dist/commands/meal.d.ts +4 -0
  146. package/dist/commands/meal.d.ts.map +1 -0
  147. package/dist/commands/meal.js +123 -0
  148. package/dist/commands/meal.js.map +1 -0
  149. package/dist/commands/profile.d.ts +4 -0
  150. package/dist/commands/profile.d.ts.map +1 -0
  151. package/dist/commands/profile.js +62 -0
  152. package/dist/commands/profile.js.map +1 -0
  153. package/dist/commands/protocol.d.ts +4 -0
  154. package/dist/commands/protocol.d.ts.map +1 -0
  155. package/dist/commands/protocol.js +79 -0
  156. package/dist/commands/protocol.js.map +1 -0
  157. package/dist/commands/provider.d.ts +4 -0
  158. package/dist/commands/provider.d.ts.map +1 -0
  159. package/dist/commands/provider.js +115 -0
  160. package/dist/commands/provider.js.map +1 -0
  161. package/dist/commands/read.d.ts +4 -0
  162. package/dist/commands/read.d.ts.map +1 -0
  163. package/dist/commands/read.js +55 -0
  164. package/dist/commands/read.js.map +1 -0
  165. package/dist/commands/recipe.d.ts +4 -0
  166. package/dist/commands/recipe.d.ts.map +1 -0
  167. package/dist/commands/recipe.js +116 -0
  168. package/dist/commands/recipe.js.map +1 -0
  169. package/dist/commands/record-mutation-command-helpers.d.ts +196 -0
  170. package/dist/commands/record-mutation-command-helpers.d.ts.map +1 -0
  171. package/dist/commands/record-mutation-command-helpers.js +150 -0
  172. package/dist/commands/record-mutation-command-helpers.js.map +1 -0
  173. package/dist/commands/research.d.ts +3 -0
  174. package/dist/commands/research.d.ts.map +1 -0
  175. package/dist/commands/research.js +104 -0
  176. package/dist/commands/research.js.map +1 -0
  177. package/dist/commands/sample-batch-command-helpers.d.ts +24 -0
  178. package/dist/commands/sample-batch-command-helpers.d.ts.map +1 -0
  179. package/dist/commands/sample-batch-command-helpers.js +99 -0
  180. package/dist/commands/sample-batch-command-helpers.js.map +1 -0
  181. package/dist/commands/sample-import-command-helpers.d.ts +24 -0
  182. package/dist/commands/sample-import-command-helpers.d.ts.map +1 -0
  183. package/dist/commands/sample-import-command-helpers.js +49 -0
  184. package/dist/commands/sample-import-command-helpers.js.map +1 -0
  185. package/dist/commands/sample-query-command-helpers.d.ts +11 -0
  186. package/dist/commands/sample-query-command-helpers.d.ts.map +1 -0
  187. package/dist/commands/sample-query-command-helpers.js +26 -0
  188. package/dist/commands/sample-query-command-helpers.js.map +1 -0
  189. package/dist/commands/samples.d.ts +4 -0
  190. package/dist/commands/samples.d.ts.map +1 -0
  191. package/dist/commands/samples.js +261 -0
  192. package/dist/commands/samples.js.map +1 -0
  193. package/dist/commands/search.d.ts +4 -0
  194. package/dist/commands/search.d.ts.map +1 -0
  195. package/dist/commands/search.js +295 -0
  196. package/dist/commands/search.js.map +1 -0
  197. package/dist/commands/supplement.d.ts +4 -0
  198. package/dist/commands/supplement.d.ts.map +1 -0
  199. package/dist/commands/supplement.js +338 -0
  200. package/dist/commands/supplement.js.map +1 -0
  201. package/dist/commands/vault.d.ts +4 -0
  202. package/dist/commands/vault.d.ts.map +1 -0
  203. package/dist/commands/vault.js +164 -0
  204. package/dist/commands/vault.js.map +1 -0
  205. package/dist/commands/workout.d.ts +4 -0
  206. package/dist/commands/workout.d.ts.map +1 -0
  207. package/dist/commands/workout.js +284 -0
  208. package/dist/commands/workout.js.map +1 -0
  209. package/dist/incur.generated.d.ts +2164 -0
  210. package/dist/incur.generated.d.ts.map +1 -0
  211. package/dist/incur.generated.js +2 -0
  212. package/dist/incur.generated.js.map +1 -0
  213. package/dist/index.d.ts +13 -0
  214. package/dist/index.d.ts.map +1 -0
  215. package/dist/index.js +14 -0
  216. package/dist/index.js.map +1 -0
  217. package/dist/research-cli-contracts.d.ts +22 -0
  218. package/dist/research-cli-contracts.d.ts.map +1 -0
  219. package/dist/research-cli-contracts.js +18 -0
  220. package/dist/research-cli-contracts.js.map +1 -0
  221. package/dist/research-runtime.d.ts +79 -0
  222. package/dist/research-runtime.d.ts.map +1 -0
  223. package/dist/research-runtime.js +351 -0
  224. package/dist/research-runtime.js.map +1 -0
  225. package/dist/run-terminal-logging.d.ts +12 -0
  226. package/dist/run-terminal-logging.d.ts.map +1 -0
  227. package/dist/run-terminal-logging.js +323 -0
  228. package/dist/run-terminal-logging.js.map +1 -0
  229. package/dist/setup-agentmail.d.ts +30 -0
  230. package/dist/setup-agentmail.d.ts.map +1 -0
  231. package/dist/setup-agentmail.js +136 -0
  232. package/dist/setup-agentmail.js.map +1 -0
  233. package/dist/setup-assistant-account.d.ts +29 -0
  234. package/dist/setup-assistant-account.d.ts.map +1 -0
  235. package/dist/setup-assistant-account.js +443 -0
  236. package/dist/setup-assistant-account.js.map +1 -0
  237. package/dist/setup-assistant.d.ts +34 -0
  238. package/dist/setup-assistant.d.ts.map +1 -0
  239. package/dist/setup-assistant.js +355 -0
  240. package/dist/setup-assistant.js.map +1 -0
  241. package/dist/setup-cli.d.ts +72 -0
  242. package/dist/setup-cli.d.ts.map +1 -0
  243. package/dist/setup-cli.js +387 -0
  244. package/dist/setup-cli.js.map +1 -0
  245. package/dist/setup-services/channels.d.ts +19 -0
  246. package/dist/setup-services/channels.d.ts.map +1 -0
  247. package/dist/setup-services/channels.js +721 -0
  248. package/dist/setup-services/channels.js.map +1 -0
  249. package/dist/setup-services/process.d.ts +18 -0
  250. package/dist/setup-services/process.d.ts.map +1 -0
  251. package/dist/setup-services/process.js +98 -0
  252. package/dist/setup-services/process.js.map +1 -0
  253. package/dist/setup-services/scheduled-updates.d.ts +9 -0
  254. package/dist/setup-services/scheduled-updates.d.ts.map +1 -0
  255. package/dist/setup-services/scheduled-updates.js +64 -0
  256. package/dist/setup-services/scheduled-updates.js.map +1 -0
  257. package/dist/setup-services/shell.d.ts +18 -0
  258. package/dist/setup-services/shell.d.ts.map +1 -0
  259. package/dist/setup-services/shell.js +447 -0
  260. package/dist/setup-services/shell.js.map +1 -0
  261. package/dist/setup-services/steps.d.ts +39 -0
  262. package/dist/setup-services/steps.d.ts.map +1 -0
  263. package/dist/setup-services/steps.js +86 -0
  264. package/dist/setup-services/steps.js.map +1 -0
  265. package/dist/setup-services/toolchain.d.ts +46 -0
  266. package/dist/setup-services/toolchain.d.ts.map +1 -0
  267. package/dist/setup-services/toolchain.js +232 -0
  268. package/dist/setup-services/toolchain.js.map +1 -0
  269. package/dist/setup-services.d.ts +44 -0
  270. package/dist/setup-services.d.ts.map +1 -0
  271. package/dist/setup-services.js +739 -0
  272. package/dist/setup-services.js.map +1 -0
  273. package/dist/setup-wizard.d.ts +101 -0
  274. package/dist/setup-wizard.d.ts.map +1 -0
  275. package/dist/setup-wizard.js +1458 -0
  276. package/dist/setup-wizard.js.map +1 -0
  277. package/dist/usecases/intervention.d.ts +63 -0
  278. package/dist/usecases/intervention.d.ts.map +1 -0
  279. package/dist/usecases/intervention.js +205 -0
  280. package/dist/usecases/intervention.js.map +1 -0
  281. package/dist/usecases/text-duration.d.ts +4 -0
  282. package/dist/usecases/text-duration.d.ts.map +1 -0
  283. package/dist/usecases/text-duration.js +63 -0
  284. package/dist/usecases/text-duration.js.map +1 -0
  285. package/dist/usecases/workout-format.d.ts +139 -0
  286. package/dist/usecases/workout-format.d.ts.map +1 -0
  287. package/dist/usecases/workout-format.js +445 -0
  288. package/dist/usecases/workout-format.js.map +1 -0
  289. package/dist/usecases/workout.d.ts +94 -0
  290. package/dist/usecases/workout.d.ts.map +1 -0
  291. package/dist/usecases/workout.js +411 -0
  292. package/dist/usecases/workout.js.map +1 -0
  293. package/dist/vault-cli-command-manifest.d.ts +562 -0
  294. package/dist/vault-cli-command-manifest.d.ts.map +1 -0
  295. package/dist/vault-cli-command-manifest.js +759 -0
  296. package/dist/vault-cli-command-manifest.js.map +1 -0
  297. package/dist/vault-cli.d.ts +6 -0
  298. package/dist/vault-cli.d.ts.map +1 -0
  299. package/dist/vault-cli.js +38 -0
  300. package/dist/vault-cli.js.map +1 -0
  301. package/package.json +85 -0
@@ -0,0 +1,1663 @@
1
+ import { Cli, z } from 'incur';
2
+ import { assistantApprovalPolicyValues, assistantAskResultSchema, assistantChatProviderValues, assistantChatResultSchema, assistantCronAddResultSchema, assistantCronListResultSchema, assistantCronPresetInstallResultSchema, assistantCronPresetListResultSchema, assistantCronPresetShowResultSchema, assistantCronRemoveResultSchema, assistantCronRunResultSchema, assistantCronRunsResultSchema, assistantCronShowResultSchema, assistantCronStatusResultSchema, assistantCronTargetSetResultSchema, assistantCronTargetShowResultSchema, assistantDeliverResultSchema, assistantDoctorResultSchema, assistantMemoryGetResultSchema, assistantMemoryQueryScopeValues, assistantMemorySearchResultSchema, assistantMemoryVisibleSectionValues, assistantRunResultSchema, assistantStateDeleteResultSchema, assistantStateListResultSchema, assistantStatePatchResultSchema, assistantStatePutResultSchema, assistantStateShowResultSchema, assistantSelfDeliveryTargetClearResultSchema, assistantSelfDeliveryTargetListResultSchema, assistantSelfDeliveryTargetSetResultSchema, assistantSelfDeliveryTargetShowResultSchema, assistantSandboxValues, assistantSessionListResultSchema, assistantSessionShowResultSchema, assistantStopResultSchema, assistantStatusResultSchema, } from '@murphai/assistant-core/assistant-cli-contracts';
3
+ import { deliverAssistantMessage } from '@murphai/assistant-core/outbound-channel';
4
+ import { addAssistantCronJob, buildAssistantCronSchedule, getAssistantCronPreset, getAssistantCronJob, getAssistantCronJobTarget, getAssistantCronStatus, getAssistantStateDocument, installAssistantCronPreset, listAssistantCronPresets, listAssistantCronJobs, listAssistantCronRuns, listAssistantStateDocuments, deleteAssistantStateDocument, patchAssistantStateDocument, putAssistantStateDocument, removeAssistantCronJob, runAssistantAutomation, runAssistantChat, runAssistantCronJobNow, sendAssistantMessage, setAssistantCronJobTarget, setAssistantCronJobEnabled, stopAssistantAutomation, } from '../assistant-runtime.js';
5
+ import { runAssistantDoctor } from '../assistant/doctor.js';
6
+ import { getAssistantStatus } from '../assistant/status.js';
7
+ import { redactAssistantSessionForDisplay, redactAssistantSessionsForDisplay, } from '@murphai/assistant-core/assistant-runtime';
8
+ import { assertAssistantMemoryTurnContextVault, getAssistantMemory, redactAssistantMemoryRecord, redactAssistantMemorySearchHit, resolveAssistantMemoryStoragePaths, resolveAssistantMemoryTurnContext, searchAssistantMemory, } from '@murphai/assistant-core/assistant-runtime';
9
+ import { redactAssistantDisplayPath, getAssistantSession, listAssistantSessions, redactAssistantStateDocumentListEntry, redactAssistantStateDocumentSnapshot, resolveAssistantStatePaths, } from '@murphai/assistant-core/assistant-state';
10
+ import { emptyArgsSchema, parseHeadersJsonOption, requestIdFromOptions, withBaseOptions, } from '@murphai/assistant-core/command-helpers';
11
+ import { inputFileOptionSchema, loadJsonInputObject, } from '@murphai/assistant-core/json-input';
12
+ import { normalizeRepeatableFlagOption } from '@murphai/assistant-core/option-utils';
13
+ import { applyAssistantSelfDeliveryTargetDefaults, clearAssistantSelfDeliveryTargets, listAssistantSelfDeliveryTargets, resolveAssistantSelfDeliveryTarget, resolveOperatorConfigPath, saveAssistantSelfDeliveryTarget, } from '@murphai/assistant-core/operator-config';
14
+ import { formatAssistantRunEventForTerminal, formatForegroundLogLine, formatInboxRunEventForTerminal, resolveForegroundTerminalLogOptions, } from '../run-terminal-logging.js';
15
+ import { VaultCliError } from '@murphai/assistant-core/vault-cli-errors';
16
+ import { requestIdSchema } from '@murphai/assistant-core/vault-cli-contracts';
17
+ const assistantSessionOptionFields = {
18
+ session: z
19
+ .string()
20
+ .min(1)
21
+ .optional()
22
+ .describe('Existing Murph assistant session id to resume.'),
23
+ alias: z
24
+ .string()
25
+ .min(1)
26
+ .optional()
27
+ .describe('Optional stable alias used to map an external conversation onto one assistant session.'),
28
+ channel: z
29
+ .string()
30
+ .min(1)
31
+ .optional()
32
+ .describe('Optional channel label such as imessage, telegram, linq, or email.'),
33
+ identity: z
34
+ .string()
35
+ .min(1)
36
+ .optional()
37
+ .describe('Optional local assistant identity id for multi-user routing.'),
38
+ participant: z
39
+ .string()
40
+ .min(1)
41
+ .optional()
42
+ .describe('Optional remote actor id for multi-user routing and direct-conversation binding.'),
43
+ sourceThread: z
44
+ .string()
45
+ .min(1)
46
+ .optional()
47
+ .describe('Optional upstream thread id from the source channel. Thread ids anchor stored conversation bindings when present.'),
48
+ };
49
+ const assistantProviderOptionFields = {
50
+ provider: z
51
+ .enum(assistantChatProviderValues)
52
+ .optional()
53
+ .describe('Chat provider adapter for the local assistant surface. The runtime is provider-backed even when only one adapter is installed.'),
54
+ codexCommand: z
55
+ .string()
56
+ .min(1)
57
+ .optional()
58
+ .describe('Optional Codex CLI executable path. Defaults to `codex`.'),
59
+ model: z
60
+ .string()
61
+ .min(1)
62
+ .optional()
63
+ .describe('Optional provider model override for local chat turns.'),
64
+ baseUrl: z
65
+ .string()
66
+ .min(1)
67
+ .optional()
68
+ .describe('Optional OpenAI-compatible base URL for local assistant chat, such as http://127.0.0.1:11434/v1 for Ollama.'),
69
+ apiKeyEnv: z
70
+ .string()
71
+ .min(1)
72
+ .optional()
73
+ .describe('Optional environment variable name that stores the OpenAI-compatible API key for local assistant chat.'),
74
+ providerName: z
75
+ .string()
76
+ .min(1)
77
+ .optional()
78
+ .describe('Optional stable provider label for OpenAI-compatible local assistant chat sessions.'),
79
+ headersJson: z
80
+ .string()
81
+ .min(1)
82
+ .optional()
83
+ .describe('Optional JSON object of extra HTTP headers for OpenAI-compatible local assistant chat sessions.'),
84
+ sandbox: z
85
+ .enum(assistantSandboxValues)
86
+ .optional()
87
+ .describe('Codex sandbox mode for local assistant chat. Codex runs as a privileged local adapter by default, so leaving this unset keeps its normal unsandboxed behavior.'),
88
+ approvalPolicy: z
89
+ .enum(assistantApprovalPolicyValues)
90
+ .optional()
91
+ .describe('Codex approval policy for local assistant chat. Defaults to never for the privileged local Codex adapter.'),
92
+ profile: z
93
+ .string()
94
+ .min(1)
95
+ .optional()
96
+ .describe('Optional Codex config profile name.'),
97
+ oss: z
98
+ .boolean()
99
+ .optional()
100
+ .describe('Use Codex OSS mode, which expects a local Ollama-backed open-source provider.'),
101
+ };
102
+ const assistantDeliveryOptionFields = {
103
+ deliverResponse: z
104
+ .boolean()
105
+ .optional()
106
+ .describe('After generating a response, deliver it over the mapped outbound channel session when available.'),
107
+ deliveryTarget: z
108
+ .string()
109
+ .min(1)
110
+ .optional()
111
+ .describe('Optional one-send outbound target override. For iMessage this can be a phone number, email handle, or chat id; for Telegram it can be a chat id or <chatId>:topic:<messageThreadId>; for Linq it can be a chat id; for email it can be a recipient address while thread-bound sessions reply in place.'),
112
+ };
113
+ const assistantCronDeliveryOptionFields = {
114
+ deliveryTarget: z
115
+ .string()
116
+ .min(1)
117
+ .optional()
118
+ .describe('Optional explicit outbound destination for each cron run. For iMessage this can be a phone number, email handle, or chat id; for Telegram it can be a chat id or <chatId>:topic:<messageThreadId>; for Linq it can be a chat id; for email it can be a recipient address while thread-bound cron jobs reply in place.'),
119
+ };
120
+ const assistantCronTargetSourceOptionFields = {
121
+ copyFrom: z
122
+ .string()
123
+ .min(1)
124
+ .optional()
125
+ .describe('Copy the current delivery target from another assistant cron job instead of providing route flags directly.'),
126
+ dryRun: z
127
+ .boolean()
128
+ .optional()
129
+ .describe('Validate and preview the target change without writing scheduler state.'),
130
+ resetContinuity: z
131
+ .boolean()
132
+ .optional()
133
+ .describe('Clear the saved sessionId and alias while retargeting so the next run starts fresh instead of rebinding the existing assistant session.'),
134
+ toSelf: z
135
+ .string()
136
+ .min(1)
137
+ .optional()
138
+ .describe('Use the saved self-delivery target for one channel, such as email or telegram.'),
139
+ };
140
+ const assistantCronStateOptionFields = {
141
+ state: z
142
+ .boolean()
143
+ .optional()
144
+ .describe('Bind this cron job to a default assistant state document under assistant-state/state/cron/<jobId>.json. Use this only when the cron needs run-to-run scratch state such as cooldowns, dedupe, unresolved follow-ups, or delivery policy.'),
145
+ stateDoc: z
146
+ .string()
147
+ .min(1)
148
+ .optional()
149
+ .describe('Optional explicit assistant state document id to bind to this cron job, such as cron/weekly-health-snapshot. Prefer this only when the cron needs stable cross-run scratch state or must share one state doc across related jobs.'),
150
+ };
151
+ const assistantSelfDeliveryTargetOptionFields = {
152
+ identity: z
153
+ .string()
154
+ .min(1)
155
+ .optional()
156
+ .describe('Optional local assistant identity id to reuse for this saved channel target.'),
157
+ participant: z
158
+ .string()
159
+ .min(1)
160
+ .optional()
161
+ .describe('Optional remote actor id to reuse for this saved channel target.'),
162
+ sourceThread: z
163
+ .string()
164
+ .min(1)
165
+ .optional()
166
+ .describe('Optional upstream thread id to reuse for this saved channel target.'),
167
+ deliveryTarget: z
168
+ .string()
169
+ .min(1)
170
+ .optional()
171
+ .describe('Optional explicit outbound destination to save for this channel target, such as a phone number, Telegram chat id, or email address.'),
172
+ };
173
+ function parseAssistantCronPresetVariables(value) {
174
+ const entries = normalizeRepeatableFlagOption(value, 'var') ?? [];
175
+ const variables = {};
176
+ for (const entry of entries) {
177
+ const separatorIndex = entry.indexOf('=');
178
+ if (separatorIndex <= 0 || separatorIndex === entry.length - 1) {
179
+ throw new VaultCliError('invalid_option', 'Preset variables must use key=value form. Repeat --var for multiple values.');
180
+ }
181
+ const key = entry.slice(0, separatorIndex).trim();
182
+ const variableValue = entry.slice(separatorIndex + 1).trim();
183
+ if (key.length === 0 || variableValue.length === 0) {
184
+ throw new VaultCliError('invalid_option', 'Preset variables must use key=value form with non-empty keys and values.');
185
+ }
186
+ if (Object.hasOwn(variables, key)) {
187
+ throw new VaultCliError('invalid_option', `Preset variable "${key}" was provided more than once. Repeat --var only for different keys.`);
188
+ }
189
+ variables[key] = variableValue;
190
+ }
191
+ return variables;
192
+ }
193
+ function assertAssistantSelfDeliveryTargetInput(input) {
194
+ if (!input.deliveryTarget && !input.participant && !input.sourceThread) {
195
+ throw new VaultCliError('invalid_option', 'Saved self delivery targets require at least --participant, --sourceThread, or --deliveryTarget.');
196
+ }
197
+ if (input.channel === 'email' && !input.identity) {
198
+ throw new VaultCliError('invalid_option', 'Saved email self delivery targets require --identity with the configured AgentMail inbox id.');
199
+ }
200
+ }
201
+ const assistantChatArgsSchema = z.object({
202
+ prompt: z
203
+ .string()
204
+ .min(1)
205
+ .optional()
206
+ .describe('Optional first prompt to send before the chat loop starts.'),
207
+ });
208
+ const assistantChatOptionsSchema = withBaseOptions({
209
+ ...assistantSessionOptionFields,
210
+ ...assistantProviderOptionFields,
211
+ });
212
+ function createAssistantStatusCommandDefinition(input) {
213
+ return {
214
+ args: emptyArgsSchema,
215
+ description: input?.description ??
216
+ 'Show a compact assistant-state snapshot including recent turn receipts and the outbound outbox backlog.',
217
+ hint: input?.hint ??
218
+ 'Use this when the assistant feels stuck, duplicated a send, or you want the latest receipt timeline without opening files under assistant-state/.',
219
+ options: withBaseOptions({
220
+ session: z
221
+ .string()
222
+ .min(1)
223
+ .optional()
224
+ .describe('Optional assistant session id to scope the recent turn receipts.'),
225
+ limit: z
226
+ .number()
227
+ .int()
228
+ .positive()
229
+ .max(50)
230
+ .default(5)
231
+ .describe('Maximum number of recent sessions, turns, and pending outbox intents to return.'),
232
+ }),
233
+ output: assistantStatusResultSchema,
234
+ async run(context) {
235
+ return getAssistantStatus({
236
+ vault: context.options.vault,
237
+ sessionId: context.options.session,
238
+ limit: context.options.limit,
239
+ });
240
+ },
241
+ };
242
+ }
243
+ function createAssistantDoctorCommandDefinition(input) {
244
+ return {
245
+ args: emptyArgsSchema,
246
+ description: input?.description ??
247
+ 'Run lightweight assistant-state diagnostics for session files, receipts, transcripts, automation state, and the outbound outbox.',
248
+ hint: input?.hint ??
249
+ 'Use --repair to migrate legacy inline secret headers into private sidecars and to tighten assistant-state permissions in place.',
250
+ options: withBaseOptions({
251
+ repair: z
252
+ .boolean()
253
+ .default(false)
254
+ .describe('Repair assistant-state secrecy issues in place by moving legacy inline secret headers into private sidecars and fixing private file permissions.'),
255
+ }),
256
+ output: assistantDoctorResultSchema,
257
+ async run(context) {
258
+ return runAssistantDoctor(context.options.vault, {
259
+ repair: context.options.repair,
260
+ });
261
+ },
262
+ };
263
+ }
264
+ function createAssistantStopCommandDefinition(input) {
265
+ return {
266
+ args: emptyArgsSchema,
267
+ description: input?.description ??
268
+ 'Stop the assistant automation loop for this vault and clear stale run-lock state when the recorded process is already gone.',
269
+ hint: input?.hint ??
270
+ 'Use this to recover from a stuck `assistant run` / `murph run`. Murph sends SIGTERM first, waits briefly, and only force-kills the recorded PID if it refuses to exit.',
271
+ options: withBaseOptions(),
272
+ output: assistantStopResultSchema,
273
+ async run(context) {
274
+ return stopAssistantAutomation({
275
+ vault: context.options.vault,
276
+ });
277
+ },
278
+ };
279
+ }
280
+ function buildAssistantVaultResultPath(vault) {
281
+ return {
282
+ vault: redactAssistantDisplayPath(vault),
283
+ };
284
+ }
285
+ function buildAssistantStateRootResultPaths(vault, stateRoot) {
286
+ return {
287
+ ...buildAssistantVaultResultPath(vault),
288
+ stateRoot: redactAssistantDisplayPath(stateRoot),
289
+ };
290
+ }
291
+ function buildAssistantStateResultPaths(vault) {
292
+ const statePaths = resolveAssistantStatePaths(vault);
293
+ return buildAssistantStateRootResultPaths(vault, statePaths.assistantStateRoot);
294
+ }
295
+ function buildAssistantStateDocumentResultPaths(vault) {
296
+ const statePaths = resolveAssistantStatePaths(vault);
297
+ return {
298
+ ...buildAssistantStateRootResultPaths(vault, statePaths.assistantStateRoot),
299
+ documentsRoot: redactAssistantDisplayPath(statePaths.stateDirectory),
300
+ };
301
+ }
302
+ function buildAssistantMemoryResultPaths(vault) {
303
+ const statePaths = resolveAssistantMemoryStoragePaths(vault);
304
+ return buildAssistantStateRootResultPaths(vault, statePaths.assistantStateRoot);
305
+ }
306
+ function buildAssistantCronResultPaths(vault) {
307
+ const statePaths = resolveAssistantStatePaths(vault);
308
+ return {
309
+ ...buildAssistantStateRootResultPaths(vault, statePaths.assistantStateRoot),
310
+ jobsPath: redactAssistantDisplayPath(statePaths.cronJobsPath),
311
+ runsRoot: redactAssistantDisplayPath(statePaths.cronRunsDirectory),
312
+ };
313
+ }
314
+ function buildAssistantOperatorConfigResult() {
315
+ return {
316
+ configPath: redactAssistantDisplayPath(resolveOperatorConfigPath()),
317
+ };
318
+ }
319
+ function assistantConversationOptionsFromCli(options) {
320
+ return {
321
+ sessionId: options.session,
322
+ alias: options.alias,
323
+ channel: options.channel,
324
+ identityId: options.identity,
325
+ participantId: options.participant,
326
+ sourceThreadId: options.sourceThread,
327
+ };
328
+ }
329
+ function assistantProviderOverridesFromCli(options) {
330
+ const headers = parseHeadersJsonOption(options.headersJson);
331
+ return {
332
+ provider: options.provider,
333
+ codexCommand: options.codexCommand,
334
+ model: options.model,
335
+ baseUrl: options.baseUrl,
336
+ apiKeyEnv: options.apiKeyEnv,
337
+ providerName: options.providerName,
338
+ sandbox: options.sandbox,
339
+ approvalPolicy: options.approvalPolicy,
340
+ profile: options.profile,
341
+ oss: options.oss,
342
+ ...(headers ? { headers } : {}),
343
+ };
344
+ }
345
+ function assistantDeliveryOverridesFromCli(options) {
346
+ return {
347
+ deliverResponse: options.deliverResponse,
348
+ deliveryTarget: options.deliveryTarget,
349
+ };
350
+ }
351
+ async function resolveAssistantDeliveryRouteFromCli(input) {
352
+ return applyAssistantSelfDeliveryTargetDefaults({
353
+ channel: input.channel,
354
+ identityId: input.identity,
355
+ participantId: input.participant,
356
+ sourceThreadId: input.sourceThread,
357
+ deliveryTarget: input.deliveryTarget,
358
+ }, {
359
+ allowSingleSavedTargetFallback: input.allowSingleSavedTargetFallback,
360
+ });
361
+ }
362
+ function assistantCronStateOptionsFromCli(options) {
363
+ return {
364
+ bindState: options.state,
365
+ stateDocId: options.stateDoc,
366
+ };
367
+ }
368
+ async function resolveAssistantCronTargetFromCli(input) {
369
+ const hasExplicitRoute = Boolean(input.channel) ||
370
+ Boolean(input.identity) ||
371
+ Boolean(input.participant) ||
372
+ Boolean(input.sourceThread) ||
373
+ Boolean(input.deliveryTarget);
374
+ const selectedSources = [
375
+ input.toSelf ? 'to-self' : null,
376
+ input.copyFrom ? 'copy-from' : null,
377
+ hasExplicitRoute ? 'explicit-route' : null,
378
+ ].filter((value) => value !== null);
379
+ if (selectedSources.length !== 1) {
380
+ throw new VaultCliError('invalid_option', 'Provide exactly one cron target source: --toSelf <channel>, --copyFrom <job>, or an explicit route via --channel/--identity/--participant/--sourceThread/--deliveryTarget.');
381
+ }
382
+ if (input.toSelf) {
383
+ const savedTarget = await resolveAssistantSelfDeliveryTarget(input.toSelf);
384
+ if (!savedTarget) {
385
+ throw new VaultCliError('ASSISTANT_SELF_TARGET_NOT_FOUND', `No saved self-delivery target exists for channel "${input.toSelf}". Save one first with \`assistant self-target set ${input.toSelf} ...\`.`);
386
+ }
387
+ return {
388
+ channel: savedTarget.channel,
389
+ identityId: savedTarget.identityId ?? undefined,
390
+ participantId: savedTarget.participantId ?? undefined,
391
+ sourceThreadId: savedTarget.sourceThreadId ?? undefined,
392
+ deliveryTarget: savedTarget.deliveryTarget ?? undefined,
393
+ };
394
+ }
395
+ if (input.copyFrom) {
396
+ const sourceJobTarget = await getAssistantCronJobTarget(input.vault, input.copyFrom);
397
+ return {
398
+ channel: sourceJobTarget.target.channel ?? undefined,
399
+ identityId: sourceJobTarget.target.identityId ?? undefined,
400
+ participantId: sourceJobTarget.target.participantId ?? undefined,
401
+ sourceThreadId: sourceJobTarget.target.sourceThreadId ?? undefined,
402
+ deliveryTarget: sourceJobTarget.target.deliveryTarget ?? undefined,
403
+ };
404
+ }
405
+ const resolvedRoute = await resolveAssistantDeliveryRouteFromCli({
406
+ allowSingleSavedTargetFallback: false,
407
+ channel: input.channel,
408
+ identity: input.identity,
409
+ participant: input.participant,
410
+ sourceThread: input.sourceThread,
411
+ deliveryTarget: input.deliveryTarget,
412
+ });
413
+ return {
414
+ channel: resolvedRoute.channel ?? undefined,
415
+ identityId: resolvedRoute.identityId ?? undefined,
416
+ participantId: resolvedRoute.participantId ?? undefined,
417
+ sourceThreadId: resolvedRoute.sourceThreadId ?? undefined,
418
+ deliveryTarget: resolvedRoute.deliveryTarget ?? undefined,
419
+ };
420
+ }
421
+ async function runAssistantChatCommand(context) {
422
+ const result = await runAssistantChat({
423
+ vault: context.options.vault,
424
+ includeFirstTurnCheckIn: true,
425
+ initialPrompt: context.args.prompt,
426
+ ...assistantConversationOptionsFromCli(context.options),
427
+ ...assistantProviderOverridesFromCli(context.options),
428
+ });
429
+ if (!context.agent && !context.formatExplicit) {
430
+ process.stderr.write(`Resume chat by typing: ${formatAssistantChatResumeCommand(result.session.sessionId)}\n`);
431
+ }
432
+ return result;
433
+ }
434
+ function formatAssistantChatResumeCommand(sessionId) {
435
+ return `murph chat --session "${sessionId}"`;
436
+ }
437
+ function createAssistantChatCommandDefinition(input) {
438
+ return {
439
+ args: assistantChatArgsSchema,
440
+ description: input?.description ??
441
+ 'Open an Ink terminal chat UI backed by the chosen provider while Murph stores session metadata plus a local transcript outside the canonical vault.',
442
+ hint: input?.hint ??
443
+ 'Type /exit to close the chat loop or /session to print the current Murph session id.',
444
+ options: assistantChatOptionsSchema,
445
+ output: assistantChatResultSchema,
446
+ outputPolicy: 'agent-only',
447
+ run: runAssistantChatCommand,
448
+ };
449
+ }
450
+ const assistantRunOptionsSchema = withBaseOptions({
451
+ model: z
452
+ .string()
453
+ .min(1)
454
+ .optional()
455
+ .describe('Optional model id for canonical inbox triage routing, such as gpt-oss:20b or an AI Gateway model string. Omit it when you only want channel auto-reply.'),
456
+ baseUrl: z
457
+ .string()
458
+ .min(1)
459
+ .optional()
460
+ .describe('Optional OpenAI-compatible base URL for the inbox routing model.'),
461
+ apiKey: z
462
+ .string()
463
+ .min(1)
464
+ .optional()
465
+ .describe('Optional explicit API key for the OpenAI-compatible routing endpoint.'),
466
+ apiKeyEnv: z
467
+ .string()
468
+ .min(1)
469
+ .optional()
470
+ .describe('Optional environment variable name that stores the routing API key.'),
471
+ providerName: z
472
+ .string()
473
+ .min(1)
474
+ .optional()
475
+ .describe('Optional stable provider label for the routing endpoint.'),
476
+ headersJson: z
477
+ .string()
478
+ .min(1)
479
+ .optional()
480
+ .describe('Optional JSON object of extra HTTP headers for the routing endpoint.'),
481
+ scanIntervalMs: z
482
+ .number()
483
+ .int()
484
+ .positive()
485
+ .max(60000)
486
+ .default(5000)
487
+ .describe('Polling interval between inbox scans when running continuously.'),
488
+ maxPerScan: z
489
+ .number()
490
+ .int()
491
+ .positive()
492
+ .max(200)
493
+ .default(50)
494
+ .describe('Maximum inbox captures to inspect during each assistant scan.'),
495
+ allowSelfAuthored: z
496
+ .boolean()
497
+ .optional()
498
+ .describe('Allow self-authored captures to trigger channel auto-reply. Useful for texting your own Mac, but only safe when you dedicate a self-chat thread to Murph.'),
499
+ sessionRolloverHours: z
500
+ .number()
501
+ .int()
502
+ .positive()
503
+ .max(24 * 30)
504
+ .optional()
505
+ .describe('Optional maximum age for a reused assistant session in hours before Murph starts a new one for the same channel thread.'),
506
+ once: z
507
+ .boolean()
508
+ .optional()
509
+ .describe('Run one assistant scan and then exit.'),
510
+ skipDaemon: z
511
+ .boolean()
512
+ .optional()
513
+ .describe('Do not start the inbox foreground daemon; only run the assistant scan loop.'),
514
+ });
515
+ function createAssistantRunCommandDefinition(inboxServices, vaultServices, input) {
516
+ return {
517
+ args: emptyArgsSchema,
518
+ description: input?.description ??
519
+ 'Start the local assistant automation loop that watches the inbox runtime, runs due assistant cron jobs, auto-replies over configured channels such as iMessage or Telegram, and optionally applies model-routed canonical promotions.',
520
+ hint: input?.hint ??
521
+ 'Use --baseUrl with a local OpenAI-compatible model endpoint such as Ollama when you also want canonical inbox triage. Channel auto-reply can run without a routing model, and assistant cron schedules fire while this loop is active.',
522
+ examples: [
523
+ {
524
+ options: {
525
+ vault: './vault',
526
+ model: 'gpt-oss:20b',
527
+ baseUrl: 'http://127.0.0.1:11434/v1',
528
+ },
529
+ description: 'Run the always-on inbox assistant against a local Ollama model.',
530
+ },
531
+ {
532
+ options: {
533
+ vault: './vault',
534
+ model: 'gpt-oss:20b',
535
+ baseUrl: 'http://127.0.0.1:11434/v1',
536
+ once: true,
537
+ skipDaemon: true,
538
+ },
539
+ description: 'Run a single inbox scan without starting the foreground daemon.',
540
+ },
541
+ {
542
+ options: {
543
+ vault: './vault',
544
+ allowSelfAuthored: true,
545
+ sessionRolloverHours: 48,
546
+ },
547
+ description: 'Run dedicated iMessage self-chat mode with two-day session rollover.',
548
+ },
549
+ ],
550
+ options: assistantRunOptionsSchema,
551
+ output: assistantRunResultSchema,
552
+ async run(context) {
553
+ const terminalLogOptions = resolveForegroundTerminalLogOptions(process.env);
554
+ return runAssistantAutomation({
555
+ inboxServices,
556
+ vaultServices,
557
+ vault: context.options.vault,
558
+ requestId: requestIdFromOptions(context.options),
559
+ modelSpec: context.options.model
560
+ ? {
561
+ model: context.options.model,
562
+ baseUrl: context.options.baseUrl,
563
+ apiKey: context.options.apiKey,
564
+ apiKeyEnv: context.options.apiKeyEnv,
565
+ providerName: context.options.providerName,
566
+ headers: parseHeadersJsonOption(context.options.headersJson),
567
+ }
568
+ : undefined,
569
+ scanIntervalMs: context.options.scanIntervalMs,
570
+ maxPerScan: context.options.maxPerScan,
571
+ allowSelfAuthored: context.options.allowSelfAuthored,
572
+ sessionMaxAgeMs: typeof context.options.sessionRolloverHours === 'number'
573
+ ? context.options.sessionRolloverHours * 60 * 60 * 1000
574
+ : null,
575
+ once: context.options.once,
576
+ startDaemon: context.options.skipDaemon ? false : true,
577
+ onEvent(event) {
578
+ const message = formatAssistantRunEventForTerminal(event, terminalLogOptions);
579
+ if (message) {
580
+ console.error(formatForegroundLogLine('assistant', message));
581
+ }
582
+ },
583
+ onInboxEvent(event) {
584
+ const message = formatInboxRunEventForTerminal(event, terminalLogOptions);
585
+ if (message) {
586
+ console.error(formatForegroundLogLine('assistant', message));
587
+ }
588
+ },
589
+ });
590
+ },
591
+ };
592
+ }
593
+ export function registerAssistantCommands(cli, inboxServices, vaultServices) {
594
+ const assistant = Cli.create('assistant', {
595
+ description: 'Murph-native assistant runtime with provider-backed local chat sessions, Ink terminal chat, outbound delivery, and auto-routing inbox automation.',
596
+ });
597
+ const registerConversationCommands = () => {
598
+ assistant.command('ask', {
599
+ args: z.object({
600
+ prompt: z.string().min(1).describe('Prompt to send to the local assistant session.'),
601
+ }),
602
+ description: 'Send one message through the local provider-backed assistant and persist session metadata plus a local transcript outside the canonical vault.',
603
+ hint: 'Murph persists a local transcript plus per-session metadata under assistant-state/, and still reuses provider-side history when available. Use --deliverResponse to send the assistant reply back out over a mapped channel such as iMessage, Telegram, or email.',
604
+ examples: [
605
+ {
606
+ args: {
607
+ prompt: 'Summarize the latest documents in this vault.',
608
+ },
609
+ options: {
610
+ vault: './vault',
611
+ },
612
+ description: 'Start a new local assistant session rooted at the vault directory.',
613
+ },
614
+ {
615
+ args: {
616
+ prompt: 'Send a quick check-in about lunch.',
617
+ },
618
+ options: {
619
+ vault: './vault',
620
+ channel: 'imessage',
621
+ participant: '+15551234567',
622
+ deliverResponse: true,
623
+ },
624
+ description: 'Generate a reply locally and deliver it over iMessage.',
625
+ },
626
+ {
627
+ args: {
628
+ prompt: 'Reply that I can review the latest labs tonight.',
629
+ },
630
+ options: {
631
+ vault: './vault',
632
+ channel: 'telegram',
633
+ participant: '123456789',
634
+ sourceThread: '123456789',
635
+ deliverResponse: true,
636
+ },
637
+ description: 'Generate a reply locally and deliver it back into a Telegram bot chat.',
638
+ },
639
+ {
640
+ args: {
641
+ prompt: "Send today's summary by email.",
642
+ },
643
+ options: {
644
+ vault: './vault',
645
+ channel: 'email',
646
+ identity: 'inbox_123',
647
+ participant: 'you@example.com',
648
+ deliverResponse: true,
649
+ },
650
+ description: 'Generate a reply locally and deliver it over AgentMail email.',
651
+ },
652
+ ],
653
+ options: withBaseOptions({
654
+ ...assistantSessionOptionFields,
655
+ ...assistantProviderOptionFields,
656
+ ...assistantDeliveryOptionFields,
657
+ }),
658
+ output: assistantAskResultSchema,
659
+ async run(context) {
660
+ const deliveryOverrides = assistantDeliveryOverridesFromCli(context.options);
661
+ const savedRoute = deliveryOverrides.deliverResponse && !context.options.session
662
+ ? await resolveAssistantDeliveryRouteFromCli({
663
+ allowSingleSavedTargetFallback: true,
664
+ channel: context.options.channel,
665
+ identity: context.options.identity,
666
+ participant: context.options.participant,
667
+ sourceThread: context.options.sourceThread,
668
+ deliveryTarget: deliveryOverrides.deliveryTarget,
669
+ })
670
+ : null;
671
+ return sendAssistantMessage({
672
+ vault: context.options.vault,
673
+ prompt: context.args.prompt,
674
+ ...assistantConversationOptionsFromCli({
675
+ ...context.options,
676
+ channel: savedRoute?.channel ?? context.options.channel,
677
+ identity: savedRoute?.identityId ?? context.options.identity,
678
+ participant: savedRoute?.participantId ?? context.options.participant,
679
+ sourceThread: savedRoute?.sourceThreadId ?? context.options.sourceThread,
680
+ }),
681
+ ...assistantProviderOverridesFromCli(context.options),
682
+ ...deliveryOverrides,
683
+ deliveryTarget: savedRoute?.deliveryTarget ?? deliveryOverrides.deliveryTarget,
684
+ });
685
+ },
686
+ });
687
+ assistant.command('chat', createAssistantChatCommandDefinition());
688
+ assistant.command('deliver', {
689
+ args: z.object({
690
+ message: z
691
+ .string()
692
+ .min(1)
693
+ .describe('Outbound message body to deliver over the mapped assistant channel.'),
694
+ }),
695
+ description: 'Deliver one outbound assistant message without invoking the chat provider. iMessage, Telegram, Linq, and email all use the same stored assistant channel binding surface.',
696
+ hint: 'Use --deliveryTarget to override the stored delivery target for one send only. For iMessage that target can be a phone number, email handle, or chat id; for Telegram it can be a chat id or <chatId>:topic:<messageThreadId>; for Linq it can be a chat id; for email it can be a recipient address while thread-bound sessions reply in place.',
697
+ examples: [
698
+ {
699
+ args: {
700
+ message: 'Here is your nutrition recap for lunch.',
701
+ },
702
+ options: {
703
+ vault: './vault',
704
+ channel: 'imessage',
705
+ participant: '+15551234567',
706
+ },
707
+ description: 'Send a direct iMessage to one participant.',
708
+ },
709
+ {
710
+ args: {
711
+ message: 'I saw the message and queued your follow-up.',
712
+ },
713
+ options: {
714
+ vault: './vault',
715
+ channel: 'linq',
716
+ sourceThread: 'chat_123',
717
+ deliveryTarget: 'chat_123',
718
+ },
719
+ description: 'Send a Linq reply back into the same direct chat.',
720
+ },
721
+ {
722
+ args: {
723
+ message: 'I imported that lab report and queued the parser.',
724
+ },
725
+ options: {
726
+ vault: './vault',
727
+ channel: 'telegram',
728
+ sourceThread: '-1001234567890:topic:42',
729
+ deliveryTarget: '-1001234567890:topic:42',
730
+ },
731
+ description: 'Send a Telegram reply into a specific chat topic.',
732
+ },
733
+ {
734
+ args: {
735
+ message: 'Your weekly summary is ready.',
736
+ },
737
+ options: {
738
+ vault: './vault',
739
+ channel: 'email',
740
+ identity: 'inbox_123',
741
+ deliveryTarget: 'you@example.com',
742
+ },
743
+ description: 'Send a one-off outbound summary email through an AgentMail inbox.',
744
+ },
745
+ {
746
+ args: {
747
+ message: 'I imported that lab report and queued the parser.',
748
+ },
749
+ options: {
750
+ vault: './vault',
751
+ session: 'asst_123',
752
+ deliveryTarget: 'chat45e2b868',
753
+ },
754
+ description: 'Reuse an existing session and override the outbound target for one message.',
755
+ },
756
+ ],
757
+ options: withBaseOptions({
758
+ ...assistantSessionOptionFields,
759
+ deliveryTarget: z
760
+ .string()
761
+ .min(1)
762
+ .optional()
763
+ .describe('Optional one-send outbound target override. For iMessage this can be a phone number, email handle, or chat id; for Telegram it can be a chat id or <chatId>:topic:<messageThreadId>; for Linq it can be a chat id; for email it can be a recipient address while thread-bound sessions reply in place.'),
764
+ }),
765
+ output: assistantDeliverResultSchema,
766
+ async run(context) {
767
+ const deliveryOverrides = assistantDeliveryOverridesFromCli(context.options);
768
+ const savedRoute = context.options.session
769
+ ? null
770
+ : await resolveAssistantDeliveryRouteFromCli({
771
+ allowSingleSavedTargetFallback: true,
772
+ channel: context.options.channel,
773
+ identity: context.options.identity,
774
+ participant: context.options.participant,
775
+ sourceThread: context.options.sourceThread,
776
+ deliveryTarget: deliveryOverrides.deliveryTarget,
777
+ });
778
+ return deliverAssistantMessage({
779
+ vault: context.options.vault,
780
+ message: context.args.message,
781
+ ...assistantConversationOptionsFromCli({
782
+ ...context.options,
783
+ channel: savedRoute?.channel ?? context.options.channel,
784
+ identity: savedRoute?.identityId ?? context.options.identity,
785
+ participant: savedRoute?.participantId ?? context.options.participant,
786
+ sourceThread: savedRoute?.sourceThreadId ?? context.options.sourceThread,
787
+ }),
788
+ target: savedRoute?.deliveryTarget ?? deliveryOverrides.deliveryTarget,
789
+ });
790
+ },
791
+ });
792
+ assistant.command('run', createAssistantRunCommandDefinition(inboxServices, vaultServices));
793
+ };
794
+ const registerStateCommands = () => {
795
+ const state = Cli.create('state', {
796
+ description: 'Inspect and update small non-canonical assistant state documents stored outside the vault under assistant-state/state.',
797
+ });
798
+ state.command('list', {
799
+ args: emptyArgsSchema,
800
+ description: 'List assistant state documents, optionally filtered by a prefix namespace.',
801
+ hint: 'Use prefixes such as `cron` or `cron/<jobId>` to narrow the scratchpad documents that belong to one workflow.',
802
+ options: withBaseOptions({
803
+ prefix: z
804
+ .string()
805
+ .min(1)
806
+ .optional()
807
+ .describe('Optional slash-delimited document prefix such as cron or cron/job_123.'),
808
+ }),
809
+ output: assistantStateListResultSchema,
810
+ async run(context) {
811
+ const documents = await listAssistantStateDocuments({
812
+ vault: context.options.vault,
813
+ prefix: context.options.prefix,
814
+ });
815
+ return {
816
+ ...buildAssistantStateDocumentResultPaths(context.options.vault),
817
+ prefix: context.options.prefix ?? null,
818
+ documents: documents.map(redactAssistantStateDocumentListEntry),
819
+ };
820
+ },
821
+ });
822
+ state.command('show', {
823
+ args: z.object({
824
+ doc: z.string().min(1).describe('Assistant state document id such as cron/job_123.'),
825
+ }),
826
+ description: 'Show one assistant state document by id.',
827
+ options: withBaseOptions(),
828
+ output: assistantStateShowResultSchema,
829
+ async run(context) {
830
+ const document = await getAssistantStateDocument({
831
+ vault: context.options.vault,
832
+ docId: context.args.doc,
833
+ });
834
+ return {
835
+ ...buildAssistantStateDocumentResultPaths(context.options.vault),
836
+ document: redactAssistantStateDocumentSnapshot(document),
837
+ };
838
+ },
839
+ });
840
+ state.command('put', {
841
+ args: z.object({
842
+ doc: z.string().min(1).describe('Assistant state document id such as cron/job_123.'),
843
+ }),
844
+ description: 'Replace one assistant state document with a JSON object payload.',
845
+ hint: 'Use this for full replacement. For incremental updates that preserve existing keys, use `assistant state patch`.',
846
+ options: withBaseOptions({
847
+ input: inputFileOptionSchema.describe('JSON object payload in @file.json form or - for stdin.'),
848
+ }),
849
+ output: assistantStatePutResultSchema,
850
+ async run(context) {
851
+ const value = await loadJsonInputObject(context.options.input, 'assistant state document');
852
+ const document = await putAssistantStateDocument({
853
+ vault: context.options.vault,
854
+ docId: context.args.doc,
855
+ value,
856
+ });
857
+ return {
858
+ ...buildAssistantStateDocumentResultPaths(context.options.vault),
859
+ document: redactAssistantStateDocumentSnapshot(document),
860
+ };
861
+ },
862
+ });
863
+ state.command('patch', {
864
+ args: z.object({
865
+ doc: z.string().min(1).describe('Assistant state document id such as cron/job_123.'),
866
+ }),
867
+ description: 'Merge-patch one assistant state document with a JSON object payload.',
868
+ hint: 'This uses JSON Merge Patch semantics: object keys merge recursively, `null` deletes a key, and arrays replace the previous value.',
869
+ options: withBaseOptions({
870
+ input: inputFileOptionSchema.describe('JSON object merge-patch payload in @file.json form or - for stdin.'),
871
+ }),
872
+ output: assistantStatePatchResultSchema,
873
+ async run(context) {
874
+ const patch = await loadJsonInputObject(context.options.input, 'assistant state patch');
875
+ const document = await patchAssistantStateDocument({
876
+ vault: context.options.vault,
877
+ docId: context.args.doc,
878
+ patch,
879
+ });
880
+ return {
881
+ ...buildAssistantStateDocumentResultPaths(context.options.vault),
882
+ document: redactAssistantStateDocumentSnapshot(document),
883
+ };
884
+ },
885
+ });
886
+ state.command('delete', {
887
+ args: z.object({
888
+ doc: z.string().min(1).describe('Assistant state document id such as cron/job_123.'),
889
+ }),
890
+ description: 'Delete one assistant state document by id.',
891
+ options: withBaseOptions(),
892
+ output: assistantStateDeleteResultSchema,
893
+ async run(context) {
894
+ const result = await deleteAssistantStateDocument({
895
+ vault: context.options.vault,
896
+ docId: context.args.doc,
897
+ });
898
+ return {
899
+ ...buildAssistantStateDocumentResultPaths(context.options.vault),
900
+ docId: result.docId,
901
+ documentPath: redactAssistantDisplayPath(result.documentPath),
902
+ existed: result.existed,
903
+ };
904
+ },
905
+ });
906
+ assistant.command(state);
907
+ };
908
+ const registerMemoryCommands = () => {
909
+ const memory = Cli.create('memory', {
910
+ description: 'Inspect non-canonical assistant memory stored as Markdown outside the vault under assistant-state/.',
911
+ });
912
+ memory.command('search', {
913
+ args: emptyArgsSchema,
914
+ description: 'Search assistant memory across durable long-term notes and short-lived daily notes.',
915
+ hint: 'Use --scope long-term for durable identity/preferences/instructions and --scope daily for recent project context.',
916
+ examples: [
917
+ {
918
+ options: {
919
+ vault: './vault',
920
+ scope: 'long-term',
921
+ text: 'concise answers',
922
+ },
923
+ description: 'Search durable assistant response preferences.',
924
+ },
925
+ {
926
+ options: {
927
+ vault: './vault',
928
+ scope: 'daily',
929
+ limit: 5,
930
+ },
931
+ description: 'List the latest recent-context notes.',
932
+ },
933
+ ],
934
+ options: withBaseOptions({
935
+ text: z
936
+ .string()
937
+ .min(1)
938
+ .optional()
939
+ .describe('Optional lexical query for assistant memory search.'),
940
+ scope: z
941
+ .enum(assistantMemoryQueryScopeValues)
942
+ .default('all')
943
+ .describe('Choose long-term memory, daily notes, or both.'),
944
+ section: z
945
+ .enum(assistantMemoryVisibleSectionValues)
946
+ .optional()
947
+ .describe('Optional section filter such as Identity or Notes.'),
948
+ limit: z
949
+ .number()
950
+ .int()
951
+ .positive()
952
+ .max(25)
953
+ .default(8)
954
+ .describe('Maximum number of assistant memory hits to return.'),
955
+ }),
956
+ output: assistantMemorySearchResultSchema,
957
+ async run(context) {
958
+ const turnContext = resolveAssistantMemoryTurnContext();
959
+ if (turnContext) {
960
+ assertAssistantMemoryTurnContextVault(turnContext, context.options.vault);
961
+ }
962
+ const result = await searchAssistantMemory({
963
+ vault: context.options.vault,
964
+ text: context.options.text,
965
+ scope: context.options.scope,
966
+ section: context.options.section,
967
+ limit: context.options.limit,
968
+ includeSensitiveHealthContext: turnContext?.allowSensitiveHealthContext ?? true,
969
+ });
970
+ return {
971
+ ...buildAssistantMemoryResultPaths(context.options.vault),
972
+ query: result.query,
973
+ scope: result.scope,
974
+ section: result.section,
975
+ results: result.results.map(redactAssistantMemorySearchHit),
976
+ };
977
+ },
978
+ });
979
+ memory.command('get', {
980
+ args: z.object({
981
+ memoryId: z.string().min(1).describe('Assistant memory id returned by search.'),
982
+ }),
983
+ description: 'Fetch one assistant memory record by id.',
984
+ options: withBaseOptions(),
985
+ output: assistantMemoryGetResultSchema,
986
+ async run(context) {
987
+ const turnContext = resolveAssistantMemoryTurnContext();
988
+ if (turnContext) {
989
+ assertAssistantMemoryTurnContextVault(turnContext, context.options.vault);
990
+ }
991
+ const memoryRecord = await getAssistantMemory({
992
+ vault: context.options.vault,
993
+ id: context.args.memoryId,
994
+ includeSensitiveHealthContext: turnContext?.allowSensitiveHealthContext ?? true,
995
+ });
996
+ return {
997
+ ...buildAssistantMemoryResultPaths(context.options.vault),
998
+ memory: redactAssistantMemoryRecord(memoryRecord),
999
+ };
1000
+ },
1001
+ });
1002
+ assistant.command(memory);
1003
+ };
1004
+ const registerSelfTargetCommands = () => {
1005
+ const selfTarget = Cli.create('self-target', {
1006
+ description: 'Manage local saved self-delivery targets for outbound assistant actions without storing them in the canonical vault.',
1007
+ });
1008
+ selfTarget.command('list', {
1009
+ args: emptyArgsSchema,
1010
+ description: 'List saved self-delivery targets from local operator config.',
1011
+ options: z.object({
1012
+ requestId: requestIdSchema,
1013
+ }),
1014
+ output: assistantSelfDeliveryTargetListResultSchema,
1015
+ async run() {
1016
+ return {
1017
+ ...buildAssistantOperatorConfigResult(),
1018
+ targets: await listAssistantSelfDeliveryTargets(),
1019
+ };
1020
+ },
1021
+ });
1022
+ selfTarget.command('show', {
1023
+ args: z.object({
1024
+ channel: z.string().min(1).describe('Saved outbound channel to inspect.'),
1025
+ }),
1026
+ description: 'Show one saved self-delivery target for a specific outbound channel.',
1027
+ options: z.object({
1028
+ requestId: requestIdSchema,
1029
+ }),
1030
+ output: assistantSelfDeliveryTargetShowResultSchema,
1031
+ async run(context) {
1032
+ const targets = await listAssistantSelfDeliveryTargets();
1033
+ return {
1034
+ ...buildAssistantOperatorConfigResult(),
1035
+ target: targets.find((target) => target.channel === context.args.channel.trim().toLowerCase()) ??
1036
+ null,
1037
+ };
1038
+ },
1039
+ });
1040
+ selfTarget.command('set', {
1041
+ args: z.object({
1042
+ channel: z
1043
+ .string()
1044
+ .min(1)
1045
+ .describe('Outbound channel to save, such as telegram, imessage, linq, or email.'),
1046
+ }),
1047
+ description: 'Save or replace the local default outbound target for one channel.',
1048
+ hint: 'Use this after the user gives you a phone number, Telegram chat, or email target so later actions can reuse it without asking again.',
1049
+ options: z.object({
1050
+ requestId: requestIdSchema,
1051
+ ...assistantSelfDeliveryTargetOptionFields,
1052
+ }),
1053
+ output: assistantSelfDeliveryTargetSetResultSchema,
1054
+ async run(context) {
1055
+ const channel = context.args.channel.trim().toLowerCase();
1056
+ assertAssistantSelfDeliveryTargetInput({
1057
+ channel,
1058
+ identity: context.options.identity,
1059
+ participant: context.options.participant,
1060
+ sourceThread: context.options.sourceThread,
1061
+ deliveryTarget: context.options.deliveryTarget,
1062
+ });
1063
+ const target = await saveAssistantSelfDeliveryTarget({
1064
+ channel,
1065
+ identityId: context.options.identity ?? null,
1066
+ participantId: context.options.participant ?? null,
1067
+ sourceThreadId: context.options.sourceThread ?? null,
1068
+ deliveryTarget: context.options.deliveryTarget ?? null,
1069
+ });
1070
+ return {
1071
+ ...buildAssistantOperatorConfigResult(),
1072
+ target,
1073
+ };
1074
+ },
1075
+ });
1076
+ selfTarget.command('clear', {
1077
+ args: z.object({
1078
+ channel: z
1079
+ .string()
1080
+ .min(1)
1081
+ .optional()
1082
+ .describe('Optional saved outbound channel to clear. Omit to clear all saved self-targets.'),
1083
+ }),
1084
+ description: 'Clear one saved self-delivery target or remove all of them.',
1085
+ options: z.object({
1086
+ requestId: requestIdSchema,
1087
+ }),
1088
+ output: assistantSelfDeliveryTargetClearResultSchema,
1089
+ async run(context) {
1090
+ return {
1091
+ ...buildAssistantOperatorConfigResult(),
1092
+ clearedChannels: await clearAssistantSelfDeliveryTargets(context.args.channel),
1093
+ };
1094
+ },
1095
+ });
1096
+ assistant.command(selfTarget);
1097
+ };
1098
+ const registerCronCommands = () => {
1099
+ const cron = Cli.create('cron', {
1100
+ description: 'Manage scheduled assistant prompts stored outside the canonical vault under assistant-state/cron.',
1101
+ });
1102
+ const preset = Cli.create('preset', {
1103
+ description: 'Browse and materialize built-in assistant cron templates without editing scheduler state files directly.',
1104
+ });
1105
+ preset.command('list', {
1106
+ args: emptyArgsSchema,
1107
+ description: 'List the built-in assistant cron presets that can be installed later.',
1108
+ hint: 'Presets are templates, not active jobs. Use `assistant cron preset install` to turn one into a real cron job.',
1109
+ options: withBaseOptions(),
1110
+ output: assistantCronPresetListResultSchema,
1111
+ async run(context) {
1112
+ return {
1113
+ ...buildAssistantVaultResultPath(context.options.vault),
1114
+ presets: listAssistantCronPresets(),
1115
+ };
1116
+ },
1117
+ });
1118
+ preset.command('show', {
1119
+ args: z.object({
1120
+ preset: z.string().min(1).describe('Assistant cron preset id to inspect.'),
1121
+ }),
1122
+ description: 'Show one built-in assistant cron preset, including its prompt template.',
1123
+ options: withBaseOptions(),
1124
+ output: assistantCronPresetShowResultSchema,
1125
+ async run(context) {
1126
+ const presetDefinition = getAssistantCronPreset(context.args.preset);
1127
+ return {
1128
+ ...buildAssistantVaultResultPath(context.options.vault),
1129
+ preset: {
1130
+ id: presetDefinition.id,
1131
+ category: presetDefinition.category,
1132
+ title: presetDefinition.title,
1133
+ description: presetDefinition.description,
1134
+ suggestedName: presetDefinition.suggestedName,
1135
+ suggestedSchedule: presetDefinition.suggestedSchedule,
1136
+ suggestedScheduleLabel: presetDefinition.suggestedScheduleLabel,
1137
+ variables: presetDefinition.variables,
1138
+ },
1139
+ promptTemplate: presetDefinition.promptTemplate,
1140
+ };
1141
+ },
1142
+ });
1143
+ preset.command('install', {
1144
+ args: z.object({
1145
+ preset: z.string().min(1).describe('Assistant cron preset id to install.'),
1146
+ }),
1147
+ description: 'Create one assistant cron job from a built-in preset template.',
1148
+ hint: 'Repeat --var key=value to fill preset slots. If you omit --at, --every, and --cron, Murph uses the preset’s suggested schedule. Cron jobs require an explicit outbound channel route and always deliver their response. Add --state only when the job needs run-to-run scratch state such as cooldowns, dedupe, unresolved follow-ups, or delivery policy; leave it off for stateless digest/report jobs.',
1149
+ examples: [
1150
+ {
1151
+ args: {
1152
+ preset: 'condition-research-roundup',
1153
+ },
1154
+ options: {
1155
+ vault: './vault',
1156
+ name: 'cholesterol-research-roundup',
1157
+ var: ['condition_or_goal=lowering cholesterol'],
1158
+ },
1159
+ description: 'Install the condition research preset with a cholesterol-focused variable override.',
1160
+ },
1161
+ {
1162
+ args: {
1163
+ preset: 'environment-health-watch',
1164
+ },
1165
+ options: {
1166
+ vault: './vault',
1167
+ var: ['location_context=Brisbane, Queensland, Australia'],
1168
+ channel: 'telegram',
1169
+ participant: '123456789',
1170
+ sourceThread: '123456789',
1171
+ },
1172
+ description: 'Install the environment-health preset and deliver the weekly report back into a Telegram chat.',
1173
+ },
1174
+ ],
1175
+ options: withBaseOptions({
1176
+ name: z
1177
+ .string()
1178
+ .min(1)
1179
+ .optional()
1180
+ .describe('Optional cron job name override. Defaults to the preset’s suggested name.'),
1181
+ var: z
1182
+ .array(z.string().min(1))
1183
+ .optional()
1184
+ .describe('Optional preset variable assignment in key=value form. Repeat --var for multiple values.'),
1185
+ instructions: z
1186
+ .string()
1187
+ .min(1)
1188
+ .optional()
1189
+ .describe('Optional extra instructions appended to the preset prompt before the job is created.'),
1190
+ at: z
1191
+ .string()
1192
+ .min(1)
1193
+ .optional()
1194
+ .describe('Optional one-shot ISO 8601 timestamp with an explicit offset.'),
1195
+ every: z
1196
+ .string()
1197
+ .min(1)
1198
+ .optional()
1199
+ .describe('Optional recurring interval such as 30m, 2h, or 1d.'),
1200
+ cron: z
1201
+ .string()
1202
+ .min(1)
1203
+ .optional()
1204
+ .describe('Optional five-field cron expression override.'),
1205
+ disabled: z
1206
+ .boolean()
1207
+ .optional()
1208
+ .describe('Create the preset-backed cron job in a disabled state without scheduling it yet.'),
1209
+ ...assistantSessionOptionFields,
1210
+ ...assistantCronDeliveryOptionFields,
1211
+ ...assistantCronStateOptionFields,
1212
+ }),
1213
+ output: assistantCronPresetInstallResultSchema,
1214
+ async run(context) {
1215
+ const schedule = context.options.at || context.options.every || context.options.cron
1216
+ ? buildAssistantCronSchedule({
1217
+ at: context.options.at,
1218
+ every: context.options.every,
1219
+ cron: context.options.cron,
1220
+ })
1221
+ : undefined;
1222
+ const savedRoute = await resolveAssistantDeliveryRouteFromCli({
1223
+ allowSingleSavedTargetFallback: true,
1224
+ channel: context.options.channel,
1225
+ identity: context.options.identity,
1226
+ participant: context.options.participant,
1227
+ sourceThread: context.options.sourceThread,
1228
+ deliveryTarget: context.options.deliveryTarget,
1229
+ });
1230
+ const result = await installAssistantCronPreset({
1231
+ vault: context.options.vault,
1232
+ presetId: context.args.preset,
1233
+ name: context.options.name,
1234
+ variables: parseAssistantCronPresetVariables(context.options.var),
1235
+ additionalInstructions: context.options.instructions,
1236
+ schedule,
1237
+ enabled: context.options.disabled ? false : true,
1238
+ ...assistantCronStateOptionsFromCli(context.options),
1239
+ ...assistantConversationOptionsFromCli({
1240
+ ...context.options,
1241
+ channel: savedRoute.channel ?? context.options.channel,
1242
+ identity: savedRoute.identityId ?? context.options.identity,
1243
+ participant: savedRoute.participantId ?? context.options.participant,
1244
+ sourceThread: savedRoute.sourceThreadId ?? context.options.sourceThread,
1245
+ }),
1246
+ deliveryTarget: savedRoute.deliveryTarget ?? context.options.deliveryTarget,
1247
+ });
1248
+ return {
1249
+ ...buildAssistantCronResultPaths(context.options.vault),
1250
+ preset: result.preset,
1251
+ job: result.job,
1252
+ resolvedPrompt: result.resolvedPrompt,
1253
+ resolvedVariables: result.resolvedVariables,
1254
+ };
1255
+ },
1256
+ });
1257
+ cron.command(preset);
1258
+ cron.command('status', {
1259
+ args: emptyArgsSchema,
1260
+ description: 'Show scheduler counts and the next upcoming assistant cron run.',
1261
+ hint: 'Assistant cron jobs execute while `assistant run` is active for the vault.',
1262
+ options: withBaseOptions(),
1263
+ output: assistantCronStatusResultSchema,
1264
+ async run(context) {
1265
+ const status = await getAssistantCronStatus(context.options.vault);
1266
+ return {
1267
+ ...buildAssistantCronResultPaths(context.options.vault),
1268
+ ...status,
1269
+ };
1270
+ },
1271
+ });
1272
+ cron.command('list', {
1273
+ args: emptyArgsSchema,
1274
+ description: 'List assistant cron jobs for one vault.',
1275
+ options: withBaseOptions(),
1276
+ output: assistantCronListResultSchema,
1277
+ async run(context) {
1278
+ const jobs = await listAssistantCronJobs(context.options.vault);
1279
+ return {
1280
+ ...buildAssistantCronResultPaths(context.options.vault),
1281
+ jobs,
1282
+ };
1283
+ },
1284
+ });
1285
+ cron.command('show', {
1286
+ args: z.object({
1287
+ job: z.string().min(1).describe('Assistant cron job id or name to inspect.'),
1288
+ }),
1289
+ description: 'Show one assistant cron job record.',
1290
+ options: withBaseOptions(),
1291
+ output: assistantCronShowResultSchema,
1292
+ async run(context) {
1293
+ const job = await getAssistantCronJob(context.options.vault, context.args.job);
1294
+ return {
1295
+ ...buildAssistantCronResultPaths(context.options.vault),
1296
+ job,
1297
+ };
1298
+ },
1299
+ });
1300
+ const target = Cli.create('target', {
1301
+ description: 'Inspect or replace the outbound delivery target for an existing assistant cron job.',
1302
+ });
1303
+ target.command('show', {
1304
+ args: z.object({
1305
+ job: z.string().min(1).describe('Assistant cron job id or name to inspect.'),
1306
+ }),
1307
+ description: 'Show the current outbound delivery target for one assistant cron job.',
1308
+ hint: 'Use this before retargeting a cron job so you can see the current channel, explicit destination, and inferred binding delivery.',
1309
+ options: withBaseOptions(),
1310
+ output: assistantCronTargetShowResultSchema,
1311
+ async run(context) {
1312
+ return {
1313
+ ...buildAssistantCronResultPaths(context.options.vault),
1314
+ cronTarget: await getAssistantCronJobTarget(context.options.vault, context.args.job),
1315
+ };
1316
+ },
1317
+ });
1318
+ target.command('set', {
1319
+ args: z.object({
1320
+ job: z.string().min(1).describe('Assistant cron job id or name to retarget.'),
1321
+ }),
1322
+ description: 'Replace the outbound delivery target for one assistant cron job in place.',
1323
+ hint: 'Provide exactly one target source: `--toSelf <channel>`, `--copyFrom <job>`, or an explicit route via `--channel` plus the usual delivery flags. When the audience changes, Murph clears stored cron session continuity so the next run does not reuse the old routed conversation.',
1324
+ examples: [
1325
+ {
1326
+ args: {
1327
+ job: 'weekly-health-snapshot',
1328
+ },
1329
+ options: {
1330
+ vault: './vault',
1331
+ toSelf: 'email',
1332
+ },
1333
+ description: 'Retarget an existing cron job to the saved email self-target.',
1334
+ },
1335
+ {
1336
+ args: {
1337
+ job: 'condition-research-roundup',
1338
+ },
1339
+ options: {
1340
+ vault: './vault',
1341
+ copyFrom: 'weekly-health-snapshot',
1342
+ },
1343
+ description: 'Copy the current delivery target from another cron job.',
1344
+ },
1345
+ ],
1346
+ options: withBaseOptions({
1347
+ ...assistantCronTargetSourceOptionFields,
1348
+ ...assistantCronDeliveryOptionFields,
1349
+ channel: assistantSessionOptionFields.channel,
1350
+ identity: assistantSessionOptionFields.identity,
1351
+ participant: assistantSessionOptionFields.participant,
1352
+ sourceThread: assistantSessionOptionFields.sourceThread,
1353
+ }),
1354
+ output: assistantCronTargetSetResultSchema,
1355
+ async run(context) {
1356
+ const resolvedTarget = await resolveAssistantCronTargetFromCli({
1357
+ vault: context.options.vault,
1358
+ channel: context.options.channel,
1359
+ copyFrom: context.options.copyFrom,
1360
+ deliveryTarget: context.options.deliveryTarget,
1361
+ identity: context.options.identity,
1362
+ participant: context.options.participant,
1363
+ sourceThread: context.options.sourceThread,
1364
+ toSelf: context.options.toSelf,
1365
+ });
1366
+ const result = await setAssistantCronJobTarget({
1367
+ vault: context.options.vault,
1368
+ job: context.args.job,
1369
+ dryRun: context.options.dryRun,
1370
+ resetContinuity: context.options.resetContinuity,
1371
+ ...resolvedTarget,
1372
+ });
1373
+ return {
1374
+ ...buildAssistantCronResultPaths(context.options.vault),
1375
+ job: result.job,
1376
+ beforeTarget: result.beforeTarget,
1377
+ afterTarget: result.afterTarget,
1378
+ changed: result.changed,
1379
+ continuityReset: result.continuityReset,
1380
+ dryRun: result.dryRun,
1381
+ };
1382
+ },
1383
+ });
1384
+ cron.command(target);
1385
+ cron.command('add', {
1386
+ args: z.object({
1387
+ prompt: z.string().min(1).describe('Prompt to send when the assistant cron job fires.'),
1388
+ }),
1389
+ description: 'Create one assistant cron job backed by the local assistant runtime.',
1390
+ hint: 'Provide exactly one of --at, --every, or --cron. One-shot jobs are deleted after they succeed unless you pass --keepAfterRun. Cron jobs require an explicit outbound channel route and always deliver their response. Add --state or --stateDoc only when the job needs run-to-run scratch state such as cooldowns, dedupe, unresolved follow-ups, or delivery policy; leave it off for stateless jobs that can recompute everything each run.',
1391
+ examples: [
1392
+ {
1393
+ args: {
1394
+ prompt: 'Check whether I have been sitting too long and remind me to stretch.',
1395
+ },
1396
+ options: {
1397
+ vault: './vault',
1398
+ name: 'stretch-reminder',
1399
+ every: '2h',
1400
+ },
1401
+ description: 'Create a recurring interval job.',
1402
+ },
1403
+ {
1404
+ args: {
1405
+ prompt: 'Remind me to review my lab results after dinner.',
1406
+ },
1407
+ options: {
1408
+ vault: './vault',
1409
+ name: 'lab-review-tonight',
1410
+ at: '2026-03-22T18:30:00+10:00',
1411
+ },
1412
+ description: 'Create a one-shot reminder at a specific timestamp.',
1413
+ },
1414
+ {
1415
+ args: {
1416
+ prompt: 'Every weekday morning, ask me for a quick symptom check-in.',
1417
+ },
1418
+ options: {
1419
+ vault: './vault',
1420
+ name: 'weekday-symptom-check',
1421
+ cron: '0 8 * * 1-5',
1422
+ channel: 'telegram',
1423
+ participant: '-1001234567890',
1424
+ },
1425
+ description: 'Create a cron expression job that also delivers the reply back out.',
1426
+ },
1427
+ ],
1428
+ options: withBaseOptions({
1429
+ name: z
1430
+ .string()
1431
+ .min(1)
1432
+ .describe('Unique assistant cron job name.'),
1433
+ at: z
1434
+ .string()
1435
+ .min(1)
1436
+ .optional()
1437
+ .describe('One-shot ISO 8601 timestamp with an explicit offset.'),
1438
+ every: z
1439
+ .string()
1440
+ .min(1)
1441
+ .optional()
1442
+ .describe('Recurring interval such as 30m, 2h, or 1d.'),
1443
+ cron: z
1444
+ .string()
1445
+ .min(1)
1446
+ .optional()
1447
+ .describe('Five-field cron expression: minute hour day-of-month month day-of-week.'),
1448
+ keepAfterRun: z
1449
+ .boolean()
1450
+ .optional()
1451
+ .describe('Keep a completed one-shot job in the scheduler instead of deleting it.'),
1452
+ disabled: z
1453
+ .boolean()
1454
+ .optional()
1455
+ .describe('Create the job in a disabled state without scheduling it yet.'),
1456
+ ...assistantSessionOptionFields,
1457
+ ...assistantCronDeliveryOptionFields,
1458
+ ...assistantCronStateOptionFields,
1459
+ }),
1460
+ output: assistantCronAddResultSchema,
1461
+ async run(context) {
1462
+ const savedRoute = await resolveAssistantDeliveryRouteFromCli({
1463
+ allowSingleSavedTargetFallback: true,
1464
+ channel: context.options.channel,
1465
+ identity: context.options.identity,
1466
+ participant: context.options.participant,
1467
+ sourceThread: context.options.sourceThread,
1468
+ deliveryTarget: context.options.deliveryTarget,
1469
+ });
1470
+ const job = await addAssistantCronJob({
1471
+ vault: context.options.vault,
1472
+ name: context.options.name,
1473
+ prompt: context.args.prompt,
1474
+ schedule: buildAssistantCronSchedule({
1475
+ at: context.options.at,
1476
+ every: context.options.every,
1477
+ cron: context.options.cron,
1478
+ }),
1479
+ enabled: context.options.disabled ? false : true,
1480
+ keepAfterRun: context.options.keepAfterRun,
1481
+ ...assistantCronStateOptionsFromCli(context.options),
1482
+ ...assistantConversationOptionsFromCli({
1483
+ ...context.options,
1484
+ channel: savedRoute.channel ?? context.options.channel,
1485
+ identity: savedRoute.identityId ?? context.options.identity,
1486
+ participant: savedRoute.participantId ?? context.options.participant,
1487
+ sourceThread: savedRoute.sourceThreadId ?? context.options.sourceThread,
1488
+ }),
1489
+ deliveryTarget: savedRoute.deliveryTarget ?? context.options.deliveryTarget,
1490
+ });
1491
+ return {
1492
+ ...buildAssistantCronResultPaths(context.options.vault),
1493
+ job,
1494
+ };
1495
+ },
1496
+ });
1497
+ cron.command('remove', {
1498
+ args: z.object({
1499
+ job: z.string().min(1).describe('Assistant cron job id or name to remove.'),
1500
+ }),
1501
+ description: 'Remove one assistant cron job from the scheduler.',
1502
+ options: withBaseOptions(),
1503
+ output: assistantCronRemoveResultSchema,
1504
+ async run(context) {
1505
+ const removed = await removeAssistantCronJob(context.options.vault, context.args.job);
1506
+ return {
1507
+ ...buildAssistantCronResultPaths(context.options.vault),
1508
+ removed,
1509
+ };
1510
+ },
1511
+ });
1512
+ cron.command('enable', {
1513
+ args: z.object({
1514
+ job: z.string().min(1).describe('Assistant cron job id or name to enable.'),
1515
+ }),
1516
+ description: 'Enable one assistant cron job.',
1517
+ options: withBaseOptions(),
1518
+ output: assistantCronShowResultSchema,
1519
+ async run(context) {
1520
+ const job = await setAssistantCronJobEnabled(context.options.vault, context.args.job, true);
1521
+ return {
1522
+ ...buildAssistantCronResultPaths(context.options.vault),
1523
+ job,
1524
+ };
1525
+ },
1526
+ });
1527
+ cron.command('disable', {
1528
+ args: z.object({
1529
+ job: z.string().min(1).describe('Assistant cron job id or name to disable.'),
1530
+ }),
1531
+ description: 'Disable one assistant cron job without deleting it.',
1532
+ options: withBaseOptions(),
1533
+ output: assistantCronShowResultSchema,
1534
+ async run(context) {
1535
+ const job = await setAssistantCronJobEnabled(context.options.vault, context.args.job, false);
1536
+ return {
1537
+ ...buildAssistantCronResultPaths(context.options.vault),
1538
+ job,
1539
+ };
1540
+ },
1541
+ });
1542
+ cron.command('run', {
1543
+ args: z.object({
1544
+ job: z.string().min(1).describe('Assistant cron job id or name to run immediately.'),
1545
+ }),
1546
+ description: 'Run one assistant cron job immediately, regardless of its next scheduled time.',
1547
+ options: withBaseOptions(),
1548
+ output: assistantCronRunResultSchema,
1549
+ async run(context) {
1550
+ const result = await runAssistantCronJobNow({
1551
+ vault: context.options.vault,
1552
+ job: context.args.job,
1553
+ });
1554
+ return {
1555
+ ...buildAssistantCronResultPaths(context.options.vault),
1556
+ job: result.job,
1557
+ removedAfterRun: result.removedAfterRun,
1558
+ run: result.run,
1559
+ };
1560
+ },
1561
+ });
1562
+ cron.command('runs', {
1563
+ args: z.object({
1564
+ job: z.string().min(1).describe('Assistant cron job id or name to inspect run history for.'),
1565
+ }),
1566
+ description: 'List recent run history for one assistant cron job.',
1567
+ options: withBaseOptions({
1568
+ limit: z
1569
+ .number()
1570
+ .int()
1571
+ .positive()
1572
+ .max(100)
1573
+ .default(20)
1574
+ .describe('Maximum number of recent runs to return.'),
1575
+ }),
1576
+ output: assistantCronRunsResultSchema,
1577
+ async run(context) {
1578
+ const result = await listAssistantCronRuns({
1579
+ vault: context.options.vault,
1580
+ job: context.args.job,
1581
+ limit: context.options.limit,
1582
+ });
1583
+ return {
1584
+ ...buildAssistantCronResultPaths(context.options.vault),
1585
+ jobId: result.jobId,
1586
+ runs: result.runs,
1587
+ };
1588
+ },
1589
+ });
1590
+ assistant.command(cron);
1591
+ };
1592
+ const registerObservabilityCommands = () => {
1593
+ assistant.command('status', createAssistantStatusCommandDefinition());
1594
+ assistant.command('doctor', createAssistantDoctorCommandDefinition());
1595
+ assistant.command('stop', createAssistantStopCommandDefinition());
1596
+ };
1597
+ const registerSessionCommands = () => {
1598
+ const session = Cli.create('session', {
1599
+ description: 'Inspect Murph assistant session metadata stored outside the canonical vault.',
1600
+ });
1601
+ session.command('list', {
1602
+ args: emptyArgsSchema,
1603
+ description: 'List known assistant sessions for one vault.',
1604
+ options: withBaseOptions(),
1605
+ output: assistantSessionListResultSchema,
1606
+ async run(context) {
1607
+ const sessions = await listAssistantSessions(context.options.vault);
1608
+ return assistantSessionListResultSchema.parse({
1609
+ ...buildAssistantStateResultPaths(context.options.vault),
1610
+ sessions: redactAssistantSessionsForDisplay(sessions),
1611
+ });
1612
+ },
1613
+ });
1614
+ session.command('show', {
1615
+ args: z.object({
1616
+ sessionId: z.string().min(1).describe('Murph assistant session id to inspect.'),
1617
+ }),
1618
+ description: 'Show one assistant session metadata record.',
1619
+ options: withBaseOptions(),
1620
+ output: assistantSessionShowResultSchema,
1621
+ async run(context) {
1622
+ const session = await getAssistantSession(context.options.vault, context.args.sessionId);
1623
+ return assistantSessionShowResultSchema.parse({
1624
+ ...buildAssistantStateResultPaths(context.options.vault),
1625
+ session: redactAssistantSessionForDisplay(session),
1626
+ });
1627
+ },
1628
+ });
1629
+ assistant.command(session);
1630
+ };
1631
+ const registerRootAliases = () => {
1632
+ cli.command('chat', createAssistantChatCommandDefinition({
1633
+ description: 'Open the same assistant chat UI as `assistant chat` directly from the CLI root.',
1634
+ hint: 'Shorthand for `assistant chat`. Type /exit to close the chat loop or /session to print the current Murph session id.',
1635
+ }));
1636
+ cli.command('run', createAssistantRunCommandDefinition(inboxServices, vaultServices, {
1637
+ description: 'Start the same assistant automation loop as `assistant run` directly from the CLI root.',
1638
+ hint: 'Shorthand for `assistant run`. This starts the always-on automation loop, so it may watch inbox state, auto-reply over configured channels, and keep the terminal attached until you stop it.',
1639
+ }));
1640
+ cli.command('status', createAssistantStatusCommandDefinition({
1641
+ description: 'Show the same assistant-state snapshot as `assistant status` directly from the CLI root.',
1642
+ hint: 'Shorthand for `assistant status`. Use this to inspect recent turn receipts, session freshness, and pending outbox work.',
1643
+ }));
1644
+ cli.command('doctor', createAssistantDoctorCommandDefinition({
1645
+ description: 'Run the same assistant-state diagnostics as `assistant doctor` directly from the CLI root.',
1646
+ hint: 'Shorthand for `assistant doctor`. Use this when debugging transcript corruption, missing receipts, or stale outbox intents.',
1647
+ }));
1648
+ cli.command('stop', createAssistantStopCommandDefinition({
1649
+ description: 'Stop the same assistant automation loop as `assistant stop` directly from the CLI root.',
1650
+ hint: 'Shorthand for `assistant stop`. Use this when `murph run` is already active for the same vault and you need a recovery command instead of manual lock cleanup.',
1651
+ }));
1652
+ };
1653
+ registerConversationCommands();
1654
+ registerStateCommands();
1655
+ registerMemoryCommands();
1656
+ registerSelfTargetCommands();
1657
+ registerCronCommands();
1658
+ registerObservabilityCommands();
1659
+ registerSessionCommands();
1660
+ cli.command(assistant);
1661
+ registerRootAliases();
1662
+ }
1663
+ //# sourceMappingURL=assistant.js.map