@astrosheep/keiyaku 4.0.6 → 4.1.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/build/src/akuma/archetype.js +8 -1
- package/build/src/akuma/provider-recipe.d.ts +2 -0
- package/build/src/akuma/provider-recipe.js +10 -0
- package/build/src/akuma/providers/acp/config.d.ts +2 -0
- package/build/src/akuma/providers/acp/config.js +9 -1
- package/build/src/akuma/providers/acp/core.d.ts +5 -2
- package/build/src/akuma/providers/acp/core.js +29 -16
- package/build/src/akuma/providers/acp/index.js +8 -2
- package/build/src/akuma/providers/claude/index.d.ts +1 -1
- package/build/src/akuma/providers/claude/index.js +9 -5
- package/build/src/akuma/providers/codex-app-server/index.js +4 -1
- package/build/src/akuma/providers/grok-build/index.js +15 -1
- package/build/src/akuma/providers/index.js +1 -1
- package/build/src/akuma/providers/opencode-sdk/index.js +3 -0
- package/build/src/akuma/providers/pi/index.js +3 -1
- package/build/src/body/amend.js +0 -9
- package/build/src/body/region.js +2 -2
- package/build/src/cli/accepted.js +2 -0
- package/build/src/cli/commands/contract.d.ts +5 -3
- package/build/src/cli/commands/contract.js +23 -13
- package/build/src/cli/commands/task-invoke.d.ts +4 -1
- package/build/src/cli/commands/task-invoke.js +6 -3
- package/build/src/cli/commands/task.js +10 -2
- package/build/src/cli/parse.js +2 -2
- package/build/src/cli/render/akuma.js +42 -11
- package/build/src/cli/render/catalog.js +5 -1
- package/build/src/cli/render/contract.js +25 -3
- package/build/src/cli/render/kanshi.js +142 -94
- package/build/src/cli/render/receipt.d.ts +1 -0
- package/build/src/cli/render/receipt.js +18 -14
- package/build/src/cli/render/refusal.js +6 -0
- package/build/src/cli/render/task.js +63 -11
- package/build/src/cli/result.d.ts +8 -1
- package/build/src/contract-worktree.d.ts +0 -2
- package/build/src/contract-worktree.js +13 -24
- package/build/src/git/nuke.js +1 -6
- package/build/src/git/reconcile.d.ts +0 -2
- package/build/src/git/reconcile.js +2 -33
- package/build/src/git/tender.d.ts +13 -2
- package/build/src/git/tender.js +43 -4
- package/build/src/kanshi/read.js +32 -12
- package/build/src/kanshi/report.d.ts +2 -1
- package/build/src/library/continuation.d.ts +26 -0
- package/build/src/library/continuation.js +78 -0
- package/build/src/library/contract.d.ts +9 -5
- package/build/src/library/contract.js +27 -2
- package/build/src/library/delivery.d.ts +18 -13
- package/build/src/library/keiyaku.d.ts +1 -1
- package/build/src/library/mutation.js +11 -4
- package/build/src/protocol/deliver.d.ts +12 -2
- package/build/src/protocol/deliver.js +52 -19
- package/build/src/protocol/operations.d.ts +7 -2
- package/build/src/settlement/fence.d.ts +0 -2
- package/build/src/settlement/fence.js +0 -24
- package/build/src/task/board.d.ts +21 -2
- package/build/src/task/board.js +25 -12
- package/build/src/task/identity.js +14 -4
- package/build/src/task/index.d.ts +1 -0
- package/build/src/task/index.js +7 -1
- package/build/src/task/operations.d.ts +2 -1
- package/build/src/task/operations.js +15 -4
- package/build/src/task/query.js +2 -2
- package/build/src/task/store.d.ts +2 -0
- package/build/src/task/store.js +12 -83
- package/package.json +1 -1
|
@@ -96,7 +96,11 @@ function decodeArchetype(name, path, markdown) {
|
|
|
96
96
|
const network = archetypeEnum(values, "network", ["disabled", "enabled"]);
|
|
97
97
|
const description = archetypeField(values, "description");
|
|
98
98
|
const allowed = effectiveAllowedActions(values.allowed);
|
|
99
|
+
const systemPromptMode = archetypeEnum(values, "systemPromptMode", ["append", "replace"]);
|
|
99
100
|
const systemPrompt = lines.slice(closing + 1).join("\n");
|
|
101
|
+
if (systemPromptMode !== undefined && systemPrompt.length === 0) {
|
|
102
|
+
throw new TypeError("Akuma systemPromptMode requires a nonempty Markdown body");
|
|
103
|
+
}
|
|
100
104
|
return Object.freeze({
|
|
101
105
|
name,
|
|
102
106
|
path,
|
|
@@ -106,7 +110,10 @@ function decodeArchetype(name, path, markdown) {
|
|
|
106
110
|
...(model === undefined ? {} : { model }),
|
|
107
111
|
...(effort === undefined ? {} : { effort }),
|
|
108
112
|
...(network === undefined ? {} : { network }),
|
|
109
|
-
...(systemPrompt.length === 0 ? {} : {
|
|
113
|
+
...(systemPrompt.length === 0 ? {} : {
|
|
114
|
+
systemPrompt,
|
|
115
|
+
systemPromptMode: systemPromptMode ?? "append",
|
|
116
|
+
}),
|
|
110
117
|
}),
|
|
111
118
|
...(readonly === undefined ? {} : { readonly }),
|
|
112
119
|
allowed,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
export type SystemPromptMode = "append" | "replace";
|
|
1
2
|
export type ProviderOptions = Readonly<{
|
|
2
3
|
model?: string;
|
|
3
4
|
effort?: string;
|
|
4
5
|
readonly?: true;
|
|
5
6
|
network?: "disabled" | "enabled";
|
|
6
7
|
systemPrompt?: string;
|
|
8
|
+
systemPromptMode?: SystemPromptMode;
|
|
7
9
|
}>;
|
|
8
10
|
export type ReadonlyRestraint = Readonly<{
|
|
9
11
|
enforcement: "native";
|
|
@@ -34,12 +34,22 @@ export function decodeProviderOptions(value) {
|
|
|
34
34
|
throw new TypeError("provider option network must be disabled, enabled");
|
|
35
35
|
}
|
|
36
36
|
const systemPrompt = optionText(options, "systemPrompt", "allow");
|
|
37
|
+
const systemPromptMode = options.systemPromptMode;
|
|
38
|
+
if (systemPromptMode !== undefined) {
|
|
39
|
+
if (systemPromptMode !== "append" && systemPromptMode !== "replace") {
|
|
40
|
+
throw new TypeError("provider option systemPromptMode must be append, replace");
|
|
41
|
+
}
|
|
42
|
+
if (systemPrompt === undefined) {
|
|
43
|
+
throw new TypeError("provider option systemPromptMode requires systemPrompt");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
37
46
|
return Object.freeze({
|
|
38
47
|
...(model === undefined ? {} : { model }),
|
|
39
48
|
...(effort === undefined ? {} : { effort }),
|
|
40
49
|
...(options.readonly === undefined ? {} : { readonly: true }),
|
|
41
50
|
...(network === undefined ? {} : { network }),
|
|
42
51
|
...(systemPrompt === undefined ? {} : { systemPrompt }),
|
|
52
|
+
...(systemPromptMode === undefined ? {} : { systemPromptMode }),
|
|
43
53
|
});
|
|
44
54
|
}
|
|
45
55
|
export function decodeReadonlyRestraint(value) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { SystemPromptMode } from "../../provider-recipe.js";
|
|
1
2
|
export type AcpExecutionConfig = Readonly<{
|
|
2
3
|
argvBefore: readonly string[];
|
|
3
4
|
argvAfter: readonly string[];
|
|
4
5
|
modelArg?: string;
|
|
5
6
|
effortArg?: string;
|
|
6
7
|
systemPromptArg?: string;
|
|
8
|
+
systemPromptMode?: SystemPromptMode;
|
|
7
9
|
}>;
|
|
8
10
|
export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
|
|
@@ -13,7 +13,7 @@ export function decodeAcpConfig(value) {
|
|
|
13
13
|
}
|
|
14
14
|
const config = value;
|
|
15
15
|
const unknown = Object.keys(config)
|
|
16
|
-
.find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg"].includes(key));
|
|
16
|
+
.find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg", "systemPromptMode"].includes(key));
|
|
17
17
|
if (unknown !== undefined)
|
|
18
18
|
throw new TypeError(`ACP provider config has unknown field ${unknown}`);
|
|
19
19
|
if (!Array.isArray(config.argvBefore)
|
|
@@ -27,11 +27,19 @@ export function decodeAcpConfig(value) {
|
|
|
27
27
|
const modelArg = argumentName(config, "modelArg");
|
|
28
28
|
const effortArg = argumentName(config, "effortArg");
|
|
29
29
|
const systemPromptArg = argumentName(config, "systemPromptArg");
|
|
30
|
+
const systemPromptMode = config.systemPromptMode;
|
|
31
|
+
if (systemPromptMode !== undefined && systemPromptMode !== "append" && systemPromptMode !== "replace") {
|
|
32
|
+
throw new TypeError("ACP provider config systemPromptMode must be append, replace");
|
|
33
|
+
}
|
|
34
|
+
if (systemPromptMode !== undefined && systemPromptArg === undefined) {
|
|
35
|
+
throw new TypeError("ACP provider config systemPromptMode requires systemPromptArg");
|
|
36
|
+
}
|
|
30
37
|
return Object.freeze({
|
|
31
38
|
argvBefore: Object.freeze([...config.argvBefore]),
|
|
32
39
|
argvAfter: Object.freeze([...config.argvAfter]),
|
|
33
40
|
...(modelArg === undefined ? {} : { modelArg }),
|
|
34
41
|
...(effortArg === undefined ? {} : { effortArg }),
|
|
35
42
|
...(systemPromptArg === undefined ? {} : { systemPromptArg }),
|
|
43
|
+
...(systemPromptMode === undefined ? {} : { systemPromptMode }),
|
|
36
44
|
});
|
|
37
45
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import type * as AcpSdk from "@agentclientprotocol/sdk";
|
|
2
2
|
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
3
3
|
import { type ProviderAdapter, type Session } from "../../provider.js";
|
|
4
4
|
import { type AcpToolInterpreter, type AcpToolUpdate } from "./events.js";
|
|
5
5
|
export type { AcpToolInterpreter, AcpToolUpdate };
|
|
6
6
|
export type AcpStartInput = Parameters<ProviderAdapter["start"]>[0] | Parameters<NonNullable<ProviderAdapter["resume"]>>[0];
|
|
7
|
+
export type AcpSessionMeta = Readonly<Record<string, unknown>>;
|
|
7
8
|
export type AcpDependencies = Readonly<{
|
|
8
9
|
spawnProcess?: typeof spawnStdioProcess;
|
|
9
10
|
interpretTool?: AcpToolInterpreter;
|
|
11
|
+
freshSessionMeta?: AcpSessionMeta;
|
|
12
|
+
loadSessionMeta?: AcpSessionMeta;
|
|
10
13
|
}>;
|
|
11
14
|
export type AcpLiveSession = Readonly<{
|
|
12
15
|
session: Session;
|
|
13
|
-
agent:
|
|
16
|
+
agent: AcpSdk.ClientContext;
|
|
14
17
|
sessionId: string;
|
|
15
18
|
open(): boolean;
|
|
16
19
|
}>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import * as acp from "@agentclientprotocol/sdk";
|
|
2
1
|
import { Readable, Writable } from "node:stream";
|
|
3
2
|
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
4
3
|
import { abortable } from "../../abort.js";
|
|
5
4
|
import { AKUMA_REQUESTS_ENV, AgentEventChannel, } from "../../provider.js";
|
|
6
5
|
import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate, } from "./events.js";
|
|
7
|
-
function diagnostic(error) {
|
|
6
|
+
function diagnostic(acp, error) {
|
|
8
7
|
if (!(error instanceof Error))
|
|
9
8
|
return String(error);
|
|
10
9
|
if (!(error instanceof acp.RequestError) || error.data === undefined)
|
|
@@ -18,7 +17,7 @@ function diagnostic(error) {
|
|
|
18
17
|
}
|
|
19
18
|
return data.length === 0 ? `${error.message} [${error.code}]` : `${error.message} [${error.code}]: ${data}`;
|
|
20
19
|
}
|
|
21
|
-
function createAcpClient(onUpdate) {
|
|
20
|
+
function createAcpClient(acp, onUpdate) {
|
|
22
21
|
return acp.client({ name: "keiyaku" })
|
|
23
22
|
.onNotification(acp.methods.client.session.update, ({ params }) => onUpdate(params));
|
|
24
23
|
}
|
|
@@ -29,7 +28,10 @@ function promptResult(response) {
|
|
|
29
28
|
}
|
|
30
29
|
return { kind: "failed", diagnostic: `ACP prompt ended ${response.stopReason}` };
|
|
31
30
|
}
|
|
32
|
-
|
|
31
|
+
function requestMeta(meta) {
|
|
32
|
+
return meta === undefined || Object.keys(meta).length === 0 ? {} : { _meta: meta };
|
|
33
|
+
}
|
|
34
|
+
async function establishSession(acp, agent, input, dependencies) {
|
|
33
35
|
const initialized = await agent.request(acp.methods.agent.initialize, {
|
|
34
36
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
35
37
|
clientInfo: { name: "keiyaku", version: "4" },
|
|
@@ -38,15 +40,24 @@ async function establishSession(agent, input) {
|
|
|
38
40
|
throw new Error("ACP agent does not advertise session/load");
|
|
39
41
|
}
|
|
40
42
|
if (input.session.kind === "fresh") {
|
|
41
|
-
return (await agent.request(acp.methods.agent.session.new, {
|
|
43
|
+
return (await agent.request(acp.methods.agent.session.new, {
|
|
44
|
+
cwd: input.cwd,
|
|
45
|
+
mcpServers: [],
|
|
46
|
+
...requestMeta(dependencies.freshSessionMeta),
|
|
47
|
+
})).sessionId;
|
|
42
48
|
}
|
|
43
49
|
const sessionId = input.session.coordinate.sessionId;
|
|
44
50
|
if (sessionId === undefined)
|
|
45
51
|
throw new Error("ACP resume coordinate has no session id");
|
|
46
|
-
await agent.request(acp.methods.agent.session.load, {
|
|
52
|
+
await agent.request(acp.methods.agent.session.load, {
|
|
53
|
+
cwd: input.cwd,
|
|
54
|
+
mcpServers: [],
|
|
55
|
+
sessionId,
|
|
56
|
+
...requestMeta(dependencies.loadSessionMeta),
|
|
57
|
+
});
|
|
47
58
|
return sessionId;
|
|
48
59
|
}
|
|
49
|
-
function createAcpTurn(connection, interpret) {
|
|
60
|
+
function createAcpTurn(acp, connection, interpret) {
|
|
50
61
|
const events = new AgentEventChannel();
|
|
51
62
|
let state = EMPTY_ACP_EVENT_STATE;
|
|
52
63
|
let terminal = false;
|
|
@@ -66,7 +77,7 @@ function createAcpTurn(connection, interpret) {
|
|
|
66
77
|
terminal = true;
|
|
67
78
|
let completionResult = result;
|
|
68
79
|
const failCleanup = (error) => {
|
|
69
|
-
completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(error)}` };
|
|
80
|
+
completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(acp, error)}` };
|
|
70
81
|
};
|
|
71
82
|
try {
|
|
72
83
|
await cleanup();
|
|
@@ -96,11 +107,12 @@ function createAcpTurn(connection, interpret) {
|
|
|
96
107
|
open: () => !terminal,
|
|
97
108
|
};
|
|
98
109
|
}
|
|
99
|
-
function beginAcpPrompt(
|
|
110
|
+
function beginAcpPrompt(context, child, turn, sessionId, input) {
|
|
111
|
+
const { acp, agent } = context;
|
|
100
112
|
void agent.request(acp.methods.agent.session.prompt, {
|
|
101
113
|
sessionId,
|
|
102
114
|
prompt: [{ type: "text", text: input.body }, ...input.launchTells.map(({ text }) => ({ type: "text", text }))],
|
|
103
|
-
}).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(error) }, () => child.endInputAndDrain()));
|
|
115
|
+
}).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(acp, error) }, () => child.endInputAndDrain()));
|
|
104
116
|
void child.exited.then((exit) => turn.finish({
|
|
105
117
|
kind: "failed",
|
|
106
118
|
diagnostic: exit.stderr || `ACP process exited${exit.code === null ? "" : ` with code ${exit.code}`}`,
|
|
@@ -115,7 +127,7 @@ function beginAcpPrompt(agent, child, turn, sessionId, input) {
|
|
|
115
127
|
await agent.notify(acp.methods.agent.session.cancel, { sessionId });
|
|
116
128
|
}
|
|
117
129
|
catch (error) {
|
|
118
|
-
result = { kind: "failed", diagnostic: diagnostic(error) };
|
|
130
|
+
result = { kind: "failed", diagnostic: diagnostic(acp, error) };
|
|
119
131
|
}
|
|
120
132
|
if (!turn.open())
|
|
121
133
|
await child.close(true);
|
|
@@ -131,6 +143,7 @@ function beginAcpPrompt(agent, child, turn, sessionId, input) {
|
|
|
131
143
|
};
|
|
132
144
|
}
|
|
133
145
|
export async function startAcpSession(launch, input, dependencies = {}) {
|
|
146
|
+
const acp = await import("@agentclientprotocol/sdk");
|
|
134
147
|
const signal = input.signal ?? new AbortController().signal;
|
|
135
148
|
signal.throwIfAborted();
|
|
136
149
|
const child = (dependencies.spawnProcess ?? spawnStdioProcess)({
|
|
@@ -144,15 +157,15 @@ export async function startAcpSession(launch, input, dependencies = {}) {
|
|
|
144
157
|
});
|
|
145
158
|
let sessionId;
|
|
146
159
|
let turn;
|
|
147
|
-
const connection = createAcpClient((notification) => {
|
|
160
|
+
const connection = createAcpClient(acp, (notification) => {
|
|
148
161
|
if (notification.sessionId === sessionId)
|
|
149
162
|
turn.update(notification.update);
|
|
150
163
|
}).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
|
|
151
|
-
turn = createAcpTurn(connection, dependencies.interpretTool);
|
|
164
|
+
turn = createAcpTurn(acp, connection, dependencies.interpretTool);
|
|
152
165
|
try {
|
|
153
|
-
sessionId = await abortable(establishSession(connection.agent, input), signal);
|
|
166
|
+
sessionId = await abortable(establishSession(acp, connection.agent, input, dependencies), signal);
|
|
154
167
|
turn.events.emit({ type: "session", coordinate: { sessionId } });
|
|
155
|
-
const session = beginAcpPrompt(connection.agent, child, turn, sessionId, input);
|
|
168
|
+
const session = beginAcpPrompt({ acp, agent: connection.agent }, child, turn, sessionId, input);
|
|
156
169
|
return { session, agent: connection.agent, sessionId, open: turn.open };
|
|
157
170
|
}
|
|
158
171
|
catch (error) {
|
|
@@ -161,7 +174,7 @@ export async function startAcpSession(launch, input, dependencies = {}) {
|
|
|
161
174
|
await child.close(true);
|
|
162
175
|
}
|
|
163
176
|
catch (cleanup) {
|
|
164
|
-
throw new Error(`${diagnostic(error)}; ACP cleanup failed: ${diagnostic(cleanup)}`, { cause: error });
|
|
177
|
+
throw new Error(`${diagnostic(acp, error)}; ACP cleanup failed: ${diagnostic(acp, cleanup)}`, { cause: error });
|
|
165
178
|
}
|
|
166
179
|
turn.events.end();
|
|
167
180
|
throw error;
|
|
@@ -11,8 +11,14 @@ function optionAdmission(options, config) {
|
|
|
11
11
|
if (options.effort !== undefined && config.effortArg === undefined) {
|
|
12
12
|
return { kind: "refused", diagnostic: "ACP provider has no effort argument mapping" };
|
|
13
13
|
}
|
|
14
|
-
if (options.systemPrompt !== undefined && options.systemPrompt.length > 0
|
|
15
|
-
|
|
14
|
+
if (options.systemPrompt !== undefined && options.systemPrompt.length > 0) {
|
|
15
|
+
if (config.systemPromptArg === undefined) {
|
|
16
|
+
return { kind: "refused", diagnostic: "ACP provider has no systemPrompt argument mapping" };
|
|
17
|
+
}
|
|
18
|
+
if (options.systemPromptMode !== undefined
|
|
19
|
+
&& options.systemPromptMode !== (config.systemPromptMode ?? "replace")) {
|
|
20
|
+
return { kind: "refused", diagnostic: "ACP provider systemPromptMode does not match the configured argument mode" };
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
return {
|
|
18
24
|
kind: "admitted",
|
|
@@ -19,7 +19,7 @@ export type ClaudeSdk = Readonly<{
|
|
|
19
19
|
sessionId: string;
|
|
20
20
|
}>>;
|
|
21
21
|
}>;
|
|
22
|
-
export declare function createClaudeProvider(
|
|
22
|
+
export declare function createClaudeProvider(loadOrExecution?: (() => Promise<ClaudeSdk>) | ClaudeExecution, execution?: ClaudeExecution): ProviderAdapter;
|
|
23
23
|
export declare const claudeProvider: Readonly<{
|
|
24
24
|
admitOptions(options: ProviderOptions): import("../../provider.js").ProviderOptionAdmission;
|
|
25
25
|
fork?(input: Readonly<{
|
|
@@ -74,7 +74,9 @@ function claudeQueryOptions(input, execution, abortController) {
|
|
|
74
74
|
...(input.options.model === undefined ? {} : { model: input.options.model }),
|
|
75
75
|
...(input.options.effort === undefined ? {} : { effort: input.options.effort }),
|
|
76
76
|
...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0 ? {} : {
|
|
77
|
-
systemPrompt:
|
|
77
|
+
systemPrompt: input.options.systemPromptMode === "replace"
|
|
78
|
+
? input.options.systemPrompt
|
|
79
|
+
: { type: "preset", preset: "claude_code", append: input.options.systemPrompt },
|
|
78
80
|
}),
|
|
79
81
|
...(input.session.kind === "fresh" ? {} : { resume: claudeSessionId(input.session.coordinate) }),
|
|
80
82
|
};
|
|
@@ -277,12 +279,14 @@ async function driveClaude(load, execution, drive) {
|
|
|
277
279
|
},
|
|
278
280
|
};
|
|
279
281
|
}
|
|
280
|
-
export function createClaudeProvider(
|
|
282
|
+
export function createClaudeProvider(loadOrExecution = async () => await import("@anthropic-ai/claude-agent-sdk"), execution = {}) {
|
|
283
|
+
const load = typeof loadOrExecution === "function" ? loadOrExecution : async () => await import("@anthropic-ai/claude-agent-sdk");
|
|
284
|
+
const selectedExecution = typeof loadOrExecution === "function" ? execution : loadOrExecution;
|
|
281
285
|
return {
|
|
282
286
|
admitOptions: admitClaudeOptions,
|
|
283
|
-
fork: (input) => forkClaude(load,
|
|
284
|
-
start: (input) => driveClaude(load,
|
|
285
|
-
resume: (input) => driveClaude(load,
|
|
287
|
+
fork: (input) => forkClaude(load, selectedExecution, input),
|
|
288
|
+
start: (input) => driveClaude(load, selectedExecution, input),
|
|
289
|
+
resume: (input) => driveClaude(load, selectedExecution, input),
|
|
286
290
|
};
|
|
287
291
|
}
|
|
288
292
|
export const claudeProvider = createClaudeProvider(async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
@@ -66,7 +66,10 @@ async function admitTurn(server, input, state, events, config) {
|
|
|
66
66
|
...(config === undefined ? {} : { config }),
|
|
67
67
|
...(input.options.model === undefined ? {} : { model: input.options.model }),
|
|
68
68
|
...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0
|
|
69
|
-
? {}
|
|
69
|
+
? {}
|
|
70
|
+
: input.options.systemPromptMode === "replace"
|
|
71
|
+
? { baseInstructions: input.options.systemPrompt }
|
|
72
|
+
: { developerInstructions: input.options.systemPrompt }),
|
|
70
73
|
};
|
|
71
74
|
if (input.session.kind === "fresh") {
|
|
72
75
|
state.threadId = threadId(await server.request("thread/start", threadParams));
|
|
@@ -71,7 +71,9 @@ function optionAdmission(options) {
|
|
|
71
71
|
if (options.network !== undefined) {
|
|
72
72
|
return { kind: "refused", diagnostic: "Grok Build does not support the network option" };
|
|
73
73
|
}
|
|
74
|
-
if (options.systemPrompt !== undefined
|
|
74
|
+
if (options.systemPrompt !== undefined
|
|
75
|
+
&& options.systemPrompt.length > 0
|
|
76
|
+
&& options.systemPromptMode === undefined) {
|
|
75
77
|
return { kind: "refused", diagnostic: "Grok Build does not support the systemPrompt option" };
|
|
76
78
|
}
|
|
77
79
|
return {
|
|
@@ -85,6 +87,17 @@ function optionAdmission(options) {
|
|
|
85
87
|
}),
|
|
86
88
|
};
|
|
87
89
|
}
|
|
90
|
+
function grokSessionMeta(options) {
|
|
91
|
+
if (options.systemPrompt === undefined || options.systemPrompt.length === 0)
|
|
92
|
+
return {};
|
|
93
|
+
if (options.systemPromptMode === "append")
|
|
94
|
+
return { freshSessionMeta: { rules: options.systemPrompt } };
|
|
95
|
+
if (options.systemPromptMode === "replace") {
|
|
96
|
+
const meta = { systemPromptOverride: options.systemPrompt };
|
|
97
|
+
return { freshSessionMeta: meta, loadSessionMeta: meta };
|
|
98
|
+
}
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
88
101
|
function argv(execution, options) {
|
|
89
102
|
if (execution.executable === undefined)
|
|
90
103
|
throw new Error("Grok Build provider execution requires executable");
|
|
@@ -129,6 +142,7 @@ export function createGrokBuildProvider(execution, dependencies = {}) {
|
|
|
129
142
|
return withInterject(await startAcpSession(launch, input, {
|
|
130
143
|
...dependencies,
|
|
131
144
|
interpretTool: interpretGrokTool,
|
|
145
|
+
...grokSessionMeta(input.options),
|
|
132
146
|
}));
|
|
133
147
|
};
|
|
134
148
|
return {
|
|
@@ -32,7 +32,7 @@ async function adapterFor(execution) {
|
|
|
32
32
|
const { claudeProvider, createClaudeProvider } = await import("./claude/index.js");
|
|
33
33
|
return execution.executable === undefined && execution.env === undefined
|
|
34
34
|
? claudeProvider
|
|
35
|
-
: createClaudeProvider(
|
|
35
|
+
: createClaudeProvider(execution);
|
|
36
36
|
}
|
|
37
37
|
if (execution.kind === "codex-app-server")
|
|
38
38
|
return (await import("./codex-app-server/index.js")).createCodexAppServerProvider(execution);
|
|
@@ -24,6 +24,9 @@ function opencodeSessionId(coordinateValue) {
|
|
|
24
24
|
function admit(options) {
|
|
25
25
|
if (options.network !== undefined)
|
|
26
26
|
throw new Error("OpenCode does not support explicit network");
|
|
27
|
+
if (options.systemPromptMode === "replace") {
|
|
28
|
+
throw new Error("OpenCode V1 does not support replacing the native system prompt");
|
|
29
|
+
}
|
|
27
30
|
if (options.model !== undefined)
|
|
28
31
|
parseModel(options.model);
|
|
29
32
|
}
|
|
@@ -32,7 +32,9 @@ async function piCreateOptions(sdk, input) {
|
|
|
32
32
|
const resourceLoader = input.options.systemPrompt === undefined ? undefined : new sdk.DefaultResourceLoader({
|
|
33
33
|
cwd: input.cwd,
|
|
34
34
|
agentDir: sdk.getAgentDir(),
|
|
35
|
-
|
|
35
|
+
...(input.options.systemPromptMode === "append"
|
|
36
|
+
? { appendSystemPromptOverride: (base) => [...base, input.options.systemPrompt] }
|
|
37
|
+
: { systemPromptOverride: () => input.options.systemPrompt }),
|
|
36
38
|
});
|
|
37
39
|
await resourceLoader?.reload();
|
|
38
40
|
const sessionManager = input.session.kind === "fresh"
|
package/build/src/body/amend.js
CHANGED
|
@@ -151,15 +151,6 @@ function applyRemove(body, operation, document) {
|
|
|
151
151
|
body.extensionIndexes.delete(normalizeTitle(operation.target));
|
|
152
152
|
}
|
|
153
153
|
function applyUpdate(body, operation, document) {
|
|
154
|
-
if (operation.target.startsWith("Criterion ")) {
|
|
155
|
-
const title = operation.target.slice("Criterion ".length);
|
|
156
|
-
const index = criterionIndex(body, title);
|
|
157
|
-
if (index < 0)
|
|
158
|
-
refusal(`unknown criterion '${title}'`);
|
|
159
|
-
const criterion = body.criteria[index];
|
|
160
|
-
body.criteria[index] = { ...criterion, body: prose(document, operation.section, "criterion") };
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
154
|
const index = extensionIndex(body, operation.target);
|
|
164
155
|
if (index < 0)
|
|
165
156
|
refusal(`unknown extension '${operation.target}'`);
|
package/build/src/body/region.js
CHANGED
|
@@ -175,8 +175,8 @@ function patternsOverlap(left, right) {
|
|
|
175
175
|
}
|
|
176
176
|
export function decodeRegion(document, section) {
|
|
177
177
|
const blocks = directChildren(section, "code_block");
|
|
178
|
-
if (blocks.length !== 1 || !blocks[0].closed || blocks[0].info.
|
|
179
|
-
refusal("Region must contain one closed fence
|
|
178
|
+
if (blocks.length !== 1 || !blocks[0].closed || (blocks[0].info !== "" && blocks[0].info !== "txt")) {
|
|
179
|
+
refusal("Region must contain one closed fence with no info string or the exact 'txt' info string");
|
|
180
180
|
}
|
|
181
181
|
const other = section.children.filter((node) => node !== blocks[0] && nonblank(document, node));
|
|
182
182
|
if (other.length > 0)
|
|
@@ -57,6 +57,7 @@ export function acceptedDeliver(result, coordinate) {
|
|
|
57
57
|
...(value.verificationReuse === undefined ? {} : { verificationReuse: value.verificationReuse }),
|
|
58
58
|
...(value.verificationSummary === undefined ? {} : { verificationSummary: value.verificationSummary }),
|
|
59
59
|
...(value.placement === undefined ? {} : { placement: value.placement }),
|
|
60
|
+
...(value.continuation === undefined ? {} : { continuation: value.continuation }),
|
|
60
61
|
...(value.cleanup === undefined ? {} : { cleanup: value.cleanup }),
|
|
61
62
|
...(value.leak === undefined ? {} : { leak: value.leak }),
|
|
62
63
|
};
|
|
@@ -78,6 +79,7 @@ export function acceptedReview(result, coordinate) {
|
|
|
78
79
|
...(value.verificationReuse === undefined ? {} : { verificationReuse: value.verificationReuse }),
|
|
79
80
|
...(value.verificationSummary === undefined ? {} : { verificationSummary: value.verificationSummary }),
|
|
80
81
|
...(value.placement === undefined ? {} : { placement: value.placement }),
|
|
82
|
+
...(value.continuation === undefined ? {} : { continuation: value.continuation }),
|
|
81
83
|
...(value.workspace === undefined ? {} : { workspace: value.workspace }),
|
|
82
84
|
...(value.cleanup === undefined ? {} : { cleanup: value.cleanup }),
|
|
83
85
|
...(value.leak === undefined ? {} : { leak: value.leak }),
|
|
@@ -5,8 +5,8 @@ export type ContractCommandSpec = Readonly<{
|
|
|
5
5
|
flags: Readonly<Record<string, ContractFlagKind>>;
|
|
6
6
|
usage: string;
|
|
7
7
|
purpose: string;
|
|
8
|
+
help?: string;
|
|
8
9
|
}>;
|
|
9
|
-
export declare const AMEND_OPERATIONS_HELP: string;
|
|
10
10
|
export declare const CONTRACT_COMMAND_SPECS: {
|
|
11
11
|
readonly bind: {
|
|
12
12
|
readonly positional: "none";
|
|
@@ -35,6 +35,7 @@ export declare const CONTRACT_COMMAND_SPECS: {
|
|
|
35
35
|
};
|
|
36
36
|
readonly usage: "amend [<contract>|@<contract>] [--after <kei/...> ... | --clear-after] [--gates <name,...>] [--actor <actor>] [-]";
|
|
37
37
|
readonly purpose: "Amend one Contract's document operations or structured terms.";
|
|
38
|
+
readonly help: string;
|
|
38
39
|
};
|
|
39
40
|
readonly deliver: {
|
|
40
41
|
readonly positional: "optional";
|
|
@@ -47,7 +48,8 @@ export declare const CONTRACT_COMMAND_SPECS: {
|
|
|
47
48
|
readonly json: "boolean";
|
|
48
49
|
};
|
|
49
50
|
readonly usage: "deliver [<contract>|@<contract>] [--message <text>] [--include-dirty] [--materialize-conflict] [--actor <actor>]";
|
|
50
|
-
readonly purpose: "Deliver one Contract candidate.";
|
|
51
|
+
readonly purpose: "Deliver one Contract candidate from the appointed worktree.";
|
|
52
|
+
readonly help: string;
|
|
51
53
|
};
|
|
52
54
|
readonly review: {
|
|
53
55
|
readonly positional: "optional";
|
|
@@ -139,7 +141,7 @@ export declare const CONTRACT_COMMAND_SPECS: {
|
|
|
139
141
|
readonly confirm: "value";
|
|
140
142
|
readonly json: "boolean";
|
|
141
143
|
};
|
|
142
|
-
readonly usage: "nuke [--confirm <WorldRoot>]";
|
|
144
|
+
readonly usage: "nuke [--confirm <WorldRoot>] [--json]";
|
|
143
145
|
readonly purpose: "Remove Keiyaku-owned data from one confirmed World.";
|
|
144
146
|
};
|
|
145
147
|
readonly settings: {
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
export const AMEND_OPERATIONS_HELP = [
|
|
2
|
-
"stdin operations (H2 sections only, no H1):",
|
|
3
|
-
" ## Replace: Context|Objective|Design|Region|Criteria|Verification|<extension>",
|
|
4
|
-
" ## Append: Context|Objective|Design|Criteria|<extension>",
|
|
5
|
-
" ## Add: Criteria|<new-extension-title>",
|
|
6
|
-
" ## Update: Criterion <existing-title>|<existing-extension-title>",
|
|
7
|
-
" ## Remove: Criterion <existing-title>|<existing-extension-title>",
|
|
8
|
-
"",
|
|
9
|
-
"full operation grammar: docs/document.md, Amend Operations",
|
|
10
|
-
].join("\n");
|
|
11
1
|
export const CONTRACT_COMMAND_SPECS = {
|
|
12
2
|
bind: { positional: "none", stdin: "required", flags: { actor: "value", task: "value", target: "value", here: "boolean", after: "repeat-value", gates: "raw-value", json: "boolean" }, usage: "bind [--task <task/...>] [--target <ref>] [--here] [--after <kei/...> ...] [--gates <name,...>] [--actor <actor>] -", purpose: "Create one Contract from stdin Markdown." },
|
|
13
|
-
amend: {
|
|
14
|
-
|
|
3
|
+
amend: {
|
|
4
|
+
positional: "optional", stdin: "optional", flags: { actor: "value", after: "repeat-value", "clear-after": "boolean", gates: "raw-value", json: "boolean" }, usage: "amend [<contract>|@<contract>] [--after <kei/...> ... | --clear-after] [--gates <name,...>] [--actor <actor>] [-]", purpose: "Amend one Contract's document operations or structured terms.",
|
|
5
|
+
help: [
|
|
6
|
+
" ## Replace: Context|Objective|Design|Region|Criteria|Verification|<extension>",
|
|
7
|
+
" ## Append: Context|Objective|Design|Criteria|<extension>",
|
|
8
|
+
" ## Add: Criteria|<new-extension-title>",
|
|
9
|
+
" ## Update: <existing-extension-title>",
|
|
10
|
+
" ## Remove: Criterion <existing-title>",
|
|
11
|
+
" ## Remove: <existing-extension-title>",
|
|
12
|
+
].join("\n"),
|
|
13
|
+
},
|
|
14
|
+
deliver: {
|
|
15
|
+
positional: "optional", stdin: "none", flags: { actor: "value", message: "value", "include-dirty": "boolean", "materialize-conflict": "boolean", json: "boolean" }, usage: "deliver [<contract>|@<contract>] [--message <text>] [--include-dirty] [--materialize-conflict] [--actor <actor>]", purpose: "Deliver one Contract candidate from the appointed worktree.",
|
|
16
|
+
help: [
|
|
17
|
+
" --include-dirty Capture the complete non-ignored worktree tree as the",
|
|
18
|
+
" candidate; stages nothing, commits nothing. Refused",
|
|
19
|
+
" while unmerged paths exist.",
|
|
20
|
+
" --materialize-conflict After a conflict result, project the judged targetHead",
|
|
21
|
+
" into the worktree as an uncommitted merge. Not a",
|
|
22
|
+
" delivery: resolve, stage, deliver again.",
|
|
23
|
+
].join("\n"),
|
|
24
|
+
},
|
|
15
25
|
review: { positional: "optional", stdin: "optional", flags: { actor: "value", satisfied: "boolean", unsatisfied: "boolean", summary: "value", json: "boolean" }, usage: "review [<contract>|@<contract>] (--satisfied | --unsatisfied) (--summary <text> | -) [--actor <actor>]", purpose: "Record one review verdict." },
|
|
16
26
|
arc: { positional: "optional", stdin: "required", flags: { actor: "value", json: "boolean" }, usage: "arc [<contract>|@<contract>] [--actor <actor>] -", purpose: "Record stdin arc Markdown for one Contract." },
|
|
17
27
|
abandon: { positional: "optional", stdin: "none", flags: { actor: "value", note: "value", json: "boolean" }, usage: "abandon [<contract>|@<contract>] [--note <text>] [--actor <actor>]", purpose: "Abandon one Contract with an optional note." },
|
|
@@ -20,7 +30,7 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
20
30
|
ls: { positional: "optional", stdin: "none", flags: { json: "boolean" }, usage: "ls task[/]\n keiyaku ls kei[/]\n keiyaku ls aku[/]\n keiyaku ls aku/<akuma>[/]\n keiyaku ls \"aku/*/*\"", purpose: "List one identity directory." },
|
|
21
31
|
audit: { positional: "optional", stdin: "none", flags: { "include-dirty": "boolean", diff: "boolean", actor: "value", json: "boolean" }, usage: "audit [<contract>|@<contract>] [--include-dirty] [--diff] [--actor <actor>]", purpose: "Ask what candidate preparation, Verification, and target placement would do." },
|
|
22
32
|
reconcile: { positional: "optional", stdin: "none", flags: { "retry-hooks": "boolean", json: "boolean" }, usage: "reconcile [<contract>|@<contract>] [--retry-hooks]", purpose: "Reconcile one Contract or the invocation world." },
|
|
23
|
-
nuke: { positional: "none", stdin: "none", flags: { confirm: "value", json: "boolean" }, usage: "nuke [--confirm <WorldRoot>]", purpose: "Remove Keiyaku-owned data from one confirmed World." },
|
|
33
|
+
nuke: { positional: "none", stdin: "none", flags: { confirm: "value", json: "boolean" }, usage: "nuke [--confirm <WorldRoot>] [--json]", purpose: "Remove Keiyaku-owned data from one confirmed World." },
|
|
24
34
|
settings: { positional: "none", stdin: "none", flags: { json: "boolean" }, usage: "settings", purpose: "Read user and project Settings resources." },
|
|
25
35
|
region: { positional: "optional", stdin: "none", flags: { overlap: "boolean", path: "value", json: "boolean" }, usage: "region [<contract>] [--overlap]\n region --path <repo-relative-path>", purpose: "Read active declared Contract Regions." },
|
|
26
36
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type BlockedTaskList, type TaskBatchResult, type TaskCompositionResult, type TaskDecompositionTree, type TaskDetail, type TaskDoctorReport, type TaskList, type TaskMutationResult, type TaskNamespaceResult, type TaskQueryResult, type TaskUpdateResult } from "../../task/index.js";
|
|
2
2
|
import type { ParsedTaskCommand } from "./task.js";
|
|
3
3
|
import type { WorldRoot } from "../../world.js";
|
|
4
|
-
export type
|
|
4
|
+
export type TaskShowResult = TaskDetail | readonly TaskDetail[] | Extract<TaskMutationResult, {
|
|
5
|
+
kind: "refused";
|
|
6
|
+
}>;
|
|
7
|
+
export type TaskInvocationResult = TaskMutationResult | TaskUpdateResult | TaskBatchResult | TaskCompositionResult | TaskShowResult | TaskList | BlockedTaskList | TaskQueryResult | TaskDecompositionTree | TaskDoctorReport | TaskNamespaceResult | TaskWorldObservation;
|
|
5
8
|
type TaskWorldRead = TaskList | BlockedTaskList | TaskQueryResult | TaskDoctorReport;
|
|
6
9
|
export type TaskWorldObservation = Readonly<{
|
|
7
10
|
kind: "present";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tasks, } from "../../task/index.js";
|
|
1
|
+
import { Tasks, observeTaskDetails, } from "../../task/index.js";
|
|
2
2
|
import { injectedBodyRequests, requestBodyTask } from "../../akuma/requests.js";
|
|
3
3
|
import { decodeTaskMutationRequest } from "../../task/mutation.js";
|
|
4
4
|
import { resolveTaskNamespaceContext, writeTaskNamespaceContext } from "../../task/context.js";
|
|
@@ -157,8 +157,11 @@ async function invokeRead(tasks, command, current) {
|
|
|
157
157
|
const id = command.positionals[0];
|
|
158
158
|
switch (command.action) {
|
|
159
159
|
case "show": {
|
|
160
|
-
const
|
|
161
|
-
|
|
160
|
+
const ids = command.positionals.map((taskId) => tasks.task({ id: taskId }).id);
|
|
161
|
+
const observed = await observeTaskDetails(tasks.root, ids);
|
|
162
|
+
if (observed.kind !== "accepted")
|
|
163
|
+
return observed;
|
|
164
|
+
return command.output === "json" || observed.value.length > 1 ? observed.value : observed.value[0];
|
|
162
165
|
}
|
|
163
166
|
case "ls": return tasks.list({ selection: command.flags.all === true ? "all" : command.flags.closed === true ? "closed" : "active", ...readScope(command, current), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
|
|
164
167
|
case "ready": return tasks.ready({ ...readScope(command, current), ...(value(command, "parent") === undefined ? {} : { parent: value(command, "parent") }), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
|
|
@@ -13,7 +13,7 @@ const TASK_COMMAND_SPECS = {
|
|
|
13
13
|
task add [--namespace <ns>] [--actor <actor>] -`,
|
|
14
14
|
purpose: "Create one Task from flags or a canonical stdin document.",
|
|
15
15
|
},
|
|
16
|
-
show: { arity: [1,
|
|
16
|
+
show: { arity: [1, Number.POSITIVE_INFINITY], flags: COMMON, usage: "task show <TaskId>...", purpose: "Read one or more Tasks and their relationships." },
|
|
17
17
|
ls: { arity: [0, 0], flags: { ...COMMON, closed: "boolean", all: "boolean", world: "boolean", limit: "value" }, usage: "task ls [--closed | --all] [--world] [--limit <n>]", purpose: "List Tasks in the selected scope." },
|
|
18
18
|
ready: { arity: [0, 0], flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" }, usage: "task ready [--world] [--parent <TaskId>] [--limit <n>]", purpose: "List open Tasks whose every need is terminal." },
|
|
19
19
|
blocked: { arity: [0, 0], flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" }, usage: "task blocked [--world] [--parent <TaskId>] [--limit <n>]", purpose: "List Tasks blocked by dependencies." },
|
|
@@ -46,7 +46,15 @@ export function isTaskAction(value) {
|
|
|
46
46
|
export function renderTaskHelp(action) {
|
|
47
47
|
if (action !== undefined) {
|
|
48
48
|
const spec = TASK_COMMAND_SPECS[action];
|
|
49
|
-
|
|
49
|
+
const queryGuide = action === "query" ? [
|
|
50
|
+
"",
|
|
51
|
+
"fields: state priority title id parent under needs blocks ready blocked created updated",
|
|
52
|
+
"operators: = != < > <= >= ~ and or not ( )",
|
|
53
|
+
"examples:",
|
|
54
|
+
" keiyaku task query --where 'priority <= 1 and ready' --world",
|
|
55
|
+
" keiyaku task query --where 'updated < 2026-08-06T00:00:00.000Z' --world",
|
|
56
|
+
].join("\n") : "";
|
|
57
|
+
return `${spec.purpose}\n\n${usageLine(spec.usage)}${queryGuide}`;
|
|
50
58
|
}
|
|
51
59
|
return [
|
|
52
60
|
"usage: keiyaku task <command> ...",
|
package/build/src/cli/parse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isTaskAction, parseTaskCommand, renderTaskHelp, renderTaskUsage, } from "./commands/task.js";
|
|
2
2
|
import { isAkumaAction, parseAkumaCatalogPath, parseAkumaCommand, renderAkumaHelp, renderAkumaRootRows, renderAkumaUsage, } from "./commands/akuma.js";
|
|
3
3
|
import { INSTALL_USAGE, parseInstallCommand, renderInstallHelp } from "./commands/install.js";
|
|
4
|
-
import {
|
|
4
|
+
import { CONTRACT_COMMAND_SPECS, } from "./commands/contract.js";
|
|
5
5
|
import { CliUsageError, isBlankInput, usageLine } from "./usage.js";
|
|
6
6
|
export { CliUsageError } from "./usage.js";
|
|
7
7
|
const ROOT_USAGE = "usage: keiyaku [-C <path>] [--repo <path>] <command> [<contract>|@<contract>] [--flag ...] [-]";
|
|
@@ -25,7 +25,7 @@ export function renderRootHelp() {
|
|
|
25
25
|
export function renderContractHelp(command) {
|
|
26
26
|
const spec = CONTRACT_COMMAND_SPECS[command];
|
|
27
27
|
const help = `${spec.purpose}\n\n${usageLine(spec.usage)}`;
|
|
28
|
-
return
|
|
28
|
+
return spec.help === undefined ? help : `${help}\n\n${spec.help}`;
|
|
29
29
|
}
|
|
30
30
|
function contractUsage(command) {
|
|
31
31
|
return usageLine(CONTRACT_COMMAND_SPECS[command].usage);
|