@north-light/crouter 0.3.163 → 0.3.165
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/build-root.d.ts +6 -6
- package/dist/build-root.js +12 -13
- package/dist/builtin-memory/00-runtime-base.md +3 -4
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
- package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
- package/dist/builtin-memory/advisor/council.md +40 -0
- package/dist/builtin-memory/design.md +8 -11
- package/dist/builtin-memory/development.md +6 -9
- package/dist/builtin-memory/internal/INDEX.md +5 -5
- package/dist/builtin-memory/internal/agent-shaping.md +5 -7
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/marketplaces.md +13 -14
- package/dist/builtin-memory/internal/memory-loading.md +45 -0
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
- package/dist/builtin-memory/internal/plugins.md +61 -45
- package/dist/builtin-memory/planning.md +4 -7
- package/dist/builtin-memory/spec.md +9 -12
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
- package/dist/cli.js +1 -1
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
- package/dist/clients/attach/__tests__/context-message.test.js +31 -20
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
- package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
- package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
- package/dist/clients/attach/render/chat-view.d.ts +20 -20
- package/dist/clients/attach/render/chat-view.js +88 -89
- package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
- package/dist/clients/attach/render/condensed-history.js +60 -0
- package/dist/clients/attach/render/context-message.js +9 -14
- package/dist/clients/attach/render/crtr-output.d.ts +2 -1
- package/dist/clients/attach/render/crtr-output.js +4 -4
- package/dist/clients/attach/render/edit-diff.d.ts +21 -6
- package/dist/clients/attach/render/edit-diff.js +90 -91
- package/dist/clients/attach/render/tool-calls.d.ts +14 -18
- package/dist/clients/attach/render/tool-calls.js +66 -41
- package/dist/clients/attach/session/bindings.d.ts +5 -3
- package/dist/clients/attach/session/bindings.js +7 -13
- package/dist/clients/attach/session/keys.d.ts +2 -0
- package/dist/clients/attach/session/keys.js +36 -37
- package/dist/clients/attach/session/profile-files.d.ts +8 -0
- package/dist/clients/attach/session/profile-files.js +157 -0
- package/dist/clients/attach/viewer.js +530 -528
- package/dist/commands/memory/lint.js +1 -1
- package/dist/commands/memory/write.js +1 -1
- package/dist/commands/node.js +2 -2
- package/dist/commands/pkg/plugin-inspect.js +6 -7
- package/dist/commands/pkg/plugin-manage.d.ts +1 -1
- package/dist/commands/pkg/plugin-manage.js +131 -19
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +6 -11
- package/dist/commands/profile/env.js +3 -3
- package/dist/commands/sys/config.js +17 -76
- package/dist/commands/sys/doctor.js +5 -91
- package/dist/commands/sys/setup-wizard.d.ts +9 -11
- package/dist/commands/sys/setup-wizard.js +47 -81
- package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
- package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
- package/dist/core/__tests__/command-plugins.test.js +36 -16
- package/dist/core/__tests__/review-model-floor.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -1
- package/dist/core/command-manifests/manifest.d.ts +24 -0
- package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
- package/dist/core/command-manifests/registry.d.ts +1 -2
- package/dist/core/command-manifests/registry.js +2 -2
- package/dist/core/command-manifests/schema.d.ts +11 -13
- package/dist/core/command-manifests/schema.js +53 -192
- package/dist/core/command-plugins/compose.d.ts +0 -6
- package/dist/core/command-plugins/compose.js +28 -75
- package/dist/core/command-plugins/discovery.d.ts +25 -58
- package/dist/core/command-plugins/discovery.js +152 -259
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +48 -0
- package/dist/core/command-plugins/store.d.ts +16 -0
- package/dist/core/command-plugins/store.js +64 -0
- package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
- package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
- package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
- package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
- package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
- package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
- package/dist/core/command.d.ts +8 -9
- package/dist/core/command.js +6 -9
- package/dist/core/config.js +6 -10
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/io.d.ts +1 -1
- package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
- package/dist/core/keybindings/attach-control.d.ts +37 -0
- package/dist/core/keybindings/attach-control.js +38 -0
- package/dist/core/keybindings/catalog.d.ts +5 -4
- package/dist/core/keybindings/catalog.js +16 -8
- package/dist/core/keybindings/index.d.ts +1 -0
- package/dist/core/keybindings/index.js +1 -0
- package/dist/core/keybindings/types.d.ts +1 -1
- package/dist/core/preview-registry.js +41 -74
- package/dist/core/runtime/bearings.d.ts +2 -5
- package/dist/core/runtime/bearings.js +2 -5
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +4 -3
- package/dist/core/runtime/situational-context.d.ts +1 -1
- package/dist/core/runtime/situational-context.js +1 -1
- package/dist/core/runtime/spawn.js +9 -9
- package/dist/core/runtime/tmux.js +29 -2
- package/dist/core/scope.d.ts +0 -5
- package/dist/core/scope.js +0 -10
- package/dist/core/user-settings.d.ts +193 -0
- package/dist/core/user-settings.js +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/pi-extensions/canvas-stophook.js +3 -2
- package/dist/pi-extensions/canvas-tool-guide.js +4 -1
- package/dist/shared/generated-context.d.ts +34 -0
- package/dist/shared/generated-context.js +98 -0
- package/dist/types.d.ts +23 -16
- package/dist/types.js +3 -14
- package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
- package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/public-api.md +1 -0
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
- package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
- package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
- package/dist/builtin-memory/internal/workflow-codification.md +0 -82
- package/dist/builtin-memory/product.md +0 -80
- package/dist/clients/attach/render/frozen-history.js +0 -100
- package/dist/commands/pkg/cli-inspect.d.ts +0 -17
- package/dist/commands/pkg/cli-inspect.js +0 -190
- package/dist/commands/pkg/cli-manage.d.ts +0 -3
- package/dist/commands/pkg/cli-manage.js +0 -206
- package/dist/commands/pkg/cli.d.ts +0 -1
- package/dist/commands/pkg/cli.js +0 -14
- package/dist/core/configured-clis/cache.d.ts +0 -16
- package/dist/core/configured-clis/cache.js +0 -57
- package/dist/core/configured-clis/compose.d.ts +0 -14
- package/dist/core/configured-clis/compose.js +0 -60
- package/dist/core/configured-clis/discovery.d.ts +0 -47
- package/dist/core/configured-clis/discovery.js +0 -173
- package/dist/core/configured-clis/manifest.d.ts +0 -24
- package/dist/core/configured-clis/registration.d.ts +0 -40
- package/dist/core/configured-clis/registration.js +0 -201
- package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
// environment and owns its own time budget (no crtr timeout) — this is a trust
|
|
9
9
|
// boundary, not a sandbox (installed code can already read the caller's files).
|
|
10
10
|
import { spawnSync } from 'node:child_process';
|
|
11
|
-
import { CrtrError } from '
|
|
12
|
-
import { diag } from '
|
|
13
|
-
import { ExitCode } from '
|
|
11
|
+
import { CrtrError } from '../../errors.js';
|
|
12
|
+
import { diag } from '../../io.js';
|
|
13
|
+
import { ExitCode } from '../../../types.js';
|
|
14
14
|
const MAX_STDOUT = 10 * 1024 * 1024; // 10 MiB hard cap on plugin stdout.
|
|
15
15
|
const RESERVED_CODES = new Set(['internal', 'unknown_path', 'command_collision', 'plugin_protocol_error']);
|
|
16
16
|
// Strict snake_case: no leading/trailing underscore, no consecutive underscores.
|
|
@@ -18,7 +18,7 @@ const SNAKE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
|
|
|
18
18
|
const NEXT = 'Run `crtr pkg plugin show` or `crtr sys doctor` to inspect the plugin.';
|
|
19
19
|
/** Validates a result object against declared output field descriptors.
|
|
20
20
|
* Returns null if valid; otherwise returns a { field, receivedType } error.
|
|
21
|
-
* Transport-neutral: used by
|
|
21
|
+
* Transport-neutral: used by exec and HTTP transport adapters. */
|
|
22
22
|
export function validateDeclaredResult(output, result) {
|
|
23
23
|
for (const f of output) {
|
|
24
24
|
const present = Object.prototype.hasOwnProperty.call(result, f.name) && result[f.name] !== undefined && result[f.name] !== null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HttpPluginFetchTarget } from '../endpoint.js';
|
|
2
|
+
import type { HttpPluginManifestStore } from '../store.js';
|
|
2
3
|
/** Result of a successful manifest fetch. */
|
|
3
4
|
export interface FetchSuccess {
|
|
4
5
|
status: 200;
|
|
@@ -11,10 +12,10 @@ export interface FetchFailure {
|
|
|
11
12
|
message: string;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
|
-
* Fetch a
|
|
15
|
+
* Fetch a HTTP-transport plugin manifest from the registered endpoint.
|
|
15
16
|
*
|
|
16
17
|
* Implements the core authenticated GET primitive for T0/T2/T4 fetches:
|
|
17
|
-
* - Reads bearer token from `
|
|
18
|
+
* - Reads bearer token from `authEnv` env var at invocation time
|
|
18
19
|
* - Fails with `auth_env_missing` USAGE before network if env var is named but empty
|
|
19
20
|
* - Performs one GET request with `Accept: application/json` header
|
|
20
21
|
* - Attaches `Authorization: Bearer <token>` header when auth var is configured
|
|
@@ -28,24 +29,14 @@ export interface FetchFailure {
|
|
|
28
29
|
*
|
|
29
30
|
* No retries, no conditional request (ETag/If-None-Match), no 304 handling.
|
|
30
31
|
*/
|
|
31
|
-
export declare function
|
|
32
|
+
export declare function fetchHttpPluginManifest(registration: HttpPluginFetchTarget): Promise<FetchResult>;
|
|
32
33
|
/**
|
|
33
34
|
* Explicit refresh: unconditional fetch and replace stored manifest.
|
|
34
35
|
*
|
|
35
|
-
* Called when user runs `crtr pkg
|
|
36
|
-
* regardless of
|
|
37
|
-
* On failure, preserves existing
|
|
36
|
+
* Called when user runs `crtr pkg plugin update <name>`. Always fetches
|
|
37
|
+
* regardless of stored-manifest state, and replaces it on success.
|
|
38
|
+
* On failure, preserves existing manifest bytes and throws CrtrError.
|
|
38
39
|
*
|
|
39
40
|
* Returns the raw manifest bytes on success.
|
|
40
41
|
*/
|
|
41
|
-
export declare function
|
|
42
|
-
/**
|
|
43
|
-
* Register-time fetch: fetch and store manifest during registration.
|
|
44
|
-
*
|
|
45
|
-
* Called when user runs `crtr pkg cli register`. Performs one authenticated
|
|
46
|
-
* GET and stores exact bytes. On failure, preserves any prior cached manifest
|
|
47
|
-
* and returns the error (does not throw).
|
|
48
|
-
*
|
|
49
|
-
* Returns a discriminated result: success with bytes, or typed failure.
|
|
50
|
-
*/
|
|
51
|
-
export declare function registerFetchConfiguredCli(registration: ConfiguredCliRegistration): Promise<FetchSuccess | FetchFailure>;
|
|
42
|
+
export declare function refreshHttpPlugin(target: HttpPluginFetchTarget, store: HttpPluginManifestStore): Promise<Uint8Array>;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { URL } from 'node:url';
|
|
2
2
|
import { request as httpsRequest } from 'node:https';
|
|
3
3
|
import { request as httpRequest } from 'node:http';
|
|
4
|
-
import {
|
|
5
|
-
import { general, network, usage } from '
|
|
4
|
+
import { writeHttpPluginManifest } from '../store.js';
|
|
5
|
+
import { general, network, usage } from '../../errors.js';
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
// Core fetch primitive
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
/**
|
|
10
|
-
* Fetch a
|
|
10
|
+
* Fetch a HTTP-transport plugin manifest from the registered endpoint.
|
|
11
11
|
*
|
|
12
12
|
* Implements the core authenticated GET primitive for T0/T2/T4 fetches:
|
|
13
|
-
* - Reads bearer token from `
|
|
13
|
+
* - Reads bearer token from `authEnv` env var at invocation time
|
|
14
14
|
* - Fails with `auth_env_missing` USAGE before network if env var is named but empty
|
|
15
15
|
* - Performs one GET request with `Accept: application/json` header
|
|
16
16
|
* - Attaches `Authorization: Bearer <token>` header when auth var is configured
|
|
@@ -24,15 +24,15 @@ import { general, network, usage } from '../errors.js';
|
|
|
24
24
|
*
|
|
25
25
|
* No retries, no conditional request (ETag/If-None-Match), no 304 handling.
|
|
26
26
|
*/
|
|
27
|
-
export async function
|
|
27
|
+
export async function fetchHttpPluginManifest(registration) {
|
|
28
28
|
// Check auth env var and fail before network if named but empty
|
|
29
29
|
let token;
|
|
30
|
-
if (registration.
|
|
31
|
-
token = process.env[registration.
|
|
30
|
+
if (registration.authEnv) {
|
|
31
|
+
token = process.env[registration.authEnv];
|
|
32
32
|
if (!token) {
|
|
33
33
|
return {
|
|
34
34
|
status: 'auth_env_missing',
|
|
35
|
-
message: `Environment variable '${registration.
|
|
35
|
+
message: `Environment variable '${registration.authEnv}' is required but not set or empty.`,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -112,15 +112,15 @@ export async function fetchConfiguredCliManifest(registration) {
|
|
|
112
112
|
/**
|
|
113
113
|
* Explicit refresh: unconditional fetch and replace stored manifest.
|
|
114
114
|
*
|
|
115
|
-
* Called when user runs `crtr pkg
|
|
116
|
-
* regardless of
|
|
117
|
-
* On failure, preserves existing
|
|
115
|
+
* Called when user runs `crtr pkg plugin update <name>`. Always fetches
|
|
116
|
+
* regardless of stored-manifest state, and replaces it on success.
|
|
117
|
+
* On failure, preserves existing manifest bytes and throws CrtrError.
|
|
118
118
|
*
|
|
119
119
|
* Returns the raw manifest bytes on success.
|
|
120
120
|
*/
|
|
121
|
-
export async function
|
|
121
|
+
export async function refreshHttpPlugin(target, store) {
|
|
122
122
|
// Unconditional fetch
|
|
123
|
-
const result = await
|
|
123
|
+
const result = await fetchHttpPluginManifest(target);
|
|
124
124
|
if (result.status !== 200) {
|
|
125
125
|
if (result.status === 'auth_env_missing') {
|
|
126
126
|
throw usage(result.message);
|
|
@@ -130,27 +130,7 @@ export async function refreshConfiguredCli(registration) {
|
|
|
130
130
|
}
|
|
131
131
|
throw general(result.message);
|
|
132
132
|
}
|
|
133
|
-
// Write to
|
|
134
|
-
|
|
133
|
+
// Write to the resolved HTTP manifest store.
|
|
134
|
+
writeHttpPluginManifest(store, result.raw);
|
|
135
135
|
return result.raw;
|
|
136
136
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Register-time fetch: fetch and store manifest during registration.
|
|
139
|
-
*
|
|
140
|
-
* Called when user runs `crtr pkg cli register`. Performs one authenticated
|
|
141
|
-
* GET and stores exact bytes. On failure, preserves any prior cached manifest
|
|
142
|
-
* and returns the error (does not throw).
|
|
143
|
-
*
|
|
144
|
-
* Returns a discriminated result: success with bytes, or typed failure.
|
|
145
|
-
*/
|
|
146
|
-
export async function registerFetchConfiguredCli(registration) {
|
|
147
|
-
// Perform fetch
|
|
148
|
-
const result = await fetchConfiguredCliManifest(registration);
|
|
149
|
-
if (result.status !== 200) {
|
|
150
|
-
// On failure, preserve existing cache and return error
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
|
-
// On success, write to cache
|
|
154
|
-
writeConfiguredCliCacheManifest(registration, result.raw);
|
|
155
|
-
return result;
|
|
156
|
-
}
|
package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts}
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { InputParam, Field } from '
|
|
2
|
-
import type { RestMapping, ManifestTimeouts } from '
|
|
3
|
-
import type { LeafRunContext } from '
|
|
4
|
-
export interface
|
|
1
|
+
import type { InputParam, Field } from '../../help.js';
|
|
2
|
+
import type { RestMapping, ManifestTimeouts } from '../../command-manifests/schema.js';
|
|
3
|
+
import type { LeafRunContext } from '../../command.js';
|
|
4
|
+
export interface HttpInvokerSpec {
|
|
5
5
|
/** The effective registration: local name, manifest endpoint, optional auth env var. */
|
|
6
6
|
registration: {
|
|
7
7
|
name: string;
|
|
8
8
|
endpoint: string;
|
|
9
|
-
|
|
9
|
+
authEnv?: string;
|
|
10
10
|
};
|
|
11
11
|
/** Manifest-level REST base override (§4.2); falls back to the endpoint origin. */
|
|
12
12
|
baseUrl?: string;
|
|
@@ -22,7 +22,7 @@ export interface ConfiguredCliInvokerSpec {
|
|
|
22
22
|
commandPath: string[];
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Execute one
|
|
25
|
+
* Execute one HTTP-transport plugin leaf. Unary leaves return the validated response
|
|
26
26
|
* object (crtr's ordinary dispatcher renders it, or mirrors it under --json).
|
|
27
27
|
* Streaming leaves relay NDJSON frames to stdout incrementally and return void.
|
|
28
28
|
*
|
|
@@ -33,4 +33,4 @@ export interface ConfiguredCliInvokerSpec {
|
|
|
33
33
|
* process.exitCode = NETWORK — it never throws into handle() and never writes a
|
|
34
34
|
* second result/error object to stdout.
|
|
35
35
|
*/
|
|
36
|
-
export declare function
|
|
36
|
+
export declare function invokeHttpLeaf(spec: HttpInvokerSpec, input: Record<string, unknown>, context?: LeafRunContext): Promise<Record<string, unknown> | void>;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `executeExternalLeaf` spawn — but the trust surface is definition + HTTP, so
|
|
7
|
-
// there is nothing to spawn and nothing to sandbox.
|
|
1
|
+
// HTTP-transport plugin invoker — the execution seam for a validated composed
|
|
2
|
+
// HTTP leaf. On explicit leaf invocation only (never on help or discovery), it
|
|
3
|
+
// turns a leaf's `rest` mapping and parsed input into one native HTTP request
|
|
4
|
+
// and maps the response onto crtr's output, error, and exit machinery. Its
|
|
5
|
+
// trust surface is the manifest definition plus HTTP, so it spawns nothing.
|
|
8
6
|
//
|
|
9
7
|
// Ownership boundary: this module owns request construction, local-file
|
|
10
8
|
// materialization, bearer attachment, the native HTTP lifecycle with three
|
|
@@ -17,10 +15,10 @@ import { readFileSync, statSync } from 'node:fs';
|
|
|
17
15
|
import { resolve as resolvePath } from 'node:path';
|
|
18
16
|
import { request as httpRequest } from 'node:http';
|
|
19
17
|
import { request as httpsRequest } from 'node:https';
|
|
20
|
-
import { validateDeclaredResult } from '
|
|
21
|
-
import { CrtrError } from '
|
|
22
|
-
import { diag, writeStdout } from '
|
|
23
|
-
import { ExitCode } from '
|
|
18
|
+
import { validateDeclaredResult } from './exec-invoke.js';
|
|
19
|
+
import { CrtrError } from '../../errors.js';
|
|
20
|
+
import { diag, writeStdout } from '../../io.js';
|
|
21
|
+
import { ExitCode } from '../../../types.js';
|
|
24
22
|
// ---------------------------------------------------------------------------
|
|
25
23
|
// Constants
|
|
26
24
|
// ---------------------------------------------------------------------------
|
|
@@ -38,7 +36,7 @@ class StreamIdleError extends Error {
|
|
|
38
36
|
// Public entry
|
|
39
37
|
// ---------------------------------------------------------------------------
|
|
40
38
|
/**
|
|
41
|
-
* Execute one
|
|
39
|
+
* Execute one HTTP-transport plugin leaf. Unary leaves return the validated response
|
|
42
40
|
* object (crtr's ordinary dispatcher renders it, or mirrors it under --json).
|
|
43
41
|
* Streaming leaves relay NDJSON frames to stdout incrementally and return void.
|
|
44
42
|
*
|
|
@@ -49,7 +47,7 @@ class StreamIdleError extends Error {
|
|
|
49
47
|
* process.exitCode = NETWORK — it never throws into handle() and never writes a
|
|
50
48
|
* second result/error object to stdout.
|
|
51
49
|
*/
|
|
52
|
-
export async function
|
|
50
|
+
export async function invokeHttpLeaf(spec, input, context) {
|
|
53
51
|
const plan = buildRequestPlan(spec, input, context);
|
|
54
52
|
if (spec.rest.streaming === true) {
|
|
55
53
|
await requestStreaming(spec, plan);
|
|
@@ -93,14 +91,14 @@ function buildRequestPlan(spec, input, context) {
|
|
|
93
91
|
// 5: auth — read the env var immediately before request creation. Missing or
|
|
94
92
|
// empty fails pre-network; the value never appears in errors or evidence.
|
|
95
93
|
let authorization;
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
const token = process.env[
|
|
94
|
+
const authEnv = spec.registration.authEnv;
|
|
95
|
+
if (authEnv !== undefined && authEnv !== '') {
|
|
96
|
+
const token = process.env[authEnv];
|
|
99
97
|
if (token === undefined || token === '') {
|
|
100
|
-
throw new CrtrError('auth_env_missing', `${label(spec)}: environment variable ${
|
|
101
|
-
field:
|
|
102
|
-
received:
|
|
103
|
-
next: `Export ${
|
|
98
|
+
throw new CrtrError('auth_env_missing', `${label(spec)}: environment variable ${authEnv} is not set or empty`, ExitCode.USAGE, {
|
|
99
|
+
field: authEnv,
|
|
100
|
+
received: authEnv,
|
|
101
|
+
next: `Export ${authEnv} with the bearer token, then retry.`,
|
|
104
102
|
});
|
|
105
103
|
}
|
|
106
104
|
authorization = `Bearer ${token}`;
|
|
@@ -510,20 +508,20 @@ function deriveExit(status) {
|
|
|
510
508
|
function protocolError(spec, received, expected) {
|
|
511
509
|
return new CrtrError('cli_protocol_error', `${label(spec)}: ${received}. Expected ${expected}.`, ExitCode.GENERAL, {
|
|
512
510
|
received,
|
|
513
|
-
next: `Run \`crtr pkg
|
|
511
|
+
next: `Run \`crtr pkg plugin update ${spec.registration.name}\` or inspect the backend response.`,
|
|
514
512
|
});
|
|
515
513
|
}
|
|
516
514
|
function unreachable(spec, plan, detail) {
|
|
517
515
|
return new CrtrError('cli_unreachable', `${label(spec)}: ${detail}`, ExitCode.NETWORK, {
|
|
518
516
|
received: plan.url.origin,
|
|
519
|
-
next: `Check connectivity to ${plan.url.host} and retry; run \`crtr pkg
|
|
517
|
+
next: `Check connectivity to ${plan.url.host} and retry; run \`crtr pkg plugin update ${spec.registration.name}\` if the command surface changed.`,
|
|
520
518
|
});
|
|
521
519
|
}
|
|
522
520
|
// ---------------------------------------------------------------------------
|
|
523
521
|
// Small helpers
|
|
524
522
|
// ---------------------------------------------------------------------------
|
|
525
523
|
function label(spec) {
|
|
526
|
-
return `
|
|
524
|
+
return `HTTP-transport plugin "${spec.registration.name}" command \`${spec.commandPath.join(' ')}\``;
|
|
527
525
|
}
|
|
528
526
|
function errCode(e) {
|
|
529
527
|
const err = e;
|
package/dist/core/command.d.ts
CHANGED
|
@@ -160,15 +160,14 @@ export interface ParseArgvOptions {
|
|
|
160
160
|
export declare function parseArgv(params: InputParam[], tokens: string[], options?: ParseArgvOptions): Promise<Record<string, unknown>>;
|
|
161
161
|
/** Optional seams for `runCli`. */
|
|
162
162
|
export interface RunCliOptions {
|
|
163
|
-
/** The
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* recognized core first token is dispatched with zero configured-CLI I/O. */
|
|
163
|
+
/** The one unknown-first-token retry seam. Called exactly once only when an
|
|
164
|
+
* unknown first token misses at root (walk stopped at root, `path` empty,
|
|
165
|
+
* on a real non-help token), where absent HTTP-plugin stores may hydrate.
|
|
166
|
+
* A deeper unknown token under a known command (`path` non-empty) never
|
|
167
|
+
* calls it. It returns a freshly rebuilt root when hydration produced new
|
|
168
|
+
* commands to re-walk the original argv against, otherwise undefined to
|
|
169
|
+
* surface the plain `unknown_path` error. crtr's core fast path passes no
|
|
170
|
+
* options, so a recognized core first token has zero HTTP-plugin I/O. */
|
|
172
171
|
onUnknownFirstTokenMiss?: () => Promise<RootDef | undefined>;
|
|
173
172
|
}
|
|
174
173
|
export declare function runCli(root: RootDef, argv: string[], options?: RunCliOptions): Promise<void>;
|
package/dist/core/command.js
CHANGED
|
@@ -489,7 +489,7 @@ export async function parseArgv(params, tokens, options) {
|
|
|
489
489
|
// supply takes its value from that environment variable when it is set and
|
|
490
490
|
// non-empty. It is recorded as PROVIDED — the caller's environment is a real
|
|
491
491
|
// source, not a display default — so it satisfies the required check below and
|
|
492
|
-
// ships through the
|
|
492
|
+
// ships through the HTTP transport's presence rule (a static `default`
|
|
493
493
|
// deliberately does neither).
|
|
494
494
|
for (const p of params) {
|
|
495
495
|
if (p.kind !== 'flag' && p.kind !== 'positional')
|
|
@@ -564,14 +564,11 @@ export async function runCli(root, argv, options) {
|
|
|
564
564
|
}
|
|
565
565
|
let { node, path, remaining } = walk(root, tokens);
|
|
566
566
|
try {
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
//
|
|
572
|
-
// re-walk the original argv once, then dispatch proceeds normally against the
|
|
573
|
-
// re-resolved node. Kept inside the try so a seam throw routes through the
|
|
574
|
-
// same error handler as everything else.
|
|
567
|
+
// An unknown first token that misses at root is the only site absent
|
|
568
|
+
// HTTP-plugin stores may hydrate. A deeper unknown token under a known
|
|
569
|
+
// command never triggers it. Invoke the seam once; if it hydrated commands,
|
|
570
|
+
// re-walk the original argv against the rebuilt root. Keeping it inside the
|
|
571
|
+
// try sends any seam failure through the ordinary error handler.
|
|
575
572
|
if (options?.onUnknownFirstTokenMiss !== undefined &&
|
|
576
573
|
node.kind === 'root' &&
|
|
577
574
|
path.length === 0 &&
|
package/dist/core/config.js
CHANGED
|
@@ -6,6 +6,7 @@ import { emitEvent } from './events/emit.js';
|
|
|
6
6
|
import { atomicWriteJson, readJsonIfExists, writeJson, ensureDir } from './fs-utils.js';
|
|
7
7
|
import { scopeRoot, requireScopeRoot, findProjectScopeRoots } from './scope.js';
|
|
8
8
|
import { profileRoot } from './profiles/manifest.js';
|
|
9
|
+
import { SCOPE_CONFIG_KEYS } from './user-settings.js';
|
|
9
10
|
function configPathFor(root) {
|
|
10
11
|
return join(root, CONFIG_FILE);
|
|
11
12
|
}
|
|
@@ -418,7 +419,6 @@ function mergeConfig(partial) {
|
|
|
418
419
|
const schema_version = defaults.schema_version;
|
|
419
420
|
const marketplaces = partial.marketplaces === undefined ? {} : partial.marketplaces;
|
|
420
421
|
const plugins = partial.plugins === undefined ? {} : partial.plugins;
|
|
421
|
-
const configuredClis = partial.configuredClis === undefined ? {} : partial.configuredClis;
|
|
422
422
|
const au = partial.auto_update;
|
|
423
423
|
const rawInterval = au && typeof au.interval_hours === 'number' ? au.interval_hours : undefined;
|
|
424
424
|
const interval_hours = rawInterval !== undefined && Number.isFinite(rawInterval) && rawInterval >= 0
|
|
@@ -431,6 +431,9 @@ function mergeConfig(partial) {
|
|
|
431
431
|
};
|
|
432
432
|
const rawMaxPanes = partial.max_panes_per_window;
|
|
433
433
|
const max_panes_per_window = normalizePositiveInteger(rawMaxPanes, defaults.max_panes_per_window);
|
|
434
|
+
const auto_open_child_viewers = typeof partial.auto_open_child_viewers === 'boolean'
|
|
435
|
+
? partial.auto_open_child_viewers
|
|
436
|
+
: defaults.auto_open_child_viewers;
|
|
434
437
|
const brokerThresholds = mergeBrokerThresholds(partial.brokerThresholds, defaults.brokerThresholds);
|
|
435
438
|
const completion_bell = typeof partial.completion_bell === 'boolean' ? partial.completion_bell : defaults.completion_bell;
|
|
436
439
|
const live_cycles = normalizePositiveInteger(partial.live_cycles, defaults.live_cycles);
|
|
@@ -445,7 +448,7 @@ function mergeConfig(partial) {
|
|
|
445
448
|
const kinds = mergeKinds(partial.kinds);
|
|
446
449
|
const remoteCanvas = mergeRemoteCanvas(partial.remoteCanvas);
|
|
447
450
|
const spawnEnv = mergeSpawnEnv(partial.spawnEnv, defaults.spawnEnv?.allow);
|
|
448
|
-
return { schema_version, marketplaces, plugins,
|
|
451
|
+
return { schema_version, marketplaces, plugins, auto_update, max_panes_per_window, auto_open_child_viewers, brokerThresholds, completion_bell, live_cycles, condensed_history, fold_finished_tools, keybindings, modelLadders, ...(Object.keys(modelRoutes).length > 0 ? { modelRoutes } : {}), ...(modelRouting !== undefined ? { modelRouting } : {}), kinds, remoteCanvas, spawnEnv };
|
|
449
452
|
}
|
|
450
453
|
/** Raw (un-defaulted) partial config for one scope, or null if the scope has
|
|
451
454
|
* no root or no config.json. Used by `readMergedLaunchConfig` to layer
|
|
@@ -580,13 +583,6 @@ export function subKindsAvailableTo(kind) {
|
|
|
580
583
|
}
|
|
581
584
|
return out.sort((a, b) => a.kind.localeCompare(b.kind));
|
|
582
585
|
}
|
|
583
|
-
/** Every top-level `ScopeConfig` key, diffed one-by-one by `updateConfig` so
|
|
584
|
-
* an untouched key is never rewritten. */
|
|
585
|
-
const CONFIG_TOP_LEVEL_KEYS = [
|
|
586
|
-
'schema_version', 'marketplaces', 'plugins', 'configuredClis', 'auto_update',
|
|
587
|
-
'max_panes_per_window', 'brokerThresholds', 'completion_bell', 'live_cycles', 'condensed_history', 'fold_finished_tools', 'keybindings', 'modelLadders', 'kinds',
|
|
588
|
-
'remoteCanvas', 'spawnEnv',
|
|
589
|
-
];
|
|
590
586
|
/** The SPARSE `modelLadders` to persist after a config mutation: the raw
|
|
591
587
|
* on-disk sparse ladder (absent rungs stay absent) plus ONLY the specific
|
|
592
588
|
* rungs / `defaultProvider` the mutation actually changed away from the
|
|
@@ -632,7 +628,7 @@ function mutateConfigAtPath(path, mutate) {
|
|
|
632
628
|
const cfg = mergeConfig(raw);
|
|
633
629
|
mutate(cfg);
|
|
634
630
|
const partial = { ...raw };
|
|
635
|
-
for (const key of
|
|
631
|
+
for (const key of SCOPE_CONFIG_KEYS) {
|
|
636
632
|
if (key === 'modelLadders')
|
|
637
633
|
continue;
|
|
638
634
|
if (!isDeepStrictEqual(cfg[key], baseline[key])) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Validate an environment variable identifier without reading its value. */
|
|
2
|
+
export function validateEnvVarName(name) {
|
|
3
|
+
const errors = [];
|
|
4
|
+
if (typeof name !== 'string')
|
|
5
|
+
errors.push('env var name must be a string');
|
|
6
|
+
else if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
|
|
7
|
+
errors.push('env var name must start with letter/underscore, followed by alphanumeric/underscore');
|
|
8
|
+
return { isValid: errors.length === 0, errors };
|
|
9
|
+
}
|
package/dist/core/io.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface ErrorPayload {
|
|
|
12
12
|
message: string;
|
|
13
13
|
received?: unknown;
|
|
14
14
|
field?: string;
|
|
15
|
-
/** Transport status of a failed backend call (
|
|
15
|
+
/** Transport status of a failed backend call (HTTP-transport plugins). It is kept out
|
|
16
16
|
* of `received` on purpose: `received` names the caller's offending VALUE, and
|
|
17
17
|
* reporting an HTTP status there is a false claim about what was sent. */
|
|
18
18
|
http_status?: number;
|
|
@@ -3,27 +3,43 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { BINDING_CATALOG, KeybindingConcurrentEditError, KeybindingValidationError, SAFETY_REQUIREMENTS, canonicalTerminalStroke, formatStroke, matchesTerminalInput, normalizeGesture, normalizeSparseOverrides, persistUserKeybindings, resolveKeybindings, resolveUserKeybindings, } from '../index.js';
|
|
6
|
+
import { ATTACH_CONTROL_BINDINGS, BINDING_CATALOG, KeybindingConcurrentEditError, KeybindingValidationError, SAFETY_REQUIREMENTS, canonicalTerminalStroke, formatStroke, matchesTerminalInput, normalizeGesture, normalizeSparseOverrides, persistUserKeybindings, resolveKeybindings, resolveUserKeybindings, } from '../index.js';
|
|
7
7
|
import { readConfig } from '../../config.js';
|
|
8
8
|
const typeCheckedBindingId = 'crtr.graph.down';
|
|
9
9
|
// @ts-expect-error The catalog type rejects IDs that are not built in.
|
|
10
10
|
const rejectedBindingId = 'crtr.graph.not-real';
|
|
11
|
+
// @ts-expect-error The Attach submenu opener has no tmux-global pair.
|
|
12
|
+
const rejectedAttachGlobalId = 'crtr.tmux.menu-global.attach';
|
|
11
13
|
void typeCheckedBindingId;
|
|
12
14
|
void rejectedBindingId;
|
|
15
|
+
void rejectedAttachGlobalId;
|
|
13
16
|
function ids(prefix, actions) {
|
|
14
17
|
return actions.map((action) => `${prefix}.${action}`);
|
|
15
18
|
}
|
|
16
19
|
function pairedMenuIds(actions) {
|
|
17
20
|
return actions.flatMap((action) => [`crtr.tmux.menu.${action}`, `crtr.tmux.menu-global.${action}`]);
|
|
18
21
|
}
|
|
22
|
+
function pairedAttachIds(pairs) {
|
|
23
|
+
return pairs.flatMap(([menuAction, directId]) => [`crtr.tmux.menu.attach.${menuAction}`, directId]);
|
|
24
|
+
}
|
|
19
25
|
const REQUIRED_IDS = [
|
|
20
26
|
'crtr.tmux.menu.open',
|
|
27
|
+
'crtr.tmux.menu.attach',
|
|
21
28
|
...pairedMenuIds(['promote', 'resume', 'fork', 'demote', 'detach', 'close-subtree', 'background-bash', 'file-review', 'pin', 'show', 'view-settings', 'fold-tools', 'metadata', 'context', 'providers', 'graph', 'focus-manager', 'copy-id', 'reload-viewer', 'issues', 'issue.idea', 'issue.bug']),
|
|
22
29
|
...ids('crtr.tmux.node', ['next', 'previous']),
|
|
23
30
|
'crtr.tmux.inbox.toggle',
|
|
24
31
|
...ids('crtr.tmux.view', ['next', 'previous']),
|
|
25
|
-
...
|
|
26
|
-
|
|
32
|
+
...pairedAttachIds([
|
|
33
|
+
['detach', 'crtr.attach.detach'],
|
|
34
|
+
['clear-or-detach', 'crtr.attach.clear-or-detach'],
|
|
35
|
+
['mode-cycle', 'crtr.mode.cycle'],
|
|
36
|
+
['graph.toggle', 'crtr.attach.graph.toggle'],
|
|
37
|
+
['model-ladder.next', 'crtr.attach.model-ladder.next'],
|
|
38
|
+
['model-ladder.previous', 'crtr.attach.model-ladder.previous'],
|
|
39
|
+
['command.inspect', 'crtr.attach.command.inspect'],
|
|
40
|
+
['file-review', 'crtr.attach.file-review'],
|
|
41
|
+
['profile-files', 'crtr.attach.profile-files'],
|
|
42
|
+
]),
|
|
27
43
|
...ids('crtr.graph', ['down', 'up', 'first', 'last', 'collapse-or-manager', 'expand-or-child', 'focus', 'revive', 'focus-manager', 'close-subtree', 'dismiss']),
|
|
28
44
|
...ids('crtr.graph.confirm', ['accept', 'cancel']),
|
|
29
45
|
...ids('crtr.view.host', ['force-quit', 'editor.backspace']),
|
|
@@ -79,6 +95,21 @@ test('catalog is closed, complete, ordered, and has valid collision-free default
|
|
|
79
95
|
for (const gesture of definition.defaults)
|
|
80
96
|
assert.equal(normalizeGesture(gesture, definition), gesture);
|
|
81
97
|
}
|
|
98
|
+
const byId = new Map(BINDING_CATALOG.map((definition) => [definition.id, definition]));
|
|
99
|
+
for (const definition of BINDING_CATALOG) {
|
|
100
|
+
if (definition.pair === undefined)
|
|
101
|
+
continue;
|
|
102
|
+
const paired = byId.get(definition.pair.pairedId);
|
|
103
|
+
assert.equal(paired?.pair?.pairedId, definition.id, `${definition.id} must have a reciprocal pair`);
|
|
104
|
+
assert.notEqual(paired?.pair?.field, definition.pair.field, `${definition.id} pair fields must differ`);
|
|
105
|
+
assert.ok(definition.pair.field === 'menu' || paired?.pair?.field === 'menu', `${definition.id} must pair with a menu selector`);
|
|
106
|
+
}
|
|
107
|
+
for (const { menuId, actionId } of ATTACH_CONTROL_BINDINGS) {
|
|
108
|
+
assert.equal(byId.get(menuId)?.pair?.field, 'menu');
|
|
109
|
+
assert.equal(byId.get(menuId)?.pair?.pairedId, actionId);
|
|
110
|
+
assert.equal(byId.get(actionId)?.pair?.field, 'direct');
|
|
111
|
+
assert.equal(byId.get(actionId)?.pair?.pairedId, menuId);
|
|
112
|
+
}
|
|
82
113
|
const resolved = resolveKeybindings();
|
|
83
114
|
assert.equal(resolved.bindings.size, BINDING_CATALOG.length);
|
|
84
115
|
assert.deepEqual(resolved.diagnostics, []);
|
|
@@ -148,6 +179,12 @@ test('tmux menu selectors collide only inside their own menu', () => {
|
|
|
148
179
|
const selector = resolveKeybindings({ 'crtr.tmux.menu.promote': ['j'] });
|
|
149
180
|
assert.deepEqual(selector.gestures('crtr.tmux.menu.promote'), ['j']);
|
|
150
181
|
assert.deepEqual(selector.diagnostics, []);
|
|
182
|
+
const attachSelector = resolveKeybindings({ 'crtr.tmux.menu.attach.detach': ['o'] });
|
|
183
|
+
assert.deepEqual(attachSelector.gestures('crtr.tmux.menu.attach.detach'), ['o']);
|
|
184
|
+
assert.deepEqual(attachSelector.diagnostics, [], 'attach submenu selectors may reuse top-level menu selectors');
|
|
185
|
+
const attachCollision = resolveKeybindings({ 'crtr.tmux.menu.attach.detach': ['c'] });
|
|
186
|
+
assert.deepEqual(attachCollision.gestures('crtr.tmux.menu.attach.detach'), ['d']);
|
|
187
|
+
assert.ok(attachCollision.diagnostics.some((item) => item.code === 'collision'), 'selectors in the same attach submenu must remain unique');
|
|
151
188
|
});
|
|
152
189
|
test('reachability rejects deliberate unbinding that would trap a context', () => {
|
|
153
190
|
const graph = resolveKeybindings({
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const ATTACH_CONTROL_BINDINGS: readonly [{
|
|
2
|
+
readonly menuId: "crtr.tmux.menu.attach.detach";
|
|
3
|
+
readonly actionId: "crtr.attach.detach";
|
|
4
|
+
}, {
|
|
5
|
+
readonly menuId: "crtr.tmux.menu.attach.clear-or-detach";
|
|
6
|
+
readonly actionId: "crtr.attach.clear-or-detach";
|
|
7
|
+
}, {
|
|
8
|
+
readonly menuId: "crtr.tmux.menu.attach.mode-cycle";
|
|
9
|
+
readonly actionId: "crtr.mode.cycle";
|
|
10
|
+
}, {
|
|
11
|
+
readonly menuId: "crtr.tmux.menu.attach.graph.toggle";
|
|
12
|
+
readonly actionId: "crtr.attach.graph.toggle";
|
|
13
|
+
}, {
|
|
14
|
+
readonly menuId: "crtr.tmux.menu.attach.model-ladder.next";
|
|
15
|
+
readonly actionId: "crtr.attach.model-ladder.next";
|
|
16
|
+
}, {
|
|
17
|
+
readonly menuId: "crtr.tmux.menu.attach.model-ladder.previous";
|
|
18
|
+
readonly actionId: "crtr.attach.model-ladder.previous";
|
|
19
|
+
}, {
|
|
20
|
+
readonly menuId: "crtr.tmux.menu.attach.command.inspect";
|
|
21
|
+
readonly actionId: "crtr.attach.command.inspect";
|
|
22
|
+
}, {
|
|
23
|
+
readonly menuId: "crtr.tmux.menu.attach.file-review";
|
|
24
|
+
readonly actionId: "crtr.attach.file-review";
|
|
25
|
+
}, {
|
|
26
|
+
readonly menuId: "crtr.tmux.menu.attach.profile-files";
|
|
27
|
+
readonly actionId: "crtr.attach.profile-files";
|
|
28
|
+
}];
|
|
29
|
+
export type AttachBindingId = typeof ATTACH_CONTROL_BINDINGS[number]['actionId'];
|
|
30
|
+
export type AttachMenuBindingId = typeof ATTACH_CONTROL_BINDINGS[number]['menuId'];
|
|
31
|
+
export interface ExtractedAttachControlInput {
|
|
32
|
+
readonly actionId: AttachBindingId;
|
|
33
|
+
readonly remaining: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function decodeAttachControlInput(data: string): AttachBindingId | undefined;
|
|
36
|
+
export declare function encodeAttachControlInput(actionId: AttachBindingId): string;
|
|
37
|
+
export declare function extractAttachControlInput(data: string): ExtractedAttachControlInput | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const ATTACH_CONTROL_BINDINGS = [
|
|
2
|
+
{ menuId: 'crtr.tmux.menu.attach.detach', actionId: 'crtr.attach.detach' },
|
|
3
|
+
{ menuId: 'crtr.tmux.menu.attach.clear-or-detach', actionId: 'crtr.attach.clear-or-detach' },
|
|
4
|
+
{ menuId: 'crtr.tmux.menu.attach.mode-cycle', actionId: 'crtr.mode.cycle' },
|
|
5
|
+
{ menuId: 'crtr.tmux.menu.attach.graph.toggle', actionId: 'crtr.attach.graph.toggle' },
|
|
6
|
+
{ menuId: 'crtr.tmux.menu.attach.model-ladder.next', actionId: 'crtr.attach.model-ladder.next' },
|
|
7
|
+
{ menuId: 'crtr.tmux.menu.attach.model-ladder.previous', actionId: 'crtr.attach.model-ladder.previous' },
|
|
8
|
+
{ menuId: 'crtr.tmux.menu.attach.command.inspect', actionId: 'crtr.attach.command.inspect' },
|
|
9
|
+
{ menuId: 'crtr.tmux.menu.attach.file-review', actionId: 'crtr.attach.file-review' },
|
|
10
|
+
{ menuId: 'crtr.tmux.menu.attach.profile-files', actionId: 'crtr.attach.profile-files' },
|
|
11
|
+
];
|
|
12
|
+
const ATTACH_CONTROL_PREFIX = '\x1b_crtr;';
|
|
13
|
+
const ATTACH_CONTROL_SUFFIX = '\x1b\\';
|
|
14
|
+
const ATTACH_BINDING_IDS = new Set(ATTACH_CONTROL_BINDINGS.map(({ actionId }) => actionId));
|
|
15
|
+
export function decodeAttachControlInput(data) {
|
|
16
|
+
if (!data.startsWith(ATTACH_CONTROL_PREFIX) || !data.endsWith(ATTACH_CONTROL_SUFFIX))
|
|
17
|
+
return undefined;
|
|
18
|
+
const actionId = data.slice(ATTACH_CONTROL_PREFIX.length, -ATTACH_CONTROL_SUFFIX.length);
|
|
19
|
+
return ATTACH_BINDING_IDS.has(actionId) ? actionId : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function encodeAttachControlInput(actionId) {
|
|
22
|
+
return `${ATTACH_CONTROL_PREFIX}${actionId}${ATTACH_CONTROL_SUFFIX}`;
|
|
23
|
+
}
|
|
24
|
+
export function extractAttachControlInput(data) {
|
|
25
|
+
let match;
|
|
26
|
+
for (const { actionId } of ATTACH_CONTROL_BINDINGS) {
|
|
27
|
+
const encoded = encodeAttachControlInput(actionId);
|
|
28
|
+
const index = data.indexOf(encoded);
|
|
29
|
+
if (index >= 0 && (match === undefined || index < match.index))
|
|
30
|
+
match = { actionId, encoded, index };
|
|
31
|
+
}
|
|
32
|
+
if (match === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
return {
|
|
35
|
+
actionId: match.actionId,
|
|
36
|
+
remaining: data.slice(0, match.index) + data.slice(match.index + match.encoded.length),
|
|
37
|
+
};
|
|
38
|
+
}
|