@uniqbit/mate-core 0.15.3 → 0.15.4-canary.3
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 +3 -2
- package/src/cli/commands/companion/hub.ts +99 -0
- package/src/cli/commands/companion/tui.ts +3 -1
- package/src/cli/commands/doctor.ts +3 -6
- package/src/cli/commands/install.ts +10 -10
- 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 +2 -2
- package/src/cli/commands/update.ts +9 -11
- package/src/cli/main.ts +25 -9
- package/src/cli/plugin-commands.ts +4 -4
- package/src/cli/usage.ts +7 -3
- package/src/distribution.ts +3 -5
- package/src/framework.ts +4 -36
- package/src/index.ts +4 -0
- package/src/lib/install.ts +1 -5
- package/src/lib/orchestrator/adapters/base.ts +3 -4
- package/src/lib/orchestrator/companion-hub.ts +374 -0
- package/src/lib/orchestrator/config-store.ts +72 -0
- package/src/lib/orchestrator/editor.ts +4 -20
- package/src/lib/orchestrator/framework-context.ts +119 -23
- package/src/lib/orchestrator/global-config-store.ts +1 -5
- package/src/lib/orchestrator/migration.ts +0 -23
- package/src/lib/orchestrator/setup-preflight.ts +4 -17
- package/src/lib/orchestrator/types.ts +26 -4
- package/src/lib/orchestrator/working-repo-store.ts +2 -2
- package/src/lib/update-checker.ts +5 -5
- package/src/playbooks/companion-guidance.ts +6 -6
- package/src/runtime/env.ts +0 -1
- package/src/templates/capabilities/openspec-cap/claude/hooks/mate-artifact-finish.sh +79 -3
- 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 +5 -5
- package/src/tools/setup/capabilities/openspec.ts +1 -1
- package/src/tools/setup/capabilities/tokensave.ts +4 -4
- 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/mate.ts +28 -31
- package/src/tools/setup/plugins/gitignore.ts +15 -8
- package/src/tools/setup/plugins/guidance.ts +1 -5
- package/src/tools/setup/providers/claude.ts +20 -8
- package/src/tools/setup/providers/opencode.ts +3 -4
- package/src/tools/setup.ts +2 -3
- package/src/tui.ts +5 -0
- package/src/tools/setup/dynamic-plugins/pin-store.ts +0 -30
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { pathToFileURL } from "node:url";
|
|
4
5
|
|
|
5
|
-
import { FRAMEWORK_NAME
|
|
6
|
+
import { FRAMEWORK_NAME } from "../../../framework";
|
|
6
7
|
import type { PluginDeclaration } from "../../../lib/orchestrator/types";
|
|
7
8
|
import type { Plugin } from "../plugin";
|
|
8
9
|
import { resolveEffectivePluginConfig } from "./config-resolution";
|
|
@@ -12,14 +13,9 @@ import {
|
|
|
12
13
|
type CreatePlugin,
|
|
13
14
|
type PluginHost,
|
|
14
15
|
} from "./host";
|
|
15
|
-
import { pluginPackageRoot } from "./paths";
|
|
16
|
-
import type { PluginPin } from "./pin-store";
|
|
16
|
+
import { dynamicPluginsWorkspaceRoot, pluginPackageRoot } from "./paths";
|
|
17
17
|
|
|
18
18
|
interface PluginManifest {
|
|
19
|
-
version?: string;
|
|
20
|
-
main?: string;
|
|
21
|
-
module?: string;
|
|
22
|
-
exports?: unknown;
|
|
23
19
|
mate?: { pluginApiVersion?: unknown };
|
|
24
20
|
}
|
|
25
21
|
|
|
@@ -35,38 +31,17 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
35
31
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
function commandName(): string {
|
|
39
|
-
try {
|
|
40
|
-
return frameworkCommandName();
|
|
41
|
-
} catch {
|
|
42
|
-
return FRAMEWORK_NAME;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function resolveEntryFromExports(exportsField: unknown): string | undefined {
|
|
47
|
-
if (typeof exportsField === "string") return exportsField;
|
|
48
|
-
if (!isRecord(exportsField)) return undefined;
|
|
49
|
-
const dot = "." in exportsField ? exportsField["."] : exportsField;
|
|
50
|
-
if (typeof dot === "string") return dot;
|
|
51
|
-
if (!isRecord(dot)) return undefined;
|
|
52
|
-
for (const condition of ["bun", "import", "default", "require"]) {
|
|
53
|
-
const candidate = dot[condition];
|
|
54
|
-
if (typeof candidate === "string") return candidate;
|
|
55
|
-
if (isRecord(candidate) && typeof candidate.default === "string") return candidate.default;
|
|
56
|
-
}
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
34
|
/**
|
|
61
|
-
* Loads one declared plugin from
|
|
62
|
-
* version gate before import,
|
|
63
|
-
*
|
|
64
|
-
*
|
|
35
|
+
* Loads one declared plugin from the shared workspace: installed-presence
|
|
36
|
+
* check, API version gate before import, entry-point resolution via the
|
|
37
|
+
* shared workspace's own module resolver, dynamic import, factory
|
|
38
|
+
* resolution, effective config, factory invocation. Every failure class
|
|
39
|
+
* returns a single warning instead of throwing, so one broken plugin never
|
|
40
|
+
* takes down the CLI.
|
|
65
41
|
*/
|
|
66
42
|
export async function loadDynamicPlugin(
|
|
67
43
|
companionPath: string,
|
|
68
44
|
declaration: PluginDeclaration,
|
|
69
|
-
pins: PluginPin[],
|
|
70
45
|
deps: DynamicPluginLoadDeps = {},
|
|
71
46
|
): Promise<DynamicPluginLoadResult> {
|
|
72
47
|
const name = declaration.package;
|
|
@@ -80,27 +55,7 @@ export async function loadDynamicPlugin(
|
|
|
80
55
|
} catch {
|
|
81
56
|
return {
|
|
82
57
|
ok: false,
|
|
83
|
-
warning: `plugin "${name}" is not installed; run \`${
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const pin = pins.find((candidate) => candidate.package === name);
|
|
88
|
-
if (!pin) {
|
|
89
|
-
return {
|
|
90
|
-
ok: false,
|
|
91
|
-
warning: `plugin "${name}" has no recorded pin; run \`${commandName()} install\`.`,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
if (pin.declaredVersion !== declaration.version) {
|
|
95
|
-
return {
|
|
96
|
-
ok: false,
|
|
97
|
-
warning: `plugin "${name}" declares version "${declaration.version}" but was pinned from "${pin.declaredVersion}"; run \`${commandName()} install\`.`,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
if (manifest.version !== pin.resolvedVersion) {
|
|
101
|
-
return {
|
|
102
|
-
ok: false,
|
|
103
|
-
warning: `plugin "${name}" has version ${manifest.version ?? "unknown"} installed but ${pin.resolvedVersion} pinned; run \`${commandName()} install\`.`,
|
|
58
|
+
warning: `plugin "${name}" is not installed; run \`${FRAMEWORK_NAME} install\`.`,
|
|
104
59
|
};
|
|
105
60
|
}
|
|
106
61
|
|
|
@@ -121,9 +76,19 @@ export async function loadDynamicPlugin(
|
|
|
121
76
|
};
|
|
122
77
|
}
|
|
123
78
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
79
|
+
let entryPath: string;
|
|
80
|
+
try {
|
|
81
|
+
const workspaceRequire = createRequire(
|
|
82
|
+
pathToFileURL(path.join(dynamicPluginsWorkspaceRoot(companionPath), "package.json")),
|
|
83
|
+
);
|
|
84
|
+
entryPath = workspaceRequire.resolve(name);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
warning: `plugin "${name}" could not be resolved: ${error instanceof Error ? error.message : String(error)}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
127
92
|
const importModule =
|
|
128
93
|
deps.importModule ??
|
|
129
94
|
((specifier: string) => import(specifier) as Promise<Record<string, unknown>>);
|
|
@@ -2,34 +2,20 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { FRAMEWORK_NAME } from "../../../framework";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
export function
|
|
7
|
-
return
|
|
5
|
+
/** Shared workspace root: one package.json + node_modules for every declared plugin. */
|
|
6
|
+
export function dynamicPluginsWorkspaceRoot(companionPath: string): string {
|
|
7
|
+
return path.join(companionPath, `.${FRAMEWORK_NAME}`, "plugins");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
return path.join(companionPath, `.${FRAMEWORK_NAME}`, "dependencies", "plugins");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Per-plugin install workspace holding a private package.json and node_modules. */
|
|
15
|
-
export function pluginInstallDir(companionPath: string, packageName: string): string {
|
|
16
|
-
return path.join(dynamicPluginsRoot(companionPath), sanitizePluginDirName(packageName));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Root of the installed plugin package itself. */
|
|
10
|
+
/** Root of one installed plugin package inside the shared workspace's node_modules. */
|
|
20
11
|
export function pluginPackageRoot(companionPath: string, packageName: string): string {
|
|
21
12
|
return path.join(
|
|
22
|
-
|
|
13
|
+
dynamicPluginsWorkspaceRoot(companionPath),
|
|
23
14
|
"node_modules",
|
|
24
15
|
...packageName.split("/"),
|
|
25
16
|
);
|
|
26
17
|
}
|
|
27
18
|
|
|
28
|
-
/** Committed pin file recording resolved plugin versions. */
|
|
29
|
-
export function pluginPinFilePath(companionPath: string): string {
|
|
30
|
-
return path.join(companionPath, `.${FRAMEWORK_NAME}`, "config", "plugins.lock.yaml");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
19
|
/** Gitignored per-machine override file deep-merged over committed plugin config. */
|
|
34
20
|
export function pluginLocalOverridesPath(companionPath: string): string {
|
|
35
21
|
return path.join(companionPath, `.${FRAMEWORK_NAME}`, "config", "plugins.local.yaml");
|
|
@@ -37,3 +23,6 @@ export function pluginLocalOverridesPath(companionPath: string): string {
|
|
|
37
23
|
|
|
38
24
|
/** Companion-relative gitignore entry for the local override file. */
|
|
39
25
|
export const PLUGIN_LOCAL_OVERRIDES_GITIGNORE_ENTRY = `.${FRAMEWORK_NAME}/config/plugins.local.yaml`;
|
|
26
|
+
|
|
27
|
+
/** Companion-relative gitignore entry for the shared workspace's local, never-committed registry credentials. */
|
|
28
|
+
export const PLUGIN_WORKSPACE_NPMRC_GITIGNORE_ENTRY = `.${FRAMEWORK_NAME}/plugins/.npmrc`;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A generic, actionable hint printed on install failure — never guesses at
|
|
3
|
+
* the actual registry URL/host. Private registries stay a plain, standard
|
|
4
|
+
* npm operation: a one-time `npm config set --global`, or (since the shared
|
|
5
|
+
* plugin workspace is a real npm project) a project-local, gitignored
|
|
6
|
+
* `.npmrc` dropped directly in it — either is picked up by `npm install`
|
|
7
|
+
* with nothing Mate-injected.
|
|
8
|
+
*/
|
|
9
|
+
export function registryConfigHint(packageName: string): string {
|
|
10
|
+
const scope = packageName.startsWith("@") ? packageName.split("/")[0] : undefined;
|
|
11
|
+
return scope
|
|
12
|
+
? `If "${scope}" lives in a private registry, configure it once with:\n npm config set "${scope}:registry" "<registry-url>" --global\n npm config set "//<registry-host>/:_authToken" "<token>" --global`
|
|
13
|
+
: `If this package lives in a private registry, configure it once via \`npm config set\` (see the npm docs for scoped registries).`;
|
|
14
|
+
}
|
package/src/tools/setup/mate.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import { frameworkCommandName } from "../../framework";
|
|
5
4
|
import { pruneEmptyAncestors } from "./utils";
|
|
6
5
|
|
|
7
6
|
export const MATE_ARTIFACT_SKILLS = ["mate-artifact-finish"] as const;
|
|
8
7
|
export const MATE_SKILLS = ["mate-artifact-finish", "mate-create-report"] as const;
|
|
9
8
|
|
|
10
|
-
const MATE_COMMAND_PLACEHOLDER = "{{MATE_COMMAND}}";
|
|
11
9
|
const MATE_SKILLS_SOURCE = path.join(
|
|
12
10
|
import.meta.dirname,
|
|
13
11
|
"../../templates/capabilities/openspec-cap/mate-skills",
|
|
@@ -16,9 +14,9 @@ const MATE_SKILLS_SOURCE = path.join(
|
|
|
16
14
|
const MATE_CREATE_REPORT_SKILL = `---
|
|
17
15
|
name: mate-create-report
|
|
18
16
|
description: Create a browser-first Mate report from an explicit structured ReportDocument. Use when a skill needs to present supplied metrics, tables, statuses, metadata, or narrative text.
|
|
19
|
-
allowed-tools: Bash(
|
|
17
|
+
allowed-tools: Bash(mate:*)
|
|
20
18
|
license: MIT
|
|
21
|
-
compatibility: Requires the
|
|
19
|
+
compatibility: Requires the mate CLI and the OpenSpec capability enabled.
|
|
22
20
|
metadata:
|
|
23
21
|
author: mate
|
|
24
22
|
version: "1.0"
|
|
@@ -57,22 +55,14 @@ Supported section types are \`metadata\`, \`metrics\`, \`key-value\`, \`table\`,
|
|
|
57
55
|
## Invocation
|
|
58
56
|
|
|
59
57
|
1. Serialize the complete document to a temporary file or pipe it to stdin.
|
|
60
|
-
2. Run \`
|
|
58
|
+
2. Run \`mate report --input <file-or->\`.
|
|
61
59
|
3. Add \`--json\` when the caller needs normalized JSON instead of browser delivery.
|
|
62
60
|
|
|
63
61
|
The default path writes self-contained HTML to a unique OS temporary directory and opens it in the default browser. The report includes a visible "Print / Save as PDF" control that calls the browser's native print dialog. If HTML delivery fails, the CLI warns on stderr and emits the complete report document as JSON on stdout.
|
|
64
62
|
|
|
65
|
-
The built-in \`
|
|
63
|
+
The built-in \`mate report\` path collects Mate usage data and adapts it to the same contract and renderer.
|
|
66
64
|
`;
|
|
67
65
|
|
|
68
|
-
function currentMateCommand(): string {
|
|
69
|
-
try {
|
|
70
|
-
return frameworkCommandName();
|
|
71
|
-
} catch {
|
|
72
|
-
return "mate";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
66
|
export async function deployMateSkillDir(src: string, dest: string): Promise<void> {
|
|
77
67
|
await fs.mkdir(dest, { recursive: true });
|
|
78
68
|
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
@@ -83,31 +73,38 @@ export async function deployMateSkillDir(src: string, dest: string): Promise<voi
|
|
|
83
73
|
await deployMateSkillDir(srcPath, destPath);
|
|
84
74
|
continue;
|
|
85
75
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
76
|
+
await fs.copyFile(srcPath, destPath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DEFAULT_MATE_SKILLS_BUCKET = "agents";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A skill's source lives under a provider bucket so its folder name always
|
|
84
|
+
* matches its `name:` frontmatter: `<tool>/<skill>/` when that tool needs its
|
|
85
|
+
* own behavior (e.g. Claude Code always confirms before `mate artifact
|
|
86
|
+
* finish` pushes, since a push is a shared-state action), falling back to
|
|
87
|
+
* `agents/<skill>/` — the shared default every other tool (e.g. opencode,
|
|
88
|
+
* which pushes automatically) uses.
|
|
89
|
+
*/
|
|
90
|
+
async function resolveMateSkillSource(skill: string, tool: string): Promise<string> {
|
|
91
|
+
const providerDir = path.join(MATE_SKILLS_SOURCE, tool, skill);
|
|
92
|
+
try {
|
|
93
|
+
await fs.access(providerDir);
|
|
94
|
+
return providerDir;
|
|
95
|
+
} catch {
|
|
96
|
+
return path.join(MATE_SKILLS_SOURCE, DEFAULT_MATE_SKILLS_BUCKET, skill);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
export async function applyMateSkills(skillsDir: string): Promise<void> {
|
|
100
|
+
export async function applyMateSkills(skillsDir: string, tool: string): Promise<void> {
|
|
100
101
|
for (const skill of MATE_SKILLS) {
|
|
101
102
|
const destination = path.join(skillsDir, skill);
|
|
102
103
|
if (skill === "mate-create-report") {
|
|
103
104
|
await fs.mkdir(destination, { recursive: true });
|
|
104
|
-
await fs.writeFile(
|
|
105
|
-
path.join(destination, "SKILL.md"),
|
|
106
|
-
MATE_CREATE_REPORT_SKILL.replaceAll(MATE_COMMAND_PLACEHOLDER, currentMateCommand()),
|
|
107
|
-
"utf8",
|
|
108
|
-
);
|
|
105
|
+
await fs.writeFile(path.join(destination, "SKILL.md"), MATE_CREATE_REPORT_SKILL, "utf8");
|
|
109
106
|
} else {
|
|
110
|
-
await deployMateSkillDir(
|
|
107
|
+
await deployMateSkillDir(await resolveMateSkillSource(skill, tool), destination);
|
|
111
108
|
}
|
|
112
109
|
}
|
|
113
110
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { getActiveDistribution } from "../../../distribution";
|
|
2
2
|
import { FRAMEWORK_NAME } from "../../../framework";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PLUGIN_LOCAL_OVERRIDES_GITIGNORE_ENTRY,
|
|
5
|
+
PLUGIN_WORKSPACE_NPMRC_GITIGNORE_ENTRY,
|
|
6
|
+
} from "../dynamic-plugins/paths";
|
|
4
7
|
import type { Plugin, SetupContext } from "../plugin";
|
|
5
8
|
|
|
6
9
|
const MANAGED_START = (name: string) => `# ${name} managed: start`;
|
|
@@ -58,22 +61,26 @@ export async function writeManagedGitignoreBlock(
|
|
|
58
61
|
|
|
59
62
|
export function collectManagedGitignoreEntries(ctx: SetupContext, plugins: Plugin[]): string[] {
|
|
60
63
|
// Baseline for every companion: node_modules never versions wherever a tool
|
|
61
|
-
// materializes it, and everything under the dependencies tree
|
|
62
|
-
//
|
|
64
|
+
// materializes it, and everything directly under the dependencies tree
|
|
65
|
+
// (context-mode, future consumers) is regenerated by setup/install —
|
|
63
66
|
// version pins live in .mate/config and in code, never in the tree itself.
|
|
64
67
|
const entries = [
|
|
65
68
|
"node_modules/",
|
|
66
|
-
`.${FRAMEWORK_NAME}/dependencies
|
|
69
|
+
`.${FRAMEWORK_NAME}/dependencies/*`,
|
|
67
70
|
".mcp.json*",
|
|
68
71
|
...plugins
|
|
69
72
|
.filter((p) => p.kind !== "root" && (p.isEnabled(ctx.config) || p.persistGitignoreEntries))
|
|
70
73
|
.flatMap((p) => p.gitignoreEntries?.(ctx) ?? []),
|
|
71
74
|
];
|
|
72
|
-
// Dynamic-plugin loading artifacts: the local override file
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
+
// Dynamic-plugin loading artifacts: the local override file and the shared
|
|
76
|
+
// workspace's local registry credentials never version. The workspace's
|
|
77
|
+
// installed tree (node_modules) is already covered by the baseline
|
|
78
|
+
// node_modules/ rule above, since the workspace lives outside the
|
|
79
|
+
// dependencies tree wildcard; its package.json and committed
|
|
80
|
+
// package-lock.json are the pin/reproducibility record and are never
|
|
81
|
+
// touched by any ignore rule.
|
|
75
82
|
if (ctx.config.plugins?.length) {
|
|
76
|
-
entries.push(PLUGIN_LOCAL_OVERRIDES_GITIGNORE_ENTRY);
|
|
83
|
+
entries.push(PLUGIN_WORKSPACE_NPMRC_GITIGNORE_ENTRY, PLUGIN_LOCAL_OVERRIDES_GITIGNORE_ENTRY);
|
|
77
84
|
}
|
|
78
85
|
return entries;
|
|
79
86
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import { FRAMEWORK_NAME
|
|
4
|
+
import { FRAMEWORK_NAME } from "../../../framework";
|
|
5
5
|
import { removeGraphifySection } from "../capabilities/graphify";
|
|
6
6
|
|
|
7
7
|
// Marker names derive from the framework identity, never the invocation name.
|
|
@@ -12,9 +12,6 @@ function allMateStarts(): string[] {
|
|
|
12
12
|
`<!-- ${upperName()}:COMPANION:START -->`,
|
|
13
13
|
"<!-- COMPANION:GUIDANCE:START -->",
|
|
14
14
|
`<!-- ${upperName()}:COMPANION:GUIDANCE:START -->`,
|
|
15
|
-
...frameworkConfig.legacyNames.map(
|
|
16
|
-
(n) => `<!-- ${n.toUpperCase()}:COMPANION:GUIDANCE:START -->`,
|
|
17
|
-
),
|
|
18
15
|
`<!-- ${upperName()}:GRAPHIFY:START -->`,
|
|
19
16
|
];
|
|
20
17
|
}
|
|
@@ -24,7 +21,6 @@ function allMateEnds(): string[] {
|
|
|
24
21
|
`<!-- ${upperName()}:COMPANION:END -->`,
|
|
25
22
|
"<!-- COMPANION:GUIDANCE:END -->",
|
|
26
23
|
`<!-- ${upperName()}:COMPANION:GUIDANCE:END -->`,
|
|
27
|
-
...frameworkConfig.legacyNames.map((n) => `<!-- ${n.toUpperCase()}:COMPANION:GUIDANCE:END -->`),
|
|
28
24
|
`<!-- ${upperName()}:GRAPHIFY:END -->`,
|
|
29
25
|
];
|
|
30
26
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { FRAMEWORK_NAME } from "../../../framework";
|
|
6
6
|
import { GlobalConfigStore } from "../../../lib/orchestrator/global-config-store";
|
|
7
7
|
import { getWrapperBinPath } from "../../../lib/package-paths";
|
|
8
8
|
import type { FrameworkConfig } from "../../../lib/orchestrator/types";
|
|
@@ -57,11 +57,7 @@ const MANAGED_HOOK_MARKERS = [
|
|
|
57
57
|
// every file. Claude Code ignores `Glob()` rules for file-permission checks
|
|
58
58
|
// (only Read/Edit rules gate file tools), so no Glob entry is emitted.
|
|
59
59
|
function getBaseManagedPermissionEntries(companionPath: string): string[] {
|
|
60
|
-
return [
|
|
61
|
-
`Bash(${frameworkCommandName()}:*)`,
|
|
62
|
-
`Read(${companionPath}/**)`,
|
|
63
|
-
`Edit(${companionPath}/**)`,
|
|
64
|
-
];
|
|
60
|
+
return [`Bash(${FRAMEWORK_NAME}:*)`, `Read(${companionPath}/**)`, `Edit(${companionPath}/**)`];
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
const LEGACY_MANAGED_PERMISSION_ENTRIES = [
|
|
@@ -80,14 +76,14 @@ function getCapabilityPermissionEntries(): Record<string, string[]> {
|
|
|
80
76
|
"Skill(openspec-archive-change)",
|
|
81
77
|
"Skill(mate-artifact-finish)",
|
|
82
78
|
"Bash(openspec:*)",
|
|
83
|
-
`Bash(${
|
|
79
|
+
`Bash(${FRAMEWORK_NAME} cap graphify:*)`,
|
|
84
80
|
`Bash(${path.join(wrapperBinPath, "openspec")}:*)`,
|
|
85
81
|
],
|
|
86
82
|
rtk: ["Bash(rtk:*)"],
|
|
87
83
|
graphify: [
|
|
88
84
|
"Skill(graphify)",
|
|
89
85
|
"Bash(graphify:*)",
|
|
90
|
-
`Bash(${
|
|
86
|
+
`Bash(${FRAMEWORK_NAME} cap graphify:*)`,
|
|
91
87
|
`Bash(${path.join(wrapperBinPath, "graphify")}:*)`,
|
|
92
88
|
],
|
|
93
89
|
"react-doctor": [
|
|
@@ -296,6 +292,22 @@ function buildManagedClaudeSettings(
|
|
|
296
292
|
},
|
|
297
293
|
...(hooks.PostToolUse ?? []),
|
|
298
294
|
];
|
|
295
|
+
// Catch-all: an archive that the PostToolUse nudge never matched (a human
|
|
296
|
+
// running the command outside Claude, an unmatched command shape) still
|
|
297
|
+
// gets caught here before the session ends, by checking the archive dir
|
|
298
|
+
// against finish tags directly rather than guessing from command text.
|
|
299
|
+
hooks.Stop = [
|
|
300
|
+
{
|
|
301
|
+
hooks: [
|
|
302
|
+
{
|
|
303
|
+
type: "command",
|
|
304
|
+
command: `sh "${companionPath}/.claude/hooks/mate-artifact-finish.sh"`,
|
|
305
|
+
timeout: 10,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
},
|
|
309
|
+
...(hooks.Stop ?? []),
|
|
310
|
+
];
|
|
299
311
|
}
|
|
300
312
|
if (reactDoctorEnabled) {
|
|
301
313
|
// Record edits cheaply, then scan once when the edited turn finishes.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { FRAMEWORK_NAME } from "../../../framework";
|
|
6
6
|
import {
|
|
7
7
|
OPENCODE_PLUGIN_PACKAGE_NAME,
|
|
8
8
|
getOpenCodePluginPackageReference,
|
|
@@ -18,7 +18,6 @@ import { getSetupProvidersRoot, getSetupRootTemplates } from "./utils";
|
|
|
18
18
|
// package-owned now; these exact filenames are removed during sync and
|
|
19
19
|
// teardown while every other companion-local plugin is preserved.
|
|
20
20
|
const LEGACY_PLUGIN_FILES = [
|
|
21
|
-
"kizuna-companion.ts",
|
|
22
21
|
"add-dir.ts",
|
|
23
22
|
"companion.ts",
|
|
24
23
|
"small-model.ts",
|
|
@@ -244,9 +243,9 @@ async function warmPluginCacheForSetup(pluginReference: string): Promise<void> {
|
|
|
244
243
|
|
|
245
244
|
process.stderr.write(
|
|
246
245
|
[
|
|
247
|
-
`${
|
|
246
|
+
`${FRAMEWORK_NAME}: could not pre-fetch ${pluginReference} into OpenCode's plugin environment.`,
|
|
248
247
|
"The first managed OpenCode launch will download it, which requires registry access.",
|
|
249
|
-
`Re-run \`${
|
|
248
|
+
`Re-run \`${FRAMEWORK_NAME} companion setup\` with network access to warm the cache ahead of time.`,
|
|
250
249
|
...(warmed.detail ? [`Details: ${warmed.detail}`] : []),
|
|
251
250
|
].join("\n") + "\n",
|
|
252
251
|
);
|
package/src/tools/setup.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import { FRAMEWORK_NAME
|
|
5
|
+
import { FRAMEWORK_NAME } from "../framework";
|
|
6
6
|
import { ConfigStore, mergeWithDefaults } from "../lib/orchestrator/config-store";
|
|
7
7
|
import { GlobalConfigStore } from "../lib/orchestrator/global-config-store";
|
|
8
8
|
import {
|
|
@@ -91,9 +91,8 @@ export async function syncCompanionFiles(
|
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// Exported for the distribution-identity tests.
|
|
95
94
|
export function mateFolderReadme(): string {
|
|
96
|
-
const n =
|
|
95
|
+
const n = FRAMEWORK_NAME;
|
|
97
96
|
const packageName =
|
|
98
97
|
getActiveDistribution().config.update?.packageName ?? `@uniqbit/${FRAMEWORK_NAME}`;
|
|
99
98
|
return [
|
package/src/tui.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SelectMenu, type SelectMenuItem } from "./lib/components/select-menu";
|
|
2
|
+
export { WizardFooter } from "./lib/components/wizard-footer";
|
|
3
|
+
export { WizardHeader } from "./lib/components/wizard-header";
|
|
4
|
+
export { TextInputDisplay } from "./lib/components/text-input-display";
|
|
5
|
+
export { ConfirmPromptApp, confirmPrompt } from "./lib/components/confirm-prompt";
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { YamlFileStore } from "../../../lib/orchestrator/yaml-file-store";
|
|
2
|
-
import { pluginPinFilePath } from "./paths";
|
|
3
|
-
|
|
4
|
-
/** Committed record of one declared plugin's resolved install. */
|
|
5
|
-
export interface PluginPin {
|
|
6
|
-
package: string;
|
|
7
|
-
declaredVersion: string;
|
|
8
|
-
resolvedVersion: string;
|
|
9
|
-
integrity?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface PluginPinFile {
|
|
13
|
-
plugins: PluginPin[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class PluginPinStore extends YamlFileStore<PluginPinFile> {
|
|
17
|
-
constructor(companionPath: string) {
|
|
18
|
-
super(pluginPinFilePath(companionPath));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
override async load(): Promise<PluginPinFile> {
|
|
22
|
-
const parsed = await super.load();
|
|
23
|
-
return { plugins: Array.isArray(parsed?.plugins) ? parsed.plugins : [] };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
protected onMissing(): Promise<PluginPinFile> {
|
|
27
|
-
// The pin file appears only once install records a resolved plugin.
|
|
28
|
-
return Promise.resolve({ plugins: [] });
|
|
29
|
-
}
|
|
30
|
-
}
|