@crouton-kit/crouter 0.3.32 → 0.3.34
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/README.md +10 -2
- package/dist/build-root.js +4 -6
- package/dist/builtin-memory/crouter-development/marketplaces.md +8 -8
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
- package/dist/builtin-memory/crouter-development/personas.md +3 -4
- package/dist/builtin-memory/crouter-development/plugins.md +31 -31
- package/dist/builtin-memory/design.md +4 -1
- package/dist/builtin-memory/development.md +4 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +9 -9
- package/dist/builtin-memory/internal/storage-tiers.md +5 -5
- package/dist/builtin-memory/planning.md +4 -1
- package/dist/builtin-memory/product.md +80 -0
- package/dist/builtin-memory/spec.md +4 -1
- package/dist/builtin-personas/advisor/PERSONA.md +10 -0
- package/dist/builtin-personas/design/PERSONA.md +2 -2
- package/dist/builtin-personas/design/orchestrator.md +3 -3
- package/dist/builtin-personas/developer/PERSONA.md +1 -1
- package/dist/builtin-personas/developer/orchestrator.md +2 -2
- package/dist/builtin-personas/explore/PERSONA.md +3 -3
- package/dist/builtin-personas/explore/orchestrator.md +4 -2
- package/dist/builtin-personas/general/PERSONA.md +1 -1
- package/dist/builtin-personas/general/orchestrator.md +1 -1
- package/dist/builtin-personas/orchestration-kernel.md +8 -15
- package/dist/builtin-personas/plan/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/orchestrator.md +2 -2
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
- package/dist/builtin-personas/product/PERSONA.md +18 -0
- package/dist/builtin-personas/product/orchestrator.md +14 -0
- package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -1
- package/dist/builtin-personas/review/orchestrator.md +1 -1
- package/dist/builtin-personas/runtime-base.md +5 -0
- package/dist/builtin-personas/spec/PERSONA.md +1 -1
- package/dist/builtin-personas/spec/orchestrator.md +2 -2
- package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
- package/dist/builtin-personas/waiting.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/crtr-help-gate.test.ts +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +663 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +52 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +418 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crouter-help.ts +87 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/filters.json +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +174 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-help-gate.ts +408 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +442 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +119 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/nested-context.ts +327 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +574 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +260 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +40 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/package.json +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.js +1 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +479 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/LICENSE +13 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/README.md +172 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/bin.mjs +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/composer.js +219 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/Document.js +335 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/directives.js +176 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/errors.js +57 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/index.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/log.js +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst.js +98 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/parser.js +975 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/public-api.js +102 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/tags.js +96 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/util.js +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/visit.js +233 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/index.js +5 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/package.json +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/cli.d.ts +8 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/cli.mjs +201 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/composer.d.ts +63 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/composer.js +224 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/Document.d.ts +141 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/Document.js +337 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/directives.d.ts +49 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/directives.js +178 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/errors.d.ts +21 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/errors.js +62 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/index.d.ts +25 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/index.js +50 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/log.d.ts +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/log.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Alias.js +118 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/options.d.ts +350 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst.d.ts +109 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst.js +112 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/parser.d.ts +84 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/parser.js +980 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/public-api.d.ts +44 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/public-api.js +107 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/tags.d.ts +48 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/tags.js +99 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/types.d.ts +92 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/test-events.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/test-events.js +134 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/util.d.ts +16 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/util.js +28 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/visit.d.ts +102 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/visit.js +236 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/package.json +97 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/util.js +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +3385 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/package.json +25 -0
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +64 -0
- package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +40 -0
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +422 -0
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +13 -0
- package/dist/builtin-views/canvas/core.mjs +82 -1
- package/dist/builtin-views/canvas/tui.mjs +9 -5
- package/dist/builtin-views/canvas/web.jsx +3 -2
- package/dist/builtin-views/chat/core.mjs +725 -0
- package/dist/builtin-views/chat/text.mjs +101 -0
- package/dist/builtin-views/chat/tui.mjs +368 -0
- package/dist/builtin-views/chat/web.jsx +367 -0
- package/dist/builtin-views/prompt-review/core.mjs +863 -0
- package/dist/builtin-views/prompt-review/text.mjs +15 -0
- package/dist/builtin-views/prompt-review/tui.mjs +196 -0
- package/dist/builtin-views/prompt-review/web.jsx +484 -0
- package/dist/builtin-views/settings/core.mjs +397 -0
- package/dist/builtin-views/settings/text.mjs +40 -0
- package/dist/builtin-views/settings/tui.mjs +95 -0
- package/dist/builtin-views/settings/web.jsx +167 -0
- package/dist/cli.js +16 -3
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +55 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +2340 -0
- package/dist/clients/attach/attach-cmd.d.ts +18 -0
- package/dist/clients/attach/attach-cmd.js +1797 -797
- package/dist/clients/attach/auth-pickers.js +2 -2
- package/dist/clients/attach/canvas-panels.js +3 -3
- package/dist/clients/attach/chat-view.d.ts +13 -2
- package/dist/clients/attach/chat-view.js +53 -10
- package/dist/clients/attach/clipboard-image.js +1 -1
- package/dist/clients/attach/clipboard-text.js +1 -1
- package/dist/clients/attach/config-load.d.ts +8 -4
- package/dist/clients/attach/config-load.js +3 -1
- package/dist/clients/attach/crtr-output-render.d.ts +48 -0
- package/dist/clients/attach/crtr-output-render.js +646 -0
- package/dist/clients/attach/extension-dialogs.d.ts +2 -3
- package/dist/clients/attach/extension-dialogs.js +8 -10
- package/dist/clients/attach/graph-overlay.js +6 -5
- package/dist/clients/attach/input-controller.d.ts +4 -3
- package/dist/clients/attach/input-controller.js +25 -4
- package/dist/clients/attach/pickers.js +1 -1
- package/dist/clients/attach/slash-commands.d.ts +7 -7
- package/dist/clients/attach/slash-commands.js +40 -17
- package/dist/clients/attach/titled-editor.d.ts +4 -0
- package/dist/clients/attach/titled-editor.js +31 -15
- package/dist/clients/attach/view-socket.d.ts +2 -1
- package/dist/clients/attach/view-socket.js +28 -9
- package/dist/clients/web/__tests__/source-cache.test.js +32 -0
- package/dist/clients/web/dev-server.js +1 -0
- package/dist/clients/web/events.js +9 -11
- package/dist/clients/web/server.d.ts +4 -3
- package/dist/clients/web/server.js +166 -35
- package/dist/clients/web/source-cache.d.ts +10 -0
- package/dist/clients/web/source-cache.js +57 -0
- package/dist/clients/web/web-cmd.js +32 -13
- package/dist/commands/__tests__/human.test.js +30 -28
- package/dist/commands/canvas-history/search.js +1 -1
- package/dist/commands/canvas-history.js +5 -8
- package/dist/commands/canvas-issue.d.ts +2 -0
- package/dist/commands/canvas-issue.js +148 -0
- package/dist/commands/canvas-prune.js +43 -2
- package/dist/commands/canvas-rebuild-index.d.ts +2 -0
- package/dist/commands/canvas-rebuild-index.js +57 -0
- package/dist/commands/canvas-snapshot.d.ts +2 -0
- package/dist/commands/canvas-snapshot.js +51 -0
- package/dist/commands/canvas-tmux-spread.d.ts +2 -0
- package/dist/commands/canvas-tmux-spread.js +188 -0
- package/dist/commands/canvas.d.ts +1 -0
- package/dist/commands/canvas.js +16 -1
- package/dist/commands/chord.js +143 -48
- package/dist/commands/daemon.js +3 -3
- package/dist/commands/human/prompts.d.ts +0 -1
- package/dist/commands/human/prompts.js +39 -54
- package/dist/commands/human/queue.d.ts +12 -1
- package/dist/commands/human/queue.js +468 -73
- package/dist/commands/human/shared.d.ts +3 -4
- package/dist/commands/human/shared.js +3 -3
- package/dist/commands/human.js +12 -11
- package/dist/commands/memory/find.js +3 -2
- package/dist/commands/memory/lint.js +15 -8
- package/dist/commands/memory/list.js +4 -3
- package/dist/commands/memory/origin.d.ts +1 -0
- package/dist/commands/memory/origin.js +88 -0
- package/dist/commands/memory/read.js +3 -3
- package/dist/commands/memory/shared.d.ts +9 -4
- package/dist/commands/memory/shared.js +28 -10
- package/dist/commands/memory/write.js +19 -14
- package/dist/commands/memory.js +6 -8
- package/dist/commands/node-context.d.ts +2 -0
- package/dist/commands/node-context.js +172 -0
- package/dist/commands/node-snapshot.d.ts +2 -0
- package/dist/commands/node-snapshot.js +126 -0
- package/dist/commands/node.d.ts +2 -2
- package/dist/commands/node.js +987 -603
- package/dist/commands/pkg/market-inspect.d.ts +2 -1
- package/dist/commands/pkg/market-inspect.js +14 -32
- package/dist/commands/pkg/market-manage.d.ts +3 -1
- package/dist/commands/pkg/market-manage.js +58 -154
- package/dist/commands/pkg/market.js +7 -7
- package/dist/commands/pkg/plugin-inspect.d.ts +2 -1
- package/dist/commands/pkg/plugin-inspect.js +13 -29
- package/dist/commands/pkg/plugin-manage.d.ts +5 -1
- package/dist/commands/pkg/plugin-manage.js +246 -172
- package/dist/commands/pkg/plugin.js +6 -6
- package/dist/commands/pkg.js +7 -7
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +79 -11
- package/dist/commands/revive.js +79 -4
- package/dist/commands/search/answer.d.ts +1 -0
- package/dist/commands/search/answer.js +50 -0
- package/dist/commands/search/contents.d.ts +1 -0
- package/dist/commands/search/contents.js +96 -0
- package/dist/commands/search/exa.d.ts +53 -0
- package/dist/commands/search/exa.js +155 -0
- package/dist/commands/search/puremd.d.ts +11 -0
- package/dist/commands/search/puremd.js +53 -0
- package/dist/commands/search/web.d.ts +1 -0
- package/dist/commands/search/web.js +65 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +24 -0
- package/dist/commands/surface.d.ts +2 -0
- package/dist/commands/surface.js +43 -0
- package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
- package/dist/commands/sys/config.js +59 -10
- package/dist/commands/sys/doctor.js +56 -4
- package/dist/commands/sys/prompt-review.d.ts +1 -0
- package/dist/commands/sys/prompt-review.js +178 -0
- package/dist/commands/sys/promptstudio.d.ts +2 -0
- package/dist/commands/sys/promptstudio.js +65 -0
- package/dist/commands/sys/settings.d.ts +2 -0
- package/dist/commands/sys/settings.js +16 -0
- package/dist/commands/sys/setup-core.d.ts +88 -0
- package/dist/commands/sys/setup-core.js +352 -0
- package/dist/commands/sys/setup.d.ts +2 -0
- package/dist/commands/sys/setup.js +516 -0
- package/dist/commands/sys/sync.js +311 -159
- package/dist/commands/sys/sysprompt.d.ts +1 -0
- package/dist/commands/sys/sysprompt.js +89 -0
- package/dist/commands/sys.js +10 -5
- package/dist/commands/view-cycle.js +5 -5
- package/dist/commands/view-list.js +3 -3
- package/dist/commands/view-new.js +6 -6
- package/dist/commands/view-pick.js +6 -6
- package/dist/commands/view-run.js +14 -14
- package/dist/commands/view.js +3 -3
- package/dist/commands/workspace.js +10 -10
- package/dist/core/__tests__/boot.test.js +140 -0
- package/dist/core/__tests__/broker-preflight.test.js +85 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +10 -16
- package/dist/core/__tests__/canvas.test.js +37 -1
- package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
- package/dist/core/__tests__/child-death-wake.test.js +11 -2
- package/dist/core/__tests__/close.test.js +39 -1
- package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
- package/dist/core/__tests__/context-intro.test.js +48 -14
- package/dist/core/__tests__/daemon-boot.test.js +204 -9
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +157 -18
- package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
- package/dist/core/__tests__/full/broker-attach-limits.test.js +1 -1
- package/dist/core/__tests__/full/broker-attach-stream.test.js +1 -1
- package/dist/core/__tests__/full/broker-crash-teardown.test.js +10 -5
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +1 -1
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +1 -1
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +1 -1
- package/dist/core/__tests__/full/cascade-close.test.js +31 -18
- package/dist/core/__tests__/full/detach-focus.test.js +2 -2
- package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
- package/dist/core/__tests__/hearth-bootstrap.test.js +136 -0
- package/dist/core/__tests__/helpers/harness.d.ts +1 -1
- package/dist/core/__tests__/helpers/harness.js +3 -3
- package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
- package/dist/core/__tests__/human-surface-target.test.js +1 -1
- package/dist/core/__tests__/kickoff.test.js +18 -19
- package/dist/core/__tests__/live-mutation-verbs.test.js +10 -10
- package/dist/core/__tests__/live-mutation.test.js +8 -8
- package/dist/core/__tests__/migration.test.js +172 -13
- package/dist/core/__tests__/on-read-dedup-resume.test.js +40 -0
- package/dist/core/__tests__/preview-registry-sync.test.js +36 -0
- package/dist/core/__tests__/prune-to-limit.test.js +91 -0
- package/dist/core/__tests__/push-final-guard.test.js +7 -1
- package/dist/core/__tests__/relaunch-root.test.js +9 -9
- package/dist/core/__tests__/revive.test.js +100 -27
- package/dist/core/__tests__/spawn-root.test.js +24 -1
- package/dist/core/__tests__/stop-guard.test.js +10 -0
- package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
- package/dist/core/__tests__/tmux-surface.test.js +5 -1
- package/dist/core/__tests__/trigger-migration.test.js +48 -0
- package/dist/core/__tests__/wake-bearings.test.js +23 -21
- package/dist/core/__tests__/wake-origin.test.js +36 -33
- package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
- package/dist/core/artifact.d.ts +2 -33
- package/dist/core/artifact.js +27 -87
- package/dist/core/bootstrap.js +2 -0
- package/dist/core/canvas/boot.d.ts +48 -0
- package/dist/core/canvas/boot.js +157 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
- package/dist/core/canvas/browse/app.js +87 -17
- package/dist/core/canvas/browse/model.d.ts +10 -1
- package/dist/core/canvas/browse/model.js +37 -1
- package/dist/core/canvas/browse/render.js +35 -33
- package/dist/core/canvas/canvas.d.ts +33 -0
- package/dist/core/canvas/canvas.js +138 -8
- package/dist/core/canvas/db.js +121 -9
- package/dist/core/canvas/index.d.ts +1 -1
- package/dist/core/canvas/index.js +1 -1
- package/dist/core/canvas/labels.d.ts +6 -8
- package/dist/core/canvas/labels.js +8 -11
- package/dist/core/canvas/nav-model.d.ts +5 -4
- package/dist/core/canvas/nav-model.js +32 -18
- package/dist/core/canvas/paths.d.ts +18 -1
- package/dist/core/canvas/paths.js +31 -2
- package/dist/core/canvas/pid.d.ts +6 -0
- package/dist/core/canvas/pid.js +13 -0
- package/dist/core/canvas/render.d.ts +25 -0
- package/dist/core/canvas/render.js +96 -13
- package/dist/core/canvas/status-glyph.d.ts +35 -0
- package/dist/core/canvas/status-glyph.js +104 -0
- package/dist/core/canvas/triggers.d.ts +108 -0
- package/dist/core/canvas/triggers.js +320 -0
- package/dist/core/canvas/types.d.ts +130 -48
- package/dist/core/command.js +13 -4
- package/dist/core/config.js +114 -16
- package/dist/core/fault-classifier.d.ts +41 -0
- package/dist/core/fault-classifier.js +110 -0
- package/dist/core/feed/inbox.d.ts +15 -0
- package/dist/core/feed/inbox.js +40 -14
- package/dist/core/frontmatter.d.ts +1 -11
- package/dist/core/frontmatter.js +1 -55
- package/dist/core/hearth/__tests__/model-auth-guest.test.js +151 -0
- package/dist/core/hearth/config.d.ts +3 -0
- package/dist/core/hearth/config.js +111 -0
- package/dist/core/hearth/guest-env.d.ts +9 -0
- package/dist/core/hearth/guest-env.js +27 -0
- package/dist/core/hearth/index.d.ts +5 -0
- package/dist/core/hearth/index.js +5 -0
- package/dist/core/hearth/model-auth-guest.d.ts +8 -0
- package/dist/core/hearth/model-auth-guest.js +435 -0
- package/dist/core/hearth/provider.d.ts +28 -0
- package/dist/core/hearth/provider.js +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +12 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.js +147 -0
- package/dist/core/hearth/providers/blaxel-home.d.ts +15 -0
- package/dist/core/hearth/providers/blaxel-home.js +136 -0
- package/dist/core/hearth/providers/blaxel.d.ts +31 -0
- package/dist/core/hearth/providers/blaxel.js +282 -0
- package/dist/core/hearth/providers/types.d.ts +77 -0
- package/dist/core/hearth/registry.d.ts +15 -0
- package/dist/core/hearth/registry.js +180 -0
- package/dist/core/hearth/types.d.ts +156 -0
- package/dist/core/help.d.ts +16 -0
- package/dist/core/help.js +2 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
- package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
- package/dist/core/host-exports/builtins.js +68 -0
- package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
- package/dist/core/{skill-sync → host-exports}/export.js +37 -14
- package/dist/core/io.js +1 -1
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +113 -0
- package/dist/core/memory-resolver.d.ts +13 -13
- package/dist/core/memory-resolver.js +70 -51
- package/dist/core/personas/index.d.ts +4 -4
- package/dist/core/personas/index.js +2 -2
- package/dist/core/personas/loader.d.ts +51 -0
- package/dist/core/personas/loader.js +54 -50
- package/dist/core/personas/resolve.d.ts +43 -27
- package/dist/core/personas/resolve.js +336 -94
- package/dist/core/preview-registry.d.ts +25 -0
- package/dist/core/preview-registry.js +2047 -0
- package/dist/core/resolver.d.ts +1 -0
- package/dist/core/resolver.js +23 -16
- package/dist/core/runtime/auth-reload.d.ts +7 -0
- package/dist/core/runtime/auth-reload.js +69 -0
- package/dist/core/runtime/bearings.d.ts +46 -25
- package/dist/core/runtime/bearings.js +313 -52
- package/dist/core/runtime/branded-host.d.ts +4 -2
- package/dist/core/runtime/branded-host.js +66 -4
- package/dist/core/runtime/broker-protocol.d.ts +40 -5
- package/dist/core/runtime/broker-protocol.js +2 -2
- package/dist/core/runtime/broker.js +343 -18
- package/dist/core/runtime/close.js +39 -31
- package/dist/core/runtime/connectivity.d.ts +12 -0
- package/dist/core/runtime/connectivity.js +73 -0
- package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
- package/dist/core/runtime/fault-recovery-nudge.js +2 -0
- package/dist/core/runtime/fault-recovery.d.ts +23 -0
- package/dist/core/runtime/fault-recovery.js +74 -0
- package/dist/core/runtime/fault.d.ts +25 -0
- package/dist/core/runtime/fault.js +176 -0
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/host.d.ts +10 -0
- package/dist/core/runtime/host.js +71 -9
- package/dist/core/runtime/kickoff.js +32 -46
- package/dist/core/runtime/launch.d.ts +41 -21
- package/dist/core/runtime/launch.js +165 -52
- package/dist/core/runtime/lifecycle.js +7 -7
- package/dist/core/runtime/model-swap.d.ts +18 -0
- package/dist/core/runtime/model-swap.js +95 -0
- package/dist/core/runtime/naming.d.ts +7 -0
- package/dist/core/runtime/naming.js +61 -9
- package/dist/core/runtime/nodes.js +6 -1
- package/dist/core/runtime/persona.js +16 -17
- package/dist/core/runtime/placement.d.ts +41 -36
- package/dist/core/runtime/placement.js +159 -74
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +23 -3
- package/dist/core/runtime/recycle.js +13 -11
- package/dist/core/runtime/reset.d.ts +6 -3
- package/dist/core/runtime/reset.js +5 -5
- package/dist/core/runtime/revive-all.d.ts +5 -2
- package/dist/core/runtime/revive-all.js +5 -2
- package/dist/core/runtime/revive.d.ts +19 -8
- package/dist/core/runtime/revive.js +79 -25
- package/dist/core/runtime/roadmap.d.ts +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/runtime/spawn.d.ts +3 -3
- package/dist/core/runtime/spawn.js +37 -13
- package/dist/core/runtime/stop-guard.js +11 -6
- package/dist/core/runtime/surface-bg.js +1 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +30 -7
- package/dist/core/runtime/tmux.js +138 -84
- package/dist/core/runtime/view-socket-client.d.ts +46 -0
- package/dist/core/runtime/view-socket-client.js +203 -0
- package/dist/core/scope.d.ts +9 -0
- package/dist/core/scope.js +37 -15
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/on-read.js +39 -25
- package/dist/core/substrate/render.d.ts +10 -13
- package/dist/core/substrate/render.js +56 -51
- package/dist/core/tui/host.js +83 -18
- package/dist/core/view/__tests__/transport-cache.test.js +62 -0
- package/dist/core/view/contract.d.ts +20 -0
- package/dist/core/view/loader.js +2 -2
- package/dist/core/view/stream-local.d.ts +3 -0
- package/dist/core/view/stream-local.js +184 -0
- package/dist/core/view/transport-cache.d.ts +8 -0
- package/dist/core/view/transport-cache.js +38 -0
- package/dist/core/view/transport-local.js +4 -0
- package/dist/core/view/transport.d.ts +7 -1
- package/dist/core/wake.d.ts +2 -2
- package/dist/core/wake.js +7 -7
- package/dist/daemon/__tests__/predicate-eval.test.js +109 -0
- package/dist/daemon/crtrd.d.ts +3 -19
- package/dist/daemon/crtrd.js +360 -280
- package/dist/daemon/manage.d.ts +18 -1
- package/dist/daemon/manage.js +54 -9
- package/dist/daemon/predicate-eval.d.ts +52 -0
- package/dist/daemon/predicate-eval.js +248 -0
- package/dist/hearth/control-plane/__tests__/node-message.test.js +60 -0
- package/dist/hearth/control-plane/__tests__/rate-limit-recurrence.test.js +49 -0
- package/dist/hearth/control-plane/__tests__/relay-security.test.js +314 -0
- package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.js +133 -0
- package/dist/hearth/control-plane/__tests__/trigger-delivery.test.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/trigger-delivery.test.js +170 -0
- package/dist/hearth/control-plane/__tests__/webhook-ingress.test.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/webhook-ingress.test.js +167 -0
- package/dist/hearth/control-plane/config.d.ts +21 -0
- package/dist/hearth/control-plane/config.js +77 -0
- package/dist/hearth/control-plane/db.d.ts +30 -0
- package/dist/hearth/control-plane/db.js +534 -0
- package/dist/hearth/control-plane/ingress/rate-limit.d.ts +24 -0
- package/dist/hearth/control-plane/ingress/rate-limit.js +100 -0
- package/dist/hearth/control-plane/ingress/route-store.d.ts +31 -0
- package/dist/hearth/control-plane/ingress/route-store.js +61 -0
- package/dist/hearth/control-plane/ingress/webhook-delivery-store.d.ts +41 -0
- package/dist/hearth/control-plane/ingress/webhook-delivery-store.js +69 -0
- package/dist/hearth/control-plane/ingress/webhook-route.d.ts +55 -0
- package/dist/hearth/control-plane/ingress/webhook-route.js +285 -0
- package/dist/hearth/control-plane/main.d.ts +1 -0
- package/dist/hearth/control-plane/main.js +88 -0
- package/dist/hearth/control-plane/node-message.d.ts +31 -0
- package/dist/hearth/control-plane/node-message.js +98 -0
- package/dist/hearth/control-plane/register.d.ts +15 -0
- package/dist/hearth/control-plane/register.js +34 -0
- package/dist/hearth/control-plane/registry.d.ts +17 -0
- package/dist/hearth/control-plane/registry.js +161 -0
- package/dist/hearth/control-plane/relay.d.ts +40 -0
- package/dist/hearth/control-plane/relay.js +642 -0
- package/dist/hearth/control-plane/scheduler/fire-store.d.ts +36 -0
- package/dist/hearth/control-plane/scheduler/fire-store.js +73 -0
- package/dist/hearth/control-plane/scheduler/recurrence.d.ts +7 -0
- package/dist/hearth/control-plane/scheduler/recurrence.js +58 -0
- package/dist/hearth/control-plane/scheduler/scan-loop.d.ts +38 -0
- package/dist/hearth/control-plane/scheduler/scan-loop.js +138 -0
- package/dist/hearth/control-plane/scheduler/schedule-store.d.ts +32 -0
- package/dist/hearth/control-plane/scheduler/schedule-store.js +66 -0
- package/dist/hearth/control-plane/secrets.d.ts +17 -0
- package/dist/hearth/control-plane/secrets.js +113 -0
- package/dist/hearth/control-plane/server.d.ts +27 -0
- package/dist/hearth/control-plane/server.js +346 -0
- package/dist/hearth/control-plane/session.d.ts +68 -0
- package/dist/hearth/control-plane/session.js +273 -0
- package/dist/hearth/control-plane/triggers/acl.d.ts +14 -0
- package/dist/hearth/control-plane/triggers/acl.js +52 -0
- package/dist/hearth/control-plane/triggers/audit-store.d.ts +38 -0
- package/dist/hearth/control-plane/triggers/audit-store.js +79 -0
- package/dist/hearth/control-plane/triggers/deliver.d.ts +43 -0
- package/dist/hearth/control-plane/triggers/deliver.js +76 -0
- package/dist/hearth/control-plane/triggers/envelope.d.ts +29 -0
- package/dist/hearth/control-plane/triggers/envelope.js +38 -0
- package/dist/hearth/control-plane/types.d.ts +66 -0
- package/dist/hearth/control-plane/types.js +1 -0
- package/dist/hearth/control-plane/wake.d.ts +80 -0
- package/dist/hearth/control-plane/wake.js +312 -0
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
- package/dist/hearth/wake-proxy/auth.d.ts +22 -0
- package/dist/hearth/wake-proxy/auth.js +128 -0
- package/dist/hearth/wake-proxy/config.d.ts +2 -0
- package/dist/hearth/wake-proxy/config.js +151 -0
- package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
- package/dist/hearth/wake-proxy/guest-source.js +130 -0
- package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
- package/dist/hearth/wake-proxy/hearth-status.js +267 -0
- package/dist/hearth/wake-proxy/home.d.ts +61 -0
- package/dist/hearth/wake-proxy/home.js +333 -0
- package/dist/hearth/wake-proxy/main.d.ts +1 -0
- package/dist/hearth/wake-proxy/main.js +134 -0
- package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
- package/dist/hearth/wake-proxy/model-auth.js +345 -0
- package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
- package/dist/hearth/wake-proxy/proxy.js +716 -0
- package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
- package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
- package/dist/hearth/wake-proxy/redact.d.ts +1 -0
- package/dist/hearth/wake-proxy/redact.js +17 -0
- package/dist/hearth/wake-proxy/server.d.ts +11 -0
- package/dist/hearth/wake-proxy/server.js +142 -0
- package/dist/hearth/wake-proxy/state.d.ts +18 -0
- package/dist/hearth/wake-proxy/state.js +342 -0
- package/dist/hearth/wake-proxy/types.d.ts +76 -0
- package/dist/hearth/wake-proxy/types.js +1 -0
- package/dist/index.js +3 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
- package/dist/pi-extensions/canvas-context-intro.js +7 -7
- package/dist/pi-extensions/canvas-doc-substrate.js +47 -24
- package/dist/pi-extensions/canvas-nav.js +16 -9
- package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
- package/dist/pi-extensions/canvas-stophook.js +78 -11
- package/dist/prompts/view.js +7 -8
- package/dist/types.d.ts +39 -20
- package/dist/types.js +38 -13
- package/dist/web/runtime.js +141 -42
- package/dist/web/transport-http.js +7 -5
- package/dist/web/transport-stream.d.ts +3 -0
- package/dist/web/transport-stream.js +204 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
- package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
- package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
- package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
- package/dist/web-client/assets/index-BRKxe-hy.js +80 -0
- package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
- package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
- package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
- package/dist/web-client/index.html +3 -2
- package/package.json +37 -11
- package/scripts/postinstall.mjs +8 -0
- package/dist/clients/attach/__tests__/action-parity.test.js +0 -48
- package/dist/clients/attach/__tests__/clipboard-image.test.js +0 -76
- package/dist/clients/attach/__tests__/editor-newline.test.js +0 -82
- package/dist/clients/attach/__tests__/git-info.test.js +0 -31
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +0 -20
- package/dist/clients/attach/__tests__/reconnect-giveup.test.js +0 -30
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +0 -42
- package/dist/clients/attach/__tests__/titled-editor.test.js +0 -39
- package/dist/commands/memory/__tests__/lint-schema.test.js +0 -52
- package/dist/core/__tests__/argv-parser.test.js +0 -217
- package/dist/core/__tests__/draw-style.test.js +0 -258
- package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
- package/dist/core/__tests__/frame-decoder-perf.test.js +0 -224
- package/dist/core/__tests__/keystone.test.js +0 -185
- package/dist/core/__tests__/listing-completeness.test.js +0 -31
- package/dist/core/__tests__/memory-resolver.test.js +0 -108
- package/dist/core/__tests__/memory.test.js +0 -60
- package/dist/core/__tests__/pane-column.test.js +0 -153
- package/dist/core/__tests__/persona-compose.test.js +0 -53
- package/dist/core/__tests__/persona-subkind.test.js +0 -65
- package/dist/core/__tests__/persona.test.js +0 -107
- package/dist/core/__tests__/unknown-path.test.js +0 -58
- package/dist/core/canvas/wakeups.d.ts +0 -76
- package/dist/core/canvas/wakeups.js +0 -185
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
- package/dist/core/skill-sync/builtins.js +0 -112
- package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
- package/dist/core/skill-sync/claude-plugins.js +0 -71
- package/dist/core/skill-sync/engine.d.ts +0 -42
- package/dist/core/skill-sync/engine.js +0 -633
- package/dist/core/skill-sync/manifest.d.ts +0 -64
- package/dist/core/skill-sync/manifest.js +0 -181
- package/dist/core/skill-sync/profile.d.ts +0 -76
- package/dist/core/skill-sync/profile.js +0 -173
- package/dist/core/skill-sync/snapshot.d.ts +0 -57
- package/dist/core/skill-sync/snapshot.js +0 -120
- package/dist/pi-extensions/canvas-commands.d.ts +0 -37
- package/dist/pi-extensions/canvas-commands.js +0 -113
- package/dist/pi-extensions/canvas-resume.d.ts +0 -21
- package/dist/pi-extensions/canvas-resume.js +0 -83
- package/dist/pi-extensions/canvas-view.d.ts +0 -21
- package/dist/pi-extensions/canvas-view.js +0 -76
- package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
- package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
- /package/dist/clients/attach/__tests__/{action-parity.test.d.ts → crtr-output-coverage.test.d.ts} +0 -0
- /package/dist/clients/attach/__tests__/{clipboard-image.test.d.ts → crtr-output-render.test.d.ts} +0 -0
- /package/dist/clients/{attach/__tests__/editor-newline.test.d.ts → web/__tests__/source-cache.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/git-info.test.d.ts → commands/sys/__tests__/sync-import.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/onrequest-wired.test.d.ts → core/__tests__/boot.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/reconnect-giveup.test.d.ts → core/__tests__/broker-preflight.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/slash-quit-copy.test.d.ts → core/__tests__/chat-view-reconnect.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/titled-editor.test.d.ts → core/__tests__/connection-reconnect-resume.test.d.ts} +0 -0
- /package/dist/{commands/memory/__tests__/lint-schema.test.d.ts → core/__tests__/hearth-bootstrap.test.d.ts} +0 -0
- /package/dist/core/__tests__/{argv-parser.test.d.ts → preview-registry-sync.test.d.ts} +0 -0
- /package/dist/core/__tests__/{draw-style.test.d.ts → prune-to-limit.test.d.ts} +0 -0
- /package/dist/core/__tests__/{error-stall-recycle.test.d.ts → stranded-relaunch.test.d.ts} +0 -0
- /package/dist/core/__tests__/{frame-decoder-perf.test.d.ts → trigger-migration.test.d.ts} +0 -0
- /package/dist/core/__tests__/{keystone.test.d.ts → yield-ensures-daemon.test.d.ts} +0 -0
- /package/dist/core/{__tests__/listing-completeness.test.d.ts → hearth/__tests__/model-auth-guest.test.d.ts} +0 -0
- /package/dist/core/{__tests__/memory-resolver.test.d.ts → hearth/providers/types.js} +0 -0
- /package/dist/core/{__tests__/memory.test.d.ts → hearth/types.js} +0 -0
- /package/dist/core/{__tests__/pane-column.test.d.ts → host-exports/__tests__/export-prunes-boot-skill.test.d.ts} +0 -0
- /package/dist/core/{__tests__/persona-compose.test.d.ts → view/__tests__/transport-cache.test.d.ts} +0 -0
- /package/dist/{core/__tests__/persona-subkind.test.d.ts → daemon/__tests__/predicate-eval.test.d.ts} +0 -0
- /package/dist/{core/__tests__/persona.test.d.ts → hearth/control-plane/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/{core/__tests__/unknown-path.test.d.ts → hearth/control-plane/__tests__/rate-limit-recurrence.test.d.ts} +0 -0
- /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → hearth/control-plane/__tests__/relay-security.test.d.ts} +0 -0
|
@@ -0,0 +1,2047 @@
|
|
|
1
|
+
// Bundle-safe mirror of LeafHelp.preview metadata (see help.ts's PreviewMeta),
|
|
2
|
+
// keyed by the leaf's full help name (e.g. "canvas snapshot", matching
|
|
3
|
+
// LeafHelp.name exactly).
|
|
4
|
+
//
|
|
5
|
+
// This file has NO imports. The attach viewer is bundled as a separate
|
|
6
|
+
// esbuild process from the one that runs a leaf, so it cannot read a leaf's
|
|
7
|
+
// command-owned `preview` field directly — walking the live command tree
|
|
8
|
+
// to find it would pull every subtree's module graph (including `surface`
|
|
9
|
+
// -> the web client -> Tailwind's native oxide binary) into the attach
|
|
10
|
+
// bundle. A flat, zero-dependency static map avoids that entirely.
|
|
11
|
+
//
|
|
12
|
+
// Source of truth: this Set must mirror every leaf that declares
|
|
13
|
+
// `help.preview.suppressOutput: true` in its own command file (currently
|
|
14
|
+
// canvas snapshot, node inspect snapshot, node inspect show, and sys
|
|
15
|
+
// sysprompt). There is no automated sync — when you add or remove a leaf's
|
|
16
|
+
// `preview.suppressOutput`, update this Set in the same commit.
|
|
17
|
+
// `preview-registry-sync.test.ts` (which imports the real command tree — safe
|
|
18
|
+
// in a test, never in the attach bundle) asserts the two stay identical.
|
|
19
|
+
// Exported (not just consumed through the helpers below) so
|
|
20
|
+
// preview-registry-sync.test.ts can diff it against the live command tree.
|
|
21
|
+
export const SUPPRESS_OUTPUT_LEAVES = new Set([
|
|
22
|
+
'canvas snapshot',
|
|
23
|
+
'node inspect snapshot',
|
|
24
|
+
'node inspect show',
|
|
25
|
+
'sys sysprompt',
|
|
26
|
+
]);
|
|
27
|
+
/** True when the leaf at this full help-name path (e.g. "canvas snapshot")
|
|
28
|
+
* declares `preview.suppressOutput: true`. */
|
|
29
|
+
export function suppressOutputForLeaf(helpName) {
|
|
30
|
+
return SUPPRESS_OUTPUT_LEAVES.has(helpName);
|
|
31
|
+
}
|
|
32
|
+
/** Same lookup from argv tokens (e.g. ['node', 'inspect', 'snapshot', 'abc'])
|
|
33
|
+
* instead of a pre-joined help name. Matches a registered leaf name against
|
|
34
|
+
* a *prefix* of the tokens, not the whole joined tail, so a leaf's own
|
|
35
|
+
* positional arguments (e.g. the snapshot id in `node inspect snapshot
|
|
36
|
+
* abc`) don't prevent the match. */
|
|
37
|
+
export function suppressOutputForPath(tokens) {
|
|
38
|
+
for (const leaf of SUPPRESS_OUTPUT_LEAVES) {
|
|
39
|
+
const words = leaf.split(' ');
|
|
40
|
+
if (words.length <= tokens.length && words.every((word, i) => tokens[i] === word))
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Icon registry (rule 12, inline-ui-placement: real Nerd Font glyphs, not the
|
|
47
|
+
// generic ◆ diamond). Every leaf's glyph is looked up first in LEAF_ICON_CP
|
|
48
|
+
// (longest-prefix match, same shape as SUPPRESS_OUTPUT_LEAVES); an unmapped
|
|
49
|
+
// leaf falls back to its root family's glyph in FAMILY_ICON_CP.
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
/** question-circle — the header glyph for a `-h`/`--help` invocation,
|
|
52
|
+
* regardless of which command's help was requested. */
|
|
53
|
+
const HELP_ICON_CP = 0xf059;
|
|
54
|
+
export const HELP_ICON = String.fromCodePoint(HELP_ICON_CP);
|
|
55
|
+
/** One glyph per root subtree (`crtr <family> ...`) — the fallback for any
|
|
56
|
+
* leaf not listed in LEAF_ICON_CP below. Codepoints are Nerd Font
|
|
57
|
+
* private-use-area icons chosen per family concept (see command-icons.md). */
|
|
58
|
+
const FAMILY_ICON_CP = {
|
|
59
|
+
canvas: 0xf0e8,
|
|
60
|
+
feed: 0xf09e,
|
|
61
|
+
human: 0xf007,
|
|
62
|
+
memory: 0xf02d,
|
|
63
|
+
node: 0xf192,
|
|
64
|
+
pkg: 0xf1b2,
|
|
65
|
+
push: 0xf0ee,
|
|
66
|
+
search: 0xf002,
|
|
67
|
+
surface: 0xf108,
|
|
68
|
+
sys: 0xf013,
|
|
69
|
+
};
|
|
70
|
+
/** Per-leaf overrides, keyed by full leaf help-name path (e.g. "memory
|
|
71
|
+
* lint"), same shape as SUPPRESS_OUTPUT_LEAVES. */
|
|
72
|
+
const LEAF_ICON_CP = {
|
|
73
|
+
'canvas dashboard': 0xf0e4,
|
|
74
|
+
'canvas snapshot': 0xf030,
|
|
75
|
+
'canvas browse': 0xf14e,
|
|
76
|
+
'canvas attention count': 0xf292,
|
|
77
|
+
'canvas attention list': 0xf03a,
|
|
78
|
+
'canvas attention map': 0xf279,
|
|
79
|
+
'canvas revive': 0xf021,
|
|
80
|
+
'canvas history search': 0xf002,
|
|
81
|
+
'canvas history read': 0xf02d,
|
|
82
|
+
'canvas history show': 0xf1da,
|
|
83
|
+
'canvas daemon start': 0xf04b,
|
|
84
|
+
'canvas daemon status': 0xf111,
|
|
85
|
+
'canvas daemon stop': 0xf04d,
|
|
86
|
+
'canvas chord': 0xf001,
|
|
87
|
+
'canvas tmux-spread': 0xf009,
|
|
88
|
+
'canvas issue new': 0xf188,
|
|
89
|
+
'canvas prune': 0xf0c4,
|
|
90
|
+
'canvas rebuild-index': 0xf0ad,
|
|
91
|
+
'feed read': 0xf02d,
|
|
92
|
+
'feed message': 0xf0e0,
|
|
93
|
+
'feed peek': 0xf06e,
|
|
94
|
+
'human ask': 0xf059,
|
|
95
|
+
'human review': 0xf044,
|
|
96
|
+
'human notify': 0xf0f3,
|
|
97
|
+
'human show': 0xf108,
|
|
98
|
+
'human cancel': 0xf00d,
|
|
99
|
+
'human inbox': 0xf01c,
|
|
100
|
+
'human list': 0xf03a,
|
|
101
|
+
'human deck': 0xf0db,
|
|
102
|
+
'human resolve': 0xf00c,
|
|
103
|
+
'memory list': 0xf03a,
|
|
104
|
+
'memory read': 0xf02d,
|
|
105
|
+
'memory find': 0xf002,
|
|
106
|
+
'memory write': 0xf040,
|
|
107
|
+
'memory origin': 0xf1bb,
|
|
108
|
+
'memory lint': 0xf14a,
|
|
109
|
+
'node new': 0xf067,
|
|
110
|
+
'node msg': 0xf0e0,
|
|
111
|
+
'node subscribe': 0xf0f3,
|
|
112
|
+
'node unsubscribe': 0xf1f6,
|
|
113
|
+
'node promote': 0xf102,
|
|
114
|
+
'node yield': 0xf28d,
|
|
115
|
+
'node focus': 0xf05b,
|
|
116
|
+
'node cycle': 0xf021,
|
|
117
|
+
'node inspect list': 0xf03a,
|
|
118
|
+
'node inspect show': 0xf06e,
|
|
119
|
+
'node inspect snapshot': 0xf030,
|
|
120
|
+
'node inspect context': 0xf07c,
|
|
121
|
+
'node lifecycle recycle': 0xf1b8,
|
|
122
|
+
'node lifecycle close': 0xf00d,
|
|
123
|
+
'node lifecycle demote': 0xf103,
|
|
124
|
+
'node config': 0xf013,
|
|
125
|
+
'node triggers list': 0xf017,
|
|
126
|
+
'node triggers cancel': 0xf05e,
|
|
127
|
+
'pkg plugin install': 0xf019,
|
|
128
|
+
'pkg plugin remove': 0xf014,
|
|
129
|
+
'pkg plugin enable': 0xf00c,
|
|
130
|
+
'pkg plugin disable': 0xf05e,
|
|
131
|
+
'pkg plugin update': 0xf021,
|
|
132
|
+
'pkg plugin list': 0xf03a,
|
|
133
|
+
'pkg plugin show': 0xf06e,
|
|
134
|
+
'pkg market add': 0xf067,
|
|
135
|
+
'pkg market remove': 0xf014,
|
|
136
|
+
'pkg market update': 0xf021,
|
|
137
|
+
'pkg market list': 0xf03a,
|
|
138
|
+
'pkg market browse': 0xf14e,
|
|
139
|
+
'push update': 0xf093,
|
|
140
|
+
'push urgent': 0xf0e7,
|
|
141
|
+
'push final': 0xf11e,
|
|
142
|
+
'search web': 0xf0ac,
|
|
143
|
+
'search answer': 0xf075,
|
|
144
|
+
'search contents': 0xf0f6,
|
|
145
|
+
'surface view list': 0xf03a,
|
|
146
|
+
'surface view run': 0xf04b,
|
|
147
|
+
'surface view cycle': 0xf021,
|
|
148
|
+
'surface view new': 0xf067,
|
|
149
|
+
'surface attach to': 0xf1e6,
|
|
150
|
+
'surface workspace enter': 0xf120,
|
|
151
|
+
'surface web serve': 0xf0ac,
|
|
152
|
+
'sys config get': 0xf019,
|
|
153
|
+
'sys config set': 0xf040,
|
|
154
|
+
'sys config path': 0xf07b,
|
|
155
|
+
'sys settings': 0xf1de,
|
|
156
|
+
'sys setup': 0xf0ad,
|
|
157
|
+
'sys doctor': 0xf0f1,
|
|
158
|
+
'sys sysprompt': 0xf15c,
|
|
159
|
+
'sys prompt-review': 0xf002,
|
|
160
|
+
'sys promptstudio': 0xf008,
|
|
161
|
+
'sys feedback': 0xf075,
|
|
162
|
+
'sys update': 0xf021,
|
|
163
|
+
'sys version': 0xf02b,
|
|
164
|
+
'sys sync': 0xf079,
|
|
165
|
+
};
|
|
166
|
+
/** Resolve the Nerd Font glyph for a command's argv tokens: the longest
|
|
167
|
+
* matching LEAF_ICON_CP prefix, else the tokens[0] family glyph, else the
|
|
168
|
+
* generic ◆ fallback for an unmapped family. */
|
|
169
|
+
export function iconForPath(tokens) {
|
|
170
|
+
let best;
|
|
171
|
+
for (const [leaf, cp] of Object.entries(LEAF_ICON_CP)) {
|
|
172
|
+
const words = leaf.split(' ');
|
|
173
|
+
if (words.length <= tokens.length && words.every((word, i) => tokens[i] === word)) {
|
|
174
|
+
if (best === undefined || words.length > best.words)
|
|
175
|
+
best = { words: words.length, cp };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (best !== undefined)
|
|
179
|
+
return String.fromCodePoint(best.cp);
|
|
180
|
+
const familyCp = tokens.length > 0 ? FAMILY_ICON_CP[tokens[0]] : undefined;
|
|
181
|
+
return familyCp !== undefined ? String.fromCodePoint(familyCp) : '◆';
|
|
182
|
+
}
|
|
183
|
+
const MEMORY_READ_CONTENT_LINES = 3;
|
|
184
|
+
/** `crtr memory read <name>` prints a `- name:/- kind:/- scope:/- path:`
|
|
185
|
+
* metadata block, then `**content:** <body>`, then a `**follow_up:**` hint.
|
|
186
|
+
* The collapsed card is a tidy frontmatter glance (Silas, 2026-07-01,
|
|
187
|
+
* option C): a `name · kind · scope` identity line (path is filesystem
|
|
188
|
+
* noise, dropped; follow_up hint dropped) plus the first few lines of the
|
|
189
|
+
* content as a teaser — the full body is what ctrl+o reveals. Returns null
|
|
190
|
+
* when neither the metadata fields nor the `**content:**` marker are
|
|
191
|
+
* present — an error result (missing/ambiguous doc name, etc.) has none of
|
|
192
|
+
* this shape, and must fall back to the raw output rather than render a
|
|
193
|
+
* blank collapsed card. */
|
|
194
|
+
function summarizeMemoryRead(text) {
|
|
195
|
+
const field = (name) => new RegExp(`^-\\s+${name}:\\s*(.+)$`, 'm').exec(text)?.[1].trim() ?? '';
|
|
196
|
+
const header = [field('name'), field('kind'), field('scope')].filter((f) => f !== '').join(' · ');
|
|
197
|
+
// Content spans from `**content:**` to the `**follow_up:**` hint (or EOF).
|
|
198
|
+
const contentMatch = /\*\*content:\*\*\s*([\s\S]*?)(?:\n\s*\*\*follow_up:\*\*|$)/.exec(text);
|
|
199
|
+
if (header === '' && contentMatch === null)
|
|
200
|
+
return null;
|
|
201
|
+
const contentLines = (contentMatch?.[1] ?? '').replace(/^\s+/, '').replace(/\s+$/, '').split('\n');
|
|
202
|
+
const teaser = contentLines.slice(0, MEMORY_READ_CONTENT_LINES);
|
|
203
|
+
const hasMore = contentLines.slice(MEMORY_READ_CONTENT_LINES).some((l) => l.trim() !== '');
|
|
204
|
+
if (hasMore)
|
|
205
|
+
teaser.push('…');
|
|
206
|
+
const lines = [{ text: header }];
|
|
207
|
+
if (teaser.some((l) => l.trim() !== '')) {
|
|
208
|
+
lines.push({ text: '' });
|
|
209
|
+
for (const l of teaser)
|
|
210
|
+
lines.push({ text: l });
|
|
211
|
+
}
|
|
212
|
+
return lines;
|
|
213
|
+
}
|
|
214
|
+
/** Cap on individual finding rows in the `memory lint` findings-present
|
|
215
|
+
* summary — the count line always shows the true total, this just bounds
|
|
216
|
+
* how many per-finding detail lines get listed before falling back to a
|
|
217
|
+
* muted "… N more" line. */
|
|
218
|
+
const MEMORY_LINT_FINDINGS_CAP = 8;
|
|
219
|
+
/** `crtr memory lint` has two real output shapes (verified against the live
|
|
220
|
+
* command, not assumed):
|
|
221
|
+
* - 0 findings (success return): `- checked: N`, `- corpora: {...}`, a
|
|
222
|
+
* prose line, blank, then `0 findings.`
|
|
223
|
+
* - >0 findings: the leaf THROWS instead of returning, so the CLI error path
|
|
224
|
+
* renders `<error code="error">\nmemory lint: N finding(s) across M
|
|
225
|
+
* files\nNext: ...\n</error>` on stdout, with one `crtr: memory lint:
|
|
226
|
+
* <path>: <error>` line per finding on stderr (merged into the same tool
|
|
227
|
+
* result text when the bash host merges the two streams). Both shapes are
|
|
228
|
+
* parsed defensively; an output that matches neither falls through to the
|
|
229
|
+
* generic truncation path (returns null). */
|
|
230
|
+
function summarizeMemoryLint(text) {
|
|
231
|
+
const checkedMatch = /^- checked:\s*(\d+)\s*$/m.exec(text);
|
|
232
|
+
const successCountMatch = /(\d+)\s+findings\.\s*$/.exec(text.trimEnd());
|
|
233
|
+
if (checkedMatch && successCountMatch) {
|
|
234
|
+
const checked = checkedMatch[1];
|
|
235
|
+
const count = Number(successCountMatch[1]);
|
|
236
|
+
if (count === 0) {
|
|
237
|
+
return [
|
|
238
|
+
{ text: 'PASS', tone: 'success' },
|
|
239
|
+
{ text: `${checked} docs checked`, tone: 'muted' },
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
return [
|
|
243
|
+
{ text: `${count} findings`, tone: 'error' },
|
|
244
|
+
{ text: `${checked} docs checked`, tone: 'muted' },
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
const failureMatch = /(\d+)\s+finding\(s\)\s+across\s+(\d+)\s+files/.exec(text);
|
|
248
|
+
if (failureMatch) {
|
|
249
|
+
const count = Number(failureMatch[1]);
|
|
250
|
+
const checked = failureMatch[2];
|
|
251
|
+
const lines = [{ text: `${count} findings`, tone: 'error' }];
|
|
252
|
+
const findingLines = [...text.matchAll(/^crtr: memory lint: (.+)$/gm)].map((m) => m[1]);
|
|
253
|
+
for (const line of findingLines.slice(0, MEMORY_LINT_FINDINGS_CAP)) {
|
|
254
|
+
lines.push({ text: line, tone: 'warning' });
|
|
255
|
+
}
|
|
256
|
+
const remaining = findingLines.length - MEMORY_LINT_FINDINGS_CAP;
|
|
257
|
+
if (remaining > 0)
|
|
258
|
+
lines.push({ text: `… ${remaining} more`, tone: 'muted' });
|
|
259
|
+
lines.push({ text: `${checked} docs checked`, tone: 'muted' });
|
|
260
|
+
return lines;
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Shared table/count parsing helpers for the canvas/node/feed runtime cockpit
|
|
266
|
+
// batch below. These parse the exact markdown shapes core/render.ts's generic
|
|
267
|
+
// renderArray/renderResult produce (a `- field: N` bullet for a scalar count,
|
|
268
|
+
// an `N <label>:` count lead-in followed by a GFM table for an array of
|
|
269
|
+
// objects, or a bare `0 <label>.` line for an empty array) — strict recognition
|
|
270
|
+
// of that one shape, never a second lenient fallback. Any deviation (missing
|
|
271
|
+
// table, column-count mismatch, non-numeric field expected to be numeric)
|
|
272
|
+
// returns null so the caller falls through to generic truncation.
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
/** Split one GFM table row into its cell strings, honoring `\|` as an escaped
|
|
275
|
+
* literal pipe (the only escape core/render.ts's cellEsc introduces) rather
|
|
276
|
+
* than a column separator. Returns null when the line isn't a `| ... |` row. */
|
|
277
|
+
function splitTableRow(line) {
|
|
278
|
+
const trimmed = line.trim();
|
|
279
|
+
if (!trimmed.startsWith('|') || !trimmed.endsWith('|'))
|
|
280
|
+
return null;
|
|
281
|
+
const body = trimmed.slice(1, -1);
|
|
282
|
+
const cells = [];
|
|
283
|
+
let current = '';
|
|
284
|
+
let i = 0;
|
|
285
|
+
while (i < body.length) {
|
|
286
|
+
const ch = body[i];
|
|
287
|
+
if (ch === '\\' && body[i + 1] === '|') {
|
|
288
|
+
current += '|';
|
|
289
|
+
i += 2;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (ch === '|') {
|
|
293
|
+
cells.push(current.trim());
|
|
294
|
+
current = '';
|
|
295
|
+
i++;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
current += ch;
|
|
299
|
+
i++;
|
|
300
|
+
}
|
|
301
|
+
cells.push(current.trim());
|
|
302
|
+
return cells;
|
|
303
|
+
}
|
|
304
|
+
/** Find the `N <headingLabel>:` + GFM table block renderArray emits for an
|
|
305
|
+
* array-of-objects field named `headingLabel` (e.g. `28 rows:`, `63 nodes:`),
|
|
306
|
+
* and parse every data row into a plain `{column: cellText}` record, using the
|
|
307
|
+
* header row's own column names as keys — no positional/column-index
|
|
308
|
+
* assumptions, so an unrelated extra column (e.g. `hanging`) is carried
|
|
309
|
+
* along and simply ignored by callers that only read specific keys. A bare
|
|
310
|
+
* `0 <headingLabel>.` line (renderArray's empty-array shape) parses as an
|
|
311
|
+
* empty (not null) array. Returns null when the heading is missing, the
|
|
312
|
+
* table shape doesn't match (header/separator/row column-count mismatch), or
|
|
313
|
+
* trailing non-blank content follows the table — never a partial parse. */
|
|
314
|
+
function parseTableRows(text, headingLabel) {
|
|
315
|
+
const lines = text.split('\n');
|
|
316
|
+
for (let i = 0; i < lines.length; i++) {
|
|
317
|
+
const trimmed = lines[i].trim();
|
|
318
|
+
if (trimmed === `0 ${headingLabel}.`)
|
|
319
|
+
return [];
|
|
320
|
+
if (!/^\d+ /.test(trimmed) || !trimmed.endsWith(`${headingLabel}:`))
|
|
321
|
+
continue;
|
|
322
|
+
let j = i + 1;
|
|
323
|
+
while (j < lines.length && lines[j].trim() === '')
|
|
324
|
+
j++;
|
|
325
|
+
if (j + 1 >= lines.length)
|
|
326
|
+
return null;
|
|
327
|
+
const header = splitTableRow(lines[j]);
|
|
328
|
+
const separator = splitTableRow(lines[j + 1]);
|
|
329
|
+
if (header === null || separator === null || header.length === 0 || separator.length !== header.length)
|
|
330
|
+
return null;
|
|
331
|
+
if (separator.some((cell) => !/^-+$/.test(cell)))
|
|
332
|
+
return null;
|
|
333
|
+
const rows = [];
|
|
334
|
+
j += 2;
|
|
335
|
+
while (j < lines.length && lines[j].trim() !== '') {
|
|
336
|
+
const cells = splitTableRow(lines[j]);
|
|
337
|
+
if (cells === null || cells.length !== header.length)
|
|
338
|
+
return null;
|
|
339
|
+
const row = {};
|
|
340
|
+
for (let c = 0; c < header.length; c++)
|
|
341
|
+
row[header[c]] = cells[c];
|
|
342
|
+
rows.push(row);
|
|
343
|
+
j++;
|
|
344
|
+
}
|
|
345
|
+
while (j < lines.length && lines[j].trim() === '')
|
|
346
|
+
j++;
|
|
347
|
+
return j === lines.length ? rows : null;
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
/** Read a `- <field>: <integer>` scalar bullet (core/render.ts's scalar-bullet
|
|
352
|
+
* shape). Returns null when the field isn't present as a plain integer. */
|
|
353
|
+
function parseIntBullet(text, field) {
|
|
354
|
+
const match = new RegExp(`^-\\s+${field}:\\s*(\\d+)\\s*$`, 'm').exec(text);
|
|
355
|
+
return match === null ? null : Number(match[1]);
|
|
356
|
+
}
|
|
357
|
+
/** Compact a token count for a muted aside: 1000+ rounds to the nearest
|
|
358
|
+
* thousand with a `k` suffix (`437000` → `437k`), matching the `ctx 365k`
|
|
359
|
+
* shorthand the dashboard tree already uses elsewhere. */
|
|
360
|
+
function compactThousands(value) {
|
|
361
|
+
return value >= 1000 ? `${Math.round(value / 1000)}k` : String(value);
|
|
362
|
+
}
|
|
363
|
+
/** The trailing path segment of a cwd, for a compact per-cwd label
|
|
364
|
+
* (`/Users/.../cli/crouter` → `crouter`) — a full absolute path is filesystem
|
|
365
|
+
* noise in a collapsed summary line. */
|
|
366
|
+
function basename(path) {
|
|
367
|
+
const trimmed = path.replace(/[\\/]+$/, '');
|
|
368
|
+
const idx = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'));
|
|
369
|
+
return idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
370
|
+
}
|
|
371
|
+
/** Render a `<count> <key>` digest across a fixed preferred key order (each
|
|
372
|
+
* shown only when its count is > 0), then any other key present sorted
|
|
373
|
+
* alphabetically — the `3 active · 24 done` shape both dashboard and node
|
|
374
|
+
* list summaries share. */
|
|
375
|
+
function summarizeOrderedCounts(counts, order) {
|
|
376
|
+
const inOrder = new Set(order);
|
|
377
|
+
const parts = [];
|
|
378
|
+
for (const key of order) {
|
|
379
|
+
const count = counts.get(key) ?? 0;
|
|
380
|
+
if (count > 0)
|
|
381
|
+
parts.push(`${count} ${key}`);
|
|
382
|
+
}
|
|
383
|
+
const extras = [...counts.entries()]
|
|
384
|
+
.filter(([key, count]) => count > 0 && !inOrder.has(key))
|
|
385
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
386
|
+
for (const [key, count] of extras)
|
|
387
|
+
parts.push(`${count} ${key}`);
|
|
388
|
+
return parts.join(' · ');
|
|
389
|
+
}
|
|
390
|
+
/** Render the top `limit` entries of a name→count map, ranked by count
|
|
391
|
+
* descending (name ascending to break ties), with a muted `… N more` tail
|
|
392
|
+
* when entries were dropped — the `developer×2, advisor×1` kind-breakdown
|
|
393
|
+
* shape. */
|
|
394
|
+
function summarizeTopCounts(counts, limit, renderItem) {
|
|
395
|
+
const entries = [...counts.entries()]
|
|
396
|
+
.filter(([, count]) => count > 0)
|
|
397
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
398
|
+
const shown = entries.slice(0, limit).map(([name, count]) => renderItem(name, count));
|
|
399
|
+
const remaining = entries.length - shown.length;
|
|
400
|
+
if (remaining > 0)
|
|
401
|
+
shown.push(`… ${remaining} more`);
|
|
402
|
+
return shown.join(', ');
|
|
403
|
+
}
|
|
404
|
+
/** `crtr canvas dashboard`'s object output carries a `rows` array (one entry
|
|
405
|
+
* per node: {node_id,name,status,kind,mode,ctx_tokens,asks,...}) rendered by
|
|
406
|
+
* renderArray as an `N rows:` table. The collapsed card is a status-count
|
|
407
|
+
* digest (active/idle plain, done/dead/canceled muted by the ordering below),
|
|
408
|
+
* the largest ctx_tokens across the view as a muted aside, and a warning line
|
|
409
|
+
* only when any row carries a pending ask — Silas's 2026-07-01 reframe ("what
|
|
410
|
+
* does a human want to know?"), not a re-rendering of the raw tree/table.
|
|
411
|
+
* Returns null when the rows table isn't in the expected shape, or is empty
|
|
412
|
+
* (an empty canvas has nothing to digest into a status line). */
|
|
413
|
+
function summarizeCanvasDashboard(text) {
|
|
414
|
+
const rows = parseTableRows(text, 'rows');
|
|
415
|
+
if (rows === null || rows.length === 0)
|
|
416
|
+
return null;
|
|
417
|
+
const statuses = new Map();
|
|
418
|
+
let largestCtx = 0;
|
|
419
|
+
let asks = 0;
|
|
420
|
+
for (const row of rows) {
|
|
421
|
+
const status = row['status'];
|
|
422
|
+
const ctx = Number(row['ctx_tokens']);
|
|
423
|
+
const ask = Number(row['asks']);
|
|
424
|
+
if (status === undefined || status === '' || Number.isNaN(ctx) || Number.isNaN(ask))
|
|
425
|
+
return null;
|
|
426
|
+
statuses.set(status, (statuses.get(status) ?? 0) + 1);
|
|
427
|
+
largestCtx = Math.max(largestCtx, ctx);
|
|
428
|
+
asks += ask;
|
|
429
|
+
}
|
|
430
|
+
const digest = summarizeOrderedCounts(statuses, ['active', 'idle', 'done', 'dead', 'canceled']);
|
|
431
|
+
if (digest === '')
|
|
432
|
+
return null;
|
|
433
|
+
const lines = [{ text: digest }];
|
|
434
|
+
lines.push({ text: `largest ctx: ${compactThousands(largestCtx)} tokens`, tone: 'muted' });
|
|
435
|
+
if (asks > 0)
|
|
436
|
+
lines.push({ text: `${asks} pending asks`, tone: 'warning' });
|
|
437
|
+
return lines;
|
|
438
|
+
}
|
|
439
|
+
/** `crtr node inspect list`'s object output carries a `nodes` array (one entry
|
|
440
|
+
* per node: {node_id,name,kind,mode,lifecycle,status,cwd,...}) rendered as an
|
|
441
|
+
* `N nodes:` table. The collapsed card is a status-count digest (same order
|
|
442
|
+
* as the dashboard summary) plus a muted kind breakdown (top 5 kinds by
|
|
443
|
+
* count). Returns null when the table isn't in the expected shape or empty. */
|
|
444
|
+
function summarizeNodeInspectList(text) {
|
|
445
|
+
const rows = parseTableRows(text, 'nodes');
|
|
446
|
+
if (rows === null || rows.length === 0)
|
|
447
|
+
return null;
|
|
448
|
+
const statuses = new Map();
|
|
449
|
+
const kinds = new Map();
|
|
450
|
+
for (const row of rows) {
|
|
451
|
+
const status = row['status'];
|
|
452
|
+
const kind = row['kind'];
|
|
453
|
+
if (status === undefined || status === '' || kind === undefined || kind === '')
|
|
454
|
+
return null;
|
|
455
|
+
statuses.set(status, (statuses.get(status) ?? 0) + 1);
|
|
456
|
+
kinds.set(kind, (kinds.get(kind) ?? 0) + 1);
|
|
457
|
+
}
|
|
458
|
+
const statusDigest = summarizeOrderedCounts(statuses, ['active', 'idle', 'done', 'dead', 'canceled']);
|
|
459
|
+
const kindDigest = summarizeTopCounts(kinds, 5, (kind, count) => `${kind}×${count}`);
|
|
460
|
+
if (statusDigest === '' || kindDigest === '')
|
|
461
|
+
return null;
|
|
462
|
+
return [
|
|
463
|
+
{ text: statusDigest },
|
|
464
|
+
{ text: `kinds: ${kindDigest}`, tone: 'muted' },
|
|
465
|
+
];
|
|
466
|
+
}
|
|
467
|
+
/** `crtr canvas attention count`'s object output is a single `- count: N`
|
|
468
|
+
* bullet. Silas's computed-status framing (2026-07-01): 0 pending asks is a
|
|
469
|
+
* genuinely PASSing state worth a green line, not an empty card, and N > 0 is
|
|
470
|
+
* worth an error-toned line naming the count — never suppress this leaf down
|
|
471
|
+
* to nothing regardless of which way the count falls. Returns null only when
|
|
472
|
+
* `count` isn't present as a plain integer (an unrecognized/error shape). */
|
|
473
|
+
function summarizeCanvasAttentionCount(text) {
|
|
474
|
+
const count = parseIntBullet(text, 'count');
|
|
475
|
+
if (count === null)
|
|
476
|
+
return null;
|
|
477
|
+
if (count === 0)
|
|
478
|
+
return [{ text: 'PASS · no pending asks', tone: 'success' }];
|
|
479
|
+
return [{ text: `${count} pending asks`, tone: 'error' }];
|
|
480
|
+
}
|
|
481
|
+
/** `crtr canvas attention list`'s object output is a `- total: N` bullet
|
|
482
|
+
* followed by an `M items:` table ({node_id,name,cwd,count}). The collapsed
|
|
483
|
+
* card leads with `total · cwd count`, calls out the hottest single cwd in
|
|
484
|
+
* warning tone (the one place a human should look first), then a muted
|
|
485
|
+
* digest of the remaining cwd basenames (paths are filesystem noise here).
|
|
486
|
+
* Zero pending asks reads as the same PASS line attention count uses —
|
|
487
|
+
* consistent health framing across both leaves, never an empty card. Returns
|
|
488
|
+
* null when the total/items shape doesn't match, or when any item is missing
|
|
489
|
+
* its cwd/count (an unrecognized row shape). */
|
|
490
|
+
function summarizeCanvasAttentionList(text) {
|
|
491
|
+
const total = parseIntBullet(text, 'total');
|
|
492
|
+
const items = parseTableRows(text, 'items');
|
|
493
|
+
if (total === null || items === null)
|
|
494
|
+
return null;
|
|
495
|
+
if (total === 0 || items.length === 0)
|
|
496
|
+
return [{ text: 'PASS · no pending asks', tone: 'success' }];
|
|
497
|
+
const entries = [];
|
|
498
|
+
for (const row of items) {
|
|
499
|
+
const cwd = row['cwd'];
|
|
500
|
+
const count = Number(row['count']);
|
|
501
|
+
if (cwd === undefined || cwd === '' || Number.isNaN(count))
|
|
502
|
+
return null;
|
|
503
|
+
entries.push({ cwd, count });
|
|
504
|
+
}
|
|
505
|
+
const sorted = entries
|
|
506
|
+
.slice()
|
|
507
|
+
.sort((a, b) => b.count - a.count || basename(a.cwd).localeCompare(basename(b.cwd)) || a.cwd.localeCompare(b.cwd));
|
|
508
|
+
const hottest = sorted[0];
|
|
509
|
+
const restBasenames = sorted.slice(1).map((item) => basename(item.cwd));
|
|
510
|
+
const lines = [
|
|
511
|
+
{ text: `${total} total · ${items.length} cwds` },
|
|
512
|
+
{ text: `hottest: ${basename(hottest.cwd)} (${hottest.count})`, tone: 'warning' },
|
|
513
|
+
];
|
|
514
|
+
if (restBasenames.length > 0) {
|
|
515
|
+
const shown = restBasenames.slice(0, 4);
|
|
516
|
+
const remaining = restBasenames.length - shown.length;
|
|
517
|
+
const digest = remaining > 0 ? `${shown.join(', ')}, … ${remaining} more` : shown.join(', ');
|
|
518
|
+
lines.push({ text: `other cwds: ${digest}`, tone: 'muted' });
|
|
519
|
+
}
|
|
520
|
+
return lines;
|
|
521
|
+
}
|
|
522
|
+
/** `crtr canvas attention map` (src/commands/attention.ts:227) has no custom
|
|
523
|
+
* `render`; its `counts` field is a nested plain object (node_id → ask
|
|
524
|
+
* count), not an array, so the generic renderer's `renderResult` emits it
|
|
525
|
+
* as ONE scalar bullet via `scalarStr`'s compact-JSON branch: `- counts:
|
|
526
|
+
* {"id1":0,"id2":3,...}` (output schema at attention.ts:190). No node names
|
|
527
|
+
* are available in this output (only ids), so the collapsed card names the
|
|
528
|
+
* hottest id, not a name. The real shape is `Record<string, number>` — a
|
|
529
|
+
* mixed object (any non-numeric value) is a shape mismatch, not a partial
|
|
530
|
+
* total, so it's rejected wholesale rather than filtered down. Returns
|
|
531
|
+
* null when `counts` isn't parseable JSON, is empty, or has any
|
|
532
|
+
* non-numeric entry. */
|
|
533
|
+
function summarizeCanvasAttentionMap(text) {
|
|
534
|
+
const m = /^- counts:\s*(\{.*\})\s*$/m.exec(text);
|
|
535
|
+
if (m === null)
|
|
536
|
+
return null;
|
|
537
|
+
let counts;
|
|
538
|
+
try {
|
|
539
|
+
counts = JSON.parse(m[1]);
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
if (counts === null || typeof counts !== 'object')
|
|
545
|
+
return null;
|
|
546
|
+
const rawEntries = Object.entries(counts);
|
|
547
|
+
if (rawEntries.length === 0)
|
|
548
|
+
return null;
|
|
549
|
+
if (rawEntries.some(([, v]) => typeof v !== 'number'))
|
|
550
|
+
return null;
|
|
551
|
+
const entries = rawEntries;
|
|
552
|
+
const total = entries.reduce((s, [, v]) => s + v, 0);
|
|
553
|
+
if (total === 0)
|
|
554
|
+
return [{ text: 'PASS · no pending asks', tone: 'success' }];
|
|
555
|
+
const withAsks = entries.filter(([, v]) => v > 0);
|
|
556
|
+
const sorted = withAsks.slice().sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
557
|
+
const hottest = sorted[0];
|
|
558
|
+
return [
|
|
559
|
+
{ text: `${total} asks across ${withAsks.length} node${withAsks.length === 1 ? '' : 's'}` },
|
|
560
|
+
{ text: `hottest: ${hottest[0]} (${hottest[1]})`, tone: 'warning' },
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
/** Find the first argv token after `skipWords` that isn't a boolean flag
|
|
564
|
+
* (`--foo`) — recovers a leaf's own positional id when its render() output
|
|
565
|
+
* carries no id of its own (see summarizeCanvasRevive below). Safe here
|
|
566
|
+
* specifically because every `canvas revive` flag is a boolean with no
|
|
567
|
+
* separate value token, so a naive forward scan can't mistake a flag's
|
|
568
|
+
* value for the positional. */
|
|
569
|
+
function firstPositionalAfter(tokens, skipWords) {
|
|
570
|
+
for (let i = skipWords; i < tokens.length; i++) {
|
|
571
|
+
if (!tokens[i].startsWith('--'))
|
|
572
|
+
return tokens[i];
|
|
573
|
+
}
|
|
574
|
+
return undefined;
|
|
575
|
+
}
|
|
576
|
+
/** `crtr canvas revive` (src/commands/revive.ts) has no custom `render`, so
|
|
577
|
+
* its object output falls through to generic renderResult. Four shapes
|
|
578
|
+
* depending on flags:
|
|
579
|
+
* - `--all` (preview or executed) carries a `- mode: preview|revived`
|
|
580
|
+
* bullet — skipped (null): `candidates`/`revived`/`failed` are
|
|
581
|
+
* prose-or-bullet depending on whether they contain a newline (multi- vs
|
|
582
|
+
* single-id), which core/render.ts's `isProse` makes genuinely ambiguous
|
|
583
|
+
* to parse strictly, and this is a rare admin path.
|
|
584
|
+
* - `--now` kick: the only shape with `- kicked: true` (revive.ts:192).
|
|
585
|
+
* - plain single-node revive: the only shape with `- resumed: true|false`.
|
|
586
|
+
* `window` (revive.ts:83) is documented "Always null", so it's never
|
|
587
|
+
* bulleted — and there is no node id anywhere in the output, so it's
|
|
588
|
+
* recovered from argv (the leaf's own positional) via
|
|
589
|
+
* firstPositionalAfter.
|
|
590
|
+
* Returns null on any other/unrecognized shape. */
|
|
591
|
+
function summarizeCanvasRevive(text, tokens) {
|
|
592
|
+
if (/^- mode:\s*\S+\s*$/m.test(text))
|
|
593
|
+
return null;
|
|
594
|
+
if (/^- kicked:\s*true\s*$/m.test(text)) {
|
|
595
|
+
return [{ text: 'kicked — daemon resumes in ~20s', tone: 'warning' }];
|
|
596
|
+
}
|
|
597
|
+
const resumedMatch = /^- resumed:\s*(true|false)\s*$/m.exec(text);
|
|
598
|
+
if (resumedMatch === null)
|
|
599
|
+
return null;
|
|
600
|
+
const id = firstPositionalAfter(tokens, 2); // 'canvas','revive' = 2 words
|
|
601
|
+
const lines = [{ text: id !== undefined ? `revived ${id}` : 'revived', tone: 'success' }];
|
|
602
|
+
const readyMatch = /^- ready:\s*(true|false)\s*$/m.exec(text);
|
|
603
|
+
if (readyMatch !== null && readyMatch[1] === 'false') {
|
|
604
|
+
lines.push({ text: 'not yet attachable (socket not ready)', tone: 'warning' });
|
|
605
|
+
}
|
|
606
|
+
return lines;
|
|
607
|
+
}
|
|
608
|
+
/** `crtr canvas history search` (renderSearch, src/commands/canvas-history/
|
|
609
|
+
* search.ts:246) has three shapes: `0 hits.`; `--full` (### heading blocks
|
|
610
|
+
* per hit, not tabular — skipped, returns null); or the normal `N of M
|
|
611
|
+
* hits:` lead-in plus a GFM table (columns `{ref,node,kind,source,ts,
|
|
612
|
+
* score?,snippet}`, or `{ref,node,ts,line,text}` under `--grep`, no
|
|
613
|
+
* `kind`). Reuses this file's own `splitTableRow` helper rather than
|
|
614
|
+
* `parseTableRows` (whose `N <label>:` lead-in must end in a literal colon
|
|
615
|
+
* immediately before the table — `N of M hits:` is a close but different
|
|
616
|
+
* shape). Returns null on `--full` or any other shape mismatch: a
|
|
617
|
+
* non-`---` separator cell, or a missing `node`/`ts` column (`kind` is
|
|
618
|
+
* read only when present, never required — `--grep` mode legitimately
|
|
619
|
+
* lacks it), or a parsed row count that doesn't match the leading `N of M
|
|
620
|
+
* hits:` displayed count `N` (the actual page size — pagination can make
|
|
621
|
+
* it less than `M`, the grand total). */
|
|
622
|
+
function summarizeCanvasHistorySearch(text) {
|
|
623
|
+
if (/^0 hits\.$/m.test(text))
|
|
624
|
+
return [{ text: '0 hits', tone: 'muted' }];
|
|
625
|
+
const lead = /^(\d+) of (\d+) hits:$/m.exec(text);
|
|
626
|
+
if (lead === null)
|
|
627
|
+
return null;
|
|
628
|
+
const displayed = Number(lead[1]);
|
|
629
|
+
const total = Number(lead[2]);
|
|
630
|
+
const lines = text.split('\n');
|
|
631
|
+
const headerIdx = lines.findIndex((l) => l.trim().startsWith('| ref |'));
|
|
632
|
+
if (headerIdx === -1)
|
|
633
|
+
return null;
|
|
634
|
+
const header = splitTableRow(lines[headerIdx]);
|
|
635
|
+
const separator = splitTableRow(lines[headerIdx + 1]);
|
|
636
|
+
if (header === null || separator === null || header.length !== separator.length)
|
|
637
|
+
return null;
|
|
638
|
+
if (separator.some((cell) => !/^-+$/.test(cell)))
|
|
639
|
+
return null;
|
|
640
|
+
if (!header.includes('node') || !header.includes('ts'))
|
|
641
|
+
return null;
|
|
642
|
+
const rows = [];
|
|
643
|
+
let j = headerIdx + 2;
|
|
644
|
+
while (j < lines.length && lines[j].trim().startsWith('|')) {
|
|
645
|
+
const cells = splitTableRow(lines[j]);
|
|
646
|
+
if (cells === null || cells.length !== header.length)
|
|
647
|
+
return null;
|
|
648
|
+
const row = {};
|
|
649
|
+
header.forEach((h, i) => { row[h] = cells[i]; });
|
|
650
|
+
rows.push(row);
|
|
651
|
+
j++;
|
|
652
|
+
}
|
|
653
|
+
if (rows.length !== displayed)
|
|
654
|
+
return null;
|
|
655
|
+
const top = rows.slice(0, 3).map((r) => 'kind' in r ? `${r['node']} · ${r['kind']} · ${r['ts']}` : `${r['node']} · ${r['ts']}`);
|
|
656
|
+
const out = [{ text: `${total} hit${total === 1 ? '' : 's'}` }];
|
|
657
|
+
for (const t of top)
|
|
658
|
+
out.push({ text: t, tone: 'muted' });
|
|
659
|
+
const remaining = rows.length - top.length;
|
|
660
|
+
if (remaining > 0)
|
|
661
|
+
out.push({ text: `… ${remaining} more shown`, tone: 'muted' });
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
/** `crtr canvas history read` (src/commands/canvas-history/read.ts:61):
|
|
665
|
+
* `${node} | source: ${source} | ts: ${ts} | ref: ${ref}\n\n${content}`
|
|
666
|
+
* — note the DOUBLE SPACES around each `|`. Mirrors summarizeMemoryRead's
|
|
667
|
+
* identity+teaser pattern (reusing MEMORY_READ_CONTENT_LINES). Returns null
|
|
668
|
+
* when the head line doesn't match this exact shape. */
|
|
669
|
+
function summarizeCanvasHistoryRead(text) {
|
|
670
|
+
const first = text.split('\n')[0] ?? '';
|
|
671
|
+
const head = /^(.+?) \| source: (.+?) \| ts: (.+?) \| ref: (.+)$/.exec(first);
|
|
672
|
+
if (head === null)
|
|
673
|
+
return null;
|
|
674
|
+
const [, node, source, ts] = head;
|
|
675
|
+
const rest = text.split('\n').slice(1);
|
|
676
|
+
let idx = 0;
|
|
677
|
+
while (idx < rest.length && rest[idx].trim() === '')
|
|
678
|
+
idx++;
|
|
679
|
+
const contentLines = rest.slice(idx);
|
|
680
|
+
const teaser = contentLines.slice(0, MEMORY_READ_CONTENT_LINES);
|
|
681
|
+
const hasMore = contentLines.slice(MEMORY_READ_CONTENT_LINES).some((l) => l.trim() !== '');
|
|
682
|
+
if (hasMore)
|
|
683
|
+
teaser.push('…');
|
|
684
|
+
const lines = [{ text: `${node} · ${source} · ${ts}` }];
|
|
685
|
+
if (teaser.some((l) => l.trim() !== '')) {
|
|
686
|
+
lines.push({ text: '' });
|
|
687
|
+
for (const l of teaser)
|
|
688
|
+
lines.push({ text: l });
|
|
689
|
+
}
|
|
690
|
+
return lines;
|
|
691
|
+
}
|
|
692
|
+
/** `crtr canvas history show` (src/commands/canvas-history/show.ts:66):
|
|
693
|
+
* `node: <name (id)>\n\n0 artifacts. | N artifacts:\n\n<table
|
|
694
|
+
* cols=ref,source,ts,detail>\n\n<follow_up>`. `source` values are
|
|
695
|
+
* colon-prefixed types (`report:final`, `report:update`, `doc`,
|
|
696
|
+
* `roadmap`). Reuses this file's `summarizeTopCounts` for the type
|
|
697
|
+
* breakdown. Returns null when the `node:` line, the table shape (a
|
|
698
|
+
* non-`---` separator cell), or a missing `source` column doesn't
|
|
699
|
+
* match — or when the parsed row count doesn't match the leading `N
|
|
700
|
+
* artifacts:` count (this render has no pagination, so the two must
|
|
701
|
+
* agree exactly). */
|
|
702
|
+
function summarizeCanvasHistoryShow(text) {
|
|
703
|
+
const nodeMatch = /^node: (.+)$/m.exec(text);
|
|
704
|
+
if (nodeMatch === null)
|
|
705
|
+
return null;
|
|
706
|
+
if (/^0 artifacts\.$/m.test(text))
|
|
707
|
+
return [{ text: `${nodeMatch[1]} · 0 artifacts`, tone: 'muted' }];
|
|
708
|
+
const countMatch = /^(\d+) artifacts:$/m.exec(text);
|
|
709
|
+
if (countMatch === null)
|
|
710
|
+
return null;
|
|
711
|
+
const lines = text.split('\n');
|
|
712
|
+
const headerIdx = lines.findIndex((l) => l.trim().startsWith('| ref |'));
|
|
713
|
+
if (headerIdx === -1)
|
|
714
|
+
return null;
|
|
715
|
+
const header = splitTableRow(lines[headerIdx]);
|
|
716
|
+
const separator = splitTableRow(lines[headerIdx + 1]);
|
|
717
|
+
if (header === null || separator === null || header.length !== separator.length)
|
|
718
|
+
return null;
|
|
719
|
+
if (separator.some((cell) => !/^-+$/.test(cell)))
|
|
720
|
+
return null;
|
|
721
|
+
const sourceIdx = header.indexOf('source');
|
|
722
|
+
if (sourceIdx === -1)
|
|
723
|
+
return null;
|
|
724
|
+
const typeCounts = new Map();
|
|
725
|
+
let rowCount = 0;
|
|
726
|
+
let j = headerIdx + 2;
|
|
727
|
+
while (j < lines.length && lines[j].trim().startsWith('|')) {
|
|
728
|
+
const cells = splitTableRow(lines[j]);
|
|
729
|
+
if (cells === null || cells.length !== header.length)
|
|
730
|
+
return null;
|
|
731
|
+
const type = (cells[sourceIdx] ?? '').split(':')[0];
|
|
732
|
+
typeCounts.set(type, (typeCounts.get(type) ?? 0) + 1);
|
|
733
|
+
rowCount++;
|
|
734
|
+
j++;
|
|
735
|
+
}
|
|
736
|
+
if (rowCount !== Number(countMatch[1]))
|
|
737
|
+
return null;
|
|
738
|
+
const digest = summarizeTopCounts(typeCounts, 5, (name, count) => `${name}s×${count}`);
|
|
739
|
+
return [{ text: `${countMatch[1]} artifacts` }, { text: digest, tone: 'muted' }];
|
|
740
|
+
}
|
|
741
|
+
/** `crtr canvas daemon start` (src/commands/daemon.ts:40, `spawnDaemon()` in
|
|
742
|
+
* src/daemon/manage.ts:89) has no custom `render`; the generic renderer
|
|
743
|
+
* bullets `{started:true,pid}` or `{started:false,existing_pid}`. `pid` is
|
|
744
|
+
* unconditionally set alongside `started:true` (manage.ts:125) — a
|
|
745
|
+
* true-with-no-pid shape is unrecognized, so it returns null rather than a
|
|
746
|
+
* pid-less success line. `existing_pid` genuinely can be absent on the
|
|
747
|
+
* false branch (a `readPidfile() ?? undefined` race), so that one stays
|
|
748
|
+
* optional. Returns null when `started` isn't a plain boolean bullet, or
|
|
749
|
+
* is `true` without a `pid` bullet. */
|
|
750
|
+
function summarizeCanvasDaemonStart(text) {
|
|
751
|
+
const m = /^- started:\s*(true|false)\s*$/m.exec(text);
|
|
752
|
+
if (m === null)
|
|
753
|
+
return null;
|
|
754
|
+
if (m[1] === 'true') {
|
|
755
|
+
const pid = /^- pid:\s*(\d+)\s*$/m.exec(text)?.[1];
|
|
756
|
+
if (pid === undefined)
|
|
757
|
+
return null;
|
|
758
|
+
return [{ text: `started · pid ${pid}`, tone: 'success' }];
|
|
759
|
+
}
|
|
760
|
+
const pid = /^- existing_pid:\s*(\d+)\s*$/m.exec(text)?.[1];
|
|
761
|
+
return [{ text: `already running${pid !== undefined ? ` · pid ${pid}` : ''}`, tone: 'muted' }];
|
|
762
|
+
}
|
|
763
|
+
/** `crtr canvas daemon status` (src/commands/daemon.ts:65) has no custom
|
|
764
|
+
* `render`; the generic renderer bullets `{running:true,pid}` or
|
|
765
|
+
* `{running:false}`. `pid` is unconditionally set alongside `running:true`
|
|
766
|
+
* (daemon.ts:70 — the true branch only fires once `readPidfile()` already
|
|
767
|
+
* returned a pid), so a true-with-no-pid shape is unrecognized. Returns
|
|
768
|
+
* null when `running` isn't a plain boolean bullet, or is `true` without a
|
|
769
|
+
* `pid` bullet. */
|
|
770
|
+
function summarizeCanvasDaemonStatus(text) {
|
|
771
|
+
const m = /^- running:\s*(true|false)\s*$/m.exec(text);
|
|
772
|
+
if (m === null)
|
|
773
|
+
return null;
|
|
774
|
+
if (m[1] === 'true') {
|
|
775
|
+
const pid = /^- pid:\s*(\d+)\s*$/m.exec(text)?.[1];
|
|
776
|
+
if (pid === undefined)
|
|
777
|
+
return null;
|
|
778
|
+
return [{ text: `running · pid ${pid}`, tone: 'success' }];
|
|
779
|
+
}
|
|
780
|
+
return [{ text: 'not running', tone: 'error' }];
|
|
781
|
+
}
|
|
782
|
+
/** `crtr canvas daemon stop` (src/commands/daemon.ts:93) has no custom
|
|
783
|
+
* `render`; the generic renderer bullets `{stopped:true,pid}` or
|
|
784
|
+
* `{stopped:false}`. `pid` is unconditionally set alongside `stopped:true`
|
|
785
|
+
* (daemon.ts:98 — the signaled pid is echoed back in the same return), so a
|
|
786
|
+
* true-with-no-pid shape is unrecognized. Returns null when `stopped` isn't
|
|
787
|
+
* a plain boolean bullet, or is `true` without a `pid` bullet. */
|
|
788
|
+
function summarizeCanvasDaemonStop(text) {
|
|
789
|
+
const m = /^- stopped:\s*(true|false)\s*$/m.exec(text);
|
|
790
|
+
if (m === null)
|
|
791
|
+
return null;
|
|
792
|
+
if (m[1] === 'true') {
|
|
793
|
+
const pid = /^- pid:\s*(\d+)\s*$/m.exec(text)?.[1];
|
|
794
|
+
if (pid === undefined)
|
|
795
|
+
return null;
|
|
796
|
+
return [{ text: `stopped · pid ${pid}` }];
|
|
797
|
+
}
|
|
798
|
+
return [{ text: 'was not running', tone: 'muted' }];
|
|
799
|
+
}
|
|
800
|
+
/** `crtr canvas issue new` (src/commands/canvas-issue.ts) has no custom
|
|
801
|
+
* `render`; the generic renderer bullets `{created:false}` (empty note,
|
|
802
|
+
* line 125), `{created:true,target:'github',type,title,url}` (line 137),
|
|
803
|
+
* or `{created:true,target:'file',type,title,path}` (line 159). `url` is
|
|
804
|
+
* unconditionally set on every `target:'github'` return, and `path` on
|
|
805
|
+
* every `target:'file'` return — so either missing (e.g. a >80-char
|
|
806
|
+
* value core/render.ts's `isProse` rule pushed to prose form instead of a
|
|
807
|
+
* bullet) is an unrecognized shape, not a success to report without the
|
|
808
|
+
* field. Returns null when `created` isn't a plain boolean bullet, or the
|
|
809
|
+
* target's required companion field isn't present as a bullet. */
|
|
810
|
+
function summarizeCanvasIssueNew(text) {
|
|
811
|
+
const createdMatch = /^- created:\s*(true|false)\s*$/m.exec(text);
|
|
812
|
+
if (createdMatch === null)
|
|
813
|
+
return null;
|
|
814
|
+
if (createdMatch[1] === 'false')
|
|
815
|
+
return [{ text: 'nothing captured', tone: 'muted' }];
|
|
816
|
+
const target = /^- target:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
817
|
+
if (target === 'github') {
|
|
818
|
+
const url = /^- url:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
819
|
+
if (url === undefined)
|
|
820
|
+
return null;
|
|
821
|
+
const num = /\/issues\/(\d+)$/.exec(url)?.[1];
|
|
822
|
+
return [
|
|
823
|
+
{ text: num !== undefined ? `issue #${num}` : 'issue filed', tone: 'success' },
|
|
824
|
+
{ text: url, tone: 'muted' },
|
|
825
|
+
];
|
|
826
|
+
}
|
|
827
|
+
if (target === 'file') {
|
|
828
|
+
const path = /^- path:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
829
|
+
if (path === undefined)
|
|
830
|
+
return null;
|
|
831
|
+
return [
|
|
832
|
+
{ text: 'note saved', tone: 'success' },
|
|
833
|
+
{ text: basename(path), tone: 'muted' },
|
|
834
|
+
];
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
/** `crtr node new` (nodeNew.render, src/commands/node.ts:318) has two
|
|
839
|
+
* shapes: a spawn (`Spawned "${name}" (${id}) — ${status}${win}.`, with no
|
|
840
|
+
* `kind`/`mode` anywhere in the output) or an armed deferred spawn (`Armed
|
|
841
|
+
* deferred spawn ${id} — a ${kind} node, ${when}.`, one shared template
|
|
842
|
+
* covering --at/--every/--when alike). Returns null when neither template
|
|
843
|
+
* matches. */
|
|
844
|
+
function summarizeNodeNew(text) {
|
|
845
|
+
const spawned = /^Spawned "([^"]*)" \((\S+)\) — (\w+)/.exec(text);
|
|
846
|
+
if (spawned !== null) {
|
|
847
|
+
const [, name, id, status] = spawned;
|
|
848
|
+
return [{ text: `spawned ${name} (${id}) · ${status}`, tone: 'success' }];
|
|
849
|
+
}
|
|
850
|
+
const armed = /^Armed deferred spawn (\S+) — a (\S+) node, (.+?)\.$/m.exec(text.split('\n')[0] ?? '');
|
|
851
|
+
if (armed !== null) {
|
|
852
|
+
const [, id, kind, detail] = armed;
|
|
853
|
+
const fires = /fires ([^\s,]+)/.exec(detail); // stop before a trailing `, recurs …` suffix
|
|
854
|
+
return [
|
|
855
|
+
{ text: fires !== null ? `trigger armed · fires ${fires[1]}` : `trigger armed · ${detail}`, tone: 'accent' },
|
|
856
|
+
{ text: `${kind} · ${id}`, tone: 'muted' },
|
|
857
|
+
];
|
|
858
|
+
}
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
/** `crtr node msg` (nodeMsg.render, src/commands/node.ts:1284) has two
|
|
862
|
+
* shapes: armed (covers --at/--every/--when/--until — one shared template
|
|
863
|
+
* `Armed ${mode} trigger ${id} (${type}) — ${when}, target ${target}.`) or
|
|
864
|
+
* immediate, whose render is JUST `${guidance}` — one of a small fixed set
|
|
865
|
+
* of strings from the code (not user text): `"Delivered."`, `"Delivered —
|
|
866
|
+
* the dormant target was revived to receive it."`, or `Fresh-revived <id>
|
|
867
|
+
* now…`. Anything else returns null (deadline/predicate guidance is long
|
|
868
|
+
* prose, but those cases are armed=true and already handled above). */
|
|
869
|
+
function summarizeNodeMsg(text) {
|
|
870
|
+
const armed = /^Armed (\S+) trigger (\S+) \((\S+)\) — (.+?), target (\S+)\.$/m.exec(text.split('\n')[0] ?? '');
|
|
871
|
+
if (armed !== null) {
|
|
872
|
+
const [, mode, id, triggerType, when, target] = armed;
|
|
873
|
+
const fires = /fires ([^\s,]+)/.exec(when); // stop before a trailing `, recurs …` suffix
|
|
874
|
+
return [
|
|
875
|
+
{ text: fires !== null ? `trigger armed · fires ${fires[1]}` : `trigger armed · ${when}`, tone: 'accent' },
|
|
876
|
+
{ text: `${mode} · ${triggerType} · → ${target} · ${id}`, tone: 'muted' },
|
|
877
|
+
];
|
|
878
|
+
}
|
|
879
|
+
if (/^Delivered\.$/.test(text))
|
|
880
|
+
return [{ text: 'delivered', tone: 'success' }];
|
|
881
|
+
if (/^Delivered — the dormant target was revived to receive it\.$/.test(text)) {
|
|
882
|
+
return [{ text: 'delivered · revived', tone: 'success' }];
|
|
883
|
+
}
|
|
884
|
+
const fresh = /^Fresh-revived (\S+) now/.exec(text);
|
|
885
|
+
if (fresh !== null)
|
|
886
|
+
return [{ text: `fresh-revived ${fresh[1]}`, tone: 'success' }];
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
/** `crtr node yield` (src/commands/node.ts:1513) has no custom `render`;
|
|
890
|
+
* the generic renderer bullets `{node_id, promoted, model?, will_refresh}`
|
|
891
|
+
* — none of these names are PROSE_FIELDS. `will_refresh` is documented
|
|
892
|
+
* "Always true" (the command has no path that returns false), so
|
|
893
|
+
* `- will_refresh: false` is itself an unrecognized/error shape, not an
|
|
894
|
+
* alternate success to report armed. Returns null unless `will_refresh` is
|
|
895
|
+
* exactly `true`, or `node_id` isn't present as a plain bullet. */
|
|
896
|
+
function summarizeNodeYield(text) {
|
|
897
|
+
if (!/^- will_refresh:\s*true\s*$/m.test(text))
|
|
898
|
+
return null;
|
|
899
|
+
const nodeId = /^- node_id:\s*(\S+)\s*$/m.exec(text);
|
|
900
|
+
if (nodeId === null)
|
|
901
|
+
return null;
|
|
902
|
+
const promoted = /^- promoted:\s*true\s*$/m.test(text);
|
|
903
|
+
const model = /^- model:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
904
|
+
const extras = [];
|
|
905
|
+
if (promoted)
|
|
906
|
+
extras.push('promoted → orchestrator');
|
|
907
|
+
if (model !== undefined)
|
|
908
|
+
extras.push(`model: ${model}`);
|
|
909
|
+
const lines = [{ text: 'yield armed — refreshes on next wake', tone: 'accent' }];
|
|
910
|
+
if (extras.length > 0)
|
|
911
|
+
lines.push({ text: extras.join(' · '), tone: 'muted' });
|
|
912
|
+
return lines;
|
|
913
|
+
}
|
|
914
|
+
/** `crtr node lifecycle close` (nodeClose.render, src/commands/node.ts:650):
|
|
915
|
+
* `Closed ${id} and its exclusive subtree — ${count} node(s)
|
|
916
|
+
* closed${spared>0 ? ", ${spared} spared..." : ''}.` on success, or a
|
|
917
|
+
* short failure line — left as null there, the generic fallback is fine for
|
|
918
|
+
* a one-liner. */
|
|
919
|
+
function summarizeNodeLifecycleClose(text) {
|
|
920
|
+
const m = /^Closed (\S+) and its exclusive subtree — (\d+) node\(s\) closed(?:, (\d+) spared)?/.exec(text);
|
|
921
|
+
if (m === null)
|
|
922
|
+
return null;
|
|
923
|
+
const [, id, countStr, sparedStr] = m;
|
|
924
|
+
const count = Number(countStr);
|
|
925
|
+
const descendants = count - 1;
|
|
926
|
+
const lines = [{ text: `closed ${id}`, tone: 'success' }];
|
|
927
|
+
if (descendants > 0)
|
|
928
|
+
lines.push({ text: `${descendants} descendant${descendants === 1 ? '' : 's'} canceled`, tone: 'warning' });
|
|
929
|
+
if (sparedStr !== undefined)
|
|
930
|
+
lines.push({ text: `${sparedStr} spared (managed elsewhere)`, tone: 'muted' });
|
|
931
|
+
return lines;
|
|
932
|
+
}
|
|
933
|
+
/** `crtr node triggers list` (nodeTriggersList.render, src/commands/
|
|
934
|
+
* node.ts:1633): `No pending triggers in scope ${scope}.` (empty) or `${N}
|
|
935
|
+
* pending trigger(s) in scope ${scope}.` plus a 15-column table
|
|
936
|
+
* (`id|owner|target|action|mode|type|next|recur|timeout|label|eval_state|
|
|
937
|
+
* last_eval_at|last_exit|last_stderr|created` — the trigger-kind column is
|
|
938
|
+
* literally named `type`, not `trigger_type`). Returns null on any shape
|
|
939
|
+
* mismatch: a non-`---` separator cell, a missing `type`/`next`/`target`
|
|
940
|
+
* column, or a row-count/table mismatch (a sanity cross-check against the
|
|
941
|
+
* leading `N pending` count). */
|
|
942
|
+
function summarizeNodeTriggersList(text) {
|
|
943
|
+
if (/^No pending triggers in scope .+\.$/m.test(text.split('\n')[0] ?? '')) {
|
|
944
|
+
return [{ text: 'none pending', tone: 'muted' }];
|
|
945
|
+
}
|
|
946
|
+
const head = /^(\d+) pending trigger\(s\) in scope (.+)\.$/.exec(text.split('\n')[0] ?? '');
|
|
947
|
+
if (head === null)
|
|
948
|
+
return null;
|
|
949
|
+
const n = Number(head[1]);
|
|
950
|
+
const lines = text.split('\n');
|
|
951
|
+
const headerIdx = lines.findIndex((l) => l.trim().startsWith('| id |'));
|
|
952
|
+
if (headerIdx === -1)
|
|
953
|
+
return null;
|
|
954
|
+
const header = splitTableRow(lines[headerIdx]);
|
|
955
|
+
const separator = splitTableRow(lines[headerIdx + 1]);
|
|
956
|
+
if (header === null || separator === null || header.length !== separator.length)
|
|
957
|
+
return null;
|
|
958
|
+
if (separator.some((cell) => !/^-+$/.test(cell)))
|
|
959
|
+
return null;
|
|
960
|
+
if (!header.includes('type') || !header.includes('next') || !header.includes('target'))
|
|
961
|
+
return null;
|
|
962
|
+
const rows = [];
|
|
963
|
+
let j = headerIdx + 2;
|
|
964
|
+
while (j < lines.length && lines[j].trim().startsWith('|')) {
|
|
965
|
+
const cells = splitTableRow(lines[j]);
|
|
966
|
+
if (cells === null || cells.length !== header.length)
|
|
967
|
+
return null;
|
|
968
|
+
const row = {};
|
|
969
|
+
header.forEach((h, i) => { row[h] = cells[i]; });
|
|
970
|
+
rows.push(row);
|
|
971
|
+
j++;
|
|
972
|
+
}
|
|
973
|
+
if (rows.length !== n)
|
|
974
|
+
return null;
|
|
975
|
+
const cap = 5;
|
|
976
|
+
const shown = rows.slice(0, cap).map((r) => `${r['type']} · next ${r['next']} · ${r['target']}`);
|
|
977
|
+
const out = [{ text: `${n} pending` }];
|
|
978
|
+
for (const s of shown)
|
|
979
|
+
out.push({ text: s, tone: 'muted' });
|
|
980
|
+
const remaining = rows.length - shown.length;
|
|
981
|
+
if (remaining > 0)
|
|
982
|
+
out.push({ text: `… ${remaining} more`, tone: 'muted' });
|
|
983
|
+
return out;
|
|
984
|
+
}
|
|
985
|
+
/** `crtr feed read` (src/commands/push.ts:209) has two shapes: drained
|
|
986
|
+
* (`${n} unread pointer(s) drained from ${node_id}'s inbox.\n\n${digest}`,
|
|
987
|
+
* where the digest — `coalesce()`, src/core/feed/inbox.ts:225 — groups by
|
|
988
|
+
* sender as `From ${sender} — ${N} update(s):` sections) or empty (`No
|
|
989
|
+
* unread pointers for ${node_id} (unread=0).\n\n${explanation}`).
|
|
990
|
+
* `coalesce()` always groups drained entries under at least one
|
|
991
|
+
* From-section, so a positive drained count with zero parsed senders is a
|
|
992
|
+
* shape mismatch, not a 0-sender success. Returns null on any other
|
|
993
|
+
* shape. */
|
|
994
|
+
function summarizeFeedRead(text) {
|
|
995
|
+
const lines = text.split('\n');
|
|
996
|
+
const first = lines[0] ?? '';
|
|
997
|
+
const drained = /^(\d+) unread pointers? drained from (.+)'s inbox\.$/.exec(first);
|
|
998
|
+
if (drained !== null) {
|
|
999
|
+
const n = Number(drained[1]);
|
|
1000
|
+
const senders = [...new Set([...text.matchAll(/^From (.+?) — \d+ update/gm)].map((m) => m[1]))];
|
|
1001
|
+
if (n > 0 && senders.length === 0)
|
|
1002
|
+
return null;
|
|
1003
|
+
const out = [{ text: `${n} pointer${n === 1 ? '' : 's'} drained from ${senders.length} node${senders.length === 1 ? '' : 's'}` }];
|
|
1004
|
+
if (senders.length > 0) {
|
|
1005
|
+
const shown = senders.slice(0, 5);
|
|
1006
|
+
const remaining = senders.length - shown.length;
|
|
1007
|
+
out.push({ text: remaining > 0 ? `${shown.join(', ')}, … ${remaining} more` : shown.join(', '), tone: 'muted' });
|
|
1008
|
+
}
|
|
1009
|
+
return out;
|
|
1010
|
+
}
|
|
1011
|
+
if (/^No unread pointers for .+ \(unread=0\)\.$/.test(first)) {
|
|
1012
|
+
return [{ text: 'nothing unread', tone: 'muted' }];
|
|
1013
|
+
}
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
/** Teaser cap for `feed message`'s body preview — mirrors
|
|
1017
|
+
* MEMORY_READ_CONTENT_LINES's role for a different leaf. */
|
|
1018
|
+
const FEED_MESSAGE_TEASER_LINES = 3;
|
|
1019
|
+
/** `crtr feed message` (src/commands/push.ts:264) has two shapes: found
|
|
1020
|
+
* (`Full message ${id} (from ${from}):\n\n${body}`) or not-found (a short
|
|
1021
|
+
* line — left as null, the generic fallback is fine for one line). */
|
|
1022
|
+
function summarizeFeedMessage(text) {
|
|
1023
|
+
const lines = text.split('\n');
|
|
1024
|
+
const head = /^Full message (\S+) \(from (.+)\):$/.exec(lines[0] ?? '');
|
|
1025
|
+
if (head === null)
|
|
1026
|
+
return null;
|
|
1027
|
+
const [, id, from] = head;
|
|
1028
|
+
const bodyLines = lines.slice(1);
|
|
1029
|
+
let idx = 0;
|
|
1030
|
+
while (idx < bodyLines.length && bodyLines[idx].trim() === '')
|
|
1031
|
+
idx++;
|
|
1032
|
+
const content = bodyLines.slice(idx);
|
|
1033
|
+
const teaser = content.slice(0, FEED_MESSAGE_TEASER_LINES);
|
|
1034
|
+
const hasMore = content.slice(FEED_MESSAGE_TEASER_LINES).some((l) => l.trim() !== '');
|
|
1035
|
+
if (hasMore)
|
|
1036
|
+
teaser.push('…');
|
|
1037
|
+
const out = [{ text: `${id} · from ${from}` }];
|
|
1038
|
+
if (teaser.some((l) => l.trim() !== '')) {
|
|
1039
|
+
out.push({ text: '' });
|
|
1040
|
+
for (const l of teaser)
|
|
1041
|
+
out.push({ text: l });
|
|
1042
|
+
}
|
|
1043
|
+
return out;
|
|
1044
|
+
}
|
|
1045
|
+
/** `crtr feed peek` (src/commands/push.ts:351) has two shapes: zero-kids
|
|
1046
|
+
* (`No nodes below you (0 subscriptions, ${unread} unread). ${tail}` — the
|
|
1047
|
+
* "0" is a hardcoded literal, not a variable) or nonzero (`${verdictLine}
|
|
1048
|
+
* \n\n${N} node(s) below you — ${working} working, ${done} done, ${dead}
|
|
1049
|
+
* dead, ${unread} unread:\n${rows}`). Returns null on any other shape. */
|
|
1050
|
+
function summarizeFeedPeek(text) {
|
|
1051
|
+
const zero = /^No nodes below you \(0 subscriptions, (\d+) unread\)\./.exec(text.split('\n')[0] ?? '');
|
|
1052
|
+
if (zero !== null) {
|
|
1053
|
+
const unread = Number(zero[1]);
|
|
1054
|
+
return unread > 0
|
|
1055
|
+
? [{ text: `0 below · ${unread} unread`, tone: 'warning' }]
|
|
1056
|
+
: [{ text: 'PASS · nothing below', tone: 'success' }];
|
|
1057
|
+
}
|
|
1058
|
+
const m = /(\d+) nodes? below you — (\d+) working, (\d+) done, (\d+) dead, (\d+) unread:/.exec(text);
|
|
1059
|
+
if (m === null)
|
|
1060
|
+
return null;
|
|
1061
|
+
const [, , workingStr, doneStr, deadStr, unreadStr] = m;
|
|
1062
|
+
const working = Number(workingStr);
|
|
1063
|
+
const done = Number(doneStr);
|
|
1064
|
+
const dead = Number(deadStr);
|
|
1065
|
+
const unread = Number(unreadStr);
|
|
1066
|
+
const parts = [`${working} working`, `${done} done`];
|
|
1067
|
+
if (dead > 0)
|
|
1068
|
+
parts.push(`${dead} dead`);
|
|
1069
|
+
const lines = [{ text: parts.join(' · ') }];
|
|
1070
|
+
if (dead > 0)
|
|
1071
|
+
lines.push({ text: `${dead} dead — will not wake you`, tone: 'warning' });
|
|
1072
|
+
else if (unread > 0)
|
|
1073
|
+
lines.push({ text: `${unread} unread`, tone: 'warning' });
|
|
1074
|
+
return lines;
|
|
1075
|
+
}
|
|
1076
|
+
/** `crtr push final` (makeTierLeaf's shared render, src/commands/push.ts:161)
|
|
1077
|
+
* leads with a fixed sentence ('Result recorded — node finished; ...')
|
|
1078
|
+
* followed unconditionally by `\n\n- report: ${report_path}` (`report_path`
|
|
1079
|
+
* is a required output field, push.ts:94). Returns null when that lead
|
|
1080
|
+
* sentence isn't present, or the `- report:` line is missing (an
|
|
1081
|
+
* unrecognized/error shape either way). */
|
|
1082
|
+
function summarizePushFinal(text) {
|
|
1083
|
+
if (!/^Result recorded — node finished/.test(text))
|
|
1084
|
+
return null;
|
|
1085
|
+
const report = /^- report: (.+)$/m.exec(text)?.[1];
|
|
1086
|
+
if (report === undefined)
|
|
1087
|
+
return null;
|
|
1088
|
+
return [
|
|
1089
|
+
{ text: 'final pushed · node done', tone: 'success' },
|
|
1090
|
+
{ text: basename(report.trim()), tone: 'muted' },
|
|
1091
|
+
];
|
|
1092
|
+
}
|
|
1093
|
+
/** `crtr push urgent` (makeTierLeaf's shared render, src/commands/push.ts:161):
|
|
1094
|
+
* `Urgent report fanned to ${n} subscriber(s) — they are force-woken.\n\n-
|
|
1095
|
+
* report: ${report_path}`, with the same unconditional `- report:` line as
|
|
1096
|
+
* push final. Returns null when the lead sentence doesn't match, or the
|
|
1097
|
+
* `- report:` line is missing. */
|
|
1098
|
+
function summarizePushUrgent(text) {
|
|
1099
|
+
const m = /^Urgent report fanned to (\d+) subscriber/.exec(text);
|
|
1100
|
+
if (m === null)
|
|
1101
|
+
return null;
|
|
1102
|
+
if (/^- report: (.+)$/m.exec(text) === null)
|
|
1103
|
+
return null;
|
|
1104
|
+
return [{ text: `urgent → ${m[1]} subscribers woken`, tone: 'warning' }];
|
|
1105
|
+
}
|
|
1106
|
+
// ---------------------------------------------------------------------------
|
|
1107
|
+
// Batch C — human/memory/pkg. Shared helpers for the two output shapes
|
|
1108
|
+
// renderResult (core/render.ts) can give the SAME logical field depending on
|
|
1109
|
+
// string length/newlines (isProse): a short value is a `- field: value`
|
|
1110
|
+
// bullet; a long/multiline one is prose — labeled `**field:** text` when 2+
|
|
1111
|
+
// prose fields are present, or a bare unlabeled paragraph when it's the SOLE
|
|
1112
|
+
// prose field (renderResult only labels when there's more than one). Several
|
|
1113
|
+
// leaves below carry a field (a path, typically) that crosses this threshold
|
|
1114
|
+
// depending on the user's home directory length, so both forms are handled
|
|
1115
|
+
// rather than assumed.
|
|
1116
|
+
// ---------------------------------------------------------------------------
|
|
1117
|
+
/** Read a `- field: value` bullet or a `**field:** text` labeled-prose block
|
|
1118
|
+
* for the same logical field — the two shapes renderResult can produce once
|
|
1119
|
+
* 2+ prose fields are present (see block comment above). Does NOT recognize
|
|
1120
|
+
* a bare unlabeled solo-prose paragraph (see soloProseAfterBullets for
|
|
1121
|
+
* that); a leaf with exactly one long field needs that instead. */
|
|
1122
|
+
function readFieldFlexible(text, field) {
|
|
1123
|
+
const bullet = new RegExp(`^-\\s+${field}:\\s*(.+)$`, 'm').exec(text);
|
|
1124
|
+
if (bullet !== null)
|
|
1125
|
+
return bullet[1].trim();
|
|
1126
|
+
const labeled = new RegExp(`^\\*\\*${field}:\\*\\*\\s*([\\s\\S]*?)(?=\\n\\n|$)`, 'm').exec(text);
|
|
1127
|
+
return labeled !== null ? labeled[1].trim() : undefined;
|
|
1128
|
+
}
|
|
1129
|
+
/** Recover the SOLE prose field's text when it renders as a bare, unlabeled
|
|
1130
|
+
* paragraph after the bullets block — renderResult's shape for exactly one
|
|
1131
|
+
* long/multiline field among otherwise-short scalars (see block comment
|
|
1132
|
+
* above). Requires at least one bullet line above it (otherwise this isn't
|
|
1133
|
+
* the bullets-then-solo-paragraph shape at all) and bails if what follows
|
|
1134
|
+
* looks like a labeled prose block or an array heading instead of a bare
|
|
1135
|
+
* paragraph — never misparse a different shape as this one. */
|
|
1136
|
+
function soloProseAfterBullets(text) {
|
|
1137
|
+
const lines = text.split('\n');
|
|
1138
|
+
let i = 0;
|
|
1139
|
+
while (i < lines.length && /^- /.test(lines[i]))
|
|
1140
|
+
i++;
|
|
1141
|
+
if (i === 0)
|
|
1142
|
+
return undefined;
|
|
1143
|
+
while (i < lines.length && lines[i].trim() === '')
|
|
1144
|
+
i++;
|
|
1145
|
+
if (i >= lines.length)
|
|
1146
|
+
return undefined;
|
|
1147
|
+
if (/^\*\*\w+:\*\*/.test(lines[i]) || /^\d+ \S+:$/.test(lines[i]) || /^0 \S+\.$/.test(lines[i]))
|
|
1148
|
+
return undefined;
|
|
1149
|
+
const para = [];
|
|
1150
|
+
while (i < lines.length && lines[i].trim() !== '') {
|
|
1151
|
+
para.push(lines[i]);
|
|
1152
|
+
i++;
|
|
1153
|
+
}
|
|
1154
|
+
const text2 = para.join(' ').trim();
|
|
1155
|
+
return text2 === '' ? undefined : text2;
|
|
1156
|
+
}
|
|
1157
|
+
/** Find the first argv token after `skip` that isn't a flag, skipping the
|
|
1158
|
+
* value token that follows any flag listed in `valueFlags` (a boolean flag
|
|
1159
|
+
* with no separate value is skipped on its own via the `startsWith('--')`
|
|
1160
|
+
* check). Generalizes firstPositionalAfter (above) for leaves that mix
|
|
1161
|
+
* boolean and value-taking flags ahead of their positional. */
|
|
1162
|
+
function firstPositionalArg(tokens, skip, valueFlags) {
|
|
1163
|
+
for (let i = skip; i < tokens.length; i++) {
|
|
1164
|
+
const t = tokens[i];
|
|
1165
|
+
if (t.startsWith('--')) {
|
|
1166
|
+
if (valueFlags.has(t))
|
|
1167
|
+
i++;
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
return t;
|
|
1171
|
+
}
|
|
1172
|
+
return undefined;
|
|
1173
|
+
}
|
|
1174
|
+
/** Truncate a single line of text to `max` chars for a muted-aside teaser,
|
|
1175
|
+
* appending an ellipsis when it was cut. */
|
|
1176
|
+
function truncateLine(line, max) {
|
|
1177
|
+
return line.length > max ? `${line.slice(0, max).trimEnd()}…` : line;
|
|
1178
|
+
}
|
|
1179
|
+
const ASK_TEASER_CHARS = 160;
|
|
1180
|
+
/** `crtr human ask` (humanAsk.run, src/commands/human/prompts.ts:97) returns
|
|
1181
|
+
* `{job_id, dir, follow_up}` — `job_id` is always short (a node id) so it
|
|
1182
|
+
* always bullets; `dir`/`follow_up` cross the prose threshold depending on
|
|
1183
|
+
* path length, so the shape is recognized by `job_id` plus one of the two
|
|
1184
|
+
* FIXED follow_up sentences (followUpResult/followUpDrain, shared.ts) being
|
|
1185
|
+
* present anywhere in the text — never by assuming dir/follow_up's bullet
|
|
1186
|
+
* vs. prose form. The question text isn't in the output at all (only the
|
|
1187
|
+
* bare-question invocation echoes it), so it's recovered from argv: the
|
|
1188
|
+
* first non-flag token after `human ask`, skipping `--context-file`'s value
|
|
1189
|
+
* — absent for a `--context-file` deck invocation, which still gets the
|
|
1190
|
+
* road-sign line with no question teaser. Returns null when neither
|
|
1191
|
+
* follow_up sentence is present (an error/unrecognized shape). */
|
|
1192
|
+
function summarizeHumanAsk(text, tokens) {
|
|
1193
|
+
const jobId = /^- job_id:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1194
|
+
if (jobId === undefined)
|
|
1195
|
+
return null;
|
|
1196
|
+
const drained = text.includes('Not in tmux: a human must drain it');
|
|
1197
|
+
const delivered = text.includes('no need to poll');
|
|
1198
|
+
if (!drained && !delivered)
|
|
1199
|
+
return null;
|
|
1200
|
+
const lines = drained
|
|
1201
|
+
? [{ text: 'ask posted — drain via `human inbox`', tone: 'warning' }]
|
|
1202
|
+
: [{ text: 'ask posted → waiting on human', tone: 'accent' }];
|
|
1203
|
+
const question = firstPositionalArg(tokens, 2, new Set(['--context-file']));
|
|
1204
|
+
if (question !== undefined) {
|
|
1205
|
+
const firstLine = truncateLine(question.split('\n')[0].trim(), ASK_TEASER_CHARS);
|
|
1206
|
+
if (firstLine !== '')
|
|
1207
|
+
lines.push({ text: firstLine, tone: 'muted' });
|
|
1208
|
+
}
|
|
1209
|
+
return lines;
|
|
1210
|
+
}
|
|
1211
|
+
/** `crtr human review` (humanReview.run, src/commands/human/prompts.ts:169)
|
|
1212
|
+
* returns `{job_id, dir, output, follow_up}` — same recognition strategy as
|
|
1213
|
+
* summarizeHumanAsk (job_id bullet + one of the two fixed follow_up
|
|
1214
|
+
* sentences, this time followUpReview/followUpDrain). The reviewed file
|
|
1215
|
+
* isn't in the output either, so its basename is recovered from argv (the
|
|
1216
|
+
* file positional, skipping `--output`'s value). Returns null when neither
|
|
1217
|
+
* follow_up sentence is present. */
|
|
1218
|
+
function summarizeHumanReview(text, tokens) {
|
|
1219
|
+
const jobId = /^- job_id:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1220
|
+
if (jobId === undefined)
|
|
1221
|
+
return null;
|
|
1222
|
+
const drained = text.includes('Not in tmux: a human must drain it');
|
|
1223
|
+
const live = text.includes("live on the human's screen for anchored, line-by-line review");
|
|
1224
|
+
if (!drained && !live)
|
|
1225
|
+
return null;
|
|
1226
|
+
const file = firstPositionalArg(tokens, 2, new Set(['--output']));
|
|
1227
|
+
const label = file !== undefined ? `review posted: ${basename(file)}` : 'review posted';
|
|
1228
|
+
return drained
|
|
1229
|
+
? [{ text: `${label} — drain via \`human inbox\``, tone: 'warning' }]
|
|
1230
|
+
: [{ text: `${label} → waiting on human`, tone: 'accent' }];
|
|
1231
|
+
}
|
|
1232
|
+
/** `crtr human list` (humanList.run, src/commands/human/queue.ts:283) has no
|
|
1233
|
+
* custom `render`; the generic renderer bullets `- total: N` then an
|
|
1234
|
+
* `N items:` table (or `0 items.`), columns including `kind` (the deck's
|
|
1235
|
+
* own interaction kind — `decision`/`question`/etc, often empty for a plain
|
|
1236
|
+
* freeform ask; NOT whether it was posed via `ask` vs `review`). Returns
|
|
1237
|
+
* null when the total/items shape doesn't match, or when `total` is
|
|
1238
|
+
* nonzero but the returned page is empty (exhausted pagination, not a
|
|
1239
|
+
* true zero total — collapsing that to "none pending" would misreport). */
|
|
1240
|
+
function summarizeHumanList(text) {
|
|
1241
|
+
const total = parseIntBullet(text, 'total');
|
|
1242
|
+
const rows = parseTableRows(text, 'items');
|
|
1243
|
+
if (total === null || rows === null)
|
|
1244
|
+
return null;
|
|
1245
|
+
if (total === 0)
|
|
1246
|
+
return [{ text: 'none pending', tone: 'success' }];
|
|
1247
|
+
if (rows.length === 0)
|
|
1248
|
+
return null;
|
|
1249
|
+
const kinds = new Map();
|
|
1250
|
+
for (const row of rows) {
|
|
1251
|
+
const kind = row['kind'];
|
|
1252
|
+
if (kind !== undefined && kind !== '')
|
|
1253
|
+
kinds.set(kind, (kinds.get(kind) ?? 0) + 1);
|
|
1254
|
+
}
|
|
1255
|
+
const lines = [{ text: `${total} pending` }];
|
|
1256
|
+
const kindDigest = summarizeTopCounts(kinds, 5, (k, c) => `${k}×${c}`);
|
|
1257
|
+
if (kindDigest !== '')
|
|
1258
|
+
lines.push({ text: kindDigest, tone: 'muted' });
|
|
1259
|
+
return lines;
|
|
1260
|
+
}
|
|
1261
|
+
const DECK_PROMPT_TEASER_CHARS = 160;
|
|
1262
|
+
/** `crtr human deck` (humanDeck.run, src/commands/human/queue.ts:319) bullets
|
|
1263
|
+
* `id`/`interaction_count` (always short) then an `N interactions:` table
|
|
1264
|
+
* (columns `id,kind,prompt,options,allow_freetext`). `options` is a
|
|
1265
|
+
* cellEsc'd array of JSON-stringified `{id,label,...}` objects joined by
|
|
1266
|
+
* `, ` (core/render.ts's scalarStr for an array of objects) — counted via
|
|
1267
|
+
* the `"id":` occurrences each option object carries, not by splitting on
|
|
1268
|
+
* `, ` (a label/description could itself contain a comma). Only the FIRST
|
|
1269
|
+
* interaction is teased — decks built by `ask`/`review` always have exactly
|
|
1270
|
+
* one; a multi-interaction context-file deck gets an `interaction_count`
|
|
1271
|
+
* aside instead of every prompt. Returns null when the `id`/
|
|
1272
|
+
* `interaction_count` bullets or the interactions table don't match, or
|
|
1273
|
+
* when `allow_freetext` isn't exactly `true`/`false` (a malformed/
|
|
1274
|
+
* truncated row must not silently default to "no freetext"). */
|
|
1275
|
+
function summarizeHumanDeck(text) {
|
|
1276
|
+
const id = /^- id:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1277
|
+
const interactionCount = parseIntBullet(text, 'interaction_count');
|
|
1278
|
+
if (id === undefined || interactionCount === null)
|
|
1279
|
+
return null;
|
|
1280
|
+
const rows = parseTableRows(text, 'interactions');
|
|
1281
|
+
if (rows === null || rows.length === 0)
|
|
1282
|
+
return null;
|
|
1283
|
+
const first = rows[0];
|
|
1284
|
+
const prompt = first['prompt'];
|
|
1285
|
+
if (prompt === undefined || prompt === '')
|
|
1286
|
+
return null;
|
|
1287
|
+
const optionCount = (first['options'] ?? '').match(/"id":/g)?.length ?? 0;
|
|
1288
|
+
const allowFreetextRaw = first['allow_freetext'];
|
|
1289
|
+
if (allowFreetextRaw !== 'true' && allowFreetextRaw !== 'false')
|
|
1290
|
+
return null;
|
|
1291
|
+
const allowFreetext = allowFreetextRaw === 'true';
|
|
1292
|
+
const lines = [{ text: truncateLine(prompt, DECK_PROMPT_TEASER_CHARS) }];
|
|
1293
|
+
const parts = [];
|
|
1294
|
+
if (optionCount > 0)
|
|
1295
|
+
parts.push(`${optionCount} option${optionCount === 1 ? '' : 's'}`);
|
|
1296
|
+
if (allowFreetext)
|
|
1297
|
+
parts.push('freetext');
|
|
1298
|
+
if (interactionCount > 1)
|
|
1299
|
+
parts.push(`${interactionCount} interactions`);
|
|
1300
|
+
if (parts.length > 0)
|
|
1301
|
+
lines.push({ text: parts.join(' · '), tone: 'muted' });
|
|
1302
|
+
return lines;
|
|
1303
|
+
}
|
|
1304
|
+
/** `crtr memory list` (listLeaf.run, src/commands/memory/list.ts:65) has no
|
|
1305
|
+
* custom `render`; the generic renderer prints the `follow_up` prose then
|
|
1306
|
+
* an `N items:` table (columns `name,title,short_form,kind,scope,path,
|
|
1307
|
+
* is_dir`). No pagination on this leaf — `rows.length` IS the true total.
|
|
1308
|
+
* Returns null when the items table doesn't match, or is empty (an
|
|
1309
|
+
* empty corpus has nothing to digest into a breakdown). */
|
|
1310
|
+
function summarizeMemoryList(text) {
|
|
1311
|
+
const rows = parseTableRows(text, 'items');
|
|
1312
|
+
if (rows === null || rows.length === 0)
|
|
1313
|
+
return null;
|
|
1314
|
+
const kinds = new Map();
|
|
1315
|
+
const scopes = new Map();
|
|
1316
|
+
for (const row of rows) {
|
|
1317
|
+
const kind = row['kind'];
|
|
1318
|
+
const scope = row['scope'];
|
|
1319
|
+
if (kind === undefined || kind === '' || scope === undefined || scope === '')
|
|
1320
|
+
return null;
|
|
1321
|
+
kinds.set(kind, (kinds.get(kind) ?? 0) + 1);
|
|
1322
|
+
scopes.set(scope, (scopes.get(scope) ?? 0) + 1);
|
|
1323
|
+
}
|
|
1324
|
+
const kindDigest = summarizeTopCounts(kinds, 5, (k, c) => `${k}×${c}`);
|
|
1325
|
+
const scopeDigest = summarizeTopCounts(scopes, 5, (s, c) => `${s}×${c}`);
|
|
1326
|
+
if (kindDigest === '' || scopeDigest === '')
|
|
1327
|
+
return null;
|
|
1328
|
+
return [
|
|
1329
|
+
{ text: `${rows.length} docs` },
|
|
1330
|
+
{ text: `${kindDigest} / ${scopeDigest}`, tone: 'muted' },
|
|
1331
|
+
];
|
|
1332
|
+
}
|
|
1333
|
+
const MEMORY_FIND_NAME_CAP = 5;
|
|
1334
|
+
/** `crtr memory find` (findLeaf.run, src/commands/memory/find.ts:141) always
|
|
1335
|
+
* bullets `- query: <query>` then prose follow_up, then either `0 hits.` or
|
|
1336
|
+
* an `N hits:` table. Ranked mode's table has a `name` column; `--grep`
|
|
1337
|
+
* mode's does not (`path,line,text` instead) — recognized and rejected
|
|
1338
|
+
* (null, generic fallback) rather than digested, since a grep hit has no
|
|
1339
|
+
* document identity to name. Returns null when the query bullet or the
|
|
1340
|
+
* hits table (in ranked-mode shape) don't match. */
|
|
1341
|
+
function summarizeMemoryFind(text) {
|
|
1342
|
+
const query = /^- query:\s*(.+)$/m.exec(text)?.[1]?.trim();
|
|
1343
|
+
if (query === undefined)
|
|
1344
|
+
return null;
|
|
1345
|
+
if (/^0 hits\.$/m.test(text))
|
|
1346
|
+
return [{ text: `0 hits for "${query}"`, tone: 'muted' }];
|
|
1347
|
+
const rows = parseTableRows(text, 'hits');
|
|
1348
|
+
if (rows === null || rows.length === 0)
|
|
1349
|
+
return null;
|
|
1350
|
+
const names = rows.map((r) => r['name']);
|
|
1351
|
+
if (names.some((n) => n === undefined || n === ''))
|
|
1352
|
+
return null;
|
|
1353
|
+
const shown = names.slice(0, MEMORY_FIND_NAME_CAP);
|
|
1354
|
+
const remaining = names.length - shown.length;
|
|
1355
|
+
const namesLine = remaining > 0 ? `${shown.join(', ')}, … ${remaining} more` : shown.join(', ');
|
|
1356
|
+
return [
|
|
1357
|
+
{ text: `${rows.length} hit${rows.length === 1 ? '' : 's'} for "${query}"` },
|
|
1358
|
+
{ text: namesLine, tone: 'muted' },
|
|
1359
|
+
];
|
|
1360
|
+
}
|
|
1361
|
+
/** `crtr memory write` (writeLeaf.run, src/commands/memory/write.ts:123)
|
|
1362
|
+
* bullets `name`/`kind`/`scope`/`created` (always short); `path` and
|
|
1363
|
+
* `follow_up` are always long, so they render as the two labeled prose
|
|
1364
|
+
* blocks (`**path:**`/`**follow_up:**`) — read via readFieldFlexible.
|
|
1365
|
+
* Returns null when `name` or `created` (a plain boolean) isn't present as
|
|
1366
|
+
* a bullet — an error/unrecognized shape. */
|
|
1367
|
+
function summarizeMemoryWrite(text) {
|
|
1368
|
+
const name = /^- name:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1369
|
+
const createdMatch = /^- created:\s*(true|false)\s*$/m.exec(text);
|
|
1370
|
+
if (name === undefined || createdMatch === null)
|
|
1371
|
+
return null;
|
|
1372
|
+
const created = createdMatch[1] === 'true';
|
|
1373
|
+
const lines = [{ text: `${created ? 'wrote' : 'updated'} ${name}`, tone: 'success' }];
|
|
1374
|
+
const path = readFieldFlexible(text, 'path');
|
|
1375
|
+
if (path !== undefined)
|
|
1376
|
+
lines.push({ text: path, tone: 'muted' });
|
|
1377
|
+
return lines;
|
|
1378
|
+
}
|
|
1379
|
+
/** `crtr pkg plugin install` (pluginInstall.run, src/commands/pkg/
|
|
1380
|
+
* plugin-manage.ts:280) has no custom `render`; the generic renderer
|
|
1381
|
+
* bullets `name`/`scope` (always short) then `path` — almost always long
|
|
1382
|
+
* enough to cross the prose threshold, and since it's the SOLE prose field
|
|
1383
|
+
* it renders as a bare unlabeled paragraph (see soloProseAfterBullets),
|
|
1384
|
+
* not a labeled one. Falls back to a `- path:` bullet for the rare
|
|
1385
|
+
* short-path case. Returns null when `name`/`scope` aren't present as
|
|
1386
|
+
* bullets — an error/unrecognized shape (path is a muted aside, not
|
|
1387
|
+
* required for a recognized digest). */
|
|
1388
|
+
function summarizePkgPluginInstall(text) {
|
|
1389
|
+
const name = /^- name:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1390
|
+
const scope = /^- scope:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1391
|
+
if (name === undefined || scope === undefined)
|
|
1392
|
+
return null;
|
|
1393
|
+
const path = readFieldFlexible(text, 'path') ?? soloProseAfterBullets(text);
|
|
1394
|
+
return [
|
|
1395
|
+
{ text: `installed ${name}`, tone: 'success' },
|
|
1396
|
+
{ text: path !== undefined ? `${scope} · ${path}` : scope, tone: 'muted' },
|
|
1397
|
+
];
|
|
1398
|
+
}
|
|
1399
|
+
/** `crtr pkg plugin update` (pluginUpdate.run, src/commands/pkg/
|
|
1400
|
+
* plugin-manage.ts:349) has no custom `render`; the generic renderer emits
|
|
1401
|
+
* an `N updated:` table (columns `name,updated,sha`) — one row when
|
|
1402
|
+
* `--name` targets a single plugin, one row per installed plugin
|
|
1403
|
+
* otherwise. There is no version field anywhere in this output (unlike
|
|
1404
|
+
* command-icons.md's hypothetical version-delta wording) — `sha` (short,
|
|
1405
|
+
* 7 chars) is the only content available to show. Returns null when the
|
|
1406
|
+
* table doesn't match this shape, or when any row's `updated` cell isn't
|
|
1407
|
+
* exactly `true`/`false` (the multi-row loop mirrors the single-row
|
|
1408
|
+
* validation above — a malformed cell must not be silently counted as
|
|
1409
|
+
* "already current"). */
|
|
1410
|
+
function summarizePkgPluginUpdate(text) {
|
|
1411
|
+
const rows = parseTableRows(text, 'updated');
|
|
1412
|
+
if (rows === null)
|
|
1413
|
+
return null;
|
|
1414
|
+
if (rows.length === 0)
|
|
1415
|
+
return [{ text: 'nothing to update', tone: 'muted' }];
|
|
1416
|
+
if (rows.length === 1) {
|
|
1417
|
+
const row = rows[0];
|
|
1418
|
+
const name = row['name'];
|
|
1419
|
+
const updated = row['updated'];
|
|
1420
|
+
if (name === undefined || (updated !== 'true' && updated !== 'false'))
|
|
1421
|
+
return null;
|
|
1422
|
+
const sha = (row['sha'] ?? '').slice(0, 7);
|
|
1423
|
+
const lines = [
|
|
1424
|
+
updated === 'true' ? { text: `updated ${name}`, tone: 'success' } : { text: `${name} already current`, tone: 'muted' },
|
|
1425
|
+
];
|
|
1426
|
+
if (sha !== '')
|
|
1427
|
+
lines.push({ text: sha, tone: 'muted' });
|
|
1428
|
+
return lines;
|
|
1429
|
+
}
|
|
1430
|
+
let updatedCount = 0;
|
|
1431
|
+
for (const row of rows) {
|
|
1432
|
+
const updated = row['updated'];
|
|
1433
|
+
if (updated !== 'true' && updated !== 'false')
|
|
1434
|
+
return null;
|
|
1435
|
+
if (updated === 'true')
|
|
1436
|
+
updatedCount++;
|
|
1437
|
+
}
|
|
1438
|
+
const current = rows.length - updatedCount;
|
|
1439
|
+
return [{ text: `${updatedCount} updated · ${current} already current` }];
|
|
1440
|
+
}
|
|
1441
|
+
const PKG_LIST_NAME_CAP = 8;
|
|
1442
|
+
/** `crtr pkg plugin list` (pluginList.run, src/commands/pkg/
|
|
1443
|
+
* plugin-inspect.ts:12) bullets `- total: N` then an `N items:` table
|
|
1444
|
+
* (columns `name,scope,version,enabled,source_marketplace,path`).
|
|
1445
|
+
* `enabled`/name counts are read from the RETURNED page only — with the
|
|
1446
|
+
* default 50-per-page limit this matches `total` in practice, but a
|
|
1447
|
+
* narrower `--limit` would undercount enabled/shown names against the
|
|
1448
|
+
* true total (the same page-vs-total caveat other paginated summaries in
|
|
1449
|
+
* this file carry). Returns null when the total/items shape doesn't
|
|
1450
|
+
* match, or when `total` is nonzero but the returned page is empty
|
|
1451
|
+
* (exhausted pagination, not a true zero total). */
|
|
1452
|
+
function summarizePkgPluginList(text) {
|
|
1453
|
+
const total = parseIntBullet(text, 'total');
|
|
1454
|
+
const rows = parseTableRows(text, 'items');
|
|
1455
|
+
if (total === null || rows === null)
|
|
1456
|
+
return null;
|
|
1457
|
+
if (total === 0)
|
|
1458
|
+
return [{ text: '0 plugins', tone: 'muted' }];
|
|
1459
|
+
if (rows.length === 0)
|
|
1460
|
+
return null;
|
|
1461
|
+
let enabledCount = 0;
|
|
1462
|
+
const names = [];
|
|
1463
|
+
for (const row of rows) {
|
|
1464
|
+
const name = row['name'];
|
|
1465
|
+
const enabled = row['enabled'];
|
|
1466
|
+
if (name === undefined || name === '' || (enabled !== 'true' && enabled !== 'false'))
|
|
1467
|
+
return null;
|
|
1468
|
+
if (enabled === 'true')
|
|
1469
|
+
enabledCount++;
|
|
1470
|
+
names.push(name);
|
|
1471
|
+
}
|
|
1472
|
+
const shown = names.slice(0, PKG_LIST_NAME_CAP);
|
|
1473
|
+
const remaining = names.length - shown.length;
|
|
1474
|
+
const namesLine = remaining > 0 ? `${shown.join(', ')}, … ${remaining} more` : shown.join(', ');
|
|
1475
|
+
return [
|
|
1476
|
+
{ text: `${total} plugins · ${enabledCount} enabled` },
|
|
1477
|
+
{ text: namesLine, tone: 'muted' },
|
|
1478
|
+
];
|
|
1479
|
+
}
|
|
1480
|
+
/** `crtr pkg plugin show` (pluginShow.run, src/commands/pkg/
|
|
1481
|
+
* plugin-inspect.ts:76) bullets `name`/`scope`/`path`/`enabled` (path is
|
|
1482
|
+
* usually short here — a plugin dir, not a nested interaction path — so
|
|
1483
|
+
* stays a bullet) then `manifest` — an OBJECT, so core/render.ts's
|
|
1484
|
+
* scalarStr always JSON.stringifies it to a bullet regardless of length
|
|
1485
|
+
* (isProse only ever triggers for strings) — then an `N docs:` table (or
|
|
1486
|
+
* `0 docs.`). `version` is read from the parsed manifest JSON, not a
|
|
1487
|
+
* top-level field. Returns null when `name`/`enabled`/`manifest` don't
|
|
1488
|
+
* match, `manifest` isn't valid JSON, or the docs count line is missing. */
|
|
1489
|
+
function summarizePkgPluginShow(text) {
|
|
1490
|
+
const name = /^- name:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1491
|
+
const enabledMatch = /^- enabled:\s*(true|false)\s*$/m.exec(text);
|
|
1492
|
+
const manifestMatch = /^- manifest:\s*(\{.*\})\s*$/m.exec(text);
|
|
1493
|
+
if (name === undefined || enabledMatch === null || manifestMatch === null)
|
|
1494
|
+
return null;
|
|
1495
|
+
let manifest;
|
|
1496
|
+
try {
|
|
1497
|
+
manifest = JSON.parse(manifestMatch[1]);
|
|
1498
|
+
}
|
|
1499
|
+
catch {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
const version = manifest !== null && typeof manifest === 'object' && 'version' in manifest && typeof manifest.version === 'string'
|
|
1503
|
+
? manifest.version
|
|
1504
|
+
: undefined;
|
|
1505
|
+
const docsCount = /^0 docs\.$/m.test(text) ? 0 : Number(/^(\d+) docs:$/m.exec(text)?.[1] ?? NaN);
|
|
1506
|
+
if (Number.isNaN(docsCount))
|
|
1507
|
+
return null;
|
|
1508
|
+
const enabled = enabledMatch[1] === 'true';
|
|
1509
|
+
return [
|
|
1510
|
+
{ text: `${name}${version !== undefined ? `@${version}` : ''} · ${enabled ? 'enabled' : 'disabled'}`, tone: enabled ? 'success' : 'muted' },
|
|
1511
|
+
{ text: `${docsCount} substrate doc${docsCount === 1 ? '' : 's'}`, tone: 'muted' },
|
|
1512
|
+
];
|
|
1513
|
+
}
|
|
1514
|
+
/** `crtr pkg market list` (marketList.run, src/commands/pkg/
|
|
1515
|
+
* market-inspect.ts:14) bullets `- total: N` then an `N items:` table
|
|
1516
|
+
* (columns `name,scope,url,ref,path`). Returns null when the total/items
|
|
1517
|
+
* shape doesn't match, or when `total` is nonzero but the returned page
|
|
1518
|
+
* is empty (exhausted pagination, not a true zero total). */
|
|
1519
|
+
function summarizePkgMarketList(text) {
|
|
1520
|
+
const total = parseIntBullet(text, 'total');
|
|
1521
|
+
const rows = parseTableRows(text, 'items');
|
|
1522
|
+
if (total === null || rows === null)
|
|
1523
|
+
return null;
|
|
1524
|
+
if (total === 0)
|
|
1525
|
+
return [{ text: '0 marketplaces', tone: 'muted' }];
|
|
1526
|
+
if (rows.length === 0)
|
|
1527
|
+
return null;
|
|
1528
|
+
const names = rows.map((r) => r['name']);
|
|
1529
|
+
if (names.some((n) => n === undefined || n === ''))
|
|
1530
|
+
return null;
|
|
1531
|
+
return [
|
|
1532
|
+
{ text: `${total} marketplaces` },
|
|
1533
|
+
{ text: names.join(', '), tone: 'muted' },
|
|
1534
|
+
];
|
|
1535
|
+
}
|
|
1536
|
+
const MARKET_BROWSE_NAME_CAP = 8;
|
|
1537
|
+
/** `crtr pkg market browse` (marketBrowse.run, src/commands/pkg/
|
|
1538
|
+
* market-inspect.ts:77) bullets `marketplace`/`total` then an `N items:`
|
|
1539
|
+
* table (columns include `name,ref,source,version,description,keywords,
|
|
1540
|
+
* installed,installed_scope`). Returns null when the marketplace/total
|
|
1541
|
+
* bullets or the items table don't match, or when `total` is nonzero but
|
|
1542
|
+
* the returned page is empty (exhausted pagination, not a true zero
|
|
1543
|
+
* total). */
|
|
1544
|
+
function summarizePkgMarketBrowse(text) {
|
|
1545
|
+
const market = /^- marketplace:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1546
|
+
const total = parseIntBullet(text, 'total');
|
|
1547
|
+
const rows = parseTableRows(text, 'items');
|
|
1548
|
+
if (market === undefined || total === null || rows === null)
|
|
1549
|
+
return null;
|
|
1550
|
+
if (total === 0)
|
|
1551
|
+
return [{ text: `0 plugins in ${market}`, tone: 'muted' }];
|
|
1552
|
+
if (rows.length === 0)
|
|
1553
|
+
return null;
|
|
1554
|
+
const names = rows.map((r) => r['name']);
|
|
1555
|
+
if (names.some((n) => n === undefined || n === ''))
|
|
1556
|
+
return null;
|
|
1557
|
+
const shown = names.slice(0, MARKET_BROWSE_NAME_CAP);
|
|
1558
|
+
const remaining = names.length - shown.length;
|
|
1559
|
+
const namesLine = remaining > 0 ? `${shown.join(', ')}, … ${remaining} more` : shown.join(', ');
|
|
1560
|
+
return [
|
|
1561
|
+
{ text: `${total} plugins in ${market}` },
|
|
1562
|
+
{ text: namesLine, tone: 'muted' },
|
|
1563
|
+
];
|
|
1564
|
+
}
|
|
1565
|
+
// ---------------------------------------------------------------------------
|
|
1566
|
+
// Batch D — search/sys/surface. search's three leaves share a rendering
|
|
1567
|
+
// convention (a numbered `### i. title` heading per item, an optional meta
|
|
1568
|
+
// line, then highlight bullets or a text blockquote — see exa.ts's
|
|
1569
|
+
// renderResult) and a fixed, unconditional follow_up sentence per leaf that
|
|
1570
|
+
// doubles as a shape boundary; sys/surface leaves have no shared shape and
|
|
1571
|
+
// are handled independently below.
|
|
1572
|
+
// ---------------------------------------------------------------------------
|
|
1573
|
+
const SEARCH_RESULT_CAP = 3;
|
|
1574
|
+
/** Pull a bare host out of a search result's meta line (renderResult,
|
|
1575
|
+
* src/commands/search/exa.ts:150 — `url · date · author · via source`, url
|
|
1576
|
+
* first when present) for a compact `title — domain` digest row. Returns
|
|
1577
|
+
* undefined when the line isn't a `https?://` url — a highlight bullet with
|
|
1578
|
+
* no meta line at all, or a meta line that leads with the date/author
|
|
1579
|
+
* because url was absent. */
|
|
1580
|
+
function extractDomain(metaLine) {
|
|
1581
|
+
const first = (metaLine.split(' · ')[0] ?? '').trim();
|
|
1582
|
+
const m = /^https?:\/\/(?:www\.)?([^/\s]+)/.exec(first);
|
|
1583
|
+
return m?.[1];
|
|
1584
|
+
}
|
|
1585
|
+
/** `search web`'s follow_up (src/commands/search/web.ts:56–57) is a FIXED
|
|
1586
|
+
* string every call appends after the results (or `No results…`) line —
|
|
1587
|
+
* its presence is required below so a corrupted/foreign document that
|
|
1588
|
+
* happens to start with a matching lead line still can't be mistaken for
|
|
1589
|
+
* this leaf's real output. */
|
|
1590
|
+
const SEARCH_WEB_FOLLOWUP_MARKER = 'Fetch full content for selected result URLs with the contents leaf after checking its schema.';
|
|
1591
|
+
/** `crtr search web` (webLeaf.render, src/commands/search/web.ts:70) has two
|
|
1592
|
+
* shapes: `No results for "<query>".` (zero hits) or `${N} results for
|
|
1593
|
+
* "${query}":` followed by one `### i. title` heading per result (each
|
|
1594
|
+
* optionally followed by a meta line — see extractDomain) then highlight
|
|
1595
|
+
* bullets or a text blockquote, blank-line separated, then the fixed
|
|
1596
|
+
* follow_up road sign (see SEARCH_WEB_FOLLOWUP_MARKER). Returns null when
|
|
1597
|
+
* the follow_up marker is absent, the lead line matches neither shape, or
|
|
1598
|
+
* the parsed heading count doesn't equal the declared total (a pi
|
|
1599
|
+
* bash-tool truncation mid-list, or an unrecognized layout). */
|
|
1600
|
+
function summarizeSearchWeb(text) {
|
|
1601
|
+
if (!text.includes(SEARCH_WEB_FOLLOWUP_MARKER))
|
|
1602
|
+
return null;
|
|
1603
|
+
const firstLine = text.split('\n')[0] ?? '';
|
|
1604
|
+
const noResults = /^No results for "(.+)"\.$/.exec(firstLine);
|
|
1605
|
+
if (noResults !== null)
|
|
1606
|
+
return [{ text: `0 results for "${noResults[1]}"`, tone: 'muted' }];
|
|
1607
|
+
const head = /^(\d+) results for ".+":$/.exec(firstLine);
|
|
1608
|
+
if (head === null)
|
|
1609
|
+
return null;
|
|
1610
|
+
const count = Number(head[1]);
|
|
1611
|
+
const lines = text.split('\n');
|
|
1612
|
+
const items = [];
|
|
1613
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1614
|
+
const h = /^### \d+\. (.+)$/.exec(lines[i]);
|
|
1615
|
+
if (h === null)
|
|
1616
|
+
continue;
|
|
1617
|
+
items.push({ title: h[1], domain: extractDomain(lines[i + 1] ?? '') });
|
|
1618
|
+
}
|
|
1619
|
+
if (items.length !== count)
|
|
1620
|
+
return null;
|
|
1621
|
+
const top = items.slice(0, SEARCH_RESULT_CAP).map((it) => (it.domain !== undefined ? `${it.title} — ${it.domain}` : it.title));
|
|
1622
|
+
const out = [{ text: `${count} result${count === 1 ? '' : 's'}` }];
|
|
1623
|
+
for (const t of top)
|
|
1624
|
+
out.push({ text: t, tone: 'muted' });
|
|
1625
|
+
const remaining = items.length - top.length;
|
|
1626
|
+
if (remaining > 0)
|
|
1627
|
+
out.push({ text: `… ${remaining} more`, tone: 'muted' });
|
|
1628
|
+
return out;
|
|
1629
|
+
}
|
|
1630
|
+
/** Fixed lead sentence of `search answer`'s follow_up (src/commands/search/
|
|
1631
|
+
* answer.ts:39) — the SAME string on every call (no branching), so its
|
|
1632
|
+
* presence marks where the answer body ends and the follow_up road sign
|
|
1633
|
+
* begins, regardless of whether citations are present. */
|
|
1634
|
+
const SEARCH_ANSWER_FOLLOWUP_MARKER = 'Read any cited source in full with the contents leaf';
|
|
1635
|
+
/** `crtr search answer` (answerLeaf.render, src/commands/search/answer.ts:52)
|
|
1636
|
+
* has two shapes: `No answer was returned.` (empty) or `${answer}\n\n` then,
|
|
1637
|
+
* when citations exist, `Sources (${N}):` plus one numbered line per
|
|
1638
|
+
* citation and a blank line, then unconditionally the fixed follow_up
|
|
1639
|
+
* sentence. Citations are optional; the follow_up marker is not, so it
|
|
1640
|
+
* locates the answer/Sources boundary either way. Mirrors
|
|
1641
|
+
* summarizeMemoryRead's teaser+ellipsis shape for the answer body. Returns
|
|
1642
|
+
* null when the follow_up marker is absent (unrecognized shape) or the
|
|
1643
|
+
* recovered answer text is empty. */
|
|
1644
|
+
function summarizeSearchAnswer(text) {
|
|
1645
|
+
const followUpIdx = text.indexOf(SEARCH_ANSWER_FOLLOWUP_MARKER);
|
|
1646
|
+
if (followUpIdx === -1)
|
|
1647
|
+
return null;
|
|
1648
|
+
if (/^No answer was returned\.$/.test(text.split('\n')[0] ?? '')) {
|
|
1649
|
+
return [{ text: 'no answer returned', tone: 'muted' }];
|
|
1650
|
+
}
|
|
1651
|
+
const sourcesMatch = /^Sources \((\d+)\):$/m.exec(text);
|
|
1652
|
+
const bodyEnd = sourcesMatch !== null ? text.indexOf(sourcesMatch[0]) : followUpIdx;
|
|
1653
|
+
if (bodyEnd <= 0)
|
|
1654
|
+
return null;
|
|
1655
|
+
const answer = text.slice(0, bodyEnd).trim();
|
|
1656
|
+
if (answer === '')
|
|
1657
|
+
return null;
|
|
1658
|
+
let citationCount = 0;
|
|
1659
|
+
if (sourcesMatch !== null) {
|
|
1660
|
+
citationCount = Number(sourcesMatch[1]);
|
|
1661
|
+
const citationsBlock = text.slice(text.indexOf(sourcesMatch[0]) + sourcesMatch[0].length, followUpIdx);
|
|
1662
|
+
const numberedLines = [...citationsBlock.matchAll(/^\d+\. .+$/gm)];
|
|
1663
|
+
if (numberedLines.length !== citationCount)
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
const answerLines = answer.split('\n').filter((l) => l.trim() !== '');
|
|
1667
|
+
const teaser = answerLines.slice(0, MEMORY_READ_CONTENT_LINES);
|
|
1668
|
+
const hasMore = answerLines.length > MEMORY_READ_CONTENT_LINES;
|
|
1669
|
+
const lines = teaser.map((l) => ({ text: l }));
|
|
1670
|
+
if (hasMore)
|
|
1671
|
+
lines.push({ text: '…', tone: 'muted' });
|
|
1672
|
+
lines.push({ text: `${citationCount} citation${citationCount === 1 ? '' : 's'}`, tone: 'muted' });
|
|
1673
|
+
return lines;
|
|
1674
|
+
}
|
|
1675
|
+
/** Fixed lead sentence of `search contents`'s follow_up (src/commands/
|
|
1676
|
+
* search/contents.ts:71) — always this exact string, used to bound the
|
|
1677
|
+
* last result block below (its true end would otherwise swallow a trailing
|
|
1678
|
+
* `Failed (…):` block and the follow_up text into the char count). */
|
|
1679
|
+
const SEARCH_CONTENTS_FOLLOWUP_MARKER = 'Stale or empty content? Re-run with --max-age-hours 0';
|
|
1680
|
+
const SEARCH_CONTENTS_NAME_CAP = 5;
|
|
1681
|
+
/** `crtr search contents` (contentsLeaf.render, src/commands/search/
|
|
1682
|
+
* contents.ts:87): `No content extracted.` (0 results) or `Extracted ${N}
|
|
1683
|
+
* URL(s):` plus one `### i. title` block per result (heading, optional
|
|
1684
|
+
* meta line, then highlight bullets or a text blockquote); either way, an
|
|
1685
|
+
* optional `Failed (${M}):` bullet list follows, then unconditionally the
|
|
1686
|
+
* fixed follow_up sentence. Per-URL `chars` is the length of that result's
|
|
1687
|
+
* own rendered content block (title/meta lines excluded) — the visible
|
|
1688
|
+
* extracted text, not Exa's own byte count. Returns null when the
|
|
1689
|
+
* follow_up marker is absent, the heading count doesn't match `N`, or (in
|
|
1690
|
+
* the 0-results case) there were no failures either — that combination
|
|
1691
|
+
* isn't a real shape this leaf produces. */
|
|
1692
|
+
function summarizeSearchContents(text) {
|
|
1693
|
+
const followUpIdx = text.indexOf(SEARCH_CONTENTS_FOLLOWUP_MARKER);
|
|
1694
|
+
if (followUpIdx === -1)
|
|
1695
|
+
return null;
|
|
1696
|
+
const failedMatch = /^Failed \((\d+)\):$/m.exec(text);
|
|
1697
|
+
const failedCount = failedMatch !== null ? Number(failedMatch[1]) : 0;
|
|
1698
|
+
const firstLine = text.split('\n')[0] ?? '';
|
|
1699
|
+
if (/^No content extracted\.$/.test(firstLine)) {
|
|
1700
|
+
if (failedCount === 0)
|
|
1701
|
+
return [{ text: 'no content extracted', tone: 'muted' }];
|
|
1702
|
+
return [{ text: `0 extracted · ${failedCount} failed`, tone: 'error' }];
|
|
1703
|
+
}
|
|
1704
|
+
const head = /^Extracted (\d+) URLs?:$/.exec(firstLine);
|
|
1705
|
+
if (head === null)
|
|
1706
|
+
return null;
|
|
1707
|
+
const count = Number(head[1]);
|
|
1708
|
+
const lines = text.split('\n');
|
|
1709
|
+
const headingIdxs = [];
|
|
1710
|
+
for (let i = 0; i < lines.length; i++)
|
|
1711
|
+
if (/^### \d+\. /.test(lines[i]))
|
|
1712
|
+
headingIdxs.push(i);
|
|
1713
|
+
if (headingIdxs.length !== count)
|
|
1714
|
+
return null;
|
|
1715
|
+
const contentEndOffset = failedMatch !== null ? text.indexOf(failedMatch[0]) : followUpIdx;
|
|
1716
|
+
const contentEndLine = text.slice(0, contentEndOffset).split('\n').length - 1;
|
|
1717
|
+
const items = [];
|
|
1718
|
+
for (let k = 0; k < headingIdxs.length; k++) {
|
|
1719
|
+
const start = headingIdxs[k];
|
|
1720
|
+
const end = k + 1 < headingIdxs.length ? headingIdxs[k + 1] : contentEndLine;
|
|
1721
|
+
const heading = /^### \d+\. (.+)$/.exec(lines[start]);
|
|
1722
|
+
if (heading === null)
|
|
1723
|
+
return null;
|
|
1724
|
+
const body = lines.slice(start + 1, end);
|
|
1725
|
+
// renderResult (exa.ts:184–189) puts the meta line — url first when
|
|
1726
|
+
// present — immediately after the heading; drop it so `chars` reflects
|
|
1727
|
+
// only the extracted content, not the URL/date/author line.
|
|
1728
|
+
const contentLines = body.length > 0 && /^https?:\/\//.test(body[0]) ? body.slice(1) : body;
|
|
1729
|
+
items.push({ title: heading[1], chars: contentLines.join('\n').trim().length });
|
|
1730
|
+
}
|
|
1731
|
+
const shown = items.slice(0, SEARCH_CONTENTS_NAME_CAP).map((it) => `${it.title} · ${it.chars} chars`);
|
|
1732
|
+
const out = [{ text: `extracted ${count} URL${count === 1 ? '' : 's'}` }];
|
|
1733
|
+
for (const s of shown)
|
|
1734
|
+
out.push({ text: s, tone: 'muted' });
|
|
1735
|
+
const remaining = items.length - shown.length;
|
|
1736
|
+
if (remaining > 0)
|
|
1737
|
+
out.push({ text: `… ${remaining} more`, tone: 'muted' });
|
|
1738
|
+
if (failedCount > 0)
|
|
1739
|
+
out.push({ text: `${failedCount} failed`, tone: 'warning' });
|
|
1740
|
+
return out;
|
|
1741
|
+
}
|
|
1742
|
+
/** `crtr sys setup` opens a pi-tui wizard when stdout is a TTY (its `render`
|
|
1743
|
+
* is always `''` — the wizard writes directly via process.stdout, and its
|
|
1744
|
+
* raw ANSI curses frames never match any marker below, so this summarizer
|
|
1745
|
+
* safely returns null in that case). In the headless/agent path
|
|
1746
|
+
* (src/commands/sys/setup.ts's `!process.stdout.isTTY` branch,
|
|
1747
|
+
* `buildSetupStaticInstructions` in setup-core.ts:328 — the shape every
|
|
1748
|
+
* bash-tool call to this leaf actually produces) it instead prints a
|
|
1749
|
+
* static inventory: the headline `crtr sys setup (non-interactive)`, a
|
|
1750
|
+
* `Packages:` section (`- <name> — <description> (installed|not
|
|
1751
|
+
* installed)`), a `System deps:` section (`- <tool>: ✓ installed` or `-
|
|
1752
|
+
* <tool>: <command/hint>`), then an `- Exa key: …` bullet and a footer line
|
|
1753
|
+
* — verified against the live command, 2026-07-01. Returns null when the
|
|
1754
|
+
* headline, both section headers, or the Exa-key marker bounding the
|
|
1755
|
+
* System deps section are missing, or any row's shape doesn't match — a
|
|
1756
|
+
* partial/truncated dump must not report a false step count. */
|
|
1757
|
+
function summarizeSysSetup(text) {
|
|
1758
|
+
if ((text.split('\n')[0] ?? '') !== 'crtr sys setup (non-interactive)')
|
|
1759
|
+
return null;
|
|
1760
|
+
const lines = text.split('\n');
|
|
1761
|
+
const pkgIdx = lines.indexOf('Packages:');
|
|
1762
|
+
const sysIdx = lines.indexOf('System deps:');
|
|
1763
|
+
if (pkgIdx === -1 || sysIdx === -1 || sysIdx <= pkgIdx)
|
|
1764
|
+
return null;
|
|
1765
|
+
const exaIdx = lines.findIndex((l, i) => i > sysIdx && /^- Exa key:/.test(l));
|
|
1766
|
+
if (exaIdx === -1)
|
|
1767
|
+
return null;
|
|
1768
|
+
const pkgLines = lines.slice(pkgIdx + 1, sysIdx).filter((l) => l.trim() !== '');
|
|
1769
|
+
const sysLines = lines.slice(sysIdx + 1, exaIdx).filter((l) => l.trim() !== '');
|
|
1770
|
+
if (pkgLines.length === 0 && sysLines.length === 0)
|
|
1771
|
+
return null;
|
|
1772
|
+
let ok = 0;
|
|
1773
|
+
const pending = [];
|
|
1774
|
+
for (const l of pkgLines) {
|
|
1775
|
+
const m = /^- (.+?) — .+\((installed|not installed)\)$/.exec(l);
|
|
1776
|
+
if (m === null)
|
|
1777
|
+
return null;
|
|
1778
|
+
if (m[2] === 'installed')
|
|
1779
|
+
ok++;
|
|
1780
|
+
else
|
|
1781
|
+
pending.push(m[1]);
|
|
1782
|
+
}
|
|
1783
|
+
for (const l of sysLines) {
|
|
1784
|
+
const m = /^- (\S+): (.+)$/.exec(l);
|
|
1785
|
+
if (m === null)
|
|
1786
|
+
return null;
|
|
1787
|
+
if (m[2] === '✓ installed')
|
|
1788
|
+
ok++;
|
|
1789
|
+
else
|
|
1790
|
+
pending.push(m[1]);
|
|
1791
|
+
}
|
|
1792
|
+
if (pending.length === 0)
|
|
1793
|
+
return [{ text: `${ok} steps ok`, tone: 'success' }];
|
|
1794
|
+
return [
|
|
1795
|
+
{ text: `${ok} ok · ${pending.length} pending`, tone: 'warning' },
|
|
1796
|
+
{ text: pending.join(', '), tone: 'muted' },
|
|
1797
|
+
];
|
|
1798
|
+
}
|
|
1799
|
+
const SYS_DOCTOR_ISSUE_CAP = 5;
|
|
1800
|
+
/** `crtr sys doctor` (sysDoctorLeaf.run, src/commands/sys/doctor.ts:283) has
|
|
1801
|
+
* no custom `render`; the generic renderer bullets `ok`/`failed` then an `N
|
|
1802
|
+
* checks:` table (columns scope,name,status,message,remediation, plus a
|
|
1803
|
+
* `fixed` column ONLY when at least one row set it — renderArray's column
|
|
1804
|
+
* set is the union of keys across every row, src/core/render.ts:60).
|
|
1805
|
+
* `failed` counts only UNRESOLVED fails (status:fail with no `fixed:true`)
|
|
1806
|
+
* — mirroring doctor.ts's own `ok`/`failed` computation, which excludes
|
|
1807
|
+
* `fixed:true` rows. Returns null when the ok/failed bullets or the checks
|
|
1808
|
+
* table don't match this shape, or when the open-fail-row count found in
|
|
1809
|
+
* the table doesn't equal the declared `failed` bullet (a truncated table,
|
|
1810
|
+
* or a row missing name/message). */
|
|
1811
|
+
function summarizeSysDoctor(text) {
|
|
1812
|
+
const okMatch = /^- ok:\s*(true|false)\s*$/m.exec(text);
|
|
1813
|
+
const failedMatch = /^- failed:\s*(\d+)\s*$/m.exec(text);
|
|
1814
|
+
if (okMatch === null || failedMatch === null)
|
|
1815
|
+
return null;
|
|
1816
|
+
const rows = parseTableRows(text, 'checks');
|
|
1817
|
+
if (rows === null)
|
|
1818
|
+
return null;
|
|
1819
|
+
const failed = Number(failedMatch[1]);
|
|
1820
|
+
const fixedCount = rows.filter((r) => r['status'] === 'fail' && r['fixed'] === 'true').length;
|
|
1821
|
+
if (failed === 0) {
|
|
1822
|
+
// A fail row with no `fixed:true` here would contradict the declared
|
|
1823
|
+
// `failed: 0` — an inconsistent/foreign shape, not a real PASS.
|
|
1824
|
+
if (rows.some((r) => r['status'] === 'fail' && r['fixed'] !== 'true'))
|
|
1825
|
+
return null;
|
|
1826
|
+
const lines = [{ text: `PASS · ${rows.length} checks`, tone: 'success' }];
|
|
1827
|
+
if (fixedCount > 0)
|
|
1828
|
+
lines.push({ text: `fixed ${fixedCount} automatically`, tone: 'muted' });
|
|
1829
|
+
return lines;
|
|
1830
|
+
}
|
|
1831
|
+
const openIssues = rows.filter((r) => r['status'] === 'fail' && r['fixed'] !== 'true');
|
|
1832
|
+
if (openIssues.length !== failed)
|
|
1833
|
+
return null;
|
|
1834
|
+
const shown = [];
|
|
1835
|
+
for (const row of openIssues.slice(0, SYS_DOCTOR_ISSUE_CAP)) {
|
|
1836
|
+
const name = row['name'];
|
|
1837
|
+
const message = row['message'];
|
|
1838
|
+
if (name === undefined || message === undefined)
|
|
1839
|
+
return null;
|
|
1840
|
+
shown.push(`${name}: ${message}`);
|
|
1841
|
+
}
|
|
1842
|
+
const lines = [{ text: `${failed} issue${failed === 1 ? '' : 's'}`, tone: 'error' }];
|
|
1843
|
+
for (const s of shown)
|
|
1844
|
+
lines.push({ text: s, tone: 'warning' });
|
|
1845
|
+
const remaining = openIssues.length - shown.length;
|
|
1846
|
+
if (remaining > 0)
|
|
1847
|
+
lines.push({ text: `… ${remaining} more`, tone: 'muted' });
|
|
1848
|
+
if (fixedCount > 0)
|
|
1849
|
+
lines.push({ text: `fixed ${fixedCount} automatically`, tone: 'muted' });
|
|
1850
|
+
return lines;
|
|
1851
|
+
}
|
|
1852
|
+
const SYS_UPDATE_OUTDATED_CAP = 5;
|
|
1853
|
+
/** `crtr sys update` (sysUpdateLeaf.run, src/commands/sys/update.ts:19) has
|
|
1854
|
+
* two shapes depending on `--check`: bounded check — a `- up_to_date:
|
|
1855
|
+
* true|false` bullet plus an `N updates:` table (columns name,kind,current,
|
|
1856
|
+
* latest,up_to_date,unreachable — no version field at the top level, so
|
|
1857
|
+
* command-icons.md's `vX.Y.Z` framing doesn't fit this leaf; that's `sys
|
|
1858
|
+
* version`'s shape, not this one); or apply — `- target: self|content|all`
|
|
1859
|
+
* + `- status: done|failed` + `- error: <msg>` (only on failure). Verified
|
|
1860
|
+
* against the live `--check` path, 2026-07-01; the apply path is read from
|
|
1861
|
+
* source only — running it mutates the install (spawns npm/git). Returns
|
|
1862
|
+
* null when neither shape's required bullets/table are present. */
|
|
1863
|
+
function summarizeSysUpdate(text) {
|
|
1864
|
+
const upToDateMatch = /^- up_to_date:\s*(true|false)\s*$/m.exec(text);
|
|
1865
|
+
if (upToDateMatch !== null) {
|
|
1866
|
+
const rows = parseTableRows(text, 'updates');
|
|
1867
|
+
if (rows === null)
|
|
1868
|
+
return null;
|
|
1869
|
+
if (upToDateMatch[1] === 'true') {
|
|
1870
|
+
// sys update's own computation is `up_to_date = updates.every(u =>
|
|
1871
|
+
// u.up_to_date || u.unreachable)` (update.ts:104) — a row that
|
|
1872
|
+
// disagrees (reachable and NOT up to date) contradicts the bullet.
|
|
1873
|
+
if (!rows.every((r) => r['up_to_date'] === 'true' || r['unreachable'] === 'true'))
|
|
1874
|
+
return null;
|
|
1875
|
+
return [{ text: `PASS · ${rows.length} up to date`, tone: 'success' }];
|
|
1876
|
+
}
|
|
1877
|
+
const outdated = [];
|
|
1878
|
+
let unreachable = 0;
|
|
1879
|
+
for (const row of rows) {
|
|
1880
|
+
const name = row['name'];
|
|
1881
|
+
const current = row['current'];
|
|
1882
|
+
const latest = row['latest'];
|
|
1883
|
+
const isUpToDate = row['up_to_date'];
|
|
1884
|
+
const isUnreachable = row['unreachable'];
|
|
1885
|
+
if (name === undefined || isUpToDate === undefined || isUnreachable === undefined)
|
|
1886
|
+
return null;
|
|
1887
|
+
if (isUnreachable === 'true') {
|
|
1888
|
+
unreachable++;
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
if (isUpToDate === 'false')
|
|
1892
|
+
outdated.push(`${name}: ${current} → ${latest}`);
|
|
1893
|
+
}
|
|
1894
|
+
const lines = [{ text: `${outdated.length} outdated · ${rows.length} checked`, tone: 'warning' }];
|
|
1895
|
+
const shown = outdated.slice(0, SYS_UPDATE_OUTDATED_CAP);
|
|
1896
|
+
for (const s of shown)
|
|
1897
|
+
lines.push({ text: s, tone: 'muted' });
|
|
1898
|
+
const remaining = outdated.length - shown.length;
|
|
1899
|
+
if (remaining > 0)
|
|
1900
|
+
lines.push({ text: `… ${remaining} more`, tone: 'muted' });
|
|
1901
|
+
if (unreachable > 0)
|
|
1902
|
+
lines.push({ text: `${unreachable} unreachable`, tone: 'muted' });
|
|
1903
|
+
return lines;
|
|
1904
|
+
}
|
|
1905
|
+
const statusMatch = /^- status:\s*(done|failed)\s*$/m.exec(text);
|
|
1906
|
+
if (statusMatch === null)
|
|
1907
|
+
return null;
|
|
1908
|
+
const target = /^- target:\s*(\S+)\s*$/m.exec(text)?.[1];
|
|
1909
|
+
if (target === undefined)
|
|
1910
|
+
return null;
|
|
1911
|
+
if (statusMatch[1] === 'done')
|
|
1912
|
+
return [{ text: `updated · ${target}`, tone: 'success' }];
|
|
1913
|
+
const error = /^- error:\s*(.+)$/m.exec(text)?.[1];
|
|
1914
|
+
const lines = [{ text: `update failed · ${target}`, tone: 'error' }];
|
|
1915
|
+
if (error !== undefined)
|
|
1916
|
+
lines.push({ text: error, tone: 'muted' });
|
|
1917
|
+
return lines;
|
|
1918
|
+
}
|
|
1919
|
+
/** `crtr sys feedback` (sysFeedbackLeaf.run, src/commands/sys/feedback.ts:
|
|
1920
|
+
* 187) has no custom `render`; the generic renderer bullets `status`
|
|
1921
|
+
* (always short) then either `url` (filed shape — a short GitHub issue
|
|
1922
|
+
* link) or `path`+`error` (saved-locally shape; `path` is usually short but
|
|
1923
|
+
* can cross the prose threshold with a long home dir, and `error` is a
|
|
1924
|
+
* free-form spawn/gh message that often does — read via
|
|
1925
|
+
* readFieldFlexible/soloProseAfterBullets, the same two-shape pattern
|
|
1926
|
+
* Batch C's human/memory summarizers use for a field whose bullet-vs-prose
|
|
1927
|
+
* form depends on string length). Read from source only — running it files
|
|
1928
|
+
* a real GitHub issue or writes a local report, so it was not invoked
|
|
1929
|
+
* live. Returns null when `status` isn't `filed`/`saved-locally`, or the
|
|
1930
|
+
* shape's required companion field is missing. */
|
|
1931
|
+
function summarizeSysFeedback(text) {
|
|
1932
|
+
const statusMatch = /^- status:\s*(filed|saved-locally)\s*$/m.exec(text);
|
|
1933
|
+
if (statusMatch === null)
|
|
1934
|
+
return null;
|
|
1935
|
+
if (statusMatch[1] === 'filed') {
|
|
1936
|
+
const url = readFieldFlexible(text, 'url') ?? soloProseAfterBullets(text);
|
|
1937
|
+
if (url === undefined)
|
|
1938
|
+
return null;
|
|
1939
|
+
return [
|
|
1940
|
+
{ text: 'issue filed', tone: 'success' },
|
|
1941
|
+
{ text: url, tone: 'muted' },
|
|
1942
|
+
];
|
|
1943
|
+
}
|
|
1944
|
+
const path = readFieldFlexible(text, 'path') ?? soloProseAfterBullets(text);
|
|
1945
|
+
if (path === undefined)
|
|
1946
|
+
return null;
|
|
1947
|
+
// The reason (gh missing, unauthenticated, or the API call itself
|
|
1948
|
+
// failing — feedback.ts:188–195) is prose the digest doesn't try to
|
|
1949
|
+
// classify; "not filed" states the fact without guessing the cause.
|
|
1950
|
+
return [
|
|
1951
|
+
{ text: 'saved locally — not filed', tone: 'warning' },
|
|
1952
|
+
{ text: basename(path), tone: 'muted' },
|
|
1953
|
+
];
|
|
1954
|
+
}
|
|
1955
|
+
const SURFACE_VIEW_LIST_ID_CAP = 8;
|
|
1956
|
+
/** `crtr surface view list` (viewListLeaf.render, src/commands/view-list.ts:
|
|
1957
|
+
* 59) has a custom render, NOT the generic renderResult: one padded plain
|
|
1958
|
+
* line per view, `<id padded> <title padded> [<scope>]<optional
|
|
1959
|
+
* desc/error>` (verified live, 2026-07-01) — no GFM table markup at all, so
|
|
1960
|
+
* this file's shared parseTableRows/splitTableRow helpers don't apply.
|
|
1961
|
+
* Real ids/titles never contain a run of 2+ spaces, so each real field
|
|
1962
|
+
* boundary (the column padding plus the literal ` ` separator together
|
|
1963
|
+
* always total 2+ spaces) is recoverable by anchoring on the `[scope]`
|
|
1964
|
+
* bracket. Returns null when the roster is empty (`No views found…`, a
|
|
1965
|
+
* single plain line the generic path already renders fine) or any line
|
|
1966
|
+
* fails to match this row shape — including the pi truncation marker,
|
|
1967
|
+
* which never contains a `[scope]` bracket or a 2+-space run. */
|
|
1968
|
+
function summarizeSurfaceViewList(text) {
|
|
1969
|
+
const lines = text.split('\n').filter((l) => l.trim() !== '');
|
|
1970
|
+
if (lines.length === 0)
|
|
1971
|
+
return null;
|
|
1972
|
+
const ids = [];
|
|
1973
|
+
for (const line of lines) {
|
|
1974
|
+
const m = /^(\S+)\s{2,}.+?\s{2,}\[\w[\w-]*\](?:\s{2,}.*)?$/.exec(line);
|
|
1975
|
+
if (m === null)
|
|
1976
|
+
return null;
|
|
1977
|
+
ids.push(m[1]);
|
|
1978
|
+
}
|
|
1979
|
+
const shown = ids.slice(0, SURFACE_VIEW_LIST_ID_CAP);
|
|
1980
|
+
const remaining = ids.length - shown.length;
|
|
1981
|
+
const idsLine = remaining > 0 ? `${shown.join(', ')}, … ${remaining} more` : shown.join(', ');
|
|
1982
|
+
return [
|
|
1983
|
+
{ text: `${ids.length} views` },
|
|
1984
|
+
{ text: idsLine, tone: 'muted' },
|
|
1985
|
+
];
|
|
1986
|
+
}
|
|
1987
|
+
const SUMMARIZERS = [
|
|
1988
|
+
{ path: 'memory read', run: summarizeMemoryRead },
|
|
1989
|
+
{ path: 'memory lint', run: summarizeMemoryLint },
|
|
1990
|
+
{ path: 'canvas dashboard', run: summarizeCanvasDashboard },
|
|
1991
|
+
{ path: 'node inspect list', run: summarizeNodeInspectList },
|
|
1992
|
+
{ path: 'canvas attention count', run: summarizeCanvasAttentionCount },
|
|
1993
|
+
{ path: 'canvas attention list', run: summarizeCanvasAttentionList },
|
|
1994
|
+
{ path: 'canvas attention map', run: summarizeCanvasAttentionMap },
|
|
1995
|
+
{ path: 'canvas revive', run: summarizeCanvasRevive },
|
|
1996
|
+
{ path: 'canvas history search', run: summarizeCanvasHistorySearch },
|
|
1997
|
+
{ path: 'canvas history read', run: summarizeCanvasHistoryRead },
|
|
1998
|
+
{ path: 'canvas history show', run: summarizeCanvasHistoryShow },
|
|
1999
|
+
{ path: 'canvas daemon start', run: summarizeCanvasDaemonStart },
|
|
2000
|
+
{ path: 'canvas daemon status', run: summarizeCanvasDaemonStatus },
|
|
2001
|
+
{ path: 'canvas daemon stop', run: summarizeCanvasDaemonStop },
|
|
2002
|
+
{ path: 'canvas issue new', run: summarizeCanvasIssueNew },
|
|
2003
|
+
{ path: 'node new', run: summarizeNodeNew },
|
|
2004
|
+
{ path: 'node msg', run: summarizeNodeMsg },
|
|
2005
|
+
{ path: 'node yield', run: summarizeNodeYield },
|
|
2006
|
+
{ path: 'node lifecycle close', run: summarizeNodeLifecycleClose },
|
|
2007
|
+
{ path: 'node triggers list', run: summarizeNodeTriggersList },
|
|
2008
|
+
{ path: 'feed read', run: summarizeFeedRead },
|
|
2009
|
+
{ path: 'feed message', run: summarizeFeedMessage },
|
|
2010
|
+
{ path: 'feed peek', run: summarizeFeedPeek },
|
|
2011
|
+
{ path: 'push urgent', run: summarizePushUrgent },
|
|
2012
|
+
{ path: 'push final', run: summarizePushFinal },
|
|
2013
|
+
{ path: 'human ask', run: summarizeHumanAsk },
|
|
2014
|
+
{ path: 'human review', run: summarizeHumanReview },
|
|
2015
|
+
{ path: 'human list', run: summarizeHumanList },
|
|
2016
|
+
{ path: 'human deck', run: summarizeHumanDeck },
|
|
2017
|
+
{ path: 'memory list', run: summarizeMemoryList },
|
|
2018
|
+
{ path: 'memory find', run: summarizeMemoryFind },
|
|
2019
|
+
{ path: 'memory write', run: summarizeMemoryWrite },
|
|
2020
|
+
{ path: 'pkg plugin install', run: summarizePkgPluginInstall },
|
|
2021
|
+
{ path: 'pkg plugin update', run: summarizePkgPluginUpdate },
|
|
2022
|
+
{ path: 'pkg plugin list', run: summarizePkgPluginList },
|
|
2023
|
+
{ path: 'pkg plugin show', run: summarizePkgPluginShow },
|
|
2024
|
+
{ path: 'pkg market list', run: summarizePkgMarketList },
|
|
2025
|
+
{ path: 'pkg market browse', run: summarizePkgMarketBrowse },
|
|
2026
|
+
{ path: 'search web', run: summarizeSearchWeb },
|
|
2027
|
+
{ path: 'search answer', run: summarizeSearchAnswer },
|
|
2028
|
+
{ path: 'search contents', run: summarizeSearchContents },
|
|
2029
|
+
{ path: 'sys setup', run: summarizeSysSetup },
|
|
2030
|
+
{ path: 'sys doctor', run: summarizeSysDoctor },
|
|
2031
|
+
{ path: 'sys update', run: summarizeSysUpdate },
|
|
2032
|
+
{ path: 'sys feedback', run: summarizeSysFeedback },
|
|
2033
|
+
{ path: 'surface view list', run: summarizeSurfaceViewList },
|
|
2034
|
+
];
|
|
2035
|
+
/** Dispatch a command's argv tokens + its raw result text to the summarizer
|
|
2036
|
+
* that owns that leaf path (longest-registered-prefix match, same shape as
|
|
2037
|
+
* suppressOutputForPath), or null when no command owns a summary — the
|
|
2038
|
+
* caller falls through to generic truncation in that case. */
|
|
2039
|
+
export function summarizePath(tokens, rawText) {
|
|
2040
|
+
for (const { path, run } of SUMMARIZERS) {
|
|
2041
|
+
const words = path.split(' ');
|
|
2042
|
+
if (words.length <= tokens.length && words.every((word, i) => tokens[i] === word)) {
|
|
2043
|
+
return run(rawText, tokens);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|