@uniqbit/mate-core 0.15.3 → 0.15.4-canary.10
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/claude-plugin/.claude-plugin/plugin.json +4 -0
- package/claude-plugin/hooks/artifact-finish-nudge.mjs +8 -0
- package/claude-plugin/hooks/hooks.json +37 -0
- package/claude-plugin/hooks/session-banner.mjs +8 -0
- package/claude-plugin/hooks/ts-loader.mjs +28 -0
- package/claude-plugin/hooks/validate-artifact-path.mjs +8 -0
- package/package.json +4 -2
- package/src/cli/commands/artifact/finish/openspec.ts +1 -5
- package/src/cli/commands/companion/hub.ts +99 -0
- package/src/cli/commands/companion/link.ts +0 -5
- package/src/cli/commands/companion/tui.ts +3 -2
- package/src/cli/commands/doctor.ts +254 -170
- package/src/cli/commands/install.ts +18 -14
- package/src/cli/commands/launch/shared.ts +4 -4
- package/src/cli/commands/plugin/install.ts +94 -0
- package/src/cli/commands/plugin/plugin.ts +17 -0
- package/src/cli/commands/setup.ts +4 -6
- package/src/cli/commands/update.ts +9 -11
- package/src/cli/companion-link-wizard.tsx +22 -78
- package/src/cli/main.ts +57 -13
- package/src/cli/plugin-commands.ts +4 -4
- package/src/cli/repo-list-table.tsx +2 -7
- package/src/cli/usage.ts +7 -3
- package/src/distribution.ts +3 -5
- package/src/framework.ts +4 -36
- package/src/hooks/artifact-finish-nudge.ts +212 -0
- package/src/hooks/session-banner.ts +25 -0
- package/src/hooks/validate-artifact-path.ts +236 -0
- package/src/index.ts +4 -0
- package/src/lib/context-mode-package.ts +5 -3
- package/src/lib/install.ts +31 -43
- package/src/lib/orchestrator/adapters/base.ts +7 -8
- package/src/lib/orchestrator/adapters/claude.ts +16 -0
- package/src/lib/orchestrator/adapters/opencode.ts +10 -60
- package/src/lib/orchestrator/companion-hub.ts +380 -0
- package/src/lib/orchestrator/companion-store.ts +1 -35
- package/src/lib/orchestrator/config-store.ts +94 -7
- package/src/lib/orchestrator/editor.ts +4 -20
- package/src/lib/orchestrator/framework-context.ts +119 -24
- package/src/lib/orchestrator/global-config-store.ts +1 -5
- package/src/lib/orchestrator/launcher.ts +3 -9
- package/src/lib/orchestrator/migration.ts +0 -23
- package/src/lib/orchestrator/opencode-guidance.ts +1 -2
- package/src/lib/orchestrator/repo-local-registry.ts +3 -1
- package/src/lib/orchestrator/root-context.ts +117 -0
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -1
- package/src/lib/orchestrator/setup-preflight.ts +5 -18
- package/src/lib/orchestrator/types.ts +27 -19
- package/src/lib/orchestrator/working-repo-store.ts +9 -3
- package/src/lib/package-paths.ts +37 -0
- package/src/lib/update-checker.ts +5 -5
- package/src/playbooks/companion-guidance.ts +7 -7
- package/src/runtime/env.ts +0 -4
- package/src/templates/capabilities/openspec-cap/mate-skills/{mate-artifact-finish → agents/mate-artifact-finish}/SKILL.md +8 -8
- package/src/templates/capabilities/openspec-cap/mate-skills/{mate-artifact-finish → agents/mate-artifact-finish}/references/openspec.md +3 -3
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +58 -0
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +139 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +6 -5
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -0
- package/src/templates/root/TEMPLATE_CLAUDE.md +2 -0
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3541 -0
- package/src/tools/setup/capabilities/context-mode.ts +57 -83
- package/src/tools/setup/capabilities/graphify-shared.ts +27 -0
- package/src/tools/setup/capabilities/graphify.ts +86 -295
- package/src/tools/setup/capabilities/openspec.ts +27 -30
- package/src/tools/setup/capabilities/react-doctor.ts +37 -37
- package/src/tools/setup/capabilities/rtk.ts +4 -1
- package/src/tools/setup/capabilities/tokensave-shared.ts +6 -0
- package/src/tools/setup/capabilities/tokensave.ts +50 -71
- package/src/tools/setup/context-services.ts +29 -0
- package/src/tools/setup/dynamic-plugins/host.ts +2 -1
- package/src/tools/setup/dynamic-plugins/hydrate.ts +3 -13
- package/src/tools/setup/dynamic-plugins/install.ts +124 -109
- package/src/tools/setup/dynamic-plugins/loader.ts +23 -58
- package/src/tools/setup/dynamic-plugins/paths.ts +8 -19
- package/src/tools/setup/dynamic-plugins/registry-hint.ts +14 -0
- package/src/tools/setup/engine.ts +65 -1
- package/src/tools/setup/mate.ts +28 -31
- package/src/tools/setup/plugin.ts +81 -0
- package/src/tools/setup/plugins/gitignore.ts +19 -9
- package/src/tools/setup/plugins/guidance.ts +2 -6
- package/src/tools/setup/policy.ts +4 -7
- package/src/tools/setup/providers/agent-file-sections.ts +78 -0
- package/src/tools/setup/providers/claude-format.ts +159 -0
- package/src/tools/setup/providers/claude.ts +283 -292
- package/src/tools/setup/providers/opencode-format.ts +146 -0
- package/src/tools/setup/providers/opencode.ts +171 -66
- package/src/tools/setup/providers/skill-tree.ts +38 -0
- package/src/tools/setup.ts +12 -7
- package/src/tui.ts +5 -0
- package/src/templates/capabilities/openspec-cap/claude/hooks/mate-artifact-finish.sh +0 -161
- package/src/templates/providers/claude/.claude/hooks/mate-session-banner +0 -28
- package/src/templates/providers/claude/.claude/hooks/validate-artifact-path +0 -242
- package/src/tools/setup/dynamic-plugins/pin-store.ts +0 -30
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { FRAMEWORK_NAME } from "../../../framework";
|
|
4
|
+
import { resolveInstallContext } from "../../../lib/install";
|
|
5
|
+
import { ConfigStore } from "../../../lib/orchestrator/config-store";
|
|
6
|
+
import type { PluginDeclaration } from "../../../lib/orchestrator/types";
|
|
7
|
+
import { hydrateDynamicPlugins } from "../../../tools/setup/dynamic-plugins/hydrate";
|
|
8
|
+
import {
|
|
9
|
+
installDeclaredPlugins,
|
|
10
|
+
type PluginInstallDeps,
|
|
11
|
+
} from "../../../tools/setup/dynamic-plugins/install";
|
|
12
|
+
import { reportPluginInstallResults } from "../install";
|
|
13
|
+
|
|
14
|
+
export function parsePackageSpec(spec: string): { package: string; version: string } {
|
|
15
|
+
const atIndex = spec.startsWith("@") ? spec.indexOf("@", 1) : spec.indexOf("@");
|
|
16
|
+
if (atIndex === -1) return { package: spec, version: "latest" };
|
|
17
|
+
return { package: spec.slice(0, atIndex), version: spec.slice(atIndex + 1) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function withDeclaration(
|
|
21
|
+
declarations: PluginDeclaration[],
|
|
22
|
+
packageName: string,
|
|
23
|
+
version: string,
|
|
24
|
+
): PluginDeclaration[] {
|
|
25
|
+
const existing = declarations.find((declaration) => declaration.package === packageName);
|
|
26
|
+
if (existing?.version === version) return declarations;
|
|
27
|
+
if (!existing) return [...declarations, { package: packageName, version }];
|
|
28
|
+
return declarations.map((declaration) =>
|
|
29
|
+
declaration.package === packageName ? { ...declaration, version } : declaration,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface PluginInstallCommandDeps {
|
|
34
|
+
cwd?: string;
|
|
35
|
+
installDeps?: PluginInstallDeps;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @command mate plugin install <package>[@version]
|
|
40
|
+
* @description Declares `<package>` in the companion's (or hub's) `framework.yaml`
|
|
41
|
+
* `plugins:` list (adding it, or updating its version if already declared)
|
|
42
|
+
* and installs it immediately into the shared workspace, the same way
|
|
43
|
+
* `mate install` would on its next run.
|
|
44
|
+
*/
|
|
45
|
+
export async function runPluginInstallCommand(
|
|
46
|
+
argv: string[],
|
|
47
|
+
deps: PluginInstallCommandDeps = {},
|
|
48
|
+
): Promise<boolean> {
|
|
49
|
+
const spec = argv[0];
|
|
50
|
+
if (!spec) {
|
|
51
|
+
process.stderr.write(
|
|
52
|
+
`${FRAMEWORK_NAME}: usage: ${FRAMEWORK_NAME} plugin install <package>[@version]\n`,
|
|
53
|
+
);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const { package: packageName, version } = parsePackageSpec(spec);
|
|
58
|
+
|
|
59
|
+
const context = await resolveInstallContext(deps.cwd ?? process.cwd());
|
|
60
|
+
if (context.kind === "ambiguous") {
|
|
61
|
+
process.stderr.write(`${context.message}\n`);
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if ((context.kind !== "companion" && context.kind !== "hub") || !context.companionPath) {
|
|
66
|
+
process.stderr.write(
|
|
67
|
+
`${FRAMEWORK_NAME}: \`plugin install\` requires a companion or hub context; run it from inside a linked working repository or a hub root.\n`,
|
|
68
|
+
);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const configPath = path.join(
|
|
74
|
+
context.companionPath,
|
|
75
|
+
`.${FRAMEWORK_NAME}`,
|
|
76
|
+
"config",
|
|
77
|
+
"framework.yaml",
|
|
78
|
+
);
|
|
79
|
+
const store = new ConfigStore(configPath);
|
|
80
|
+
const nextPlugins = withDeclaration(context.config.plugins ?? [], packageName, version);
|
|
81
|
+
if (nextPlugins !== context.config.plugins) {
|
|
82
|
+
await store.save({ ...context.config, plugins: nextPlugins });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const results = await installDeclaredPlugins(
|
|
86
|
+
context.companionPath,
|
|
87
|
+
nextPlugins,
|
|
88
|
+
deps.installDeps,
|
|
89
|
+
);
|
|
90
|
+
reportPluginInstallResults(results);
|
|
91
|
+
await hydrateDynamicPlugins({ companionPath: context.companionPath });
|
|
92
|
+
|
|
93
|
+
return results.find((result) => result.package === packageName)?.status !== "failed";
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { usage } from "../../usage";
|
|
2
|
+
import { runPluginInstallCommand } from "./install";
|
|
3
|
+
|
|
4
|
+
export async function runPluginCommand(
|
|
5
|
+
subcommand: string | undefined,
|
|
6
|
+
argv: string[],
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
switch (subcommand) {
|
|
9
|
+
case "install":
|
|
10
|
+
await runPluginInstallCommand(argv);
|
|
11
|
+
return;
|
|
12
|
+
default:
|
|
13
|
+
console.error(`Unknown plugin command: ${subcommand ?? ""}`);
|
|
14
|
+
console.error(usage());
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
import { FRAMEWORK_NAME
|
|
3
|
+
import { FRAMEWORK_NAME } from "../../framework";
|
|
4
4
|
import { ConfigStore, defaultConfig } from "../../lib/orchestrator/config-store";
|
|
5
5
|
import { executeSetup } from "../../tools/setup";
|
|
6
6
|
import {
|
|
@@ -56,7 +56,7 @@ export async function runSetupCommand(argv: string[]): Promise<void> {
|
|
|
56
56
|
* Resolves agent/package-manager/capability selections either from CLI flags
|
|
57
57
|
* (non-interactive) or from the interactive `selectSetupCompatibilities` picker,
|
|
58
58
|
* seeded from the existing config when one exists. Writes the resulting config
|
|
59
|
-
* via `setup.execute` and prints a summary table of
|
|
59
|
+
* via `setup.execute` and prints a summary table of allowed agents, package managers,
|
|
60
60
|
* and capabilities.
|
|
61
61
|
* @flags
|
|
62
62
|
* - `--agents <list>` — parsed via `parseAllowedAgents`.
|
|
@@ -98,7 +98,7 @@ async function runSetupFlow(
|
|
|
98
98
|
if (await checkWorkingRepo(cwd)) {
|
|
99
99
|
process.stderr.write(
|
|
100
100
|
`Warning: this directory looks like a working repository (project files detected).\n` +
|
|
101
|
-
`Initializing it as a ${
|
|
101
|
+
`Initializing it as a ${FRAMEWORK_NAME} companion here may not be what you want.\n`,
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
const ok = await askConfirm("Initialize this directory as a Mate companion repository? [y/N] ");
|
|
@@ -192,9 +192,7 @@ async function runSetupFlow(
|
|
|
192
192
|
const { config } = result;
|
|
193
193
|
const rows: [string, string][] = [];
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
rows.push([`profile:${profileName}`, (profile.allowedAgents ?? []).join(", ") || "(none)"]);
|
|
197
|
-
}
|
|
195
|
+
rows.push(["allowedAgents", (config.allowedAgents ?? []).join(", ") || "(none)"]);
|
|
198
196
|
|
|
199
197
|
const capabilities = config.capabilities ?? [];
|
|
200
198
|
rows.push(["packageManagers", (config.packageManagers ?? ["bun"]).join(", ") || "(none)"]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { FRAMEWORK_NAME } from "../../framework";
|
|
5
5
|
import {
|
|
6
6
|
OPENCODE_PLUGIN_PACKAGE_NAME,
|
|
7
7
|
warmOpenCodePluginCache,
|
|
@@ -20,7 +20,7 @@ type InstallResult = ReturnType<typeof installPublicPackageSync>;
|
|
|
20
20
|
function writeNpmOnlyRecoveryMessage(): void {
|
|
21
21
|
const { packageName, registry } = getUpdateConfig();
|
|
22
22
|
process.stderr.write(
|
|
23
|
-
`${
|
|
23
|
+
`${FRAMEWORK_NAME}: self-update is only supported for npm-installed Mate.\n`,
|
|
24
24
|
);
|
|
25
25
|
if (packageName.startsWith("@uniqbit/")) {
|
|
26
26
|
process.stderr.write(
|
|
@@ -87,16 +87,14 @@ export async function runUpdateCommand(argv: string[]): Promise<void> {
|
|
|
87
87
|
try {
|
|
88
88
|
latest = await updateCommandDeps.fetchLatestVersion();
|
|
89
89
|
} catch {
|
|
90
|
-
process.stderr.write(
|
|
91
|
-
`${frameworkCommandName()}: could not reach registry to check for updates\n`,
|
|
92
|
-
);
|
|
90
|
+
process.stderr.write(`${FRAMEWORK_NAME}: could not reach registry to check for updates\n`);
|
|
93
91
|
process.exitCode = 1;
|
|
94
92
|
return;
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
if (checkOnly) {
|
|
98
96
|
if (updateCommandDeps.isNewer(latest, current)) {
|
|
99
|
-
console.log(`${
|
|
97
|
+
console.log(`${FRAMEWORK_NAME}: update available (${current} → ${latest})`);
|
|
100
98
|
process.exitCode = 1;
|
|
101
99
|
} else {
|
|
102
100
|
console.log("Up to date.");
|
|
@@ -127,11 +125,11 @@ export async function runUpdateCommand(argv: string[]): Promise<void> {
|
|
|
127
125
|
} catch (error) {
|
|
128
126
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
129
127
|
process.stderr.write(
|
|
130
|
-
`${
|
|
128
|
+
`${FRAMEWORK_NAME}: npm is required for self-update but was not found on PATH\n`,
|
|
131
129
|
);
|
|
132
130
|
} else {
|
|
133
131
|
process.stderr.write(
|
|
134
|
-
`${
|
|
132
|
+
`${FRAMEWORK_NAME}: could not verify the npm installation used for self-update\n`,
|
|
135
133
|
);
|
|
136
134
|
}
|
|
137
135
|
writeNpmOnlyRecoveryMessage();
|
|
@@ -148,7 +146,7 @@ export async function runUpdateCommand(argv: string[]): Promise<void> {
|
|
|
148
146
|
const result = updateCommandDeps.installLatest(latest);
|
|
149
147
|
if (result.status !== 0 || result.error) {
|
|
150
148
|
process.stderr.write(
|
|
151
|
-
`${
|
|
149
|
+
`${FRAMEWORK_NAME}: upgrade command exited with status ${result.status ?? 1}\n`,
|
|
152
150
|
);
|
|
153
151
|
process.exitCode = 1;
|
|
154
152
|
return;
|
|
@@ -163,7 +161,7 @@ export async function runUpdateCommand(argv: string[]): Promise<void> {
|
|
|
163
161
|
if (!warmed.ok) {
|
|
164
162
|
process.stderr.write(
|
|
165
163
|
[
|
|
166
|
-
`${
|
|
164
|
+
`${FRAMEWORK_NAME}: could not pre-fetch ${OPENCODE_PLUGIN_PACKAGE_NAME}@${latest} for OpenCode.`,
|
|
167
165
|
"The next managed OpenCode launch will download it (requires registry access).",
|
|
168
166
|
...(warmed.detail ? [`Details: ${warmed.detail}`] : []),
|
|
169
167
|
].join("\n") + "\n",
|
|
@@ -173,7 +171,7 @@ export async function runUpdateCommand(argv: string[]): Promise<void> {
|
|
|
173
171
|
const postInstall = updateCommandDeps.runPostInstall(skipConfirm);
|
|
174
172
|
if (postInstall.status !== 0 || postInstall.error) {
|
|
175
173
|
process.stderr.write(
|
|
176
|
-
`\nUpgraded to ${latest}, but installation is incomplete. Run \`${
|
|
174
|
+
`\nUpgraded to ${latest}, but installation is incomplete. Run \`${FRAMEWORK_NAME} install\`.\n`,
|
|
177
175
|
);
|
|
178
176
|
process.exitCode = 1;
|
|
179
177
|
return;
|
|
@@ -17,7 +17,6 @@ export interface CompanionLinkInputs {
|
|
|
17
17
|
gitUrl?: string;
|
|
18
18
|
localPath?: string;
|
|
19
19
|
companionPaths?: string[];
|
|
20
|
-
profile: string;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
interface SourceStep {
|
|
@@ -45,15 +44,7 @@ interface TextStep {
|
|
|
45
44
|
error?: string;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
kind: "select";
|
|
50
|
-
field: "profile";
|
|
51
|
-
label: string;
|
|
52
|
-
options: string[];
|
|
53
|
-
activeIndex: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
type WizardStep = SourceStep | CompanionMultiSelectStep | TextStep | ProfileStep;
|
|
47
|
+
type WizardStep = SourceStep | CompanionMultiSelectStep | TextStep;
|
|
57
48
|
|
|
58
49
|
interface WizardAppProps {
|
|
59
50
|
step: WizardStep;
|
|
@@ -99,10 +90,7 @@ function CompanionLinkWizardApp({ step, stepNumber, totalSteps, onMounted }: Wiz
|
|
|
99
90
|
);
|
|
100
91
|
}
|
|
101
92
|
|
|
102
|
-
const items =
|
|
103
|
-
step.field === "source"
|
|
104
|
-
? step.options.map((option) => ({ key: option.key, label: option.label }))
|
|
105
|
-
: step.options.map((option) => ({ key: option, label: option }));
|
|
93
|
+
const items = step.options.map((option) => ({ key: option.key, label: option.label }));
|
|
106
94
|
|
|
107
95
|
return (
|
|
108
96
|
<Box flexDirection="column" width="100%">
|
|
@@ -116,7 +104,6 @@ function CompanionLinkWizardApp({ step, stepNumber, totalSteps, onMounted }: Wiz
|
|
|
116
104
|
}
|
|
117
105
|
|
|
118
106
|
export interface CompanionLinkWizardOptions {
|
|
119
|
-
profiles: string[];
|
|
120
107
|
existingCompanions?: string[];
|
|
121
108
|
stdin?: NodeJS.ReadStream;
|
|
122
109
|
stdout?: NodeJS.WriteStream;
|
|
@@ -125,7 +112,6 @@ export interface CompanionLinkWizardOptions {
|
|
|
125
112
|
|
|
126
113
|
function buildSteps(
|
|
127
114
|
source: CompanionSource,
|
|
128
|
-
profiles: string[],
|
|
129
115
|
values: {
|
|
130
116
|
gitUrl: string;
|
|
131
117
|
localPath: string;
|
|
@@ -176,21 +162,7 @@ function buildSteps(
|
|
|
176
162
|
selected: values.companionPaths,
|
|
177
163
|
};
|
|
178
164
|
|
|
179
|
-
|
|
180
|
-
return [sourceStep, sourceSpecificStep];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return [
|
|
184
|
-
sourceStep,
|
|
185
|
-
sourceSpecificStep,
|
|
186
|
-
{
|
|
187
|
-
kind: "select",
|
|
188
|
-
field: "profile",
|
|
189
|
-
label: "Profile",
|
|
190
|
-
options: profiles,
|
|
191
|
-
activeIndex: 0,
|
|
192
|
-
},
|
|
193
|
-
];
|
|
165
|
+
return [sourceStep, sourceSpecificStep];
|
|
194
166
|
}
|
|
195
167
|
|
|
196
168
|
export async function selectCompanionLinkInputs(
|
|
@@ -199,7 +171,6 @@ export async function selectCompanionLinkInputs(
|
|
|
199
171
|
const stdin = options.stdin ?? process.stdin;
|
|
200
172
|
const stdout = options.stdout ?? process.stdout;
|
|
201
173
|
const stderr = options.stderr ?? process.stderr;
|
|
202
|
-
const profiles = options.profiles;
|
|
203
174
|
const existingCompanions = options.existingCompanions ?? [];
|
|
204
175
|
|
|
205
176
|
return new Promise<CompanionLinkInputs | null>((resolve, reject) => {
|
|
@@ -214,7 +185,7 @@ export async function selectCompanionLinkInputs(
|
|
|
214
185
|
let currentStepIndex = 0;
|
|
215
186
|
const keepAlive = setInterval(() => {}, 1 << 30);
|
|
216
187
|
let rendered: ReturnType<typeof inkRender> | undefined;
|
|
217
|
-
let steps = buildSteps(source,
|
|
188
|
+
let steps = buildSteps(source, values, existingCompanions);
|
|
218
189
|
|
|
219
190
|
const cleanup = () => {
|
|
220
191
|
clearInterval(keepAlive);
|
|
@@ -232,24 +203,12 @@ export async function selectCompanionLinkInputs(
|
|
|
232
203
|
resolve(value);
|
|
233
204
|
};
|
|
234
205
|
|
|
235
|
-
const buildResult = (): CompanionLinkInputs => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
(steps.find((step) => step.kind === "select" && step.field === "profile") as ProfileStep)
|
|
242
|
-
.activeIndex
|
|
243
|
-
] as string);
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
source,
|
|
247
|
-
gitUrl: source === "git" ? values.gitUrl : undefined,
|
|
248
|
-
localPath: source === "local" ? values.localPath : undefined,
|
|
249
|
-
companionPaths: source === "existing" ? [...values.companionPaths] : undefined,
|
|
250
|
-
profile,
|
|
251
|
-
};
|
|
252
|
-
};
|
|
206
|
+
const buildResult = (): CompanionLinkInputs => ({
|
|
207
|
+
source,
|
|
208
|
+
gitUrl: source === "git" ? values.gitUrl : undefined,
|
|
209
|
+
localPath: source === "local" ? values.localPath : undefined,
|
|
210
|
+
companionPaths: source === "existing" ? [...values.companionPaths] : undefined,
|
|
211
|
+
});
|
|
253
212
|
|
|
254
213
|
const rerender = () => {
|
|
255
214
|
rendered?.rerender(
|
|
@@ -262,16 +221,7 @@ export async function selectCompanionLinkInputs(
|
|
|
262
221
|
};
|
|
263
222
|
|
|
264
223
|
const rebuildSteps = () => {
|
|
265
|
-
|
|
266
|
-
(step): step is ProfileStep => step.kind === "select" && step.field === "profile",
|
|
267
|
-
);
|
|
268
|
-
steps = buildSteps(source, profiles, values, existingCompanions);
|
|
269
|
-
const nextProfileStep = steps.find(
|
|
270
|
-
(step): step is ProfileStep => step.kind === "select" && step.field === "profile",
|
|
271
|
-
);
|
|
272
|
-
if (previousProfileStep && nextProfileStep) {
|
|
273
|
-
nextProfileStep.activeIndex = previousProfileStep.activeIndex;
|
|
274
|
-
}
|
|
224
|
+
steps = buildSteps(source, values, existingCompanions);
|
|
275
225
|
};
|
|
276
226
|
|
|
277
227
|
const onData = (chunk: string) => {
|
|
@@ -352,7 +302,7 @@ export async function selectCompanionLinkInputs(
|
|
|
352
302
|
return;
|
|
353
303
|
}
|
|
354
304
|
|
|
355
|
-
if (step.kind === "select"
|
|
305
|
+
if (step.kind === "select") {
|
|
356
306
|
source = step.options[step.activeIndex]!.key;
|
|
357
307
|
rebuildSteps();
|
|
358
308
|
if (currentStepIndex < steps.length - 1) {
|
|
@@ -362,23 +312,17 @@ export async function selectCompanionLinkInputs(
|
|
|
362
312
|
return;
|
|
363
313
|
}
|
|
364
314
|
|
|
365
|
-
if (step.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
rendered?.unmount();
|
|
376
|
-
}
|
|
377
|
-
return;
|
|
315
|
+
if (step.selected.size === 0) {
|
|
316
|
+
const option = step.options[step.activeIndex];
|
|
317
|
+
if (option) step.selected.add(option);
|
|
318
|
+
}
|
|
319
|
+
if (currentStepIndex < steps.length - 1) {
|
|
320
|
+
currentStepIndex++;
|
|
321
|
+
rerender();
|
|
322
|
+
} else {
|
|
323
|
+
doResolve(buildResult());
|
|
324
|
+
rendered?.unmount();
|
|
378
325
|
}
|
|
379
|
-
|
|
380
|
-
doResolve(buildResult());
|
|
381
|
-
rendered?.unmount();
|
|
382
326
|
};
|
|
383
327
|
|
|
384
328
|
const onMounted = () => {
|
package/src/cli/main.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getActiveDistribution } from "../distribution";
|
|
2
|
-
import {
|
|
2
|
+
import { FRAMEWORK_NAME } from "../framework";
|
|
3
3
|
import {
|
|
4
4
|
enforceUpdateIfRequired,
|
|
5
5
|
scheduleBackgroundCheck,
|
|
@@ -9,14 +9,17 @@ import {
|
|
|
9
9
|
import { runArtifactCommand } from "./commands/artifact/artifact";
|
|
10
10
|
import { runCapCommand } from "./commands/cap";
|
|
11
11
|
import { runCompanionCommand } from "./commands/companion/companion";
|
|
12
|
+
import { runHubCommand } from "./commands/companion/hub";
|
|
12
13
|
import { runConfigCommand } from "./commands/config";
|
|
13
14
|
import { runDoctorCommand } from "./commands/doctor";
|
|
14
15
|
import { runLaunchClaudeCommand } from "./commands/launch/claude";
|
|
15
16
|
import { runLaunchOpenCodeCommand } from "./commands/launch/opencode";
|
|
17
|
+
import { runPluginCommand } from "./commands/plugin/plugin";
|
|
16
18
|
import { runReportCommand } from "./commands/report";
|
|
17
19
|
import { runUpdateCommand } from "./commands/update";
|
|
18
20
|
import { runInstallCommand } from "./commands/install";
|
|
19
21
|
import { inspectInstallPreflight } from "../lib/install";
|
|
22
|
+
import { resolveRootContext } from "../lib/orchestrator/root-context";
|
|
20
23
|
import { ensureUnambiguousCompanion } from "./commands/shared/companion-selection";
|
|
21
24
|
import { hydrateDynamicPlugins } from "../tools/setup/dynamic-plugins/hydrate";
|
|
22
25
|
import { findPluginCliCommand } from "./plugin-commands";
|
|
@@ -25,6 +28,10 @@ import { usage } from "./usage";
|
|
|
25
28
|
export interface GateNeeds {
|
|
26
29
|
/** Block the command while an enforced update is pending. */
|
|
27
30
|
updateGuard?: boolean;
|
|
31
|
+
/** Block the command when the current directory resolves to a hub root. */
|
|
32
|
+
notHubRoot?: boolean;
|
|
33
|
+
/** Block the command when the current directory resolves to a companion root. */
|
|
34
|
+
notCompanionRoot?: boolean;
|
|
28
35
|
/** Require an unambiguous companion (selection wizard on ambiguity) before dispatch. */
|
|
29
36
|
companion?: boolean;
|
|
30
37
|
/** Require a complete installation (install preflight) before dispatch. */
|
|
@@ -35,12 +42,14 @@ export interface MainDeps {
|
|
|
35
42
|
ensureUnambiguousCompanion: typeof ensureUnambiguousCompanion;
|
|
36
43
|
inspectInstallPreflight: typeof inspectInstallPreflight;
|
|
37
44
|
hydrateDynamicPlugins: typeof hydrateDynamicPlugins;
|
|
45
|
+
resolveRootContext: typeof resolveRootContext;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
const mainDeps: MainDeps = {
|
|
41
49
|
ensureUnambiguousCompanion,
|
|
42
50
|
inspectInstallPreflight,
|
|
43
51
|
hydrateDynamicPlugins,
|
|
52
|
+
resolveRootContext,
|
|
44
53
|
};
|
|
45
54
|
|
|
46
55
|
export async function main(argv = process.argv, deps: MainDeps = mainDeps): Promise<void> {
|
|
@@ -51,10 +60,10 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
51
60
|
return;
|
|
52
61
|
}
|
|
53
62
|
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
63
|
+
// Dynamic plugins register before cap-command detection and before help
|
|
64
|
+
// text is printed, so their commands route like compiled-in ones
|
|
65
|
+
// (including MCP servers whose command is their own cap subcommand) and
|
|
66
|
+
// show up in `mate help`. Diagnostics stay on stderr; a missing or
|
|
58
67
|
// ambiguous companion makes this a no-op.
|
|
59
68
|
await deps.hydrateDynamicPlugins();
|
|
60
69
|
|
|
@@ -84,6 +93,24 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
84
93
|
await showUpdateBannerIfAvailable(updateStore);
|
|
85
94
|
}
|
|
86
95
|
|
|
96
|
+
if (needs.notHubRoot || needs.notCompanionRoot) {
|
|
97
|
+
const kind = (await deps.resolveRootContext()).kind;
|
|
98
|
+
if (needs.notHubRoot && kind === "hub") {
|
|
99
|
+
console.error(
|
|
100
|
+
`${FRAMEWORK_NAME}: this directory resolves to a companion hub root; companion commands are not available here. Use \`${FRAMEWORK_NAME} hub ...\` to manage the hub.`,
|
|
101
|
+
);
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (needs.notCompanionRoot && kind === "companion") {
|
|
106
|
+
console.error(
|
|
107
|
+
`${FRAMEWORK_NAME}: this directory resolves to a companion root; hub commands are not available here. Run them from a hub root.`,
|
|
108
|
+
);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
87
114
|
if (needs.companion && !(await deps.ensureUnambiguousCompanion())) {
|
|
88
115
|
process.exitCode = 1;
|
|
89
116
|
return false;
|
|
@@ -92,10 +119,8 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
92
119
|
if (needs.install) {
|
|
93
120
|
const preflight = await deps.inspectInstallPreflight();
|
|
94
121
|
if (!preflight.ok) {
|
|
95
|
-
console.error(
|
|
96
|
-
|
|
97
|
-
);
|
|
98
|
-
console.error(`Run \`${frameworkCommandName()} install\` before continuing.`);
|
|
122
|
+
console.error(`${FRAMEWORK_NAME}: ${preflight.reason ?? "installation is incomplete"}`);
|
|
123
|
+
console.error(`Run \`${FRAMEWORK_NAME} install\` before continuing.`);
|
|
99
124
|
process.exitCode = 1;
|
|
100
125
|
return false;
|
|
101
126
|
}
|
|
@@ -111,6 +136,14 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
111
136
|
if (!(await gate({ companion: true }))) return;
|
|
112
137
|
await runInstallCommand(argv.slice(3));
|
|
113
138
|
return;
|
|
139
|
+
case "plugin":
|
|
140
|
+
// Declares into framework.yaml and installs on the spot, so it needs
|
|
141
|
+
// the same gating as `install` — an unambiguous companion (hubs pass
|
|
142
|
+
// trivially), but must stay runnable when installation is otherwise
|
|
143
|
+
// incomplete.
|
|
144
|
+
if (!(await gate({ companion: true }))) return;
|
|
145
|
+
await runPluginCommand(subcommand, rest);
|
|
146
|
+
return;
|
|
114
147
|
case "artifact":
|
|
115
148
|
if (!(await gate({ updateGuard: true, companion: true, install: true }))) return;
|
|
116
149
|
await runArtifactCommand(subcommand, rest);
|
|
@@ -118,22 +151,33 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
118
151
|
case "companion":
|
|
119
152
|
switch (subcommand) {
|
|
120
153
|
// setup/link are recovery paths and must never be gated on the
|
|
121
|
-
// state they exist to repair.
|
|
154
|
+
// state they exist to repair — but hubs are never companions.
|
|
122
155
|
case "setup":
|
|
123
156
|
case "link":
|
|
124
|
-
if (!(await gate({}))) return;
|
|
157
|
+
if (!(await gate({ notHubRoot: true }))) return;
|
|
158
|
+
break;
|
|
159
|
+
// Hub commands establish and operate on a local hub root directly;
|
|
160
|
+
// they must not require a linked working repository or installation —
|
|
161
|
+
// but companions are never hubs.
|
|
162
|
+
case "hub":
|
|
163
|
+
if (!(await gate({ updateGuard: true, notCompanionRoot: true }))) return;
|
|
125
164
|
break;
|
|
126
165
|
// open/tui consume a companion context.
|
|
127
166
|
case "open":
|
|
128
167
|
case "tui":
|
|
129
|
-
if (!(await gate({ updateGuard: true, companion: true }))) return;
|
|
168
|
+
if (!(await gate({ updateGuard: true, notHubRoot: true, companion: true }))) return;
|
|
130
169
|
break;
|
|
131
170
|
// list and unknown subcommands (which fail inside the command).
|
|
132
171
|
default:
|
|
133
|
-
if (!(await gate({ updateGuard: true }))) return;
|
|
172
|
+
if (!(await gate({ updateGuard: true, notHubRoot: true }))) return;
|
|
134
173
|
}
|
|
135
174
|
await runCompanionCommand(subcommand, rest);
|
|
136
175
|
return;
|
|
176
|
+
case "hub":
|
|
177
|
+
// Hubs are never companions: hub commands are blocked in companion roots.
|
|
178
|
+
if (!(await gate({ updateGuard: true, notCompanionRoot: true }))) return;
|
|
179
|
+
await runHubCommand(subcommand ? [subcommand, ...rest] : []);
|
|
180
|
+
return;
|
|
137
181
|
case "claude":
|
|
138
182
|
if (!(await gate({ updateGuard: true, companion: true, install: true }))) return;
|
|
139
183
|
await runLaunchClaudeCommand(argv.slice(3), { directPassthrough: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getActiveDistribution } from "../distribution";
|
|
2
|
-
import {
|
|
2
|
+
import { FRAMEWORK_NAME } from "../framework";
|
|
3
3
|
import { resolveInstallContext } from "../lib/install";
|
|
4
4
|
import type { FrameworkConfig } from "../lib/orchestrator/types";
|
|
5
5
|
import type { Plugin, PluginCliCommand } from "../tools/setup/plugin";
|
|
@@ -86,7 +86,7 @@ export async function ensureCapabilityEnabled(
|
|
|
86
86
|
const loadConfig = deps.loadConfig ?? (async () => (await resolveInstallContext()).config);
|
|
87
87
|
if (plugin?.isEnabled(await loadConfig())) return true;
|
|
88
88
|
|
|
89
|
-
const name =
|
|
89
|
+
const name = FRAMEWORK_NAME;
|
|
90
90
|
console.error(
|
|
91
91
|
`${name}: the "${pluginId}" capability is not enabled for this companion. ` +
|
|
92
92
|
`Enable it via \`${name} companion setup\`.`,
|
|
@@ -104,7 +104,7 @@ function formatCommand(
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
function usageFor(plugin: Plugin): string {
|
|
107
|
-
const distributionName =
|
|
107
|
+
const distributionName = FRAMEWORK_NAME;
|
|
108
108
|
const namespace = namespaceOf(plugin);
|
|
109
109
|
const lines = (plugin.cliCommands ?? []).map((command) =>
|
|
110
110
|
formatCommand(distributionName, namespace, command),
|
|
@@ -119,7 +119,7 @@ function usageFor(plugin: Plugin): string {
|
|
|
119
119
|
* commands show up in `mate help` alongside framework builtins.
|
|
120
120
|
*/
|
|
121
121
|
export function pluginCliCommandLines(): string[] {
|
|
122
|
-
const distributionName =
|
|
122
|
+
const distributionName = FRAMEWORK_NAME;
|
|
123
123
|
return getActiveDistribution()
|
|
124
124
|
.registry.getAll()
|
|
125
125
|
.flatMap((plugin) =>
|
|
@@ -39,10 +39,6 @@ function RepoListTable({
|
|
|
39
39
|
"ID",
|
|
40
40
|
repositories.map((repository) => repository.id),
|
|
41
41
|
);
|
|
42
|
-
const profileWidth = columnWidth(
|
|
43
|
-
"Profile",
|
|
44
|
-
repositories.map((repository) => repository.profile),
|
|
45
|
-
);
|
|
46
42
|
const pathWidth = columnWidth(
|
|
47
43
|
"Path",
|
|
48
44
|
repositories.map((repository) => repository.path),
|
|
@@ -53,8 +49,7 @@ function RepoListTable({
|
|
|
53
49
|
<Text bold>Linked repositories</Text>
|
|
54
50
|
<Box marginTop={1} flexDirection="column">
|
|
55
51
|
<Text dimColor>
|
|
56
|
-
{pad("Active", markerWidth)} {pad("ID", idWidth)} {pad("
|
|
57
|
-
{pad("Path", pathWidth)}
|
|
52
|
+
{pad("Active", markerWidth)} {pad("ID", idWidth)} {pad("Path", pathWidth)}
|
|
58
53
|
</Text>
|
|
59
54
|
{repositories.map((repository) => {
|
|
60
55
|
const isActive = repository.id === activeRepositoryId;
|
|
@@ -63,7 +58,7 @@ function RepoListTable({
|
|
|
63
58
|
return (
|
|
64
59
|
<Text key={repository.id} color={isActive ? "green" : undefined}>
|
|
65
60
|
{pad(marker, markerWidth)} {pad(repository.id, idWidth)}{" "}
|
|
66
|
-
{pad(repository.
|
|
61
|
+
{pad(repository.path, pathWidth)}
|
|
67
62
|
</Text>
|
|
68
63
|
);
|
|
69
64
|
})}
|
package/src/cli/usage.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { getActiveDistribution } from "../distribution";
|
|
2
|
-
import { FRAMEWORK_NAME
|
|
2
|
+
import { FRAMEWORK_NAME } from "../framework";
|
|
3
3
|
import { pluginCliCommandLines } from "./plugin-commands";
|
|
4
4
|
|
|
5
5
|
export function usage(): string {
|
|
6
|
-
const n =
|
|
6
|
+
const n = FRAMEWORK_NAME;
|
|
7
7
|
const packageName =
|
|
8
8
|
getActiveDistribution().config.update?.packageName ?? `@uniqbit/${FRAMEWORK_NAME}`;
|
|
9
9
|
return [
|
|
10
|
-
|
|
10
|
+
`Mate CLI (${packageName})`,
|
|
11
11
|
"",
|
|
12
12
|
"Commands:",
|
|
13
13
|
` ${n} install [--yes]`,
|
|
@@ -16,6 +16,9 @@ export function usage(): string {
|
|
|
16
16
|
` ${n} companion list`,
|
|
17
17
|
` ${n} companion open`,
|
|
18
18
|
` ${n} companion tui`,
|
|
19
|
+
` ${n} hub init [folder]`,
|
|
20
|
+
` ${n} hub add [source] [--id ID] [--path PATH]`,
|
|
21
|
+
` ${n} hub sync [--json] (companions + hub plugins)`,
|
|
19
22
|
` ${n} artifact finish <change-name> [--type openspec] [--force] [--no-push] [--json]`,
|
|
20
23
|
` ${n} doctor`,
|
|
21
24
|
` ${n} report [--days N] [--input FILE|-] [--json]`,
|
|
@@ -25,6 +28,7 @@ export function usage(): string {
|
|
|
25
28
|
` ${n} cap openspec <subcommand> [args...]`,
|
|
26
29
|
` ${n} cap graphify <subcommand> [args...]`,
|
|
27
30
|
` ${n} cap index [--graphify] [--tokensave]`,
|
|
31
|
+
` ${n} plugin install <package>[@version]`,
|
|
28
32
|
...pluginCliCommandLines(),
|
|
29
33
|
` ${n} update`,
|
|
30
34
|
` ${n} update --check`,
|