@north-light/crouter 0.3.220 → 0.3.222
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 +29 -0
- package/dist/api/client.js +44 -0
- package/dist/api/dto/chat-inventory.d.ts +56 -0
- package/dist/api/dto/chat-inventory.js +11 -0
- package/dist/api/dto/human-requests.d.ts +88 -0
- package/dist/api/dto/human-requests.js +4 -0
- package/dist/api/dto/human.d.ts +3 -0
- package/dist/api/dto/profiles.d.ts +19 -5
- package/dist/api/dto/profiles.js +2 -1
- package/dist/api/dto/reviews.d.ts +2 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/routes.d.ts +8 -0
- package/dist/api/routes.js +11 -0
- package/dist/build-root.d.ts +2 -6
- package/dist/build-root.js +51 -4
- package/dist/builtin-memory/00-runtime-base/00-authoring.md +31 -0
- package/dist/builtin-memory/00-runtime-base/01-escalation.md +14 -0
- package/dist/builtin-memory/{insights/listen.md → 00-runtime-base/02-insight-capture.md} +1 -0
- package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +27 -0
- package/dist/builtin-memory/{02-lifecycle/01-resident.md → 02-turn-lifecycle/02-resident.md} +5 -0
- package/dist/builtin-memory/04-base-worker.md +4 -8
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/design/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/design/design-contract.md +19 -0
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/general/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +28 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -0
- package/dist/builtin-memory/05-kinds/review/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/security-findings.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/00-base.md +4 -3
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -0
- package/dist/builtin-memory/design/guide.md +35 -0
- package/dist/builtin-memory/design/roadmap.md +21 -0
- package/dist/builtin-memory/insights/capture.md +1 -1
- package/dist/builtin-memory/internal/agent-shaping.md +3 -1
- package/dist/builtin-memory/internal/memory-loading.md +4 -0
- package/dist/builtin-memory/internal/plugins.md +10 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/plan/roadmap.md +6 -22
- package/dist/builtin-memory/spec/guide.md +23 -6
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +34 -16
- package/dist/clients/attach/__tests__/ref-autocomplete.test.js +1 -1
- package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -1
- package/dist/clients/attach/overlays/file-review.js +2 -2
- package/dist/clients/attach/render/markdown-source.js +106 -1
- package/dist/clients/attach/session/file-links.d.ts +13 -4
- package/dist/clients/attach/session/file-links.js +54 -58
- package/dist/clients/attach/session/keys.d.ts +1 -1
- package/dist/clients/attach/session/profile-files.js +1 -1
- package/dist/clients/attach/viewer.js +698 -696
- package/dist/clients/inbox/controller.js +1 -1
- package/dist/clients/inbox/resolve.d.ts +1 -0
- package/dist/clients/inbox/review/launch.d.ts +8 -4
- package/dist/clients/inbox/review/launch.js +55 -5
- package/dist/clients/inbox/review/review-client.d.ts +1 -0
- package/dist/clients/inbox/review/review-client.js +7 -1
- package/dist/clients/inbox/review-adapter.d.ts +1 -8
- package/dist/clients/inbox/review-adapter.js +4 -52
- package/dist/commands/__tests__/human.test.js +2 -2
- package/dist/commands/human/request.d.ts +2 -0
- package/dist/commands/human/request.js +281 -0
- package/dist/commands/human.js +5 -2
- package/dist/commands/memory/lint.js +2 -1
- package/dist/commands/memory/read.js +1 -0
- package/dist/commands/memory.js +1 -1
- package/dist/commands/pkg/market-manage.js +165 -75
- package/dist/commands/pkg/plugin-inspect.js +19 -2
- package/dist/commands/pkg/plugin-manage.d.ts +8 -3
- package/dist/commands/pkg/plugin-manage.js +72 -24
- package/dist/commands/profile/default.js +6 -10
- package/dist/commands/profile/list.js +5 -3
- package/dist/commands/profile/new.js +21 -8
- package/dist/commands/profile/project.js +25 -19
- package/dist/commands/profile/show.js +3 -3
- package/dist/commands/surface-inbox.js +1 -0
- package/dist/commands/sys/__tests__/migrate.test.js +16 -5
- package/dist/commands/sys/config.js +2 -2
- package/dist/commands/sys/doctor.js +87 -5
- package/dist/commands/sys/migrate.js +38 -19
- package/dist/commands/sys/setup-core.js +1 -1
- package/dist/commands/sys/sync-project-guidance.js +1 -1
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +7 -4
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +1 -1
- package/dist/core/__tests__/fixtures/c5-command-boundary-ext.js +24 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -18
- package/dist/core/__tests__/fixtures/fake-engine.js +8 -1
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +71 -0
- package/dist/core/__tests__/human-action-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/human-action-delivery.test.js +140 -0
- package/dist/core/__tests__/human-actions.test.d.ts +1 -0
- package/dist/core/__tests__/human-actions.test.js +116 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +36 -2
- package/dist/core/__tests__/profile-project-memory-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +217 -0
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.d.ts +1 -0
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.js +91 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.d.ts +1 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +127 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.d.ts +1 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.js +31 -0
- package/dist/core/__tests__/serial/broker-sdk-wiring.test.js +102 -2
- package/dist/core/bootstrap.js +6 -0
- package/dist/core/canvas/browse/app.js +5 -2
- package/dist/core/canvas/browse/model.d.ts +25 -15
- package/dist/core/canvas/browse/model.js +86 -65
- package/dist/core/canvas/db.js +23 -0
- package/dist/core/canvas/human-deliveries.d.ts +53 -0
- package/dist/core/canvas/human-deliveries.js +75 -0
- package/dist/core/canvas/render-source.d.ts +6 -0
- package/dist/core/canvas/render-source.js +7 -1
- package/dist/core/canvas/render.js +10 -2
- package/dist/core/command-hooks/artifact.d.ts +10 -0
- package/dist/core/command-hooks/artifact.js +129 -0
- package/dist/core/command-hooks/catalog.d.ts +14 -0
- package/dist/core/command-hooks/catalog.js +38 -0
- package/dist/core/command-hooks/compose.d.ts +15 -0
- package/dist/core/command-hooks/compose.js +99 -0
- package/dist/core/command-hooks/discovery.d.ts +87 -0
- package/dist/core/command-hooks/discovery.js +174 -0
- package/dist/core/command-hooks/help.d.ts +5 -0
- package/dist/core/command-hooks/help.js +18 -0
- package/dist/core/command-hooks/index.d.ts +6 -0
- package/dist/core/command-hooks/index.js +6 -0
- package/dist/core/command-hooks/report.d.ts +23 -0
- package/dist/core/command-hooks/report.js +19 -0
- package/dist/core/command-hooks/schema.d.ts +27 -0
- package/dist/core/command-hooks/schema.js +68 -0
- package/dist/core/command-hooks/transport/exec-invoke.d.ts +22 -0
- package/dist/core/command-hooks/transport/exec-invoke.js +274 -0
- package/dist/core/command-plugins/presence.d.ts +2 -0
- package/dist/core/command-plugins/presence.js +17 -0
- package/dist/core/command-plugins/transport/exec-invoke.d.ts +5 -0
- package/dist/core/command-plugins/transport/exec-invoke.js +58 -5
- package/dist/core/command.d.ts +8 -1
- package/dist/core/command.js +12 -10
- package/dist/core/config.d.ts +13 -1
- package/dist/core/config.js +51 -1
- package/dist/core/feed/inbox.d.ts +6 -0
- package/dist/core/feed/inbox.js +9 -1
- package/dist/core/help.d.ts +7 -1
- package/dist/core/human/action-binding.d.ts +21 -0
- package/dist/core/human/action-binding.js +40 -0
- package/dist/core/human/completion.d.ts +38 -0
- package/dist/core/human/completion.js +27 -0
- package/dist/core/human/convention.d.ts +2 -0
- package/dist/core/human/convention.js +2 -0
- package/dist/core/human/tickets.d.ts +25 -6
- package/dist/core/human/tickets.js +19 -13
- package/dist/core/human/types.d.ts +5 -0
- package/dist/core/human-actions.d.ts +25 -0
- package/dist/core/human-actions.js +101 -0
- package/dist/core/io.d.ts +9 -1
- package/dist/core/io.js +44 -2
- package/dist/core/memory/inline-ref-inventory.d.ts +2 -1
- package/dist/core/memory/inline-ref-inventory.js +15 -8
- package/dist/core/memory-resolver.d.ts +13 -1
- package/dist/core/memory-resolver.js +26 -20
- package/dist/core/profiles/manifest.d.ts +13 -2
- package/dist/core/profiles/manifest.js +84 -18
- package/dist/core/profiles/select.d.ts +2 -0
- package/dist/core/profiles/select.js +29 -12
- package/dist/core/render.js +11 -0
- package/dist/core/runtime/advertised-command-invocation.d.ts +20 -0
- package/dist/core/runtime/advertised-command-invocation.js +233 -0
- package/dist/core/runtime/bearings.js +1 -1
- package/dist/core/runtime/broker/event-projection.js +7 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +13 -11
- package/dist/core/runtime/broker/read-ops.d.ts +4 -0
- package/dist/core/runtime/broker/read-ops.js +6 -2
- package/dist/core/runtime/broker-extension-render.js +1 -1
- package/dist/core/runtime/broker-inventory.d.ts +4 -0
- package/dist/core/runtime/broker-inventory.js +116 -0
- package/dist/core/runtime/broker-persona-guidance.js +1 -1
- package/dist/core/runtime/broker-protocol.d.ts +9 -2
- package/dist/core/runtime/broker.js +10 -1
- package/dist/core/runtime/chat-inventory-rows.d.ts +8 -0
- package/dist/core/runtime/chat-inventory-rows.js +105 -0
- package/dist/core/runtime/command-surface.d.ts +38 -0
- package/dist/core/runtime/command-surface.js +117 -0
- package/dist/core/runtime/launch-target.d.ts +25 -0
- package/dist/core/runtime/launch-target.js +54 -0
- package/dist/core/runtime/node-read.js +5 -0
- package/dist/core/runtime/persona.js +3 -3
- package/dist/core/runtime/prospective-inventory-cli.d.ts +1 -0
- package/dist/core/runtime/prospective-inventory-cli.js +61 -0
- package/dist/core/runtime/prospective-inventory.d.ts +10 -0
- package/dist/core/runtime/prospective-inventory.js +88 -0
- package/dist/core/runtime/spawn.d.ts +3 -1
- package/dist/core/runtime/spawn.js +5 -3
- package/dist/core/scope.d.ts +26 -1
- package/dist/core/scope.js +52 -12
- package/dist/core/substrate/on-read.d.ts +7 -1
- package/dist/core/substrate/on-read.js +30 -32
- package/dist/core/substrate/render-node.d.ts +3 -2
- package/dist/core/substrate/render-node.js +3 -2
- package/dist/core/substrate/render.js +65 -24
- package/dist/core/substrate/schema.d.ts +16 -2
- package/dist/core/substrate/schema.js +14 -5
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +56 -7
- package/dist/daemon/api/handlers/chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/chat-inventory.js +25 -0
- package/dist/daemon/api/handlers/human-requests.d.ts +2 -0
- package/dist/daemon/api/handlers/human-requests.js +409 -0
- package/dist/daemon/api/handlers/human.js +3 -0
- package/dist/daemon/api/handlers/inbox.js +3 -0
- package/dist/daemon/api/handlers/nodes.d.ts +1 -3
- package/dist/daemon/api/handlers/nodes.js +11 -46
- package/dist/daemon/api/handlers/profiles.js +7 -1
- package/dist/daemon/api/handlers/prospective-chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/prospective-chat-inventory.js +59 -0
- package/dist/daemon/api/handlers/reviews.js +10 -2
- package/dist/daemon/api/map.d.ts +2 -1
- package/dist/daemon/api/map.js +3 -2
- package/dist/daemon/api/server.js +6 -0
- package/dist/daemon/crtrd.js +6 -0
- package/dist/daemon/human/deliver-action.d.ts +16 -0
- package/dist/daemon/human/deliver-action.js +168 -0
- package/dist/daemon/human/finish.d.ts +8 -5
- package/dist/daemon/human/finish.js +45 -6
- package/dist/daemon/human/sweep.js +4 -1
- package/dist/daemon/reconcilers/human-delivery-lane.d.ts +10 -0
- package/dist/daemon/reconcilers/human-delivery-lane.js +41 -0
- package/dist/daemon/review/finish.d.ts +8 -3
- package/dist/daemon/review/finish.js +19 -1
- package/dist/hook-authoring.d.ts +75 -0
- package/dist/hook-authoring.js +358 -0
- package/dist/hook-process.d.ts +7 -0
- package/dist/hook-process.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/migrations/002-profile-project-memory.d.ts +2 -0
- package/dist/migrations/002-profile-project-memory.js +71 -0
- package/dist/migrations/profile-manifests.d.ts +30 -0
- package/dist/migrations/profile-manifests.js +70 -0
- package/dist/migrations/registry.js +10 -5
- package/dist/migrations/types.d.ts +28 -1
- package/dist/migrations/types.js +15 -9
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +21 -4
- package/dist/pi-extensions/canvas-structured-output.js +85 -2
- package/dist/types.d.ts +23 -6
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/builtin-memory/00-runtime-base.md +0 -55
- package/dist/builtin-memory/design.md +0 -55
- package/dist/clients/attach/__tests__/file-review-focus.test.js +0 -49
- /package/dist/builtin-memory/{02-lifecycle/00-terminal.md → 02-turn-lifecycle/01-terminal.md} +0 -0
- /package/dist/{clients/attach/__tests__/file-review-focus.test.d.ts → core/__tests__/fixtures/memory-slash-live-probe.d.ts} +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
1
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
2
2
|
import { renameSync } from 'node:fs';
|
|
3
3
|
import { defineLeaf } from '../../core/command.js';
|
|
4
|
-
import { notFound } from '../../core/errors.js';
|
|
4
|
+
import { general, notFound } from '../../core/errors.js';
|
|
5
5
|
import { listAllMarketplaces, listAllPlugins, findMarketplaceByName } from '../../core/resolver.js';
|
|
6
6
|
import { resolveScopeArg, requireScopeRoot } from '../../core/scope.js';
|
|
7
7
|
import { updateConfig, updateState, ensureScopeInitialized, readConfig } from '../../core/config.js';
|
|
8
|
-
import { pathExists, ensureDir, removePath, isSymlink, nowIso } from '../../core/fs-utils.js';
|
|
9
|
-
import { clone,
|
|
10
|
-
import { readMarketplaceManifest } from '../../core/manifest.js';
|
|
8
|
+
import { pathExists, ensureDir, removePath, isSymlink, nowIso, readSymlinkTarget } from '../../core/fs-utils.js';
|
|
9
|
+
import { clone, deriveNameFromUrl, currentSha, gitSync, isGitRepo } from '../../core/git.js';
|
|
10
|
+
import { readMarketplaceManifest, readPluginManifest } from '../../core/manifest.js';
|
|
11
|
+
import { assertPluginCandidateValid, isMarketplaceRelativeSource } from './plugin-manage.js';
|
|
11
12
|
import { resolveInstallScope } from './shared.js';
|
|
12
13
|
/** Clone a marketplace repo into `scope` and register it in that scope's
|
|
13
14
|
* config. Shared by the `pkg market add` leaf and `crtr sys setup`, which
|
|
@@ -146,13 +147,158 @@ export const marketRemove = defineLeaf({
|
|
|
146
147
|
return { name, removed: true };
|
|
147
148
|
},
|
|
148
149
|
});
|
|
150
|
+
function stageGitRoot(url, ref, active, candidate) {
|
|
151
|
+
const result = gitSync(['clone', '--depth', '1', '--branch', ref, url, candidate]);
|
|
152
|
+
if (result.status !== 0) {
|
|
153
|
+
removePath(candidate);
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return { active, candidate, previous: `${candidate}.previous` };
|
|
157
|
+
}
|
|
158
|
+
function activateStagedRoots(staged) {
|
|
159
|
+
const moved = [];
|
|
160
|
+
const activated = [];
|
|
161
|
+
try {
|
|
162
|
+
for (const root of staged) {
|
|
163
|
+
if (pathExists(root.previous))
|
|
164
|
+
throw general(`prior staged backup remains at ${root.previous}`);
|
|
165
|
+
renameSync(root.active, root.previous);
|
|
166
|
+
moved.push(root);
|
|
167
|
+
}
|
|
168
|
+
for (const root of staged) {
|
|
169
|
+
renameSync(root.candidate, root.active);
|
|
170
|
+
activated.push(root);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
for (const root of [...activated].reverse())
|
|
175
|
+
renameSync(root.active, root.candidate);
|
|
176
|
+
for (const root of [...moved].reverse())
|
|
177
|
+
renameSync(root.previous, root.active);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
for (const root of staged)
|
|
181
|
+
removePath(root.previous);
|
|
182
|
+
}
|
|
183
|
+
function candidatePlugin(plugin, root, manifest) {
|
|
184
|
+
return { plugin: { ...plugin, root, manifest }, scopeRoot: dirname(dirname(plugin.root)) };
|
|
185
|
+
}
|
|
186
|
+
async function updateMarketplaceTarget(target) {
|
|
187
|
+
const activeSha = currentSha(target.root) ?? '';
|
|
188
|
+
const sourcedPlugins = listAllPlugins().filter((plugin) => plugin.scope === target.scope && plugin.sourceMarketplace === target.name);
|
|
189
|
+
const stagedRoots = [];
|
|
190
|
+
const cleanupRoots = [];
|
|
191
|
+
let manifest;
|
|
192
|
+
let marketplaceCandidate;
|
|
193
|
+
const candidates = [];
|
|
194
|
+
try {
|
|
195
|
+
if (isGitRepo(target.root)) {
|
|
196
|
+
const candidate = join(dirname(target.root), `.${target.name}.${process.pid}.${Date.now()}.candidate`);
|
|
197
|
+
marketplaceCandidate = stageGitRoot(target.url, target.ref, target.root, candidate);
|
|
198
|
+
if (marketplaceCandidate !== undefined) {
|
|
199
|
+
cleanupRoots.push(candidate);
|
|
200
|
+
const candidateManifest = readMarketplaceManifest(candidate);
|
|
201
|
+
if (candidateManifest === null)
|
|
202
|
+
throw notFound(`marketplace manifest not found in candidate checkout: ${candidate}/.crouter-marketplace/marketplace.json`);
|
|
203
|
+
manifest = candidateManifest;
|
|
204
|
+
if (currentSha(candidate) !== activeSha)
|
|
205
|
+
stagedRoots.push(marketplaceCandidate);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const activeManifest = readMarketplaceManifest(target.root);
|
|
209
|
+
if (activeManifest === null)
|
|
210
|
+
throw notFound(`marketplace manifest not found after refresh: ${target.root}/.crouter-marketplace/marketplace.json`);
|
|
211
|
+
manifest = activeManifest;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const activeManifest = readMarketplaceManifest(target.root);
|
|
216
|
+
if (activeManifest === null)
|
|
217
|
+
throw notFound(`marketplace manifest not found after refresh: ${target.root}/.crouter-marketplace/marketplace.json`);
|
|
218
|
+
manifest = activeManifest;
|
|
219
|
+
}
|
|
220
|
+
for (const plugin of sourcedPlugins) {
|
|
221
|
+
if (isSymlink(plugin.root)) {
|
|
222
|
+
if (marketplaceCandidate === undefined)
|
|
223
|
+
continue;
|
|
224
|
+
const entry = manifest.plugins.find((candidate) => candidate.name === plugin.name);
|
|
225
|
+
if (entry === undefined || !isMarketplaceRelativeSource(entry.source)) {
|
|
226
|
+
throw general(`marketplace candidate removes relative-source plugin "${plugin.name}" while it is installed`);
|
|
227
|
+
}
|
|
228
|
+
const symlinkTarget = readSymlinkTarget(plugin.root);
|
|
229
|
+
if (symlinkTarget === null)
|
|
230
|
+
throw general(`installed marketplace plugin "${plugin.name}" has no readable symlink target`);
|
|
231
|
+
const observedSource = relative(target.root, resolve(dirname(plugin.root), symlinkTarget));
|
|
232
|
+
const candidateRoot = resolve(marketplaceCandidate.candidate, entry.source);
|
|
233
|
+
if (candidateRoot !== resolve(marketplaceCandidate.candidate, observedSource)) {
|
|
234
|
+
throw general(`marketplace candidate moves installed relative-source plugin "${plugin.name}" from ${observedSource} to ${entry.source}`);
|
|
235
|
+
}
|
|
236
|
+
if (!pathExists(candidateRoot))
|
|
237
|
+
throw notFound(`marketplace candidate source path does not exist for plugin "${plugin.name}": ${candidateRoot}`);
|
|
238
|
+
const candidateManifest = readPluginManifest(candidateRoot);
|
|
239
|
+
if (candidateManifest === null)
|
|
240
|
+
throw notFound(`plugin manifest not found in marketplace candidate: ${candidateRoot}/.crouter-plugin/plugin.json`);
|
|
241
|
+
candidates.push(candidatePlugin(plugin, candidateRoot, candidateManifest));
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (!pathExists(plugin.root) || !isGitRepo(plugin.root))
|
|
245
|
+
continue;
|
|
246
|
+
const remote = gitSync(['config', '--get', 'remote.origin.url'], plugin.root).stdout.trim();
|
|
247
|
+
const branch = gitSync(['symbolic-ref', '--short', 'HEAD'], plugin.root).stdout.trim();
|
|
248
|
+
if (remote === '' || branch === '')
|
|
249
|
+
continue;
|
|
250
|
+
const scopeRoot = plugin.scope === 'project' ? requireScopeRoot('project') : requireScopeRoot('user');
|
|
251
|
+
const candidate = join(scopeRoot, 'tmp', `${plugin.name}.${process.pid}.${Date.now()}.candidate`);
|
|
252
|
+
ensureDir(join(scopeRoot, 'tmp'));
|
|
253
|
+
const staged = stageGitRoot(remote, branch, plugin.root, candidate);
|
|
254
|
+
if (staged === undefined)
|
|
255
|
+
continue;
|
|
256
|
+
cleanupRoots.push(candidate);
|
|
257
|
+
const candidateManifest = readPluginManifest(candidate);
|
|
258
|
+
if (candidateManifest === null)
|
|
259
|
+
throw notFound(`plugin manifest not found in update candidate: ${candidate}/.crouter-plugin/plugin.json`);
|
|
260
|
+
candidates.push(candidatePlugin(plugin, candidate, candidateManifest));
|
|
261
|
+
if (currentSha(candidate) !== currentSha(plugin.root))
|
|
262
|
+
stagedRoots.push(staged);
|
|
263
|
+
}
|
|
264
|
+
for (const candidate of candidates) {
|
|
265
|
+
await assertPluginCandidateValid(candidate.plugin.manifest, candidate.plugin.root, candidate.plugin.scope, candidate.scopeRoot, `${candidate.plugin.root}/.crouter-plugin/plugin.json`, candidates);
|
|
266
|
+
}
|
|
267
|
+
activateStagedRoots(stagedRoots);
|
|
268
|
+
updateState(target.scope, (state) => {
|
|
269
|
+
if (state.marketplaces[target.name] === undefined)
|
|
270
|
+
state.marketplaces[target.name] = {};
|
|
271
|
+
state.marketplaces[target.name].last_updated = nowIso();
|
|
272
|
+
});
|
|
273
|
+
for (const plugin of sourcedPlugins) {
|
|
274
|
+
const entry = manifest.plugins.find((candidate) => candidate.name === plugin.name);
|
|
275
|
+
if (entry?.version !== undefined) {
|
|
276
|
+
updateConfig(plugin.scope, (config) => {
|
|
277
|
+
if (config.plugins[plugin.name] !== undefined)
|
|
278
|
+
config.plugins[plugin.name].version = entry.version;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
updateState(plugin.scope, (state) => {
|
|
282
|
+
if (state.plugins[plugin.name] === undefined)
|
|
283
|
+
state.plugins[plugin.name] = {};
|
|
284
|
+
state.plugins[plugin.name].last_updated = nowIso();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const sha = currentSha(target.root) ?? '';
|
|
288
|
+
return { name: target.name, updated: activeSha !== sha, sha };
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
for (const root of cleanupRoots)
|
|
292
|
+
removePath(root);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
149
295
|
export const marketUpdate = defineLeaf({
|
|
150
296
|
name: 'update',
|
|
151
297
|
description: 'pull latest marketplace source',
|
|
152
298
|
whenToUse: 'refreshing one named marketplace source from git, or all registered marketplace sources when no name is given; also updates installed plugins sourced from refreshed marketplaces',
|
|
153
299
|
help: {
|
|
154
300
|
name: 'pkg market update',
|
|
155
|
-
summary: '
|
|
301
|
+
summary: 'refresh one or all registered marketplaces',
|
|
156
302
|
params: [
|
|
157
303
|
{ kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Marketplace name to update. Omit to update all registered marketplaces.' },
|
|
158
304
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution.' },
|
|
@@ -161,79 +307,23 @@ export const marketUpdate = defineLeaf({
|
|
|
161
307
|
{ name: 'updated', type: 'object[]', required: true, constraint: 'One entry per marketplace processed: {name, updated, sha}.' },
|
|
162
308
|
],
|
|
163
309
|
outputKind: 'object',
|
|
164
|
-
effects: ['
|
|
310
|
+
effects: ['Stages and validates marketplace and sourced-plugin revisions before atomically activating them. Updates installed plugin content/version for plugins sourced from refreshed marketplaces.'],
|
|
165
311
|
},
|
|
166
312
|
run: async (input) => {
|
|
167
313
|
const name = input['name'];
|
|
168
314
|
const scopeInput = input['scope'];
|
|
169
315
|
const scope = scopeInput === undefined ? undefined : resolveScopeArg(scopeInput);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const results = [];
|
|
183
|
-
for (const target of targets) {
|
|
184
|
-
const pullSummary = pullIfGit(target.root);
|
|
185
|
-
const freshManifest = readMarketplaceManifest(target.root);
|
|
186
|
-
if (freshManifest === null) {
|
|
187
|
-
throw notFound(`marketplace manifest not found after refresh: ${target.root}/.crouter-marketplace/marketplace.json`);
|
|
188
|
-
}
|
|
189
|
-
updateState(target.scope, (s) => {
|
|
190
|
-
if (s.marketplaces[target.name] === undefined) {
|
|
191
|
-
s.marketplaces[target.name] = {};
|
|
192
|
-
}
|
|
193
|
-
s.marketplaces[target.name].last_updated = nowIso();
|
|
194
|
-
});
|
|
195
|
-
const sourcedPlugins = listAllPlugins().filter((plugin) => plugin.scope === target.scope && plugin.sourceMarketplace === target.name);
|
|
196
|
-
for (const plugin of sourcedPlugins) {
|
|
197
|
-
if (isSymlink(plugin.root)) {
|
|
198
|
-
// Marketplace pull above updates symlinked relative-source plugins.
|
|
199
|
-
}
|
|
200
|
-
else if (pathExists(plugin.root)) {
|
|
201
|
-
pullIfGit(plugin.root);
|
|
202
|
-
}
|
|
203
|
-
const pluginEntry = freshManifest.plugins.find((p) => p.name === plugin.name);
|
|
204
|
-
if (pluginEntry !== undefined && pluginEntry.version !== undefined) {
|
|
205
|
-
updateConfig(plugin.scope, (c) => {
|
|
206
|
-
if (c.plugins[plugin.name] !== undefined) {
|
|
207
|
-
c.plugins[plugin.name].version = pluginEntry.version;
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
updateState(plugin.scope, (s) => {
|
|
212
|
-
if (s.plugins[plugin.name] === undefined) {
|
|
213
|
-
s.plugins[plugin.name] = {};
|
|
214
|
-
}
|
|
215
|
-
s.plugins[plugin.name].last_updated = nowIso();
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
results.push({ name: target.name, updated: pullSummary.updated, sha: pullSummary.sha });
|
|
219
|
-
}
|
|
220
|
-
return results;
|
|
221
|
-
}
|
|
222
|
-
if (name !== undefined) {
|
|
223
|
-
const found = scope === undefined || scope === 'all' || scope === 'builtin'
|
|
224
|
-
? findMarketplaceByName(name)
|
|
225
|
-
: findMarketplaceByName(name, scope);
|
|
226
|
-
if (!found)
|
|
227
|
-
throw notFound(`marketplace not found: ${name}${scope === undefined || scope === 'all' || scope === 'builtin' ? '' : ` (scope: ${scope})`}`);
|
|
228
|
-
const results = await doUpdate([{ name: found.name, scope: found.scope, root: found.root }]);
|
|
229
|
-
return { updated: results };
|
|
230
|
-
}
|
|
231
|
-
const targets = (scope === undefined || scope === 'all'
|
|
232
|
-
? listAllMarketplaces()
|
|
233
|
-
: scope === 'builtin'
|
|
234
|
-
? []
|
|
235
|
-
: listAllMarketplaces().filter((marketplace) => marketplace.scope === scope)).map((m) => ({ name: m.name, scope: m.scope, root: m.root }));
|
|
236
|
-
const results = await doUpdate(targets);
|
|
237
|
-
return { updated: results };
|
|
316
|
+
const targets = name === undefined
|
|
317
|
+
? (scope === undefined || scope === 'all' ? listAllMarketplaces() : scope === 'builtin' ? [] : listAllMarketplaces().filter((marketplace) => marketplace.scope === scope))
|
|
318
|
+
: (() => {
|
|
319
|
+
const found = scope === undefined || scope === 'all' || scope === 'builtin' ? findMarketplaceByName(name) : findMarketplaceByName(name, scope);
|
|
320
|
+
if (found === null)
|
|
321
|
+
throw notFound(`marketplace not found: ${name}${scope === undefined || scope === 'all' || scope === 'builtin' ? '' : ` (scope: ${scope})`}`);
|
|
322
|
+
return [found];
|
|
323
|
+
})();
|
|
324
|
+
const updated = [];
|
|
325
|
+
for (const target of targets)
|
|
326
|
+
updated.push(await updateMarketplaceTarget(target));
|
|
327
|
+
return { updated };
|
|
238
328
|
},
|
|
239
329
|
});
|
|
@@ -4,8 +4,22 @@ 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 {
|
|
7
|
+
import { compileHookRegistry, discoverHookRegistry } from '../../core/command-hooks/discovery.js';
|
|
8
|
+
import { hasDeclaredHooks, hookReport } from '../../core/command-hooks/report.js';
|
|
9
|
+
import { SUBTREE_NAMES, coreCommandPaths, coreHookCatalog } from '../../build-root.js';
|
|
8
10
|
import { resolveScopeArg, projectScopeRoot } from '../../core/scope.js';
|
|
11
|
+
async function hookInspectionReport(plugin) {
|
|
12
|
+
if (!hasDeclaredHooks(plugin.manifest))
|
|
13
|
+
return undefined;
|
|
14
|
+
const catalog = await coreHookCatalog();
|
|
15
|
+
const effective = discoverHookRegistry(catalog);
|
|
16
|
+
const validation = effective.validations.find((entry) => entry.plugin.root === plugin.root);
|
|
17
|
+
if (validation === undefined) {
|
|
18
|
+
const isolated = compileHookRegistry(catalog, [plugin]);
|
|
19
|
+
return hookReport(isolated.validations[0]);
|
|
20
|
+
}
|
|
21
|
+
return hookReport(validation, effective.issues.filter((issue) => issue.plugin === plugin.name && issue.scope === plugin.scope));
|
|
22
|
+
}
|
|
9
23
|
export const pluginList = defineLeaf({
|
|
10
24
|
name: 'list',
|
|
11
25
|
description: 'paginated list of installed plugins',
|
|
@@ -93,9 +107,10 @@ export const pluginShow = defineLeaf({
|
|
|
93
107
|
{ name: 'manifest', type: 'object', required: true, constraint: 'Full plugin.json contents.' },
|
|
94
108
|
{ name: 'docs', type: 'object[]', required: true, constraint: 'Each: {name, path}. Memory docs provided by the plugin (its `<pluginName>/` memory subtree).' },
|
|
95
109
|
{ name: 'commands', type: 'object', required: false, constraint: 'Present only when the plugin declares a command manifest (manifest.commands). {manifestPath: string, mounts: string[] (accepted top-level command names), issues: object[] ({code, path?, message, received, expected, next})}. Validated statically; command execution never occurs.' },
|
|
110
|
+
{ name: 'hooks', type: 'object', required: false, constraint: 'Present for every hook declaration, including hook-only plugins. {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], issues: object[] ({code, path?, message, received, expected, next}), trust: string}. Static only: no hook executable runs.' },
|
|
96
111
|
],
|
|
97
112
|
outputKind: 'object',
|
|
98
|
-
effects: ['None. Read-only. Command manifests are validated statically;
|
|
113
|
+
effects: ['None. Read-only. Command and hook manifests are validated statically; plugin executables are never executed.'],
|
|
99
114
|
},
|
|
100
115
|
run: async (input) => {
|
|
101
116
|
const name = input['name'];
|
|
@@ -134,6 +149,7 @@ export const pluginShow = defineLeaf({
|
|
|
134
149
|
issues: v.issues,
|
|
135
150
|
};
|
|
136
151
|
}
|
|
152
|
+
const hooks = await hookInspectionReport(found);
|
|
137
153
|
return {
|
|
138
154
|
name: found.name,
|
|
139
155
|
scope: found.scope,
|
|
@@ -145,6 +161,7 @@ export const pluginShow = defineLeaf({
|
|
|
145
161
|
path: d.path,
|
|
146
162
|
})),
|
|
147
163
|
...(commands !== undefined ? { commands } : {}),
|
|
164
|
+
...(hooks !== undefined ? { hooks } : {}),
|
|
148
165
|
};
|
|
149
166
|
},
|
|
150
167
|
});
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HookPluginCandidate } from '../../core/command-hooks/discovery.js';
|
|
2
|
+
import { type PluginManifest, type Scope } from '../../types.js';
|
|
2
3
|
interface MarketplaceRef {
|
|
3
4
|
marketplace: string;
|
|
4
5
|
plugin: string;
|
|
5
6
|
}
|
|
7
|
+
/** Validate a package candidate before it gains an active plugin path. The
|
|
8
|
+
* candidate namespace shadows any installed declaration of the same name. */
|
|
9
|
+
export declare function assertPluginCandidateValid(manifest: PluginManifest, root: string, scope: Scope, scopeRootPath: string, where: string, candidates?: readonly HookPluginCandidate[]): Promise<void>;
|
|
10
|
+
export declare function isMarketplaceRelativeSource(source: string): boolean;
|
|
6
11
|
/** Install one marketplace plugin into a scope. Exported so `sys setup` can
|
|
7
12
|
* install the default plugin set through the same path `pkg plugin install`
|
|
8
13
|
* uses — refresh the source, resolve the entry, link/clone, register. */
|
|
9
|
-
export declare function installFromMarketplace(ref: MarketplaceRef, scope: Scope): {
|
|
14
|
+
export declare function installFromMarketplace(ref: MarketplaceRef, scope: Scope): Promise<{
|
|
10
15
|
name: string;
|
|
11
16
|
scope: Scope;
|
|
12
17
|
path: string;
|
|
13
|
-
}
|
|
18
|
+
}>;
|
|
14
19
|
export declare const pluginInstall: import("../../core/command.js").LeafDef;
|
|
15
20
|
export declare const pluginRemove: import("../../core/command.js").LeafDef;
|
|
16
21
|
export declare const pluginEnable: import("../../core/command.js").LeafDef;
|
|
@@ -17,8 +17,10 @@ import { validatePluginCommands, validateEffectiveCommandPlugins } from '../../c
|
|
|
17
17
|
import { normalizeEndpoint, validateEndpoint, validateHttpPluginTransport } from '../../core/command-plugins/endpoint.js';
|
|
18
18
|
import { buildBrokerEnv, resolvePathExecutable } from '../../core/runtime/spawn-env.js';
|
|
19
19
|
import { validatePluginBundle } from '../../core/command-plugins/bundle.js';
|
|
20
|
+
import { compileHookRegistry, discoverCandidateHookRegistry, discoverCandidateHookRegistryForCandidates, discoverHookRegistry } from '../../core/command-hooks/discovery.js';
|
|
21
|
+
import { hasDeclaredHooks, hookReport } from '../../core/command-hooks/report.js';
|
|
20
22
|
import { fetchHttpPluginBundle } from '../../core/command-plugins/transport/http-fetch.js';
|
|
21
|
-
import { SUBTREE_NAMES, coreCommandPaths } from '../../build-root.js';
|
|
23
|
+
import { SUBTREE_NAMES, coreCommandPaths, coreHookCatalog } from '../../build-root.js';
|
|
22
24
|
import { isGitUrl, setPluginEnabled, resolveInstallScope } from './shared.js';
|
|
23
25
|
/** Validate a just-installed/updated plugin's command manifest, if it declares
|
|
24
26
|
* one. Returns undefined for plugins without a `commands` pointer. Never
|
|
@@ -121,9 +123,44 @@ function assertManifestPageComponentsValid(manifest, where) {
|
|
|
121
123
|
throw general(`plugin manifest at ${where} declares an invalid page_components block:\n${reasons.map((r) => `- ${r}`).join('\n')}`, { issues: reasons });
|
|
122
124
|
}
|
|
123
125
|
}
|
|
126
|
+
/** Static hook report for an installed plugin. An inactive/shadowed plugin
|
|
127
|
+
* still receives its own artifact and target validation; only effective plugins
|
|
128
|
+
* additionally carry collisions from the complete active registry. */
|
|
129
|
+
async function hookLifecycleReport(name, scope) {
|
|
130
|
+
const plugin = findPluginByName(name, scope);
|
|
131
|
+
if (plugin === null || !hasDeclaredHooks(plugin.manifest))
|
|
132
|
+
return undefined;
|
|
133
|
+
const catalog = await coreHookCatalog();
|
|
134
|
+
const effective = discoverHookRegistry(catalog);
|
|
135
|
+
const validation = effective.validations.find((entry) => entry.plugin.root === plugin.root);
|
|
136
|
+
if (validation === undefined) {
|
|
137
|
+
const isolated = compileHookRegistry(catalog, [plugin]);
|
|
138
|
+
return hookReport(isolated.validations[0]);
|
|
139
|
+
}
|
|
140
|
+
return hookReport(validation, effective.issues.filter((issue) => issue.plugin === plugin.name && issue.scope === plugin.scope));
|
|
141
|
+
}
|
|
142
|
+
/** Validate a source candidate's complete hook behavior before activation. The
|
|
143
|
+
* candidate claims its future scope/name slot, so stale targets and replacement
|
|
144
|
+
* collisions reject the candidate without disturbing installed bytes. */
|
|
145
|
+
async function assertManifestHooksValid(manifest, root, scope, scopeRootPath, where, candidates) {
|
|
146
|
+
if (!hasDeclaredHooks(manifest))
|
|
147
|
+
return;
|
|
148
|
+
const candidate = { name: manifest.name, scope, root, manifest, enabled: true };
|
|
149
|
+
const registryCandidates = candidates ?? [{ plugin: candidate, scopeRoot: scopeRootPath }];
|
|
150
|
+
const catalog = await coreHookCatalog();
|
|
151
|
+
const effective = candidates === undefined
|
|
152
|
+
? discoverCandidateHookRegistry(catalog, candidate, scopeRootPath)
|
|
153
|
+
: discoverCandidateHookRegistryForCandidates(catalog, registryCandidates);
|
|
154
|
+
const validation = effective.validations.find((entry) => entry.plugin.root === root);
|
|
155
|
+
const issues = validation === undefined
|
|
156
|
+
? compileHookRegistry(catalog, [candidate]).issues
|
|
157
|
+
: effective.issues.filter((issue) => issue.plugin === candidate.name && issue.scope === candidate.scope);
|
|
158
|
+
if (issues.length > 0)
|
|
159
|
+
throw invalidBundleError(`plugin hook behavior at ${where} is invalid`, issues);
|
|
160
|
+
}
|
|
124
161
|
/** Validate a package candidate before it gains an active plugin path. The
|
|
125
162
|
* candidate namespace shadows any installed declaration of the same name. */
|
|
126
|
-
function assertPluginCandidateValid(manifest, root, scope, scopeRootPath, where) {
|
|
163
|
+
export async function assertPluginCandidateValid(manifest, root, scope, scopeRootPath, where, candidates) {
|
|
127
164
|
try {
|
|
128
165
|
validatePluginManifestMemoryExtensions(manifest);
|
|
129
166
|
}
|
|
@@ -135,6 +172,11 @@ function assertPluginCandidateValid(manifest, root, scope, scopeRootPath, where)
|
|
|
135
172
|
assertManifestPageComponentsValid(manifest, where);
|
|
136
173
|
assertManifestBinValid(manifest, where);
|
|
137
174
|
assertManifestRequiresValid(manifest, where);
|
|
175
|
+
const commandValidation = validatePluginCommands({ name: manifest.name, scope, root, manifest, enabled: true }, new Set(SUBTREE_NAMES), await coreCommandPaths());
|
|
176
|
+
if (commandValidation.issues.length > 0) {
|
|
177
|
+
throw invalidBundleError(`plugin command behavior at ${where} is invalid`, commandValidation.issues);
|
|
178
|
+
}
|
|
179
|
+
await assertManifestHooksValid(manifest, root, scope, scopeRootPath, where, candidates);
|
|
138
180
|
const memoryDir = join(root, 'memory');
|
|
139
181
|
const catalogDocPath = join(scopeRootPath, 'memory', '__plugin_candidate__.md');
|
|
140
182
|
for (const file of walkFiles(memoryDir, (name) => name.endsWith('.md'), (dir) => dir.startsWith('.'))) {
|
|
@@ -176,7 +218,7 @@ function writeStagedBundle(stagingRoot, manifest, bundle) {
|
|
|
176
218
|
}
|
|
177
219
|
}
|
|
178
220
|
async function validateStagedBundle(name, scope, root, scopeRootPath, manifest) {
|
|
179
|
-
assertPluginCandidateValid(manifest, root, scope, scopeRootPath, `${root}/.crouter-plugin/plugin.json`);
|
|
221
|
+
await assertPluginCandidateValid(manifest, root, scope, scopeRootPath, `${root}/.crouter-plugin/plugin.json`);
|
|
180
222
|
const staged = { name, scope, root, manifest, enabled: true };
|
|
181
223
|
const validation = validatePluginCommands(staged, new Set(SUBTREE_NAMES), await coreCommandPaths());
|
|
182
224
|
if (validation.issues.length > 0) {
|
|
@@ -300,10 +342,10 @@ function classifyInstallRef(raw) {
|
|
|
300
342
|
throw usage(`invalid plugin install ref: ${raw}\n` +
|
|
301
343
|
'Accepted forms: marketplace/plugin, git URL (https://, ssh://, git@, file://, or *.git), or local path starting ./, ../, or /. Bare plugin names are not install refs.');
|
|
302
344
|
}
|
|
303
|
-
function isMarketplaceRelativeSource(source) {
|
|
345
|
+
export function isMarketplaceRelativeSource(source) {
|
|
304
346
|
return source.startsWith('./') || source.startsWith('../') || (!isAbsolute(source) && !isGitUrl(source));
|
|
305
347
|
}
|
|
306
|
-
function refreshMarketplace(name, cache, opts) {
|
|
348
|
+
async function refreshMarketplace(name, cache, opts) {
|
|
307
349
|
const cached = cache.get(name);
|
|
308
350
|
if (cached !== undefined)
|
|
309
351
|
return cached;
|
|
@@ -349,7 +391,7 @@ function refreshMarketplace(name, cache, opts) {
|
|
|
349
391
|
if (candidateManifest === null)
|
|
350
392
|
throw notFound(`plugin manifest not found in marketplace candidate: ${candidateRoot}/.crouter-plugin/plugin.json`);
|
|
351
393
|
const pluginScopeRoot = plugin.scope === 'project' ? requireScopeRoot('project') : userScopeRoot();
|
|
352
|
-
assertPluginCandidateValid(candidateManifest, candidateRoot, plugin.scope, pluginScopeRoot, `${candidateRoot}/.crouter-plugin/plugin.json`);
|
|
394
|
+
await assertPluginCandidateValid(candidateManifest, candidateRoot, plugin.scope, pluginScopeRoot, `${candidateRoot}/.crouter-plugin/plugin.json`);
|
|
353
395
|
}
|
|
354
396
|
renameSync(marketplace.root, previous);
|
|
355
397
|
try {
|
|
@@ -397,7 +439,7 @@ function updatePluginVersion(scope, name, version) {
|
|
|
397
439
|
s.plugins[name].last_updated = nowIso();
|
|
398
440
|
});
|
|
399
441
|
}
|
|
400
|
-
function installFromGit(source, ref, scope) {
|
|
442
|
+
async function installFromGit(source, ref, scope) {
|
|
401
443
|
const scopeRootPath = scope === 'project' ? ensureProjectScopeRoot() : userScopeRoot();
|
|
402
444
|
ensureScopeInitialized(scope, scopeRootPath);
|
|
403
445
|
const pDir = join(scopeRootPath, 'plugins');
|
|
@@ -409,7 +451,7 @@ function installFromGit(source, ref, scope) {
|
|
|
409
451
|
const manifest = readPluginManifest(staging);
|
|
410
452
|
if (manifest === null)
|
|
411
453
|
throw general(`cloned repo does not contain a valid .crouter-plugin/plugin.json: ${source}`);
|
|
412
|
-
assertPluginCandidateValid(manifest, staging, scope, scopeRootPath, `${source}/.crouter-plugin/plugin.json`);
|
|
454
|
+
await assertPluginCandidateValid(manifest, staging, scope, scopeRootPath, `${source}/.crouter-plugin/plugin.json`);
|
|
413
455
|
const finalDir = join(pDir, manifest.name);
|
|
414
456
|
if (pathExists(finalDir))
|
|
415
457
|
throw general(`plugin "${manifest.name}" is already installed at ${finalDir}`);
|
|
@@ -424,14 +466,14 @@ function installFromGit(source, ref, scope) {
|
|
|
424
466
|
removePath(staging);
|
|
425
467
|
}
|
|
426
468
|
}
|
|
427
|
-
function installFromLocal(source, scope) {
|
|
469
|
+
async function installFromLocal(source, scope) {
|
|
428
470
|
const sourcePath = resolve(source);
|
|
429
471
|
const manifest = readPluginManifest(sourcePath);
|
|
430
472
|
if (manifest === null) {
|
|
431
473
|
throw notFound(`plugin manifest not found at ${sourcePath}/.crouter-plugin/plugin.json`);
|
|
432
474
|
}
|
|
433
475
|
const scopeRootPath = scope === 'project' ? ensureProjectScopeRoot() : userScopeRoot();
|
|
434
|
-
assertPluginCandidateValid(manifest, sourcePath, scope, scopeRootPath, `${sourcePath}/.crouter-plugin/plugin.json`);
|
|
476
|
+
await assertPluginCandidateValid(manifest, sourcePath, scope, scopeRootPath, `${sourcePath}/.crouter-plugin/plugin.json`);
|
|
435
477
|
ensureScopeInitialized(scope, scopeRootPath);
|
|
436
478
|
const destDir = join(scopeRootPath, 'plugins', manifest.name);
|
|
437
479
|
if (pathExists(destDir)) {
|
|
@@ -449,8 +491,8 @@ function installFromLocal(source, scope) {
|
|
|
449
491
|
/** Install one marketplace plugin into a scope. Exported so `sys setup` can
|
|
450
492
|
* install the default plugin set through the same path `pkg plugin install`
|
|
451
493
|
* uses — refresh the source, resolve the entry, link/clone, register. */
|
|
452
|
-
export function installFromMarketplace(ref, scope) {
|
|
453
|
-
const refreshed = refreshMarketplace(ref.marketplace, new Map(), { strict: true });
|
|
494
|
+
export async function installFromMarketplace(ref, scope) {
|
|
495
|
+
const refreshed = await refreshMarketplace(ref.marketplace, new Map(), { strict: true });
|
|
454
496
|
if (refreshed === null)
|
|
455
497
|
throw notFound(`marketplace not found: ${ref.marketplace}`);
|
|
456
498
|
const entry = refreshed.manifest.plugins.find((p) => p.name === ref.plugin);
|
|
@@ -470,7 +512,7 @@ export function installFromMarketplace(ref, scope) {
|
|
|
470
512
|
const candidateManifest = readPluginManifest(sourcePath);
|
|
471
513
|
if (candidateManifest === null)
|
|
472
514
|
throw notFound(`plugin manifest not found at ${sourcePath}/.crouter-plugin/plugin.json`);
|
|
473
|
-
assertPluginCandidateValid(candidateManifest, sourcePath, scope, destRoot, `${sourcePath}/.crouter-plugin/plugin.json`);
|
|
515
|
+
await assertPluginCandidateValid(candidateManifest, sourcePath, scope, destRoot, `${sourcePath}/.crouter-plugin/plugin.json`);
|
|
474
516
|
linkOrCopy(sourcePath, destPluginDir);
|
|
475
517
|
pluginManifest = candidateManifest;
|
|
476
518
|
}
|
|
@@ -482,7 +524,7 @@ export function installFromMarketplace(ref, scope) {
|
|
|
482
524
|
const candidateManifest = readPluginManifest(staging);
|
|
483
525
|
if (candidateManifest === null)
|
|
484
526
|
throw notFound(`plugin manifest not found at ${staging}/.crouter-plugin/plugin.json`);
|
|
485
|
-
assertPluginCandidateValid(candidateManifest, staging, scope, destRoot, `${staging}/.crouter-plugin/plugin.json`);
|
|
527
|
+
await assertPluginCandidateValid(candidateManifest, staging, scope, destRoot, `${staging}/.crouter-plugin/plugin.json`);
|
|
486
528
|
ensureDir(join(destRoot, 'plugins'));
|
|
487
529
|
renameSync(staging, destPluginDir);
|
|
488
530
|
pluginManifest = candidateManifest;
|
|
@@ -503,13 +545,13 @@ export function installFromMarketplace(ref, scope) {
|
|
|
503
545
|
});
|
|
504
546
|
return { name: ref.plugin, scope, path: destPluginDir };
|
|
505
547
|
}
|
|
506
|
-
function updateGitPluginCandidate(plugin) {
|
|
548
|
+
async function updateGitPluginCandidate(plugin) {
|
|
507
549
|
if (isSymlink(plugin.root)) {
|
|
508
550
|
const manifest = readPluginManifest(plugin.root);
|
|
509
551
|
if (manifest === null)
|
|
510
552
|
throw notFound(`plugin manifest not found at ${plugin.root}/.crouter-plugin/plugin.json`);
|
|
511
553
|
const scopeRootPath = plugin.scope === 'project' ? requireScopeRoot('project') : userScopeRoot();
|
|
512
|
-
assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`);
|
|
554
|
+
await assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`);
|
|
513
555
|
return { updated: false, sha: currentSha(plugin.root) ?? '', manifest };
|
|
514
556
|
}
|
|
515
557
|
if (!isGitRepo(plugin.root)) {
|
|
@@ -517,7 +559,7 @@ function updateGitPluginCandidate(plugin) {
|
|
|
517
559
|
if (manifest === null)
|
|
518
560
|
throw notFound(`plugin manifest not found at ${plugin.root}/.crouter-plugin/plugin.json`);
|
|
519
561
|
const scopeRootPath = plugin.scope === 'project' ? requireScopeRoot('project') : userScopeRoot();
|
|
520
|
-
assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`);
|
|
562
|
+
await assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`);
|
|
521
563
|
return { updated: false, sha: '', manifest };
|
|
522
564
|
}
|
|
523
565
|
const remote = gitSync(['config', '--get', 'remote.origin.url'], plugin.root).stdout.trim();
|
|
@@ -536,7 +578,7 @@ function updateGitPluginCandidate(plugin) {
|
|
|
536
578
|
const manifest = readPluginManifest(staging);
|
|
537
579
|
if (manifest === null)
|
|
538
580
|
throw notFound(`plugin manifest not found in update candidate: ${staging}/.crouter-plugin/plugin.json`);
|
|
539
|
-
assertPluginCandidateValid(manifest, staging, plugin.scope, scopeRootPath, `${staging}/.crouter-plugin/plugin.json`);
|
|
581
|
+
await assertPluginCandidateValid(manifest, staging, plugin.scope, scopeRootPath, `${staging}/.crouter-plugin/plugin.json`);
|
|
540
582
|
renameSync(plugin.root, previous);
|
|
541
583
|
try {
|
|
542
584
|
renameSync(staging, plugin.root);
|
|
@@ -566,16 +608,17 @@ async function updateOnePlugin(plugin, marketplaceCache, opts) {
|
|
|
566
608
|
let marketplaceVersion;
|
|
567
609
|
let marketplaceUpdated = false;
|
|
568
610
|
if (plugin.sourceMarketplace !== undefined) {
|
|
569
|
-
const refreshed = refreshMarketplace(plugin.sourceMarketplace, marketplaceCache, opts);
|
|
611
|
+
const refreshed = await refreshMarketplace(plugin.sourceMarketplace, marketplaceCache, opts);
|
|
570
612
|
if (refreshed !== null) {
|
|
571
613
|
const entry = refreshed.manifest.plugins.find((candidate) => candidate.name === plugin.name);
|
|
572
614
|
marketplaceVersion = entry?.version;
|
|
573
615
|
marketplaceUpdated = refreshed.pull.updated;
|
|
574
616
|
}
|
|
575
617
|
}
|
|
576
|
-
const candidate = updateGitPluginCandidate(plugin);
|
|
618
|
+
const candidate = await updateGitPluginCandidate(plugin);
|
|
577
619
|
updatePluginVersion(plugin.scope, plugin.name, marketplaceVersion ?? candidate.manifest.version);
|
|
578
620
|
const commands = await commandReport(plugin.name, plugin.scope);
|
|
621
|
+
const hooks = await hookLifecycleReport(plugin.name, plugin.scope);
|
|
579
622
|
const warnings = missingRequirementWarnings(candidate.manifest);
|
|
580
623
|
return {
|
|
581
624
|
name: plugin.name,
|
|
@@ -583,6 +626,7 @@ async function updateOnePlugin(plugin, marketplaceCache, opts) {
|
|
|
583
626
|
updated: marketplaceUpdated || candidate.updated,
|
|
584
627
|
sha: candidate.sha,
|
|
585
628
|
...(commands !== undefined ? { commands } : {}),
|
|
629
|
+
...(hooks !== undefined ? { hooks } : {}),
|
|
586
630
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
587
631
|
};
|
|
588
632
|
}
|
|
@@ -611,6 +655,7 @@ export const pluginInstall = defineLeaf({
|
|
|
611
655
|
{ name: 'kinds', type: 'integer', required: false, constraint: 'Kind-registry entry count declared by an --endpoint archive\u2019s bundle.json.' },
|
|
612
656
|
{ name: 'pageComponents', type: 'integer', required: false, constraint: 'Page-component registration count declared by an --endpoint archive\u2019s bundle.json.' },
|
|
613
657
|
{ name: 'commands', type: 'object', required: false, constraint: 'Present only when the plugin declares a command manifest. {mounts: string[] (accepted top-level command names now live for the next invocation), issues: object[] (typed validation issues that rejected a contribution — {code, path?, message, received, expected, next})}. Validated statically; commands are never executed.' },
|
|
658
|
+
{ name: 'hooks', type: 'object', required: false, constraint: 'Present only when the source plugin declares hooks. {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], issues: object[], trust: string}. Declarations, eligible targets, and replacement collisions are validated statically; hook executables are never run.' },
|
|
614
659
|
{ name: 'warnings', type: 'string[]', required: false, constraint: 'Advisory missing PATH executable requirements declared by the installed plugin. The install still succeeds and leaves the plugin enabled.' },
|
|
615
660
|
],
|
|
616
661
|
outputKind: 'object',
|
|
@@ -618,6 +663,7 @@ export const pluginInstall = defineLeaf({
|
|
|
618
663
|
'A ref install clones, links, or copies the plugin into the scope plugins directory and registers it with enabled=true. Marketplace installs refresh the source marketplace before resolving the plugin entry.',
|
|
619
664
|
'An --endpoint install fetches and validates one authenticated uncompressed tar archive before writing. It replaces the complete plugin directory with synthesized provenance and invocation metadata, commands.json, memory docs, any kind-registry entries the bundle.json declares (they join the launch registry below this scope\u2019s own config.json kinds), and any page components it declares (they join the active page-component catalog alongside the user config entries); a fetch or validation failure preserves the prior package.',
|
|
620
665
|
'If the plugin declares a command manifest, its commands go live on the next crtr invocation. Exec commands run trusted local code only when explicitly invoked; HTTP commands call their declared endpoint only when explicitly invoked.',
|
|
666
|
+
'Source plugin hooks go live on the next crtr invocation. They run implicitly with caller authority, may receive normalized inputs and after results, may block or replace declared targets, and may transmit received data. Hook executables are never run during install or update validation.',
|
|
621
667
|
'A plugin manifest `requires` declaration is checked against the PATH a node bash receives after install. Missing executables emit a warning with the declared install hint but never fail the install, disable the plugin, or remove its bare-binary contributions.',
|
|
622
668
|
],
|
|
623
669
|
},
|
|
@@ -644,16 +690,18 @@ export const pluginInstall = defineLeaf({
|
|
|
644
690
|
if (authEnv !== undefined)
|
|
645
691
|
throw usage('--auth-env is only valid with --endpoint');
|
|
646
692
|
const classified = classifyInstallRef(rawRef);
|
|
647
|
-
const installed = classified.kind === 'marketplace' ? installFromMarketplace(classified.value, scope)
|
|
648
|
-
: classified.kind === 'local' ? installFromLocal(classified.value, scope)
|
|
649
|
-
: installFromGit(classified.value, gitRef, scope);
|
|
693
|
+
const installed = classified.kind === 'marketplace' ? await installFromMarketplace(classified.value, scope)
|
|
694
|
+
: classified.kind === 'local' ? await installFromLocal(classified.value, scope)
|
|
695
|
+
: await installFromGit(classified.value, gitRef, scope);
|
|
650
696
|
const plugin = findPluginByName(installed.name, installed.scope);
|
|
651
697
|
const commands = await commandReport(installed.name, installed.scope);
|
|
698
|
+
const hooks = await hookLifecycleReport(installed.name, installed.scope);
|
|
652
699
|
const warnings = plugin === null ? [] : missingRequirementWarnings(plugin.manifest);
|
|
653
700
|
return {
|
|
654
701
|
...installed,
|
|
655
702
|
...(plugin?.manifest.transport !== undefined ? { transport: plugin.manifest.transport.kind } : {}),
|
|
656
703
|
...(commands !== undefined ? { commands } : {}),
|
|
704
|
+
...(hooks !== undefined ? { hooks } : {}),
|
|
657
705
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
658
706
|
};
|
|
659
707
|
},
|
|
@@ -766,7 +814,7 @@ export const pluginUpdate = defineLeaf({
|
|
|
766
814
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution.' },
|
|
767
815
|
],
|
|
768
816
|
output: [
|
|
769
|
-
{ name: 'updated', type: 'object[]', required: true, constraint: 'One entry per plugin processed: {name, transport?, updated, sha?, version?, docs?, kinds?, pageComponents?, commands?, warnings?, error?}. Archive plugins report transport=http and replace their complete directory after an authenticated refetch; version is the archive content identifier, docs is the written memory-document count, kinds is the declared kind-registry entry count, and pageComponents is the declared page-component registration count. sha is present for git updates. A bulk update reports and skips a failed archive plugin. commands is present for validated command plugins: {mounts: string[], issues: object[]}. warnings lists advisory PATH requirements that remain absent after update.' },
|
|
817
|
+
{ name: 'updated', type: 'object[]', required: true, constraint: 'One entry per plugin processed: {name, transport?, updated, sha?, version?, docs?, kinds?, pageComponents?, commands?, hooks?, warnings?, error?}. Archive plugins report transport=http and replace their complete directory after an authenticated refetch; version is the archive content identifier, docs is the written memory-document count, kinds is the declared kind-registry entry count, and pageComponents is the declared page-component registration count. sha is present for git updates. A bulk update reports and skips a failed archive plugin. commands is present for validated command plugins: {mounts: string[], issues: object[]}. hooks is present for source hook plugins: {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], issues: object[], trust: string}; it is static and never executes hooks. warnings lists advisory PATH requirements that remain absent after update.' },
|
|
770
818
|
],
|
|
771
819
|
outputKind: 'object',
|
|
772
820
|
effects: ['Archive plugins unconditionally re-fetch, validate, and replace their complete package; a named failure exits nonzero and preserves the prior package. Bulk update reports a failed archive plugin and continues. Source-installed plugins retain their source update, version, and last_updated behavior.'],
|