@north-light/crouter 0.3.227 → 0.3.228
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/dist/api/client.d.ts +5 -5
- package/dist/api/client.js +3 -3
- package/dist/api/dto/broker-ops.d.ts +8 -0
- package/dist/api/dto/memory.d.ts +8 -7
- package/dist/api/dto/memory.js +2 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
- package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
- package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
- package/dist/builtin-memory/design/guide.md +38 -16
- package/dist/builtin-memory/design/roadmap.md +4 -4
- package/dist/builtin-memory/insights/init.md +5 -5
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +6 -6
- package/dist/builtin-memory/internal/plugins.md +1 -1
- package/dist/builtin-memory/plan/guide.md +53 -0
- package/dist/builtin-memory/plan/roadmap.md +10 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
- package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
- package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
- package/dist/clients/attach/chrome/inbox-strip.js +11 -7
- package/dist/clients/attach/session/pane-tag.d.ts +28 -2
- package/dist/clients/attach/session/pane-tag.js +48 -22
- package/dist/clients/attach/session/profile-files.d.ts +6 -0
- package/dist/clients/attach/session/profile-files.js +44 -11
- package/dist/clients/attach/slash/dispatch.js +4 -7
- package/dist/clients/attach/viewer.js +588 -596
- package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
- package/dist/commands/api-client.d.ts +3 -7
- package/dist/commands/api-client.js +7 -12
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
- package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
- package/dist/commands/memory/delete.js +51 -24
- package/dist/commands/memory/edit.js +50 -10
- package/dist/commands/memory/find.js +105 -56
- package/dist/commands/memory/history.js +51 -31
- package/dist/commands/memory/lint.d.ts +0 -9
- package/dist/commands/memory/lint.js +78 -322
- package/dist/commands/memory/list.d.ts +12 -4
- package/dist/commands/memory/list.js +64 -25
- package/dist/commands/memory/move.js +103 -73
- package/dist/commands/memory/origin.js +35 -5
- package/dist/commands/memory/read.d.ts +4 -0
- package/dist/commands/memory/read.js +132 -80
- package/dist/commands/memory/shared.d.ts +115 -25
- package/dist/commands/memory/shared.js +331 -74
- package/dist/commands/memory/write.js +72 -21
- package/dist/commands/memory.js +2 -2
- package/dist/commands/node/lifecycle.js +2 -2
- package/dist/commands/pkg/market-manage.js +25 -24
- package/dist/commands/pkg/plugin-manage.d.ts +22 -6
- package/dist/commands/pkg/plugin-manage.js +118 -25
- package/dist/commands/pkg/shared.d.ts +8 -0
- package/dist/commands/pkg/shared.js +23 -20
- package/dist/commands/surface-reload.d.ts +5 -0
- package/dist/commands/surface-reload.js +9 -1
- package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
- package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
- package/dist/commands/sys/migrate.js +126 -140
- package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
- package/dist/commands/sys/panels/profiles-panel.js +599 -0
- package/dist/commands/sys/settings-shell.d.ts +4 -2
- package/dist/commands/sys/settings-shell.js +35 -4
- package/dist/commands/sys/settings.js +21 -5
- package/dist/commands/sys/sync-deps.d.ts +2 -0
- package/dist/commands/sys/sync-deps.js +26 -15
- package/dist/commands/sys/sync-project-guidance.js +228 -145
- package/dist/commands/sys/sync-skills.js +28 -17
- package/dist/commands/sys/update.js +11 -3
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
- package/dist/core/__tests__/config-change-delta.test.js +106 -0
- package/dist/core/__tests__/context-intro.test.js +13 -9
- package/dist/core/__tests__/daemon-boot.test.js +22 -16
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
- package/dist/core/__tests__/human-deliver.test.js +19 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
- package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
- package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
- package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
- package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
- package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
- package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
- package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
- package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
- package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
- package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
- package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
- package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
- package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
- package/dist/core/__tests__/kickoff.test.js +16 -5
- package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
- package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
- package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
- package/dist/core/__tests__/repository-association.test.d.ts +1 -0
- package/dist/core/__tests__/repository-association.test.js +153 -0
- package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
- package/dist/core/__tests__/repository-root-identity.test.js +219 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
- package/dist/core/canvas/canvas.d.ts +3 -0
- package/dist/core/canvas/canvas.js +30 -10
- package/dist/core/canvas/db.js +28 -1
- package/dist/core/canvas/paths.d.ts +4 -6
- package/dist/core/canvas/paths.js +8 -6
- package/dist/core/canvas/render-source.js +2 -2
- package/dist/core/canvas/types.d.ts +9 -13
- package/dist/core/exclusive-lock.d.ts +2 -0
- package/dist/core/exclusive-lock.js +21 -0
- package/dist/core/git.d.ts +0 -1
- package/dist/core/git.js +0 -3
- package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
- package/dist/core/human/feedback-companion.js +3 -0
- package/dist/core/human/scan.js +8 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/memory/doc-link-grammar.js +2 -2
- package/dist/core/memory/history.d.ts +24 -0
- package/dist/core/memory/history.js +66 -1
- package/dist/core/memory/identity.d.ts +65 -0
- package/dist/core/memory/identity.js +185 -0
- package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
- package/dist/core/memory/inline-ref-guidance.js +1 -1
- package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
- package/dist/core/memory/inline-ref-inventory.js +30 -62
- package/dist/core/memory/lint.d.ts +129 -0
- package/dist/core/memory/lint.js +517 -0
- package/dist/core/memory/project-namespace.d.ts +31 -0
- package/dist/core/memory/project-namespace.js +85 -0
- package/dist/core/memory/repository-association.d.ts +31 -0
- package/dist/core/memory/repository-association.js +129 -0
- package/dist/core/memory/tree.d.ts +39 -0
- package/dist/core/memory/tree.js +93 -0
- package/dist/core/memory-resolver.d.ts +193 -90
- package/dist/core/memory-resolver.js +461 -375
- package/dist/core/nested-stores.js +6 -13
- package/dist/core/profiles/select.d.ts +4 -1
- package/dist/core/profiles/select.js +108 -48
- package/dist/core/review/__tests__/capture-origin.test.js +2 -2
- package/dist/core/review/__tests__/stage-identity.test.js +2 -2
- package/dist/core/review/companion.js +11 -2
- package/dist/core/runtime/bearings.d.ts +2 -2
- package/dist/core/runtime/bearings.js +3 -3
- package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
- package/dist/core/runtime/broker/rebind.js +5 -0
- package/dist/core/runtime/broker-extension-render.d.ts +7 -3
- package/dist/core/runtime/broker-extension-render.js +10 -5
- package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
- package/dist/core/runtime/broker-persona-guidance.js +122 -27
- package/dist/core/runtime/deliver-live.d.ts +16 -4
- package/dist/core/runtime/deliver-live.js +29 -15
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +8 -16
- package/dist/core/runtime/lifecycle.js +2 -3
- package/dist/core/runtime/nodes.d.ts +3 -4
- package/dist/core/runtime/nodes.js +3 -4
- package/dist/core/runtime/persona.d.ts +8 -12
- package/dist/core/runtime/persona.js +24 -96
- package/dist/core/runtime/promote.d.ts +2 -2
- package/dist/core/runtime/promote.js +8 -21
- package/dist/core/runtime/revive.js +20 -17
- package/dist/core/runtime/spawn.js +18 -7
- package/dist/core/runtime/tmux-bindings.js +2 -3
- package/dist/core/runtime/warm-pool.js +3 -4
- package/dist/core/scope.js +2 -0
- package/dist/core/self-update.d.ts +0 -2
- package/dist/core/self-update.js +2 -35
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.d.ts +43 -27
- package/dist/core/substrate/injected-store.js +208 -104
- package/dist/core/substrate/listings.d.ts +19 -12
- package/dist/core/substrate/listings.js +75 -52
- package/dist/core/substrate/on-read-node.d.ts +4 -7
- package/dist/core/substrate/on-read-node.js +6 -8
- package/dist/core/substrate/on-read.d.ts +22 -25
- package/dist/core/substrate/on-read.js +103 -147
- package/dist/core/substrate/render-node.d.ts +4 -7
- package/dist/core/substrate/render-node.js +5 -7
- package/dist/core/substrate/render.d.ts +21 -3
- package/dist/core/substrate/render.js +291 -223
- package/dist/core/substrate/schema.d.ts +1 -13
- package/dist/core/substrate/schema.js +5 -40
- package/dist/core/substrate/session-cache.d.ts +14 -4
- package/dist/core/substrate/session-cache.js +40 -22
- package/dist/core/substrate/surface-match.d.ts +9 -7
- package/dist/core/substrate/surface-match.js +26 -25
- package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
- package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
- package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
- package/dist/daemon/api/handlers/broker-ops.js +21 -16
- package/dist/daemon/api/handlers/memory.js +2 -0
- package/dist/daemon/crtrd.js +2 -0
- package/dist/daemon/human/finish.js +8 -1
- package/dist/daemon/manage.d.ts +0 -1
- package/dist/daemon/manage.js +7 -16
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/migrations/001-surfaces-frontmatter.js +21 -109
- package/dist/migrations/002-profile-project-memory.js +1 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
- package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
- package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
- package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
- package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
- package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
- package/dist/migrations/__tests__/activation.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation.test.js +149 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
- package/dist/migrations/activation.d.ts +16 -0
- package/dist/migrations/activation.js +78 -0
- package/dist/migrations/convergent.d.ts +14 -3
- package/dist/migrations/convergent.js +21 -10
- package/dist/migrations/corpus.d.ts +78 -0
- package/dist/migrations/corpus.js +497 -0
- package/dist/migrations/frontmatter-splice.d.ts +15 -0
- package/dist/migrations/frontmatter-splice.js +176 -0
- package/dist/migrations/registry.d.ts +6 -1
- package/dist/migrations/registry.js +7 -2
- package/dist/migrations/runner.d.ts +41 -0
- package/dist/migrations/runner.js +81 -0
- package/dist/migrations/types.d.ts +148 -9
- package/dist/migrations/types.js +9 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
- package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
- package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
- package/dist/pi-extensions/canvas-context-intro.js +50 -46
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
- package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
- package/dist/pi-extensions/canvas-stophook.js +6 -13
- package/dist/shared/generated-context.d.ts +0 -3
- package/dist/shared/generated-context.js +0 -57
- package/dist/shared/tool-groups.js +2 -3
- package/package.json +5 -4
- package/runtime.lock.json +2 -2
- /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
- /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
- /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
- /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
- /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
- /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
- /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
// The Profiles settings destination: a map of profiles × their directories,
|
|
2
|
+
// anchored on the directory the page was opened in.
|
|
3
|
+
//
|
|
4
|
+
// The startup profile menu (`core/profiles/select.ts`) is a CHOOSER — it shows
|
|
5
|
+
// only profiles covering cwd, one matched dir each. This panel is the manager
|
|
6
|
+
// behind its `v` key: every profile, every directory each one owns, which
|
|
7
|
+
// directory is pinned where, and the edits that follow from seeing that
|
|
8
|
+
// (adopt this dir, pin here, rename, remove a stale dir, delete).
|
|
9
|
+
//
|
|
10
|
+
// Mutations go straight through `core/profiles/manifest.ts` and
|
|
11
|
+
// `default-binding.ts`, exactly as the `crtr profile` leaves do. Deletion is
|
|
12
|
+
// the one exception: it cascades across nodes, crons, tickets and reviews, so
|
|
13
|
+
// it routes through crtrd like `crtr profile delete`.
|
|
14
|
+
import { Input, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
15
|
+
import { basename } from "node:path";
|
|
16
|
+
import { PROFILE_PROJECT_MEMORY_VALUES } from "../../../api/dto/profiles.js";
|
|
17
|
+
import { cliClient } from "../../api-client.js";
|
|
18
|
+
import { clearDefaultProfile, defaultProfileDirs, getDefaultProfileId, setDefaultProfileId, } from "../../../core/profiles/default-binding.js";
|
|
19
|
+
import { ROOT_PROFILE_ID, addProfileProject, createProfile, listProfiles, pauseProfile, removeProfileProject, renameProfile, resumeProfile, } from "../../../core/profiles/manifest.js";
|
|
20
|
+
import { profileCoversCwd, tildify } from "../../../core/profiles/select.js";
|
|
21
|
+
import { padAnsi, theme, visibleRange, wrapText, } from "../../../core/tui/panel.js";
|
|
22
|
+
/** Coarse "used ..." for telling otherwise-alike profiles apart. */
|
|
23
|
+
function relativeUsed(iso) {
|
|
24
|
+
if (iso === null)
|
|
25
|
+
return "never used";
|
|
26
|
+
const then = Date.parse(iso);
|
|
27
|
+
if (Number.isNaN(then))
|
|
28
|
+
return "used recently";
|
|
29
|
+
const secs = Math.max(0, Math.floor((Date.now() - then) / 1000));
|
|
30
|
+
if (secs < 60)
|
|
31
|
+
return "used just now";
|
|
32
|
+
const mins = Math.floor(secs / 60);
|
|
33
|
+
if (mins < 60)
|
|
34
|
+
return `used ${mins}m ago`;
|
|
35
|
+
const hours = Math.floor(mins / 60);
|
|
36
|
+
if (hours < 24)
|
|
37
|
+
return `used ${hours}h ago`;
|
|
38
|
+
const days = Math.floor(hours / 24);
|
|
39
|
+
if (days < 30)
|
|
40
|
+
return `used ${days}d ago`;
|
|
41
|
+
const months = Math.floor(days / 30);
|
|
42
|
+
if (months < 12)
|
|
43
|
+
return `used ${months}mo ago`;
|
|
44
|
+
return `used ${Math.floor(months / 12)}y ago`;
|
|
45
|
+
}
|
|
46
|
+
function messageOf(error) {
|
|
47
|
+
return error instanceof Error ? error.message : String(error);
|
|
48
|
+
}
|
|
49
|
+
export class ProfilesPanel {
|
|
50
|
+
cwd;
|
|
51
|
+
onChange;
|
|
52
|
+
nameInput = new Input();
|
|
53
|
+
dirInput = new Input();
|
|
54
|
+
entries = [];
|
|
55
|
+
listIndex = 0;
|
|
56
|
+
dirIndex = 0;
|
|
57
|
+
mode = "list";
|
|
58
|
+
/** Profile the detail level / a confirm is about. */
|
|
59
|
+
openId = null;
|
|
60
|
+
notice;
|
|
61
|
+
noticeTone = "success";
|
|
62
|
+
mutating = false;
|
|
63
|
+
constructor(cwd, onChange) {
|
|
64
|
+
this.cwd = cwd;
|
|
65
|
+
this.onChange = onChange;
|
|
66
|
+
this.nameInput.onSubmit = () => {
|
|
67
|
+
this.submitName();
|
|
68
|
+
this.onChange();
|
|
69
|
+
};
|
|
70
|
+
this.nameInput.onEscape = () => {
|
|
71
|
+
this.mode = this.openId !== null ? "detail" : "list";
|
|
72
|
+
this.leaveField();
|
|
73
|
+
this.onChange();
|
|
74
|
+
};
|
|
75
|
+
this.dirInput.onSubmit = () => {
|
|
76
|
+
this.submitDir();
|
|
77
|
+
this.onChange();
|
|
78
|
+
};
|
|
79
|
+
this.dirInput.onEscape = () => {
|
|
80
|
+
this.mode = "detail";
|
|
81
|
+
this.leaveField();
|
|
82
|
+
this.onChange();
|
|
83
|
+
};
|
|
84
|
+
this.reload();
|
|
85
|
+
}
|
|
86
|
+
badge() {
|
|
87
|
+
return String(this.entries.length);
|
|
88
|
+
}
|
|
89
|
+
/** A live text field or a confirm keeps EVERY key, so a name containing `q`
|
|
90
|
+
* can never close the page. The detail level claims only Escape and ← — they
|
|
91
|
+
* mean "back to the profile list" here rather than "jump to the settings
|
|
92
|
+
* rail" — and leaves the page's own navigation and cancel keys alone. */
|
|
93
|
+
ownsInput(data) {
|
|
94
|
+
if (this.mode === "list")
|
|
95
|
+
return false;
|
|
96
|
+
if (this.mode === "detail")
|
|
97
|
+
return matchesKey(data, "escape") || matchesKey(data, "left");
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
// --- state ----------------------------------------------------------------
|
|
101
|
+
/** Profiles covering cwd first (most recently used within), then the rest by
|
|
102
|
+
* recency, with root last — the ones a startup pick is deciding between sit
|
|
103
|
+
* at the top, where the eye starts. */
|
|
104
|
+
reload() {
|
|
105
|
+
const rank = (entry) => {
|
|
106
|
+
if (entry.profileId === ROOT_PROFILE_ID)
|
|
107
|
+
return 2;
|
|
108
|
+
return profileCoversCwd(entry, this.cwd) ? 0 : 1;
|
|
109
|
+
};
|
|
110
|
+
const used = (entry) => entry.manifest.last_used_at ?? "";
|
|
111
|
+
this.entries = listProfiles().sort((a, b) => {
|
|
112
|
+
const byRank = rank(a) - rank(b);
|
|
113
|
+
if (byRank !== 0)
|
|
114
|
+
return byRank;
|
|
115
|
+
const byUsed = used(b).localeCompare(used(a));
|
|
116
|
+
return byUsed !== 0 ? byUsed : a.manifest.name.localeCompare(b.manifest.name);
|
|
117
|
+
});
|
|
118
|
+
if (this.listIndex >= this.entries.length)
|
|
119
|
+
this.listIndex = Math.max(0, this.entries.length - 1);
|
|
120
|
+
const open = this.open();
|
|
121
|
+
if (open === null) {
|
|
122
|
+
if (this.mode !== "list")
|
|
123
|
+
this.mode = "list";
|
|
124
|
+
this.openId = null;
|
|
125
|
+
}
|
|
126
|
+
else if (this.dirIndex >= open.manifest.projects.length) {
|
|
127
|
+
this.dirIndex = Math.max(0, open.manifest.projects.length - 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
selected() {
|
|
131
|
+
return this.entries[this.listIndex] ?? null;
|
|
132
|
+
}
|
|
133
|
+
open() {
|
|
134
|
+
if (this.openId === null)
|
|
135
|
+
return null;
|
|
136
|
+
return this.entries.find((e) => e.profileId === this.openId) ?? null;
|
|
137
|
+
}
|
|
138
|
+
currentDir() {
|
|
139
|
+
return this.open()?.manifest.projects[this.dirIndex] ?? null;
|
|
140
|
+
}
|
|
141
|
+
pinnedHere() {
|
|
142
|
+
return getDefaultProfileId(this.cwd);
|
|
143
|
+
}
|
|
144
|
+
say(text, tone = "success") {
|
|
145
|
+
this.notice = text;
|
|
146
|
+
this.noticeTone = tone;
|
|
147
|
+
}
|
|
148
|
+
/** Run a manifest mutation, turning its structured failure into the panel's
|
|
149
|
+
* notice line rather than tearing the page down. */
|
|
150
|
+
attempt(what, ok) {
|
|
151
|
+
try {
|
|
152
|
+
what();
|
|
153
|
+
this.reload();
|
|
154
|
+
this.say(ok);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
this.say(messageOf(error), "warning");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// --- input ----------------------------------------------------------------
|
|
161
|
+
handleInput(data, matches) {
|
|
162
|
+
const handled = this.route(data, matches);
|
|
163
|
+
if (handled)
|
|
164
|
+
this.onChange();
|
|
165
|
+
return handled;
|
|
166
|
+
}
|
|
167
|
+
route(data, matches) {
|
|
168
|
+
switch (this.mode) {
|
|
169
|
+
case "new-name":
|
|
170
|
+
case "rename":
|
|
171
|
+
return this.routeText(this.nameInput, data);
|
|
172
|
+
case "add-dir":
|
|
173
|
+
return this.routeText(this.dirInput, data);
|
|
174
|
+
case "confirm-delete":
|
|
175
|
+
return this.routeConfirm(data, () => void this.deleteOpen());
|
|
176
|
+
case "confirm-remove-dir":
|
|
177
|
+
return this.routeConfirm(data, () => this.removeDir());
|
|
178
|
+
case "detail":
|
|
179
|
+
return this.routeDetail(data, matches);
|
|
180
|
+
case "list":
|
|
181
|
+
return this.routeList(data, matches);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
routeText(input, data) {
|
|
185
|
+
input.handleInput(data);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
routeConfirm(data, confirm) {
|
|
189
|
+
const key = data.toLowerCase();
|
|
190
|
+
if (key === "y")
|
|
191
|
+
confirm();
|
|
192
|
+
else if (key === "n" || matchesKey(data, "escape") || matchesKey(data, "return")) {
|
|
193
|
+
this.mode = this.openId !== null ? "detail" : "list";
|
|
194
|
+
this.say("Cancelled.");
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
routeList(data, matches) {
|
|
199
|
+
if (matches?.("crtr.setup.list-down", data) === true) {
|
|
200
|
+
this.move(1);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
if (matches?.("crtr.setup.list-up", data) === true) {
|
|
204
|
+
this.move(-1);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (matches?.("crtr.setup.advance", data) === true) {
|
|
208
|
+
const entry = this.selected();
|
|
209
|
+
if (entry !== null) {
|
|
210
|
+
this.openId = entry.profileId;
|
|
211
|
+
this.dirIndex = 0;
|
|
212
|
+
this.mode = "detail";
|
|
213
|
+
this.notice = undefined;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
switch (data.toLowerCase()) {
|
|
218
|
+
case "a":
|
|
219
|
+
return this.addCwdTo(this.selected());
|
|
220
|
+
case "d":
|
|
221
|
+
return this.togglePin();
|
|
222
|
+
case "n":
|
|
223
|
+
this.mode = "new-name";
|
|
224
|
+
this.edit(this.nameInput, basename(this.cwd) || "profile");
|
|
225
|
+
return true;
|
|
226
|
+
case "x":
|
|
227
|
+
return this.askDelete();
|
|
228
|
+
default:
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
routeDetail(data, matches) {
|
|
233
|
+
const open = this.open();
|
|
234
|
+
if (open === null) {
|
|
235
|
+
this.mode = "list";
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
if (matchesKey(data, "escape") || matchesKey(data, "left")) {
|
|
239
|
+
this.mode = "list";
|
|
240
|
+
this.notice = undefined;
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
if (matches?.("crtr.setup.list-down", data) === true) {
|
|
244
|
+
this.moveDir(1);
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (matches?.("crtr.setup.list-up", data) === true) {
|
|
248
|
+
this.moveDir(-1);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
switch (data.toLowerCase()) {
|
|
252
|
+
case "a":
|
|
253
|
+
this.mode = "add-dir";
|
|
254
|
+
this.edit(this.dirInput, this.cwd);
|
|
255
|
+
return true;
|
|
256
|
+
case "m":
|
|
257
|
+
return this.cycleMemory();
|
|
258
|
+
case "h":
|
|
259
|
+
return this.setHome();
|
|
260
|
+
case "x":
|
|
261
|
+
return this.askRemoveDir();
|
|
262
|
+
case "r":
|
|
263
|
+
this.mode = "rename";
|
|
264
|
+
this.edit(this.nameInput, open.manifest.name);
|
|
265
|
+
return true;
|
|
266
|
+
case "p":
|
|
267
|
+
return this.togglePause();
|
|
268
|
+
default:
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** Seed a field with the value the user most likely wants, cursor at its end
|
|
273
|
+
* (`\x05` — the field is an edit-or-accept, never a blank you must retype). */
|
|
274
|
+
edit(input, value) {
|
|
275
|
+
input.setValue(value);
|
|
276
|
+
input.handleInput("\x05");
|
|
277
|
+
input.focused = true;
|
|
278
|
+
this.notice = undefined;
|
|
279
|
+
}
|
|
280
|
+
leaveField() {
|
|
281
|
+
this.nameInput.focused = false;
|
|
282
|
+
this.dirInput.focused = false;
|
|
283
|
+
}
|
|
284
|
+
move(delta) {
|
|
285
|
+
if (this.entries.length === 0)
|
|
286
|
+
return;
|
|
287
|
+
this.listIndex =
|
|
288
|
+
(this.listIndex + delta + this.entries.length) % this.entries.length;
|
|
289
|
+
this.notice = undefined;
|
|
290
|
+
}
|
|
291
|
+
moveDir(delta) {
|
|
292
|
+
const count = this.open()?.manifest.projects.length ?? 0;
|
|
293
|
+
if (count === 0)
|
|
294
|
+
return;
|
|
295
|
+
this.dirIndex = (this.dirIndex + delta + count) % count;
|
|
296
|
+
this.notice = undefined;
|
|
297
|
+
}
|
|
298
|
+
// --- actions --------------------------------------------------------------
|
|
299
|
+
addCwdTo(entry) {
|
|
300
|
+
if (entry === null)
|
|
301
|
+
return true;
|
|
302
|
+
this.attempt(() => addProfileProject(entry.profileId, this.cwd, { memory: "content" }), `${tildify(this.cwd)} now belongs to "${entry.manifest.name}".`);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
togglePin() {
|
|
306
|
+
const entry = this.selected();
|
|
307
|
+
if (entry === null)
|
|
308
|
+
return true;
|
|
309
|
+
if (this.pinnedHere() === entry.profileId) {
|
|
310
|
+
clearDefaultProfile(this.cwd);
|
|
311
|
+
this.say(`${tildify(this.cwd)} no longer defaults to any profile.`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
setDefaultProfileId(this.cwd, entry.profileId);
|
|
315
|
+
this.say(`${tildify(this.cwd)} now starts as "${entry.manifest.name}" without asking.`);
|
|
316
|
+
}
|
|
317
|
+
this.onChange();
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
submitName() {
|
|
321
|
+
const value = this.nameInput.getValue().trim();
|
|
322
|
+
const creating = this.mode === "new-name";
|
|
323
|
+
this.mode = creating ? "list" : "detail";
|
|
324
|
+
this.leaveField();
|
|
325
|
+
if (value === "") {
|
|
326
|
+
this.say("A profile name cannot be empty.", "warning");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (creating) {
|
|
330
|
+
let createdId = null;
|
|
331
|
+
this.attempt(() => {
|
|
332
|
+
createdId = createProfile(value, [
|
|
333
|
+
{ path: this.cwd, memory: "content" },
|
|
334
|
+
]).profileId;
|
|
335
|
+
}, `Created "${value}", owning ${tildify(this.cwd)}.`);
|
|
336
|
+
const at = this.entries.findIndex((e) => e.profileId === createdId);
|
|
337
|
+
if (at >= 0)
|
|
338
|
+
this.listIndex = at;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const open = this.open();
|
|
342
|
+
if (open === null)
|
|
343
|
+
return;
|
|
344
|
+
this.attempt(() => renameProfile(open.profileId, value), `Renamed to "${value}".`);
|
|
345
|
+
}
|
|
346
|
+
submitDir() {
|
|
347
|
+
const value = this.dirInput.getValue().trim();
|
|
348
|
+
const open = this.open();
|
|
349
|
+
this.mode = "detail";
|
|
350
|
+
this.leaveField();
|
|
351
|
+
if (open === null)
|
|
352
|
+
return;
|
|
353
|
+
if (value === "") {
|
|
354
|
+
this.say("Type the directory to add.", "warning");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
this.attempt(() => addProfileProject(open.profileId, value, { memory: "content" }), `${tildify(value)} added to "${open.manifest.name}".`);
|
|
358
|
+
}
|
|
359
|
+
cycleMemory() {
|
|
360
|
+
const open = this.open();
|
|
361
|
+
const dir = this.currentDir();
|
|
362
|
+
if (open === null || dir === null)
|
|
363
|
+
return true;
|
|
364
|
+
const next = PROFILE_PROJECT_MEMORY_VALUES[(PROFILE_PROJECT_MEMORY_VALUES.indexOf(dir.memory) + 1) %
|
|
365
|
+
PROFILE_PROJECT_MEMORY_VALUES.length];
|
|
366
|
+
this.attempt(() => addProfileProject(open.profileId, dir.path, { memory: next }), `${tildify(dir.path)} discloses up to "${next}".`);
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
setHome() {
|
|
370
|
+
const open = this.open();
|
|
371
|
+
const dir = this.currentDir();
|
|
372
|
+
if (open === null || dir === null)
|
|
373
|
+
return true;
|
|
374
|
+
this.attempt(() => addProfileProject(open.profileId, dir.path, {
|
|
375
|
+
memory: dir.memory,
|
|
376
|
+
home: true,
|
|
377
|
+
}), `Nodes under "${open.manifest.name}" now run in ${tildify(dir.path)}.`);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
askRemoveDir() {
|
|
381
|
+
if (this.currentDir() === null) {
|
|
382
|
+
this.say("This profile owns no directories.", "warning");
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
this.mode = "confirm-remove-dir";
|
|
386
|
+
this.notice = undefined;
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
removeDir() {
|
|
390
|
+
const open = this.open();
|
|
391
|
+
const dir = this.currentDir();
|
|
392
|
+
this.mode = "detail";
|
|
393
|
+
if (open === null || dir === null)
|
|
394
|
+
return;
|
|
395
|
+
this.attempt(() => removeProfileProject(open.profileId, dir.path), `${tildify(dir.path)} removed from "${open.manifest.name}".`);
|
|
396
|
+
}
|
|
397
|
+
togglePause() {
|
|
398
|
+
const open = this.open();
|
|
399
|
+
if (open === null)
|
|
400
|
+
return true;
|
|
401
|
+
const paused = open.manifest.paused_at !== null;
|
|
402
|
+
this.attempt(() => paused ? resumeProfile(open.profileId) : pauseProfile(open.profileId), paused
|
|
403
|
+
? `"${open.manifest.name}" is active again.`
|
|
404
|
+
: `"${open.manifest.name}" is paused — no new nodes start under it.`);
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
askDelete() {
|
|
408
|
+
const entry = this.selected();
|
|
409
|
+
if (entry === null)
|
|
410
|
+
return true;
|
|
411
|
+
if (entry.profileId === ROOT_PROFILE_ID) {
|
|
412
|
+
this.say("The root profile is the fallback identity for uncovered directories and cannot be deleted.", "warning");
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
this.openId = entry.profileId;
|
|
416
|
+
this.mode = "confirm-delete";
|
|
417
|
+
this.notice = undefined;
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
async deleteOpen() {
|
|
421
|
+
const open = this.open();
|
|
422
|
+
this.mode = "list";
|
|
423
|
+
if (open === null || this.mutating)
|
|
424
|
+
return;
|
|
425
|
+
this.mutating = true;
|
|
426
|
+
this.say(`Deleting "${open.manifest.name}"…`);
|
|
427
|
+
this.onChange();
|
|
428
|
+
try {
|
|
429
|
+
const result = await cliClient().deleteProfile(open.profileId, {
|
|
430
|
+
force: true,
|
|
431
|
+
caller_node_id: process.env["CRTR_NODE_ID"] ?? null,
|
|
432
|
+
});
|
|
433
|
+
this.openId = null;
|
|
434
|
+
this.reload();
|
|
435
|
+
this.say(`Deleted "${result.name}" — ${result.nodes} nodes, ${result.crons} crons, ${result.default_pins} pins.`);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
this.say(messageOf(error), "warning");
|
|
439
|
+
}
|
|
440
|
+
finally {
|
|
441
|
+
this.mutating = false;
|
|
442
|
+
this.onChange();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// --- rendering ------------------------------------------------------------
|
|
446
|
+
footerHint() {
|
|
447
|
+
switch (this.mode) {
|
|
448
|
+
case "new-name":
|
|
449
|
+
return "type a name · enter create · esc cancel";
|
|
450
|
+
case "rename":
|
|
451
|
+
return "type a name · enter save · esc cancel";
|
|
452
|
+
case "add-dir":
|
|
453
|
+
return "type a directory · enter add · esc cancel";
|
|
454
|
+
case "confirm-delete":
|
|
455
|
+
case "confirm-remove-dir":
|
|
456
|
+
return "y confirm · n cancel";
|
|
457
|
+
case "detail":
|
|
458
|
+
return "↑↓ dir · a add · m memory · h home · x remove · r rename · p pause · esc back";
|
|
459
|
+
case "list":
|
|
460
|
+
return "↑↓ move · enter open · a add this dir · d pin here · n new · x delete";
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
render(width, height) {
|
|
464
|
+
// Each prompt renders over the level it acts on: naming a new profile and
|
|
465
|
+
// confirming a deletion belong to the list, while renaming, adding a
|
|
466
|
+
// directory and confirming its removal belong to the open profile.
|
|
467
|
+
const overList = this.mode === "list" ||
|
|
468
|
+
this.mode === "new-name" ||
|
|
469
|
+
this.mode === "confirm-delete";
|
|
470
|
+
const out = overList
|
|
471
|
+
? this.renderList(width, height)
|
|
472
|
+
: this.renderDetail(width, height);
|
|
473
|
+
if (this.notice !== undefined) {
|
|
474
|
+
out.push("");
|
|
475
|
+
for (const line of wrapText(this.notice, width))
|
|
476
|
+
out.push(truncateToWidth(theme.fg(this.noticeTone, line), width));
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
header(width, title, meta) {
|
|
481
|
+
const left = theme.fg("accent", theme.bold(title));
|
|
482
|
+
const right = theme.fg("dim", meta);
|
|
483
|
+
return truncateToWidth(`${left} ${right}`, width);
|
|
484
|
+
}
|
|
485
|
+
renderList(width, height) {
|
|
486
|
+
const out = [
|
|
487
|
+
this.header(width, "Profiles", `${this.entries.length} profile${this.entries.length === 1 ? "" : "s"}`),
|
|
488
|
+
truncateToWidth(theme.fg("dim", `Identities your sessions run under. Here: ${tildify(this.cwd)}`), width),
|
|
489
|
+
"",
|
|
490
|
+
];
|
|
491
|
+
// Prompts first: the very first profile is created from the empty list, so
|
|
492
|
+
// an empty-state message that returned before them would hide the field the
|
|
493
|
+
// user is typing into.
|
|
494
|
+
if (this.mode === "new-name")
|
|
495
|
+
return [...out, ...this.field(width, "New profile name", this.nameInput)];
|
|
496
|
+
if (this.mode === "confirm-delete")
|
|
497
|
+
return [...out, ...this.deletePrompt(width)];
|
|
498
|
+
if (this.entries.length === 0) {
|
|
499
|
+
out.push(truncateToWidth(theme.fg("dim", " No profiles yet — press n to create one here."), width));
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
const pinned = this.pinnedHere();
|
|
503
|
+
const nameWidth = Math.min(22, Math.max(...this.entries.map((e) => e.manifest.name.length)));
|
|
504
|
+
const capacity = Math.max(1, height - 8);
|
|
505
|
+
const range = visibleRange(this.entries.length, this.listIndex, capacity);
|
|
506
|
+
for (let index = range.start; index < range.end; index += 1) {
|
|
507
|
+
const entry = this.entries[index];
|
|
508
|
+
const count = entry.manifest.projects.length;
|
|
509
|
+
const dirs = count === 0 ? "—" : `${count} dir${count === 1 ? "" : "s"}`;
|
|
510
|
+
const badges = [];
|
|
511
|
+
if (profileCoversCwd(entry, this.cwd))
|
|
512
|
+
badges.push("covers here");
|
|
513
|
+
if (pinned === entry.profileId)
|
|
514
|
+
badges.push("pinned");
|
|
515
|
+
if (entry.manifest.paused_at !== null)
|
|
516
|
+
badges.push("paused");
|
|
517
|
+
const body = `${padAnsi(entry.manifest.name, nameWidth)} ${padAnsi(dirs, 8)} ${padAnsi(relativeUsed(entry.manifest.last_used_at), 14)} ${badges.join(" · ")}`;
|
|
518
|
+
if (index === this.listIndex)
|
|
519
|
+
out.push(theme.bg("selectedBg", padAnsi(`\u25b8 ${body}`, width)));
|
|
520
|
+
else
|
|
521
|
+
out.push(truncateToWidth(` ${padAnsi(entry.manifest.name, nameWidth)} ${theme.fg("dim", `${padAnsi(dirs, 8)} ${padAnsi(relativeUsed(entry.manifest.last_used_at), 14)}`)} ${theme.fg("muted", badges.join(" · "))}`, width));
|
|
522
|
+
}
|
|
523
|
+
if (range.start > 0 || range.end < this.entries.length)
|
|
524
|
+
out.push(truncateToWidth(theme.fg("dim", ` ${this.listIndex + 1}/${this.entries.length}`), width));
|
|
525
|
+
return out;
|
|
526
|
+
}
|
|
527
|
+
renderDetail(width, height) {
|
|
528
|
+
const open = this.open();
|
|
529
|
+
if (open === null)
|
|
530
|
+
return this.renderList(width, height);
|
|
531
|
+
const manifest = open.manifest;
|
|
532
|
+
const facts = [
|
|
533
|
+
manifest.home !== null ? `home ${tildify(manifest.home)}` : "no home",
|
|
534
|
+
`kind ${manifest.default_kind ?? "general"}`,
|
|
535
|
+
relativeUsed(manifest.last_used_at),
|
|
536
|
+
...(manifest.paused_at !== null ? ["paused"] : []),
|
|
537
|
+
];
|
|
538
|
+
const out = [
|
|
539
|
+
this.header(width, manifest.name, open.profileId),
|
|
540
|
+
truncateToWidth(theme.fg("dim", facts.join(" \u00b7 ")), width),
|
|
541
|
+
"",
|
|
542
|
+
];
|
|
543
|
+
if (this.mode === "rename")
|
|
544
|
+
return [...out, ...this.field(width, "Rename profile", this.nameInput)];
|
|
545
|
+
if (this.mode === "add-dir")
|
|
546
|
+
return [
|
|
547
|
+
...out,
|
|
548
|
+
...this.field(width, "Directory to add to this profile", this.dirInput),
|
|
549
|
+
];
|
|
550
|
+
const projects = manifest.projects;
|
|
551
|
+
if (projects.length === 0) {
|
|
552
|
+
out.push(truncateToWidth(theme.fg("dim", " Owns no directories — press a to add one."), width));
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
555
|
+
const pins = new Set(defaultProfileDirs(open.profileId));
|
|
556
|
+
const pathWidth = Math.min(Math.max(20, width - 26), Math.max(...projects.map((p) => tildify(p.path).length)));
|
|
557
|
+
const capacity = Math.max(1, height - 8);
|
|
558
|
+
const range = visibleRange(projects.length, this.dirIndex, capacity);
|
|
559
|
+
for (let index = range.start; index < range.end; index += 1) {
|
|
560
|
+
const project = projects[index];
|
|
561
|
+
const badges = [];
|
|
562
|
+
if (project.path === manifest.home)
|
|
563
|
+
badges.push("home");
|
|
564
|
+
if (pins.has(project.path))
|
|
565
|
+
badges.push("pinned");
|
|
566
|
+
if (project.path === this.cwd)
|
|
567
|
+
badges.push("\u2190 here");
|
|
568
|
+
const path = padAnsi(tildify(project.path), pathWidth);
|
|
569
|
+
const body = `${path} ${padAnsi(project.memory, 8)} ${badges.join(" \u00b7 ")}`;
|
|
570
|
+
if (index === this.dirIndex)
|
|
571
|
+
out.push(theme.bg("selectedBg", padAnsi(`\u25b8 ${body}`, width)));
|
|
572
|
+
else
|
|
573
|
+
out.push(truncateToWidth(` ${path} ${theme.fg("success", padAnsi(project.memory, 8))} ${theme.fg("muted", badges.join(" \u00b7 "))}`, width));
|
|
574
|
+
}
|
|
575
|
+
if (this.mode === "confirm-remove-dir") {
|
|
576
|
+
const dir = this.currentDir();
|
|
577
|
+
out.push("");
|
|
578
|
+
out.push(truncateToWidth(theme.fg("warning", `Remove ${dir !== null ? tildify(dir.path) : "this directory"} from "${manifest.name}"? Its files are untouched. y / n`), width));
|
|
579
|
+
}
|
|
580
|
+
return out;
|
|
581
|
+
}
|
|
582
|
+
/** A labelled single-line text field, used by every prompt in the panel. */
|
|
583
|
+
field(width, label, input) {
|
|
584
|
+
return [
|
|
585
|
+
truncateToWidth(theme.fg("accent", label), width),
|
|
586
|
+
truncateToWidth(` ${input.render(Math.max(8, width - 2))[0] ?? ""}`, width),
|
|
587
|
+
];
|
|
588
|
+
}
|
|
589
|
+
deletePrompt(width) {
|
|
590
|
+
const open = this.open();
|
|
591
|
+
const name = open?.manifest.name ?? "";
|
|
592
|
+
const out = [];
|
|
593
|
+
for (const line of wrapText(`Delete "${name}" and everything it owns — every node, cron, inbox ticket and review under it, plus its memory store and directory pins? This cannot be undone.`, width))
|
|
594
|
+
out.push(truncateToWidth(theme.fg("warning", line), width));
|
|
595
|
+
out.push("");
|
|
596
|
+
out.push(truncateToWidth(theme.fg("dim", " y delete n cancel"), width));
|
|
597
|
+
return out;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { TUI, type Component, type Focusable } from "@earendil-works/pi-tui";
|
|
3
3
|
import { type BindingId, type BindingResolution } from "../../core/keybindings/index.js";
|
|
4
|
+
export type DestinationId = "providers" | "models" | "keybindings" | "display" | "profiles" | "panes" | "broker-limits" | "updates";
|
|
4
5
|
export declare class SettingsShell implements Component, Focusable {
|
|
5
6
|
private readonly tui;
|
|
6
7
|
private readonly bindings;
|
|
@@ -9,6 +10,7 @@ export declare class SettingsShell implements Component, Focusable {
|
|
|
9
10
|
private readonly models;
|
|
10
11
|
private readonly keybindings;
|
|
11
12
|
private readonly brokerLimits;
|
|
13
|
+
private readonly profiles;
|
|
12
14
|
private readonly destinations;
|
|
13
15
|
private index;
|
|
14
16
|
/** The rail owns the arrow keys until you step into a panel. */
|
|
@@ -18,7 +20,7 @@ export declare class SettingsShell implements Component, Focusable {
|
|
|
18
20
|
private cachedHeight;
|
|
19
21
|
private cachedLines;
|
|
20
22
|
onCancel?: () => void;
|
|
21
|
-
constructor(tui: TUI, agentDir: string, runtime: ModelRuntime, registry: ModelRegistry, bindings: BindingResolution<BindingId>, rawKeybindings: unknown, terminalRows: () => number, rawTmuxPassthrough?: unknown);
|
|
23
|
+
constructor(tui: TUI, agentDir: string, runtime: ModelRuntime, registry: ModelRegistry, bindings: BindingResolution<BindingId>, rawKeybindings: unknown, terminalRows: () => number, rawTmuxPassthrough?: unknown, initial?: DestinationId);
|
|
22
24
|
set focused(value: boolean);
|
|
23
25
|
get focused(): boolean;
|
|
24
26
|
private get current();
|
|
@@ -49,4 +51,4 @@ export declare class SettingsShell implements Component, Focusable {
|
|
|
49
51
|
private panelHint;
|
|
50
52
|
render(width: number): string[];
|
|
51
53
|
}
|
|
52
|
-
export declare function runSettings(): Promise<void>;
|
|
54
|
+
export declare function runSettings(initial?: DestinationId): Promise<void>;
|