indusagi-coding-agent 0.2.8 → 0.2.10

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.
Files changed (222) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/entry.js +4 -4
  3. package/dist/index.js +4 -4
  4. package/package.json +3 -2
  5. package/src/_decls/entry.ts +18 -0
  6. package/src/_decls/guardrails.ts +35 -0
  7. package/src/_decls/index.ts +26 -0
  8. package/src/addons/contract.ts +236 -0
  9. package/src/addons/dispatch/event-dispatcher.ts +164 -0
  10. package/src/addons/dispatch/index.ts +25 -0
  11. package/src/addons/dispatch/tool-interceptor.ts +208 -0
  12. package/src/addons/host.ts +225 -0
  13. package/src/addons/index.ts +112 -0
  14. package/src/addons/manifest.ts +158 -0
  15. package/src/addons/sandbox.ts +170 -0
  16. package/src/addons/surface.ts +78 -0
  17. package/src/boot/auth-vault.ts +195 -0
  18. package/src/boot/boot.ts +138 -0
  19. package/src/boot/contract.ts +238 -0
  20. package/src/boot/heap.ts +59 -0
  21. package/src/boot/index.ts +28 -0
  22. package/src/boot/invocation.ts +93 -0
  23. package/src/boot/runners/addon-wiring.ts +153 -0
  24. package/src/boot/runners/checkpoint.ts +169 -0
  25. package/src/boot/runners/delegate-runner.ts +294 -0
  26. package/src/boot/runners/index.ts +13 -0
  27. package/src/boot/runners/link-runner.ts +45 -0
  28. package/src/boot/runners/memdir.ts +168 -0
  29. package/src/boot/runners/oneshot-runner.ts +58 -0
  30. package/src/boot/runners/read-state.ts +90 -0
  31. package/src/boot/runners/registry.ts +42 -0
  32. package/src/boot/runners/repl-runner.ts +143 -0
  33. package/src/boot/runners/server-mode.ts +121 -0
  34. package/src/boot/runners/session.ts +641 -0
  35. package/src/boot/server-token.ts +148 -0
  36. package/src/boot/stages.ts +167 -0
  37. package/src/boot/upgrade/apply.ts +94 -0
  38. package/src/boot/upgrade/index.ts +13 -0
  39. package/src/boot/upgrade/upgrades.ts +289 -0
  40. package/src/briefing/compose.ts +150 -0
  41. package/src/briefing/context-docs.ts +19 -0
  42. package/src/briefing/contract.ts +717 -0
  43. package/src/briefing/index.ts +31 -0
  44. package/src/briefing/macros.ts +97 -0
  45. package/src/briefing/skills.ts +47 -0
  46. package/src/capability-deck/bridge-ledger/index.ts +27 -0
  47. package/src/capability-deck/bridge-ledger/key.ts +67 -0
  48. package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
  49. package/src/capability-deck/bridge-ledger/network.ts +117 -0
  50. package/src/capability-deck/builtin-bridge.ts +312 -0
  51. package/src/capability-deck/cards/bg-process-card.ts +335 -0
  52. package/src/capability-deck/cards/index.ts +115 -0
  53. package/src/capability-deck/cards/memory-card.ts +146 -0
  54. package/src/capability-deck/cards/plan-file.ts +97 -0
  55. package/src/capability-deck/cards/plan-tools.ts +185 -0
  56. package/src/capability-deck/cards/saas-card.ts +183 -0
  57. package/src/capability-deck/cards/task-card.ts +207 -0
  58. package/src/capability-deck/cards/todo-card.ts +168 -0
  59. package/src/capability-deck/cards/workflow-card.ts +247 -0
  60. package/src/capability-deck/contract.ts +388 -0
  61. package/src/capability-deck/index.ts +48 -0
  62. package/src/capability-deck/manifest.ts +109 -0
  63. package/src/capability-deck/provision.ts +169 -0
  64. package/src/channels/contract.ts +191 -0
  65. package/src/channels/framer.ts +50 -0
  66. package/src/channels/index.ts +101 -0
  67. package/src/channels/link/dialog.ts +129 -0
  68. package/src/channels/link/driver.ts +190 -0
  69. package/src/channels/link/index.ts +34 -0
  70. package/src/channels/link/server.ts +134 -0
  71. package/src/channels/oneshot.ts +90 -0
  72. package/src/channels/ops.ts +65 -0
  73. package/src/channels/session-ops.ts +81 -0
  74. package/src/conductor/bash-guard.ts +599 -0
  75. package/src/conductor/catalog/catalog.ts +116 -0
  76. package/src/conductor/catalog/index.ts +8 -0
  77. package/src/conductor/catalog/matcher.ts +134 -0
  78. package/src/conductor/conductor.ts +234 -0
  79. package/src/conductor/contract.ts +842 -0
  80. package/src/conductor/diagnostics.ts +227 -0
  81. package/src/conductor/index.ts +33 -0
  82. package/src/conductor/permissions.ts +588 -0
  83. package/src/conductor/quota-error.ts +49 -0
  84. package/src/conductor/signal-hub/hub.ts +46 -0
  85. package/src/conductor/signal-hub/index.ts +2 -0
  86. package/src/conductor/signal-hub/translate.test.ts +81 -0
  87. package/src/conductor/signal-hub/translate.ts +74 -0
  88. package/src/conductor/skill-parse/index.ts +2 -0
  89. package/src/conductor/skill-parse/parse.ts +108 -0
  90. package/src/conductor/transcript-store/index.ts +22 -0
  91. package/src/conductor/transcript-store/serialize.ts +116 -0
  92. package/src/conductor/transcript-store/store.ts +205 -0
  93. package/src/console/auth-status.ts +56 -0
  94. package/src/console/components/AgentsView.ts +165 -0
  95. package/src/console/components/BackgroundAgents.ts +155 -0
  96. package/src/console/components/Banner.ts +334 -0
  97. package/src/console/components/Composer.ts +94 -0
  98. package/src/console/components/StatusBar.ts +49 -0
  99. package/src/console/components/TerminalConsole.ts +1090 -0
  100. package/src/console/components/WorkingIndicator.ts +98 -0
  101. package/src/console/components/banner-sweep.ts +24 -0
  102. package/src/console/components/welcome.ts +74 -0
  103. package/src/console/contract.ts +630 -0
  104. package/src/console/index.ts +34 -0
  105. package/src/console/input/complete.ts +127 -0
  106. package/src/console/input/dir-reader.ts +34 -0
  107. package/src/console/input/index.ts +23 -0
  108. package/src/console/input/keymap.ts +159 -0
  109. package/src/console/input/paste.ts +104 -0
  110. package/src/console/mount.ts +56 -0
  111. package/src/console/overlays/approval-queue.ts +57 -0
  112. package/src/console/overlays/approval.ts +130 -0
  113. package/src/console/overlays/auth.ts +342 -0
  114. package/src/console/overlays/boards.ts +308 -0
  115. package/src/console/overlays/host.ts +36 -0
  116. package/src/console/overlays/index.ts +26 -0
  117. package/src/console/overlays/pickers.ts +258 -0
  118. package/src/console/overlays/sessions.ts +190 -0
  119. package/src/console/reducer.ts +182 -0
  120. package/src/console/slash/builtins.ts +81 -0
  121. package/src/console/slash/commands/dynamic.ts +83 -0
  122. package/src/console/slash/commands/integrations.ts +695 -0
  123. package/src/console/slash/commands/shared.ts +75 -0
  124. package/src/console/slash/commands/transcript.ts +263 -0
  125. package/src/console/slash/commands/workbench.ts +246 -0
  126. package/src/console/slash/index.ts +15 -0
  127. package/src/console/slash/registry.ts +70 -0
  128. package/src/console/slash/resolve.ts +63 -0
  129. package/src/console/startup.ts +209 -0
  130. package/src/console/theme/adapter.ts +45 -0
  131. package/src/console/theme/index.ts +7 -0
  132. package/src/console/theme/palette.ts +68 -0
  133. package/src/console/theme/resolve.ts +39 -0
  134. package/src/console/theme/tokens.ts +71 -0
  135. package/src/entry.ts +55 -0
  136. package/src/guardrails.ts +37 -0
  137. package/src/index.ts +18 -0
  138. package/src/insight/channel.ts +88 -0
  139. package/src/insight/contract.ts +185 -0
  140. package/src/insight/index.ts +110 -0
  141. package/src/insight/recorder.ts +213 -0
  142. package/src/insight/redaction.ts +157 -0
  143. package/src/insight/replay.ts +158 -0
  144. package/src/insight/sampling.ts +70 -0
  145. package/src/insight/serialize.ts +50 -0
  146. package/src/insight/sinks/console.ts +64 -0
  147. package/src/insight/sinks/file.ts +40 -0
  148. package/src/insight/sinks/index.ts +24 -0
  149. package/src/insight/sinks/stream.ts +54 -0
  150. package/src/integrations/sarvam/attach.ts +239 -0
  151. package/src/integrations/sarvam/config.ts +156 -0
  152. package/src/integrations/sarvam/index.ts +25 -0
  153. package/src/integrations/sarvam/sarvam.test.ts +60 -0
  154. package/src/integrations/sarvam/types.ts +27 -0
  155. package/src/integrations/zoho/attach.ts +342 -0
  156. package/src/integrations/zoho/config.ts +125 -0
  157. package/src/integrations/zoho/index.ts +27 -0
  158. package/src/integrations/zoho/types.ts +21 -0
  159. package/src/integrations/zoho/zoho.test.ts +50 -0
  160. package/src/kit/clipboard-image.ts +107 -0
  161. package/src/kit/external-editor.ts +48 -0
  162. package/src/kit/image.ts +59 -0
  163. package/src/kit/index.ts +51 -0
  164. package/src/kit/shell.ts +19 -0
  165. package/src/kit/tool-fetch.ts +85 -0
  166. package/src/launch/catalog.ts +148 -0
  167. package/src/launch/contract.ts +187 -0
  168. package/src/launch/credentials.ts +625 -0
  169. package/src/launch/index.ts +98 -0
  170. package/src/launch/invocation/attachments.ts +179 -0
  171. package/src/launch/invocation/flags.ts +196 -0
  172. package/src/launch/invocation/index.ts +25 -0
  173. package/src/launch/invocation/read.ts +260 -0
  174. package/src/launch/invocation/usage.ts +67 -0
  175. package/src/launch/login.ts +324 -0
  176. package/src/launch/oauth.test.ts +18 -0
  177. package/src/launch/oauth.ts +203 -0
  178. package/src/launch/packages.ts +194 -0
  179. package/src/launch/pickers.ts +189 -0
  180. package/src/runtime-bridge/bridges/_drive.ts +96 -0
  181. package/src/runtime-bridge/bridges/builtins.ts +68 -0
  182. package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
  183. package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
  184. package/src/runtime-bridge/bridges/index.ts +33 -0
  185. package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
  186. package/src/runtime-bridge/broker.ts +227 -0
  187. package/src/runtime-bridge/contract.ts +122 -0
  188. package/src/runtime-bridge/index.ts +79 -0
  189. package/src/runtime-bridge/sink.ts +180 -0
  190. package/src/sessions/contract.ts +81 -0
  191. package/src/sessions/index.ts +13 -0
  192. package/src/sessions/library.ts +229 -0
  193. package/src/settings/contract.ts +114 -0
  194. package/src/settings/index.ts +32 -0
  195. package/src/settings/manager.ts +117 -0
  196. package/src/transcript-export/index.ts +45 -0
  197. package/src/transcript-export/publish.ts +260 -0
  198. package/src/transcript-export/sgr.ts +315 -0
  199. package/src/transcript-export/template.ts +272 -0
  200. package/src/transcript-export/theme-bridge.ts +150 -0
  201. package/src/window-budget/budget/estimate.ts +135 -0
  202. package/src/window-budget/budget/gate.ts +33 -0
  203. package/src/window-budget/budget/index.ts +16 -0
  204. package/src/window-budget/budget/slice.ts +56 -0
  205. package/src/window-budget/condenser.ts +58 -0
  206. package/src/window-budget/contract.ts +184 -0
  207. package/src/window-budget/index.ts +19 -0
  208. package/src/window-budget/microcompact.ts +95 -0
  209. package/src/window-budget/rehydrate.ts +136 -0
  210. package/src/window-budget/summarize/condense.ts +103 -0
  211. package/src/window-budget/summarize/index.ts +14 -0
  212. package/src/window-budget/summarize/prompt.ts +149 -0
  213. package/src/workflow-engine/agent-runner.ts +181 -0
  214. package/src/workflow-engine/display.ts +224 -0
  215. package/src/workflow-engine/engine.ts +294 -0
  216. package/src/workflow-engine/index.ts +23 -0
  217. package/src/workflow-engine/parse.ts +172 -0
  218. package/src/workflow-engine/structured-output.ts +35 -0
  219. package/src/workspace/brand.ts +29 -0
  220. package/src/workspace/index.ts +18 -0
  221. package/src/workspace/locator.ts +103 -0
  222. package/src/workspace/runtime-detect.ts +64 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Attach Sarvam MCP tools to the coding agent via indusagi/sarvam-mcp.
