@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.91
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/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +13 -13
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +333 -7
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +5 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +29 -29
- package/dist/core/engine/anvil-client.js +214 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +129 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1792 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +46 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +216 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/hooks/worktree-events.js +158 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +551 -41
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +664 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2148 -217
- package/dist/core/repl/slash-commands.js +501 -41
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +324 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +30 -30
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree/include-parser.js +249 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4185 -549
- package/dist/runtime/commands/agents.js +31 -31
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +73 -39
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +27 -4
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +187 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +12 -12
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +8 -8
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +22 -22
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/runtime/worktree-bootstrap.js +579 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +89 -28
- package/dist/tools/ask-user-question.js +337 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +624 -46
- package/dist/tools/brief.js +224 -0
- package/dist/tools/cron.js +433 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +377 -1
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +99 -4
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +315 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/multi-file-diff-approval.js +375 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +36 -1
- package/dist/tui/repl-render.js +176 -25
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +125 -45
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/package.json +31 -16
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +12 -0
- package/test/scenarios/identity.scenario.txt +12 -0
- package/test/scenarios/persona-handoff.scenario.txt +12 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi memory` — operator surface for the persona-memory layer
|
|
3
|
+
* .
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
*
|
|
7
|
+
* pugi memory list [--persona <slug>] [--kind <kind>] [--limit <n>]
|
|
8
|
+
* List memories for the current tenant — recency-first. Renders a
|
|
9
|
+
* table with TTL countdown.
|
|
10
|
+
*
|
|
11
|
+
* pugi memory recall <query> [--persona <slug>] [--top-k <n>]
|
|
12
|
+
* Hybrid recall (vector + BM25 + recency) for one persona.
|
|
13
|
+
*
|
|
14
|
+
* pugi memory write <kind> <content> [--persona <slug>] [--forget-after <iso>]
|
|
15
|
+
* Persist a new memory. Queues locally + retries on next sync when
|
|
16
|
+
* the admin-api is unreachable.
|
|
17
|
+
*
|
|
18
|
+
* pugi memory forget <id>
|
|
19
|
+
* Hard-delete one memory by id.
|
|
20
|
+
*
|
|
21
|
+
* pugi memory sync
|
|
22
|
+
* Push the local pending-write queue to the admin-api. Idempotent:
|
|
23
|
+
* successful ops are dropped from the queue, failed ops stay queued.
|
|
24
|
+
*
|
|
25
|
+
* Auth surface: the same `resolveActiveCredential` flow every other
|
|
26
|
+
* pugi command uses — bearer token + apiUrl from `~/.pugi/credentials.json`.
|
|
27
|
+
* The admin-api enforces the tenant boundary via `BridgeOrJwtGuard` +
|
|
28
|
+
* `prisma.withTenant`.
|
|
29
|
+
*
|
|
30
|
+
* Feature flag: the admin-api side honours `PERSONA_MEMORY_ENABLED` and
|
|
31
|
+
* returns HTTP 503 `{error: feature_disabled}` until the flag is
|
|
32
|
+
* flipped. The CLI surfaces that response verbatim so the operator
|
|
33
|
+
* sees the actual gate.
|
|
34
|
+
*/
|
|
35
|
+
import { request } from 'undici';
|
|
36
|
+
import { resolveActiveCredential } from '../../core/credentials.js';
|
|
37
|
+
import { isScanDisabled, redactSecrets, scanForSecrets, } from '../../core/memory/secret-scanner.js';
|
|
38
|
+
import { PERSONA_MEMORY_KINDS, countPending, defaultQueuePath, enqueueMemoryOp, readMemoryQueue, rewriteMemoryQueue, } from '../../core/memory-sync/queue.js';
|
|
39
|
+
const SUB_USAGE = [
|
|
40
|
+
'pugi memory list [--persona <slug>] [--kind <k>] [--limit <n>]',
|
|
41
|
+
'pugi memory recall <query> [--persona <slug>] [--top-k <n>]',
|
|
42
|
+
'pugi memory write <kind> <content> [--persona <slug>] [--forget-after <iso>]',
|
|
43
|
+
'pugi memory forget <id>',
|
|
44
|
+
'pugi memory sync',
|
|
45
|
+
].join('\n ');
|
|
46
|
+
const DEFAULT_PERSONA = 'mira';
|
|
47
|
+
/** Single CLI entry — top-level `pugi memory` AND the in-REPL `/memory` slash both call this. */
|
|
48
|
+
export async function runMemoryCommand(args, ctx) {
|
|
49
|
+
const sub = (args[0] ?? '').toLowerCase();
|
|
50
|
+
const tail = args.slice(1);
|
|
51
|
+
switch (sub) {
|
|
52
|
+
case '':
|
|
53
|
+
case '-h':
|
|
54
|
+
case '--help':
|
|
55
|
+
return printUsage(ctx);
|
|
56
|
+
case 'list':
|
|
57
|
+
return runList(tail, ctx);
|
|
58
|
+
case 'recall':
|
|
59
|
+
return runRecall(tail, ctx);
|
|
60
|
+
case 'write':
|
|
61
|
+
case 'remember':
|
|
62
|
+
return runWrite(tail, ctx);
|
|
63
|
+
case 'forget':
|
|
64
|
+
case 'delete':
|
|
65
|
+
return runForget(tail, ctx);
|
|
66
|
+
case 'sync':
|
|
67
|
+
return runSync(ctx);
|
|
68
|
+
default:
|
|
69
|
+
ctx.writeOutput({ command: 'memory', sub, status: 'unknown_sub' }, `Unknown sub-command "${sub}".\nUsage:\n ${SUB_USAGE}`);
|
|
70
|
+
return { command: 'memory', sub, status: 'unknown_sub' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function printUsage(ctx) {
|
|
74
|
+
ctx.writeOutput({ command: 'memory', sub: 'help', usage: SUB_USAGE }, `pugi memory — persona-memory operator surface .\nUsage:\n ${SUB_USAGE}`);
|
|
75
|
+
return { command: 'memory', sub: 'help', status: 'listed' };
|
|
76
|
+
}
|
|
77
|
+
// ===========================================================================
|
|
78
|
+
// list
|
|
79
|
+
// ===========================================================================
|
|
80
|
+
async function runList(args, ctx) {
|
|
81
|
+
const flags = parseFlags(args);
|
|
82
|
+
const cred = resolveActiveCredential();
|
|
83
|
+
if (!cred)
|
|
84
|
+
return emitUnauth('list', ctx);
|
|
85
|
+
const url = new URL(`${stripTrailing(cred.apiUrl)}/api/persona-memory`);
|
|
86
|
+
if (flags.persona)
|
|
87
|
+
url.searchParams.set('personaSlug', flags.persona);
|
|
88
|
+
if (flags.kind)
|
|
89
|
+
url.searchParams.set('kind', flags.kind);
|
|
90
|
+
if (flags.limit)
|
|
91
|
+
url.searchParams.set('limit', String(flags.limit));
|
|
92
|
+
const res = await safeGet(url.toString(), cred.apiKey);
|
|
93
|
+
if (res.status === 503)
|
|
94
|
+
return emitFeatureDisabled('list', ctx);
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
ctx.writeOutput({ command: 'memory', sub: 'list', status: 'invalid_args', reason: res.detail }, `pugi memory list: HTTP ${res.statusCode} — ${res.detail}`);
|
|
97
|
+
return { command: 'memory', sub: 'list', status: 'invalid_args', reason: res.detail };
|
|
98
|
+
}
|
|
99
|
+
const items = (res.body ?? []);
|
|
100
|
+
const now = (ctx.now ?? (() => new Date()))();
|
|
101
|
+
const lines = items.map((item) => formatMemoryRow(item, now));
|
|
102
|
+
const text = items.length === 0
|
|
103
|
+
? 'No memories for the active tenant + filters.'
|
|
104
|
+
: ['kind strength age ttl id content', ...lines].join('\n');
|
|
105
|
+
ctx.writeOutput({ command: 'memory', sub: 'list', count: items.length, items }, text);
|
|
106
|
+
return { command: 'memory', sub: 'list', status: 'listed', count: items.length };
|
|
107
|
+
}
|
|
108
|
+
// ===========================================================================
|
|
109
|
+
// recall
|
|
110
|
+
// ===========================================================================
|
|
111
|
+
async function runRecall(args, ctx) {
|
|
112
|
+
const { positional, flags } = splitPositionalFlags(args);
|
|
113
|
+
if (positional.length === 0) {
|
|
114
|
+
ctx.writeOutput({ command: 'memory', sub: 'recall', status: 'invalid_args' }, 'pugi memory recall <query> — query is required.');
|
|
115
|
+
return { command: 'memory', sub: 'recall', status: 'invalid_args' };
|
|
116
|
+
}
|
|
117
|
+
const query = positional.join(' ').trim();
|
|
118
|
+
const persona = flags.persona ?? DEFAULT_PERSONA;
|
|
119
|
+
const topK = flags.topK ?? 5;
|
|
120
|
+
const cred = resolveActiveCredential();
|
|
121
|
+
if (!cred)
|
|
122
|
+
return emitUnauth('recall', ctx);
|
|
123
|
+
const url = new URL(`${stripTrailing(cred.apiUrl)}/api/persona-memory/recall`);
|
|
124
|
+
url.searchParams.set('personaSlug', persona);
|
|
125
|
+
url.searchParams.set('query', query);
|
|
126
|
+
url.searchParams.set('topK', String(topK));
|
|
127
|
+
const res = await safeGet(url.toString(), cred.apiKey);
|
|
128
|
+
if (res.status === 503)
|
|
129
|
+
return emitFeatureDisabled('recall', ctx);
|
|
130
|
+
if (!res.ok) {
|
|
131
|
+
ctx.writeOutput({ command: 'memory', sub: 'recall', status: 'invalid_args', reason: res.detail }, `pugi memory recall: HTTP ${res.statusCode} — ${res.detail}`);
|
|
132
|
+
return { command: 'memory', sub: 'recall', status: 'invalid_args', reason: res.detail };
|
|
133
|
+
}
|
|
134
|
+
const results = (res.body ?? []);
|
|
135
|
+
const text = results.length === 0
|
|
136
|
+
? `No matches for "${query.slice(0, 64)}" on persona "${persona}".`
|
|
137
|
+
: results
|
|
138
|
+
.map((r, idx) => `${(idx + 1).toString().padStart(2, ' ')}. [${r.item.kind}] score=${r.score.toFixed(3)} ${r.item.content}`)
|
|
139
|
+
.join('\n');
|
|
140
|
+
ctx.writeOutput({ command: 'memory', sub: 'recall', count: results.length, results }, text);
|
|
141
|
+
return { command: 'memory', sub: 'recall', status: 'recalled', count: results.length };
|
|
142
|
+
}
|
|
143
|
+
// ===========================================================================
|
|
144
|
+
// write
|
|
145
|
+
// ===========================================================================
|
|
146
|
+
async function runWrite(args, ctx) {
|
|
147
|
+
// Backlog : `--curate` opts into the proposal-first
|
|
148
|
+
// mode powered by `pugi remember`. Default behaviour (silent enqueue)
|
|
149
|
+
// is preserved for back-compat per the bundled-skills spec — the
|
|
150
|
+
// operator has to explicitly opt in.
|
|
151
|
+
const curateRequested = args.includes('--curate');
|
|
152
|
+
// Backlog : `--allow-redacted` opts into the
|
|
153
|
+
// auto-scrub flow. Default behaviour is HARD reject; this flag tells
|
|
154
|
+
// the secret scanner to replace each secret with `[SECRET:<pattern>]`
|
|
155
|
+
// and persist the scrubbed payload, surfacing a warning so the
|
|
156
|
+
// operator notices.
|
|
157
|
+
const allowRedacted = args.includes('--allow-redacted');
|
|
158
|
+
const argsWithoutCurate = args
|
|
159
|
+
.filter((a) => a !== '--curate')
|
|
160
|
+
.filter((a) => a !== '--allow-redacted');
|
|
161
|
+
const { positional, flags } = splitPositionalFlags(argsWithoutCurate);
|
|
162
|
+
if (positional.length < 2) {
|
|
163
|
+
ctx.writeOutput({ command: 'memory', sub: 'write', status: 'invalid_args' }, 'pugi memory write <kind> <content> — kind + content required.');
|
|
164
|
+
return { command: 'memory', sub: 'write', status: 'invalid_args' };
|
|
165
|
+
}
|
|
166
|
+
if (curateRequested) {
|
|
167
|
+
// Hand off to the bundled remember skill. We surface a single line
|
|
168
|
+
// hint and a structured payload so scripted callers can detect the
|
|
169
|
+
// delegation without grepping prose.
|
|
170
|
+
const hint = 'Use `pugi remember "<content>"` for proposal-first curation. ' +
|
|
171
|
+
'`pugi memory write --curate` will route there once the interactive ' +
|
|
172
|
+
'bundle wiring lands; meanwhile the silent enqueue is preserved.';
|
|
173
|
+
ctx.writeOutput({
|
|
174
|
+
command: 'memory',
|
|
175
|
+
sub: 'write',
|
|
176
|
+
status: 'curate_hint',
|
|
177
|
+
hint,
|
|
178
|
+
}, hint);
|
|
179
|
+
}
|
|
180
|
+
const kind = positional[0]?.toLowerCase() ?? '';
|
|
181
|
+
// `content` is `let` (not `const`) so the secret-scanner branch
|
|
182
|
+
// below can swap in a redacted version when --allow-redacted fires.
|
|
183
|
+
let content = positional.slice(1).join(' ').trim();
|
|
184
|
+
if (!PERSONA_MEMORY_KINDS.includes(kind)) {
|
|
185
|
+
ctx.writeOutput({
|
|
186
|
+
command: 'memory',
|
|
187
|
+
sub: 'write',
|
|
188
|
+
status: 'invalid_args',
|
|
189
|
+
reason: 'unknown_kind',
|
|
190
|
+
}, `pugi memory write: unknown kind "${kind}". Expected one of ${PERSONA_MEMORY_KINDS.join(' | ')}.`);
|
|
191
|
+
return { command: 'memory', sub: 'write', status: 'invalid_args', reason: 'unknown_kind' };
|
|
192
|
+
}
|
|
193
|
+
if (content.length === 0) {
|
|
194
|
+
ctx.writeOutput({ command: 'memory', sub: 'write', status: 'invalid_args' }, 'pugi memory write: content is empty.');
|
|
195
|
+
return { command: 'memory', sub: 'write', status: 'invalid_args' };
|
|
196
|
+
}
|
|
197
|
+
// Backlog — secret-scanner middleware. Runs BEFORE network +
|
|
198
|
+
// BEFORE offline enqueue so both code paths share the gate.
|
|
199
|
+
// - default: HARD reject (status: 'secret_blocked'), no write.
|
|
200
|
+
// - PUGI_MEMORY_SECRET_SCAN_DISABLE=1: skipped entirely.
|
|
201
|
+
// - --allow-redacted: matches replaced with `[SECRET:<pattern>]`
|
|
202
|
+
// placeholders, write proceeds + warning.
|
|
203
|
+
// The post-scan content is what gets enqueued / POSTed. We mutate
|
|
204
|
+
// `content` to keep the rest of the function untouched.
|
|
205
|
+
if (!isScanDisabled()) {
|
|
206
|
+
const matches = scanForSecrets(content);
|
|
207
|
+
if (matches.length > 0) {
|
|
208
|
+
if (allowRedacted) {
|
|
209
|
+
const scrubbed = redactSecrets(content);
|
|
210
|
+
content = scrubbed.redacted;
|
|
211
|
+
const names = Array.from(new Set(scrubbed.matches.map((m) => m.pattern))).join(', ');
|
|
212
|
+
ctx.writeOutput({
|
|
213
|
+
command: 'memory',
|
|
214
|
+
sub: 'write',
|
|
215
|
+
status: 'redacted',
|
|
216
|
+
patterns: names,
|
|
217
|
+
count: scrubbed.matches.length,
|
|
218
|
+
}, `pugi memory write: --allow-redacted scrubbed ${scrubbed.matches.length} secret(s) — pattern(s): ${names}. Persisting redacted content.`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const names = Array.from(new Set(matches.map((m) => m.pattern))).join(', ');
|
|
222
|
+
ctx.writeOutput({
|
|
223
|
+
command: 'memory',
|
|
224
|
+
sub: 'write',
|
|
225
|
+
status: 'secret_blocked',
|
|
226
|
+
patterns: names,
|
|
227
|
+
count: matches.length,
|
|
228
|
+
hint: 'pass --allow-redacted to auto-scrub, or PUGI_MEMORY_SECRET_SCAN_DISABLE=1 to bypass',
|
|
229
|
+
}, `pugi memory write: refused — content contains secret(s): ${names}. ` +
|
|
230
|
+
`Re-run with --allow-redacted to auto-scrub, or set PUGI_MEMORY_SECRET_SCAN_DISABLE=1 to bypass.`);
|
|
231
|
+
return {
|
|
232
|
+
command: 'memory',
|
|
233
|
+
sub: 'write',
|
|
234
|
+
status: 'secret_blocked',
|
|
235
|
+
reason: names,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const persona = flags.persona ?? DEFAULT_PERSONA;
|
|
241
|
+
const cred = resolveActiveCredential();
|
|
242
|
+
if (!cred) {
|
|
243
|
+
// Queue offline. Operator can run `pugi memory sync` once auth is up.
|
|
244
|
+
const pending = enqueueMemoryOp({
|
|
245
|
+
op: 'write',
|
|
246
|
+
personaSlug: persona,
|
|
247
|
+
kind: kind,
|
|
248
|
+
content,
|
|
249
|
+
forgetAfter: flags.forgetAfter ?? null,
|
|
250
|
+
});
|
|
251
|
+
ctx.writeOutput({
|
|
252
|
+
command: 'memory',
|
|
253
|
+
sub: 'write',
|
|
254
|
+
status: 'queued_offline',
|
|
255
|
+
pending,
|
|
256
|
+
}, `Queued offline (no active credential). ${pending} pending — run \`pugi memory sync\` after \`pugi login\`.`);
|
|
257
|
+
return { command: 'memory', sub: 'write', status: 'queued_offline', pending };
|
|
258
|
+
}
|
|
259
|
+
const url = `${stripTrailing(cred.apiUrl)}/api/persona-memory`;
|
|
260
|
+
const body = {
|
|
261
|
+
personaSlug: persona,
|
|
262
|
+
kind,
|
|
263
|
+
content,
|
|
264
|
+
forgetAfter: flags.forgetAfter ?? null,
|
|
265
|
+
};
|
|
266
|
+
const res = await safePost(url, cred.apiKey, body);
|
|
267
|
+
if (res.status === 503)
|
|
268
|
+
return emitFeatureDisabled('write', ctx);
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
// Server unreachable / 5xx → queue for retry. 4xx → surface error,
|
|
271
|
+
// don't queue (a malformed write would just loop on sync).
|
|
272
|
+
if (res.statusCode >= 500 || res.statusCode === 0) {
|
|
273
|
+
const pending = enqueueMemoryOp({
|
|
274
|
+
op: 'write',
|
|
275
|
+
personaSlug: persona,
|
|
276
|
+
kind: kind,
|
|
277
|
+
content,
|
|
278
|
+
forgetAfter: flags.forgetAfter ?? null,
|
|
279
|
+
});
|
|
280
|
+
ctx.writeOutput({
|
|
281
|
+
command: 'memory',
|
|
282
|
+
sub: 'write',
|
|
283
|
+
status: 'queued_offline',
|
|
284
|
+
pending,
|
|
285
|
+
reason: res.detail,
|
|
286
|
+
}, `Server unreachable (${res.detail}). Queued — ${pending} pending. Run \`pugi memory sync\` later.`);
|
|
287
|
+
return {
|
|
288
|
+
command: 'memory',
|
|
289
|
+
sub: 'write',
|
|
290
|
+
status: 'queued_offline',
|
|
291
|
+
pending,
|
|
292
|
+
reason: res.detail,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
ctx.writeOutput({ command: 'memory', sub: 'write', status: 'invalid_args', reason: res.detail }, `pugi memory write: HTTP ${res.statusCode} — ${res.detail}`);
|
|
296
|
+
return {
|
|
297
|
+
command: 'memory',
|
|
298
|
+
sub: 'write',
|
|
299
|
+
status: 'invalid_args',
|
|
300
|
+
reason: res.detail,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const created = (res.body ?? {});
|
|
304
|
+
ctx.writeOutput({ command: 'memory', sub: 'write', status: 'written', item: created }, `Persisted [${kind}] on persona "${persona}": ${created.id}`);
|
|
305
|
+
return { command: 'memory', sub: 'write', status: 'written' };
|
|
306
|
+
}
|
|
307
|
+
// ===========================================================================
|
|
308
|
+
// forget
|
|
309
|
+
// ===========================================================================
|
|
310
|
+
async function runForget(args, ctx) {
|
|
311
|
+
const id = (args[0] ?? '').trim();
|
|
312
|
+
if (!id) {
|
|
313
|
+
ctx.writeOutput({ command: 'memory', sub: 'forget', status: 'invalid_args' }, 'pugi memory forget <id> — memory id is required.');
|
|
314
|
+
return { command: 'memory', sub: 'forget', status: 'invalid_args' };
|
|
315
|
+
}
|
|
316
|
+
const cred = resolveActiveCredential();
|
|
317
|
+
if (!cred) {
|
|
318
|
+
const pending = enqueueMemoryOp({ op: 'forget', id });
|
|
319
|
+
ctx.writeOutput({ command: 'memory', sub: 'forget', status: 'queued_offline', pending }, `Queued offline (no active credential). ${pending} pending — run \`pugi memory sync\` after \`pugi login\`.`);
|
|
320
|
+
return { command: 'memory', sub: 'forget', status: 'queued_offline', pending };
|
|
321
|
+
}
|
|
322
|
+
const url = `${stripTrailing(cred.apiUrl)}/api/persona-memory/${encodeURIComponent(id)}`;
|
|
323
|
+
const res = await safeDelete(url, cred.apiKey);
|
|
324
|
+
if (res.status === 503)
|
|
325
|
+
return emitFeatureDisabled('forget', ctx);
|
|
326
|
+
if (res.statusCode === 404) {
|
|
327
|
+
ctx.writeOutput({ command: 'memory', sub: 'forget', status: 'forget_not_found', id }, `No memory with id "${id}" in this tenant.`);
|
|
328
|
+
return { command: 'memory', sub: 'forget', status: 'forget_not_found' };
|
|
329
|
+
}
|
|
330
|
+
if (!res.ok) {
|
|
331
|
+
if (res.statusCode >= 500 || res.statusCode === 0) {
|
|
332
|
+
const pending = enqueueMemoryOp({ op: 'forget', id });
|
|
333
|
+
ctx.writeOutput({
|
|
334
|
+
command: 'memory',
|
|
335
|
+
sub: 'forget',
|
|
336
|
+
status: 'queued_offline',
|
|
337
|
+
pending,
|
|
338
|
+
reason: res.detail,
|
|
339
|
+
}, `Server unreachable. Queued — ${pending} pending.`);
|
|
340
|
+
return {
|
|
341
|
+
command: 'memory',
|
|
342
|
+
sub: 'forget',
|
|
343
|
+
status: 'queued_offline',
|
|
344
|
+
pending,
|
|
345
|
+
reason: res.detail,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
ctx.writeOutput({ command: 'memory', sub: 'forget', status: 'invalid_args', reason: res.detail }, `pugi memory forget: HTTP ${res.statusCode} — ${res.detail}`);
|
|
349
|
+
return {
|
|
350
|
+
command: 'memory',
|
|
351
|
+
sub: 'forget',
|
|
352
|
+
status: 'invalid_args',
|
|
353
|
+
reason: res.detail,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
ctx.writeOutput({ command: 'memory', sub: 'forget', status: 'forgot', id }, `Forgot "${id}".`);
|
|
357
|
+
return { command: 'memory', sub: 'forget', status: 'forgot' };
|
|
358
|
+
}
|
|
359
|
+
// ===========================================================================
|
|
360
|
+
// sync
|
|
361
|
+
// ===========================================================================
|
|
362
|
+
async function runSync(ctx) {
|
|
363
|
+
const cred = resolveActiveCredential();
|
|
364
|
+
if (!cred)
|
|
365
|
+
return emitUnauth('sync', ctx);
|
|
366
|
+
const pendingBefore = countPending();
|
|
367
|
+
if (pendingBefore === 0) {
|
|
368
|
+
ctx.writeOutput({ command: 'memory', sub: 'sync', status: 'sync_noop', pending: 0 }, 'No pending operations to sync.');
|
|
369
|
+
return { command: 'memory', sub: 'sync', status: 'sync_noop', pending: 0 };
|
|
370
|
+
}
|
|
371
|
+
const ops = readMemoryQueue();
|
|
372
|
+
const remaining = [];
|
|
373
|
+
let synced = 0;
|
|
374
|
+
for (const op of ops) {
|
|
375
|
+
const ok = await flushOne(cred.apiUrl, cred.apiKey, op);
|
|
376
|
+
if (ok) {
|
|
377
|
+
synced++;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
remaining.push(op);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
rewriteMemoryQueue(remaining);
|
|
384
|
+
if (remaining.length === 0) {
|
|
385
|
+
ctx.writeOutput({
|
|
386
|
+
command: 'memory',
|
|
387
|
+
sub: 'sync',
|
|
388
|
+
status: 'synced',
|
|
389
|
+
pending: 0,
|
|
390
|
+
count: synced,
|
|
391
|
+
}, `Synced ${synced} pending operation(s). Queue is empty.`);
|
|
392
|
+
return { command: 'memory', sub: 'sync', status: 'synced', pending: 0, count: synced };
|
|
393
|
+
}
|
|
394
|
+
ctx.writeOutput({
|
|
395
|
+
command: 'memory',
|
|
396
|
+
sub: 'sync',
|
|
397
|
+
status: 'sync_partial',
|
|
398
|
+
pending: remaining.length,
|
|
399
|
+
count: synced,
|
|
400
|
+
}, `Synced ${synced} — ${remaining.length} still pending (server unreachable / 4xx).`);
|
|
401
|
+
return {
|
|
402
|
+
command: 'memory',
|
|
403
|
+
sub: 'sync',
|
|
404
|
+
status: 'sync_partial',
|
|
405
|
+
pending: remaining.length,
|
|
406
|
+
count: synced,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function flushOne(apiUrl, apiKey, op) {
|
|
410
|
+
if (op.op === 'write') {
|
|
411
|
+
const res = await safePost(`${stripTrailing(apiUrl)}/api/persona-memory`, apiKey, {
|
|
412
|
+
personaSlug: op.personaSlug,
|
|
413
|
+
kind: op.kind,
|
|
414
|
+
content: op.content,
|
|
415
|
+
forgetAfter: op.forgetAfter ?? null,
|
|
416
|
+
});
|
|
417
|
+
if (res.ok)
|
|
418
|
+
return true;
|
|
419
|
+
// 4xx → drop from queue (would loop forever otherwise).
|
|
420
|
+
if (res.statusCode >= 400 && res.statusCode < 500)
|
|
421
|
+
return true;
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
// op.op === 'forget'
|
|
425
|
+
const res = await safeDelete(`${stripTrailing(apiUrl)}/api/persona-memory/${encodeURIComponent(op.id)}`, apiKey);
|
|
426
|
+
if (res.ok)
|
|
427
|
+
return true;
|
|
428
|
+
// 404 → drop (already gone); other 4xx → drop too.
|
|
429
|
+
if (res.statusCode >= 400 && res.statusCode < 500)
|
|
430
|
+
return true;
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
function parseFlags(args) {
|
|
434
|
+
const out = {};
|
|
435
|
+
for (let i = 0; i < args.length; i++) {
|
|
436
|
+
const a = args[i] ?? '';
|
|
437
|
+
if (a === '--persona' && args[i + 1]) {
|
|
438
|
+
out.persona = args[i + 1];
|
|
439
|
+
i++;
|
|
440
|
+
}
|
|
441
|
+
else if (a === '--kind' && args[i + 1]) {
|
|
442
|
+
out.kind = args[i + 1];
|
|
443
|
+
i++;
|
|
444
|
+
}
|
|
445
|
+
else if (a === '--limit' && args[i + 1]) {
|
|
446
|
+
const n = Number.parseInt(args[i + 1] ?? '', 10);
|
|
447
|
+
if (Number.isFinite(n))
|
|
448
|
+
out.limit = n;
|
|
449
|
+
i++;
|
|
450
|
+
}
|
|
451
|
+
else if (a === '--top-k' && args[i + 1]) {
|
|
452
|
+
const n = Number.parseInt(args[i + 1] ?? '', 10);
|
|
453
|
+
if (Number.isFinite(n))
|
|
454
|
+
out.topK = n;
|
|
455
|
+
i++;
|
|
456
|
+
}
|
|
457
|
+
else if (a === '--forget-after' && args[i + 1]) {
|
|
458
|
+
out.forgetAfter = args[i + 1];
|
|
459
|
+
i++;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
function splitPositionalFlags(args) {
|
|
465
|
+
const positional = [];
|
|
466
|
+
const consumed = new Set();
|
|
467
|
+
for (let i = 0; i < args.length; i++) {
|
|
468
|
+
const a = args[i] ?? '';
|
|
469
|
+
if (a.startsWith('--')) {
|
|
470
|
+
consumed.add(i);
|
|
471
|
+
if (i + 1 < args.length && !(args[i + 1] ?? '').startsWith('--')) {
|
|
472
|
+
consumed.add(i + 1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else if (!consumed.has(i)) {
|
|
476
|
+
positional.push(a);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const flagArgs = [];
|
|
480
|
+
for (let i = 0; i < args.length; i++) {
|
|
481
|
+
if (consumed.has(i))
|
|
482
|
+
flagArgs.push(args[i] ?? '');
|
|
483
|
+
}
|
|
484
|
+
return { positional, flags: parseFlags(flagArgs) };
|
|
485
|
+
}
|
|
486
|
+
function stripTrailing(url) {
|
|
487
|
+
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
488
|
+
}
|
|
489
|
+
function formatMemoryRow(item, now) {
|
|
490
|
+
const age = ageHuman(item.lastUsedAt, now);
|
|
491
|
+
const ttl = item.forgetAfter ? ttlHuman(item.forgetAfter, now) : 'none';
|
|
492
|
+
const content = item.content.replace(/[\r\n\t]+/g, ' ').slice(0, 60);
|
|
493
|
+
return `${item.kind.padEnd(12)} ${item.strength.toFixed(2).padEnd(8)} ${age.padEnd(8)} ${ttl.padEnd(11)} ${item.id.padEnd(36)} ${content}`;
|
|
494
|
+
}
|
|
495
|
+
function ageHuman(iso, now) {
|
|
496
|
+
const t = Date.parse(iso);
|
|
497
|
+
if (!Number.isFinite(t))
|
|
498
|
+
return '?';
|
|
499
|
+
const days = Math.floor((now.getTime() - t) / (1000 * 60 * 60 * 24));
|
|
500
|
+
if (days <= 0)
|
|
501
|
+
return '<1d';
|
|
502
|
+
if (days < 30)
|
|
503
|
+
return `${days}d`;
|
|
504
|
+
return `${Math.floor(days / 30)}mo`;
|
|
505
|
+
}
|
|
506
|
+
function ttlHuman(iso, now) {
|
|
507
|
+
const t = Date.parse(iso);
|
|
508
|
+
if (!Number.isFinite(t))
|
|
509
|
+
return '?';
|
|
510
|
+
const ms = t - now.getTime();
|
|
511
|
+
if (ms <= 0)
|
|
512
|
+
return 'expired';
|
|
513
|
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
514
|
+
if (days >= 1)
|
|
515
|
+
return `${days}d`;
|
|
516
|
+
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
517
|
+
return `${hours}h`;
|
|
518
|
+
}
|
|
519
|
+
async function safeGet(url, apiKey) {
|
|
520
|
+
return safeRequest(url, apiKey, 'GET');
|
|
521
|
+
}
|
|
522
|
+
async function safeDelete(url, apiKey) {
|
|
523
|
+
return safeRequest(url, apiKey, 'DELETE');
|
|
524
|
+
}
|
|
525
|
+
async function safePost(url, apiKey, body) {
|
|
526
|
+
return safeRequest(url, apiKey, 'POST', body);
|
|
527
|
+
}
|
|
528
|
+
async function safeRequest(url, apiKey, method, body) {
|
|
529
|
+
try {
|
|
530
|
+
const headers = {
|
|
531
|
+
authorization: `Bearer ${apiKey}`,
|
|
532
|
+
accept: 'application/json',
|
|
533
|
+
};
|
|
534
|
+
let bodyText;
|
|
535
|
+
if (body !== undefined) {
|
|
536
|
+
bodyText = JSON.stringify(body);
|
|
537
|
+
headers['content-type'] = 'application/json';
|
|
538
|
+
}
|
|
539
|
+
const res = await request(url, { method, headers, body: bodyText });
|
|
540
|
+
const sc = res.statusCode;
|
|
541
|
+
if (sc < 200 || sc >= 300) {
|
|
542
|
+
const detail = await res.body.text().catch(() => '');
|
|
543
|
+
return {
|
|
544
|
+
ok: false,
|
|
545
|
+
status: sc,
|
|
546
|
+
statusCode: sc,
|
|
547
|
+
detail: detail.slice(0, 300),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
const parsed = await res.body.json().catch(() => undefined);
|
|
551
|
+
return { ok: true, status: sc, statusCode: sc, body: parsed, detail: '' };
|
|
552
|
+
}
|
|
553
|
+
catch (err) {
|
|
554
|
+
return {
|
|
555
|
+
ok: false,
|
|
556
|
+
status: 0,
|
|
557
|
+
statusCode: 0,
|
|
558
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function emitUnauth(sub, ctx) {
|
|
563
|
+
ctx.writeOutput({
|
|
564
|
+
command: 'memory',
|
|
565
|
+
sub,
|
|
566
|
+
status: 'unauthenticated',
|
|
567
|
+
hint: 'pugi login',
|
|
568
|
+
}, 'pugi memory: no active credential. Run `pugi login` first.');
|
|
569
|
+
return { command: 'memory', sub, status: 'unauthenticated' };
|
|
570
|
+
}
|
|
571
|
+
function emitFeatureDisabled(sub, ctx) {
|
|
572
|
+
ctx.writeOutput({
|
|
573
|
+
command: 'memory',
|
|
574
|
+
sub,
|
|
575
|
+
status: 'feature_disabled',
|
|
576
|
+
hint: 'set PERSONA_MEMORY_ENABLED=true on the admin-api host',
|
|
577
|
+
}, 'persona-memory feature is disabled on the admin-api host (HTTP 503).');
|
|
578
|
+
return { command: 'memory', sub, status: 'feature_disabled' };
|
|
579
|
+
}
|
|
580
|
+
/** Re-export for the `pugi doctor` queue-pending probe + the spec. */
|
|
581
|
+
export { defaultQueuePath };
|
|
582
|
+
//# sourceMappingURL=memory.js.map
|