indusagi-coding-agent 0.2.5 → 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.
Files changed (224) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +9 -5
  3. package/dist/entry.js +14267 -24282
  4. package/dist/guardrails.js +924 -7867
  5. package/dist/index.js +13706 -23775
  6. package/package.json +3 -2
  7. package/src/_decls/entry.ts +18 -0
  8. package/src/_decls/guardrails.ts +35 -0
  9. package/src/_decls/index.ts +26 -0
  10. package/src/addons/contract.ts +236 -0
  11. package/src/addons/dispatch/event-dispatcher.ts +164 -0
  12. package/src/addons/dispatch/index.ts +25 -0
  13. package/src/addons/dispatch/tool-interceptor.ts +208 -0
  14. package/src/addons/host.ts +225 -0
  15. package/src/addons/index.ts +112 -0
  16. package/src/addons/manifest.ts +158 -0
  17. package/src/addons/sandbox.ts +170 -0
  18. package/src/addons/surface.ts +78 -0
  19. package/src/boot/auth-vault.ts +195 -0
  20. package/src/boot/boot.ts +138 -0
  21. package/src/boot/contract.ts +238 -0
  22. package/src/boot/heap.ts +59 -0
  23. package/src/boot/index.ts +28 -0
  24. package/src/boot/invocation.ts +93 -0
  25. package/src/boot/runners/addon-wiring.ts +153 -0
  26. package/src/boot/runners/checkpoint.ts +169 -0
  27. package/src/boot/runners/delegate-runner.ts +294 -0
  28. package/src/boot/runners/index.ts +13 -0
  29. package/src/boot/runners/link-runner.ts +45 -0
  30. package/src/boot/runners/memdir.ts +168 -0
  31. package/src/boot/runners/oneshot-runner.ts +58 -0
  32. package/src/boot/runners/read-state.ts +90 -0
  33. package/src/boot/runners/registry.ts +42 -0
  34. package/src/boot/runners/repl-runner.ts +143 -0
  35. package/src/boot/runners/server-mode.ts +121 -0
  36. package/src/boot/runners/session.ts +641 -0
  37. package/src/boot/server-token.ts +148 -0
  38. package/src/boot/stages.ts +167 -0
  39. package/src/boot/upgrade/apply.ts +94 -0
  40. package/src/boot/upgrade/index.ts +13 -0
  41. package/src/boot/upgrade/upgrades.ts +289 -0
  42. package/src/briefing/compose.ts +150 -0
  43. package/src/briefing/context-docs.ts +19 -0
  44. package/src/briefing/contract.ts +717 -0
  45. package/src/briefing/index.ts +31 -0
  46. package/src/briefing/macros.ts +97 -0
  47. package/src/briefing/skills.ts +47 -0
  48. package/src/capability-deck/bridge-ledger/index.ts +27 -0
  49. package/src/capability-deck/bridge-ledger/key.ts +67 -0
  50. package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
  51. package/src/capability-deck/bridge-ledger/network.ts +117 -0
  52. package/src/capability-deck/builtin-bridge.ts +312 -0
  53. package/src/capability-deck/cards/bg-process-card.ts +335 -0
  54. package/src/capability-deck/cards/index.ts +115 -0
  55. package/src/capability-deck/cards/memory-card.ts +146 -0
  56. package/src/capability-deck/cards/plan-file.ts +97 -0
  57. package/src/capability-deck/cards/plan-tools.ts +185 -0
  58. package/src/capability-deck/cards/saas-card.ts +183 -0
  59. package/src/capability-deck/cards/task-card.ts +207 -0
  60. package/src/capability-deck/cards/todo-card.ts +168 -0
  61. package/src/capability-deck/cards/workflow-card.ts +247 -0
  62. package/src/capability-deck/contract.ts +388 -0
  63. package/src/capability-deck/index.ts +48 -0
  64. package/src/capability-deck/manifest.ts +109 -0
  65. package/src/capability-deck/provision.ts +169 -0
  66. package/src/channels/contract.ts +191 -0
  67. package/src/channels/framer.ts +50 -0
  68. package/src/channels/index.ts +101 -0
  69. package/src/channels/link/dialog.ts +129 -0
  70. package/src/channels/link/driver.ts +190 -0
  71. package/src/channels/link/index.ts +34 -0
  72. package/src/channels/link/server.ts +134 -0
  73. package/src/channels/oneshot.ts +90 -0
  74. package/src/channels/ops.ts +65 -0
  75. package/src/channels/session-ops.ts +81 -0
  76. package/src/conductor/bash-guard.ts +599 -0
  77. package/src/conductor/catalog/catalog.ts +116 -0
  78. package/src/conductor/catalog/index.ts +8 -0
  79. package/src/conductor/catalog/matcher.ts +134 -0
  80. package/src/conductor/conductor.ts +234 -0
  81. package/src/conductor/contract.ts +842 -0
  82. package/src/conductor/diagnostics.ts +227 -0
  83. package/src/conductor/index.ts +33 -0
  84. package/src/conductor/permissions.ts +588 -0
  85. package/src/conductor/quota-error.ts +49 -0
  86. package/src/conductor/signal-hub/hub.ts +46 -0
  87. package/src/conductor/signal-hub/index.ts +2 -0
  88. package/src/conductor/signal-hub/translate.test.ts +81 -0
  89. package/src/conductor/signal-hub/translate.ts +74 -0
  90. package/src/conductor/skill-parse/index.ts +2 -0
  91. package/src/conductor/skill-parse/parse.ts +108 -0
  92. package/src/conductor/transcript-store/index.ts +22 -0
  93. package/src/conductor/transcript-store/serialize.ts +116 -0
  94. package/src/conductor/transcript-store/store.ts +205 -0
  95. package/src/console/auth-status.ts +56 -0
  96. package/src/console/components/AgentsView.ts +165 -0
  97. package/src/console/components/BackgroundAgents.ts +155 -0
  98. package/src/console/components/Banner.ts +334 -0
  99. package/src/console/components/Composer.ts +94 -0
  100. package/src/console/components/StatusBar.ts +49 -0
  101. package/src/console/components/TerminalConsole.ts +1090 -0
  102. package/src/console/components/WorkingIndicator.ts +98 -0
  103. package/src/console/components/banner-sweep.ts +24 -0
  104. package/src/console/components/welcome.ts +74 -0
  105. package/src/console/contract.ts +630 -0
  106. package/src/console/index.ts +34 -0
  107. package/src/console/input/complete.ts +127 -0
  108. package/src/console/input/dir-reader.ts +34 -0
  109. package/src/console/input/index.ts +23 -0
  110. package/src/console/input/keymap.ts +159 -0
  111. package/src/console/input/paste.ts +104 -0
  112. package/src/console/mount.ts +56 -0
  113. package/src/console/overlays/approval-queue.ts +57 -0
  114. package/src/console/overlays/approval.ts +130 -0
  115. package/src/console/overlays/auth.ts +342 -0
  116. package/src/console/overlays/boards.ts +308 -0
  117. package/src/console/overlays/host.ts +36 -0
  118. package/src/console/overlays/index.ts +26 -0
  119. package/src/console/overlays/pickers.ts +258 -0
  120. package/src/console/overlays/sessions.ts +190 -0
  121. package/src/console/reducer.ts +182 -0
  122. package/src/console/slash/builtins.ts +81 -0
  123. package/src/console/slash/commands/dynamic.ts +83 -0
  124. package/src/console/slash/commands/integrations.ts +695 -0
  125. package/src/console/slash/commands/shared.ts +75 -0
  126. package/src/console/slash/commands/transcript.ts +263 -0
  127. package/src/console/slash/commands/workbench.ts +246 -0
  128. package/src/console/slash/index.ts +15 -0
  129. package/src/console/slash/registry.ts +70 -0
  130. package/src/console/slash/resolve.ts +63 -0
  131. package/src/console/startup.ts +209 -0
  132. package/src/console/theme/adapter.ts +45 -0
  133. package/src/console/theme/index.ts +7 -0
  134. package/src/console/theme/palette.ts +68 -0
  135. package/src/console/theme/resolve.ts +39 -0
  136. package/src/console/theme/tokens.ts +71 -0
  137. package/src/entry.ts +55 -0
  138. package/src/guardrails.ts +37 -0
  139. package/src/index.ts +18 -0
  140. package/src/insight/channel.ts +88 -0
  141. package/src/insight/contract.ts +185 -0
  142. package/src/insight/index.ts +110 -0
  143. package/src/insight/recorder.ts +213 -0
  144. package/src/insight/redaction.ts +157 -0
  145. package/src/insight/replay.ts +158 -0
  146. package/src/insight/sampling.ts +70 -0
  147. package/src/insight/serialize.ts +50 -0
  148. package/src/insight/sinks/console.ts +64 -0
  149. package/src/insight/sinks/file.ts +40 -0
  150. package/src/insight/sinks/index.ts +24 -0
  151. package/src/insight/sinks/stream.ts +54 -0
  152. package/src/integrations/sarvam/attach.ts +239 -0
  153. package/src/integrations/sarvam/config.ts +156 -0
  154. package/src/integrations/sarvam/index.ts +25 -0
  155. package/src/integrations/sarvam/sarvam.test.ts +60 -0
  156. package/src/integrations/sarvam/types.ts +27 -0
  157. package/src/integrations/zoho/attach.ts +342 -0
  158. package/src/integrations/zoho/config.ts +125 -0
  159. package/src/integrations/zoho/index.ts +27 -0
  160. package/src/integrations/zoho/types.ts +21 -0
  161. package/src/integrations/zoho/zoho.test.ts +50 -0
  162. package/src/kit/clipboard-image.ts +107 -0
  163. package/src/kit/external-editor.ts +48 -0
  164. package/src/kit/image.ts +59 -0
  165. package/src/kit/index.ts +51 -0
  166. package/src/kit/shell.ts +19 -0
  167. package/src/kit/tool-fetch.ts +85 -0
  168. package/src/launch/catalog.ts +148 -0
  169. package/src/launch/contract.ts +187 -0
  170. package/src/launch/credentials.ts +625 -0
  171. package/src/launch/index.ts +98 -0
  172. package/src/launch/invocation/attachments.ts +179 -0
  173. package/src/launch/invocation/flags.ts +196 -0
  174. package/src/launch/invocation/index.ts +25 -0
  175. package/src/launch/invocation/read.ts +260 -0
  176. package/src/launch/invocation/usage.ts +67 -0
  177. package/src/launch/login.ts +324 -0
  178. package/src/launch/oauth.test.ts +18 -0
  179. package/src/launch/oauth.ts +203 -0
  180. package/src/launch/packages.ts +194 -0
  181. package/src/launch/pickers.ts +189 -0
  182. package/src/runtime-bridge/bridges/_drive.ts +96 -0
  183. package/src/runtime-bridge/bridges/builtins.ts +68 -0
  184. package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
  185. package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
  186. package/src/runtime-bridge/bridges/index.ts +33 -0
  187. package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
  188. package/src/runtime-bridge/broker.ts +227 -0
  189. package/src/runtime-bridge/contract.ts +122 -0
  190. package/src/runtime-bridge/index.ts +79 -0
  191. package/src/runtime-bridge/sink.ts +180 -0
  192. package/src/sessions/contract.ts +81 -0
  193. package/src/sessions/index.ts +13 -0
  194. package/src/sessions/library.ts +229 -0
  195. package/src/settings/contract.ts +114 -0
  196. package/src/settings/index.ts +32 -0
  197. package/src/settings/manager.ts +117 -0
  198. package/src/transcript-export/index.ts +45 -0
  199. package/src/transcript-export/publish.ts +260 -0
  200. package/src/transcript-export/sgr.ts +315 -0
  201. package/src/transcript-export/template.ts +272 -0
  202. package/src/transcript-export/theme-bridge.ts +150 -0
  203. package/src/window-budget/budget/estimate.ts +135 -0
  204. package/src/window-budget/budget/gate.ts +33 -0
  205. package/src/window-budget/budget/index.ts +16 -0
  206. package/src/window-budget/budget/slice.ts +56 -0
  207. package/src/window-budget/condenser.ts +58 -0
  208. package/src/window-budget/contract.ts +184 -0
  209. package/src/window-budget/index.ts +19 -0
  210. package/src/window-budget/microcompact.ts +95 -0
  211. package/src/window-budget/rehydrate.ts +136 -0
  212. package/src/window-budget/summarize/condense.ts +103 -0
  213. package/src/window-budget/summarize/index.ts +14 -0
  214. package/src/window-budget/summarize/prompt.ts +149 -0
  215. package/src/workflow-engine/agent-runner.ts +181 -0
  216. package/src/workflow-engine/display.ts +224 -0
  217. package/src/workflow-engine/engine.ts +294 -0
  218. package/src/workflow-engine/index.ts +23 -0
  219. package/src/workflow-engine/parse.ts +172 -0
  220. package/src/workflow-engine/structured-output.ts +35 -0
  221. package/src/workspace/brand.ts +29 -0
  222. package/src/workspace/index.ts +18 -0
  223. package/src/workspace/locator.ts +103 -0
  224. package/src/workspace/runtime-detect.ts +64 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * OAuth integration — the browser-sign-in counterpart to the api-key flow.