3
+ */
4
+
5
+ import { connectSarvamMcp } from "indusagi/sarvam-mcp";
6
+ import { createMCPAgentToolFactory } from "indusagi/mcp";
7
+ import type { SarvamAttachResult, SarvamHostConfig } from "./types.js";
8
+ import { resolveSarvamConfig, redactApiKey, type ResolveSarvamOptions } from "./config.js";
9
+
10
+ /** Live connection handle for /sarvam status and disconnect. */
11
+ let live: {
12
+ id: string;
13
+ toolNames: readonly string[];
14
+ close: () => Promise<void>;
15
+ keyRedacted: string;
16
+ basePath?: string;
17
+ } | null = null;
18
+
19
+ let connecting = false;
20
+ let cancelConnect = false;
21
+
22
+ export function getLiveSarvam(): typeof live {
23
+ return live;
24
+ }
25
+
26
+ export function isSarvamConnecting(): boolean {
27
+ return connecting;
28
+ }
29
+
30
+ /**
31
+ * Convert a connected Sarvam MCP client into AgentTool[] using the same factory
32
+ * the coding agent uses for generic MCP servers.
33
+ */
34
+ async function toolsFromConnection(
35
+ conn: Awaited<ReturnType<typeof connectSarvamMcp>>,
36
+ ): Promise<unknown[]> {
37
+ const tools: unknown[] = [];
38
+ const client = conn.client as {
39
+ listTools: () => Promise<
40
+ Array<{ name: string; description?: string; inputSchema: Record<string, unknown> }>
41
+ >;
42
+ serverName: string;
43
+ connected: boolean;
44
+ callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
45
+ };
46
+
47
+ try {
48
+ const defs = await client.listTools();
49
+ for (const def of defs) {
50
+ const factory = createMCPAgentToolFactory(def, client as never);
51
+ tools.push(factory());
52
+ }
53
+ } catch (err) {
54
+ const message = err instanceof Error ? err.message : String(err);
55
+ console.error(`[sarvam] listTools/factory failed: ${message}`);
56
+ }
57
+ return tools;
58
+ }
59
+
60
+ export interface AttachSarvamOptions {
61
+ closables?: Array<() => Promise<void>>;
62
+ onStatus?: (message: string) => void;
63
+ }
64
+
65
+ /**
66
+ * Connect to Sarvam MCP (spawns uvx sarvam-mcp) and return AgentTool-compatible tools.
67
+ */
68
+ export async function attachSarvamTools(
69
+ config: SarvamHostConfig,
70
+ opts?: AttachSarvamOptions,
71
+ ): Promise<SarvamAttachResult> {
72
+ if (live) {
73
+ try {
74
+ await live.close();
75
+ } catch {
76
+ /* ignore */
77
+ }
78
+ live = null;
79
+ }
80
+
81
+ connecting = true;
82
+ cancelConnect = false;
83
+ opts?.onStatus?.(
84
+ "Spawning uvx sarvam-mcp… (first run may download the package, ~30–60s)",
85
+ );
86
+
87
+ try {
88
+ const conn = await connectSarvamMcp({
89
+ apiKey: config.apiKey,
90
+ id: config.id ?? "sarvam",
91
+ baseUrl: config.baseUrl,
92
+ basePath: config.basePath,
93
+ command: config.command,
94
+ args: config.args,
95
+ timeoutMs: 120_000,
96
+ });
97
+
98
+ if (cancelConnect) {
99
+ try {
100
+ await conn.close();
101
+ } catch {
102
+ /* ignore */
103
+ }
104
+ throw new Error("Sarvam connect cancelled (disconnect).");
105
+ }
106
+
107
+ const tools = await toolsFromConnection(conn);
108
+ const fromTools = tools
109
+ .map((t) => (t as { name?: string }).name)
110
+ .filter((n): n is string => typeof n === "string" && n.length > 0);
111
+ const toolNames =
112
+ fromTools.length > 0
113
+ ? fromTools
114
+ : conn.toolNames.length > 0
115
+ ? [...conn.toolNames]
116
+ : [];
117
+
118
+ const close = async () => {
119
+ try {
120
+ await conn.close();
121
+ } finally {
122
+ if (live?.id === (config.id ?? "sarvam")) live = null;
123
+ }
124
+ };
125
+
126
+ opts?.closables?.push(close);
127
+
128
+ const keyRedacted = config.apiKey ? redactApiKey(config.apiKey) : "(env)";
129
+
130
+ live = {
131
+ id: config.id ?? "sarvam",
132
+ toolNames,
133
+ close,
134
+ keyRedacted,
135
+ basePath: config.basePath,
136
+ };
137
+
138
+ return { tools, toolNames, id: config.id ?? "sarvam", close };
139
+ } finally {
140
+ connecting = false;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Session helper: resolve config and attach, or return [] if Sarvam is off.
146
+ * Soft-fails so missing uvx / bad key does not crash the coding agent.
147
+ */
148
+ export async function loadSarvamAgentTools(opts: {
149
+ cwd: string;
150
+ closables?: Array<() => Promise<void>>;
151
+ sarvamKey?: string;
152
+ noSarvam?: boolean;
153
+ forceSarvam?: boolean;
154
+ basePath?: string;
155
+ }): Promise<unknown[]> {
156
+ const resolveOpts: ResolveSarvamOptions = {
157
+ cwd: opts.cwd,
158
+ apiKey: opts.sarvamKey,
159
+ disabled: opts.noSarvam,
160
+ forceEnable: opts.forceSarvam,
161
+ basePath: opts.basePath,
162
+ };
163
+ const config = resolveSarvamConfig(resolveOpts);
164
+ if (!config) return [];
165
+
166
+ try {
167
+ const result = await attachSarvamTools(config, { closables: opts.closables });
168
+ return [...result.tools];
169
+ } catch (err) {
170
+ const message = err instanceof Error ? err.message : String(err);
171
+ console.error(`[sarvam] failed to attach: ${message}`);
172
+ if (/uvx|ENOENT|spawn/i.test(message)) {
173
+ console.error(
174
+ "[sarvam] Install uv (provides uvx): https://docs.astral.sh/uv/ then re-run /sarvam-connect",
175
+ );
176
+ }
177
+ return [];
178
+ }
179
+ }
180
+
181
+ export async function disconnectLiveSarvam(): Promise<boolean> {
182
+ if (connecting) {
183
+ cancelConnect = true;
184
+ }
185
+ if (!live) {
186
+ return connecting;
187
+ }
188
+ try {
189
+ await live.close();
190
+ } catch {
191
+ /* ignore */
192
+ }
193
+ live = null;
194
+ return true;
195
+ }
196
+
197
+ export function sarvamStatusLines(): string {
198
+ if (connecting) {
199
+ return [
200
+ "Sarvam MCP: connecting… (spawning uvx sarvam-mcp)",
201
+ "First run may download the Python package (~30–60s).",
202
+ "Run /sarvam-disconnect to cancel.",
203
+ ].join("\n");
204
+ }
205
+ if (!live) {
206
+ return [
207
+ "Sarvam MCP: not connected.",
208
+ "Set SARVAM_API_KEY or run /sarvam-connect [key].",
209
+ "Requires uv/uvx: https://docs.astral.sh/uv/",
210
+ "Key: https://dashboard.sarvam.ai/key-management",
211
+ "",
212
+ "Commands: /sarvam-connect · /sarvam-disconnect · /sarvam-status · /sarvam-tools · /sarvam-help",
213
+ ].join("\n");
214
+ }
215
+ return [
216
+ `Sarvam MCP: connected (id=${live.id}).`,
217
+ `API key: ${live.keyRedacted}`,
218
+ live.basePath ? `Base path: ${live.basePath}` : "Base path: ~/Desktop (default)",
219
+ `Tools (${live.toolNames.length}):`,
220
+ ...live.toolNames.map((n) => ` • ${n}`),
221
+ "",
222
+ "Run /sarvam-disconnect to close the connection.",
223
+ ].join("\n");
224
+ }
225
+
226
+ export function sarvamConnectingText(): string {
227
+ return [
228
+ "Sarvam MCP — connecting",
229
+ "",
230
+ "Spawning: uvx sarvam-mcp",
231
+ "First run may download the package (30–60 seconds).",
232
+ "",
233
+ "Prerequisites:",
234
+ " • uv installed → https://docs.astral.sh/uv/",
235
+ " • SARVAM_API_KEY from https://dashboard.sarvam.ai/key-management",
236
+ "",
237
+ "Cancel: /sarvam-disconnect",
238
+ ].join("\n");
239
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Resolve Sarvam MCP config from flags / env / config files.
3
+ *
4
+ * Precedence (high → low):
5
+ * 1. explicit apiKey argument
6
+ * 2. --sarvam-key
7
+ * 3. SARVAM_API_KEY env
8
+ * 4. project .indusagi/sarvam.json (enabled + basePath; key still from env preferred)
9
+ * 5. profile ~/.indusagi/agent/sarvam.json
10
+ *
11
+ * Non-secret fields (basePath, baseUrl, enabled) come from files; API keys
12
+ * prefer env / CLI to avoid committing secrets.
13
+ */
14
+
15
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
16
+ import { homedir } from "node:os";
17
+ import { dirname, join } from "node:path";
18
+ import type { SarvamHostConfig } from "./types.js";
19
+
20
+ const ENV_KEY = "SARVAM_API_KEY";
21
+ const ENV_DISABLE = "SARVAM_DISABLED";
22
+ const ENV_BASE_URL = "SARVAM_API_BASE_URL";
23
+ const ENV_BASE_PATH = "SARVAM_MCP_BASE_PATH";
24
+
25
+ function readJsonFile(path: string): Record<string, unknown> | undefined {
26
+ try {
27
+ if (!existsSync(path)) return undefined;
28
+ const raw = readFileSync(path, "utf8");
29
+ const parsed = JSON.parse(raw) as unknown;
30
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
31
+ return undefined;
32
+ }
33
+ return parsed as Record<string, unknown>;
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ function configFromRecord(rec: Record<string, unknown> | undefined): SarvamHostConfig | undefined {
40
+ if (!rec) return undefined;
41
+ const enabled = rec.enabled === false ? false : true;
42
+ const id = typeof rec.id === "string" && rec.id.trim() ? rec.id.trim() : "sarvam";
43
+ const apiKey = typeof rec.apiKey === "string" ? rec.apiKey.trim() : undefined;
44
+ const baseUrl = typeof rec.baseUrl === "string" ? rec.baseUrl.trim() : undefined;
45
+ const basePath = typeof rec.basePath === "string" ? rec.basePath.trim() : undefined;
46
+ const command = typeof rec.command === "string" ? rec.command.trim() : undefined;
47
+ // File alone without key is still useful when env has SARVAM_API_KEY
48
+ return {
49
+ id,
50
+ enabled,
51
+ ...(apiKey ? { apiKey } : {}),
52
+ ...(baseUrl ? { baseUrl } : {}),
53
+ ...(basePath ? { basePath } : {}),
54
+ ...(command ? { command } : {}),
55
+ };
56
+ }
57
+
58
+ /** Profile path: ~/.indusagi/agent/sarvam.json */
59
+ export function profileSarvamPath(home = homedir()): string {
60
+ return join(home, ".indusagi", "agent", "sarvam.json");
61
+ }
62
+
63
+ /** Project path: <cwd>/.indusagi/sarvam.json */
64
+ export function projectSarvamPath(cwd: string): string {
65
+ return join(cwd, ".indusagi", "sarvam.json");
66
+ }
67
+
68
+ /**
69
+ * Save non-secret Sarvam prefs. Does not write apiKey unless explicitly present
70
+ * on config (prefer env for secrets).
71
+ */
72
+ export function saveSarvamConfig(
73
+ config: SarvamHostConfig,
74
+ where: "profile" | "project" = "profile",
75
+ cwd = process.cwd(),
76
+ ): string {
77
+ const path = where === "project" ? projectSarvamPath(cwd) : profileSarvamPath();
78
+ mkdirSync(dirname(path), { recursive: true });
79
+ const body: Record<string, unknown> = {
80
+ id: config.id ?? "sarvam",
81
+ enabled: config.enabled !== false,
82
+ };
83
+ if (config.basePath) body.basePath = config.basePath;
84
+ if (config.baseUrl) body.baseUrl = config.baseUrl;
85
+ if (config.command) body.command = config.command;
86
+ // Only persist key if caller intentionally set it (e.g. interactive connect with key arg).
87
+ if (config.apiKey) body.apiKey = config.apiKey;
88
+ writeFileSync(path, JSON.stringify(body, null, 2) + "\n", "utf8");
89
+ return path;
90
+ }
91
+
92
+ export interface ResolveSarvamOptions {
93
+ /** Explicit API key (e.g. from --sarvam-key). */
94
+ apiKey?: string;
95
+ /** Force disable (--no-sarvam). */
96
+ disabled?: boolean;
97
+ /** Force enable when key present (--sarvam). */
98
+ forceEnable?: boolean;
99
+ basePath?: string;
100
+ baseUrl?: string;
101
+ cwd?: string;
102
+ env?: NodeJS.ProcessEnv;
103
+ /** Override home for profile path (tests). */
104
+ home?: string;
105
+ }
106
+
107
+ /**
108
+ * Resolve effective Sarvam config or undefined if Sarvam should not attach.
109
+ * Requires a resolvable API key (arg, env, or file).
110
+ */
111
+ export function resolveSarvamConfig(opts: ResolveSarvamOptions = {}): SarvamHostConfig | undefined {
112
+ const env = opts.env ?? process.env;
113
+ const cwd = opts.cwd ?? process.cwd();
114
+
115
+ if (opts.disabled || env[ENV_DISABLE] === "1" || env[ENV_DISABLE] === "true") {
116
+ return undefined;
117
+ }
118
+
119
+ const fromArgKey = opts.apiKey?.trim();
120
+ const fromEnvKey = env[ENV_KEY]?.trim();
121
+ const fromProject = configFromRecord(readJsonFile(projectSarvamPath(cwd)));
122
+ const fromProfile = configFromRecord(
123
+ readJsonFile(profileSarvamPath(opts.home ?? homedir())),
124
+ );
125
+
126
+ const fileBase = fromProject ?? fromProfile;
127
+ const apiKey = fromArgKey || fromEnvKey || fileBase?.apiKey;
128
+ if (!apiKey) {
129
+ // No key → only attach if forceEnable is false and we still want to signal "off"
130
+ return undefined;
131
+ }
132
+
133
+ if (fileBase?.enabled === false && !opts.forceEnable) {
134
+ return undefined;
135
+ }
136
+
137
+ return {
138
+ apiKey,
139
+ id: fileBase?.id ?? "sarvam",
140
+ enabled: true,
141
+ baseUrl:
142
+ opts.baseUrl?.trim() ||
143
+ env[ENV_BASE_URL]?.trim() ||
144
+ fileBase?.baseUrl,
145
+ basePath:
146
+ opts.basePath?.trim() ||
147
+ env[ENV_BASE_PATH]?.trim() ||
148
+ fileBase?.basePath,
149
+ command: fileBase?.command,
150
+ };
151
+ }
152
+
153
+ export function redactApiKey(key: string): string {
154
+ if (key.length <= 8) return "****";
155
+ return `${key.slice(0, 4)}…${key.slice(-4)}`;
156
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Sarvam integration for indusagi-coding-agent.
3
+ *
4
+ * Consumes `indusagi/sarvam-mcp` from the local framework package.
5
+ */
6
+
7
+ export type { SarvamHostConfig, SarvamAttachResult } from "./types.js";
8
+ export {
9
+ resolveSarvamConfig,
10
+ saveSarvamConfig,
11
+ profileSarvamPath,
12
+ projectSarvamPath,
13
+ redactApiKey,
14
+ } from "./config.js";
15
+ export type { ResolveSarvamOptions } from "./config.js";
16
+ export {
17
+ attachSarvamTools,
18
+ loadSarvamAgentTools,
19
+ disconnectLiveSarvam,
20
+ getLiveSarvam,
21
+ sarvamStatusLines,
22
+ sarvamConnectingText,
23
+ isSarvamConnecting,
24
+ } from "./attach.js";
25
+ export type { AttachSarvamOptions } from "./attach.js";
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { resolveSarvamConfig, redactApiKey } from "./config.js";
3
+
4
+ describe("resolveSarvamConfig", () => {
5
+ const isolatedHome = "/tmp/indusagi-sarvam-test-home-empty";
6
+
7
+ it("returns undefined when nothing is configured", () => {
8
+ expect(
9
+ resolveSarvamConfig({ env: {}, cwd: "/tmp", home: isolatedHome, disabled: false }),
10
+ ).toBeUndefined();
11
+ });
12
+
13
+ it("prefers explicit key over env", () => {
14
+ const cfg = resolveSarvamConfig({
15
+ apiKey: "key-from-arg",
16
+ env: { SARVAM_API_KEY: "key-from-env" },
17
+ cwd: "/tmp",
18
+ home: isolatedHome,
19
+ });
20
+ expect(cfg?.apiKey).toBe("key-from-arg");
21
+ });
22
+
23
+ it("honors SARVAM_API_KEY", () => {
24
+ const cfg = resolveSarvamConfig({
25
+ env: { SARVAM_API_KEY: "sk-test-123" },
26
+ cwd: "/tmp",
27
+ home: isolatedHome,
28
+ });
29
+ expect(cfg?.apiKey).toBe("sk-test-123");
30
+ expect(cfg?.id).toBe("sarvam");
31
+ });
32
+
33
+ it("disabled wins", () => {
34
+ const cfg = resolveSarvamConfig({
35
+ apiKey: "sk-test",
36
+ disabled: true,
37
+ cwd: "/tmp",
38
+ home: isolatedHome,
39
+ });
40
+ expect(cfg).toBeUndefined();
41
+ });
42
+
43
+ it("honors basePath from options", () => {
44
+ const cfg = resolveSarvamConfig({
45
+ apiKey: "sk-test",
46
+ basePath: "/tmp/sarvam-out",
47
+ cwd: "/tmp",
48
+ home: isolatedHome,
49
+ });
50
+ expect(cfg?.basePath).toBe("/tmp/sarvam-out");
51
+ });
52
+ });
53
+
54
+ describe("redactApiKey", () => {
55
+ it("masks middle", () => {
56
+ const r = redactApiKey("abcdefghijklmnop");
57
+ expect(r).toContain("…");
58
+ expect(r).not.toBe("abcdefghijklmnop");
59
+ });
60
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Sarvam host config for the coding agent.
3
+ */
4
+
5
+ export interface SarvamHostConfig {
6
+ /** Sarvam dashboard API key. Optional if SARVAM_API_KEY is in env. */
7
+ readonly apiKey?: string;
8
+ /** Tool prefix / id (default: sarvam). */
9
+ readonly id?: string;
10
+ /** When false, skip attach even if key is set. */
11
+ readonly enabled?: boolean;
12
+ /** Override Sarvam API base URL. */
13
+ readonly baseUrl?: string;
14
+ /** Where TTS/docs files land (default ~/Desktop). */
15
+ readonly basePath?: string;
16
+ /** Subprocess command (default: uvx). */
17
+ readonly command?: string;
18
+ /** Subprocess args (default: ["sarvam-mcp"]). */
19
+ readonly args?: readonly string[];
20
+ }
21
+
22
+ export interface SarvamAttachResult {
23
+ readonly tools: readonly unknown[];
24
+ readonly toolNames: readonly string[];
25
+ readonly id: string;
26
+ close(): Promise<void>;
27
+ }