@uniqbit/mate-core 0.17.0-canary.8 → 0.17.1
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/package.json +11 -5
- package/src/cli/commands/artifact/finish/index.ts +0 -9
- package/src/cli/commands/artifact/finish/openspec.ts +2 -2
- package/src/cli/commands/artifact/finish/registry.ts +1 -1
- package/src/cli/commands/artifact/pending/discovery.ts +1 -1
- package/src/cli/commands/artifact/pending/index.ts +0 -16
- package/src/cli/commands/cap/graphify.ts +1 -1
- package/src/cli/commands/cap/index-cmd.ts +1 -1
- package/src/cli/commands/cap/tokensave.ts +4 -3
- package/src/cli/commands/companion/companion.ts +4 -0
- package/src/cli/commands/companion/hub.ts +2 -1
- package/src/cli/commands/companion/list.ts +2 -1
- package/src/cli/commands/companion/prepare.ts +2 -2
- package/src/cli/commands/companion/update.ts +334 -0
- package/src/cli/commands/doctor.ts +2 -1
- package/src/cli/commands/install.ts +59 -5
- package/src/cli/commands/launch/shared.ts +2 -1
- package/src/cli/commands/plugin/plugin.ts +4 -0
- package/src/cli/commands/plugin/verify.ts +25 -0
- package/src/cli/commands/report/highlight.ts +1 -4
- package/src/cli/commands/report/renderer.ts +0 -2
- package/src/cli/commands/studio/access.ts +201 -0
- package/src/cli/commands/studio/index.ts +207 -26
- package/src/cli/commands/studio/openspec-cli.ts +1 -1
- package/src/cli/commands/studio/selection.ts +127 -11
- package/src/cli/commands/studio/server.ts +355 -64
- package/src/cli/commands/studio/terminal-assets.ts +50 -0
- package/src/cli/commands/studio/terminal-launch.ts +71 -0
- package/src/cli/commands/studio/terminal.ts +593 -0
- package/src/cli/commands/studio/vault.ts +320 -75
- package/src/cli/commands/studio/views/client.ts +90 -9
- package/src/cli/commands/studio/views/companion-picker.tsx +9 -5
- package/src/cli/commands/studio/views/companion-selector.tsx +11 -7
- package/src/cli/commands/studio/views/document.tsx +411 -107
- package/src/cli/commands/studio/views/model.ts +14 -1
- package/src/cli/commands/studio/views/selection-fields.tsx +14 -0
- package/src/cli/commands/studio/views/styles.ts +106 -13
- package/src/cli/commands/studio/views/terminal.tsx +426 -0
- package/src/cli/commands/studio/views/vault/state.ts +42 -0
- package/src/cli/commands/studio/views/workflow/steps.ts +10 -3
- package/src/cli/commands/update.ts +11 -8
- package/src/cli/companion-link-wizard.tsx +1 -1
- package/src/cli/companion-selector.tsx +1 -1
- package/src/cli/launch-selector.tsx +1 -1
- package/src/cli/main.ts +2 -1
- package/src/cli/usage.ts +7 -3
- package/src/cli/write-json-stdout.ts +2 -2
- package/src/lib/components/spinner.tsx +2 -2
- package/src/lib/components/startup-progress.tsx +1 -1
- package/src/lib/context-mode-package.ts +1 -1
- package/src/lib/opencode-plugin-package.ts +3 -0
- package/src/lib/orchestrator/adapters/opencode.ts +53 -13
- package/src/lib/orchestrator/companion-hub.ts +3 -3
- package/src/lib/orchestrator/companion-registry-store.ts +1 -1
- package/src/lib/orchestrator/config-store.ts +2 -2
- package/src/lib/orchestrator/editor.ts +4 -12
- package/src/lib/orchestrator/engine-guard.ts +9 -1
- package/src/lib/orchestrator/projection-entries.ts +1 -1
- package/src/lib/orchestrator/projection-record.ts +1 -1
- package/src/lib/orchestrator/projection-types.ts +0 -4
- package/src/lib/orchestrator/repo-local-registry.ts +0 -1
- package/src/lib/orchestrator/root-context.ts +1 -1
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -1
- package/src/lib/orchestrator/setup-preflight.ts +1 -1
- package/src/lib/orchestrator/types.ts +1 -1
- package/src/lib/orchestrator/working-repo-projection.ts +1 -1
- package/src/lib/orchestrator/workspace-inventory.ts +2 -2
- package/src/lib/package-paths.ts +1 -11
- package/src/lib/package-root.ts +43 -0
- package/src/lib/prebuilt-workspace.ts +1 -1
- package/src/lib/public-npm.ts +1 -1
- package/src/opencode/companion-hooks.ts +131 -153
- package/src/opencode/index.ts +12 -6
- package/src/opencode/tui.tsx +46 -49
- package/src/playbooks/companion-guidance.ts +1 -28
- package/src/runtime/companion-sync.ts +0 -3
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +2 -1
- package/src/templates/providers/opencode/opencode.json +6 -4
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +500 -408
- package/src/tools/setup/capabilities/context-mode.ts +45 -25
- package/src/tools/setup/capabilities/graphify-shared.ts +3 -3
- package/src/tools/setup/capabilities/graphify.ts +2 -2
- package/src/tools/setup/capabilities/openspec.ts +2 -7
- package/src/tools/setup/capabilities/react-doctor.ts +1 -1
- package/src/tools/setup/capabilities/rtk.ts +2 -2
- package/src/tools/setup/capabilities/tokensave.ts +98 -12
- package/src/tools/setup/context-services.ts +3 -6
- package/src/tools/setup/dynamic-plugins/declarations.ts +47 -0
- package/src/tools/setup/dynamic-plugins/frozen.ts +215 -0
- package/src/tools/setup/dynamic-plugins/hydrate.ts +1 -40
- package/src/tools/setup/dynamic-plugins/loader.ts +16 -0
- package/src/tools/setup/dynamic-plugins/policy.ts +55 -0
- package/src/tools/setup/dynamic-plugins/staging.ts +53 -0
- package/src/tools/setup/dynamic-plugins/verify.ts +70 -0
- package/src/tools/setup/mate.ts +1 -1
- package/src/tools/setup/providers/claude-format.ts +1 -1
- package/src/tools/setup/providers/claude.ts +1 -1
- package/src/tools/setup/providers/opencode-format.ts +220 -25
- package/src/tools/setup/providers/opencode.ts +63 -21
- package/src/tools/setup/runtime-documents.ts +9 -11
- package/src/tools/setup.ts +0 -4
- package/src/cli/commands/studio/views/pairings.tsx +0 -38
- package/src/templates/providers/opencode/tui.json +0 -3
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
1
|
import { spawnSync } from "node:child_process";
|
|
3
2
|
|
|
4
3
|
import { FRAMEWORK_NAME } from "../../framework";
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
isNewer,
|
|
15
14
|
} from "../../lib/update-checker";
|
|
16
15
|
import { confirmPrompt } from "../../lib/components/confirm-prompt";
|
|
16
|
+
import { resolvePackageRoot } from "../../lib/package-root";
|
|
17
17
|
|
|
18
18
|
type InstallResult = ReturnType<typeof installPublicPackageSync>;
|
|
19
19
|
|
|
@@ -38,11 +38,11 @@ export const updateCommandDeps = {
|
|
|
38
38
|
getCurrentVersion,
|
|
39
39
|
isNewer,
|
|
40
40
|
confirmPrompt,
|
|
41
|
-
isNpmManagedInstall: () =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
isNpmManagedInstall: () => {
|
|
42
|
+
const packageName = getUpdateConfig().packageName;
|
|
43
|
+
const packageRoot = resolvePackageRoot(process.argv[1], packageName);
|
|
44
|
+
return packageRoot !== undefined && isPackageInstalledAtNpmGlobalRoot(packageRoot, packageName);
|
|
45
|
+
},
|
|
46
46
|
installLatest: (latest: string): InstallResult => {
|
|
47
47
|
const { packageName, registry } = getUpdateConfig();
|
|
48
48
|
return installPublicPackageSync(`${packageName}@${latest}`, registry);
|
|
@@ -55,14 +55,17 @@ export const updateCommandDeps = {
|
|
|
55
55
|
latestVersion: latest,
|
|
56
56
|
});
|
|
57
57
|
},
|
|
58
|
+
/** Re-runs install from dev (`cli.ts`) or prod (`cli.mjs`/bootstrap) entrypoints so config reconciles during `mate update`. */
|
|
58
59
|
runPostInstall: (skipConfirm: boolean): InstallResult => {
|
|
59
60
|
const entrypoint = process.argv[1];
|
|
60
|
-
if (!entrypoint
|
|
61
|
+
if (!entrypoint || !/(?:^|[\\/])(?:cli\.ts|cli\.mjs|cli-bootstrap\.cjs)$/.test(entrypoint)) {
|
|
61
62
|
return { status: 0, error: undefined } as InstallResult;
|
|
62
63
|
}
|
|
63
64
|
const args = [entrypoint, "install", ...(skipConfirm ? ["--yes"] : [])];
|
|
64
|
-
return
|
|
65
|
+
return updateCommandDeps.spawnPostInstall(process.execPath, args);
|
|
65
66
|
},
|
|
67
|
+
spawnPostInstall: (executable: string, args: string[]): InstallResult =>
|
|
68
|
+
spawnSync(executable, args, { stdio: "inherit" }) as InstallResult,
|
|
66
69
|
warmOpenCodePluginCache: (latest: string) =>
|
|
67
70
|
warmOpenCodePluginCache(latest, process.env, getUpdateConfig().registry),
|
|
68
71
|
};
|
|
@@ -8,7 +8,7 @@ import { WizardHeader } from "../lib/components/wizard-header";
|
|
|
8
8
|
import type { CompanionSource } from "../lib/orchestrator/types";
|
|
9
9
|
import { parseRepoLinkInput } from "./wizard-key-input";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const companionLinkWizardDeps = {
|
|
12
12
|
render: inkRender,
|
|
13
13
|
};
|
|
14
14
|
|
package/src/cli/main.ts
CHANGED
|
@@ -206,10 +206,11 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
206
206
|
case "hub":
|
|
207
207
|
if (!(await gate({ updateGuard: true, notCompanionRoot: true }))) return;
|
|
208
208
|
break;
|
|
209
|
-
// open/tui/sync consume a companion context.
|
|
209
|
+
// open/tui/sync/update consume a companion context.
|
|
210
210
|
case "open":
|
|
211
211
|
case "tui":
|
|
212
212
|
case "sync":
|
|
213
|
+
case "update":
|
|
213
214
|
if (!(await gate({ updateGuard: true, notHubRoot: true, companion: true }))) return;
|
|
214
215
|
break;
|
|
215
216
|
// list and unknown subcommands (which fail inside the command).
|
package/src/cli/usage.ts
CHANGED
|
@@ -10,13 +10,14 @@ export function usage(): string {
|
|
|
10
10
|
`Mate CLI (${packageName})`,
|
|
11
11
|
"",
|
|
12
12
|
"Commands:",
|
|
13
|
-
` ${n} install [--yes]`,
|
|
13
|
+
` ${n} install [--yes] [--frozen-plugins]`,
|
|
14
14
|
` ${n} companion link`,
|
|
15
15
|
` ${n} companion setup`,
|
|
16
16
|
` ${n} companion register [path]`,
|
|
17
17
|
` ${n} companion prepare --from <bundle> [path]`,
|
|
18
18
|
` ${n} companion list`,
|
|
19
19
|
` ${n} companion sync`,
|
|
20
|
+
` ${n} companion update [--yes]`,
|
|
20
21
|
` ${n} companion open`,
|
|
21
22
|
` ${n} companion tui`,
|
|
22
23
|
` ${n} working cleanup`,
|
|
@@ -30,8 +31,10 @@ export function usage(): string {
|
|
|
30
31
|
` ${n} artifact pending [--json]`,
|
|
31
32
|
` ${n} doctor`,
|
|
32
33
|
` ${n} report [--days N] [--input FILE|-] [--json]`,
|
|
33
|
-
` ${n} studio [--writable]`,
|
|
34
|
-
` ${n} studio serve --port <port> [--host <host>] [--writable]`,
|
|
34
|
+
` ${n} studio [--writable] [--terminal] [--detach-timeout <minutes>]`,
|
|
35
|
+
` ${n} studio serve --port <port> [--host <host>] [--writable] [--terminal]`,
|
|
36
|
+
" [--detach-timeout <minutes>] [--token <value>] [--allowed-host <host>]",
|
|
37
|
+
" [--public-origin <origin>] [--companion <path>] [--no-git]",
|
|
35
38
|
` ${n} config`,
|
|
36
39
|
` ${n} claude [args...] (after --: --no-git, --companion, --yes no-op)`,
|
|
37
40
|
` ${n} opencode [args...] (after --: --no-git, --companion, --yes no-op)`,
|
|
@@ -39,6 +42,7 @@ export function usage(): string {
|
|
|
39
42
|
` ${n} cap graphify <subcommand> [args...]`,
|
|
40
43
|
` ${n} cap index [--graphify] [--tokensave]`,
|
|
41
44
|
` ${n} plugin install <package>[@version]`,
|
|
45
|
+
` ${n} plugin verify`,
|
|
42
46
|
...pluginCliCommandLines(),
|
|
43
47
|
` ${n} update`,
|
|
44
48
|
` ${n} update --check`,
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* before the write flushes (observed at exactly the 64KB pipe boundary) —
|
|
6
6
|
* awaiting the write callback is what actually guarantees delivery.
|
|
7
7
|
*/
|
|
8
|
-
export function writeJsonStdout(data: unknown): Promise<void> {
|
|
8
|
+
export function writeJsonStdout(data: unknown, options: { space?: number } = {}): Promise<void> {
|
|
9
9
|
return new Promise((resolve, reject) => {
|
|
10
|
-
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`, (error) =>
|
|
10
|
+
process.stdout.write(`${JSON.stringify(data, null, options.space ?? 2)}\n`, (error) =>
|
|
11
11
|
error ? reject(error) : resolve(),
|
|
12
12
|
);
|
|
13
13
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const SPINNER_FRAMES = ["·", "✦", "✧", "✦"];
|
|
4
|
+
const SPINNER_INTERVAL_MS = 120;
|
|
5
5
|
|
|
6
6
|
export function useSpinnerFrame(active: boolean, intervalMs: number = SPINNER_INTERVAL_MS): string {
|
|
7
7
|
const [frame, setFrame] = useState(0);
|
|
@@ -38,7 +38,7 @@ export interface StartupProgressAppProps {
|
|
|
38
38
|
steps: StartupStep[];
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
function StartupProgressApp({ title, steps }: StartupProgressAppProps) {
|
|
42
42
|
return (
|
|
43
43
|
<Box flexDirection="column">
|
|
44
44
|
<Text bold color={MIDNIGHT_PURPLE_BRIGHT}>
|
|
@@ -39,7 +39,7 @@ export function isContextModeNodeVersionSupported(version: string): boolean {
|
|
|
39
39
|
return major > 22 || (major === 22 && minor >= 5);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
function validateContextModeNodeRuntime(): void {
|
|
43
43
|
// env passed explicitly: bun's spawnSync otherwise ignores in-process PATH
|
|
44
44
|
// changes when resolving the executable.
|
|
45
45
|
const result = spawnSync("node", ["--version"], { encoding: "utf8", env: process.env });
|
|
@@ -21,6 +21,9 @@ export function getOpenCodePluginPackageReference(version: string = getCurrentVe
|
|
|
21
21
|
|
|
22
22
|
/** Matches the published reference and a reference bound to an installed copy alike. */
|
|
23
23
|
export function isMateOpenCodePluginReference(value: unknown): boolean {
|
|
24
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && "package" in value) {
|
|
25
|
+
return isMateOpenCodePluginReference(value.package);
|
|
26
|
+
}
|
|
24
27
|
if (typeof value !== "string") return false;
|
|
25
28
|
if (
|
|
26
29
|
value === OPENCODE_PLUGIN_PACKAGE_NAME ||
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// oxlint-disable no-await-in-loop
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
|
|
2
4
|
const fs = await import("node:fs/promises");
|
|
3
5
|
const path = await import("node:path");
|
|
4
6
|
|
|
@@ -21,18 +23,61 @@ import { buildOpenCodeGuidance } from "../opencode-guidance";
|
|
|
21
23
|
import { LaunchPreflightError } from "../types";
|
|
22
24
|
import { type AdapterContext, LaunchAdapter } from "./base";
|
|
23
25
|
|
|
26
|
+
/** The Mate plugin is a V2 plugin; an older host rejects it at load time. */
|
|
27
|
+
const MIN_OPENCODE_MAJOR = 2;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* OpenCode 2.x attaches to a shared background service by default; plugins and
|
|
31
|
+
* config load in that server, so the per-launch Mate env never reaches it.
|
|
32
|
+
*/
|
|
33
|
+
const STANDALONE_FLAG = "--standalone";
|
|
34
|
+
|
|
35
|
+
/** Raw `opencode --version` output, or `undefined` when the binary cannot run. */
|
|
36
|
+
export type OpenCodeVersionProbe = () => string | undefined;
|
|
37
|
+
|
|
38
|
+
const probeOpenCodeVersion: OpenCodeVersionProbe = () => {
|
|
39
|
+
const result = spawnSync("opencode", ["--version"], { encoding: "utf8", timeout: 10_000 });
|
|
40
|
+
return result.status === 0 ? result.stdout.trim() : undefined;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function parseOpenCodeMajor(output: string | undefined): number | undefined {
|
|
44
|
+
const match = output?.match(/(\d+)\.\d+\.\d+/);
|
|
45
|
+
return match ? Number(match[1]) : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
export class OpenCodeAdapter extends LaunchAdapter {
|
|
25
49
|
readonly toolName = "opencode";
|
|
26
50
|
readonly interactive = true;
|
|
51
|
+
private readonly requiredRuntimeAssets = [path.join(".opencode", "opencode.json")] as const;
|
|
52
|
+
|
|
53
|
+
constructor(private readonly probeVersion: OpenCodeVersionProbe = probeOpenCodeVersion) {
|
|
54
|
+
super();
|
|
55
|
+
}
|
|
27
56
|
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
private validateOpenCodeVersion(): void {
|
|
58
|
+
const output = this.probeVersion();
|
|
59
|
+
const major = parseOpenCodeMajor(output);
|
|
60
|
+
if (major !== undefined && major >= MIN_OPENCODE_MAJOR) return;
|
|
61
|
+
throw new LaunchPreflightError(
|
|
62
|
+
[
|
|
63
|
+
output === undefined
|
|
64
|
+
? "OpenCode is not installed or `opencode --version` failed."
|
|
65
|
+
: `OpenCode ${output} is not supported.`,
|
|
66
|
+
`Mate requires OpenCode ${MIN_OPENCODE_MAJOR}.x or newer. Upgrade OpenCode and retry.`,
|
|
67
|
+
].join("\n"),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
32
70
|
|
|
33
71
|
buildArgs(context: AdapterContext, args: string[]): string[] {
|
|
34
72
|
const codeDir = context.launchWorkingDirectory;
|
|
35
|
-
|
|
73
|
+
const isTui = args.length === 0 || args[0]?.startsWith("-");
|
|
74
|
+
const [command, ...rest] = isTui ? [codeDir, ...args] : args;
|
|
75
|
+
if (!command) return args;
|
|
76
|
+
const hostsServer = isTui || command === "run";
|
|
77
|
+
const choosesServer = rest.some(
|
|
78
|
+
(arg) => arg === "--standalone" || arg === "--server" || arg.startsWith("--server="),
|
|
79
|
+
);
|
|
80
|
+
return hostsServer && !choosesServer ? [command, STANDALONE_FLAG, ...rest] : [command, ...rest];
|
|
36
81
|
}
|
|
37
82
|
|
|
38
83
|
extendEnvironment(context: AdapterContext): NodeJS.ProcessEnv {
|
|
@@ -40,9 +85,7 @@ export class OpenCodeAdapter extends LaunchAdapter {
|
|
|
40
85
|
OPENCODE_CONFIG_DIR: path.join(context.companionPath, ".opencode"),
|
|
41
86
|
OPENCODE_CONFIG_CONTENT: mergeOpenCodeConfigContent(
|
|
42
87
|
{
|
|
43
|
-
|
|
44
|
-
external_directory: renderCompanionExternalDirectoryPermissions(context.companionPath),
|
|
45
|
-
},
|
|
88
|
+
permissions: renderCompanionExternalDirectoryPermissions(context.companionPath),
|
|
46
89
|
references: {
|
|
47
90
|
mate: context.companionPath,
|
|
48
91
|
},
|
|
@@ -63,6 +106,8 @@ export class OpenCodeAdapter extends LaunchAdapter {
|
|
|
63
106
|
}
|
|
64
107
|
|
|
65
108
|
async validateLaunch(context: AdapterContext): Promise<void> {
|
|
109
|
+
this.validateOpenCodeVersion();
|
|
110
|
+
|
|
66
111
|
const missingAssets = await Promise.all(
|
|
67
112
|
this.requiredRuntimeAssets.map(async (asset) => {
|
|
68
113
|
const assetPath = path.join(context.companionPath, asset);
|
|
@@ -92,11 +137,6 @@ export class OpenCodeAdapter extends LaunchAdapter {
|
|
|
92
137
|
path.join(".opencode", "opencode.json"),
|
|
93
138
|
expectedPluginReference,
|
|
94
139
|
)),
|
|
95
|
-
...(await this.validatePluginReference(
|
|
96
|
-
context,
|
|
97
|
-
path.join(".opencode", "tui.json"),
|
|
98
|
-
expectedPluginReference,
|
|
99
|
-
)),
|
|
100
140
|
...this.validateGuidance(context),
|
|
101
141
|
];
|
|
102
142
|
if (errors.length > 0) {
|
|
@@ -63,7 +63,7 @@ function isInsideDir(parent: string, candidate: string): boolean {
|
|
|
63
63
|
return relative !== "" && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
function normalizeHubMemberId(value: string): string {
|
|
67
67
|
const normalized = value
|
|
68
68
|
.replace(/\.git$/i, "")
|
|
69
69
|
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
@@ -72,7 +72,7 @@ export function normalizeHubMemberId(value: string): string {
|
|
|
72
72
|
return normalized;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
function validateHubMemberPath(hubPath: string, memberPath: string): string {
|
|
76
76
|
if (!memberPath || path.isAbsolute(memberPath)) {
|
|
77
77
|
throw new Error("Hub member path must be relative to the hub root.");
|
|
78
78
|
}
|
|
@@ -266,7 +266,7 @@ function syncTarget(memberPath: string, git: GitCommand): string | null {
|
|
|
266
266
|
return upstream.stdout;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
async function syncHubMember(
|
|
270
270
|
hubPath: string,
|
|
271
271
|
member: HubMember,
|
|
272
272
|
git: GitCommand = defaultGitCommand,
|
|
@@ -5,7 +5,7 @@ import { migrateRegistryData } from "./migration";
|
|
|
5
5
|
import { YamlFileStore } from "./yaml-file-store";
|
|
6
6
|
import { ConfigError, type CompanionRegistryConfig } from "./types";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
function getDefaultCompanionRegistryPath(): string {
|
|
9
9
|
return `.${FRAMEWORK_NAME}/config/registry.yaml`;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -30,7 +30,7 @@ interface LegacyProfilesShape {
|
|
|
30
30
|
* list (`profiles.default.allowedAgents` wins; other profiles are dropped).
|
|
31
31
|
* The new shape persists on the next save.
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
function migrateProfilesToAllowedAgents(config: FrameworkConfig): FrameworkConfig {
|
|
34
34
|
const legacy = config as FrameworkConfig & LegacyProfilesShape;
|
|
35
35
|
if (!legacy.profiles) return config;
|
|
36
36
|
const { profiles, ...rest } = legacy;
|
|
@@ -130,7 +130,7 @@ export const PLUGIN_DECLARATION_POLICIES = ["default", "optional"] as const;
|
|
|
130
130
|
* Validates the `plugins` list of a loaded config. Declared plugins may not
|
|
131
131
|
* claim `required` — required-ness is a distribution prerogative.
|
|
132
132
|
*/
|
|
133
|
-
|
|
133
|
+
function validatePluginDeclarations(config: FrameworkConfig): void {
|
|
134
134
|
for (const declaration of config.plugins ?? []) {
|
|
135
135
|
const policy = declaration.policy as string | undefined;
|
|
136
136
|
if (policy !== undefined && !PLUGIN_DECLARATION_POLICIES.includes(policy as never)) {
|
|
@@ -13,7 +13,7 @@ const CURSOR_FALLBACK_PATHS = [
|
|
|
13
13
|
"/opt/homebrew/bin/cursor",
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
function isCursorSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
17
17
|
const termProgram = env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
18
18
|
const bundleId = env.__CFBundleIdentifier ?? "";
|
|
19
19
|
return (
|
|
@@ -21,7 +21,7 @@ export function isCursorSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
function isVsCodeSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
25
25
|
const termProgram = env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
26
26
|
const bundleId = env.__CFBundleIdentifier ?? "";
|
|
27
27
|
return (
|
|
@@ -69,18 +69,10 @@ function resolveEditorCli(cli: string): { cli: string; binary: string } | null {
|
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function writeMissingEditorCliGuidance(
|
|
73
|
-
cli: string,
|
|
74
|
-
write: (chunk: string) => boolean = process.stderr.write.bind(process.stderr),
|
|
75
|
-
): void {
|
|
76
|
-
write(`mate: warning: ${cli} CLI not found on PATH.\n`);
|
|
77
|
-
write(`mate: run "Install '${cli}' command in PATH" from the Command Palette to enable this.\n`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
72
|
/** The Projection Root owns the path; this is the editor's name for it. */
|
|
81
73
|
export const editorWorkspacePath = workspaceDocumentPath;
|
|
82
74
|
|
|
83
|
-
|
|
75
|
+
interface WorkspaceDocument {
|
|
84
76
|
workspacePath: string;
|
|
85
77
|
folders: [string, string];
|
|
86
78
|
}
|
|
@@ -91,7 +83,7 @@ export interface WorkspaceDocument {
|
|
|
91
83
|
* order. Called by editor-launching `injectEditorFolder`, which backs
|
|
92
84
|
* `mate companion open`.
|
|
93
85
|
*/
|
|
94
|
-
|
|
86
|
+
async function writeWorkspaceDocument(
|
|
95
87
|
companionPath: string,
|
|
96
88
|
repoPath: string,
|
|
97
89
|
): Promise<WorkspaceDocument> {
|
|
@@ -27,7 +27,15 @@ export function checkEngineRequirement(
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
const
|
|
30
|
+
const current = semver.parse(currentVersion);
|
|
31
|
+
const currentReleaseVersion =
|
|
32
|
+
current && current.prerelease.length > 0
|
|
33
|
+
? `${current.major}.${current.minor}.${current.patch}`
|
|
34
|
+
: null;
|
|
35
|
+
const satisfied =
|
|
36
|
+
semver.satisfies(currentVersion, range, { includePrerelease: true }) ||
|
|
37
|
+
(currentReleaseVersion !== null &&
|
|
38
|
+
semver.satisfies(currentReleaseVersion, range, { includePrerelease: true }));
|
|
31
39
|
if (!satisfied) {
|
|
32
40
|
return {
|
|
33
41
|
ok: false,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
} from "./repo-local-store";
|
|
53
53
|
import type { ProjectionEntry, ProjectionInput } from "./projection-types";
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
const WORKSPACE_DOCUMENT_FILE = "workspace.code-workspace";
|
|
56
56
|
|
|
57
57
|
/** The shared editor workspace document, inside the Projection Root. */
|
|
58
58
|
export function workspaceDocumentPath(repoPath: string): string {
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { getReactDoctorBinPath, getWrapperBinPath } from "../package-paths";
|
|
14
14
|
import type { LinkedRepository } from "./types";
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const COMPANION_GRAPHIFY_REPOSITORY_ID = "__companion__";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* The one derivation of the projected paths. Both the Projection Root and a
|
|
@@ -159,10 +159,6 @@ export interface ProjectionDescription {
|
|
|
159
159
|
projection: ProjectionFile | null;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
export function anyChanged(outcomes: ProjectionEntryOutcome[]): boolean {
|
|
163
|
-
return outcomes.some((outcome) => outcome.state === "written" || outcome.state === "removed");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
162
|
/** A degradable entry's failure is reported in the outcomes and nowhere else. */
|
|
167
163
|
export function firstFailure(outcomes: ProjectionEntryOutcome[]): ProjectionEntryOutcome | null {
|
|
168
164
|
return outcomes.find((outcome) => outcome.state === "failed" && !outcome.degradable) ?? null;
|
|
@@ -101,7 +101,7 @@ export async function resolveRootContext(
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/** Nearest ancestor of `cwd` (inclusive) holding `.<framework>/config/framework.yaml`. */
|
|
104
|
-
|
|
104
|
+
async function findConfiguredRoot(cwd: string): Promise<string | null> {
|
|
105
105
|
let dir = path.resolve(cwd);
|
|
106
106
|
for (;;) {
|
|
107
107
|
try {
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
|
|
9
9
|
export type OpenSpecSchemaSelection = OpenSpecSchemaProfile | "default";
|
|
10
10
|
export type GitModeSelection = GitModeProfile | "default";
|
|
11
|
-
|
|
11
|
+
const DEFAULT_OPENSPEC_SCHEMA_SELECTION: OpenSpecSchemaSelection = "default";
|
|
12
12
|
export const DEFAULT_GIT_MODE_SELECTION: GitModeSelection = "default";
|
|
13
13
|
|
|
14
14
|
export interface OpenSpecSchemaCompatibility {
|
|
@@ -124,7 +124,7 @@ export async function looksLikeWorkingRepo(cwd: string): Promise<boolean> {
|
|
|
124
124
|
return false;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
async function hasLocalCompanionConfig(cwd: string): Promise<boolean> {
|
|
128
128
|
const resolvedCwd = path.resolve(cwd);
|
|
129
129
|
return fileExists(path.join(resolvedCwd, `.${FRAMEWORK_NAME}`, "config", "framework.yaml"));
|
|
130
130
|
}
|
|
@@ -110,7 +110,7 @@ export async function unproject(input: ProjectionRemovalInput): Promise<Projecti
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/** Removes entries that wrap owns but an unwrap must withdraw independently. */
|
|
113
|
-
|
|
113
|
+
async function removeWorkingRepositoryUnwrapEntries(
|
|
114
114
|
repoPath: string,
|
|
115
115
|
): Promise<ProjectionEntryOutcome[]> {
|
|
116
116
|
const entries = projectionEntries().filter(
|
|
@@ -6,7 +6,7 @@ import { GlobalConfigStore } from "./global-config-store";
|
|
|
6
6
|
import type { LinkedRepository } from "./types";
|
|
7
7
|
|
|
8
8
|
/** Envelope version for the aggregate inventory shape; bump on any breaking shape change. */
|
|
9
|
-
|
|
9
|
+
const WORKSPACE_INVENTORY_SCHEMA_VERSION = 1;
|
|
10
10
|
|
|
11
11
|
export type CompanionInventoryHealth = "ready" | "missing" | "unreadable";
|
|
12
12
|
export type PairingInventoryHealth =
|
|
@@ -41,7 +41,7 @@ export interface WorkspaceInventoryDeps {
|
|
|
41
41
|
readCompanionRegistry: typeof readCompanionRegistry;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
function defaultWorkspaceInventoryDeps(
|
|
45
45
|
globalConfigStore = new GlobalConfigStore(),
|
|
46
46
|
): WorkspaceInventoryDeps {
|
|
47
47
|
return {
|
package/src/lib/package-paths.ts
CHANGED
|
@@ -3,19 +3,9 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { getActiveDistribution } from "../distribution";
|
|
6
|
-
import { mateInstallPath } from "../runtime/install";
|
|
7
6
|
|
|
8
7
|
const require = createRequire(import.meta.url);
|
|
9
8
|
|
|
10
|
-
/**
|
|
11
|
-
* Root of the installed mate-core package. A stamp input for the durable
|
|
12
|
-
* projection: a mate installed at a different path resolves different wrapper
|
|
13
|
-
* and plugin assets.
|
|
14
|
-
*/
|
|
15
|
-
export function getMateInstallPath(): string {
|
|
16
|
-
return mateInstallPath();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
9
|
/**
|
|
20
10
|
* Resolved wrapper directory: a distribution asset root that ships
|
|
21
11
|
* `wrappers/bin` wins over core's bundled default.
|
|
@@ -42,7 +32,7 @@ export function getClaudePluginRoot(): string {
|
|
|
42
32
|
return path.resolve(import.meta.dirname, "../../claude-plugin");
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
|
|
35
|
+
const CLAUDE_PLUGIN_HOOK_SHIMS = [
|
|
46
36
|
"validate-artifact-path.mjs",
|
|
47
37
|
"session-banner.mjs",
|
|
48
38
|
"session-guidance.mjs",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the distribution package containing a CLI entrypoint.
|
|
6
|
+
*
|
|
7
|
+
* Missing manifests are skipped while walking upward; malformed manifests and
|
|
8
|
+
* filesystem errors stop the search so callers cannot guess a package root.
|
|
9
|
+
*/
|
|
10
|
+
export function resolvePackageRoot(
|
|
11
|
+
entrypoint: string | undefined,
|
|
12
|
+
packageName: string,
|
|
13
|
+
): string | undefined {
|
|
14
|
+
if (!entrypoint || !packageName) return undefined;
|
|
15
|
+
|
|
16
|
+
let current: string;
|
|
17
|
+
try {
|
|
18
|
+
current = path.dirname(fs.realpathSync(entrypoint));
|
|
19
|
+
} catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
while (true) {
|
|
24
|
+
try {
|
|
25
|
+
const manifest = JSON.parse(
|
|
26
|
+
fs.readFileSync(path.join(current, "package.json"), "utf8"),
|
|
27
|
+
) as unknown;
|
|
28
|
+
if (
|
|
29
|
+
manifest &&
|
|
30
|
+
typeof manifest === "object" &&
|
|
31
|
+
(manifest as { name?: unknown }).name === packageName
|
|
32
|
+
) {
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const parent = path.dirname(current);
|
|
40
|
+
if (parent === current) return undefined;
|
|
41
|
+
current = parent;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -17,7 +17,7 @@ import { getLocalWorkspaceDir } from "./preinstalled-plugins";
|
|
|
17
17
|
* Packages the distribution selects and pins for the machine-local workspace.
|
|
18
18
|
* A bundle may carry any subset; it may carry nothing else.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
function getDistributionLocalDependencies(): Record<string, string> {
|
|
21
21
|
return {
|
|
22
22
|
[CONTEXT_MODE_PACKAGE_NAME]: CONTEXT_MODE_VERSION,
|
|
23
23
|
[OPENCODE_PLUGIN_PACKAGE_NAME]: getCurrentVersion(),
|
package/src/lib/public-npm.ts
CHANGED
|
@@ -113,7 +113,7 @@ export function installPublicPackageSync(packageSpec: string, registry = PUBLIC_
|
|
|
113
113
|
);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
function getNpmGlobalPackageRootSync(packageName: string): string {
|
|
117
117
|
const npmRoot = publicNpmDeps
|
|
118
118
|
.execFileSync("npm", ["root", "-g"], {
|
|
119
119
|
encoding: "utf8",
|