@north-light/crouter 0.3.180 → 0.3.182
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 +10 -1
- package/dist/api/client.js +13 -0
- package/dist/api/dto/broker.d.ts +32 -0
- package/dist/api/dto/crons.d.ts +17 -0
- package/dist/api/dto/memory.d.ts +17 -0
- package/dist/api/dto/memory.js +6 -0
- package/dist/api/dto/messages.d.ts +5 -0
- package/dist/api/dto/reviews.d.ts +8 -4
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +4 -0
- package/dist/build-root.d.ts +7 -0
- package/dist/build-root.js +21 -0
- package/dist/builtin-memory/insights/init.md +48 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/__tests__/insights-active-init.test.ts +98 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +7 -50
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +16 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +34 -0
- package/dist/cli.js +1 -2
- package/dist/clients/attach/__tests__/context-message.test.js +5 -2
- package/dist/clients/attach/assets/README.md +7 -0
- package/dist/clients/attach/assets/whip-06.mp3 +0 -0
- package/dist/clients/attach/assets/whip-crack.mp3 +0 -0
- package/dist/clients/attach/assets/whip-snap.mp3 +0 -0
- package/dist/clients/attach/chrome/canvas-panels.d.ts +7 -1
- package/dist/clients/attach/chrome/canvas-panels.js +20 -3
- package/dist/clients/attach/chrome/review-wait.d.ts +6 -0
- package/dist/clients/attach/chrome/review-wait.js +22 -0
- package/dist/clients/attach/chrome/roster.js +23 -2
- package/dist/clients/attach/chrome/widgets.js +1 -1
- package/dist/clients/attach/input/controller.js +4 -3
- package/dist/clients/attach/overlays/mcp.js +3 -1
- package/dist/clients/attach/render/chat-view.js +1 -1
- package/dist/clients/attach/session/whip.d.ts +1 -0
- package/dist/clients/attach/session/whip.js +26 -0
- package/dist/clients/attach/slash/dispatch.js +2 -0
- package/dist/clients/attach/viewer.js +578 -573
- package/dist/clients/inbox/review/document-surface.d.ts +1 -1
- package/dist/clients/inbox/review/document-surface.js +4 -4
- package/dist/clients/inbox/review/launch.js +16 -4
- package/dist/clients/inbox/review/review-client.d.ts +9 -4
- package/dist/clients/inbox/review/review-client.js +3 -0
- package/dist/commands/cron.js +30 -8
- package/dist/commands/human/prompts.d.ts +7 -2
- package/dist/commands/human/prompts.js +15 -10
- package/dist/commands/human.js +1 -2
- package/dist/commands/memory/find.js +11 -8
- package/dist/commands/memory/read.js +111 -11
- package/dist/commands/memory/write.js +1 -1
- package/dist/commands/memory.js +1 -1
- package/dist/commands/pkg/market-manage.d.ts +13 -0
- package/dist/commands/pkg/market-manage.js +39 -33
- package/dist/commands/pkg/plugin-inspect.js +4 -3
- package/dist/commands/pkg/plugin-manage.js +12 -11
- package/dist/commands/surface/node/focus.js +1 -2
- package/dist/commands/sys/doctor.js +4 -4
- package/dist/commands/sys/setup-core.d.ts +14 -7
- package/dist/commands/sys/setup-core.js +66 -11
- package/dist/commands/sys/setup-wizard.js +2 -2
- package/dist/commands/sys/setup.js +1 -1
- package/dist/core/__tests__/cron-held-settlement.test.d.ts +1 -0
- package/dist/core/__tests__/cron-held-settlement.test.js +222 -0
- package/dist/core/__tests__/helpers/harness.js +1 -2
- package/dist/core/__tests__/phase4-review-store.test.js +1 -0
- package/dist/core/__tests__/serial/command-plugins.test.js +88 -1
- package/dist/core/__tests__/session-model.test.js +5 -3
- package/dist/core/bootstrap.d.ts +0 -4
- package/dist/core/bootstrap.js +1 -55
- package/dist/core/canvas/crons.d.ts +54 -2
- package/dist/core/canvas/crons.js +48 -4
- package/dist/core/canvas/db.js +23 -0
- package/dist/core/command-manifests/manifest.d.ts +11 -0
- package/dist/core/command-manifests/manifest.js +45 -4
- package/dist/core/command-manifests/schema.d.ts +1 -1
- package/dist/core/command-plugins/bundle.d.ts +1 -0
- package/dist/core/command-plugins/bundle.js +3 -3
- package/dist/core/command-plugins/discovery.d.ts +5 -2
- package/dist/core/command-plugins/discovery.js +5 -5
- package/dist/core/command-plugins/help-addenda.d.ts +12 -0
- package/dist/core/command-plugins/help-addenda.js +30 -0
- package/dist/core/command.js +25 -2
- package/dist/core/config.js +0 -1
- package/dist/core/human/convention.d.ts +0 -1
- package/dist/core/human/convention.js +0 -6
- package/dist/core/keybindings/inbox.d.ts +6 -8
- package/dist/core/keybindings/inbox.js +6 -15
- package/dist/core/keybindings/index.d.ts +1 -1
- package/dist/core/keybindings/index.js +1 -1
- package/dist/core/memory/doc-link-grammar.js +4 -1
- package/dist/core/memory-resolver.d.ts +28 -4
- package/dist/core/memory-resolver.js +51 -39
- package/dist/core/review/stage.js +1 -0
- package/dist/core/review/store.d.ts +5 -0
- package/dist/core/review/store.js +10 -0
- package/dist/core/review/types.d.ts +4 -0
- package/dist/core/runtime/broker/event-projection.d.ts +8 -1
- package/dist/core/runtime/broker/event-projection.js +25 -1
- package/dist/core/runtime/broker/frame-dispatch.d.ts +2 -0
- package/dist/core/runtime/broker/frame-dispatch.js +50 -8
- package/dist/core/runtime/broker/inbox.d.ts +4 -0
- package/dist/core/runtime/broker/inbox.js +17 -8
- package/dist/core/runtime/broker/message-ledger.d.ts +53 -0
- package/dist/core/runtime/broker/message-ledger.js +143 -0
- package/dist/core/runtime/broker/rebind.js +14 -0
- package/dist/core/runtime/broker-protocol.d.ts +46 -1
- package/dist/core/runtime/broker.js +11 -2
- package/dist/core/runtime/interactive-deliver.d.ts +5 -2
- package/dist/core/runtime/interactive-deliver.js +6 -3
- package/dist/core/runtime/shell-expansion.d.ts +32 -0
- package/dist/core/runtime/shell-expansion.js +102 -0
- package/dist/core/session-model/session-state.d.ts +9 -4
- package/dist/core/session-model/session-state.js +5 -1
- package/dist/daemon/api/handlers/broker-ops.js +8 -0
- package/dist/daemon/api/handlers/crons.js +14 -1
- package/dist/daemon/api/handlers/inbox.js +5 -0
- package/dist/daemon/api/handlers/memory.d.ts +2 -0
- package/dist/daemon/api/handlers/memory.js +48 -0
- package/dist/daemon/api/handlers/messages.js +7 -1
- package/dist/daemon/api/handlers/reviews.js +7 -5
- package/dist/daemon/api/map.js +3 -0
- package/dist/daemon/api/server.js +2 -0
- package/dist/daemon/cron-run.js +71 -3
- package/dist/daemon/crtrd.js +3 -0
- package/dist/daemon/reconcilers/pending-review-submit.d.ts +7 -0
- package/dist/daemon/reconcilers/pending-review-submit.js +35 -0
- package/dist/daemon/review/companion.d.ts +8 -0
- package/dist/daemon/review/companion.js +35 -0
- package/dist/daemon/review/deliver.js +2 -1
- package/dist/daemon/review/finish.d.ts +29 -2
- package/dist/daemon/review/finish.js +75 -2
- package/dist/pi-extensions/canvas-inbox-watcher.js +34 -1
- package/dist/shared/generated-context.d.ts +3 -4
- package/dist/shared/generated-context.js +24 -6
- package/dist/types.d.ts +0 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -9,6 +9,44 @@ import { pathExists, ensureDir, removePath, isSymlink, nowIso } from '../../core
|
|
|
9
9
|
import { clone, pull, deriveNameFromUrl, currentSha, isGitRepo } from '../../core/git.js';
|
|
10
10
|
import { readMarketplaceManifest } from '../../core/manifest.js';
|
|
11
11
|
import { resolveInstallScope } from './shared.js';
|
|
12
|
+
/** Clone a marketplace repo into `scope` and register it in that scope's
|
|
13
|
+
* config. Shared by the `pkg market add` leaf and `crtr sys setup`, which
|
|
14
|
+
* offers the official marketplace as a checked-by-default entry. */
|
|
15
|
+
export function addMarketplace(opts) {
|
|
16
|
+
const { url, ref, scope } = opts;
|
|
17
|
+
const root = requireScopeRoot(scope);
|
|
18
|
+
ensureScopeInitialized(scope, root);
|
|
19
|
+
const tempName = deriveNameFromUrl(url);
|
|
20
|
+
const mktsDir = join(root, 'marketplaces');
|
|
21
|
+
ensureDir(mktsDir);
|
|
22
|
+
const tempDest = join(mktsDir, tempName);
|
|
23
|
+
if (pathExists(tempDest)) {
|
|
24
|
+
removePath(tempDest);
|
|
25
|
+
}
|
|
26
|
+
clone(url, tempDest, { depth: 1, ref: ref !== undefined ? ref : undefined });
|
|
27
|
+
const manifest = readMarketplaceManifest(tempDest);
|
|
28
|
+
if (!manifest) {
|
|
29
|
+
removePath(tempDest);
|
|
30
|
+
throw notFound(`marketplace manifest not found at ${tempDest}/.crouter-marketplace/marketplace.json — not a valid marketplace`);
|
|
31
|
+
}
|
|
32
|
+
const finalName = manifest.name;
|
|
33
|
+
const finalDest = join(mktsDir, finalName);
|
|
34
|
+
if (finalName !== tempName) {
|
|
35
|
+
if (pathExists(finalDest)) {
|
|
36
|
+
removePath(finalDest);
|
|
37
|
+
}
|
|
38
|
+
renameSync(tempDest, finalDest);
|
|
39
|
+
}
|
|
40
|
+
const effectiveRef = ref !== undefined ? ref : 'main';
|
|
41
|
+
updateConfig(scope, (cfg) => {
|
|
42
|
+
cfg.marketplaces[finalName] = {
|
|
43
|
+
url,
|
|
44
|
+
ref: effectiveRef,
|
|
45
|
+
installed_at: nowIso(),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
return { name: finalName, scope, path: finalDest };
|
|
49
|
+
}
|
|
12
50
|
export const marketAdd = defineLeaf({
|
|
13
51
|
name: 'add',
|
|
14
52
|
description: 'add a marketplace by git URL',
|
|
@@ -33,39 +71,7 @@ export const marketAdd = defineLeaf({
|
|
|
33
71
|
const url = input['url'];
|
|
34
72
|
const ref = input['ref'];
|
|
35
73
|
const scopeInput = input['scope'];
|
|
36
|
-
|
|
37
|
-
const root = requireScopeRoot(scope);
|
|
38
|
-
ensureScopeInitialized(scope, root);
|
|
39
|
-
const tempName = deriveNameFromUrl(url);
|
|
40
|
-
const mktsDir = join(root, 'marketplaces');
|
|
41
|
-
ensureDir(mktsDir);
|
|
42
|
-
const tempDest = join(mktsDir, tempName);
|
|
43
|
-
if (pathExists(tempDest)) {
|
|
44
|
-
removePath(tempDest);
|
|
45
|
-
}
|
|
46
|
-
clone(url, tempDest, { depth: 1, ref: ref !== undefined ? ref : undefined });
|
|
47
|
-
const manifest = readMarketplaceManifest(tempDest);
|
|
48
|
-
if (!manifest) {
|
|
49
|
-
removePath(tempDest);
|
|
50
|
-
throw notFound(`marketplace manifest not found at ${tempDest}/.crouter-marketplace/marketplace.json — not a valid marketplace`);
|
|
51
|
-
}
|
|
52
|
-
const finalName = manifest.name;
|
|
53
|
-
const finalDest = join(mktsDir, finalName);
|
|
54
|
-
if (finalName !== tempName) {
|
|
55
|
-
if (pathExists(finalDest)) {
|
|
56
|
-
removePath(finalDest);
|
|
57
|
-
}
|
|
58
|
-
renameSync(tempDest, finalDest);
|
|
59
|
-
}
|
|
60
|
-
const effectiveRef = ref !== undefined ? ref : 'main';
|
|
61
|
-
updateConfig(scope, (cfg) => {
|
|
62
|
-
cfg.marketplaces[finalName] = {
|
|
63
|
-
url,
|
|
64
|
-
ref: effectiveRef,
|
|
65
|
-
installed_at: nowIso(),
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
return { name: finalName, scope, path: finalDest };
|
|
74
|
+
return addMarketplace({ url, ref, scope: resolveInstallScope(scopeInput) });
|
|
69
75
|
},
|
|
70
76
|
});
|
|
71
77
|
export const marketRemove = defineLeaf({
|
|
@@ -4,7 +4,7 @@ import { paginate } from '../../core/pagination.js';
|
|
|
4
4
|
import { listInstalledPlugins, findPluginByName, } from '../../core/resolver.js';
|
|
5
5
|
import { listPluginMemoryDocs } from '../../core/memory-resolver.js';
|
|
6
6
|
import { validatePluginCommands, validateEffectiveCommandPlugins } from '../../core/command-plugins/discovery.js';
|
|
7
|
-
import { SUBTREE_NAMES } from '../../build-root.js';
|
|
7
|
+
import { SUBTREE_NAMES, coreCommandPaths } from '../../build-root.js';
|
|
8
8
|
import { resolveScopeArg, projectScopeRoot } from '../../core/scope.js';
|
|
9
9
|
export const pluginList = defineLeaf({
|
|
10
10
|
name: 'list',
|
|
@@ -120,13 +120,14 @@ export const pluginShow = defineLeaf({
|
|
|
120
120
|
let commands;
|
|
121
121
|
if (typeof found.manifest.commands === 'string' && found.manifest.commands.length > 0) {
|
|
122
122
|
const reserved = new Set(SUBTREE_NAMES);
|
|
123
|
+
const corePaths = await coreCommandPaths();
|
|
123
124
|
// Validate against the whole effective set so cross-plugin top-level
|
|
124
125
|
// name collisions show up in this plugin's issues (a collided command is
|
|
125
126
|
// NOT mounted). A copy outside the effective set (disabled, or shadowed
|
|
126
127
|
// by a higher-precedence copy) cannot collide, so the per-plugin
|
|
127
128
|
// validator is exact for it.
|
|
128
|
-
const v = validateEffectiveCommandPlugins(reserved).find((x) => x.plugin.root === found.root) ??
|
|
129
|
-
validatePluginCommands(found, reserved);
|
|
129
|
+
const v = validateEffectiveCommandPlugins(reserved, undefined, undefined, corePaths).find((x) => x.plugin.root === found.root) ??
|
|
130
|
+
validatePluginCommands(found, reserved, corePaths);
|
|
130
131
|
commands = {
|
|
131
132
|
manifestPath: v.manifestPath,
|
|
132
133
|
mounts: v.contributions.map((c) => c.node.name),
|
|
@@ -13,22 +13,23 @@ import { validatePluginCommands, validateEffectiveCommandPlugins } from '../../c
|
|
|
13
13
|
import { normalizeEndpoint, validateEndpoint, validateHttpPluginTransport } from '../../core/command-plugins/endpoint.js';
|
|
14
14
|
import { validatePluginBundle } from '../../core/command-plugins/bundle.js';
|
|
15
15
|
import { fetchHttpPluginBundle } from '../../core/command-plugins/transport/http-fetch.js';
|
|
16
|
-
import { SUBTREE_NAMES } from '../../build-root.js';
|
|
16
|
+
import { SUBTREE_NAMES, coreCommandPaths } from '../../build-root.js';
|
|
17
17
|
import { isGitUrl, setPluginEnabled, resolveInstallScope } from './shared.js';
|
|
18
18
|
/** Validate a just-installed/updated plugin's command manifest, if it declares
|
|
19
19
|
* one. Returns undefined for plugins without a `commands` pointer. Never
|
|
20
20
|
* executes the plugin binary — validation is fully static. */
|
|
21
|
-
function commandReport(name, scope) {
|
|
21
|
+
async function commandReport(name, scope) {
|
|
22
22
|
const plugin = findPluginByName(name, scope);
|
|
23
23
|
if (plugin === null || typeof plugin.manifest.commands !== 'string' || plugin.manifest.commands.length === 0) {
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
26
|
const reserved = new Set(SUBTREE_NAMES);
|
|
27
|
+
const corePaths = await coreCommandPaths();
|
|
27
28
|
// Report from the effective set so a cross-plugin top-level collision is
|
|
28
29
|
// visible at install time (a collided command is NOT mounted). A copy
|
|
29
30
|
// outside the effective set (disabled, or shadowed) cannot collide.
|
|
30
|
-
const v = validateEffectiveCommandPlugins(reserved).find((x) => x.plugin.root === plugin.root) ??
|
|
31
|
-
validatePluginCommands(plugin, reserved);
|
|
31
|
+
const v = validateEffectiveCommandPlugins(reserved, undefined, undefined, corePaths).find((x) => x.plugin.root === plugin.root) ??
|
|
32
|
+
validatePluginCommands(plugin, reserved, corePaths);
|
|
32
33
|
return { mounts: v.contributions.map((c) => c.node.name), issues: v.issues };
|
|
33
34
|
}
|
|
34
35
|
const PLUGIN_NAME_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -65,9 +66,9 @@ function writeStagedBundle(stagingRoot, manifest, bundle) {
|
|
|
65
66
|
writeFileSync(target, document.bytes);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
|
-
function validateStagedBundle(name, scope, root, manifest) {
|
|
69
|
+
async function validateStagedBundle(name, scope, root, manifest) {
|
|
69
70
|
const staged = { name, scope, root, manifest, enabled: true };
|
|
70
|
-
const validation = validatePluginCommands(staged, new Set(SUBTREE_NAMES));
|
|
71
|
+
const validation = validatePluginCommands(staged, new Set(SUBTREE_NAMES), await coreCommandPaths());
|
|
71
72
|
if (validation.issues.length > 0) {
|
|
72
73
|
throw general(`bundle staged package for "${name}" is invalid`, { issues: validation.issues });
|
|
73
74
|
}
|
|
@@ -104,7 +105,7 @@ async function replaceBundlePlugin(name, bundleSource, scope, options) {
|
|
|
104
105
|
throw network(message);
|
|
105
106
|
throw general(message);
|
|
106
107
|
}
|
|
107
|
-
const validated = await validatePluginBundle(fetched.raw, { reservedCoreNames: new Set(SUBTREE_NAMES) });
|
|
108
|
+
const validated = await validatePluginBundle(fetched.raw, { reservedCoreNames: new Set(SUBTREE_NAMES), coreCommandPaths: await coreCommandPaths() });
|
|
108
109
|
if (validated.bundle === undefined)
|
|
109
110
|
throw general(`HTTP plugin bundle for "${name}" is invalid`, { issues: validated.issues });
|
|
110
111
|
const version = createHash('sha256').update(fetched.raw).digest('hex').slice(0, 12);
|
|
@@ -126,7 +127,7 @@ async function replaceBundlePlugin(name, bundleSource, scope, options) {
|
|
|
126
127
|
try {
|
|
127
128
|
ensureDir(staging);
|
|
128
129
|
writeStagedBundle(staging, manifest, validated.bundle);
|
|
129
|
-
validateStagedBundle(name, scope, staging, manifest);
|
|
130
|
+
await validateStagedBundle(name, scope, staging, manifest);
|
|
130
131
|
let movedPrevious = false;
|
|
131
132
|
if (pathExists(root)) {
|
|
132
133
|
renameSync(root, previous);
|
|
@@ -155,7 +156,7 @@ async function replaceBundlePlugin(name, bundleSource, scope, options) {
|
|
|
155
156
|
state.plugins[name] = {};
|
|
156
157
|
state.plugins[name].last_updated = nowIso();
|
|
157
158
|
});
|
|
158
|
-
const commands = commandReport(name, scope);
|
|
159
|
+
const commands = await commandReport(name, scope);
|
|
159
160
|
if (commands === undefined)
|
|
160
161
|
throw general(`bundle plugin "${name}" has no staged command report`);
|
|
161
162
|
return { name, scope, path: root, transport: 'http', version, docs: validated.bundle.memory.length, commands };
|
|
@@ -368,7 +369,7 @@ async function updateOnePlugin(plugin, marketplaceCache, opts) {
|
|
|
368
369
|
}
|
|
369
370
|
const manifest = readPluginManifest(plugin.root);
|
|
370
371
|
updatePluginVersion(plugin.scope, plugin.name, marketplaceVersion ?? manifest?.version);
|
|
371
|
-
const commands = commandReport(plugin.name, plugin.scope);
|
|
372
|
+
const commands = await commandReport(plugin.name, plugin.scope);
|
|
372
373
|
return {
|
|
373
374
|
name: plugin.name,
|
|
374
375
|
...(plugin.manifest.transport !== undefined ? { transport: plugin.manifest.transport.kind } : {}),
|
|
@@ -435,7 +436,7 @@ export const pluginInstall = defineLeaf({
|
|
|
435
436
|
: classified.kind === 'local' ? installFromLocal(classified.value, scope)
|
|
436
437
|
: installFromGit(classified.value, gitRef, scope);
|
|
437
438
|
const plugin = findPluginByName(installed.name, installed.scope);
|
|
438
|
-
const commands = commandReport(installed.name, installed.scope);
|
|
439
|
+
const commands = await commandReport(installed.name, installed.scope);
|
|
439
440
|
return {
|
|
440
441
|
...installed,
|
|
441
442
|
...(plugin?.manifest.transport !== undefined ? { transport: plugin.manifest.transport.kind } : {}),
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// `crtr surface node focus` — bring one viewer forward.
|
|
2
2
|
import { defineLeaf } from '../../../core/command.js';
|
|
3
3
|
import { InputError } from '../../../core/io.js';
|
|
4
|
-
import { inboxHint } from '../../../core/keybindings/index.js';
|
|
5
4
|
import { cliClient, rethrowAsCliError } from '../../api-client.js';
|
|
6
5
|
import { currentTmux, displayPopup, focusViewer, shellQuote } from './placement.js';
|
|
7
6
|
export const surfaceNodeFocusLeaf = defineLeaf({
|
|
@@ -39,7 +38,7 @@ export const surfaceNodeFocusLeaf = defineLeaf({
|
|
|
39
38
|
throw new InputError({
|
|
40
39
|
error: 'not_focusable',
|
|
41
40
|
message: `node ${id} is a human-ask (kind:human), not a conversation — it has no pi session to focus.`,
|
|
42
|
-
next: `The pending question is queued for the human
|
|
41
|
+
next: `The pending question is queued for the human; see it with \`crtr human list\`, or retract it with \`crtr human cancel ${id}\`.`,
|
|
43
42
|
});
|
|
44
43
|
}
|
|
45
44
|
// Opening history is not resuming work. A terminal/canceled/crashed node has
|
|
@@ -13,7 +13,7 @@ import { readFault } from '../../core/runtime/fault.js';
|
|
|
13
13
|
import { detectNerdFont, nerdFontInstallCommand, NERD_FONT_SELECT_HINT } from '../../core/runtime/nerd-font.js';
|
|
14
14
|
import { detectPackageManager } from './setup-core.js';
|
|
15
15
|
import { validateEffectiveCommandPlugins } from '../../core/command-plugins/discovery.js';
|
|
16
|
-
import { SUBTREE_NAMES } from '../../build-root.js';
|
|
16
|
+
import { SUBTREE_NAMES, coreCommandPaths } from '../../build-root.js';
|
|
17
17
|
import { processGeneration, processLocatedGeneration, runtimeManifest, selectedGeneration } from './shared.js';
|
|
18
18
|
function pass(scope, name, message) {
|
|
19
19
|
return { scope, name, status: 'pass', message };
|
|
@@ -109,14 +109,14 @@ function faultRemediation(fault, nodeId) {
|
|
|
109
109
|
* disable/update/remove the plugin). Only plugins whose scope is in `scopes`
|
|
110
110
|
* are reported, so `--scope` narrows these checks too.
|
|
111
111
|
*/
|
|
112
|
-
function runCommandPluginChecks(scopes) {
|
|
112
|
+
async function runCommandPluginChecks(scopes) {
|
|
113
113
|
const results = [];
|
|
114
114
|
const inScope = new Set(scopes);
|
|
115
115
|
const reserved = new Set(SUBTREE_NAMES);
|
|
116
116
|
// Validate the whole effective set together so cross-plugin top-level name
|
|
117
117
|
// collisions surface here — a collided command is NOT mounted, and every
|
|
118
118
|
// claimant must fail, not pass.
|
|
119
|
-
for (const v of validateEffectiveCommandPlugins(reserved)) {
|
|
119
|
+
for (const v of validateEffectiveCommandPlugins(reserved, undefined, undefined, await coreCommandPaths())) {
|
|
120
120
|
const plugin = v.plugin;
|
|
121
121
|
if (!inScope.has(plugin.scope))
|
|
122
122
|
continue;
|
|
@@ -442,7 +442,7 @@ export const sysDoctorLeaf = defineLeaf({
|
|
|
442
442
|
}
|
|
443
443
|
if (scopes.includes('user'))
|
|
444
444
|
allResults.push(runNerdFontCheck());
|
|
445
|
-
allResults.push(...runCommandPluginChecks(scopes));
|
|
445
|
+
allResults.push(...await runCommandPluginChecks(scopes));
|
|
446
446
|
allResults.push(...await runFaultChecks());
|
|
447
447
|
const processLocation = processGeneration();
|
|
448
448
|
allResults.push(...await generationChecks('selected', selectedGeneration(), processLocation === null));
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { type BindingId, type BindingResolution, type KeybindingDiagnostic, type SparseKeybindingOverrides } from '../../core/keybindings/index.js';
|
|
2
|
-
export
|
|
2
|
+
export declare const OFFICIAL_MARKETPLACE_NAME = "crouter-official-marketplace";
|
|
3
|
+
export declare const OFFICIAL_MARKETPLACE_URL = "https://github.com/crouton-labs/crouter-official-marketplace.git";
|
|
4
|
+
export declare const OFFICIAL_MARKETPLACE_REF = "main";
|
|
5
|
+
export type SetupPackageId = 'official-marketplace' | 'crtr-extensions' | 'oauth-adapter' | 'capture-plugin';
|
|
3
6
|
/** `nerd-font` is not a command on PATH like the other two — it is an installed
|
|
4
7
|
* font face, detected by fontconfig/font-directory scan. The viewer renders
|
|
5
8
|
* every tool and command icon with Nerd Font codepoints, so without it a fresh
|
|
6
9
|
* machine shows tofu boxes. */
|
|
7
10
|
export type SetupTool = 'tmux' | 'nvim' | 'nerd-font';
|
|
8
|
-
/** `marketplace-
|
|
9
|
-
* (`installSource` is
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
11
|
+
/** `marketplace-source` clones and registers a marketplace repo
|
|
12
|
+
* (`installSource` is its git URL); `marketplace-plugin` installs one plugin
|
|
13
|
+
* from a registered marketplace (`installSource` is a `<marketplace>/<plugin>`
|
|
14
|
+
* ref). Neither runs the bundled pi CLI, and neither happens anywhere but
|
|
15
|
+
* setup: an install that never runs setup — a Northlight guest image, CI —
|
|
16
|
+
* fetches no catalog and carries no command whose external binary it lacks. */
|
|
17
|
+
export type SetupPackageSourceKind = 'local-path' | 'npm' | 'marketplace-source' | 'marketplace-plugin';
|
|
14
18
|
export type SetupPackageIdentity = string;
|
|
15
19
|
export type PackageManager = 'brew' | 'apt-get' | 'dnf' | 'pacman' | 'manual';
|
|
16
20
|
export interface SetupPackageManifestEntry {
|
|
@@ -83,6 +87,9 @@ export declare class SetupKeybindingsDraft {
|
|
|
83
87
|
markSaved(overrides: SparseKeybindingOverrides<BindingId>, tmuxPassthrough?: readonly BindingId[]): void;
|
|
84
88
|
}
|
|
85
89
|
export declare function buildSetupManifest(resolveBuiltinPackageDir?: (name: string) => string): SetupPackageManifestEntry[];
|
|
90
|
+
/** One-line description of what installing an entry will actually run — the
|
|
91
|
+
* wizard's confirm step and the non-TTY dump both show it. */
|
|
92
|
+
export declare function packagePlanAction(entry: SetupPackageManifestEntry): string;
|
|
86
93
|
export declare function normalizePackageIdentity(source: string, settingsDir: string, homeDir: string): SetupPackageIdentity;
|
|
87
94
|
export declare function detectInstalledSetupPackageIds(settingsJson: unknown, manifest?: SetupPackageManifestEntry[], settingsPath?: string, homeDir?: string): Set<SetupPackageId>;
|
|
88
95
|
export declare function detectCommandOnPath(command: string): boolean;
|
|
@@ -13,8 +13,12 @@ import { normalizeTmuxPassthrough } from '../../core/config.js';
|
|
|
13
13
|
import { detectNerdFont, nerdFontInstallCommand, NERD_FONT_SELECT_HINT } from '../../core/runtime/nerd-font.js';
|
|
14
14
|
import { ensureRenderer, isRendererReady } from '../../core/termrender/termrender.js';
|
|
15
15
|
import { installFromMarketplace } from '../pkg/plugin-manage.js';
|
|
16
|
-
import {
|
|
16
|
+
import { addMarketplace } from '../pkg/market-manage.js';
|
|
17
|
+
import { findPluginByName, findMarketplaceByName } from '../../core/resolver.js';
|
|
17
18
|
import { expandTilde } from '../../core/fs-utils.js';
|
|
19
|
+
export const OFFICIAL_MARKETPLACE_NAME = 'crouter-official-marketplace';
|
|
20
|
+
export const OFFICIAL_MARKETPLACE_URL = 'https://github.com/crouton-labs/crouter-official-marketplace.git';
|
|
21
|
+
export const OFFICIAL_MARKETPLACE_REF = 'main';
|
|
18
22
|
function isKeybindingsObject(value) {
|
|
19
23
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
20
24
|
}
|
|
@@ -169,6 +173,11 @@ export class SetupKeybindingsDraft {
|
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
const PACKAGE_COPY = {
|
|
176
|
+
'official-marketplace': {
|
|
177
|
+
name: OFFICIAL_MARKETPLACE_NAME,
|
|
178
|
+
shortDescription: 'the official crtr plugin catalog — registered as a source so its plugins are installable by ref.',
|
|
179
|
+
longDescription: 'Shallow-clones github.com/crouton-labs/crouter-official-marketplace into the user scope and registers it, which is what makes `crtr pkg plugin install crouter-official-marketplace/<name>` resolve and what fills `crtr pkg market browse`. Registering the source installs no plugin by itself. Uncheck it on a machine that should reach no external catalog — a plugin selected below still registers the marketplace it comes from, because its ref cannot resolve otherwise.',
|
|
180
|
+
},
|
|
172
181
|
'crtr-extensions': {
|
|
173
182
|
name: 'pi-crtr-extensions',
|
|
174
183
|
shortDescription: 'crtr companion extensions — command palette, help gating, statusline, subscription/OAuth rotation, nested context.',
|
|
@@ -186,17 +195,22 @@ const PACKAGE_COPY = {
|
|
|
186
195
|
},
|
|
187
196
|
};
|
|
188
197
|
const PACKAGE_SOURCES = {
|
|
198
|
+
'official-marketplace': { kind: 'marketplace-source', source: OFFICIAL_MARKETPLACE_URL },
|
|
189
199
|
'crtr-extensions': { kind: 'local-path', source: builtinPiPackageDir('pi-crtr-extensions') },
|
|
190
200
|
'oauth-adapter': { kind: 'npm', source: 'npm:pi-claude-oauth-adapter@0.1.2' },
|
|
191
201
|
'capture-plugin': { kind: 'marketplace-plugin', source: 'crouter-official-marketplace/capture' },
|
|
192
202
|
};
|
|
193
203
|
const LOCAL_PACKAGE_NAME_ALIASES = {
|
|
204
|
+
'official-marketplace': [],
|
|
194
205
|
'crtr-extensions': ['pi-crtr-extensions'],
|
|
195
206
|
'oauth-adapter': [],
|
|
196
207
|
'capture-plugin': [],
|
|
197
208
|
};
|
|
198
209
|
export function buildSetupManifest(resolveBuiltinPackageDir = builtinPiPackageDir) {
|
|
210
|
+
// Order matters at execution: the marketplace source registers before the
|
|
211
|
+
// plugin whose ref needs it.
|
|
199
212
|
return [
|
|
213
|
+
'official-marketplace',
|
|
200
214
|
'crtr-extensions',
|
|
201
215
|
'oauth-adapter',
|
|
202
216
|
'capture-plugin',
|
|
@@ -215,6 +229,18 @@ export function buildSetupManifest(resolveBuiltinPackageDir = builtinPiPackageDi
|
|
|
215
229
|
};
|
|
216
230
|
});
|
|
217
231
|
}
|
|
232
|
+
/** One-line description of what installing an entry will actually run — the
|
|
233
|
+
* wizard's confirm step and the non-TTY dump both show it. */
|
|
234
|
+
export function packagePlanAction(entry) {
|
|
235
|
+
switch (entry.sourceKind) {
|
|
236
|
+
case 'marketplace-source':
|
|
237
|
+
return `clone + register ${entry.installSource}`;
|
|
238
|
+
case 'marketplace-plugin':
|
|
239
|
+
return `crtr pkg plugin install ${entry.installSource}`;
|
|
240
|
+
default:
|
|
241
|
+
return `pi install ${entry.installSource}`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
218
244
|
function npmPackageName(source) {
|
|
219
245
|
if (!source.startsWith('npm:'))
|
|
220
246
|
return null;
|
|
@@ -390,7 +416,7 @@ export function buildSetupInitialState(opts = {}) {
|
|
|
390
416
|
const manifest = opts.manifest ?? buildSetupManifest(builtinPiPackageDir);
|
|
391
417
|
const settingsJson = readJsonIfExists(settingsPath);
|
|
392
418
|
const installedPackageIds = detectInstalledSetupPackageIds(settingsJson, manifest, settingsPath, homeDir);
|
|
393
|
-
for (const id of
|
|
419
|
+
for (const id of detectInstalledMarketplaceEntries(manifest))
|
|
394
420
|
installedPackageIds.add(id);
|
|
395
421
|
const commandExists = opts.commandExists ?? detectCommandOnPath;
|
|
396
422
|
const packageManager = opts.packageManager ?? detectPackageManager(process.platform, commandExists);
|
|
@@ -468,21 +494,37 @@ export function formatSetupExecutionSummary(summary) {
|
|
|
468
494
|
summary.exaKeyWritten ? `exa key written: ${summary.exaKeyPath}` : 'exa key written: no',
|
|
469
495
|
].join('\n') + '\n';
|
|
470
496
|
}
|
|
471
|
-
/** Marketplace
|
|
472
|
-
* plugin
|
|
473
|
-
function
|
|
497
|
+
/** Marketplace manifest entries already present in any scope — the marketplace
|
|
498
|
+
* and plugin registries are their install record, not pi's settings.json. */
|
|
499
|
+
function detectInstalledMarketplaceEntries(manifest) {
|
|
474
500
|
const installed = [];
|
|
475
501
|
for (const entry of manifest) {
|
|
476
|
-
if (entry.sourceKind !== 'marketplace-plugin')
|
|
477
|
-
continue;
|
|
478
502
|
try {
|
|
479
|
-
if (
|
|
480
|
-
|
|
503
|
+
if (entry.sourceKind === 'marketplace-source') {
|
|
504
|
+
if (findMarketplaceByName(entry.name) !== null)
|
|
505
|
+
installed.push(entry.id);
|
|
506
|
+
}
|
|
507
|
+
else if (entry.sourceKind === 'marketplace-plugin') {
|
|
508
|
+
if (findPluginByName(entry.name) !== null)
|
|
509
|
+
installed.push(entry.id);
|
|
510
|
+
}
|
|
481
511
|
}
|
|
482
512
|
catch { /* unreadable scope config — treat as not installed */ }
|
|
483
513
|
}
|
|
484
514
|
return installed;
|
|
485
515
|
}
|
|
516
|
+
/** A `<marketplace>/<plugin>` ref only resolves once its marketplace is
|
|
517
|
+
* registered, so selecting a plugin selects its source. Returns the
|
|
518
|
+
* marketplace name when this call registered it. */
|
|
519
|
+
function ensureMarketplaceRegistered(name) {
|
|
520
|
+
if (findMarketplaceByName(name) !== null)
|
|
521
|
+
return null;
|
|
522
|
+
if (name !== OFFICIAL_MARKETPLACE_NAME) {
|
|
523
|
+
throw new Error(`marketplace not registered: ${name}`);
|
|
524
|
+
}
|
|
525
|
+
addMarketplace({ url: OFFICIAL_MARKETPLACE_URL, ref: OFFICIAL_MARKETPLACE_REF, scope: 'user' });
|
|
526
|
+
return name;
|
|
527
|
+
}
|
|
486
528
|
export async function executeSetupSubmission(submission) {
|
|
487
529
|
const installedPackages = [];
|
|
488
530
|
const skippedPackages = [];
|
|
@@ -493,15 +535,28 @@ export async function executeSetupSubmission(submission) {
|
|
|
493
535
|
skippedPackages.push(`${entry.name} (already installed)`);
|
|
494
536
|
continue;
|
|
495
537
|
}
|
|
538
|
+
// A marketplace that will not clone or refresh (offline, diverged cache,
|
|
539
|
+
// moved repo) must not abort the rest of setup.
|
|
540
|
+
if (entry.sourceKind === 'marketplace-source') {
|
|
541
|
+
try {
|
|
542
|
+
addMarketplace({ url: entry.installSource, ref: OFFICIAL_MARKETPLACE_REF, scope: 'user' });
|
|
543
|
+
installedPackages.push(entry.name);
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
skippedPackages.push(`${entry.name} (${e instanceof Error ? e.message : String(e)})`);
|
|
547
|
+
}
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
496
550
|
if (entry.sourceKind === 'marketplace-plugin') {
|
|
497
551
|
const [marketplace, plugin] = entry.installSource.split('/');
|
|
498
552
|
try {
|
|
553
|
+
const registered = ensureMarketplaceRegistered(marketplace);
|
|
554
|
+
if (registered !== null)
|
|
555
|
+
installedPackages.push(registered);
|
|
499
556
|
installFromMarketplace({ marketplace: marketplace, plugin: plugin }, 'user');
|
|
500
557
|
installedPackages.push(entry.name);
|
|
501
558
|
}
|
|
502
559
|
catch (e) {
|
|
503
|
-
// A marketplace that will not refresh (offline, diverged cache) must not
|
|
504
|
-
// abort the rest of setup.
|
|
505
560
|
skippedPackages.push(`${entry.name} (${e instanceof Error ? e.message : String(e)})`);
|
|
506
561
|
}
|
|
507
562
|
continue;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// confirmed install, that one is a persistent editor that saves as you go.
|
|
8
8
|
import { matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
9
9
|
import { formatGesture, matchesPiTuiInput, } from "../../core/keybindings/index.js";
|
|
10
|
-
import { buildSetupInitialState, executeSetupSubmission, formatSetupExecutionSummary, reconcileInboxFrontDoor, renderSetupStaticInstructions, resolveExaKeyPath, } from "./setup-core.js";
|
|
10
|
+
import { buildSetupInitialState, executeSetupSubmission, formatSetupExecutionSummary, packagePlanAction, reconcileInboxFrontDoor, renderSetupStaticInstructions, resolveExaKeyPath, } from "./setup-core.js";
|
|
11
11
|
import { runSetupSettingsProcess } from "./setup-settings-runner.js";
|
|
12
12
|
import { DependenciesPanel } from "./panels/dependencies-panel.js";
|
|
13
13
|
import { KeysPanel } from "./panels/keys-panel.js";
|
|
@@ -239,7 +239,7 @@ export class SetupWizard {
|
|
|
239
239
|
if (submission.packages.length) {
|
|
240
240
|
out.push(truncateToWidth(theme.fg("accent", "Packages"), width));
|
|
241
241
|
for (const entry of submission.packages)
|
|
242
|
-
out.push(truncateToWidth(` \u2022 ${entry.name} ${theme.fg("dim", "\u2192")} ${this.installedPackageIds.has(entry.id) ? theme.fg("success", "skip (already installed)") : theme.fg("muted",
|
|
242
|
+
out.push(truncateToWidth(` \u2022 ${entry.name} ${theme.fg("dim", "\u2192")} ${this.installedPackageIds.has(entry.id) ? theme.fg("success", "skip (already installed)") : theme.fg("muted", packagePlanAction(entry))}`, width));
|
|
243
243
|
}
|
|
244
244
|
if (submission.systemDeps.length) {
|
|
245
245
|
out.push(truncateToWidth(theme.fg("accent", "System deps"), width));
|
|
@@ -23,7 +23,7 @@ export const sysSetupLeaf = defineLeaf({
|
|
|
23
23
|
'Opens a four-step pi-tui installer. Step one logs in to a model provider (pi\'s real OAuth/API-key flow, same as in-app /login) so a fresh install has credentials before running any node.',
|
|
24
24
|
'The remaining steps collect companion package installs, system dependency installs, and an Exa API key, then confirm them all at once.',
|
|
25
25
|
'Runs the bundled pi CLI to install the selected companion packages, writes EXA_API_KEY to ~/.crouter/exa.key when provided, and prints a summary.',
|
|
26
|
-
'The companion-package list includes crtr marketplace plugins (currently `capture`), checked by default and installed into the user scope when left checked. An unreachable marketplace is reported as skipped rather than failing setup.',
|
|
26
|
+
'The companion-package list includes the official crtr marketplace and the plugins installed from it (currently `capture`), all checked by default and registered or installed into the user scope when left checked. Nothing else registers a marketplace, so an install that never runs setup fetches no catalog. An unreachable marketplace is reported as skipped rather than failing setup.',
|
|
27
27
|
'On every invocation (best-effort, idempotent), refreshes crouter-owned tmux bindings and registers known nodes\' interaction roots so resolved tickets can call back into the asking node. The inbox popup toggle is a crouter-owned keybinding, editable on the keybindings tab.',
|
|
28
28
|
],
|
|
29
29
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|