3
+ */
4
+ import { spawn } from "node:child_process";
5
+ import {
6
+ anthropicOAuthProvider,
7
+ getOAuthProvider,
8
+ getOAuthProviders,
9
+ githubCopilotOAuthProvider,
10
+ openaiCodexOAuthProvider,
11
+ registerOAuthProvider,
12
+ type OAuthLoginCallbacks,
13
+ } from "indusagi/ai";
14
+ import type { AuthVault } from "./contract.js";
15
+ import { SERVER_TIER_PROVIDER_ID, runServerTierLogin } from "./login.js";
16
+
17
+ const BUILT_IN_OAUTH: { provider: { id: string; name: string }; label: string }[] = [
18
+ { provider: anthropicOAuthProvider, label: "Anthropic (Claude sign-in)" },
19
+ { provider: openaiCodexOAuthProvider, label: "OpenAI (ChatGPT sign-in)" },
20
+ { provider: githubCopilotOAuthProvider, label: "GitHub Copilot" },
21
+ ];
22
+
23
+ const OAUTH_LABELS: Record<string, string> = Object.fromEntries(
24
+ BUILT_IN_OAUTH.map((entry) => [entry.provider.id, entry.label]),
25
+ );
26
+
27
+ /**
28
+ * Register the framework's built-in sign-in providers, idempotently.
29
+ */
30
+ export function registerBuiltInOAuthProviders(): string[] {
31
+ for (const { provider } of BUILT_IN_OAUTH) {
32
+ if (getOAuthProvider(provider.id) === undefined) {
33
+ registerOAuthProvider(provider);
34
+ }
35
+ }
36
+ return getOAuthProviders().map((provider) => provider.id);
37
+ }
38
+
39
+ registerBuiltInOAuthProviders();
40
+
41
+ const OAUTH_CLIENT_ID_WIRING: Record<string, { envVar: string; sentinel: string }> = {
42
+ anthropic: {
43
+ envVar: "INDUSAGI_ANTHROPIC_OAUTH_CLIENT_ID",
44
+ sentinel: "<UNREGISTERED-ANTHROPIC-OAUTH-APP>",
45
+ },
46
+ "github-copilot": {
47
+ envVar: "INDUSAGI_GITHUB_COPILOT_CLIENT_ID",
48
+ sentinel: "<UNREGISTERED-COPILOT-APP>",
49
+ },
50
+ };
51
+
52
+ /**
53
+ * Whether a registered sign-in provider has a *real* OAuth client id wired in.
54
+ */
55
+ export function hasRegisteredOAuthClientId(providerId: string): boolean {
56
+ const wiring = OAUTH_CLIENT_ID_WIRING[providerId];
57
+ if (wiring === undefined) return true;
58
+ const value = process.env[wiring.envVar];
59
+ return value !== undefined && value.length > 0 && value !== wiring.sentinel;
60
+ }
61
+
62
+ function oauthClientIdEnvVar(providerId: string): string | undefined {
63
+ return OAUTH_CLIENT_ID_WIRING[providerId]?.envVar;
64
+ }
65
+
66
+ export type AuthKind = "oauth" | "apiKey";
67
+
68
+ export interface LoginProvider {
69
+ readonly id: string;
70
+ readonly label: string;
71
+ readonly authKind: AuthKind;
72
+ }
73
+
74
+ interface ProviderDirectoryEntry {
75
+ readonly id: string;
76
+ readonly label: string;
77
+ readonly envKey: string;
78
+ readonly docsUrl: string;
79
+ }
80
+
81
+ import { PROVIDER_DIRECTORY } from "./credentials.js";
82
+
83
+ /**
84
+ * The merged sign-in directory.
85
+ */
86
+ export function listLoginProviders(): LoginProvider[] {
87
+ const serverTier: LoginProvider = {
88
+ id: SERVER_TIER_PROVIDER_ID,
89
+ label: "Indus Server (sign in for server-tier model access)",
90
+ authKind: "oauth",
91
+ };
92
+ const oauth = getOAuthProviders().map((provider) => ({
93
+ id: provider.id,
94
+ label: OAUTH_LABELS[provider.id] ?? provider.name,
95
+ authKind: "oauth" as const,
96
+ }));
97
+ const apiKey = (PROVIDER_DIRECTORY as readonly ProviderDirectoryEntry[]).map((entry) => ({
98
+ id: String(entry.id),
99
+ label: entry.label,
100
+ authKind: "apiKey" as const,
101
+ }));
102
+ return [serverTier, ...oauth, ...apiKey];
103
+ }
104
+
105
+ function isWebUrl(url: string): boolean {
106
+ try {
107
+ const parsed = new URL(url);
108
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+
114
+ interface LaunchAttempt {
115
+ command: string;
116
+ args: string[];
117
+ }
118
+
119
+ export function launchAttempts(url: string, platform: NodeJS.Platform = process.platform): LaunchAttempt[] {
120
+ switch (platform) {
121
+ case "darwin":
122
+ return [{ command: "open", args: [url] }];
123
+ case "win32":
124
+ return [{ command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url] }];
125
+ default:
126
+ return [
127
+ { command: "xdg-open", args: [url] },
128
+ { command: "x-www-browser", args: [url] },
129
+ { command: "google-chrome", args: [url] },
130
+ { command: "chromium", args: [url] },
131
+ ];
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Open a sign-in url in the user's default browser.
137
+ */
138
+ export function openLoginUrl(url: string): Promise<boolean> {
139
+ if (!isWebUrl(url)) return Promise.resolve(false);
140
+ const attempts = launchAttempts(url);
141
+ const tryAt = (index: number): Promise<boolean> => {
142
+ if (index >= attempts.length) return Promise.resolve(false);
143
+ const { command, args } = attempts[index];
144
+ return new Promise<boolean>((resolve) => {
145
+ let settled = false;
146
+ const finish = (ok: boolean): void => {
147
+ if (settled) return;
148
+ settled = true;
149
+ resolve(ok);
150
+ };
151
+ try {
152
+ const child = spawn(command, [...args], {
153
+ stdio: "ignore",
154
+ windowsHide: true,
155
+ });
156
+ child.once("error", () => finish(false));
157
+ child.once("spawn", () => {
158
+ child.unref();
159
+ finish(true);
160
+ });
161
+ } catch {
162
+ finish(false);
163
+ }
164
+ }).then((ok) => (ok ? true : tryAt(index + 1)));
165
+ };
166
+ return tryAt(0);
167
+ }
168
+
169
+ export interface OAuthLoginResult {
170
+ readonly provider: string;
171
+ readonly account: string;
172
+ readonly madeDefault: boolean;
173
+ }
174
+
175
+ /**
176
+ * Drive a framework provider's browser sign-in and persist the result.
177
+ */
178
+ export async function startOAuthLogin(
179
+ providerId: string,
180
+ callbacks: OAuthLoginCallbacks,
181
+ vault: AuthVault,
182
+ account: string = "default",
183
+ ): Promise<OAuthLoginResult> {
184
+ if (providerId === SERVER_TIER_PROVIDER_ID) {
185
+ await runServerTierLogin(callbacks);
186
+ return { provider: providerId, account, madeDefault: true };
187
+ }
188
+ const provider = getOAuthProvider(providerId);
189
+ if (provider === undefined) {
190
+ throw new Error(`No browser sign-in is available for "${providerId}".`);
191
+ }
192
+ if (!hasRegisteredOAuthClientId(providerId)) {
193
+ const envVar = oauthClientIdEnvVar(providerId);
194
+ const hint =
195
+ envVar !== undefined ? `set ${envVar} and restart` : "configure its OAuth client id";
196
+ throw new Error(`Browser sign-in for "${providerId}" is not configured: ${hint}.`);
197
+ }
198
+ const credentials = await provider.login(callbacks);
199
+ const existing = await vault.listAccounts(providerId);
200
+ const madeDefault = existing.length === 0;
201
+ await vault.putOAuth(providerId, account, credentials, madeDefault);
202
+ return { provider: providerId, account, madeDefault };
203
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Package command — `runPackageCommand`.
3
+ *
4
+ * The top-level `install` / `remove` / `update` / `list` / `config` surface that
5
+ * manages the extension-package sources the launcher loads.
6
+ */
7
+ import type { PreferenceStore } from "../settings/index.js";
8
+
9
+ export type PackageCommand = "install" | "remove" | "update" | "list" | "config";
10
+
11
+ export const PACKAGE_COMMANDS: readonly PackageCommand[] = [
12
+ "install",
13
+ "remove",
14
+ "update",
15
+ "list",
16
+ "config",
17
+ ];
18
+
19
+ export interface PackageIo {
20
+ print(line: string): void;
21
+ printError(line: string): void;
22
+ }
23
+
24
+ export function defaultPackageIo(): PackageIo {
25
+ return {
26
+ print(line: string): void {
27
+ process.stdout.write(line.endsWith("\n") ? line : `${line}\n`);
28
+ },
29
+ printError(line: string): void {
30
+ process.stderr.write(line.endsWith("\n") ? line : `${line}\n`);
31
+ },
32
+ };
33
+ }
34
+
35
+ export interface PackageCommandOptions {
36
+ readonly store: PreferenceStore;
37
+ readonly io?: PackageIo;
38
+ }
39
+
40
+ export interface PackageResult {
41
+ readonly handled: boolean;
42
+ readonly command?: PackageCommand;
43
+ readonly code?: number;
44
+ }
45
+
46
+ const PACKAGES_KEY = "extensionPackages";
47
+
48
+ function asPackageCommand(token: string | undefined): PackageCommand | undefined {
49
+ return token !== undefined && (PACKAGE_COMMANDS as readonly string[]).includes(token)
50
+ ? (token as PackageCommand)
51
+ : undefined;
52
+ }
53
+
54
+ function sourceKey(source: string): string {
55
+ const trimmed = source.trim();
56
+ if (trimmed.startsWith("npm:")) {
57
+ const spec = trimmed.slice("npm:".length);
58
+ const at = spec.lastIndexOf("@");
59
+ const name = at > 0 ? spec.slice(0, at) : spec;
60
+ return `npm:${name}`;
61
+ }
62
+ const gitlike =
63
+ trimmed.startsWith("git:") ||
64
+ trimmed.startsWith("http://") ||
65
+ trimmed.startsWith("https://");
66
+ if (gitlike) {
67
+ const withoutScheme = trimmed.replace(/^git:/, "").replace(/^https?:\/\//, "");
68
+ const withoutVersion = withoutScheme.split("@")[0] ?? withoutScheme;
69
+ return `git:${withoutVersion.replace(/\.git$/, "")}`;
70
+ }
71
+ return `local:${trimmed}`;
72
+ }
73
+
74
+ function sourcesMatch(a: string, b: string): boolean {
75
+ return sourceKey(a) === sourceKey(b);
76
+ }
77
+
78
+ function readSources(store: PreferenceStore): string[] {
79
+ const value = store.get(PACKAGES_KEY);
80
+ return Array.isArray(value) ? [...value] : [];
81
+ }
82
+
83
+ function writeSources(store: PreferenceStore, sources: string[]): void {
84
+ store.set(PACKAGES_KEY, sources);
85
+ store.save();
86
+ }
87
+
88
+ function readSource(rest: readonly string[]): string | undefined {
89
+ return rest.find((token) => !token.startsWith("-"));
90
+ }
91
+
92
+ function doInstall(
93
+ rest: readonly string[],
94
+ store: PreferenceStore,
95
+ io: PackageIo,
96
+ ): PackageResult {
97
+ const source = readSource(rest);
98
+ if (source === undefined) {
99
+ io.printError(
100
+ "install: a package source is required (npm:, git:, https:, or a local path).",
101
+ );
102
+ return { handled: true, command: "install", code: 1 };
103
+ }
104
+ const sources = readSources(store);
105
+ if (sources.some((existing) => sourcesMatch(existing, source))) {
106
+ io.print(`Already installed: ${source}`);
107
+ return { handled: true, command: "install", code: 0 };
108
+ }
109
+ writeSources(store, [...sources, source]);
110
+ io.print(`Installed ${source}`);
111
+ return { handled: true, command: "install", code: 0 };
112
+ }
113
+
114
+ function doRemove(
115
+ rest: readonly string[],
116
+ store: PreferenceStore,
117
+ io: PackageIo,
118
+ ): PackageResult {
119
+ const source = readSource(rest);
120
+ if (source === undefined) {
121
+ io.printError("remove: a package source is required.");
122
+ return { handled: true, command: "remove", code: 1 };
123
+ }
124
+ const sources = readSources(store);
125
+ const next = sources.filter((existing) => !sourcesMatch(existing, source));
126
+ if (next.length === sources.length) {
127
+ io.printError(`remove: not installed: ${source}`);
128
+ return { handled: true, command: "remove", code: 1 };
129
+ }
130
+ writeSources(store, next);
131
+ io.print(`Removed ${source}`);
132
+ return { handled: true, command: "remove", code: 0 };
133
+ }
134
+
135
+ function doUpdate(store: PreferenceStore, io: PackageIo): PackageResult {
136
+ store.reload();
137
+ const sources = readSources(store);
138
+ if (sources.length === 0) {
139
+ io.print("No packages to update.");
140
+ return { handled: true, command: "update", code: 0 };
141
+ }
142
+ io.print(`Refreshed ${sources.length} package source${sources.length === 1 ? "" : "s"}.`);
143
+ for (const source of sources) {
144
+ io.print(` ${source}`);
145
+ }
146
+ return { handled: true, command: "update", code: 0 };
147
+ }
148
+
149
+ function doList(store: PreferenceStore, io: PackageIo): PackageResult {
150
+ const sources = readSources(store);
151
+ if (sources.length === 0) {
152
+ io.print("No packages installed.");
153
+ return { handled: true, command: "list", code: 0 };
154
+ }
155
+ io.print("Installed packages:");
156
+ for (const source of sources) {
157
+ io.print(` ${source}`);
158
+ }
159
+ return { handled: true, command: "list", code: 0 };
160
+ }
161
+
162
+ function doConfig(store: PreferenceStore, io: PackageIo): PackageResult {
163
+ const locations = store.paths();
164
+ io.print(`Global settings: ${locations.global}`);
165
+ io.print(`Project settings: ${locations.project}`);
166
+ io.print("Resolved settings:");
167
+ io.print(JSON.stringify(store.snapshot(), null, 2));
168
+ return { handled: true, command: "config", code: 0 };
169
+ }
170
+
171
+ /**
172
+ * Run the package command.
173
+ */
174
+ export async function runPackageCommand(
175
+ argv: readonly string[],
176
+ opts: PackageCommandOptions,
177
+ ): Promise<PackageResult> {
178
+ const command = asPackageCommand(argv[0]);
179
+ if (command === undefined) return { handled: false };
180
+ const io = opts.io ?? defaultPackageIo();
181
+ const rest = argv.slice(1);
182
+ switch (command) {
183
+ case "install":
184
+ return doInstall(rest, opts.store, io);
185
+ case "remove":
186
+ return doRemove(rest, opts.store, io);
187
+ case "update":
188
+ return doUpdate(opts.store, io);
189
+ case "list":
190
+ return doList(opts.store, io);
191
+ case "config":
192
+ return doConfig(opts.store, io);
193
+ }
194
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Launch pickers — `pickResumeTarget` and `browseSettings`.
3
+ */
4
+ import { createInterface } from "node:readline/promises";
5
+ import { createElement } from "indusagi/react-host";
6
+ import { render } from "indusagi/react-host/ink";
7
+ import { StartupSessionPicker } from "indusagi/react-ink";
8
+ import type {
9
+ ResumeFault,
10
+ ResumeRef,
11
+ SessionLoader,
12
+ SettingsBrowseOptions,
13
+ } from "./contract.js";
14
+
15
+ export interface ResumeDeps {
16
+ isInteractive(): boolean;
17
+ mountPicker(sessions: readonly ResumeRef[], totalCount: number): Promise<ResumeRef | null>;
18
+ }
19
+
20
+ /**
21
+ * The live {@link ResumeDeps}: a real TTY probe and the Ink picker mount.
22
+ */
23
+ export function defaultResumeDeps(): ResumeDeps {
24
+ return {
25
+ isInteractive(): boolean {
26
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
27
+ },
28
+ mountPicker(sessions, totalCount): Promise<ResumeRef | null> {
29
+ const byPath = new Map(sessions.map((s) => [s.path, s]));
30
+ return new Promise<ResumeRef | null>((resolve, reject) => {
31
+ let settled = false;
32
+ const settle = (value: ResumeRef | null): void => {
33
+ if (settled) return;
34
+ settled = true;
35
+ instance.unmount();
36
+ resolve(value);
37
+ };
38
+ let instance: { unmount: () => void; waitUntilExit: () => Promise<void> };
39
+ try {
40
+ instance = render(
41
+ createElement(StartupSessionPicker, {
42
+ sessions: [...sessions],
43
+ totalCount,
44
+ onClose: () => settle(null),
45
+ onSelect: (session: ResumeRef) => settle(byPath.get(session.path) ?? null),
46
+ }),
47
+ );
48
+ } catch (cause) {
49
+ reject(cause);
50
+ return;
51
+ }
52
+ void instance.waitUntilExit().then(() => {
53
+ if (!settled) {
54
+ settled = true;
55
+ resolve(null);
56
+ }
57
+ });
58
+ });
59
+ },
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Merge two session lists into one newest-first list de-duplicated by path.
65
+ */
66
+ export function mergeSessions(
67
+ current: readonly ResumeRef[],
68
+ all: readonly ResumeRef[],
69
+ ): ResumeRef[] {
70
+ const byPath = new Map<string, ResumeRef>();
71
+ for (const session of [...current, ...all]) {
72
+ if (!byPath.has(session.path)) byPath.set(session.path, session);
73
+ }
74
+ return [...byPath.values()].sort((a, b) => b.lastModified - a.lastModified);
75
+ }
76
+
77
+ export interface ResumeOutcome {
78
+ readonly path: string | null;
79
+ readonly fault?: ResumeFault;
80
+ }
81
+
82
+ /**
83
+ * Choose a session to resume.
84
+ */
85
+ export async function pickResumeTarget(
86
+ loadCurrent: SessionLoader,
87
+ loadAll: SessionLoader,
88
+ deps: ResumeDeps = defaultResumeDeps(),
89
+ ): Promise<ResumeOutcome> {
90
+ let current: ResumeRef[];
91
+ let all: ResumeRef[];
92
+ try {
93
+ [current, all] = await Promise.all([loadCurrent(), loadAll()]);
94
+ } catch (cause) {
95
+ return {
96
+ path: null,
97
+ fault: {
98
+ message: "Failed to load resumable sessions.",
99
+ cause,
100
+ },
101
+ };
102
+ }
103
+ const sessions = mergeSessions(current, all);
104
+ if (sessions.length === 0) return { path: null };
105
+ if (!deps.isInteractive()) {
106
+ return { path: sessions[0].path };
107
+ }
108
+ try {
109
+ const chosen = await deps.mountPicker(sessions, sessions.length);
110
+ return { path: chosen === null ? null : chosen.path };
111
+ } catch (cause) {
112
+ return {
113
+ path: null,
114
+ fault: {
115
+ message: "The session picker failed to start.",
116
+ cause,
117
+ },
118
+ };
119
+ }
120
+ }
121
+
122
+ export interface SettingsBrowseIo {
123
+ print(line: string): void;
124
+ pause(prompt: string): Promise<void>;
125
+ }
126
+
127
+ export function defaultSettingsBrowseIo(): SettingsBrowseIo {
128
+ return {
129
+ print(line: string): void {
130
+ process.stdout.write(line.endsWith("\n") ? line : `${line}\n`);
131
+ },
132
+ async pause(prompt: string): Promise<void> {
133
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
134
+ try {
135
+ await rl.question(prompt);
136
+ } finally {
137
+ rl.close();
138
+ }
139
+ },
140
+ };
141
+ }
142
+
143
+ function renderSection(title: string, paths: readonly string[]): string[] {
144
+ if (paths.length === 0) return [`${title}: (none)`];
145
+ return [`${title}:`, ...paths.map((path) => ` - ${path}`)];
146
+ }
147
+
148
+ /**
149
+ * Build the entire settings listing as an array of lines.
150
+ */
151
+ export function renderSettingsListing(opts: SettingsBrowseOptions): string[] {
152
+ const lines: string[] = [];
153
+ lines.push("Configuration");
154
+ lines.push(` Working directory: ${opts.cwd}`);
155
+ lines.push(` Profile directory: ${opts.profileDir}`);
156
+ lines.push("");
157
+ lines.push("Settings");
158
+ lines.push(` Default model: ${opts.settings.defaultModel ?? "(unset)"}`);
159
+ lines.push(
160
+ ` System prompt: ${opts.settings.systemPrompt !== undefined ? "(custom)" : "(default)"}`,
161
+ );
162
+ const serverCount = opts.settings.mcpServers?.length ?? 0;
163
+ lines.push(` MCP servers: ${serverCount}`);
164
+ lines.push("");
165
+ const categories = Object.keys(opts.resolvedPaths).sort();
166
+ if (categories.length === 0) {
167
+ lines.push("Resources: (none discovered)");
168
+ } else {
169
+ lines.push("Resources");
170
+ for (const category of categories) {
171
+ for (const line of renderSection(` ${category}`, opts.resolvedPaths[category])) {
172
+ lines.push(line);
173
+ }
174
+ }
175
+ }
176
+ return lines;
177
+ }
178
+
179
+ /**
180
+ * Print the settings / resource listing and wait for the user to dismiss it.
181
+ */
182
+ export async function browseSettings(
183
+ opts: SettingsBrowseOptions,
184
+ io: SettingsBrowseIo = defaultSettingsBrowseIo(),
185
+ ): Promise<void> {
186
+ for (const line of renderSettingsListing(opts)) io.print(line);
187
+ io.print("");
188
+ await io.pause("Press Enter to continue. ");
189
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Shared exchange driver — the transport-wiring half every bridge reuses.
3
+ */
4
+ import type {
5
+ Api,
6
+ AssistantMessageEventStream,
7
+ BridgeEventSink,
8
+ ChildMessage,
9
+ ChildRequest,
10
+ ChildTransport,
11
+ ExchangeOptions,
12
+ Model,
13
+ } from "../contract.js";
14
+ import { createBridgeSink, type BridgeMessageSeed } from "../sink.js";
15
+
16
+ export type ParseStep = { readonly status: "continue" } | { readonly status: "done" };
17
+
18
+ export type ChildParser = (message: ChildMessage, sink: BridgeEventSink) => ParseStep;
19
+
20
+ export function seedFromModel(model: Model<Api>): BridgeMessageSeed {
21
+ return { api: model.api, provider: model.provider, model: model.id };
22
+ }
23
+
24
+ export function errorText(cause: unknown): string {
25
+ if (cause instanceof Error) return cause.message;
26
+ if (typeof cause === "string") return cause;
27
+ return "external runtime error";
28
+ }
29
+
30
+ export function asRecord(value: unknown): Record<string, unknown> | undefined {
31
+ return typeof value === "object" && value !== null
32
+ ? (value as Record<string, unknown>)
33
+ : undefined;
34
+ }
35
+
36
+ export function strField(record: Record<string, unknown>, key: string): string | undefined {
37
+ const value = record[key];
38
+ return typeof value === "string" ? value : undefined;
39
+ }
40
+
41
+ export function driveExchange(
42
+ model: Model<Api>,
43
+ opts: ExchangeOptions,
44
+ transport: ChildTransport,
45
+ request: ChildRequest,
46
+ parse: ChildParser,
47
+ ): AssistantMessageEventStream {
48
+ const sink = createBridgeSink(seedFromModel(model));
49
+ let finished = false;
50
+ let unsubscribe: (() => void) | undefined;
51
+
52
+ const teardown = (): void => {
53
+ unsubscribe?.();
54
+ unsubscribe = undefined;
55
+ void transport.close().catch(() => undefined);
56
+ };
57
+
58
+ const settleSuccess = (): void => {
59
+ if (finished) return;
60
+ finished = true;
61
+ sink.finishSuccess();
62
+ teardown();
63
+ };
64
+
65
+ const settleError = (message: string, aborted: boolean, cause?: unknown): void => {
66
+ if (finished) return;
67
+ finished = true;
68
+ sink.finishError({ message, aborted, cause });
69
+ teardown();
70
+ };
71
+
72
+ const signal = opts.signal;
73
+ if (signal) {
74
+ if (signal.aborted) {
75
+ queueMicrotask(() => settleError("exchange aborted", true));
76
+ return sink.stream;
77
+ }
78
+ signal.addEventListener("abort", () => settleError("exchange aborted", true), { once: true });
79
+ }
80
+
81
+ unsubscribe = transport.onMessage((message) => {
82
+ if (finished) return;
83
+ try {
84
+ const step = parse(message, sink);
85
+ if (step.status === "done") settleSuccess();
86
+ } catch (cause) {
87
+ settleError(errorText(cause), false, cause);
88
+ }
89
+ });
90
+
91
+ void transport.send(request).catch((cause) => {
92
+ settleError(errorText(cause), false, cause);
93
+ });
94
+
95
+ return sink.stream;
96
+ }