@agentwhy/cli 0.0.0-dev
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 +202 -0
- package/NOTICE +2 -0
- package/README.md +181 -0
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/dist/adapter/claude-code/contract/assumptions.js +208 -0
- package/dist/adapter/claude-code/contract/denials.js +15 -0
- package/dist/adapter/claude-code/contract/entry-points.js +15 -0
- package/dist/adapter/claude-code/contract/fields.js +73 -0
- package/dist/adapter/claude-code/contract/hooks.js +96 -0
- package/dist/adapter/claude-code/contract/identifiers.js +15 -0
- package/dist/adapter/claude-code/contract/layout.js +29 -0
- package/dist/adapter/claude-code/contract/line-types.js +26 -0
- package/dist/adapter/claude-code/contract/projects.js +23 -0
- package/dist/adapter/claude-code/contract/recognition.js +14 -0
- package/dist/adapter/claude-code/contract/session-title.js +20 -0
- package/dist/adapter/claude-code/contract/settings.js +27 -0
- package/dist/adapter/claude-code/contract/task-notifications.js +33 -0
- package/dist/adapter/claude-code/contract/tools.js +37 -0
- package/dist/adapter/claude-code/contract/version.js +51 -0
- package/dist/adapter/claude-code/discovery/claude-code-project-catalogue.js +129 -0
- package/dist/adapter/claude-code/discovery/claude-code-recognition.js +46 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-catalogue.js +100 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-discovery.js +61 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-titles.js +28 -0
- package/dist/adapter/claude-code/discovery/discovered-session.js +1 -0
- package/dist/adapter/claude-code/discovery/session-entries.js +41 -0
- package/dist/adapter/claude-code/discovery/transcript-tail.js +81 -0
- package/dist/adapter/claude-code/events/claude-code-session-source.js +197 -0
- package/dist/adapter/claude-code/events/delegation-index.js +15 -0
- package/dist/adapter/claude-code/events/transcript-scan.js +179 -0
- package/dist/adapter/claude-code/hooks/hook-output.js +42 -0
- package/dist/adapter/claude-code/hooks/pre-tool-use-input.js +23 -0
- package/dist/adapter/claude-code/hooks/stop-input.js +25 -0
- package/dist/adapter/claude-code/hooks/subagent-stop-input.js +26 -0
- package/dist/adapter/claude-code/policy/deny-rules.js +46 -0
- package/dist/adapter/claude-code/probe/claude-code-probe.js +86 -0
- package/dist/adapter/claude-code/probe/collectors/agent-tool-input-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/denial-kind-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/denied-call-collector.js +11 -0
- package/dist/adapter/claude-code/probe/collectors/identifier-collector.js +19 -0
- package/dist/adapter/claude-code/probe/collectors/line-collector.js +1 -0
- package/dist/adapter/claude-code/probe/collectors/line-type-collector.js +29 -0
- package/dist/adapter/claude-code/probe/collectors/sidechain-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/task-notification-collector.js +51 -0
- package/dist/adapter/claude-code/probe/collectors/tool-result-reference-collector.js +31 -0
- package/dist/adapter/claude-code/probe/collectors/tool-use-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/tool-version-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/top-level-key-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/unknown-line-type-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/working-directory-collector.js +28 -0
- package/dist/adapter/claude-code/probe/doctor-report.js +1 -0
- package/dist/adapter/claude-code/probe/meta-tally.js +42 -0
- package/dist/adapter/claude-code/probe/session-probe.js +1 -0
- package/dist/adapter/claude-code/probe/session-tally.js +41 -0
- package/dist/adapter/claude-code/probe/transcript-line.js +30 -0
- package/dist/adapter/claude-code/probe/transcript-tally.js +74 -0
- package/dist/adapter/claude-code/settings/deny-entries.js +65 -0
- package/dist/adapter/claude-code/settings/hook-entries.js +303 -0
- package/dist/adapter/codex/contract/actions.js +69 -0
- package/dist/adapter/codex/contract/assumptions.js +55 -0
- package/dist/adapter/codex/contract/capabilities.js +24 -0
- package/dist/adapter/codex/contract/delegations.js +30 -0
- package/dist/adapter/codex/contract/deliveries.js +25 -0
- package/dist/adapter/codex/contract/envelope.js +33 -0
- package/dist/adapter/codex/contract/hook-refusals.js +14 -0
- package/dist/adapter/codex/contract/hooks.js +46 -0
- package/dist/adapter/codex/contract/messages.js +47 -0
- package/dist/adapter/codex/contract/permissions.js +27 -0
- package/dist/adapter/codex/contract/reviews.js +13 -0
- package/dist/adapter/codex/contract/session.js +31 -0
- package/dist/adapter/codex/contract/turns.js +23 -0
- package/dist/adapter/codex/contract/version.js +15 -0
- package/dist/adapter/codex/discovery/codex-conversations.js +44 -0
- package/dist/adapter/codex/discovery/codex-project-catalogue.js +49 -0
- package/dist/adapter/codex/discovery/codex-recognition.js +20 -0
- package/dist/adapter/codex/discovery/codex-session-catalogue.js +28 -0
- package/dist/adapter/codex/discovery/codex-session-discovery.js +115 -0
- package/dist/adapter/codex/discovery/codex-session-index.js +21 -0
- package/dist/adapter/codex/discovery/codex-session-titles.js +135 -0
- package/dist/adapter/codex/discovery/session-header.js +86 -0
- package/dist/adapter/codex/discovery/session-owners.js +11 -0
- package/dist/adapter/codex/discovery/session-roots.js +49 -0
- package/dist/adapter/codex/events/action-items.js +138 -0
- package/dist/adapter/codex/events/capability-records.js +26 -0
- package/dist/adapter/codex/events/codex-session-source.js +121 -0
- package/dist/adapter/codex/events/hook-refusals.js +27 -0
- package/dist/adapter/codex/events/rollout-scan.js +614 -0
- package/dist/adapter/codex/events/turn-permissions.js +60 -0
- package/dist/adapter/codex/hooks/pre-tool-use-input.js +24 -0
- package/dist/adapter/codex/hooks/stop-refusals.js +57 -0
- package/dist/adapter/codex/probe/codex-doctor-report.js +1 -0
- package/dist/adapter/codex/probe/codex-probe.js +268 -0
- package/dist/adapter/codex/settings/codex-hooks.js +83 -0
- package/dist/cli/cli-command.js +1 -0
- package/dist/cli/command-router.js +42 -0
- package/dist/cli/commands/check-cli-command.js +128 -0
- package/dist/cli/commands/codex-stop-cli-command.js +27 -0
- package/dist/cli/commands/doctor-arguments.js +32 -0
- package/dist/cli/commands/doctor-cli-command.js +31 -0
- package/dist/cli/commands/doctor-usage.js +14 -0
- package/dist/cli/commands/init-cli-command.js +123 -0
- package/dist/cli/commands/menu-cli-command.js +58 -0
- package/dist/cli/commands/notify-cli-command.js +158 -0
- package/dist/cli/commands/refuse-cli-command.js +79 -0
- package/dist/cli/commands/report-arguments.js +62 -0
- package/dist/cli/commands/report-cli-command.js +36 -0
- package/dist/cli/commands/report-usage.js +41 -0
- package/dist/cli/commands/sessions-cli-command.js +184 -0
- package/dist/cli/commands/start-cli-command.js +83 -0
- package/dist/cli/commands/watch-cli-command.js +142 -0
- package/dist/cli/exit-codes.js +13 -0
- package/dist/cli/project-switches.js +46 -0
- package/dist/cli.js +80 -0
- package/dist/composition-root.js +411 -0
- package/dist/core/access/command-line.js +454 -0
- package/dist/core/access/listing.js +58 -0
- package/dist/core/access/path-shape.js +59 -0
- package/dist/core/access/path-tokens.js +37 -0
- package/dist/core/access/protected-access.js +310 -0
- package/dist/core/access/protected-values.js +102 -0
- package/dist/core/access/recorded-effect.js +67 -0
- package/dist/core/access/returns.js +173 -0
- package/dist/core/access/search-output.js +34 -0
- package/dist/core/access/search-reach.js +268 -0
- package/dist/core/access/uses.js +143 -0
- package/dist/core/access/words-before.js +52 -0
- package/dist/core/agent.js +1 -0
- package/dist/core/capability.js +11 -0
- package/dist/core/combined-project-catalogue.js +36 -0
- package/dist/core/combined-session-catalogue.js +25 -0
- package/dist/core/completeness.js +10 -0
- package/dist/core/context.js +1 -0
- package/dist/core/correlation/correlate.js +329 -0
- package/dist/core/delegation.js +1 -0
- package/dist/core/detector/prompt-signals.js +78 -0
- package/dist/core/entry-point.js +1 -0
- package/dist/core/event.js +1 -0
- package/dist/core/evidence.js +9 -0
- package/dist/core/late-message.js +32 -0
- package/dist/core/message.js +1 -0
- package/dist/core/nearest-project.js +28 -0
- package/dist/core/policy/default-policy.js +32 -0
- package/dist/core/policy/glob.js +56 -0
- package/dist/core/policy/parse-policy.js +70 -0
- package/dist/core/policy/policy.js +20 -0
- package/dist/core/policy/resolve-policy.js +16 -0
- package/dist/core/project-catalogue.js +1 -0
- package/dist/core/project-root.js +133 -0
- package/dist/core/provider-titles.js +10 -0
- package/dist/core/redaction/entropy.js +46 -0
- package/dist/core/redaction/redacted.js +4 -0
- package/dist/core/redaction/redactor.js +339 -0
- package/dist/core/redaction/secret-patterns.js +22 -0
- package/dist/core/redaction/value-shapes.js +81 -0
- package/dist/core/redaction/value-trace.js +110 -0
- package/dist/core/review.js +1 -0
- package/dist/core/session-catalogue.js +19 -0
- package/dist/core/session-filter.js +42 -0
- package/dist/core/session-format.js +27 -0
- package/dist/core/session-model.js +1 -0
- package/dist/core/session-reader.js +22 -0
- package/dist/core/session-records.js +1 -0
- package/dist/core/session-source.js +1 -0
- package/dist/core/session-titles.js +21 -0
- package/dist/core/turn.js +1 -0
- package/dist/doctor/codex-doctor.js +14 -0
- package/dist/doctor/doctor-use-case.js +1 -0
- package/dist/doctor/format-selecting-doctor.js +28 -0
- package/dist/doctor/render/attention/attention-rule.js +1 -0
- package/dist/doctor/render/attention/doctor-attention-rules.js +28 -0
- package/dist/doctor/render/attention/rules/damaged-meta-files-rule.js +8 -0
- package/dist/doctor/render/attention/rules/damaged-transcripts-rule.js +9 -0
- package/dist/doctor/render/attention/rules/incomplete-subagent-pairs-rule.js +6 -0
- package/dist/doctor/render/attention/rules/missing-main-transcript-rule.js +5 -0
- package/dist/doctor/render/attention/rules/missing-spilled-results-rule.js +6 -0
- package/dist/doctor/render/attention/rules/multiple-working-directories-rule.js +15 -0
- package/dist/doctor/render/attention/rules/unknown-denial-kinds-rule.js +5 -0
- package/dist/doctor/render/attention/rules/unknown-line-types-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unrecognised-entries-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unusable-directories-rule.js +12 -0
- package/dist/doctor/render/attention/transcript-scopes.js +6 -0
- package/dist/doctor/render/codex-text-doctor-renderer.js +97 -0
- package/dist/doctor/render/doctor-renderers.js +1 -0
- package/dist/doctor/render/json-doctor-renderer.js +7 -0
- package/dist/doctor/render/output-format.js +1 -0
- package/dist/doctor/render/text-doctor-renderer.js +124 -0
- package/dist/doctor/session-doctor.js +16 -0
- package/dist/infrastructure/clack-asker.js +24 -0
- package/dist/infrastructure/clack-chooser.js +40 -0
- package/dist/infrastructure/clack-multi-chooser.js +31 -0
- package/dist/infrastructure/file-alert-store.js +120 -0
- package/dist/infrastructure/file-checked-store.js +55 -0
- package/dist/infrastructure/file-mark-store.js +67 -0
- package/dist/infrastructure/file-onboarding-store.js +93 -0
- package/dist/infrastructure/folder-window.js +26 -0
- package/dist/infrastructure/node-agentwhy-invocation.js +99 -0
- package/dist/infrastructure/node-browser.js +40 -0
- package/dist/infrastructure/node-file-system.js +111 -0
- package/dist/infrastructure/node-local-server.js +115 -0
- package/dist/infrastructure/node-text-input.js +26 -0
- package/dist/infrastructure/os-notifier.js +21 -0
- package/dist/infrastructure/osascript-folder-chooser.js +53 -0
- package/dist/infrastructure/powershell-folder-chooser.js +78 -0
- package/dist/infrastructure/real-directory.js +18 -0
- package/dist/infrastructure/stream-printer.js +11 -0
- package/dist/infrastructure/terminal-banner.js +19 -0
- package/dist/ports/agentwhy-invocation.js +1 -0
- package/dist/ports/alert-store.js +1 -0
- package/dist/ports/asker.js +1 -0
- package/dist/ports/browser.js +1 -0
- package/dist/ports/checked-store.js +1 -0
- package/dist/ports/chooser.js +1 -0
- package/dist/ports/directory-reader.js +1 -0
- package/dist/ports/file-access-error.js +11 -0
- package/dist/ports/file-reader.js +1 -0
- package/dist/ports/file-tail-reader.js +1 -0
- package/dist/ports/file-writer.js +1 -0
- package/dist/ports/folder-chooser.js +1 -0
- package/dist/ports/local-server.js +1 -0
- package/dist/ports/mark-store.js +10 -0
- package/dist/ports/multi-chooser.js +1 -0
- package/dist/ports/notifier.js +1 -0
- package/dist/ports/onboarding-store.js +1 -0
- package/dist/ports/printer.js +1 -0
- package/dist/ports/text-input.js +1 -0
- package/dist/refuse/command-reach.js +135 -0
- package/dist/refuse/command-refusal.js +88 -0
- package/dist/refuse/render/codex-stop-words.js +21 -0
- package/dist/refuse/render/refusal-words.js +34 -0
- package/dist/report/build-report.js +1037 -0
- package/dist/report/check/actions-digest.js +73 -0
- package/dist/report/check/check-lines.js +83 -0
- package/dist/report/check/mark-request.js +90 -0
- package/dist/report/check/marks.js +84 -0
- package/dist/report/check/render/text-digest-renderer.js +332 -0
- package/dist/report/check/session-actions.js +90 -0
- package/dist/report/check/session-check.js +91 -0
- package/dist/report/choose-policy.js +61 -0
- package/dist/report/flow-reads.js +18 -0
- package/dist/report/private-files/tell-lists.js +84 -0
- package/dist/report/project-rules.js +33 -0
- package/dist/report/render/home-relative.js +18 -0
- package/dist/report/render/html-head.js +26 -0
- package/dist/report/render/html-report-components.js +48 -0
- package/dist/report/render/html-report-styles.js +500 -0
- package/dist/report/render/html-sidebar.js +42 -0
- package/dist/report/render/path-tail.js +14 -0
- package/dist/report/render/report-copy.js +1177 -0
- package/dist/report/render/report-page/advanced-view.js +167 -0
- package/dist/report/render/report-page/file-story.js +114 -0
- package/dist/report/render/report-page/files-view.js +381 -0
- package/dist/report/render/report-page/files.js +77 -0
- package/dist/report/render/report-page/fix-wizard-script.js +269 -0
- package/dist/report/render/report-page/fix-wizard.js +310 -0
- package/dist/report/render/report-page/helpers-view.js +383 -0
- package/dist/report/render/report-page/helpers.js +51 -0
- package/dist/report/render/report-page/item-names.js +63 -0
- package/dist/report/render/report-page/protect-patterns.js +13 -0
- package/dist/report/render/report-page/providers.js +208 -0
- package/dist/report/render/report-page/record-view.js +367 -0
- package/dist/report/render/report-page/report-page-renderer.js +129 -0
- package/dist/report/render/report-page/report-views.js +31 -0
- package/dist/report/render/report-page/story-window.js +356 -0
- package/dist/report/render/report-page/times.js +25 -0
- package/dist/report/render/report-page/to-do-view.js +299 -0
- package/dist/report/render/report-page/to-do.js +152 -0
- package/dist/report/render/report-page.js +1 -0
- package/dist/report/render/session-view.js +250 -0
- package/dist/report/render/text-report-renderer.js +981 -0
- package/dist/report/render/ui/add-file-popup.js +122 -0
- package/dist/report/render/ui/app-sidebar.js +78 -0
- package/dist/report/render/ui/ask-panel.js +80 -0
- package/dist/report/render/ui/avatar.js +19 -0
- package/dist/report/render/ui/backdrop.js +29 -0
- package/dist/report/render/ui/brand-mark.js +4 -0
- package/dist/report/render/ui/button.js +67 -0
- package/dist/report/render/ui/callout.js +20 -0
- package/dist/report/render/ui/chat-example.js +17 -0
- package/dist/report/render/ui/checklist.js +50 -0
- package/dist/report/render/ui/confirm-dialog.js +34 -0
- package/dist/report/render/ui/data-table.js +49 -0
- package/dist/report/render/ui/drawer.js +25 -0
- package/dist/report/render/ui/file-chip.js +37 -0
- package/dist/report/render/ui/fold-line.js +22 -0
- package/dist/report/render/ui/guide-card.js +25 -0
- package/dist/report/render/ui/hero.js +37 -0
- package/dist/report/render/ui/labelled-select.js +10 -0
- package/dist/report/render/ui/live-script.js +222 -0
- package/dist/report/render/ui/local-date.js +58 -0
- package/dist/report/render/ui/mode-icon.js +6 -0
- package/dist/report/render/ui/page-shell.js +101 -0
- package/dist/report/render/ui/pill-tabs.js +47 -0
- package/dist/report/render/ui/popup.js +77 -0
- package/dist/report/render/ui/progress.js +35 -0
- package/dist/report/render/ui/project-list.js +284 -0
- package/dist/report/render/ui/scope-choice.js +19 -0
- package/dist/report/render/ui/stats.js +21 -0
- package/dist/report/render/ui/status-icon.js +30 -0
- package/dist/report/render/ui/status-look.js +21 -0
- package/dist/report/render/ui/switch.js +11 -0
- package/dist/report/render/ui/tag.js +19 -0
- package/dist/report/render/ui/task-list.js +56 -0
- package/dist/report/render/ui/tokens.js +46 -0
- package/dist/report/render/ui/update-notice.js +146 -0
- package/dist/report/render/ui/words/app-words.js +163 -0
- package/dist/report/render/ui/words/conversations-words.js +457 -0
- package/dist/report/render/ui/words/month-words.js +59 -0
- package/dist/report/render/ui/words/onboarding-words.js +446 -0
- package/dist/report/render/ui/words/projects-words.js +217 -0
- package/dist/report/render/ui/words/report-words.js +2099 -0
- package/dist/report/render/ui/words/settings-words.js +611 -0
- package/dist/report/render/ui/words/to-fix-words.js +287 -0
- package/dist/report/report-model.js +2 -0
- package/dist/report/report-use-case.js +1 -0
- package/dist/report/rule-names.js +20 -0
- package/dist/report/session-report.js +163 -0
- package/dist/report/start/app-nav.js +37 -0
- package/dist/report/start/conversations/calendar-view.js +104 -0
- package/dist/report/start/conversations/conversation-columns.js +187 -0
- package/dist/report/start/conversations/conversations-renderer.js +54 -0
- package/dist/report/start/conversations/empty-week.js +120 -0
- package/dist/report/start/conversations/files-window.js +166 -0
- package/dist/report/start/conversations/period-section.js +280 -0
- package/dist/report/start/conversations/periods-script.js +440 -0
- package/dist/report/start/conversations/periods.js +110 -0
- package/dist/report/start/conversations/week-view.js +30 -0
- package/dist/report/start/conversations/weeks.js +17 -0
- package/dist/report/start/month/month-renderer.js +50 -0
- package/dist/report/start/month/month-view.js +135 -0
- package/dist/report/start/month/months.js +17 -0
- package/dist/report/start/onboarding/done.js +171 -0
- package/dist/report/start/onboarding/finish-onboarding.js +93 -0
- package/dist/report/start/onboarding/intro.js +100 -0
- package/dist/report/start/onboarding/onboarding-changes.js +40 -0
- package/dist/report/start/onboarding/onboarding-renderer.js +195 -0
- package/dist/report/start/onboarding/onboarding-script.js +400 -0
- package/dist/report/start/onboarding/onboarding-view.js +98 -0
- package/dist/report/start/onboarding/project-step.js +210 -0
- package/dist/report/start/onboarding/steps.js +239 -0
- package/dist/report/start/onboarding/welcome.js +66 -0
- package/dist/report/start/page-notice.js +10 -0
- package/dist/report/start/projects/index-projects.js +43 -0
- package/dist/report/start/projects/moved-page.js +82 -0
- package/dist/report/start/projects/project-switch.js +138 -0
- package/dist/report/start/projects/projects-window.js +67 -0
- package/dist/report/start/projects/temporary-space.js +18 -0
- package/dist/report/start/render/air-datepicker-vendor.js +27 -0
- package/dist/report/start/render/session-status.js +41 -0
- package/dist/report/start/render/start-words.js +84 -0
- package/dist/report/start/repository.js +28 -0
- package/dist/report/start/serve/index-handler.js +372 -0
- package/dist/report/start/serve/onboarding-request.js +50 -0
- package/dist/report/start/serve/settings-request.js +1 -0
- package/dist/report/start/serve/settings-setup.js +193 -0
- package/dist/report/start/session-index.js +1 -0
- package/dist/report/start/session-start.js +974 -0
- package/dist/report/start/settings/alerts-tab.js +104 -0
- package/dist/report/start/settings/files-tab.js +169 -0
- package/dist/report/start/settings/general-tab.js +71 -0
- package/dist/report/start/settings/settings-renderer.js +254 -0
- package/dist/report/start/settings/settings-script.js +161 -0
- package/dist/report/start/settings/settings-view.js +240 -0
- package/dist/report/start/settings/settings-windows.js +334 -0
- package/dist/report/start/settings-files.js +43 -0
- package/dist/report/start/to-fix/done-list.js +45 -0
- package/dist/report/start/to-fix/file-window.js +110 -0
- package/dist/report/start/to-fix/to-fix-list.js +70 -0
- package/dist/report/start/to-fix/to-fix-renderer.js +237 -0
- package/dist/report/start/to-fix/to-fix-script.js +94 -0
- package/dist/report/start/to-fix/to-fix-view.js +124 -0
- package/dist/report/watch/agent-alert.js +72 -0
- package/dist/report/watch/notice-choices.js +35 -0
- package/dist/report/watch/notice-settings.js +65 -0
- package/dist/report/watch/preferences.js +100 -0
- package/dist/report/watch/render/instruction-words.js +54 -0
- package/dist/report/watch/render/notice-words.js +182 -0
- package/dist/report/watch/subagent-watch.js +345 -0
- package/dist/report/watch/watch-notice.js +1 -0
- package/dist/setup/behind.js +18 -0
- package/dist/setup/codex-mirror.js +171 -0
- package/dist/setup/not-a-project.js +19 -0
- package/dist/setup/project-setup.js +644 -0
- package/dist/setup/protected-patterns.js +30 -0
- package/dist/shared/colour.js +23 -0
- package/dist/shared/compare.js +4 -0
- package/dist/shared/content-version.js +13 -0
- package/dist/shared/counter.js +11 -0
- package/dist/shared/file-stamp.js +4 -0
- package/dist/shared/json.js +13 -0
- package/dist/shared/label.js +13 -0
- package/dist/shared/package-name.js +6 -0
- package/dist/shared/plain-invocation.js +44 -0
- package/dist/shared/plain-version.js +21 -0
- package/dist/shared/printable.js +25 -0
- package/dist/shared/renderer.js +1 -0
- package/dist/shared/sentence.js +20 -0
- package/dist/shared/terminal-logo.js +41 -0
- package/dist/shared/text-table.js +15 -0
- package/dist/shared/wrap.js +29 -0
- package/package.json +70 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { widthFor } from './report-usage.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses the arguments after `report`. Never throws: bad input is a usage error. `columns` is what the terminal
|
|
5
|
+
* reported, and is used only when `--width` says nothing.
|
|
6
|
+
*/
|
|
7
|
+
export function parseReportArguments(args, columns) {
|
|
8
|
+
let values;
|
|
9
|
+
try {
|
|
10
|
+
values = parseValues(args);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return usageError(error instanceof Error ? error.message : String(error));
|
|
14
|
+
}
|
|
15
|
+
if (values.help)
|
|
16
|
+
return { kind: 'help' };
|
|
17
|
+
const width = values.width === undefined ? widthFor(columns) : Number(values.width);
|
|
18
|
+
if (!Number.isInteger(width) || width < 40)
|
|
19
|
+
return usageError('--width must be a whole number of at least 40');
|
|
20
|
+
// `--input` is optional: without it the command reads the newest session of the current project, which is
|
|
21
|
+
// what someone standing in their repository means by "report".
|
|
22
|
+
return {
|
|
23
|
+
kind: 'options',
|
|
24
|
+
options: {
|
|
25
|
+
...(values.input === undefined || values.input === '' ? {} : { input: values.input }),
|
|
26
|
+
...(values.policy === undefined ? {} : { policyPath: values.policy }),
|
|
27
|
+
...(values.settings === undefined ? {} : { settingsPath: values.settings }),
|
|
28
|
+
...(values.html === undefined ? {} : { htmlPath: values.html }),
|
|
29
|
+
ascii: values.ascii === true,
|
|
30
|
+
full: values.full === true,
|
|
31
|
+
colour: values['no-color'] !== true,
|
|
32
|
+
open: values.open === true,
|
|
33
|
+
...(values.quiet === true ? { quiet: true } : {}),
|
|
34
|
+
share: values.share === true,
|
|
35
|
+
width,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseValues(args) {
|
|
40
|
+
return parseArgs({
|
|
41
|
+
args: [...args],
|
|
42
|
+
options: {
|
|
43
|
+
input: { type: 'string' },
|
|
44
|
+
policy: { type: 'string' },
|
|
45
|
+
settings: { type: 'string' },
|
|
46
|
+
html: { type: 'string' },
|
|
47
|
+
ascii: { type: 'boolean', default: false },
|
|
48
|
+
full: { type: 'boolean', default: false },
|
|
49
|
+
'no-color': { type: 'boolean', default: false },
|
|
50
|
+
open: { type: 'boolean', default: false },
|
|
51
|
+
quiet: { type: 'boolean', default: false },
|
|
52
|
+
share: { type: 'boolean', default: false },
|
|
53
|
+
width: { type: 'string' },
|
|
54
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
55
|
+
},
|
|
56
|
+
strict: true,
|
|
57
|
+
allowPositionals: false,
|
|
58
|
+
}).values;
|
|
59
|
+
}
|
|
60
|
+
function usageError(message) {
|
|
61
|
+
return { kind: 'usage-error', message };
|
|
62
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { EXIT_CODE } from '../exit-codes.js';
|
|
2
|
+
import { parseReportArguments } from './report-arguments.js';
|
|
3
|
+
import { REPORT_USAGE } from './report-usage.js';
|
|
4
|
+
// A Record over the outcome union: a new outcome does not compile until it has an exit code. An incomplete
|
|
5
|
+
// report still exits 0 - it said what was missing, which is the report doing its job.
|
|
6
|
+
export const EXIT_CODE_BY_OUTCOME = {
|
|
7
|
+
complete: EXIT_CODE.ok,
|
|
8
|
+
incomplete: EXIT_CODE.ok,
|
|
9
|
+
'policy-refused': EXIT_CODE.usage,
|
|
10
|
+
'session-unreadable': EXIT_CODE.mainTranscriptMissing,
|
|
11
|
+
'no-session': EXIT_CODE.mainTranscriptMissing,
|
|
12
|
+
};
|
|
13
|
+
export class ReportCliCommand {
|
|
14
|
+
name = 'report';
|
|
15
|
+
usage = REPORT_USAGE;
|
|
16
|
+
#report;
|
|
17
|
+
/** The terminal's width, read once in the shell. `undefined` where there is no terminal to read. */
|
|
18
|
+
#columns;
|
|
19
|
+
constructor(report, columns) {
|
|
20
|
+
this.#report = report;
|
|
21
|
+
this.#columns = columns;
|
|
22
|
+
}
|
|
23
|
+
async execute(args) {
|
|
24
|
+
const parsed = parseReportArguments(args, this.#columns);
|
|
25
|
+
switch (parsed.kind) {
|
|
26
|
+
case 'help':
|
|
27
|
+
return { kind: 'help', usage: this.usage };
|
|
28
|
+
case 'usage-error':
|
|
29
|
+
return { kind: 'usage-error', message: parsed.message, usage: this.usage };
|
|
30
|
+
case 'options': {
|
|
31
|
+
const result = await this.#report.run(parsed.options);
|
|
32
|
+
return { kind: 'completed', output: result.output, exitCode: EXIT_CODE_BY_OUTCOME[result.outcome] };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const DEFAULT_WIDTH = 100;
|
|
2
|
+
/** Narrower than this the layout stops being a layout; wider, a line is too long to run an eye back along. */
|
|
3
|
+
const WIDTH_BOUNDS = { least: 60, most: 120 };
|
|
4
|
+
/**
|
|
5
|
+
* The width a report is drawn at when `--width` is not given: the terminal's, kept inside bounds. With no terminal
|
|
6
|
+
* - piped, redirected, in CI - it is `DEFAULT_WIDTH`, so a script's output does not change with the window.
|
|
7
|
+
*/
|
|
8
|
+
export function widthFor(columns) {
|
|
9
|
+
if (columns === undefined || !Number.isFinite(columns))
|
|
10
|
+
return DEFAULT_WIDTH;
|
|
11
|
+
return Math.min(Math.max(Math.trunc(columns), WIDTH_BOUNDS.least), WIDTH_BOUNDS.most);
|
|
12
|
+
}
|
|
13
|
+
export const REPORT_USAGE = `Usage: agentwhy report --input <session-dir | session.jsonl> [options]
|
|
14
|
+
|
|
15
|
+
--input <path> the session to read: a directory or its <session-id>.jsonl
|
|
16
|
+
--policy <file> the policy that says which paths are protected
|
|
17
|
+
--settings <file> a settings file whose deny rules stand in for a policy
|
|
18
|
+
--html <file> also write the report as one self-contained HTML file
|
|
19
|
+
--open open the HTML report in whatever this machine opens HTML with.
|
|
20
|
+
Without --html it is written to a temporary file first
|
|
21
|
+
--quiet say where the report is and nothing else. The summary names paths and
|
|
22
|
+
what became of each, which is not what an agent should be handed when it
|
|
23
|
+
opens a report because you said yes
|
|
24
|
+
--share a report fit to hand to someone else: paths are shown relative to the
|
|
25
|
+
project root and nothing above it appears. This reduces exposure; it does
|
|
26
|
+
not anonymise - a path inside the repository still names what the
|
|
27
|
+
repository names
|
|
28
|
+
--full every section in detail: for each file, who reached it, what they had
|
|
29
|
+
been asked to do, what they ran and which record says so. Without it
|
|
30
|
+
the terminal gets a summary, and its last line says how to see the rest
|
|
31
|
+
--ascii draw the tree with + - | instead of box-drawing characters
|
|
32
|
+
--no-color no colour, even at a terminal. It is also off under NO_COLOR, with
|
|
33
|
+
--ascii, and whenever the output is not a terminal
|
|
34
|
+
--width <columns> width of the output. At a terminal it follows the window, between
|
|
35
|
+
${WIDTH_BOUNDS.least} and ${WIDTH_BOUNDS.most} columns; piped or redirected it is ${DEFAULT_WIDTH}
|
|
36
|
+
-h, --help this text
|
|
37
|
+
|
|
38
|
+
Without --policy or --settings the built-in default is used, and the report says so.
|
|
39
|
+
The summary is printed either way; --html adds a file, it does not replace the output.
|
|
40
|
+
Values never appear in the output: they are replaced on the way into the report, not on the way out.
|
|
41
|
+
`;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { parseSince, splitBySince } from '../../core/session-filter.js';
|
|
3
|
+
import { sessionKey } from '../../core/session-catalogue.js';
|
|
4
|
+
import { PROVIDER_NAMES } from '../../core/session-format.js';
|
|
5
|
+
import { recogniseAll } from '../../core/session-titles.js';
|
|
6
|
+
import { table } from '../../shared/text-table.js';
|
|
7
|
+
import { EXIT_CODE } from '../exit-codes.js';
|
|
8
|
+
import { EXIT_CODE_BY_OUTCOME } from './report-cli-command.js';
|
|
9
|
+
export const SESSIONS_USAGE = `Usage: agentwhy sessions [--since <span | date>] [--no-interactive]
|
|
10
|
+
|
|
11
|
+
Lists the sessions Claude Code has kept for the current directory's project, newest first,
|
|
12
|
+
so that reporting on one does not require typing the path it is stored under.
|
|
13
|
+
|
|
14
|
+
At a terminal, with no --since, it first asks how far back to look - there can be a lot of
|
|
15
|
+
sessions - then the list can be chosen from: typing narrows it by title, date or id, the arrow
|
|
16
|
+
keys move, Enter opens a report on that session, Escape leaves. A title is the one Claude Code
|
|
17
|
+
gave the session, passed through the redactor. Piped or redirected it prints the table and
|
|
18
|
+
exits, unchanged, so that a script sees the same output either way.
|
|
19
|
+
|
|
20
|
+
--since <span|date> only sessions last active since then: 7d, 12h, 2w, or 2026-09-01 (UTC).
|
|
21
|
+
Off a terminal, without it, every session is listed
|
|
22
|
+
--no-interactive asks for the table at a terminal too, every session, unfiltered
|
|
23
|
+
-h, --help this text
|
|
24
|
+
`;
|
|
25
|
+
const QUICK_RANGES = [
|
|
26
|
+
{ label: 'Today', span: '1d' },
|
|
27
|
+
{ label: 'Last 7 days', span: '7d' },
|
|
28
|
+
{ label: 'Last 30 days', span: '30d' },
|
|
29
|
+
];
|
|
30
|
+
/** `agentwhy sessions` - the answer to "which session did I mean?", and a way to act on the answer. */
|
|
31
|
+
export class SessionsCliCommand {
|
|
32
|
+
name = 'sessions';
|
|
33
|
+
usage = SESSIONS_USAGE;
|
|
34
|
+
#dependencies;
|
|
35
|
+
constructor(dependencies) {
|
|
36
|
+
this.#dependencies = dependencies;
|
|
37
|
+
}
|
|
38
|
+
async execute(args) {
|
|
39
|
+
let values;
|
|
40
|
+
try {
|
|
41
|
+
({ values } = parseArgs({
|
|
42
|
+
args: [...args],
|
|
43
|
+
options: {
|
|
44
|
+
since: { type: 'string' },
|
|
45
|
+
'no-interactive': { type: 'boolean', default: false },
|
|
46
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
47
|
+
},
|
|
48
|
+
strict: true,
|
|
49
|
+
allowPositionals: false,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return { kind: 'usage-error', message: error instanceof Error ? error.message : String(error), usage: this.usage };
|
|
54
|
+
}
|
|
55
|
+
if (values.help)
|
|
56
|
+
return { kind: 'help', usage: this.usage };
|
|
57
|
+
let since;
|
|
58
|
+
if (values.since !== undefined) {
|
|
59
|
+
const parsed = parseSince(values.since, this.#dependencies.now);
|
|
60
|
+
if ('error' in parsed)
|
|
61
|
+
return { kind: 'usage-error', message: parsed.error, usage: this.usage };
|
|
62
|
+
since = parsed;
|
|
63
|
+
}
|
|
64
|
+
const { catalogue, workingDirectory } = this.#dependencies;
|
|
65
|
+
const listing = await catalogue.list(workingDirectory);
|
|
66
|
+
// "Nothing here" and "looked in the wrong place" are different answers, and the second is far more likely
|
|
67
|
+
// to be the truth when a path encoding is involved.
|
|
68
|
+
if (!listing.found) {
|
|
69
|
+
return {
|
|
70
|
+
kind: 'completed',
|
|
71
|
+
output: `No sessions are stored for this directory.\nLooked in ${listing.searched.map((place) => place.directory).join(' and ')}\n` +
|
|
72
|
+
`agentwhy reads sessions Claude Code and Codex already keep, so either this directory has not been used with either yet, or it is not the one being worked in.\n` +
|
|
73
|
+
`Use Claude Code or Codex here, then run agentwhy sessions again - or run agentwhy init now, to be protected in Claude Code before there is history to check.\n`,
|
|
74
|
+
exitCode: EXIT_CODE.ok,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (listing.sessions.length === 0) {
|
|
78
|
+
return { kind: 'completed', output: `That project has no sessions yet.\n${listing.directory}\n`, exitCode: EXIT_CODE.ok };
|
|
79
|
+
}
|
|
80
|
+
const interactive = this.#dependencies.interactive && !values['no-interactive'];
|
|
81
|
+
// At a terminal, with no --since on the command line: asked before the list, because the list itself can be
|
|
82
|
+
// the very thing too long to read (the reason this exists at all).
|
|
83
|
+
if (interactive && since === undefined) {
|
|
84
|
+
const picked = await this.#pickRange(listing.sessions.length);
|
|
85
|
+
if (picked.kind === 'left')
|
|
86
|
+
return { kind: 'completed', output: '', exitCode: EXIT_CODE.ok };
|
|
87
|
+
if (picked.kind === 'since')
|
|
88
|
+
since = picked.since;
|
|
89
|
+
// 'all': since stays undefined, and every session is kept below.
|
|
90
|
+
}
|
|
91
|
+
const sessions = since === undefined ? listing.sessions : splitBySince(listing.sessions, since.since).inRange;
|
|
92
|
+
if (since !== undefined && sessions.length === 0) {
|
|
93
|
+
return { kind: 'completed', output: `No sessions of this project were active since ${since.asked}.\n`, exitCode: EXIT_CODE.ok };
|
|
94
|
+
}
|
|
95
|
+
return interactive ? this.#choose(sessions, since) : { kind: 'completed', output: render(sessions, since), exitCode: EXIT_CODE.ok };
|
|
96
|
+
}
|
|
97
|
+
/** R: how far back to look, asked before the list itself, because the list can be the thing too long to read. */
|
|
98
|
+
async #pickRange(total) {
|
|
99
|
+
const { chooser, asker, now } = this.#dependencies;
|
|
100
|
+
const rows = [
|
|
101
|
+
...QUICK_RANGES.map(({ label, span }) => ({ label, detail: `--since ${span}` })),
|
|
102
|
+
{ label: 'All sessions', detail: `${count(total)}, unfiltered` },
|
|
103
|
+
{ label: 'Type a date or span', detail: 'e.g. 2026-09-01, or 12h' },
|
|
104
|
+
];
|
|
105
|
+
const at = await chooser.choose('How far back? There can be a lot of sessions.', rows);
|
|
106
|
+
if (at === undefined)
|
|
107
|
+
return { kind: 'left' };
|
|
108
|
+
if (at === QUICK_RANGES.length)
|
|
109
|
+
return { kind: 'all' };
|
|
110
|
+
if (at < QUICK_RANGES.length) {
|
|
111
|
+
const range = QUICK_RANGES[at];
|
|
112
|
+
if (range === undefined)
|
|
113
|
+
return { kind: 'left' };
|
|
114
|
+
const parsed = parseSince(range.span, now);
|
|
115
|
+
return 'error' in parsed ? { kind: 'left' } : { kind: 'since', since: parsed };
|
|
116
|
+
}
|
|
117
|
+
for (let question = 'Since when? 7d, 12h, 2w, or a date such as 2026-09-01';;) {
|
|
118
|
+
const typed = await asker.ask(question, '7d');
|
|
119
|
+
if (typed === undefined || typed === '')
|
|
120
|
+
return { kind: 'left' };
|
|
121
|
+
const parsed = parseSince(typed, now);
|
|
122
|
+
if (!('error' in parsed))
|
|
123
|
+
return { kind: 'since', since: parsed };
|
|
124
|
+
question = `${parsed.error}. Try again, or leave it blank to go back`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** The list, chosen from. Leaving without choosing is an ordinary way to finish, not a failure. */
|
|
128
|
+
async #choose(sessions, since) {
|
|
129
|
+
const { chooser, report, titles, width } = this.#dependencies;
|
|
130
|
+
const titled = (await recogniseAll(titles, sessions)).map((recognised) => recognised.title);
|
|
131
|
+
const heading = `${count(sessions.length)}${rangeWords(since)}, newest first. Type to narrow the list.`;
|
|
132
|
+
const at = await chooser.choose(heading, sessions.map((session, position) => toChoice(session, titled[position])));
|
|
133
|
+
const chosen = at === undefined ? undefined : sessions[at];
|
|
134
|
+
if (chosen === undefined)
|
|
135
|
+
return { kind: 'completed', output: '', exitCode: EXIT_CODE.ok };
|
|
136
|
+
const result = await report.run({
|
|
137
|
+
input: chosen.path,
|
|
138
|
+
ascii: false,
|
|
139
|
+
full: false,
|
|
140
|
+
// Printed at the terminal the list was chosen at; the shell still decides whether it may be coloured.
|
|
141
|
+
colour: true,
|
|
142
|
+
open: true,
|
|
143
|
+
share: false,
|
|
144
|
+
width,
|
|
145
|
+
});
|
|
146
|
+
return { kind: 'completed', output: result.output, exitCode: EXIT_CODE_BY_OUTCOME[result.outcome] };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function count(sessions) {
|
|
150
|
+
return `${sessions} ${sessions === 1 ? 'session' : 'sessions'}`;
|
|
151
|
+
}
|
|
152
|
+
function rangeWords(since) {
|
|
153
|
+
return since === undefined ? '' : ` active since ${since.asked}`;
|
|
154
|
+
}
|
|
155
|
+
function when(session) {
|
|
156
|
+
return new Date(session.modifiedAt).toISOString().slice(0, 16).replace('T', ' ');
|
|
157
|
+
}
|
|
158
|
+
function delegations(session) {
|
|
159
|
+
return session.delegations === 0 ? 'no delegations' : `${session.delegations} delegations`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The title when there is one, and the key either way: two sessions can carry the same title. Every row names the AI
|
|
163
|
+
* that wrote it (`2026-09-27-what-codex-wrote.md` X28).
|
|
164
|
+
*/
|
|
165
|
+
function toChoice(session, title) {
|
|
166
|
+
const ai = PROVIDER_NAMES[session.provider];
|
|
167
|
+
if (title === undefined)
|
|
168
|
+
return { label: `untitled · ${sessionKey(session)}`, detail: `${ai} · ${when(session)} · ${delegations(session)}` };
|
|
169
|
+
return { label: title, detail: `${ai} · ${when(session)} · ${delegations(session)} · ${session.id.slice(0, 8)}` };
|
|
170
|
+
}
|
|
171
|
+
function render(sessions, since) {
|
|
172
|
+
const rows = sessions.map((session, index) => [
|
|
173
|
+
`${index === 0 ? '*' : ' '} ${sessionKey(session)}`,
|
|
174
|
+
`${when(session)} ${PROVIDER_NAMES[session.provider]} ${delegations(session)}`,
|
|
175
|
+
]);
|
|
176
|
+
return [
|
|
177
|
+
`${count(sessions.length)}${rangeWords(since)}, newest first. \`agentwhy report\` reads the one marked *.`,
|
|
178
|
+
'',
|
|
179
|
+
...table(rows, 2),
|
|
180
|
+
'',
|
|
181
|
+
'To read another: agentwhy report --input <the id above>',
|
|
182
|
+
'',
|
|
183
|
+
].join('\n');
|
|
184
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { parseSince } from '../../core/session-filter.js';
|
|
3
|
+
import { EXIT_CODE } from '../exit-codes.js';
|
|
4
|
+
export const START_USAGE = `Usage: agentwhy start [--since <span | date>] [--out <dir>] [--policy <file> | --settings <file>]
|
|
5
|
+
[--no-open] [--serve | --no-serve] [--share]
|
|
6
|
+
|
|
7
|
+
Lists every session of the current directory's project on one page, writes a report for each
|
|
8
|
+
session active inside the range, and opens the page. The page it opens is served from 127.0.0.1,
|
|
9
|
+
at an address holding a random token, so a file marked done on it is recorded at once; it stops
|
|
10
|
+
after 30 minutes with no request, or with Ctrl+C. The reports are files either way.
|
|
11
|
+
|
|
12
|
+
--since <span|date> sessions last active since then: 7d, 12h, 2w, or 2026-09-01 (UTC). Default 7d
|
|
13
|
+
--out <dir> where to write; default a new directory in the system temporary location.
|
|
14
|
+
Refused inside a repository
|
|
15
|
+
--policy <file> the policy every report reads, as for \`report\`
|
|
16
|
+
--settings <file> a settings file whose deny rules stand in for a policy, as for \`report\`.
|
|
17
|
+
Without either, every report uses the built-in default and says so
|
|
18
|
+
--no-open write the files and print where, without opening anything
|
|
19
|
+
--serve serve the page even with --no-open, and print its address
|
|
20
|
+
--no-serve open the page as a file and return; marks on it are copied as commands
|
|
21
|
+
--share no session ids, no project path, nothing above the project root. This
|
|
22
|
+
reduces exposure; it does not anonymise
|
|
23
|
+
-h, --help this text
|
|
24
|
+
`;
|
|
25
|
+
// A Record over the outcome union: a new outcome does not compile until it has an exit code.
|
|
26
|
+
const EXIT_CODE_BY_OUTCOME = {
|
|
27
|
+
written: EXIT_CODE.ok,
|
|
28
|
+
// No sessions is an answer about this directory, the same one `sessions` gives with exit 0.
|
|
29
|
+
'no-sessions': EXIT_CODE.ok,
|
|
30
|
+
refused: EXIT_CODE.usage,
|
|
31
|
+
unwritable: EXIT_CODE.usage,
|
|
32
|
+
};
|
|
33
|
+
/** `agentwhy start` - a page to begin from, for someone who has a date and not a session id. */
|
|
34
|
+
export class StartCliCommand {
|
|
35
|
+
name = 'start';
|
|
36
|
+
usage = START_USAGE;
|
|
37
|
+
#dependencies;
|
|
38
|
+
constructor(dependencies) {
|
|
39
|
+
this.#dependencies = dependencies;
|
|
40
|
+
}
|
|
41
|
+
async execute(args) {
|
|
42
|
+
let values;
|
|
43
|
+
try {
|
|
44
|
+
({ values } = parseArgs({
|
|
45
|
+
args: [...args],
|
|
46
|
+
options: {
|
|
47
|
+
since: { type: 'string', default: '7d' },
|
|
48
|
+
out: { type: 'string' },
|
|
49
|
+
policy: { type: 'string' },
|
|
50
|
+
settings: { type: 'string' },
|
|
51
|
+
'no-open': { type: 'boolean', default: false },
|
|
52
|
+
serve: { type: 'boolean' },
|
|
53
|
+
'no-serve': { type: 'boolean', default: false },
|
|
54
|
+
share: { type: 'boolean', default: false },
|
|
55
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
56
|
+
},
|
|
57
|
+
strict: true,
|
|
58
|
+
allowPositionals: false,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return { kind: 'usage-error', message: error instanceof Error ? error.message : String(error), usage: this.usage };
|
|
63
|
+
}
|
|
64
|
+
if (values.help)
|
|
65
|
+
return { kind: 'help', usage: this.usage };
|
|
66
|
+
if (values.serve === true && values['no-serve']) {
|
|
67
|
+
return { kind: 'usage-error', message: '--serve and --no-serve cannot be given together', usage: this.usage };
|
|
68
|
+
}
|
|
69
|
+
const since = parseSince(values.since, this.#dependencies.now);
|
|
70
|
+
if ('error' in since)
|
|
71
|
+
return { kind: 'usage-error', message: since.error, usage: this.usage };
|
|
72
|
+
const result = await this.#dependencies.start.run({
|
|
73
|
+
since,
|
|
74
|
+
...(values.out === undefined || values.out === '' ? {} : { out: values.out }),
|
|
75
|
+
...(values.policy === undefined ? {} : { policyPath: values.policy }),
|
|
76
|
+
...(values.settings === undefined ? {} : { settingsPath: values.settings }),
|
|
77
|
+
open: !values['no-open'],
|
|
78
|
+
share: values.share,
|
|
79
|
+
...(values['no-serve'] ? { serve: false } : values.serve === true ? { serve: true } : {}),
|
|
80
|
+
});
|
|
81
|
+
return { kind: 'completed', output: result.output, exitCode: EXIT_CODE_BY_OUTCOME[result.outcome] };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { ALERT_THRESHOLDS } from '../../report/watch/agent-alert.js';
|
|
3
|
+
import { SAID_AS } from '../../report/watch/notice-choices.js';
|
|
4
|
+
import { CLEAN_MODES, DEFAULT_CHANNELS, DEFAULT_CLEAN, NOTICE_CHANNELS, } from '../../report/watch/subagent-watch.js';
|
|
5
|
+
import { EXIT_CODE } from '../exit-codes.js';
|
|
6
|
+
export const WATCH_USAGE = `Usage: agentwhy watch [--policy <file> | --settings <file>]
|
|
7
|
+
[--on value | reached | refused]
|
|
8
|
+
[--clean off | once | every-turn] [--say agent | line]
|
|
9
|
+
[--notify chat,terminal,os]
|
|
10
|
+
|
|
11
|
+
Run by Claude Code hooks, never by hand: reads the hook input on standard input and works out which
|
|
12
|
+
event it was. On SubagentStop it checks the agent that just finished and keeps what it found; on Stop
|
|
13
|
+
it says, in the conversation, what that turn's agents did. It speaks when an agent wrote a value from
|
|
14
|
+
a protected file into its own messages, and otherwise says nothing - unless --on asks for less.
|
|
15
|
+
The notice names no path and no value, and reaches no model; agentwhy report --open shows the rest.
|
|
16
|
+
It always exits 0: a SubagentStop hook that exits 2 hands its error to the agent as an instruction,
|
|
17
|
+
and a Stop hook that exits 2 keeps the turn from ending.
|
|
18
|
+
|
|
19
|
+
--policy <file> the policy to read the session under, as for \`report\`
|
|
20
|
+
--settings <file> a settings file whose deny rules stand in for a policy, as for \`report\`
|
|
21
|
+
--on <level> value (default): only a value written from a protected file.
|
|
22
|
+
reached: also an agent that reached a protected file.
|
|
23
|
+
refused: also an attempt a rule refused, where nothing was reached -
|
|
24
|
+
the one notice that asks for nothing and says a rule held
|
|
25
|
+
--clean <when> when a turn that found nothing says so, in the conversation
|
|
26
|
+
once (default): the first quiet turn says the session is being watched.
|
|
27
|
+
off: nothing is ever said about a quiet turn, as before.
|
|
28
|
+
every-turn: after every reply. A quiet turn of a session that was
|
|
29
|
+
not quiet says both, so a clean turn never reads as a clean session
|
|
30
|
+
--say <how> how a value in the conversation is said
|
|
31
|
+
agent (default): the turn is kept open and your own agent says it,
|
|
32
|
+
in its words and your language, and offers to open the report. The
|
|
33
|
+
line is said beside it. Only where a person is reading: not in claude -p
|
|
34
|
+
line: the grey line under the reply, and nothing else
|
|
35
|
+
--notify <channels> where the notice is shown, comma-separated. Default chat
|
|
36
|
+
chat: a line in the conversation, at the end of the turn, in any interface
|
|
37
|
+
terminal: a notification the terminal interface of Claude Code shows
|
|
38
|
+
os: a macOS notification, shown whatever interface is in use
|
|
39
|
+
-h, --help this text
|
|
40
|
+
|
|
41
|
+
To use it, run agentwhy init in the project, which asks before it writes. Or add this to
|
|
42
|
+
.claude/settings.local.json yourself - watch itself installs nothing:
|
|
43
|
+
|
|
44
|
+
"hooks": {
|
|
45
|
+
"SubagentStop": [
|
|
46
|
+
{ "hooks": [ { "type": "command",
|
|
47
|
+
"command": "agentwhy watch --settings \\"$CLAUDE_PROJECT_DIR/.claude/settings.json\\"" } ] }
|
|
48
|
+
],
|
|
49
|
+
"Stop": [
|
|
50
|
+
{ "hooks": [ { "type": "command",
|
|
51
|
+
"command": "agentwhy watch --settings \\"$CLAUDE_PROJECT_DIR/.claude/settings.json\\"" } ] }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
The same command line in both: it reads the event it was given. Without the Stop hook nothing is said
|
|
56
|
+
in the conversation, and the notice waits for a channel that shows it.
|
|
57
|
+
|
|
58
|
+
A hook can fail to run at all. agentwhy start still reads every session, and stays the backstop.
|
|
59
|
+
`;
|
|
60
|
+
/** `agentwhy watch` - the hook end of `specs/2026-09-16-when-an-agent-finishes.md`. */
|
|
61
|
+
export class WatchCliCommand {
|
|
62
|
+
name = 'watch';
|
|
63
|
+
usage = WATCH_USAGE;
|
|
64
|
+
#dependencies;
|
|
65
|
+
constructor(dependencies) {
|
|
66
|
+
this.#dependencies = dependencies;
|
|
67
|
+
}
|
|
68
|
+
async execute(args) {
|
|
69
|
+
let values;
|
|
70
|
+
try {
|
|
71
|
+
({ values } = parseArgs({
|
|
72
|
+
args: [...args],
|
|
73
|
+
options: {
|
|
74
|
+
policy: { type: 'string' },
|
|
75
|
+
settings: { type: 'string' },
|
|
76
|
+
on: { type: 'string' },
|
|
77
|
+
clean: { type: 'string' },
|
|
78
|
+
say: { type: 'string' },
|
|
79
|
+
notify: { type: 'string' },
|
|
80
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
81
|
+
},
|
|
82
|
+
strict: true,
|
|
83
|
+
allowPositionals: false,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// The channels are still read where they can be: a hook with one wrong flag was still told where to speak.
|
|
88
|
+
const asked = channelsIn(looseNotify(args));
|
|
89
|
+
return this.#notChecked(asked === 'unknown' ? DEFAULT_CHANNELS : asked);
|
|
90
|
+
}
|
|
91
|
+
if (values.help)
|
|
92
|
+
return { kind: 'help', usage: this.usage };
|
|
93
|
+
// The only usage error this command returns: at a terminal no hook is listening, so exit 2 instructs nobody (R13).
|
|
94
|
+
if (this.#dependencies.interactive) {
|
|
95
|
+
return { kind: 'usage-error', message: 'watch reads a hook input on standard input, and this is a terminal', usage: this.usage };
|
|
96
|
+
}
|
|
97
|
+
/*
|
|
98
|
+
* A flag that was not written is not a choice: it is left to what the person set, and the use case settles it
|
|
99
|
+
* (`the-agent-tells-you.md` R24). A flag that was written and is not a value this version knows is still an
|
|
100
|
+
* error - a hook asking for `--on everything` is a hook whose person believes they are being told more.
|
|
101
|
+
*/
|
|
102
|
+
const on = read(values.on, ALERT_THRESHOLDS);
|
|
103
|
+
const clean = read(values.clean, CLEAN_MODES);
|
|
104
|
+
const say = read(values.say, SAID_AS);
|
|
105
|
+
const channels = values.notify === undefined ? undefined : channelsIn(values.notify);
|
|
106
|
+
if (on === 'unknown' || clean === 'unknown' || say === 'unknown' || channels === 'unknown') {
|
|
107
|
+
return this.#notChecked(channels === undefined || channels === 'unknown' ? DEFAULT_CHANNELS : channels);
|
|
108
|
+
}
|
|
109
|
+
const result = await this.#dependencies.watch.run({
|
|
110
|
+
...(on === undefined ? {} : { on }),
|
|
111
|
+
...(clean === undefined ? {} : { clean }),
|
|
112
|
+
...(say === undefined ? {} : { say }),
|
|
113
|
+
...(channels === undefined ? {} : { channels }),
|
|
114
|
+
...(values.policy === undefined ? {} : { policyPath: values.policy }),
|
|
115
|
+
...(values.settings === undefined ? {} : { settingsPath: values.settings }),
|
|
116
|
+
});
|
|
117
|
+
// R8: every outcome is exit 0. What could not be checked is said in the output, never in the exit code.
|
|
118
|
+
return { kind: 'completed', output: result.output, exitCode: EXIT_CODE.ok };
|
|
119
|
+
}
|
|
120
|
+
/** Arguments a hook was configured with and this version cannot use: said to the user, exit 0 (R8). */
|
|
121
|
+
async #notChecked(channels) {
|
|
122
|
+
const result = await this.#dependencies.watch.announce({ kind: 'not-checked', reason: 'arguments' }, channels);
|
|
123
|
+
return { kind: 'completed', output: result.output, exitCode: EXIT_CODE.ok };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** One flag, as written: what it names, nothing where it was not written, and `unknown` where it names nothing. */
|
|
127
|
+
function read(value, known) {
|
|
128
|
+
if (value === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
return known.find((candidate) => candidate === value) ?? 'unknown';
|
|
131
|
+
}
|
|
132
|
+
/** `--notify` as given, read without refusing anything else in the arguments. */
|
|
133
|
+
function looseNotify(args) {
|
|
134
|
+
const { values } = parseArgs({ args: [...args], options: { notify: { type: 'string' } }, strict: false, allowPositionals: true });
|
|
135
|
+
return typeof values.notify === 'string' ? values.notify : undefined;
|
|
136
|
+
}
|
|
137
|
+
/** The channels named, or `unknown` when one is not a channel or none is named. */
|
|
138
|
+
function channelsIn(list) {
|
|
139
|
+
const names = (list ?? '').split(',').map((name) => name.trim()).filter((name) => name !== '');
|
|
140
|
+
const channels = names.map((name) => NOTICE_CHANNELS.find((channel) => channel === name));
|
|
141
|
+
return channels.length === 0 || channels.includes(undefined) ? 'unknown' : [...new Set(channels)];
|
|
142
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Documented in each command's usage text; tests/cli/commands/doctor-usage.test.ts pins the doctor usage to it.
|
|
2
|
+
export const EXIT_CODE = {
|
|
3
|
+
ok: 0,
|
|
4
|
+
mainTranscriptMissing: 1,
|
|
5
|
+
usage: 2,
|
|
6
|
+
unexpected: 3,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* What a `PreToolUse` hook exits with to block the call; the hooks reference documents that its stderr then reaches the
|
|
10
|
+
* model as the reason (`specs/2026-09-16-worth-running-every-day.md` R19). The same number as a usage error, on purpose
|
|
11
|
+
* named apart: one is this tool's own convention, the other is Claude Code's.
|
|
12
|
+
*/
|
|
13
|
+
export const HOOK_BLOCK_EXIT_CODE = 2;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agentwhy` in the folder it was typed in, and every project a page switches to after it, in this one process
|
|
3
|
+
* (`.ai/specs/2026-09-27-which-project.md` V14, V18). A switch starts `start` for the chosen folder with the range the page
|
|
4
|
+
* was drawn with; the page is answered with the new run's address once that run serves, and the run it came from ends.
|
|
5
|
+
* A run that ends before it serves - nothing there to show, a folder that cannot be read - answers the page with what it
|
|
6
|
+
* said, and nothing else changes. The terminal is given back when the last run shown has ended: Ctrl+C there stops it,
|
|
7
|
+
* as it stopped the first.
|
|
8
|
+
*
|
|
9
|
+
* `write` is given each run's result as it ends; the last one's is returned for the shell to write and exit with.
|
|
10
|
+
*/
|
|
11
|
+
export async function runSwitching(runIn, workingDirectory, argv, write) {
|
|
12
|
+
const shown = [];
|
|
13
|
+
const past = [];
|
|
14
|
+
const seams = (folder, handedOver, arrivedFrom) => ({
|
|
15
|
+
switchTo: (folder, since, from) => new Promise((resolve) => {
|
|
16
|
+
let answered = false;
|
|
17
|
+
const run = runIn(folder, ['start', '--since', since], seams(folder, (url) => {
|
|
18
|
+
answered = true;
|
|
19
|
+
shown.push(run);
|
|
20
|
+
resolve({ url });
|
|
21
|
+
}, from));
|
|
22
|
+
run.then((result) => {
|
|
23
|
+
if (!answered)
|
|
24
|
+
resolve({ failed: reasonOf(result) });
|
|
25
|
+
}, (error) => {
|
|
26
|
+
if (!answered)
|
|
27
|
+
resolve({ failed: error instanceof Error ? error.message : String(error) });
|
|
28
|
+
});
|
|
29
|
+
}),
|
|
30
|
+
...(handedOver === undefined ? {} : { handedOver }),
|
|
31
|
+
...(arrivedFrom === undefined ? {} : { arrivedFrom }),
|
|
32
|
+
served: (token) => { past.push({ token, folder }); },
|
|
33
|
+
pastRuns: () => past,
|
|
34
|
+
});
|
|
35
|
+
let result = await runIn(workingDirectory, argv, seams(workingDirectory));
|
|
36
|
+
for (let at = 0; at < shown.length; at += 1) {
|
|
37
|
+
write(result);
|
|
38
|
+
result = await shown[at];
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/** What a run that never served said, as one line a page can show. */
|
|
43
|
+
function reasonOf(result) {
|
|
44
|
+
const said = result.kind === 'completed' ? result.output : result.kind === 'hook-block' ? result.reason : result.kind === 'usage-error' ? result.message : result.usage;
|
|
45
|
+
return said.trim().split('\n')[0] || 'agentwhy could not show that project.';
|
|
46
|
+
}
|