@geminixiang/mikan 1.0.0-beta.6 → 1.0.0-beta.8
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/CHANGELOG.md +31 -0
- package/dist/adapters/github/bot.d.ts +41 -0
- package/dist/adapters/github/bot.d.ts.map +1 -0
- package/dist/adapters/github/bot.js +429 -0
- package/dist/adapters/github/bot.js.map +1 -0
- package/dist/adapters/github/client.d.ts +53 -0
- package/dist/adapters/github/client.d.ts.map +1 -0
- package/dist/adapters/github/client.js +149 -0
- package/dist/adapters/github/client.js.map +1 -0
- package/dist/adapters/github/context.d.ts +9 -0
- package/dist/adapters/github/context.d.ts.map +1 -0
- package/dist/adapters/github/context.js +160 -0
- package/dist/adapters/github/context.js.map +1 -0
- package/dist/adapters/github/ids.d.ts +25 -0
- package/dist/adapters/github/ids.d.ts.map +1 -0
- package/dist/adapters/github/ids.js +39 -0
- package/dist/adapters/github/ids.js.map +1 -0
- package/dist/adapters/github/types.d.ts +79 -0
- package/dist/adapters/github/types.d.ts.map +1 -0
- package/dist/adapters/github/types.js +2 -0
- package/dist/adapters/github/types.js.map +1 -0
- package/dist/cli/ext-git.d.ts +22 -0
- package/dist/cli/ext-git.d.ts.map +1 -0
- package/dist/cli/ext-git.js +76 -0
- package/dist/cli/ext-git.js.map +1 -0
- package/dist/cli/ext.d.ts +2 -0
- package/dist/cli/ext.d.ts.map +1 -0
- package/dist/cli/ext.js +217 -0
- package/dist/cli/ext.js.map +1 -0
- package/dist/harness/extensions/loader.d.ts +28 -4
- package/dist/harness/extensions/loader.d.ts.map +1 -1
- package/dist/harness/extensions/loader.js +195 -44
- package/dist/harness/extensions/loader.js.map +1 -1
- package/dist/harness/index.d.ts +1 -1
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +1 -1
- package/dist/harness/index.js.map +1 -1
- package/dist/main.js +50 -2
- package/dist/main.js.map +1 -1
- package/dist/sessions/policy.d.ts.map +1 -1
- package/dist/sessions/policy.js +1 -0
- package/dist/sessions/policy.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a `mikan ext install` source that points at a git repository into a
|
|
3
|
+
* local directory: clone it, descend into an optional `#subpath`, and run
|
|
4
|
+
* `npm install --omit=dev` when the extension declares dependencies. The
|
|
5
|
+
* caller validates + copies the returned directory, then calls cleanup().
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
/**
|
|
12
|
+
* Recognize a git source and split off an optional `#subpath`:
|
|
13
|
+
* https://host/owner/repo.git#dir/ext → { url, subpath }
|
|
14
|
+
* git@host:owner/repo.git → { url }
|
|
15
|
+
* github:owner/repo → expands to https clone URL
|
|
16
|
+
* Returns undefined for anything that looks like a local path.
|
|
17
|
+
*/
|
|
18
|
+
export function parseGitSource(source) {
|
|
19
|
+
const [locator, subpath] = splitSubpath(source);
|
|
20
|
+
if (locator.startsWith("github:")) {
|
|
21
|
+
const repo = locator.slice("github:".length).replace(/\.git$/, "");
|
|
22
|
+
return { url: `https://github.com/${repo}.git`, subpath };
|
|
23
|
+
}
|
|
24
|
+
if (locator.startsWith("https://") ||
|
|
25
|
+
locator.startsWith("http://") ||
|
|
26
|
+
locator.startsWith("git://") ||
|
|
27
|
+
locator.startsWith("ssh://") ||
|
|
28
|
+
locator.startsWith("file://") ||
|
|
29
|
+
/^git@[^:]+:/.test(locator)) {
|
|
30
|
+
return { url: locator, subpath };
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
function splitSubpath(source) {
|
|
35
|
+
const hash = source.indexOf("#");
|
|
36
|
+
if (hash === -1)
|
|
37
|
+
return [source, undefined];
|
|
38
|
+
return [source.slice(0, hash), source.slice(hash + 1) || undefined];
|
|
39
|
+
}
|
|
40
|
+
/** Clone a git source (shallow) and resolve the extension directory within it. */
|
|
41
|
+
export function resolveGitSource(spec) {
|
|
42
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "mikan-ext-git-"));
|
|
43
|
+
const cleanup = () => rmSync(tempRoot, { recursive: true, force: true });
|
|
44
|
+
try {
|
|
45
|
+
execFileSync("git", ["clone", "--depth", "1", spec.url, tempRoot], { stdio: "inherit" });
|
|
46
|
+
const dir = spec.subpath ? join(tempRoot, spec.subpath) : tempRoot;
|
|
47
|
+
if (!existsSync(dir)) {
|
|
48
|
+
throw new Error(`Subpath not found in repository: ${spec.subpath}`);
|
|
49
|
+
}
|
|
50
|
+
// Install runtime dependencies only when the extension declares any.
|
|
51
|
+
if (hasDependencies(dir)) {
|
|
52
|
+
execFileSync("npm", ["install", "--omit=dev", "--no-audit", "--no-fund"], {
|
|
53
|
+
cwd: dir,
|
|
54
|
+
stdio: "inherit",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return { dir, cleanup };
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
cleanup();
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function hasDependencies(dir) {
|
|
65
|
+
const packageJsonPath = join(dir, "package.json");
|
|
66
|
+
if (!existsSync(packageJsonPath))
|
|
67
|
+
return false;
|
|
68
|
+
try {
|
|
69
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
70
|
+
return Boolean(pkg.dependencies && Object.keys(pkg.dependencies).length > 0);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=ext-git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ext-git.js","sourceRoot":"","sources":["../../src/cli/ext-git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resolve a `mikan ext install` source that points at a git repository into a\n * local directory: clone it, descend into an optional `#subpath`, and run\n * `npm install --omit=dev` when the extension declares dependencies. The\n * caller validates + copies the returned directory, then calls cleanup().\n */\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdtempSync, readFileSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface ResolvedGitSource {\n /** Local directory holding the extension (clone root or a subpath within). */\n dir: string;\n /** Remove the temporary clone. */\n cleanup: () => void;\n}\n\ninterface GitSpec {\n url: string;\n subpath?: string;\n}\n\n/**\n * Recognize a git source and split off an optional `#subpath`:\n * https://host/owner/repo.git#dir/ext → { url, subpath }\n * git@host:owner/repo.git → { url }\n * github:owner/repo → expands to https clone URL\n * Returns undefined for anything that looks like a local path.\n */\nexport function parseGitSource(source: string): GitSpec | undefined {\n const [locator, subpath] = splitSubpath(source);\n\n if (locator.startsWith(\"github:\")) {\n const repo = locator.slice(\"github:\".length).replace(/\\.git$/, \"\");\n return { url: `https://github.com/${repo}.git`, subpath };\n }\n if (\n locator.startsWith(\"https://\") ||\n locator.startsWith(\"http://\") ||\n locator.startsWith(\"git://\") ||\n locator.startsWith(\"ssh://\") ||\n locator.startsWith(\"file://\") ||\n /^git@[^:]+:/.test(locator)\n ) {\n return { url: locator, subpath };\n }\n return undefined;\n}\n\nfunction splitSubpath(source: string): [string, string | undefined] {\n const hash = source.indexOf(\"#\");\n if (hash === -1) return [source, undefined];\n return [source.slice(0, hash), source.slice(hash + 1) || undefined];\n}\n\n/** Clone a git source (shallow) and resolve the extension directory within it. */\nexport function resolveGitSource(spec: GitSpec): ResolvedGitSource {\n const tempRoot = mkdtempSync(join(tmpdir(), \"mikan-ext-git-\"));\n const cleanup = () => rmSync(tempRoot, { recursive: true, force: true });\n try {\n execFileSync(\"git\", [\"clone\", \"--depth\", \"1\", spec.url, tempRoot], { stdio: \"inherit\" });\n\n const dir = spec.subpath ? join(tempRoot, spec.subpath) : tempRoot;\n if (!existsSync(dir)) {\n throw new Error(`Subpath not found in repository: ${spec.subpath}`);\n }\n\n // Install runtime dependencies only when the extension declares any.\n if (hasDependencies(dir)) {\n execFileSync(\"npm\", [\"install\", \"--omit=dev\", \"--no-audit\", \"--no-fund\"], {\n cwd: dir,\n stdio: \"inherit\",\n });\n }\n return { dir, cleanup };\n } catch (err) {\n cleanup();\n throw err;\n }\n}\n\nfunction hasDependencies(dir: string): boolean {\n const packageJsonPath = join(dir, \"package.json\");\n if (!existsSync(packageJsonPath)) return false;\n try {\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as {\n dependencies?: Record<string, string>;\n };\n return Boolean(pkg.dependencies && Object.keys(pkg.dependencies).length > 0);\n } catch {\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ext.d.ts","sourceRoot":"","sources":["../../src/cli/ext.ts"],"names":[],"mappings":"AA2EA,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBnE"}
|
package/dist/cli/ext.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mikan ext` — manage extensions from the CLI.
|
|
3
|
+
*
|
|
4
|
+
* mikan ext install <source> [--global | --conversation <id>] [--state-dir <dir>]
|
|
5
|
+
* mikan ext validate <path>
|
|
6
|
+
* mikan ext list [--conversation <id>] [--state-dir <dir>]
|
|
7
|
+
* mikan ext remove <slug> (--global | --conversation <id>) [--state-dir <dir>]
|
|
8
|
+
*
|
|
9
|
+
* `<source>` is a local path or a git URL (https://…, git@…, or github:owner/repo)
|
|
10
|
+
* with an optional `#subpath` for extensions inside a repo. Reinstalling over an
|
|
11
|
+
* existing extension updates it (data is preserved). Extensions install into the
|
|
12
|
+
* host-only state dir (never the workspace); see src/harness/extensions/LAYOUT.md.
|
|
13
|
+
*/
|
|
14
|
+
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { basename, join, resolve } from "node:path";
|
|
17
|
+
import { defaultExtensionDirs, listInstalledExtensions, validateExtension, } from "../harness/index.js";
|
|
18
|
+
import { parseGitSource, resolveGitSource } from "./ext-git.js";
|
|
19
|
+
function parseExtArgs(argv) {
|
|
20
|
+
let stateDir = join(homedir(), ".mikan");
|
|
21
|
+
let scope;
|
|
22
|
+
let conversationId;
|
|
23
|
+
const positional = [];
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg === "--global")
|
|
27
|
+
scope = "global";
|
|
28
|
+
else if (arg === "--conversation") {
|
|
29
|
+
scope = "conversation";
|
|
30
|
+
conversationId = argv[++i];
|
|
31
|
+
}
|
|
32
|
+
else if (arg.startsWith("--conversation=")) {
|
|
33
|
+
scope = "conversation";
|
|
34
|
+
conversationId = arg.slice("--conversation=".length);
|
|
35
|
+
}
|
|
36
|
+
else if (arg === "--state-dir")
|
|
37
|
+
stateDir = resolve(argv[++i] ?? "");
|
|
38
|
+
else if (arg.startsWith("--state-dir="))
|
|
39
|
+
stateDir = resolve(arg.slice("--state-dir=".length));
|
|
40
|
+
else
|
|
41
|
+
positional.push(arg);
|
|
42
|
+
}
|
|
43
|
+
return { action: positional[0], target: positional[1], stateDir, scope, conversationId };
|
|
44
|
+
}
|
|
45
|
+
/** Directory that extension CODE for the chosen scope lives in. */
|
|
46
|
+
function scopeExtensionsDir(args) {
|
|
47
|
+
if (args.scope === "global")
|
|
48
|
+
return join(args.stateDir, "global", "extensions");
|
|
49
|
+
if (args.scope === "conversation" && args.conversationId) {
|
|
50
|
+
// defaultExtensionDirs returns [global, conversation]; take the conversation one.
|
|
51
|
+
return defaultExtensionDirs(args.conversationId, args.stateDir)[1];
|
|
52
|
+
}
|
|
53
|
+
throw new Error("Specify a scope: --global or --conversation <id>");
|
|
54
|
+
}
|
|
55
|
+
const USAGE = `Usage:
|
|
56
|
+
mikan ext install <source> (--global | --conversation <id>) [--state-dir <dir>]
|
|
57
|
+
<source>: a local path, or a git URL / github:owner/repo with optional #subpath
|
|
58
|
+
e.g. github:geminixiang/mikan#examples/extensions/agent-pm
|
|
59
|
+
Reinstalling over an existing extension updates it (data preserved).
|
|
60
|
+
mikan ext validate <path>
|
|
61
|
+
mikan ext list [--conversation <id>] [--state-dir <dir>]
|
|
62
|
+
mikan ext remove <slug> (--global | --conversation <id>) [--state-dir <dir>]`;
|
|
63
|
+
function noop() { }
|
|
64
|
+
export async function runExtCommand(argv) {
|
|
65
|
+
const args = parseExtArgs(argv);
|
|
66
|
+
switch (args.action) {
|
|
67
|
+
case "validate":
|
|
68
|
+
return validateAction(args);
|
|
69
|
+
case "install":
|
|
70
|
+
return installAction(args);
|
|
71
|
+
case "list":
|
|
72
|
+
return listAction(args);
|
|
73
|
+
case "remove":
|
|
74
|
+
return removeAction(args);
|
|
75
|
+
default:
|
|
76
|
+
console.error(USAGE);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function validateAction(args) {
|
|
81
|
+
if (!args.target) {
|
|
82
|
+
console.error("ext validate: missing <path>");
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
const result = await validateExtension(resolve(args.target));
|
|
86
|
+
printValidation(result);
|
|
87
|
+
return result.ok ? 0 : 1;
|
|
88
|
+
}
|
|
89
|
+
async function installAction(args) {
|
|
90
|
+
if (!args.target) {
|
|
91
|
+
console.error("ext install: missing <path>");
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
let destDir;
|
|
95
|
+
try {
|
|
96
|
+
destDir = scopeExtensionsDir(args);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
// Resolve the source: a git URL (optionally with #subpath) is cloned to a
|
|
103
|
+
// temp dir; a local path is used directly.
|
|
104
|
+
const gitSpec = parseGitSource(args.target);
|
|
105
|
+
let source;
|
|
106
|
+
let cleanup = noop;
|
|
107
|
+
if (gitSpec) {
|
|
108
|
+
try {
|
|
109
|
+
const resolved = resolveGitSource(gitSpec);
|
|
110
|
+
source = resolved.dir;
|
|
111
|
+
cleanup = resolved.cleanup;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error(`Failed to fetch ${gitSpec.url}: ${err instanceof Error ? err.message : err}`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
source = resolve(args.target);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return await installResolved(args, source, destDir);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
cleanup();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function installResolved(args, source, destDir) {
|
|
129
|
+
const result = await validateExtension(source);
|
|
130
|
+
printValidation(result);
|
|
131
|
+
if (!result.ok) {
|
|
132
|
+
console.error("Refusing to install: fix the errors above.");
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
// Install into a named subdirectory so the slug never degenerates to the
|
|
136
|
+
// scope name. A bare file installs as <name>.<ext>; a directory as <name>/.
|
|
137
|
+
const destName = basename(source);
|
|
138
|
+
const dest = join(destDir, destName);
|
|
139
|
+
mkdirSync(destDir, { recursive: true });
|
|
140
|
+
// Reinstalling over an existing install is how updates work: replace it.
|
|
141
|
+
const replaced = existsSync(dest);
|
|
142
|
+
if (replaced)
|
|
143
|
+
rmSync(dest, { recursive: true, force: true });
|
|
144
|
+
cpSync(source, dest, { recursive: true });
|
|
145
|
+
const scopeLabel = args.scope === "global" ? "all conversations" : `conversation ${args.conversationId}`;
|
|
146
|
+
const dataDir = args.scope === "global"
|
|
147
|
+
? join(args.stateDir, "global", "extension-data", result.slug)
|
|
148
|
+
: join(args.stateDir, "conversations", args.conversationId, "extension-data", result.slug);
|
|
149
|
+
const verb = replaced ? "Reinstalled" : "Installed";
|
|
150
|
+
console.log(`\n${verb} ${result.name} (slug: ${result.slug}) for ${scopeLabel}.`);
|
|
151
|
+
console.log(` code: ${dest}`);
|
|
152
|
+
console.log(` data: ${dataDir} (created on first use, preserved on reinstall)`);
|
|
153
|
+
console.log("Run /pi-new in the conversation to activate.");
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
function listAction(args) {
|
|
157
|
+
// List the requested scope, or both when no conversation is given.
|
|
158
|
+
const dirs = args.conversationId
|
|
159
|
+
? defaultExtensionDirs(args.conversationId, args.stateDir)
|
|
160
|
+
: [join(args.stateDir, "global", "extensions")];
|
|
161
|
+
const installed = listInstalledExtensions(dirs);
|
|
162
|
+
if (installed.length === 0) {
|
|
163
|
+
console.log("No extensions installed.");
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
for (const info of installed) {
|
|
167
|
+
const scope = basename(resolve(info.dir, "..")) === "global" ? "global" : "conversation";
|
|
168
|
+
const version = info.version ? `@${info.version}` : "";
|
|
169
|
+
console.log(`${info.name}${version} [${scope}] slug=${info.slug}`);
|
|
170
|
+
if (info.description)
|
|
171
|
+
console.log(` ${info.description}`);
|
|
172
|
+
if (info.skillNames.length > 0)
|
|
173
|
+
console.log(` skills: ${info.skillNames.join(", ")}`);
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
function removeAction(args) {
|
|
178
|
+
if (!args.target) {
|
|
179
|
+
console.error("ext remove: missing <slug>");
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
let destDir;
|
|
183
|
+
try {
|
|
184
|
+
destDir = scopeExtensionsDir(args);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
// Match by installed slug so the caller doesn't need the exact dir name.
|
|
191
|
+
const match = listInstalledExtensions([destDir]).find((info) => info.slug === args.target);
|
|
192
|
+
if (!match) {
|
|
193
|
+
console.error(`No extension with slug '${args.target}' in ${destDir}`);
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
// match.path is the entrypoint. Dir-form: its parent is the extension dir to
|
|
197
|
+
// remove. Bare-file: the parent is the scan dir, so remove the file itself.
|
|
198
|
+
const parent = resolve(match.path, "..");
|
|
199
|
+
const removeTarget = parent === resolve(destDir) ? match.path : parent;
|
|
200
|
+
rmSync(removeTarget, { recursive: true, force: true });
|
|
201
|
+
console.log(`Removed ${match.name} (slug: ${match.slug}). Data left in place.`);
|
|
202
|
+
console.log("Run /pi-new in the conversation to apply.");
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
function printValidation(result) {
|
|
206
|
+
console.log(`${result.name}${result.version ? `@${result.version}` : ""} (slug: ${result.slug})`);
|
|
207
|
+
if (result.entrypoint)
|
|
208
|
+
console.log(` entrypoint: ${result.entrypoint}`);
|
|
209
|
+
if (result.skillNames.length > 0)
|
|
210
|
+
console.log(` skills: ${result.skillNames.join(", ")}`);
|
|
211
|
+
for (const warning of result.warnings)
|
|
212
|
+
console.log(` warning: ${warning}`);
|
|
213
|
+
for (const error of result.errors)
|
|
214
|
+
console.error(` error: ${error}`);
|
|
215
|
+
console.log(result.ok ? " ✓ valid" : " ✗ invalid");
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=ext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ext.js","sourceRoot":"","sources":["../../src/cli/ext.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `mikan ext` — manage extensions from the CLI.\n *\n * mikan ext install <source> [--global | --conversation <id>] [--state-dir <dir>]\n * mikan ext validate <path>\n * mikan ext list [--conversation <id>] [--state-dir <dir>]\n * mikan ext remove <slug> (--global | --conversation <id>) [--state-dir <dir>]\n *\n * `<source>` is a local path or a git URL (https://…, git@…, or github:owner/repo)\n * with an optional `#subpath` for extensions inside a repo. Reinstalling over an\n * existing extension updates it (data is preserved). Extensions install into the\n * host-only state dir (never the workspace); see src/harness/extensions/LAYOUT.md.\n */\nimport { cpSync, existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, join, resolve } from \"node:path\";\nimport {\n defaultExtensionDirs,\n listInstalledExtensions,\n validateExtension,\n} from \"../harness/index.js\";\nimport { parseGitSource, resolveGitSource } from \"./ext-git.js\";\n\ninterface ExtArgs {\n action?: string;\n target?: string;\n stateDir: string;\n scope: \"global\" | \"conversation\" | undefined;\n conversationId?: string;\n}\n\nfunction parseExtArgs(argv: string[]): ExtArgs {\n let stateDir = join(homedir(), \".mikan\");\n let scope: ExtArgs[\"scope\"];\n let conversationId: string | undefined;\n const positional: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === \"--global\") scope = \"global\";\n else if (arg === \"--conversation\") {\n scope = \"conversation\";\n conversationId = argv[++i];\n } else if (arg.startsWith(\"--conversation=\")) {\n scope = \"conversation\";\n conversationId = arg.slice(\"--conversation=\".length);\n } else if (arg === \"--state-dir\") stateDir = resolve(argv[++i] ?? \"\");\n else if (arg.startsWith(\"--state-dir=\")) stateDir = resolve(arg.slice(\"--state-dir=\".length));\n else positional.push(arg);\n }\n\n return { action: positional[0], target: positional[1], stateDir, scope, conversationId };\n}\n\n/** Directory that extension CODE for the chosen scope lives in. */\nfunction scopeExtensionsDir(args: ExtArgs): string {\n if (args.scope === \"global\") return join(args.stateDir, \"global\", \"extensions\");\n if (args.scope === \"conversation\" && args.conversationId) {\n // defaultExtensionDirs returns [global, conversation]; take the conversation one.\n return defaultExtensionDirs(args.conversationId, args.stateDir)[1];\n }\n throw new Error(\"Specify a scope: --global or --conversation <id>\");\n}\n\nconst USAGE = `Usage:\n mikan ext install <source> (--global | --conversation <id>) [--state-dir <dir>]\n <source>: a local path, or a git URL / github:owner/repo with optional #subpath\n e.g. github:geminixiang/mikan#examples/extensions/agent-pm\n Reinstalling over an existing extension updates it (data preserved).\n mikan ext validate <path>\n mikan ext list [--conversation <id>] [--state-dir <dir>]\n mikan ext remove <slug> (--global | --conversation <id>) [--state-dir <dir>]`;\n\nfunction noop(): void {}\n\nexport async function runExtCommand(argv: string[]): Promise<number> {\n const args = parseExtArgs(argv);\n\n switch (args.action) {\n case \"validate\":\n return validateAction(args);\n case \"install\":\n return installAction(args);\n case \"list\":\n return listAction(args);\n case \"remove\":\n return removeAction(args);\n default:\n console.error(USAGE);\n return 1;\n }\n}\n\nasync function validateAction(args: ExtArgs): Promise<number> {\n if (!args.target) {\n console.error(\"ext validate: missing <path>\");\n return 1;\n }\n const result = await validateExtension(resolve(args.target));\n printValidation(result);\n return result.ok ? 0 : 1;\n}\n\nasync function installAction(args: ExtArgs): Promise<number> {\n if (!args.target) {\n console.error(\"ext install: missing <path>\");\n return 1;\n }\n let destDir: string;\n try {\n destDir = scopeExtensionsDir(args);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n // Resolve the source: a git URL (optionally with #subpath) is cloned to a\n // temp dir; a local path is used directly.\n const gitSpec = parseGitSource(args.target);\n let source: string;\n let cleanup = noop;\n if (gitSpec) {\n try {\n const resolved = resolveGitSource(gitSpec);\n source = resolved.dir;\n cleanup = resolved.cleanup;\n } catch (err) {\n console.error(`Failed to fetch ${gitSpec.url}: ${err instanceof Error ? err.message : err}`);\n return 1;\n }\n } else {\n source = resolve(args.target);\n }\n\n try {\n return await installResolved(args, source, destDir);\n } finally {\n cleanup();\n }\n}\n\nasync function installResolved(args: ExtArgs, source: string, destDir: string): Promise<number> {\n const result = await validateExtension(source);\n printValidation(result);\n if (!result.ok) {\n console.error(\"Refusing to install: fix the errors above.\");\n return 1;\n }\n\n // Install into a named subdirectory so the slug never degenerates to the\n // scope name. A bare file installs as <name>.<ext>; a directory as <name>/.\n const destName = basename(source);\n const dest = join(destDir, destName);\n mkdirSync(destDir, { recursive: true });\n // Reinstalling over an existing install is how updates work: replace it.\n const replaced = existsSync(dest);\n if (replaced) rmSync(dest, { recursive: true, force: true });\n cpSync(source, dest, { recursive: true });\n\n const scopeLabel =\n args.scope === \"global\" ? \"all conversations\" : `conversation ${args.conversationId}`;\n const dataDir =\n args.scope === \"global\"\n ? join(args.stateDir, \"global\", \"extension-data\", result.slug)\n : join(args.stateDir, \"conversations\", args.conversationId!, \"extension-data\", result.slug);\n const verb = replaced ? \"Reinstalled\" : \"Installed\";\n console.log(`\\n${verb} ${result.name} (slug: ${result.slug}) for ${scopeLabel}.`);\n console.log(` code: ${dest}`);\n console.log(` data: ${dataDir} (created on first use, preserved on reinstall)`);\n console.log(\"Run /pi-new in the conversation to activate.\");\n return 0;\n}\n\nfunction listAction(args: ExtArgs): number {\n // List the requested scope, or both when no conversation is given.\n const dirs = args.conversationId\n ? defaultExtensionDirs(args.conversationId, args.stateDir)\n : [join(args.stateDir, \"global\", \"extensions\")];\n const installed = listInstalledExtensions(dirs);\n if (installed.length === 0) {\n console.log(\"No extensions installed.\");\n return 0;\n }\n for (const info of installed) {\n const scope = basename(resolve(info.dir, \"..\")) === \"global\" ? \"global\" : \"conversation\";\n const version = info.version ? `@${info.version}` : \"\";\n console.log(`${info.name}${version} [${scope}] slug=${info.slug}`);\n if (info.description) console.log(` ${info.description}`);\n if (info.skillNames.length > 0) console.log(` skills: ${info.skillNames.join(\", \")}`);\n }\n return 0;\n}\n\nfunction removeAction(args: ExtArgs): number {\n if (!args.target) {\n console.error(\"ext remove: missing <slug>\");\n return 1;\n }\n let destDir: string;\n try {\n destDir = scopeExtensionsDir(args);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n return 1;\n }\n // Match by installed slug so the caller doesn't need the exact dir name.\n const match = listInstalledExtensions([destDir]).find((info) => info.slug === args.target);\n if (!match) {\n console.error(`No extension with slug '${args.target}' in ${destDir}`);\n return 1;\n }\n // match.path is the entrypoint. Dir-form: its parent is the extension dir to\n // remove. Bare-file: the parent is the scan dir, so remove the file itself.\n const parent = resolve(match.path, \"..\");\n const removeTarget = parent === resolve(destDir) ? match.path : parent;\n rmSync(removeTarget, { recursive: true, force: true });\n console.log(`Removed ${match.name} (slug: ${match.slug}). Data left in place.`);\n console.log(\"Run /pi-new in the conversation to apply.\");\n return 0;\n}\n\nfunction printValidation(result: {\n ok: boolean;\n name: string;\n slug: string;\n version?: string;\n entrypoint?: string;\n skillNames: string[];\n errors: string[];\n warnings: string[];\n}): void {\n console.log(`${result.name}${result.version ? `@${result.version}` : \"\"} (slug: ${result.slug})`);\n if (result.entrypoint) console.log(` entrypoint: ${result.entrypoint}`);\n if (result.skillNames.length > 0) console.log(` skills: ${result.skillNames.join(\", \")}`);\n for (const warning of result.warnings) console.log(` warning: ${warning}`);\n for (const error of result.errors) console.error(` error: ${error}`);\n console.log(result.ok ? \" ✓ valid\" : \" ✗ invalid\");\n}\n"]}
|
|
@@ -51,12 +51,36 @@ export interface LoadExtensionsResult {
|
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* Filesystem-safe identifier for an extension, derived from its install
|
|
54
|
-
* location
|
|
55
|
-
* The slug keys the extension's data dir, secrets
|
|
56
|
-
* ownership — installing the same extension globally and
|
|
57
|
-
* shares one slug and therefore one data dir.
|
|
54
|
+
* location: the directory name for a directory-form extension, or the file
|
|
55
|
+
* basename for a bare file. The slug keys the extension's data dir, secrets
|
|
56
|
+
* vault, and schedule ownership — installing the same extension globally and
|
|
57
|
+
* per-conversation shares one slug and therefore one shared data dir.
|
|
58
58
|
*/
|
|
59
59
|
export declare function extensionSlug(entrypoint: string): string;
|
|
60
60
|
/** Load and activate all extensions found in the given directories. */
|
|
61
61
|
export declare function loadExtensions(options: LoadExtensionsOptions): Promise<LoadExtensionsResult>;
|
|
62
|
+
export interface ExtensionValidation {
|
|
63
|
+
ok: boolean;
|
|
64
|
+
/** Slug the extension would install as (from the source dir/file name). */
|
|
65
|
+
slug: string;
|
|
66
|
+
/** Display name (manifest name, else slug). */
|
|
67
|
+
name: string;
|
|
68
|
+
version?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
/** Resolved entrypoint that would be imported. */
|
|
71
|
+
entrypoint?: string;
|
|
72
|
+
/** Skill names shipped alongside the extension. */
|
|
73
|
+
skillNames: string[];
|
|
74
|
+
errors: string[];
|
|
75
|
+
warnings: string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Validate an extension source path (a directory or a single file) WITHOUT
|
|
79
|
+
* activating it: resolves the entrypoint (package.json `mikan.extensions`,
|
|
80
|
+
* index file, or the file itself), imports it, and checks it exports an
|
|
81
|
+
* `activate` function. Used by `mikan ext install` as a preflight and by
|
|
82
|
+
* `mikan ext validate`. Importing runs top-level module code but not
|
|
83
|
+
* `activate`, so there are no schedule/tool side effects.
|
|
84
|
+
*/
|
|
85
|
+
export declare function validateExtension(sourcePath: string): Promise<ExtensionValidation>;
|
|
62
86
|
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/harness/extensions/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/harness/extensions/loader.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAKlB,eAAe,EAIhB,MAAM,YAAY,CAAC;AAiBpB;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,QAAQ,GAAE,MAAkC,GAC3C,MAAM,EAAE,CAKV;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,sBAAsB,EAAE,CAkBhF;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE;QACP,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,aAAa,EAAE,aAAa,CAAC;KAC9B,CAAC;IACF,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,4EAA4E;IAC5E,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAuHD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAQxD;AA0OD,uEAAuE;AACvE,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAkD/B;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsDxF"}
|