@gavin-hu/my-pi-agent 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -52
- package/README.zh-CN.md +56 -46
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +507 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +774 -574
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +561 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +62 -52
- package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
- package/{extensions/plan/README.md → docs/extensions/plan.md} +440 -322
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +298 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
- package/docs/extensions/team.md +254 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +260 -163
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +137 -79
- package/extensions/board/commands.ts +105 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +170 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +388 -0
- package/extensions/board/result.ts +108 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +90 -0
- package/extensions/board/seat.ts +28 -0
- package/extensions/board/snapshot.ts +42 -0
- package/extensions/board/tools.ts +444 -0
- package/extensions/board/transport/router.ts +91 -0
- package/extensions/board/transport/server.ts +138 -0
- package/extensions/board/types.ts +121 -0
- package/extensions/board/view/client.ts +51 -0
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +125 -0
- package/extensions/board/view/live.ts +231 -0
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +118 -0
- package/extensions/board/view/render.ts +519 -0
- package/extensions/board/view/styles.ts +228 -0
- package/extensions/board/view/team.ts +327 -0
- package/extensions/bytefly/footer/budget.ts +6 -5
- package/extensions/bytefly/footer/collect/entries.ts +12 -4
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
- package/extensions/bytefly/footer/config.ts +19 -8
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +117 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/panel/commands.ts +67 -7
- package/extensions/bytefly/panel/filter.ts +68 -0
- package/extensions/bytefly/panel/format.ts +16 -15
- package/extensions/bytefly/panel/join.ts +56 -13
- package/extensions/bytefly/panel/prices.ts +43 -0
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +75 -43
- package/extensions/bytefly/panel/tui/entries.ts +26 -8
- package/extensions/bytefly/panel/tui/frame.ts +130 -93
- package/extensions/bytefly/panel/tui/keys.ts +352 -0
- package/extensions/bytefly/panel/tui/list-view.ts +25 -46
- package/extensions/bytefly/panel/tui/panes.ts +76 -0
- package/extensions/bytefly/panel/tui/section-view.ts +126 -0
- package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
- package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
- package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
- package/extensions/bytefly/panel/tui.ts +306 -259
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/check/commands.ts +2 -1
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
- package/extensions/file-browse/files/classify.ts +136 -0
- package/extensions/file-browse/files/files.ts +12 -0
- package/extensions/file-browse/files/index.ts +90 -0
- package/extensions/file-browse/files/list.ts +261 -0
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/files/tree.ts +101 -0
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
- package/extensions/file-browse/{router.ts → transport/router.ts} +21 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
- package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
- package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +19 -18
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +18 -20
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +19 -14
- package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/job/runtime.ts +4 -4
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +21 -16
- package/extensions/plan/feedback.ts +185 -52
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +23 -5
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plan-index.ts +43 -9
- package/extensions/plan/plan-text.ts +33 -23
- package/extensions/plan/plans.ts +27 -34
- package/extensions/plan/policy.ts +30 -17
- package/extensions/plan/review.ts +3 -27
- package/extensions/plan/runtime.ts +52 -11
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +3 -22
- package/extensions/plan/todos.ts +51 -3
- package/extensions/plan/tools/exit.ts +20 -95
- package/extensions/plan/tools/todo-seed.ts +4 -2
- package/extensions/plan/tools/write.ts +12 -1
- package/extensions/plan/tui.ts +5 -34
- package/extensions/plan/types.ts +15 -8
- package/extensions/plan/web-blocks.ts +89 -0
- package/extensions/plan/web-chrome.ts +3 -5
- package/extensions/plan/web-client.ts +179 -330
- package/extensions/plan/web-collab.ts +3 -3
- package/extensions/plan/web-comments.ts +18 -8
- package/extensions/plan/web-composer.ts +80 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +134 -0
- package/extensions/plan/web-outline.ts +192 -0
- package/extensions/plan/web-page.ts +4 -8
- package/extensions/plan/web-plan.ts +150 -0
- package/extensions/plan/web-router.ts +3 -58
- package/extensions/plan/web-settle.ts +194 -0
- package/extensions/plan/web-share-session.ts +24 -30
- package/extensions/plan/web-streams.ts +14 -14
- package/extensions/plan/web-tokens.ts +2 -2
- package/extensions/plan/web.ts +66 -120
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +15 -14
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +17 -69
- package/extensions/session-bus/format.ts +22 -45
- package/extensions/session-bus/index.ts +13 -7
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +32 -4
- package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
- package/extensions/session-bus/protocol.ts +19 -49
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +16 -51
- package/extensions/session-bus/rows.ts +11 -5
- package/extensions/session-bus/runtime.ts +220 -267
- package/extensions/session-bus/store.ts +55 -7
- package/extensions/session-bus/tools.ts +31 -66
- package/extensions/session-bus/tui.ts +5 -5
- package/extensions/session-bus/types.ts +23 -14
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/commands.ts +2 -1
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +145 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +119 -0
- package/extensions/team/index.ts +34 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +267 -0
- package/extensions/team/types.ts +65 -0
- package/extensions/todo/index.ts +4 -1
- package/extensions/todo/nudge.ts +8 -2
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +106 -251
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/intake.ts +245 -0
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
- package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/commands.ts +24 -17
- package/extensions/worktree/exit.ts +12 -6
- package/extensions/worktree/git.ts +22 -14
- package/extensions/worktree/guard.ts +18 -290
- package/extensions/worktree/handoff.ts +10 -2
- package/extensions/worktree/index.ts +2 -1
- package/extensions/worktree/lifecycle.ts +2 -0
- package/extensions/worktree/registry.ts +20 -11
- package/extensions/worktree/root-tools.ts +13 -3
- package/extensions/worktree/runtime.ts +8 -2
- package/extensions/worktree/screen.ts +15 -8
- package/extensions/worktree/shell.ts +303 -0
- package/extensions/worktree/state.ts +1 -1
- package/extensions/worktree/status.ts +42 -9
- package/extensions/worktree/tools.ts +19 -13
- package/lib/README.md +11 -8
- package/lib/board/card.ts +479 -0
- package/lib/board/links.ts +141 -0
- package/lib/board/page-pointer.ts +310 -0
- package/lib/board/round-text.ts +81 -0
- package/lib/board/round.ts +94 -0
- package/lib/board/rules.ts +437 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +497 -0
- package/lib/board/view.ts +234 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +35 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/serve.ts +237 -0
- package/lib/http/server.ts +12 -10
- package/lib/http/sink.ts +135 -0
- package/lib/http/token.ts +38 -0
- package/lib/plan-entry.ts +11 -0
- package/lib/policy.ts +25 -2
- package/lib/requirements.ts +6 -7
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +201 -0
- package/lib/session-tool.ts +184 -0
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +38 -2
- package/lib/tui/glyphs.ts +3 -2
- package/lib/web/README.md +57 -4
- package/lib/web/client-runtime.ts +123 -0
- package/lib/web/components.ts +5 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +88 -1
- package/lib/web/names.ts +42 -0
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +26 -14
- package/skills/next/SKILL.md +4 -3
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
- package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
- package/extensions/file-browse/README.md +0 -506
- package/extensions/file-browse/files.ts +0 -564
- package/extensions/file-browse/server.ts +0 -141
- package/extensions/job/process.ts +0 -35
- package/extensions/plan/split.ts +0 -87
- package/extensions/session-bus/README.md +0 -247
- package/extensions/session-bus/team.ts +0 -102
- package/extensions/wechat/outbound.ts +0 -100
- package/lib/plans/sections.ts +0 -172
- package/lib/plans/setting.ts +0 -52
- package/lib/plans/split.ts +0 -215
- package/lib/plans/store.ts +0 -19
- package/lib/plans/team.ts +0 -151
- package/skills/changelog/SKILL.md +0 -148
- package/skills/parallel-dev/SKILL.md +0 -150
- /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `/bytefly` surface: one command, `/bytefly [doctor] [settings] [skills] [update] [extension] [refresh]`.
|
|
2
|
+
* The `/bytefly` surface: one command, `/bytefly [doctor] [settings] [skills] [update] [upgrade] [extension] [refresh]`.
|
|
3
3
|
*
|
|
4
4
|
* Without a section word it opens the extension screen: the dock in `tui`, the
|
|
5
5
|
* grouped listing elsewhere. `settings` opens the Settings section, or prints
|
|
6
6
|
* its grouped listing outside `tui` without resolving packages. `skills` opens
|
|
7
7
|
* the Skills section and lists the resolved skills in text modes. `update`
|
|
8
8
|
* re-runs the published-version check and reports what it found, which is the
|
|
9
|
-
* one place a request is made on demand
|
|
9
|
+
* one place a request is made on demand; `upgrade` asks Pi's own package manager
|
|
10
|
+
* to install what that check found, behind a confirmation, so the two verbs stay
|
|
11
|
+
* one job each — ask, and act. With `doctor` it produces the doctor
|
|
10
12
|
* report — the same text `run_doctor` returns — for the whole set or one named
|
|
11
13
|
* extension; in `tui` the report is the dock screen. Discovery, the doctor
|
|
12
14
|
* probe, and the dirty-to-reload confirm all live here. The doctor runtime and
|
|
@@ -27,7 +29,11 @@ import type { UpdateHandle } from "../update/register.ts";
|
|
|
27
29
|
import { createSettingsStores, discoverResources, type DiscoveryDeps } from "../picker/discovery.ts";
|
|
28
30
|
import { createPickerRuntime, type PickerRuntime } from "../picker/runtime.ts";
|
|
29
31
|
import type { Discovery } from "../picker/types.ts";
|
|
32
|
+
import { usageRecords } from "../footer/collect/entries.ts";
|
|
33
|
+
import { loadByteflyConfig } from "../footer/price/config.ts";
|
|
34
|
+
import { createPricer } from "../footer/price/price.ts";
|
|
30
35
|
import { formatListing, formatSettingsListing, SKILL_NOUN } from "./format.ts";
|
|
36
|
+
import { pricesReport } from "./prices.ts";
|
|
31
37
|
import { buildPanelModel, buildSkillModel, extensionName } from "./join.ts";
|
|
32
38
|
import { createSettingsRuntime, type SettingsRuntime, type SettingsRuntimeDeps } from "./settings-runtime.ts";
|
|
33
39
|
import { loadSkillIndex, resolvedSkillPaths, type SkillIndex } from "./skills.ts";
|
|
@@ -63,14 +69,23 @@ function selectableNames(discovery: Discovery): Set<string> {
|
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
/** The values a bare `/bytefly <tab>` offers, in screen order. */
|
|
66
|
-
const FIRST_TOKENS = [
|
|
72
|
+
const FIRST_TOKENS = [
|
|
73
|
+
"doctor",
|
|
74
|
+
"prices",
|
|
75
|
+
"settings",
|
|
76
|
+
"skills",
|
|
77
|
+
"update",
|
|
78
|
+
"upgrade",
|
|
79
|
+
...Object.keys(REQUIREMENTS),
|
|
80
|
+
"refresh",
|
|
81
|
+
] as const;
|
|
67
82
|
|
|
68
83
|
/**
|
|
69
84
|
* Argument completions. The host replaces the whole argument text with the
|
|
70
85
|
* returned `value`, so a second token returns the full `doctor <name>` or
|
|
71
86
|
* `<name> refresh` argument rather than just the word. `/bytefly settings`,
|
|
72
|
-
* `/bytefly skills`, and `/bytefly
|
|
73
|
-
* followed by another token.
|
|
87
|
+
* `/bytefly skills`, `/bytefly update`, and `/bytefly upgrade` take no name, and
|
|
88
|
+
* `refresh` is never followed by another token.
|
|
74
89
|
*/
|
|
75
90
|
function completions(prefix: string): Array<{ value: string; label: string }> | null {
|
|
76
91
|
const [first = "", second, ...rest] = prefix.trimStart().split(/\s+/);
|
|
@@ -79,7 +94,8 @@ function completions(prefix: string): Array<{ value: string; label: string }> |
|
|
|
79
94
|
const options = FIRST_TOKENS.filter((option) => option.startsWith(first));
|
|
80
95
|
return options.length > 0 ? options.map((option) => ({ value: `${option} `, label: option })) : null;
|
|
81
96
|
}
|
|
82
|
-
if (first === "settings" || first === "skills" || first === "refresh" || first === "update"
|
|
97
|
+
if (first === "settings" || first === "skills" || first === "refresh" || first === "update" || first === "upgrade")
|
|
98
|
+
return null;
|
|
83
99
|
const options = (first === "doctor" ? [...Object.keys(REQUIREMENTS), "refresh"] : ["refresh"]).filter((option) =>
|
|
84
100
|
option.startsWith(second),
|
|
85
101
|
);
|
|
@@ -87,7 +103,7 @@ function completions(prefix: string): Array<{ value: string; label: string }> |
|
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
/** Which bytefly surface the arguments select. */
|
|
90
|
-
type PanelMode = "screen" | "doctor" | "update";
|
|
106
|
+
type PanelMode = "screen" | "doctor" | "update" | "upgrade" | "prices";
|
|
91
107
|
|
|
92
108
|
/** `/bytefly [doctor] [settings] [extension] [refresh]`, parsed. */
|
|
93
109
|
interface PanelArgs {
|
|
@@ -109,6 +125,13 @@ function parseArgs(args: string): PanelArgs {
|
|
|
109
125
|
// `update` is already a refresh of the published version, so a stray `refresh`
|
|
110
126
|
// beside it is accepted and ignored rather than refused.
|
|
111
127
|
if (rest[0] === "update") return { mode: "update", section: "extensions", refresh };
|
|
128
|
+
// `upgrade` is the same subject as `update`, one verb further: it installs what a
|
|
129
|
+
// check found. Refusing to fold them into one word is what keeps a command that
|
|
130
|
+
// writes behind a confirmation instead of behind an argument.
|
|
131
|
+
if (rest[0] === "upgrade") return { mode: "upgrade", section: "extensions", refresh };
|
|
132
|
+
// `prices` answers a question about this session's totals, so it takes no name and
|
|
133
|
+
// no refresh: it prices what the session holds, once, when asked.
|
|
134
|
+
if (rest[0] === "prices") return { mode: "prices", section: "extensions", refresh };
|
|
112
135
|
if (rest[0] === "settings") return { mode: "screen", section: "settings", name: rest[1], refresh };
|
|
113
136
|
if (rest[0] === "skills") return { mode: "screen", section: "skills", name: rest[1], refresh };
|
|
114
137
|
return { mode: "screen", section: "extensions", name: rest[0], refresh };
|
|
@@ -166,6 +189,18 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
166
189
|
return;
|
|
167
190
|
}
|
|
168
191
|
const cwd = ctx.cwd ?? process.cwd();
|
|
192
|
+
// `/bytefly prices` answers one question about the session and resolves nothing: it
|
|
193
|
+
// prices what the transcript already recorded and names what that left out. It runs
|
|
194
|
+
// in every mode, because the footer that would carry the same fact exists only in
|
|
195
|
+
// `tui`.
|
|
196
|
+
if (mode === "prices") {
|
|
197
|
+
const config = loadByteflyConfig(cwd);
|
|
198
|
+
const active = ctx.model ? { provider: ctx.model.provider, model: ctx.model.id } : undefined;
|
|
199
|
+
const scanned = usageRecords(ctx.sessionManager.getEntries(), active);
|
|
200
|
+
ctx.ui.notify(pricesReport(createPricer(config)(scanned.records), config.currency).join("\n"), "info");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
169
204
|
const agentDir = getAgentDir();
|
|
170
205
|
const trusted = ctx.isProjectTrusted();
|
|
171
206
|
const themes = settingsThemeNames(ctx);
|
|
@@ -190,6 +225,24 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
190
225
|
return;
|
|
191
226
|
}
|
|
192
227
|
|
|
228
|
+
// `/bytefly upgrade` is the only path here that writes, and it writes outside
|
|
229
|
+
// the session: Pi's package manager replaces the installed files, so the
|
|
230
|
+
// confirmation and the reload line are part of the answer, not decoration.
|
|
231
|
+
if (mode === "upgrade") {
|
|
232
|
+
const confirmed = await ctx.ui.confirm(
|
|
233
|
+
"Update packages?",
|
|
234
|
+
`Run Pi's package manager for every configured package, then reload to use it.`,
|
|
235
|
+
);
|
|
236
|
+
if (!confirmed) {
|
|
237
|
+
ctx.ui.notify("bytefly: update cancelled.", "info");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const outcome = await deps.update.apply();
|
|
241
|
+
const { text, severity } = updateOutcomeText(outcome, pkg.version);
|
|
242
|
+
ctx.ui.notify(text, severity);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
193
246
|
// Settings outside `tui` prints its grouped listing without resolving
|
|
194
247
|
// packages or probing the doctor.
|
|
195
248
|
if (section === "settings" && ctx.mode !== "tui") {
|
|
@@ -293,6 +346,13 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
293
346
|
refresh: async (force) => {
|
|
294
347
|
reports = await deps.doctor.probeAll({ refresh: force === true });
|
|
295
348
|
},
|
|
349
|
+
reload: async () => {
|
|
350
|
+
// The reload takes the session, so the screen asks first and says
|
|
351
|
+
// what will change; a refusal leaves the dock exactly as it was.
|
|
352
|
+
const reload = await ctx.ui.confirm("Reload to apply changes?", "Changes take effect after a reload.");
|
|
353
|
+
if (!reload) return;
|
|
354
|
+
await ctx.reload();
|
|
355
|
+
},
|
|
296
356
|
onClose: () => {
|
|
297
357
|
// A late error falls back to the transcript, never a disposed screen.
|
|
298
358
|
panel = undefined;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two filters the screen offers: a text query, and a switch that keeps only
|
|
3
|
+
* the rows worth acting on.
|
|
4
|
+
*
|
|
5
|
+
* A filter is part of the model build, not a post-pass over a rendered list, so a
|
|
6
|
+
* group's count and the range row stay truthful about what is on screen. The chips
|
|
7
|
+
* the tab row prints come from the same object, so the screen cannot say it is
|
|
8
|
+
* filtered without saying by what.
|
|
9
|
+
*
|
|
10
|
+
* Value-only: pure predicates over rows and views.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { SummarySegment } from "./format.ts";
|
|
14
|
+
import { rowBadge } from "./format.ts";
|
|
15
|
+
import type { PanelRow } from "./types.ts";
|
|
16
|
+
import type { SettingView } from "./settings.ts";
|
|
17
|
+
|
|
18
|
+
/** What the reader narrowed the list to. */
|
|
19
|
+
export interface PanelFilter {
|
|
20
|
+
/** Case-insensitive text matched against a row's name, path and source. */
|
|
21
|
+
query: string;
|
|
22
|
+
/** Keep only rows that need attention (settings: values changed from default). */
|
|
23
|
+
attentionOnly: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The unfiltered state. */
|
|
27
|
+
export const NO_FILTER: PanelFilter = { query: "", attentionOnly: false };
|
|
28
|
+
|
|
29
|
+
/** Whether anything is narrowed, so the chrome can say so. */
|
|
30
|
+
export function isFiltering(filter: PanelFilter): boolean {
|
|
31
|
+
return filter.query.trim() !== "" || filter.attentionOnly;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Whether the row's own text mentions `query`. */
|
|
35
|
+
function mentions(text: readonly (string | undefined)[], query: string): boolean {
|
|
36
|
+
const needle = query.toLowerCase();
|
|
37
|
+
return text.some((value) => value !== undefined && value.toLowerCase().includes(needle));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether a resource row survives the filter. The query reaches the picker's path
|
|
42
|
+
* and source as well as the name, because "which file is this" is the question a
|
|
43
|
+
* reader asks of a row they cannot place.
|
|
44
|
+
*/
|
|
45
|
+
export function rowMatches(row: PanelRow, filter: PanelFilter): boolean {
|
|
46
|
+
if (filter.attentionOnly && rowBadge(row).glyph === undefined) return false;
|
|
47
|
+
if (!mentions([row.label, row.key, row.item?.path, row.report?.extension, row.skill?.description], filter.query)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether a settings row survives the filter; "changed" means not at its default. */
|
|
54
|
+
export function settingMatches(view: SettingView, filter: PanelFilter): boolean {
|
|
55
|
+
if (filter.attentionOnly && view.origin === "default") return false;
|
|
56
|
+
return mentions([view.label, view.id, view.description, view.valueLabel], filter.query);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The chips the tab row shows while a filter is on. */
|
|
60
|
+
export function filterChips(filter: PanelFilter, kind: "rows" | "settings"): SummarySegment[] {
|
|
61
|
+
const chips: SummarySegment[] = [];
|
|
62
|
+
const query = filter.query.trim();
|
|
63
|
+
if (query !== "") chips.push({ text: `/${query}`, color: "meta" });
|
|
64
|
+
if (filter.attentionOnly) {
|
|
65
|
+
chips.push({ text: kind === "settings" ? "changed only" : "needs attention", color: "warn" });
|
|
66
|
+
}
|
|
67
|
+
return chips;
|
|
68
|
+
}
|
|
@@ -127,23 +127,24 @@ const SECTION_LABELS: Record<PanelSection, string> = {
|
|
|
127
127
|
settings: "Settings",
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
/** One section's name, as the tab row prints it. */
|
|
131
|
+
export function sectionLabel(section: PanelSection): string {
|
|
132
|
+
return SECTION_LABELS[section];
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
/**
|
|
131
|
-
* The
|
|
132
|
-
*
|
|
136
|
+
* The header's attention line: the worst thing the screen knows, then how many
|
|
137
|
+
* changes a reload would apply. `all clear` is said out loud rather than left
|
|
138
|
+
* blank, so the header never reads as "nothing loaded".
|
|
133
139
|
*/
|
|
134
|
-
export function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
export function settingsSummarySegments(scope: PanelScope, count: number): SummarySegment[] {
|
|
143
|
-
return [
|
|
144
|
-
{ text: scope === "global" ? "Global" : "Project", color: "accent" },
|
|
145
|
-
{ text: `${count} settings`, color: "meta" },
|
|
146
|
-
];
|
|
140
|
+
export function attentionSegments(counts: PanelCounts, dirty: boolean): SummarySegment[] {
|
|
141
|
+
const segments: SummarySegment[] = [];
|
|
142
|
+
if (counts.missing > 0) segments.push({ text: `${counts.missing} missing`, color: "bad" });
|
|
143
|
+
else if (counts.degraded > 0) segments.push({ text: `${counts.degraded} degraded`, color: "warn" });
|
|
144
|
+
else if (counts.off > 0) segments.push({ text: `${counts.off} off`, color: "faint" });
|
|
145
|
+
else segments.push({ text: "all clear", color: "ok" });
|
|
146
|
+
if (dirty) segments.push({ text: "unsaved changes", color: "warn" });
|
|
147
|
+
return segments;
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
/**
|
|
@@ -31,6 +31,17 @@ export function extensionName(path: string): string {
|
|
|
31
31
|
return basename(dirname(path));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The name a resource is called by on screen: the directory for an entry file
|
|
36
|
+
* (`…/worktree/index.ts`, `…/pdf/SKILL.md`), and the file's own stem otherwise, so
|
|
37
|
+
* a flat `extensions/auto.ts` is `auto` rather than the directory every flat file
|
|
38
|
+
* shares.
|
|
39
|
+
*/
|
|
40
|
+
export function resourceName(path: string): string {
|
|
41
|
+
const stem = basename(path).replace(/\.(ts|js|mjs|cjs|md)$/u, "");
|
|
42
|
+
return stem === "index" || stem === "SKILL" ? basename(dirname(path)) : stem;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
/** Row key for a report that has no resolved extension item. */
|
|
35
46
|
function reportKey(name: string): string {
|
|
36
47
|
return `report:${name}`;
|
|
@@ -46,13 +57,24 @@ export interface PanelJoinOptions {
|
|
|
46
57
|
agentDir: string;
|
|
47
58
|
/** Narrow to one extension directory or report name, or `undefined` for the full list. */
|
|
48
59
|
filter?: string;
|
|
60
|
+
/** Keep only the rows this accepts; a group's count follows what survives. */
|
|
61
|
+
where?: (row: PanelRow) => boolean;
|
|
49
62
|
/** Which resource kind the list is, for labels and grouping. */
|
|
50
63
|
kind?: ResourceKind;
|
|
51
64
|
}
|
|
52
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The rows one group list produces, group headings included. The screen measures
|
|
68
|
+
* a section's height from this rather than from a built model, so a section it is
|
|
69
|
+
* not showing costs no `SKILL.md` read.
|
|
70
|
+
*/
|
|
71
|
+
export function groupedEntryCount(items: readonly ResourceItem[], agentDir: string, kind: ResourceKind): number {
|
|
72
|
+
return buildEntries([...items], agentDir, kind).length;
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
/** Build the merged model for the current items and reports. */
|
|
54
76
|
export function buildPanelModel(options: PanelJoinOptions): PanelModel {
|
|
55
|
-
const { items, reports, agentDir, filter, kind = "extensions" } = options;
|
|
77
|
+
const { items, reports, agentDir, filter, where, kind = "extensions" } = options;
|
|
56
78
|
const byName = new Map(reports.map((report) => [report.extension, report]));
|
|
57
79
|
|
|
58
80
|
// A directory name may claim a report only when exactly one unique resource
|
|
@@ -60,7 +82,7 @@ export function buildPanelModel(options: PanelJoinOptions): PanelModel {
|
|
|
60
82
|
// the report and it stays available to the trailing `Not resolved` group.
|
|
61
83
|
const pathsByName = new Map<string, Set<string>>();
|
|
62
84
|
for (const item of items) {
|
|
63
|
-
const name =
|
|
85
|
+
const name = resourceName(item.path);
|
|
64
86
|
const paths = pathsByName.get(name) ?? new Set<string>();
|
|
65
87
|
paths.add(resourceKey(item));
|
|
66
88
|
pathsByName.set(name, paths);
|
|
@@ -83,12 +105,23 @@ export function buildPanelModel(options: PanelJoinOptions): PanelModel {
|
|
|
83
105
|
const report = unambiguous(name) ? byName.get(name) : undefined;
|
|
84
106
|
if (report) claimed.add(name);
|
|
85
107
|
if (filter !== undefined && name !== filter) continue;
|
|
86
|
-
|
|
108
|
+
// The row is named for the resource, not for its file: a name is only unique
|
|
109
|
+
// enough to be a name when one resource uses it, which is the same rule that
|
|
110
|
+
// lets a directory claim a report. Otherwise the picker's `folder/file.ts`
|
|
111
|
+
// stays, because two rows would otherwise read alike.
|
|
112
|
+
const label = unambiguous(name) ? resourceName(item.path) : entry.label;
|
|
113
|
+
const row: PanelRow = { key: resourceKey(item), item, report, label, group: current?.key ?? "" };
|
|
114
|
+
if (where !== undefined && !where(row)) continue;
|
|
115
|
+
rows.push(row);
|
|
87
116
|
if (current) current.count += 1;
|
|
88
117
|
}
|
|
89
118
|
|
|
90
119
|
const extras = reports.filter(
|
|
91
|
-
(report) =>
|
|
120
|
+
(report) =>
|
|
121
|
+
!claimed.has(report.extension) &&
|
|
122
|
+
(filter === undefined || report.extension === filter) &&
|
|
123
|
+
(where === undefined ||
|
|
124
|
+
where({ key: reportKey(report.extension), report, label: report.extension, group: REPORT_ONLY_GROUP })),
|
|
92
125
|
);
|
|
93
126
|
if (extras.length > 0) {
|
|
94
127
|
const group: PanelGroup = { key: REPORT_ONLY_GROUP, label: REPORT_ONLY_LABEL, count: extras.length };
|
|
@@ -111,14 +144,24 @@ export function buildSkillModel(options: {
|
|
|
111
144
|
items: readonly ResourceItem[];
|
|
112
145
|
index: ReadonlyMap<string, SkillInfo>;
|
|
113
146
|
agentDir: string;
|
|
147
|
+
where?: (row: PanelRow) => boolean;
|
|
114
148
|
}): PanelModel {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
149
|
+
// The filter runs after the join: a skill's name and its `SKILL.md` facts are
|
|
150
|
+
// what a reader searches and what decides whether it needs attention.
|
|
151
|
+
const model = buildPanelModel({
|
|
152
|
+
items: options.items,
|
|
153
|
+
reports: [],
|
|
154
|
+
agentDir: options.agentDir,
|
|
155
|
+
kind: "skills",
|
|
156
|
+
});
|
|
157
|
+
const joined = model.rows.map((row) => {
|
|
158
|
+
const skill = row.item ? options.index.get(row.item.path) : undefined;
|
|
159
|
+
if (skill === undefined) return row;
|
|
160
|
+
return { ...row, label: skill.name ?? row.label, skill };
|
|
161
|
+
});
|
|
162
|
+
const rows = options.where === undefined ? joined : joined.filter(options.where);
|
|
163
|
+
const groups = model.groups
|
|
164
|
+
.map((group) => ({ ...group, count: rows.filter((row) => row.group === group.key).length }))
|
|
165
|
+
.filter((group) => group.count > 0);
|
|
166
|
+
return { groups, rows };
|
|
124
167
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/bytefly prices` report: which models this session could not price.
|
|
3
|
+
*
|
|
4
|
+
* A record nothing priced contributes no cost, so the footer's total is smaller than
|
|
5
|
+
* the work done and the reason is a *model*, not a record count. This is the answer to
|
|
6
|
+
* the question the footer can only hint at: the keys, in the `price.rates` vocabulary,
|
|
7
|
+
* and the one line saying what to do with them.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here quotes a price: a rate is the user's to state, and inventing one is the
|
|
10
|
+
* rate table this command exists instead of.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { sanitizeLine } from "../../../lib/format.ts";
|
|
14
|
+
import type { PricedUsage } from "../footer/types.ts";
|
|
15
|
+
import { formatCost } from "../footer/render/format.ts";
|
|
16
|
+
import { recordsText } from "../footer/unpriced.ts";
|
|
17
|
+
import type { Currency } from "../footer/price/types.ts";
|
|
18
|
+
|
|
19
|
+
/** How many keys the report prints before it counts the rest. */
|
|
20
|
+
export const REPORTED_MODELS = 8;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The report as the lines a reader gets.
|
|
24
|
+
*
|
|
25
|
+
* The headline says what the total is and how much of the session it leaves out; one
|
|
26
|
+
* line per key says what to add; the last line says where to add it. Pure: the caller
|
|
27
|
+
* has already priced the session.
|
|
28
|
+
*/
|
|
29
|
+
export function pricesReport(priced: PricedUsage, currency: Currency): string[] {
|
|
30
|
+
const models = priced.unpricedModels;
|
|
31
|
+
if (models.length === 0) return ["bytefly: every record in this session has a rate — nothing to add."];
|
|
32
|
+
const count = models.reduce((total, model) => total + model.records, 0);
|
|
33
|
+
const headline =
|
|
34
|
+
`bytefly: ${formatCost(priced.cost, currency)} priced · ` +
|
|
35
|
+
`${recordsText(count)} in ${models.length === 1 ? "1 model" : `${models.length} models`} ${count === 1 ? "has" : "have"} no rate`;
|
|
36
|
+
const keys = models
|
|
37
|
+
.slice(0, REPORTED_MODELS)
|
|
38
|
+
.map((model) => ` ${sanitizeLine(model.key)} · ${recordsText(model.records)}`);
|
|
39
|
+
const rest = models.length - keys.length;
|
|
40
|
+
if (rest > 0) keys.push(` +${rest} more`);
|
|
41
|
+
keys.push(' add these under "price.rates" in bytefly.json — a key may be `*`, or a prefix like `gpt-*`.');
|
|
42
|
+
return [headline, ...keys];
|
|
43
|
+
}
|
|
@@ -88,12 +88,6 @@ function applyAppSetting(store: SettingsStore, row: SettingDescriptor, raw: stri
|
|
|
88
88
|
case "byteflyUi":
|
|
89
89
|
store.setQuietStartup(value as QuietStartup);
|
|
90
90
|
return;
|
|
91
|
-
case "outputPad":
|
|
92
|
-
store.setOutputPad(value as 0 | 1);
|
|
93
|
-
return;
|
|
94
|
-
case "showHardwareCursor":
|
|
95
|
-
store.setShowHardwareCursor(value as boolean);
|
|
96
|
-
return;
|
|
97
91
|
case "theme":
|
|
98
92
|
store.setTheme(value as string);
|
|
99
93
|
return;
|