@edwardlee5423/magi 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/agent/headless-agent.d.ts +15 -0
- package/dist/agent/headless-agent.js +105 -0
- package/dist/agent/headless-agent.js.map +1 -0
- package/dist/agent/local-plan.d.ts +20 -0
- package/dist/agent/local-plan.js +30 -0
- package/dist/agent/local-plan.js.map +1 -0
- package/dist/agent/query-engine.d.ts +71 -0
- package/dist/agent/query-engine.js +938 -0
- package/dist/agent/query-engine.js.map +1 -0
- package/dist/agent/query.d.ts +128 -0
- package/dist/agent/query.js +674 -0
- package/dist/agent/query.js.map +1 -0
- package/dist/agent/system-prompt.d.ts +12 -0
- package/dist/agent/system-prompt.js +104 -0
- package/dist/agent/system-prompt.js.map +1 -0
- package/dist/agent/tools.d.ts +71 -0
- package/dist/agent/tools.js +37 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/agents/roles.d.ts +9 -0
- package/dist/agents/roles.js +18 -0
- package/dist/agents/roles.js.map +1 -0
- package/dist/agents/task-queue.d.ts +13 -0
- package/dist/agents/task-queue.js +50 -0
- package/dist/agents/task-queue.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +1540 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors.d.ts +20 -0
- package/dist/colors.js +92 -0
- package/dist/colors.js.map +1 -0
- package/dist/commands/agents.d.ts +9 -0
- package/dist/commands/agents.js +37 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/clear.d.ts +9 -0
- package/dist/commands/clear.js +20 -0
- package/dist/commands/clear.js.map +1 -0
- package/dist/commands/commit.d.ts +8 -0
- package/dist/commands/commit.js +35 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/compact.d.ts +8 -0
- package/dist/commands/compact.js +29 -0
- package/dist/commands/compact.js.map +1 -0
- package/dist/commands/copy.d.ts +8 -0
- package/dist/commands/copy.js +49 -0
- package/dist/commands/copy.js.map +1 -0
- package/dist/commands/cost.d.ts +8 -0
- package/dist/commands/cost.js +94 -0
- package/dist/commands/cost.js.map +1 -0
- package/dist/commands/diff.d.ts +8 -0
- package/dist/commands/diff.js +38 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +23 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.js +42 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/fork.d.ts +8 -0
- package/dist/commands/fork.js +20 -0
- package/dist/commands/fork.js.map +1 -0
- package/dist/commands/goal.d.ts +9 -0
- package/dist/commands/goal.js +54 -0
- package/dist/commands/goal.js.map +1 -0
- package/dist/commands/help.d.ts +8 -0
- package/dist/commands/help.js +65 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/hooks.d.ts +8 -0
- package/dist/commands/hooks.js +56 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/image.d.ts +17 -0
- package/dist/commands/image.js +74 -0
- package/dist/commands/image.js.map +1 -0
- package/dist/commands/init.d.ts +26 -0
- package/dist/commands/init.js +214 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp.d.ts +8 -0
- package/dist/commands/mcp.js +217 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +8 -0
- package/dist/commands/memory.js +79 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/model.d.ts +8 -0
- package/dist/commands/model.js +21 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/permissions.d.ts +9 -0
- package/dist/commands/permissions.js +30 -0
- package/dist/commands/permissions.js.map +1 -0
- package/dist/commands/proactive.d.ts +9 -0
- package/dist/commands/proactive.js +24 -0
- package/dist/commands/proactive.js.map +1 -0
- package/dist/commands/register-all.d.ts +1 -0
- package/dist/commands/register-all.js +68 -0
- package/dist/commands/register-all.js.map +1 -0
- package/dist/commands/registry.d.ts +64 -0
- package/dist/commands/registry.js +235 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/rename.d.ts +8 -0
- package/dist/commands/rename.js +16 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/review.d.ts +8 -0
- package/dist/commands/review.js +13 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/rewind.d.ts +8 -0
- package/dist/commands/rewind.js +45 -0
- package/dist/commands/rewind.js.map +1 -0
- package/dist/commands/route.d.ts +9 -0
- package/dist/commands/route.js +83 -0
- package/dist/commands/route.js.map +1 -0
- package/dist/commands/sessions.d.ts +9 -0
- package/dist/commands/sessions.js +25 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/skill.d.ts +9 -0
- package/dist/commands/skill.js +46 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +41 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/summary.d.ts +8 -0
- package/dist/commands/summary.js +39 -0
- package/dist/commands/summary.js.map +1 -0
- package/dist/commands/tasks.d.ts +9 -0
- package/dist/commands/tasks.js +70 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/commands/todos.d.ts +9 -0
- package/dist/commands/todos.js +27 -0
- package/dist/commands/todos.js.map +1 -0
- package/dist/commands/tutorial.d.ts +8 -0
- package/dist/commands/tutorial.js +159 -0
- package/dist/commands/tutorial.js.map +1 -0
- package/dist/commands/upgrade.d.ts +9 -0
- package/dist/commands/upgrade.js +73 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/usage.d.ts +8 -0
- package/dist/commands/usage.js +54 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/vim.d.ts +10 -0
- package/dist/commands/vim.js +34 -0
- package/dist/commands/vim.js.map +1 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.js +533 -0
- package/dist/config.js.map +1 -0
- package/dist/context/compaction.d.ts +66 -0
- package/dist/context/compaction.js +353 -0
- package/dist/context/compaction.js.map +1 -0
- package/dist/context/layers.d.ts +32 -0
- package/dist/context/layers.js +114 -0
- package/dist/context/layers.js.map +1 -0
- package/dist/context/token-budget.d.ts +21 -0
- package/dist/context/token-budget.js +70 -0
- package/dist/context/token-budget.js.map +1 -0
- package/dist/control/auth.d.ts +17 -0
- package/dist/control/auth.js +31 -0
- package/dist/control/auth.js.map +1 -0
- package/dist/control/daemon.d.ts +38 -0
- package/dist/control/daemon.js +135 -0
- package/dist/control/daemon.js.map +1 -0
- package/dist/control/mdns.d.ts +51 -0
- package/dist/control/mdns.js +344 -0
- package/dist/control/mdns.js.map +1 -0
- package/dist/control/peer-client.d.ts +49 -0
- package/dist/control/peer-client.js +170 -0
- package/dist/control/peer-client.js.map +1 -0
- package/dist/control/server.d.ts +30 -0
- package/dist/control/server.js +887 -0
- package/dist/control/server.js.map +1 -0
- package/dist/cost.d.ts +23 -0
- package/dist/cost.js +80 -0
- package/dist/cost.js.map +1 -0
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.js +22 -0
- package/dist/doctor.js.map +1 -0
- package/dist/env.d.ts +7 -0
- package/dist/env.js +70 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +19 -0
- package/dist/events.js +236 -0
- package/dist/events.js.map +1 -0
- package/dist/fs-utils.d.ts +21 -0
- package/dist/fs-utils.js +48 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/goal.d.ts +32 -0
- package/dist/goal.js +172 -0
- package/dist/goal.js.map +1 -0
- package/dist/headless.d.ts +38 -0
- package/dist/headless.js +547 -0
- package/dist/headless.js.map +1 -0
- package/dist/history.d.ts +8 -0
- package/dist/history.js +77 -0
- package/dist/history.js.map +1 -0
- package/dist/hooks/events.d.ts +14 -0
- package/dist/hooks/events.js +37 -0
- package/dist/hooks/events.js.map +1 -0
- package/dist/hooks/runner.d.ts +73 -0
- package/dist/hooks/runner.js +256 -0
- package/dist/hooks/runner.js.map +1 -0
- package/dist/hooks/trigger.d.ts +9 -0
- package/dist/hooks/trigger.js +18 -0
- package/dist/hooks/trigger.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions.d.ts +102 -0
- package/dist/interactions.js +250 -0
- package/dist/interactions.js.map +1 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.js +115 -0
- package/dist/logger.js.map +1 -0
- package/dist/markdown.d.ts +18 -0
- package/dist/markdown.js +293 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mcp/approval.d.ts +9 -0
- package/dist/mcp/approval.js +27 -0
- package/dist/mcp/approval.js.map +1 -0
- package/dist/mcp/client.d.ts +61 -0
- package/dist/mcp/client.js +276 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/connection-manager.d.ts +30 -0
- package/dist/mcp/connection-manager.js +126 -0
- package/dist/mcp/connection-manager.js.map +1 -0
- package/dist/mcp/oauth-callback.d.ts +23 -0
- package/dist/mcp/oauth-callback.js +108 -0
- package/dist/mcp/oauth-callback.js.map +1 -0
- package/dist/mcp/oauth-flow.d.ts +45 -0
- package/dist/mcp/oauth-flow.js +145 -0
- package/dist/mcp/oauth-flow.js.map +1 -0
- package/dist/mcp/oauth.d.ts +82 -0
- package/dist/mcp/oauth.js +169 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp/tool-registry.d.ts +29 -0
- package/dist/mcp/tool-registry.js +207 -0
- package/dist/mcp/tool-registry.js.map +1 -0
- package/dist/mcp/transport.d.ts +33 -0
- package/dist/mcp/transport.js +493 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/mcp/types.d.ts +57 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/memdir.d.ts +58 -0
- package/dist/memdir.js +219 -0
- package/dist/memdir.js.map +1 -0
- package/dist/memory-selection.d.ts +30 -0
- package/dist/memory-selection.js +102 -0
- package/dist/memory-selection.js.map +1 -0
- package/dist/memory.d.ts +85 -0
- package/dist/memory.js +259 -0
- package/dist/memory.js.map +1 -0
- package/dist/paths.d.ts +25 -0
- package/dist/paths.js +99 -0
- package/dist/paths.js.map +1 -0
- package/dist/permissions.d.ts +17 -0
- package/dist/permissions.js +72 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugins/manifest.d.ts +17 -0
- package/dist/plugins/manifest.js +98 -0
- package/dist/plugins/manifest.js.map +1 -0
- package/dist/plugins/marketplace.d.ts +22 -0
- package/dist/plugins/marketplace.js +98 -0
- package/dist/plugins/marketplace.js.map +1 -0
- package/dist/proactive.d.ts +15 -0
- package/dist/proactive.js +49 -0
- package/dist/proactive.js.map +1 -0
- package/dist/providers/errors.d.ts +14 -0
- package/dist/providers/errors.js +66 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/format-proxy.d.ts +100 -0
- package/dist/providers/format-proxy.js +235 -0
- package/dist/providers/format-proxy.js.map +1 -0
- package/dist/providers/http.d.ts +4 -0
- package/dist/providers/http.js +21 -0
- package/dist/providers/http.js.map +1 -0
- package/dist/providers/ir.d.ts +84 -0
- package/dist/providers/ir.js +76 -0
- package/dist/providers/ir.js.map +1 -0
- package/dist/providers/messages-compatible.d.ts +19 -0
- package/dist/providers/messages-compatible.js +592 -0
- package/dist/providers/messages-compatible.js.map +1 -0
- package/dist/providers/openai.d.ts +18 -0
- package/dist/providers/openai.js +430 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +10 -0
- package/dist/providers/registry.js +16 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/sse.d.ts +6 -0
- package/dist/providers/sse.js +104 -0
- package/dist/providers/sse.js.map +1 -0
- package/dist/routing/model-alias.d.ts +8 -0
- package/dist/routing/model-alias.js +23 -0
- package/dist/routing/model-alias.js.map +1 -0
- package/dist/routing/model-router.d.ts +55 -0
- package/dist/routing/model-router.js +233 -0
- package/dist/routing/model-router.js.map +1 -0
- package/dist/routing/router.d.ts +26 -0
- package/dist/routing/router.js +63 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/rules/agents-loader.d.ts +12 -0
- package/dist/rules/agents-loader.js +61 -0
- package/dist/rules/agents-loader.js.map +1 -0
- package/dist/runner/client.d.ts +67 -0
- package/dist/runner/client.js +175 -0
- package/dist/runner/client.js.map +1 -0
- package/dist/session-store.d.ts +250 -0
- package/dist/session-store.js +629 -0
- package/dist/session-store.js.map +1 -0
- package/dist/skills/bundled.d.ts +14 -0
- package/dist/skills/bundled.js +148 -0
- package/dist/skills/bundled.js.map +1 -0
- package/dist/skills/loader.d.ts +11 -0
- package/dist/skills/loader.js +73 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/slash-menu.d.ts +14 -0
- package/dist/slash-menu.js +166 -0
- package/dist/slash-menu.js.map +1 -0
- package/dist/slash.d.ts +75 -0
- package/dist/slash.js +126 -0
- package/dist/slash.js.map +1 -0
- package/dist/spinner.d.ts +27 -0
- package/dist/spinner.js +68 -0
- package/dist/spinner.js.map +1 -0
- package/dist/ssh/exec.d.ts +20 -0
- package/dist/ssh/exec.js +39 -0
- package/dist/ssh/exec.js.map +1 -0
- package/dist/ssh/file.d.ts +32 -0
- package/dist/ssh/file.js +60 -0
- package/dist/ssh/file.js.map +1 -0
- package/dist/syntax-highlight.d.ts +19 -0
- package/dist/syntax-highlight.js +181 -0
- package/dist/syntax-highlight.js.map +1 -0
- package/dist/tools/agent-tool.d.ts +53 -0
- package/dist/tools/agent-tool.js +62 -0
- package/dist/tools/agent-tool.js.map +1 -0
- package/dist/tools/archive-create.d.ts +35 -0
- package/dist/tools/archive-create.js +37 -0
- package/dist/tools/archive-create.js.map +1 -0
- package/dist/tools/archive-extract.d.ts +28 -0
- package/dist/tools/archive-extract.js +36 -0
- package/dist/tools/archive-extract.js.map +1 -0
- package/dist/tools/base64.d.ts +27 -0
- package/dist/tools/base64.js +19 -0
- package/dist/tools/base64.js.map +1 -0
- package/dist/tools/browser.d.ts +125 -0
- package/dist/tools/browser.js +241 -0
- package/dist/tools/browser.js.map +1 -0
- package/dist/tools/config-tool.d.ts +31 -0
- package/dist/tools/config-tool.js +167 -0
- package/dist/tools/config-tool.js.map +1 -0
- package/dist/tools/cron.d.ts +110 -0
- package/dist/tools/cron.js +299 -0
- package/dist/tools/cron.js.map +1 -0
- package/dist/tools/date.d.ts +24 -0
- package/dist/tools/date.js +26 -0
- package/dist/tools/date.js.map +1 -0
- package/dist/tools/dir-create.d.ts +21 -0
- package/dist/tools/dir-create.js +19 -0
- package/dist/tools/dir-create.js.map +1 -0
- package/dist/tools/dir-list.d.ts +36 -0
- package/dist/tools/dir-list.js +47 -0
- package/dist/tools/dir-list.js.map +1 -0
- package/dist/tools/disk-usage.d.ts +30 -0
- package/dist/tools/disk-usage.js +22 -0
- package/dist/tools/disk-usage.js.map +1 -0
- package/dist/tools/download-file.d.ts +28 -0
- package/dist/tools/download-file.js +42 -0
- package/dist/tools/download-file.js.map +1 -0
- package/dist/tools/environment.d.ts +31 -0
- package/dist/tools/environment.js +33 -0
- package/dist/tools/environment.js.map +1 -0
- package/dist/tools/errors.d.ts +5 -0
- package/dist/tools/errors.js +9 -0
- package/dist/tools/errors.js.map +1 -0
- package/dist/tools/file-copy.d.ts +34 -0
- package/dist/tools/file-copy.js +45 -0
- package/dist/tools/file-copy.js.map +1 -0
- package/dist/tools/file-delete.d.ts +34 -0
- package/dist/tools/file-delete.js +42 -0
- package/dist/tools/file-delete.js.map +1 -0
- package/dist/tools/file-find.d.ts +46 -0
- package/dist/tools/file-find.js +33 -0
- package/dist/tools/file-find.js.map +1 -0
- package/dist/tools/file-move.d.ts +33 -0
- package/dist/tools/file-move.js +32 -0
- package/dist/tools/file-move.js.map +1 -0
- package/dist/tools/files.d.ts +38 -0
- package/dist/tools/files.js +123 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/git-branch-delete.d.ts +27 -0
- package/dist/tools/git-branch-delete.js +25 -0
- package/dist/tools/git-branch-delete.js.map +1 -0
- package/dist/tools/git-reset.d.ts +30 -0
- package/dist/tools/git-reset.js +28 -0
- package/dist/tools/git-reset.js.map +1 -0
- package/dist/tools/git-stash.d.ts +28 -0
- package/dist/tools/git-stash.js +24 -0
- package/dist/tools/git-stash.js.map +1 -0
- package/dist/tools/git.d.ts +77 -0
- package/dist/tools/git.js +300 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/github.d.ts +68 -0
- package/dist/tools/github.js +77 -0
- package/dist/tools/github.js.map +1 -0
- package/dist/tools/head-tail.d.ts +34 -0
- package/dist/tools/head-tail.js +23 -0
- package/dist/tools/head-tail.js.map +1 -0
- package/dist/tools/http-request.d.ts +44 -0
- package/dist/tools/http-request.js +57 -0
- package/dist/tools/http-request.js.map +1 -0
- package/dist/tools/json-query.d.ts +27 -0
- package/dist/tools/json-query.js +90 -0
- package/dist/tools/json-query.js.map +1 -0
- package/dist/tools/kill-process.d.ts +32 -0
- package/dist/tools/kill-process.js +51 -0
- package/dist/tools/kill-process.js.map +1 -0
- package/dist/tools/lsp.d.ts +98 -0
- package/dist/tools/lsp.js +398 -0
- package/dist/tools/lsp.js.map +1 -0
- package/dist/tools/monitor.d.ts +29 -0
- package/dist/tools/monitor.js +78 -0
- package/dist/tools/monitor.js.map +1 -0
- package/dist/tools/network-check.d.ts +34 -0
- package/dist/tools/network-check.js +57 -0
- package/dist/tools/network-check.js.map +1 -0
- package/dist/tools/notebook.d.ts +63 -0
- package/dist/tools/notebook.js +158 -0
- package/dist/tools/notebook.js.map +1 -0
- package/dist/tools/plan-mode.d.ts +48 -0
- package/dist/tools/plan-mode.js +57 -0
- package/dist/tools/plan-mode.js.map +1 -0
- package/dist/tools/process-list.d.ts +35 -0
- package/dist/tools/process-list.js +42 -0
- package/dist/tools/process-list.js.map +1 -0
- package/dist/tools/registry.d.ts +132 -0
- package/dist/tools/registry.js +2262 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/search.d.ts +38 -0
- package/dist/tools/search.js +327 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/shell.d.ts +18 -0
- package/dist/tools/shell.js +249 -0
- package/dist/tools/shell.js.map +1 -0
- package/dist/tools/skill-tool.d.ts +22 -0
- package/dist/tools/skill-tool.js +66 -0
- package/dist/tools/skill-tool.js.map +1 -0
- package/dist/tools/sleep.d.ts +16 -0
- package/dist/tools/sleep.js +24 -0
- package/dist/tools/sleep.js.map +1 -0
- package/dist/tools/snip.d.ts +26 -0
- package/dist/tools/snip.js +95 -0
- package/dist/tools/snip.js.map +1 -0
- package/dist/tools/system-info.d.ts +17 -0
- package/dist/tools/system-info.js +33 -0
- package/dist/tools/system-info.js.map +1 -0
- package/dist/tools/tasks.d.ts +191 -0
- package/dist/tools/tasks.js +348 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/text-stats.d.ts +25 -0
- package/dist/tools/text-stats.js +25 -0
- package/dist/tools/text-stats.js.map +1 -0
- package/dist/tools/todo.d.ts +69 -0
- package/dist/tools/todo.js +197 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/tool-search.d.ts +29 -0
- package/dist/tools/tool-search.js +114 -0
- package/dist/tools/tool-search.js.map +1 -0
- package/dist/tools/tree-view.d.ts +34 -0
- package/dist/tools/tree-view.js +39 -0
- package/dist/tools/tree-view.js.map +1 -0
- package/dist/tools/user-message.d.ts +39 -0
- package/dist/tools/user-message.js +70 -0
- package/dist/tools/user-message.js.map +1 -0
- package/dist/tools/user-question.d.ts +88 -0
- package/dist/tools/user-question.js +236 -0
- package/dist/tools/user-question.js.map +1 -0
- package/dist/tools/web-browser.d.ts +53 -0
- package/dist/tools/web-browser.js +373 -0
- package/dist/tools/web-browser.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +22 -0
- package/dist/tools/web-fetch.js +146 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +51 -0
- package/dist/tools/web-search.js +198 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tools/which.d.ts +22 -0
- package/dist/tools/which.js +21 -0
- package/dist/tools/which.js.map +1 -0
- package/dist/tools/whoami.d.ts +17 -0
- package/dist/tools/whoami.js +24 -0
- package/dist/tools/whoami.js.map +1 -0
- package/dist/tools/workspace-diagnostics.d.ts +62 -0
- package/dist/tools/workspace-diagnostics.js +421 -0
- package/dist/tools/workspace-diagnostics.js.map +1 -0
- package/dist/tools/workspace.d.ts +14 -0
- package/dist/tools/workspace.js +65 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/tools/worktree.d.ts +70 -0
- package/dist/tools/worktree.js +150 -0
- package/dist/tools/worktree.js.map +1 -0
- package/dist/tui/interactions.d.ts +25 -0
- package/dist/tui/interactions.js +232 -0
- package/dist/tui/interactions.js.map +1 -0
- package/dist/tui/multiline-input.d.ts +14 -0
- package/dist/tui/multiline-input.js +550 -0
- package/dist/tui/multiline-input.js.map +1 -0
- package/dist/tui/paste.d.ts +40 -0
- package/dist/tui/paste.js +113 -0
- package/dist/tui/paste.js.map +1 -0
- package/dist/tui/prompt-reader.d.ts +26 -0
- package/dist/tui/prompt-reader.js +553 -0
- package/dist/tui/prompt-reader.js.map +1 -0
- package/dist/tui/transcript.d.ts +34 -0
- package/dist/tui/transcript.js +275 -0
- package/dist/tui/transcript.js.map +1 -0
- package/dist/tui.d.ts +53 -0
- package/dist/tui.js +669 -0
- package/dist/tui.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/dist/vim/lineEditor.d.ts +18 -0
- package/dist/vim/lineEditor.js +420 -0
- package/dist/vim/lineEditor.js.map +1 -0
- package/dist/web/panel-html.d.ts +1 -0
- package/dist/web/panel-html.js +532 -0
- package/dist/web/panel-html.js.map +1 -0
- package/dist/web/panel.d.ts +4 -0
- package/dist/web/panel.js +121 -0
- package/dist/web/panel.js.map +1 -0
- package/package.json +42 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1540 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { MagiConfigError, MagiUsageError } from "./errors.js";
|
|
6
|
+
import { ProviderError } from "./providers/errors.js";
|
|
7
|
+
import { formatConfig, loadConfig } from "./config.js";
|
|
8
|
+
import { formatDoctorReport } from "./doctor.js";
|
|
9
|
+
import { loadMagiEnvFile } from "./env.js";
|
|
10
|
+
import { runHeadlessPrompt } from "./headless.js";
|
|
11
|
+
import { appendMemory, formatMemory, searchMemory, formatMemorySearchResults } from "./memory.js";
|
|
12
|
+
import { McpConnectionManager } from "./mcp/connection-manager.js";
|
|
13
|
+
import { ensureMagiHome, getMagiPaths, getRuntimeSettings } from "./paths.js";
|
|
14
|
+
import { formatAgentInstructions, loadAgentInstructions } from "./rules/agents-loader.js";
|
|
15
|
+
import { SessionStore } from "./session-store.js";
|
|
16
|
+
import { formatSessionList, formatSessionResume, runInteractiveTerminal } from "./tui.js";
|
|
17
|
+
import { startControlServer } from "./control/server.js";
|
|
18
|
+
import { getDaemonStatus, startDaemon, stopDaemon, writeDaemonPidFile, clearDaemonPidFile } from "./control/daemon.js";
|
|
19
|
+
import { createJsonLogger } from "./logger.js";
|
|
20
|
+
import { setColorEnabled } from "./colors.js";
|
|
21
|
+
import { compactSessionWithHooks, formatCompactResult } from "./context/compaction.js";
|
|
22
|
+
import { computeSessionContextBudget, formatSessionContextBudget } from "./context/token-budget.js";
|
|
23
|
+
import { cancelAgentTask, completeAgentTask, spawnAgentTask, startAgentTask, waitAgentTask } from "./agents/task-queue.js";
|
|
24
|
+
import { resolveRunnerCommand, RunnerClient } from "./runner/client.js";
|
|
25
|
+
import { formatPluginList, listLocalPlugins } from "./plugins/manifest.js";
|
|
26
|
+
import { discoverLocalMarketplaceSources, formatMarketplaces, loadMarketplace } from "./plugins/marketplace.js";
|
|
27
|
+
import { findSkill, formatSkillList, listSkills } from "./skills/loader.js";
|
|
28
|
+
import { formatSessionSearch } from "./slash.js";
|
|
29
|
+
import { formatWorkspaceDiagnostics, runWorkspaceDiagnostics } from "./tools/workspace-diagnostics.js";
|
|
30
|
+
import { VERSION } from "./version.js";
|
|
31
|
+
import { triggerHooks } from "./hooks/events.js";
|
|
32
|
+
import { buildProviderRegistry } from "./providers/registry.js";
|
|
33
|
+
import { resolveModelAlias } from "./routing/model-alias.js";
|
|
34
|
+
import { createGoal, clearGoal, formatGoal, formatGoalStatus, getGoal, isGoalCreationArgs, listGoals, updateGoalStatus } from "./goal.js";
|
|
35
|
+
export async function runCli(argv, env = process.env, cwd = process.cwd()) {
|
|
36
|
+
try {
|
|
37
|
+
return await runCliUnsafe(argv, env, cwd);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error instanceof MagiConfigError || error instanceof MagiUsageError) {
|
|
41
|
+
return { exitCode: 2, stdout: "", stderr: `${error.message}\n` };
|
|
42
|
+
}
|
|
43
|
+
if (error instanceof ProviderError) {
|
|
44
|
+
// Provider errors (HTTP 401/429/502/etc) already carry a user-friendly
|
|
45
|
+
// message. Don't print the stack — it adds noise without information.
|
|
46
|
+
return { exitCode: 1, stdout: "", stderr: `${error.message}\n` };
|
|
47
|
+
}
|
|
48
|
+
const detail = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
49
|
+
return { exitCode: 1, stdout: "", stderr: `${detail}\n` };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function runCliUnsafe(argv, env, cwd) {
|
|
53
|
+
const parsed = parseArgs(argv);
|
|
54
|
+
const command = parsed.command;
|
|
55
|
+
// Honor --no-color flag (NO_COLOR env var is handled by colors.ts default).
|
|
56
|
+
if (argv.includes("--no-color")) {
|
|
57
|
+
setColorEnabled(false);
|
|
58
|
+
}
|
|
59
|
+
else if (env.NO_COLOR && env.NO_COLOR !== "") {
|
|
60
|
+
// env passed in may differ from process.env (tests); honor it explicitly.
|
|
61
|
+
setColorEnabled(false);
|
|
62
|
+
}
|
|
63
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
64
|
+
if (!command) {
|
|
65
|
+
const runtimeEnv = loadMagiEnvFile(env).env;
|
|
66
|
+
const paths = getMagiPaths(runtimeEnv);
|
|
67
|
+
ensureMagiHome(paths);
|
|
68
|
+
const config = loadConfig(paths, runtimeEnv);
|
|
69
|
+
const store = SessionStore.open(paths);
|
|
70
|
+
try {
|
|
71
|
+
const resumeSession = parsed.continueSession ? store.getMostRecentSession(cwd) : undefined;
|
|
72
|
+
const exitCode = await runInteractiveTerminal({
|
|
73
|
+
cwd,
|
|
74
|
+
config,
|
|
75
|
+
store,
|
|
76
|
+
paths,
|
|
77
|
+
env: runtimeEnv,
|
|
78
|
+
modelAlias: parsed.modelAlias ?? "main",
|
|
79
|
+
sessionId: resumeSession?.id
|
|
80
|
+
});
|
|
81
|
+
return { exitCode, stdout: "", stderr: "" };
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
store.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { exitCode: 0, stdout: helpText(), stderr: "" };
|
|
88
|
+
}
|
|
89
|
+
if (command === "--version" || command === "-v") {
|
|
90
|
+
return runCliUnsafeWithParsed(parsed, env, cwd);
|
|
91
|
+
}
|
|
92
|
+
const runtimeEnv = loadMagiEnvFile(env).env;
|
|
93
|
+
if (!command?.startsWith("-") && !knownCommands().has(command)) {
|
|
94
|
+
parsed.command = "-p";
|
|
95
|
+
parsed.prompt = [command, ...parsed.rest].join(" ");
|
|
96
|
+
parsed.rest = [];
|
|
97
|
+
return runCliUnsafeWithParsed(parsed, runtimeEnv, cwd);
|
|
98
|
+
}
|
|
99
|
+
return runCliUnsafeWithParsed(parsed, runtimeEnv, cwd);
|
|
100
|
+
}
|
|
101
|
+
async function runCliUnsafeWithParsed(parsed, env, cwd) {
|
|
102
|
+
const command = parsed.command;
|
|
103
|
+
// First-run bootstrap: ensure ~/.magi-next exists and bundled skills are
|
|
104
|
+
// installed. Called every CLI invocation but is idempotent (skips existing).
|
|
105
|
+
try {
|
|
106
|
+
const paths = getMagiPaths(env);
|
|
107
|
+
ensureMagiHome(paths);
|
|
108
|
+
const { installBundledSkills } = await import("./skills/bundled.js");
|
|
109
|
+
installBundledSkills(paths);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Best-effort. Anything important fails again later with a clearer error.
|
|
113
|
+
}
|
|
114
|
+
if (command === "--version" || command === "-v") {
|
|
115
|
+
return { exitCode: 0, stdout: `magi ${VERSION}\n`, stderr: "" };
|
|
116
|
+
}
|
|
117
|
+
if (command === "-p" || command === "--prompt") {
|
|
118
|
+
const prompt = parsed.prompt;
|
|
119
|
+
if (!prompt || !prompt.trim()) {
|
|
120
|
+
throw new MagiUsageError("magi -p requires a non-empty prompt");
|
|
121
|
+
}
|
|
122
|
+
const paths = getMagiPaths(env);
|
|
123
|
+
ensureMagiHome(paths);
|
|
124
|
+
const config = loadConfig(paths, env);
|
|
125
|
+
const setupSessionId = `setup-${Date.now()}`;
|
|
126
|
+
const setupStore = SessionStore.open(paths);
|
|
127
|
+
try {
|
|
128
|
+
await triggerHooks({
|
|
129
|
+
event: "setup",
|
|
130
|
+
hooks: config.hooks,
|
|
131
|
+
store: setupStore,
|
|
132
|
+
sessionId: setupSessionId,
|
|
133
|
+
cwd,
|
|
134
|
+
env
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setupStore.close();
|
|
139
|
+
}
|
|
140
|
+
const store = SessionStore.open(paths);
|
|
141
|
+
try {
|
|
142
|
+
const resumeSession = parsed.resumeSessionId
|
|
143
|
+
? store.getSession(parsed.resumeSessionId)
|
|
144
|
+
: parsed.continueSession ? store.getMostRecentSession(cwd) : undefined;
|
|
145
|
+
if (parsed.resumeSessionId && !resumeSession) {
|
|
146
|
+
throw new MagiUsageError(`Session not found: ${parsed.resumeSessionId}`);
|
|
147
|
+
}
|
|
148
|
+
if (parsed.sessionId && !store.getSession(parsed.sessionId)) {
|
|
149
|
+
store.createSession({
|
|
150
|
+
id: parsed.sessionId,
|
|
151
|
+
title: parsed.sessionName ?? prompt.slice(0, 80),
|
|
152
|
+
cwd,
|
|
153
|
+
metadata: { mode: "headless", explicitSessionId: true }
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const result = await runHeadlessPrompt({
|
|
157
|
+
prompt,
|
|
158
|
+
cwd,
|
|
159
|
+
store,
|
|
160
|
+
config,
|
|
161
|
+
env,
|
|
162
|
+
paths,
|
|
163
|
+
stateRoot: paths.stateRoot,
|
|
164
|
+
modelAlias: parsed.modelAlias ?? "main",
|
|
165
|
+
sessionId: parsed.sessionId ?? resumeSession?.id,
|
|
166
|
+
sessionName: parsed.sessionName,
|
|
167
|
+
persistSession: parsed.persistSession,
|
|
168
|
+
collectEvents: parsed.outputFormat === "stream-json"
|
|
169
|
+
});
|
|
170
|
+
if (parsed.outputFormat === "stream-json") {
|
|
171
|
+
return {
|
|
172
|
+
exitCode: 0,
|
|
173
|
+
stdout: formatStreamJson(result),
|
|
174
|
+
stderr: ""
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (parsed.outputFormat === "json") {
|
|
178
|
+
return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: "" };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
exitCode: 0,
|
|
182
|
+
stdout: [
|
|
183
|
+
result.message,
|
|
184
|
+
`sessionId: ${result.sessionId}`,
|
|
185
|
+
`jobId: ${result.jobId}`,
|
|
186
|
+
`stateDb: ${paths.sessionDbFile}`,
|
|
187
|
+
""
|
|
188
|
+
].join("\n"),
|
|
189
|
+
stderr: ""
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
store.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (command === "doctor") {
|
|
197
|
+
const paths = getMagiPaths(env);
|
|
198
|
+
ensureMagiHome(paths);
|
|
199
|
+
const runtime = getRuntimeSettings(env);
|
|
200
|
+
const config = loadConfig(paths, env);
|
|
201
|
+
return {
|
|
202
|
+
exitCode: 0,
|
|
203
|
+
stdout: formatDoctorReport({ paths, runtime, config, legacyAccessDetected: false }),
|
|
204
|
+
stderr: ""
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (command === "config") {
|
|
208
|
+
const paths = getMagiPaths(env);
|
|
209
|
+
ensureMagiHome(paths);
|
|
210
|
+
const config = loadConfig(paths, env);
|
|
211
|
+
return {
|
|
212
|
+
exitCode: 0,
|
|
213
|
+
stdout: [`configFile: ${paths.configFile}`, formatConfig(config)].join("\n"),
|
|
214
|
+
stderr: ""
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (command === "sessions") {
|
|
218
|
+
const paths = getMagiPaths(env);
|
|
219
|
+
ensureMagiHome(paths);
|
|
220
|
+
loadConfig(paths, env);
|
|
221
|
+
const store = SessionStore.open(paths);
|
|
222
|
+
try {
|
|
223
|
+
return { exitCode: 0, stdout: formatSessionList(store), stderr: "" };
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
store.close();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (command === "resume") {
|
|
230
|
+
const sessionId = parsed.rest[0];
|
|
231
|
+
if (!sessionId) {
|
|
232
|
+
throw new MagiUsageError("magi resume requires a session id");
|
|
233
|
+
}
|
|
234
|
+
const paths = getMagiPaths(env);
|
|
235
|
+
ensureMagiHome(paths);
|
|
236
|
+
loadConfig(paths, env);
|
|
237
|
+
const store = SessionStore.open(paths);
|
|
238
|
+
try {
|
|
239
|
+
const output = formatSessionResume(store, sessionId);
|
|
240
|
+
return { exitCode: output.startsWith("Session not found:") ? 2 : 0, stdout: output, stderr: "" };
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
store.close();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (command === "-r" || command === "--resume") {
|
|
247
|
+
const paths = getMagiPaths(env);
|
|
248
|
+
ensureMagiHome(paths);
|
|
249
|
+
loadConfig(paths, env);
|
|
250
|
+
const store = SessionStore.open(paths);
|
|
251
|
+
try {
|
|
252
|
+
if (parsed.resumeSessionId) {
|
|
253
|
+
const output = formatSessionResume(store, parsed.resumeSessionId);
|
|
254
|
+
return { exitCode: output.startsWith("Session not found:") ? 2 : 0, stdout: output, stderr: "" };
|
|
255
|
+
}
|
|
256
|
+
return { exitCode: 0, stdout: `${formatSessionSearch(store, "")}\n`, stderr: "" };
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
store.close();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (command === "context") {
|
|
263
|
+
const paths = getMagiPaths(env);
|
|
264
|
+
ensureMagiHome(paths);
|
|
265
|
+
loadConfig(paths, env);
|
|
266
|
+
const store = SessionStore.open(paths);
|
|
267
|
+
try {
|
|
268
|
+
const session = resolveSessionForCommand(store, parsed.rest[0], cwd);
|
|
269
|
+
const summaries = store.listContextSummaries(session.id);
|
|
270
|
+
return {
|
|
271
|
+
exitCode: 0,
|
|
272
|
+
stdout: formatSessionContextBudget(computeSessionContextBudget({ session, summaries })),
|
|
273
|
+
stderr: ""
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
store.close();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (command === "compact") {
|
|
281
|
+
const paths = getMagiPaths(env);
|
|
282
|
+
ensureMagiHome(paths);
|
|
283
|
+
const config = loadConfig(paths, env);
|
|
284
|
+
const setupSessionId = `setup-${Date.now()}`;
|
|
285
|
+
const setupStore = SessionStore.open(paths);
|
|
286
|
+
try {
|
|
287
|
+
await triggerHooks({
|
|
288
|
+
event: "setup",
|
|
289
|
+
hooks: config.hooks,
|
|
290
|
+
store: setupStore,
|
|
291
|
+
sessionId: setupSessionId,
|
|
292
|
+
cwd,
|
|
293
|
+
env
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
setupStore.close();
|
|
298
|
+
}
|
|
299
|
+
const store = SessionStore.open(paths);
|
|
300
|
+
try {
|
|
301
|
+
const session = resolveSessionForCommand(store, parsed.rest[0], cwd);
|
|
302
|
+
const modelRunner = parsed.modelAlias ? resolveCompactionModelRunner(config, env, parsed.modelAlias) : undefined;
|
|
303
|
+
const compacted = await compactSessionWithHooks({
|
|
304
|
+
store,
|
|
305
|
+
sessionId: session.id,
|
|
306
|
+
hooks: config.hooks,
|
|
307
|
+
cwd,
|
|
308
|
+
env,
|
|
309
|
+
modelRunner,
|
|
310
|
+
trigger: "manual"
|
|
311
|
+
});
|
|
312
|
+
return {
|
|
313
|
+
exitCode: 0,
|
|
314
|
+
stdout: formatCompactResult(compacted),
|
|
315
|
+
stderr: ""
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
store.close();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (command === "goal") {
|
|
323
|
+
const paths = getMagiPaths(env);
|
|
324
|
+
ensureMagiHome(paths);
|
|
325
|
+
loadConfig(paths, env);
|
|
326
|
+
const store = SessionStore.open(paths);
|
|
327
|
+
try {
|
|
328
|
+
const sub = parsed.rest[0]?.toLowerCase();
|
|
329
|
+
const session = resolveGoalSessionForCommand({
|
|
330
|
+
store,
|
|
331
|
+
sessionId: parsed.sessionId ?? parsed.resumeSessionId,
|
|
332
|
+
cwd,
|
|
333
|
+
create: isGoalCreationArgs(parsed.rest),
|
|
334
|
+
title: parsed.rest.join(" ").slice(0, 80) || "goal"
|
|
335
|
+
});
|
|
336
|
+
if (!sub || sub === "status" || sub === "show") {
|
|
337
|
+
return { exitCode: 0, stdout: `${formatGoal(getGoal(paths, session.id))}\n`, stderr: "" };
|
|
338
|
+
}
|
|
339
|
+
if (sub === "list") {
|
|
340
|
+
const goals = listGoals(paths, session.id);
|
|
341
|
+
return {
|
|
342
|
+
exitCode: 0,
|
|
343
|
+
stdout: goals.length === 0
|
|
344
|
+
? "No goals for this session.\n"
|
|
345
|
+
: `${["Goals for this session:", ...goals.map((goal) => `- ${formatGoalStatus(goal.status).padEnd(16)} ${goal.objective} (${goal.updatedAt})`)].join("\n")}\n`,
|
|
346
|
+
stderr: ""
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (sub === "done" || sub === "complete" || sub === "completed") {
|
|
350
|
+
const goal = updateGoalStatus(paths, { sessionId: session.id, status: "completed", note: parsed.rest.slice(1).join(" ") });
|
|
351
|
+
return { exitCode: goal ? 0 : 2, stdout: `${goal ? `Goal completed: ${goal.objective}` : "No active goal."}\n`, stderr: "" };
|
|
352
|
+
}
|
|
353
|
+
if (sub === "blocked" || sub === "block") {
|
|
354
|
+
const goal = updateGoalStatus(paths, { sessionId: session.id, status: "blocked", note: parsed.rest.slice(1).join(" ") });
|
|
355
|
+
return { exitCode: goal ? 0 : 2, stdout: `${goal ? `Goal blocked: ${goal.objective}` : "No active goal."}\n`, stderr: "" };
|
|
356
|
+
}
|
|
357
|
+
if (sub === "cancel" || sub === "cancelled" || sub === "clear" || sub === "reset" || sub === "stop") {
|
|
358
|
+
const goal = clearGoal(paths, session.id);
|
|
359
|
+
return { exitCode: goal ? 0 : 2, stdout: `${goal ? `Goal cancelled: ${goal.objective}` : "No active goal."}\n`, stderr: "" };
|
|
360
|
+
}
|
|
361
|
+
const goal = createGoal(paths, { sessionId: session.id, objective: parsed.rest.join(" ") });
|
|
362
|
+
return { exitCode: 0, stdout: `Goal started: ${goal.objective}\n`, stderr: "" };
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
store.close();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (command === "rules") {
|
|
369
|
+
return { exitCode: 0, stdout: formatAgentInstructions(loadAgentInstructions(cwd)), stderr: "" };
|
|
370
|
+
}
|
|
371
|
+
if (command === "workspace") {
|
|
372
|
+
const subcommand = parsed.rest[0] ?? "diagnose";
|
|
373
|
+
if (subcommand !== "diagnose" && subcommand !== "diagnostics") {
|
|
374
|
+
throw new MagiUsageError(`Unknown workspace command: ${subcommand}`);
|
|
375
|
+
}
|
|
376
|
+
const format = parsed.outputFormat === "json" ? "json" : "text";
|
|
377
|
+
const diagnostics = runWorkspaceDiagnostics({
|
|
378
|
+
cwd,
|
|
379
|
+
request: {
|
|
380
|
+
path: parsed.rest[1],
|
|
381
|
+
format,
|
|
382
|
+
maxFiles: 2_000
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
exitCode: 0,
|
|
387
|
+
stdout: formatWorkspaceDiagnostics(diagnostics, format),
|
|
388
|
+
stderr: ""
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (command === "memory") {
|
|
392
|
+
const paths = getMagiPaths(env);
|
|
393
|
+
ensureMagiHome(paths);
|
|
394
|
+
loadConfig(paths, env);
|
|
395
|
+
const subcommand = parsed.rest[0] ?? "view";
|
|
396
|
+
if (subcommand === "view") {
|
|
397
|
+
const scope = readMemoryScope(parsed.rest[1]);
|
|
398
|
+
const sessionId = parsed.resumeSessionId ?? parsed.sessionId;
|
|
399
|
+
if (scope === "session" && !sessionId) {
|
|
400
|
+
throw new MagiUsageError("magi memory view session requires --session-id <id>");
|
|
401
|
+
}
|
|
402
|
+
return { exitCode: 0, stdout: formatMemory({ paths, cwd, scope, sessionId }), stderr: "" };
|
|
403
|
+
}
|
|
404
|
+
if (subcommand === "search") {
|
|
405
|
+
const query = parsed.rest.slice(1).join(" ");
|
|
406
|
+
if (!query.trim()) {
|
|
407
|
+
throw new MagiUsageError("magi memory search requires a query");
|
|
408
|
+
}
|
|
409
|
+
const sessionId = parsed.resumeSessionId ?? parsed.sessionId;
|
|
410
|
+
const results = searchMemory({ paths, cwd, sessionId, query });
|
|
411
|
+
return { exitCode: 0, stdout: `${formatMemorySearchResults(results) || "No matching memory"}\n`, stderr: "" };
|
|
412
|
+
}
|
|
413
|
+
if (subcommand === "append") {
|
|
414
|
+
const scope = readMemoryScope(parsed.rest[1]);
|
|
415
|
+
const text = parsed.rest.slice(2).join(" ");
|
|
416
|
+
if (!text.trim()) {
|
|
417
|
+
throw new MagiUsageError("magi memory append <user|project|session> requires text");
|
|
418
|
+
}
|
|
419
|
+
const store = SessionStore.open(paths);
|
|
420
|
+
try {
|
|
421
|
+
const sessionId = parsed.resumeSessionId ?? parsed.sessionId ?? store.createSession({
|
|
422
|
+
title: `memory append ${scope}`,
|
|
423
|
+
cwd,
|
|
424
|
+
metadata: { command: "memory append", scope }
|
|
425
|
+
});
|
|
426
|
+
const result = appendMemory({ paths, scope, cwd, text, store, sessionId, detailed: true });
|
|
427
|
+
const status = result.appended
|
|
428
|
+
? `Appended ${scope} memory`
|
|
429
|
+
: result.duplicate ? `Skipped duplicate ${scope} memory` : `Skipped conflicting ${scope} memory`;
|
|
430
|
+
return {
|
|
431
|
+
exitCode: 0,
|
|
432
|
+
stdout: [
|
|
433
|
+
`${status}: ${result.file}`,
|
|
434
|
+
`sessionId: ${sessionId}`,
|
|
435
|
+
result.conflicts.length > 0 ? `conflicts: ${result.conflicts.length}` : undefined,
|
|
436
|
+
""
|
|
437
|
+
].filter((line) => Boolean(line)).join("\n"),
|
|
438
|
+
stderr: ""
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
store.close();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
throw new MagiUsageError(`Unknown memory command: ${subcommand}`);
|
|
446
|
+
}
|
|
447
|
+
if (command === "mcp") {
|
|
448
|
+
const paths = getMagiPaths(env);
|
|
449
|
+
ensureMagiHome(paths);
|
|
450
|
+
const config = loadConfig(paths, env);
|
|
451
|
+
const subcommand = parsed.rest[0] ?? "list";
|
|
452
|
+
if (subcommand !== "list" && subcommand !== "resources" && subcommand !== "read-resource") {
|
|
453
|
+
throw new MagiUsageError(`Unknown mcp command: ${subcommand}`);
|
|
454
|
+
}
|
|
455
|
+
const serverName = parsed.rest[1];
|
|
456
|
+
if (!serverName) {
|
|
457
|
+
return {
|
|
458
|
+
exitCode: 0,
|
|
459
|
+
stdout: `${Object.keys(config.mcp.servers).join("\n") || "No MCP servers configured"}\n`,
|
|
460
|
+
stderr: ""
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
if (!config.mcp.servers[serverName]) {
|
|
464
|
+
throw new MagiUsageError(`MCP server is not configured: ${serverName}`);
|
|
465
|
+
}
|
|
466
|
+
const manager = new McpConnectionManager({ servers: config.mcp.servers, env });
|
|
467
|
+
try {
|
|
468
|
+
const client = await manager.connect(serverName);
|
|
469
|
+
if (subcommand === "resources") {
|
|
470
|
+
const resources = await client.listResources();
|
|
471
|
+
return {
|
|
472
|
+
exitCode: 0,
|
|
473
|
+
stdout: `${resources.map((resource) => [
|
|
474
|
+
resource.uri,
|
|
475
|
+
resource.name,
|
|
476
|
+
resource.mimeType,
|
|
477
|
+
resource.description
|
|
478
|
+
].filter(Boolean).join(" ")).join("\n")}\n`,
|
|
479
|
+
stderr: ""
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
if (subcommand === "read-resource") {
|
|
483
|
+
const uri = requireArg(parsed.rest[2], "resource uri");
|
|
484
|
+
const result = await client.readResource(uri);
|
|
485
|
+
return {
|
|
486
|
+
exitCode: 0,
|
|
487
|
+
stdout: `${result.contents.map((content) => [
|
|
488
|
+
content.uri ? `uri: ${content.uri}` : undefined,
|
|
489
|
+
content.mimeType ? `mime: ${content.mimeType}` : undefined,
|
|
490
|
+
content.text ?? content.blob ?? ""
|
|
491
|
+
].filter(Boolean).join("\n")).join("\n\n")}\n`,
|
|
492
|
+
stderr: ""
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
const tools = await client.listTools();
|
|
496
|
+
return {
|
|
497
|
+
exitCode: 0,
|
|
498
|
+
stdout: `${tools.map((tool) => tool.name).join("\n")}\n`,
|
|
499
|
+
stderr: ""
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
manager.disconnectAll();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (command === "plugins") {
|
|
507
|
+
const paths = getMagiPaths(env);
|
|
508
|
+
ensureMagiHome(paths);
|
|
509
|
+
loadConfig(paths, env);
|
|
510
|
+
return { exitCode: 0, stdout: formatPluginList(listLocalPlugins(paths)), stderr: "" };
|
|
511
|
+
}
|
|
512
|
+
if (command === "marketplace") {
|
|
513
|
+
const paths = getMagiPaths(env);
|
|
514
|
+
ensureMagiHome(paths);
|
|
515
|
+
loadConfig(paths, env);
|
|
516
|
+
const records = discoverLocalMarketplaceSources(paths).map(loadMarketplace);
|
|
517
|
+
return { exitCode: 0, stdout: formatMarketplaces(records), stderr: "" };
|
|
518
|
+
}
|
|
519
|
+
if (command === "skills") {
|
|
520
|
+
const paths = getMagiPaths(env);
|
|
521
|
+
ensureMagiHome(paths);
|
|
522
|
+
loadConfig(paths, env);
|
|
523
|
+
const subcommand = parsed.rest[0] ?? "list";
|
|
524
|
+
if (subcommand === "list") {
|
|
525
|
+
return { exitCode: 0, stdout: formatSkillList(listSkills(paths)), stderr: "" };
|
|
526
|
+
}
|
|
527
|
+
if (subcommand === "show") {
|
|
528
|
+
const name = requireArg(parsed.rest[1], "skill name");
|
|
529
|
+
const skill = findSkill(paths, name);
|
|
530
|
+
if (!skill) {
|
|
531
|
+
throw new MagiUsageError(`Skill not found: ${name}`);
|
|
532
|
+
}
|
|
533
|
+
return { exitCode: 0, stdout: `${skill.body ?? ""}\n`, stderr: "" };
|
|
534
|
+
}
|
|
535
|
+
throw new MagiUsageError(`Unknown skills command: ${subcommand}`);
|
|
536
|
+
}
|
|
537
|
+
if (command === "agents") {
|
|
538
|
+
const paths = getMagiPaths(env);
|
|
539
|
+
ensureMagiHome(paths);
|
|
540
|
+
const config = loadConfig(paths, env);
|
|
541
|
+
const setupSessionId = `setup-${Date.now()}`;
|
|
542
|
+
const setupStore = SessionStore.open(paths);
|
|
543
|
+
try {
|
|
544
|
+
await triggerHooks({
|
|
545
|
+
event: "setup",
|
|
546
|
+
hooks: config.hooks,
|
|
547
|
+
store: setupStore,
|
|
548
|
+
sessionId: setupSessionId,
|
|
549
|
+
cwd,
|
|
550
|
+
env
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
finally {
|
|
554
|
+
setupStore.close();
|
|
555
|
+
}
|
|
556
|
+
const store = SessionStore.open(paths);
|
|
557
|
+
try {
|
|
558
|
+
const subcommand = parsed.rest[0] ?? "list";
|
|
559
|
+
if (subcommand === "list") {
|
|
560
|
+
const tasks = store.listAgentTasks(50);
|
|
561
|
+
return {
|
|
562
|
+
exitCode: 0,
|
|
563
|
+
stdout: tasks.length === 0
|
|
564
|
+
? "No agent tasks\n"
|
|
565
|
+
: `${tasks.map((task) => `${task.id} ${task.role} ${task.status} ${task.prompt}`).join("\n")}\n`,
|
|
566
|
+
stderr: ""
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (subcommand === "spawn") {
|
|
570
|
+
const role = readAgentRole(parsed.rest[1]);
|
|
571
|
+
const prompt = parsed.rest.slice(2).join(" ");
|
|
572
|
+
if (!prompt.trim()) {
|
|
573
|
+
throw new MagiUsageError("magi agents spawn <explorer|worker> <prompt> requires prompt");
|
|
574
|
+
}
|
|
575
|
+
const sessionId = store.createSession({ title: `agent task ${role}`, cwd, metadata: { command: "agents spawn", role } });
|
|
576
|
+
const task = spawnAgentTask(store, {
|
|
577
|
+
role,
|
|
578
|
+
prompt,
|
|
579
|
+
cwd,
|
|
580
|
+
sessionId,
|
|
581
|
+
writeFiles: parsed.writeFiles
|
|
582
|
+
});
|
|
583
|
+
await triggerHooks({
|
|
584
|
+
event: "task_created",
|
|
585
|
+
hooks: config.hooks,
|
|
586
|
+
store,
|
|
587
|
+
sessionId,
|
|
588
|
+
cwd,
|
|
589
|
+
env,
|
|
590
|
+
context: {
|
|
591
|
+
taskId: task.id,
|
|
592
|
+
taskSubject: prompt,
|
|
593
|
+
taskDescription: prompt,
|
|
594
|
+
agentId: task.id,
|
|
595
|
+
agentType: task.role
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
return { exitCode: 0, stdout: `${JSON.stringify(task)}\n`, stderr: "" };
|
|
599
|
+
}
|
|
600
|
+
if (subcommand === "start") {
|
|
601
|
+
const task = startAgentTask(store, requireArg(parsed.rest[1], "task id"));
|
|
602
|
+
const sessionId = task.sessionId ?? store.createSession({ title: "cli agent start", cwd: task.cwd });
|
|
603
|
+
await triggerHooks({
|
|
604
|
+
event: "subagent_start",
|
|
605
|
+
hooks: config.hooks,
|
|
606
|
+
store,
|
|
607
|
+
sessionId,
|
|
608
|
+
cwd: task.cwd,
|
|
609
|
+
env,
|
|
610
|
+
context: {
|
|
611
|
+
agentId: task.id,
|
|
612
|
+
agentType: task.role,
|
|
613
|
+
taskId: task.id,
|
|
614
|
+
taskSubject: task.prompt
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
return { exitCode: 0, stdout: `${JSON.stringify(task)}\n`, stderr: "" };
|
|
618
|
+
}
|
|
619
|
+
if (subcommand === "wait") {
|
|
620
|
+
return { exitCode: 0, stdout: `${JSON.stringify(waitAgentTask(store, requireArg(parsed.rest[1], "task id")))}\n`, stderr: "" };
|
|
621
|
+
}
|
|
622
|
+
if (subcommand === "cancel") {
|
|
623
|
+
const task = cancelAgentTask(store, requireArg(parsed.rest[1], "task id"));
|
|
624
|
+
const sessionId = task.sessionId ?? store.createSession({ title: "cli agent stop", cwd: task.cwd });
|
|
625
|
+
await triggerHooks({
|
|
626
|
+
event: "stop",
|
|
627
|
+
hooks: config.hooks,
|
|
628
|
+
store,
|
|
629
|
+
sessionId,
|
|
630
|
+
cwd: task.cwd,
|
|
631
|
+
env,
|
|
632
|
+
context: {
|
|
633
|
+
message: `Agent task ${task.id} cancelled`,
|
|
634
|
+
notificationType: "agent_task_cancelled",
|
|
635
|
+
lastAssistantMessage: task.result ?? undefined
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
await triggerHooks({
|
|
639
|
+
event: "subagent_stop",
|
|
640
|
+
hooks: config.hooks,
|
|
641
|
+
store,
|
|
642
|
+
sessionId,
|
|
643
|
+
cwd: task.cwd,
|
|
644
|
+
env,
|
|
645
|
+
context: {
|
|
646
|
+
agentId: task.id,
|
|
647
|
+
agentType: task.role,
|
|
648
|
+
taskId: task.id,
|
|
649
|
+
taskSubject: task.prompt,
|
|
650
|
+
message: `Agent task ${task.id} cancelled`,
|
|
651
|
+
notificationType: "agent_task_cancelled"
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
return { exitCode: 0, stdout: `${JSON.stringify(task)}\n`, stderr: "" };
|
|
655
|
+
}
|
|
656
|
+
if (subcommand === "complete") {
|
|
657
|
+
const task = completeAgentTask(store, requireArg(parsed.rest[1], "task id"), parsed.rest.slice(2).join(" "));
|
|
658
|
+
const sessionId = task.sessionId ?? store.createSession({ title: "cli agent notification", cwd: task.cwd });
|
|
659
|
+
await triggerHooks({
|
|
660
|
+
event: "notification",
|
|
661
|
+
hooks: config.hooks,
|
|
662
|
+
store,
|
|
663
|
+
sessionId,
|
|
664
|
+
cwd: task.cwd,
|
|
665
|
+
env,
|
|
666
|
+
context: {
|
|
667
|
+
message: `Agent task ${task.id} completed`,
|
|
668
|
+
title: "Agent task completed",
|
|
669
|
+
notificationType: "agent_task_completed",
|
|
670
|
+
lastAssistantMessage: task.result ?? undefined
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
await triggerHooks({
|
|
674
|
+
event: "task_completed",
|
|
675
|
+
hooks: config.hooks,
|
|
676
|
+
store,
|
|
677
|
+
sessionId,
|
|
678
|
+
cwd: task.cwd,
|
|
679
|
+
env,
|
|
680
|
+
context: {
|
|
681
|
+
taskId: task.id,
|
|
682
|
+
taskSubject: task.prompt,
|
|
683
|
+
taskDescription: task.prompt,
|
|
684
|
+
agentId: task.id,
|
|
685
|
+
agentType: task.role,
|
|
686
|
+
lastAssistantMessage: task.result ?? undefined
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
await triggerHooks({
|
|
690
|
+
event: "subagent_stop",
|
|
691
|
+
hooks: config.hooks,
|
|
692
|
+
store,
|
|
693
|
+
sessionId,
|
|
694
|
+
cwd: task.cwd,
|
|
695
|
+
env,
|
|
696
|
+
context: {
|
|
697
|
+
agentId: task.id,
|
|
698
|
+
agentType: task.role,
|
|
699
|
+
taskId: task.id,
|
|
700
|
+
taskSubject: task.prompt,
|
|
701
|
+
lastAssistantMessage: task.result ?? undefined
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
return {
|
|
705
|
+
exitCode: 0,
|
|
706
|
+
stdout: `${JSON.stringify(task)}\n`,
|
|
707
|
+
stderr: ""
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
throw new MagiUsageError(`Unknown agents command: ${subcommand}`);
|
|
711
|
+
}
|
|
712
|
+
finally {
|
|
713
|
+
store.close();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (command === "runner") {
|
|
717
|
+
const subcommand = parsed.rest[0] ?? "ping";
|
|
718
|
+
const client = new RunnerClient({ command: resolveRunnerCommand(env), env });
|
|
719
|
+
try {
|
|
720
|
+
if (subcommand === "ping") {
|
|
721
|
+
const initialized = await client.initialize();
|
|
722
|
+
const ping = await client.ping();
|
|
723
|
+
return {
|
|
724
|
+
exitCode: 0,
|
|
725
|
+
stdout: [
|
|
726
|
+
`runner: ${initialized.runner}`,
|
|
727
|
+
`version: ${initialized.version}`,
|
|
728
|
+
`capabilities: ${initialized.capabilities.join(",")}`,
|
|
729
|
+
`ok: ${ping.ok ? "true" : "false"}`,
|
|
730
|
+
""
|
|
731
|
+
].join("\n"),
|
|
732
|
+
stderr: ""
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
if (subcommand === "run") {
|
|
736
|
+
const shellCommand = parsed.rest.slice(1).join(" ");
|
|
737
|
+
if (!shellCommand.trim()) {
|
|
738
|
+
throw new MagiUsageError("magi runner run requires a command");
|
|
739
|
+
}
|
|
740
|
+
const result = await client.runProcess({
|
|
741
|
+
command: shellCommand,
|
|
742
|
+
cwd,
|
|
743
|
+
timeoutMs: parsed.runnerTimeoutMs
|
|
744
|
+
});
|
|
745
|
+
return {
|
|
746
|
+
exitCode: result.timedOut ? 124 : result.exitCode ?? 1,
|
|
747
|
+
stdout: [
|
|
748
|
+
`command: ${result.command}`,
|
|
749
|
+
`cwd: ${result.cwd}`,
|
|
750
|
+
`exitCode: ${result.exitCode ?? "null"}`,
|
|
751
|
+
`timedOut: ${result.timedOut ? "true" : "false"}`,
|
|
752
|
+
"stdout:",
|
|
753
|
+
result.stdout,
|
|
754
|
+
"stderr:",
|
|
755
|
+
result.stderr
|
|
756
|
+
].join("\n"),
|
|
757
|
+
stderr: ""
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (subcommand === "pty-smoke") {
|
|
761
|
+
const result = await client.ptySmoke();
|
|
762
|
+
return {
|
|
763
|
+
exitCode: result.ok ? 0 : 1,
|
|
764
|
+
stdout: [
|
|
765
|
+
`ok: ${result.ok ? "true" : "false"}`,
|
|
766
|
+
"stdout:",
|
|
767
|
+
result.stdout,
|
|
768
|
+
"stderr:",
|
|
769
|
+
result.stderr
|
|
770
|
+
].join("\n"),
|
|
771
|
+
stderr: ""
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
if (subcommand === "apply") {
|
|
775
|
+
const filePath = parsed.rest[1];
|
|
776
|
+
const content = parsed.rest.slice(2).join(" ");
|
|
777
|
+
if (!filePath || !content) {
|
|
778
|
+
throw new MagiUsageError("magi runner apply <file> <content> requires file and content");
|
|
779
|
+
}
|
|
780
|
+
if (!parsed.approve) {
|
|
781
|
+
throw new MagiUsageError("magi runner apply requires --approve");
|
|
782
|
+
}
|
|
783
|
+
const paths = getMagiPaths(env);
|
|
784
|
+
ensureMagiHome(paths);
|
|
785
|
+
loadConfig(paths, env);
|
|
786
|
+
const store = SessionStore.open(paths);
|
|
787
|
+
try {
|
|
788
|
+
const sessionId = parsed.sessionId ?? store.createSession({
|
|
789
|
+
title: `runner apply ${filePath}`,
|
|
790
|
+
cwd,
|
|
791
|
+
metadata: { command: "runner apply" }
|
|
792
|
+
});
|
|
793
|
+
const result = await client.applyPatch({
|
|
794
|
+
cwd,
|
|
795
|
+
filePath,
|
|
796
|
+
content,
|
|
797
|
+
approved: parsed.approve
|
|
798
|
+
});
|
|
799
|
+
store.recordAudit({
|
|
800
|
+
sessionId,
|
|
801
|
+
action: result.auditEvent.action,
|
|
802
|
+
target: result.auditEvent.target ?? result.path,
|
|
803
|
+
metadata: result.auditEvent.metadata
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
806
|
+
exitCode: 0,
|
|
807
|
+
stdout: [
|
|
808
|
+
`path: ${result.path}`,
|
|
809
|
+
`approved: ${result.approved ? "true" : "false"}`,
|
|
810
|
+
`sessionId: ${sessionId}`,
|
|
811
|
+
"diff:",
|
|
812
|
+
result.diff
|
|
813
|
+
].join("\n"),
|
|
814
|
+
stderr: ""
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
finally {
|
|
818
|
+
store.close();
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
throw new MagiUsageError(`Unknown runner command: ${subcommand}`);
|
|
822
|
+
}
|
|
823
|
+
finally {
|
|
824
|
+
client.close();
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (command === "peers") {
|
|
828
|
+
const sub = parsed.rest[0];
|
|
829
|
+
// peers add <name> <url> <device-id> <token>
|
|
830
|
+
if (sub === "add") {
|
|
831
|
+
const [, name, url, deviceId, token] = parsed.rest;
|
|
832
|
+
if (!name || !url || !deviceId || !token) {
|
|
833
|
+
throw new MagiUsageError("Usage: magi peers add <name> <url> <device-id> <token>");
|
|
834
|
+
}
|
|
835
|
+
const paths = getMagiPaths(env);
|
|
836
|
+
ensureMagiHome(paths);
|
|
837
|
+
const store = SessionStore.open(paths);
|
|
838
|
+
try {
|
|
839
|
+
store.upsertMcpOAuthToken({
|
|
840
|
+
serverName: `peer:${name}`,
|
|
841
|
+
accessToken: token,
|
|
842
|
+
tokenType: "Bearer",
|
|
843
|
+
authServerUrl: url,
|
|
844
|
+
metadata: { deviceId, peerUrl: url }
|
|
845
|
+
});
|
|
846
|
+
return {
|
|
847
|
+
exitCode: 0,
|
|
848
|
+
stdout: `Saved peer credentials for "${name}" (${url}).\nUse it as a target: Agent({target: "${name}"})\n`,
|
|
849
|
+
stderr: ""
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
finally {
|
|
853
|
+
store.close();
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (sub === "remove" || sub === "rm") {
|
|
857
|
+
const name = parsed.rest[1];
|
|
858
|
+
if (!name)
|
|
859
|
+
throw new MagiUsageError("Usage: magi peers remove <name>");
|
|
860
|
+
const paths = getMagiPaths(env);
|
|
861
|
+
ensureMagiHome(paths);
|
|
862
|
+
const store = SessionStore.open(paths);
|
|
863
|
+
try {
|
|
864
|
+
store.deleteMcpOAuthToken(`peer:${name}`);
|
|
865
|
+
return { exitCode: 0, stdout: `Removed peer credentials for "${name}".\n`, stderr: "" };
|
|
866
|
+
}
|
|
867
|
+
finally {
|
|
868
|
+
store.close();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (sub === "saved") {
|
|
872
|
+
const paths = getMagiPaths(env);
|
|
873
|
+
ensureMagiHome(paths);
|
|
874
|
+
const store = SessionStore.open(paths);
|
|
875
|
+
try {
|
|
876
|
+
const tokens = store.listMcpOAuthTokens().filter(t => t.serverName.startsWith("peer:"));
|
|
877
|
+
if (tokens.length === 0) {
|
|
878
|
+
return { exitCode: 0, stdout: "No saved peers.\nUse 'magi peers add <name> <url> <device-id> <token>' to register one.\n", stderr: "" };
|
|
879
|
+
}
|
|
880
|
+
const lines = ["Saved peers:", ""];
|
|
881
|
+
for (const t of tokens) {
|
|
882
|
+
const name = t.serverName.replace(/^peer:/, "");
|
|
883
|
+
const url = t.metadata?.peerUrl ?? t.authServerUrl ?? "?";
|
|
884
|
+
lines.push(` ${name.padEnd(24)} ${url}`);
|
|
885
|
+
}
|
|
886
|
+
return { exitCode: 0, stdout: lines.join("\n") + "\n", stderr: "" };
|
|
887
|
+
}
|
|
888
|
+
finally {
|
|
889
|
+
store.close();
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
// Default: discover via mDNS
|
|
893
|
+
const { browseMdns } = await import("./control/mdns.js");
|
|
894
|
+
const handle = browseMdns({});
|
|
895
|
+
const waitMs = sub === "list" ? Number(parsed.rest[1]) || 2500 : Number(sub) || 2500;
|
|
896
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
897
|
+
const peers = handle.peers();
|
|
898
|
+
handle.stop();
|
|
899
|
+
if (peers.length === 0) {
|
|
900
|
+
return {
|
|
901
|
+
exitCode: 0,
|
|
902
|
+
stdout: [
|
|
903
|
+
"No Magi peers discovered on the LAN.",
|
|
904
|
+
"",
|
|
905
|
+
`Scanned for ${waitMs}ms via mDNS (_magi._tcp.local.).`,
|
|
906
|
+
"Make sure other daemons are running with mDNS enabled.",
|
|
907
|
+
"Set MAGI_DISABLE_MDNS=1 to disable advertisement on this host.",
|
|
908
|
+
"",
|
|
909
|
+
"To register a peer manually with credentials:",
|
|
910
|
+
" magi peers add <name> <url> <device-id> <token>"
|
|
911
|
+
].join("\n") + "\n",
|
|
912
|
+
stderr: ""
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
const lines = [`Discovered ${peers.length} Magi peer(s):`, ""];
|
|
916
|
+
for (const peer of peers) {
|
|
917
|
+
lines.push(` ${peer.instanceName}`);
|
|
918
|
+
lines.push(` Host: ${peer.hostname}`);
|
|
919
|
+
lines.push(` Address: ${peer.address}:${peer.port}`);
|
|
920
|
+
if (Object.keys(peer.txt).length > 0) {
|
|
921
|
+
lines.push(` Info: ${Object.entries(peer.txt).map(([k, v]) => `${k}=${v}`).join(", ")}`);
|
|
922
|
+
}
|
|
923
|
+
lines.push("");
|
|
924
|
+
}
|
|
925
|
+
lines.push("Use 'magi peers add <name> <url> <device-id> <token>' to save credentials for cross-machine dispatch.");
|
|
926
|
+
return { exitCode: 0, stdout: lines.join("\n"), stderr: "" };
|
|
927
|
+
}
|
|
928
|
+
if (command === "ps") {
|
|
929
|
+
const paths = getMagiPaths(env);
|
|
930
|
+
ensureMagiHome(paths);
|
|
931
|
+
const limit = Number(parsed.rest[0]) || 30;
|
|
932
|
+
const store = SessionStore.open(paths);
|
|
933
|
+
try {
|
|
934
|
+
const jobs = store.listJobs(limit);
|
|
935
|
+
if (jobs.length === 0) {
|
|
936
|
+
return { exitCode: 0, stdout: "No jobs found.\n", stderr: "" };
|
|
937
|
+
}
|
|
938
|
+
const lines = ["Recent jobs (newest first):", ""];
|
|
939
|
+
lines.push(` ${"ID".padEnd(38)} ${"Status".padEnd(11)} ${"Kind".padEnd(16)} ${"Created".padEnd(20)} Title`);
|
|
940
|
+
for (const job of jobs) {
|
|
941
|
+
const meta = (job.metadata ?? {});
|
|
942
|
+
const desc = typeof meta.description === "string" ? meta.description
|
|
943
|
+
: typeof meta.title === "string" ? meta.title
|
|
944
|
+
: "";
|
|
945
|
+
const created = job.createdAt.replace("T", " ").slice(0, 19);
|
|
946
|
+
lines.push(` ${job.id.padEnd(38)} ${job.status.padEnd(11)} ${job.kind.padEnd(16)} ${created.padEnd(20)} ${desc}`);
|
|
947
|
+
}
|
|
948
|
+
lines.push("");
|
|
949
|
+
lines.push("Use 'magi logs <id>' for events, 'magi kill <id>' to cancel a running job.");
|
|
950
|
+
return { exitCode: 0, stdout: lines.join("\n") + "\n", stderr: "" };
|
|
951
|
+
}
|
|
952
|
+
finally {
|
|
953
|
+
store.close();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (command === "logs") {
|
|
957
|
+
const jobId = parsed.rest[0];
|
|
958
|
+
if (!jobId) {
|
|
959
|
+
throw new MagiUsageError("Usage: magi logs <job-id> [tail-count]");
|
|
960
|
+
}
|
|
961
|
+
const tail = Number(parsed.rest[1]) || 100;
|
|
962
|
+
const paths = getMagiPaths(env);
|
|
963
|
+
ensureMagiHome(paths);
|
|
964
|
+
const store = SessionStore.open(paths);
|
|
965
|
+
try {
|
|
966
|
+
const job = store.getJob(jobId);
|
|
967
|
+
if (!job) {
|
|
968
|
+
return { exitCode: 0, stdout: `Job not found: ${jobId}\n`, stderr: "" };
|
|
969
|
+
}
|
|
970
|
+
const events = store.listAuditEvents(2000).filter((e) => e.jobId === jobId);
|
|
971
|
+
const lines = [
|
|
972
|
+
`Job: ${job.id}`,
|
|
973
|
+
`Status: ${job.status} Kind: ${job.kind} Session: ${job.sessionId}`,
|
|
974
|
+
`Created: ${job.createdAt}`
|
|
975
|
+
];
|
|
976
|
+
if (job.updatedAt)
|
|
977
|
+
lines.push(`Updated: ${job.updatedAt}`);
|
|
978
|
+
const meta = (job.metadata ?? {});
|
|
979
|
+
if (typeof meta.error === "string")
|
|
980
|
+
lines.push(`Error: ${meta.error}`);
|
|
981
|
+
if (typeof meta.result === "string") {
|
|
982
|
+
const r = meta.result.length > 400 ? meta.result.slice(0, 400) + "..." : meta.result;
|
|
983
|
+
lines.push("", "Result:", r);
|
|
984
|
+
}
|
|
985
|
+
lines.push("", `Events (${events.length}):`);
|
|
986
|
+
const slice = events.slice(0, tail).reverse();
|
|
987
|
+
for (const event of slice) {
|
|
988
|
+
const time = event.createdAt.slice(11, 19);
|
|
989
|
+
const target = event.target ? ` ${event.target}` : "";
|
|
990
|
+
lines.push(` ${time} ${event.action}${target}`);
|
|
991
|
+
}
|
|
992
|
+
return { exitCode: 0, stdout: lines.join("\n") + "\n", stderr: "" };
|
|
993
|
+
}
|
|
994
|
+
finally {
|
|
995
|
+
store.close();
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (command === "kill") {
|
|
999
|
+
const jobId = parsed.rest[0];
|
|
1000
|
+
if (!jobId) {
|
|
1001
|
+
throw new MagiUsageError("Usage: magi kill <job-id>");
|
|
1002
|
+
}
|
|
1003
|
+
const paths = getMagiPaths(env);
|
|
1004
|
+
ensureMagiHome(paths);
|
|
1005
|
+
const status = getDaemonStatus(paths);
|
|
1006
|
+
if (!status.running) {
|
|
1007
|
+
return {
|
|
1008
|
+
exitCode: 1,
|
|
1009
|
+
stdout: "",
|
|
1010
|
+
stderr: "Magi daemon is not running. Only running jobs can be cancelled.\nStart it with: magi daemon start\n"
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
const reason = parsed.rest.slice(1).join(" ").trim() || "cancelled by user";
|
|
1014
|
+
const url = `http://${status.bind ?? "127.0.0.1"}:${status.port}/jobs/${encodeURIComponent(jobId)}/cancel`;
|
|
1015
|
+
try {
|
|
1016
|
+
const response = await fetch(url, {
|
|
1017
|
+
method: "POST",
|
|
1018
|
+
headers: { "Content-Type": "application/json" },
|
|
1019
|
+
body: JSON.stringify({ reason })
|
|
1020
|
+
});
|
|
1021
|
+
if (!response.ok) {
|
|
1022
|
+
const text = await response.text();
|
|
1023
|
+
return { exitCode: 1, stdout: "", stderr: `Daemon rejected cancel (${response.status}): ${text}\n` };
|
|
1024
|
+
}
|
|
1025
|
+
return { exitCode: 0, stdout: `Cancelled job ${jobId}\n`, stderr: "" };
|
|
1026
|
+
}
|
|
1027
|
+
catch (error) {
|
|
1028
|
+
return { exitCode: 1, stdout: "", stderr: `Failed to reach daemon: ${error instanceof Error ? error.message : String(error)}\n` };
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (command === "tutorial") {
|
|
1032
|
+
const { runTutorial } = await import("./commands/tutorial.js");
|
|
1033
|
+
await runTutorial();
|
|
1034
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
1035
|
+
}
|
|
1036
|
+
if (command === "init") {
|
|
1037
|
+
const paths = getMagiPaths(env);
|
|
1038
|
+
ensureMagiHome(paths);
|
|
1039
|
+
const { runInit } = await import("./commands/init.js");
|
|
1040
|
+
const presetArg = parsed.rest[0];
|
|
1041
|
+
const preset = presetArg === "anthropic" || presetArg === "openai" || presetArg === "deepseek"
|
|
1042
|
+
? presetArg
|
|
1043
|
+
: undefined;
|
|
1044
|
+
const nonInteractive = parsed.rest.includes("--non-interactive") || parsed.rest.includes("-y");
|
|
1045
|
+
const result = await runInit({ paths, env, preset, nonInteractive });
|
|
1046
|
+
if (!result.wrote && result.reason) {
|
|
1047
|
+
return { exitCode: 0, stdout: `${result.reason}\n`, stderr: "" };
|
|
1048
|
+
}
|
|
1049
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
1050
|
+
}
|
|
1051
|
+
if (command === "pair") {
|
|
1052
|
+
const paths = getMagiPaths(env);
|
|
1053
|
+
ensureMagiHome(paths);
|
|
1054
|
+
const status = getDaemonStatus(paths);
|
|
1055
|
+
if (!status.running) {
|
|
1056
|
+
throw new MagiUsageError("Magi daemon is not running. Start it first: magi daemon start");
|
|
1057
|
+
}
|
|
1058
|
+
const deviceName = parsed.rest[0] ?? `device-${Date.now().toString(36)}`;
|
|
1059
|
+
const url = `http://${status.bind ?? "127.0.0.1"}:${status.port ?? 8765}/pairing`;
|
|
1060
|
+
const response = await fetch(url, {
|
|
1061
|
+
method: "POST",
|
|
1062
|
+
headers: { "Content-Type": "application/json" },
|
|
1063
|
+
body: JSON.stringify({ name: deviceName })
|
|
1064
|
+
});
|
|
1065
|
+
if (!response.ok) {
|
|
1066
|
+
throw new MagiUsageError(`Pairing request failed (${response.status}): ${await response.text()}`);
|
|
1067
|
+
}
|
|
1068
|
+
const token = await response.json();
|
|
1069
|
+
// Build the connection URL (works for phone) — replace bind with actual LAN IP if needed
|
|
1070
|
+
const { networkInterfaces } = await import("node:os");
|
|
1071
|
+
const ifaces = networkInterfaces();
|
|
1072
|
+
const lanIps = [];
|
|
1073
|
+
for (const list of Object.values(ifaces)) {
|
|
1074
|
+
for (const iface of list ?? []) {
|
|
1075
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
1076
|
+
lanIps.push(iface.address);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const port = status.port ?? 8765;
|
|
1081
|
+
const lines = [
|
|
1082
|
+
`Pairing token created for "${deviceName}".`,
|
|
1083
|
+
"",
|
|
1084
|
+
`Device ID: ${token.deviceId}`,
|
|
1085
|
+
`Token: ${token.token}`,
|
|
1086
|
+
`Expires: ${token.expiresAt}`,
|
|
1087
|
+
"",
|
|
1088
|
+
"Use these on the client side. Set headers on every request:",
|
|
1089
|
+
" X-Magi-Device-Id: <device-id>",
|
|
1090
|
+
" Authorization: Bearer <token>",
|
|
1091
|
+
""
|
|
1092
|
+
];
|
|
1093
|
+
if (status.bind === "0.0.0.0" || status.bind === "::" || lanIps.length > 0) {
|
|
1094
|
+
lines.push("Open the panel on your phone (paired automatically):");
|
|
1095
|
+
lines.push("Device ID: " + token.deviceId);
|
|
1096
|
+
lines.push("Token: " + token.token);
|
|
1097
|
+
lines.push("");
|
|
1098
|
+
lines.push("Paste this URL into your phone's browser (token NOT in URL for security):");
|
|
1099
|
+
for (const ip of lanIps) {
|
|
1100
|
+
lines.push(` http://${ip}:${port}/panel`);
|
|
1101
|
+
}
|
|
1102
|
+
lines.push("");
|
|
1103
|
+
}
|
|
1104
|
+
lines.push(`Local: http://127.0.0.1:${port}/panel`);
|
|
1105
|
+
lines.push("");
|
|
1106
|
+
if (status.bind !== "0.0.0.0" && status.bind !== "::") {
|
|
1107
|
+
lines.push("To allow LAN access (for phone), restart the daemon with MAGI_CONTROL_BIND=0.0.0.0:");
|
|
1108
|
+
lines.push(" magi daemon stop && MAGI_CONTROL_BIND=0.0.0.0 magi daemon start");
|
|
1109
|
+
}
|
|
1110
|
+
return { exitCode: 0, stdout: lines.join("\n") + "\n", stderr: "" };
|
|
1111
|
+
}
|
|
1112
|
+
if (command === "daemon") {
|
|
1113
|
+
const sub = parsed.rest[0] ?? "status";
|
|
1114
|
+
const paths = getMagiPaths(env);
|
|
1115
|
+
ensureMagiHome(paths);
|
|
1116
|
+
if (sub === "start") {
|
|
1117
|
+
const status = getDaemonStatus(paths);
|
|
1118
|
+
if (status.running) {
|
|
1119
|
+
return {
|
|
1120
|
+
exitCode: 0,
|
|
1121
|
+
stdout: `Magi daemon is already running (pid ${status.pid}, ${status.bind}:${status.port}).\nLog: ${status.logFile}\n`,
|
|
1122
|
+
stderr: ""
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
const binPath = process.argv[1];
|
|
1126
|
+
const result = startDaemon(paths, { binPath, env });
|
|
1127
|
+
return {
|
|
1128
|
+
exitCode: 0,
|
|
1129
|
+
stdout: [
|
|
1130
|
+
`Magi daemon started (pid ${result.pid}).`,
|
|
1131
|
+
`Log: ${result.logFile}`,
|
|
1132
|
+
`PID: ${result.pidFile}`,
|
|
1133
|
+
`Use 'magi daemon status' to verify, 'magi daemon stop' to stop.`
|
|
1134
|
+
].join("\n") + "\n",
|
|
1135
|
+
stderr: ""
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
if (sub === "stop") {
|
|
1139
|
+
const result = stopDaemon(paths);
|
|
1140
|
+
if (!result.stopped) {
|
|
1141
|
+
return { exitCode: 0, stdout: "Magi daemon is not running.\n", stderr: "" };
|
|
1142
|
+
}
|
|
1143
|
+
return { exitCode: 0, stdout: `Stopped Magi daemon (pid ${result.pid}).\n`, stderr: "" };
|
|
1144
|
+
}
|
|
1145
|
+
if (sub === "status") {
|
|
1146
|
+
const status = getDaemonStatus(paths);
|
|
1147
|
+
if (!status.running) {
|
|
1148
|
+
return {
|
|
1149
|
+
exitCode: 0,
|
|
1150
|
+
stdout: [
|
|
1151
|
+
"Magi daemon is not running.",
|
|
1152
|
+
`PID file: ${status.pidFile}`,
|
|
1153
|
+
`Log file: ${status.logFile}`,
|
|
1154
|
+
"Use 'magi daemon start' to start it."
|
|
1155
|
+
].join("\n") + "\n",
|
|
1156
|
+
stderr: ""
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
return {
|
|
1160
|
+
exitCode: 0,
|
|
1161
|
+
stdout: [
|
|
1162
|
+
`Magi daemon is running (pid ${status.pid}).`,
|
|
1163
|
+
`Address: ${status.bind ?? "?"}:${status.port ?? "?"}`,
|
|
1164
|
+
`Started: ${status.startedAt ?? "?"}`,
|
|
1165
|
+
`Log: ${status.logFile}`
|
|
1166
|
+
].join("\n") + "\n",
|
|
1167
|
+
stderr: ""
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
if (sub === "restart") {
|
|
1171
|
+
stopDaemon(paths);
|
|
1172
|
+
// Wait briefly for the process to terminate
|
|
1173
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
1174
|
+
const binPath = process.argv[1];
|
|
1175
|
+
const result = startDaemon(paths, { binPath, env });
|
|
1176
|
+
return {
|
|
1177
|
+
exitCode: 0,
|
|
1178
|
+
stdout: `Restarted Magi daemon (pid ${result.pid}).\n`,
|
|
1179
|
+
stderr: ""
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
if (sub === "logs") {
|
|
1183
|
+
const status = getDaemonStatus(paths);
|
|
1184
|
+
const { readFileSync, existsSync } = await import("node:fs");
|
|
1185
|
+
if (!existsSync(status.logFile)) {
|
|
1186
|
+
return { exitCode: 0, stdout: "No daemon logs yet.\n", stderr: "" };
|
|
1187
|
+
}
|
|
1188
|
+
const tail = parsed.rest[1] ? Number(parsed.rest[1]) : 50;
|
|
1189
|
+
const content = readFileSync(status.logFile, "utf8");
|
|
1190
|
+
const lines = content.split("\n");
|
|
1191
|
+
const lastN = lines.slice(-tail).join("\n");
|
|
1192
|
+
return { exitCode: 0, stdout: lastN.endsWith("\n") ? lastN : lastN + "\n", stderr: "" };
|
|
1193
|
+
}
|
|
1194
|
+
throw new MagiUsageError(`Unknown daemon subcommand: ${sub}. Use start/stop/restart/status/logs.`);
|
|
1195
|
+
}
|
|
1196
|
+
if (command === "serve") {
|
|
1197
|
+
const paths = getMagiPaths(env);
|
|
1198
|
+
ensureMagiHome(paths);
|
|
1199
|
+
const runtime = getRuntimeSettings(env);
|
|
1200
|
+
const config = loadConfig(paths, env);
|
|
1201
|
+
const setupSessionId = `setup-${Date.now()}`;
|
|
1202
|
+
const setupStore = SessionStore.open(paths);
|
|
1203
|
+
try {
|
|
1204
|
+
await triggerHooks({
|
|
1205
|
+
event: "setup",
|
|
1206
|
+
hooks: config.hooks,
|
|
1207
|
+
store: setupStore,
|
|
1208
|
+
sessionId: setupSessionId,
|
|
1209
|
+
cwd,
|
|
1210
|
+
env
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
finally {
|
|
1214
|
+
setupStore.close();
|
|
1215
|
+
}
|
|
1216
|
+
const store = SessionStore.open(paths);
|
|
1217
|
+
const handle = await startControlServer({ paths, runtime, config, store, cwd, env });
|
|
1218
|
+
// If running as a daemon, write the real PID file with the bound port
|
|
1219
|
+
let daemonLogger;
|
|
1220
|
+
if (env?.MAGI_DAEMON === "1") {
|
|
1221
|
+
const portMatch = /:(\d+)$/.exec(handle.url);
|
|
1222
|
+
const boundPort = portMatch ? Number(portMatch[1]) : runtime.controlPort;
|
|
1223
|
+
writeDaemonPidFile(paths, {
|
|
1224
|
+
pid: process.pid,
|
|
1225
|
+
port: boundPort,
|
|
1226
|
+
bind: runtime.controlBind
|
|
1227
|
+
});
|
|
1228
|
+
// Structured JSON log for the daemon process
|
|
1229
|
+
const logLevel = env.MAGI_LOG_LEVEL ?? "info";
|
|
1230
|
+
daemonLogger = createJsonLogger({
|
|
1231
|
+
filePath: path.join(paths.logsRoot, "magi-daemon.log"),
|
|
1232
|
+
level: logLevel
|
|
1233
|
+
});
|
|
1234
|
+
daemonLogger.info("daemon started", {
|
|
1235
|
+
pid: process.pid,
|
|
1236
|
+
port: boundPort,
|
|
1237
|
+
bind: runtime.controlBind,
|
|
1238
|
+
url: handle.url,
|
|
1239
|
+
version: VERSION
|
|
1240
|
+
});
|
|
1241
|
+
// Cleanup PID file and logger on graceful shutdown
|
|
1242
|
+
const cleanup = () => {
|
|
1243
|
+
try {
|
|
1244
|
+
daemonLogger?.info("daemon stopping", { pid: process.pid });
|
|
1245
|
+
}
|
|
1246
|
+
catch { }
|
|
1247
|
+
clearDaemonPidFile(paths);
|
|
1248
|
+
try {
|
|
1249
|
+
daemonLogger?.close();
|
|
1250
|
+
}
|
|
1251
|
+
catch { }
|
|
1252
|
+
};
|
|
1253
|
+
process.on("SIGTERM", cleanup);
|
|
1254
|
+
process.on("SIGINT", cleanup);
|
|
1255
|
+
process.on("exit", cleanup);
|
|
1256
|
+
}
|
|
1257
|
+
if (isMain(import.meta.url, process.argv[1])) {
|
|
1258
|
+
process.stdout.write(`Magi Control API listening on ${handle.url}\n`);
|
|
1259
|
+
await waitForShutdown();
|
|
1260
|
+
await handle.close();
|
|
1261
|
+
store.close();
|
|
1262
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
1263
|
+
}
|
|
1264
|
+
// Non-main invocation: close resources to prevent leaks
|
|
1265
|
+
await handle.close();
|
|
1266
|
+
store.close();
|
|
1267
|
+
return { exitCode: 0, stdout: `Magi Control API listening on ${handle.url}\n`, stderr: "" };
|
|
1268
|
+
}
|
|
1269
|
+
throw new MagiUsageError(`Unknown magi command: ${command}`);
|
|
1270
|
+
}
|
|
1271
|
+
function formatStreamJson(result) {
|
|
1272
|
+
const lines = [
|
|
1273
|
+
JSON.stringify({
|
|
1274
|
+
type: "session.started",
|
|
1275
|
+
sessionId: result.sessionId,
|
|
1276
|
+
jobId: result.jobId,
|
|
1277
|
+
provider: result.provider,
|
|
1278
|
+
model: result.model
|
|
1279
|
+
}),
|
|
1280
|
+
...(result.events ?? []).map((event) => JSON.stringify({ type: `agent.${event.type}`, event })),
|
|
1281
|
+
JSON.stringify({
|
|
1282
|
+
type: "session.completed",
|
|
1283
|
+
sessionId: result.sessionId,
|
|
1284
|
+
jobId: result.jobId,
|
|
1285
|
+
message: result.message,
|
|
1286
|
+
provider: result.provider,
|
|
1287
|
+
model: result.model
|
|
1288
|
+
})
|
|
1289
|
+
];
|
|
1290
|
+
return `${lines.join("\n")}\n`;
|
|
1291
|
+
}
|
|
1292
|
+
function helpText() {
|
|
1293
|
+
return [
|
|
1294
|
+
"Magi Next clean-room CLI",
|
|
1295
|
+
"",
|
|
1296
|
+
"Usage:",
|
|
1297
|
+
" magi --version",
|
|
1298
|
+
" magi doctor",
|
|
1299
|
+
" magi config",
|
|
1300
|
+
" magi --model <alias-or-model> -p <prompt>",
|
|
1301
|
+
" magi --output-format json -p <prompt>",
|
|
1302
|
+
" magi -c -p <prompt>",
|
|
1303
|
+
" magi -p <prompt>",
|
|
1304
|
+
" magi sessions",
|
|
1305
|
+
" magi resume <session-id>",
|
|
1306
|
+
" magi goal [objective] [--session-id <id>]",
|
|
1307
|
+
" magi context [session-id]",
|
|
1308
|
+
" magi compact [session-id]",
|
|
1309
|
+
" magi rules",
|
|
1310
|
+
" magi workspace diagnose [path]",
|
|
1311
|
+
" magi memory view [user|project|session] [--session-id <id>]",
|
|
1312
|
+
" magi memory search <query> [--session-id <id>]",
|
|
1313
|
+
" magi memory append <user|project|session> <text> [--session-id <id>]",
|
|
1314
|
+
" magi mcp list [server]",
|
|
1315
|
+
" magi mcp resources <server>",
|
|
1316
|
+
" magi mcp read-resource <server> <uri>",
|
|
1317
|
+
" magi plugins",
|
|
1318
|
+
" magi marketplace",
|
|
1319
|
+
" magi skills list",
|
|
1320
|
+
" magi skills show <name>",
|
|
1321
|
+
" magi agents list",
|
|
1322
|
+
" magi agents spawn <explorer|worker> <prompt>",
|
|
1323
|
+
" magi runner ping",
|
|
1324
|
+
" magi runner run <command>",
|
|
1325
|
+
" magi runner pty-smoke",
|
|
1326
|
+
" magi runner apply <file> <content> --approve",
|
|
1327
|
+
" magi serve",
|
|
1328
|
+
""
|
|
1329
|
+
].join("\n");
|
|
1330
|
+
}
|
|
1331
|
+
function knownCommands() {
|
|
1332
|
+
return new Set([
|
|
1333
|
+
"help", "--help", "-h", "--version", "-v", "-p", "--prompt", "--print",
|
|
1334
|
+
"doctor", "config", "sessions", "resume", "context", "compact", "rules",
|
|
1335
|
+
"goal",
|
|
1336
|
+
"workspace", "memory", "mcp", "plugins", "marketplace", "skills", "agents", "runner",
|
|
1337
|
+
"serve", "daemon", "pair", "peers", "ps", "logs", "kill", "init", "tutorial", "-r", "--resume"
|
|
1338
|
+
]);
|
|
1339
|
+
}
|
|
1340
|
+
function parseArgs(argv) {
|
|
1341
|
+
const rest = [];
|
|
1342
|
+
let command;
|
|
1343
|
+
let prompt;
|
|
1344
|
+
let modelAlias;
|
|
1345
|
+
let outputFormat = "text";
|
|
1346
|
+
let continueSession = false;
|
|
1347
|
+
let resumeSessionId;
|
|
1348
|
+
let sessionId;
|
|
1349
|
+
let sessionName;
|
|
1350
|
+
let persistSession = true;
|
|
1351
|
+
const writeFiles = [];
|
|
1352
|
+
let runnerTimeoutMs;
|
|
1353
|
+
let approve = false;
|
|
1354
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1355
|
+
const arg = argv[index];
|
|
1356
|
+
if (arg === "-p" || arg === "--print" || arg === "--prompt") {
|
|
1357
|
+
command = "-p";
|
|
1358
|
+
prompt = argv[++index];
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
if (arg === "--model") {
|
|
1362
|
+
modelAlias = argv[++index];
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
if (arg === "--output-format") {
|
|
1366
|
+
const value = argv[++index];
|
|
1367
|
+
if (value !== "text" && value !== "json" && value !== "stream-json") {
|
|
1368
|
+
throw new MagiUsageError("--output-format must be text, json, or stream-json");
|
|
1369
|
+
}
|
|
1370
|
+
outputFormat = value;
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (arg === "-c" || arg === "--continue") {
|
|
1374
|
+
continueSession = true;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
if (arg === "-r" || arg === "--resume") {
|
|
1378
|
+
command = arg;
|
|
1379
|
+
const next = argv[index + 1];
|
|
1380
|
+
if (next && !next.startsWith("-")) {
|
|
1381
|
+
resumeSessionId = argv[++index];
|
|
1382
|
+
}
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (arg === "--session-id") {
|
|
1386
|
+
sessionId = argv[++index];
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
if (arg === "-n" || arg === "--name") {
|
|
1390
|
+
sessionName = argv[++index];
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
if (arg === "--no-session-persistence") {
|
|
1394
|
+
persistSession = false;
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (arg === "--write-file") {
|
|
1398
|
+
writeFiles.push(argv[++index]);
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
if (arg === "--timeout-ms") {
|
|
1402
|
+
runnerTimeoutMs = readPositiveInteger(argv[++index], "--timeout-ms");
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
if (arg === "--no-color") {
|
|
1406
|
+
// Handled at the start of runCliUnsafe; ignore here (don't push to rest).
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
if (arg === "--approve") {
|
|
1410
|
+
approve = true;
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
if (!command) {
|
|
1414
|
+
command = arg;
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
rest.push(arg);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return {
|
|
1421
|
+
command,
|
|
1422
|
+
rest,
|
|
1423
|
+
prompt,
|
|
1424
|
+
modelAlias,
|
|
1425
|
+
outputFormat,
|
|
1426
|
+
continueSession,
|
|
1427
|
+
resumeSessionId,
|
|
1428
|
+
sessionId,
|
|
1429
|
+
sessionName,
|
|
1430
|
+
persistSession,
|
|
1431
|
+
writeFiles,
|
|
1432
|
+
runnerTimeoutMs,
|
|
1433
|
+
approve
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
function readPositiveInteger(value, label) {
|
|
1437
|
+
if (!value || !/^\d+$/.test(value)) {
|
|
1438
|
+
throw new MagiUsageError(`${label} must be a positive integer`);
|
|
1439
|
+
}
|
|
1440
|
+
const parsed = Number(value);
|
|
1441
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
1442
|
+
throw new MagiUsageError(`${label} must be a positive integer`);
|
|
1443
|
+
}
|
|
1444
|
+
return parsed;
|
|
1445
|
+
}
|
|
1446
|
+
function readAgentRole(value) {
|
|
1447
|
+
if (value === "explorer" || value === "worker") {
|
|
1448
|
+
return value;
|
|
1449
|
+
}
|
|
1450
|
+
throw new MagiUsageError("agent role must be explorer or worker");
|
|
1451
|
+
}
|
|
1452
|
+
function requireArg(value, label) {
|
|
1453
|
+
if (!value) {
|
|
1454
|
+
throw new MagiUsageError(`Missing ${label}`);
|
|
1455
|
+
}
|
|
1456
|
+
return value;
|
|
1457
|
+
}
|
|
1458
|
+
function resolveSessionForCommand(store, sessionId, cwd) {
|
|
1459
|
+
if (sessionId) {
|
|
1460
|
+
const session = store.getSession(sessionId);
|
|
1461
|
+
if (!session) {
|
|
1462
|
+
throw new MagiUsageError(`Session not found: ${sessionId}`);
|
|
1463
|
+
}
|
|
1464
|
+
return session;
|
|
1465
|
+
}
|
|
1466
|
+
const session = store.getMostRecentSession(cwd) ?? store.getMostRecentSession();
|
|
1467
|
+
if (!session) {
|
|
1468
|
+
throw new MagiUsageError("No sessions found");
|
|
1469
|
+
}
|
|
1470
|
+
return session;
|
|
1471
|
+
}
|
|
1472
|
+
function resolveGoalSessionForCommand(input) {
|
|
1473
|
+
if (input.sessionId) {
|
|
1474
|
+
const session = input.store.getSession(input.sessionId);
|
|
1475
|
+
if (!session) {
|
|
1476
|
+
throw new MagiUsageError(`Session not found: ${input.sessionId}`);
|
|
1477
|
+
}
|
|
1478
|
+
return session;
|
|
1479
|
+
}
|
|
1480
|
+
const session = input.store.getMostRecentSession(input.cwd) ?? input.store.getMostRecentSession();
|
|
1481
|
+
if (session)
|
|
1482
|
+
return session;
|
|
1483
|
+
if (input.create) {
|
|
1484
|
+
const id = input.store.createSession({
|
|
1485
|
+
title: input.title,
|
|
1486
|
+
cwd: input.cwd,
|
|
1487
|
+
metadata: { mode: "goal", command: "goal" }
|
|
1488
|
+
});
|
|
1489
|
+
const created = input.store.getSession(id);
|
|
1490
|
+
if (created)
|
|
1491
|
+
return created;
|
|
1492
|
+
}
|
|
1493
|
+
throw new MagiUsageError("No sessions found");
|
|
1494
|
+
}
|
|
1495
|
+
function resolveCompactionModelRunner(config, env, alias) {
|
|
1496
|
+
const registry = buildProviderRegistry({ config, env });
|
|
1497
|
+
const resolved = resolveModelAlias(config, alias);
|
|
1498
|
+
const adapter = registry.get(resolved.providerName);
|
|
1499
|
+
if (!adapter) {
|
|
1500
|
+
throw new MagiUsageError(`Provider ${resolved.providerName} is not configured for compaction model ${JSON.stringify(alias)}`);
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
adapter,
|
|
1504
|
+
model: resolved.model,
|
|
1505
|
+
providerName: resolved.providerName
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
function readMemoryScope(value) {
|
|
1509
|
+
if (value === "user" || value === "project" || value === "session") {
|
|
1510
|
+
return value;
|
|
1511
|
+
}
|
|
1512
|
+
if (value === undefined) {
|
|
1513
|
+
return "project";
|
|
1514
|
+
}
|
|
1515
|
+
throw new MagiUsageError("memory scope must be user, project, or session");
|
|
1516
|
+
}
|
|
1517
|
+
function waitForShutdown() {
|
|
1518
|
+
return new Promise((resolve) => {
|
|
1519
|
+
const done = () => resolve();
|
|
1520
|
+
process.once("SIGINT", done);
|
|
1521
|
+
process.once("SIGTERM", done);
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
if (isMain(import.meta.url, process.argv[1])) {
|
|
1525
|
+
const result = await runCli(process.argv.slice(2));
|
|
1526
|
+
if (result.stdout) {
|
|
1527
|
+
process.stdout.write(result.stdout);
|
|
1528
|
+
}
|
|
1529
|
+
if (result.stderr) {
|
|
1530
|
+
process.stderr.write(result.stderr);
|
|
1531
|
+
}
|
|
1532
|
+
process.exitCode = result.exitCode;
|
|
1533
|
+
}
|
|
1534
|
+
function isMain(moduleUrl, argvPath) {
|
|
1535
|
+
if (!argvPath) {
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(argvPath);
|
|
1539
|
+
}
|
|
1540
|
+
//# sourceMappingURL=cli.js.map
|