@modootoday/pmcp 0.14.0 → 0.15.0
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/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +46 -8
- package/dist/cli.js +1080 -537
- package/dist/http.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +94 -32
- package/dist/mailbox/index.js +7 -7
- package/dist/stdio.js +8 -8
- package/dist/tui-worker.js +321 -20
- package/docs/skills.md +3 -3
- package/docs/terminal-workspace.md +63 -0
- package/gemini-extension.json +1 -1
- package/package.json +3 -2
- package/plugin.json +1 -1
- package/plugins/pmcp/.claude-plugin/plugin.json +1 -1
- package/plugins/pmcp/gemini-extension.json +1 -1
- package/plugins/pmcp/plugin.json +1 -1
package/dist/tui-worker.js
CHANGED
|
@@ -71,9 +71,24 @@ var tuiOptions = [
|
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
name: "config",
|
|
74
|
-
describe: "
|
|
74
|
+
describe: "Project pmcp.toml (or mailbox config for create)",
|
|
75
75
|
placeholder: "<file>"
|
|
76
76
|
},
|
|
77
|
+
{
|
|
78
|
+
name: "runtime",
|
|
79
|
+
describe: "Main native runtime ID for launch",
|
|
80
|
+
placeholder: "<runtime>"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "yes",
|
|
84
|
+
describe: "Confirm the first native runtime start",
|
|
85
|
+
boolean: true
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "new",
|
|
89
|
+
describe: "Create a workspace after the previous group is stopped",
|
|
90
|
+
boolean: true
|
|
91
|
+
},
|
|
77
92
|
{
|
|
78
93
|
name: "actor-file",
|
|
79
94
|
describe: "Optional private mailbox observer credential",
|
|
@@ -81,6 +96,8 @@ var tuiOptions = [
|
|
|
81
96
|
}
|
|
82
97
|
];
|
|
83
98
|
var allowed = {
|
|
99
|
+
launch: ["config", "runtime", "yes", "new"],
|
|
100
|
+
plan: ["config", "runtime"],
|
|
84
101
|
doctor: [],
|
|
85
102
|
create: ["group-file", "columns", "rows", "config", "actor-file"],
|
|
86
103
|
inspect: ["view"],
|
|
@@ -101,7 +118,8 @@ var allowed = {
|
|
|
101
118
|
check: ["view"]
|
|
102
119
|
};
|
|
103
120
|
function parseTui(args) {
|
|
104
|
-
|
|
121
|
+
let [action, ...extra] = args.positional;
|
|
122
|
+
if (action === void 0) action = one(args, "view") ? "open" : "launch";
|
|
105
123
|
const options = action ? allowed[action] : void 0;
|
|
106
124
|
if (!action || !options || extra.length)
|
|
107
125
|
throw new ArgumentError("Provide one supported TUI operation");
|
|
@@ -121,6 +139,17 @@ function parseTui(args) {
|
|
|
121
139
|
return Number(value);
|
|
122
140
|
};
|
|
123
141
|
if (action === "doctor") return { action, input: {} };
|
|
142
|
+
if (action === "launch" || action === "plan")
|
|
143
|
+
return {
|
|
144
|
+
action,
|
|
145
|
+
input: {},
|
|
146
|
+
startup: {
|
|
147
|
+
config: one(args, "config"),
|
|
148
|
+
runtime: one(args, "runtime"),
|
|
149
|
+
fresh: args.flags.has("new"),
|
|
150
|
+
confirmed: args.flags.has("yes")
|
|
151
|
+
}
|
|
152
|
+
};
|
|
124
153
|
if (action === "create") {
|
|
125
154
|
const config = one(args, "config");
|
|
126
155
|
const actorFile = one(args, "actor-file");
|
|
@@ -677,9 +706,9 @@ async function attachNative(spec, renew) {
|
|
|
677
706
|
}, 2e4) : void 0;
|
|
678
707
|
let exitCode;
|
|
679
708
|
try {
|
|
680
|
-
exitCode = await new Promise((
|
|
709
|
+
exitCode = await new Promise((resolve5, reject) => {
|
|
681
710
|
client.once("error", reject);
|
|
682
|
-
client.once("exit", (code) =>
|
|
711
|
+
client.once("exit", (code) => resolve5(code ?? 1));
|
|
683
712
|
});
|
|
684
713
|
} finally {
|
|
685
714
|
if (timer) clearInterval(timer);
|
|
@@ -1594,7 +1623,7 @@ var DockerEngine = class {
|
|
|
1594
1623
|
if (!/^1\.\d+$/.test(version))
|
|
1595
1624
|
throw new Error("invalid_docker_api_version");
|
|
1596
1625
|
const payload = JSON.stringify(configuration);
|
|
1597
|
-
return new Promise((
|
|
1626
|
+
return new Promise((resolve5, reject) => {
|
|
1598
1627
|
const req = request(
|
|
1599
1628
|
{
|
|
1600
1629
|
socketPath: new URL(endpoint).pathname,
|
|
@@ -1629,7 +1658,7 @@ var DockerEngine = class {
|
|
|
1629
1658
|
);
|
|
1630
1659
|
if (!/^[a-f0-9]{64}$/.test(id2))
|
|
1631
1660
|
throw new Error("invalid_container_id");
|
|
1632
|
-
|
|
1661
|
+
resolve5(id2);
|
|
1633
1662
|
} catch (error) {
|
|
1634
1663
|
reject(error);
|
|
1635
1664
|
}
|
|
@@ -2709,7 +2738,7 @@ async function watchSession(operation) {
|
|
|
2709
2738
|
)
|
|
2710
2739
|
});
|
|
2711
2740
|
if (index + 1 < count)
|
|
2712
|
-
await new Promise((
|
|
2741
|
+
await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
|
|
2713
2742
|
}
|
|
2714
2743
|
return {
|
|
2715
2744
|
schemaVersion: 1,
|
|
@@ -3398,6 +3427,13 @@ var CommonHarness = class {
|
|
|
3398
3427
|
// src/tui/application/feedback.ts
|
|
3399
3428
|
import { join as join20 } from "path";
|
|
3400
3429
|
var hints = {
|
|
3430
|
+
startup_requires_recovery: "Inspect the saved group with pmcp tui plan and pmcp harness recovery inspect. No runtime was restarted.",
|
|
3431
|
+
startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
|
|
3432
|
+
startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
|
|
3433
|
+
startup_busy: "Another workspace launch is running. Wait and retry.",
|
|
3434
|
+
startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
|
|
3435
|
+
workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
|
|
3436
|
+
runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
|
|
3401
3437
|
control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
|
|
3402
3438
|
control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
|
|
3403
3439
|
native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
|
|
@@ -3573,9 +3609,9 @@ var ViewConnection = class {
|
|
|
3573
3609
|
["-S", this.socket, "attach-session", "-E", "-t", view.session],
|
|
3574
3610
|
{ stdio: "inherit", env }
|
|
3575
3611
|
);
|
|
3576
|
-
return new Promise((
|
|
3612
|
+
return new Promise((resolve5, reject) => {
|
|
3577
3613
|
client.once("error", reject);
|
|
3578
|
-
client.once("exit", (code) =>
|
|
3614
|
+
client.once("exit", (code) => resolve5(code ?? 1));
|
|
3579
3615
|
});
|
|
3580
3616
|
}
|
|
3581
3617
|
};
|
|
@@ -3880,7 +3916,7 @@ async function waitFor(predicate, timeoutMs) {
|
|
|
3880
3916
|
const deadline = Date.now() + timeoutMs;
|
|
3881
3917
|
while (Date.now() < deadline) {
|
|
3882
3918
|
if (await predicate()) return;
|
|
3883
|
-
await new Promise((
|
|
3919
|
+
await new Promise((resolve5) => setTimeout(resolve5, 50));
|
|
3884
3920
|
}
|
|
3885
3921
|
throw new Error("writer_not_ready");
|
|
3886
3922
|
}
|
|
@@ -4076,6 +4112,7 @@ function resolveEncoder(model = DEFAULT_MODEL_ID, options = {}) {
|
|
|
4076
4112
|
}
|
|
4077
4113
|
|
|
4078
4114
|
// src/config.ts
|
|
4115
|
+
var CONFIG_FILE = "pmcp.toml";
|
|
4079
4116
|
function embeddingSection(path, dir, value) {
|
|
4080
4117
|
if (value === void 0) return void 0;
|
|
4081
4118
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
@@ -4166,6 +4203,17 @@ var CATALOG_KEYS = /* @__PURE__ */ new Set([
|
|
|
4166
4203
|
"scopes",
|
|
4167
4204
|
"builtin"
|
|
4168
4205
|
]);
|
|
4206
|
+
function findConfig(start2) {
|
|
4207
|
+
let dir = resolve2(start2);
|
|
4208
|
+
for (; ; ) {
|
|
4209
|
+
const candidate = join23(dir, CONFIG_FILE);
|
|
4210
|
+
if (existsSync10(candidate)) return candidate;
|
|
4211
|
+
if (existsSync10(join23(dir, ".git"))) return null;
|
|
4212
|
+
const parent = dirname5(dir);
|
|
4213
|
+
if (parent === dir) return null;
|
|
4214
|
+
dir = parent;
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4169
4217
|
function stringList(path, key, value) {
|
|
4170
4218
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
|
|
4171
4219
|
throw new ConfigError(path, `[catalog] ${key} must be a list of strings`);
|
|
@@ -4521,19 +4569,19 @@ import { setTimeout as delay } from "timers/promises";
|
|
|
4521
4569
|
|
|
4522
4570
|
// src/storage/sqlite.ts
|
|
4523
4571
|
async function openSqlite(path, options = {}) {
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4572
|
+
const provider = process.versions.bun ? "bun:sqlite" : "node:sqlite";
|
|
4573
|
+
const sqlite = await import(provider);
|
|
4574
|
+
if (provider === "node:sqlite") {
|
|
4575
|
+
const { DatabaseSync } = sqlite;
|
|
4576
|
+
return { db: new DatabaseSync(path, options), provider };
|
|
4528
4577
|
}
|
|
4529
|
-
const
|
|
4530
|
-
const { Database } = await import(name);
|
|
4578
|
+
const { Database } = sqlite;
|
|
4531
4579
|
const native = new Database(path, {
|
|
4532
4580
|
create: !options.readOnly,
|
|
4533
4581
|
readonly: options.readOnly
|
|
4534
4582
|
});
|
|
4535
4583
|
return {
|
|
4536
|
-
provider
|
|
4584
|
+
provider,
|
|
4537
4585
|
db: {
|
|
4538
4586
|
exec: (sql) => native.run(sql),
|
|
4539
4587
|
prepare: (sql) => native.query(sql),
|
|
@@ -5551,7 +5599,7 @@ ${content}
|
|
|
5551
5599
|
stopped = true;
|
|
5552
5600
|
});
|
|
5553
5601
|
while (!stopped) {
|
|
5554
|
-
await new Promise((
|
|
5602
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1500));
|
|
5555
5603
|
await draw();
|
|
5556
5604
|
}
|
|
5557
5605
|
}
|
|
@@ -5880,6 +5928,8 @@ async function createView(input) {
|
|
|
5880
5928
|
|
|
5881
5929
|
// src/tui/application/invoke.ts
|
|
5882
5930
|
async function invokeTui(invocation) {
|
|
5931
|
+
if (invocation.action === "launch" || invocation.action === "plan")
|
|
5932
|
+
throw new Error("startup_requires_cli_context");
|
|
5883
5933
|
if (invocation.action === "doctor")
|
|
5884
5934
|
return { schemaVersion: 1, ok: true, ...dependencies() };
|
|
5885
5935
|
if (invocation.action === "create") return createView(invocation.create);
|
|
@@ -5917,11 +5967,262 @@ function failedInvocation(action, error) {
|
|
|
5917
5967
|
};
|
|
5918
5968
|
}
|
|
5919
5969
|
|
|
5970
|
+
// src/tui/startup/workspace.ts
|
|
5971
|
+
import { existsSync as existsSync16 } from "fs";
|
|
5972
|
+
|
|
5973
|
+
// src/tui/startup/config.ts
|
|
5974
|
+
import { realpathSync as realpathSync12 } from "fs";
|
|
5975
|
+
import { resolve as resolve4 } from "path";
|
|
5976
|
+
var preference = [
|
|
5977
|
+
"codex-cli",
|
|
5978
|
+
"gemini-cli",
|
|
5979
|
+
"antigravity",
|
|
5980
|
+
"grok-cli",
|
|
5981
|
+
"claude-code"
|
|
5982
|
+
];
|
|
5983
|
+
function startupSettings(request2) {
|
|
5984
|
+
const path = request2.config ? resolve4(request2.cwd, request2.config) : findConfig(request2.cwd);
|
|
5985
|
+
const config = path ? readConfig(path) : void 0;
|
|
5986
|
+
const source2 = config?.raw.tui;
|
|
5987
|
+
const fail = (message) => {
|
|
5988
|
+
throw new ConfigError(path ?? "pmcp.toml", `[tui] ${message}`);
|
|
5989
|
+
};
|
|
5990
|
+
if (source2 !== void 0 && (source2 === null || typeof source2 !== "object" || Array.isArray(source2)))
|
|
5991
|
+
fail("must be a table");
|
|
5992
|
+
const values = source2 ?? {};
|
|
5993
|
+
const allowed2 = /* @__PURE__ */ new Set([
|
|
5994
|
+
"runtime",
|
|
5995
|
+
"memory_mb",
|
|
5996
|
+
"session_memory_mb",
|
|
5997
|
+
"max_active",
|
|
5998
|
+
"group_file"
|
|
5999
|
+
]);
|
|
6000
|
+
for (const key of Object.keys(values)) {
|
|
6001
|
+
if (!allowed2.has(key)) fail(`unknown key ${key}`);
|
|
6002
|
+
}
|
|
6003
|
+
const string = (key) => {
|
|
6004
|
+
const value = values[key];
|
|
6005
|
+
if (value === void 0) return void 0;
|
|
6006
|
+
if (typeof value !== "string" || !value.trim())
|
|
6007
|
+
fail(`${key} must be a non-empty string`);
|
|
6008
|
+
return value;
|
|
6009
|
+
};
|
|
6010
|
+
const integer2 = (key, fallback, min, max) => {
|
|
6011
|
+
const value = values[key] ?? fallback;
|
|
6012
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value))
|
|
6013
|
+
fail(`${key} must be an integer`);
|
|
6014
|
+
if (Number(value) < min || Number(value) > max)
|
|
6015
|
+
fail(`${key} must be between ${min} and ${max}`);
|
|
6016
|
+
return Number(value);
|
|
6017
|
+
};
|
|
6018
|
+
const configuredRuntime = string("runtime");
|
|
6019
|
+
if (configuredRuntime !== void 0 && configuredRuntime !== "auto" && !Object.hasOwn(runtimeExecutables, configuredRuntime))
|
|
6020
|
+
fail("runtime must be auto or a supported runtime ID");
|
|
6021
|
+
const available = preference.filter(
|
|
6022
|
+
(runtime3) => locateExecutable(runtimeExecutables[runtime3], request2.cwd)
|
|
6023
|
+
);
|
|
6024
|
+
let runtime2 = request2.runtime ?? configuredRuntime ?? "auto";
|
|
6025
|
+
if (runtime2 === "auto") runtime2 = available[0] ?? "codex-cli";
|
|
6026
|
+
if (!Object.hasOwn(runtimeExecutables, runtime2))
|
|
6027
|
+
fail("runtime must be a supported runtime ID");
|
|
6028
|
+
const memoryMb = integer2("memory_mb", 2048, 128, 4096);
|
|
6029
|
+
const sessionMemoryMb = integer2("session_memory_mb", 768, 32, 1536);
|
|
6030
|
+
if (sessionMemoryMb > memoryMb)
|
|
6031
|
+
fail("session memory exceeds the group budget");
|
|
6032
|
+
const groupFile = string("group_file");
|
|
6033
|
+
return {
|
|
6034
|
+
projectRoot: realpathSync12(config?.dir ?? request2.cwd),
|
|
6035
|
+
...config ? { configFile: config.path } : {},
|
|
6036
|
+
runtime: runtime2,
|
|
6037
|
+
allowedRuntimes: [.../* @__PURE__ */ new Set([...available, runtime2])],
|
|
6038
|
+
memoryMb,
|
|
6039
|
+
sessionMemoryMb,
|
|
6040
|
+
maxActive: integer2("max_active", 2, 1, 5),
|
|
6041
|
+
...groupFile ? { groupFile: resolve4(config?.dir ?? request2.cwd, groupFile) } : {}
|
|
6042
|
+
};
|
|
6043
|
+
}
|
|
6044
|
+
|
|
6045
|
+
// src/tui/startup/state.ts
|
|
6046
|
+
import { createHash as createHash8 } from "crypto";
|
|
6047
|
+
import { existsSync as existsSync15, lstatSync as lstatSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
6048
|
+
import { homedir as homedir6 } from "os";
|
|
6049
|
+
import { dirname as dirname9, join as join30 } from "path";
|
|
6050
|
+
function startupFile(projectRoot) {
|
|
6051
|
+
const root = join30(
|
|
6052
|
+
process.env.XDG_STATE_HOME ?? join30(homedir6(), ".local/state"),
|
|
6053
|
+
"pmcp/launcher"
|
|
6054
|
+
);
|
|
6055
|
+
const key = createHash8("sha256").update(projectRoot).digest("hex");
|
|
6056
|
+
return join30(root, `${key}.json`);
|
|
6057
|
+
}
|
|
6058
|
+
function readStartup(file, projectRoot) {
|
|
6059
|
+
if (!existsSync15(file)) return null;
|
|
6060
|
+
const value = object(readPrivateJson(file));
|
|
6061
|
+
if (value.schemaVersion !== 1 || value.projectRoot !== projectRoot)
|
|
6062
|
+
throw new Error("foreign_startup_state");
|
|
6063
|
+
if (!["creating", "starting", "view-pending", "ready"].includes(
|
|
6064
|
+
String(value.phase)
|
|
6065
|
+
))
|
|
6066
|
+
throw new Error("invalid_startup_state");
|
|
6067
|
+
text(value.runtime, "runtime");
|
|
6068
|
+
if (value.phase !== "creating") {
|
|
6069
|
+
text(value.groupFile, "group_file");
|
|
6070
|
+
text(value.generation, "generation");
|
|
6071
|
+
}
|
|
6072
|
+
if (value.phase === "ready") text(value.viewFile, "view_file");
|
|
6073
|
+
return value;
|
|
6074
|
+
}
|
|
6075
|
+
function saveStartup(file, state) {
|
|
6076
|
+
privateJson(file, state);
|
|
6077
|
+
}
|
|
6078
|
+
|
|
6079
|
+
// src/tui/startup/workspace.ts
|
|
6080
|
+
function succeeded(receipt) {
|
|
6081
|
+
if (receipt.ok !== true)
|
|
6082
|
+
throw new Error(String(receipt.error ?? "startup_operation_failed"));
|
|
6083
|
+
return receipt;
|
|
6084
|
+
}
|
|
6085
|
+
async function prepareWorkspace(request2) {
|
|
6086
|
+
const settings = startupSettings(request2);
|
|
6087
|
+
const file = startupFile(settings.projectRoot);
|
|
6088
|
+
let state = readStartup(file, settings.projectRoot);
|
|
6089
|
+
if (state && !["ready", "view-pending"].includes(state.phase)) {
|
|
6090
|
+
if (!request2.fresh || state.phase === "creating")
|
|
6091
|
+
throw new Error("startup_requires_recovery");
|
|
6092
|
+
}
|
|
6093
|
+
if (state && settings.groupFile && state.groupFile !== settings.groupFile)
|
|
6094
|
+
throw new Error("startup_group_conflict");
|
|
6095
|
+
if (state) {
|
|
6096
|
+
const group = new GroupStore(state.groupFile).state.group;
|
|
6097
|
+
assertGroup(group, settings.projectRoot, state.generation);
|
|
6098
|
+
if (group.state === "paused") throw new Error("startup_group_paused");
|
|
6099
|
+
if (request2.fresh) {
|
|
6100
|
+
if (group.state !== "stopped")
|
|
6101
|
+
throw new Error("workspace_already_running");
|
|
6102
|
+
state = null;
|
|
6103
|
+
} else {
|
|
6104
|
+
if (group.state !== "ready") throw new Error("startup_requires_recovery");
|
|
6105
|
+
if (request2.runtime && request2.runtime !== state.runtime)
|
|
6106
|
+
throw new Error("startup_runtime_conflict");
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
if (!state && settings.groupFile) {
|
|
6110
|
+
const store = new GroupStore(settings.groupFile);
|
|
6111
|
+
const group = store.state.group;
|
|
6112
|
+
assertGroup(group, settings.projectRoot);
|
|
6113
|
+
if (group.state !== "ready") throw new Error("startup_group_not_ready");
|
|
6114
|
+
state = {
|
|
6115
|
+
schemaVersion: 1,
|
|
6116
|
+
projectRoot: settings.projectRoot,
|
|
6117
|
+
phase: "view-pending",
|
|
6118
|
+
groupFile: store.path,
|
|
6119
|
+
generation: group.generation,
|
|
6120
|
+
runtime: settings.runtime
|
|
6121
|
+
};
|
|
6122
|
+
saveStartup(file, state);
|
|
6123
|
+
}
|
|
6124
|
+
if (!state) {
|
|
6125
|
+
state = {
|
|
6126
|
+
schemaVersion: 1,
|
|
6127
|
+
projectRoot: settings.projectRoot,
|
|
6128
|
+
phase: "creating",
|
|
6129
|
+
runtime: settings.runtime
|
|
6130
|
+
};
|
|
6131
|
+
saveStartup(file, state);
|
|
6132
|
+
const created = succeeded(
|
|
6133
|
+
await invoke({
|
|
6134
|
+
family: "group",
|
|
6135
|
+
action: "create",
|
|
6136
|
+
input: {
|
|
6137
|
+
projectRoot: settings.projectRoot,
|
|
6138
|
+
allowedRuntimes: [...settings.allowedRuntimes],
|
|
6139
|
+
memoryMb: settings.memoryMb,
|
|
6140
|
+
maxActive: settings.maxActive
|
|
6141
|
+
}
|
|
6142
|
+
})
|
|
6143
|
+
);
|
|
6144
|
+
state.groupFile = String(created.groupFile);
|
|
6145
|
+
state.generation = created.group.generation;
|
|
6146
|
+
state.phase = "starting";
|
|
6147
|
+
saveStartup(file, state);
|
|
6148
|
+
succeeded(
|
|
6149
|
+
await invoke({
|
|
6150
|
+
family: "session",
|
|
6151
|
+
action: "start",
|
|
6152
|
+
groupFile: state.groupFile,
|
|
6153
|
+
generation: state.generation,
|
|
6154
|
+
input: {
|
|
6155
|
+
runtime: settings.runtime,
|
|
6156
|
+
role: "main",
|
|
6157
|
+
memoryMb: settings.sessionMemoryMb
|
|
6158
|
+
}
|
|
6159
|
+
})
|
|
6160
|
+
);
|
|
6161
|
+
state.phase = "view-pending";
|
|
6162
|
+
saveStartup(file, state);
|
|
6163
|
+
}
|
|
6164
|
+
const observed = succeeded(
|
|
6165
|
+
await invoke({
|
|
6166
|
+
family: "session",
|
|
6167
|
+
action: "list",
|
|
6168
|
+
groupFile: state.groupFile
|
|
6169
|
+
})
|
|
6170
|
+
);
|
|
6171
|
+
const sessions = observed.sessions;
|
|
6172
|
+
const main = sessions.find(
|
|
6173
|
+
(session) => session.role === "main" && session.alive
|
|
6174
|
+
);
|
|
6175
|
+
if (!main) throw new Error("startup_main_unavailable");
|
|
6176
|
+
state.runtime = main.runtime;
|
|
6177
|
+
const existing = state.viewFile && existsSync16(state.viewFile) ? readView(state.viewFile).view : void 0;
|
|
6178
|
+
if (existing) {
|
|
6179
|
+
if (existing.groupFile !== state.groupFile || existing.generation !== state.generation)
|
|
6180
|
+
throw new Error("foreign_startup_view");
|
|
6181
|
+
}
|
|
6182
|
+
if (!existing || existing.state === "closed") {
|
|
6183
|
+
const created = await createView({
|
|
6184
|
+
groupFile: state.groupFile,
|
|
6185
|
+
columns: process.stdout.columns ?? Number(process.env.PMCP_TUI_COLUMNS),
|
|
6186
|
+
rows: process.stdout.rows ?? Number(process.env.PMCP_TUI_ROWS)
|
|
6187
|
+
});
|
|
6188
|
+
state.viewFile = created.viewFile;
|
|
6189
|
+
}
|
|
6190
|
+
state.phase = "ready";
|
|
6191
|
+
saveStartup(file, state);
|
|
6192
|
+
return {
|
|
6193
|
+
schemaVersion: 1,
|
|
6194
|
+
ok: true,
|
|
6195
|
+
viewFile: state.viewFile,
|
|
6196
|
+
groupFile: state.groupFile,
|
|
6197
|
+
generation: state.generation,
|
|
6198
|
+
runtime: state.runtime,
|
|
6199
|
+
readOnly: true
|
|
6200
|
+
};
|
|
6201
|
+
}
|
|
6202
|
+
function assertGroup(group, projectRoot, generation) {
|
|
6203
|
+
if (group.projectRoot !== projectRoot)
|
|
6204
|
+
throw new Error("startup_project_mismatch");
|
|
6205
|
+
if (generation && group.generation !== generation)
|
|
6206
|
+
throw new Error("stale_group_generation");
|
|
6207
|
+
}
|
|
6208
|
+
|
|
5920
6209
|
// src/tui/application/worker.ts
|
|
5921
6210
|
var [role, ...argv] = process.argv.slice(2);
|
|
5922
6211
|
var callback2 = argv.includes("--callback");
|
|
5923
6212
|
try {
|
|
5924
|
-
if (role === "
|
|
6213
|
+
if (role === "startup") {
|
|
6214
|
+
const [encoded, ...extra] = argv;
|
|
6215
|
+
if (!encoded || extra.length || encoded.length > 16384 || !/^[A-Za-z0-9_-]+$/.test(encoded))
|
|
6216
|
+
throw new Error("invalid_startup_arguments");
|
|
6217
|
+
const request2 = JSON.parse(
|
|
6218
|
+
Buffer.from(encoded, "base64url").toString("utf8")
|
|
6219
|
+
);
|
|
6220
|
+
if (typeof request2.cwd !== "string" || !request2.cwd)
|
|
6221
|
+
throw new Error("invalid_startup_arguments");
|
|
6222
|
+
const result = await prepareWorkspace(request2);
|
|
6223
|
+
process.stdout.write(`${JSON.stringify(result)}
|
|
6224
|
+
`);
|
|
6225
|
+
} else if (role === "attach") {
|
|
5925
6226
|
const [file, sessionId, leaseFile] = argv;
|
|
5926
6227
|
if (!file || !sessionId) throw new Error("invalid_attachment_arguments");
|
|
5927
6228
|
await waitFor(() => {
|
|
@@ -5953,7 +6254,7 @@ try {
|
|
|
5953
6254
|
tuiOptions
|
|
5954
6255
|
)
|
|
5955
6256
|
);
|
|
5956
|
-
if (
|
|
6257
|
+
if (["create", "doctor", "launch", "plan"].includes(invocation.action))
|
|
5957
6258
|
throw new Error("unsupported_worker_operation");
|
|
5958
6259
|
const result = role === "locked" ? await operate(
|
|
5959
6260
|
invocation.action,
|
package/docs/skills.md
CHANGED
|
@@ -78,12 +78,12 @@ existing URLs and immutable archive bytes remain available.
|
|
|
78
78
|
For native marketplace commands, follow the [README](../README.md).
|
|
79
79
|
Gemini and Antigravity need their own package formats for selected library plugins;
|
|
80
80
|
do not assume that identically named manifest files accept the same schema.
|
|
81
|
-
|
|
81
|
+
Since 0.14.0, `pmcp marketplace export <plugin> --runtime <tool>
|
|
82
82
|
--output <new-directory>` materializes a selected package without altering native
|
|
83
83
|
settings or installing it. Use the native manager to review and install that folder.
|
|
84
84
|
Existing export destinations are preserved.
|
|
85
85
|
|
|
86
|
-
The built-in catalog
|
|
86
|
+
The built-in catalog is available without remote fetching.
|
|
87
87
|
`--no-builtin` or `[catalog] builtin = false` opts out; library APIs continue to
|
|
88
88
|
read explicitly supplied catalog roots. `--marketplace <directory>` reads installed
|
|
89
89
|
Claude, Codex and Grok local descriptors, rejects path escapes and does not fetch
|
|
@@ -91,7 +91,7 @@ remote plugin sources. Add a remote marketplace with its native manager first.
|
|
|
91
91
|
|
|
92
92
|
Public npm skill installation uses the bundled catalog, a matching installed
|
|
93
93
|
dependency version and immutable free archive URLs. It verifies archive integrity,
|
|
94
|
-
content-only metadata, instruction digests and installed file bytes. npm or
|
|
94
|
+
content-only metadata, instruction digests and installed file bytes. npm, Bun or pnpm
|
|
95
95
|
changes the project and lockfile only after confirmation, with scripts disabled.
|
|
96
96
|
`--provider hosted` remains a separate service path with its own access policy.
|
|
97
97
|
Native plugin installation does not automatically version-match dependencies:
|
|
@@ -13,6 +13,69 @@ retain their existing group budgets and execution profile.
|
|
|
13
13
|
|
|
14
14
|
## Open a workspace
|
|
15
15
|
|
|
16
|
+
From a supported interactive terminal, run `pmcp` or `pmcp tui`. The first
|
|
17
|
+
launch chooses an installed main in this order: Codex, Gemini, agy, Grok,
|
|
18
|
+
Claude. It displays the selection and asks before starting. Press Enter to
|
|
19
|
+
start or enter q to cancel. No provider request or group creation occurs before
|
|
20
|
+
this confirmation. Use `--runtime` to choose a supported runtime ID explicitly.
|
|
21
|
+
Provider login and folder trust still use the native screen.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pmcp
|
|
25
|
+
pmcp tui --runtime codex-cli
|
|
26
|
+
pmcp tui plan
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The start creates a private owned group and read-only view, then reconnects to
|
|
30
|
+
that workspace on later launches. Defaults are 2048 MiB for the group, 768 MiB
|
|
31
|
+
for its main and at most two active sessions. Terminal size selects the existing
|
|
32
|
+
A or compact layout. Taking control remains explicit with Ctrl-g then c.
|
|
33
|
+
This launches a native process; the defaults are cooperative host supervision,
|
|
34
|
+
not a Docker isolation policy. Use an explicitly configured Docker group when
|
|
35
|
+
you require that execution profile.
|
|
36
|
+
|
|
37
|
+
The nearest `pmcp.toml` within the Git repository determines the project root,
|
|
38
|
+
including from nested cwd. A submodule does not inherit its parent's config;
|
|
39
|
+
use `--config` when you explicitly want a different configuration scope.
|
|
40
|
+
Without a config, the current real directory is the project root. Customize only
|
|
41
|
+
the values you need:
|
|
42
|
+
|
|
43
|
+
```toml
|
|
44
|
+
[tui]
|
|
45
|
+
runtime = "codex-cli"
|
|
46
|
+
memory_mb = 2048
|
|
47
|
+
session_memory_mb = 768
|
|
48
|
+
max_active = 2
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
runtime defaults to auto. Supported IDs are codex-cli, gemini-cli, antigravity,
|
|
52
|
+
grok-cli and claude-code. CLI `--runtime` overrides the config for a new main.
|
|
53
|
+
An optional group_file points to a deliberately selected existing owned group
|
|
54
|
+
with a live main; relative paths resolve from the config directory. `--config`
|
|
55
|
+
selects a config file explicitly. Unknown keys and invalid budgets are errors.
|
|
56
|
+
Config budget changes apply to a new group; existing groups keep their grants.
|
|
57
|
+
|
|
58
|
+
`pmcp tui launch --yes` explicitly confirms the first start without the prompt.
|
|
59
|
+
Launch still requires a supported interactive terminal. `pmcp tui plan` reports
|
|
60
|
+
resolved settings and saved paths without writing or invoking a provider.
|
|
61
|
+
The launcher checkpoint lives under XDG_STATE_HOME/pmcp/launcher, or
|
|
62
|
+
~/.local/state/pmcp/launcher, with private file permissions.
|
|
63
|
+
|
|
64
|
+
Reopening never adopts an unrelated tmux session or restarts a stopped main.
|
|
65
|
+
A paused group, interrupted start or changed generation requires explicit
|
|
66
|
+
harness inspection and recovery. After explicitly stopping the old owned group,
|
|
67
|
+
`pmcp tui --new` creates a new workspace. Detach preserves the existing native
|
|
68
|
+
process and draft. Closing only the view permits a new presentation over the
|
|
69
|
+
same live group. Keep original state when investigating a failed start.
|
|
70
|
+
If a creation checkpoint has no group path yet, keep it for manual inspection;
|
|
71
|
+
the launcher refuses to infer ownership or restart from that incomplete state.
|
|
72
|
+
|
|
73
|
+
Piped bare launches and leading MCP server options preserve the existing stdio
|
|
74
|
+
contract. Use `pmcp serve` explicitly in MCP configuration. Unsupported
|
|
75
|
+
interactive hosts show help; they do not wait for JSON-RPC input.
|
|
76
|
+
|
|
77
|
+
## Advanced view operations
|
|
78
|
+
|
|
16
79
|
First [create a harness group and its sessions](terminal-harness.md). Set GROUP_FILE
|
|
17
80
|
to that group's private state path, then check prerequisites and create a view:
|
|
18
81
|
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modootoday/pmcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"author": "modootoday",
|
|
6
6
|
"description": "Use the skills your packages already ship, and keep your AI tools' project configuration in one place.",
|
|
@@ -91,7 +91,8 @@
|
|
|
91
91
|
"marketplace:check": "bun scripts/build-marketplace.mjs --check",
|
|
92
92
|
"docs:check": "prettier --check --ignore-path /dev/null README.md docs/*.md && npm run marketplace:check && npm run skills:check && npm run site:check",
|
|
93
93
|
"site:deployed": "node scripts/check-deployed.mjs",
|
|
94
|
-
"
|
|
94
|
+
"package:verify": "node scripts/verify-package.mjs",
|
|
95
|
+
"prepack": "npm run docs:check && npm run build && npm run typecheck && npm test && npm run package:verify -- --pack --matrix --report-stderr",
|
|
95
96
|
"format": "prettier --write .",
|
|
96
97
|
"format:check": "prettier --check ."
|
|
97
98
|
},
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "pmcp",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"description": "Find package skills and use native runtime project configuration.",
|
|
6
6
|
"author": { "name": "modootoday" },
|
|
7
7
|
"license": "Elastic-2.0",
|
package/plugins/pmcp/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "pmcp",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"description": "Find package skills and use native runtime project configuration.",
|
|
6
6
|
"author": { "name": "modootoday" },
|
|
7
7
|
"license": "Elastic-2.0",
|