grokbox 0.0.1
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/README.md +34 -0
- package/bin/grok-bot +2 -0
- package/package.json +34 -0
- package/skills/core.md +81 -0
- package/src/commands/agents.ts +110 -0
- package/src/commands/doctor.ts +50 -0
- package/src/commands/events.ts +123 -0
- package/src/commands/history.ts +65 -0
- package/src/commands/is.ts +23 -0
- package/src/commands/memory.ts +47 -0
- package/src/commands/roster.ts +11 -0
- package/src/commands/send.ts +72 -0
- package/src/deps.ts +49 -0
- package/src/errors.ts +132 -0
- package/src/gateway.ts +421 -0
- package/src/index.ts +6 -0
- package/src/opts.ts +52 -0
- package/src/output.ts +58 -0
- package/src/program.ts +213 -0
- package/src/redaction.ts +130 -0
- package/src/registry.ts +202 -0
- package/src/skills.ts +55 -0
- package/src/util.ts +59 -0
package/src/output.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { CliError, ErrorBody } from "./errors.ts";
|
|
2
|
+
|
|
3
|
+
export type Writable = {
|
|
4
|
+
write(chunk: string): void;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type SuccessEnvelope = {
|
|
8
|
+
ok: true;
|
|
9
|
+
data: unknown;
|
|
10
|
+
meta?: { gateway: { pid: number; startedAt: number } };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function writeJsonLine(out: Writable, value: unknown): void {
|
|
14
|
+
out.write(`${JSON.stringify(value)}\n`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function writeSuccess(
|
|
18
|
+
out: Writable,
|
|
19
|
+
data: unknown,
|
|
20
|
+
gateway?: { pid: number; startedAt: number },
|
|
21
|
+
): void {
|
|
22
|
+
const envelope: SuccessEnvelope = { ok: true, data };
|
|
23
|
+
if (gateway) envelope.meta = { gateway };
|
|
24
|
+
writeJsonLine(out, envelope);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function writeFailure(out: Writable, error: CliError | ErrorBody): void {
|
|
28
|
+
const body = "toErrorBody" in error ? error.toErrorBody() : error;
|
|
29
|
+
writeJsonLine(out, { ok: false, error: body });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatTable(rows: Array<Record<string, string>>): string {
|
|
33
|
+
if (rows.length === 0) return "";
|
|
34
|
+
const first = rows[0];
|
|
35
|
+
if (!first) return "";
|
|
36
|
+
const cols = Object.keys(first);
|
|
37
|
+
const widths = cols.map((col) =>
|
|
38
|
+
Math.max(col.length, ...rows.map((row) => (row[col] ?? "").length)),
|
|
39
|
+
);
|
|
40
|
+
const pad = (value: string, i: number): string => value.padEnd(widths[i] ?? value.length);
|
|
41
|
+
const header = cols.map(pad).join(" ");
|
|
42
|
+
const sep = widths.map((w) => "-".repeat(w)).join(" ");
|
|
43
|
+
const body = rows.map((row) => cols.map((col, i) => pad(row[col] ?? "", i)).join(" ")).join("\n");
|
|
44
|
+
return `${header}\n${sep}\n${body}\n`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function flattenRows(record: Record<string, unknown>, prefix = ""): Array<Record<string, string>> {
|
|
48
|
+
const rows: Array<Record<string, string>> = [];
|
|
49
|
+
for (const [key, value] of Object.entries(record)) {
|
|
50
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
51
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
52
|
+
rows.push(...flattenRows(value as Record<string, unknown>, path));
|
|
53
|
+
} else {
|
|
54
|
+
rows.push({ field: path, value: value === null || value === undefined ? "" : String(value) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return rows;
|
|
58
|
+
}
|
package/src/program.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Command, CommanderError } from "commander";
|
|
2
|
+
import { runAgentsList, runAgentsSearch, runAgentsShow } from "./commands/agents.ts";
|
|
3
|
+
import { runDoctor } from "./commands/doctor.ts";
|
|
4
|
+
import { runEvents } from "./commands/events.ts";
|
|
5
|
+
import { runHistoryTail, runHistoryThread } from "./commands/history.ts";
|
|
6
|
+
import { runIsRunning } from "./commands/is.ts";
|
|
7
|
+
import { runMemoryList } from "./commands/memory.ts";
|
|
8
|
+
import { runSend } from "./commands/send.ts";
|
|
9
|
+
import type { CliDeps } from "./deps.ts";
|
|
10
|
+
import { CliError, usage } from "./errors.ts";
|
|
11
|
+
import { writeFailure } from "./output.ts";
|
|
12
|
+
import { DEFAULT_TIMEOUT_MS, START_HERE, TOP_LEVEL_COMMANDS } from "./registry.ts";
|
|
13
|
+
import { runSkillsGet, runSkillsList } from "./skills.ts";
|
|
14
|
+
|
|
15
|
+
function addIoOptions(command: Command): Command {
|
|
16
|
+
return command
|
|
17
|
+
.option("--json", "Write JSON")
|
|
18
|
+
.option("--table", "Human-readable table for declared commands")
|
|
19
|
+
.option("--timeout-ms <n>", "Finite HTTP timeout in milliseconds", String(DEFAULT_TIMEOUT_MS));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function publicCommanderMessage(error: CommanderError): string {
|
|
23
|
+
const text = error.message.replace(/^error: /i, "").trim();
|
|
24
|
+
if (/bearer|authorization|token/i.test(text)) return "Invalid usage.";
|
|
25
|
+
return text.length > 0 ? text : "Invalid usage.";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function unexpectedMessage(error: unknown): string {
|
|
29
|
+
if (error instanceof Error) {
|
|
30
|
+
if (/bearer|authorization|token/i.test(error.message)) return "Unexpected CLI failure.";
|
|
31
|
+
return error.message;
|
|
32
|
+
}
|
|
33
|
+
return "Unexpected CLI failure.";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createProgram(deps: CliDeps): Command {
|
|
37
|
+
const program = new Command();
|
|
38
|
+
program
|
|
39
|
+
.name("grok-bot")
|
|
40
|
+
.description("Agent-first CLI for the box-local Grok Bot Gateway.")
|
|
41
|
+
.version(deps.cliVersion, "--version")
|
|
42
|
+
.enablePositionalOptions()
|
|
43
|
+
.helpCommand(false)
|
|
44
|
+
.showSuggestionAfterError(false)
|
|
45
|
+
.showHelpAfterError(false)
|
|
46
|
+
.exitOverride()
|
|
47
|
+
.addHelpText("before", `${START_HERE}\n`);
|
|
48
|
+
|
|
49
|
+
addIoOptions(program);
|
|
50
|
+
|
|
51
|
+
program.configureOutput({
|
|
52
|
+
writeOut: (chunk) => {
|
|
53
|
+
deps.stdout.write(chunk);
|
|
54
|
+
},
|
|
55
|
+
writeErr: () => {
|
|
56
|
+
// Usage failures are emitted as JSON by runCli.
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
program.action(() => {
|
|
61
|
+
throw usage("Missing command. Try grok-bot --help or grok-bot skills get core --full.");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const skills = program.command("skills").description("Bundled skills (version-matched)");
|
|
65
|
+
addIoOptions(skills.command("list").description("List bundled skills")).action(async (opts) => {
|
|
66
|
+
await runSkillsList(deps, opts);
|
|
67
|
+
});
|
|
68
|
+
addIoOptions(
|
|
69
|
+
skills
|
|
70
|
+
.command("get")
|
|
71
|
+
.argument("<name>", "Skill name")
|
|
72
|
+
.option("--full", "Include generated command reference")
|
|
73
|
+
.description("Print a bundled skill"),
|
|
74
|
+
).action(async (name: string, opts) => {
|
|
75
|
+
await runSkillsGet(deps, name, opts);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
addIoOptions(program.command("doctor").description("Check discovery and host health")).action(
|
|
79
|
+
async (opts) => {
|
|
80
|
+
await runDoctor(deps, opts);
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const agents = program.command("agents").description("Roster and transcript search");
|
|
85
|
+
addIoOptions(
|
|
86
|
+
agents
|
|
87
|
+
.command("list")
|
|
88
|
+
.option("--kind <kind>", "all, agent, or group", "all")
|
|
89
|
+
.option("--include-hidden", "Include sidebar-hidden rows")
|
|
90
|
+
.description("List compact roster rows"),
|
|
91
|
+
).action(async (opts) => {
|
|
92
|
+
await runAgentsList(deps, opts);
|
|
93
|
+
});
|
|
94
|
+
addIoOptions(
|
|
95
|
+
agents
|
|
96
|
+
.command("search")
|
|
97
|
+
.argument("<query>", "Transcript search query")
|
|
98
|
+
.option("--limit <n>", "Max matches (1..100)")
|
|
99
|
+
.description("Search transcript messages"),
|
|
100
|
+
).action(async (query: string, opts) => {
|
|
101
|
+
await runAgentsSearch(deps, query, opts);
|
|
102
|
+
});
|
|
103
|
+
addIoOptions(
|
|
104
|
+
agents
|
|
105
|
+
.command("show")
|
|
106
|
+
.argument("<id>", "Agent or group id")
|
|
107
|
+
.option("--history <n>", "Tail sample size (0..100)")
|
|
108
|
+
.description("Show one roster row"),
|
|
109
|
+
).action(async (id: string, opts) => {
|
|
110
|
+
await runAgentsShow(deps, id, opts);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
addIoOptions(
|
|
114
|
+
program
|
|
115
|
+
.command("send")
|
|
116
|
+
.option("--agent <id>", "Ordinary agent id")
|
|
117
|
+
.option("--group <id>", "Product group id")
|
|
118
|
+
.option("--text <text>", "Prompt text")
|
|
119
|
+
.option("--nonce <uuid>", "Stable client nonce (UUID v4)")
|
|
120
|
+
.description("Send a Human message via sendPrompt"),
|
|
121
|
+
).action(async (opts) => {
|
|
122
|
+
await runSend(deps, opts);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const history = program.command("history").description("Read display transcript");
|
|
126
|
+
addIoOptions(
|
|
127
|
+
history
|
|
128
|
+
.command("tail")
|
|
129
|
+
.argument("<id>", "Agent or group id")
|
|
130
|
+
.option("--limit <n>", "Entry limit (1..200)")
|
|
131
|
+
.option("--before-seq <n>", "Page backward from this seq")
|
|
132
|
+
.description("Read display transcript tail"),
|
|
133
|
+
).action(async (id: string, opts) => {
|
|
134
|
+
await runHistoryTail(deps, id, opts);
|
|
135
|
+
});
|
|
136
|
+
addIoOptions(
|
|
137
|
+
history
|
|
138
|
+
.command("thread")
|
|
139
|
+
.argument("<id>", "Agent or group id")
|
|
140
|
+
.requiredOption("--root <entry-id>", "Thread root entry id")
|
|
141
|
+
.description("Read one display thread"),
|
|
142
|
+
).action(async (id: string, opts) => {
|
|
143
|
+
await runHistoryThread(deps, id, opts);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const memory = program.command("memory").description("Read agent Memory metadata");
|
|
147
|
+
addIoOptions(
|
|
148
|
+
memory
|
|
149
|
+
.command("list")
|
|
150
|
+
.requiredOption("--agent <id>", "Agent id")
|
|
151
|
+
.option("--limit <n>", "Local slice (1..1000)")
|
|
152
|
+
.option("--content", "Include Memory content")
|
|
153
|
+
.description("List agent-scope Memory metadata"),
|
|
154
|
+
).action(async (opts) => {
|
|
155
|
+
await runMemoryList(deps, opts);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
addIoOptions(
|
|
159
|
+
program
|
|
160
|
+
.command("events")
|
|
161
|
+
.option("--channels <csv>", "Allowed channel subset")
|
|
162
|
+
.option("--once", "Exit after the first allowed event")
|
|
163
|
+
.option("--include-memory-content", "Keep memory event content")
|
|
164
|
+
.description("Subscribe to Gateway events (NDJSON)"),
|
|
165
|
+
).action(async (opts) => {
|
|
166
|
+
await runEvents(deps, opts);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const isCmd = program.command("is").description("Read roster running projection");
|
|
170
|
+
addIoOptions(
|
|
171
|
+
isCmd
|
|
172
|
+
.command("running")
|
|
173
|
+
.argument("<id>", "Agent or group id")
|
|
174
|
+
.description("Read roster running projection"),
|
|
175
|
+
).action(async (id: string, opts) => {
|
|
176
|
+
await runIsRunning(deps, id, opts);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const names = program.commands.map((command) => command.name());
|
|
180
|
+
for (const expected of TOP_LEVEL_COMMANDS) {
|
|
181
|
+
if (!names.includes(expected)) {
|
|
182
|
+
throw new Error(`command registry missing ${expected}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return program;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function runCli(argv: string[], deps: CliDeps): Promise<number> {
|
|
190
|
+
const program = createProgram(deps);
|
|
191
|
+
try {
|
|
192
|
+
await program.parseAsync(argv, { from: "user" });
|
|
193
|
+
return 0;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error instanceof CommanderError) {
|
|
196
|
+
if (
|
|
197
|
+
error.exitCode === 0 ||
|
|
198
|
+
error.code === "commander.helpDisplayed" ||
|
|
199
|
+
error.code === "commander.version"
|
|
200
|
+
) {
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
writeFailure(deps.stderr, usage(publicCommanderMessage(error)));
|
|
204
|
+
return 2;
|
|
205
|
+
}
|
|
206
|
+
if (error instanceof CliError) {
|
|
207
|
+
writeFailure(deps.stderr, error);
|
|
208
|
+
return error.exitCode;
|
|
209
|
+
}
|
|
210
|
+
writeFailure(deps.stderr, new CliError("gateway_internal", unexpectedMessage(error)));
|
|
211
|
+
return 15;
|
|
212
|
+
}
|
|
213
|
+
}
|
package/src/redaction.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { asBoolean, asNumber, asString, emptyToNull, isRecord, utf8Bytes } from "./util.ts";
|
|
2
|
+
|
|
3
|
+
export type AgentKind = "agent" | "group";
|
|
4
|
+
|
|
5
|
+
export function agentKind(row: Record<string, unknown>): AgentKind {
|
|
6
|
+
return row.isGroup === true ? "group" : "agent";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type CompactRoster = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
title: string | null;
|
|
13
|
+
kind: AgentKind;
|
|
14
|
+
isHidden: boolean;
|
|
15
|
+
isRunning: boolean;
|
|
16
|
+
isRunningTurn: boolean;
|
|
17
|
+
awaitingUserResponse: boolean;
|
|
18
|
+
hasUnread: boolean;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function compactRosterRow(row: Record<string, unknown>): CompactRoster {
|
|
23
|
+
return {
|
|
24
|
+
id: asString(row.id),
|
|
25
|
+
name: asString(row.name),
|
|
26
|
+
title: emptyToNull(row.title),
|
|
27
|
+
kind: agentKind(row),
|
|
28
|
+
isHidden: asBoolean(row.isHiddenFromSidebar),
|
|
29
|
+
isRunning: asBoolean(row.isRunning),
|
|
30
|
+
isRunningTurn: asBoolean(row.isRunningTurn),
|
|
31
|
+
awaitingUserResponse: asBoolean(row.awaitingUserResponse),
|
|
32
|
+
hasUnread: asBoolean(row.hasUnread),
|
|
33
|
+
updatedAt: asNumber(row.updatedAt),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function detailRosterRow(row: Record<string, unknown>): CompactRoster & {
|
|
38
|
+
description: string;
|
|
39
|
+
memberIds: string[];
|
|
40
|
+
} {
|
|
41
|
+
return {
|
|
42
|
+
...compactRosterRow(row),
|
|
43
|
+
description: asString(row.description),
|
|
44
|
+
memberIds: Array.isArray(row.memberIds)
|
|
45
|
+
? row.memberIds.filter((id): id is string => typeof id === "string")
|
|
46
|
+
: [],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function runningProjection(row: Record<string, unknown>, observedAtMs: number) {
|
|
51
|
+
return {
|
|
52
|
+
id: asString(row.id),
|
|
53
|
+
kind: agentKind(row),
|
|
54
|
+
isRunning: asBoolean(row.isRunning),
|
|
55
|
+
isRunningTurn: asBoolean(row.isRunningTurn),
|
|
56
|
+
awaitingUserResponse: asBoolean(row.awaitingUserResponse),
|
|
57
|
+
isComposingMessage: asBoolean(row.isComposingMessage),
|
|
58
|
+
isRetrying: asBoolean(row.isRetrying),
|
|
59
|
+
observedAtMs,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function projectSearchHit(row: unknown): {
|
|
64
|
+
agentId: string;
|
|
65
|
+
entryId: string;
|
|
66
|
+
role: string;
|
|
67
|
+
timestampMs: number;
|
|
68
|
+
snippet: string;
|
|
69
|
+
} {
|
|
70
|
+
const rec = isRecord(row) ? row : {};
|
|
71
|
+
return {
|
|
72
|
+
agentId: asString(rec.agentId),
|
|
73
|
+
entryId: asString(rec.entryId),
|
|
74
|
+
role: asString(rec.role),
|
|
75
|
+
timestampMs: asNumber(rec.timestampMs),
|
|
76
|
+
snippet: asString(rec.snippet),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function projectMemory(
|
|
81
|
+
row: unknown,
|
|
82
|
+
includeContent: boolean,
|
|
83
|
+
): Record<string, unknown> {
|
|
84
|
+
const rec = isRecord(row) ? row : {};
|
|
85
|
+
const content = asString(rec.content);
|
|
86
|
+
const projected: Record<string, unknown> = {
|
|
87
|
+
id: asString(rec.id),
|
|
88
|
+
kind: asString(rec.kind, "log"),
|
|
89
|
+
createdAt: asNumber(rec.createdAt),
|
|
90
|
+
contentBytes:
|
|
91
|
+
typeof rec.contentBytes === "number" && Number.isFinite(rec.contentBytes)
|
|
92
|
+
? rec.contentBytes
|
|
93
|
+
: utf8Bytes(content),
|
|
94
|
+
};
|
|
95
|
+
if (includeContent) projected.content = content;
|
|
96
|
+
return projected;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stripRosterSecrets(row: unknown): unknown {
|
|
100
|
+
if (!isRecord(row)) return row;
|
|
101
|
+
const next = { ...row };
|
|
102
|
+
delete next.path;
|
|
103
|
+
delete next.avatarDataUrl;
|
|
104
|
+
delete next.lastMessagePreview;
|
|
105
|
+
return next;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function redactEventPayload(
|
|
109
|
+
channel: string,
|
|
110
|
+
payload: unknown,
|
|
111
|
+
includeMemoryContent: boolean,
|
|
112
|
+
): unknown {
|
|
113
|
+
if (!isRecord(payload)) return payload;
|
|
114
|
+
if (channel === "agents") {
|
|
115
|
+
const agents = Array.isArray(payload.agents) ? payload.agents.map(stripRosterSecrets) : payload.agents;
|
|
116
|
+
return { ...payload, agents };
|
|
117
|
+
}
|
|
118
|
+
if (channel === "agent-upserted") {
|
|
119
|
+
return { ...payload, agent: stripRosterSecrets(payload.agent) };
|
|
120
|
+
}
|
|
121
|
+
if (channel === "memory") {
|
|
122
|
+
const memories = Array.isArray(payload.memories) ? payload.memories : [];
|
|
123
|
+
if (includeMemoryContent) return payload;
|
|
124
|
+
return {
|
|
125
|
+
agentId: payload.agentId,
|
|
126
|
+
count: memories.length,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return payload;
|
|
130
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
export const CLI_NAME = "grok-bot";
|
|
2
|
+
export const DEFAULT_DISCOVERY_PATH = "/home/box/sand-data/gateway.json";
|
|
3
|
+
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
4
|
+
export const PROMPT_MAX_BYTES = 64 * 1024;
|
|
5
|
+
export const MEMORY_UPSTREAM_CAP = 1000;
|
|
6
|
+
export const DEFAULT_EVENT_CHANNELS = ["agents", "agent-upserted", "transcript"] as const;
|
|
7
|
+
export const ALLOWED_EVENT_CHANNELS = [
|
|
8
|
+
"agents",
|
|
9
|
+
"agent-upserted",
|
|
10
|
+
"transcript",
|
|
11
|
+
"memory",
|
|
12
|
+
"subagents",
|
|
13
|
+
"async-tasks",
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export const TOP_LEVEL_COMMANDS = [
|
|
17
|
+
"skills",
|
|
18
|
+
"doctor",
|
|
19
|
+
"agents",
|
|
20
|
+
"send",
|
|
21
|
+
"history",
|
|
22
|
+
"memory",
|
|
23
|
+
"events",
|
|
24
|
+
"is",
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export type TopLevelCommand = (typeof TOP_LEVEL_COMMANDS)[number];
|
|
28
|
+
|
|
29
|
+
export type LeafCommand = {
|
|
30
|
+
path: readonly string[];
|
|
31
|
+
usage: string;
|
|
32
|
+
summary: string;
|
|
33
|
+
table: boolean;
|
|
34
|
+
stdin: boolean;
|
|
35
|
+
gateway: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const LEAF_COMMANDS: readonly LeafCommand[] = [
|
|
39
|
+
{
|
|
40
|
+
path: ["skills", "list"],
|
|
41
|
+
usage: "grok-bot skills list [--table]",
|
|
42
|
+
summary: "List bundled skills (version-matched).",
|
|
43
|
+
table: true,
|
|
44
|
+
stdin: false,
|
|
45
|
+
gateway: false,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: ["skills", "get"],
|
|
49
|
+
usage: "grok-bot skills get core [--full] [--json]",
|
|
50
|
+
summary: "Print the bundled core skill.",
|
|
51
|
+
table: false,
|
|
52
|
+
stdin: false,
|
|
53
|
+
gateway: false,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
path: ["doctor"],
|
|
57
|
+
usage: "grok-bot doctor [--json|--table]",
|
|
58
|
+
summary: "Check discovery, loopback mapping, and GET /health.",
|
|
59
|
+
table: true,
|
|
60
|
+
stdin: false,
|
|
61
|
+
gateway: true,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: ["agents", "list"],
|
|
65
|
+
usage: "grok-bot agents list [--kind all|agent|group] [--include-hidden] [--table]",
|
|
66
|
+
summary: "List compact roster rows via listAgents.",
|
|
67
|
+
table: true,
|
|
68
|
+
stdin: false,
|
|
69
|
+
gateway: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
path: ["agents", "search"],
|
|
73
|
+
usage: "grok-bot agents search <query> [--limit <1..100>]",
|
|
74
|
+
summary: "Search transcript messages via searchAgents.",
|
|
75
|
+
table: false,
|
|
76
|
+
stdin: false,
|
|
77
|
+
gateway: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
path: ["agents", "show"],
|
|
81
|
+
usage: "grok-bot agents show <id> [--history <0..100>]",
|
|
82
|
+
summary: "Show one roster row plus tail sample size.",
|
|
83
|
+
table: false,
|
|
84
|
+
stdin: false,
|
|
85
|
+
gateway: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
path: ["send"],
|
|
89
|
+
usage: "grok-bot send (--agent <id> | --group <id>) [--text <text> | stdin] [--nonce <uuid>]",
|
|
90
|
+
summary: "Send a Human message via sendPrompt.",
|
|
91
|
+
table: false,
|
|
92
|
+
stdin: true,
|
|
93
|
+
gateway: true,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
path: ["history", "tail"],
|
|
97
|
+
usage: "grok-bot history tail <id> [--limit <1..200>] [--before-seq <n>]",
|
|
98
|
+
summary: "Read display transcript tail via getAgentTranscriptTail.",
|
|
99
|
+
table: false,
|
|
100
|
+
stdin: false,
|
|
101
|
+
gateway: true,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
path: ["history", "thread"],
|
|
105
|
+
usage: "grok-bot history thread <id> --root <entry-id>",
|
|
106
|
+
summary: "Read one display thread via getAgentThread.",
|
|
107
|
+
table: false,
|
|
108
|
+
stdin: false,
|
|
109
|
+
gateway: true,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
path: ["memory", "list"],
|
|
113
|
+
usage: "grok-bot memory list --agent <id> [--limit <1..1000>] [--content]",
|
|
114
|
+
summary: "List agent-scope Memory metadata via getAgentMemories.",
|
|
115
|
+
table: false,
|
|
116
|
+
stdin: false,
|
|
117
|
+
gateway: true,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
path: ["events"],
|
|
121
|
+
usage: "grok-bot events [--channels <csv>] [--once] [--include-memory-content]",
|
|
122
|
+
summary: "Subscribe to a narrow GET /events channel subset (NDJSON).",
|
|
123
|
+
table: false,
|
|
124
|
+
stdin: false,
|
|
125
|
+
gateway: true,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
path: ["is", "running"],
|
|
129
|
+
usage: "grok-bot is running <id> [--json|--table]",
|
|
130
|
+
summary: "Read roster isRunning projection via listAgents.",
|
|
131
|
+
table: true,
|
|
132
|
+
stdin: false,
|
|
133
|
+
gateway: true,
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
export const START_HERE = `Start here (for Agents):
|
|
138
|
+
grok-bot skills get core --full`;
|
|
139
|
+
|
|
140
|
+
export const EXAMPLES = [
|
|
141
|
+
"grok-bot skills get core --full",
|
|
142
|
+
"grok-bot doctor",
|
|
143
|
+
"grok-bot agents list --kind all --table",
|
|
144
|
+
'grok-bot agents search "deployment status" --limit 10',
|
|
145
|
+
'grok-bot send --agent <tom-agent-id> --text "hello"',
|
|
146
|
+
"printf '%s' 'summarize the current blockers' | grok-bot send --group <group-id>",
|
|
147
|
+
"grok-bot history tail <agent-id> --limit 50",
|
|
148
|
+
"grok-bot history tail <agent-id> --limit 50 --before-seq <next-before-seq>",
|
|
149
|
+
"grok-bot history thread <agent-id> --root <entry-id>",
|
|
150
|
+
"grok-bot memory list --agent <agent-id> --limit 100",
|
|
151
|
+
"grok-bot events",
|
|
152
|
+
"grok-bot is running <agent-id>",
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
export function renderCommandReference(cliVersion: string): string {
|
|
156
|
+
const lines: string[] = [
|
|
157
|
+
`# grok-bot command reference`,
|
|
158
|
+
``,
|
|
159
|
+
`CLI version: ${cliVersion}`,
|
|
160
|
+
``,
|
|
161
|
+
`Top-level names: ${TOP_LEVEL_COMMANDS.join(", ")}.`,
|
|
162
|
+
`There is no raw / api fallback. Unknown names are local usage errors.`,
|
|
163
|
+
``,
|
|
164
|
+
`## Leaf commands`,
|
|
165
|
+
``,
|
|
166
|
+
];
|
|
167
|
+
for (const leaf of LEAF_COMMANDS) {
|
|
168
|
+
lines.push(`### \`${leaf.path.join(" ")}\``);
|
|
169
|
+
lines.push("");
|
|
170
|
+
lines.push("```text");
|
|
171
|
+
lines.push(leaf.usage);
|
|
172
|
+
lines.push("```");
|
|
173
|
+
lines.push("");
|
|
174
|
+
lines.push(leaf.summary);
|
|
175
|
+
lines.push("");
|
|
176
|
+
lines.push(`- table: ${leaf.table ? "yes" : "no"}`);
|
|
177
|
+
lines.push(`- stdin: ${leaf.stdin ? "yes" : "no"}`);
|
|
178
|
+
lines.push(`- gateway: ${leaf.gateway ? "yes" : "no"}`);
|
|
179
|
+
lines.push("");
|
|
180
|
+
}
|
|
181
|
+
lines.push("## Copy-paste");
|
|
182
|
+
lines.push("");
|
|
183
|
+
lines.push("```bash");
|
|
184
|
+
for (const example of EXAMPLES) lines.push(example);
|
|
185
|
+
lines.push("```");
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push("Do not pass `--token`, `--gateway-url`, `--host`, or `--port`.");
|
|
188
|
+
lines.push("Reuse `--nonce` when retrying the same send.");
|
|
189
|
+
lines.push("");
|
|
190
|
+
return lines.join("\n");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const GATEWAY_METHODS = [
|
|
194
|
+
"listAgents",
|
|
195
|
+
"searchAgents",
|
|
196
|
+
"getAgentTranscriptTail",
|
|
197
|
+
"getAgentThread",
|
|
198
|
+
"getAgentMemories",
|
|
199
|
+
"sendPrompt",
|
|
200
|
+
] as const;
|
|
201
|
+
|
|
202
|
+
export type GatewayMethod = (typeof GATEWAY_METHODS)[number];
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { CliDeps } from "./deps.ts";
|
|
3
|
+
import { usage } from "./errors.ts";
|
|
4
|
+
import { formatTable, writeSuccess } from "./output.ts";
|
|
5
|
+
import { ioFromOpts, rejectTable } from "./opts.ts";
|
|
6
|
+
import { renderCommandReference } from "./registry.ts";
|
|
7
|
+
|
|
8
|
+
export const CORE_SKILL_NAME = "core";
|
|
9
|
+
export const CORE_SKILL_SUMMARY = "Core grok-bot usage guide.";
|
|
10
|
+
|
|
11
|
+
export async function loadCoreMarkdown(deps: CliDeps, full: boolean): Promise<string> {
|
|
12
|
+
const overview = await deps.readFile(join(deps.skillsDir, "core.md"));
|
|
13
|
+
if (!full) return overview.trimEnd() + "\n";
|
|
14
|
+
return `${overview.trimEnd()}\n\n${renderCommandReference(deps.cliVersion)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function runSkillsList(
|
|
18
|
+
deps: CliDeps,
|
|
19
|
+
raw: { json?: boolean; table?: boolean; timeoutMs?: string },
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const io = ioFromOpts(raw);
|
|
22
|
+
const data = {
|
|
23
|
+
cliVersion: deps.cliVersion,
|
|
24
|
+
skills: [{ name: CORE_SKILL_NAME, summary: CORE_SKILL_SUMMARY, fullAvailable: true }],
|
|
25
|
+
};
|
|
26
|
+
if (io.table) {
|
|
27
|
+
deps.stdout.write(
|
|
28
|
+
formatTable(
|
|
29
|
+
data.skills.map((skill) => ({
|
|
30
|
+
name: skill.name,
|
|
31
|
+
summary: skill.summary,
|
|
32
|
+
full: skill.fullAvailable ? "yes" : "no",
|
|
33
|
+
})),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
writeSuccess(deps.stdout, data);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function runSkillsGet(
|
|
42
|
+
deps: CliDeps,
|
|
43
|
+
name: string,
|
|
44
|
+
raw: { full?: boolean; json?: boolean; table?: boolean; timeoutMs?: string },
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const io = ioFromOpts(raw);
|
|
47
|
+
rejectTable(io.table, false);
|
|
48
|
+
if (name !== CORE_SKILL_NAME) throw usage(`Unknown skill '${name}'. v1 only bundles core.`);
|
|
49
|
+
const content = await loadCoreMarkdown(deps, Boolean(raw.full));
|
|
50
|
+
if (io.json) {
|
|
51
|
+
writeSuccess(deps.stdout, { name: CORE_SKILL_NAME, cliVersion: deps.cliVersion, content });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
deps.stdout.write(content.endsWith("\n") ? content : `${content}\n`);
|
|
55
|
+
}
|