indusagi-coding-agent 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration slash commands — auth launchers, the external bridges, and the
|
|
3
|
+
* clipboard / export / share I/O verbs.
|
|
4
|
+
*/
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { createMCPAgentToolFactory, loadMCPConfig, MCPClientPool } from "indusagi/mcp";
|
|
10
|
+
import { createComposioGateway } from "indusagi/connectors-saas";
|
|
11
|
+
import type { SlashCommand, SlashContext } from "../../contract.js";
|
|
12
|
+
import { FAMILY, HANDLED, familyRunner, info, warn, splitVerb } from "./shared.js";
|
|
13
|
+
import { publishTranscript } from "../../../transcript-export/index.js";
|
|
14
|
+
import { APP_NOVEL_CARDS, memoryCard } from "../../../capability-deck/index.js";
|
|
15
|
+
import { getServerToken, hasValidServerToken, INDUS_SERVER_URL } from "../../../boot/server-token.js";
|
|
16
|
+
import { isGatewayEligibleProvider } from "../../../boot/runners/server-mode.js";
|
|
17
|
+
|
|
18
|
+
const CLIPBOARD_WRITERS: Array<{ bin: string; args: string[] }> = [
|
|
19
|
+
{ bin: "pbcopy", args: [] },
|
|
20
|
+
{ bin: "clip", args: [] },
|
|
21
|
+
{ bin: "xclip", args: ["-selection", "clipboard"] },
|
|
22
|
+
{ bin: "wl-copy", args: [] }
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const PUBLISH_ROLES = new Set(["user", "assistant", "tool", "system", "condense", "note"]);
|
|
26
|
+
|
|
27
|
+
let mcpPool: MCPClientPool | undefined;
|
|
28
|
+
let mcpServers: ReturnType<typeof loadMCPConfig> = [];
|
|
29
|
+
|
|
30
|
+
let memoryEnabled = true;
|
|
31
|
+
|
|
32
|
+
const COMPOSIO_ENV_KEY = "COMPOSIO_API_KEY";
|
|
33
|
+
let composioGateway: ReturnType<typeof createComposioGateway> | undefined;
|
|
34
|
+
let composioResolved = false;
|
|
35
|
+
|
|
36
|
+
function writeClipboard(text: string): boolean {
|
|
37
|
+
for (const writer of CLIPBOARD_WRITERS) {
|
|
38
|
+
try {
|
|
39
|
+
const result = spawnSync(writer.bin, [...writer.args], {
|
|
40
|
+
input: text,
|
|
41
|
+
encoding: "utf-8"
|
|
42
|
+
});
|
|
43
|
+
if (!result.error && result.status === 0) return true;
|
|
44
|
+
} catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function lastAssistantText(messages: any[]): string {
|
|
52
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
53
|
+
const message = messages[i];
|
|
54
|
+
if (message.role !== "assistant") continue;
|
|
55
|
+
const content = message.content;
|
|
56
|
+
if (!Array.isArray(content)) continue;
|
|
57
|
+
const text = content
|
|
58
|
+
.filter(
|
|
59
|
+
(b) => typeof b === "object" && b !== null && b.type === "text" && typeof b.text === "string"
|
|
60
|
+
)
|
|
61
|
+
.map((b) => b.text)
|
|
62
|
+
.join("")
|
|
63
|
+
.trim();
|
|
64
|
+
return text;
|
|
65
|
+
}
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function toPublishRole(role: string): string {
|
|
70
|
+
return PUBLISH_ROLES.has(role) ? role : "note";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toPublishEntries(messages: any[]) {
|
|
74
|
+
return messages.map((message) => {
|
|
75
|
+
const m = message;
|
|
76
|
+
return {
|
|
77
|
+
role: toPublishRole(m.role),
|
|
78
|
+
message: {
|
|
79
|
+
role: m.role,
|
|
80
|
+
content: typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content : "",
|
|
81
|
+
...(typeof m.toolCallId === "string" ? { toolCallId: m.toolCallId } : {}),
|
|
82
|
+
...(typeof m.toolName === "string" ? { toolName: m.toolName } : {})
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function defaultExportPath() {
|
|
89
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").replace(/Z$/, "");
|
|
90
|
+
return resolve(process.cwd(), `transcript-${stamp}.html`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function exportTranscriptHtml(ctx: SlashContext, target: string): { ok: boolean; path?: string; reason?: string } {
|
|
94
|
+
const messages = ctx.conductor.messages();
|
|
95
|
+
if (messages.length === 0) {
|
|
96
|
+
return { ok: false, reason: "Nothing to export yet — the transcript is empty." };
|
|
97
|
+
}
|
|
98
|
+
const outPath = target.length > 0 ? resolve(process.cwd(), target) : defaultExportPath();
|
|
99
|
+
try {
|
|
100
|
+
const html = publishTranscript(toPublishEntries(messages), {
|
|
101
|
+
title: "Session Transcript"
|
|
102
|
+
});
|
|
103
|
+
writeFileSync(outPath, html, "utf-8");
|
|
104
|
+
return { ok: true, path: outPath };
|
|
105
|
+
} catch (cause) {
|
|
106
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
107
|
+
return { ok: false, reason: `Could not write the HTML export: ${message}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function loadMcpPool() {
|
|
112
|
+
mcpServers = loadMCPConfig(process.cwd());
|
|
113
|
+
mcpPool = new MCPClientPool({ servers: mcpServers });
|
|
114
|
+
return mcpPool;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function ensureMcpPool() {
|
|
118
|
+
return mcpPool ?? loadMcpPool();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function gatherMcpStatus() {
|
|
122
|
+
if (mcpServers.length === 0 && mcpPool === void 0) {
|
|
123
|
+
mcpServers = loadMCPConfig(process.cwd());
|
|
124
|
+
}
|
|
125
|
+
if (mcpServers.length === 0) {
|
|
126
|
+
return "No MCP servers are configured.\nAdd one to .indusvx/mcp.json, then run /mcp connect.";
|
|
127
|
+
}
|
|
128
|
+
const pool = mcpPool;
|
|
129
|
+
const lines: string[] = [`Configured MCP servers (${mcpServers.length}):`];
|
|
130
|
+
let toolsByServer: Record<string, Array<{ name: string }>> = {};
|
|
131
|
+
if (pool !== void 0) {
|
|
132
|
+
try {
|
|
133
|
+
toolsByServer = await pool.listAllTools();
|
|
134
|
+
} catch {
|
|
135
|
+
toolsByServer = {};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const server of mcpServers) {
|
|
139
|
+
const connected = pool?.isConnected(server.name) ?? false;
|
|
140
|
+
const state = connected ? "connected" : "not connected";
|
|
141
|
+
lines.push(` • ${server.name} — ${state}`);
|
|
142
|
+
const tools = toolsByServer[server.name] ?? [];
|
|
143
|
+
if (tools.length > 0) {
|
|
144
|
+
lines.push(` tools: ${tools.map((t) => t.name).join(", ")}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function mcpPoolTools(pool: MCPClientPool) {
|
|
151
|
+
const tools: any[] = [];
|
|
152
|
+
for (const client of pool.getAllClients()) {
|
|
153
|
+
try {
|
|
154
|
+
const defs = await client.listTools();
|
|
155
|
+
for (const def of defs) tools.push(createMCPAgentToolFactory(def, client)());
|
|
156
|
+
} catch {
|
|
157
|
+
// ignore
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return tools;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function openMcpOverlay(ctx: SlashContext, text: string) {
|
|
164
|
+
ctx.openModal("plugin", { surface: "mcp", title: "MCP servers", text });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function runMcp(ctx: SlashContext) {
|
|
168
|
+
const verb = ctx.args.trim().toLowerCase() || "status";
|
|
169
|
+
try {
|
|
170
|
+
switch (verb) {
|
|
171
|
+
case "connect":
|
|
172
|
+
case "reconnect": {
|
|
173
|
+
const pool = verb === "reconnect" ? loadMcpPool() : ensureMcpPool();
|
|
174
|
+
await pool.connectAll();
|
|
175
|
+
ctx.conductor.registerTools(await mcpPoolTools(pool));
|
|
176
|
+
const text = await gatherMcpStatus();
|
|
177
|
+
ctx.setStatus(info(`MCP ${verb}ed.`));
|
|
178
|
+
openMcpOverlay(ctx, text);
|
|
179
|
+
return HANDLED;
|
|
180
|
+
}
|
|
181
|
+
case "disconnect": {
|
|
182
|
+
if (mcpPool === void 0) {
|
|
183
|
+
ctx.setStatus(info("No MCP connections are open."));
|
|
184
|
+
return HANDLED;
|
|
185
|
+
}
|
|
186
|
+
await mcpPool.disconnectAll();
|
|
187
|
+
ctx.conductor.registerTools([]);
|
|
188
|
+
ctx.setStatus(info("Disconnected from all MCP servers."));
|
|
189
|
+
openMcpOverlay(ctx, await gatherMcpStatus());
|
|
190
|
+
return HANDLED;
|
|
191
|
+
}
|
|
192
|
+
case "tools": {
|
|
193
|
+
const pool = ensureMcpPool();
|
|
194
|
+
let toolsByServer: Record<string, Array<{ name: string }>> = {};
|
|
195
|
+
try {
|
|
196
|
+
toolsByServer = await pool.listAllTools();
|
|
197
|
+
} catch {
|
|
198
|
+
toolsByServer = {};
|
|
199
|
+
}
|
|
200
|
+
const names = Object.values(toolsByServer).flatMap((list) => list.map((t) => t.name));
|
|
201
|
+
const text =
|
|
202
|
+
names.length === 0
|
|
203
|
+
? "No MCP tools are loaded. Run /mcp connect first."
|
|
204
|
+
: `Loaded MCP tools (${names.length}):\n ${names.join("\n ")}`;
|
|
205
|
+
openMcpOverlay(ctx, text);
|
|
206
|
+
return HANDLED;
|
|
207
|
+
}
|
|
208
|
+
case "status":
|
|
209
|
+
case "":
|
|
210
|
+
openMcpOverlay(ctx, await gatherMcpStatus());
|
|
211
|
+
return HANDLED;
|
|
212
|
+
default:
|
|
213
|
+
ctx.setStatus(warn("/mcp expects: connect, reconnect, disconnect, tools, or status."));
|
|
214
|
+
return HANDLED;
|
|
215
|
+
}
|
|
216
|
+
} catch (cause) {
|
|
217
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
218
|
+
ctx.setStatus(warn(`MCP ${verb} failed: ${message}`));
|
|
219
|
+
return HANDLED;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function memoryToolNames() {
|
|
224
|
+
const id = memoryCard.id;
|
|
225
|
+
return Array.from(new Set([id, "memory"]));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function memoryPresent() {
|
|
229
|
+
return APP_NOVEL_CARDS.some((card: any) => card.id === memoryCard.id);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function gatherMemoryStatus() {
|
|
233
|
+
const present = memoryPresent();
|
|
234
|
+
if (!present) {
|
|
235
|
+
return "The working-memory capability is not registered in this build.";
|
|
236
|
+
}
|
|
237
|
+
const lines = [
|
|
238
|
+
`Working memory: ${memoryEnabled ? "active" : "inactive"}.`,
|
|
239
|
+
`Tool: ${memoryToolNames().join(", ")}.`,
|
|
240
|
+
memoryCard.summary
|
|
241
|
+
];
|
|
242
|
+
return lines.join("\n");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function openMemoryOverlay(ctx: SlashContext, text: string) {
|
|
246
|
+
ctx.openModal("plugin", { surface: "memory", title: "Working memory", text });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const MEMORY_SUBS = [
|
|
250
|
+
{
|
|
251
|
+
verb: "status",
|
|
252
|
+
describe: "show memory state + tool",
|
|
253
|
+
run(ctx: SlashContext) {
|
|
254
|
+
openMemoryOverlay(ctx, gatherMemoryStatus());
|
|
255
|
+
return HANDLED;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
verb: "on",
|
|
260
|
+
describe: "mark memory active",
|
|
261
|
+
run(ctx: SlashContext) {
|
|
262
|
+
if (!memoryPresent()) {
|
|
263
|
+
ctx.setStatus(warn("No working-memory capability to enable in this build."));
|
|
264
|
+
return HANDLED;
|
|
265
|
+
}
|
|
266
|
+
memoryEnabled = true;
|
|
267
|
+
ctx.setStatus(info("Working memory marked active."));
|
|
268
|
+
openMemoryOverlay(ctx, gatherMemoryStatus());
|
|
269
|
+
return HANDLED;
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
verb: "off",
|
|
274
|
+
describe: "mark memory inactive",
|
|
275
|
+
run(ctx: SlashContext) {
|
|
276
|
+
if (!memoryPresent()) {
|
|
277
|
+
ctx.setStatus(warn("No working-memory capability to disable in this build."));
|
|
278
|
+
return HANDLED;
|
|
279
|
+
}
|
|
280
|
+
memoryEnabled = false;
|
|
281
|
+
ctx.setStatus(info("Working memory marked inactive."));
|
|
282
|
+
openMemoryOverlay(ctx, gatherMemoryStatus());
|
|
283
|
+
return HANDLED;
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
verb: "tools",
|
|
288
|
+
describe: "list memory tool names",
|
|
289
|
+
run(ctx: SlashContext) {
|
|
290
|
+
const text = memoryPresent()
|
|
291
|
+
? `Working-memory tools:\n ${memoryToolNames().join("\n ")}`
|
|
292
|
+
: "No working-memory capability is registered in this build.";
|
|
293
|
+
openMemoryOverlay(ctx, text);
|
|
294
|
+
return HANDLED;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
function ensureComposioGateway() {
|
|
300
|
+
if (composioResolved) return composioGateway;
|
|
301
|
+
composioResolved = true;
|
|
302
|
+
const apiKey = process.env[COMPOSIO_ENV_KEY]?.trim();
|
|
303
|
+
if (apiKey === void 0 || apiKey.length === 0) {
|
|
304
|
+
composioGateway = void 0;
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
composioGateway = createComposioGateway({ apiKey });
|
|
309
|
+
} catch {
|
|
310
|
+
composioGateway = void 0;
|
|
311
|
+
}
|
|
312
|
+
return composioGateway;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function openComposioOverlay(ctx: SlashContext, text: string) {
|
|
316
|
+
ctx.openModal("plugin", { surface: "composio", title: "Composio", text });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function gatherComposioStatus(gateway: any) {
|
|
320
|
+
const report = await gateway.status();
|
|
321
|
+
const lines: string[] = [];
|
|
322
|
+
if (report.accounts.length === 0) {
|
|
323
|
+
lines.push("No connected accounts.");
|
|
324
|
+
} else {
|
|
325
|
+
lines.push(`Connected accounts (${report.accounts.length}):`);
|
|
326
|
+
for (const account of report.accounts) {
|
|
327
|
+
lines.push(` • ${account.toolkit} — ${account.status} (${account.id})`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
lines.push(
|
|
331
|
+
report.enabledTools.length === 0
|
|
332
|
+
? "No remote tools enabled yet. Run /composio enable <toolkit>."
|
|
333
|
+
: `Enabled tools (${report.enabledTools.length}): ${report.enabledTools.join(", ")}`
|
|
334
|
+
);
|
|
335
|
+
return lines.join("\n");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const COMPOSIO_NO_KEY = `Composio is not configured. Export ${COMPOSIO_ENV_KEY} with a Composio API key, then re-run /composio connect.`;
|
|
339
|
+
|
|
340
|
+
const COMPOSIO_SUBS = [
|
|
341
|
+
{
|
|
342
|
+
verb: "status",
|
|
343
|
+
describe: "show accounts + tools",
|
|
344
|
+
run(ctx: SlashContext) {
|
|
345
|
+
const gateway = ensureComposioGateway();
|
|
346
|
+
if (gateway === void 0) {
|
|
347
|
+
ctx.setStatus(warn("Composio is not configured."));
|
|
348
|
+
openComposioOverlay(ctx, COMPOSIO_NO_KEY);
|
|
349
|
+
return HANDLED;
|
|
350
|
+
}
|
|
351
|
+
void gatherComposioStatus(gateway)
|
|
352
|
+
.then((text) => openComposioOverlay(ctx, text))
|
|
353
|
+
.catch((cause) => {
|
|
354
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
355
|
+
openComposioOverlay(ctx, `Could not read Composio status: ${message}`);
|
|
356
|
+
});
|
|
357
|
+
return HANDLED;
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
verb: "accounts",
|
|
362
|
+
describe: "list connected accounts",
|
|
363
|
+
run(ctx: SlashContext) {
|
|
364
|
+
const gateway = ensureComposioGateway();
|
|
365
|
+
if (gateway === void 0) {
|
|
366
|
+
openComposioOverlay(ctx, COMPOSIO_NO_KEY);
|
|
367
|
+
return HANDLED;
|
|
368
|
+
}
|
|
369
|
+
void gateway
|
|
370
|
+
.status()
|
|
371
|
+
.then((report: any) => {
|
|
372
|
+
const text =
|
|
373
|
+
report.accounts.length === 0
|
|
374
|
+
? "No connected Composio accounts."
|
|
375
|
+
: `Connected accounts (${report.accounts.length}):\n` +
|
|
376
|
+
report.accounts.map((a: any) => ` • ${a.toolkit} — ${a.status} (${a.id})`).join("\n");
|
|
377
|
+
openComposioOverlay(ctx, text);
|
|
378
|
+
})
|
|
379
|
+
.catch((cause) => {
|
|
380
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
381
|
+
openComposioOverlay(ctx, `Could not list accounts: ${message}`);
|
|
382
|
+
});
|
|
383
|
+
return HANDLED;
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
verb: "tools",
|
|
388
|
+
describe: "list enabled tool names",
|
|
389
|
+
run(ctx: SlashContext) {
|
|
390
|
+
const gateway = ensureComposioGateway();
|
|
391
|
+
if (gateway === void 0) {
|
|
392
|
+
openComposioOverlay(ctx, COMPOSIO_NO_KEY);
|
|
393
|
+
return HANDLED;
|
|
394
|
+
}
|
|
395
|
+
void gateway
|
|
396
|
+
.status()
|
|
397
|
+
.then((report: any) => {
|
|
398
|
+
const text =
|
|
399
|
+
report.enabledTools.length === 0
|
|
400
|
+
? "No Composio tools enabled. Run /composio enable <toolkit>."
|
|
401
|
+
: `Enabled Composio tools (${report.enabledTools.length}):\n ${report.enabledTools.join("\n ")}`;
|
|
402
|
+
openComposioOverlay(ctx, text);
|
|
403
|
+
})
|
|
404
|
+
.catch((cause) => {
|
|
405
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
406
|
+
openComposioOverlay(ctx, `Could not list tools: ${message}`);
|
|
407
|
+
});
|
|
408
|
+
return HANDLED;
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
verb: "connect",
|
|
413
|
+
describe: "link a toolkit",
|
|
414
|
+
run(ctx: SlashContext, rest: string) {
|
|
415
|
+
const toolkit = rest.trim();
|
|
416
|
+
if (toolkit.length === 0) {
|
|
417
|
+
ctx.setStatus(warn("/composio connect expects: <toolkit> to link."));
|
|
418
|
+
return HANDLED;
|
|
419
|
+
}
|
|
420
|
+
const gateway = ensureComposioGateway();
|
|
421
|
+
if (gateway === void 0) {
|
|
422
|
+
openComposioOverlay(ctx, COMPOSIO_NO_KEY);
|
|
423
|
+
return HANDLED;
|
|
424
|
+
}
|
|
425
|
+
ctx.setStatus(info(`Starting Composio connect for ${toolkit}...`));
|
|
426
|
+
void gateway
|
|
427
|
+
.connect(toolkit)
|
|
428
|
+
.then((report: any) => {
|
|
429
|
+
const lines: string[] = [`Connect ${toolkit}: ${report.action}.`];
|
|
430
|
+
if (report.authUrl) lines.push(`Authorize at: ${report.authUrl}`);
|
|
431
|
+
if (report.accountId) lines.push(`Account: ${report.accountId}`);
|
|
432
|
+
if (report.reason) lines.push(`Reason: ${report.reason}`);
|
|
433
|
+
openComposioOverlay(ctx, lines.join("\n"));
|
|
434
|
+
})
|
|
435
|
+
.catch((cause) => {
|
|
436
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
437
|
+
openComposioOverlay(ctx, `Composio connect failed: ${message}`);
|
|
438
|
+
});
|
|
439
|
+
return HANDLED;
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
verb: "enable",
|
|
444
|
+
describe: "hydrate a toolkit's tools",
|
|
445
|
+
run(ctx: SlashContext, rest: string) {
|
|
446
|
+
const toolkit = rest.trim();
|
|
447
|
+
if (toolkit.length === 0) {
|
|
448
|
+
ctx.setStatus(warn("/composio enable expects: <toolkit> to hydrate."));
|
|
449
|
+
return HANDLED;
|
|
450
|
+
}
|
|
451
|
+
const gateway = ensureComposioGateway();
|
|
452
|
+
if (gateway === void 0) {
|
|
453
|
+
openComposioOverlay(ctx, COMPOSIO_NO_KEY);
|
|
454
|
+
return HANDLED;
|
|
455
|
+
}
|
|
456
|
+
ctx.setStatus(info(`Enabling Composio toolkit ${toolkit}...`));
|
|
457
|
+
void gateway
|
|
458
|
+
.enable(toolkit)
|
|
459
|
+
.then((report: any) => {
|
|
460
|
+
const text =
|
|
461
|
+
report.hydrated.length === 0
|
|
462
|
+
? `Toolkit ${toolkit} exposed no tools.`
|
|
463
|
+
: `Enabled ${report.toolkit} (${report.hydrated.length} tools${report.cached ? ", cached" : ""}):\n ${report.hydrated.join("\n ")}`;
|
|
464
|
+
openComposioOverlay(ctx, text);
|
|
465
|
+
})
|
|
466
|
+
.catch((cause) => {
|
|
467
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
468
|
+
openComposioOverlay(ctx, `Composio enable failed: ${message}`);
|
|
469
|
+
});
|
|
470
|
+
return HANDLED;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
function runCopy(ctx: SlashContext) {
|
|
476
|
+
const text = lastAssistantText(ctx.conductor.messages());
|
|
477
|
+
if (text.length === 0) {
|
|
478
|
+
ctx.setStatus(warn("Nothing to copy yet — no assistant reply on screen."));
|
|
479
|
+
return HANDLED;
|
|
480
|
+
}
|
|
481
|
+
if (!writeClipboard(text)) {
|
|
482
|
+
ctx.setStatus(warn("Could not reach a clipboard tool on this system."));
|
|
483
|
+
return HANDLED;
|
|
484
|
+
}
|
|
485
|
+
ctx.setStatus(info("Copied the last reply to your clipboard."));
|
|
486
|
+
return HANDLED;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function runExport(ctx: SlashContext) {
|
|
490
|
+
const result = exportTranscriptHtml(ctx, ctx.args.trim());
|
|
491
|
+
if (!result.ok) {
|
|
492
|
+
ctx.setStatus(warn(result.reason!));
|
|
493
|
+
return HANDLED;
|
|
494
|
+
}
|
|
495
|
+
ctx.setStatus({ kind: "success", text: `Exported transcript to ${result.path}` });
|
|
496
|
+
return HANDLED;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function runShare(ctx: SlashContext) {
|
|
500
|
+
const exported = exportTranscriptHtml(ctx, "");
|
|
501
|
+
if (!exported.ok) {
|
|
502
|
+
ctx.setStatus(warn(exported.reason!));
|
|
503
|
+
return HANDLED;
|
|
504
|
+
}
|
|
505
|
+
let result: ReturnType<typeof spawnSync>;
|
|
506
|
+
try {
|
|
507
|
+
result = spawnSync("gh", ["gist", "create", exported.path!, "--public=false"], {
|
|
508
|
+
encoding: "utf-8"
|
|
509
|
+
});
|
|
510
|
+
} catch {
|
|
511
|
+
ctx.setStatus(warn(`GitHub CLI (gh) is not installed. Saved HTML locally at ${exported.path}`));
|
|
512
|
+
return HANDLED;
|
|
513
|
+
}
|
|
514
|
+
if (result.error !== void 0 && (result.error as any).code === "ENOENT") {
|
|
515
|
+
ctx.setStatus(warn(`GitHub CLI (gh) is not installed. Saved HTML locally at ${exported.path}`));
|
|
516
|
+
return HANDLED;
|
|
517
|
+
}
|
|
518
|
+
if (result.status !== 0) {
|
|
519
|
+
const stderr = String(result.stderr ?? "").trim();
|
|
520
|
+
const authHint = /auth|logged in|gh auth login/i.test(stderr)
|
|
521
|
+
? "Run `gh auth login` to authenticate, then retry."
|
|
522
|
+
: stderr || "gh gist create failed.";
|
|
523
|
+
ctx.setStatus(warn(`Share failed: ${authHint} Saved HTML locally at ${exported.path}`));
|
|
524
|
+
return HANDLED;
|
|
525
|
+
}
|
|
526
|
+
const url = String(result.stdout ?? "").trim().split(/\s+/)[0] ?? "";
|
|
527
|
+
ctx.setStatus({
|
|
528
|
+
kind: "success",
|
|
529
|
+
text: url.length > 0 ? `Shared as secret gist: ${url}` : `Shared. Local HTML: ${exported.path}`
|
|
530
|
+
});
|
|
531
|
+
return HANDLED;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function runLogin(ctx: SlashContext) {
|
|
535
|
+
const provider = ctx.args.trim().toLowerCase();
|
|
536
|
+
if (provider.length > 0) {
|
|
537
|
+
ctx.openModal("signIn", { providerId: provider });
|
|
538
|
+
} else {
|
|
539
|
+
ctx.openModal("signIn");
|
|
540
|
+
}
|
|
541
|
+
return HANDLED;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function hasLocalProviderKey(provider: string) {
|
|
545
|
+
if (provider.length === 0) return false;
|
|
546
|
+
try {
|
|
547
|
+
const file = join(homedir(), ".indusagi", "agent", "auth.json");
|
|
548
|
+
const vault = JSON.parse(readFileSync(file, "utf8"));
|
|
549
|
+
const entry = vault[provider];
|
|
550
|
+
return entry !== null && typeof entry === "object" && Object.keys(entry).length > 0;
|
|
551
|
+
} catch {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function runStatus(ctx: SlashContext) {
|
|
557
|
+
const modelId = ctx.conductor.snapshot().modelId;
|
|
558
|
+
const provider = modelId.includes("/") ? modelId.split("/")[0] : modelId;
|
|
559
|
+
void (async () => {
|
|
560
|
+
let signedIn = false;
|
|
561
|
+
try {
|
|
562
|
+
signedIn = await hasValidServerToken();
|
|
563
|
+
} catch {
|
|
564
|
+
signedIn = false;
|
|
565
|
+
}
|
|
566
|
+
const eligible = isGatewayEligibleProvider(provider);
|
|
567
|
+
const localKey = hasLocalProviderKey(provider);
|
|
568
|
+
const viaServer = signedIn && eligible && !localKey;
|
|
569
|
+
if (viaServer) {
|
|
570
|
+
ctx.setStatus(info(`● server tier — ${modelId} routing via ${INDUS_SERVER_URL} (signed in)`));
|
|
571
|
+
} else if (localKey) {
|
|
572
|
+
ctx.setStatus(info(`○ local key — ${modelId}${signedIn ? " (signed in to server)" : ""}`));
|
|
573
|
+
} else if (!signedIn) {
|
|
574
|
+
ctx.setStatus(warn(`signed out — ${modelId} has no key. Run /login to use the server tier.`));
|
|
575
|
+
} else {
|
|
576
|
+
ctx.setStatus(info(`signed in — ${modelId} (${provider} is not gateway-eligible; needs a local key)`));
|
|
577
|
+
}
|
|
578
|
+
})();
|
|
579
|
+
return HANDLED;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function runUsage(ctx: SlashContext) {
|
|
583
|
+
void (async () => {
|
|
584
|
+
const token = await getServerToken();
|
|
585
|
+
if (!token?.access_token) {
|
|
586
|
+
ctx.setStatus(warn("Not signed in to the server — run /login to use the server tier."));
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
try {
|
|
590
|
+
const res = await fetch(`${INDUS_SERVER_URL}/api/usage`, {
|
|
591
|
+
headers: { Authorization: `Bearer ${token.access_token}` }
|
|
592
|
+
});
|
|
593
|
+
if (!res.ok) {
|
|
594
|
+
ctx.setStatus(warn(`Couldn't read usage (HTTP ${res.status}).`));
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const u: any = await res.json();
|
|
598
|
+
const pct = u.limit > 0 ? Math.round((u.used / u.limit) * 100) : 0;
|
|
599
|
+
const line =
|
|
600
|
+
`Usage this month: ${u.used.toLocaleString()} / ${u.limit.toLocaleString()} tokens (${pct}%) · ${u.remaining.toLocaleString()} left · ${u.requests} reqs · $${(u.costUsd ?? 0).toFixed(4)}` +
|
|
601
|
+
(u.allowed ? "" : " · LIMIT REACHED — requests are blocked");
|
|
602
|
+
ctx.setStatus(u.allowed ? info(line) : warn(line));
|
|
603
|
+
} catch (cause) {
|
|
604
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
605
|
+
ctx.setStatus(warn(`Usage check failed: ${message}`));
|
|
606
|
+
}
|
|
607
|
+
})();
|
|
608
|
+
return HANDLED;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const runMemory = familyRunner(FAMILY.memory, MEMORY_SUBS);
|
|
612
|
+
const runComposio = familyRunner(FAMILY.composio, COMPOSIO_SUBS);
|
|
613
|
+
|
|
614
|
+
export const integrationCommands: SlashCommand[] = [
|
|
615
|
+
{
|
|
616
|
+
name: "login",
|
|
617
|
+
summary: "Sign in to a model provider.",
|
|
618
|
+
takesArgs: true,
|
|
619
|
+
run(ctx) {
|
|
620
|
+
return runLogin(ctx);
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: "logout",
|
|
625
|
+
summary: "Sign out: pick a provider account or the Indus Server session to clear.",
|
|
626
|
+
run(ctx) {
|
|
627
|
+
ctx.openModal("signOut");
|
|
628
|
+
return HANDLED;
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "status",
|
|
633
|
+
summary: "Show the server session and whether this model uses the server.",
|
|
634
|
+
run(ctx) {
|
|
635
|
+
return runStatus(ctx);
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "usage",
|
|
640
|
+
summary: "Show your server usage this month (tokens used / limit / cost).",
|
|
641
|
+
run(ctx) {
|
|
642
|
+
return runUsage(ctx);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: "mcp",
|
|
647
|
+
summary: "Manage MCP servers and their tools.",
|
|
648
|
+
takesArgs: true,
|
|
649
|
+
run(ctx) {
|
|
650
|
+
return runMcp(ctx);
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "memory",
|
|
655
|
+
summary: "Inspect and toggle the working-memory capability.",
|
|
656
|
+
family: FAMILY.memory,
|
|
657
|
+
takesArgs: true,
|
|
658
|
+
run(ctx) {
|
|
659
|
+
return runMemory(ctx);
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
name: "composio",
|
|
664
|
+
summary: "Connect and inspect Composio app bridges.",
|
|
665
|
+
family: FAMILY.composio,
|
|
666
|
+
takesArgs: true,
|
|
667
|
+
run(ctx) {
|
|
668
|
+
return runComposio(ctx);
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: "copy",
|
|
673
|
+
summary: "Copy the last reply to the clipboard.",
|
|
674
|
+
run(ctx) {
|
|
675
|
+
return runCopy(ctx);
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: "export",
|
|
680
|
+
summary: "Export the transcript to an HTML file.",
|
|
681
|
+
takesArgs: true,
|
|
682
|
+
run(ctx) {
|
|
683
|
+
return runExport(ctx);
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "share",
|
|
688
|
+
summary: "Share the session as a secret GitHub gist.",
|
|
689
|
+
run(ctx) {
|
|
690
|
+
return runShare(ctx);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
];
|
|
694
|
+
// Re-export shared verbs used internally
|
|
695
|
+
export { splitVerb };
|