@north-light/crouter 0.3.184 → 0.3.186
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/dto/broker.d.ts +0 -32
- package/dist/api/dto/messages.d.ts +0 -5
- package/dist/build-root.js +10 -3
- package/dist/builtin-memory/internal/plugins.md +1 -1
- package/dist/clients/attach/input/capabilities.js +3 -0
- package/dist/clients/attach/input/controller.js +3 -4
- package/dist/clients/attach/overlays/mcp.js +1 -3
- package/dist/clients/attach/slash/dispatch.js +12 -2
- package/dist/clients/attach/viewer.js +349 -349
- package/dist/commands/memory/list.js +7 -3
- package/dist/commands/pkg/browse/actions.d.ts +7 -0
- package/dist/commands/pkg/browse/actions.js +199 -0
- package/dist/commands/pkg/browse/catalog.d.ts +9 -0
- package/dist/commands/pkg/browse/catalog.js +466 -0
- package/dist/commands/pkg/browse/command-view.d.ts +3 -0
- package/dist/commands/pkg/browse/command-view.js +191 -0
- package/dist/commands/pkg/browse/detail-panel.d.ts +66 -0
- package/dist/commands/pkg/browse/detail-panel.js +402 -0
- package/dist/commands/pkg/browse/doc-view.d.ts +3 -0
- package/dist/commands/pkg/browse/doc-view.js +81 -0
- package/dist/commands/pkg/browse/list-panel.d.ts +28 -0
- package/dist/commands/pkg/browse/list-panel.js +168 -0
- package/dist/commands/pkg/browse/model.d.ts +230 -0
- package/dist/commands/pkg/browse/model.js +36 -0
- package/dist/commands/pkg/browse/shell.d.ts +93 -0
- package/dist/commands/pkg/browse/shell.js +640 -0
- package/dist/commands/pkg/browse.d.ts +2 -0
- package/dist/commands/pkg/browse.js +27 -0
- package/dist/commands/pkg/market-manage.js +13 -4
- package/dist/commands/pkg/plugin-manage.js +8 -3
- package/dist/commands/pkg.js +2 -1
- package/dist/commands/sys/doctor.js +6 -4
- package/dist/commands/sys/panels/broker-limits-panel.d.ts +1 -1
- package/dist/commands/sys/panels/broker-limits-panel.js +1 -1
- package/dist/commands/sys/panels/dependencies-panel.d.ts +1 -1
- package/dist/commands/sys/panels/dependencies-panel.js +1 -1
- package/dist/commands/sys/panels/keybindings-panel.js +1 -1
- package/dist/commands/sys/panels/models-panel.d.ts +1 -1
- package/dist/commands/sys/panels/models-panel.js +1 -1
- package/dist/commands/sys/panels/plugins-panel.d.ts +1 -1
- package/dist/commands/sys/panels/plugins-panel.js +1 -1
- package/dist/commands/sys/panels/prefs-panel.d.ts +1 -1
- package/dist/commands/sys/panels/prefs-panel.js +1 -1
- package/dist/commands/sys/panels/provider-panel.d.ts +1 -1
- package/dist/commands/sys/panels/provider-panel.js +1 -1
- package/dist/commands/sys/settings-shell.js +1 -1
- package/dist/commands/sys/setup-settings-runner.d.ts +7 -18
- package/dist/commands/sys/setup-settings-runner.js +3 -52
- package/dist/commands/sys/setup-wizard.js +1 -1
- package/dist/core/__tests__/serial/command-plugins.test.js +21 -40
- package/dist/core/__tests__/session-model.test.js +2 -4
- package/dist/core/command-manifests/registry.d.ts +10 -3
- package/dist/core/command-manifests/registry.js +36 -22
- package/dist/core/command-plugins/compose.js +1 -1
- package/dist/core/command-plugins/discovery.js +15 -7
- package/dist/core/keybindings/catalog.d.ts +1 -1
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/resolver.d.ts +2 -2
- package/dist/core/runtime/broker/event-projection.d.ts +1 -8
- package/dist/core/runtime/broker/event-projection.js +1 -25
- package/dist/core/runtime/broker/frame-dispatch.d.ts +0 -2
- package/dist/core/runtime/broker/frame-dispatch.js +8 -41
- package/dist/core/runtime/broker-protocol.d.ts +1 -46
- package/dist/core/runtime/broker.js +1 -8
- package/dist/core/runtime/interactive-deliver.d.ts +2 -5
- package/dist/core/runtime/interactive-deliver.js +3 -6
- package/dist/core/runtime/tmux-bindings.js +2 -0
- package/dist/core/session-model/session-state.d.ts +3 -6
- package/dist/core/session-model/session-state.js +1 -5
- package/dist/core/tui/page-host.d.ts +25 -0
- package/dist/core/tui/page-host.js +65 -0
- package/dist/daemon/api/handlers/messages.js +1 -7
- package/dist/pi-extensions/canvas-bash-valve.js +45 -1
- package/dist/types.d.ts +4 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/core/runtime/broker/message-ledger.d.ts +0 -53
- package/dist/core/runtime/broker/message-ledger.js +0 -143
- /package/dist/{commands/sys/panels → core/tui}/panel.d.ts +0 -0
- /package/dist/{commands/sys/panels → core/tui}/panel.js +0 -0
|
@@ -22,9 +22,13 @@ export function listMemoryDocs(kindFilter, scopeFilter, quiet = false, docs = li
|
|
|
22
22
|
if (kindFilter !== undefined && sub.kind !== kindFilter)
|
|
23
23
|
continue;
|
|
24
24
|
// A directory INDEX surfaces as a dir entry: its dir path with a trailing
|
|
25
|
-
// slash, flagged is_dir, so the inventory distinguishes it from a doc.
|
|
25
|
+
// slash, flagged is_dir, so the inventory distinguishes it from a doc. A
|
|
26
|
+
// store-root INDEX has no dir path, so it keeps its canonical `INDEX`
|
|
27
|
+
// name — every name in this column must be one `crtr memory read` and a
|
|
28
|
+
// `[[link]]` accept, and a bare `/` is neither.
|
|
26
29
|
const isDir = isIndexName(sub.name);
|
|
27
|
-
const
|
|
30
|
+
const dirPath = isDir ? indexDirOf(sub.name) : '';
|
|
31
|
+
const name = dirPath === '' ? sub.name : dirPath + '/';
|
|
28
32
|
addItem({ name, kind: sub.kind, scope: sub.scope, path: sub.path, shortForm: sub.shortForm, isDir, slash: sub.slash });
|
|
29
33
|
}
|
|
30
34
|
items.sort((a, b) => {
|
|
@@ -53,7 +57,7 @@ export const listLeaf = defineLeaf({
|
|
|
53
57
|
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: 'Opaque token from next_cursor. Omit on first call.' },
|
|
54
58
|
],
|
|
55
59
|
output: [
|
|
56
|
-
{ name: 'items', type: 'object[]', required: true, constraint: 'One row per document: {name, short_form, kind, scope, is_dir}. path is included only with --paths. A directory INDEX.md surfaces as a dir entry (is_dir true, name = the directory path with a trailing slash); is_dir is false for an ordinary doc. short_form is the abbreviated hook — shown here and nowhere else. Sorted by scope then kind then name ascending.' },
|
|
60
|
+
{ name: 'items', type: 'object[]', required: true, constraint: 'One row per document: {name, short_form, kind, scope, is_dir}. path is included only with --paths. A directory INDEX.md surfaces as a dir entry (is_dir true, name = the directory path with a trailing slash, or `INDEX` for a store-root INDEX that has no directory path); is_dir is false for an ordinary doc. Every name is readable as-is with `crtr memory read`. short_form is the abbreviated hook — shown here and nowhere else. Sorted by scope then kind then name ascending.' },
|
|
57
61
|
{ name: 'next_cursor', type: 'string | null', required: true, constraint: 'Opaque token for the next page; null means no more items.' },
|
|
58
62
|
{ name: 'total', type: 'integer | null', required: true, constraint: 'Exact total across all pages when cheap to compute (always cheap here); never null in practice for this leaf.' },
|
|
59
63
|
{ name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands — read a document in full or narrow the inventory.' },
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ActionPresentation, ActionRequest, ActionOutcome } from "./model.js";
|
|
2
|
+
/** How an action reads before it runs — and whether it needs confirming. */
|
|
3
|
+
export declare function presentAction(request: ActionRequest): ActionPresentation;
|
|
4
|
+
/** Actions available for a request's subject right now, in offer order. */
|
|
5
|
+
export declare function availableActions(request: Pick<ActionRequest, "entry" | "marketplace" | "scope">): ActionRequest[];
|
|
6
|
+
/** Run one action. Never throws: a failure comes back as `ok: false`. */
|
|
7
|
+
export declare function runAction(request: ActionRequest): Promise<ActionOutcome>;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Mutations for `crtr pkg browse`. Each action delegates to the same leaf the
|
|
2
|
+
// CLI runs, so the browser and `crtr pkg …` can never drift.
|
|
3
|
+
import { marketAdd, marketRemove, marketUpdate } from "../market-manage.js";
|
|
4
|
+
import { pluginDisable, pluginEnable, pluginInstall, pluginRemove, pluginUpdate, } from "../plugin-manage.js";
|
|
5
|
+
import { listAllMarketplaces } from "../../../core/resolver.js";
|
|
6
|
+
import { projectScopeRoot } from "../../../core/scope.js";
|
|
7
|
+
function pluginName(request) {
|
|
8
|
+
return request.entry?.name ?? "plugin";
|
|
9
|
+
}
|
|
10
|
+
function marketplaceName(request) {
|
|
11
|
+
return request.marketplace ?? "marketplace";
|
|
12
|
+
}
|
|
13
|
+
/** How an action reads before it runs — and whether it needs confirming. */
|
|
14
|
+
export function presentAction(request) {
|
|
15
|
+
switch (request.kind) {
|
|
16
|
+
case "install": {
|
|
17
|
+
const scope = request.scope ?? "user";
|
|
18
|
+
return { label: `Install into ${scope} scope`, progress: `Installing ${pluginName(request)}…` };
|
|
19
|
+
}
|
|
20
|
+
case "remove":
|
|
21
|
+
return {
|
|
22
|
+
label: `Remove ${pluginName(request)}`,
|
|
23
|
+
progress: `Removing ${pluginName(request)}…`,
|
|
24
|
+
confirm: `Remove ${pluginName(request)} and delete its files?`,
|
|
25
|
+
};
|
|
26
|
+
case "enable":
|
|
27
|
+
return { label: `Enable ${pluginName(request)}`, progress: `Enabling ${pluginName(request)}…` };
|
|
28
|
+
case "disable":
|
|
29
|
+
return { label: `Disable ${pluginName(request)}`, progress: `Disabling ${pluginName(request)}…` };
|
|
30
|
+
case "update":
|
|
31
|
+
return { label: `Update ${pluginName(request)}`, progress: `Updating ${pluginName(request)}…` };
|
|
32
|
+
case "market-add":
|
|
33
|
+
return {
|
|
34
|
+
label: "Add marketplace",
|
|
35
|
+
progress: `Adding ${request.url ?? "marketplace"}…`,
|
|
36
|
+
};
|
|
37
|
+
case "market-update":
|
|
38
|
+
return { label: `Update ${marketplaceName(request)}`, progress: `Updating ${marketplaceName(request)}…` };
|
|
39
|
+
case "market-remove":
|
|
40
|
+
return {
|
|
41
|
+
label: `Remove ${marketplaceName(request)}`,
|
|
42
|
+
progress: `Removing ${marketplaceName(request)}…`,
|
|
43
|
+
confirm: `Remove ${marketplaceName(request)} and its sourced plugins?`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return { label: "Run action", progress: "Running action…" };
|
|
47
|
+
}
|
|
48
|
+
/** Actions available for a request's subject right now, in offer order. */
|
|
49
|
+
export function availableActions(request) {
|
|
50
|
+
if (request.entry !== undefined) {
|
|
51
|
+
const { entry } = request;
|
|
52
|
+
if (entry.install.kind === "absent") {
|
|
53
|
+
return [
|
|
54
|
+
{ kind: "install", entry, scope: "user" },
|
|
55
|
+
...(projectScopeRoot() === null ? [] : [{ kind: "install", entry, scope: "project" }]),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
const actions = [
|
|
59
|
+
{ kind: entry.install.enabled ? "disable" : "enable", entry },
|
|
60
|
+
];
|
|
61
|
+
if (entry.install.scope !== "builtin")
|
|
62
|
+
actions.push({ kind: "remove", entry });
|
|
63
|
+
if (entry.install.updateAvailable)
|
|
64
|
+
actions.push({ kind: "update", entry });
|
|
65
|
+
return actions;
|
|
66
|
+
}
|
|
67
|
+
if (request.marketplace !== undefined) {
|
|
68
|
+
const matching = marketplacesNamed(request.marketplace);
|
|
69
|
+
const scope = matching.length === 1 ? matching[0].scope : request.scope;
|
|
70
|
+
return [
|
|
71
|
+
...(scope === undefined ? [] : [{ kind: "market-update", marketplace: request.marketplace, scope }]),
|
|
72
|
+
...(scope === undefined ? [] : [{ kind: "market-remove", marketplace: request.marketplace, scope }]),
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
function marketplacesNamed(name) {
|
|
78
|
+
try {
|
|
79
|
+
return listAllMarketplaces().filter((marketplace) => marketplace.name === name);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function asRecord(value) {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
87
|
+
}
|
|
88
|
+
function strings(value) {
|
|
89
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
90
|
+
}
|
|
91
|
+
function commandDetail(value) {
|
|
92
|
+
const commands = asRecord(value);
|
|
93
|
+
const mounts = strings(commands["mounts"]);
|
|
94
|
+
const issues = Array.isArray(commands["issues"]) ? commands["issues"] : [];
|
|
95
|
+
return [
|
|
96
|
+
...mounts.map((mount) => `Mounted: crtr ${mount}`),
|
|
97
|
+
...issues.map((issue) => {
|
|
98
|
+
const detail = asRecord(issue);
|
|
99
|
+
const message = detail["message"];
|
|
100
|
+
return `Command issue: ${typeof message === "string" ? message : "invalid command manifest"}`;
|
|
101
|
+
}),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
function updateDetail(value, noun) {
|
|
105
|
+
const updated = Array.isArray(asRecord(value)["updated"]) ? asRecord(value)["updated"] : [];
|
|
106
|
+
return updated.flatMap((item) => {
|
|
107
|
+
const result = asRecord(item);
|
|
108
|
+
const name = typeof result["name"] === "string" ? result["name"] : noun.toLowerCase();
|
|
109
|
+
const sha = typeof result["sha"] === "string" && result["sha"] !== "" ? ` at ${result["sha"].slice(0, 12)}` : "";
|
|
110
|
+
const state = result["updated"] === true ? `${noun} ${name} updated${sha}.` : `${noun} ${name} is already current${sha}.`;
|
|
111
|
+
return [state, ...commandDetail(result["commands"]), ...(typeof result["error"] === "string" ? [`${noun} ${name}: ${result["error"]}`] : [])];
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function missing(message) {
|
|
115
|
+
return { ok: false, message };
|
|
116
|
+
}
|
|
117
|
+
/** Run one action. Never throws: a failure comes back as `ok: false`. */
|
|
118
|
+
export async function runAction(request) {
|
|
119
|
+
try {
|
|
120
|
+
switch (request.kind) {
|
|
121
|
+
case "install": {
|
|
122
|
+
if (request.entry === undefined)
|
|
123
|
+
return missing("Choose a plugin to install.");
|
|
124
|
+
if (request.scope === undefined)
|
|
125
|
+
return missing("Choose an install scope.");
|
|
126
|
+
if (request.entry.marketplace !== undefined && marketplacesNamed(request.entry.marketplace).length > 1) {
|
|
127
|
+
return missing(`Cannot safely install ${request.entry.name}: marketplace "${request.entry.marketplace}" exists in more than one scope.`);
|
|
128
|
+
}
|
|
129
|
+
const result = await pluginInstall.run({ installRef: request.entry.ref, scope: request.scope });
|
|
130
|
+
const installed = asRecord(result);
|
|
131
|
+
const name = typeof installed["name"] === "string" ? installed["name"] : request.entry.name;
|
|
132
|
+
const scope = typeof installed["scope"] === "string" ? installed["scope"] : request.scope;
|
|
133
|
+
return { ok: true, message: `Installed ${name} in ${scope} scope.`, detail: commandDetail(installed["commands"]) };
|
|
134
|
+
}
|
|
135
|
+
case "remove": {
|
|
136
|
+
if (request.entry === undefined || request.entry.install.kind !== "installed")
|
|
137
|
+
return missing("Choose an installed plugin to remove.");
|
|
138
|
+
const result = asRecord(await pluginRemove.run({ name: request.entry.name, scope: request.entry.install.scope }));
|
|
139
|
+
const scopes = strings(result["scopes"]);
|
|
140
|
+
return { ok: true, message: `Removed ${request.entry.name}${scopes.length > 0 ? ` from ${scopes.join(", ")} scope${scopes.length === 1 ? "" : "s"}` : ""}.` };
|
|
141
|
+
}
|
|
142
|
+
case "enable": {
|
|
143
|
+
if (request.entry === undefined || request.entry.install.kind !== "installed")
|
|
144
|
+
return missing("Choose an installed plugin to enable.");
|
|
145
|
+
const result = asRecord(await pluginEnable.run({ name: request.entry.name, scope: request.entry.install.scope }));
|
|
146
|
+
const scope = typeof result["scope"] === "string" ? result["scope"] : request.entry.install.scope;
|
|
147
|
+
return { ok: true, message: `Enabled ${request.entry.name} in ${scope} scope.` };
|
|
148
|
+
}
|
|
149
|
+
case "disable": {
|
|
150
|
+
if (request.entry === undefined || request.entry.install.kind !== "installed")
|
|
151
|
+
return missing("Choose an installed plugin to disable.");
|
|
152
|
+
const result = asRecord(await pluginDisable.run({ name: request.entry.name, scope: request.entry.install.scope }));
|
|
153
|
+
const scope = typeof result["scope"] === "string" ? result["scope"] : request.entry.install.scope;
|
|
154
|
+
return { ok: true, message: `Disabled ${request.entry.name} in ${scope} scope.` };
|
|
155
|
+
}
|
|
156
|
+
case "update": {
|
|
157
|
+
if (request.entry === undefined || request.entry.install.kind !== "installed")
|
|
158
|
+
return missing("Choose an installed plugin to update.");
|
|
159
|
+
const result = await pluginUpdate.run({ name: request.entry.name, scope: request.entry.install.scope });
|
|
160
|
+
const detail = updateDetail(result, "Plugin");
|
|
161
|
+
return { ok: true, message: detail[0] ?? `Updated ${request.entry.name}.`, detail };
|
|
162
|
+
}
|
|
163
|
+
case "market-add": {
|
|
164
|
+
if (request.url === undefined || request.url === "")
|
|
165
|
+
return missing("Enter a marketplace git URL.");
|
|
166
|
+
const result = asRecord(await marketAdd.run({
|
|
167
|
+
url: request.url,
|
|
168
|
+
...(request.ref !== undefined ? { ref: request.ref } : {}),
|
|
169
|
+
...(request.scope !== undefined ? { scope: request.scope } : {}),
|
|
170
|
+
}));
|
|
171
|
+
const name = typeof result["name"] === "string" ? result["name"] : request.url;
|
|
172
|
+
const scope = typeof result["scope"] === "string" ? result["scope"] : request.scope;
|
|
173
|
+
return { ok: true, message: `Added marketplace ${name}${scope === undefined ? "" : ` in ${scope} scope`}.` };
|
|
174
|
+
}
|
|
175
|
+
case "market-update": {
|
|
176
|
+
if (request.marketplace === undefined)
|
|
177
|
+
return missing("Choose a marketplace to update.");
|
|
178
|
+
if (request.scope === undefined)
|
|
179
|
+
return missing("Choose a marketplace scope to update.");
|
|
180
|
+
const result = await marketUpdate.run({ name: request.marketplace, scope: request.scope });
|
|
181
|
+
const detail = updateDetail(result, "Marketplace");
|
|
182
|
+
return { ok: true, message: detail[0] ?? `Updated ${request.marketplace}.`, detail };
|
|
183
|
+
}
|
|
184
|
+
case "market-remove": {
|
|
185
|
+
if (request.marketplace === undefined)
|
|
186
|
+
return missing("Choose a marketplace to remove.");
|
|
187
|
+
if (request.scope === undefined)
|
|
188
|
+
return missing("Choose a marketplace scope to remove.");
|
|
189
|
+
const result = asRecord(await marketRemove.run({ name: request.marketplace, scope: request.scope }));
|
|
190
|
+
const name = typeof result["name"] === "string" ? result["name"] : request.marketplace;
|
|
191
|
+
return { ok: true, message: `Removed marketplace ${name}.` };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return missing("Unsupported action.");
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Catalog, type PluginCapability, type PluginEntry } from "./model.js";
|
|
2
|
+
/** Every rail source with its rows: the installed library first, then each
|
|
3
|
+
* registered marketplace. Called on open and after any action that could
|
|
4
|
+
* change install state. Must never throw for a broken marketplace — record it
|
|
5
|
+
* in `warnings` and keep the rest of the catalog. */
|
|
6
|
+
export declare function loadCatalog(): Catalog;
|
|
7
|
+
/** What the focused plugin adds to this machine. Resolved from `entry.root`;
|
|
8
|
+
* null when the plugin has no directory on disk yet. */
|
|
9
|
+
export declare function loadCapability(entry: PluginEntry): Promise<PluginCapability | null>;
|