magus-cli 7.13.0 → 7.14.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 +7 -4
- package/package.json +4 -4
- package/src/__tests__/market-r2-adapters.test.ts +190 -0
- package/src/__tests__/market-r2-company-cli.test.ts +350 -0
- package/src/__tests__/market-r2-company-unit.test.ts +224 -0
- package/src/__tests__/market-r2-local-items.test.ts +197 -0
- package/src/__tests__/market-r2-plugin-add.test.ts +470 -0
- package/src/__tests__/market-r2-plugin-publish.test.ts +568 -0
- package/src/__tests__/market-r2-routing.test.ts +127 -0
- package/src/__tests__/market-r2-skill-create-plugin.test.ts +315 -0
- package/src/__tests__/market-r2b-display-path.test.ts +180 -0
- package/src/__tests__/market-r2b-fit-row.test.ts +287 -0
- package/src/__tests__/market-r2b-local-items.test.ts +253 -0
- package/src/__tests__/market-r2b-plugin-add.test.ts +130 -0
- package/src/__tests__/market-router.test.ts +4 -4
- package/src/cli/market-view.ts +59 -18
- package/src/cli/market.ts +128 -44
- package/src/cli/router.ts +42 -10
- package/src/data/marketplaces.ts +1 -0
- package/src/main.tsx +1 -1
- package/src/market-tui.tsx +1 -1
- package/src/services/local-marketplace.ts +8 -0
- package/src/services/market-change.ts +6 -6
- package/src/services/market-local.ts +93 -0
- package/src/services/market-ops-plugin.ts +366 -47
- package/src/services/market-ops-publish.ts +363 -15
- package/src/services/market-ops-repo.ts +94 -13
- package/src/services/market-ops-skill.ts +93 -11
- package/src/services/market-ops.ts +72 -8
- package/src/services/market-publish.ts +10 -1
- package/src/services/market-types.ts +23 -0
- package/src/tui-runtime.tsx +2 -2
- package/src/types/index.ts +2 -0
- package/src/ui/adapters/marketAdapter.ts +123 -4
- package/src/ui/adapters/pluginsAdapter.ts +25 -1
- package/src/ui/components/primitives/ListCategoryRow.tsx +22 -2
- package/src/ui/market/MarketplaceScreen.tsx +55 -24
- package/src/ui/market/MarketplacesDetail.snowflake.tsx +60 -4
- package/src/ui/market/MarketplacesScreen.tsx +120 -29
- package/src/ui/market/PluginDetail.snowflake.tsx +12 -1
- package/src/ui/market/marketKeymap.ts +29 -2
- package/src/ui/market/useMarketState.ts +146 -5
- package/src/utils/display-path.ts +33 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
2
|
import {
|
|
3
3
|
type MarketplaceItem,
|
|
4
|
+
fitRow,
|
|
4
5
|
freshnessWords,
|
|
5
6
|
marketplaceItems,
|
|
6
7
|
plural,
|
|
@@ -40,12 +41,36 @@ const SKILL_INDENT = 6;
|
|
|
40
41
|
/** Cells a plugin row starts in. */
|
|
41
42
|
const PLUGIN_INDENT = 2;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
/** Columns the scope squares and the ▶ marker take before a plugin's name. */
|
|
45
|
+
const PLUGIN_PREFIX = 6;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One row of screen 2 in `width` columns, never wrapped: the name always
|
|
49
|
+
* shows; the version, the skill count and the marks are cut from the end.
|
|
50
|
+
*/
|
|
51
|
+
export function renderMarketplaceRow(
|
|
44
52
|
item: MarketplaceItem,
|
|
45
|
-
_index: number,
|
|
46
53
|
selected: boolean,
|
|
54
|
+
width: number,
|
|
47
55
|
): React.ReactNode {
|
|
48
56
|
if (item.kind === "plugin") {
|
|
57
|
+
const fit = fitRow(
|
|
58
|
+
Math.max(1, width - PLUGIN_INDENT),
|
|
59
|
+
item.name,
|
|
60
|
+
PLUGIN_PREFIX,
|
|
61
|
+
[
|
|
62
|
+
{ text: item.version, tone: "muted" as const },
|
|
63
|
+
{ text: plural(item.skills.length, "skill"), tone: "muted" as const },
|
|
64
|
+
...(item.edited.length > 0
|
|
65
|
+
? [
|
|
66
|
+
{
|
|
67
|
+
text: `● ${plural(item.edited.length, "skill")} edited here`,
|
|
68
|
+
tone: "warning" as const,
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
: []),
|
|
72
|
+
],
|
|
73
|
+
);
|
|
49
74
|
return (
|
|
50
75
|
<SelectableRow selected={selected} indent={PLUGIN_INDENT}>
|
|
51
76
|
<ScopeSquares
|
|
@@ -55,31 +80,32 @@ function renderRow(
|
|
|
55
80
|
selected={selected}
|
|
56
81
|
/>
|
|
57
82
|
{item.expanded ? " ▼ " : " ▶ "}
|
|
58
|
-
<strong>{
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/>
|
|
83
|
+
<strong>{fit.name}</strong>
|
|
84
|
+
{fit.secondary.map((p) => (
|
|
85
|
+
<React.Fragment key={p.text}>
|
|
86
|
+
{" "}
|
|
87
|
+
<MetaText text={p.text} tone={p.tone} selected={selected} />
|
|
88
|
+
</React.Fragment>
|
|
89
|
+
))}
|
|
66
90
|
</SelectableRow>
|
|
67
91
|
);
|
|
68
92
|
}
|
|
69
93
|
const mark = skillMark(item.local);
|
|
94
|
+
const fit = fitRow(
|
|
95
|
+
Math.max(1, width - SKILL_INDENT),
|
|
96
|
+
item.skill,
|
|
97
|
+
0,
|
|
98
|
+
mark ? [{ text: `${mark.glyph} ${mark.words}`, tone: mark.tone }] : [],
|
|
99
|
+
);
|
|
70
100
|
return (
|
|
71
101
|
<SelectableRow selected={selected} indent={SKILL_INDENT}>
|
|
72
|
-
{
|
|
73
|
-
{
|
|
74
|
-
|
|
102
|
+
{fit.name}
|
|
103
|
+
{fit.secondary.map((p) => (
|
|
104
|
+
<React.Fragment key={p.text}>
|
|
75
105
|
{" "}
|
|
76
|
-
<MetaText
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
selected={selected}
|
|
80
|
-
/>
|
|
81
|
-
</>
|
|
82
|
-
) : null}
|
|
106
|
+
<MetaText text={p.text} tone={p.tone} selected={selected} />
|
|
107
|
+
</React.Fragment>
|
|
108
|
+
))}
|
|
83
109
|
</SelectableRow>
|
|
84
110
|
);
|
|
85
111
|
}
|
|
@@ -125,6 +151,8 @@ export function MarketplaceScreen({
|
|
|
125
151
|
publish: () => {
|
|
126
152
|
if (item?.kind === "skill" && item.local) {
|
|
127
153
|
void market.publish(item.skill, item.ref, name, item.local);
|
|
154
|
+
} else if (item?.kind === "plugin") {
|
|
155
|
+
void market.publishPlugin(item.ref, name);
|
|
128
156
|
}
|
|
129
157
|
},
|
|
130
158
|
add: () => {
|
|
@@ -167,7 +195,7 @@ export function MarketplaceScreen({
|
|
|
167
195
|
|
|
168
196
|
return (
|
|
169
197
|
<ScreenLayout
|
|
170
|
-
title={`magus
|
|
198
|
+
title={`magus edit › ${name}`}
|
|
171
199
|
subtitle={
|
|
172
200
|
market.data.status === "loading"
|
|
173
201
|
? "reading…"
|
|
@@ -186,7 +214,7 @@ export function MarketplaceScreen({
|
|
|
186
214
|
bindings: [...MARKETPLACE_KEYS, ...MARKETPLACE_APP_KEYS],
|
|
187
215
|
pinned: [MARKET_HELP],
|
|
188
216
|
}}
|
|
189
|
-
listPanel={
|
|
217
|
+
listPanel={(width: number) =>
|
|
190
218
|
failed ? (
|
|
191
219
|
<ErrorNotice
|
|
192
220
|
title={`Could not read ${name}.`}
|
|
@@ -200,6 +228,7 @@ export function MarketplaceScreen({
|
|
|
200
228
|
count={entry?.plugins.length ?? 0}
|
|
201
229
|
selected={false}
|
|
202
230
|
expandable={false}
|
|
231
|
+
width={width}
|
|
203
232
|
/>
|
|
204
233
|
{items.length === 0 && market.data.status !== "loading" ? (
|
|
205
234
|
<ToneLine tone="muted">
|
|
@@ -211,7 +240,9 @@ export function MarketplaceScreen({
|
|
|
211
240
|
<ScrollableList
|
|
212
241
|
items={items}
|
|
213
242
|
selectedIndex={index}
|
|
214
|
-
renderItem={
|
|
243
|
+
renderItem={(it, _i, selected) =>
|
|
244
|
+
renderMarketplaceRow(it, selected, width)
|
|
245
|
+
}
|
|
215
246
|
maxHeight={Math.max(
|
|
216
247
|
1,
|
|
217
248
|
dimensions.listPanelHeight - HEADING_ROWS,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { displayPath } from "../../utils/display-path.js";
|
|
1
2
|
import {
|
|
2
3
|
type MarketplacesItem,
|
|
3
4
|
freshnessTone,
|
|
4
5
|
freshnessWords,
|
|
6
|
+
localKindWords,
|
|
5
7
|
plural,
|
|
6
8
|
} from "../adapters/marketAdapter.js";
|
|
7
9
|
import {
|
|
@@ -22,12 +24,53 @@ import {
|
|
|
22
24
|
*/
|
|
23
25
|
export function MarketplacesDetail({
|
|
24
26
|
item,
|
|
27
|
+
canAdd,
|
|
28
|
+
cwd,
|
|
25
29
|
note,
|
|
26
30
|
}: {
|
|
27
31
|
item: MarketplacesItem | undefined;
|
|
32
|
+
/** Whether there is a company marketplace to add a local row to. */
|
|
33
|
+
canAdd: boolean;
|
|
34
|
+
/** The project, against which a local folder is shown. */
|
|
35
|
+
cwd: string;
|
|
28
36
|
note: string | undefined;
|
|
29
37
|
}) {
|
|
30
|
-
if (!item) return null;
|
|
38
|
+
if (!item || item.kind === "local-heading") return null;
|
|
39
|
+
if (item.kind === "local") {
|
|
40
|
+
const local = item.item;
|
|
41
|
+
return (
|
|
42
|
+
<DetailSection title={local.name}>
|
|
43
|
+
<KeyValueLine label="kind" value={localKindWords(local)} />
|
|
44
|
+
<KeyValueLine
|
|
45
|
+
label="scope"
|
|
46
|
+
value={
|
|
47
|
+
<ScopeBadge scope={local.scope === "user" ? "USER" : "PROJECT"} />
|
|
48
|
+
}
|
|
49
|
+
/>
|
|
50
|
+
<KeyValueLine
|
|
51
|
+
label="folder"
|
|
52
|
+
value={<MetaText text={displayPath(local.path, cwd)} />}
|
|
53
|
+
/>
|
|
54
|
+
<ToneLine>
|
|
55
|
+
{local.kind === "plugin"
|
|
56
|
+
? "A plugin folder of your own, in no company marketplace."
|
|
57
|
+
: "A skill of your own, in no company plugin."}
|
|
58
|
+
</ToneLine>
|
|
59
|
+
{canAdd ? (
|
|
60
|
+
<ToneLine tone="muted">
|
|
61
|
+
{local.kind === "plugin"
|
|
62
|
+
? "a adds it to a company marketplace as a new plugin; the folder stays."
|
|
63
|
+
: "a adds it to a plugin of a company marketplace, or to a new one."}
|
|
64
|
+
</ToneLine>
|
|
65
|
+
) : (
|
|
66
|
+
<ToneLine tone="muted">
|
|
67
|
+
To add it, first create a company marketplace (c) or connect one
|
|
68
|
+
(o).
|
|
69
|
+
</ToneLine>
|
|
70
|
+
)}
|
|
71
|
+
</DetailSection>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
31
74
|
if (item.kind === "create") {
|
|
32
75
|
return (
|
|
33
76
|
<DetailSection title="Create a company marketplace">
|
|
@@ -36,8 +79,8 @@ export function MarketplacesDetail({
|
|
|
36
79
|
to this machine at USER scope.
|
|
37
80
|
</ToneLine>
|
|
38
81
|
<ToneLine tone="muted">
|
|
39
|
-
Enter or c asks for the owner, the name
|
|
40
|
-
what it will do.
|
|
82
|
+
Enter or c asks for the owner, the company name magus shows it by, the
|
|
83
|
+
repository name and who can see it, then shows what it will do.
|
|
41
84
|
</ToneLine>
|
|
42
85
|
</DetailSection>
|
|
43
86
|
);
|
|
@@ -55,10 +98,13 @@ export function MarketplacesDetail({
|
|
|
55
98
|
</DetailSection>
|
|
56
99
|
);
|
|
57
100
|
}
|
|
58
|
-
const { entry } = item;
|
|
101
|
+
const { entry, toPush } = item;
|
|
59
102
|
return (
|
|
60
103
|
<box flexDirection="column">
|
|
61
104
|
<DetailSection title={entry.name}>
|
|
105
|
+
{entry.company ? (
|
|
106
|
+
<KeyValueLine label="company" value={entry.company} />
|
|
107
|
+
) : null}
|
|
62
108
|
<KeyValueLine
|
|
63
109
|
label="repository"
|
|
64
110
|
value={<MetaText text={entry.repo} tone="info" />}
|
|
@@ -83,6 +129,16 @@ export function MarketplacesDetail({
|
|
|
83
129
|
</ToneLine>
|
|
84
130
|
) : null}
|
|
85
131
|
</DetailSection>
|
|
132
|
+
{toPush.length > 0 ? (
|
|
133
|
+
<DetailSection title="To push">
|
|
134
|
+
<ToneLine tone="warning">
|
|
135
|
+
● {toPush.join(", ")} — skills edited here
|
|
136
|
+
</ToneLine>
|
|
137
|
+
<ToneLine tone="muted">
|
|
138
|
+
Enter opens the marketplace; p on a plugin pushes a new version.
|
|
139
|
+
</ToneLine>
|
|
140
|
+
</DetailSection>
|
|
141
|
+
) : null}
|
|
86
142
|
{entry.plugins.length > 0 ? (
|
|
87
143
|
<DetailSection title="Plugins">
|
|
88
144
|
{entry.plugins.map((p) => (
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
2
|
import {
|
|
3
3
|
type MarketplacesItem,
|
|
4
|
+
fitRow,
|
|
5
|
+
localKindWords,
|
|
4
6
|
marketplacesItems,
|
|
7
|
+
nextSelectable,
|
|
5
8
|
plural,
|
|
9
|
+
selectable,
|
|
6
10
|
} from "../adapters/marketAdapter.js";
|
|
7
11
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
12
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
@@ -18,6 +22,7 @@ import { type KeyHandler, moveDirection, runBindings } from "../keymap.js";
|
|
|
18
22
|
import { useModal } from "../state/AppContext.js";
|
|
19
23
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
20
24
|
import { asyncValue } from "../state/types.js";
|
|
25
|
+
import { truncate } from "../text-width.js";
|
|
21
26
|
import { MarketplacesDetail } from "./MarketplacesDetail.snowflake.js";
|
|
22
27
|
import {
|
|
23
28
|
MARKETPLACES_APP_KEYS,
|
|
@@ -31,37 +36,99 @@ import type { MarketState } from "./useMarketState.js";
|
|
|
31
36
|
/** Rows above the list: the heading. */
|
|
32
37
|
const HEADING_ROWS = 1;
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
/** Cells a row starts in, the lead cell included. */
|
|
40
|
+
const ROW_INDENT = 2;
|
|
41
|
+
|
|
42
|
+
type Part = { text: string; tone: "muted" | "info" | "warning" };
|
|
43
|
+
|
|
44
|
+
/** The secondary texts after a row's name and badge, cut first when narrow. */
|
|
45
|
+
function Secondary({
|
|
46
|
+
parts,
|
|
47
|
+
selected,
|
|
48
|
+
}: {
|
|
49
|
+
parts: readonly Part[];
|
|
50
|
+
selected: boolean;
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
{parts.map((p) => (
|
|
55
|
+
<React.Fragment key={p.text}>
|
|
56
|
+
{" "}
|
|
57
|
+
<MetaText text={p.text} tone={p.tone} selected={selected} />
|
|
58
|
+
</React.Fragment>
|
|
59
|
+
))}
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One row of screen 1 in `width` columns, never wrapped: the name and the
|
|
66
|
+
* scope badge always show, the rest is cut or dropped from the end.
|
|
67
|
+
*/
|
|
68
|
+
export function renderMarketplacesRow(
|
|
35
69
|
item: MarketplacesItem,
|
|
36
|
-
_index: number,
|
|
37
70
|
selected: boolean,
|
|
71
|
+
width: number,
|
|
38
72
|
): React.ReactNode {
|
|
73
|
+
const room = Math.max(1, width - ROW_INDENT);
|
|
39
74
|
if (item.kind === "create" || item.kind === "connect") {
|
|
40
75
|
return (
|
|
41
|
-
<SelectableRow selected={selected} variant="action" indent={
|
|
42
|
-
{item.kind === "create" ? "+ Create…" : "+ Connect…"}
|
|
76
|
+
<SelectableRow selected={selected} variant="action" indent={ROW_INDENT}>
|
|
77
|
+
{truncate(item.kind === "create" ? "+ Create…" : "+ Connect…", room)}
|
|
43
78
|
</SelectableRow>
|
|
44
79
|
);
|
|
45
80
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
text={plural(entry.plugins.length, "plugin")}
|
|
55
|
-
selected={selected}
|
|
81
|
+
if (item.kind === "local-heading") {
|
|
82
|
+
return (
|
|
83
|
+
<ListCategoryRow
|
|
84
|
+
title="Here, in no marketplace"
|
|
85
|
+
count={item.count}
|
|
86
|
+
selected={false}
|
|
87
|
+
expandable={false}
|
|
88
|
+
width={width}
|
|
56
89
|
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (item.kind === "local") {
|
|
93
|
+
const scope = item.item.scope === "user" ? "USER" : "PROJECT";
|
|
94
|
+
const fit = fitRow<Part>(room, item.item.name, scope.length + 4, [
|
|
95
|
+
{ text: localKindWords(item.item), tone: "muted" },
|
|
96
|
+
]);
|
|
97
|
+
return (
|
|
98
|
+
<SelectableRow selected={selected} indent={ROW_INDENT}>
|
|
99
|
+
<strong>{fit.name}</strong>
|
|
100
|
+
{" "}
|
|
101
|
+
<ScopeBadge scope={scope} selected={selected} />
|
|
102
|
+
<Secondary parts={fit.secondary} selected={selected} />
|
|
103
|
+
</SelectableRow>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const { entry, toPush } = item;
|
|
107
|
+
const parts: Part[] = [
|
|
108
|
+
...(entry.company
|
|
109
|
+
? [{ text: `by ${entry.company}`, tone: "muted" as const }]
|
|
110
|
+
: []),
|
|
111
|
+
...(toPush.length > 0
|
|
112
|
+
? [
|
|
113
|
+
{
|
|
114
|
+
text: `● ${plural(toPush.length, "plugin")} to push`,
|
|
115
|
+
tone: "warning" as const,
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
: []),
|
|
119
|
+
...(entry.freshness === "stale"
|
|
120
|
+
? [{ text: "⚠ not current", tone: "warning" as const }]
|
|
121
|
+
: []),
|
|
122
|
+
{ text: plural(entry.plugins.length, "plugin"), tone: "muted" },
|
|
123
|
+
{ text: entry.repo, tone: "info" },
|
|
124
|
+
];
|
|
125
|
+
const fit = fitRow(room, entry.name, "USER".length + 4, parts);
|
|
126
|
+
return (
|
|
127
|
+
<SelectableRow selected={selected} indent={ROW_INDENT}>
|
|
128
|
+
<strong>{fit.name}</strong>
|
|
57
129
|
{" "}
|
|
58
130
|
<ScopeBadge scope="USER" selected={selected} />
|
|
59
|
-
{
|
|
60
|
-
<>
|
|
61
|
-
{" "}
|
|
62
|
-
<MetaText text="⚠ not current" tone="warning" selected={selected} />
|
|
63
|
-
</>
|
|
64
|
-
) : null}
|
|
131
|
+
<Secondary parts={fit.secondary} selected={selected} />
|
|
65
132
|
</SelectableRow>
|
|
66
133
|
);
|
|
67
134
|
}
|
|
@@ -77,8 +144,15 @@ export function MarketplacesScreen({
|
|
|
77
144
|
const modal = useModal();
|
|
78
145
|
const dimensions = useDimensions();
|
|
79
146
|
const value = asyncValue(market.data);
|
|
80
|
-
const items = marketplacesItems(
|
|
81
|
-
|
|
147
|
+
const items = marketplacesItems(
|
|
148
|
+
value?.marketplaces ?? [],
|
|
149
|
+
value?.status ?? [],
|
|
150
|
+
value?.local ?? [],
|
|
151
|
+
);
|
|
152
|
+
const clamped = Math.min(market.selected.marketplaces, items.length - 1);
|
|
153
|
+
const index = selectable(items[clamped])
|
|
154
|
+
? clamped
|
|
155
|
+
: nextSelectable(items, clamped, -1);
|
|
82
156
|
const item = items[index];
|
|
83
157
|
|
|
84
158
|
const select = (next: number) =>
|
|
@@ -92,6 +166,10 @@ export function MarketplacesScreen({
|
|
|
92
166
|
if (item?.kind === "marketplace") market.open(item.entry.name);
|
|
93
167
|
else if (item?.kind === "create") void market.create();
|
|
94
168
|
else if (item?.kind === "connect") void market.connect();
|
|
169
|
+
else if (item?.kind === "local") void market.addLocal(item.item);
|
|
170
|
+
},
|
|
171
|
+
add: () => {
|
|
172
|
+
if (item?.kind === "local") void market.addLocal(item.item);
|
|
95
173
|
},
|
|
96
174
|
create: () => void market.create(),
|
|
97
175
|
connect: () => void market.connect(),
|
|
@@ -99,7 +177,7 @@ export function MarketplacesScreen({
|
|
|
99
177
|
if (item?.kind === "marketplace") void market.remove(item.entry.name);
|
|
100
178
|
},
|
|
101
179
|
fetch: () => void market.fetch(),
|
|
102
|
-
move: (key) => select(index
|
|
180
|
+
move: (key) => select(nextSelectable(items, index, moveDirection(key))),
|
|
103
181
|
};
|
|
104
182
|
useKeyLayer("screen", true, (key) =>
|
|
105
183
|
runBindings(MARKETPLACES_KEYS, handlers, key),
|
|
@@ -122,13 +200,13 @@ export function MarketplacesScreen({
|
|
|
122
200
|
|
|
123
201
|
return (
|
|
124
202
|
<ScreenLayout
|
|
125
|
-
title="magus
|
|
203
|
+
title="magus edit"
|
|
126
204
|
subtitle={loading ? "reading…" : "company plugin marketplaces"}
|
|
127
205
|
keymap={{
|
|
128
206
|
bindings: [...MARKETPLACES_KEYS, ...MARKETPLACES_APP_KEYS],
|
|
129
207
|
pinned: [MARKET_HELP],
|
|
130
208
|
}}
|
|
131
|
-
listPanel={
|
|
209
|
+
listPanel={(width: number) =>
|
|
132
210
|
failed ? (
|
|
133
211
|
<ErrorNotice
|
|
134
212
|
title="Could not read the company marketplaces."
|
|
@@ -142,14 +220,21 @@ export function MarketplacesScreen({
|
|
|
142
220
|
count={count}
|
|
143
221
|
selected={false}
|
|
144
222
|
expandable={false}
|
|
223
|
+
width={width}
|
|
145
224
|
/>
|
|
146
225
|
<ScrollableList
|
|
147
226
|
items={items}
|
|
148
227
|
selectedIndex={index}
|
|
149
|
-
renderItem={
|
|
228
|
+
renderItem={(it, _i, selected) =>
|
|
229
|
+
renderMarketplacesRow(it, selected, width)
|
|
230
|
+
}
|
|
150
231
|
maxHeight={Math.max(1, dimensions.listPanelHeight - HEADING_ROWS)}
|
|
151
232
|
getKey={(it) =>
|
|
152
|
-
it.kind === "marketplace"
|
|
233
|
+
it.kind === "marketplace"
|
|
234
|
+
? `m:${it.entry.name}`
|
|
235
|
+
: it.kind === "local"
|
|
236
|
+
? `l:${it.item.scope}:${it.item.name}`
|
|
237
|
+
: it.kind
|
|
153
238
|
}
|
|
154
239
|
/>
|
|
155
240
|
</box>
|
|
@@ -158,6 +243,8 @@ export function MarketplacesScreen({
|
|
|
158
243
|
detailPanel={
|
|
159
244
|
<MarketplacesDetail
|
|
160
245
|
item={item}
|
|
246
|
+
canAdd={count > 0}
|
|
247
|
+
cwd={market.cwd}
|
|
161
248
|
note={
|
|
162
249
|
item?.kind === "marketplace"
|
|
163
250
|
? market.notes[item.entry.name]
|
|
@@ -166,7 +253,11 @@ export function MarketplacesScreen({
|
|
|
166
253
|
/>
|
|
167
254
|
}
|
|
168
255
|
detailKey={
|
|
169
|
-
item?.kind === "marketplace"
|
|
256
|
+
item?.kind === "marketplace"
|
|
257
|
+
? `m:${item.entry.name}`
|
|
258
|
+
: item?.kind === "local"
|
|
259
|
+
? `l:${item.item.scope}:${item.item.name}`
|
|
260
|
+
: item?.kind
|
|
170
261
|
}
|
|
171
262
|
/>
|
|
172
263
|
);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
DetailSection,
|
|
4
4
|
KeyValueLine,
|
|
5
5
|
ScopeVersionLine,
|
|
6
|
+
ToneLine,
|
|
6
7
|
} from "../components/primitives/index.js";
|
|
7
8
|
|
|
8
9
|
const SCOPE_WORDS = [
|
|
@@ -12,7 +13,8 @@ const SCOPE_WORDS = [
|
|
|
12
13
|
] as const;
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* The detail panel for a company plugin's row: its version, skills
|
|
16
|
+
* The detail panel for a company plugin's row: its version, skills, the
|
|
17
|
+
* skills edited here and its installs.
|
|
16
18
|
*
|
|
17
19
|
* A snowflake: the Marketplace screen's own panel, over the market adapter's
|
|
18
20
|
* row type and that screen's words. It is composed only from library
|
|
@@ -36,6 +38,15 @@ export function PluginDetail({
|
|
|
36
38
|
}
|
|
37
39
|
/>
|
|
38
40
|
</DetailSection>
|
|
41
|
+
{item.edited.length > 0 ? (
|
|
42
|
+
<DetailSection title="Edited here">
|
|
43
|
+
<ToneLine tone="warning">● {item.edited.join(", ")}</ToneLine>
|
|
44
|
+
<ToneLine>p push a new version</ToneLine>
|
|
45
|
+
<ToneLine tone="muted">
|
|
46
|
+
Every edited skill goes out together, as one new version.
|
|
47
|
+
</ToneLine>
|
|
48
|
+
</DetailSection>
|
|
49
|
+
) : null}
|
|
39
50
|
<DetailSection title="Installed">
|
|
40
51
|
{SCOPE_WORDS.map(([scope, word]) => (
|
|
41
52
|
<ScopeVersionLine
|
|
@@ -33,6 +33,13 @@ export const MARKETPLACES_KEYS = [
|
|
|
33
33
|
"Connect an existing repository as a company marketplace",
|
|
34
34
|
75,
|
|
35
35
|
),
|
|
36
|
+
bind(
|
|
37
|
+
"add",
|
|
38
|
+
["a"],
|
|
39
|
+
"add",
|
|
40
|
+
"Add the skill or plugin folder here to a company marketplace",
|
|
41
|
+
70,
|
|
42
|
+
),
|
|
36
43
|
bind(
|
|
37
44
|
"remove",
|
|
38
45
|
["d"],
|
|
@@ -59,7 +66,13 @@ export const MARKETPLACE_KEYS = [
|
|
|
59
66
|
"Show or hide the plugin's skills",
|
|
60
67
|
90,
|
|
61
68
|
),
|
|
62
|
-
bind(
|
|
69
|
+
bind(
|
|
70
|
+
"publish",
|
|
71
|
+
["p"],
|
|
72
|
+
"publish",
|
|
73
|
+
"Publish edits: the skill's, or on a plugin every edited skill as one new version",
|
|
74
|
+
88,
|
|
75
|
+
),
|
|
63
76
|
bind(
|
|
64
77
|
"add",
|
|
65
78
|
["a"],
|
|
@@ -134,6 +147,16 @@ export const MARKETPLACE_APP_KEYS = [
|
|
|
134
147
|
|
|
135
148
|
export type AppKeyId = (typeof MARKETPLACES_APP_KEYS)[number]["id"];
|
|
136
149
|
|
|
150
|
+
/** What the marks on screen 1 mean. */
|
|
151
|
+
const MARKETPLACES_LEGEND = [
|
|
152
|
+
{ symbol: "●", meaning: "plugins with skills edited here, ready to push" },
|
|
153
|
+
{
|
|
154
|
+
symbol: "USER",
|
|
155
|
+
meaning:
|
|
156
|
+
"under Here, in no marketplace: your USER skills; PROJECT is this project's",
|
|
157
|
+
},
|
|
158
|
+
] as const;
|
|
159
|
+
|
|
137
160
|
/** What the glyphs on screen 2 mean. */
|
|
138
161
|
const MARKETPLACE_LEGEND = [
|
|
139
162
|
{
|
|
@@ -141,7 +164,10 @@ const MARKETPLACE_LEGEND = [
|
|
|
141
164
|
meaning: "installed at that scope; the squares are user, project, local",
|
|
142
165
|
},
|
|
143
166
|
{ symbol: "□", meaning: "not installed at that scope" },
|
|
144
|
-
{
|
|
167
|
+
{
|
|
168
|
+
symbol: "●",
|
|
169
|
+
meaning: "edited here, ready to publish; on a plugin, how many skills",
|
|
170
|
+
},
|
|
145
171
|
{ symbol: "◇", meaning: "edited here, origin unknown; publish asks twice" },
|
|
146
172
|
{ symbol: "⚠", meaning: "changed here and on GitHub; publish asks twice" },
|
|
147
173
|
{ symbol: "↓", meaning: "the plugin has a newer version than this copy" },
|
|
@@ -155,6 +181,7 @@ export function marketHelpSections(
|
|
|
155
181
|
const one: HelpSection = {
|
|
156
182
|
title: "Company marketplaces",
|
|
157
183
|
bindings: MARKETPLACES_KEYS,
|
|
184
|
+
legend: MARKETPLACES_LEGEND,
|
|
158
185
|
};
|
|
159
186
|
const two: HelpSection = {
|
|
160
187
|
title: "A marketplace",
|